动态库&@rpath

技能回顾

在上一篇文章静态库、Framework 的链接与合并我们详细的讲解了静态库是什么,链接一个静态库需要的三大要素是什么,有需要的小伙伴请前去查看。

下面我们来通过实践的方式 深入理解动态库 以及链接一个动态库都有哪些坑

动态库

首先准备环境

屏幕快照 2021-04-06 下午9.40.40.png

屏幕快照 2021-04-06 下午9.43.50.png
  • 看到上面的环境就知道我们要干嘛了。首先将A_Manager.m 编译为dylib 。然后将test.m 和dylib进行链接 生成可执行文件,

shell 脚本搞起

echo "-----开始编译test.m"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./dylib \
-c test.m -o test.o

echo "-----开始进入dylib目录"
pushd ./dylib
echo "-----开始编译A_Manager.m  为 A_Manager.o"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-c A_Manager.m -o A_Manager.o

echo "-----A_Manager.o  编译为 libA_Manager.dylib"
# -dynamiclib: 告诉clang我要编译的是动态库
clang -dynamiclib \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
 A_Manager.o -o libA_Manager.dylib

popd
echo "-----链接 libA_Manager.dylib 生成 test EXEC"

clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-L./dylib \
-lA_Manager \
test.o -o test
  • 第一步 将test.m 编译为.o 因为.m 中 引用着A_Manager.h 及方法。所以需要-I(大i)来告诉clang 头文件的路径,其他的不必多说。

  • 第二步 进入dylib路径
    1、将 A_Manger.m编译为.o
    2、将.o 编译为 dylib (这里用到了一个参数 -dynamiclib 它是告诉编译器要编译成动态库)

  • 第三方 返回test.m所在目录 并将test.o 链接 libA_manager.dylib生成 test ExEC
    这里可以看到 链接库需要-L(大L)库所在的位置 -l(小L)xxxx 库的名称

链接成功后我们运行test可执行文件

  dyldTest lldb -file test
(lldb) target create "test"
r
Current executable set to 'test' (x86_64).
(lldb) r
Process 723 launched: '/Users/liuhao/Desktop/dyldTest/test' (x86_64)
dyld: Library not loaded: libA_Manager.dylib
  Referenced from: /Users/liuhao/Desktop/dyldTest/test
  Reason: image not found
Process 723 stopped
* thread #1, stop reason = signal SIGABRT
    frame #0: 0x000000010003424a dyld`__abort_with_payload + 10
