上一節(jié)我們熟悉了啟動(dòng)優(yōu)化
中二進(jìn)制重排
的原理
和方法
。本節(jié)繼續(xù)講解如何自動(dòng)
生成order文件
薄啥。
- 什么是hook
- clang插樁
- 獲取函數(shù)符號(hào)
- 存儲(chǔ)和導(dǎo)出
- swift二進(jìn)制重排
1. 什么是hook
hook曙博,是鉤子换淆。
獲取
原有函數(shù)符號(hào)
的內(nèi)存地址
和實(shí)現(xiàn)
,勾住
它岸霹,做
一些自己想做
的事情
。
- 例如: 你遇到在
公路
上攔
到一輛車
。你可以跟
他的車
一起走(附加自己代碼
)召烂,也可以直接搶
了他的車
自己開(重寫實(shí)現(xiàn)
)
很明顯,我們此刻就是想勾
住啟動(dòng)結(jié)束前
的所有函數(shù)
娃承,附加
一些代碼
奏夫,把函數(shù)名
按順序存下來
,生成我們的order文件
历筝。
Q: 有沒有
API
桶蛔,能讓我hook一切
我想hook的東西?swift
漫谷、oc
仔雷、c
函數(shù)我都
要hook
?
A: 有,clang插樁
。 語法樹都是它生成的碟婆,順序它說了算电抚。
2. clang插樁
官方介紹: https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs
官方提供了
LLVM
的代碼覆蓋監(jiān)測(cè)工具
。其中包含了Tracing PCs
(追蹤PC)竖共。我們創(chuàng)建
TranceDemo
項(xiàng)目蝙叛,按照官方
給的示例
,來嘗試開發(fā)
2.1 添加trace
-
按照官方描述公给,可以加入
跟蹤代碼
借帘,并給出了回調(diào)函數(shù)
。
image.png 打開
TranceDemo
,Build Settings
中搜索Other C
,加入-fsanitize-coverage=trace-pc-guard
-
復(fù)制
項(xiàng)目案例
淌铐,粘貼
到項(xiàng)目的ViewController
中肺然,去除注釋
和extern 聲明
,加入幾個(gè)測(cè)試函數(shù)
:
#import "ViewController.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
@interface ViewController ()
@end
@implementation ViewController
+(void)load {}
void (^block)(void) = ^{ printf("123"); };
void test() { block(); }
- (void)viewDidLoad {
[super viewDidLoad];
}
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
char PcDescr[1024];
// __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
test();
}
@end
Command+B
編譯腿准,發(fā)現(xiàn)找不到
符號(hào)__sanitizer_symbolize_pc
(需要導(dǎo)入庫)际起,我們暫時(shí)把這一行注釋掉
-
運(yùn)行程序:
image.png
start
和stop
表示當(dāng)前文件的開始內(nèi)存地址
和結(jié)束內(nèi)存地址
。單位是int32
4字節(jié)
- 如果
多加
幾個(gè)函數(shù)
吐葱,會(huì)發(fā)現(xiàn)stop
地址值也會(huì)
相應(yīng)的增加
街望。- 此處是指從
start
到stop
的前閉后開
區(qū)間。[ , )
弟跑,所以stop地址
往前
偏移4字節(jié)
涂滴,才是最后
一個(gè)函數(shù)符號(hào)
的地址
褪猛。
-
清空
打印區(qū)懈费,點(diǎn)擊屏幕
锌钮,觸發(fā)touchBegin
。我們發(fā)現(xiàn)觸發(fā)了3次guard
扑浸。
image.png - 這3次分別是
touchBegin
烧给、test
、block
三個(gè)函數(shù)被觸發(fā)
時(shí)的打印
喝噪。
我們?cè)?code>touchBegin础嫡、
test
、block
和__sanitizer_cov_trace_pc_guard
都加入斷點(diǎn)酝惧,運(yùn)行代碼:
image.png
【驗(yàn)證一】執(zhí)行順序是:
touchBegin
->__sanitizer_cov_trace_pc_guard
->
test
->__sanitizer_cov_trace_pc_guard
->
block
->__sanitizer_cov_trace_pc_guard
【驗(yàn)證二】
touchBegin
時(shí)榴鼎,進(jìn)入?yún)R編:
image.png確實(shí)
每個(gè)函數(shù)
在觸發(fā)
時(shí),都調(diào)用了__sanitizer_cov_trace_pc_guard
函數(shù)晚唇。原因:
- 只要在
Other C Flags
處加
入標(biāo)記
巫财,開啟了trace
功能。LLVM
會(huì)在每個(gè)函數(shù)邊緣
(開始位置)哩陕,插入
一行調(diào)用__sanitizer_cov_trace_pc_guard
的代碼平项。編譯期
就插入
了赫舒。所以可以100%覆蓋。
- 以上闽瓢,就是
Clang插樁
接癌。插樁
操作完成
后,我們需要獲取
所有函數(shù)符號(hào)
扣讼、存儲(chǔ)
并導(dǎo)出order文件
缺猛。
3. 獲取函數(shù)符號(hào)
-
__builtin_return_address
: return的地址。
函數(shù)
return
椭符,是返回到上一層
的函數(shù)
荔燎。
- 通過
return
的地址,拿到的是上一層級(jí)
的函數(shù)信息
销钝。- 參數(shù):
0
: 表示當(dāng)前函數(shù)的上一層
有咨。1
:是上一層
的上一層
地址。
- 導(dǎo)入
#import <dlfcn.h>
曙搬,通過Dl_info
拿到函數(shù)信息:
typedef struct dl_info {
const char *dli_fname; /* 文件地址*/
void *dli_fbase; /* 起始地址(machO模塊的虛擬地址)*/
const char *dli_sname; /* 符號(hào)名稱 */
void *dli_saddr; /* 內(nèi)存真實(shí)地址(偏移后的真實(shí)物理地址) */
} Dl_info;
- 在
__sanitizer_cov_trace_pc_guard
函數(shù)加入代碼:
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if(!*guard) return;
void *PC = __builtin_return_address(0); //0 當(dāng)前函數(shù)地址, 1 上一層級(jí)函數(shù)地址
Dl_info info; // 聲明對(duì)象
dladdr(PC, &info); // 讀取PC地址鸽嫂,賦值給info
printf("dli_fname:%s \n dli_fbase:%p \n dli_sname:%s \n dli_saddr:%p \n ", info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr);
}
- 運(yùn)行程序纵装,可以看到:
dli_fname
: 文件地址dli_fbase
: 起始地址(machO模塊的虛擬地址)dli_sname
: 符號(hào)名稱dli_saddr
: 內(nèi)存真實(shí)地址(偏移后的真實(shí)物理內(nèi)存地址)
- 此時(shí),我們
成功
拿到函數(shù)符號(hào)
据某。
4.存儲(chǔ)符號(hào)
注意:__sanitizer_cov_trace_pc_guard
函數(shù)是在多線程
環(huán)境下橡娄,所以需要注意寫入安全
寫入安全
,就是上鎖
癣籽。 可參考【第二十八挽唉、第二十九節(jié)】,此處我使用OSAtomic原子鎖
筷狼。存儲(chǔ)方式
瓶籽,也有很多種, 此處我使用隊(duì)列
進(jìn)行存儲(chǔ)
埂材。
- 導(dǎo)入
#include <libkern/OSAtomic.h>
原子頭文件塑顺,創(chuàng)建原子隊(duì)列
,定義節(jié)點(diǎn)結(jié)構(gòu)體
:
#import "ViewController.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h> // 原子操作
@interface ViewController ()
@end
@implementation ViewController
+(void)load {}
void (^block)(void) = ^{ printf("123"); };
void test666() { block(); }
- (void)viewDidLoad {
[super viewDidLoad];
}
// 定義原子隊(duì)列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT; // 原子隊(duì)列初始化
// 定義符號(hào)結(jié)構(gòu)體
typedef struct {
void * pc;
void * next;
}SYNode;
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// 這里是多線程俏险,會(huì)有資源搶奪严拒。
// 這個(gè)會(huì)影響load函數(shù),所以需要移除哨兵
// if(!*guard) return;
void *PC = __builtin_return_address(0); //0 當(dāng)前函數(shù)地址竖独, 1 上一層級(jí)函數(shù)地址
Dl_info info; // 聲明對(duì)象
dladdr(PC, &info); // 讀取PC地址裤唠,賦值給info
// 創(chuàng)建結(jié)構(gòu)體
SYNode * node = malloc(sizeof(SYNode)); // 創(chuàng)建結(jié)構(gòu)體空間
*node = (SYNode){PC, NULL}; // node節(jié)點(diǎn)的初始化賦值(pc為當(dāng)前PC值,NULL為next值)
// 加入結(jié)構(gòu) (offsetof: 按照參數(shù)1大小作為偏移值莹痢,給到next)
// 拿到并賦值
// 拿到symbolList地址种蘸,偏移SYNode字節(jié)墓赴,將node賦值給symbolList最后節(jié)點(diǎn)的next指針。
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 創(chuàng)建可變數(shù)組
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
// 每次while循環(huán)劈彪,都會(huì)加入一次hook (__sanitizer_cov_trace_pc_guard) 只要是跳轉(zhuǎn)竣蹦,就會(huì)被block
// 直接修改[other c clang]: -fsanitize-coverage=func,trace-pc-guard 指定只有func才加Hook
while (1) {
// 去除鏈表
SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
if(node ==NULL) break;
Dl_info info = {0};
// 取出節(jié)點(diǎn)的pc,賦值給info
dladdr(node->pc, &info);
// 釋放節(jié)點(diǎn)
free(node);
// 存名字
NSString *name = @(info.dli_sname);
// 三目運(yùn)算符 寫法
BOOL isObjc = [name hasPrefix: @"+["] || [name hasPrefix: @"-["];
NSString * symbolName = isObjc ? name : [NSString stringWithFormat:@"_%@",name];
[symbolNames addObject:symbolName];
}
// 反向集合
NSEnumerator * enumerator = [symbolNames reverseObjectEnumerator];
// 創(chuàng)建數(shù)組
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
// 臨時(shí)變量
NSString * name;
// 遍歷集合,去重沧奴,添加到funcs中
while (name = [enumerator nextObject]) {
// 數(shù)組中去重添加
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
// 移除當(dāng)前touchesBegan函數(shù) (跟啟動(dòng)無關(guān))
[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
// 數(shù)組轉(zhuǎn)字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
// 文件路徑
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ht.order"];
// 文件內(nèi)容
NSData * fielContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
// 創(chuàng)建文件
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fielContents attributes:nil];
NSLog(@"%@",funcs);
NSLog(@"%@",filePath);
NSLog(@"%@",fielContents);
}
@end
坑點(diǎn):
if(!*guard) return;
需要去掉痘括,會(huì)影響+load
的寫入
while循環(huán)
,也會(huì)觸發(fā)__sanitizer_cov_trace_pc_guard
:
【現(xiàn)象】:
image.png【原因】:
- 通過看匯編滔吠,可以看到while也觸發(fā)了
__sanitizer_cov_trace_pc_guard
的跳轉(zhuǎn)纲菌。原因是,trace
的觸發(fā)
疮绷,并不是
根據(jù)函數(shù)
來進(jìn)行hook
的翰舌,而是hook
了每一個(gè)跳轉(zhuǎn)(bl)
。while
也有跳轉(zhuǎn)
冬骚,所以進(jìn)入了死循環(huán)
椅贱。【方案】:
Build Settings
的Other C Flags
配置,添加一個(gè)func
指定條件:-fsanitize-coverage=func,trace-pc-guard
image.png
-
運(yùn)行代碼
只冻,點(diǎn)擊屏幕
:
image.png -
根據(jù)打印路徑庇麦,查看
ht.order
文件,完美喜德!
image.png
真機(jī)的沙盒文件山橄,可以從這里下載:
選擇設(shè)備
,點(diǎn)擊Add ...
image.png- 選擇
真機(jī)
->選擇APP
->點(diǎn)擊設(shè)置
image.png點(diǎn)擊下載
舍悯,就可以拿到手機(jī)沙盒
信息了
image.png- 包內(nèi)容中航棱,可以找到
ht.order文件
- 復(fù)制
ht.order
文件,放到根目錄
萌衬,就完成了饮醇。
image.png
可以根據(jù) 上一節(jié)的內(nèi)容,打開
link Map
查看最終
的符號(hào)排序
秕豫,使用Instruments
檢查自己應(yīng)用的PageFault數(shù)量
和耗時(shí)
注意
- 【二進(jìn)制重排
order文件
】需要代碼封版后
驳阎,再生成
。 (代碼還在變動(dòng)馁蒂,生成就沒意義了)- 【二進(jìn)制重排
相關(guān)代碼
】不要寫到
自己項(xiàng)目中
去呵晚。寫個(gè)小工具
跑一下,拿到order文件
即可沫屡。
5. Swift二進(jìn)制重排
-
Swift 二進(jìn)制重排
饵隙,與OC一樣
。只是LLVM前端不同
沮脖。
OC
的前端編譯器
是Clang
金矛,所以在other c flags
處添加-fsanitize-coverage=func,trace-pc-guard
Swift
的前端編譯器
是Swift
芯急,所以在other Swift Flags
處添加-sanitize=undefined
和-sanitize-coverage=func
image.png
-
項(xiàng)目中添加
SwiftTest.swift
文件,創(chuàng)建橋接頭
:
image.png
image.png -
在
ViewController.m
中導(dǎo)入橋接頭
文件:#import "TranceDemo-Swift.h"
image.png -
運(yùn)行
項(xiàng)目驶俊,點(diǎn)擊屏幕
娶耍,去打印
的目錄
下,拿到ht.order
文件:
image.png
補(bǔ)充:
1 .
swift符號(hào)
自帶名稱混淆
未改變
代碼時(shí)饼酿,swift符號(hào)
不會(huì)變榕酒。
總之,order文件
故俐,請(qǐng)?jiān)?code>代碼封版后想鹰,再生成
。
- 至此药版,
Clang插樁
和自動(dòng)生成Order文件
辑舷,都已完成
。 去實(shí)戰(zhàn)
試試吧槽片!