• 主页
  • 系列总集
  • OpenCV
  • CMake
  • iOS
  • Java
  • 前端
所有文章 关于我

  • 主页
  • 系列总集
  • OpenCV
  • CMake
  • iOS
  • Java
  • 前端

iOS崩溃闪退Crash分析原因

2018-06-06

官方的分析文档

苹果官方十年间(2009-01-29 ~ 2018-01-08)维护了一个关于Crash分析的文档十分有价值,主要讲解了以下内容

  1. 什么是符号,符号和App的关系
  2. 如何符号化(让人看得懂)
  3. 如何分析各种Crash的可能原因
  4. 内存和CPU的使用限制引起的Crash

本篇博客记录其中一些重点,推荐大家都去阅读原文

符号化某个地址的命令行工具

在 Symbolicating Crash Reports With atos 章节里提到了命令行 atos(addresses to symbols) 可以帮助我们单行符号化某个地址

1
2
3
4
5
6
7
8
9
10

## 解释
# atos -arch <Binary Architecture> \
# -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> \
# -l <load address> <address to symbolicate>

## 实践
atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]

如何区别OC的Crash和NULL指针的Crash

异常一般来说分为 OC 层的 NSException 和 底层的 NULL 错误,可以通过Crash报告的字段不同进行分析

OC层的长这个样子,包含Type、Codes、Note 三个字段

1
2
3
4
5
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0

NULL错误的长这个样子,包含 Type、SubType、Signal、Reason、Process等多个字段

1
2
3
4
5
6
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0

常见的 EXC SIG 崩溃原因分析

Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]

访问了错误的内存,内存在使用时会有一定的保护等级,例如只可读,可读可写

官方文档列举了以下的常见原因:

  1. 如果堆栈顶部最后调用 objc_msgSend 或者 objc_release,大概率是访问了一个本来应该被释放的对象,可以通过僵尸对象( Zombies instrument)调试方法来查找
  2. gpus_ReturnNotPermittedKillClient 如果这个在堆栈顶部,一般都是OpenGL ES 或者 Metal 在后台渲染了什么东西

这种常见的内存访问错误的Crash,也可以在开发的Debug阶段开启 Address Sanitizer 他会帮你进行警告

