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è)置的明文密碼,完成后按下回車(chē)鍵即可生成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)啟并配置密碼即可。
審核編輯:湯梓紅
?
評(píng)論
查看更多