陳鐵 + 原創作品轉載請註明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
學習過程其實就是模仿老師的過程,萬一足夠熟練了,就變成自己的了。內核代碼部分的確有些痛苦,好在本週回到了用戶shell層面,畢竟有些瞭解。將整個學習過程記錄如下,也是自己的成長經歷。
一、可執行文件的生成過程。我們說的可執行文件是給cpu執行的二進制代碼,那它又是我們人所編輯的。這個過程我就簡單點,有下面的shell命令說明一下。結果如圖。
245 vi hello.c 246 gcc -E -o hello.cpp hello.c -m32 247 gcc -x cpp-output -S -o hello.s hello.cpp -m32 248 gcc -x assembler -c hello.s -o hello.o -m32 249 gcc -0 hello hello.o -m32 250 gcc -o hello hello.o -m32 251 gcc -o hello.static hello.o -m32 -static
我們在windows上的可執行文件是PE文件。在linux下是elf格式。
文本編輯器編輯源代碼文件->預編譯處理->彙編成.s文件->編譯生成目標.o文件->鏈接程序將目標文件連接成可執行文件。
二、對於靜態鏈接的elf文件,基本上在加載時對應加上程序入口地址,將相應的代碼數據加載到對應的內存空間中,然後逐步執行代碼。以下是我的ELF Header情況。
我用gdb跟一下看看執行時的代碼入口地址。由於實際上我們的main函數是由_start調用的,所以我們設置斷點 break _start。結果如下:
三、通常的linux下的可執行程序都是在shell下執行的,所以會進行相應的執行前處理過程。比如/bin/ls -l這個命令,就是先fork出一個進程,調用execve系統調用,然後再調用具體的execlp調用將相關的命令行參數傳遞給程序的main函數的。
四、通常我們的程序還需要使用動態鏈接庫。分爲裝載時動態鏈接和運行時動態鏈接。演示代碼,
通過這樣的命令生成共享庫:
gcc -shared shlibexample.c -o libshlibexample.so -m32
以下命令生成運行時鏈接庫:
gcc -shared dllibexample.c -o libdllibexample.so -m32
生成文件及運行情況如下:
五、execve系統調用和fork系統調用一樣,都是特殊的系統調用。fork系統調用返回兩次,一次返回父進程執行,一回返回特定的點ret_from_fork執行,然後返回到用戶態。execve系統調用在內核中將當前的可執行程序覆蓋掉了,當返回時不再是原來的可執行程序了。
Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數:
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
而所有的庫函數exec*都是execve的封裝例程。
系統調用sys_execve會解析可執行文件格式do_execve->do_execve_common->exec_binprm然後執行search_binary_handler找到符合文件頭部標明的文件格式的解析模塊。對於linux下的ELF文件,fmt->load_binary(bprm)實際執行的就是static int load_elf_binary(struct linux_binprm *bprm)。
執行start_thread(regs, elf_entry, bprm->p)時,如果是靜態鏈接的,elf_entry就是文件頭部標明的入口。進入內核後
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp) { set_user_gs(regs, 0); regs->fs = 0; regs->ds = __USER_DS; regs->es = __USER_DS; regs->ss = __USER_DS; regs->cs = __USER_CS; regs->ip = new_ip; regs->sp = new_sp; regs->flags = X86_EFLAGS_IF; /* * force it to the iret return path by making it look as if there was * some work pending. */ set_thread_flag(TIF_NOTIFY_RESUME); }
execve在返回前用新的ip和sp更新了進程的ip和sp。對於需要動態鏈接的程序,elf_entry就會加載動態鏈接器ld的入口地址。
總結,在linux環境下,可執行文件是以ELF格式存在的,文件頭部標明瞭文件在加載到內存中需要的相關信息,隨後的部分是以段的形式存在的代碼和數據,段的劃分主要依據加載到內存中的讀寫屬性。系統調用execve負責可執行文件的調度工作,先進行相關參數的傳遞和調用前環境的處理,然後加載可執行文件的信息,查找相應的可執行文件解析模塊,對於ELF格式的可執行文件,按照格式要求加載到內存中相應的地址空間,如果是靜態鏈接的就將文件頭部標明的入口地址作爲開始;如果是依賴動態鏈接庫的可執行文件則需要將動態鏈接器ld的入口地址作爲開始。