iOS線上崩潰追蹤

極地冰原(加載圖).jpg

目錄

一奠支、崩潰收集介紹
二、第三方庫(kù)收集崩潰信息
三贵白、原生收集崩潰信息
四率拒、崩潰信息符號(hào)化
五、崩潰中斷攔截

一禁荒、崩潰收集介紹

??App線上崩潰一直都是比較棘手的問(wèn)題猬膨,盡管我們努力在開(kāi)發(fā)與測(cè)試過(guò)程中檢測(cè)與避免崩潰代碼,但依然會(huì)在不同系統(tǒng)呛伴,不同網(wǎng)絡(luò)環(huán)境與不同的使用方式中出現(xiàn)特殊情況勃痴。

??面對(duì)崩潰情況的最好解決方式就是對(duì)崩潰的程序進(jìn)行現(xiàn)場(chǎng)調(diào)試,顯然這很難做到热康。所以我們只能收集App崩潰時(shí)產(chǎn)生的信息沛申,通過(guò)對(duì)崩潰信息的解析來(lái)找到產(chǎn)生崩潰的原因,之后修復(fù)崩潰的代碼并發(fā)布新的版本姐军。

??市面上對(duì)于崩潰信息收集有很多種方案铁材,比如第三方的Bugly友盟奕锌,KSCrash衫贬,plcrashreporter等,蘋果自帶的崩潰收集有crash文件歇攻,crashes固惯。

??本次會(huì)簡(jiǎn)單介紹并對(duì)比不同方案的優(yōu)劣以及其他關(guān)于崩潰收集的知識(shí)。

二缴守、第三方庫(kù)收集崩潰信息

??第三方庫(kù)主要介紹兩種類型葬毫,第一種是騰訊的Bugly友盟的APM。這兩種都是將崩潰信息收集到自己的平臺(tái)進(jìn)行統(tǒng)計(jì)與符號(hào)化屡穗。第二種是KSCrash贴捡,這是一個(gè)開(kāi)源的第三方庫(kù),它可以通過(guò)郵件或上傳的方式將收集到的崩潰信息發(fā)送到內(nèi)部服務(wù)器村砂,從而保證信息的安全性烂斋。

??首先是Bugly友盟的對(duì)比:
??8月1日更新,友盟免費(fèi)版現(xiàn)只能查看當(dāng)天的崩潰記錄,其他時(shí)間的崩潰記錄需要開(kāi)會(huì)員才可以汛骂。

