@loader_path - ShenYj/ShenYj.github.io GitHub Wiki

@loader_path

loader_path: 表示被加载的 Mach-O 所在的目录,每次加载时,都可能被设置为不同的路径,由上层指定

在此之后继续探索 @loader_path

实际场景为例: 我们的App链接了一个动态库,动态库中链接了另外一个动态库

最终生成的 test 可执行文件链接了 TestExample 这个动态库,TestExample 中又链接了 TestExampleLog 这个动态库

  • 目录结构

    .
    ├── Frameworks
    │   └── TestExample.framework
    │       ├── Frameworks
    │       │   └── TestExampleLog.framework
    │       │       ├── Headers
    │       │       │   └── TestExampleLog.h
    │       │       ├── TestExampleLog
    │       │       ├── TestExampleLog.m
    │       │       └── buildTestExampleLog.sh
    │       ├── Headers
    │       │   └── TestExample.h
    │       ├── TestExample.m
    │       └── buildTestExample.sh
    ├── build.sh
    └── test.m
  • 代码

    test、TestExample、TestExampleLog
    • test.m

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

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

      #import "TestExample.h"
      #import "TestExampleLog.h"
      
      @implementation TestExample
      
      - (void)test:(_Nullable id)e {
          NSLog(@"TestExample----");
          TestExampleLog *log = [TestExampleLog new];
          [log test_example_log: self];
      }
      @end
    • TestExampleLog.h

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

      #import "TestExampleLog.h"
      
      @implementation TestExampleLog
      
      - (void)test_example_log:(_Nullable id)e {
          NSLog(@"TestExampleLog---%@", e);
      }
      @end
      

脚本

