iOS 的崩潰捕獲-堆棧符號(hào)化-崩潰分析

一、獲取 Crash、dSYM 文件

獲取到的 .ips 改后綴為 .crash 即可

  • 真機(jī) Crash 文件目錄:var/mobile/Library/Logs/CrashReporter

    通過(guò) iTunes 同步后在 macOS 目錄:~/Library/Logs/CrashReporter/MobileDevice/

  • 在 iOS 設(shè)備上直接查看:設(shè)置 -> 隱私 -> 分析 -> 分析數(shù)據(jù)(不同系統(tǒng)版本不一樣)

  • macOS Archives 目錄(.dSYM 和 .app):~/Library/Developer/Xcode/Archives

  • 通過(guò) iTunes Connect :Manage Your Applications -> View Details -> Crash Reports

    需要用戶(hù)在設(shè)置->隱私里同意共享診斷數(shù)據(jù)

    使用 bitcode 編譯成中間碼上傳的,本地不會(huì)留下 dSYM,而需要從 iTunes Connect 或者 Xcode 下載 dSYM(編譯成機(jī)器碼才能生成)

  • 通過(guò) Xcode:Xcode -> Window -> Devices and Simulators -> 選中設(shè)備 -> View Device Logs

  • 通過(guò) Xcode 直接查看:Xcode -> Window -> Organizer -> Crashes

    上傳到App Store的時(shí)候,同時(shí)上傳dsym文件,那么從Xcode中的 Crash 會(huì)自動(dòng)符號(hào)化。

  • 通過(guò) iTools -> 工具箱 -> 崩潰日志 -> 在以下路徑查看

  • # Mac
    ~/Library/Logs/CrashReporter/MobileDevice/<DEVICE_NAME>
    # Windows
    C://Users/<USER_NAME>/AppDataRoamingApple/ComputerLogsCrashReporterMobileDevice/<DEVICE_NAME>/
    
  • 第三方 Crash 統(tǒng)計(jì)庫(kù):KSCrash、plcrashReporter、CrashKit

  • 第三方 Crash 統(tǒng)計(jì)服務(wù):Crashlytics、Hockeyapp、友盟、Bugly

    注意:

    • 最好只集成一個(gè) Crash 統(tǒng)計(jì)服務(wù),當(dāng)各家的服務(wù)都以保證自己的Crash統(tǒng)計(jì)正確完整為目的時(shí),難免出現(xiàn)時(shí)序手腳,強(qiáng)行覆蓋等等的惡意競(jìng)爭(zhēng)。
    • 應(yīng)用層參與收集 Crash日志的服務(wù)方越多,越有可能影響iOS系統(tǒng)自帶的 Crash Reporter。

二、Crash 符號(hào)化(Symbolicating crash logs)

symbols 和 Symbolicate

symbols 就是函數(shù)名或變量名。符號(hào)化的過(guò)程就是把 crash log 中的內(nèi)存地址轉(zhuǎn)化為相應(yīng)的函數(shù)調(diào)用關(guān)系。

一般來(lái)說(shuō),debug 模式構(gòu)建的 app 會(huì)把符號(hào)表存儲(chǔ)在編譯好的 binary 信息中,而 release 模式構(gòu)建的app會(huì)把符號(hào)表存儲(chǔ)在 dSYM 文件中以節(jié)省體積。

系統(tǒng)庫(kù)符號(hào)化文件

每當(dāng) Xcode 連接一臺(tái)從未在當(dāng)前電腦調(diào)試過(guò)的 iOS 版本的設(shè)備時(shí),都會(huì)花一段時(shí)間把手機(jī)的系統(tǒng)庫(kù)符號(hào)化文件自動(dòng)導(dǎo)入到 ~/Library/Developer/Xcode/iOS DeviceSupport,這個(gè)過(guò)程叫 Processing symbol files。每個(gè)系統(tǒng)版本的 symbols 文件約占 2GB,所以這個(gè)文件夾會(huì)占用不少磁盤(pán)空間。但是,最好將這些內(nèi)容備份到外置硬盤(pán),需要符號(hào)化的時(shí)候再重新拷貝回來(lái),而不是使用清理工具清理掉。因?yàn)?,系統(tǒng)符號(hào)化文件的獲取沒(méi)有那么容易。

系統(tǒng)庫(kù)符號(hào)文件不是通用的,而是對(duì)應(yīng)crash所在設(shè)備的系統(tǒng)版本和CPU型號(hào)的。獲取系統(tǒng)符號(hào)化文件的兩大方式就是通過(guò)真機(jī),或者通過(guò)各版本 Xcode 附帶,蘋(píng)果官方?jīng)]有提供任何下載方式。有技術(shù)員總結(jié)了搜集方式,并給出了 github 下載方式,可查看附錄。

通過(guò) Xcode 符號(hào)化

需要3個(gè)文件,放在同一目錄下

  • crash報(bào)告(.crash文件)
  • Debug Symbol 符號(hào)文件 (.dsym文件)
  • 解壓 ipa 包后的 .app 文件

操作過(guò)程:Xcode -> Devices and Simulators -> 選中設(shè)備 -> View Device Logs

然后把 .crash文件 拖到 Device Logs 或者選擇下面的import導(dǎo)入.crash文件。這樣你就可以看到crash的詳細(xì)log了。

通過(guò)命令行工具 symbolicatecrash 符號(hào)化
  • 將 .app、.dSYM、.crash 文件放到同一個(gè)目錄下。
# 找到 symbolicatecrash 工具并拷貝出來(lái)
find /Applications/Xcode.app -name symbolicatecrash -type f
# 會(huì)返回幾個(gè)路徑,拷貝其中一個(gè)
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash

# 引入環(huán)境變量
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
# 符號(hào)解析
./symbolicatecrash appName.crash .dSYM文件路徑 > appName.log
./symbolicatecrash appName.crash appName.app > appName.log
# 將符號(hào)化的 crash log 保存在 appName.log 中
./symbolicatecrash appName.crash appName.app > appName.log
通過(guò)命令行工具 atos 符號(hào)化

有多個(gè) .app、.dSYM、.crash 的時(shí)候很好用。用于符號(hào)化單個(gè)地址(可使用腳本批量化)。

每一個(gè)可執(zhí)行程序都有一個(gè)build UUID來(lái)唯一標(biāo)識(shí)(每次 build 都不同)。Crash日志包含發(fā)生crash的這個(gè)應(yīng)用(app)的 build UUID以及crash發(fā)生的時(shí)候,應(yīng)用加載的所有庫(kù)文件的[build UUID]。