類別 Bugly 友盟(免費(fèi)版) 區(qū)別
最近更新時(shí)間 符號(hào)表工具更新時(shí)間2021-6
sdk更新時(shí)間為2021-4
sdk更新時(shí)間為2022-6 \color{#3CB371}{友盟更新時(shí)間更近一些}
集成方式 pod / sdk pod / sdk \color{#3CB371}{Bugly可以直接導(dǎo)入Bugly}
友盟需要橋接并且導(dǎo)入五個(gè)文件
啟動(dòng)方式 Bugly.start() UMConfigure.initWithAppkey() \color{#3CB371}{兩者啟動(dòng)方式近似}
上傳dSYM方式 通過(guò)命令上傳 在友盟網(wǎng)頁(yè)上傳 Bugly需要jdk1.8環(huán)境才可以上傳
\color{#3CB371}{友盟可以直接在監(jiān)測(cè)頁(yè)面上傳}
崩潰刷新時(shí)間 2分鐘左右 1-10分鐘甚至更久 \color{#3CB371}{Bugly收到新崩潰消息的時(shí)間更短}
崩潰解析內(nèi)容 1.顯示崩潰原因
2.顯示待還原的堆棧名稱
3.顯示頁(yè)面操作流程
1.顯示還原后的堆棧名稱
2.顯示更詳細(xì)的手機(jī)信息
\color{#3CB371}{Bugly崩潰位置需要命令還原}
友盟崩潰原因只能手動(dòng)重現(xiàn)
錯(cuò)誤分類 相同的崩潰不會(huì)出現(xiàn)多條數(shù)據(jù)罕模,只會(huì)增加次數(shù) 相同的崩潰會(huì)出現(xiàn)多條數(shù)據(jù) \color{#3CB371}{Bugly崩潰查詢更清晰}

??通過(guò)兩者的對(duì)比發(fā)現(xiàn),Bugly更擅長(zhǎng)定位錯(cuò)誤原因帘瞭,友盟更擅長(zhǎng)定位錯(cuò)誤代碼位置淑掌。

??我個(gè)人更推薦Bugly,因?yàn)閷?duì)于崩潰信息Bugly可以很清晰的展示出錯(cuò)誤原因蝶念,例如數(shù)組越界抛腕,空指針等。而友盟只能展示崩潰的方法名媒殉,如果代碼過(guò)于復(fù)雜的話還需要多次調(diào)試才可以找到具體的崩潰原因担敌。

??Bugly中想要還原堆棧名稱可以在終端使用命令:

xcrun swift-demangle [待還原的堆棧名稱]

# 例如:
# 未還原的堆棧名稱:
0x0000000100a27a04 
$s9ErrorDemo14ViewControllerC05tableC0_14didSelectRowAtySo07UITableC0C_10Foundation9IndexPathVtFTf4dnn_n

# 還原命令(需要去掉開(kāi)頭的$字符)
xcrun swift-demangle s9ErrorDemo14ViewControllerC05tableC0_14didSelectRowAtySo07UITableC0C_10Foundation9IndexPathVtFTf4dnn_n

# 還原后的堆棧名稱:
ErrorDemo.ViewController.tableView(_: __C.UITableView, didSelectRowAt: Foundation.IndexPath)

# 通過(guò)還原后的堆棧名稱可以看出項(xiàng)目名稱,文件名稱以及崩潰的方法名稱廷蓉。 

??不同于友盟直接上傳dSYM的方式柄错,Bugly上傳dSYM文件需要使用以下命令:

# 0.首先安裝 jdk1.8 環(huán)境(超過(guò)1.8的環(huán)境會(huì)出現(xiàn)無(wú)法打開(kāi)jar文件的問(wèn)題)
# 1.然后進(jìn)入到從 Bugly 官網(wǎng)下載的包含 buglyqq-upload-symbol.jar 文件的文件夾內(nèi)
# 2.在終端執(zhí)行上傳命令
java -jar buglyqq-upload-symbol.jar -appid [appid] -appkey [appkey] -bundleid [bundleid] -version [versionv] -platform IOS -inputSymbol [dSYM文件路徑]

# 例如
java -jar buglyqq-upload-symbol.jar -appid c441111b37 -appkey 36211ca0-1111-1111-1111-9c8b1111535a -bundleid com.fdm.ErrorDemo -version 1.0.2 -platform IOS -inputSymbol /Users/kenan/Desktop/ErrorDemo.app.dSYM

# 3.當(dāng)上傳狀態(tài)為200即代表上傳成功

??接下來(lái)是KSCrash與上面兩種的對(duì)比:

??首先是它們的記錄方式不同Bugly友盟可以通過(guò)手機(jī)信息,崩潰信息苦酱,崩潰類型等進(jìn)行一個(gè)統(tǒng)計(jì)與歸類售貌,方便開(kāi)發(fā)人員進(jìn)行查閱。而KSCrash由于沒(méi)有自己的服務(wù)器疫萤,若需要統(tǒng)計(jì)歸類的話都需要開(kāi)發(fā)人員自行完成颂跨。

??第二是它們的符號(hào)化方式不同Bugly友盟必須要開(kāi)發(fā)人員上傳dSYM文件才可以進(jìn)行正常符號(hào)化。而KSCrash支持在設(shè)備上符號(hào)化扯饶,也就是說(shuō)App在收集到崩潰信息后可以在手機(jī)上進(jìn)行符號(hào)化恒削,然后將符號(hào)化后的崩潰信息存儲(chǔ)在手機(jī)中,上傳時(shí)直接上傳符號(hào)化后的崩潰信息尾序。

??第三是它們的安全性不同Bugly友盟只能將崩潰信息上傳到它們的服務(wù)器中钓丰,而KSCrash可以通過(guò)郵件或上傳的方式發(fā)送到本地的郵箱或服務(wù)器中,一定程度上保障了信息的安全性每币。

??最后是它們的代碼權(quán)限不同Bugly友盟都是閉源的携丁,只能使用其提供的方法。而KSCrash由于是開(kāi)源項(xiàng)目兰怠,可以對(duì)其功能進(jìn)行修改與擴(kuò)展梦鉴,也可以通過(guò)閱讀源碼的方式了解其底層的邏輯。

??對(duì)于崩潰日志的內(nèi)容揭保,KSCrash所收集的崩潰日志與系統(tǒng)自帶分析中的崩潰日志幾乎相同肥橙,并且本地默認(rèn)以JSON字符串的方式對(duì)日志內(nèi)容進(jìn)行存儲(chǔ)。

??總結(jié):

??本次只對(duì)常規(guī)的屬性進(jìn)行了簡(jiǎn)單的崩潰測(cè)試秸侣,對(duì)于更復(fù)雜更詳細(xì)的功能與檢測(cè)方式還需要根據(jù)實(shí)際情況自行查看文檔與測(cè)試存筏。
??個(gè)人認(rèn)為對(duì)于常規(guī)的應(yīng)用使用Bugly友盟足夠宠互,需要更豐富的功能或注重安全性的應(yīng)用可以考慮KSCrash等類似的第三方庫(kù)。如果覺(jué)得這些還不夠用的話也可以考慮使用其他功能更豐富的收費(fèi)平臺(tái)椭坚。

三予跌、原生收集崩潰信息

??原生收集崩潰信息的方式我總結(jié)了三種,分別為:系統(tǒng)crash文件藕溅,crashes工具代碼監(jiān)聽(tīng)继榆。

??1.使用系統(tǒng)crash文件收集:

??這種收集方式主要面向B端產(chǎn)品巾表。在用戶發(fā)生程序崩潰后,產(chǎn)品支持的同學(xué)可以通過(guò)設(shè)置 -> 隱私 -> 分析與改進(jìn)略吨,在分析數(shù)據(jù)的列表中找到對(duì)應(yīng)的APP名稱并打開(kāi)集币,然后點(diǎn)擊右上角的分享按鈕將錯(cuò)誤報(bào)告通過(guò)微信,郵件或其他方式發(fā)送給開(kāi)發(fā)人員翠忠。開(kāi)發(fā)人員拿到該文件后便可以通過(guò)符號(hào)化的方式找到發(fā)生崩潰的具體位置并修復(fù)該問(wèn)題鞠苟。

??這種方式除了收集不方便外還有一個(gè)問(wèn)題,就是系統(tǒng)只會(huì)保存第一次出現(xiàn)的崩潰日志秽之,后續(xù)如果因?yàn)橄嗤脑虮罎⒌庇椋到y(tǒng)不會(huì)創(chuàng)建新的日志,會(huì)出現(xiàn)在尋找崩潰日志時(shí)無(wú)法找到對(duì)應(yīng)日期的問(wèn)題考榨。

??注意:崩潰文件有可能是.ips后綴跨细,也有可能是.crash后綴,實(shí)際上通過(guò)直接修改后綴名的方式可以進(jìn)行轉(zhuǎn)換河质。如果崩潰內(nèi)容是JSON格式可以將后綴名改為.ips冀惭,看起來(lái)會(huì)更直觀。

??2.使用crashes工具收集:

??crashes工具是Xcode自帶的崩潰分析工具掀鹅。使用該工具收集崩潰信息需要用戶手機(jī)開(kāi)啟共享iphone分析功能散休,具體操作流程為設(shè)置 -> 隱私 -> 分析與改進(jìn),開(kāi)啟共享iphone分析乐尊,并開(kāi)啟與App開(kāi)發(fā)者共享選項(xiàng)戚丸。之后應(yīng)用發(fā)生崩潰后,崩潰信息會(huì)自動(dòng)發(fā)送到Apple的服務(wù)器上扔嵌,當(dāng)你進(jìn)入crashes工具時(shí)會(huì)自動(dòng)下載崩潰信息昏滴。缺點(diǎn)是崩潰信息上傳不及時(shí),會(huì)有幾天的延遲对人。

??你可以通過(guò)Xcode中的Window -> Organizer 進(jìn)入打包頁(yè)面谣殊,在左上角選擇需要查看的App,點(diǎn)擊左側(cè)crashes選項(xiàng)即可顯示崩潰列表牺弄。

crashes崩潰列表

??崩潰列表上方為分類選項(xiàng)姻几,可以選擇崩潰時(shí)間,版本號(hào),build版本等蛇捌。點(diǎn)擊一個(gè)崩潰項(xiàng)可以看到具體的崩潰線程以及堆棧信息抚恒。右側(cè)Crash Log Details中展示了本次崩潰的項(xiàng)目名稱,build版本號(hào)络拌,手機(jī)系統(tǒng)俭驮,手機(jī)型號(hào)等信息。下方Statistics顯示了因?yàn)樵撛虮罎⒌脑O(shè)備數(shù)量春贸,開(kāi)發(fā)人員可通過(guò)崩潰設(shè)備數(shù)量來(lái)制定修復(fù)優(yōu)先級(jí)混萝。

??如果你在發(fā)布App時(shí)上傳了dSYM文件,當(dāng)你使用<font color=red>crashes工具</font>時(shí)就會(huì)自動(dòng)符號(hào)化崩潰信息萍恕,并且可以通過(guò)符號(hào)化后的信息快速定位到崩潰代碼逸嘀,我們可以通過(guò)點(diǎn)擊崩潰堆棧右側(cè)的小箭頭或Open in Project...并在彈出的選項(xiàng)中選擇對(duì)應(yīng)的項(xiàng)目名稱即可定位到具體的錯(cuò)誤代碼。【還需要驗(yàn)證】

crashes定位崩潰代碼

??有時(shí)崩潰定位不準(zhǔn)確或崩潰信息未符號(hào)化允粤,我們可以通過(guò)崩潰日志手動(dòng)進(jìn)行符號(hào)化崭倘。只需要在崩潰堆棧處右鍵選擇Show in Finder即可定位到crash崩潰文件。具體符號(hào)化崩潰信息的方式在下一節(jié)类垫。

??3.使用代碼監(jiān)聽(tīng)收集:

??使用代碼監(jiān)聽(tīng)是通過(guò)代碼注冊(cè)異乘竟猓回調(diào)。當(dāng)應(yīng)用程序發(fā)生異常時(shí)優(yōu)先將異常信息保存在本地悉患,并在適當(dāng)?shù)臅r(shí)機(jī)上傳給開(kāi)發(fā)人員飘庄。該方式需要先將開(kāi)發(fā)模式改為Release模式,并且在Edit Scheme中關(guān)閉Debug executable選項(xiàng)购撼。否則在應(yīng)用發(fā)生崩潰時(shí)會(huì)優(yōu)先被Xcode攔截跪削,不會(huì)走監(jiān)聽(tīng)方法。

??在OC項(xiàng)目中可以使用NSSetUncaughtExceptionHandler()方法進(jìn)行異常監(jiān)聽(tīng)迂求,例如當(dāng)程序出現(xiàn)崩潰時(shí)該方法會(huì)在Block中傳入NSException參數(shù)碾盐,通過(guò)打印可以發(fā)現(xiàn)NSException中包含了崩潰時(shí)的錯(cuò)誤堆棧信息,以及崩潰原因等數(shù)據(jù)揩局。

??在Swift項(xiàng)目中毫玖,需要使用objc_setUncaughtExceptionHandler()來(lái)替代NSSetUncaughtExceptionHandler()方法,并且該方法只有在OC代碼產(chǎn)生異沉瓒ⅲ或崩潰時(shí)才會(huì)調(diào)用付枫。而Swift代碼異常需要使用signal()監(jiān)聽(tīng)注冊(cè)異常信號(hào),該方法也會(huì)同時(shí)監(jiān)聽(tīng)OC代碼的崩潰與異常驰怎。


// 崩潰代碼
+ (void) testArrayError {
   NSArray *ary = [NSArray array];
   ary[10];
}

// 監(jiān)測(cè)代碼
objc_setUncaughtExceptionHandler { exception in
   let exception = exception as? NSException
   for text in exception?.callStackSymbols ?? [] {
       print(text)
   }
   print(exception?.name)
   print(exception?.reason)
}

// 崩潰信息打印
============================================================================
0   CoreFoundation                      0x00000001badbd29c 5198FB57-5645-3B34-A49F-F32B52256CF3 + 627356
1   libobjc.A.dylib                     0x00000001d3ab7744 objc_exception_throw + 60
2   CoreFoundation                      0x00000001bae455c4 5198FB57-5645-3B34-A49F-F32B52256CF3 + 1185220
3   ErrorDemo                           0x00000001041bd804 +[TestObject testArrayError] + 60
4   ErrorDemo                           0x00000001041be708 $sIeg_IeyB_TR + 28
5   libdispatch.dylib                   0x00000001baa22e6c 355ACCF4-3917-3730-BC55-EF7003887ABE + 7788
6   libdispatch.dylib                   0x00000001baa24a30 355ACCF4-3917-3730-BC55-EF7003887ABE + 14896
7   libdispatch.dylib                   0x00000001baa27b44 355ACCF4-3917-3730-BC55-EF7003887ABE + 27460
8   libdispatch.dylib                   0x00000001baa36164 355ACCF4-3917-3730-BC55-EF7003887ABE + 86372
9   libdispatch.dylib                   0x00000001baa3696c 355ACCF4-3917-3730-BC55-EF7003887ABE + 88428
10  libsystem_pthread.dylib             0x000000022c680080 _pthread_wqthread + 228
11  libsystem_pthread.dylib             0x000000022c67fe5c start_wqthread + 8
============================================================================
Optional(__C.NSExceptionName(_rawValue: NSRangeException))
============================================================================
Optional("*** -[__NSArray0 objectAtIndex:]: index 10 beyond bounds for empty NSArray")
libc++abi: terminating with uncaught exception of type NSException

??使用signal()可以注冊(cè)多種信號(hào)阐滩,具體的信號(hào)列表可以通過(guò)Darwin.sys.signal文件進(jìn)行查看,其中比較常用的信號(hào)為SIGTRAP县忌,在常規(guī)造成的異车嗬疲或崩潰中一般都會(huì)觸發(fā)SIGTRAP信號(hào)继效。

??signal()方法需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)為信號(hào)標(biāo)識(shí)装获,例如SIGTRAP這種瑞信。第二個(gè)參數(shù)為觸發(fā)異常信號(hào)時(shí)的回調(diào),由于是C語(yǔ)言閉包在使用時(shí)需要增加@convention(c)標(biāo)識(shí)穴豫,之后在閉包中獲取異常堆棧結(jié)構(gòu)樹(shù)并打印凡简。

// 注冊(cè)監(jiān)聽(tīng)
signal(SIGTRAP, handleSignalException)

// 閉包監(jiān)聽(tīng)
let handleSignalException: @convention(c) (Int32) -> Void = { value in
    let pointer = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: 128)

    // backtrace 兩個(gè)方法需要橋接導(dǎo)入頭文件 #import <execinfo.h>
    let frame = backtrace(pointer, 128)
    let strs = backtrace_symbols(pointer, frame)

    for i in 0 ..< Int(frame) {
        let str = strs?[Int(i)]
        print(NSString.init(cString: str!, encoding: String.Encoding.utf8.rawValue) ?? "")
    }

    free(strs)

    // 在監(jiān)聽(tīng)完成后需要取消監(jiān)聽(tīng),否則主線程崩潰的App會(huì)一直處于卡死狀態(tài)精肃,不會(huì)退出秤涩。
    signal(SIGTRAP, SIG_DFL)
}

// 打印內(nèi)容
============================================================================
0   ErrorDemo                           0x000000010227baa8 $s9ErrorDemo11AppDelegateC08registerA0yyFys5Int32VcfU0_Tf4d_n + 108
1   libsystem_platform.dylib            0x000000022c678c10 68A13C2E-80DD-3754-B166-C0A5992A7417 + 7184
2   ???                                 0xffffff810227ab98 0x0 + 18446743528284859288
3   ErrorDemo                           0x000000010227a468 $s9ErrorDemo14ViewControllerC05tableC0_14didSelectRowAtySo07UITableC0C_10Foundation9IndexPathVtFTo + 136
4   UIKitCore                           0x00000001be258518 3ED35565-456D-33CB-B554-6C567FA81585 + 17540376
5   UIKitCore                           0x00000001be257ea8 3ED35565-456D-33CB-B554-6C567FA81585 + 17538728
6   UIKitCore                           0x00000001be258798 3ED35565-456D-33CB-B554-6C567FA81585 + 17541016
7   UIKitCore                           0x00000001bd4c64dc 3ED35565-456D-33CB-B554-6C567FA81585 + 3310812
8   UIKitCore                           0x00000001bd3d9864 3ED35565-456D-33CB-B554-6C567FA81585 + 2340964
9   UIKitCore                           0x00000001bd303074 3ED35565-456D-33CB-B554-6C567FA81585 + 1462388
10  UIKitCore                           0x00000001bd303798 3ED35565-456D-33CB-B554-6C567FA81585 + 1464216
11  UIKitCore                           0x00000001bd6dc1a4 3ED35565-456D-33CB-B554-6C567FA81585 + 5497252
12  UIKitCore                           0x00000001bd97697c 3ED35565-456D-33CB-B554-6C567FA81585 + 8227196
13  UIKitCore                           0x00000001bdffcc48 3ED35565-456D-33CB-B554-6C567FA81585 + 15068232
14  UIKitCore                           0x00000001bdffc410 3ED35565-456D-33CB-B554-6C567FA81585 + 15066128
15  CoreFoundation                      0x00000001baddf414 5198FB57-5645-3B34-A49F-F32B52256CF3 + 766996
16  CoreFoundation                      0x00000001badf01a0 5198FB57-5645-3B34-A49F-F32B52256CF3 + 836000
17  CoreFoundation                      0x00000001bad29694 5198FB57-5645-3B34-A49F-F32B52256CF3 + 22164
18  CoreFoundation                      0x00000001bad2f05c 5198FB57-5645-3B34-A49F-F32B52256CF3 + 45148
19  CoreFoundation                      0x00000001bad42bc8 CFRunLoopRunSpecific + 600
20  GraphicsServices                    0x00000001d6e76374 GSEventRunModal + 164
21  UIKitCore                           0x00000001bd6b2648 3ED35565-456D-33CB-B554-6C567FA81585 + 5326408
22  UIKitCore                           0x00000001bd433d90 UIApplicationMain + 364
23  libswiftUIKit.dylib                 0x00000001d2aafecc $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 104
24  ErrorDemo                           0x000000010227a5e0 main + 108
25  dyld                                0x0000000102835ce4 start + 520

??從打印內(nèi)容可以看出,在ErrorDemo項(xiàng)中也包含了未還原的堆棧名稱肋杖,并且該內(nèi)容與最開(kāi)始Bugly中未還原的堆棧名稱類似(因?yàn)槎际窃邳c(diǎn)擊列表項(xiàng)時(shí)觸發(fā)的崩潰)溉仑。由此可以得出Bugly也抓取到了類似的信息挖函,只是暫時(shí)還無(wú)法得知Bugly是否使用該方式收集的異常信息状植,以及它是如何從該信息中解析出數(shù)組越界等崩潰的具體原因。

??除此之外怨喘,我們?cè)趶牡谌綆?kù)中可以看到崩潰信息的組成中包含了多個(gè)線程的堆棧信息津畸。而我們使用的該方法目前只能打印出觸發(fā)異常線程的堆棧信息,所以我認(rèn)為還有其他方法能拿到其他線程的信息必怜,在查閱其他文章后找到了最有可能的文件 -- Darwin.Mach.task肉拓。

??從文件名可以看出,該文件屬于Mach線程文件梳庆,其中有一個(gè)方法名為task_threads()暖途,該方法的功能為獲取所有的線程列表,正符合目前獲取多個(gè)線程信息的需求膏执。不過(guò)由于時(shí)間原因驻售,對(duì)崩潰收集的調(diào)研只能先暫停在這里,該文件是對(duì)多線程崩潰信息收集很好的一個(gè)切入點(diǎn)更米,后邊可以沿著這條線繼續(xù)摸索下去欺栗。當(dāng)然,閱讀第三方庫(kù)的源碼會(huì)是更好的選擇征峦。

四迟几、崩潰信息符號(hào)化

// 在我們收集的崩潰信息中,未符號(hào)化前的堆棧為十六進(jìn)制碼栏笆。 
// 例如:
0   ErrorDemo 0x0000000100be8234 0x100be0000 + 33332

??符號(hào)化的方式有三種:

??第一種為symbolicatecrash:該方式可以將完整的崩潰信息符號(hào)化类腮,但是會(huì)出現(xiàn)自己項(xiàng)目的堆棧信息未符號(hào)化的情況,iOS14及以下系統(tǒng)可用蛉加。

??第二種為atos:該方式可以只將某一條堆棧單獨(dú)符號(hào)化存哲,但是只能符號(hào)化自己項(xiàng)目的堆棧因宇,無(wú)法符號(hào)化系統(tǒng)堆棧,沒(méi)有系統(tǒng)限制祟偷。

??第三種為CrashSymbolicator.py:可以將完整的崩潰信息全部符號(hào)化(包括系統(tǒng)堆棧與項(xiàng)目堆棧)察滑,并且得到的崩潰信息比symbolicatecrash更詳細(xì),iOS15及以上系統(tǒng)可用修肠。

??注:三種方式都需要與之對(duì)應(yīng)的dSYM文件贺辰,由于每次編譯都會(huì)創(chuàng)建新的dSYM文件,所以一定要在編譯或打包后將dSYM文件存好嵌施。并且不同系統(tǒng)的崩潰信息需要使用對(duì)應(yīng)的方法進(jìn)行符號(hào)化饲化,否則可能會(huì)出現(xiàn)失敗的情況。

??dSYM文件的獲取方式:

??對(duì)于未打包的項(xiàng)目獲取dSYM文件可以通過(guò)項(xiàng)目目錄中的Products -> [項(xiàng)目名稱].app -> Show in Finder即可找到[項(xiàng)目名稱].app.DSYM文件吗伤。

??對(duì)于打包完成的項(xiàng)目通過(guò)對(duì)打包后的Archives項(xiàng)目Show in Finder -> 顯示包內(nèi)容 -> dSYMs即可找到[項(xiàng)目名稱].app.DSYM文件吃靠。

??這里需要注意的是默認(rèn)情況下只有Release模式才產(chǎn)生dSYM文件。如果想要Debug模式下也產(chǎn)生dSYM文件的話足淆,需要在Build Settings -> Debug Information Format中將DWARF改為DWARF with dSYM File巢块。

??1.使用symbolicatecrash符號(hào)化(iOS14及以下系統(tǒng)可用):

??首先我們需要找到symbolicatecrash文件:

// 該文件一般在該路徑下
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash

// 如果在上面路徑找不到的話可以通過(guò)命令進(jìn)行查找。
find /Applications/Xcode.app -name symbolicatecrash -type f

??在找到symbolicatecrash文件后巧号,隨意新建一個(gè)文件夾族奢。將該文件copy一份與dSYM文件和crash文件同放在新建的文件夾內(nèi)。

// 首先打開(kāi) .bash_profile 文件丹鸿,添加環(huán)境變量
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

// 保存并執(zhí)行命令
source ~/.bash_profile

// 然后cd到新建的文件夾內(nèi)
cd xxx

// 使用命令進(jìn)行符號(hào)化
./symbolicatecrash [crash文件或ips文件] [dSYM文件] > [符號(hào)化后的新文件名稱.crash]

// 例如
./symbolicatecrash ErrorTest.crash ErrorTest.app.dSYM > newCrash.crash

// 符號(hào)化后的部分信息
============================================================================
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   ErrorTest                       0x0000000100b64e50 0x100b60000 + 20048
1   UIKit                           0x000000018bf3183c forwardTouchMethod + 340
2   UIKit                           0x000000018bdd7760 -[UIResponder touchesBegan:withEvent:] + 60
3   UIKit                           0x000000018bdd17c8 -[UIWindow _sendTouchesForEvent:] + 1892
4   UIKit                           0x000000018bdc6890 -[UIWindow sendEvent:] + 3160
5   UIKit                           0x000000018bdc51d0 -[UIApplication sendEvent:] + 340
6   UIKit                           0x000000018c5a6d1c __dispatchPreprocessedEventFromEventQueue + 2340
7   UIKit                           0x000000018c5a92c8 __handleEventQueueInternal + 4744
8   UIKit                           0x000000018c5a2368 __handleHIDEventFetcherDrain + 152
9   CoreFoundation                  0x0000000181f8b404 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
10  CoreFoundation                  0x0000000181f8ac2c __CFRunLoopDoSources0 + 276
11  CoreFoundation                  0x0000000181f8879c __CFRunLoopRun + 1204
12  CoreFoundation                  0x0000000181ea8da8 CFRunLoopRunSpecific + 552
13  GraphicsServices                0x0000000183e8d020 GSEventRunModal + 100
14  UIKit                           0x000000018bec5758 UIApplicationMain + 236
15  libswiftUIKit.dylib             0x0000000100c9d468 0x100c94000 + 37992
16  ErrorTest                       0x0000000100b65004 0x100b60000 + 20484
17  libdyld.dylib                   0x0000000181939fc0 start + 4

??從符號(hào)化后的堆棧信息中可以發(fā)現(xiàn)ErrorTest項(xiàng)目堆棧并未符號(hào)化越走,需要再次使用atos進(jìn)行符號(hào)化。

??如果執(zhí)行命令報(bào)錯(cuò):

??錯(cuò)誤一:

Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 75.

// 需要設(shè)置環(huán)境變量靠欢,并執(zhí)行 source ~/.bash_profile

