程序的加载与链接(三) 链接器所扮演的角色

前面研究了执行文件的结构,今天主要研究一下目标文件的结构,以及在目标文件链接的过程中,链接器具体做了些什么。

// 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结构的文件,所以它的内部结构其实是类似,只是少了或者多了一些项而已。现在主要跟踪一下代码段。

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

以上是main函数的汇编代码。

首先我们看到是内存地址是从0开始的,之前提到过执行文件的内存地址不是从0开始,它前面有一段很长的不可访问内存。汇编代码其实只看最关键的一句就可以了bl #0x20,bl语句可以就是跳转到指定位置,但你仔细观察就会发现我调用了add(3, 4); 4次,可是每次调用的地址都不一样,而且明显是个死循环,它跳转的位置又是它自己。

这里就是关键的地方,链接的过程中需要用到重定位技术,而目标文件中常包含重定位表,关键点: 重定位表符号表,字符串符号表

: 左上角我那个苹果图标我切换到了灰色的,红色的和灰色的区别在于红色的显示的是代码在文件中的偏移地址,灰色的显示的代码被加载到内存后的地址。

符号表

符号表存储了所有符号的信息比如变量、函数等的信息都可以找到,例如我们经常会遇到undifined symbol或者duplicate symbol, 就是它出了问题。

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

_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 状态,这个符号就是需要我们重定位的

符号字符串表

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

这个表结构简单,可以理解为一个字符串数组,存储的是目标文件的所有符号名。

符号表和符号字符串表解析

链接器如何解析这两个表呢,需要用到LC_SYMTAB

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

这个命令用来加载符号表

  • Symbol table Offset : 符号表的位置
  • Number of Symbols: 符号数量
  • String Table Offset: 字符串表位置
  • String Table Size: 字符串的大小

有了这些信息就可以解析符号表和符号字符串表了。

如何重定位

先看下链接之后的执行文件样子

  • 函数调用地址被修正


    屏幕快照 2019-11-02 下午4.50.19.png
  • 符号表被修正


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

看下它具体是怎么修正这些地址的,回到目标文件main.o,查看一下Section(__text) header:


每个代码段的header都对应有重定位表的信息:

  • Relocation Table Offset: 可以定位到重定位表的位置
  • Number Of Relocations: 该Section有多少符号是需要重定位的

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


屏幕快照 2019-11-02 下午5.05.08.png
  • 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符号表中的信息就好啦,当然重定位表最终还是要能正确定位到正确的符号,这里具体细节不太清楚。符号冲突的有很多种,感兴趣的可以看下弱符号和强符号。

当重定位表修正了地址之后,它的意义也就没有了,所以最终会被删掉节省空间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容