1、可執行目標檔案
註:ELF(Executable and Linkable Format)
從中可以看出,程式碼片段的地址總是比資料區段的地址小。
2、載入可執行目標檔案
任何Unix程式都可以通過調用execve函數來調用載入器。載入器將可執行目標檔案中的代碼和資料從磁碟拷貝到儲存空間中,然後通過跳轉到程式的第1條指令,即進入點(Entry Point),來運行該程式。將程式拷貝到儲存空間並啟動並執行過程叫做載入(loading)。
每個Unix程式都有一個運行時儲存空間映像,中。在Linux系統中,程式碼片段總是從地址0x08048000處開始。資料區段是在接下來的下一個4KB對齊的地址處。堆在接下來的讀/寫段之後的第一個4KB對齊的地址處,並通過調用malloc庫往上增長。從0x4000000處的段是為共用庫保留的。使用者棧總是從地址0xbfffffff處開始,並向下增長的(高地址向低地址增長的)。從棧的上部開始於地址0xc0000000處的段是為作業系統駐留儲存空間的部分(也就是核心)的代碼和資料保留的。
3、動態連結共用庫
共用庫是一個目標模組,在運行時,可以載入到任意的地儲存空間地址,並在儲存空間中和一個程式連結起來。這個過程稱為動態連結(dynamic linking),是由一個叫做動態連結器(dynamic linker)的程式執行的。
共用庫也稱為共用目標(shared object),在Unix系統中通常用.so尾碼來表示。(在MS OS 中為DLL檔案)
注意:靜態連結與動態連結的區別:靜態連結是把程式所需要的庫代碼和資料拷貝和嵌入到引用它們的可執行檔中;而動態連結是所有引用該庫的可執行檔檔案分享權限設定這個.so(dll)檔案中的代碼和資料。
4、從應用程式中載入和連結共用庫
通過幾個函數,dlopen載入和連結共用庫,dlsym通過輸入的共用庫的符號名字,返回符號的地址;dlclose卸載共用庫,dlerror返回前面函數執行情況的一個字串。
範例程式碼
#include <stdio.h>#include <dlfcn.h>int x[2] = {1, 2};int y[2] = {3, 4};int z[2];int main(){void *handle;void (*addvec)(int *, int *, int *, int);char *error;* dynamically load the shared library that contains addvec() */handle = dlopen("./libvector.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());exit(1);}/* get a pointer to the addvec() function we just loaded */addvec = dlsym(handle, "addvec");if ((error = dlerror()) != NULL) {fprintf(stderr, "%s\n", error); exit(1);} /* Now we can call addvec() it just like any other function */ addvec(x, y, z, 2); printf("z = [%d %d]\n", z[0], z[1]); /* unload the shared library */ if (dlclose(handle) < 0) { fprintf(stderr, "%s\n", dlerror()); exit(1); } return 0; }
5、PIC(與位置無關的代碼,Position-independent code)
共用庫的一個主要目的就是允許多個正在啟動並執行進程共用儲存空間中相同的庫代碼,因而節約儲存空間資源。
PIC:編譯庫代碼,不需要連結器修改庫代碼,就可以在任何地址載入和執行這些代碼。
在一個IA32系統中,對同一個目標模組中過程的調用不需要特殊處理的,因為引用是PC相關的,已知位移量,就是PIC了。然而,對外部已定義流程調用和對全域變數的引用通常不是PIC,因為它們都要求在連結時重定位。
如何對全域變數產生PIC引用呢?基於以下事實:無論我們在儲存空間中的何處載入一個目標模組(包括共用模組),資料區段總是分配為緊隨在程式碼片段後面。因此,程式碼片段中任何指令和資料區段中任何變數之間的距離都是一個運行時常量。
關於具體樣本,可以參見本書7.12節。
6、處理目標檔案的工具
GNU binutils包。如objdump,ar,ldd。
7、連結可以在編譯時間由靜態編譯器完成,也可以載入和運行時由動態編譯器完成。連結器處理稱為目標檔案的二進位檔案,它有三種不同的形式:可重定位的,可執行檔,和共用的。可重定位的目標檔案由靜態連結器組合成一個可執行檔目標檔案,它可以載入到儲存空間中並執行。共用目標檔案(共用庫)是在運行時由動態連結器連結和載入的,或者隱含地在調用程式被載入和開始執行時,或者根據需要在程式中調用dlopen庫的函數時。
The main tasks of linkers are symbol resolution, where each global symbol is
bound to a unique definition, and relocation, where the ultimate(最終的)memory
address for each symbol is determined and where references to those objects are
modified.
<Computer Systems:A Programmer's Perspective>