??錯(cuò)誤二:

No crash report version in [文件名].crash at ./symbolicatecrash line 1371.

// 該崩潰信息可能需要使用 CrashSymbolicator.py 進(jìn)行解析

??2.使用atos符號(hào)化(無(wú)系統(tǒng)限制):

# 例如
0   ErrorDemo                              0x1001f4660 0x1001ec000 + 34400
1   ErrorDemo                              0x1001f4504 0x1001ec000 + 34052
2   ErrorDemo                              0x1001f41c8 0x1001ec000 + 33224

# 注意這里的路徑為dSYM文件顯示包內(nèi)容后的項(xiàng)目路徑
atos -o /ErrorDemo.app.dSYM/Contents/Resources/DWARF/ErrorDemo -l 0x1001ec000 0x1001f4660

# 其中 0x1001ec000 為起始地址廊敌,0x1001f4660為偏移地址。注意順序C殴帧B獬骸!

# 符號(hào)化后
specialized ViewController.tableView(_:didSelectRowAt:) (in ErrorDemo) (ViewController.swift:66)

??3.使用CrashSymbolicator.py符號(hào)化(iOS15及以上系統(tǒng)可用):

??該方式與symbolicatecrash方式類似薪缆,首先需要找到CrashSymbolicator.py文件:

# 該文件一般在該路徑下
/Applications/Xcode.app/Contents/SharedFrameworks/CoreSymbolicationDT.framework/Versions/A/Resources/CrashSymbolicator.py

# 如果在上面路徑找不到的話可以通過(guò)命令進(jìn)行查找秧廉。
find /Applications/Xcode.app -name CrashSymbolicator -type f

??在找到該文件后不要將它c(diǎn)opy到其他文件夾內(nèi),否則會(huì)出現(xiàn)找不到頭文件的問(wèn)題拣帽。

# 執(zhí)行命令
python3 [CrashSymbolicator.py文件路徑] -d [dSYM文件路徑] -o [符號(hào)化后的新文件名稱.ips] -p [崩潰信息ips文件路徑]

# 例如
python3 /Applications/Xcode.app/Contents/SharedFrameworks/CoreSymbolicationDT.framework/Versions/A/Resources/CrashSymbolicator.py -d /Users/kenan/Desktop/15.0.2/ErrorDemo.app.dSYM -o newCrash.ips -p /Users/kenan/Desktop/15.0.2/ErrorDemo.ips

# 符號(hào)化后的部分信息
============================================================================
Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   ErrorDemo                              0x1001f4660 Swift runtime failure: Index out of range + 0 (ViewController.swift:0) [inlined]
1   ErrorDemo                              0x1001f4660 specialized _ArrayBuffer._checkInoutAndNativeTypeCheckedBounds(_:wasNativeTypeChecked:) + 0 (<compiler-generated>:0) [inlined]
2   ErrorDemo                              0x1001f4660 specialized Array._checkSubscript(_:wasNativeTypeChecked:) + 0 (<compiler-generated>:0) [inlined]
3   ErrorDemo                              0x1001f4660 specialized Array.subscript.getter + 0 (<compiler-generated>:0) [inlined]
4   ErrorDemo                              0x1001f4660 ViewController.arrayError() + 0 (ViewController.swift:84) [inlined]
5   ErrorDemo                              0x1001f4660 specialized ViewController.tableView(_:didSelectRowAt:) + 456 (ViewController.swift:66)
6   ErrorDemo                              0x1001f4504 specialized ViewController.tableView(_:didSelectRowAt:) + 107 (<compiler-generated>:0)
7   ErrorDemo                              0x1001f41c8 ViewController.tableView(_:didSelectRowAt:) + 11 (<compiler-generated>:0) [inlined]
8   ErrorDemo                              0x1001f41c8 @objc ViewController.tableView(_:didSelectRowAt:) + 135 (<

··· ···

24  ErrorDemo                              0x1001f2c14 specialized static UIApplicationDelegate.main() + 79 (LoadAnimationView_01.swift:0) [inlined]
25  ErrorDemo                              0x1001f2c14 static AppDelegate.$main() + 91 (<compiler-generated>:16) [inlined]
26  ErrorDemo                              0x1001f2c14 main + 107 (LoadAnimationView_01.swift:0)
27  dyld                                   0x1005b4190 start + 443

??從符號(hào)化后的堆棧信息可以看出疼电,本次崩潰原因?yàn)?code>Swift runtime failure: Index out of range,崩潰的位置為ViewController.swift文件减拭。崩潰的操作為ViewController.tableView(_:didSelectRowAt:)蔽豺。

五、崩潰中斷攔截

??崩潰中斷攔截是在代碼監(jiān)聽(tīng)的基礎(chǔ)上拧粪,在崩潰前進(jìn)行某些操作修陡,完成后再退出應(yīng)用沧侥。有同學(xué)表示這種操作可能會(huì)導(dǎo)致APP崩潰后無(wú)法再次啟動(dòng),目前雖無(wú)法證實(shí)這種說(shuō)法魄鸦,但還是不太建議使用宴杀,該部分屬于擴(kuò)展知識(shí)。

??實(shí)現(xiàn)中斷攔截首先是注冊(cè)signal()信號(hào)并實(shí)現(xiàn)它的閉包方法拾因,當(dāng)程序發(fā)生崩潰時(shí)會(huì)調(diào)用handleSignalException閉包旺罢。這時(shí)可以將錯(cuò)誤堆棧打印到控制臺(tái),由于在代碼監(jiān)聽(tīng)部分演示過(guò)绢记,這里就不再添加這部分代碼了扁达。

??這里我們?cè)贏ppDelegate外邊定義了一個(gè)全局變量appDelegate,因?yàn)?code>handleSignalException是一個(gè)c轉(zhuǎn)義閉包蠢熄,我們沒(méi)有辦法在其中直接獲取或使用局部變量跪解,所以需要定義一個(gè)全局變量進(jìn)行操作(也許還有其他方法可以實(shí)現(xiàn),如果你找到了請(qǐng)告訴我)签孔。

