鸿蒙HarmonyOS实战开辟:CppCrash故障分析(历程崩溃)

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

 鸿蒙NEXT开辟实战往期必看文章:
一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开辟!
“非常详细的” 鸿蒙HarmonyOS Next应用开辟学习路线!(从零根本入门到精通)
HarmonyOS NEXT应用开辟案例实践总结合(连续更新......)
HarmonyOS NEXT应用开辟性能优化实践总结(连续更新......)

历程崩溃指C/C++运行时崩溃。FaultLogger模块提供历程崩溃故障检测、日志采集、日志存储、日志上报的本领,为开辟者提供详细的维测日志以辅助故障定位。
本文将分别介绍历程崩溃检测本领、崩溃标题定位分析思路,以及具体的案例分析。在使用本指导分析处置惩罚崩溃日志前,必要开辟者了解C/C++程序堆栈信息的根本知识。
Cpp Crash异常检测本领

历程崩溃基于posix信号机制,目前主要支持对以下崩溃异常信号的处置惩罚:
信号值(signo)信号解释触发原因4SIGILL非法指令。历程实验了非法、格式错误、未知或特权指令。5SIGTRAP断点或陷阱异常。异常或trap指令发生。6SIGABRT历程终止。历程异常终止,通常为历程自身调用标准函数库的abort()函数。7SIGBUS非法内存访问。历程访问了对齐或者不存在的物理地址。8SIGFPE浮点异常。历程实验了错误的算术运算,如除数为0、浮点溢出、整数溢出等。11SIGSEGV无效内存访问。历程访问了无效内存引用。16SIGSTKFLT栈错误。处置惩罚器实验了错误的栈操作,如栈空时弹出、栈满时压入。31SIGSYS错误的体系调用。体系调用时使用了错误或非法参数。 以上部门故障信号,根据具体的场景还有二级分类(code):
SIGILL是一个在Unix和类Unix操作体系中的信号,它表示非法指令异常。SIGILL信号通常由以下几种类型的标题场景引起:
二级分类信号字符串解释触发原因1ILL_ILLOPC非法操作码异常这种异常通常发生在实验不被CPU支持的指令时,或者在实验实验特权指令时。2ILL_ILLOPN非法操作数异常这种异常通常发生在指令使用了不正确的操作数,或者是操作数的类型不正确时。3ILL_ILLADR非法地址异常这种异常通常发生在程序实验访问无效的内存地址时,或者是在实验实验未对齐的内存访问时。4ILL_ILLTRP非法陷阱异常这种异常通常发生在程序实验实验一个非法的陷阱指令时,或者是在实验实验一个未定义的操作时。5ILL_PRVOPC特权操作码异常这种异常通常发生在普通用户实验实验特权指令时。6ILL_PRVREG特权寄存器异常这种异常通常发生在普通用户实验访问特权寄存器时。7ILL_COPROC协处置惩罚器异常这种异常通常发生在程序实验使用未定义的协处置惩罚器指令时。8ILL_BADSTK无效的堆栈异常这种异常通常发生在程序实验在无效的堆栈地址上实验操作时,或者是在堆栈溢出时。 SIGTRAP信号通常用于调试和跟踪程序的实验。下面是上面列出的四种SIGTRAP信号类别的标题场景介绍:
二级分类信号字符串解释触发原因1TRAP_BRKPT软件断点这个信号是由软件断点引起的,当程序实验到设置的断点时会触发该信号。软件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的实验并检查变量值等信息。2TRAP_TRACE单步调试这个信号是由单步实验引起的,当程序实验单个指令时会触发该信号。单步实验通常用于调试程序,可以渐渐实验程序并检查每个指令的实验结果。3TRAP_BRANCH分支跟踪这个信号是由分支指令引起的,当程序实验分支指令时会触发该信号。分支指令通常用于控制程序的实验流程,比方if语句和循环语句等。4TRAP_HWBKPT硬件断点这个信号是由硬件断点引起的,当程序实验到设置的硬件断点时会触发该信号。硬件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的实验并检查变量值等信息。与软件断点差别的是,硬件断点是由CPU硬件实现的,因此可以在程序实验过程中实时检测断点是否被触发。 SIGBUS是一种由操作体系向历程发送的信号,通常表示内存访问错误。其中,差别的信号类别表示差别的错误场景:
二级分类信号字符串解释触发原因1BUS_ADRALN内存地址对齐错误这种错误通常发生在实验访问未对齐的内存地址时,比方实验访问一个4字节整数的非偶数地址。2BUS_ADRERR非法内存地址错误这种错误通常发生在实验访问不属于历程地址空间的内存地址时,比方实验访问一个空指针。3BUS_OBJERR对象访问错误这种错误通常发生在实验访问一个已经被删除或未初始化的对象时。4BUS_MCEERR_AR硬件内存校验错误发生在访问内存时检测到校验和错误。5BUS_MCEERR_AO硬件内存校验错误发生在访问内存时检测到地址和校验和错误。 SIGFPE是一个信号,它表示浮点异常或算术异常。下面是这些SIGFPE信号类别的标题场景:
二级分类信号字符串解释触发原因1FPE_INTDIV整数除法错误这个信号表示整数除法中的除数为零的情况。当一个程序实验进行整数除法,但除数为零时,会发出这个信号。2FPE_INTOVF整数溢出错误这个信号表示整数除法中的除数为负数的情况。当一个程序实验进行整数除法,但除数为负数时,会发出这个信号。3FPE_FLTDIV浮点除法错误这个信号表示浮点数除法中的除数为零的情况。当一个程序实验进行浮点数除法,但除数为零时,会发出这个信号。4FPE_FLTOVF浮点溢出错误这个信号表示浮点数除法中的除数为负数的情况。当一个程序实验进行浮点数除法,但除数为负数时,会发出这个信号。5FPE_FLTUND浮点下溢错误这个信号表示浮点数除法中的除数为零的情况。当一个程序实验进行浮点数除法,但除数为零时,会发出这个信号。6FPE_FLTRES浮点结果未定义错误这个信号表示浮点数除法中的除数为正数的情况。当一个程序实验进行浮点数除法,但除数为正数时,会发出这个信号。7FPE_FLTINV无效浮点操作错误这个信号表示浮点数除法中的除数为负数的情况。当一个程序实验进行浮点数除法,但除数为负数时,会发出这个信号。8FPE_FLTSUB浮点陷阱错误这个信号表示浮点数除法中的除数为零的情况。当一个程序实验进行浮点数除法,但除数为零时,会发出这个信号。 SIGSEGV是一种信号,它表示历程试图访问一个不属于它的内存地址,或者试图访问一个已被操作体系标记为不可访问的内存地址。SIGSEGV信号通常是由以下两种情况引起的:
二级分类信号字符串解释触发原因1SEGV_MAPERR不存在的内存地址历程试图访问一个不存在的内存地址,或者试图访问一个没有映射到历程地址空间的内存地址。这种情况通常是由于程序中的指针错误或内存泄漏引起的。2SEGV_ACCERR不可访问的内存地址历程试图访问一个已被操作体系标记为不可访问的内存地址,比方只读内存或没有实验权限的内存。这种情况通常是由于程序中的缓冲区溢出或者试图修改只读内存等错误引起的。 二级分类(code)除了以上根据信号值(signo)维度分类,还可以根据信号产生的原因维度分类。其中根据信号值(signo)维度分类是每个信号值(signo)特有的,根据信号产生的原因维度分类是所有信号值(signo)共有的,当前已有信号产生原因分类的code值如下:
二级分类信号字符串解释触发原因0SI_USER用户空间信号该信号是由用户空间的历程发送给另一个历程的,通常是通过 kill() 体系调用发送的。比方,当用户在终端中按下Ctrl+C时,会发送一个SIGINT信号给前台历程组中的所有历程。0x80SI_KERNEL内核信号该信号是由内核发送给历程的,通常是由内核检测到某些错误或异常情况时发出的。比方,当历程访问无效的内存地址或者实验非法指令时,内核会发送一个SIGSEGV信号给历程。-1SI_QUEUEsigqueue()函数信号该信号是由sigqueue()体系调用发送的,可以携带一个附加的整数值和一个指针。通常用于历程间高级通信,比方传递数据或者关照历程某个事件已经发生。-2SI_TIMER定时器信号该信号是由定时器发送的,通常用于定时任务或者周期性任务的实验。比方,当一个定时器到期时,内核会向历程发送一个SIGALRM信号。-3SI_MESGQ消息队列信号该信号是由消息队列发送的,通常用于历程间通信。比方,当一个历程向一个消息队列发送消息时,内核会向接收历程发送一个SIGIO信号。-4SI_ASYNCIO异步I/O信号该信号是由异步I/O操作发送的,通常用于非阻塞I/O操作。比方,当一个文件形貌符上的I/O操作完成时,内核会向历程发送一个SIGIO信号。-5SI_SIGIO同步I/O信号该信号是由异步I/O操作发送的,通常用于非阻塞I/O操作。比方,当一个文件形貌符上的I/O操作完成时,内核会向历程发送一个SIGIO信号。-6SI_TKILLtkill()函数信号该信号是由tkill()体系调用发送的,与kill()体系调用类似,但是可以指定发送信号的线程ID。通常用于多线程程序中,向指定线程发送信号。 标题定位步调与思路

