Re: [問題] 連結&載入器,分段分頁,Binding關係

看板LinuxDev作者 (躂躂..)時間9年前 (2015/07/11 23:13), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串3/3 (看更多)
※ 引述《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

07/16 21:59, , 1F
搞compiler的,我要跪著看文章了…
07/16 21:59, 1F
文章代碼(AID): #1LeJAV1x (LinuxDev)
文章代碼(AID): #1LeJAV1x (LinuxDev)