欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

iOS監(jiān)控筆記之啟動crash

前言

目前成都創(chuàng)新互聯(lián)公司已為上千余家的企業(yè)提供了網(wǎng)站建設、域名、網(wǎng)絡空間、網(wǎng)站托管、服務器托管、企業(yè)網(wǎng)站設計、景洪網(wǎng)站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

相較于正常的崩潰問題,啟動crash造成的損失要遠遠大得多。正常來說,如果有足夠強健的構建發(fā)布系統(tǒng),大多數(shù)時候能在版本上線之前及時發(fā)現(xiàn)問題并且修復,但是仍然存在小概率的線上意外。啟動crash一般同時具備損害嚴重以及難以捕獲兩大特點

啟動過程

從應用圖標被用戶點擊開始,直到應用可以開始響應發(fā)生了很多事情。正常來說,盡管我們希望crash監(jiān)控工具啟動的盡可能早,但接入方往往總是等到launch事件之后才能啟動工具,而在這個時間之前發(fā)生的崩潰就是啟動crash,下面列出了在應用直到launch時,存在的可能發(fā)生啟動crash的階段:

iOS監(jiān)控筆記之啟動crash

其中initialize的順序可能在更早,但總是會在load和launch之間。從圖中來說,如果我們想要監(jiān)控啟動crash,那么開始監(jiān)控的時間點必須要放到load階段,才能保證最好的監(jiān)控效果

如何監(jiān)控

最簡單的方式是不管接入方愿不愿意啟動crash監(jiān)控,我們在load方法中直接啟動監(jiān)控功能。但是這樣的做法會讓應用面臨四個風險點:

  • 類似A/B的線上開關方案失去了對監(jiān)控工具的控制能力
  • crash監(jiān)控啟動存在崩潰問題,這將導致應用完全癱瘓
  • load階段類未加載完畢,啟動工具過程的遞歸加載引發(fā)的崩潰無法監(jiān)控

綜合這些風險點,啟動crash監(jiān)控的方案應該滿足這些條件:

  • 啟動過程不依賴類,避免遞歸加載造成的crash
  • 一旦過程發(fā)生crash,能夠保證日志記錄的安全性

最終得出監(jiān)控的流程圖:

iOS監(jiān)控筆記之啟動crash

不依賴類

不依賴類意味著監(jiān)控工具需要使用C接口來實現(xiàn)功能,雖然比較麻煩,但由于runtime的機制決定了所有方法調用最終要以objc_msgSend函數(shù)作為入口,因此如果能夠hook掉這個函數(shù)并且實現(xiàn)一個調用棧結構,將所有調用入棧記錄,那么追蹤方法調用就不是難事。fishhook提供了hook掉函數(shù)的能力:

__unused static id (*orig_objc_msgSend)(id, SEL, ...);

__attribute__((__naked__)) static void hook_Objc_msgSend() {
 /// save stack data
 /// push msgSend
 /// resume stack data
 
 /// call origin msgSend
 
 /// save stack data
 /// pop msgSend
 /// resume stack data
}

void observe_Objc_msgSend() {
 struct rebinding msgSend_rebinding = { "objc_msgSend", hook_Objc_msgSend, (void *)&orig_objc_msgSend };
 rebind_symbols((struct rebinding[1]){msgSend_rebinding}, 1);
}

實現(xiàn)msgSend

__naked__修飾的函數(shù)告訴編譯器在函數(shù)調用的時候不使用棧保存參數(shù)信息,同時函數(shù)返回地址會被保存到LR寄存器上。由于msgSend本身就是用這個修飾符的,因此在記錄函數(shù)調用的出入棧操作中,必須保證能夠保存以及還原寄存器數(shù)據(jù)。msgSend利用x0 - x9的寄存器存儲參數(shù)信息,可以手動使用sp寄存器來存儲和還原這些參數(shù)信息:

/// 保存寄存器參數(shù)信息
#define save() \
__asm volatile ( \
 "stp x8, x9, [sp, #-16]!\n" \
 "stp x6, x7, [sp, #-16]!\n" \
 "stp x4, x5, [sp, #-16]!\n" \
 "stp x2, x3, [sp, #-16]!\n" \
 "stp x0, x1, [sp, #-16]!\n");

/// 還原寄存器參數(shù)信息
#define resume() \
__asm volatile ( \
 "ldp x0, x1, [sp], #16\n" \
 "ldp x2, x3, [sp], #16\n" \
 "ldp x4, x5, [sp], #16\n" \
 "ldp x6, x7, [sp], #16\n" \
 "ldp x8, x9, [sp], #16\n" );
 
