iOS 基于二進(jìn)制重排的啟動(dòng)優(yōu)化

參考鏈接: 抖音研發(fā)實(shí)踐:基于二進(jìn)制文件重排的解決方案 APP啟動(dòng)速度提升超15%

一、原理

1痪伦、虛擬內(nèi)存和物理內(nèi)存

早期計(jì)算機(jī)沒(méi)有虛擬地址,一旦加載都會(huì)全部加載到內(nèi)存中,而且進(jìn)程都是按順序排列的谦屑,這樣別的進(jìn)程只需要把自己的地址加一些就能訪問(wèn)到別的進(jìn)程這樣就很不安全。

現(xiàn)在軟件發(fā)展的比硬件快篇梭,軟件占用的內(nèi)存越來(lái)越大氢橙,這就導(dǎo)致計(jì)算機(jī)的內(nèi)存不夠用,當(dāng)開(kāi)啟多個(gè)軟件時(shí)候恬偷,如果內(nèi)存不夠用就只能等待悍手,只有等前面的軟件關(guān)掉后才能加載打開(kāi),這就是早期計(jì)算機(jī)有時(shí)候?yàn)樯吨挥邪亚懊娴能浖P(guān)掉才能打開(kāi)新軟件的原因袍患。用戶使用軟件時(shí)候并不是使用到全部?jī)?nèi)存坦康,只會(huì)使用到一部分,如果軟件一打開(kāi)就把軟件全部加載到內(nèi)存中诡延,這樣會(huì)很浪費(fèi)內(nèi)存空間滞欠。

基于上面原因虛擬內(nèi)存技術(shù)出現(xiàn)了,軟件打開(kāi)后肆良,軟件自己以為有一大片內(nèi)存空間筛璧,但實(shí)際上是虛擬的,而虛擬內(nèi)存和物理內(nèi)存是通過(guò)一張表來(lái)關(guān)聯(lián)的惹恃,可以看下下面兩張表:


image.png

進(jìn)程1運(yùn)行時(shí)候會(huì)開(kāi)辟一塊內(nèi)存空間夭谤,但訪問(wèn)到內(nèi)存條的時(shí)候并不是這塊內(nèi)存空間,而且通過(guò)訪問(wèn)地址通過(guò)進(jìn)程1的映射表映射到不同的物理內(nèi)存空間巫糙,這個(gè)叫地址翻譯朗儒,這個(gè)過(guò)程需要CPU和操作系統(tǒng)配合,因?yàn)檫@個(gè)映射表是操作系統(tǒng)來(lái)管理的参淹,當(dāng)我們調(diào)試時(shí)候發(fā)現(xiàn)訪問(wèn)數(shù)據(jù)的內(nèi)存地址都是連續(xù)的醉锄,其實(shí)這是一個(gè)假象,在這個(gè)進(jìn)程內(nèi)部可以訪問(wèn)浙值,是因?yàn)樵L問(wèn)時(shí)候會(huì)通過(guò)該進(jìn)程的內(nèi)存映射表去拿到真正的物理內(nèi)存地址恳不,假如其他進(jìn)程訪問(wèn)的話,其他進(jìn)程沒(méi)有相應(yīng)的映射表亥鸠,自然就訪問(wèn)不到真正的物理內(nèi)存地址妆够,這樣就解決了內(nèi)存安全問(wèn)題识啦。

內(nèi)存使用率問(wèn)題:
內(nèi)存分頁(yè)管理,映射表不能以字節(jié)為單位神妹,是以頁(yè)為單位颓哮,Linux是以4K為一頁(yè),iOS是以16K位一頁(yè)鸵荠,但是mac系統(tǒng)是4K一頁(yè)冕茅,可以在mac終端輸入pageSize,發(fā)現(xiàn)返回的是4096蛹找。
為啥分頁(yè)后內(nèi)存就夠用呢姨伤,因?yàn)閼?yīng)用內(nèi)存是虛擬的,所以當(dāng)程序啟動(dòng)時(shí)候程序會(huì)認(rèn)為自己有很多的內(nèi)存:

