[爆卦]連結器linker是什麼?優點缺點精華區懶人包

為什麼這篇連結器linker鄉民發文收入到精華區:因為在連結器linker這個討論話題中,有許多相關的文章在討論,這篇最有參考價值!作者cole945 (躂躂..)看板LinuxDev標題Re: [問題] 連結&載入器,分段...


※ 引述《mshockwave (夏克維夫)》之銘言:
: ※ 引述《gigigigi (gigigigi)》之銘言:
: : 何謂Binding
: : Def: 決定程式執行的起始位址。
: : 即:程式要在內存的哪個地方開始執行。
好奇問一下, 這個定義是哪來的?
就如 mshockwave 所說, 一般講 binding 是指 name binding.
而 binding time 就是指什麼時候決定 "name" 與他 "所指之事"
算是較 high-level 的事情, 偏軟體的問題, 雖然是跟位址有關,
但其實位址本身不是重點, 和 OS 也比較沒有直接關係,
這裡的定義不太正確, 所以後面的討論好像整個歪掉 orz

: : 可能的Binding時期有三個:
: : 1. Compiling Time
: : 2. Loading Time
: : 3. Execution Time
: : 3-1 : Dynamic Binding
: : 3-2 : Dynamic Loading
: : 鏈接器( Linker )是把不同部分的代碼和數據,收集、組合成為一個可加載、可執行的文
: : 件。
: : 加載器( Loader )把可執行文件從外存裝入內存並進行執行
補充一下, 平常講 loader, 一般應該是指 dynamic linker,
在 load time 時 resolve symbol 與 address 的東西.
相對於 static 的 linker, 是 link-time 時 resovle symble
你這邊講的 loader 是指把 executable 從 file system 放到 memory 的東西,
並不會做 symbol resolving, 功能比較單純. 一般 loader 不是指這個.
loader (dynamic linker) 在 GNU/linux 是 ld.so 或 ld-linux.so
linker 像是 GNU binutils 裡的 ld 或 gold (gold linker)
你提到的 loader 會是想 linux kernel 的 binfmt_elf 之類的東西,
http://lxr.free-electrons.com/source/fs/binfmt_elf.c
還有像 binfmt_script 是 linux 用來 load shell script 執行的東西

: : MMU : 分段 + 分頁
: : 分段 - 邏輯位址 -> 線性位址
: : 分頁 - 線性位址 -> 實體位址
: : _________________________________________________________________________________
: : 我被上面情況給搞的有點亂 , 有下面幾點疑惑
: : 1.
: : Binging 三個時期程式位址都算是虛擬位址?
: 是的 除非你玩的是沒MMU的處理器
: : Compiling Time 位址是由編譯器計算出來?
: 不算是 其實是由連結器那邊設定的
: : Loading Time 是由 加載器 計算出位址?
: : Execution Time : 位址是 Local Address + Base Register ?

承前面所說, binding 並不是在指位址上的問題

拿這一小段 C code 來說

static int foo (int a, int b) {
return a + b;
}

int bar () {
return foo (1, 2);
}

int qux () {
return bar ();
}

當講 xxx-time 做 binding, 也就是說在 xxx-time 後, 這個 binding 就不能再改變
但是可以通過重新 xxx 改變 binding.
"通常" 越早 binding 的話, 效率越好, overhead 越低, optimization 越容易介入

* compile-time
bar call foo, 靜態就能決定是上面那個 static int foo
例如這個階段可以做 inline optimization.
但若如果 foo 的內容改了, 那就只有重新編譯一途, 不然 bar 會和 foo 不一致

* link-time,
現在程式通常會分檔編譯, 如果不在同一個檔有定義,
那可能在 link-time 從其他的 .o 或 .a 繫結

* load-time
可以想成從程式 load 到 memory, 到他真的能開始跑前的時間.
例如, 程式中有用到 libc.so (standard C library) 的東西 (printf, etc),
那就是 load-time 才會決定要 call 哪個版本.
這時才決定的東西, 可以透過重新執行來改變 binding,
例如 printf 有 bug, 可以更新 libc.so 再重新執行,
或是可以透過 LD_PRELOAD 來影響 ld.so (loader) 要使用哪個 shard object
來的定義.
另外, qux call bar, 與 bar 之間的 binding, 在現在 GNU/Linux toolchain
的情況下, 若是 position indepedent code (PIC) 的 shared object
會是 load-time, 而不是 link-time.

* execution-time, 又指 run-time
是指程式正在 run 到時才能知道決定的, 不同語言的狀況很不一樣,
例如像 C++ 的 virtual function.

load-time 和 run-time 有時況狀很像, 但還是有差異.
很多 script 類的 language 可以直接呼叫 foo function,
foo funtion 根本就不存在 (例如 typo 的 bug), 但 run 到時才會跟你
抱怨找不到 foo. 若是 load-time 做 binding, 就會在一開始執行時就
說無法 resolving foo

而一般 C/C++ 的 PIC code 通常會經過 GOT/PLT 查表來執行,
但 run-time 查 vtable 的狀況 (overhead) 類似.
但能 optimize 的策略方法不同, load-time 在程式一開始就決定了不會改變,
若用基本的 JIT 就可以避開這個 overhead. 但 run-time 的 overhead
可能就要再透過 run-time profile 和 inline-cache 等方式

