天天看點

Linux核心如何裝載和啟動一個可執行程式

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

    學習過程其實就是模仿老師的過程,萬一足夠熟練了,就變成自己的了。核心代碼部分的确有些痛苦,好在本周回到了使用者shell層面,畢竟有些了解。将整個學習過程記錄如下,也是自己的成長經曆。

一、可執行檔案的生成過程。我們說的可執行檔案是給cpu執行的二進制代碼,那它又是我們人所編輯的。這個過程我就簡單點,有下面的shell指令說明一下。結果如圖。

Linux核心如何裝載和啟動一個可執行程式

我們在windows上的可執行檔案是PE檔案。在linux下是elf格式。

文本編輯器編輯源代碼檔案->預編譯處理->彙編成.s檔案->編譯生成目标.o檔案->連結程式将目标檔案連接配接成可執行檔案。

二、對于靜态連結的elf檔案,基本上在加載時對應加上程式入口位址,将相應的代碼資料加載到對應的記憶體空間中,然後逐漸執行代碼。以下是我的ELF Header情況。

Linux核心如何裝載和啟動一個可執行程式

我用gdb跟一下看看執行時的代碼入口位址。由于實際上我們的main函數是由_start調用的,是以我們設定斷點 break _start。結果如下:

Linux核心如何裝載和啟動一個可執行程式

三、通常的linux下的可執行程式都是在shell下執行的,是以會進行相應的執行前處理過程。比如/bin/ls -l這個指令,就是先fork出一個程序,調用execve系統調用,然後再調用具體的execlp調用将相關的指令行參數傳遞給程式的main函數的。

四、通常我們的程式還需要使用動态連結庫。分為裝載時動态連結和運作時動态連結。示範代碼,

    通過這樣的指令生成共享庫:

    以下指令生成運作時連結庫:

生成檔案及運作情況如下:

Linux核心如何裝載和啟動一個可執行程式

五、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就是檔案頭部标明的入口。進入核心後

execve在傳回前用新的ip和sp更新了程序的ip和sp。對于需要動态連結的程式,elf_entry就會加載動态連結器ld的入口位址。

總結,在linux環境下,可執行檔案是以ELF格式存在的,檔案頭部标明了檔案在加載到記憶體中需要的相關資訊,随後的部分是以段的形式存在的代碼和資料,段的劃分主要依據加載到記憶體中的讀寫屬性。系統調用execve負責可執行檔案的排程工作,先進行相關參數的傳遞和調用前環境的處理,然後加載可執行檔案的資訊,查找相應的可執行檔案解析子產品,對于ELF格式的可執行檔案,按照格式要求加載到記憶體中相應的位址空間,如果是靜态連結的就将檔案頭部标明的入口位址作為開始;如果是依賴動态連結庫的可執行檔案則需要将動态連結器ld的入口位址作為開始。

繼續閱讀