崩溃日志获取

历程崩溃日志是一种故障日志,与应用无响应日志、JS应用崩溃等都由FaultLogger模块进行管理,可通过以下方式获取:


  • 方式一:通过DevEco Studio获取日志
    DevEco Studio会收集设备/data/log/faultlog/faultlogger/路径下的历程崩溃故障日志到FaultLog下,根据历程名和故障和时间分类显示。获取日志的方法拜见:DevEco Studio使用指南-FaultLog。
  • 方式二:通过hiAppEvent接口订阅
    hiAppEvent 提供了故障订阅接口,可以订阅各类故障打点,详见HiAppEvent介绍。
日志格式 - 空指针故障场景
该场景会在日志中打印出提示信息,表明故障很有可能是因为空指针解引用导致
以下是一份DevEco Studio归档在FaultLog的历程崩溃日志的焦点内容,与设备/data/log/faultlog/faultlogger下归档的日志内容相同。
  1. Generated by HiviewDFX@HarmonyOS
  2. ================================================================
  3. Device info:HarmonyOS 3.2        <- 设备信息
  4. Build info:HarmonyOS 5.0.0.23    <- 版本信息
  5. Fingerprint:cdf52fd0cc328fc432459928f3ed8edfe8a72a92ee7316445143bed179138073 <- 标识故障特征
  6. Module name:crasher_cpp            <- 模块名
  7. Timestamp:2024-05-06 20:10:51.000  <- 故障发生时间戳
  8. Pid:9623   <- 进程号
  9. Uid:0         <- 用户ID
  10. Process name:./crasher_cpp         <- 进程名称
  11. Process life time:1s               <- 进程存活时间
  12. Reason:Signal:SIGSEGV(SEGV_MAPERR)@0x00000004  probably caused by NULL pointer dereference   <- 故障原因和空指针提示
  13. Fault thread info:
  14. Tid:9623, Name:crasher_cpp         <- 故障线程号,线程名
  15. #00 pc 00008d22 /system/bin/crasher_cpp(TestNullPointerDereferenceCrash0()+22)(adfc673300571d2da1e47d1d12f48b44)  <- 调用栈
  16. #01 pc 000064d1 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+160)(adfc673300571d2da1e47d1d12f48b44)
  17. #02 pc 00006569 /system/bin/crasher_cpp(main+92)(adfc673300571d2da1e47d1d12f48b44)
  18. #03 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83)
  19. #04 pc 00004e28 /system/bin/crasher_cpp(_start_c+84)(adfc673300571d2da1e47d1d12f48b44)
  20. #05 pc 00004dcc /system/bin/crasher_cpp(adfc673300571d2da1e47d1d12f48b44)
  21. Registers:   <- 故障现场寄存器
  22. r0:ffffafd2 r1:00000004 r2:00000001 r3:00000000
  23. r4:ffd27e39 r5:0096e000 r6:00000a40 r7:0096fdfc
  24. r8:f7ba58d5 r9:f7baea86 r10:f7cadd38
  25. fp:ffd27308 ip:f7cb2078 sp:ffd272a0 lr:f7c7ab98 pc:0096ad22
  26. Memory near registers:  <-  故障现场寄存器附近内存
  27. r4([stack]):
  28.     ffd27e30 72656873
  29.     ffd27e34 7070635f
  30.     ...
  31.     ffd27eac 3d73746f
  32. r5(/system/bin/crasher_cpp):
  33.     0096dff8 00000000
  34.     0096dffc 0096717d
  35.     ...
  36.     0096e074 00000000
  37. r7(/system/lib/ld-musl-arm.so.1):
  38.     f7cabb58 00000000
  39.     f7cabb5c 0034ba00
  40.     ...
  41.     f7cabbd4 00000000
  42. r8(/system/lib/ld-musl-arm.so.1):
  43.     f7ba58cc 63637573
  44.     f7ba58d0 2e737365
  45.     ...
  46.     f7ba5948 70206269
  47. r9(/system/lib/ld-musl-arm.so.1):
  48.     f7baea7c 20746f6e
  49.     f7baea80 6e756f66
  50.     ...
  51.     f7baeaf8 25206e69
  52. r10([anon:ld-musl-arm.so.1.bss]):
  53.     f7cadd30 00000000
  54.     f7cadd34 00000000
  55.     ...
  56.     f7caddac 00000000
  57. r12([anon:ld-musl-arm.so.1.bss]):
  58.     f7cb2070 56726562
  59.     f7cb2074 65756c61
  60.     ...
  61.     f7cb20ec 00000000
  62. sp([stack]):
  63.     ffd27328 00000000
  64.     ffd2732c 00966dd0
  65.     ...
  66.     ffd273a4 00000004
  67. pc(/system/bin/crasher_cpp):
  68.     00966dc8 e1a0d00c
  69.     00966dcc eb000000
  70.     ...
  71.     00966e44 e5907008
  72. pc(/system/bin/crasher_cpp):
  73.     00966dc8 e1a0d00c
  74.     00966dcc eb000000
  75.     ...
  76.     00966e44 e5907008
  77. FaultStack:   <- 崩溃线程的栈地址空间
  78.     ffd27260 00000000
  79.     ffd27264 f7cac628
  80.     ...
  81.     ffd2729c 0096ad1f
  82. sp0:ffd272a0 0096fdfc <- #00栈顶
  83.     ffd272a4 009684d3
  84. sp1:ffd272a8 00000001
  85.     ffd272ac 73657408
  86.     ffd272b0 f7590074
  87.     ...
  88.     ffd272dc 0096856d
  89. sp2:ffd272e0 ffd27334
  90.     ffd272e4 ffd27334
  91.     ffd272e8 00000002
  92.     ....
  93.     ffd272f4 f7bfbb9c
  94. sp3:ffd272f8 00000000
  95.     ffd272fc ffd27334
  96. Maps:   <-  故障时进程maps
  97. 962000-966000 r--p 00000000 /system/bin/crasher_cpp
  98. 966000-96c000 r-xp 00003000 /system/bin/crasher_cpp
  99. 96c000-96f000 r--p 00008000 /system/bin/crasher_cpp
  100. 96f000-970000 rw-p 0000a000 /system/bin/crasher_cpp
  101. 149f000-14a0000 ---p 00000000 [heap]
  102. 14a0000-14a2000 rw-p 00000000 [heap]
  103. ...
  104. f7b89000-f7be1000 r--p 00000000 /system/lib/ld-musl-arm.so.1
  105. f7be1000-f7ca9000 r-xp 00057000 /system/lib/ld-musl-arm.so.1
  106. f7ca9000-f7cab000 r--p 0011e000 /system/lib/ld-musl-arm.so.1
  107. f7cab000-f7cad000 rw-p 0011f000 /system/lib/ld-musl-arm.so.1
  108. f7cad000-f7cbc000 rw-p 00000000 [anon:ld-musl-arm.so.1.bss]
  109. ffd07000-ffd28000 rw-p 00000000 [stack]
  110. ffff0000-ffff1000 r-xp 00000000 [vectors]
  111. OpenFiles:   <-  故障时进程打开文件Fd信息
  112. 0->/dev/pts/1 native object of unknown type 0
  113. 1->/dev/pts/1 native object of unknown type 0
  114. 2->/dev/pts/1 native object of unknown type 0
  115. 3->socket:[67214] native object of unknown type 0
  116. ...
  117. 11->pipe:[67219] native object of unknown type 0
  118. 12->socket:[29074] native object of unknown type 0
  119. 25->/dev/ptmx native object of unknown type 0
  120. 26->/dev/ptmx native object of unknown type 0
  121. HiLog:   <-  故障时的Hilog日志
  122. 05-06 20:10:51.301  9623  9623 E C03f00/MUSL-SIGCHAIN: signal_chain_handler call 2 rd sigchain action for signal: 11
  123. 05-06 20:10:51.306  9623  9623 I C02d11/DfxSignalHandler: DFX_SigchainHandler :: sig(11), pid(9623), tid(9623).
  124. 05-06 20:10:51.307  9623  9623 I C02d11/DfxSignalHandler: DFX_SigchainHandler :: sig(11), pid(9623), processName(./crasher_cpp), threadName(crasher_cpp).
  125. 05-06 20:10:51.389  9623  9623 I C02d11/DfxSignalHandler: processdump have get all resgs
