iOS高級進階系列之-庫(中)動態庫探索

系列文章:OC底層原理系列OC基礎知識系列Swift底層探索系列iOS高級進階系列shell

補充

以前的文章iOS高級進階系列之-庫(上)靜態庫探索在後面講到了Dead_Strip,也說道了-all_load-ObjC以及-force_load,這篇文章讓你們以爲-noall_load-all_load-ObjC以及-force_load能夠控制Dead_Strip,其實不是這樣的markdown

  • Dead_Strip在Xcode中是Build Settings 查找Dead

image.png

經過上面能夠看出來這個Dead_Strip-noall_load-all_load-ObjC以及-force_load沒有太大關係-noall_load、-all_load、-ObjC以及-force_load只是在鏈接靜態庫的時候起做用,而Dead_Strip只是給開發者提供了一種優化代碼的方式架構

代碼驗證

image.png image.png

上圖是test.m的代碼,下圖是文件,經過腳本將test.m編譯成可執行文件。前面的文章介紹過:先將test.m編譯成靜態庫test.o文件,再將TestExample.m編譯成靜態庫TestExample.o,最後經過將test.o鏈接TestExample.o生成可執行文件。咱們直接執行腳本函數

當生成可執行文件後,查看macho信息 image.png 咱們能夠發現這個裏面沒有任何關於TestExample內容,下面咱們對腳本進行改進 image.pngpost

腳本默認的爲-noall_load,它不會將沒有用過的代碼放入到靜態庫優化

改完腳本再次執行腳本 image.pngui

發現多了不少東西,並且未使用的TestExample也導入到項目中spa

咱們看下dead_strip的定義 image.png3d

大意就是刪除沒有被入口點或者導出符號使用過的代碼。咱們再改一下腳本code

image.png 運行下腳本,看下變化 image.png

這裏看的是符號表,咱們看下以前代碼

image.png

咱們發現global_function全局導出符號,可是未被使用,可是在導出的符號表中並沒有global_function,這就是-dead_strip做用

下面咱們調用下global_function方法 image.png 在執行腳本,讀取符號表 image.png

發現global_function在符號表

總結Dead_Strip

  • 1.沒有被入口點使用就會被幹掉
  • 2.沒有被導出符號使用的也會被幹掉

-all_load

下面咱們將TestExample代碼放開,在執行腳本 image.png 再來看下符號表 image.png

發現都在符號表裏

下面該下腳本和代碼 image.png image.png 問題:此時符號表仍是否有TestExample內容 image.png

發現仍是存在的,緣由是OC代碼動態運行的代碼,因此不敢幹掉權限不夠

擴展

shell命令有個能夠查看某個代碼爲何存在的指令:-why_live image.png

查看global_function爲何會存在

image.png

解釋爲何global_function符號存在,它被從test.o來的,它被main函數使用,main函數又從test.o來

上面講完後對Dead_Strip和-noall_load、-all_load、-ObjC以及-force_load沒有太大關係有了更好的理解了吧

動態庫

首先咱們來把test.m和AFNetworking動態庫進行鏈接 image.png

下圖爲test.m代碼,咱們引入了AFN,並初始化AFHTTPSessionManager image.png

  • 1.先將test.m編譯成目標文件

image.png

靜態庫中介紹過命令的用法,這裏再也不說明

image.png

  • 2.鏈接AFN動態庫

image.png image.png

鏈接動態庫鏈接靜態庫使用的指令都是同樣

  • 3.運行test可執行文件

image.png

當咱們運行,當即發生了錯誤

那究竟爲何會出錯了?咱們下面探究一下

動態庫原理

準備以前的代碼 image.png image.png

  • 1.爲了節省時間,寫一個腳本去編譯可執行文件

image.png

紅框的-dynamiclib就是告訴編譯器我下面要執行的是編譯動態庫

  • 2.執行腳本
    • 執行腳本報下面錯誤 image.png
    • 執行chmod +x ./build.sh權限

再次運行腳本 image.png image.png

  • 3.運行可執行文件

image.png 咱們發現仍是報和上面相同的錯誤,爲何呢,是否是腳本命令是否是有問題?

分析原理