# 獲取 crash 文件的 UUID
grep "appName armv" *crash
# 或者
grep --after-context=2 "Binary Images:" *crash

# 獲取 app 的 UUID
xcrun dwarfdump --uuid appName.app/appName
# 獲取 dSYM 的 UUID
xcrun dwarfdump --uuid appName.dSYM

# 對(duì)比 app 和 crash 的 UUID 進(jìn)行匹配

# 用 atos 命令來(lái)符號(hào)化某個(gè)特定模塊加載地址 (3種方式都可以)
# 0x4000 是模塊的加載地址(必須是DWARF文件地址,而不是dSYM地址,dSYM只是一個(gè)bundle)
xcrun atos -o appName.app.dSYM/Contents/Resources/DWARF/appName -l 0x4000 -arch armv7
xcrun atos -o appName.app.dSYM/Contents/Resources/DWARF/appName -arch armv7
xcrun atos -o appName.app/appName -arch armv7

# 另外,應(yīng)用內(nèi) 獲取 UUID 的方法

#import <mach-o/ldsyms.h>
NSString *executableUUID() {
    const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
    for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
        if (((const struct load_command *)command)->cmd == LC_UUID) {
            command += sizeof(struct load_command);
            return [NSString stringWithFormat:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                    command[0], command[1], command[2], command[3],
                    command[4], command[5],
                    command[6], command[7],
                    command[8], command[9],
                    command[10], command[11], command[12], command[13], command[14], command[15]];
        } else {
            command += ((const struct load_command *)command)->cmdsize;
        }
    }
    return nil;
}

# 通過(guò) iTunes Connect 網(wǎng)站來(lái)下載 dSYM 的話,對(duì)下載下來(lái)的每個(gè) dSYM 文件都執(zhí)行一次
xcrun dsymutil -symbol-map ~/Library/Developer/Xcode/Archives/[...]/BCSymbolMaps [UUID].dSYM

示例:

# 有兩行未符號(hào)化的 crash log
* 3 appName 0x000f462a 0x4000 + 984618 
* 4 appName **0x00352aee** 0x4000 + 3468014

# 1. 執(zhí)行
xcrun atos -o appName.app.dSYM/Contents/Resources/DWARF/appName -l 0x4000 -arch armv7
# 2. 然后輸入 0x00352aee
# 3. 符號(hào)化結(jié)果:
-[UIScrollView(UITouch) touchesEnded:withEvent:] (in appName) (UIScrollView+UITouch.h:26)
注意:
  • 使用 symbolicatecrash,先拷貝出 symbolicatecrash 文件比較方便。
  • 無(wú)論是 symbolicatecrash 還是 atos,都只需要.crash.dSYM ,或 .crash.app,就可以符號(hào)化了。
  • 系統(tǒng)方法的堆棧符號(hào)化需要系統(tǒng)符號(hào)化文件,如果本地 macOS 沒(méi)有該文件,也沒(méi)有該版本 iOS 設(shè)備可拷貝,可通過(guò) Github iOS-System-Symbols(iOS 各版本系統(tǒng)符號(hào)庫(kù)) 下載,

三、Crash 文件結(jié)構(gòu)

1. Process Information(進(jìn)程信息)
Incident Idnetifier 崩潰報(bào)告的唯一標(biāo)識(shí)符,不同的Crash
CrashReporter Key 設(shè)備的 id(不是 uuid)。通常同一個(gè)設(shè)備上同一版本的 app 發(fā)生Crash時(shí),該值都是一樣的。
Hardware Model 設(shè)備類(lèi)型
Process 進(jìn)程名稱(chēng)[進(jìn)程 id],進(jìn)程通常是 app 名字
Path 可執(zhí)行程序的位置
Identifier com.companyName.appName
Version app 版本號(hào)
Code Type CPU 架構(gòu)
Parent Process 父進(jìn)程,iOS中App通常都是單進(jìn)程的,一般父進(jìn)程都是 launchd
2. Basic Information(基本信息)
Date/Time Crash發(fā)生的時(shí)間,可讀的字符串
OS Version 系統(tǒng)版本(build 號(hào))
Report Version Crash日志的格式,目前基本上都是104,不同的version里面包含的字段可能有不同
3. Exception(異常)
Exception Type 異常類(lèi)型
Exception Subtype: 異常子類(lèi)型
Crashed Thread 發(fā)生異常的線程號(hào)
Exception Information 額外診斷信息

從macOS Sierra, iOS 10, watchOS 3, 和 tvOS 10開(kāi)始,額外診斷信息,包括:

  1. 應(yīng)用的具體信息:在進(jìn)程被終止前捕捉到的框架錯(cuò)誤信息

  2. 內(nèi)核信息:關(guān)于代碼簽名問(wèn)題的細(xì)節(jié)

  3. Dyld (動(dòng)態(tài)鏈接庫(kù))錯(cuò)誤信息:被動(dòng)態(tài)鏈接器提交的錯(cuò)誤信息

# 一段因?yàn)檎也坏芥溄訋?kù)而導(dǎo)致進(jìn)程被終止的Crash Report的摘錄
Dyld Error Message:

Dyld Message: Library not loaded: @rpath/MyCustomFramework.framework/MyCustomFramework

 Referenced from: /private/var/containers/Bundle/Application/CD9DB546-A449-41A4-A08B-87E57EE11354/TheElements.app/TheElements  
 Reason: no suitable image found.
 
 # 一段因?yàn)闆](méi)能快速加載初始view controller而導(dǎo)致進(jìn)程被終止的Crash Report的摘錄
Application Specific Information:
com.example.apple-samplecode.TheElements failed to scene-create after 19.81s (launch took 0.19s of total time limit 20.00s)
Elapsed total CPU time (seconds): 7.690 (user 7.690, system 0.000), 19% CPU
Elapsed application CPU time (seconds): 0.697, 2% CPU
4. Thread Backtrace(線程回溯)

Crash 發(fā)生時(shí)的線程的調(diào)用棧,沒(méi)有符號(hào)化前是內(nèi)存地址。

5. Thread State(線程狀態(tài))

Crash 發(fā)生時(shí)的寄存器狀態(tài)。在你讀一個(gè) Crash Report 的時(shí)候,了解線程狀態(tài)并非必須,但是如果你想更好地了解crash的細(xì)節(jié),這會(huì)起一些幫助,這需要一些處理器硬件只是和匯編知識(shí)的儲(chǔ)備。