dyld`__abort_with_payload:
->  0x10003424a <+10>: jae    0x100034254               ; <+20>
    0x10003424c <+12>: movq   %rax, %rdi
    0x10003424f <+15>: jmp    0x100033aa8               ; cerror_nocancel
    0x100034254 <+20>: retq   
Target 0: (test) stopped.
(lldb) 
  • 可以看到报了一个经典的错误 image not found

动态库的本质

为了闹清楚这个错误 我们需要理解 动态库到底是个什么东西,上片文章我们有实操,如何将一个.o 直接变成一个静态库,也有说过静态库是一个.o文件的合集,而动态库是编译链接的最终产物,那也就意味着 能不能将一个.a链接生成一个动态库呢?当然是可以的,因为它是一个.o文件,.o文件是不是可以进一步生成可执行文件,或者 动态库。下面我们就来修改我们的脚本尝试着将A_Manager 编译为一个.a 文件. 再去链接生成一个动态库

上片文章我们是通过ar将一个.o生成静态库
echo "-----开始将目标文件打包为 libA_Manager.a"
ar -rc libA_Manager.a A_Manager.o

今天我们在用xcode内置的libtool 命令

libtool -static -arch_only x86_64 A_Manager.o -o libA_Manager.a
  • -static 告诉libtool 我要编译为一个静态库
  • -arch_only 指定架构
  • 后面跟上你要把哪些.o 编译为静态库

那编译为.a了那怎样将它链接生成动态库呢?
用 clang 也可以 为了让大家更好的理解 ld 链接器,下面直接给ld传递参数 生成动态库

ld -dylib -arch x86_64 \
-macosx_version_min 10.13 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-lsystem -framework Foundation \
libA_Manager.a -o libA_Manager.dylib
  • -dylib 告诉ld 是要把一个.a 或者一个.o 链接生成一个动态库。
  • -arch x86_64 指定架构
  • -macosx_version_min 告诉ld 支持的最小版本
  • -syslibroot 指定用到的系统的如Foundation 的sdk到底在哪个路径 (有点像clang的参数 -isysroot)
  • 再去告诉它需要链接哪些库 这里需要 两个系统库作为支撑
    第一个 -lsystem :这个给我们提供 dylib dyld 相关的一些链接器的函数的
    第二个 Foundation : 这里们用到了NSLog
  • 然后将libA_Manager.a 生成 libA_Manager.dylib

知道了命令下面修改脚本

echo "-----开始编译test.m"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./dylib \
-c test.m -o test.o

echo "-----开始进入dylib目录"
pushd ./dylib
echo "-----开始编译A_Manager.m  为 A_Manager.o"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-c A_Manager.m -o A_Manager.o

echo "----- 编译A_Manager.o 为 libA_Manager.a"
# Xcode->静态库
libtool -static -arch_only x86_64 A_Manager.o -o libA_Manager.a

# -dynamiclib:动态库
#echo "-----A_Manager.o  编译为 libA_Manager.dylib"
#
#clang -dynamiclib \
#-target x86_64-apple-macos10.13 \
#-fobjc-arc \
#-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
# A_Manager.o -o libA_Manager.dylib

echo "----- 通过ld 把 libA_Manager.a 链接去生成 libA_Manager.dylib"

ld -dylib -arch x86_64 \
-macosx_version_min 10.13 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-lsystem -framework Foundation \
libA_Manager.a -o libA_Manager.dylib



popd
echo "-----链接 libA_Manager.dylib 生成 test EXEC"

clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-L./dylib \
-lA_Manager \
test.o -o test

运行脚本


屏幕快照 2021-04-07 下午9.57.10.png
  • 运行发现报错了没有找到上述符号 在链接libA_Manager.dylib 生成可执行文件的时候,上述符号找不到,未定义的符号
  • 那为啥会出现这个问题呢?

我们来看一下出问题的脚本

echo "-----链接 libA_Manager.dylib 生成 test EXEC"

clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-L./dylib \
-lA_Manager \
test.o -o test

在看一眼test.m

屏幕快照 2021-04-07 下午10.02.52.png
  • 那请问对于所使用的A_Manager 还有所调用的方法 是上述脚本中 -L./dylib 的导出还是导入?想什么呢那肯定是导出符号了,那也就意味着 我们的动态库的导出符号表中并不存在我用到的符号 下面我看一下libA_Manager.dylib的导出符号表


    屏幕快照 2021-04-07 下午10.10.16.png
  • 发现空空如也啥也没有。那为啥呢?在回到上面我们修改的脚本 哪里出问题了呢?
echo "----- 通过ld 把 libA_Manager.a 链接去生成 libA_Manager.dylib"

ld -dylib -arch x86_64 \
-macosx_version_min 10.13 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-lsystem -framework Foundation \
libA_Manager.a -o libA_Manager.dylib


  • 我们将一个.a 通过链接器ld 生成一个动态库,符合我们上篇文章讲解的-noall_load ,它默认是-noall_load 此时 libA_Manager.dylib 动态库在链接 libA_Manager.a 并没有使用libA_Manager.a 中的代码,所以要给它拼接一个 -all_load 或者-Objc 都可以
屏幕快照 2021-04-07 下午10.26.54.png

接下来继续运行脚本


屏幕快照 2021-04-07 下午10.30.49.png
  • 可以看到此时链接成功 可执行文件test Mach-O 被完美的编译链接出来
  • 查看动态库的导出符号 发现也被完美的导出了。

继续运行 test 可执行文件


屏幕快照 2021-04-07 下午10.33.47.png
  • 依旧是上面最原始的问题 image not found

先总一下

  • 静态库是.o 文件的合集
  • 动态库是.o文件链接过后的产物 (所以就是说为啥我们可以将一个.a链接生成一个dylib)
  • 动态库是 最终链接产物 并不能进行合并
  • 动态库要比静态库多走一次链接的过程。它是和我们的exec 是同一级别的。

解决 image not found

LC_LOAD_DYLIB

为什么会出现这个问题,这就要从我们的dyld去加载动态来说起


DYLD加载Mach-O
  • 当dyld 去加载Mach-O的时候,通过它里面的一个Load command LC_LOAD_DYLIB 去查找它所使用的动态库。
    也就是LC_LOAD_DYLIB 保存着 上面我们的test可执行文件的用到的动态库的路径
  • 因为动态库是运行时的时候加载的
屏幕快照 2021-04-11 下午3.07.50.png

我们来看一下test的Load command 可以用otool 也可用 objdump


屏幕快照 2021-04-11 下午3.15.36.png
  • 可以清晰的看到我们自己的动态库并没有有将路径写入

解决

LC_ID_DYLIB

既然知道为啥了那就好办了,我直接给他搞一个路径不就可以了吗 ?怎么搞?其实在编译链接成动态库库文件的时候,在它自己的mach-O中存在一个command 来告诉外界,我在哪里。这个 Load command 的cmd 就是LC_ID_DYLIB,我们来看一下libA_Manager.dylib


屏幕快照 2021-04-11 下午3.37.56.png
  • 可以看到和test mach-o里libA_Manager.dylib 信息的内容一样

可通过外置命令来修改


屏幕快照 2021-04-11 下午3.42.52.png
  • 描述:改变动态库的安装目录

下面我们找到动态库所在的目录 并通过命令将其修改

屏幕快照 2021-04-11 下午3.50.17.png
  • 我们 通过 install_name_tool -id 将它的绝对路径写入
  • 这里需要注意上面的报错 原因只写了路径 没告诉它往谁身

再次查看libA_Manager.dylib的LC_ID_DYLIB


屏幕快照 2021-04-11 下午3.56.20.png
  • 可以看到被我们成功的写入了
  • 改了意味着我们需要主工程重新链接 动态库

修改脚本 屏蔽掉libA_Manager.dylib的编译及生成步骤,直接重新编译 test 链接我们用命令修改的 dylib

echo "-----开始编译test.m"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./dylib \
-c test.m -o test.o

#echo "-----开始进入dylib目录"
#pushd ./dylib
#echo "-----开始编译A_Manager.m  为 A_Manager.o"
#clang -x objective-c \
#-target x86_64-apple-macos10.13 \
#-fobjc-arc \
#-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
#-c A_Manager.m -o A_Manager.o
#
#echo "----- 编译A_Manager.o 为 libA_Manager.a"
# Xcode->静态库
#libtool -static -arch_only x86_64 A_Manager.o -o libA_Manager.a

# -dynamiclib:动态库
#echo "-----A_Manager.o  编译为 libA_Manager.dylib"
#
#clang -dynamiclib \
#-target x86_64-apple-macos10.13 \
#-fobjc-arc \
#-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
# A_Manager.o -o libA_Manager.dylib

#echo "----- 通过ld 把 libA_Manager.a 链接去生成 libA_Manager.dylib"
#
#ld -dylib -arch x86_64 \
#-macosx_version_min 10.13 \
#-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
#-lsystem -framework Foundation \
#-all_load \
#libA_Manager.a -o libA_Manager.dylib



#popd
echo "-----链接 libA_Manager.dylib 生成 test EXEC"

clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-L./dylib \
-lA_Manager \
test.o -o test

运行脚本后查看test的Load command LC_LOAD_DYLIB


屏幕快照 2021-04-11 下午4.08.25.png
  • 可以看到被我们成功的修改

运行 test

屏幕快照 2021-04-11 下午4.19.18.png

总结

动态库保存自己的位置路径,存在mach-O的 LC_ID_DYLIB 段儿。 谁在链接我的时候我把路径告诉你,并存放在谁的Mach-O的LC_LOAD_DYLIB段儿,当程序启动时候,dyld会通过 解析可执行文件的Mach-O 的 LC_LOAD_DYLIB 来找到所用到的动态库,进行加载,运行。

拓: 我们这是通过修改install_name 那能不能在创建的时候直接给它附上?当然可以 查看ld的接口


屏幕快照 2021-04-11 下午4.25.48.png

@rpath

通过上面的操作,是可以跑起来,但是细心的你应该发现了我给的是一个绝对路径,别人用我的动态库不照样找不到么?那怎么办?我们是不是需要双方约定一个规则:动态库说,你给我提供一个变量,我基于你这个变量做一个相对路径,比如说你test 所在的路径。我只给你提供一个相对于你的路径 。
test说:妥了。
这个变量就是@rpath :谁链接
我 谁来提供。

通过install_name_tool -id 来改成相对路径


屏幕快照 2021-04-11 下午4.42.12.png

查看 libA_Manager.dylib的 load command


屏幕快照 2021-04-11 下午4.44.22.png

运行脚本 test 重新链接 libA_Manager.dylib
屏幕快照 2021-04-11 下午4.46.29.png

查看 test 的load command


屏幕快照 2021-04-11 下午4.47.19.png

运行
屏幕快照 2021-04-11 下午4.48.17.png
  • ?为啥又成了image not found 了

这时候动态库站出来了:大哥这绝对不是我的锅,让你给我提供变量,你有给这变量赋值么?
test:,我看看,这。。。这不能怨我 啊 我 找 install_name_tool

查看 install_name_tool


屏幕快照 2021-04-11 下午4.58.53.png
  • 在指定的Mach-O二进制文件中添加新的rpath路径名。不止一个
    可以指定选项。如果Mach-O二进制文件已经包含新的rpath路径名
    在-add\ rpath中指定这是一个错误。
  • install_name_tool:我明明有这个功能 这能怨我?

好吧你们别争了怨作者
给test 添加一个rpath 供动态库使用。


屏幕快照 2021-04-11 下午5.05.14.png

查看test的Mach-O


屏幕快照 2021-04-11 下午5.06.56.png
  • 发现此时多了一个Load command cmd为:LC_RPATH

运行


屏幕快照 2021-04-11 下午5.09.55.png

总结:
项目报了image not found 就证明在启动的时候,dyld解析自身Mach-O LC_LOAD_DYLIB 段 来找到动态库真实所在的路径,并未找到,首先排查第三库,的路径是否引入正确,也就是在第三方动态库的 Mach-O LC_ID_DYLIB 段 存有自描述路径 @rpath。 还要排查自身是否有提供LC_RPATH

疑问:
通过上面的操作我们知道了@rpath是什么,但是你们有没有发现,它能算相对路径?对于上面的 libA_Manager.dylib 来说它是LC_ID_DYLIB是相对路径 因为它前面拼接了test为它提供的@rpath的赋值LC_RPATH 那对于test来说我们写入的LC_RPATH 依旧是绝对路径。我给test换个位置在运行,依然找不到。 为了解决这个问题系统为我提供了两个变量:@ executable_path @loader_path

@executable_path

表示可执行程序所在的目录,解析为可执行文件的绝对路径。

  • 这是什么意思 带入到我们上面的目录结构,就是代表test的目录

@loader_path

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

  • 这就代表,谁加载我谁的路径
对于test来说 @executable_path 就是它所在的路径。
对于libA_Manager.dylib 来说@loader_path 也是 test 所在的路径。因为是test来链接的。

那现在我要将test提供的LC_RPATH路径改为相对路径怎么改?这里不妨停下想一下。

有人说了我有@executable_path了那@loader_path啥时候用?
对于上面的例子我们只是可执行文件->动态库,那请问 当可执行文件->动态库1->动态库2怎么办?

你可能感兴趣的:(动态库&@rpath)