1. 程式人生 > >Linux核心裝載和啟動一個可執行程式

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

      首先,我們需要了解,什麼是可執行程式。可執行程式是一種可以被計算機識別的程式,是原始碼經過預處理、編譯、連結等步驟後形成的程式,大體關係如下圖所示。

                                     圖1.c語言程式執行流程

       C源程式標頭檔案->預編譯處理(cpp)->編譯程式本身->優化程式->彙編程式->連結程式->可執行檔案。這大概就是整個c語言程式變成最終可以執行的檔案的流程,看起來還是比較複雜,實際情況可能更加複雜。

編譯預處理:主要包括對巨集定義指令、條件編譯指令、標頭檔案包含指令、特殊符號進行處理。

編譯階段:編譯的工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將檔案翻譯成等價的中間程式碼表示或彙編程式碼。

彙編階段:彙編過程實際上指把組合語言程式碼翻譯成目標機器指令的過程。

       以上是在一般情況下c語言變成可執行檔案的流程,那麼在Linux系統中有什麼不同呢?要想了解這個Linux下面的流程,我們先來了解一種檔案格式ELF。ELF是一種用於二進位制檔案、可執行檔案、目的碼、共享庫和核心轉儲的標準檔案格式。是UNIX系統實驗室作為應用程式二進位制介面而開發的,也是Linux的主要可執行檔案格式。

ELF檔案由4部分組成,分別是:

ELF頭:是對elf檔案整體資訊的描述,在32位系統下是56的位元組,在64位系統下是64個位元組。

segment表:這個表是載入指示器

elf的主題:對於可執行檔案來說,最主要的就是資料段和程式碼段。

section表:對可執行檔案來說,沒有用,在連結的時候有用,是對程式碼段資料段在連結時的一種描述。

       針對ELF檔案有幾種操作方式,比如檢視ELF檔案的頭部:readelf –h hello;檢視該ELF檔案依賴的共享庫:readelf  -d  main。

       ELF檔案準備好了,現在可以開始裝載了。當我們在Linux的命令列下輸入一個命令執行某個ELF程式時,shell程序會呼叫fork()建立一個新的程序,然後新的程序呼叫execve()來執行ELF檔案,原先的shell程序繼續返回等待剛才啟動時新程序結束,然後繼續等待使用者輸入命令。隨著一個新程序的出現,作業系統會為它建立一個獨立的虛擬地址空間。

      在進入execve()系統呼叫之後,Linux核心就開始進行真正的裝載工作。在核心中,execve()系統呼叫相應的入口是sys_execve(),該sys_execve()函式作用是引數的檢查複製;呼叫do_execve(),該do_execve()函式的流程是這樣的,首先查詢被執行的檔案,然後讀取檔案的前128個位元組以判斷檔案的格式是elf還是其它,最後執行相應的操作;最後呼叫search_binary_handle(),該函式的流程為通過判斷檔案頭部的模數確定檔案的格式,並且呼叫相應的裝載處理程式。ELF可執行檔案的裝載處理過程叫load_elf_binary()。該函式的執行流程可以分為以下五步。

    1.檢查ELF可執行檔案格式的有效性。

    2.找到動態連結器的路徑,為動態連結準備。

    3.讀取可執行檔案的程式頭,並且建立虛擬空間與可執行檔案的對映關係。

    4.初始化ELF程序環境。

    5.將系統呼叫的返回地址修改成ELF可執行檔案的入口點。


                       圖2.ELF檔案格式

       這個入口點取決於程式的連結方式,對於靜態連結的ELF可執行檔案,它就是ELF檔案的檔案頭中e_entry所指的地址;對於動態連結的ELF可執行檔案,程式入口點就是動態連結器。

還有一個問題是可執行程式的環境是怎麼樣的呢?我們這裡直接使用shell,Shell會呼叫execve將命令列引數和環境引數傳遞給可執行程式的main函式。

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

庫函式exec*都是execve的封裝例程。

        最後就是連結問題了,連結分為可執行程式裝載時動態連結和執行時動態連結。連結好的程式就可以執行了,現在來實驗驗證一下是否是我們分析的這樣麼。

                      

                               圖3.編輯hello等檔案


                             圖4.hello.c檔案的內容


                            圖5.test.c檔案增加函式exec呼叫


                                        圖6.hello函式內容

                             圖7.修改makefile檔案


                           圖8.列印內容


                                 圖9.設定兩個斷點

 

                                  圖10.編譯啟動

                                       圖11.斷點處函式


                               圖12.單步除錯


                                圖13.除錯跟蹤丟失


                              圖14.檢視入口地址


                                      圖15.匹配新函式入口地址


                             圖16.跟蹤除錯完成

       總結:通過上面的分析,應該可以大概瞭解Linux系統是如何操作呼叫一個函式,總的來說,exec本質就是個替換程序程式碼段的過程,這裡面的難點在於elf檔案格式的解析,以及新的程式碼段堆疊資訊和暫存器上下文的設定,上面已經清楚的解釋elf檔案,對於堆疊變化部分,感興趣的讀者可以自己去查閱相關資料