LLDB與匯編調(diào)試-提高你的調(diào)試效率

6. Binary Images(二進(jìn)制映像)

Crash 發(fā)生時(shí) app 可執(zhí)行文件、加載的所有系統(tǒng)庫(kù)和第三方庫(kù)。

 # app 可執(zhí)行文件 Elephant
 0x104e80000 -        0x107b2bfff +Elephant arm64  <38c058044caa34818a83d88981986fad> /var/containers/Bundle/Application/5694FC83-018E-46E7-B060-A008867D3C9D/Elephant.app/Elephant
 
 # WCDB 可執(zhí)行文件。b512f6d343e73a0db1bcb499d2597c8a 是 WCDB 的 UUID
 # 符號(hào)化時(shí) dsym 的 UUID 需要與之匹配才能符號(hào)化
 0x10b724000 -        0x10b86ffff  WCDB arm64  <b512f6d343e73a0db1bcb499d2597c8a> /private/var/containers/Bundle/Application/5694FC83-018E-46E7-B060-A008867D3C9D/Elephant.app/Frameworks/WCDB.framework/WCDB

四、Crash 的類(lèi)型

4.1 兩類(lèi)主要的 Crash

引發(fā)崩潰的代碼本質(zhì)上就兩類(lèi),

一類(lèi)是 c/c++ 語(yǔ)言層面的錯(cuò)誤,比如野指針,除零,內(nèi)存訪問(wèn)異常等等(相對(duì)復(fù)雜)

對(duì)于前者,無(wú)論是 iOS 還是 Android 系統(tǒng),其底層都是 unix 或者是類(lèi) unix 系統(tǒng),都可以通過(guò)信號(hào)機(jī)制來(lái)獲取 signal 或者是 sigaction (但是只能捕捉有限的幾種類(lèi)型),設(shè)置一個(gè)回調(diào)函數(shù)。

  • Watchdog 超時(shí)、用戶(hù)強(qiáng)制退出、低內(nèi)存終止等,系統(tǒng)拋出Unix信號(hào),沒(méi)有任何的錯(cuò)誤堆棧信息

另一類(lèi)是未捕獲異常 Uncaught Exception(相對(duì)簡(jiǎn)單)

iOS 下面最常見(jiàn)的就是 Objective-C 的NSException(@throw 拋出),可以使用NSUncaughtExceptionHandler catch 住防止崩潰。

  • 如數(shù)組越界,給對(duì)象發(fā)送了無(wú)法識(shí)別的消息(selector方法沒(méi)有實(shí)現(xiàn),對(duì)象調(diào)用方法出錯(cuò))等,系統(tǒng)拋出一個(gè)NSException對(duì)象,對(duì)象中有出錯(cuò)的堆棧,描述了出錯(cuò)的代碼位置、類(lèi)名和方法名
4.1.1 Bus Error
  • Non-existent address(訪問(wèn)不存在的內(nèi)存地址)
  • Unaligned access(訪問(wèn)未對(duì)齊的內(nèi)存地址)
  • Paging errors(分頁(yè)錯(cuò)誤)

在檢測(cè)順序上,先檢測(cè) SIGBUS,再檢測(cè) SIGSEGV。

SIGBUS 地址被放到地址總線之后,檢測(cè)出地址不對(duì)齊,發(fā)出異常信號(hào),

SIGSEGV 地址已經(jīng)放到地址總線上后,在后續(xù)流程中檢測(cè)出內(nèi)存違法訪問(wèn),發(fā)出異常信號(hào)。

  • SIGBUS (Bus error)訪問(wèn)非法地址

    指針?biāo)鶎?duì)應(yīng)的地址是有效地址,但總線不能正常使用該指針,通常是未對(duì)齊的數(shù)據(jù)訪問(wèn)所致。

    一些處理器架構(gòu)上要求對(duì)齊訪問(wèn)數(shù)據(jù),比如只能從4字節(jié)邊界上讀取一個(gè)4字節(jié)的數(shù)據(jù)類(lèi)型(對(duì)于長(zhǎng)度4個(gè)字節(jié)的對(duì)象,其存放地址起碼要被4整除才可以)。否則向當(dāng)前進(jìn)程分發(fā)SIGBUS信號(hào)。

  • SIGSEGV (Segmentation fault、segfault)合法地址的非法訪問(wèn)

    在 ARC 后很少遇到,意味著指針?biāo)鶎?duì)應(yīng)的地址是無(wú)效地址,沒(méi)有物理內(nèi)存對(duì)應(yīng)該地址。

    訪問(wèn)不屬于本進(jìn)程的內(nèi)存地址

    往沒(méi)有寫(xiě)權(quán)限的內(nèi)存地址寫(xiě)數(shù)據(jù)

    訪問(wèn)已被釋放的內(nèi)存

  • SEGV(Segmentation Violation)

    代表無(wú)效內(nèi)存地址,比如空指針,未初始化指針,棧溢出等。

4.1.2 其他異常類(lèi)型
  • EXC_CRASH(SIGABRT)

    情形:Abnormal Exit 異常退出

    未捕獲的 Objective-C 異常(NSException),導(dǎo)致系統(tǒng)發(fā)送了 Abort 信號(hào)退出,導(dǎo)致這類(lèi)異常崩潰的原因是捕獲到 Objective-C/C++ 異常,并且調(diào)用了 abort() 函數(shù),會(huì)在斷言/app內(nèi)部/操作系統(tǒng)用終止方法拋出。

    1. 通常發(fā)生在異步執(zhí)行系統(tǒng)方法的時(shí)候,如CoreData/NSUserDefaults等,還有一些其他的系統(tǒng)多線程操作。這并不一定意味著是系統(tǒng)代碼存在bug,代碼僅僅是成了無(wú)效狀態(tài),或者異常狀態(tài)。
    2. 通常Foundation庫(kù)中的容器為了保護(hù)狀態(tài)正常會(huì)做一些檢測(cè),例如插入nil到數(shù)組中等會(huì)遇到此類(lèi)錯(cuò)誤。
    3. App Extensions,例如輸入法,如果花了太多時(shí)間做初始化的話就會(huì)以這種異常退出(看門(mén)狗機(jī)制)。如果擴(kuò)展程序由于在啟動(dòng)時(shí)掛起進(jìn)而被kill掉,那 Report 中的Exception Subtype字段會(huì)寫(xiě) LAUNCH_HANG。因?yàn)閿U(kuò)展App沒(méi)有main函數(shù),所以任何情況下的在static constructors和+load方法里的初始化時(shí)間都會(huì)體現(xiàn)在你的擴(kuò)展或者依賴(lài)庫(kù)中。因此你應(yīng)當(dāng)盡可能的推遲這些邏輯。
    example 1: unrecognized selector sent to instance
    example 1: attempt to insert nil object from objects
    

    對(duì)于可能在別處被釋放的對(duì)象,要自己持有一份(alloc 或 copy)。

  • EXC_BREAKPOINT(SIGTRAP)

    情形:Trace Trap 追蹤捕獲

    和進(jìn)程異常退出類(lèi)似,這種異常是由于在特殊的節(jié)點(diǎn)加入debugger調(diào)試節(jié)點(diǎn),如果當(dāng)前沒(méi)有調(diào)試器(debugger)依附,那么則會(huì)導(dǎo)致進(jìn)程被殺掉。可以通過(guò) __builtin_trap() 在代碼里手動(dòng)出發(fā)這種異常。

    1. 這種 Crash 在 iOS 底層的框架中經(jīng)常出現(xiàn),最常見(jiàn)的是GCD。底層庫(kù)(例如libdispatch)會(huì)在遇到fatal錯(cuò)誤的時(shí)候陷入這個(gè)困局。

    2. Swift代碼會(huì)在運(yùn)行時(shí)的時(shí)候遇到下述問(wèn)題時(shí)拋出這種異常:

      一個(gè)non-optional的類(lèi)型被賦予一個(gè)nil值

      一個(gè)失敗的強(qiáng)制轉(zhuǎn)換

    遇到這種錯(cuò)誤,查下堆棧信息并想清楚是在哪里遇到了未知情況(unexpected condition)。額外信息也可能會(huì)在設(shè)備的控制臺(tái)的日志里出現(xiàn)。你應(yīng)當(dāng)盡量修改你的代碼,去優(yōu)雅的處理這種運(yùn)行時(shí)錯(cuò)誤。例如,處理一個(gè)optional的值,通過(guò)可選綁定(Optional binding)而不是強(qiáng)制解包來(lái)獲得其值。

  • EXC_BAD_INSTRUCTION(SIGILL)

    情形:Illegal Instruction 非法指令

    當(dāng)嘗試去執(zhí)行一個(gè)非法或者未定義的指令時(shí)會(huì)觸發(fā)該異常。有可能是因?yàn)榫€程在一個(gè)配置錯(cuò)誤的函數(shù)指針的誤導(dǎo)下嘗試jump到一個(gè)無(wú)效地址。
    在Intel處理器上,ud2操作碼會(huì)導(dǎo)致一個(gè)EXC_BAD_INSTRUCTION異常,但是這個(gè)通常用來(lái)做調(diào)試用途。

    在Intel處理器上,Swift會(huì)在運(yùn)行時(shí)碰到未知情況時(shí)被停止。 詳情參考Trace Trap。

  • SIGKILL

    情形:Killed

    進(jìn)程收到系統(tǒng)指令被干掉。請(qǐng)自行查看Termination Reason(會(huì)包含一個(gè)命名空間和代碼)來(lái)定位線程被干掉的原因。

  • SIGQUIT

    情形:Quit 退出

    這個(gè)異常是由于其它進(jìn)程擁有高優(yōu)先級(jí)且可以管理本進(jìn)程(因此被高優(yōu)先級(jí)進(jìn)程Kill掉)所導(dǎo)致。SIGQUIT不代表進(jìn)程發(fā)生Crash了,但是它確實(shí)反映了某種不合理的行為。

    iOS中,如果占用了太長(zhǎng)時(shí)間,鍵盤(pán)擴(kuò)展程序會(huì)隨著宿主app被干掉。因此,這種情況的異常下不太可能會(huì)在Crash Report中出現(xiàn)合理可讀的異常代碼。大概率是因?yàn)橐恍┢渌a在啟動(dòng)時(shí)占用了太長(zhǎng)時(shí)間但是在總時(shí)間限制前(看門(mén)狗的時(shí)間限制,見(jiàn)上文中的表格)成功結(jié)束了,但是執(zhí)行邏輯在extension退出的時(shí)候被錯(cuò)誤的執(zhí)行了。你應(yīng)該運(yùn)行Profile,仔細(xì)分析一下extension的各部分消耗時(shí)間,把耗時(shí)較多的邏輯放到background或者推遲(推遲到extension加載完畢)。

  • EXC_ARITHMETIC

    除零錯(cuò)誤會(huì)拋出此類(lèi)異常

    arithmetic [?'r?θm?t?k] 算術(shù),算法

  • SIGPIPE 管道破裂

    這個(gè)信號(hào)通常在進(jìn)程間通信產(chǎn)生,比如采用FIFO(管道)通信的兩個(gè)進(jìn)程,讀管道沒(méi)打開(kāi)或者意外終止就往管道寫(xiě),寫(xiě)進(jìn)程會(huì)收到SIGPIPE信號(hào)。

    此外用Socket通信的兩個(gè)進(jìn)程,寫(xiě)進(jìn)程在寫(xiě)Socket的時(shí)候,讀進(jìn)程已經(jīng)終止。

    對(duì)一個(gè)端已經(jīng)關(guān)閉的socket調(diào)用兩次寫(xiě)入操作,第二次寫(xiě)入將會(huì)產(chǎn)生SIGPIPE信號(hào),該信號(hào)默認(rèn)結(jié)束進(jìn)程。

    // 預(yù)防方式,寫(xiě)在 pch 文件
    // 僅在 iOS 系統(tǒng)上支持 SO_NOSIGPIPE
    #if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
        // We do not want SIGPIPE if writing to socket.
        const int value = 1;
        setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(int));
    #endif
    
    另外一些異常類(lèi)型

    為了防止一個(gè)應(yīng)用占用過(guò)多的系統(tǒng)資源,設(shè)計(jì)了 watchdog 的機(jī)制, watchdog 會(huì)監(jiān)測(cè)應(yīng)用的性能。如果超出了該場(chǎng)景所規(guī)定的運(yùn)行時(shí)間,watchdog 強(qiáng)制終結(jié)這個(gè)應(yīng)用的進(jìn)程。

    Exception Code 說(shuō)明
    0xbaaaaaad 并非一個(gè)真正的Crash,由用戶(hù)同時(shí)按Home鍵和音量鍵觸發(fā)。
    0xbad22222 當(dāng)VoIP程序在后臺(tái)太過(guò)頻繁的激活時(shí),系統(tǒng)可能會(huì)終止此類(lèi)程序。
    0x8badf00d(badfood) launch/resume/suspend/quit/background 響應(yīng)超過(guò)規(guī)定時(shí)間會(huì)被 Watchdog 終止(詳見(jiàn)下表), 并產(chǎn)生一個(gè)崩潰日志。在連接X(jué)code調(diào)試時(shí)為了便于調(diào)試,系統(tǒng)會(huì)暫時(shí)禁用掉Watchdog,所以此類(lèi)問(wèn)題的發(fā)現(xiàn)需要使用正常的啟動(dòng)模式。
    0xc00010ff 程序執(zhí)行大量耗費(fèi)CPU和GPU的運(yùn)算,導(dǎo)致設(shè)備過(guò)熱,觸發(fā)系統(tǒng)過(guò)熱保護(hù)被系統(tǒng)終止。這個(gè)也許是和發(fā)生crash的特定設(shè)備有關(guān),或者是和它所在的環(huán)境有關(guān)。
    0xdead10cc(deadlock) 程序退到后臺(tái)時(shí)還占用系統(tǒng)資源(如通訊錄)被系統(tǒng)終止?;蛘叱绦驋炱饡r(shí)拿到了文件鎖或者sqlite數(shù)據(jù)庫(kù)所長(zhǎng)期不釋放直到被凍結(jié)。如果你的app在掛起時(shí)拿到了文件鎖或者sqlite數(shù)據(jù)庫(kù)鎖,它必須請(qǐng)求額外的后臺(tái)執(zhí)行時(shí)間(request additional background execution time )并在被掛起前完成解鎖操作。
    0xdeadfa11(deadfall) 程序無(wú)響應(yīng)用戶(hù)強(qiáng)制退出。當(dāng)用戶(hù)長(zhǎng)按電源鍵,直到屏幕出現(xiàn)關(guān)機(jī)確認(rèn)畫(huà)面后再長(zhǎng)按Home鍵,將強(qiáng)制退出應(yīng)用。(不是雙擊 home 的強(qiáng)退)Exception Note 會(huì)有 SIMULATED 字段
    0x2bad45ec app因?yàn)榘踩`規(guī)操作被iOS系統(tǒng)終止。終止描述會(huì)寫(xiě):“進(jìn)程被查到在安全模式進(jìn)行非安全操作”,暗示app嘗試在禁止屏幕繪制的時(shí)候繪制屏幕,例如當(dāng)屏幕鎖定時(shí)。用戶(hù)可能會(huì)忽略這種異常,尤其當(dāng)屏幕是關(guān)閉的或者當(dāng)這種終止發(fā)生時(shí)正好鎖屏。

    說(shuō)明:通過(guò)App Switcher(就是雙擊home鍵出現(xiàn)的那個(gè)界面)并不會(huì)生成Crash Report。一旦app進(jìn)入掛起狀態(tài),被iOS在任何時(shí)間終止掉都是合理的,因此這時(shí)候不會(huì)生成Crash Report。

    以下異常代碼只針對(duì) watchOS

    Exception Code 說(shuō)明
    0xc51bad01 在后臺(tái)任務(wù)占用了過(guò)多的cpu時(shí)間而導(dǎo)致watch app被干掉。想要解決這個(gè)問(wèn)題,優(yōu)化后臺(tái)任務(wù),提高CPU執(zhí)行效率,或者減少后臺(tái)的任務(wù)運(yùn)行數(shù)量。
    0xc51bad02 在后臺(tái)的規(guī)定時(shí)間內(nèi)沒(méi)有完成指定的后臺(tái)任務(wù)而導(dǎo)致watch app被干掉。想要解決這個(gè)問(wèn)題,需要當(dāng)app在后臺(tái)運(yùn)行時(shí)減少app的處理任務(wù)。
    0xc51bad03 沒(méi)有在規(guī)定時(shí)間內(nèi)完成后臺(tái)任務(wù),且系統(tǒng)一直非常忙以至于app無(wú)法獲取足夠的CPU時(shí)間來(lái)完成后臺(tái)任務(wù)。雖然一個(gè)app可以通過(guò)減少自身在后臺(tái)的運(yùn)行任務(wù)來(lái)避免這個(gè)問(wèn)題,但是0xc51bad03這個(gè)錯(cuò)誤把矛頭指向了過(guò)高的系統(tǒng)負(fù)載,而非app本身有什么問(wèn)題。
  • 附:Watchdog 超時(shí)時(shí)間

    場(chǎng)景 超時(shí)時(shí)間
    launch(啟動(dòng)) 20s
    resume(恢復(fù)) 10s
    suspend(掛起) 10s
    quit(退出) 6s
    background(后臺(tái)) 10min

    簡(jiǎn)單說(shuō),就是以下代理必須在規(guī)定時(shí)間內(nèi)執(zhí)行完畢,讓程序響應(yīng)起來(lái)。

    - (void)applicationDidFinishLaunching:(UIApplication *)application;
    - (void)applicationDidBecomeActive:(UIApplication *)application;
    - (void)applicationWillResignActive:(UIApplication *)application;
    - (void)applicationDidEnterBackground:(UIApplication *)application;
    - (void)applicationWillEnterForeground:(UIApplication *)application;
    - (void)applicationWillTerminate:(UIApplication *)application;
    

崩潰(準(zhǔn)確的說(shuō)是程序異常終止)是程序接收到未處理信號(hào)的結(jié)果。

未處理信號(hào)有三個(gè)來(lái)源:內(nèi)核、其他進(jìn)程和應(yīng)用本身。導(dǎo)致崩潰最常見(jiàn)的兩個(gè)信號(hào)如下:

  • EXC_BAD_ACCESS 是一種由內(nèi)核發(fā)出的Mach異常,通常是因?yàn)閼?yīng)用試圖訪問(wèn)不存在的內(nèi)存空間導(dǎo)致的。如果未能在Mach內(nèi)核級(jí)別進(jìn)行處理,它將被轉(zhuǎn)化為SIGBUS或者SIGSEGV BSD信號(hào)。
  • SIGABRT是當(dāng)產(chǎn)生未捕獲的NSException或者obj_exception_throw時(shí),應(yīng)用發(fā)給自身的BSD信號(hào)。

在 Objective-C 異常中,導(dǎo)致異常拋出最常見(jiàn)的原因是應(yīng)用向?qū)ο蟀l(fā)送了未實(shí)現(xiàn)的方法選擇器(比如拼寫(xiě)錯(cuò)誤,對(duì)象混淆或者向已經(jīng)釋放的對(duì)象發(fā)送消息)。

4.2 Low Memory Report 低內(nèi)存報(bào)告

Low Memory Termination

跟一般的Crash結(jié)構(gòu)不太一樣,通常有Free pages,Wired Pages,Purgeable pages,largest process 組成,同時(shí)會(huì)列出當(dāng)前時(shí)刻系統(tǒng)運(yùn)行所有進(jìn)程的信息。

Low Memory Report 與其它 Crash Report 不同,它沒(méi)有堆棧信息,所以不需要符號(hào)化。一個(gè)低內(nèi)存 Report的Header會(huì)和 Crash Report 的header有些類(lèi)似。緊接著Header的時(shí)各個(gè)字段的系統(tǒng)級(jí)別的內(nèi)存統(tǒng)計(jì)信息。記錄下頁(yè)大小(Page Size)字段。每一個(gè)進(jìn)程的內(nèi)存占用大小是根據(jù)內(nèi)存的頁(yè)的數(shù)量來(lái) Report的。一個(gè)低內(nèi)存 Report最重要的部分是進(jìn)程表格。這個(gè)表格列出了所有的運(yùn)行進(jìn)程,包括系統(tǒng)在生成低內(nèi)存 Report時(shí)的守護(hù)進(jìn)程。如果一個(gè)進(jìn)程被”遺棄”了,會(huì)在[原因]一列附上具體的原因。一個(gè)進(jìn)程可能被遺棄的原因有:

  • [per-process-limit]

    進(jìn)程占用超過(guò)了它的最大內(nèi)存值。每一個(gè)進(jìn)程在常駐內(nèi)存上的限制是早已經(jīng)由系統(tǒng)為每個(gè)應(yīng)用分配好了的。超過(guò)這個(gè)限制會(huì)導(dǎo)致進(jìn)程被系統(tǒng)干掉。

    注意:擴(kuò)展程序(nimo: Extension app, 例如輸入法等)的最大內(nèi)存值更少。一些技術(shù),例如地圖視圖和SpriteKit,占用非常多的基礎(chǔ)內(nèi)存,因此不適合用在擴(kuò)展程序里。

  • [vm-pageshortage]/[vm-thrashing]/[vm]

    由于系統(tǒng)內(nèi)存壓力被干掉。

  • [vnode-limit]

    打開(kāi)太多文件了。

    注意:系統(tǒng)會(huì)盡量避免在vnodes已經(jīng)枯竭的時(shí)候干掉高頻app。因此你的應(yīng)用如果在后臺(tái),即便并沒(méi)有占用什么vnode,而有可能被殺掉。

  • [highwater]

    一個(gè)系統(tǒng)守護(hù)進(jìn)程超過(guò)過(guò)了它的內(nèi)存占用高水位(就是已經(jīng)很危險(xiǎn)了)。

  • [jettisoned]

    進(jìn)程因?yàn)槠渌豢擅枋龅脑虮粴⒌簟?/p>

當(dāng)你發(fā)現(xiàn)一個(gè)低內(nèi)存crash,與其去擔(dān)心哪一部分的代碼出現(xiàn)問(wèn)題,還不如仔細(xì)審視一下自己的內(nèi)存使用習(xí)慣和針對(duì)低內(nèi)存告警(low-memory warning)的處理措施。Locating Memory Issues in Your App 列出了如何使用Leaks Instrument工具來(lái)檢查內(nèi)存泄漏,和如何使用Allocations Instrument的Mark Heap 功能來(lái)避免內(nèi)存浪費(fèi)。 Memory Usage Performance Guidelines 討論了如何處理接受到低內(nèi)存告警的問(wèn)題,以及如何高效使用內(nèi)存。當(dāng)然,也推薦你去看下2010年的WWDC中的 Advanced Memory Analysis with Instruments 那一章節(jié)。

重要:Leaks和Allocation工具不能檢測(cè)所有的內(nèi)存使用情況。你需要和VM Tracker工具一起運(yùn)行(包含在Allocation工具里)來(lái)查看你的內(nèi)存運(yùn)行。默認(rèn)VM Tracker是不可用的。如果想通過(guò)VM Tracker來(lái)profile你的應(yīng)用,點(diǎn)擊instrument工具,選中”Automatic Snapshotting”標(biāo)簽或者手動(dòng)點(diǎn)擊”Snapshot Now”按鈕。

五、Crash 的捕獲

5.0 Last Exception Backtrace

若程序因 NSException 而 Crash,系統(tǒng)日志中的 Last Exception Backtrace 信息是完整準(zhǔn)確的,不會(huì)受應(yīng)用層的 Crash 統(tǒng)計(jì)服務(wù)影響,可作為排查問(wèn)題的參考線索。如果 Last Exception Backtrace,只包含16進(jìn)制信息的日志,必須進(jìn)行符號(hào)化來(lái)獲取有價(jià)值的堆棧信息

# 未符號(hào)化的異常堆棧
Last Exception Backtrace:

(0x18eee41c0 0x18d91c55c 0x18eee3e88 0x18f8ea1a0 0x195013fe4 0x1951acf20 0x18ee03dc4 0x1951ab8f4 0x195458128 0x19545fa20 0x19545fc7c 0x19545ff70 0x194de4594 0x194e94e8c 0x194f47d8c 0x194f39b40 0x194ca92ac 0x18ee917dc 0x18ee8f40c 0x18ee8f89c 0x18edbe048 0x19083f198 0x194d21bd0 0x194d1c908 0x1000ad45c 0x18dda05b8)

5.1 處理未捕獲異常(uncaught exceptions)

Demo :https://github.com/xcysuccess/iOSCrashUncaught

有兩種方式可以捕獲那些會(huì)導(dǎo)致崩潰的未捕獲狀態(tài)。

  • 使用 NSUncaughtExceptionHandler 函數(shù)來(lái)安裝未捕獲 Objective-C 異常的處理器。
  • 使用 signal 函數(shù)來(lái)安裝 BSD 信號(hào)處理器。

注意:signal 要在沒(méi)有附加 debugger 的環(huán)境下獲取,否則會(huì)被 debugger 優(yōu)先攔截。UncaughtExceptionHandler可以在調(diào)試狀態(tài)下捕獲

抓取 NSException
// 安裝 Objective-C 異常處理器和信號(hào)處理的代碼如下:
void InstallUncaughtExceptionHandler() {
    NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}
// 對(duì)于異常和信號(hào)的響應(yīng)會(huì)在 MyUncaughtExceptionHandler 和 SignalHandler 中實(shí)現(xiàn)。在樣例程序中,以上二者的處理方式相同。

void MyUncaughtExceptionHandler(NSException *exception) {
    NSString *ret = [NSString stringWithFormat:@"異常名稱(chēng):\n%@\n\n異常原因:\n%@\n\n出錯(cuò)堆棧內(nèi)容:\n%@\n",exception.name, exception.reason, exception.callStackSymbols];
    // 將捕獲到的 exception 細(xì)節(jié)上傳到后臺(tái)
}
抓取 Signal

signal信號(hào)是Unix系統(tǒng)中的,是一種異步通知機(jī)制.信號(hào)傳遞給進(jìn)程后,在沒(méi)有處理函數(shù)的情況下,程序可以指定三種行為:

  1. 忽略該信號(hào),但是對(duì)于信號(hào)SIGKILLSIGSTOP不可忽略
  2. 使用默認(rèn)的處理函數(shù)SIG_DFL(即 signal(sig, SIG_DFL);),大多數(shù)信號(hào)的默認(rèn)動(dòng)作是終止進(jìn)程
  3. 捕獲信號(hào),執(zhí)行用戶(hù)定義的函數(shù)

有兩個(gè)特殊的常量:

  • SIG_IGN,向內(nèi)核表示忽略此信號(hào).對(duì)于不能忽略的兩個(gè)信號(hào)SIGKILLSIGSTOP,調(diào)用時(shí)會(huì)報(bào)錯(cuò)
  • SIG_DFL,執(zhí)行該信號(hào)的系統(tǒng)默認(rèn)動(dòng)作.

還有兩個(gè)常用的函數(shù)

  • int kill(pid_t pid, int signo);,發(fā)送信號(hào)到指定的進(jìn)程
  • int raise(int signo);,發(fā)送信號(hào)給自己.
// UNIX系統(tǒng)中常用的信號(hào)有以下幾種:
SIGABRT--程序中止命令中止信號(hào) 
SIGBUS--程序內(nèi)存字節(jié)未對(duì)齊中止信號(hào)
SIGFPE--程序浮點(diǎn)異常信號(hào)
SIGILL--程序非法指令信號(hào)
SIGSEGV--程序無(wú)效內(nèi)存中止信號(hào)
SIGTERM--程序kill中止信號(hào)
SIGKILL--程序結(jié)束接收中止信號(hào) 
    
SIGALRM--程序超時(shí)信號(hào) 
SIGHUP--程序終端中止信號(hào)
SIGINT--程序鍵盤(pán)中斷信號(hào) 
SIGSTOP--程序鍵盤(pán)中止信號(hào)  
SIGPIPE--程序Socket發(fā)送失敗中止信號(hào)

// 抓取的是以下幾種
static int Beacon_errorSignals[] = {
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGSEGV,
    SIGTRAP,
    SIGTERM,
    SIGKILL,
};
for (int i = 0; i < Beacon_errorSignalsNum; i++) {
    signal(Beacon_errorSignals[i], &mysighandler);
}
// 抓取信號(hào)的處理函數(shù)
void mysighandler(int sig) {
    void* callstack[128];
    NSString* name ;
    int i, frames = backtrace(callstack, 128);
    for (i = 0; i < Beacon_errorSignalsNum; i++) {
        if (Beacon_errorSignals[i] == sig ) {
            name = [Beacon_errorSignalNames[i] copy];
            break;
        }
    }
    char** strs = backtrace_symbols(callstack, frames);
    NSMutableString* exceptionStr = [[NSMutableString alloc]initWithFormat:@"異常名稱(chēng):\n%@\n\n出錯(cuò)堆棧內(nèi)容:\n",name];
    for (i =0; i <frames; i++) {
        [exceptionStr appendFormat:@"%s\n",strs[i]];
    }
    free(strs);
}

// 在應(yīng)用崩潰后,保持運(yùn)行狀態(tài)而不退出,讓響應(yīng)更加友好
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

while (!dismissed) {
    for (NSString *mode in (__bridge NSArray *)allModes) {
        CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
    }
}

CFRelease(allModes);

這里只處理最常見(jiàn)的信號(hào),但是,你可以為自己的程序添加所需的所有異常信號(hào)。

注意,有兩種異常是不能捕獲的:SIGKILL和SIGSTOP。它們會(huì)終止或者暫停應(yīng)用。(SIGKILL是命令行函數(shù)kill -9發(fā)出的,SIGSTOP是鍵入Control-Z發(fā)出的)。

如果你發(fā)現(xiàn)本應(yīng)該被捕捉的異常并沒(méi)有被捕捉到,請(qǐng)確定您沒(méi)有在building應(yīng)用或者library時(shí)添加了-no_compact_unwind標(biāo)簽。

64位 iOS 用了zero-cost的異常實(shí)現(xiàn)機(jī)制。在zero-cost系統(tǒng)里,每一個(gè)函數(shù)都有一個(gè)額外的數(shù)據(jù),它會(huì)描述如果一個(gè)異常在跨函數(shù)范圍內(nèi)實(shí)現(xiàn),該如何展開(kāi)相應(yīng)的堆棧信息。如果一個(gè)異常發(fā)生在多個(gè)堆棧但是沒(méi)有可展開(kāi)的數(shù)據(jù),那么異常處理函數(shù)自然無(wú)法跟蹤并記錄。也許在堆棧很上層的地方有異常處理函數(shù),但是如果那里沒(méi)有一個(gè)片段的可展開(kāi)信息,沒(méi)辦法從發(fā)生異常的地方到那里。指定了-no_compact_unwind標(biāo)簽表明你那些代碼沒(méi)有可展開(kāi)信息,所以你不能跨越函數(shù)拋出異常(也就是說(shuō)無(wú)法通過(guò)別的函數(shù)捕捉當(dāng)前函數(shù)的異常)。

5.2 Xcode 提供的調(diào)試工具

都在 Edit Scheme -> Diagnostics(診斷) 依次可以找到

Runtime Sanitization
  • Address Sanitizer(地址消毒劑)

    AddressSanitizer的原理是當(dāng)程序創(chuàng)建變量分配一段內(nèi)存時(shí),將此內(nèi)存后面的一段內(nèi)存也凍結(jié)住,標(biāo)識(shí)為中毒內(nèi)存。當(dāng)程序訪問(wèn)到中毒內(nèi)存時(shí)(越界訪問(wèn)),就會(huì)拋出異常,并打印出相應(yīng)log信息。調(diào)試者可以根據(jù)中斷位置和的log信息,識(shí)別bug。如果變量釋放了,變量所占的內(nèi)存也會(huì)標(biāo)識(shí)為中毒內(nèi)存,這時(shí)候訪問(wèn)這段內(nèi)存同樣會(huì)拋出異常(訪問(wèn)已經(jīng)釋放的對(duì)象)。

  • Thread Sanitizer

    用于解決多線程問(wèn)題:如何用Xcode8解決多線程問(wèn)題

    • Use of uninitialized mutexes(使用未初始化的互斥器)
    • Thread leaks (missing pthread_join) 線程泄漏(缺少pthread_join)
    • Unsafe calls in signal handlers (ex:malloc) 信號(hào)處理程序中的不安全調(diào)用(例如:malloc)
    • Unlock from wrong thread 從錯(cuò)誤的線程解鎖
    • Data races 數(shù)據(jù)競(jìng)爭(zhēng)(只要涉及到多線程編程,遇到的概率非常之高,寫(xiě)多線程代碼時(shí)最容易遇到的問(wèn)題,一旦踩坑,現(xiàn)象往往是偶現(xiàn)的,難以調(diào)試)

    大致原理是記錄每個(gè)線程訪問(wèn)變量的信息來(lái)做分析,值得一提的是,現(xiàn)階段的Thread Sanitizer最多只同時(shí)記錄4個(gè)線程的訪問(wèn)信息,在復(fù)雜的場(chǎng)景下,可能出現(xiàn)偶爾檢測(cè)不出data race的場(chǎng)景,所以需要長(zhǎng)時(shí)間經(jīng)常性的運(yùn)行來(lái)盡可能多的發(fā)現(xiàn)data race,這也是為什么蘋(píng)果建議默認(rèn)開(kāi)啟Thread Sanitizer,而且Thread Sanitizer 造成的額外性能損耗非常之小。

    Thread Sanitizer 現(xiàn)階段只能在模擬器環(huán)境下執(zhí)行,真機(jī)還不支持,有同學(xué)測(cè)試發(fā)現(xiàn),只支持64位系統(tǒng),也就是說(shuō)iPhone 5及其更早的模擬器也不支持,iPhone 5s 之后才是64位系統(tǒng)。

Memory Management
  • Malloc Scribble

    申請(qǐng)內(nèi)存后在申請(qǐng)的內(nèi)存上填 0xAA,內(nèi)存釋放后在釋放的內(nèi)存上填 0x55;再就是說(shuō)如果內(nèi)存未被初始化就被訪問(wèn),或者釋放后被訪問(wèn),就會(huì)引發(fā)異常,這樣就可以使問(wèn)題盡快暴漏出來(lái)。

    Scribble 其實(shí)是 malloc 庫(kù) libsystem_malloc.dylib 自身提供的調(diào)試方案

  • Malloc Guard Edges

    申請(qǐng)大片內(nèi)存的時(shí)候在前后page上加保護(hù),詳見(jiàn)保護(hù)模式。

  • Guard Malloc

    使用 libgmalloc 捕獲常見(jiàn)的內(nèi)存問(wèn)題,比如越界、釋放之后繼續(xù)使用。

    由于 libgmalloc 在真機(jī)上不存在,因此這個(gè)功能只能在模擬器上使用.

  • Zombie Objects(僵尸對(duì)象)

    Instrument 也有一個(gè) Zombie 工具,使用起來(lái)差不多。

    Zombie 的原理是用生成僵尸對(duì)象來(lái)替換 dealloc 的實(shí)現(xiàn),當(dāng)對(duì)象引用計(jì)數(shù)為 0 的時(shí)候,將需要 dealloc 的對(duì)象轉(zhuǎn)化為僵尸對(duì)象。如果之后再給這個(gè)僵尸對(duì)象發(fā)消息,則拋出異常,并打印出相應(yīng)的信息,調(diào)試者可以很輕松的找到異常發(fā)生位置。

    如果 objc_msgSend 或者 objc_release出現(xiàn)在crash的線程的附近,則進(jìn)程有可能?chē)L試去給一個(gè)被釋放的對(duì)象發(fā)送消息,那么可使用 Zombie 調(diào)試

    # 控制臺(tái)會(huì)多一些調(diào)試信息
    message sent to deallocated instance 0x60800000c380
    
Analyze(靜態(tài)代碼分析)

不是那么準(zhǔn)確,但是會(huì)發(fā)現(xiàn)一些問(wèn)題

可以發(fā)現(xiàn)編譯中的 warning,內(nèi)存泄漏隱患,甚至還可以檢查出邏輯上的問(wèn)題;所以在自測(cè)階段一定要解決Analyze發(fā)現(xiàn)的問(wèn)題,可以避免出現(xiàn)嚴(yán)重的bug。

主要分析以下四種問(wèn)題:

  • 邏輯錯(cuò)誤:訪問(wèn)空指針或未初始化的變量等;
  • 內(nèi)存管理錯(cuò)誤:如內(nèi)存泄漏等;
  • 聲明錯(cuò)誤:從未使用過(guò)的變量;
  • 調(diào)用錯(cuò)誤:未包含使用的庫(kù)和框架。
# 內(nèi)存泄漏隱患
Potential(潛在) Leak of an object allocated on line ……
# 數(shù)據(jù)賦值隱患
The left operand of …… is a garbage value;
# 對(duì)象引用隱患
Reference-Counted object is used after it is released;
Profile(就是運(yùn)行 Instrument)

真正運(yùn)行程序,對(duì)程序進(jìn)行內(nèi)存分析(查看內(nèi)存分配情況、內(nèi)存泄露)

優(yōu)點(diǎn):分析非常準(zhǔn)確,如果發(fā)現(xiàn)有提示內(nèi)存泄露,基本可以斷定代碼問(wèn)題

缺點(diǎn):分析效率低(真正運(yùn)行了一段代碼,才能對(duì)該代碼進(jìn)行內(nèi)存分析)

六、附錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容