likes
comments
collection
share

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

作者站长头像
站长
· 阅读数 29

一、前言

  什么是崩溃

   崩溃是指用户在使用应用程序时,由于该应用程序代码存在缺陷,或者操作系统本身存在缺陷而导致的应用程序终止(应用程序被系统“杀死”)而回到系统界面的现象(“闪退”)。

通常应用程序崩溃时会生成一个崩溃文件,开发人员可依据崩溃文件中的信息去修复引起崩溃的问题。

二、如何获取崩溃日志

  1. 从iPhone的隐私设置里面导出来。

      当app发生崩溃的时候,可以从【系统设置】->【隐私与安全性】->【分析与改进】里面找到当时的崩溃日志,通常,崩溃日志格式包含了程序包名,找到对应的文件及时间以及后缀ips或者beta.点击进去,右上角点击分享可以各种方式分享出去。

  1. 通过Xcode导出崩溃日志。

      将iPhone连接到Mac上,打开Xcode,选择顶部菜单栏【Windows】-> 选中【Devices and Simulators】-> 在弹出的弹框中选中Devices下你的设备,右边选中【View Devices Logs】,打开日志窗口 -> 选中你想要导出的日志,点击右键可以导出即可。

  1. 使用Xcode导出线上已发布APP的崩溃日志

      电脑连接手机,打开xcode,选择顶部菜单栏Windows,选中Organizer,选择Crash,APP名字以及版本等,就可以查看各种崩溃日志了。

  1. 在Apple Connets里面查看TF版本用户同意上报的崩溃日志(TF,iOS13版本以上)。

      登录App Store Connects -> 选中你的APP -> 选中【TestFlight】选项 -> 选择【崩溃】选项就能看到TF版本用户上报的崩溃了。

  1. 使用第三方 sdk 获取崩溃信息,并在其对应的网页后台查看对应的崩溃信息。

比如:bugly、firebase、友盟、听云等。

  1. Xcode调试时获取崩溃日志。

      去掉Xcode -> Edit Scheme -> Debug executable选项,如果调试途中发生了崩溃,Xcode就会自动生成crash文件了。

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

三、如何对崩溃日志进行符号化

  1. 对崩溃文件进行符号化以前,要确保ipa包、dSYM文件、crash文件的uuid都一致,如何查看这些文件的uuid呢?如下:

  1. 如何查看 xxx.ipa包的 uuid

    1. 首先,将xxx.ipa的后缀改为xxx.zip,并进行解压,并在文件夹中找到xxx.app。
    2. 打开终端 -> cd到xxx.app的文件夹下 -> 在终端输入“dwarfdump --uuid xxx.app/xxx ”(xxx是你的项目名称)即可。 或者:直接打开终端输入“dwarfdump --uuid ”(注意末尾有空格)后,直接拖入xxx.app的路径,然后再在路径后拼接“/xxx”即可(xxx是你的项目名称)。

    【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

  2. 如何查看dSYM文件的uuid,如下两种方式都可:

    1. 方式一:打开终端 -> cd到有dSYM文件的文件夹下面,在终端输入“dwarfdump --uuid xxx.app.dSYM”即可。
    2. 方式二:打开终端,在终端输入“dwarfdump --uuid xxxxxx”,这里的xxxxxx是 xxx.app.dSYM.dSYM文件的路径,直接拖入就好。

    【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

  3. 如何查看.crash文件的uuid

    1. 打开终端 -> cd到xxx.crash文件的文件夹下面 -> 输入命令:“grep --after-context=2 "Binary Images:" xxx.crash”。 或者直接打开终端,输入“grep --after-context=2 "Binary Images:" ”命令后拖入xxx.crash文件路径即可。如下图所示找到自己工程名,然后后面的<>中的就是uuid:

    【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

    1. 这种方法更简单:直接打开xxx.crash文件,在“Binary Images:”下找到对应的工程名那一行,尖括号内的就是uuid,如:

    【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

  1. 将crash文件具体符号化

    1.   使用 symbolicatecrash 符号化。

          symbolicatecrash 是苹果官方提供的符号化工具,需要我们在终端输入命令执行,可大致分为以下几个步骤执行:

      1. 在桌面新建一个文件夹testCrash,把崩溃日志xxx.crash和 dSYM 文件放进去。

      2. 找到symbolicatecrash路径,不同的Mac可能路径不一样,在终端输入:

        find /Applications/Xcode.app -name symbolicatecrash -type f
        

        此时终端会输出结果: /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash

      3. 通过finder -> 前往文件夹 -> 复制上述地址便可打开symbolicatecrash所在文件夹,此时将symbolicatecrash复制到第一步的testCrash文件夹内。

      4. cd到第一步的testCrash文件夹内,执行以下命令定义 DEVELOPER_DIR(这一步很重要,不执行可能会解析错误):

         export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
        
      5. 执行符号化:

        ./symbolicatecrash ./xxx.crash ./xxx.app.dSYM > crash.txt
        

        命令执行完成后,在文件夹中会多出一个 crash.txt 文件,不出意外的话,这个就是完成符号化的崩溃日志。生成的文件拓展名也可以为 log、crash等。

      Tips:

      symbolicatecrash还有另外一种符号化 方式

      1. 在桌面新建一个文件夹testCrash,把崩溃日志xxx.crash和 dSYM 文件放进去。

      2. 选中xxx.app.dSYM,然后右键找到【显示包内容】并点击,然后沿文件夹找到/Contents/Resources/DWARF/xxx。

      3. 执行命令:symbolicatecrash crashPath dSYM-path > crash.txt

        1. crashPath:xxx.crash文件的路径,直接拖进去就行。
        2. dSYM-path:xxx.app.dSYM文件夹下xxx.app.dSYM/Contents/Resources/DWARF/xxx的路径,将xxx.app.dSYM/Contents/Resources/DWARF/xxx的xxx文件直接拖进去就行
      4. 如果出现报错,则执行以下命令(执行完后再执行第3条命令即可):

      export DEVELOPER_DIR="/Applications/XCode.app/Contents/Developer"
      alias symbolicatecrash="/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash"
      
  1. 通过atos命令进行符号化。这种方式适合于对于单行崩溃信息进行符号化,用法:

      在终端输入:atos -arch cpu指令集种类 -o dSYM文件路径 -l 堆栈地址1 堆栈地址0。

      参数含义:

       cpu指令集种类:可以为armv6、armv7、armv7s、arm64,与崩溃手机能对应起来就行。

        堆栈地址1 堆栈地址0 的含义以下图的16行进行符号化进行说明:

    【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

        地址0:运行时的堆栈地址。

        地址1:进程运行时的起始地址。

        偏移量:运行时的偏移地址。

        实际上:运行时堆栈地址=运行时起始地址+ 偏移地址,以第0行为例:

          0x000000018e691c20 = 0x18e690000 + 1c20(7200 )