靜態庫的文章已經說過靜態庫.o的合集(文章直接演示將.o文件直接改爲靜態庫能夠運行),可是動態庫是一個連接編譯最終產物。這也就意味着靜態庫能夠經過連接變成動態庫

靜態庫連接編譯爲動態庫

  • 1.修改腳本,將修改部分貼出來

image.png

  • 2.跑腳本

image.png

報錯了,就是在連接的過程當中找不到_TestExample導出符號

  • 3.咱們查找一下動態庫的符號表

image.png

發現導出符號表空的,什麼都沒有

  • 4.爲何是空的呢?咱們看下腳本

image.png

前面介紹編譯期鏈接的時候默認執行的是-noall_load,由於動態庫沒有使用.a內容,因此不加載。那麼是否是這個緣由形成的呢?咱們改下腳本

  • 5.添加-all_load

image.png

  • 6.再次運行腳本

image.png

發現符號表有內容

  • 7.再次跑可執行文件

image.png

仍是熟悉的味道,是否是有點無語,問題究竟出在哪裏呢?下面就來介紹動態庫的另外一個特性

總結

  • 1.靜態庫是.o文件的合集
  • 2.動態庫是.o文件連接事後的產物
  • 3.靜態庫能夠經過連接生成動態庫
  • 4.動態庫也就是最終產物,這也是動態庫沒法合併緣由

動態庫解析

下面咱們連連接下動態庫,咱們下面來說解LoginApp怎麼去連接動態庫SYCSSColor動態庫 image.png 咱們再看看SYCSSColor裏面的內容 image.png

tbd格式

image.png

這個是連接路徑

  • SYCSSColor.tbd文件直接拖到項目中,不在xcconfig中寫編譯連接,看看會發生什麼
    image.png image.png image.png

經過上面咱們看到了,咱們引入了我SYCSSColor頭文件,使用了代碼而且編譯成功

咱們只是將SYCSSColor.tbd文件放入項目中,那麼什麼是tbd格式呢?

  • 1.tbd全稱是text-based stub libraries,本質上就是一個YAML描述的文本文件
  • 2.他的做用是用於記錄動態庫的一些信息,包括導出的符號動態庫的架構信息動態庫的依賴信息
  • 3.用於避免真機開發過程當中直接使用傳統的dylib
  • 4.對於真機來講,因爲動態庫都是在設備上,在Xcode上使用基於tbd格式僞framework能夠大大減小Xcode的大小

分析tbd格式

咱們查看下SYCSSColor.tbd的內容, image.png

  • 1.第9行exports導出的意思
  • 2.第11行symbols是符號的意思,也就意味着11-14行都是導出符號
  • 3.objc-classes是objc類的集合

經過上面咱們能夠知道咱們用腳本去連接庫的是用到-L,-l連接是符號,也就是咱們只須要知道符號所在的位置不須要知道源碼位置。上面編譯經過了,下面就運行看看 image.png 上面看到直接崩潰,爲何呢?這是由於動態庫是動態連接的,在運行的時候,由dyld動態加載動態庫,在加載的時候,它回去找這個符號真實地址的時候,找不到了,而靜態庫編譯的時候就已將符號歸放在一塊兒,不會動態加載

tbd怎麼生成

Xcode的Build Settings搜索text image.png

原理:就是經過拼上一些參數,來掃描Headers裏面的頭文件,而後把這個符號寫到文件裏去

查找Framework實際位置

image.png image.png

  • 1.下面咱們使用腳本編譯,先看framework文件中的腳本

image.png

  • 2.運行腳本

image.png

  • 3.咱們看到動態庫是.dyld的形式,其實是沒有什麼標識的,下面咱們吧生成的刪除,重新生成一份叫TestExample

image.png image.png

  • 4.此時咱們的Frameworks準備好了,下面就是將test.m和咱們的Frameworks進行連接

image.png

  • 5.運行腳本

image.png

這就說明咱們前面製做的.framework成功

  • 6.運行驗證可執行文件

