OC底層原理三十四:?jiǎn)?dòng)優(yōu)化(Clang插樁)

OC底層原理 學(xué)習(xí)大綱

上一節(jié)我們熟悉了啟動(dòng)優(yōu)化二進(jìn)制重排原理方法。本節(jié)繼續(xù)講解如何自動(dòng)生成order文件薄啥。

  1. 什么是hook
  2. clang插樁
  3. 獲取函數(shù)符號(hào)
  4. 存儲(chǔ)和導(dǎo)出
  5. 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

image.png
  • 復(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

startstop表示當(dāng)前文件的開始內(nèi)存地址結(jié)束內(nèi)存地址。單位是int32 4字節(jié)

  • 如果多加幾個(gè)函數(shù)吐葱,會(huì)發(fā)現(xiàn)stop地址值也會(huì)相應(yīng)的增加街望。
  • 此處是指從startstop前閉后開區(qū)間。[ , )弟跑,所以stop地址偏移4字節(jié)涂滴,才是最后一個(gè)函數(shù)符號(hào)地址褪猛。
  • 清空打印區(qū)懈费,點(diǎn)擊屏幕锌钮,觸發(fā)touchBegin。我們發(fā)現(xiàn)觸發(fā)了3次guard扑浸。
    image.png
  • 這3次分別是touchBegin烧给、testblock三個(gè)函數(shù)被觸發(fā)時(shí)的打印喝噪。

我們?cè)?code>touchBegin础嫡、testblock__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)行程序纵装,可以看到:
image.png
  • 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):

  1. if(!*guard) return;需要去掉痘括,會(huì)影響+load寫入

  2. 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 SettingsOther 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í)

注意

  1. 【二進(jìn)制重排order文件】需要代碼封版后驳阎,再生成。 (代碼還在變動(dòng)馁蒂,生成就沒意義了)
  2. 【二進(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)自帶名稱混淆

  1. 未改變代碼時(shí)饼酿,swift符號(hào)不會(huì)變榕酒。
    總之,order文件故俐,請(qǐng)?jiān)?code>代碼封版后想鹰,再生成
  • 至此药版,Clang插樁自動(dòng)生成Order文件辑舷,都已完成。 去實(shí)戰(zhàn)試試吧槽片!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末何缓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子还栓,更是在濱河造成了極大的恐慌碌廓,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝙云,死亡現(xiàn)場(chǎng)離奇詭異氓皱,居然都是意外死亡路召,警方通過查閱死者的電腦和手機(jī)勃刨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來股淡,“玉大人身隐,你說我怎么就攤上這事∥椋” “怎么了贾铝?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)埠帕。 經(jīng)常有香客問我垢揩,道長(zhǎng),這世上最難降的妖魔是什么敛瓷? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任叁巨,我火速辦了婚禮,結(jié)果婚禮上呐籽,老公的妹妹穿的比我還像新娘锋勺。我一直安慰自己蚀瘸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布庶橱。 她就那樣靜靜地躺著贮勃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苏章。 梳的紋絲不亂的頭發(fā)上寂嘉,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音布近,去河邊找鬼垫释。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撑瞧,可吹牛的內(nèi)容都是我干的棵譬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼预伺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼订咸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酬诀,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤脏嚷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瞒御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體父叙,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年肴裙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趾唱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜻懦,死狀恐怖甜癞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宛乃,我是刑警寧澤悠咱,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站征炼,受9級(jí)特大地震影響析既,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谆奥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一眼坏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雄右,春花似錦空骚、人聲如沸纺讲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熬甚。三九已至,卻和暖如春肋坚,著一層夾襖步出監(jiān)牢的瞬間乡括,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工智厌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诲泌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓铣鹏,卻偏偏與公主長(zhǎng)得像敷扫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诚卸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容