image.png

在應(yīng)用加載時(shí)候不會(huì)把所有數(shù)據(jù)放內(nèi)存中庸疾,因?yàn)閿?shù)據(jù)是懶加載乍楚,當(dāng)進(jìn)程訪問(wèn)虛擬地址時(shí)候,首先看頁(yè)表届慈,如果發(fā)現(xiàn)該頁(yè)表數(shù)據(jù)為0徒溪,說(shuō)明該頁(yè)面數(shù)據(jù)未在物理地址上,這個(gè)時(shí)候系統(tǒng)會(huì)阻塞該進(jìn)程金顿,這個(gè)行為就叫做頁(yè)中斷(page Fault)臊泌,也叫缺頁(yè)異常,然后將磁盤(pán)中對(duì)應(yīng)頁(yè)面的數(shù)據(jù)加載到內(nèi)存中揍拆,然后讓虛擬內(nèi)存指向剛加載的物理內(nèi)存渠概,將數(shù)據(jù)加載到內(nèi)存中時(shí)候,如果有空的內(nèi)存空間嫂拴,就放空的內(nèi)存空間中播揪,如果沒(méi)有的話,就會(huì)去覆蓋其他進(jìn)程的數(shù)據(jù)顷牌,具體怎么覆蓋操作系統(tǒng)有一個(gè)算法剪芍,這樣永遠(yuǎn)都會(huì)保證當(dāng)前進(jìn)程的使用塞淹,這就是靈活管理內(nèi)存窟蓝。

但是這時(shí)候有個(gè)問(wèn)題,虛擬內(nèi)存解決了安全和效率問(wèn)題饱普,但是出現(xiàn)了另個(gè)安全問(wèn)題运挫,因?yàn)樘摂M內(nèi)存在編譯鏈接時(shí)候就確定了,那么黑客很容易通過(guò)分析拿到對(duì)應(yīng)的虛擬內(nèi)存去操作 套耕,這樣就造成所有的代碼都很好hook谁帕,代碼注入,這個(gè)時(shí)候就出現(xiàn)了新技術(shù)ASLR(Address space layout randomization 地址空間隨機(jī)化)冯袍,就是進(jìn)程每次加載的時(shí)候都會(huì)給一個(gè)隨機(jī)的偏移量匈挖,這樣就保證每次加載進(jìn)程時(shí)候虛擬內(nèi)存也在變化碾牌,iOS從iOS4就開(kāi)始了。

二進(jìn)制重拍:
因?yàn)樘摂M內(nèi)存中有個(gè)很大問(wèn)題就是缺頁(yè)中斷儡循,這個(gè)操作很耗時(shí)間舶吗,并且iOS不僅僅是將數(shù)據(jù)加載到內(nèi)存,還要對(duì)這頁(yè)做簽名認(rèn)證择膝,所以iOS耗時(shí)比較長(zhǎng)誓琼,并且每頁(yè)耗時(shí)有很大差距,0.1ms到0.8毫秒肴捉,使用過(guò)程中可能時(shí)間段感覺(jué)不到腹侣,但是啟動(dòng)時(shí)候會(huì)有很多數(shù)據(jù)要加載,這樣就會(huì)導(dǎo)致耗時(shí)很長(zhǎng)齿穗,假如我們啟動(dòng)時(shí)候在不同頁(yè)面傲隶,因?yàn)榇a在machO的位置不是根據(jù)調(diào)用瞬間,而是通過(guò)文件編譯的位置來(lái)的窃页,有可能啟動(dòng)時(shí)候在運(yùn)行時(shí)候會(huì)調(diào)用很多次page Fault伦籍,那么如果把所有啟動(dòng)時(shí)候的代碼都放在一頁(yè)或者兩頁(yè),這樣就很大程度上優(yōu)化啟動(dòng)速度腮出,這種方法就叫做二進(jìn)制重拍帖鸦。

