busybox版本:1.35.0
ash程序入口分析
ash程序是linux內(nèi)核啟動(dòng)后期進(jìn)入busybox后,在busybox中啟動(dòng)的默認(rèn)shell,用于響應(yīng)和執(zhí)行命令輸入。ash的操作入口由ash_main()函數(shù)代表,定義在/shell/ash.c文件中。
貼上ash_main函數(shù)的完整代碼(出自/shell/ash.c):
?
int?ash_main(int?argc,?char?**argv)?MAIN_EXTERNALLY_VISIBLE; #if?NUM_SCRIPTS?>?0 int?ash_main(int?argc,?char?**argv) #else int?ash_main(int?argc?UNUSED_PARAM,?char?**argv) #endif /*?note:?'argc'?is?used?only?if?embedded?scripts?are?enabled?*/ { ?volatile?smallint?state; ?struct?jmploc?jmploc; ?struct?stackmark?smark; ?int?login_sh; ?/*?Initialize?global?data?*/ ?INIT_G_misc(); ?INIT_G_memstack(); ?INIT_G_var(); #if?ENABLE_ASH_ALIAS ?INIT_G_alias(); #endif ?INIT_G_cmdtable(); #if?PROFILE ?monitor(4,?etext,?profile_buf,?sizeof(profile_buf),?50); #endif ?state?=?0; ?if?(setjmp(jmploc.loc))?{ ??smallint?e; ??smallint?s; ??exitreset(); ??e?=?exception_type; ??s?=?state; ??if?(e?==?EXEND?||?e?==?EXEXIT?||?s?==?0?||?iflag?==?0?||?shlvl)?{ ???exitshell(); ??} ??reset(); ??if?(e?==?EXINT)?{ ???newline_and_flush(stderr); ??} ??popstackmark(&smark); ??FORCE_INT_ON;?/*?enable?interrupts?*/ ??if?(s?==?1) ???goto?state1; ??if?(s?==?2) ???goto?state2; ??if?(s?==?3) ???goto?state3; ??goto?state4; ?} ?exception_handler?=?&jmploc; ?rootpid?=?getpid(); ?init(); ?setstackmark(&smark); #if?NUM_SCRIPTS?>?0 ?if?(argc?0) ??/*?Non-NULL?minusc?tells?procargs?that?an?embedded?script?is?being?run?*/ ??minusc?=?get_script_content(-argc?-?1); #endif ?login_sh?=?procargs(argv); #if?DEBUG ?TRACE(("Shell?args:?")); ?trace_puts_args(argv); #endif ?if?(login_sh)?{ ??const?char?*hp; ??state?=?1; ??read_profile("/etc/profile"); ?state1: ??state?=?2; ??hp?=?lookupvar("HOME"); ??if?(hp) ???read_profile("$HOME/.profile"); ?} ?state2: ?state?=?3; ?if?(iflag #ifndef?linux ??&&?getuid()?==?geteuid()?&&?getgid()?==?getegid() #endif ?)?{ ??const?char?*shinit?=?lookupvar("ENV"); ??if?(shinit?!=?NULL?&&?*shinit?!=?'') ???read_profile(shinit); ?} ?popstackmark(&smark); ?state3: ?state?=?4; ?if?(minusc)?{ ??/*?evalstring?pushes?parsefile?stack. ???*?Ensure?we?don't?falsely?claim?that?0?(stdin) ???*?is?one?of?stacked?source?fds. ???*?Testcase:?ash?-c?'exec?1>&0'?must?not?complain.?*/ ??//?if?(!sflag)?g_parsefile->pf_fd?=?-1; ??//?^^?not?necessary?since?now?we?special-case?fd?0 ??//?in?save_fd_on_redirect() ??lineno?=?0;?//?bash?compat ??//?dash:?evalstring(minusc,?sflag???0?:?EV_EXIT); ??//?The?above?makes ??//??ash?-sc?'echo?$-' ??//?continue?reading?input?from?stdin?after?running?'echo'. ??//?bash?does?not?do?this:?it?prints?"hBcs"?and?exits. ??evalstring(minusc,?EV_EXIT); ?} ?if?(sflag?||?minusc?==?NULL)?{ #if?MAX_HISTORY?>?0?&&?ENABLE_FEATURE_EDITING_SAVEHISTORY ??if?(line_input_state)?{ ???const?char?*hp?=?lookupvar("HISTFILE"); ???if?(!hp)?{ ????hp?=?lookupvar("HOME"); ????if?(hp)?{ ?????INT_OFF; ?????hp?=?concat_path_file(hp,?".ash_history"); ?????setvar0("HISTFILE",?hp); ?????free((char*)hp); ?????INT_ON; ?????hp?=?lookupvar("HISTFILE"); ????} ???} ???if?(hp) ????line_input_state->hist_file?=?xstrdup(hp); #?if?ENABLE_FEATURE_SH_HISTFILESIZE ???hp?=?lookupvar("HISTFILESIZE"); ???line_input_state->max_history?=?size_from_HISTFILESIZE(hp); #?endif ??} #endif ?state4:?/*?XXX?????-?why?isn't?this?before?the?"if"?statement?*/ ??cmdloop(1); ?} #if?PROFILE ?monitor(0); #endif #ifdef?GPROF ?{ ??extern?void?_mcleanup(void); ??_mcleanup(); ?} #endif ?TRACE(("End?of?main?reached ")); ?exitshell(); ?/*?NOTREACHED?*/ }
?
下文將分段描述該函數(shù)。
首先是調(diào)用幾個(gè)INIT_G_XXX命名的宏定義:
?
?INIT_G_misc(); ?INIT_G_memstack(); ?INIT_G_var(); #if?ENABLE_ASH_ALIAS ?INIT_G_alias(); #endif ?INIT_G_cmdtable();
?
用于初始化全局變量。
然后將state變量值設(shè)置為0:
?
state?=?0;
?
接著是調(diào)用一個(gè)C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的setjmp()函數(shù)實(shí)現(xiàn)異常處理機(jī)制:
?
?if?(setjmp(jmploc.loc))?{
??smallint?e;
??smallint?s;
??exitreset();
??e?=?exception_type;
??s?=?state;
??if?(e?==?EXEND?||?e?==?EXEXIT?||?s?==?0?||?iflag?==?0?||?shlvl)?{
???exitshell();
??}
??reset();
??if?(e?==?EXINT)?{
???newline_and_flush(stderr);
??}
??popstackmark(&smark);
??FORCE_INT_ON;?/*?enable?interrupts?*/
??
??printf("s?:?%d
",s);
??if?(s?==?1)
???goto?state1;
??if?(s?==?2)
???goto?state2;
??if?(s?==?3)
???goto?state3;
??goto?state4;
?}
?exception_handler?=?&jmploc;
?
在接下來(lái)的代碼中,會(huì)調(diào)用procargs(argv)處理命令行參數(shù);調(diào)用read_profile("/etc/profile")讀取配置文件,這個(gè)文件正是在busybox中需要我們自己添加的用于配置shell的描述文件。
在最后,則會(huì)調(diào)用cmdloop(1)函數(shù)用于執(zhí)行命令行循環(huán)操作。該函數(shù)用于讀取執(zhí)行命令。
ash_main總結(jié)
ash_main()函數(shù)用于初始化和解析參數(shù),讀取/etc/profile配置文件,然后調(diào)用cmdloop()來(lái)執(zhí)行命令。setjmp()函數(shù)是一個(gè)C語(yǔ)言庫(kù)函數(shù),用于設(shè)置當(dāng)事件發(fā)生時(shí)跳轉(zhuǎn)到的位置。在異常發(fā)生時(shí),變量"state"可用于計(jì)算跳轉(zhuǎn)的位置。
login進(jìn)程
在busybox運(yùn)行后,在命令行下輸入login命令則可以運(yùn)行l(wèi)ogin程序,默認(rèn)busybox配置下,在啟動(dòng)busybox后,會(huì)執(zhí)行ash程序而不是login程序。在實(shí)際應(yīng)用需求中,我們可以將login設(shè)置為busybox啟動(dòng)后的運(yùn)行程序。方法如下:
(1)使用make menuconfig編譯構(gòu)建出busybox的圖形配置界面,選擇下列選項(xiàng):

(2)進(jìn)入Login/Password Management Utilities選項(xiàng),將該配置下的所有項(xiàng)目都配置上:

(3)使用make -j12編譯構(gòu)建busybox。
(4)安裝busybox
通過(guò)以上步驟,這時(shí)候的busybox是支持login程序的,接下來(lái),在/etc/inittab文件中設(shè)置啟動(dòng)項(xiàng):
?
:-/bin/login 或者 :/sbin/getty?115200?console
?
注:上述配置任選一種
配置/etc/group,/etc/passwd,/etc/shadow三個(gè)文件(如果在busybox中沒(méi)有,則需要自己創(chuàng)建)
在/etc/group文件中,添加如下配置:
?
root0:root
?
在/etc/passwd文件中添加root用戶的密鑰信息:
?
root0root:/root:/bin/sh
?
小生這里/etc/passed文件中內(nèi)容如下:

在/etc/shadow文件中配置用戶(這里是root用戶)密碼:
?
root199999::
?
DH9Ade75qXIdI表示設(shè)置的密碼
該串密文可使用mkpasswd命令生成,在命令行終端輸入mkpasswd后會(huì)提示輸入密碼,這時(shí)候輸入我們想要設(shè)置的明文密碼,完成后按下回車鍵即可生成crypt格式的字符串:

上述操作就將login登錄的密碼設(shè)置為iriczhao,用戶名為root。
至此,通過(guò)上述步驟,就完成了login的配置,運(yùn)行busybox后,即可進(jìn)入login程序,如下圖所示:

鍵入root和密碼(本文是iriczhao)后,即可進(jìn)入shell。
login程序入口分析
根據(jù)busybox的工具特征,知道login程序?qū)?yīng)的入口則是login_main(),本小節(jié)將分析該函數(shù):
當(dāng)在busybox中運(yùn)行l(wèi)ogin程序后,會(huì)提示輸入登錄名,然后會(huì)提示輸入密碼,按下Enter鍵后,將會(huì)去驗(yàn)證登錄密碼是否正確,這一系列的操作是由login_main函數(shù)中的while(1){}結(jié)構(gòu)完成的,代碼如下(出自/loginutils/login.c):
?
while?(1)?{
??/*?flush?away?any?type-ahead?(as?getty?does)?*/
??tcflush(0,?TCIFLUSH);
??if?(!username[0])
???get_username_or_die(username,?sizeof(username));
#if?ENABLE_PAM
??pamret?=?pam_start("login",?username,?&conv,?&pamh);
??if?(pamret?!=?PAM_SUCCESS)?{
???failed_msg?=?"start";
???goto?pam_auth_failed;
??}
??/*?set?TTY?(so?things?like?securetty?work)?*/
??pamret?=?pam_set_item(pamh,?PAM_TTY,?short_tty);
??if?(pamret?!=?PAM_SUCCESS)?{
???failed_msg?=?"set_item(TTY)";
???goto?pam_auth_failed;
??}
??/*?set?RHOST?*/
??if?(opt_host)?{
???pamret?=?pam_set_item(pamh,?PAM_RHOST,?opt_host);
???if?(pamret?!=?PAM_SUCCESS)?{
????failed_msg?=?"set_item(RHOST)";
????goto?pam_auth_failed;
???}
??}
??if?(!(opt?&?LOGIN_OPT_f))?{
???pamret?=?pam_authenticate(pamh,?0);
???if?(pamret?!=?PAM_SUCCESS)?{
????failed_msg?=?"authenticate";
????goto?pam_auth_failed;
????/*?TODO:?or?just?"goto?auth_failed"
?????*?since?user?seems?to?enter?wrong?password
?????*?(in?this?case?pamret?==?7)
?????*/
???}
??}
??/*?check?that?the?account?is?healthy?*/
??pamret?=?pam_acct_mgmt(pamh,?0);
??if?(pamret?==?PAM_NEW_AUTHTOK_REQD)?{
???pamret?=?pam_chauthtok(pamh,?PAM_CHANGE_EXPIRED_AUTHTOK);
??}
??if?(pamret?!=?PAM_SUCCESS)?{
???failed_msg?=?"acct_mgmt";
???goto?pam_auth_failed;
??}
??/*?read?user?back?*/
??pamuser?=?NULL;
??/*?gcc:?"dereferencing?type-punned?pointer?breaks?aliasing?rules..."
???*?thus?we?cast?to?(void*)?*/
??if?(pam_get_item(pamh,?PAM_USER,?(void*)&pamuser)?!=?PAM_SUCCESS)?{
???failed_msg?=?"get_item(USER)";
???goto?pam_auth_failed;
??}
??if?(!pamuser?||?!pamuser[0])
???goto?auth_failed;
??safe_strncpy(username,?pamuser,?sizeof(username));
??/*?Don't?use?"pw?=?getpwnam(username);",
???*?PAM?is?said?to?be?capable?of?destroying?static?storage
???*?used?by?getpwnam().?We?are?using?safe(r)?function?*/
??pw?=?NULL;
??getpwnam_r(username,?&pwdstruct,?pwdbuf,?sizeof(pwdbuf),?&pw);
??if?(!pw)
???goto?auth_failed;
??pamret?=?pam_open_session(pamh,?0);
??if?(pamret?!=?PAM_SUCCESS)?{
???failed_msg?=?"open_session";
???goto?pam_auth_failed;
??}
??pamret?=?pam_setcred(pamh,?PAM_ESTABLISH_CRED);
??if?(pamret?!=?PAM_SUCCESS)?{
???failed_msg?=?"setcred";
???goto?pam_auth_failed;
??}
??break;?/*?success,?continue?login?process?*/
?pam_auth_failed:
??/*?syslog,?because?we?don't?want?potential?attacker
???*?to?know?_why_?login?failed?*/
??syslog(LOG_WARNING,?"pam_%s?call?failed:?%s?(%d)",?failed_msg,
?????pam_strerror(pamh,?pamret),?pamret);
??login_pam_end(pamh);
??safe_strncpy(username,?"UNKNOWN",?sizeof(username));
#else?/*?not?PAM?*/
??pw?=?getpwnam(username);
??if?(!pw)?{
???strcpy(username,?"UNKNOWN");
???goto?fake_it;
??}
??if?(pw->pw_passwd[0]?==?'!'?||?pw->pw_passwd[0]?==?'*')
???goto?auth_failed;
??if?(opt?&?LOGIN_OPT_f)
???break;?/*?-f?USER:?success?without?asking?passwd?*/
??if?(pw->pw_uid?==?0?&&?!is_tty_secure(short_tty))
???goto?auth_failed;
??/*?Don't?check?the?password?if?password?entry?is?empty?(!)?*/
??if?(!pw->pw_passwd[0])
???break;
?fake_it:
??/*?Password?reading?and?authorization?takes?place?here.
???*?Note?that?reads?(in?no-echo?mode)?trash?tty?attributes.
???*?If?we?get?interrupted?by?SIGALRM,?we?need?to?restore?attrs.
???*/
??if?(ask_and_check_password(pw)?>?0)
???break;
#endif?/*?ENABLE_PAM?*/
?auth_failed:
??opt?&=?~LOGIN_OPT_f;
??pause_after_failed_login();
??/*?TODO:?doesn't?sound?like?correct?English?phrase?to?me?*/
??puts("Login?incorrect");
??syslog(LOG_WARNING,?"invalid?password?for?'%s'%s",
?????username,?fromhost);
??if?(++count?==?3)?{
???if?(ENABLE_FEATURE_CLEAN_UP)
????free(fromhost);
???return?EXIT_FAILURE;
??}
??username[0]?=?'';
?}?/*?while?(1)?*/
?
在login_main函數(shù)中所操作的一個(gè)重要數(shù)據(jù)結(jié)構(gòu)是pw,pw是一個(gè)指向struct passwd的結(jié)構(gòu)指針,結(jié)構(gòu)定義如下:
?
#include?? #include? ? struct?passwd?{ ???char?*pw_name;????????????????/*?用戶登錄名?*/ ???char?*pw_passwd;??????????????/*?密碼(加密后)?*/ ???__uid_t?pw_uid;???????????????/*?用戶ID?*/ ???__gid_t?pw_gid;???????????????/*?組ID?*/ ???char?*pw_gecos;???????????????/*?詳細(xì)用戶名?*/ ???char?*pw_dir;?????????????????/*?用戶目錄?*/ ???char?*pw_shell;???????????????/*?Shell程序名?*/? };
?
在login_main()函數(shù)中使用:
?
pw?=?getpwnam(username);
?
可根據(jù)用戶名獲取用戶口令信息pw。
如果密碼驗(yàn)證成功,將會(huì)在while(1)中使用break跳出循環(huán),繼續(xù)執(zhí)行后續(xù)代碼;如果密碼驗(yàn)證失敗,則會(huì)跳轉(zhuǎn)到auth_failed標(biāo)簽處,返回EXIT_FAILURE。
在login_main函數(shù)的最后,會(huì)調(diào)用exec_login_shell(pw->pw_shell);登錄shell。本質(zhì)上則是execv()系統(tǒng)調(diào)用:

login_main總結(jié)
在命令行輸入login命令后,則會(huì)執(zhí)行l(wèi)ogin程序;我們也可以將login程序設(shè)置為busybox啟動(dòng)后執(zhí)行的程序,實(shí)現(xiàn)帶用戶名和密碼的登錄方式。在buildroot構(gòu)建工具中則自動(dòng)實(shí)現(xiàn)了login機(jī)制(基于busybox方式),只需要在圖形配置界面中開(kāi)啟并配置密碼即可。
審核編輯:湯梓紅
?
電子發(fā)燒友App





















評(píng)論