在终端对第16行进行符号化后的结果为:

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

可以看到解析后的内容是:-[---ion get***tion:] (****ple) (*****tion.m:209】,与第三方解析工具中解析好的文件进行对比,两个结果一致:

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

注意:如果起始地址相同,可以同时符号化多行信息,只需要输入一个起始地址+多个堆栈地址即可,格式。 如:atos -arch cpu指令集种类 -o dSYM文件路径 -l 起始地址 堆栈地址-1 堆栈地址-2 堆栈地址-3。如下所 示,起始地址后面加了3个堆栈地址,相应的就会连续符号化三行代码信息:

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

  1.   如何符号化系统库文件

    1. 符号化之前需要知道崩溃手机的系统版本号和cup类型,此次崩溃的系统版本为14.5,cup类型为arm64e。

    2. 下载系统版本为14.5、cup类型为arm64e的系统符号文件到Mac上,并解压。

    3. 上面的崩溃可以看出来0-10行的崩溃信息都是libnetwork.dylib库的堆栈信息,所以我们需要符号化libnetwork.dylib相关的崩溃信息。

    4. 查找libnetwork.dylib的路径。如果你将下载好的系统符号文件是保存到桌面上的,可以通过终端用以下命令查找:

         find /Users/ 你的电脑名 /Desktop -name libnetwork.dylib
      

      注意:上面的【你的电脑名 是你的电脑名!注意替换! 【libnetwork.dylib】是需要符号化的系统库名。

      【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

    如上图我这里找出来两个libnetwork.dylib路径,由于我们的我们发生崩溃的机器是14.5(arm64e),所以我们复制第一行就行了。

    1. 通过finder -> 前往文件夹 -> 输入刚刚复制的路径,打开文件夹,就会找到libnetwork.dylib文件。

    2. 在终端通过类似2.3中的atos命令即可符号化相关信息:

      atos -arch cpu指令集种类 -o 系统库在mac的路径 -l 堆栈地址1 堆栈地址0
      
      1. cpu指令集种类:根据这个崩溃我们这里输入arm64e就好。
      2. 系统库在mac的路径:直接用上一步找到的libnetwork.dylib文件,直接拖入路径就好,这里为什么没有直接用find出来的路径,对比一下拖入路径就知道有差别了(下图中画红线的位置)。

      【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

      1. 这里我只符号化了前四行信息,可以与第三方crash工具符号化后的信息对比可知是准确的:

      【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

这里我在网上找到了部分版本的系统符号文件:

百度网盘 提取码: mijx

iOS system symbol files(16.0-16.4.1)

iOS system symbol files(15.6.1-15.7.5)

iOS system symbol files(15.0.0-15.6)

iOS system symbol files(14.0.0-14.8.1)

iOS system symbol files(13.0.0-13.7.0)

iOS system symbol files(12.0.0-12.5.7)

其他系统的符号文件可以通过下面的链接进行查找:

github.com/CXTretar/iO…

四、崩溃日志大致构成

如下图所示,崩溃日志大致可以由6个部分组成:

【iOS】一文学会分析崩溃堆栈(高效crash崩溃分析手册)

  1.   进程信息 

  发生crash进程的相关信息:

Incident Identifier: 6156848E-344E-4D9E-84E0
CrashReporter Key:   76f2fb60060d6a7f814973377cbdc8
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.TouchCa
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)
  • Incident Identifier:报告的唯一标识符。两份报告从不共享相同的内容Incident Identifier
  • CrashReporter Key:匿名的每台设备标识符。来自同一台设备的两份报告包含相同的值。此标识符在擦除设备时重置。
  • Beta Identifier:崩溃应用程序的设备和供应商组合的唯一标识符。来自同一供应商和同一设备的应用程序的两份报告包含相同的值。此字段仅适用于应用程序的 TestFlight 构建,并替换该字段。CrashReporter Key
  • Hardware Model:运行应用程序的特定设备型号。
  • Process: 崩溃进程的可执行名称。这与应用的信息属性列表中的值相匹配。括号中的数字是进程 ID。CFBundleExecutable
  • Path:可执行文件在磁盘上的位置。macOS 将用户可识别的路径组件替换为占位符值以保护隐私。
  • Identifier:崩溃的进程。如果二进制文件没有,则此字段包含进程名称或占位符值。CFBundleIdentifierCFBundleIdentifier
  • Version: 崩溃的进程版本。该值是应用程序的和的串联。CFBundleVersion``CFBundleShortVersionString
  • AppStoreTools:Xcode 的版本用于编译您的应用程序的位代码并将您的应用程序精简为特定于设备的变体。
  • AppVariant:由应用程序细化生成的应用程序的特定变体。该字段包含多个值,将在本节后面进行介绍。
  • Code Type: 崩溃进程的 CPU 架构。该值为ARM-64ARMX86-64或之一X86
  • Role:终止时分配给进程的task_role 。当您分析崩溃报告时,此字段通常没有帮助。
  • Parent Process:启动崩溃进程的进程的名称和进程 ID(在方括号中)。
  • Coalition:包含应用程序的进程联盟的名称。进程联盟跟踪相关进程组之间的资源使用情况,例如支持应用程序中特定 API 功能的操作系统进程。大多数进程,包括应用程序扩展,都形成了自己的联盟。
  • Date/Time:崩溃的日期和时间。
  • Launch Time:应用程序启动的日期和时间。
  • OS Version:发生崩溃的操作系统版本,包括内部版本号。
  1.   异常信息(Exception information

    Exception Type:  EXC_CRASH (SIGKILL)
    Exception Codes: 0x0000000000000000, 0x0000000000000000
    Exception Note:  EXC_CORPSE_NOTIFY(?)
    Triggered by Thread:  0
    

        每个崩溃报告都包含异常信息。此信息部分告诉您进程如何终止,但可能无法完全解释应用程序终止的原因。这些信息很重要,但经常被忽视。异常信息通常包含了下面字段所提供有关异常的信息,但是崩溃报告不会包含以下所有字段,只会展示部分字段:

    • Exception Type: 终止进程的 Mach 异常的名称,以及括号中相应的 BSD 终止信号的名称。
    • Exception Codes:处理器的具体信息有关的异常编码成一个或多个64位进制数。通常情况下,这个区域不会被呈现,因为将异常代码解析成人们可以看懂的描述是在其它区域进行的。
    • Exception Subtype:异常代码的名字。
    • Exception Message:从异常代码中提取的额外的可阅读的信息。
    • Exception Note:不特定于一种异常类型的附加信息。如果此字段包含,则崩溃不是由硬件陷阱引起的,因为该进程已被操作系统明确终止或调用的进程。如果此字段包含,则进程没有崩溃,但操作系统可能随后请求终止进程。如果此字段包含,则进程不会终止,因为创建崩溃报告的问题不是致命的。EXC_CORPSE_NOTIFYabort()SIMULATED (this is NOT a crash)NON-FATAL CONDITION (this is NOT a crash)
    • Termination Reason: 操作系统终止进程时指定的退出原因信息。进程内部和外部的关键操作系统组件在遇到致命错误时终止进程,并在此字段中记录原因。您可以在此字段中找到的信息示例包括有关无效代码签名、缺少依赖库或访问没有目的字符串的隐私敏感信息的消息。
    • Triggered by Threador Crashed Thread:引发异常的线程。
  1.   诊断信息(Application Specific Information:)

    Application Specific Information:
    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
    UserInfo:(null)'
    

        操作系统有时包括额外的诊断信息。此信息使用多种格式,具体取决于崩溃的原因,但是“诊断信息”不一定每个crash文件都有。因为Application Specific Information有时会从崩溃报告中删除,以避免在消息中记录隐私敏感信息。

  1.   线程回溯(Exception Backtrace)

        崩溃进程的每个线程都被捕获为回溯,记录进程终止时在线程上运行的代码。回溯与使用调试器暂停进程时看到的类似。由语言异常引起的崩溃包括一个额外的回溯,Last Exception Backtrace位于第一个线程之前。如果您的崩溃报告包含Last Exception Backtrace,请参阅解决语言异常崩溃以获取特定于语言异常崩溃的信息。

        每个回溯的第一行列出了线程号和线程名称。出于隐私原因,通过Xcode 中的崩溃管理器传送的崩溃报告不包含线程名称。这个例子显示了三个线程的回溯;Thread 0崩溃,并通过其名称被识别为应用程序的主线程:

    Thread 0 name: Dispatch queue: com.apple.main-threadThread 0 Crashed: 0 TouchCanvas 0x0000000102afb3d0 CanvasView.updateEstimatedPropertiesForTouches(:) + 62416 (CanvasView.swift:231) 1 TouchCanvas 0x0000000102afb3d0 CanvasView.updateEstimatedPropertiesForTouches(:) + 62416 (CanvasView.swift:231)

    2 TouchCanvas 0x0000000102af7d10 ViewController.touchesMoved(_:with:) + 48400 (:0)

    3 TouchCanvas 0x0000000102af80b8 @objc ViewController.touchesMoved(_:with:) + 49336 (:0)

    4 UIKitCore 0x00000001ba9d8da4 forwardTouchMethod + 328

    5 UIKitCore 0x00000001ba9d8e40 -[UIResponder touchesMoved:withEvent:] + 60

    6 UIKitCore 0x00000001ba9d8da4 forwardTouchMethod + 328

    7 UIKitCore 0x00000001ba9d8e40 -[UIResponder touchesMoved:withEvent:] + 60

    8 UIKitCore 0x00000001ba9e6ea4 -[UIWindow _sendTouchesForEvent:] + 1896

    9 UIKitCore 0x00000001ba9e8390 -[UIWindow sendEvent:] + 3352

    10 UIKitCore 0x00000001ba9c4a9c -[UIApplication sendEvent:] + 344

    11 UIKitCore 0x00000001baa3cc20 __dispatchPreprocessedEventFromEventQueue + 5880

    12 UIKitCore 0x00000001baa3f17c __handleEventQueueInternal + 4924

    13 UIKitCore 0x00000001baa37ff0 __handleHIDEventFetcherDrain + 108

    14 CoreFoundation 0x00000001b68a4a00 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 24

    15 CoreFoundation 0x00000001b68a4958 __CFRunLoopDoSource0 + 80

    16 CoreFoundation 0x00000001b68a40f0 __CFRunLoopDoSources0 + 180

    17 CoreFoundation 0x00000001b689f23c __CFRunLoopRun + 1080

    18 CoreFoundation 0x00000001b689eadc CFRunLoopRunSpecific + 464

    19 GraphicsServices 0x00000001c083f328 GSEventRunModal + 104

    20 UIKitCore 0x00000001ba9ac63c UIApplicationMain + 1936

    21 TouchCanvas 0x0000000102af16dc main + 22236 (AppDelegate.swift:12)

    22 libdyld.dylib 0x00000001b6728360 start + 4

    Thread 1:

    0 libsystem_pthread.dylib 0x00000001b6645758 start_wqthread + 0

    Thread 2:

    0 libsystem_pthread.dylib 0x00000001b6645758 start_wqthread + 0...

        在线程号之后,回溯的每一行代表回溯中的一个栈帧。

    0   TouchCanvas                       0x0000000102afb3d0 CanvasView.updateEstimatedPropertiesForTouches(_:) + 62416 (CanvasView.swift:231)
    

        堆栈帧的每一列都包含有关崩溃时执行的代码的信息。以下列表使用上述示例中堆栈帧 0 的组件。

    • 0. 栈帧号。堆栈帧按调用顺序排列,其中帧 0 是在执行暂停时正在执行的函数。第 1 帧是调用第 0 帧中的函数的函数,依此类推。
    • TouchCanvas. 包含正在执行的函数的二进制文件的名称。
    • 0x0000000102afb3d0. 正在执行的机器指令的地址。对于每个回溯中的第 0 帧,这是进程终止时在线程上执行的机器指令的地址。对于其他堆栈帧,这是控制返回到该堆栈帧后执行的第一条机器指令的地址。
    • CanvasView.updateEstimatedPropertiesForTouches(_:). 在完全符号化的崩溃报告中,正在执行的函数的名称。出于隐私原因,函数名称有时限制为前 100 个字符。
      1. 后面的数字+是从函数入口点到函数中当前指令的字节偏移量。
    • CanvasView.swift:231. 如果您有二进制文件,则包含代码的文件名和行号。
  1.   线程状态(Thread state)

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000001   x1: 0x0000000000000000   x2: 0x0000000000000000   x3: 0x000000000000000f    
    x4: 0x00000000000001c2   x5: 0x000000010327f6c0   x6: 0x000000010327f724   x7: 0x0000000000000120    
    x8: 0x0000000000000001   x9: 0x0000000000000001  x10: 0x0000000000000001  x11: 0x0000000000000000   
    x12: 0x00000001038612b0  x13: 0x000005a102b075a7  x14: 0x0000000000000100  x15: 0x0000010000000000   
    x16: 0x00000001c3e6c630  x17: 0x00000001bae4bbf8  x18: 0x0000000000000000  x19: 0x0000000282c14280   
    x20: 0x00000001fe64a3e0  x21: 0x4000000281f1df10  x22: 0x0000000000000001  x23: 0x0000000000000000   
    x24: 0x0000000000000000  x25: 0x0000000282c14280  x26: 0x0000000103203140  x27: 0x00000001bacf4b7c   
    x28: 0x00000001fe5ded08   fp: 0x000000016d311310   lr: 0x0000000102afb3d0    
    sp: 0x000000016d311200   pc: 0x0000000102afb3d0 cpsr: 0x60000000   
    esr: 0xf2000001  Address size fault

  闪退时寄存器中的值。一般不需要这部分的信息,因为回溯部分的信息已经足够让你找出问题所在。

  1.   二进制映像(Binary Images)

    Binary Images:
           0x102488000 -        0x10469bfff +YourAppName <9d51dda0560f3dac890fb73d86876579>
           0x1b650c000 -        0x1b654fe1f  libobjc.A.dylib <c1140b6565c73a26bd127bc730320ea0>
           0x1b76f6000 -        0x1b803ffff  Foundation <e5f615c7cc5e3656860041c767812a35>
           0x1bd198000 -        0x1bd38afff  CoreServices <12faf5a90d44309691bbab43a7296839>
           0x1bd38b000 -        0x1bd770fff  CoreFoundation <42ccfc7bff323d258f01ccb2ad843a8b>
           0x1bd771000 -        0x1be51efff  Network <66645d9c43363a4d8060866f18c64bd2>
           0x1be51f000 -        0x1be8e8fff  CFNetwork <7f3313c9143533338c82dc961429d0b3>
           0x1be8e9000 -        0x1bea2dfff  CoreTelephony <ed20b0bfbece34b09eed6d49abcff135>
           0x1bef51000 -        0x1bf55ffff  CoreGraphics <19e38c811d933c40af2a05af98468741>
           0x1bf560000 -        0x1c0d53fff  UIKitCore <cf21ad9cefbf3961a7c054bd30cefea9>
           0x1c26a4000 -        0x1c2b43fff  ImageIO <ea97f04aaa543f58af8b7f92aeac763c>
    

        闪退时已经加载的二进制文件。崩溃报告的二进制图像部分列出了终止时进程中加载的所有代码,例如应用程序可执行文件和系统框架。Binary Images部分中的每一行表示一个二进制图像。

    • 0x102488000 - 0x10469bfff 进程中二进制图像的地址范围。第一个地址是二进制文件的加载地址。
    • +YourAppName 二进制名称(包名,每个SDK都有包名-app项目也会被当做一个SDK)。+前缀表示二进制文件不是macOS的一部分。
    • <9d51dda0560f3dac890fb73d86876579> 唯一标识二进制映像的生成UUID。在符号化崩溃报告时,使用此值查找相应的dSYM文件。

五、常见异常信息归类

  1. Exception Type: 异常类型:

    1. EXC_BAD_ACCESS

        此类型的Excpetion是我们最常碰到的Crash,通常用于访问了不该访问的内存导致。野指针引起的崩溃,访问了一个已经释放的内存而导致,向已经释放的对象或向它发送消息时,EXC_BAD_ACCESS就会出现。造成EXC_BAD_ACCESS最常见的原因是,在初始化方法中初始化变量时用错了所有权修饰符,这会导致对象过早地被释放。举个例子,在viewDidLoad方法中为UIViewController创建了一个包含元素的NSArray,却将该数组的所有权修饰符设成了assign而不是strong。现在在viewWillAppear中,若要访问已经释放掉的对象时,就会得到名为EXC_BAD_ACCESS的崩溃。

    1. EXC_BAD_INSTRUCTION

    此类异常通常由于线程执行非法指令导致。

    • 在代码中修改了storyboard与outlet的对应关系,但是storyboard没有更新时发生过此crash。

    • 与第三方库中方法冲突时发生过此crash。

    • 调用系统方法时传入了不恰当的指针类型。

    1. EXC_ARITHMETIC

    崩溃的线程执行了无效的算术运算,例如除以零或浮点错误。

    1. EXC_CRASH (SIGABRT)

    非正常退出,大多数发生这种crash的原因是因为未捕获的Objective-C/C++异常,导致进程调用abort()方法退出。例如:如果APP消耗了太多的时间在初始化,watchdog(看门狗定时器)就会终止程序运行。

    1. EXC_BREAKPOINT (SIGTRAP)

    这个异常是为了让一个附加的调试器有机会在执行过程中的某个特定时刻中断进程,我们可以在代码里面添加__builtin_trap()方法手动触发这个异常。如果没有附加调试器,则会生成crash文件。 较低级别的库(例如libdispatch)会在遇到致命错误时捕获该进程。 swift代码在遇到下面2种情况的时候也会抛出这个异常:

    • 一个非可选类型的值为nil

    • 错误的强制类型转换,如把NSString转换为NSDate等等。

    1. EXC_BAD_INSTRUCTION (SIGILL)

    程序尝试的去执行一个非法的或者没有定义的指令。如果配置的函数地址有问题,进程在执行函数跳转的时候,就会发生这个crash.

  2. 一般 Exception Type后面的"()"还会带有补充信息。补充信息大致有:

  1. SIGSEGV:  通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。

  2. SIGABRT(Abnormal Exit):  收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。

  3. SEGV:( Segmentation Violation)代表无效内存地址,比如空指针,未初始化指针,栈溢出等,一般是表示内存不合法。

  4. SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)。

  5. SIGILL(illegal): 尝试执行非法的指令,可能不被识别或者没有权限。

  6. SIGFPE: 数学计算相关问题, 比如除零操作 。

  7. SIGIPIPE:  管道另一端没有进程接收数据 。

  8. SIGTRAP(Trace Trap): 调试器相关。

  9. SIGKILL: 进程被系统强制结束,通过查看Termination Reason找到crash信息。

  10. SIGQUIT: 该进程在具有管理其生命周期的权限的另一进程的请求下终止。 SIGQUIT并不意味着进程崩溃,但是可以说明该进程存在一些问题。 ****比如在iOS中,第三方键盘应用可能在在其他APP中被唤起,但是如果键盘应用需要很长的时间去加载,则会被强制退出。

  11. Exception Codes: 异常代码。

      有关异常的处理器特定信息编码为一个或多个64位十六进制数,下面列出了几种常见的异常代码:

  • 0xbaaaaaad 此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发。
  • 0xbad22222 当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序。
  • 0x8badf00d (发音为“ate bad food”)。操作系统的监视器终止了应用程序。程序启动或者恢复时间过长被watch dog终止。
  • 0xc00010ff (发音为“cool off”)。由于热事件,操作系统终止了应用程序。这可能是发生此崩溃的特定设备或其运行环境的问题。程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止。
  • 0xdead10cc (发音为“dead lock”)。操作系统终止了应用程序,因为它在挂起期间持有文件锁或SQLite数据库锁。使用beginBackgroundTask(withName:expirationHandler:)在主线程上请求额外的后台执行时间。在开始写入文件之前发出此请求,以便在应用程序挂起之前完成这些操作并放弃锁定。在应用程序扩展中,使用 beginActivity(options:reason:) 管理此工作。程序退到后台时还占用系统资源,如通讯录被系统终止。
  • 0xbaadca11(发音为“bad call”)。操作系统因未能响应PushKit通知报告CallKit调用而终止应用程序。
  • 0xdeadfa11 程序无响应用户强制关闭。
  • 0xbaddd15c(发音为“bad disc”)。操作系统终止了删除缓存的应用程序,试图回收磁盘空间。许多因素导致磁盘空间不足。您可以通过最小化向磁盘写入的内容并管理文件的整个生命周期来提供帮助。
  • 0xc51bad01 watchOS终止了应用程序,因为它在执行后台任务时占用了太多的CPU时间。优化执行后台任务的代码以提高CPU效率,或减少应用程序在后台运行时执行的工作量以解决此崩溃。
  • 0xc51bad02 watchOS终止了应用程序,因为它未能在分配的时间内完成后台任务。减少应用程序在后台运行时执行的工作量,以解决此崩溃。
  • 0xc51bad03 watchOS终止了应用程序,因为它未能在分配的时间内完成后台任务,但系统总体上非常繁忙,应用程序可能没有收到太多CPU时间来执行后台任务。虽然您可以通过减少应用程序在后台任务中执行的工作量来避免此问题,但0xc51bad03并不表示应用程序做错了什么。更可能的是,由于系统整体负载,该应用程序无法完成其工作。