進(jìn)程如果能直接訪問(wèn)物理內(nèi)存無(wú)疑是很不安全的,所以操作系統(tǒng)在物理內(nèi)存的上又建立了一層虛擬內(nèi)存胚嘲。為了提高效率和方便管理作儿,又對(duì)虛擬內(nèi)存和物理內(nèi)存又進(jìn)行分頁(yè)(Page)。當(dāng)進(jìn)程訪問(wèn)一個(gè)虛擬內(nèi)存Page而對(duì)應(yīng)的物理內(nèi)存卻不存在時(shí)馋劈,會(huì)觸發(fā)一次缺頁(yè)中斷(Page Fault)攻锰,分配物理內(nèi)存,有需要的話會(huì)從磁盤(pán)mmap讀人數(shù)據(jù)妓雾。
通過(guò)App Store渠道分發(fā)的App娶吞,Page Fault還會(huì)進(jìn)行簽名驗(yàn)證,所以一次Page Fault的耗時(shí)比想象的要多械姻!

image.png

編譯器在生成二進(jìn)制代碼的時(shí)候妒蛇,默認(rèn)按照鏈接的Object File(.o)順序?qū)懳募凑誒bject File內(nèi)部的函數(shù)順序?qū)懞瘮?shù)楷拳。
靜態(tài)庫(kù)文件.a就是一組.o文件的ar包绣夺,可以用ar -t查看.a包含的所有.o。

默認(rèn)布局:

image.png

簡(jiǎn)化問(wèn)題:假設(shè)我們只有兩個(gè)page:page1/page2欢揖,其中綠色的method1和method3啟動(dòng)時(shí)候需要調(diào)用陶耍,為了執(zhí)行對(duì)應(yīng)的代碼,系統(tǒng)必須進(jìn)行兩個(gè)Page Fault她混。
但如果我們把method1和method3排布到一起烈钞,那么只需要一個(gè)Page Fault即可泊碑,這就是二進(jìn)制文件重排的核心原理。

image.png

重排之后毯欣,我們的經(jīng)驗(yàn)是優(yōu)化一個(gè)Page Fault蛾狗,啟動(dòng)速度提升0.6~0.8ms

二仪媒、實(shí)現(xiàn)

1沉桌、System Trace調(diào)試

首先優(yōu)化,要先學(xué)會(huì)調(diào)試算吩,只有調(diào)試才能發(fā)現(xiàn)需要優(yōu)化的地方留凭,我們知道內(nèi)存分虛擬內(nèi)存和物理內(nèi)存,而內(nèi)存是通過(guò)分頁(yè)管理的偎巢,當(dāng)我們啟動(dòng)的時(shí)候調(diào)用很多方法蔼夜,假如這些方法不在同一個(gè)page上面,就會(huì)造成缺頁(yè)中斷(page fault),而這個(gè)操作是要消耗時(shí)間的压昼,所以假如啟動(dòng)的方法都在一頁(yè)上面求冷,就會(huì)很大程度上減少啟動(dòng)時(shí)間的消耗,這個(gè)就需要用到二進(jìn)制重拍來(lái)將啟動(dòng)時(shí)候調(diào)用的方法放在同一個(gè)page上

  • 首先我們打開(kāi)項(xiàng)目command + i打開(kāi)Instruments調(diào)試工具

    image.png

  • 選擇System Trace窍霞,這個(gè)軟件可以看到我們項(xiàng)目中每個(gè)線程的數(shù)據(jù):


    image.png
  • 點(diǎn)擊開(kāi)始后這里我們搜索Main thread匠题,選擇我們的app,然后點(diǎn)擊Main thread ,再到下面選擇Main Thread --> Virtual Memory(虛擬內(nèi)存)


    image.png
  • 這里面File Backed Page In就是page fault的次數(shù)但金。

  • 當(dāng)我們把APP殺死后里面再啟動(dòng)韭山,結(jié)果發(fā)現(xiàn)File Backed Page In這個(gè)值變得很小,說(shuō)明APP就算殺死后冷溃,在啟動(dòng)不是冷啟動(dòng)钱磅,還是有一部數(shù)據(jù)在系統(tǒng)的緩存中。

  • 要做到真正的冷啟動(dòng)似枕,我們可以把APP殺掉后啟動(dòng)多個(gè)手機(jī)里面的APP盖淡,然后再啟動(dòng)APP,發(fā)現(xiàn)File Backed Page In又變得很大凿歼。

  • 說(shuō)明虛擬內(nèi)存是在系統(tǒng)中的褪迟,當(dāng)系統(tǒng)內(nèi)存不夠的時(shí)候,其他APP會(huì)覆蓋老的APP的虛擬內(nèi)存毅往。

  • 二進(jìn)制重拍是在鏈接階段生成的牵咙,重排之后生成可執(zhí)行文件,所以我們只能在編譯階段來(lái)優(yōu)化攀唯,而無(wú)法對(duì)已生成的ipa進(jìn)行優(yōu)化。