Abnormal Exit [EXC_CRASH // SIGABRT]

异常退出,一般都是有没有Catch住的 OC/C++ 的异常导致的

官方文档列举了一种可能情况是

如果你的App每次启动就Crash,可能是你的启动App的时候写的逻辑太多了,被系统的看门狗干掉了,简化你的启动逻辑

Trace Trap [EXC_BREAKPOINT // SIGTRAP]

这种类似于异常退出,主要是在代码里主动插入 __builtin_trap() 方法来给debugger提供调试的能力

如果没有debugger就会Crash,底层的一些库用他来处理一些重要的异常

根据官方文档描述,这种日志应该只会存在于Debug期间,常见的是Swift中的两种情况

  1. 给一个非optional的变量赋值了nil
  2. 或者错误的强制类型转换

Illegal Instruction [EXC_BAD_INSTRUCTION // SIGILL]

这个错误比较硬核,代表着执行了一个未定义的指令,或者是跳转到了一个非法的地址

其中前者 EXC_BAD_INSTRUCTION 多由Intel的 ud2 opcode 产生

目前文档中没有其他的官方提示

Quit [SIGQUIT]

这个是退出信号,一般来讲都是错误的操作或者被其他任务结束了

官方文档举出一个例子,比如键盘程序在某个App中被唤醒,如果长时间没有弹出来,就会被App杀掉,通过这个信号

从文档来看,如果出现了这个Crash,可以从输入框优先排查

Killed [SIGKILL]

这个信号和Quit的区别在于该App被系统强杀掉了,经常出现在Watch App中,有三个编码代表三个意思

代码 含义
0xc51bad01 Watch App在后台的任务占用了太多CPU,优化后台任务逻辑解决
0xc51bad02 Watch App在后台的任务alloc失败,减少后台任务可以解决
0xc51bad03 Watch App在后台的任务alloc失败,这个和 02 的区别在于不是App的原因,更可能是系统能力不足

Guarded Resource Violation [EXC_GUARD]

这个异常只会发生在MacOS或者早期的iOS版本,原因是系统会将某些文件设为保护,如果使用普通的文件操作方法去访问,就会报错

正确的方式是使用系统的私有API访问,但是这些错误都有比较详细的描述,可以通过描述确定是哪个方法产生Crash

Resource Limit [EXC_RESOURCE]

使用了过多的资源,这里的资源包括内存和CPU两种,可以通过Exception Subtype来区别

出现这个报告不代表App一定产生了Crash,当Exception Note 包含了 NON-FATAL CONDITION 字眼时,代表仅仅是警告,并没有真的Crash

官方文档详细的解析了SubType

SubType 含义
MEMORY 使用了过多的内存,会产生被系统强制杀掉的风险
WAKEUPS 代表某个Thread每秒钟唤醒次数太多了,这样会导致CPU唤醒次数过多从而消耗电量

不常见的Crash (Other Exception Types)

苹果还列举了一系列不常见的Crash的ID,例如 0xbaaaaaad、0xbad22222、0x8badf00d、0xc00010ff、0xdead10cc、0x2bad45ec

大多这些Crash没有什么共性,都是很特别的Case,如果实在找不到答案,建议直接联系苹果官方

Low Memory 的原因总结

在所有的Crash报告里,还需要关注的是一种和Crash报告头部很像,但是用于描述低内存情况的报告

iOS的内存机制中,如果触发了低内存阈值,系统级会有通知让所有App进行内存回收,如果进行完第一轮回收后,内存仍然不够

系统就会尝试杀掉当前的进程(process),具体机制不知道是不是随机的

如果你运气不好,你的App被干掉了,就会收获一个Low Memory Report,它和Crash Report的区别在于没有堆栈

而当前导致Low Memory的原因,就会被记录在最重要的 table of processes 的 reason 字段 ,常见的原因如下

Reason 描述
per-process-limit 单一进程超过了系统规定的进程内存限制大小,常见于使用了MapView、SpriteKit的模块
vm-pageshortage/vm-thrashing/vm 进程被强制杀掉了
vnode-limit 打开了太多的文件
highwater 系统的守护进程超过了最大值(水位线)
jettisoned 不知道什么原因,总之进程被废弃了

最重要的一点:如果你没有看到之上的原因,那么你这个报告就不是 Low Memory Report

关于其他内存的文章,可以参考Memory Usage Performance Guidelines,我还没看,但是看名字很厉害

赏

请问老板还招人么(/ω\)

支付宝
微信
  • iOS
  • OC
  • Tips

扫一扫,分享到微信

微信分享二维码
iOS储存自定义类进入UserDefaults
IPA安装包下载和重签名
© 2021 Alan Li
Hexo Theme Yilia by Litten
  • 所有文章
  • 关于我

tag:

  • iOS
  • Java
  • Collection
  • Python
  • Shell
  • CMake
  • Memory
  • JavaScript
  • Architecture
  • AnchorPoint
  • Android
  • Web
  • Annotation
  • AFNetworking
  • Window
  • ViewController
  • AutoLayout
  • Dozer
  • CoreAnimation
  • Cycle Retain
  • Block
  • UI
  • IDE
  • FrontEnd
  • CSS
  • Category
  • TableViewCell
  • Security
  • Net
  • JSP
  • Spring
  • C
  • MyBatis
  • Date
  • React
  • GCD
  • UITouch
  • Gesture
  • UIControl
  • Git
  • HTML
  • HTTPS
  • HTTP
  • Servlet
  • Server
  • DataBase
  • MySQL
  • Linux
  • Tutorial
  • Ajax
  • Type
  • JQuery
  • JSON
  • Exception
  • Parameter
  • Reflect
  • Thread
  • Sort
  • KVO
  • MKMapKit
  • Overlay
  • Maven
  • Configure
  • Tips
  • Transaction
  • Swift
  • NavigationBar
  • Nginx
  • Runtime
  • OpenCV
  • Property
  • Playground
  • Protocol
  • Redux
  • ScrollView
  • Session
  • Cookie
  • Shiro
  • Error
  • Singleton
  • RegEx
  • StackView
  • StatusBar
  • Base64
  • Socket
  • TCP
  • IP
  • TextField
  • CALayer
  • UILabel
  • View
  • Animation
  • Xcode
  • Hexo
  • Terminal
  • OC
  • Device
  • Log
  • Image
  • JUnit
  • Oval
  • Archive
  • XSS
  • Compiler
  • Aspect
  • Responder
  • Class
  • FireWall
  • RetainCount
  • Const
  • Frame
  • String
  • Symbols
  • Framework
  • CocoaPods
  • Unity
  • Message
  • Button
  • AuthorizationStatus
  • Struct
  • XCTest
  • NSNotification
  • Contact

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

我写的,大概率是错的。。。。。