exec*函數對應的系統調用處理過程


“casualet + 原創作品轉載請注明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”

exec*函數對應的系統調用會加載可執行程序到內存進行執行。本文將分析可執行程序加載的過程,包括可執行程序的個是ELF,動態鏈接以及靜態鏈接相關內容,並通過gdb調試的方式展示該系統調用的執行過程。

具體的實驗環境設置流程可以參考之前的系列文章。 首先我們設置一些斷點,然后依然使用qemu命令運行內核,開始調試。我們輸入命令exec,可以運行到第一個斷點sys_execve:

在這一部,我們通過系統調用函數陷入內核,走過了int 0x80 =>sys_call_table=>中斷處理函數sys_execve的流程。我們輸入c,可以執行到下一個短點do_execve,這是中斷處理函數執行的結果。

進入do_execve以后執行,可以進入do_execve_common函數,我們在這里也設置了斷點,所以執行c以后,可以看到這樣的結果:

這個函數的參數是文件名,參數以及環境變量。這些參數是在原始的函數調用時通過棧的方式調用的。在執行系統調用函數的時候,又通過寄存器的方式拷貝了這個參數。我們在do_execve_common函數

中單步執行進行跟蹤,可以看到:

這個函數持續執行,可以看到進入了1474行的函數do_open_exec,這里會打開文件,獲得一個file 變量。然后繼續執行,我們可以看到其部分的代碼:

bprm->file = file; 

....

在bprm變量中收入了我們打開的文件,以及一些相關的參數。這個bprm變量是我們在do_execve_common函數中定義的一個linux_binprm類型的結構體指針。最后在1513行會執行

retval = exec_binprm(bprm),進入一個新的函數。在這個函數里面,會繼續調用ret=search_binary_handler(bprm); 這是我們的下一個斷點。

在這個函數里面,有list_for_each_entry(fmt,&formats,1h),會尋找能夠解析當前可執行文件的代碼模塊。然后繼續執行retval=fmt-》load_binary(bprm),這里實際上調用了load_elf_binary函數,開始對

可執行文件進行解析。load_elf_binary的代碼可以參考linux源碼arch/x86/kernel/process_32.c. 在這個函數里面,處理了動態鏈接和靜態鏈接的兩種情況。對於動態鏈接,設置了elf_entry為動態鏈接器ld的起始地址,

對於靜態鏈接,則是設置成main函數的入口地址。然后在后面執行start_thread(regs,elf_entry,bprm->p), 來對內核棧進行設置,其中就用到上面的elf_entry的入口地址。此外,該函數還會對用戶態的棧空間進行設置。

這樣就完成了程序的加載過程。

 

總結:

1.可執行程序的elf格式

  我們編譯獲得一個可執行程序,會被加載到內存進行執行。這個可執行程序是有一定的格式的,其中現在用的比較多的就是elf格式。對於一個可執行文件,必須保護程序運行必要的相關信息,比如說這個程序依賴那些動態鏈接庫,程序的入口地址等,我們通過這樣一種格式化的方式,保存了這些信息共系統進行解析,從而可以加載我們的代碼段和數據段進入內存。關於這個格式具體細節,可以查看相關的說明文檔,在linux內核中也通過load_elf_binary對這個文件進行了解析,是通過參考文檔來做的。

2.程序的加載以及執行過程的關鍵行為分析

  我們在shell中使用./main 來運行一個程序的時候,shell其實是通過創建子進程,然后使用execve函數來完成程序的加載和執行的。execve函數會獲得一些shell通過函數調用的機制傳遞的參數進行執行,經歷上面調試過程講解的一系列步驟:

execve=>do_execve=>do_execve_common=>(do_open_exec/exec_binprm)=>search_binary_handler=>list_for_each_entry=>load_elf_binary=>start_thread...總體來講,就是通過構造一些結構體,首先對文件進行打開操作,並且在結構體中保

存一些必要的信息,然后根據文件的類型,使用不同的模塊對文件進行解析,解析的過程中知道了這是一個動態鏈接的程序還是一個靜態鏈接的程序,根據這個設置內核棧中的ip,這樣在進程調度的時候,就有了合適的入口。如果是靜態鏈接的程序,這個入口就是main

函數,所以我們在學習C語言的時候,說main函數是程序的入口,是函數調用的起點,而現在我們知道,main函數本身是操作系統通過進程的方式來調用的,調用的方式就是設置內核棧中的ip的值,通過進程調度來實現main函數的調用。在上述的過程中,內核還會創建

用戶態的棧空間,這樣main函數執行的時候就有棧可以用了,就可以進入函數調用的機制。 另一方面,如果是一個動態鏈接的程序,那 么這個入口被設置成鏈接器ld的入口,執行函數的時候,先進入鏈接程序,這個鏈接程序會分析用戶程序以來的動態鏈接庫,把相應的

庫文件加載到內存中來,然后設置ip為main函數的入口,開始執行。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM