線上發(fā)現(xiàn)了一個(gè)bug桅锄,需要用自產(chǎn)的熱補(bǔ)丁對(duì) Bugly 的一個(gè)方法進(jìn)行替換琉雳,改變返回值
/**
* App 是否發(fā)生了連續(xù)閃退
* 如果啟動(dòng)SDK 且 5秒內(nèi) 閃退,且次數(shù)達(dá)到 3次 則判定為連續(xù)閃退
*
* @return 是否連續(xù)閃退
*/
+ (BOOL)isAppCrashedOnStartUpExceedTheLimit;
可是重試了很多次發(fā)現(xiàn)熱補(bǔ)丁都沒(méi)生效友瘤。翠肘。。
后面發(fā)現(xiàn)我們調(diào)用該 Bugly 方法過(guò)早辫秧,熱補(bǔ)丁又生效過(guò)遲锯茄,所以熱補(bǔ)丁一直失效!
沒(méi)辦法直接攔截 [Bugly isAppCrashedOnStartUpExceedTheLimit]
返回 YES
茶没。 那我們只能在閃退次數(shù)累加的時(shí)候進(jìn)行攔截了肌幽,不讓它次數(shù)累加起來(lái)
按照 bugly的注釋 次數(shù)累加是在應(yīng)用閃退的時(shí)候進(jìn)行的,這個(gè)時(shí)候我們熱補(bǔ)丁已經(jīng)加載抓半,可以發(fā)揮作用了喂急。 可是我們?cè)趺粗?Bugly 怎么累加閃退次數(shù)的呢?
Hopper 登場(chǎng)
之前有簡(jiǎn)略介紹過(guò) Hopper 逆向的使用笛求,但是找不到文章了廊移。。探入。
找個(gè)項(xiàng)目=> Bugly SDK 丟進(jìn)去 => 編譯狡孔,由于是debug包,所以可以不用脫殼蜂嗽,直接找到生成的 app 文件苗膝,拖到 hopper 工程窗口中
等 Hopper 解析完,直接輸入 Bugly 方法名植旧,找到對(duì)應(yīng)實(shí)現(xiàn)的匯編段辱揭, 點(diǎn)擊 右上角的 if(b)f(x)
按鈕离唐。 hopper 會(huì)自動(dòng)幫我們解析為 OC 代碼。
其實(shí)可以看到 Bugly 只是個(gè)殼问窃,具體邏輯是在 BLYSDKManager
類(lèi)中實(shí)現(xiàn)的亥鬓, 我們繼續(xù)跟進(jìn)
然后發(fā)現(xiàn) [BLYSDKManager isAppCrashedOnStartUpExceedTheLimit]
也是個(gè)殼, 是直接返回 crashedOverFlow
方法值。
分析 crashedOverFlow 實(shí)現(xiàn)域庇,發(fā)現(xiàn)其實(shí)是個(gè) get 方法嵌戈, 直接返回了 bool 類(lèi)型的 _crashedOverFlow 變量,BLYSDKManager 也實(shí)現(xiàn)了該變量的 set 方法听皿。
所以我們重點(diǎn)就變?yōu)榱斯颈穑睦镎{(diào)用 setCrashedOverFlow:
方法?写穴?
看上圖惰拱,其實(shí)直接搜關(guān)鍵字是搜不到調(diào)用方的,這個(gè)時(shí)候需要用到 strings tab
啊送。
這個(gè) strings 表示搜文本區(qū)
因?yàn)?OC 以發(fā)消息的方式進(jìn)行方法調(diào)用偿短,方法名都會(huì)以 strings的方式在常量區(qū)內(nèi)。 后續(xù)的 XREF 表示引用到該變量的地方馋没, 可直接點(diǎn)擊跳轉(zhuǎn)昔逗。
我們跟過(guò)去看看
發(fā)現(xiàn)只有在 -[BLYSDKManager startWithAppId:developmentDevice:config:]+249
這一個(gè)地方調(diào)用了該方法
我們過(guò)去看看該方法的實(shí)現(xiàn)
發(fā)現(xiàn) Bugly 內(nèi)部的次數(shù)累加是放在 NSUserDefaults
里面的。 key 名也找到了篷朵。 條件確實(shí)是 超過(guò)3次勾怒,就設(shè)置閃退標(biāo)示位 為 true
但是哪里對(duì)這個(gè)值進(jìn)行累加呢?
所以我們需要找哪里使用了這個(gè) com.tencent.bugly.exitedonstartUplimitkey
字符串
方法1: 直接讀匯編声旺,名字太明顯了
方法2: 還是通過(guò) strings tab 進(jìn)行搜索
引用區(qū)太長(zhǎng) 直接通過(guò)代碼來(lái)看吧
0000000100eff3f8 dq 0x0000000100f58d60 ; @"com.tencent.bugly.exitedonstartUplimitkey", XREF=
-[BLYSDKManager startWithAppId:developmentDevice:config:]+202,
-[BLYSDKManager startWithAppId:developmentDevice:config:]_block_invoke+49,
-[BLYAnalyticsManager willTerminateHandler]+72
可以看到 就3個(gè)地方有用到該變量笔链。。 我們排查量就小了
[BLYAnalyticsManager willTerminateHandler] 是把累加次數(shù)重置0腮猖,可以排除
看方法名鉴扫,應(yīng)該是收到 系統(tǒng) willTerminate 通知的時(shí)候調(diào)用,就是應(yīng)用被正常終止時(shí)會(huì)調(diào)用
-[BLYSDKManager startWithAppId:developmentDevice:config:]+202,
-[BLYSDKManager startWithAppId:developmentDevice:config:]_block_invoke+49,
有兩個(gè)區(qū)域引用澈缺,一個(gè)在 block 內(nèi)坪创,內(nèi)部邏輯是 重置為0, 可以排除姐赡。
外部區(qū)域也有兩個(gè)地方使用莱预,一個(gè) get,一個(gè) set项滑, 那真正累加的代碼就是 set
那塊的邏輯了
后面就是還原 bugly 內(nèi)部邏輯了依沮, OC 偽代碼
- (void) startWithAppId:developmentDevice:config: {
int count = [NSUserDefaults integerForKey:@"LimitKey"];
if (count >= 3) {
// 累加次數(shù) 大等于3,就設(shè)置連續(xù)閃退成立
// isAppCrashedOnStartUpExceedTheLimit 方法返回 YES
[self setCrashedOverFlow:YES];
}
// 對(duì)閃退次數(shù)進(jìn)行累加, 并保存回 NSUserDefaults
count += 1;
[NSUserDefaults setInteger:count forKey:@"LimitKey"];
// 如果5秒后 程序還在執(zhí)行,把閃退次數(shù) 重置為 0
dispatch_after(5, ^{
[NSUserDefaults setInteger:0 forKey:@"LimitKey"];
});
...
}
其實(shí)可以看到 bugly 整個(gè)累加的邏輯悉抵,現(xiàn)有的熱補(bǔ)丁是沒(méi)法插手的
肩狂。摘完。姥饰。
而且 bugly 并不是在閃退的時(shí)候?qū)Υ螖?shù)進(jìn)行累加,而是在一啟動(dòng)的時(shí)候就累加孝治,只是 5秒后 或者 系統(tǒng)調(diào)用了 willTerminate 通知列粪, 然后把次數(shù)重置為 0。
整體邏輯符合 bugly 自己的注釋谈飒。岂座。。
題外話
怎么判斷是 5秒 過(guò)后杭措?
dispatch_time(0x0, 0x12a05f200)
0x12a05f200 = 5000000000
#define DISPATCH_TIME_NOW (0ull)
#define NSEC_PER_SEC 1000000000ull
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)