程序的加載與鏈接(三) 鏈接器所扮演的角色

前面研究了執(zhí)行文件的結(jié)構(gòu),今天主要研究一下目標(biāo)文件的結(jié)構(gòu),以及在目標(biāo)文件鏈接的過(guò)程中,鏈接器具體做了些什么。

// main.c
int add(int,int);
int main(int argc, const char * argv[]) {
    
    add(3, 4);
    add(3, 4);
    add(3, 4);
    add(3, 4);
    
    return 0;
}

// add.c 

int add(int a,int b) {
    return a + b;
}

  • main.c 只是申明了add方法,具體實(shí)現(xiàn)需要依賴(lài)add.c文件

使用命令行工具編譯和鏈接一下

# 編譯
xcrun -sdk iphoneos clang -c main.c add.c  -target arm64-apple-ios12.2

# 鏈接
xcrun -sdk iphoneos clang main.o add.o -o main -target arm64-apple-ios12.2

這里放棄使用xcode進(jìn)行編譯鏈接,其原因在于IDE在編譯的時(shí)候會(huì)使用很多編譯選項(xiàng)來(lái)進(jìn)行編譯,導(dǎo)致不利于觀(guān)察最終結(jié)果。

CompileC /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/Objects-normal/x86_64/main.o /Users/litengfang/Desktop/helloWorld/helloWorld/main.c normal x86_64 c com.apple.compilers.llvm.clang.1_0.compiler (in target: helloWorld)
    cd /Users/litengfang/Desktop/helloWorld
    export LANG=en_US.US-ASCII
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x c -arch x86_64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu11 -fmodules -gmodules -fmodules-cache-path=/Users/litengfang/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -fbuild-session-file=/Users/litengfang/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -fmodules-validate-once-per-build-session -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Werror=deprecated-objc-isa-usage -Werror=objc-root-class -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -DDEBUG=1 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk -fasm-blocks -fstrict-aliasing -Wdeprecated-declarations -mmacosx-version-min=10.14 -g -Wno-sign-conversion -Winfinite-recursion -Wcomma -Wblock-capture-autoreleasing -Wstrict-prototypes -Wno-semicolon-before-method-body -Wunguarded-availability -index-store-path /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Index/DataStore -iquote /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/helloWorld-generated-files.hmap -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/helloWorld-own-target-headers.hmap -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/helloWorld-all-target-headers.hmap -iquote /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/helloWorld-project-headers.hmap -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Products/Debug/include -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/DerivedSources-normal/x86_64 -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/DerivedSources/x86_64 -I/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/DerivedSources -F/Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Products/Debug -MMD -MT dependencies -MF /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/Objects-normal/x86_64/main.d --serialize-diagnostics /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/Objects-normal/x86_64/main.dia -c /Users/litengfang/Desktop/helloWorld/helloWorld/main.c -o /Users/litengfang/Library/Developer/Xcode/DerivedData/helloWorld-dscpwsatfpebbehkomoduqtxyrly/Build/Intermediates.noindex/helloWorld.build/Debug/helloWorld.build/Objects-normal/x86_64/main.o

這個(gè)是XCode使用的編譯命令,非常復(fù)雜。

好了,先看下目標(biāo)文件的結(jié)構(gòu)


目標(biāo)文件仍然是一個(gè)macho結(jié)構(gòu)的文件,所以它的內(nèi)部結(jié)構(gòu)其實(shí)是類(lèi)似,只是少了或者多了一些項(xiàng)而已?,F(xiàn)在主要跟蹤一下代碼段。

屏幕快照 2019-11-02 下午3.28.16.png

以上是main函數(shù)的匯編代碼。

首先我們看到是內(nèi)存地址是從0開(kāi)始的,之前提到過(guò)執(zhí)行文件的內(nèi)存地址不是從0開(kāi)始,它前面有一段很長(zhǎng)的不可訪(fǎng)問(wèn)內(nèi)存。匯編代碼其實(shí)只看最關(guān)鍵的一句就可以了bl #0x20,bl語(yǔ)句可以就是跳轉(zhuǎn)到指定位置,但你仔細(xì)觀(guān)察就會(huì)發(fā)現(xiàn)我調(diào)用了add(3, 4); 4次,可是每次調(diào)用的地址都不一樣,而且明顯是個(gè)死循環(huán),它跳轉(zhuǎn)的位置又是它自己。

這里就是關(guān)鍵的地方,鏈接的過(guò)程中需要用到重定位技術(shù),而目標(biāo)文件中常包含重定位表,關(guān)鍵點(diǎn): 重定位表、符號(hào)表,字符串符號(hào)表

: 左上角我那個(gè)蘋(píng)果圖標(biāo)我切換到了灰色的,紅色的和灰色的區(qū)別在于紅色的顯示的是代碼在文件中的偏移地址,灰色的顯示的代碼被加載到內(nèi)存后的地址。

符號(hào)表

符號(hào)表存儲(chǔ)了所有符號(hào)的信息比如變量、函數(shù)等的信息都可以找到,例如我們經(jīng)常會(huì)遇到undifined symbol或者duplicate symbol, 就是它出了問(wèn)題。

屏幕快照 2019-11-02 下午3.47.51.png

_ltmp0

index of table: 這個(gè)是在字符串表中的索引位置,這里只存儲(chǔ)序號(hào),然后再通過(guò)查找符號(hào)字符串表獲得真正的symbol string,是一種典型的時(shí)間換空間的操作
section index: 表面它處于具體那個(gè)section,換句話(huà)說(shuō)就是哪個(gè)section定義的它
type: N_SECT 而且只有這一個(gè)標(biāo)志,這表示它僅僅是一個(gè)section
value: file offset