更多开发中遇到的错误码可以参考链接en.wikipedia.org/wiki/Hexspe…

  1. Termination Reason: 崩溃原因

例如中常常有类似如下的描述,描述中写出了导致崩溃的大致原因:

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
UserInfo:(null)'
  1. NSInvalidArgumentException

      非法参数异常是objective-C代码最常出现的错误,所以平时写代码的时候,需要多加注意,加强对参数的检查,避免传入非法参数导致异常,其中尤以nil参数为甚。

    1. 集合数据的参数传递 比如NSMutableArray,NSMutableDictionary的数据操作

      1. NSDictionary不能删除nil的key NSInvalidArgumentException reason, -[__NSCFDictionary removeObjectForKey:]:attempt to remove nil key
      2. NSDictionary不能添加nil的对象 NSInvalidArgumentException reason,***-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[4]
      3. 不能插入nil的对象 NSInvalidArgumentException reason,***-[__NSArrayM insertObject:atIndex:]: object cannot be nil
      4. 其他一些nil参数 NSInvalidArgumentException reason, -[__NSCFString hasSuffix:]: nil argument
    2. 其他一些API的使用 APP一般都会有网络操作,免不了使用网络相关接口,比如NSURL的初始化,不能传入nil的http地址:NSInvalidArgumentException reason,***-[NSURL initFileURLWithPath:]: nil string parameter

    3. 人为实现的方法 (1).h文件里函数名,却忘了修改.m文件里对应的函数名 (2)使用第三方库时,没有添加”-ObjC”flag (3)MRC时,大部分情况下是因为对象被提前release了,在我们心里不希望他release的情况下,指针还在,对象已经不在了。 NSInvalidArgumentException reason =-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to … NSInvalidArgumentException reason =-[UIThreadSafeNode_responderForEditing]: unrecognized selector sent …

  1. NSRangeException

      越界异常(NSRangeException)也是比较常出现的异常,有如下几种类型:

    1. 数组最大下标处理错误 比如数组长度count,index的下标范围[0,count-1],在开发时,可能index的最大值超过数组的范围; NSRangeException reason = ***-[_NSarrayM objectAtIndex:]: index 19 beyond bounds [0..15]
    2. 下标的值是其他变量赋值 这样会有很大的不确定性,可能是一个很大的整数值 NSRangeException reason = *-[_NSarrayM objectAtIndex:]: index 2147483647 beyond bounds [0..4] *NSRangeException reason = -[_NSarrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0..4]
    3. 使用空数组 如果一个数组刚刚初始化还是空的,就对它进行相关操作 NSRangeException reason = ***-[_NSarrayM objectAtIndex:]: index 0 beyond bounds for empty array 所以,为了避免NSRangeException的发生,必须对传入的index参数进行合法性检查,是否在集合数据的个数范围内。
  1. NSGenericException

      NSGenericException这个异常最容易出现在foreach操作中,在for in循环中如果修改所遍历的数组,无论你是add或remove,都会出错,比如:

      执行上面的代码会出现以下错误: NSGenericException reason = *** Collection<__NSArrayM:0x175058330>was mutated while being enumerated. 原因就在这 “for in”,它的内部遍历使用了类似 Iterator进行迭代遍历,一旦元素变动,之前的元素全部被失效,所以在foreach的循环当中,最好不要去进行元素的修改动作,若需要修改,循环改为for遍历,由于内部机制不同,不会产生修改后结果失效的问题。

  1. NSMallocException

      这也是内存不足的问题,无法分配足够的内存空间 NSMallocException reason = ***-[NSConcreteMutableData appendBytes:length:]: unable to allocate memory for length(81310)

  1. NSFileHandleOperationException

      处理文件时的一些异常,最常见的还是存储空间不足的问题,比如应用频繁的保存文档,缓存资料或者处理比较大的数据: NSFileHandleOperationException reason:***-[NSConcreteFileHandle writeData:]: No space left on device 所以在文件处理里,需要考虑到手机存储空间的问题。

  1. NSInternalInconsistencyException