/// 函數(shù)調用,value傳入函數(shù)地址
#define call(b, value) \
 __asm volatile ("stp x8, x9, [sp, #-16]!\n"); \
 __asm volatile ("mov x12, %0\n" :: "r"(value)); \
 __asm volatile ("ldp x8, x9, [sp], #16\n"); \
 __asm volatile (#b " x12\n");


/// msgSend必須使用匯編實現(xiàn)
__attribute__((__naked__)) static void hook_Objc_msgSend() {

 save()
 __asm volatile ("mov x2, lr\n");
 __asm volatile ("mov x3, x4\n");
 
 call(blr, &push_msgSend)
 resume()
 call(blr, orig_objc_msgSend)
 
 save()
 call(blr, &pop_msgSend)
 
 __asm volatile ("mov lr, x0\n");
 resume()
 __asm volatile ("ret\n");
}

日志記錄

常規(guī)的I/O處理不能保證crash發(fā)生的數(shù)據(jù)安全,因此mmap是最適合用于此場景的方案。mmap能保證即便是應用發(fā)生了不可抗拒的崩潰時,也能完成將文件寫入IO的工作。另外我們只需記錄class和selector的調用棧信息,在不存在遞歸算法的情況下,只需要很小的內存使用就能記錄這些數(shù)據(jù):

time_t ts = time(NULL);
const char *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingString: [NSString stringWithFormat: @"%d", ts]].UTF8String;

unsigned char *buffer = NULL;
int fileDescriptor = open(filePath, O_RDWR, 0);
buffer = (unsigned char *)mmap(NULL, MB * 4, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fileDescriptor, 0);

buffer就是我們寫入數(shù)據(jù)的緩沖區(qū),為了保證調用棧的信息準確,每次調用函數(shù)信息出入棧的時候,都需要更新緩沖區(qū)的數(shù)據(jù)。一個可行的方式是每個調用記錄添加一個@符號前綴,總是保存最后一個調用記錄的此符號下標,出棧時清除該下標之后的所有數(shù)據(jù)即可

static inline void push_msgSend(id _self, Class _cls, SEL _cmd, uintptr_t lr) {
 _lastIdx = _length;
 buffer[_lastIdx] = '@';
 ......
}

static inline void pop_msgSend(id _self, SEL _cmd, uintptr_t lr) {
 ......
 buffer[_lastIdx] = '\0';
 _length = _lastIdx;
 size_t idx = _lastIdx - 1;
 
 while (idx >= 0) {
 if (buffer[idx] == '@') {
  _lastIdx = idx;
  break;
 }
 idx--;
 }
}

清空日志

由于msgSend的調用非常頻繁,這種監(jiān)控方案并不適合長時間啟動,因此需要在某個時機關閉監(jiān)控。由于正常的崩潰監(jiān)控啟動時也可能會存在crash,監(jiān)聽becomeActive通知來關閉功能是最合適的選擇,因為此時已經(jīng)過了launch啟動崩潰監(jiān)控工具的階段,可以保證該工具本身是正常使用的:

[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(closeMsgSendObserve) name: UIApplicationDidBecomeActiveNotification object: nil];

- (void)closeMsgSendObserve {
 close(fileDescriptor);
 munmap(buffer, MB * 4);
 [[NSFileManager defaultManager] removeItemAtPath: _logPath error: nil];
}

回滾

當需要回滾時,說明已經(jīng)發(fā)生了啟動crash,此時根據(jù)日志內容,也有不同的處理方式:

日志文件是空文件

這種情況是最危險的情況,如果日志文件為空,說明文件已經(jīng)建立,但是還沒有產生任何方法調用。很有可能在fishhook的處理過程中存在crash,此時應該直接關閉監(jiān)控方案,即便不是它的原因,并且快速增發(fā)版本

日志文件不為空

如果日志文件不為空,說明成功的監(jiān)控到了crash,此時應該同步上傳日志文件,快速反饋到業(yè)務方及時止損。首先止損手段都應該采用同步的方式,保證應用能夠繼續(xù)運行,根據(jù)情況不同,止損的回滾方式包括以下:

  1. 如果crash發(fā)生在并不干擾正常業(yè)務執(zhí)行的功能組件中,可以通過A/B線上開關關閉對應的功能,前提是功能組件使用開關控制
  2. 崩潰處代碼已經(jīng)干擾正常業(yè)務執(zhí)行,但是錯誤代碼短,可以嘗試通過服務器下發(fā)patch包動態(tài)修復錯誤代碼,但是patch包要提防引入其他問題
  3. 在A/B Test和patch包都無法解決問題的情況下,假如項目采用了合理的組件化設計,通過路由轉發(fā)來使用h6完成應用的正常運行
  4. 缺少動態(tài)修復的手段且crash不干擾正常業(yè)務執(zhí)行,考慮停止一切插件、輔助組件運行
  5. 缺少動態(tài)修復的手段,包括1, 2, 3的方案。可考慮通過第三方越獄市場提供逆向包,提示用戶下載安裝
  6. 缺少動態(tài)修復的手段,包括1, 2, 3的方案。增發(fā)版本快速止損,使用Test Flight分批次快速讓用戶恢復使用

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。

網(wǎng)頁題目:iOS監(jiān)控筆記之啟動crash
文章地址:http://www.chinadenli.net/article28/joihjp.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設計公司動態(tài)網(wǎng)站網(wǎng)站內鏈網(wǎng)站營銷Google企業(yè)建站

廣告

聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司