Flutter App crash日志搜集包括三部分,一部分来自于Dart code引起的异常,可以在flutter framework的main函数进行全局捕获,此外还需对Native端
iOS
和Android
的异常进行捕获.
iOS异常搜集与分析
开启
DWARF
文件搜集-
获取Mach异常和Unix信号(
),用于捕获系统内核的异常,在http://opensource.apple.com 可以看到内核的完整代码; void InstallUncaughtExceptionHandler() {
//根据需要选择性的配置
signal(SIGABRT, CustomSignalHandler);
signal(SIGILL, CustomSignalHandler);
signal(SIGSEGV, CustomSignalHandler);
signal(SIGFPE, CustomSignalHandler);
signal(SIGBUS, CustomSignalHandler);
signal(SIGPIPE, CustomSignalHandler);
...
} 注册
NSSetUncaughtExceptionHandler
捕获应用异常事件通过三方库搜集,KSCrash,plcrashreporter,CrashKit,Crashlytics,Hockeyapp,友盟,Bugly,App Dynamic
-
获取Crash符号日志
- 通过手机链接上
Xcode
直接获取 - 通过应用内置的异常获框架获取crash日志并上传到服务器.
- 通过手机链接上
Crash日志解析
-
隐藏符号替换: 根据官方文档的说明通过
AppStore
发布之后下载的dysm文件会有部分敏感符号被隐藏,本地生成的则不会替换。所以在使用时需要先将其替换dsymutil -symbol-map <PathToXcodeArchive>/MyGreatApp.xcarchive/BCSymbolMaps <PathToDownloadedDSYMs>/<UUID>.dSYM
-
检测
Crash
日志的uuid和ipa的执行文件uuid是否相同dwarfdump --uuid <PathToDSYMFile>/Contents/Resources/DWARF/<BinaryName> dwarfdump --uuid <PathToBinary>
-
当crash日志和对应的ipa执行文件uuid对应之后,利用xcode的
Symbolicate
工具进行符号化,注意这里的-l
后面的参数,分别指定了atos -arch <BinaryArchitecture> -o <PathToDSYMFile>/Contents/Resources/DWARF/<BinaryName> -l <LoadAddress> <AddressesToSymbolicate> atos -arch arm64 -o TouchCanvas.app.dSYM/Contents/Resources/DWARF/TouchCanvas -l 0x1022c0000 0x00000001022df754
ViewController.touchesEstimatedPropertiesUpdated(_:) (in TouchCanvas) + 304 -
使用内置的工具解析
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./symbolicatecrash /Users/yourUserName/Desktop/CrashSignifying/crashFileName.crash /Users/UserName/Desktop/CrashSignifying/dSYMFileName.dSYM > crashFileName.crash
Crash日志介绍,
一份crash日志包含了以下几个部分
-
Header:
Incident Identifier: 6156848E-344E-4D9E-84E0-87AFD0D0AE7B CrashReporter Key: 76f2fb60060d6a7f814973377cbdc866fffd521f
Hardware Model: iPhone8,1
Process: TouchCanvas [1052]
Path: /private/var/containers/Bundle/Application/51346174-37EF-4F60-B72D-8DE5F01035F5/TouchCanvas.app/TouchCanvas
Identifier: com.example.apple-samplecode.TouchCanvas
Version: 1 (3.0)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.example.apple-samplecode.TouchCanvas [1806]
Date/Time: 2020-03-27 18:06:51.4969 -0700
Launch Time: 2020-03-27 18:06:31.7593 -0700
OS Version: iPhone OS 13.3.1 (17D50) -
Exception Information:
Exception Type: EXC_BREAKPOINT (SIGTRAP) 异常类型 Exception Subtype: 异常类型可读取的描述信息
Exception Message: 对Exception Type的解释
Exception Codes: 0x0000000000000001, 0x0000000102afb3d0 一个64bit的异常code
Exception Note:
不属于一个特定的异常类型。如果此字段包含EXC_corpost_NOTIFY,则崩溃并非源自硬件陷阱,原因可能是该进程已被操作系统或名为abort()的进程显式终止。如果此字段包含SIMULATED(这不是崩溃),则进程没有崩溃,但操作系统可能随后请求终止进程。如果此字段包含非致命条件(这不是崩溃),则进程不会终止,因为创建崩溃报告的问题不是致命的。
Termination Reason: 应用程序终止的具体原因
Triggered by Thread or Crashed Thread: 触发崩溃或者崩溃的线程 -
Diagnostic Messages: 系统当前对app的诊断信息
Termination Description: SPRINGBOARD, scene-create watchdog transgression: application<com.example.MyCoolApp>:667
exhausted real (wall clock) time allowance of 19.97 seconds
常见的异常处理方案
-
WatchDog timeout: 这种异常通常是由于主线程阻塞造成,同步的网络请求,处理大量的model转换,如json解析,3d models的解析,同步的处理大量的coredata数据,应用计算机视觉算法对输入图像和视频执行各种任务。
- 下面2张图时是改善前后的方案
改进之后
-
scence-create超时
Termination Description: SPRINGBOARD, scene-create watchdog transgression: application<com.example.MyCoolApp>:667
exhausted real (wall clock) time allowance of 19.97 seconds
| ProcessVisibility: Foreground
| ProcessState: Running
| WatchdogEvent: scene-create
| WatchdogVisibility: Foreground
| WatchdogCPUStatistics: (
| "Elapsed total CPU time (seconds): 15.290 (user 15.290, system 0.000), 28% CPU",
| "Elapsed application CPU time (seconds): 0.367, 1% CPU"
| ) -
后台任务执行超时
Termination Reason: CAROUSEL, WatchConnectivity watchdog transgression. Exhausted wall time allowance of 15.00 seconds.
Termination Description: SPRINGBOARD,
CSLHandleBackgroundWCSessionAction watchdog transgression: xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220
exhausted real (wall clock) time allowance of 15.00 seconds
| <FBExtensionProcess: 0x16df02a0; xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220; typeID: com.apple.watchkit>
Elapsed total CPU time (seconds): 24.040 (user 24.040, system 0.000), 81% CPU
| Elapsed application CPU time (seconds): 1.223, 6% CPU, lastUpdate 2020-01-20 11:56:01 +0000
-
Memory Access Crash: 内存访问crash
- 取消引用指向无效的地址空间,往只读的空间写入数据,访问无效地址的指令,通常会出现
EXC_BAD_ACCESS
(SIGSEGV),或者是EXC_BAD_ACCESS
(SIGBUS)异常text
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000 - 坏的内存访问有时候不会生成
EXC_BAD_ACCESS
,直接抛出一个signal
异常,例如:SIGSEGV, SEGV_MAPERR, or SEGV_NOOP
text Exception Type: SIGSEGV
Exception Codes: SEGV_MAPERR at 0x41e0af0c5ab8 -
内存访问异常分析
- Address Sanitizer(gcc ... -fsanitize=address): 内存错误检查,在Xcode运行面板中可以开启此项检查。
- 比如使用一个释放的对象
- 数组越界,栈缓存溢出
- Undefined Behavior Sanitizer: 不安全的操作指令
- 缓冲区溢出
- 使用未初始化的变量
- 使用释放后的变量
- 重复释放
- 多线程数据竞争
- Thread Sanitizer: 线程安全检查
- 记录内存访问信息,判断是否有不同线程访问修改其值
- Profile Analytics: 静态分析检查
- 在运行时检查,未初始化变量,泄漏,未使用变量
- Enabling the Malloc Debugging Features: 开启僵尸检查模式,可以给释放的对象发送消息,来检测异常
- Address Sanitizer(gcc ... -fsanitize=address): 内存错误检查,在Xcode运行面板中可以开启此项检查。
-
异常子类型检查
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
...
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_MEMORY_ERROR at 0x00000001098c1000
KERN_INVALID_ADDRESS: 无效的内存地址
KERN_PROTECTION_FAILURE: 使用受保护的地址空间,不属于当前线程的空间
KERN_MEMORY_ERROR: 访问的地址无法提供数据,如分页缺失
EXC_ARM_DA_ALIGN: 访问的内存地址未对齐 -
虚拟内存引用错误,应用了其它的app的vm region
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
VM Region Info: 0 is not in any region. Bytes before following region: 4307009536
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 0000000100b7c000-0000000100b84000 [ 32K] r-x/r-x SM=COW ...pp/MyGreatApp- 如果
objc_msgSend, objc_retain, or objc_release
在堆栈信息的最上方,考虑开启Zombie Objects
检查 . - 如果
gpus_ReturnNotPermittedKillClient
出现,则说明试图在后台使用OpenGL ES进行渲染,需要将OpenGL ES代码移植到Metal,移植参考Migrating OpenGL Code to Metal.
- 如果
- 取消引用指向无效的地址空间,往只读的空间写入数据,访问无效地址的指令,通常会出现
-
EXC_BREAKPOINT
- EXC_BREAKPOINT (SIGTRAP) and EXC_BAD_INSTRUCTION (SIGILL): 跟中陷阱终断,或者不合法的指令(illegal), 模拟 __builtin_trap
EXC_CRASH (SIGABRT): 程序执行了abort()函数,如算数逻辑错误保护,程序启动的必选参数强制保护.扩展初始化占用时间过长被系统执行了abort()
-
EXC_CRASH (SIGKILL): kill,
- 0x8badf00d(bad food,watch dog issue);
- 0xc00010ff(cool off),系统由于热事件终止应用程序, 运行环境异常,电量使用过大造成,可以参考iOS Performance and Power Optimization with Instruments;
- 0xbaadca11(bad call),操作系统终止了应用程序,因为未能报告对PushKit通知的CallKit调用。
- 0xbad22222,系统终止了VoIP应用程序,因为它恢复得太频繁,(提示,2重复了很多次);
- 0xc51bad01(backgroud),watchOS终止了应用程序,因为它在执行后台任务时占用了太多的CPU时间。(自身的问题)
- 0xc51bad02(backgroud), watchOS终止了应用程序,因为它未能在分配的时间内完成后台任务。(自身的问题)
- 0xc51bad03(backgroud), watchOS终止了应用程序,因为它未能在分配的时间内完成后台任务,但系统总体上非常繁忙,应用程序可能没有收到太多CPU时间来执行后台任务。(系统的问题)
- EXC_CRASH (SIGQUIT): 进程在另一个进程的请求下终止(具备管理这个进程的生命周期)。
- EXC_GUARD: 违反了保护资源的原则。,但大多数受保护的资源崩溃都来自受保护的文件描述符,这些描述符在异常子类型字段中具有GUARD_TYPE_FD(guard type file descriptor)值。操作系统将文件描述符标记为受保护的,这样普通的文件描述符API就无法修改它们, CLOSE,DUP(duplicate),NOCLOEXEC,SOCKET_IPC,FILEPORT,WRITE
- EXC_RESOURCE: 进程超出了资源消耗限制。CPU and CPU_FATAL,MEMORY,IO(disk task),WAKEUPS(每秒醒来次数太多,这会消耗电池寿命), such as perform(_:on:with:waitUntilDone:), async(execute:), or dispatch_async(_:_:),执行过于频繁,GCD负荷过重,参考Modernizing Grand Central Dispatch Usage, 队列控制,计划执行,并发数控制
- EXC_ARITHMETIC: 逻辑运算错误,除0或浮点运算错误
-
App内存不足被强制杀掉: 虚拟内存占用过高导致被杀掉
- app在启动后以pageFault的方式,将物理空间上的内存以分页的形式映射到内存中
- crash demo: 通常它不会作为crash日志搜集,在系统设置统计分析中可以找到这些日志.
text
"crashReporterKey" : "b9aa251a63bd9e743afbb906f43eb7ea5f206292",
"product" : "iPad8,2",
"incident" : "32B05E3C-CB45-40F8-BA66-5668779740E1",
"date" : "2019-10-10 23:30:39.48 -0700",
"build" : "iPhone OS 13.1.2 (17A860)",
"memoryStatus" : {
"pageSize" : 16384,
},
"largestProcess" : "OneCoolApp", //占用内存最大进程,因某种原因被系统杀掉
- 特别注意: 一个jetsam event报告包括一个数组进程,如果你的app崩溃了,但是被挂掉的进程不是你的app,就需通过其它的诊断途径来查看问题,确认手机内是否有其它的crash日志
- reason: per-process-limit,进程超过了系统限定的程序内存驻留最大限制,超过此大小,将极大可能被系统杀掉,应用程序的扩展进程也被考虑在内,并且他们的内存占用限制更少
text
{
"uuid" : "a02fb850-9725-4051-817a-8a5dc0950872",
"states" : [
"frontmost"
],
"lifetimeMax" : 92802,
"purgeable" : 0,
"coalition" : 68,
"rpages" : 92802,
"reason" : "per-process-limit",
"name" : "MyCoolApp"
}- reason: vm-pageshortage,系统内存压力过重,杀掉后台应用
- reason: vnode-limit,系统打开了太多的文件,内核有限制vnode数量,会放弃部分后台进程,保证前台应用存活
- reason: highwater,系统守护进程超出了最高内存占用
- reason: fc-thrashing, 当内存映射文件的非连续部分读写过于频繁时,就会发生这种情况。为了避免终止最前端的应用程序,系统可能会在后台终止您的应用程序,以释放文件缓存中的空间,即使您的应用程序没有破坏文件缓存。
- reason: jettisoned,系统杀掉了应用程序因为一些其它的原因
- pageSize默认为16KB
- pageSize优化: 方法重排,常用方法集中到一个页,避免频繁切换,合理利用数据结构,减少不必要的开销,字节对齐,进一步优化数据占用空间,
- 可以通过
vm_stat
查看进程的vm空间text Mach Virtual Memory Statistics: (page size of 4096 bytes)
Pages free: 3194.
Pages active: 34594.
Pages inactive: 17870.
Pages wired down: 9878.
"Translation faults": 6333197.
Pages copy-on-write: 81385.
Pages zero filled: 3180051.
Pages reactivated: 343961.
Pageins: 33043.
Pageouts: 78496.
Object cache: 66227 hits of 96952 lookups (68% hit rate)
-
Diagnosing Memory, Thread, and Crash Issues Early
UBSan check Compiler flag Misaligned Pointer -fsanitize=alignment Invalid Boolean -fsanitize=bool Out-of-Bounds Array Access -fsanitize=bounds Invalid Enumeration Value -fsanitize=enum Dynamic Type Violation -fsanitize=vptr Invalid Float Cast -fsanitize=float-cast-overflow Division by Zero -fsanitize=integer-divide-by-zero,-fsanitize=float-divide-by-zero Nonnull Argument Violation -fsanitize=nonnull-attribute, -fsanitize=nullability-arg Nonnull Return Value Violation -fsanitize=returns-nonnull-attribute, -fsanitize-nullability-return Nonnull Variable Assignment Violation -fsanitize=nullability-assign Null Reference Creation and Null Pointer Dereference -fsanitize=null Invalid Object Size -fsanitize=object-size Invalid Shift -fsanitize=shift Integer Overflow -fsanitize=signed-integer-overflow Reaching of Unreachable Point -fsanitize=unreachable Invalid Variable-Length Array -fsanitize=vla-bound
参考地址
Understanding the Exception Types in a Crash Report
iOS Performance and Power Optimization with Instruments
Adding Identifiable Symbol Names to a Crash Report
Diagnosing Memory, Thread, and Crash Issues Early
Improving Your App's Performance
Examining the Fields in a Crash Report
Viewing Virtual Memory Usage
Writing ARM64 Code for Apple Platforms
Investigating Memory Access Crashes
Apple官方文档WatchDog处理方案
Identifying High-Memory Use with Jetsam Event Reports