Linux內核分析——第七周學習筆記20135308


第七周 可執行程序的裝載

一、預處理、編譯、鏈接和目標文件的格式

1.可執行程序是怎么來的

C代碼—>預處理—>匯編代碼—>目標代碼—>可執行文件

.asm匯編代碼

.o目標碼

a.out可執行文件

(1)預處理:負責把include的文件包含進來及宏替換工作,即文字替換,.c變成.i  gcc -E -o main.i main.c

(2)編譯:.i變成.asm,是ASCⅡ  gcc -S -o main.asm main.i

(3)匯編:  gcc -c -o main.o main.asm

objdump 或者 readelf -h main.o命令可以查看其格式

2.目標文件的格式ELF

(1)常見的ELF格式文件:

(2)ABI——應用程序二進制接口

在目標文件中,他已經是二進制兼容,即適應二進制指令。

(3)ELF中三種目標文件:

  1. 一個可重定位(relocatable)文件保存着代碼和適當的數據,用來和其他的object文件一起來創建一個可執行文件或者是一個共享文件。(主要是.o文件)
  2. 一個可執行(executable)文件保存着一個用來執行的程序;該文件指出了exec(BA_OS)如何來創建程序進程映象。
  3. 一個共享object文件保存着代碼和合適的數據,用來被下面的兩個鏈接器鏈接。第一個是連接編輯器[請參看ld(SD_CMD)],可以和其他的可重定位和共享object文件來創建其他的object。第二個是動態鏈接器,聯合一個可執行文件和其他的共享object文件來創建一個進程映象。(主要是.so文件)

(4)目標文件格式

左邊是ELF格式,右邊是執行時候的格式,其中,ELF頭描述了該文件的組織情況,程序投標告訴系統如何創建一個進程的內存映像,section頭表包含了描述文件sections的信息。

當創建或增加一個進程映像的時候,系統在理論上將拷貝一個文件的段到一個虛擬的內存段。

Text segment拷貝到進程中的起點,Data segment拷貝到虛擬地址的某段……

可執行文件格式和進程地址空間有一個影射關系。

3.靜態鏈接的ELF可執行文件和進程的地址空間

 

一個ELF可執行文件加載到內存:

可執行文件加載到內存中開始執行的第一行代碼,默認從0x8048000開始加載,由於頭部大小不同,程序實際入口可能不同。

一般靜態鏈接將會把所有代碼放在同一個代碼段。

二、可執行程序、共享庫和動態鏈接

1.裝載可執行程序之前的工作

一般我們執行一個程序的Shell環境,我們的實驗直接使用execve系統調用。

(1)$ ls -l /usr/bin 列出/usr/bin下的目錄信息

ls是一個可執行程序

  • Shell本身不限制命令行參數的個數, 命令行參數的個數受限於命令自身

我們寫的main函數是否願意接收命令行

願意接收命令行參數
int main(int argc, char *argv[])
還願意接收shell相關環境變量
int main(int argc, char *argv[], char *envp[]) //char *envp[]是shell命令自動加的

(2)shell怎樣把環境變量傳遞

Shell會調用execve將命令行參數和環境參數傳遞給可執行程序的main函數

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

庫函數exec*都是execve的封裝。

例子:

1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <unistd.h>
4.int main(int argc, char * argv[])  //這里不是完整的命令函數,沒有寫命令行參數
5.{
6. int pid;
7. /* fork another process */  //避免原有的shell程序被覆蓋掉
8. pid = fork();  
9. if (pid<0)
10. {
11. /* error occurred */
12. fprintf(stderr,"Fork Failed!");
13. exit(-1);
14. }
15. else if (pid==0)
16. {
17. /* child process */
18. execlp("/bin/ls","ls",NULL);  //以ls命令為例
19. }
20. else
21. {
22. /* parent process */
23. /* parent will wait for the child to complete*/
24. wait(NULL);
25. printf("Child Complete!");
26. exit(0);
27. }
28.}

(3)命令行參數和環境變量是如何保存和傳遞的

命令行參數和環境串都放在用戶態堆棧中

shell程序—>execv—>sys_execve

然后在初始化新程序堆棧時拷貝進去

  • 先函數調用參數傳遞,再系統調用參數傳遞

2.裝載時動態鏈接和運行時動態鏈接應用舉例

大多數可執行程序依賴動態鏈接庫。

舉例:

動態鏈接分為可執行程序裝載時動態鏈接運行時動態鏈接

  • 准備.so文件 (在Linux下動態鏈接文件)

  • main.c  (1.9 KB) - Main program

 這里只提供shlibexample的-L(庫對應的接口頭文件所在目錄)和-l(庫名,如libshlibexample.so去掉lib和.so的部分),並沒有提供dllibexample的相關信息,只是指明了-ldl

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH
=$PWD #將當前目錄加入默認路徑,否則main找不到依賴的庫文件,當然也可以將庫文件copy到默認路徑下。
$ .
/main
This
is a Main program!
Calling SharedLibApi() function of libshlibexample.so
!    //調用共享庫
This
is a shared libary!
Calling DynamicalLoadingLibApi() function of libdllibexample.so
!    //調用動態裝載庫
This
is a Dynamical Loading libary!

三、可執行程序的裝載

1.可執行程序裝載相關關鍵問題分析

(1)execve和fork都是特殊的系統調用

  • 正常的系統調用:陷入到內核態,返回到用戶態,執行系統調用的下一條指令。
  • fork:進入到內核態,兩次返回:第一次返回到父進程的位置,繼續執行。第二次,在子進程中從ret_from_fork開始執行然后返回用戶態。
  • execve:當前的可執行程序執行到execve時,陷入到內核態,用execve加載的可執行文件將當前的可執行程序覆蓋掉,當execve系統調用返回時,返回的不是原來的系統調用,而是新的可執行程序的執行起點,即main函數的位置。

(2)sys_execve內核處理過程

sys_execve內部會解析可執行文件格式

  • do_execve -> do_execve_common -> exec_binprm
  • search_binary_handler符合尋找文件格式對應的解析模塊,如下:
1369    list_for_each_entry(fmt, &formats, lh) {        //在鏈表中尋找可以處理ELF格式的模塊
1370 if (!try_module_get(fmt->module))
1371 continue;
1372 read_unlock(&binfmt_lock);
1373 bprm->recursion_depth++;
1374 retval = fmt->load_binary(bprm);          //對於ELF格式的可執行文件fmt->load_binary(bprm);執行的應該是load_elf_binary其內部是和ELF文件格式解析的部分需要和ELF文件格式標准結合起來閱讀
1375        read_lock(&binfmt_lock);

(3)Linux內核是如何支持多種不同的可執行文件格式的

82static struct linux_binfmt elf_format = {      //elf_foemat結構體
83 .module = THIS_MODULE,
84 .load_binary = load_elf_binary,          //多態機制,觀察者模式
85 .load_shlib = load_elf_library,
86 .core_dump = elf_core_dump,
87 .min_coredump = ELF_EXEC_PAGESIZE,
88};
2198
static int __init init_elf_binfmt(void)
2199{
2200 register_binfmt(&elf_format);      //把elf_format變量注冊到fmt鏈表中
2201 return 0; 2202}

 

庄生夢蝶

 

  • 庄周(調用execve的可執行程序)入睡(調用execve陷入內核),醒來(系統調用execve返回用戶態)發現自己是蝴蝶(被execve加載的可執行程序)

  • 修改int 0x80壓入內核堆棧的EIP

  • load_elf_binary ->  start_thread

2.sys_execve內部處理過程

  • 需要動態鏈接的可執行文件先加載連接器ld;否則直接把elf文件entry地址賦值給entry即可。
  • start_thread(regs, elf_entry, bprm->p)會將CPU控制權交給ld來加載依賴庫並完成動態鏈接;對於靜態鏈接的文件elf_entry是新程序執行的起點

3.使用gdb跟蹤sys_execve內核函數的處理過程

1.更新menu

2.查看test.c文件,可以看到增加了exec系統調用,其源代碼與之前的fork類似

3.查看Makefile,發現增加了gcc -o hello hello.c -m32 -static,並且依據視頻補充上那兩句代碼。

4.make rootfs,發現多了exec功能,並且比fork多了Hello World!

5.凍結內核,開始gdb調試,加載符號表,target remote

6.設置三個斷點,開始跟蹤

7.開始執行exec,到這里停下,開始系統調用

8.列出來,跟蹤

9.跑到load_elf_binary,看這部分的代碼

10.對照hello可執行程序的入口點地址

11.進入后,逐步跟蹤,發現在壓棧

3.可執行程序與庄生夢蝶的故事

4.淺析動態鏈接的可執行程序的裝載

(1)動態鏈接的過程中,內核做了什么?

  • 可執行程序需要依賴動態鏈接庫,而這個動態鏈接庫可能會依賴其他的庫,這樣形成了一個樹形結構;
  • elf_interpreter:需要依賴動態鏈接器進行加載這些庫(ld)並進行解析,entry返回動態鏈接器的入口,加載所有需要的動態鏈接庫,即廣度遍歷樹,然后ld將CPU的控制權交給可執行程序入口(頭部起點位置)
  • 動態鏈接的過程主要是動態鏈接器來完成,而不是內核。

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com