这里采取的链接器 Xlinker -install_name 的时候直接设置路径,免去了生成后在借助install_name_tool修改的环节

  • 脚本中重点关注 -Xlinker -install_name 的不同设置, 尤其是 buildTestExample.sh
  • buildTestExampleLog.sh

    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -I./Headers \
    -c TestExampleLog.m -o TestExampleLog.o
    
    clang -dynamiclib  \
    -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -Xlinker -install_name -Xlinker @rpath/TestExampleLog.framework/TestExampleLog \
    TestExampleLog.o -o TestExampleLog
    
    otool -l TestExampleLog | grep 'ID' -A 5
    buildTestExampleLog.sh 编译结果
    ❯ ./buildTestExampleLog.sh
            cmd LC_ID_DYLIB
        cmdsize 72
            name @rpath/TestExampleLog.framework/TestExampleLog (offset 24)
    time stamp 1 Thu Jan  1 08:00:01 1970
        current version 0.0.0
    compatibility version 0.0.0
    --
        cmd LC_UUID
    cmdsize 24
        uuid 948B94AA-6EB1-33A3-9BCE-D4F13B5A107D
    Load command 10
        cmd LC_BUILD_VERSION
    cmdsize 32
  • buildTestExample.sh

    clang -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -I./Headers \
    -I./Frameworks/TestExampleLog.framework/Headers \
    -c TestExample.m -o TestExample.o
    
    clang -dynamiclib  \
    -target x86_64-apple-macos12.1 \
    -fobjc-arc \
    -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
    -Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample \
    -Xlinker -rpath -Xlinker @loader_path/Frameworks \
    -Xlinker -reexport_framework -Xlinker TestExampleLog \
    -F./Frameworks \
    -framework TestExampleLog \
    TestExample.o -o TestExample
    
    echo "-------DYLIB---------"
    otool -l TestExample | grep 'DYLIB' -A 5
    echo "-------ID---------"
    otool -l TestExample | grep 'ID' -A 5
    1. -Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample
      • 给当前 TestExample 动态库配置 rpath
    2. -Xlinker -rpath -Xlinker @loader_path/Frameworks
      • 这一行是为了保证在 buildTestExample链接TestExampleLog时能够找到正确的路径, 为TestExampleLog补全前面的路径部分
      • 若此时使用 @executable_path 又要拼接中间这个动态库的路径
        可执行程序与TestExampleLog 之间隔着一个 TestExample动态库, 拼接起来不方便
        当然使用 @executable_path/Frameworks/TestExample.framework/Frameworks 也是没有问题的
      • 通过 @loader_path 就能很好的解决这个问题, 谁来链接 TestExampleLog,路径就代表谁
        • 在这个示例中 TestExample 链接了 TestExampleLog, 那么 @loader_path 就能代表 TestExample 所在的路径, 即 .../Frameworks/TestExample.framework
    3. -Xlinker -reexport_framework -Xlinker TestExampleLog 这里保证了可执行程序中能够直接调用 TestExampleLog 的方法
      • TestExample动态库中将TestExampleLog 重新导出放入 LC_REEXPORT_DYLIB,保证能够在可执行文件中直接调用到 TestExampleLog api
        • 可执行文件 --> LC_REEXPORT_DYLIB --> TestExampleLog 的符号
    buildTestExample.sh 编译结果
    ❯ ./buildTestExample.sh
    -------DYLIB---------
            cmd LC_ID_DYLIB
        cmdsize 72
            name @rpath/TestExample.framework/TestExample (offset 24)
    time stamp 1 Thu Jan  1 08:00:01 1970
        current version 0.0.0
    compatibility version 0.0.0
    --
            cmd LC_REEXPORT_DYLIB
        cmdsize 72
            name @rpath/TestExampleLog.framework/TestExampleLog (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
    -------ID---------
            cmd LC_ID_DYLIB
        cmdsize 72
            name @rpath/TestExample.framework/TestExample (offset 24)
    time stamp 1 Thu Jan  1 08:00:01 1970
        current version 0.0.0
    compatibility version 0.0.0
    --
        cmd LC_UUID
    cmdsize 24
        uuid 5F06CDBF-5B3C-3B62-ADFA-1C8EE11B6010
    Load command 10
        cmd LC_BUILD_VERSION
    cmdsize 32
    
  • build.sh

    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 \
    -I./Frameworks/TestExample.framework/Frameworks/TestExampleLog.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 \
    -Xlinker -rpath -Xlinker @executable_path/Frameworks \
    -F./Frameworks \
    -framework TestExample \
    test.o -o test
    
    otool -l test | grep 'RPATH' -A 3
    otool -l test | grep 'DYLIB' -A 3
    build.sh 编译结果
    ❯ ./build.sh
            cmd LC_RPATH
        cmdsize 40
            path @executable_path/Frameworks (offset 12)
    Load command 19
            cmd LC_LOAD_DYLIB
        cmdsize 72
            name @rpath/TestExample.framework/TestExample (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
    --
            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
    --
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libobjc.A.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
    --
            cmd LC_LOAD_DYLIB
        cmdsize 56
            name /usr/lib/libSystem.B.dylib (offset 24)
    time stamp 2 Thu Jan  1 08:00:02 1970
    --
            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
  • 最后通过 lldb 环境运行,在可执行文件中成功调用了两个动态库的方法

    ❯ lldb -file test
    (lldb) target create "test"
    Current executable set to '/Users/shenyj/Documents/CodeForTest/lib/loader_path/test' (x86_64).
    (lldb) r
    Process 5186 launched: '/Users/shenyj/Documents/CodeForTest/lib/loader_path/test' (x86_64)
    2022-02-12 01:02:47.507558+0800 test[5186:3840218] testApp----
    2022-02-12 01:02:47.507807+0800 test[5186:3840218] TestExample----
    2022-02-12 01:02:47.507899+0800 test[5186:3840218] TestExampleLog---<TestExample: 0x600000010000>
    2022-02-12 01:02:47.507927+0800 test[5186:3840218] testApp----<TestExampleLog: 0x600000010010>
    Process 5186 exited with status = 0 (0x00000000)
⚠️ **GitHub.com Fallback** ⚠️