前面研究了执行文件的结构,今天主要研究一下目标文件的结构,以及在目标文件链接的过程中,链接器具体做了些什么。
// 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方法,具体实现需要依赖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进行编译链接,其原因在于IDE在编译的时候会使用很多编译选项来进行编译,导致不利于观察最终结果。
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
这个是XCode使用的编译命令,非常复杂。
好了,先看下目标文件的结构
目标文件仍然是一个macho结构的文件,所以它的内部结构其实是类似,只是少了或者多了一些项而已。现在主要跟踪一下代码段。
以上是main函数的汇编代码。
首先我们看到是内存地址是从0开始的,之前提到过执行文件的内存地址不是从0开始,它前面有一段很长的不可访问内存。汇编代码其实只看最关键的一句就可以了bl #0x20
,bl语句可以就是跳转到指定位置,但你仔细观察就会发现我调用了add(3, 4);
4次,可是每次调用的地址都不一样,而且明显是个死循环,它跳转的位置又是它自己。
这里就是关键的地方,链接的过程中需要用到重定位技术,而目标文件中常包含重定位表,关键点: 重定位表
、符号表
,字符串符号表
注: 左上角我那个苹果图标我切换到了灰色的,红色的和灰色的区别在于红色的显示的是代码在文件中的偏移地址,灰色的显示的代码被加载到内存后的地址。
符号表
符号表存储了所有符号的信息比如变量、函数等的信息都可以找到,例如我们经常会遇到undifined symbol
或者duplicate symbol
, 就是它出了问题。
_ltmp0
index of table: 这个是在字符串表中的索引位置,这里只存储序号,然后再通过查找符号字符串表获得真正的symbol string,是一种典型的时间换空间的操作
section index: 表面它处于具体那个section,换句话说就是哪个section定义的它
type: N_SECT 而且只有这一个标志,这表示它仅仅是一个section
value: file offset
综合就是它表示的是section(__TEXT,__text)的起始位置, __TEXT表示的是segment name
ltmp1
与之相似不做重述。
_main
就是我们main函数的symbol了,为什么变成_main了呢,这是因为编译器在编译的时候对其进行了修饰统一在前面加了_
,你现在明白为什么编译器报的symbol error的时候,那个symbol看起来怪怪的原因了吧。
- type: N_SECT and N_EXT 表面它是可以执行的,但不一定是函数,例如变量的话也会是这个值
- value: 0 main的入口地址是0
_add
- type: N_EXT and N_UNDF ,这个表面它同样可以执行,但是处于undifned 状态,这个符号就是需要我们重定位的
符号字符串表
这个表结构简单,可以理解为一个字符串数组,存储的是目标文件的所有符号名。
符号表和符号字符串表解析
链接器如何解析这两个表呢,需要用到LC_SYMTAB
这个命令用来加载符号表
- Symbol table Offset : 符号表的位置
- Number of Symbols: 符号数量
- String Table Offset: 字符串表位置
- String Table Size: 字符串的大小
有了这些信息就可以解析符号表和符号字符串表了。
如何重定位
先看下链接之后的执行文件样子
-
函数调用地址被修正
-
符号表被修正
重定位表不存在
看下它具体是怎么修正这些地址的,回到目标文件main.o,查看一下Section(__text) header:
每个代码段的header都对应有重定位表的信息:
- Relocation Table Offset: 可以定位到重定位表的位置
- Number Of Relocations: 该Section有多少符号是需要重定位的
查看一下重定位表的信息:
- address: 重定位的位置
- symbol: 符号, 这里的data = 2D000003, 2D不知道干什么用的,但是03表示的,该symbol为符号表中第6个符号
- type: 重定位的类型,因为寻址有绝对寻址相对寻址等,因此一个地址有多种计算方式,所以需要备注使用那种具体的方式进行重定位
所以这个重定位符号表的符号表示的意识就是:
__TEXT Segment中, __text段,有一个需要重定位的地方,地址是****,具体的符号信息存在SymbolTable[Index]中。
所以链接器只要找到对应的SymbolTable中真实的地址就完成了重定位工作。
但是问题是我main.o符号表中,_add的类型是undefined, 那我怎么搞了,所以问题就变成了链接过程中链接器是怎么修正符号表的。
首先链接器在链接的时候,会将相似的段合并比如: add.o.text 段和
mach.o.text合并成为一个段,段合并的同时,符号表也要合并,伴随着符号表符号地址的修正。
就拿add.o 文件来说 _add 符号地址是0
mian.o ,_main符号地址是0
两个合并之后肯定有一个就不是0,而且前面还提到过程序虚拟内存不是从0开始的,所以要挨个修正符号表的地址信息。这里有意思的是。
# 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
最后得到的执行文件是不一样的,那个在前面哪个的内存地址就相应靠前。
刚才只是最普通的符号修正,那么对_add符号是怎么修正的呢,add.o 中是N_EXT 类型, main.o 中是 N_UNDF,像这种情况舍弃掉main.o符号表中的信息就好啦,当然重定位表最终还是要能正确定位到正确的符号,这里具体细节不太清楚。符号冲突的有很多种,感兴趣的可以看下弱符号和强符号。
当重定位表修正了地址之后,它的意义也就没有了,所以最终会被删掉节省空间。