LINUX核心分析。7

2puT發表於2016-07-30

一、得到一個可執行程式

1. 預處理、編譯、連結

gcc hello.c -o hello.exe
gcc編譯原始碼生成最終可執行的二進位制程式,GCC後臺隱含執行了四個階段步驟。
預處理 => 編譯 => 彙編 => 連結
預處理:編譯器將C原始碼中包含的標頭檔案編譯進來和執行巨集替換等工作。
gcc -E hello.c -o hello.i
編譯:gcc首先要檢查程式碼的規範性、是否有語法錯誤等,以確定程式碼的實際要做的工作,在檢查無誤後,gcc把程式碼翻譯成組合語言。
gcc –S hello.i –o hello.s
-S:該選項只進行編譯而不進行彙編,生成彙編程式碼。
彙編:把編譯階段生成的.s檔案轉成二進位制目的碼.gcc –c hello.s –o hello.o
連結:將編譯輸出.o檔案連結成最終的可執行檔案。
gcc hello.o –o hello
執行:若連結沒有-o指明,則生成可執行檔案預設為a.out
./hello

2. 目標檔案格式

(1)檔案格式
a.out是最早的可執行檔案格式
注:ABI——應用程式二進位制介面
(2)ELF分類
可重定位檔案:儲存著程式碼和適當的資料,用來和其他的object檔案一起來建立一個可執行檔案或者是一個共享檔案。
可執行檔案:儲存著一個用來執行的程式;該檔案指出了exec(BA_OS)如何來建立程式程式映象。
共享檔案:儲存著程式碼和合適的資料,用來被下面的兩個連結器連結。 •第一個是連線編輯器[請參看ld(SD_CMD)],可以和其他的可重定位和共享object檔案來建立其他的object。
第二個是動態連結器,聯合一個可執行檔案和其他的共享object檔案來建立一個程式映象。
object檔案參與程式的連結(建立)和執行。
(3)ELF頭
檢視ELF檔案的頭部:readelf
在檔案開始儲存了:

  • 路線圖:描述該檔案組織情況
  • 程式頭表:告訴系統如何建立一個程式的記憶體映像
  • section頭表:描述檔案的section資訊。(每個section在這個表中有一個入口,給出該section資訊)
    當建立或增加一個程式映像時,系統在理論上將拷貝一個檔案的段到一個虛擬的記憶體段。

    3. 靜態連結的ELF可執行檔案和程式的地址空間

    入口點:程式從0x804800開始。
    可執行檔案載入到記憶體中開始執行的第一行程式碼。
    一般靜態連結將會把所有程式碼放在同一個程式碼段。
    動態連線的程式會有多個程式碼段。

    二、可執行程式的執行環境

    1. 命令列引數和shell環境

    列出/usr/bin下的目錄資訊
    $ ls -l /usr/bin
    Shell本身不限制命令列引數的個數,命令列引數的個數受限於命令自身
    int main(int argc, char argv[], char envp[])
    Shell會呼叫execve將命令列引數和環境引數傳遞給可執行程式的main函式
    int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
    庫函式exec*都是execve的封裝例程

    2. 命令列引數和shell環境變數的儲存與傳遞

    shell程式 => execve => sys_execve
    命令列引數和環境串都放在使用者態堆疊中

初始化新程式堆疊時拷貝進去

  1. 可執行程式動態連結

(1)動態連結

實際上,裝載過程是一個廣度遍歷,遍歷的物件是“依賴樹”。

主要過程是動態連結器完成、使用者態完成。

(2)裝載時動態連結
/準備.so檔案/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example

/編譯成libshlibexample.so檔案/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32

/使用庫檔案(因為已經包含了標頭檔案所以可以直接呼叫函式)/
SharedLibApi();

(3)執行時動態連結
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example

/編譯成libdllibexample.so檔案/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32

/使用庫檔案/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先載入進來
int (*func)(void);//宣告一個函式指標
func = dlsym(handle,"DynamicalLoadingLibApi");//根據名稱找到函式指標
func(); //呼叫已宣告函式

(4)執行
$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/將當前目錄加入預設路徑,否則main找不到依賴的庫檔案,當然也可以將庫檔案copy到預設路徑下。/

三、可執行程式的裝載

  1. sys_execve核心處理過程

(1)新的可執行程式起點
一般是地址空間為0x8048000或0x8048300

(2)execve與fork
execve和fork都是特殊一點的系統呼叫:一般的都是陷入到核心態再返回到使用者態。

fork兩次返回,第一次返回到父程式繼續向下執行,第二次是子程式返回到ret_from_fork然後正常返回到使用者態。

execve執行的時候陷入到核心態,用execve中載入的程式把當前正在執行的程式覆蓋掉,當系統呼叫返回的時候也就返回到新的可執行程式起點。

execve
執行到可執行程式 -> 陷入核心
構造新的可執行檔案 -> 覆蓋掉原可執行程式
返回到新的可執行程式,作為起點(也就是main函式)
需要構造其執行環境;
Shell會呼叫execve將命令列引數和環境引數傳遞給可執行程式的main函式,先函式呼叫引數傳遞,再系統呼叫引數傳遞。

(3)靜態連結的可執行程式和動態連結的可執行程式execve系統呼叫返回時不同
靜態連結:elf_entry指向可執行檔案的頭部,一般是main函式,是新程式執行的起點。
動態連結:elf_entry指向ld(動態連結器)的起點,載入load_elf_interp

  1. 動態連結的可執行程式的裝載

(1)可執行檔案開始執行的起點在哪裡?如何才能讓execve系統呼叫返回到使用者態時執行新程式?
修改int 0x80壓入核心堆疊的EIP,通過修改核心堆疊中EIP的值作為新程式的起點。

(2)Linux核心是如何支援多種不同的可執行檔案格式
static struct linux_binfmt elf_format//宣告一個全域性變數 = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,//觀察者自動執行
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};

static int __iit init_elf_binfmt(void)
{n
register_binfmt(&elf_format);//把變數註冊進核心連結串列,在連結串列裡查詢檔案的格式
return 0;
}

(3)動態連結
可執行程式需要依賴動態連結庫,而這個動態連結庫可能會依賴其他的庫,這樣形成了一個關係圖——動態連結庫會生成依賴樹。
依賴動態連結器進行載入庫並進行解析(這就是一個圖的遍歷),裝載所有需要的動態連結庫;之後ld將CPU的控制權交給可執行程式
動態連結的過程主要是動態連結器在起作用,而不是核心完成的。

四,實驗

初始化環境:

跟蹤:

五,感想

本週學習的內容上學期在婁老師中有所涉及,所以還比較親切,可執行檔案的生成的過程,還學習了靜態庫,動態庫。其中預處理,編譯成彙編指令,變成二進位制程式碼,最後執行可執行檔案這四步已經深深的印入我們的腦海。

參考:http://www.cnblogs.com/hyq20135317/p/5362269.html

相關文章