复制代码
日志格式 - 栈溢出故障场景
该场景会在日志中打印出提示信息,表明故障很有可能是因为栈溢出导致。焦点日志如下:
  1. Generated by HiviewDFX@HarmonyOS
  2. ================================================================
  3. Device info:HarmonyOS 3.2            <- 设备信息
  4. Build info:HarmonyOS 5.0.0.23        <- 版本信息
  5. Fingerprint:8bc3343f50024204e258b8dce86f41f8fcc50c4d25d56b24e71fe26c0a23e321  <- 标识故障特征
  6. Module name:crasher_cpp                <- 模块名
  7. Timestamp:2024-05-06 20:18:24.000      <- 故障发生时间戳
  8. Pid:9838                               <- 进程号
  9. Uid:0                                  <- 用户ID
  10. Process name:./crasher_cpp             <- 进程名称
  11. Process life time:2s                   <- 进程存活时间
  12. Reason:Signal:SIGSEGV(SEGV_ACCERR)@0xf76b7ffc  current thread stack low address = 0xf76b8000, probably caused by stack-buffer-overflow    <- 故障原因和栈溢出提示
  13. ...
复制代码
日志格式 - 栈覆盖故障场景
在栈覆盖场景下,由于栈上内存被踩,无法成功回溯栈帧,该场景会在日志中打印出提示信息,说明回栈失败并实验从线程栈里解析获取不可靠的调用栈,尽可能提供开辟者信息以分析标题。焦点日志如下:
  1. Generated by HiviewDFX@HarmonyOS
  2. ================================================================
  3. Device info:HarmonyOS 3.2               <- 设备信息
  4. Build info:HarmonyOS 5.0.0.23           <- 版本信息
  5. Fingerprint:79b6d47b87495edf27135a83dda8b1b4f9b13d37bda2560d43f2cf65358cd528    <- 标识故障特征
  6. Module name:crasher_cpp                   <- 模块名
  7. Timestamp:2024-05-06 20:27:23.2035266415  <- 故障发生时间戳
  8. Pid:10026                                 <- 进程号
  9. Uid:0                                     <- 用户ID
  10. Process name:./crasher_cpp                <- 进程名称
  11. Process life time:1s                      <- 进程存活时间
  12. Reason:Signal:SIGSEGV(SEGV_MAPERR)@0000000000  probably caused by NULL pointer dereference      <- 故障原因
  13. LastFatalMessage: Failed to unwind stack, try to get unreliable call stack from #02 by reparsing thread stack   <- 尝试从线程栈里获取不可靠的堆栈
  14. Fault thread info:
  15. Tid:10026, Name:crasher_cpp               <- 故障线程号,线程名
  16. #00 pc 00000000 Not mapped
  17. #01 pc 00008d22 /system/bin/crasher_cpp(TestNullPointerDereferenceCrash0()+22)(adfc673300571d2da1e47d1d12f48b44)  <- 调用栈
  18. #02 pc 000064d1 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+160)(adfc673300571d2da1e47d1d12f48b44)
  19. #03 pc 00006569 /system/bin/crasher_cpp(main+92)(adfc673300571d2da1e47d1d12f48b44)
  20. #04 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83)
  21. ...
复制代码
日志格式 - 异步线程场景故障
(目前支持ARM64架构,且在调试应用(HAP_DEBUGGABLE)下开启)
当异步线程发生崩溃后,把提交该异步任务的线程的栈也打印出来,资助定位由于异步任务提交者造成的崩溃标题。崩溃线程的调用栈和其提交线程的调用栈用SubmitterStacktrace分割开。焦点日志如下:
  1. Generated by HiviewDFX@HarmonyOS
  2. ================================================================
  3. Device info:HarmonyOS 3.2                 <- 设备信息
  4. Build info:HarmonyOS 5.0.0.23             <- 版本信息
  5. Fingerprint:8bc3343f50024204e258b8dce86f41f8fcc50c4d25d56b24e71fe26c0a23e321  <- 标识故障特征
  6. Module name:crasher_cpp                     <- 模块名
  7. Timestamp:2024-05-06 20:28:24.000           <- 故障发生时间戳
  8. Pid:9838                                    <- 进程号
  9. Uid:0                                       <- 用户ID
  10. Process name:./crasher_cpp                  <- 进程名称
  11. Process life time:2s                        <- 进程存活时间
  12. Reason:Signal:SIGSEGV(SI_TKILL)@0x000000000004750  from:18256:0  <- 故障原因
  13. Fault thread info:
  14. Tid:18257, Name:crasher_cpp                 <- 故障线程号,线程名
  15. #00 pc 000054e6 /system/bin/ld-musl-aarch64.so.l(raise+228)(adfc673300571d2da1e47d1d12f48b44)  <- 调用栈
  16. #01 pc 000054f9 /system/bin/crasher_cpp(CrashInSubThread(void*)+56)(adfc673300571d2da1e47d1d12f48b50)
  17. #02 pc 000054f9 /system/bin/ld-musl-aarch64.so.l(start+236)(adfc673300571d2da1e47d1d12f48b44)
  18. ========SubmitterStacktrace========       <- 任务异常时打印任务提交者调用栈
  19. #00 pc 000094dc /system/bin/crasher_cpp(DfxCrasher::AsyncStacktrace()+36)(adfc673300571d2da1e47d1d12f48b50)
  20. #01 pc 00009a58 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+232)(adfc673300571d2da1e47d1d12f48b50)
  21. #02 pc 00009b40 /system/bin/crasher_cpp(main+140)(adfc673300571d2da1e47d1d12f48b50)
  22. #03 pc 0000a4e1c /system/bin/ld-musl-aarch64.so.l(libc_start_main_stage2+68)(adfc673300571d2da1e47d1d12f48b44)
  23. ...
复制代码
基于崩溃栈定位行号

方式一:DevEco Studio 开辟者环境下,支持调用栈直接跳转到对应行号
在应用开辟场景,对于应用自身的动态库,生成的cppcrash堆栈可直接跳转到代码行处,支持Native栈帧和JS栈帧,无需开辟者自行进行解行号操作。对于部门未能解析跳转到对应行号的栈帧,可参考方式二解析。


方式二:通过SDK llvm-addr2line 工具定位行号

  • 获取符号表
    获取崩溃栈中so文件对应的带符号版本,保证与应用/体系内运行时的so文件版本一致。
    对于应用自身的动态库,经DevEco编译构建,生成在工程的 /build/default/intermediates/libs 目录下,默认是带符号的版本。可通过Linux file 命令查询二进制文件的 BuildID 以查对是否匹配。其中,BuildID 是用于标识二进制文件的唯一标识符,通常由编译器在编译时生成,not stripped 表示该动态库是包含符号表的。
    1. $ file libbabel.so
    2. libbabel.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fdb1b5432b9ea4e2a3d29780c3abf30e2a22da9d, with debug_info, not stripped
    复制代码
    **说明:**对于体系动态库符号表,随版本进行归档。
  • 通过 llvm-addr2line 工具定位行号
    llvm-addr2line 工具归档在:[SDK DIR PATH]\HarmonyOS\11\native\llvm\bin 路径下。根据实际的SDK版本路径略有差别,开辟者请自行识别或在路径下搜索。
    比方有堆栈如下(有省略):
    1. Generated by HiviewDFX@HarmonyOS
    2. ================================================================
    3. Device info:HarmonyOS 3.2
    4. Build info:HarmonyOS 5.0.0.22
    5. Fingerprint:50577c0a1a1b5644ac030ba8f08c241cca0092026b59f29e7b142d5d4d5bb934
    6. Module name:com.samples.recovery
    7. Version:1.0.0
    8. VersionCode:1000000
    9. PreInstalled:No
    10. Foreground:No
    11. Timestamp:2017-08-05 17:03:40.000
    12. Pid:2396
    13. Uid:20010044
    14. Process name:com.samples.recovery
    15. Process life time:7s
    16. Reason:Signal:SIGSEGV(SEGV_MAPERR)@0000000000  probably caused by NULL pointer dereference
    17. Tid:2396, Name:amples.recovery
    18. # 00 pc 00003510 /data/storage/el1/bundle/libs/arm/libentry.so(TriggerCrash(napi_env__*, napi_callback_info__*)+24)(446ff75d3f6a518172cc52e8f8055650b02b0e54)
    19. # 01 pc 0002b0c5 /system/lib/platformsdk/libace_napi.z.so(panda::JSValueRef ArkNativeFunctionCallBack<true>(panda::JsiRuntimeCallInfo*)+448)(a84fbb767fd826946623779c608395bf)
    20. # 02 pc 001e7597 /system/lib/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaInterpreter::RunInternal(panda::ecmascript::JSThread*, unsigned char const*, unsigned long long*)+14710)(106c552f6ce4420b9feac95e8b21b792)
    21. # 03 pc 001e0439 /system/lib/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaInterpreter::Execute(panda::ecmascript::EcmaRuntimeCallInfo*)+984)(106c552f6ce4420b9feac95e8b21b792)
    22. ...
    23. # 39 pc 00072998 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(5b1e036c4f1369ecfdbb7a96aec31155)
    24. # 40 pc 00005b48 /system/bin/appspawn(_start_c+84)(cb0631260fa74df0bc9b0323e30ca03d)
    25. # 41 pc 00005aec /system/bin/appspawn(cb0631260fa74df0bc9b0323e30ca03d)
    26. Registers:
    27. r0:00000000 r1:ffc47af8 r2:00000001 r3:f6555c94
    28. r4:00000000 r5:f4d90f64 r6:bd8434f8 r7:00000000
    29. r8:00000000 r9:ffc48808 r10:ffc47b70
    30. fp:f7d8a5a0 ip:00000000 sp:ffc47aac lr:f4d6b0c7 pc:bd843510
    复制代码
    基于SDK llvm-addr2line解析行号如下所示:
    1. [SDK DIR PATH]\HarmonyOS\11\native\llvm\bin> .\llvm-addr2line.exe -Cfie libentry.so 3150
    2. TrggerCrash(napi_env__*, napi_callback_info__*)
    3. D:/code/apprecovery-demo/entry/src/main/cpp/hello.cpp:48
    复制代码
    llvm-addr2line 逐行解析的命令为:llvm-addr2line.exe -fCpie libutils.z.so 偏移量,偏移量可以多个一起解:llvm-addr2line.exe -fCpie libxxx.so 0x1bc868 0x1be28c xxx。使用llvm-addr2line后,如果得出的行号看起来不是很正确,可以思量对 地址进行微调(如减1),或者思量关闭一些编译优化。
方式三:通过 DevEco Studio hstack 工具解析堆栈信息
hstack是DevEco Studio为开辟人员提供的用于将release应用混淆后的crash堆栈还原为源码对应堆栈的工具,支持Windows、Mac、Linux三个平台。DevEco Studio hstack使用指南
结合业务检视代码

根据基于崩溃栈定位行号章节中介绍的三种方式获取到栈顶对应的行号后,回到代码中,检视上下文。如下图所示,hello.cpp中的48行是一个空指针解引用的代码标题。


本场景是一个故障构造的应用,实际的场景往往不会这么简单,必要结合实际业务进行分析。
反汇编(可选)

一样平常而言,如果是比较明确的标题,反编译定位到代码行就可以或许定位;较少数的情况,比如定位到某一行里面调用的方法有多个参数,参数又涉及到结构体等,就必要借助反汇编来进一步分析。
  1. objdump -S xxx.so > xxx.txt
  2. objdump -d xxxx                    对 xxxx 文件反汇编
  3. objdump -S -l xxxx                 对 xxxx 文件反汇编,同时将指令对应的源码行显示出来
复制代码
CppCrash 常见标题分类与原因



  • 空指针解引用 NULL pointer dereference
    形如 SIGSEGV(SEGV_MAPERR)@0x00000000 或 cppcrash日志的Register中打印的r0,r1 等传参寄存器的值为0时,应首先思量调用时是否传入了空指针。
    形如 SIGSEGV(SEGV_MAPERR)@0x0000000c 或 cppcrash日志Register中打印的r1 等传参寄存器的值为一个很小的值时应思量调用入参的结构体成员是否包含空指针。
  • 程序主动终止SIGABRT
    一样平常为用户/框架/C库主动触发,大部门场景下跳过C库/abort发起的框架库的第一帧即为崩溃原因,这里主要检测的是资源使用类的标题,如线程创建,文件形貌符使用,接口调用时序等。
  • SIGSEGV无效内存访问

    • 多线程操作集合,std库的集合为非线程安全,如果多线程添加删除,轻易出现SIGSEGV类崩溃,如果使用 llvm-addr2line 后的代码行与集合干系,可以思量这个原因。
    • 不匹配的对象生命周期,比如使用裸指针(不含有封装、自动内存管理等特性的指针)保存sptr类型以及shared_ptr类型,会导致内存泄漏和悬空指针标题。裸指针是指不含有封装、自动内存管理等特性的指针。它只是一个指向内存地址的简单指针,没有对指针指向的内存进行保护或管理。裸指针可以直接访问指向的内存,但也轻易出现内存泄漏、空指针引用等标题。因此,在使用裸指针时必要特殊警惕,避免出现潜在的安全标题;推荐使用智能指针来管理内存。

  • use after free标题
    返回暂时变量、野指针:比如返回栈变量的引用,释放后未置空继续访问。
    1. # include <iostream>
    2. int& getStackReference() {
    3.     int x = 5;
    4.     return x;  // 返回 x 的引用
    5. }
    6. int main() {
    7.     int& ref = getStackReference();  // 获取 x 的引用
    8.     // x 在 getStackReference 函数返回后被释放
    9.     // ref 现在是悬空引用,继续访问会导致未定义行为
    10.     std::cout << ref << std::endl;  // 试图输出 x 的值,这是未定义行为
    11.     return 0;
    12. }
    复制代码
  • 栈溢出:如递归调用,析构函数相互调用,特殊的栈(信号栈)中使用大块栈内存。
    1. # include <iostream>
    2. class RecursiveClass {
    3. public:
    4.     RecursiveClass() {
    5.         std::cout << "Constructing RecursiveClass" << std::endl;
    6.     }
    7.     ~RecursiveClass() {
    8.         std::cout << "Destructing RecursiveClass" << std::endl;
    9.         // 在析构函数中递归调用
    10.         RecursiveClass obj;
    11.     }
    12. };
    13. int main() {
    14.     RecursiveClass obj;
    15.     return 0;
    16. }
    复制代码
    创建一个 RecursiveClass 对象时,它的构造函数被调用。销毁这个对象时,它的析构函数被调用。在析构函数中,创建了一个新的RecursiveClass对象,这会导致递归调用,直到栈溢出。递归调用导致了无穷的函数调用,最终导致栈空间耗尽,程序崩溃。
  • 二进制不匹配:通常由ABI(应用程序二进制接口)不匹配引起,如自己编译二进制与实际运行的二进制接口存在差别,数据结构定义存在差别,这种一样平常会产生随机的崩溃栈。
  • 踩内存:使用有效的野指针,并修改了其中的内存为非法值,访问越界,覆盖了正常的数据这种一样平常会产生随机的崩溃栈。
  • SIGBUS (Aligment)思量对指针进行强转之后地址是否已经处于非对齐状态。