??之后我們?cè)陂]包中調(diào)用一個(gè)外部方法demo()叉讥,在該方法里我們做一些其他操作。


// 全局變量
var appDelegate: AppDelegate?

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    // 狀態(tài)控制
    var state = true

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        appDelegate = self

        // 回調(diào)方法
        let handleSignalException: @convention(c) (Int32) -> Void = { value in
            appDelegate?.demo()
        }

        // 注冊(cè)各種信號(hào)
        signal(SIGSEGV, handleSignalException)
        signal(SIGFPE, handleSignalException)
        signal(SIGBUS, handleSignalException)
        signal(SIGPIPE, handleSignalException)
        signal(SIGHUP, handleSignalException)
        signal(SIGINT, handleSignalException)
        signal(SIGQUIT, handleSignalException)
        signal(SIGABRT, handleSignalException)
        signal(SIGILL, handleSignalException)
        signal(SIGTRAP, handleSignalException)
                
        return true
    }

    /// 取消監(jiān)聽(tīng)
    /// 如果是主線程崩潰骏啰,只有執(zhí)行了該方法應(yīng)用程序才會(huì)退出节吮,否則程序會(huì)一直處于卡死狀態(tài)抽高,會(huì)不斷的回調(diào) handleSignalException 
    func unRegisterError() {
        signal(SIGSEGV, SIG_DFL)
        signal(SIGFPE, SIG_DFL)
        signal(SIGBUS, SIG_DFL)
        signal(SIGPIPE, SIG_DFL)
        signal(SIGHUP, SIG_DFL)
        signal(SIGINT, SIG_DFL)
        signal(SIGQUIT, SIG_DFL)
        signal(SIGABRT, SIG_DFL)
        signal(SIGILL, SIG_DFL)
        signal(SIGTRAP, SIG_DFL)
    }

    func demo() {
        // do something
    }
}


??例如判耕,我們希望在應(yīng)用程序崩潰后直接將崩潰信息上傳到我們的服務(wù)器,在上傳完成后再退出應(yīng)用翘骂。

??首先我們需要獲取當(dāng)前崩潰線程的Runloop以及它的所有Mode壁熄,然后發(fā)送網(wǎng)絡(luò)請(qǐng)求,當(dāng)請(qǐng)求完成后更改state的狀態(tài)碳竟。在此期間強(qiáng)制Runloop運(yùn)行防止程序退出草丧,這時(shí)該線程會(huì)陷入死循環(huán),卡在While的位置莹桅。一直等到state狀態(tài)修改為false昌执,才會(huì)執(zhí)行unRegisterError()方法退出程序。

func demo() {
    // 1. 獲取 Runloop 與 modes
    let runloop = CFRunLoopGetCurrent()
    let modes = CFRunLoopCopyAllModes(runloop) as? NSArray

    // 2. 運(yùn)行網(wǎng)絡(luò)請(qǐng)求

    var request = URLRequest(url: URL(string: "https://www.baidu.com/")!)
    request.httpMethod = "GET"

    let session = URLSession.shared
    let task = session.dataTask(with: request) { data, response, error in
        if let data = data {
            print(String(data: data, encoding: .utf8))
        }
        
        // 請(qǐng)求完成修改狀態(tài)
        self.state = false
    }

    task.resume()


    // 3. 運(yùn)行防止程序退出
    while (state) {
        for model in modes ?? [] {
            let current = model as? NSString ?? ""
            CFRunLoopRunInMode(.init(rawValue:current), 0.001, false)
        }
    }

    // 4. 取消監(jiān)聽(tīng)诈泼,退出應(yīng)用
    unRegisterError()
}

??這樣我們就實(shí)現(xiàn)了在崩潰時(shí)進(jìn)行網(wǎng)絡(luò)請(qǐng)求的功能懂拾。不過(guò)由于當(dāng)前程序已經(jīng)崩潰,應(yīng)用也會(huì)卡死造成用戶操作無(wú)反應(yīng)铐达。這時(shí)過(guò)長(zhǎng)時(shí)間的操作可能會(huì)使用戶強(qiáng)制退出應(yīng)用岖赋,導(dǎo)致我們的操作都無(wú)法完成。所以除了網(wǎng)絡(luò)請(qǐng)求外瓮孙,我們也可以再崩潰時(shí)給用戶一個(gè)彈窗唐断,告訴用戶程序已經(jīng)崩潰正在收集崩潰信息选脊∪裱模或者我們也可以添加一個(gè)可以其他按鈕矮慕,點(diǎn)擊后退出也可以。

func demo() {
    // 1. 獲取 Runloop 與 modes
    let runloop = CFRunLoopGetCurrent()
    let modes = CFRunLoopCopyAllModes(runloop) as? NSArray

    // 2. 運(yùn)行網(wǎng)絡(luò)請(qǐng)求

    var request = URLRequest(url: URL(string: "https://www.baidu.com/")!)
    request.httpMethod = "GET"

    let session = URLSession.shared
    let task = session.dataTask(with: request) { data, response, error in
        if let data = data {
            print(String(data: data, encoding: .utf8))
        }
        
        // 請(qǐng)求完成修改狀態(tài)
        self.state = false
    }

    task.resume()

    // 2.1 顯示提示信息
    let alertLabel = UILabel()
    alertLabel.backgroundColor = .red
    alertLabel.frame = .init(x: 100, y: 100, width: 100, height: 100)
    alertLabel.text = "程序出現(xiàn)異常星瘾,正在收集錯(cuò)誤信息丹诀,請(qǐng)不要退出"
    window?.addSubview(alertLabel)

    // 3. 運(yùn)行防止程序退出
    // 長(zhǎng)時(shí)間的while會(huì)導(dǎo)致 Cpu 發(fā)熱
    while (state) {
        for model in modes ?? [] {
            let current = model as? NSString ?? ""
            CFRunLoopRunInMode(.init(rawValue:current), 0.001, false)
        }
    }

    // 4. 取消監(jiān)聽(tīng)角寸,退出應(yīng)用
    unRegisterError()
}