系统内部的一些问题导致,这种原因的崩溃多种多样,最常见的如子线程刷新UI、命中断言。

六、其他

相关高频单词英汉翻译:

【ACCESS:入口 、访问 】、【instruction:指令、命令】、【arithmetic:算术、算术的】、【illegal :非法的】、【Segmentation:分割】、【Violation:违规;违背;违例;违反】、【Malloc:内存分配;分配内存】、【Handle:管理、指挥、手柄】、【Operation:运算;运转】、【Generic:通用的;一般的】、【Range:范围】、【invalid:无效的】、【Argument:参数、论点】、【Collection:集合】、【mutate:变异,突变 ; 转变】、【Internal:里面的;内部的】、【Inconsistency:矛盾;不一致】、【trigger:引发、引起、触发因素 】

几个容易混淆的专业术语解释

  1. 未初始化指针,eg:Person *a。

  2. 空指针,没有存储任何内存地址的指针就称为空指针也就是nil,NULL。eg:Person *a = nil/NULL;(nil是OC对象类型的空指针,NULL是C类型的空指针)

  3. 野指针,指向的对象内存被释放或者收回(这个地址可能给其他对象使用了),但是指针没有让其变为空指针,依然还指向那块内存,如果再向其发消息就会crash。

  4. 僵尸对象,被释放的对象比如上面的a所指向的对象执行了release,那么对象的内存还未被重新分配的时候,这个对象其实还能被访问因为内存还存在,这个时候就被称之为僵尸对象。

对于野指针相关的疑问回答:

1. 为什么野指针会导致崩溃?

野指针导致崩溃的主要原因是它指向了一个已经被释放或回收的内存地址,但指针本身并没有被更新为nil或指向其他有效的内存地址。当程序试图通过这个野指针访问或操作内存时,它实际上是在访问一个不再属于该对象的内存区域,这可能导致数据损坏、内存泄漏或其他未定义的行为。

具体来说,野指针的产生通常是由于以下情况之一:

  1. 所指向的对象被释放或回收,但是指针本身没有被置为nil
  2. 在声明指针变量时没有为其赋初始值,导致它指向一个随机的内存空间。

由于野指针指向的内存可能已经被系统分配给其他对象或用于其他目的,因此通过野指针进行的任何操作都是危险的。当程序尝试读取或写入这块内存时,它可能会触发EXC_BAD_ACCESS异常,导致应用程序崩溃。此外,如果野指针恰好指向了一个已经被释放但尚未被重用的僵尸对象,那么在一段时间内可能不会立即崩溃,但这样的行为是不稳定的,随时可能导致问题23。

2. 如何避免野指针导致的崩溃,开发者应该采取以下措施:

  • 确保在释放对象后立即将指向该对象的指针置为nil
  • 在使用指针之前检查其是否为nil,避免对空指针进行解引用。
  • 使用ARC(自动引用计数)来管理对象的生命周期,以减少手动管理内存的需要。
  • 使用Xcode提供的工具,如内存检测器(Memory Debugger)和僵尸对象检测(Zombie Objects Detection),来识别和修复内存管理问题。

总之,野指针是iOS开发中常见的内存管理问题,开发者需要仔细处理指针和对象的生命周期,以确保程序的稳定性和可靠性。

转载自:https://juejin.cn/post/7371370680515166246
评论
请登录