前面研究了執(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)在主要跟蹤一下代碼段。

以上是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)題。

_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)字符串表

這個(gè)表結(jié)構(gòu)簡(jiǎn)單,可以理解為一個(gè)字符串?dāng)?shù)組,存儲(chǔ)的是目標(biāo)文件的所有符號(hào)名。
符號(hào)表和符號(hào)字符串表解析
鏈接器如何解析這兩個(gè)表呢,需要用到LC_SYMTAB

這個(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)是需要重定位的
查看一下重定位表的信息:

- 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é)省空間。