2渴丸、二進(jìn)制重排

可以在XCode配置二進(jìn)制重拍侯嘀,首先要確定符號(hào)的順序另凌,才能知道怎么重拍。XCode使用的鏈接器叫做ld戒幔,ld有個(gè)參數(shù)叫order_file吠谢,只要有這個(gè)文件,我們可以將文件的路徑告訴XCode诗茎,在order_file文件中把符號(hào)的順序?qū)戇M(jìn)去工坊,XCode編譯的時(shí)候就會(huì)按照文件中的符號(hào)順序打包成二進(jìn)制可執(zhí)行文件。

  • 在蘋(píng)果的objc4-750源碼中找到這種文件


    image.png
  • 打開(kāi)后是下面這種格式:


    image.png
  • 里面全是函數(shù)符號(hào)敢订,打開(kāi)項(xiàng)目王污,在build setting 里面搜索order file


    image.png
  • 這里面指定了order的文件路徑,因?yàn)橐坏┰谶@里指定了order file的路徑楚午,XCode就會(huì)在編譯的時(shí)候按照文件里面寫(xiě)進(jìn)去的順序昭齐。
    現(xiàn)在寫(xiě)一個(gè)Demo,然后編譯矾柜,我們知道XCode編譯的時(shí)候文件會(huì)有一個(gè)鏈接阱驾,鏈接是按照Build Phases的Compile SourceL里面的文件順序?qū)?m文件轉(zhuǎn)換成.o文件,然后將這些.o文件鏈接在一起生成可執(zhí)行文件:


    image.png
  • 做一個(gè)實(shí)驗(yàn)怪蔑,在ViewController和AppDelegate里面都寫(xiě)一個(gè)load方法里覆,然后運(yùn)行

+(void)load
{
   NSLog(@"ViewController");
}
+(void)load
{
   NSLog(@"AppDelegate");
}
  • Build Phases的Compile Source順序:


    image.png
  • 運(yùn)行,看下打永掳辍:


    image.png
  • 把Compile Source順序改一下:


    image.png
  • 運(yùn)行后看打印結(jié)果:


    image.png
  • 打印順序跟Compile Source文件順序一樣租谈,驗(yàn)證了上面的結(jié)論

  • 如何查看整個(gè)項(xiàng)目的符號(hào)順序呢,到Build Settings搜索link map

    image.png

  • Link Map就是我們鏈接的符號(hào)表捆愁,我們把它改成YES割去,這樣編譯的時(shí)候就會(huì)把鏈接的符號(hào)表給我們寫(xiě)出來(lái),command + R我們運(yùn)行下昼丑,然后在Products里面的.app文件呻逆,在我們Intermediates.noindex-->項(xiàng)目名.build--->Debug-iphoneos-->項(xiàng)目名.build--->項(xiàng)目名-LinkMap-normal-arm64.txt,這個(gè)文件里面就有鏈接的符號(hào)順序表

    image.png

  • 其中 Object files:就是鏈接了哪些.o文件

