可執行程序的裝載
作者:20135304劉世鵬
《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一、預處理、編譯、鏈接和目的文件的格式
1、1可執行程序是怎么得來的

- C代碼經過編譯器的預處理編譯、編譯成匯編代碼、編譯器將其編譯成目標代碼、鏈接成可執行文件。
- 預處理負責把include的文件包含進來及宏替換等工作。
- 預處理之后的文件編譯成匯編代碼。
- 匯編代碼.s編譯成.o
1、2目標文件格式的ELF
- 常見的文件格式:A.out(最古老的)、 COFF 、PE(windows系統)、 ELF(Linux系統)
- ABI應用程序二進制接口,在目標文件中已經是二進制兼容的格式了,目標文件適應到某一種CPU體系結構
- ELF格式中主要有3種可執行文件:可重定位文件.o,可執行文件,共享目標文件

- 一個ELF頭在文件的開始,保存了路線圖,描述了文件的組織情況
- 當創建或增加一個進程映像的時候,系統在理論上將拷貝一個文件的段到一個虛擬的內存段
1、3靜態鏈接的ELF可執行文件和進程的地址空間

- 可執行文件ELF加載到內存中去:代碼的數據加載到內存中去
- 默認ELF文件加載到0x8048000
- 程序的實際入口0x8048300(啟動一個剛加載過可執行文件之后開始執行的入口點)
- 一般靜態鏈接會將所有代碼放在一個代碼段
- 動態鏈接的進程會有多個代碼段
二、可執行程序、共享庫和動態鏈接
2、1裝載可執行程序之前的動作

- 可執行程序的執行環境:shell命令行、main函數的參數和execve參數
- shell本身不限制命令行參數的個數,命令行參數的個數受限於命令本身
- shell會調用execve將命令行參數和環境傳遞給可執行程序的main函數
- 命令行參數和環境變量是如何保存和傳遞的:execve將原來的執行環境覆蓋掉了,shell程序——>execve——>sys_execve然后在初始化新程序堆棧時拷貝進去
- 先函數調用參數傳遞,再系統調用參數傳遞
2、2裝載動態鏈接和運行時動態鏈接應用舉例
- 動態鏈接分為:可執行程序裝載時動態鏈接和運行時動態鏈接
- 准備.so文件——>編譯成libshlbexample.so文件(共享庫)——>編譯成libdllibexample.so文件(動態裝載)
- 編譯main:只提供了shellexample的-L和-l,並沒有提供dllibexample的相關信息,只指明了-ldl



三、可執行程序的裝載
3、1可執行程序的裝載相關關鍵問題分析
- execve和fork都是特殊的系統的調用,當前程序執行到execve系統調用時陷入到內核態,execve加載可執行文件把當前進程的可執行文件覆蓋掉,execve返回新的可執行程序的執行起點。

- int execve(把命令行和環境參數加載進來)
- sys_execve內部會解析可執行文件格式:do_execve->do_execve_common->exec_binprm
- search_binaty_handle符合文件格式,對應的解析模塊(根據文件頭部信息尋找對應的文件格式處理模塊)
- 對於ELF格式的可執行文件執行的是load_elf_binary

- elf_format和init_elf_binfnt是觀察者模式中的觀察者
- 尋找頭文件是被觀察者,出現elf格式文件,觀察者自動執行elf_format模塊
- execve系統調用返回用戶態從哪里開始執行:load_elf_binary->start_thread(通過修改內核堆棧中EIP的值作為新程序的起點)
3、2sys_execve的內部處理過程
- search_binary_handler(尋找可執行文件的處理函數) fmt->load_binary(加載可執行程序的處理函數)
- register_binfmt(注冊結構體變量)
- kernal_read讀取文件信息
- ELF可執行文件會被默認映射到0x8048000這個地址
- 需要動態鏈接的可執行文件先加載連接器ld這個共享庫 load_elf_interp(加載動態連接器的起點)
- 如果是靜態鏈接直接進入elf_entry,elf_entry是新程序的起點
- start_thread(如果是靜態鏈接,直接指向main8048000;如果可執行文件是動態鏈接庫,指向動態連接器的起點
3、3使用gdb跟蹤sys_execve的內部處理過程
自己實驗截圖:
(1)克隆,覆蓋test.c

test.c文件代碼:

(2)生成根文件系統時,將init hello放入rootfs地址中,這樣在執行exec文件時,就自動加載hello文件


(二)使用gdb跟蹤sys_execve內核函數的處理過程
1、加載符號表,並連接到端口1234

2、設置斷點:b sys_execve(可以先停在sys_execve然后再設置其他斷點),b load_elf_binary,b start_thread。

3、執行

4、輸入c繼續執行,輸入指令exec,list查看,按s可以跟蹤進行到do_execve的內部。

3、4可執行程序的裝載與庄生夢蝶的故事
- 庄周(調用execve的可執行程序)
- 入睡(調用execve陷入內核)
- 醒來(系統調用execve返回用戶態)
- 發現自己是蝴蝶(被execve加載的可執行程序)
3、5淺析動態鏈接的可執行程序裝載
- 動態鏈接的過程,內核做了什么?動態鏈接庫的依賴會形成一個圖
-
ELF格式中的,.interp和.dynamic需要依賴動態鏈接器來解析
-
entry返回到用戶態時不是返回到可執行程序規定的起點,返回到動態鏈接器的程序入口
-
裝載和鏈接之后ld將CPU的控制權交給可執行程序
- 動態鏈接庫的裝載過程是一個圖的遍歷過程
四、總結
1、可執行程序的產生:
C語言代碼-->編譯器預處理-->編譯成匯編代碼-->匯編器編譯成目標代碼-->鏈接成可執行文件,再由操作系統加載到內存中執行。
2、ELF格式中主要有3種可執行文件:可重定位文件.o,可執行文件,共享目標文件。
3、ELF可執行文件會被默認映射到0x8048000這個地址。
4、命令行參數和環境變量是如何進入新程序的堆棧的?
Shell程序-->execve-->sys_execve,然后在初始化新程序堆棧時拷貝進去。
先函數調用參數傳遞,再系統調用參數傳遞。
5、當前程序執行到execve系統調用時陷入內核態,在內核中用execve加載可執行文件,把當前進程的可執行文件覆蓋掉,execve系統調用返回到新的可執行程序的起點。
6、動態鏈接庫的裝載過程是一個圖的遍歷過程,
ELF格式中的.interp和.dynamic需要依賴動態鏈接器來解析,entry返回到用戶態時不是返回到可執行程序規定的起點,返回到動態鏈接器的程序入口。
