內核如何裝載和啓動一個可執行程序

陳民禾——原創做品轉載請註明出處—— 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000編輯器

一.上週內容總結複習函數

上一週學習了Linux 如何存放和表示進程(用task_ struct 和thread_info ),如何建立進程(經過fork(),實際上最終是clone()),如何把新的執行映像裝入到地址空間(經過execO 系統調用族〉,如何表示進程的層次關係,父進程又是如何收集其後代的信息(經過wait()系統調用族),以及進程最終如何消亡〈強制或自願地調用exit()) 。學習

進程的狀態大體以下圖所示spa

二.幾個重要概念3d

可執行程序:可執行程序以C語言代碼爲例,通過編譯器的預處理,處理完以後把它編譯成彙編代碼,而後有個彙編器將它編譯成彙編代碼,而後,將其連接成可執行文件。調試

目標文件的格式ELF,常見的目標文件格式:A.out  COFF後來發展爲PE和ELF,目標文件也常常叫作ABI ,也就是應用程序二進制接口,實際上在二進制兼容的格式這個目標文件已經適應了某種CPU體系結構上的二進制指令,好比說一個32位X86文件鏈接成arm可執行文件是不能夠的,在ELF文件中有三種可執行文件,可重定位文件:保存着代碼和適當的數據,用來和其餘的object文件一塊兒建立一個可執行文件或者是一個共享文件。可執行文件:保存着一個用來執行的程序,該文件指出了exec如何來建立程序進程映像共享object文件:保存着代碼和合適的數據,用來被下面兩個連接器鏈接,一個是鏈接編輯器,能夠和其餘的可重定位和共享object文件來建立其餘的object;第二個是動態連接器,聯合一個可執行文件和其餘共享的object文件來建立一個進程映像ELF目標文件格式:code

object文件參與程序的聯接(建立一個程序)和程序的執行(運行一個程序),orm

-Figure 1-1;Object File Format
  Linking view                                                          Execute View
  ELF header                                                             ELF header
  Program header table(optional)                          Program header table
  Section 1                                                                Segment 1
  ....                                                                             Segment 2
  Section n                                                                 ...
  Section header table                                              Section header table(optional)
一個ELF文件在文件的開頭,保存了路線圖(road map),描述了該文件的組織狀況,程序頭表(program header table)告訴系統如何來建立一個進程的內存映像。section頭表(section header table)包含了描述文件的section信息,每一個section在這個表中有一個入口;每一個入口給出了該section的名字,大小,等等信息。
可使用read elf來看elf文件的信息。當建立或者增長一個進程映像的時候,系統在理論上將拷貝一個文件的段到一個虛擬的內存段。拷貝到進程的起點 0x8048000

動態可執行文件:它要依賴這個可執行程序,須要其餘的動態連接庫,這個動態連接庫,某一個點它也要依賴其餘的動態連接庫,動態連接庫的動態連接庫,動態連接庫包括可執行文件,實際上動態連接庫的依賴關係會造成一個圖,ELF格式的文件,假如說都是同樣的,就會對ELF文件進行解析,看它依賴了哪些動態連接庫,這樣它就會加載。blog

三.重要知識和過程接口

可執行文件產生過程:好比咱們以一個hello world程序爲例,咱們能夠把.c文件作預處理

預處理命令:
gcc -E -o hello.cpp hello.c -m32
預處理負責把include的文件包含進來及宏替換等工做,咱們能夠看到hello.cpp裏面把原來的文件對字符串進行一個處理。
把與處理的文件編譯成彙編代碼:
gcc -x cpp -output -S -o hello.s hello.cpp -m32
彙編代碼處理成目標文件:
gcc -x assembler -c hello.s -o hello.o -m32
編譯彙編和連接,這樣就獲得了一個二進制的文件hello.o,前面的都是文本文件是可讀的,這裏hello.o打開以後是文本文件是亂碼
hello.o將其連接爲可執行文件
gcc -o hello hello.o -m32
這時候能夠看到hello.o是一個可執行文件,也是一個二進制文件,這個二進制文件打開後都是elf格式的文件,
這樣編譯出來的hello文件時使用共享庫的,它會調用printf也就是libc,c庫裏面的函數,若是咱們是靜態編譯的那就加一個-static.c,咱們編譯出來的是一個徹底全部須要執行的程序都在內部,能夠看到:
gcc -o hello.static hello.o -m32
hello.static把c庫裏面的東西也放到可執行程序裏面了,待會兒會涉及到靜態連接動態連接,共享庫等內容,是怎樣執行可執行文件的可能會有幫助

可執行文件和進程的地址空間:當一個可執行文件ELF加載到內存的時候,它是怎麼加載的呢,咱們加載的效果知道,把代碼的數據加載到一塊內存中來,把數據加載到內存中來,固然代碼有不少塊代碼,不少代碼段,加載進來以後默認elf加載到0x8048000從這個位置開始加載,那麼加載以後可能以前是一個ELF頭部文件的信息,通常來說,這個頭部大小的文件信息可能就是會有不一樣,因此加載時的入口點的位置可能不一樣,這個地方就是程序的實際入口,當啓動一個新的程序的時候,它就是從這個地方開始執行,加載到啓動一個新的進程,啓動一個剛加載過可執行文件的進程,一個新的進程只是fork了原來的一份,它的執行位置仍是執行了原來那個進程的位置,加載了新的可執行文件以後,開始執行的入口點,這個是一個靜態連接的ELF可執行文件,這個時候都已經幫咱們連接好了,從這裏開始一個文件一個文件的開始執行,怎麼壓棧出棧,怎麼來操做,能把整個程序執行完,也就是從main函數到main函數執行完畢。

裝載可執行程序以前的工做:咱們通常是經過share程序來執行一個可執行程序,當咱們裝載一個可執行程序,也就是咱們發起一個系統調用execve,咱們還須要準備哪些,這個share環境爲咱們準備了哪些可執行的上下文環境,這樣咱們就大概在用戶態的執行文件大概瞭解一下,而後咱們看一下一個execve,它怎麼把一個可執行文件在內核裏面裝載起來,又返回到用戶態。

四.實驗過程截圖及分析

 打開窗口加載qemu,克隆新版本

查看makefile的代碼

查看關鍵代碼:父子進程

凍結窗口開始進行調試:

使用gdb設置斷點,能夠看到分別在sys_execve和load_elf_binary處設置斷點

查看附近的代碼:給新棧的賦值。

執行到start_thread的時候有一個問題:
new ip究竟是指向哪裏的?
用po(print object)指令:

po new_ip 能夠看到一個地址:0x80495ba

enter description here

readelf -h hello 找到hello這個可執行程序的入口地址。 這是一個靜態編譯的可執行文件。

enter description here

new ip是返回用戶態的第一條指令的地址。

五.學習本週知識的總結

   用一個比較形象的比喻就是實際上咱們把原來的可執行程序,也就是share可執行程序,int 0x80進入到這個execve的系統調用入睡,當他入睡的時候加載到一個新的可執行程序,加載到新的可執行程序,return 返回以後,也就是它醒來了,蝴蝶執行了在蝴蝶內部的程序,它若是加載莊子,這二者老是想相對的,但都是同一進程,只是把進程裏面的可執行程序給替換掉了,這就是咱們對進程如何加載和運行的一個基本的描述。 

相關文章
相關標籤/搜索