Sections:中
Address:
Size:
Segment:__TEXT代碼代碼段菩帝,只可讀咖城;__DATA是數(shù)據(jù)段,可讀可寫(xiě)
Section:

再下面就是我們關(guān)心的符號(hào):
Symbols:
Address:方法代碼的地址
Size:方法占用的空間
File:文件的編號(hào)
Name:.o文件里面的方法符號(hào)
對(duì)于Address呼奢,我們從.app中拿到項(xiàng)目的可執(zhí)行文件宜雀,然后用MachOView打開(kāi),然后在Section中看下Assembly

image.png

  • 符號(hào)表里的0x100004B70在MachOView對(duì)應(yīng)的value是匯編代碼握础,也就是我們寫(xiě)的代碼轉(zhuǎn)換成的匯編辐董,所以這個(gè)地址就是代碼地址,所以二進(jìn)制重拍就是把所有的代碼順序重新排一下禀综,把啟動(dòng)時(shí)候調(diào)用的代碼排到前面去简烘,減少啟動(dòng)時(shí)候加載page的數(shù)量(沒(méi)一個(gè)page大小是16K)

  • 添加order file,我們創(chuàng)建一個(gè)hank.order文件苔严,在文件中寫(xiě)入:


    image.png
  • 放到工程的根目錄中,然后在Build setting里面搜下order file孤澎,然后在后面將該文件地址添加進(jìn)去:


    image.png
  • Xcode在編譯時(shí)候就會(huì)按照order文件中的符號(hào)順序鏈接代碼了届氢,我們編譯一下,再看一下LinkMap-normal-arm64.txt文件


    image.png
  • 結(jié)果是按照order的符號(hào)順序來(lái)的覆旭,而且如果order里面寫(xiě)了項(xiàng)目中不存在的方法符號(hào)退子,XCode會(huì)自動(dòng)過(guò)濾掉,不存在影響型将。還有一種查看符號(hào)表的方法是在終端cd到項(xiàng)目可執(zhí)行文件的目錄寂祥,然后輸入。

nm 可執(zhí)行文件名
image.png

查看全部的符號(hào)茶敏,還有查看自定義方法的符號(hào)

nm -Up TraceDemo
image.png

查看系統(tǒng)的符號(hào)

nm -up TraceDemo

3壤靶、獲取APP啟動(dòng)時(shí)候調(diào)用的所有方法

以上就是二進(jìn)制重拍的步驟,但是如何知道APP啟動(dòng)時(shí)候的調(diào)用了哪些方法呢惊搏?

  • 第一個(gè)方式:是用fishHook去hook 系統(tǒng)的objc_msgSend這個(gè)函數(shù)贮乳,因?yàn)閛c的方法都是通過(guò)發(fā)送消息的形式,但是這個(gè)函數(shù)參數(shù)是可變的參數(shù)恬惯,所以只能通過(guò)匯編形式hook向拆,但是這種情況initialize和block以及直接調(diào)用函數(shù)方式hook不到。

  • 第二種方式:clang插裝形式: 官方文檔:clang

OC方法酪耳、函數(shù)浓恳、block都能hook到!

1碗暗、首先在Build Setting里面搜索Other C Flags 在里面添加參數(shù):-fsanitize-coverage=trace-pc-guard

-fsanitize-coverage=func,trace-pc-guard

2颈将、然后編譯,發(fā)現(xiàn)會(huì)報(bào)錯(cuò):提示報(bào)錯(cuò)

Showing Recent Messages
Undefined symbol: ___sanitizer_cov_trace_pc_guard_init
image.png

提示找不到__sanitizer_cov_trace_pc_guard__sanitizer_cov_trace_pc_guard_init方法言疗。
看一下文檔晴圾,發(fā)現(xiàn)有測(cè)試代碼:

image.png

把這段代碼copy到項(xiàng)目中,發(fā)現(xiàn)噪奄,錯(cuò)誤沒(méi)有了
__sanitizer_cov_trace_pc_guard_init

分析一下__sanitizer_cov_trace_pc_guard_init函數(shù)死姚,這里面有個(gè)startstop,打個(gè)斷點(diǎn)勤篮,看一下startstop內(nèi)存里面的值:

image.png

start里每4個(gè)字節(jié)里面都有一個(gè)數(shù)組都毒,而且是按照1、2碰缔、3账劲、4的順序排列的,再看一下stop,按照start的規(guī)則涤垫,減4個(gè)字節(jié)看一下姑尺,發(fā)現(xiàn)是13竟终,這里面存的是我們項(xiàng)目自定義文件中符號(hào)的數(shù)量蝠猬,無(wú)論是方法、函數(shù)還是block统捶,都會(huì)統(tǒng)計(jì)進(jìn)來(lái)榆芦,我們可以多加幾個(gè)方法或者函數(shù)、block試一下坡慌,就可以驗(yàn)證:
__sanitizer_cov_trace_pc_guard
我們?cè)俜治鲆幌?code>__sanitizer_cov_trace_pc_guard
我們運(yùn)行時(shí)候發(fā)現(xiàn)打印了好多guard

image.png

實(shí)現(xiàn)個(gè)個(gè)手勢(shì)

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
 
}

點(diǎn)擊一下屏幕芋绸,發(fā)現(xiàn)


image.png

點(diǎn)擊一下打印一下榜跌,猜測(cè)每執(zhí)行一個(gè)函數(shù)都會(huì)調(diào)用一次,說(shuō)明該函數(shù)hook了所有的方法崎淳,為了進(jìn)一步驗(yàn)證,定義一個(gè)函數(shù)和一個(gè)block愕把,在點(diǎn)擊屏幕時(shí)候調(diào)用一個(gè)函:

void(^block1)(void) = ^(void) {
 
};
 
void test(){
    block1();
 
}
 
guard: 0x100d8381c a PC ?
guard: 0x100d83814 8 PC ?
guard: 0x100d83810 7 PC

發(fā)現(xiàn)點(diǎn)擊一次拣凹,該函數(shù)調(diào)用了三次

通過(guò)匯編驗(yàn)證一下,在toubegain恨豁、函數(shù)嚣镜、block出都加上斷點(diǎn),然后打開(kāi)匯編橘蜜,運(yùn)行

image.png

bl指令代表調(diào)用一個(gè)方法或者一個(gè)函數(shù) 菊匿,過(guò)掉這個(gè)斷點(diǎn)
image.png

test也調(diào)用,再過(guò)一下
image.png

block也調(diào)用了计福,當(dāng)我們配置了chang的代碼覆蓋工具跌捆,實(shí)現(xiàn)了上面兩個(gè)函數(shù),clang會(huì)以靜態(tài)插裝形式在所有方法象颖、函數(shù)block內(nèi)部插入一行代碼佩厚,而且是在第一行一開(kāi)始插入的,做到了全局的hook
我們?cè)僭诜治鱿?code>__sanitizer_cov_trace_pc_guard的作用力麸,我們現(xiàn)在這個(gè)函數(shù)里面加一個(gè)斷點(diǎn)
image.png

再運(yùn)行
image.png

在左邊發(fā)現(xiàn)有個(gè)函數(shù)調(diào)用棧可款,并且在每次調(diào)用方法時(shí)候都會(huì)調(diào)起__sanitizer_cov_trace_pc_guard函數(shù),而這個(gè)函數(shù)就是相應(yīng)方法調(diào)起來(lái)的
實(shí)例代碼中有個(gè)PC克蚂,我們加個(gè)斷點(diǎn)打印一下這個(gè)PC看看闺鲸,先把啟動(dòng)時(shí)候的函數(shù)都過(guò)掉再打開(kāi)斷點(diǎn),然后點(diǎn)擊一下屏幕觸發(fā)touchesBegan的方法進(jìn)行攔截:
image.png

