动态库&@rpath

2021-04-11  本文已影响0人  猿人

技能回顾

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

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

动态库

首先准备环境

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

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可执行文件

  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) 

动态库的本质

为了闹清楚这个错误 我们需要理解 动态库到底是个什么东西,上片文章我们有实操,如何将一个.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

那编译为.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

知道了命令下面修改脚本

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

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

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
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


屏幕快照 2021-04-07 下午10.26.54.png

接下来继续运行脚本


屏幕快照 2021-04-07 下午10.30.49.png

继续运行 test 可执行文件


屏幕快照 2021-04-07 下午10.33.47.png

先总一下

解决 image not found

LC_LOAD_DYLIB

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


DYLD加载Mach-O 屏幕快照 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

可通过外置命令来修改


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

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

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

再次查看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

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

查看 install_name_tool


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

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


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

查看test的Mach-O


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

运行


屏幕快照 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

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

@loader_path

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

对于test来说 @executable_path 就是它所在的路径。
对于libA_Manager.dylib 来说@loader_path 也是 test 所在的路径。因为是test来链接的。

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

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

上一篇 下一篇

猜你喜欢

热点阅读