Linux核心原理第八次作業
阿新 • • 發佈:2018-12-02
Linux核心如何裝載和啟動一個可執行程式
一、ELF可執行檔案格式
- ELF格式分類:
可重定位檔案:用來和其他object檔案一起建立可執行檔案和共享檔案
可執行檔案:指出應該從哪裡開始執行
共享檔案:主要是.so檔案,用來被連結編輯器和動態連結器連結
(1)對ELF頭的描述告訴系統如何建立一個程序的記憶體映像,section頭表包含了描述檔案sections的資訊。當建立或增加一個程序映像時,理論上它會把 程式段拷貝到虛擬記憶體中某個段 (2)ELF檔案的頭部規定了許多與二進位制相容性相關的資訊。所以在載入ELF檔案的時候,必須先載入頭部,分析ELF的具體資訊 (3)entry代表剛載入過新的可執行檔案之後的程式的入口地址,頭部後是程式碼和資料,程序的地址空間是4G,上面的1G是核心用,下面的3G是程式使用。 預設的ELF頭載入地址是0x8048000
- 靜態連結的ELF可執行檔案和程序的地址空間:
1.可執行檔案載入到記憶體
載入效果:將程式碼段資料載入到記憶體中,再把資料載入到記憶體,預設從0x8048000地址開始載入
啟動一個剛載入過可執行檔案的程序時,可執行檔案載入到記憶體之後執行的第一條程式碼地址
一般靜態連結會將所有程式碼放在一個程式碼段,而動態連結的程序會有多個程式碼段
2.流程
(1) 分析頭部 (2) 檢視是否需要動態連結。如果是靜態連結的ELF檔案,那麼直接載入檔案即可。如果是動態連結的可執行檔案,那麼需要載入的是動態連結器 (3) 裝載檔案,為其準備程序映像 (4) 為新的程式碼段設定暫存器以及堆疊資訊
二、實驗內容
更新menu後用test.c覆蓋test_exec.c,然後開啟test.c(shitf+G 直接到檔案尾的快捷鍵)
可以看到添加了exec命令,執行一個程式的功能。
其函式內容為
int Exec(int argc, char * argv[]) { int pid; /* fork another process */ pid = fork(); if (pid<0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid==0) { /* child process */ execlp("/bin/ls","ls",NULL); } else { /* parent process */ /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!"); exit(0); } }
由於在Makefile中做了修改。編譯的時候執行了hello.c
並把init 和 hello 放到了rootfs.img目錄下,所以在執行exec命令的時候就相當於自動了載入了hello這個程式
下面是通過gdb進行跟蹤分析
設定b sys_execve ,b load_elf_binary 斷點
可以先停在sys_execve然後設定其他斷點
new_ip是返回到使用者態的第一條指令的地址
此時可以通過po new_ip和新視窗的 readelf -h helloc檢視入口地址是否一致
可以看到hello的入口地址和new_ip的值都是0x8048d0a 。說明對hello程式連結到了執行程式中。
三、總結
在建立一個新的使用者態堆疊的時候,實際上是把命令列引數的內容和環境變數的內容通過指標的方式傳遞到系統呼叫的核心處理函式的,核心處理函式在建立一個新的可執行堆疊的時候會將
命令列引數的內容和環境變數的內容拷貝到使用者態堆疊裡面來初始化新的可執行程式執行的上下文環境,先函式呼叫引數傳遞,在系統呼叫引數傳遞
shell程式 -> execve -> sys_execve
- sys_execve的內部處理過程 ( 裝載和啟動一個可執行程式依次呼叫以下函式):
sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()