??不過(guò)顯示UI的方法并不是所有手機(jī)都可以,經(jīng)過(guò)測(cè)試13 Pro 15.5(能測(cè)到的最好的手機(jī)了) 的真機(jī)會(huì)出現(xiàn)View顯示不出來(lái)的情況忿墅,需要進(jìn)入后臺(tái)重新進(jìn)入應(yīng)用才會(huì)顯示彈窗扁藕。但是相同系統(tǒng)型號(hào)的模擬器就可以,其他幾個(gè)系統(tǒng)和不同型號(hào)的真機(jī)也都可以(13 Pro max 不清楚)疚脐。

??最開(kāi)始猜測(cè)是程序崩潰后刷新率變?yōu)?導(dǎo)致UI不刷新亿柑。然后在頁(yè)面上加了個(gè)持續(xù)旋轉(zhuǎn)的動(dòng)畫,發(fā)現(xiàn)崩潰后動(dòng)畫還在轉(zhuǎn)但是彈窗依然沒(méi)有出現(xiàn)棍弄,證明并不是屏幕沒(méi)有刷新望薄。所以目前也還沒(méi)有找到原因,感覺(jué)應(yīng)該和刷新率有關(guān)呼畸。不過(guò)這也只是擴(kuò)展知識(shí)痕支,不建議大家再項(xiàng)目中做過(guò)于復(fù)雜的操作,程序崩潰時(shí)本身就處于一個(gè)不穩(wěn)定的狀態(tài)蛮原,在做其他操作可能會(huì)導(dǎo)致意想不到的Bug卧须。

??以上就是iOS崩潰追蹤的全部?jī)?nèi)容了,涉及的內(nèi)容比較雜也并沒(méi)有研究很深的技術(shù)儒陨,了解一下就可以了花嘶。本文出于學(xué)習(xí)與記錄的目的,如有不嚴(yán)謹(jǐn)?shù)牡胤竭€請(qǐng)?zhí)岢鲂薷摹?/strong>

??????????如果覺(jué)得有用就點(diǎn)個(gè)贊吧蹦漠,你的贊是我最大的動(dòng)力??????????

解釋
① 符號(hào)化:符號(hào)化是指通過(guò)命令將dSYM文件與崩潰信息進(jìn)行對(duì)比與解析椭员,將十六進(jìn)制碼轉(zhuǎn)換為可讀的錯(cuò)誤代碼。
② dSYM:dSYM是App編譯后的符號(hào)表文件笛园,需要使用對(duì)應(yīng)的dSYM文件才可以解析錯(cuò)誤堆棧信息隘击。
③ Mach:Mach是一個(gè)用于支持操作系統(tǒng)研究的操作系統(tǒng)內(nèi)核,在iOS系統(tǒng)的內(nèi)核中混合使用了該內(nèi)核研铆。

參考
iOS/OSX Crash:崩潰日志報(bào)告
iOS 崩潰符號(hào)化工具- iOS 15 CrashSymbolicator
iOS獲取任意線程調(diào)用堆棧信息
iOS dSYM詳解和分析crash埋同,ips文件
iOS堆棧信息解析(函數(shù)地址與符號(hào)關(guān)聯(lián))
iOS Crash從捕獲到符號(hào)化解析分析
iOS中線程Call Stack的捕獲和解析(二)
iOS/OSX Crash:捕捉異常
iOS異常處理-signal信號(hào)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蚜印,隨后出現(xiàn)的幾起案子莺禁,更是在濱河造成了極大的恐慌,老刑警劉巖窄赋,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哟冬,死亡現(xiàn)場(chǎng)離奇詭異楼熄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)浩峡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門可岂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人翰灾,你說(shuō)我怎么就攤上這事缕粹。” “怎么了纸淮?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵平斩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我咽块,道長(zhǎng)绘面,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任侈沪,我火速辦了婚禮揭璃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亭罪。我一直安慰自己瘦馍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布应役。 她就那樣靜靜地躺著情组,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扛吞。 梳的紋絲不亂的頭發(fā)上呻惕,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天荆责,我揣著相機(jī)與錄音滥比,去河邊找鬼。 笑死做院,一個(gè)胖子當(dāng)著我的面吹牛盲泛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播键耕,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼寺滚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了屈雄?” 一聲冷哼從身側(cè)響起村视,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酒奶,沒(méi)想到半個(gè)月后蚁孔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奶赔,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年杠氢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了站刑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鼻百,死狀恐怖绞旅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情温艇,我是刑警寧澤因悲,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站勺爱,受9級(jí)特大地震影響囤捻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邻寿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一蝎土、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绣否,春花似錦誊涯、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至段磨,卻和暖如春取逾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苹支。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工砾隅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人债蜜。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓晴埂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親寻定。 傳聞我的和親對(duì)象是個(gè)殘疾皇子儒洛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 一、獲取 Crash狼速、dSYM 文件 獲取到的 .ips 改后綴為 .crash 即可 真機(jī) Crash 文件目錄...
    midmirror閱讀 9,452評(píng)論 0 31
  • 收集crash日志方式 1.設(shè)備上直接查看 2.xcode獲取設(shè)備上信息 3.xcode獲取發(fā)布版本崩潰信息 下圖...
    皮皮蟹pipixie閱讀 8,590評(píng)論 1 11
  • 該文章屬于劉小壯原創(chuàng)琅锻,轉(zhuǎn)載請(qǐng)注明:劉小壯[http://www.reibang.com/u/2de707c93d...
    劉小壯閱讀 37,563評(píng)論 45 122
  • 前言 iOS崩潰是讓iOS開(kāi)發(fā)人員比較頭痛的事情,app崩潰了,說(shuō)明代碼寫的有問(wèn)題恼蓬,這時(shí)如何快速定位到崩潰的地方很...
    齊滇大圣閱讀 65,318評(píng)論 29 443
  • 挺久沒(méi)寫簡(jiǎn)書了沫浆,這邊是比較早的時(shí)候?qū)懙墓P記這邊就給整理整理發(fā)到簡(jiǎn)書上也比較規(guī)范點(diǎn)。網(wǎng)上看到關(guān)于日志解析的帖子也很多...
    走著走著就會(huì)敲代碼了閱讀 2,610評(píng)論 0 7