: 小弟不才 可能不完全正確 但其實元PO問的事情沒那麼複雜
: 用一句話回答的話就是:把一切交給虛擬位址就對了!
: 基本上會考慮到實體位址的就只有一位:核心
: 包括編譯器連結器在內 都是用虛擬位址在思考
: 而我剛剛講的 編譯完的位址 其實是由叫做linker script的東西設定的
: 這些script是ld在編譯的鏈結時期讀取的
: (script路徑可由 ld --verbose | grep SEARCH_DIR 得知)
: 決定的事情包括最重要也最基本的:執行檔的開頭要載到哪一個位址(虛擬位址)
: 也多虧了虛擬位址 每一個執行檔 檔案裡寫的開始執行位址都可以一樣
: 反正實際在記憶體中的位址是由核心分配的嘛
: linker script其實常常用在一些很hack的地方
: 例如linux kernel 會把某些符號在鏈結時期改成另外一個名字
: Mozilla B2G (Firfox OS)也利用linker script
: 把一些重要的libc符號 映射到他們自己實作的版本 說可以避免concurrency(?

以上好像有點複雜化這個問題XD

: : 2.
: : 目前Linux 是用MMU 段式 + 頁式 ?
: 這個問題蠻好玩的 因為x86大力鼓吹段式(segment) 但Linux為了跨平台著想
: 因為很多RISC家族根本沒有segment的概念 所以是採用頁式(page)

segment 還留著只是因為相容的問題, 現在算是沒在用了,
在 8086 16-bit 的年代, 利用 segment 是一個簡單有效存取超過
64K 位址的一個方法 --- address = segment + offset
但現在直接都有 32/64-bit 的 (offset) register, 所以 segment 都直接
設成 0 (例如 code/data segment). 其他 segment 也拿去做別的特殊用途,
例如拿 FS (還是GS?) 當做 thread pointer 用來加速存取 thead-local-storage

: : Linux 跟 Binding三個時期有關係嘛?
: : Binding三個時期技術是早期的技術嘛? 目前有機會使用到嘛?
: 其實我不太知道你這邊的Binding是什麼意思
: 因為小弟是搞編譯器的 第一個就想到Name Binding XDD

自我介紹一下, 小弟我之前是搞 assembler, linker, debugger 和一點點 loader
因為要寫能 run linux user space program 的 emulator,
所以對 linux 怎麼 load/mapping executable 那邊有花些時間研究,
最近幾年也是在搞 compiler XD

: : 3.
: : 鏈接器( Linker )是把不同部分的代碼和數據,收集、組合成為一個可加載、可執行的文
: : 件。
: : 我認知編譯出執行文件使用 objdump -d 就可以看到虛擬位址 , 就位址是ld Linker
: : 計算出來的嘛? 如果是它是屬於哪個Binding?
: : gcc -g test.c
: : 使用 objdump -d ./a.out
: : 08048414 <main>:
: : 8048414: 55 push %ebp
: : 8048415: 89 e5 mov %esp,%ebp
: : 8048417: 6a 03 push $0x3
: : 8048419: 6a 02 push $0x2
: : 804841b: e8 e1 ff ff ff call 8048401 <foo>
: : 8048420: 83 c4 08 add $0x8,%esp
: : 8048423: b8 00 00 00 00 mov $0x0,%eax
: : 8048428: c9 leave
: : 8048429: c3 ret
: : 804842a: 66 90 xchg %ax,%ax
: : 804842c: 66 90 xchg %ax,%ax
: : 804842e: 66 90 xchg %ax,%ax
: : 加載器( Loader )把可執行文件從外存裝入內存並進行執行 <-- 這過程有經過虛擬位址
: : 映射實體位址轉換嘛?
: 虛擬位址的映射(到實體位址)完全是執行的時候做的事喔
: : Linux 系統的加載器( Loader ) 這是位於 linux kernel 裡面?
: 是的 加載執行檔一定是作業系統的事
: ld.so的角色呢(不是編譯時期的ld)?他是負責解析動態函式庫(.so)的相關事情
: 例如幫忙resolve現在執行需要的so並加以載入
: 那那個so載入的位址呢?前面講過 每個執行檔編譯出來 開始的虛擬位址可以一樣
: 但so的虛擬位址並不是寫死的
: 其中的技術就是PIC(Position independent code) 也就是編譯so時下的 -fPIC
: 就如字面上講的 他並不是絕對位址 而是相對位址
: 因此ld.so就可以把他載到執行位址空間的任何一個地方
: 詳細的技術比較複雜一點 這邊寫不下 推薦原PO去讀 程式設計師的自我修養
: 那本書真的很珍貴 因為我竟然發現 這麼重要的技術 竟然很少原文書

那本書的內容很針對特定環境的實作, 建議還是要多 trace/survey
現在不同平台的實作, 才不會被侷限住..

另外推薦兩本書, 都是 Morgan Kaufmann 出版
想往 linker/loader 看的話, 有一本 Linkers and Loaders
作者看已的網頁有 draft 可以抓

要往 language 看的話, 可以看
Programming Language Pragmatics

: 上述回答可能有誤 請各位大大多多指教了<(_ _)>
: : 謝謝

--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 123.110.214.155
※ 文章網址: https://www.ptt.cc/bbs/LinuxDev/M.1436627615.A.07B.html
cobrasgo: 搞compiler的,我要跪著看文章了… 07/16 21:59

你可能也想看看

搜尋相關網站