Flutter Crash Analytics(iOS)

Flutter App crash日志搜集包括三部分,一部分来自于Dart code引起的异常,可以在flutter framework的main函数进行全局捕获,此外还需对Native端iOSAndroid的异常进行捕获.

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

常见的异常处理方案

  1. 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
  2. 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: 开启僵尸检查模式,可以给释放的对象发送消息,来检测异常
    • 异常子类型检查

       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.
  3. EXC_BREAKPOINT

    • EXC_BREAKPOINT (SIGTRAP) and EXC_BAD_INSTRUCTION (SIGILL): 跟中陷阱终断,或者不合法的指令(illegal), 模拟 __builtin_trap
  4. EXC_CRASH (SIGABRT): 程序执行了abort()函数,如算数逻辑错误保护,程序启动的必选参数强制保护.扩展初始化占用时间过长被系统执行了abort()

  5. 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或浮点运算错误
  6. 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)
  7. 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

原文地址:https://www.cnblogs.com/wwoo/p/flutter-crash-analyticsios.html