分析案例

本章节从信号分类、标题场景分类和维测工具分类三个维度来对CppCrash典型标题进行分析和归纳。
信号分类,侧重对常见崩溃信号覆盖介绍,各类信号提供一个典型案例。
标题场景分类,侧重归纳目前高频标题背后的通用场景,各类场景提供一个典型案例。
维测工具分类,侧重总结各类维测工具如果使用类分析相应的标题,各类工具提供一个典型案例。
从信号维度分析标题

类型一:SIGSEGV类崩溃标题
SIGSEGV信号陪同着程序发生段错误(Segmentation Fault)故障,其故障场景为当程序试图访问不被答应访问的内存区域(比如,实验写一块属于操作体系的内存),或以错误的类型访问内存区域(比如,实验写一块只读内存)。概括有如下几点:


  • SIGSEGV是在访问内存时发生的错误,它属于内存管理的范畴。
  • SIGSEGV是一个用户态的概念,是操作体系在用户态程序错误访问内存时所做出的处置惩罚。
  • 当用户态程序访问(访问表示读、写或实验)不答应访问的内存时,产生SIGSEGV。
  • 当用户态程序以错误的方式访问答应访问的内存时,产生SIGSEGV。
SIGSEGV在许多时候是由于指针越界引起的,但并不是所有的指针越界都会引发SIGSEGV。如果不解引用越界指针,是不会引起SIGSEGV崩溃的。而且即使解引用了一个越界的指针,也不一定会引起SIGSEGV。SIGSEGV涉及到操作体系、C库、编译器、链接器各方面的内容,以如下具体的例子来说明。

  • 错误的访问类型
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3.     char *s = "hello world";
    4.     s[1] = 'H';
    5.     return 0;
    6. }
    复制代码
    这是最常见的一个例子。此例中,"hello world" 作为一个常量字符串,在编译后会被放在 .rodata 节(GCC),最后链接生成目的程序时 .rodata 节会被归并到 text segment 与代码段放在一起,故其所处内存区域是只读的。这就是错误的访问类型引起的 SIGSEGV(SEGV_ACCERR) 崩溃。


  • 访问不属于历程地址空间的内存
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3.     uint64_t* p = (uint64_t*)0xffffffcfc42ae6f4;
    4.     *p = 10;
    5.     return 0;
    6. }
    复制代码
    在这个例子中,我们访问了一个属于内核的地址。当然很少会有人这样写程序,但程序可能在不经意的情况下做出这样的行为,产生SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4的崩溃。本例中的CppCrash故障日志(仅展示焦点日志内容)如下:
    1. Device info:xxxxxx xxxx xx xxx
    2. Build info:xxxxxxx
    3. Fingerprint:73a5dcdf3e509605563aa11ac8cb4f3d7f99b9946dc142212246b53b741c4129
    4. Module name:com.samples.recovery
    5. Version:1.0.0
    6. VersionCode:1000000
    7. PreInstalled:No
    8. Foreground:Yes
    9. Timestamp:2024-04-29 14:07:12.082
    10. Pid:21374
    11. Uid:20020144
    12. Process name:com.samples.recovery
    13. Process life time:8s
    14. Reason:Signal:SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4  <-崩溃地址,即进程在
    15. Fault thread info:
    16. Tid:21374, Name:amples.recovery
    17. # 00 pc 0000000000001ccc /data/storage/el1/bundle/libs/arm64/libentry.so(TriggerCrash(napi_env__*, napi_callback_info__*)+36)(4dd115fa8b8c1b3f37bdb5b7b67fc70f31f0dbac)
    18. # 01 pc 0000000000033678 /system/lib64/platformsdk/libace_napi.z.so(ArkNativeFunctionCallBack(panda::JsiRuntimeCallInfo*)+372)(7d6f229764fdd4b72926465066bc475e)
    19. # 02 pc 00000000001d7f38 /system/lib64/module/arkcompiler/stub.an(RTStub_PushCallArgsAndDispatchNative+40)
    20. # 03 at doTriggerException (entry/src/main/ets/pages/FaultTriggerPage.ets:72:7)
    21. # 04 at triggerNativeException (entry/src/main/ets/pages/FaultTriggerPage.ets:79:5)
    22. # 05 at anonymous (entry/src/main/ets/pages/FaultTriggerPage.ets:353:19)
    23. # 06 pc 000000000048e024 /system/lib64/platformsdk/libark_jsruntime.so(panda::FunctionRef::Call(panda::ecmascript::EcmaVM const*, panda::Local<panda::JSValueRef>, panda::Local<panda::JSValueRef> const*, int)+1040)(9fa942a1d42bd4ae607257975fbc1b77)
    24. ...
    25. # 38 pc 00000000000324b0 /system/bin/appspawn(AppSpawnRun+172)(c992404f8d1cf03c84c067fbf3e1dff9)
    26. # 39 pc 00000000000213a8 /system/bin/appspawn(main+956)(c992404f8d1cf03c84c067fbf3e1dff9)
    27. # 40 pc 00000000000a4b98 /system/lib/ld-musl-aarch64.so.1(libc_start_main_stage2+64)(ff4c94d996663814715bedb2032b2bbc)
    复制代码
  • 访问不存在的内存
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3.     int *a = NULL;
    4.     *a = 1;
    5.     return 0;
    6. }
    复制代码
    在实际情况中,此例中的空指针可能指向用户态地址空间,但其所指向的页面实际不存在,便是最常见的空指针解引用的场景,这类场景CppCrash日志会识别出来,并在Reason字段打印推断信息 Reason:Signal:SIGSEGV(SEGV_MAPERR)@000000000000000000 probably caused by NULL pointer dereference,如下图所示:


  • 重复free
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3.     void *pc = malloc(1024);
    4.     free(pc);
    5.     free(pc);  // 重复free
    6.     printf("free ok!\n");
    7.     return 0;
    8. }
    复制代码
    重复释放内存的场景,体系会抛出 SIGSEGV(SI_TKILL) 类故障提示为非法的内存操作,如下图所示:


    以上是 SIGSEGV 类崩溃比较常见的原因,除此之外还有栈溢出内存访问、堆溢出内存访问、访问全局区野指针、函数跳转到一个非法的地址上实验,以及非法的体系调用参数等一些场景都有可能触发 SIGSEGV 。SIGSEGV和操作体系栈分配接纳、编译器有着密切的联系。
类型二:SIGABRT类崩溃标题
SIGABRT信号被发送到历程,告诉历程中止。既可以历程自己调用C标准库的abort()函数,信号通常由历程自己发起,也可以跟其他信号一样从外部发送给历程。

  • 实验abort函数
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3.     OH_LOG_FATAL(LOG_APP, "test fatal log.");
    4.     abort();
    5.     return 0;
    6. }
    复制代码
    该场景是主动调用 abort() 函数构造,对应的场景是各根本库可能会存在一些安全校验,对于识别为会导致历程无法安全运行性的场景,会主动 abort。对应如下场景如下图所示,会将历程退出前的最后一条fatal级别日志打印到崩溃日志中。


  • 实验assert函数
    样例代码如下:
    1. static napi_value TriggerCrash(napi_env env, napi_callback_info info)
    2. {
    3. # if 0  //该值为0,则报错;为1,则正常
    4.     void *pc = malloc(1024);
    5. # else
    6.     void *pc = nullptr;
    7. # endif
    8.     assert(pc != nullptr);
    9.     return 0;
    10. }
    复制代码
    除了调用 abort() 函数外,C++中的另一个异常处置惩罚机制还包罗 assert() 函数,其他的还有 exit() 函数,异常捕获机制(try-catch)、exception类等。assert用于校验当前函数实验流程中的一些数据,校验失败历程会主动 abort。对应的故障场景如下图所示:


从场景维度分析标题

类型一:内存访问类崩溃标题
标题背景
每次崩溃地址0x7f82764b70都在libace_napi_ark.z.so的可读可实验段上。崩溃原因是必要对地址进行写操作,而对应的maps段只有可读、可实验权限没有写权限,当历程试图访问不被答应访问的内存区域时,历程发生内存访问类崩溃。
  1. 7f82740000-7f8275c000 r--p 00000000 /system/lib64/libace_napi_ark.z.so
  2. 7f8275c000-7f8276e000 r-xp 0001b000 /system/lib64/libace_napi_ark.z.so <-崩溃地址落在该地址区间
  3. 7f8276e000-7f82773000 r--p 0002c000 /system/lib64/libace_napi_ark.z.so
  4. 7f82773000-7f82774000 rw-p 00030000 /system/lib64/libace_napi_ark.z.so
复制代码
崩溃调用栈如下图:


定位思路
每次地址出错都很有规律,但node地址不应该落在libace_napi_ark.z.so,从此类标题的现象来看,很有可能是踩内存标题。踩内存标题可使用ASAN工具排查标题。于是后续使用ASAN版本进行压测复现,也找到了稳定必现的场景。ASAN版本检测出来的标题也和上面崩溃栈反映的标题一致。堆栈报的是heap-use-after-free,实际上是对同一个address进行重复释放,只是在重复释放那次操作时,使用该地址去访问了其对象成员,进而报出了UAF标题。
ASAN焦点日志如下:
  1. =================================================================
  2. ==appspawn==2029==ERROR: AddressSanitizer: heap-use-after-free on address 0x003a375eb724 at pc 0x002029ba8514 bp 0x007fd8175710 sp 0x007fd8175708
  3. READ of size 1 at 0x003a375eb724 thread T0 (thread name)
  4.     # 0 0x2029ba8510  (/system/asan/lib64/platformsdk/libark_jsruntime.so+0xca8510) panda::ecmascript::Node::IsUsing() const at arkcompiler/ets_runtime/ecmascript/ecma_global_storage.h:82:16
  5. (inlined by) panda::JSNApi::DisposeGlobalHandleAddr(panda::ecmascript::EcmaVM const*, unsigned long) at arkcompiler/ets_runtime/ecmascript/napi/jsnapi.cpp:749:67 BuildID[md5/uuid]=9a18e2ec0dc8a83216800b2f0dd7b76a
  6.     # 1 0x403ee94d30  (/system/asan/lib64/libace.z.so+0x6194d30) panda::CopyableGlobal<panda::ObjectRef>::Free() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:1520:9
  7. (inlined by) panda::CopyableGlobal<panda::ObjectRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9
  8. (inlined by) OHOS::Ace::Framework::JsiType<panda::ObjectRef>::Reset() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_types.inl:112:13
  9. (inlined by) OHOS::Ace::Framework::JsiWeak<OHOS::Ace::Framework::JsiObject>::~JsiWeak() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_ref.h:167:16
  10. (inlined by) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:44:5 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  11.     # 2 0x403ee9296c  (/system/asan/lib64/libace.z.so+0x619296c) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:42:5
  12. (inlined by) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:42:5 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  13.     # 3 0x403ed9b130  (/system/asan/lib64/libace.z.so+0x609b130) OHOS::Ace::Referenced::DecRefCount() at foundation/arkui/ace_engine/frameworks/base/memory/referenced.h:76:13
  14. (inlined by) OHOS::Ace::RefPtr<OHOS::Ace::Framework::ViewFunctions>::~RefPtr() at foundation/arkui/ace_engine/frameworks/base/memory/referenced.h:148:22 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  15.     # 4 0x403ed9b838  (/system/asan/lib64/libace.z.so+0x609b838) OHOS::Ace::RefPtr<OHOS::Ace::Framework::ViewFunctions>::Reset() at foundation/arkui/ace_engine/frameworks/base/memory/referenced.h:163:9
  16. (inlined by) OHOS::Ace::Framework::JSViewFullUpdate::~JSViewFullUpdate() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view.cpp:159:21 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  17.     # 5 0x403ed9bf24  (/system/asan/lib64/libace.z.so+0x609bf24) OHOS::Ace::Framework::JSViewFullUpdate::~JSViewFullUpdate() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view.cpp:157:1
  18. (inlined by) OHOS::Ace::Framework::JSViewFullUpdate::~JSViewFullUpdate() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view.cpp:157:1 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  19. ...
  20. freed by thread T0 (thread name) here:
  21.     # 0 0x2024ed3abc  (/system/asan/lib64/libclang_rt.asan.so+0xd3abc)
  22.     # 1 0x2029ba8424  (/system/asan/lib64/platformsdk/libark_jsruntime.so+0xca8424) std::__h::__function::__value_func<void (unsigned long)>::operator()[abi:v15004](unsigned long&&) const at prebuilts/clang/ohos/linux-x86_64/llvm/bin/../include/libcxx-ohos/include/c++/v1/__functional/function.h:512:16
  23. (inlined by) std::__h::function<void (unsigned long)>::operator()(unsigned long) const at prebuilts/clang/ohos/linux-x86_64/llvm/bin/../include/libcxx-ohos/include/c++/v1/__functional/function.h:1197:12
  24. (inlined by) panda::ecmascript::JSThread::DisposeGlobalHandle(unsigned long) at arkcompiler/ets_runtime/ecmascript/js_thread.h:604:9
  25. (inlined by) panda::JSNApi::DisposeGlobalHandleAddr(panda::ecmascript::EcmaVM const*, unsigned long) at arkcompiler/ets_runtime/ecmascript/napi/jsnapi.cpp:752:24 BuildID[md5/uuid]=9a18e2ec0dc8a83216800b2f0dd7b76a
  26.     # 2 0x403ee94b68  (/system/asan/lib64/libace.z.so+0x6194b68) panda::CopyableGlobal<panda::FunctionRef>::Free() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:1520:9
  27. (inlined by) panda::CopyableGlobal<panda::FunctionRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9
  28. (inlined by) OHOS::Ace::Framework::JsiType<panda::FunctionRef>::Reset() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_types.inl:112:13
  29. (inlined by) OHOS::Ace::Framework::JsiWeak<OHOS::Ace::Framework::JsiFunction>::~JsiWeak() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_ref.h:167:16
  30. (inlined by) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:44:5 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  31.     # 3 0x403ee9296c  (/system/asan/lib64/libace.z.so+0x619296c) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:42:5
  32. (inlined by) OHOS::Ace::Framework::ViewFunctions::~ViewFunctions() at foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.h:42:5 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  33.     # 4 0x403ed9b130  (/system/asan/lib64/libace.z.so+0x609b130) OHOS::Ace::Referenced::DecRefCount() at foundation/arkui/ace_engine/frameworks/base/memory/referenced.h:76:13
  34. (inlined by) OHOS::Ace::RefPtr<OHOS::Ace::Framework::ViewFunctions>::~RefPtr() at foundation/arkui/ace_engine/frameworks/base/memory/referenced.h:148:22 BuildID[md5/uuid]=1330f8b9be73bdb76ae18107c2a60ca1
  35. ...
  36. previously allocated by thread T0 (thread name) here:
  37.     # 0 0x2024ed3be4  (/system/asan/lib64/libclang_rt.asan.so+0xd3be4)
  38.     # 1 0x2029ade778  (/system/asan/lib64/platformsdk/libark_jsruntime.so+0xbde778) panda::ecmascript::NativeAreaAllocator::AllocateBuffer(unsigned long) at arkcompiler/ets_runtime/ecmascript/mem/native_area_allocator.cpp:98:17 BuildID[md5/uuid]=9a18e2ec0dc8a83216800b2f0dd7b76a
  39.     # 2 0x2029a39064  (/system/asan/lib64/platformsdk/libark_jsruntime.so+0xb39064) std::__h::enable_if<!std::is_array_v<panda::ecmascript::NodeList<panda::ecmascript::WeakNode>>, panda::ecmascript::NodeList<panda::ecmascript::WeakNode>*>::type panda::ecmascript::NativeAreaAllocator::New<panda::ecmascript::NodeList<panda::ecmascript::WeakNode>>() at arkcompiler/ets_runtime/ecmascript/mem/native_area_allocator.h:61:19
  40. (inlined by) unsigned long panda::ecmascript::EcmaGlobalStorage<panda::ecmascript::Node>::NewGlobalHandleImplement<panda::ecmascript::WeakNode>(panda::ecmascript::NodeList<panda::ecmascript::WeakNode>**, panda::ecmascript::NodeList<panda::ecmascript::WeakNode>**, unsigned long) at arkcompiler/ets_runtime/ecmascript/ecma_global_storage.h:565:34
  41. (inlined by) panda::ecmascript::EcmaGlobalStorage<panda::ecmascript::Node>::SetWeak(unsigned long, void*, void (*)(void*), void (*)(void*)) at arkcompiler/ets_runtime/ecmascript/ecma_global_storage.h:455:26 BuildID[md5/uuid]=9a18e2ec0dc8a83216800b2f0dd7b76a
  42.     # 3 0x2029ba5620  (/system/asan/lib64/platformsdk/libark_jsruntime.so+0xca5620) std::__h::__function::__value_func<unsigned long (unsigned long, void*, void (*)(void*), void (*)(void*))>::operator()[abi:v15004](unsigned long&&, void*&&, void (*&&)(void*), void (*&&)(void*)) const at prebuilts/clang/ohos/linux-x86_64/llvm/bin/../include/libcxx-ohos/include/c++/v1/__functional/function.h:512:16
  43. (inlined by) std::__h::function<unsigned long (unsigned long, void*, void (*)(void*), void (*)(void*))>::operator()(unsigned long, void*, void (*)(void*), void (*)(void*)) const at prebuilts/clang/ohos/linux-x86_64/llvm/bin/../include/libcxx-ohos/include/c++/v1/__functional/function.h:1197:12
  44. (inlined by) panda::ecmascript::JSThread::SetWeak(unsigned long, void*, void (*)(void*), void (*)(void*)) at arkcompiler/ets_runtime/ecmascript/js_thread.h:610:16
  45. (inlined by) panda::JSNApi::SetWeak(panda::ecmascript::EcmaVM const*, unsigned long) at arkcompiler/ets_runtime/ecmascript/napi/jsnapi.cpp:711:31 BuildID[md5/uuid]=9a18e2ec0dc8a83216800b2f0dd7b76a
  46. ...
