# 動態連結器 ## 簡易運作流程觀察 以下為一個在認識動態連結器的功用前,讓我們觀察動態連結器其中一個功能的例子: ```C= #include <stddef.h> #include <string.h> #include <stdio.h> #include <dlfcn.h> #include <unistd.h> void *malloc(size_t size) { char buf[32]; static void *(*real_malloc)(size_t) = NULL; if (real_malloc == NULL) { real_malloc = dlsym(RTLD_NEXT, "malloc"); } sprintf(buf, "malloc called, size = %zu\n", size); write(2, buf, strlen(buf)); return real_malloc(size); } ``` 透過以下指令編譯程式 ``` gcc -D_GNU_SOURCE -shared -ldl -fPIC -o /tmp/libmcount.so malloc_count.c ``` 並搭配環境變數執行 `ls` 指令後 ``` LD_PRELOAD=/tmp/libmcount.so ls ``` 可以發現 `ls` 會呼叫我們定義的 `malloc()` * 透過設定 `LD_PRELOAD` 環境變數,glibc 的 dynamic linker (`ld.so`) 會在載入和重定位 (relocation) `libc.so` 之前,載入我們撰寫的 `/tmp/libmcount.so` 動態連結函式庫,如此一來,我們實作的 malloc 就會在 `libc.so` 提供的 malloc 函式之前被載入。 以上實驗可以讓我們對於程式被連結的流程有以下概念: * 程式呼叫的函式有可能不是在經過編譯與連結後就確定的,也有可能會在執行時才被連結。 ## 相關背景知識 ### Compilation Units 為了易於開發與管理專案,會將許多程式分開撰寫 (將不同功能的程式放置於不同 .h 與 .c 檔)。這些不同的 .c 與 .h 檔便是一個 compilation unit。 * 這樣做對開發與管理上來說是好事,但容易使編譯器無法偵測到一些潛在能做最佳化的程式。 * 當將許多從不同 compilation units 產生的 .o 檔在透過 linker 連結產生執行檔前,是無法確認其他 compilation unit 的資訊的 (像是不知道用 extern 宣告的變數實際上是什麼) ## 動機 當我們要做 relocation 的 symbols 很多的時候,在開始執行 main 函式之前會需要耗費大量時間去完成所有 symbols 的 relocation。因此為了減少程式開始執行所需的時間,會透過 lazy binding 的方式使程式在需要的時候才對 symbol 做 relocation。 ## 運作原理 透過 procedure linkage table (PLT) 與 global offset table (GOT) 達到 lazy binding。 ### Procedure Linkage Table (PLT) & Global Offset Table (GOT) 以下為 PLT 與 GOT 在動態連結中解析需動態連結的 symbols 的位址的過程: 1. 編譯時程式碼中各個不確定的 symbols 的位址會填入 0。 2. 多個 compilation units 做 linking 時,部分位址才會被解析 * 靜態連結: 其他 compilation units 中能看見的 symbols 的位址會填到指令 (在 .code section 裡) 中。 * 動態連結: 要動態載入的 symbols 會新增一個 PLT 表格欄位 (同樣存放於 read only 的 .code section 中),欄位中存放著 GOT 的位址 (GOT 存放在可修改的 .data section 中)。 * GOT 的初始值為幫忙查找 symbol 實際位址的動態連結器的位址。 * 動態連結器找到的位址會更新到 PLT 指向的 GOT。 3. 程式執行時,當第一次用到需要動態連結的 symbol 時,會先透過該 symbol 對應的 PLT 去跳到存放初始值的 GOT,也就是先執行解析 symbol 實際位址的動態連結器查詢 symbol 對應的程式碼的存放位址,將查詢結果更新到 GOT 後再去執行 symbol 實際上對應到的程式。 ## 相關應用場景與工具 ### Link Time Optimization (LTO) 為了能讓編譯器有辦法對多個 compilation units 做最佳化,因此產生了在連結階段做最佳化的概念與技術。 * 這是一個簡便,且能不更動程式碼,維持程式執行結果的優化方式。 #### LTO 的功能 LTO 能做到的事包含但不僅限以下事項: * 縮減程式大小,使程式在記憶體佔據的空間較少。 * 應用場景: 當硬體資源 (記憶體大小) 很少 * 實作方式: 折疊函式 (function folding),將兩個很類似的函式濃縮 (兩份類似的程式碼變為一份),把不同的部分獨立出來 * 透過展開函式 (function inlining) 最佳化執行效率 ### Symbol Visibility 有時候會想控制 symbol 的可視範圍,像是只在沒有其他 compilation units 定義特定 symbol 時才使用自己的定義,便能使用 gcc 的 visibility 屬性 (`__attribute__((visibility()))`) 與 `-fvisibility` 編譯參數來做到這件事情。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up