image.png 仍是相同錯誤,爲何還會有這樣的錯誤,這就要從dyld加載動態庫提及。以下圖: image.png

  • 1.這裏的Mach-o至關於咱們的可執行文件Test
  • 2.Test的Mach-o有個專門的LC_LOAD_DYLIB,它裏面有咱們須要用到的動態庫路徑`
  • 3.當它找不到這個路徑的時候就會報錯

下面咱們看下路徑究竟是什麼東西 image.png 咱們發現咱們Test中一共使用了5個動態庫,那麼使用哪5個呢? image.png

-A 5 意思就是找到動態庫後多現實5行

咱們看到咱們使用的動態庫中name字段就是該動態庫路徑,可是咱們導入的動態庫TestExample路徑只有一個名字,別的什麼都沒有,這樣確定找不到TestExample。 image.png 下面咱們就來解決這個問題

動態庫路徑

動態庫有一個專門的地方保存本身的路徑,也就是說動態庫的路徑是保存在本身的Mach-o中的,下面咱們就來查看下這個路徑 image.png

-A是向下展現向上展現時-B

咱們發現這個地方的路徑就沒有給對,我要給對正確的路徑,外部有個命令來更改路徑名稱:install_name_tool image.png image.png

咱們看到install_name_tool給的解釋就是改變更態庫install name

下面咱們就使用一下,來改變路徑 image.png

發現報錯,這是由於咱們並沒有告訴編譯期往哪裏加這個路徑

image.png

發現此時的路徑已經更改了,那咱們再來執行一次build.sh腳本,從新生成test文件,在查看下引入的動態庫 image.png

咱們發現此時的路徑已經改變了,那麼此時咱們再運行可執行文件

image.png

此時並無報錯,成功運行了起來。也說明咱們此時將test.m成功的鏈接了TestExample動態庫

優化

腳本優化

上面咱們講了是生成動態庫後去更改路徑的,那麼能夠在生成前進行更改呢?答案是固然能夠,咱們在腳本中更改,咱們使用命令-install_name,先看下install_name解釋 image.png

就是添加路徑

image.png 【問題】上面咱們看到給到的是一個絕對路徑,絕對路徑有一個很大的問題,當咱們作好SDK後給別人使用,那麼這個路徑就會改變,別人就須要更改路徑,走着沒法調用成功

路徑優化

上面的問題怎麼解決,這就須要雙方約定好規則,什麼規則,看下圖: image.png

A使用B,B提供一個TestExample路徑(Frameworks/TestExample.framework/TestExample),而A給B提供一個使用者路徑(也就是test的路徑)而後B給拼接起來,此時路徑不久完整了嘛!

@rpath

這就牽扯到@rpath@rpath:Runpath search Paths! dyld搜索路徑。運行時@rpath指示dyld按順序搜索路徑列表,以找到動態庫@rpath保存一個或多個路徑的變量! 也就是誰鏈接我誰給我提供@rpath,下面咱們直接修改路徑 image.png

此時咱們看到路徑已經修改完了

下面就是須要test給TestExample提供@rpath了,怎麼提供,可執行文件test的mach-o中存一個@rpath,在鏈接的時候,將其提供給TestExample。 image.png

此時咱們在test可執行文件中搜索rpath,發現不存在,說明此時若是咱們去連接TestExample是找不到位置的,此時須要咱們手動添加

咱們查找一下命令 image.png

上面是替換rpath,下面的是添加rpath到指定的Mach-o

image.png

這時候就完成了,此時咱們從新運行可執行文件

image.png

成功了

路徑再優化

咱們上面看到咱們打印的path,依然是一個絕對路徑,那麼此時有麼有什麼辦法,改成相對路徑呢?這裏系統給咱們提供了兩個方法:

  • 1.@executable_path:表示可執行程序所在的目錄,解析爲可執行文件的絕對路徑。
  • 2.@loader_path:表示被加載的Mach-O所在的目錄,每次加載時,均可能被設置爲不一樣的路徑,由上層指定。

@executable_path

也就是說若是用了@executable_path,無論在誰的電腦上它都指向執行程序所在的路徑 下面咱們來執行如下,由於咱們已經設置過了rpath,因此須要替換,上面已經鎖了替換方法,來進行操做 image.png 此時咱們再看下test的rpath路徑 image.png 已經將絕對路徑修改爲@executable_path,運行一下test文件,驗證一下 image.png 咱們看到運行成功,說明咱們的修改是正確的

@loader_path

咱們看下這種狀況,test中使用的framework中包含TestExample.framework,而TestExample.framework的framework中包含TestExampleLog.framework image.png 下面咱們編譯一遍,從後往前編譯

  • 1.先編譯TestExampleLog.framework

image.png

  • 2.編譯TestExample.framework

image.png image.png

咱們發現連接的TestExampleLog.framework的name是不正確的,並且自身的name一樣不正確

  • 3.編譯test

image.png 此時編譯完成,但此時運行時報錯的,由於路徑不對,下面咱們來改一下

  • 4.改動TestExampleLog.framework中的腳本

image.png

加入name,地址使用rpath,再運行腳本

image.png 此時的路徑就對了

  • 5.改動TestExample.framework的腳本

image.png 運行腳本 image.png

  • 6.改動test的腳本

test是提供rpath路徑image.png 運行腳本 image.png

  • 7.運行可執行文件

image.png

咱們發現報錯了,由於找不到TestExampleLog的路徑,也就是咱們在TestExample.framework的腳本中配了本身的路徑,可是並無配TestExampleLog的路徑

  • 8.修改TestExample.framework的腳本(截取部分)

image.png 運行腳本 image.png 下面咱們手動看一下 image.png

咱們看到這個路徑比較長,下面咱們再來生成下test,若是上面寫的都是對的,那就能運行起來

image.png

咱們看到這樣運行時運行起來了,因此說咱們以前配置的路徑是對的

可是有個問題,咱們上面的路徑太長了,下面就用到了@loader_path,上面講了那麼多,一方面爲了給你們屬性下腳本,第二是讓你們更好的理解。

  • 9.再次改腳本

image.png 在運行腳本,執行可執行文件 image.png

查看cocoaPod

爲了加深@executable_path印象,咱們能夠看看cocoaPods的xcconfig文件 image.png

此時的name是一個名稱

當咱們編譯代碼,看下可執行文件的包內容,咱們發現動態庫都會存放在framework文件中,也就是上面設置的文件裏 image.png

總結

到此最難的連接動態庫的一種方式已經說完了,連接動態庫因爲動態加載,因此必定要指定好路徑,否咋就會報錯。

  • 1.咱們介紹了經過install_name_tool更改路徑
  • 2.爲了優化路徑,咱們引入@rpath
  • 3.-rpath lod new 是替換路徑-add_rpath是將路徑添加到指定位置
  • 4.爲了再優化,咱們又引入了@executable_path@loader_path

上面講了能夠替換路徑,那麼就想到了破解,由於咱們能夠改變路徑,將咱們本身寫的動態庫做爲目標軟件的依賴達到破解的目的。咱們這麼作事改變了Mach-o,以前講過Mach-o只有簽名後纔會被蘋果系統認可,因此再破解軟件是都會調用一句命令:code sign --force --deep --sign - 這句命令也就是強制打一個簽名

實際應用

當咱們作動態庫時,能夠在Xcode中設置咱們上面說的命令 image.png 咱們在搜下rpath image.png 咱們上面的路徑設置均可以在Xcode去處理

擴展

咱們上面說的test是能夠使用TestExample的方法,那麼他test能夠使用TestExampleLog的代碼嗎? 咱們看下導出符號 image.png 咱們看到這裏面沒有TestExampleLog的導出符號,因此不能使用,那麼怎麼纔可以使用呢?在以前在講符號的時候,說過從新導出符號,下面說下命令:reexport image.png

從新導出framework

image.png

從新導出TestExampleLog,運行腳本

image.png

多了一個從新導出符號TestExampleLog,此時就可使用TestExampleLog代碼

  • 驗證,咱們將test.m代碼作以下改動,若運行打印沒有問題,那就說明咱們以前的是對的

image.png

  • 運行腳本,運行可執行文件

image.png 咱們更改的代碼以及打印都能正常運行,驗證了咱們上面改的東西

寫到最後

動態庫寫的內容比較多,寫了一週多時間!寫了將近6000字,寫的比較細,都是把本身探索過程記錄下來,但願你們可以按着文章去實操一遍,這部分也比較無聊,可是這是高階必走的路,在過程當中學會shell語言,會寫腳本,這部分都是基礎,後面會介紹項目高階應用:靜態庫合併,動態庫優化等內容。有什麼疑問能夠在下面留言,也但願你們多多交流,點贊!

相關文章
相關標籤/搜索