動態庫&@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閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容