动态库原理 - ShenYj/ShenYj.github.io GitHub Wiki

动态库原理

顺着探索静态库原理的思路,来探索动态库

准备

  • test.m文件

    #import <Foundation/Foundation.h>
    #import "TestExample.h"
    
    int main(){
        NSLog(@"testApp----");
        TestExample *manager = [TestExample new];
        [manager lg_test: nil];
        return 0;
    }
  • TestExample.h

    #import <Foundation/Foundation.h>
    
    @interface TestExample : NSObject
    
    - (void)lg_test:(_Nullable id)e;
    
    @end
  • TestExample.m

    #import "TestExample.h"
    
    @implementation TestExample
    
    - (void)lg_test:(_Nullable id)e {
        NSLog(@"TestExample----");
    }
    @end
  • 脚本

    目录结构:

    ├── build.sh
    ├── dylib
    │   ├── TestExample.h
    │   └── TestExample.m
    └── test.m

复现常见报错 Library not loaded

先尝试按照探索静态库的方式处理

  1. 编译生成.dylib
  2. 编译生成.o
  3. .o链接 .dylib,生成可执行文件
  4. lldb环境运行验证
  • 脚本

    echo "编译test.m --- test.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -I./dylib \
    -c test.m -o test.o
    
    pushd ./dylib
    
    echo "编译TestExample.m --- TestExample.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -c TestExample.m -o TestExample.o
    
    echo "编译TestExample.o --- libTestExample.dylib"
    clang -dynamiclib \ # 链接成动态库
    -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    TestExample.o -o libTestExample.dylib
    
    popd
    
    echo "链接libTestExample.dylib -- test EXEC"
    
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -L./dylib \
    -lTestExample \
    test.o -o test
    • 在探究静态库时,使用手动修改后缀的方式,libtool, ar 生成静态库, 这里通过 clang 提供的参数 -dynamiclib 来将 .o链接成一个动态库
  • 脚本处理过程中并未出现任何错误,并且生成了我们预期的.o.dylib.out等文件
    但是在 lldb环境下运行环节却出现了错误

    ❯ lldb -file test
    (lldb) target create "test"
    Current executable set to '/Users/shenyj/动态库原理/test' (x86_64).
    (lldb) run
    Process 33122 launched: '/Users/shenyj/动态库原理/test' (x86_64)
    dyld[33122]: Library not loaded: libTestExample.dylib
    Referenced from: /Users/shenyj//动态库原理/test
    Reason: tried: 'libTestExample.dylib' (no such file), '/usr/local/lib/libTestExample.dylib' (no such file), '/usr/lib/libTestExample.dylib' (no such file), '/Users/shenyj/动态库原理/libTestExample.dylib' (no such file), '/usr/local/lib/libTestExample.dylib' (no such file), '/usr/lib/libTestExample.dylib' (no such file)
    Process 33122 stopped
    * thread #1, stop reason = signal SIGABRT
        frame #0: 0x00000001000560ce dyld`__abort_with_payload + 10
    dyld`__abort_with_payload:
    ->  0x1000560ce <+10>: jae    0x1000560d8               ; <+20>
        0x1000560d0 <+12>: movq   %rax, %rdi
        0x1000560d3 <+15>: jmp    0x100013120               ; cerror_nocancel
        0x1000560d8 <+20>: retq
    Target 0: (test) stopped.
    (lldb)

动态库是链接后的产物

换个思路:

  1. 先将 TestExample 通过libtool生成静态库
  2. 再通过ld链接器将其链接成动态库
  3. 再用 .o 去链接这静态库变成的动态库,生成可执行文件
  4. lldb环境执行验证
  • 修改脚本

    echo "编译test.m --- test.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -I./dylib \
    -c test.m -o test.o
    
    pushd ./dylib
    
    echo "编译TestExample.m --- TestExample.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -c TestExample.m -o TestExample.o
    
    echo "编译TestExample.o --- libTestExample.a"
    
    # 新增 >>>>>>>>>>>
    
    # Xcode (默认使用的就是 libtool 来编译静态库) -> 静态库
    libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a
    
    echo "编译TestExample.m --- libTestExample.dylib"
    ld -dylib -arch x86_64 \
    -macosx_version_min 12.1 \
    -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -lsystem -framework Foundation \ # -lsystem: 需要系统库支撑: dylib dyld,-framework Foundation: 链接Foundation 这个动态库
    libTestExample.a -o libTestExample.dylib
    
    # 新增 <<<<<<<<<<<
    popd
    
    echo "链接libTestExample.dylib -- test EXEC"
    
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -L./dylib \
    -lTestExample \
    test.o -o test
    • ar更多的是查看相关的功能,默认Xcode是用libtool来创建静态库的,所以脚本中使用了libtool先创建一个静态库
    • ld, 对比开头的脚本,这里没有使用clang创建动态库,而是直接用了链接器

因-noall_load引起的链接失败

  • 执行脚本后报错:

    编译TestExample.m --- TestExample.o
    编译TestExample.o --- libTestExample.a
    编译TestExample.m --- libTestExample.dylib
    ~/xxxxxx链接libTestExample.dylib -- test EXEC
    Undefined symbols for architecture x86_64:
    "_OBJC_CLASS_$_TestExample", referenced from:
        objc-class-ref in test.o
    ld: symbol(s) not found for architecture x86_64

    在链接动态库生成可执行文件的时候失败, _OBJC_CLASS_$_TestExample 这个符号找不到

    推理出,动态库的导出符号表中不包含此符号

    借助 objdump --macho -exports-trie 来验证一下

    ❯ objdump --macho -exports-trie /Users/s/动态库原理/dylib/libTestExample.dylib
    /Users/s//动态库原理/dylib/libTestExample.dylib:
    Exports trie:
    

    发现导出符号表的确是空的,这是因为 Xcode 默认链接静态库时使用的是 noall_load,将其剥离了,所以在上述脚本中添加配置,将其改为-all_load,将符号保留

成功将静态库链接成动态库

  • 添加 -all_load 后的脚本

    echo "编译test.m --- test.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -I./dylib \
    -c test.m -o test.o
    
    pushd ./dylib
    
    echo "编译TestExample.m --- TestExample.o"
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -c TestExample.m -o TestExample.o
    
    echo "编译TestExample.o --- libTestExample.a"
    
    # Xcode (默认使用的就是 libtool 来编译静态库) -> 静态库
    libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a
    
    echo "编译TestExample.m --- libTestExample.dylib"
    ld -dylib -arch x86_64 \
    -macosx_version_min 12.1 \
    -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -lsystem -framework Foundation \
    # 新增 >>>>>>>>>>>
    -all_load \
    # 新增 <<<<<<<<<<<
    libTestExample.a -o libTestExample.dylib
    
    popd
    
    echo "链接libTestExample.dylib -- test EXEC"
    
    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -L./dylib \
    -lTestExample \
    test.o -o test
  • lldb环境下运行可执行文件

    Process 37789 launched: '/Users/shenyj/动态库原理/test' (x86_64)
    dyld[37789]: Library not loaded: libTestExample.dylib
    Referenced from: /Users/shenyj/动态库原理/test
    Reason: tried: 'libTestExample.dylib' (no such file), '/usr/local/lib/libTestExample.dylib' (no such file), '/usr/lib/libTestExample.dylib' (no such file), '/Users/shenyj/动态库原理/libTestExample.dylib' (no such file), '/usr/local/lib/libTestExample.dylib' (no such file), '/usr/lib/libTestExample.dylib' (no such file)
    Process 37789 stopped
    * thread #1, stop reason = signal SIGABRT
        frame #0: 0x00000001000560ce dyld`__abort_with_payload + 10
    dyld`__abort_with_payload:
    ->  0x1000560ce <+10>: jae    0x1000560d8               ; <+20>
        0x1000560d0 <+12>: movq   %rax, %rdi
        0x1000560d3 <+15>: jmp    0x100013120               ; cerror_nocancel
        0x1000560d8 <+20>: retq
    Target 0: (test) stopped.
    (lldb)

    运行结果仍然是失败的 Library not loaded

虽然仍然没能成功在 lldb 环境下运行起来,但目前已经可以得出结论

当使用 -L-l链接一个库的时候,只需要知道符号所在的位置,并不需要源码,本质上是标记了导出符号所在的位置

  • 动态库: 在运行时,由 dyld 动态加载, 在查找符号真实地址的时候找不到了
  • 静态库: 链接的时候代码、符号表已经被合并在一起

通过探索静态库得知,其是目标文件的集合,而动态库和可执行文件一样,是链接后的产物,所以才能将一个静态库链接成动态库
动态库是链接最终产物,无法与 .o 进行合并的

动态库路径

以上操作最终之所以没能成功在 lldb 环境下运行起来,是因为动态库中缺少路径,而链接生成可执行文件后,同样缺少了路径,导致 dyld加载时找不到动态库

具体过程的探索与分析: framework动态库 - 链接 - Library not loaded -- 失败分析

并借助 install_name_tool 这个工具为动态库添加真实路径, 重新链接动态库生成可执行文件后,便可成功在 lldb 下运行

链接framework动态库 这种先生成 framework,再通过 install_name_tool 设置路径的的方式多了那么一个环节,并且设置的是一个绝对路径, 在设置路径时, 可以尝试以下方案

另外我们完全可以在链接的时候就为动态库指定路径 链接动态库时指定路径

结论

因为动态库不像静态库一样被合并到 Mach-O
那么动态库就会自己记录其所存在的路径,并在链接的时候,将自己的路径告诉 Mach-O,记录在 Mach-OLOAD_COMMAND中,这样 dylb在加载的时候才能找到动态库

⚠️ **GitHub.com Fallback** ⚠️