复制代码
根据堆栈继续分析,
JsiWeak析构或重置的时候会触发其成员(类型为JsiObject/JsiValue/JsiFunction)父类JsiType中CopyableGlobal被释放,如下图:


运行时在GC过程中IterateWeakEcmaGlobalStorage,会对无callback的WeakNode调用DisposeGlobalHandle操作,也对其进行释放,如下图:


因此,对于同一个WeakNode,可能会存在两个入口释放。如果是GC过程中IterateWeakEcmaGlobalStorage先释放,因为无callback回调关照到JsiWeak进行整理,JsiWeak那边仍保存一个对已释放的WeakNode引用,即CopyableGlobal;当前面讲的WeakNode所在的NodeList被整体释放,归还给操作体系后,JsiWeak处保存的CopyableGlobal再释放,就会存在double-free标题。


修改方法
JsiWeak调用SetWeakCallback,传入callback,在GC过程中IterateWeakEcmaGlobalStorage释放WeakNode时,关照JsiWeak对其保存的CopyableGlobal进行重置,确保同一个地址不被double-free。
建议与总结
使用内存时应思量是否存在重复释放或者未释放的可能,别的定位内存访问类崩溃标题(一样平常是SIGSEGV类型标题)时,如果根据崩溃栈分析标题无头绪时,应优先思量跑ASAN版本复现标题。
类型二:多线程类标题
标题背景
napi_env释放后仍被使用。
标题场景
napi接口的env传入非法,崩溃栈直接挂在NativeEngineInterface::ClearLastError()中,根据日志打印env地址定位,发现是env被释放后仍然被使用。


焦点崩溃栈如下:


修改方法
一个线程的创建的env,不要传给另一个线程使用。
建议与总结
对于多线程类标题可以打开方舟多线程检测功能,可以或许更加方便定位标题,见工具类方舟多线程检测章节。
注:napi接口中的env,是引擎创建时候的arkNativeEngine。
类型三:生命周期类标题
标题背景
开辟者在写native代码创建napi_value时,必要配合napi_handle_scope一起使用。napi_handle_scope的作用是管理napi_value的生命周期,napi_value只能在napi_handle_scope的作用域范围内进行使用,离开napi_handle_scope作用域范围后,napi_value及它所持有的js对象的生命周期不再得到保护,一旦引用计数为0,就会被GC接纳掉,此时再去使用napi_value就会访问已释放的内存,产生标题。
标题场景
napi_value实在是个裸指针(结构体指针),其作用是持有js对象,用于保持js对象的生命周期,保证js对象不被GC当成垃圾对象接纳。napi_handle_scope用来管理napi_value,离开napi_handle_scope作用域之后,napi_value由GC接纳,napi_value不再持有js对象(不再保护js对象生命周期)。
定位思路
根据崩溃栈反编译找到出现标题的napi接口的上层接口,在上层接口内找到出标题的napi_value,检查napi_value的使用范围是否超出了napi_handle_scope的作用域范围。
案例
napi_value超出NAPI框架的scope,如下:


js侧通过Add接口添加数据,native侧以napi_value保存到vector,js侧通过get接口获取添加的数据,native侧将保存的napi_value以数组情势返回回去,然后js侧读取数据的属性。出现报错:Can not get Prototype on non ECMA Object。跨napi的native_value未使用napi_ref保存,导致native_value失效。
注:NAPI框架的scope即napi_handle_scope,napi开辟者可以通过napi_handle_scope来管理napi_value的生命周期。框架层的scope嵌入在js call native的端到端流程中,即进入开辟者自己写的native方法前open scope,native方法竣过后close scope。
类型四:指针类标题
标题背景
智能指针使用之前未判空,造成历程运行时发生空指针解引用崩溃标题。
标题影响
历程发生崩溃,影响历程的稳定运行,非预期退出。
定位思路


空指针类型崩溃可以从故障原因得到提示信息。通过llvm-addr2line解行号发现业务代码中在使用智能指针之前未对智能指针判空,对清闲址进行访问导致崩溃产生。
修复方法
对所有使用该指针的地方进行保护性判空。
建议与总结
指针在使用之前应该要进行判空处置惩罚,防止访问空指针造成历程崩溃退出。
配合工具分析标题

工具一:ASAN
ASAN使用指南
工具二:方舟多线程检测
根本原理
js是单线程的,操作js对象只答应发生在创建该js线程上,否则将会有多线程安全标题(主线程创建的js对象只能在主线程上操作,worker创建的js对象只能在worker线程上操作)。napi接口会直接涉及到对象的操作,因此绝大部门(95%)的napi接口只答应在js线程上使用。多线程检测机制检测的是:当前线程和使用的vm/env中的js thread id是否一致,若不一致,则表明vm/env被跨线程使用,存在多线程安全标题。常见标题有:1. 非js线程使用napi接口,2. napi接口使用其他线程的env。
使用方法


DevEco勾选Multi Thread Check选项即可开启方舟多线程检测功能。
使用场景
如果crash日志的堆栈难以分析,出现概率也相对比较高,对于此类标题,应该思量开启多线程检测。 开启多线程检测之后,如果cpp_crash日志中fatal信息为Fatal: ecma_vm cannot run in multi-thread! thread:3096 currentThread:3550,则发生了多线程安全标题,意思是当前线程号为3550,而使用的js thread却是3096线程创建出来的,跨线程使用vm。
案例
打开后重新触发崩溃,如果是多线程标题,会显示fatal 信息,参考如下:
  1. Fatal: ecma_vm cannot run in multi-thread! thread:xxx currentThread:yyy
