链接framework动态库 - ShenYj/ShenYj.github.io GitHub Wiki

链接framework动态库

通过 将dylib包装成framework动态库 这篇笔记,模仿真实环境手动包装了一个 framework 动态库

接下来使用并验证,这样的操作是否是可行的

代码

  • test.m

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

    导入并使用了这个 framework的方法

脚本

  • 通过 clang 先将 test.m编译成 .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./Frameworks/TestExample.framework/Headers \
      -c test.m -o 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 \
      -F./Frameworks \
      -framework TestExample \
      test.o -o test
    
    • -F: 在指定目录寻找framework (framework search path)
    • -frame: 指定链接的framework名称 (other link flags -framework AFNetworking)
  • 目录结构

    .
    ├── Frameworks
    │   └── TextExample.framework
    │       ├── Headers
    │       │   └── TestExample.h
    │       ├── TestExample
    │       ├── TestExample.m
    │       ├── TestExample.o
    │       └── build.sh
    ├── build.sh
    └── test.m

链接

通过以上准备成功生成 test 可执行文件

Library not loaded

  • lldb 环境下运行

    ❯ lldb -file test
    (lldb) target create "test"
    Current executable set to '/Users/shenyj/framework动态库/test' (x86_64).
    (lldb) r
    Process 59818 launched: '/Users/shenyj/framework动态库/test' (x86_64)
    dyld[59818]: Library not loaded: TestExample
    Referenced from: /Users/shenyj/framework动态库/test
    Reason: tried: 'TestExample' (no such file), '/usr/local/lib/TestExample' (no such file), '/usr/lib/TestExample' (no such file), '/Users/shenyj/framework动态库/TestExample' (no such file), '/usr/local/lib/TestExample' (no such file), '/usr/lib/TestExample' (no such file)
    Process 59818 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 环境下运行报错,找不到 TestExample这个动态库

失败分析

  • 动态库加载原理

    在运行 Mach-O执行文件时
    LC_LOAD_DYLIB 这个 load command中,记录着当前 Mach-O使用的动态库的路径
    dyld 在加载 Mach-o 可执行文件文件时, dyld 通过这个路径来找动态库, 找不到了就会报错

    • 这里我反复尝试,出现的报错都是 Reason: tried: 'TestExample' (no such file),没能复现出image not found
  • 借助otool来查看test 可执行文件的 Load Command

    otool -l /Users/shenyj/framework动态库/test

    Load command 13
            cmd LC_LOAD_DYLIB
        cmdsize 40
            name TestExample (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 0.0.0
    compatibility version 0.0.0
    Load command 14
            cmd LC_LOAD_DYLIB
        cmdsize 96
            name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1856.105.0
    compatibility version 300.0.0
    Load command 15
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libobjc.A.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 228.0.0
    compatibility version 1.0.0
    Load command 16
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libSystem.B.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1311.0.0
    compatibility version 1.0.0
    Load command 17
            cmd LC_LOAD_DYLIB
        cmdsize 104
            name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1856.105.0
    compatibility version 150.0.0

    通过输出看到 5个 动态库, 后4个都是系统的,并且能清晰看到name参数对应的就是动态库的路径,指向了系统根目录
    第1个 正是我们自己的 TestExample 动态库,而 name路径不正确,导致出现了上面的报错

  • 动态库的路径记录在动态库自身当中, 通过 ID 这个关键字来筛选我们包装好的 TestExample 动态库的 Load Command可以得到如下信息

    ❯ otool -l TestExample | grep 'ID' -A 5 -B 5
    maxprot 0x00000001
    initprot 0x00000001
    nsects 0
        flags 0x0
    Load command 4
            cmd LC_ID_DYLIB
        cmdsize 40
            name TestExample (offset 24)
    time stamp 1 Thu Jan  1 08:00:01 1970
        current version 0.0.0
    compatibility version 0.0.0
    --
        extreloff 0
            nextrel 0
        locreloff 0
            nlocrel 0
    Load command 9
        cmd LC_UUID
    cmdsize 24
        uuid 751402DD-EBFE-36BD-9D78-C918909190BD
    Load command 10
        cmd LC_BUILD_VERSION
    cmdsize 32
    
  • 借助一个标准的RxSwift动态库来进行对比

    ❯ otool -l RxSwift | grep 'ID' -A 5 -B 5
    maxprot 0x00000001
    initprot 0x00000001
    nsects 0
        flags 0x0
    Load command 3
            cmd LC_ID_DYLIB
        cmdsize 64
            name @rpath/RxSwift.framework/RxSwift (offset 24)
    time stamp 1 Thu Jan  1 08:00:01 1970
        current version 1.0.0
    compatibility version 1.0.0
    --
        extreloff 0
            nextrel 0
        locreloff 0
            nlocrel 0
    Load command 7
        cmd LC_UUID
    cmdsize 24
        uuid D88635B7-BE9F-3B08-8FC4-F417EA7F54C5
    Load command 8
        cmd LC_VERSION_MIN_IPHONEOS
    cmdsize 16
    

对比一下 name 的信息

TestExample (自己的包装的动态库) RxSwift (真实项目中生成的)
name TestExample (offset 24) @rpath/RxSwift.framework/RxSwift (offset 24)

所以,当我们在手动链接生成动态库的时候,这个路径缺失了

install_name_tool

通过 install_name_tool 这个工具,可以更改动态共享库的 name 等信息, 参考 install_name_tool

修改了动态库的name后,使用之前build的脚本重新链接生成可执行文件 test

  • 再来查看 test可执行文件的 Load Command

    ❯ otool -l test | grep 'DYLIB' -A 5
            cmd LC_LOAD_DYLIB
        cmdsize 200
            name /Users/shenyj/framework动态库/Frameworks/TestExample.framework/TestExample (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 0.0.0
    compatibility version 0.0.0
    --
            cmd LC_LOAD_DYLIB
        cmdsize 96
            name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1856.105.0
    compatibility version 300.0.0
    --
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libobjc.A.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 228.0.0
    compatibility version 1.0.0
    --
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libSystem.B.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1311.0.0
    compatibility version 1.0.0
    --
            cmd LC_LOAD_DYLIB
        cmdsize 104
            name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
        current version 1856.105.0
    compatibility version 150.0.0

目前,动态库自身中的name成功写入了路径,并且重新链接后的test可执行文件中也记录了动态库的路径,并且是一一对应的

  • lldb下再次运行

    ❯ lldb -file test
    (lldb) target create "test"
    Current executable set to '/Users/shenyj/framework动态库/test' (x86_64).
    (lldb) r
    Process 77167 launched: '/Users/shenyj/framework动态库/test' (x86_64)
    2022-02-11 20:22:42.344816+0800 test[77167:3610752] TestExample----
    2022-02-11 20:22:42.345091+0800 test[77167:3610752] testApp----
    Process 77167 exited with status = 0 (0x00000000)
    (lldb)

    成功运行

整个过程当中,我们只是用了 install_name_tool 给动态库自身添加路径,原来链接生成可执行文件的脚本并没有修改,因此可以确定,最终Mach-O可执行文件中LOAD_COMMAND中的动态库路径就是动态库在链接的时候给到 Mach-O

@rpath

虽然我们链接动态库并成功运行了可执行文件,但是动态库的 path 指定的是一个绝对路径,也就代表着如果项目路径发生变化,就会再次找不到

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