美文网首页程序的加载与链接
程序的加载与链接(三) 链接器所扮演的角色

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

作者: 狼性刀锋 | 来源:发表于2019-11-02 17:39 被阅读0次

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

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

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

    相关文章

      网友评论

        本文标题:程序的加载与链接(三) 链接器所扮演的角色

        本文链接:https://www.haomeiwen.com/subject/qazcbctx.html