复制代码
该信息意思是当前线程号为17585,而使用的 js thread 却是17688 线程创建出来的,跨线程使用 vm。vm 就是 js thread 的 napi_env__* ,运行线程代码的环境,一个线程使用一个 vm。
崩溃日志焦点部门如下所示:
  1. Reason:Signal:SIGABRT(SI_TKILL)@0x01317b9f000044b1 from:17585: 20020127
  2. LastFatalMessage: [default] CheckThread:177 Fatal: ecma_vm cannot run in multi-thread! thread:17688 currentThread:17585
  3. Fault thread Info:
  4. Tid:17585, Name:xxxxx
  5. # 00 pc 00000000000f157c /system/lib/ld-musl-aarch64-asan.so.1(__restore_sigs+52)(38eb4ca904ae601d4b4dca502e948960)
  6. # 01 pc 00000000000f1800 /system/lib/ld-musl-aarch64-asan.so.1(raise+112) (38eb4ca904aeó01d4b4dca502e948960)
  7. # 02 pc 00000000000adc74 /system/lib/ld-musl-aarch64-asan.so.1(abort.+20) (38eb4ca904ae601d4b4dca502e948960)
  8. # 03 pc 0000000000844fdc /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaVM::CheckThread() const+2712)(1df055932338c14060b864435aec88ab)
  9. # 04 pc 0000000000f3d930 /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::0bjectRef:: New(panda::ecmascript::EcmaVM const*)+908)(1df055932338c14060b864435aec88
  10. # 05 pC 0000000000095048 /sYstem/asan/lib64/platformsdk/libace_napi.z.so(napi_create_object+80)(efc1b3d1378f56b4b800489fb30dcded)
  11. # 06 pc 00000000005d9770 /data/ storage/el1/bundle/libs/arm64/xxxxx.so (c0f1735eada49fadc5197745f5afOc0a52246270)
复制代码
多线程标题分析步调:
i. 检查 libace_napi.z.so 下面的第一个栈帧,上图为xxxxx.so,判断是否把 17688 线程的 napi_env 传给了 17585 线程;
ii. 如果 libace_napi.z.so 下面的栈帧没有明显的 napi_env 参数传递,必要检查是否以结构体成员变量的方式传递;
工具三:objdump
使用方法
objdump二进制是体系侧工具,开辟者必要具备HarmonyOS编译环境,项目代码在gitee上可获取,命令如下:
  1. repo init -u git@gitee.com:HarmonyOS/manifest.git -b master --no-repo-verify --no-clone-bundle --depth=1
  2. repo sync -c
  3. ./build/prebuilts_download.sh
复制代码
工具在工程目录下prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump,命令如下:
  1. prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump -d libark_jsruntime.so > dump.txt
复制代码
使用场景
有些情况下,通过addr2line只能看出代码某一行有标题,无法确认具体是哪个变量异常,此时可以通过objdump反汇编并结合cppcrash寄存器内容,进一步确认具体崩溃原因。
案例
日志内容如下:
  1. Tid:6655, Name:GC_WorkerThread
  2. # 00 pc 00000000004492d4 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkObject(unsigned int, panda::ecmascript::TaggedObject*)+124)(21cf5411626d5986a4ba6383e959b3cc)
  3. # 01 pc 000000000044b580 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkValue(unsigned int, panda::ecmascript::ObjectSlot&, panda::ecmascript::Region*, bool)+72)(21cf5411626d5986a4ba6383e959b3cc)
  4. # 02 pc 000000000044b4e8 /system/lib64/platformsdk/libark_jsruntime.so(std::__h::__function::__func<panda::ecmascript::NonMovableMarker::ProcessMarkStack(unsigned int)::$_2, std::__h::allocator<panda::ecmascript::NonMovableMarker::ProcessMarkStack(unsigned int)::$_2>, void (panda::ecmascript::TaggedObject*, panda::ecmascript::ObjectSlot, panda::ecmascript::ObjectSlot, panda::ecmascript::VisitObjectArea)>::operator()(panda::ecmascript::TaggedObject*&&, panda::ecmascript::ObjectSlot&&, panda::ecmascript::ObjectSlot&&, panda::ecmascript::VisitObjectArea&&)+256)(21cf5411626d5986a4ba6383e959b3cc)
  5. # 03 pc 0000000000442ac0 /system/lib64/platformsdk/libark_jsruntime.so(void panda::ecmascript::ObjectXRay::VisitObjectBody<(panda::ecmascript::VisitType)1>(panda::ecmascript::TaggedObject*, panda::ecmascript::JSHClass*, std::__h::function<void (panda::ecmascript::TaggedObject*, panda::ecmascript::ObjectSlot, panda::ecmascript::ObjectSlot, panda::ecmascript::VisitObjectArea)> const&)+216)(21cf5411626d5986a4ba6383e959b3cc)
  6. # 04 pc 0000000000447ccc /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::ProcessMarkStack(unsigned int)+248)(21cf5411626d5986a4ba6383e959b3cc)
  7. # 05 pc 0000000000438588 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Heap::ParallelGCTask::Run(unsigned int)+148)(21cf5411626d5986a4ba6383e959b3cc)
  8. # 06 pc 00000000004e31c8 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Runner::Run(unsigned int)+144)(21cf5411626d5986a4ba6383e959b3cc)
  9. # 07 pc 00000000004e3780 /system/lib64/platformsdk/libark_jsruntime.so(void* std::__h::__thread_proxy[abi:v15004]<std::__h::tuple<std::__h::unique_ptr<std::__h::__thread_struct, std::__h::default_delete<std::__h::__thread_struct>>, void (panda::ecmascript::Runner::*)(unsigned int), panda::ecmascript::Runner*, unsigned int>>(void*)+64)(21cf5411626d5986a4ba6383e959b3cc)
  10. # 08 pc 000000000014d894 /system/lib/ld-musl-aarch64.so.1
  11. # 09 pc 0000000000085d04 /system/lib/ld-musl-aarch64.so.1
复制代码
首先先用addr2line查察出错的行,如下:


能看出的信息是判断IsYongSpace的时候访问到了空指针挂了,可以或许大概猜测出来是Region是空指针。
继续使用objdump反汇编,搜索出错地址4492d4 , 对应的汇编指令如下。


查察x20寄存器,发现为0x000000000000000,x20从上面可以看出是基于x2做位运算(清除掉后18位,典型的Region::ObjectAddressToRange操作)。这样分析之后,就清晰了,x2为MarkObject函数的第二个参数object,x20为变量objectRegion,如下:
  1. Registers: x0:0000007f0fe31560 x1:0000000000000003 x2:0000000000000000 x3:0000005593100000
  2.         x4:0000000000000000 x5:0000000000000000 x6:0000000000000000 x7:0000005596374fa0
  3.         x8:0000000000000000 x9:0000000000000000 x10:0000000000000000 x11:0000007f9cb42bb8
  4.         x12:000000000000005e x13:000000000061f59e x14:00000005d73d60fb x15:0000000000000000
  5.         x16:0000007f9cc5f200 x17:0000007f9f201f68 x18:0000000000000000 x19:0000000000000000
  6.         x20:0000000000000000 x21:0000000000000000 x22:0000000000000000 x23:000000559313f860
  7.         x24:000000559313f868 x25:0000000000000003 x26:00000055a0e19960 x27:0000007f9cc57b38
  8.         x28:0000007f9f21a1c0 x29:00000055a0e19700 lr:0000007f9cb4b584 sp:00000055a0e19700 pc:0000007f9cb492d4
复制代码
上面ldrb w8, [x20]对应 packedData_.flags_.spaceFlag_ 是因为,packedData_是region的第一个域,flags_是packedData_的第一个域,spaceFlag_是flags_的第一个域,以是直接取objectRegion地址对应的第一个字节。
查察汇编代码必要认识常见的汇编指令,以及传参规则,比方对于c++非inline的成员函数r0一样平常保存的是this指针。别的,由于编译器优化,源码和汇编代码对应关系可能不是很直观,我们可以根据代码中的一些特征值(常量),较快地找到对应关系。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表