ELF文件的主體是各類節,以及描述這些節屬性的信息(Program header table和 Section header table),以及ELF文件的總體性信息(ELF header),以下圖。git
程序從源代碼到可執行文件的步驟:預處理、編譯、彙編、銜接--以hello.c爲例。github
預處理: gcc -E hello.c -o hello.i -m32 編譯:gcc -S hello.i -o hello.s -m32 彙編:gcc -c hello.s -o hello.o -m32 默認銜接(動態庫):gcc hello.o -o hello -m32 銜接靜態庫:gcc hello.o -o hello.static -m32 -static
最後獲得的hello和hello-static文件就是可執行文件。可執行文件中的內容包括有編譯後的機器指令代碼、數據還包括了連接時所需要的一些信息,好比符號表、調試信息、字符串等。
其文件格式,以下圖。編程
靜態連接:在編譯連接時直接將須要的執行代碼複製到最終可執行文件中,優勢是代碼的裝載速度快,執行速度也比較快,對外部環境依賴度低。缺點是若是多個應用程序使用同一庫函數,會被裝載屢次,浪費內存。
動態連接:編譯時不直接複製可執行代碼,而是經過記錄一系列符號和參數,在程序運行或加載時將這些信息傳遞給操做系統。操做系統負責將須要的動態庫加載到內存中,在程序運行到指定的代碼時,去共享執行內存中已經加載的動態庫去執行代碼,最終達到運行時連接的目的。優勢是多個程序能夠共享同一段代碼,而不須要在磁盤上存儲多個複製。缺點是在運行時加載可能會影響程序的前期執行性能,並且對使用的庫依賴性較高。(分爲裝載時動態連接和運行時動態連接)vim
使用gcc hello.o -o hello.static -static進行靜態連接,發現獲得的可執行程序文件大小可達到動態連接的100倍,如圖。編輯器
動態銜接分爲如下兩種;函數
裝載時動態銜接意味着在程序一開始啓動的時候其所調用的庫須要在一開始就提供,而運行時動態銜接只有程序運行到相關語句纔會訪問dllibexample。性能
對於fork():
一、子進程複製父進程的全部進程內存到其內存地址空間中。父、子進程的
「數據段」,「堆棧段」和「代碼段」徹底相同,即子進程中的每個字節都
和父進程同樣。
二、子進程的當前工做目錄、umask掩碼值和父進程相同,fork()以前父進程
打開的文件描述符,在子進程中一樣打開,而且都指向相同的文件表項。
三、子進程擁有本身的進程ID。測試
對於exec():
一、進程調用exec()後,將在同一塊進程內存裏用一個新程序來代替調用
exec()的那個進程,新程序代替當前進程映像,當前進程的「數據段」,
「堆棧段」和「代碼段」背新程序改寫。
二、新程序會保持調用exec()進程的ID不變。
三、調用exec()以前打開打開的描述字繼續打開(好像有什麼參數能夠令打開
的描述字在新程序中關閉)網站
編程使用exec* 庫函數加載一個可執行文件,動態連接分爲可執行程序裝載時動態連接和運行時動態連接,編程練習動態連接庫的這兩種使用方式。操作系統
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
可是後來查到是網易雲付費課的附件,爲了兩個代碼不必花這麼多錢,再說書上已經有源碼了,能夠本身寫一遍,可是書上的代碼有一個大坑,直接影響了我後面作實驗的進度,以後來我會說明。
shlibexample.h #ifndef _SH_LTB_EXAMPLE_H_ #define _SH_LTB_EXAMPLE_H_ #define SUCCESS 0 #define FAILURE (-1) #ifdef __cplusplus extern "C" { #endif int SharedLibApi(); #ifdef __cplusplus } #endif #endif dllibexample.h #ifndef _DL_LTB_EXAMPLE_H_ #define _DL_LTB_EXAMPLE_H_ #ifdef _cplusplus extern "C"{ #endif int DynamicalLoadingLibApi(); #ifdef _cplusplus } #endif #endif shlibexample.c #include <stdio.h> #include "shlibexample.h" int SharedLibApi() { printf("This is a shared libary!\n"); return SUCCESS; } dllibexample.c #include <stdio.h> #include "dllibexample.h" #define SUCCESS 0 #define FAILURE(-1) int DynamicalLoadingLibApi() { printf(「This is a Dynamical Loading library」!\n」); return SUCCESS; }
$ gcc -shared shlibexample.c -o libshlibexample.so -m32 $ gcc -shared dllibexample.c -o libdllibexample.so -m32
錯誤到這裏就顯現出來了,爲何會報錯我在網上查了不少答案,什麼須要更新gcc啊,什麼C文件頭名字衝突啊,反正各類答案,耗費了好久時間就是依舊會報錯,因而我從新打了好幾遍代碼,直到發現有一個賊細小的地方,書上很容易誤導。
注意看!這分明看着是就是LIB對吧,我在vim編輯器中敲的時候也和書上的樣子差很少,然而我仔細看了一下I的上橫和下橫仍是有點長短不一的,這貨該不會是T吧!!,結果事實驗證了個人判斷,這貨就是T,LTB。終於找到緣由了
main.c #include <stdio.h> #include "shlibexample.h" #include <dlfcn.h> int main() { printf("This is a Main program!\n"); /* Use Shared Lib */ printf("Calling SharedLibApi() function of libshlibexample.so!\n"); SharedLibApi(); //直接調用共享庫 /* Use Dynamical Loading Lib */ void * handle = dlopen("libdllibexample.so",RTLD_NOW);//打開動態庫並將其加載到內存 if(handle == NULL) { printf("Open Lib libdllibexample.so Error:%s\n",dlerror()); return FAILURE; } int (*func)(void); char * error; func = dlsym(handle,"DynamicalLoadingLibApi"); if((error = dlerror()) != NULL) { printf("DynamicalLoadingLibApi not found:%s\n",error); return FAILURE; } printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n"); func(); dlclose(handle); //卸載庫 return SUCCESS; }
由於shilibexample在銜接時就須要提供路徑,對應的頭文件shilibexample.h也須要在編譯器能找到位置。使用參數-L代表文件路徑,-l表示庫文件名。
dllibexample只有在程序運行到相關語句纔會訪問,在編譯時不須要任何的相關信息,使用-ldl指明其所須要的共享庫dlopen,同時修改LD_LIBRARY_PATH確保dllibexample.so能夠查到。
gcc main.c -o main -L./ -l shlibexample -ldl -m32 export LD_LIBRARY_PATH=$PWD ./main
使用gdb跟蹤分析一個execve系統調用內核處理函數sys_execve ,驗證您對Linux系統加載可執行程序所需處理過程的理解。
實驗二就只能在實驗樓環境作了,畢竟本地虛擬機沒有配好的環境。
ls cd ~/LinuxKernel rm menu -rf git clone https://github.com/mengning/menu.git cd menu mv test_exec.c test.c make rootfs