在控制欄中輸入bt埃叭,查看一下函數(shù)調(diào)用棧
image.png

看一下0x0000000104349abc這個(gè)地址的信息
image.png

發(fā)現(xiàn)這個(gè)地址是在touchesBegan里面摸恍,但是不在touchesBegan開(kāi)頭,我們把它減4個(gè)字節(jié)
image.png

第一個(gè)指令是bl,這時(shí)才是touchesBegan的開(kāi)頭
在touchesBegan方法里面加一個(gè)斷點(diǎn)立镶,然后跳到touchesBegan方法里面壁袄,再打開(kāi)匯編:
image.png

bl是調(diào)用的意思,我們發(fā)現(xiàn)0x104349ab8是touchesBegan方法的開(kāi)頭媚媒,0x00000001000bdabc是調(diào)用下一個(gè)函數(shù)的指令的下一個(gè)地址嗜逻,PC打印的就是0x104349abc
image.png

再來(lái)看一下函數(shù)調(diào)用棧
image.png

調(diào)用棧的左邊是上一個(gè)函數(shù)的開(kāi)始地址,最后面有個(gè)+64缭召,最后面那個(gè)數(shù)字是偏移量栈顷,也就是說(shuō)函數(shù)的開(kāi)始位置+偏移量才是函數(shù)的真正的位置,這個(gè)時(shí)候touchesBegan的偏移量是44嵌巷,我們測(cè)試一下:

image.png

這才是touchesBegan的真正實(shí)現(xiàn)萄凤,也就是匯編的這一段
image.png

說(shuō)明在__sanitizer_cov_trace_pc_guard里面我們能拿到下一個(gè)函數(shù)調(diào)用的首地址:
看一下__sanitizer_cov_trace_pc_guard的匯編調(diào)用
image.png

最后面有個(gè)ret也就是return返回的意思,每個(gè)函數(shù)或者方法都有一個(gè)return搪哪, 在底層實(shí)現(xiàn)靡努,每一個(gè)函數(shù)調(diào)用完成后都會(huì)返回下一個(gè)需要調(diào)用的函數(shù)的地址,也就是匯編中每次bl的時(shí)候會(huì)把下次要調(diào)用的指令的地址存在x30中晓折,當(dāng)函數(shù)執(zhí)行時(shí)候遇到ret時(shí)候就會(huì)從x30中的值返回回去 ,例如我們點(diǎn)擊屏幕時(shí)候在__sanitizer_cov_trace_pc_guard加個(gè)斷點(diǎn)惑朦,然后讀取x30數(shù)據(jù),就得到了touchesBegan的地址
image.png

所以__sanitizer_cov_trace_pc_guard中的
image.png

拿到的是下一個(gè)要調(diào)用的函數(shù)的地址已维,因?yàn)開(kāi)_sanitizer_cov_trace_pc_guard函數(shù)都是在hook函數(shù)前執(zhí)行的行嗤,所以在這里面拿到的函數(shù)地址就是我們hook的函數(shù)地址
既然能拿到函數(shù)地址,我們可以通過(guò)這個(gè)函數(shù)去拿到函數(shù)名稱垛耳。

#import <dlfcn.h>
dladdr(<#const void *#>, <#Dl_info *#>)
  • 第一個(gè)參數(shù)是函數(shù)的地址栅屏。
    第二個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體指針。
    我們看看這個(gè)結(jié)構(gòu)體格式
typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object  */
        void            *dli_fbase;     /* Base address of shared object  */
        const char      *dli_sname;     /* Name of nearest symbol  */
        void            *dli_saddr;     /* Address of nearest symbol  */
} Dl_info;
void *PC = __builtin_return_address(0);
 
    Dl_info info;
    dladdr(PC, &info);
    printf("fname:%s \nfbase:%p \nsname:%s \nsaddr:%p\n",
           info.dli_fname,
           info.dli_fbase,
           info.dli_sname,
           info.dli_saddr);
 
