动态库&@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怎么办?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 什么事动态库?与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运...
    Jax_YD阅读 1,164评论 0 6
  • 什么是库,使用库有哪些好处?库就是将代码编译成一个二进制文件,再加头文件。常见的库文件格式有.a .dylib ...
    崔希羽阅读 999评论 0 1
  • 什么是动态库? 与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序中只会存储指向动态库的引⽤。等到程序...
    帅驼驼阅读 664评论 0 5
  • iOS 开发进阶 文章汇总[https://www.jianshu.com/p/c40b31400816] 目录 ...
    differ_iOSER阅读 2,948评论 0 18
  • 弱引用动态库 弱引用动态库:标记-weak_framework参数,允许在运行时不链接该动态库 正常情况下,运行时...
    帅驼驼阅读 648评论 0 5