綜合就是它表示的是section(__TEXT,__text)的起始位置, __TEXT表示的是segment name

ltmp1與之相似不做重述。

_main

就是我們main函數(shù)的symbol了,為什么變成_main了呢,這是因?yàn)榫幾g器在編譯的時(shí)候?qū)ζ溥M(jìn)行了修飾統(tǒng)一在前面加了_,你現(xiàn)在明白為什么編譯器報(bào)的symbol error的時(shí)候,那個(gè)symbol看起來(lái)怪怪的原因了吧。

  • type: N_SECT and N_EXT 表面它是可以執(zhí)行的,但不一定是函數(shù),例如變量的話(huà)也會(huì)是這個(gè)值
  • value: 0 main的入口地址是0

_add

  • type: N_EXT and N_UNDF ,這個(gè)表面它同樣可以執(zhí)行,但是處于undifned 狀態(tài),這個(gè)符號(hào)就是需要我們重定位的

符號(hào)字符串表

屏幕快照 2019-11-02 下午4.35.56.png

這個(gè)表結(jié)構(gòu)簡(jiǎn)單,可以理解為一個(gè)字符串?dāng)?shù)組,存儲(chǔ)的是目標(biāo)文件的所有符號(hào)名。

符號(hào)表和符號(hào)字符串表解析

鏈接器如何解析這兩個(gè)表呢,需要用到LC_SYMTAB

屏幕快照 2019-11-02 下午4.40.01.png

這個(gè)命令用來(lái)加載符號(hào)表

  • Symbol table Offset : 符號(hào)表的位置
  • Number of Symbols: 符號(hào)數(shù)量
  • String Table Offset: 字符串表位置
  • String Table Size: 字符串的大小

有了這些信息就可以解析符號(hào)表和符號(hào)字符串表了。

如何重定位

先看下鏈接之后的執(zhí)行文件樣子

  • 函數(shù)調(diào)用地址被修正


    屏幕快照 2019-11-02 下午4.50.19.png
  • 符號(hào)表被修正


    屏幕快照 2019-11-02 下午4.50.30.png
  • 重定位表不存在

看下它具體是怎么修正這些地址的,回到目標(biāo)文件main.o,查看一下Section(__text) header:


每個(gè)代碼段的header都對(duì)應(yīng)有重定位表的信息:

  • Relocation Table Offset: 可以定位到重定位表的位置
  • Number Of Relocations: 該Section有多少符號(hào)是需要重定位的

查看一下重定位表的信息:


屏幕快照 2019-11-02 下午5.05.08.png
  • address: 重定位的位置
  • symbol: 符號(hào), 這里的data = 2D000003, 2D不知道干什么用的,但是03表示的,該symbol為符號(hào)表中第6個(gè)符號(hào)
  • type: 重定位的類(lèi)型,因?yàn)閷ぶ酚薪^對(duì)尋址相對(duì)尋址等,因此一個(gè)地址有多種計(jì)算方式,所以需要備注使用那種具體的方式進(jìn)行重定位

所以這個(gè)重定位符號(hào)表的符號(hào)表示的意識(shí)就是:
__TEXT Segment中, __text段,有一個(gè)需要重定位的地方,地址是****,具體的符號(hào)信息存在SymbolTable[Index]中。

所以鏈接器只要找到對(duì)應(yīng)的SymbolTable中真實(shí)的地址就完成了重定位工作。

但是問(wèn)題是我main.o符號(hào)表中,_add的類(lèi)型是undefined, 那我怎么搞了,所以問(wèn)題就變成了鏈接過(guò)程中鏈接器是怎么修正符號(hào)表的。

首先鏈接器在鏈接的時(shí)候,會(huì)將相似的段合并比如: add.o.text 段和
mach.o.text合并成為一個(gè)段,段合并的同時(shí),符號(hào)表也要合并,伴隨著符號(hào)表符號(hào)地址的修正。
就拿add.o 文件來(lái)說(shuō) _add 符號(hào)地址是0
mian.o ,_main符號(hào)地址是0
兩個(gè)合并之后肯定有一個(gè)就不是0,而且前面還提到過(guò)程序虛擬內(nèi)存不是從0開(kāi)始的,所以要挨個(gè)修正符號(hào)表的地址信息。這里有意思的是。

# add.o 在前
 xcrun -sdk iphoneos clang add.o main.o -o ab -target arm64-apple-ios12.2

# main.o 在前
 xcrun -sdk iphoneos clang main.o add.o  -o ab -target arm64-apple-ios12.2

最后得到的執(zhí)行文件是不一樣的,那個(gè)在前面哪個(gè)的內(nèi)存地址就相應(yīng)靠前。

剛才只是最普通的符號(hào)修正,那么對(duì)_add符號(hào)是怎么修正的呢,add.o 中是N_EXT 類(lèi)型, main.o 中是 N_UNDF,像這種情況舍棄掉main.o符號(hào)表中的信息就好啦,當(dāng)然重定位表最終還是要能正確定位到正確的符號(hào),這里具體細(xì)節(jié)不太清楚。符號(hào)沖突的有很多種,感興趣的可以看下弱符號(hào)和強(qiáng)符號(hào)。

當(dāng)重定位表修正了地址之后,它的意義也就沒(méi)有了,所以最終會(huì)被刪掉節(jié)省空間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容