打犹孟省:
 
fname:/private/var/containers/Bundle/Application/38C6E838-7D51-4546-9882-BF5858D08C16/TraceDemo.app/TraceDemo
fbase:0x1000e0000
sname:-[ViewController touchesBegan:withEvent:]
saddr:0x1000e5a0c
 
  • fname:文件路徑
  • fbase:文件地址
  • sname:函數(shù)符號(hào)名稱
  • saddr:函數(shù)符號(hào)地址栈雳,也就是函數(shù)的起始地址

當(dāng)我們能拿到項(xiàng)目所有調(diào)用函數(shù)的符號(hào)時(shí)候,我們就能通過(guò)這種方法來(lái)拿到APP啟動(dòng)時(shí)候調(diào)用的所有的函數(shù)缔莲、方法哥纫、block符號(hào),然后創(chuàng)建order文件進(jìn)行自動(dòng)二進(jìn)制重拍上代碼:

//原子隊(duì)列
static  OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定義符號(hào)結(jié)構(gòu)體
typedef struct {
    void *pc;
    void *next;
}SYNode;
 
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//    if (!*guard) return;  // Duplicate the guard check.
    /*  精確定位 哪里開(kāi)始 到哪里結(jié)束!  在這里面做判斷寫(xiě)條件!*/
    void *PC = __builtin_return_address(0);
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    //進(jìn)入,因?yàn)樵摵瘮?shù)可能在子線程中操作痴奏,所以用原子性操作蛀骇,保證線程安全
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
//
}
 
-(void)createOrderFile{
 
    NSMutableArray <NSString *> * symbolNames = [NSMutableArray array];
 
    while (YES) {
        SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info;
        dladdr(node->pc, &info);
        NSString * name = @(info.dli_sname);
        BOOL  isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
        [symbolNames addObject:symbolName];
    }
    //取反
    NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
    //去重
    NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString * name;
    while (name = [emt nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    //干掉自己!
    [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    //將數(shù)組變成字符串
    NSString * funcStr = [funcs  componentsJoinedByString:@"\n"];
 
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"%@",funcStr);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市读拆,隨后出現(xiàn)的幾起案子擅憔,更是在濱河造成了極大的恐慌,老刑警劉巖檐晕,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暑诸,死亡現(xiàn)場(chǎng)離奇詭異蚌讼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)个榕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén)篡石,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人西采,你說(shuō)我怎么就攤上這事凰萨。” “怎么了苛让?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵沟蔑,是天一觀的道長(zhǎng)湿诊。 經(jīng)常有香客問(wèn)我狱杰,道長(zhǎng),這世上最難降的妖魔是什么厅须? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任仿畸,我火速辦了婚禮,結(jié)果婚禮上朗和,老公的妹妹穿的比我還像新娘错沽。我一直安慰自己,他們只是感情好眶拉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布千埃。 她就那樣靜靜地躺著,像睡著了一般忆植。 火紅的嫁衣襯著肌膚如雪放可。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,730評(píng)論 1 289
  • 那天朝刊,我揣著相機(jī)與錄音耀里,去河邊找鬼。 笑死拾氓,一個(gè)胖子當(dāng)著我的面吹牛冯挎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咙鞍,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼房官,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了续滋?” 一聲冷哼從身側(cè)響起翰守,我...
    開(kāi)封第一講書(shū)人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吃粒,沒(méi)想到半個(gè)月后潦俺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年事示,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了早像。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肖爵,死狀恐怖卢鹦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劝堪,我是刑警寧澤冀自,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站秒啦,受9級(jí)特大地震影響熬粗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜余境,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一驻呐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芳来,春花似錦含末、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至顽聂,卻和暖如春肥惭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芜飘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工务豺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嗦明。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓笼沥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親娶牌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奔浅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348