1# 分析CppCrash(进程崩溃) 2 3进程崩溃指C/C++运行时崩溃。FaultLogger模块提供进程崩溃故障检测、日志采集、日志存储、日志上报的能力,为开发者提供详细的维测日志以辅助故障定位。 4 5本文将分别介绍进程崩溃检测能力、崩溃问题定位分析思路,以及具体的案例分析。在使用本指导分析处理崩溃日志前,需要开发者了解C/C++程序堆栈信息的基础知识。 6 7## Cpp Crash异常检测能力 8 9进程崩溃基于posix信号机制,目前主要支持对以下崩溃异常信号的处理: 10 11| 信号值(signo) | 信号 | 解释 | 触发原因 | 12| -------- | -------- | -------- | -------- | 13| 4 | SIGILL | 非法指令。 | 进程执行了非法、格式错误、未知或特权指令。 | 14| 5 | SIGTRAP | 断点或陷阱异常。 | 异常或trap指令发生。 | 15| 6 | SIGABRT | 进程终止。 | 进程异常终止,通常为进程自身调用标准函数库的abort()函数。 | 16| 7 | SIGBUS | 非法内存访问。 | 进程访问了对齐或者不存在的物理地址。 | 17| 8 | SIGFPE | 浮点异常。 | 进程执行了错误的算术运算,如除数为0、浮点溢出、整数溢出等。 | 18| 11 | SIGSEGV | 无效内存访问。 | 进程访问了无效内存引用。 | 19| 16 | SIGSTKFLT | 栈错误。 | 处理器执行了错误的栈操作,如栈空时弹出、栈满时压入。 | 20| 31 | SIGSYS | 错误的系统调用。 | 系统调用时使用了错误或非法参数。 | 21 22以上部分故障信号,根据具体的场景还有二级分类(code): 23SIGILL是一个在Unix和类Unix操作系统中的信号,它表示非法指令异常。SIGILL信号通常由以下几种类型的问题场景引起: 24| 二级分类 | 信号字符串 | 解释 | 触发原因 | 25| -------- | -------- | -------- | -------- | 26| 1 | ILL_ILLOPC | 非法操作码异常 | 这种异常通常发生在执行不被CPU支持的指令时,或者在尝试执行特权指令时。 | 27| 2 | ILL_ILLOPN | 非法操作数异常 | 这种异常通常发生在指令使用了不正确的操作数,或者是操作数的类型不正确时。| 28| 3 | ILL_ILLADR | 非法地址异常 | 这种异常通常发生在程序尝试访问无效的内存地址时,或者是在尝试执行未对齐的内存访问时。| 29| 4 | ILL_ILLTRP | 非法陷阱异常 | 这种异常通常发生在程序尝试执行一个非法的陷阱指令时,或者是在尝试执行一个未定义的操作时。| 30| 5 | ILL_PRVOPC | 特权操作码异常 | 这种异常通常发生在普通用户尝试执行特权指令时。| 31| 6 | ILL_PRVREG | 特权寄存器异常 | 这种异常通常发生在普通用户尝试访问特权寄存器时。| 32| 7 | ILL_COPROC | 协处理器异常 | 这种异常通常发生在程序尝试使用未定义的协处理器指令时。| 33| 8 | ILL_BADSTK | 无效的堆栈异常 | 这种异常通常发生在程序尝试在无效的堆栈地址上执行操作时,或者是在堆栈溢出时。| 34 35SIGTRAP信号通常用于调试和跟踪程序的执行。下面是上面列出的四种SIGTRAP信号类别的问题场景介绍: 36| 二级分类 | 信号字符串 | 解释 | 触发原因 | 37| -------- | -------- | -------- | -------- | 38| 1 | TRAP_BRKPT | 软件断点 | 这个信号是由软件断点引起的,当程序执行到设置的断点时会触发该信号。软件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的执行并检查变量值等信息。| 39| 2 | TRAP_TRACE | 单步调试 | 这个信号是由单步执行引起的,当程序执行单个指令时会触发该信号。单步执行通常用于调试程序,可以逐步执行程序并检查每个指令的执行结果。| 40| 3 | TRAP_BRANCH | 分支跟踪 | 这个信号是由分支指令引起的,当程序执行分支指令时会触发该信号。分支指令通常用于控制程序的执行流程,例如if语句和循环语句等。| 41| 4 | TRAP_HWBKPT | 硬件断点 | 这个信号是由硬件断点引起的,当程序执行到设置的硬件断点时会触发该信号。硬件断点通常用于调试程序,可以在程序的关键位置设置断点,以便在调试时暂停程序的执行并检查变量值等信息。与软件断点不同的是,硬件断点是由CPU硬件实现的,因此可以在程序执行过程中实时检测断点是否被触发。| 42 43SIGBUS是一种由操作系统向进程发送的信号,通常表示内存访问错误。其中,不同的信号类别表示不同的错误场景: 44| 二级分类 | 信号字符串 | 解释 | 触发原因 | 45| -------- | -------- | -------- | -------- | 46| 1 | BUS_ADRALN | 内存地址对齐错误 | 这种错误通常发生在尝试访问未对齐的内存地址时,例如尝试访问一个4字节整数的非偶数地址。| 47| 2 | BUS_ADRERR | 非法内存地址错误 | 这种错误通常发生在尝试访问不属于进程地址空间的内存地址时,例如尝试访问一个空指针。| 48| 3 | BUS_OBJERR | 对象访问错误 | 这种错误通常发生在尝试访问一个已经被删除或未初始化的对象时。| 49| 4 | BUS_MCEERR_AR | 硬件内存校验错误 | 发生在访问内存时检测到校验和错误。| 50| 5 | BUS_MCEERR_AO | 硬件内存校验错误 | 发生在访问内存时检测到地址和校验和错误。| 51 52SIGFPE是一个信号,它表示浮点异常或算术异常。下面是这些SIGFPE信号类别的问题场景: 53| 二级分类 | 信号字符串 | 解释 | 触发原因 | 54| -------- | -------- | -------- | -------- | 55| 1 | FPE_INTDIV | 整数除法错误 | 这个信号表示整数除法中的除数为零的情况。当一个程序尝试进行整数除法,但除数为零时,会发出这个信号。| 56| 2 | FPE_INTOVF | 整数溢出错误 | 这个信号表示整数除法中的除数为负数的情况。当一个程序尝试进行整数除法,但除数为负数时,会发出这个信号。| 57| 3 | FPE_FLTDIV | 浮点除法错误 | 这个信号表示浮点数除法中的除数为零的情况。当一个程序尝试进行浮点数除法,但除数为零时,会发出这个信号。| 58| 4 | FPE_FLTOVF | 浮点溢出错误 | 这个信号表示浮点数除法中的除数为负数的情况。当一个程序尝试进行浮点数除法,但除数为负数时,会发出这个信号。| 59| 5 | FPE_FLTUND | 浮点下溢错误 | 这个信号表示浮点数除法中的除数为零的情况。当一个程序尝试进行浮点数除法,但除数为零时,会发出这个信号。| 60| 6 | FPE_FLTRES | 浮点结果未定义错误 | 这个信号表示浮点数除法中的除数为正数的情况。当一个程序尝试进行浮点数除法,但除数为正数时,会发出这个信号。| 61| 7 | FPE_FLTINV | 无效浮点操作错误 | 这个信号表示浮点数除法中的除数为负数的情况。当一个程序尝试进行浮点数除法,但除数为负数时,会发出这个信号。| 62| 8 | FPE_FLTSUB | 浮点陷阱错误 | 这个信号表示浮点数除法中的除数为零的情况。当一个程序尝试进行浮点数除法,但除数为零时,会发出这个信号。| 63 64SIGSEGV是一种信号,它表示进程试图访问一个不属于它的内存地址,或者试图访问一个已被操作系统标记为不可访问的内存地址。SIGSEGV信号通常是由以下两种情况引起的: 65| 二级分类 | 信号字符串 | 解释 | 触发原因 | 66| -------- | -------- | -------- | -------- | 67| 1 | SEGV_MAPERR | 不存在的内存地址 | 进程试图访问一个不存在的内存地址,或者试图访问一个没有映射到进程地址空间的内存地址。这种情况通常是由于程序中的指针错误或内存泄漏引起的。| 68| 2 | SEGV_ACCERR | 不可访问的内存地址 | 进程试图访问一个已被操作系统标记为不可访问的内存地址,例如只读内存或没有执行权限的内存。这种情况通常是由于程序中的缓冲区溢出或者试图修改只读内存等错误引起的。| 69 70二级分类(code)除了以上根据信号值(signo)维度分类,还可以根据信号产生的原因维度分类。其中根据信号值(signo)维度分类是每个信号值(signo)特有的,根据信号产生的原因维度分类是所有信号值(signo)共有的,当前已有信号产生原因分类的code值如下: 71| 二级分类 | 信号字符串 | 解释 | 触发原因 | 72| -------- | -------- | -------- | -------- | 73| 0 | SI_USER | 用户空间信号 |该信号是由用户空间的进程发送给另一个进程的,通常是通过 kill() 系统调用发送的。例如,当用户在终端中按下Ctrl+C时,会发送一个SIGINT信号给前台进程组中的所有进程。| 74| 0x80 | SI_KERNEL | 内核信号 |该信号是由内核发送给进程的,通常是由内核检测到某些错误或异常情况时发出的。例如,当进程访问无效的内存地址或者执行非法指令时,内核会发送一个SIGSEGV信号给进程。| 75| -1 | SI_QUEUE | sigqueue()函数信号 |该信号是由sigqueue()系统调用发送的,可以携带一个附加的整数值和一个指针。通常用于进程间高级通信,例如传递数据或者通知进程某个事件已经发生。| 76| -2 | SI_TIMER | 定时器信号 |该信号是由定时器发送的,通常用于定时任务或者周期性任务的执行。例如,当一个定时器到期时,内核会向进程发送一个SIGALRM信号。| 77| -3 | SI_MESGQ | 消息队列信号 |该信号是由消息队列发送的,通常用于进程间通信。例如,当一个进程向一个消息队列发送消息时,内核会向接收进程发送一个SIGIO信号。| 78| -4 | SI_ASYNCIO | 异步I/O信号 |该信号是由异步I/O操作发送的,通常用于非阻塞I/O操作。例如,当一个文件描述符上的I/O操作完成时,内核会向进程发送一个SIGIO信号。| 79| -5 | SI_SIGIO | 同步I/O信号 |该信号是由异步I/O操作发送的,通常用于非阻塞I/O操作。例如,当一个文件描述符上的I/O操作完成时,内核会向进程发送一个SIGIO信号。| 80| -6 | SI_TKILL | tkill()函数信号 |该信号是由tkill()系统调用发送的,与kill()系统调用类似,但是可以指定发送信号的线程ID。通常用于多线程程序中,向指定线程发送信号。| 81 82## 问题定位步骤与思路 83 84### 崩溃日志获取 85 86进程崩溃日志是一种故障日志,与应用无响应日志、JS应用崩溃等都由FaultLogger模块进行管理,可通过以下方式获取: 87 88- 方式一:通过DevEco Studio获取日志 89 90 DevEco Studio会收集设备`/data/log/faultlog/faultlogger/`路径下的进程崩溃故障日志到FaultLog下,根据进程名和故障和时间分类显示。获取日志的方法参见:<!--RP1-->[DevEco Studio使用指南-FaultLog](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-fault-log-V5)<!--RP1End-->。 91 92- 方式二:通过hiAppEvent接口订阅 93 94 hiAppEvent 提供了故障订阅接口,可以订阅各类故障打点,详见[HiAppEvent介绍](hiappevent-intro.md)。 95 96<!--Del--> 97- 方式三:设备ROOT模式下通过shell获取日志 98 99 1. 进程崩溃后,系统会在设备`/data/log/faultlog/temp/`路径下的故障日志,其文件名格式为`cppcrash-进程PID-系统毫秒级时间戳`,日志内容包含进程崩溃调用栈,进程崩溃现场寄存器、栈内存、maps,进程文件句柄列表等信息。 100 101  102 103 2. CppCrash故障会同步在`/data/log/faultlog/faultlogger/`路径下生成一份完善日志,故障日志文件名格式为`cppcrash-进程名-进程UID-秒级时间`,日志内容较`/data/log/faultlog/temp`下日志更加完善,增加有设备名,系统版本,进程流水日志等信息。 104 105  106 107<!--DelEnd--> 108**日志格式 - 空指针故障场景** 109该场景会在日志中打印出提示信息,表明故障很有可能是因为空指针解引用导致 110以下是一份DevEco Studio归档在FaultLog的进程崩溃日志的核心内容,与`设备/data/log/faultlog/faultlogger`下归档的日志内容相同。 111 112``` 113Generated by HiviewDFX@OpenHarmony 114================================================================ 115Device info:OpenHarmony 3.2 <- 设备信息 116Build info:OpenHarmony 5.0.0.23 <- 版本信息 117Fingerprint:cdf52fd0cc328fc432459928f3ed8edfe8a72a92ee7316445143bed179138073 <- 标识故障特征 118Module name:crasher_cpp <- 模块名 119Timestamp:2024-05-06 20:10:51.000 <- 故障发生时间戳 120Pid:9623 <- 进程号 121Uid:0 <- 用户ID 122Process name:./crasher_cpp <- 进程名称 123Process life time:1s <- 进程存活时间 124Reason:Signal:SIGSEGV(SEGV_MAPERR)@0x00000004 probably caused by NULL pointer dereference <- 故障原因和空指针提示 125Fault thread info: 126Tid:9623, Name:crasher_cpp <- 故障线程号,线程名 127#00 pc 00008d22 /system/bin/crasher_cpp(TestNullPointerDereferenceCrash0()+22)(adfc673300571d2da1e47d1d12f48b44) <- 调用栈 128#01 pc 000064d1 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+160)(adfc673300571d2da1e47d1d12f48b44) 129#02 pc 00006569 /system/bin/crasher_cpp(main+92)(adfc673300571d2da1e47d1d12f48b44) 130#03 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83) 131#04 pc 00004e28 /system/bin/crasher_cpp(_start_c+84)(adfc673300571d2da1e47d1d12f48b44) 132#05 pc 00004dcc /system/bin/crasher_cpp(adfc673300571d2da1e47d1d12f48b44) 133Registers: <- 故障现场寄存器 134r0:ffffafd2 r1:00000004 r2:00000001 r3:00000000 135r4:ffd27e39 r5:0096e000 r6:00000a40 r7:0096fdfc 136r8:f7ba58d5 r9:f7baea86 r10:f7cadd38 137fp:ffd27308 ip:f7cb2078 sp:ffd272a0 lr:f7c7ab98 pc:0096ad22 138Memory near registers: <- 故障现场寄存器附近内存 139r4([stack]): 140 ffd27e30 72656873 141 ffd27e34 7070635f 142 ... 143 ffd27eac 3d73746f 144r5(/system/bin/crasher_cpp): 145 0096dff8 00000000 146 0096dffc 0096717d 147 ... 148 0096e074 00000000 149r7(/system/lib/ld-musl-arm.so.1): 150 f7cabb58 00000000 151 f7cabb5c 0034ba00 152 ... 153 f7cabbd4 00000000 154r8(/system/lib/ld-musl-arm.so.1): 155 f7ba58cc 63637573 156 f7ba58d0 2e737365 157 ... 158 f7ba5948 70206269 159r9(/system/lib/ld-musl-arm.so.1): 160 f7baea7c 20746f6e 161 f7baea80 6e756f66 162 ... 163 f7baeaf8 25206e69 164r10([anon:ld-musl-arm.so.1.bss]): 165 f7cadd30 00000000 166 f7cadd34 00000000 167 ... 168 f7caddac 00000000 169r12([anon:ld-musl-arm.so.1.bss]): 170 f7cb2070 56726562 171 f7cb2074 65756c61 172 ... 173 f7cb20ec 00000000 174sp([stack]): 175 ffd27328 00000000 176 ffd2732c 00966dd0 177 ... 178 ffd273a4 00000004 179pc(/system/bin/crasher_cpp): 180 00966dc8 e1a0d00c 181 00966dcc eb000000 182 ... 183 00966e44 e5907008 184pc(/system/bin/crasher_cpp): 185 00966dc8 e1a0d00c 186 00966dcc eb000000 187 ... 188 00966e44 e5907008 189FaultStack: <- 崩溃线程的栈地址空间 190 ffd27260 00000000 191 ffd27264 f7cac628 192 ... 193 ffd2729c 0096ad1f 194sp0:ffd272a0 0096fdfc <- #00栈顶 195 ffd272a4 009684d3 196sp1:ffd272a8 00000001 197 ffd272ac 73657408 198 ffd272b0 f7590074 199 ... 200 ffd272dc 0096856d 201sp2:ffd272e0 ffd27334 202 ffd272e4 ffd27334 203 ffd272e8 00000002 204 .... 205 ffd272f4 f7bfbb9c 206sp3:ffd272f8 00000000 207 ffd272fc ffd27334 208 209Maps: <- 故障时进程maps 210962000-966000 r--p 00000000 /system/bin/crasher_cpp 211966000-96c000 r-xp 00003000 /system/bin/crasher_cpp 21296c000-96f000 r--p 00008000 /system/bin/crasher_cpp 21396f000-970000 rw-p 0000a000 /system/bin/crasher_cpp 214149f000-14a0000 ---p 00000000 [heap] 21514a0000-14a2000 rw-p 00000000 [heap] 216... 217f7b89000-f7be1000 r--p 00000000 /system/lib/ld-musl-arm.so.1 218f7be1000-f7ca9000 r-xp 00057000 /system/lib/ld-musl-arm.so.1 219f7ca9000-f7cab000 r--p 0011e000 /system/lib/ld-musl-arm.so.1 220f7cab000-f7cad000 rw-p 0011f000 /system/lib/ld-musl-arm.so.1 221f7cad000-f7cbc000 rw-p 00000000 [anon:ld-musl-arm.so.1.bss] 222ffd07000-ffd28000 rw-p 00000000 [stack] 223ffff0000-ffff1000 r-xp 00000000 [vectors] 224OpenFiles: <- 故障时进程打开文件Fd信息 2250->/dev/pts/1 native object of unknown type 0 2261->/dev/pts/1 native object of unknown type 0 2272->/dev/pts/1 native object of unknown type 0 2283->socket:[67214] native object of unknown type 0 229... 23011->pipe:[67219] native object of unknown type 0 23112->socket:[29074] native object of unknown type 0 23225->/dev/ptmx native object of unknown type 0 23326->/dev/ptmx native object of unknown type 0 234 235HiLog: <- 故障时的Hilog日志 23605-06 20:10:51.301 9623 9623 E C03f00/MUSL-SIGCHAIN: signal_chain_handler call 2 rd sigchain action for signal: 11 23705-06 20:10:51.306 9623 9623 I C02d11/DfxSignalHandler: DFX_SigchainHandler :: sig(11), pid(9623), tid(9623). 23805-06 20:10:51.307 9623 9623 I C02d11/DfxSignalHandler: DFX_SigchainHandler :: sig(11), pid(9623), processName(./crasher_cpp), threadName(crasher_cpp). 23905-06 20:10:51.389 9623 9623 I C02d11/DfxSignalHandler: processdump have get all resgs 240 241``` 242 243<!--Del--> 244通过Shell获取的`/data/log/faultlog/temp`获取到的日志内容格式如下: 245 246``` 247Timestamp:2024-05-06 20:10:51.000 <- 故障发生时间戳 248Pid:9623 <- 进程号 249Uid:0 <- 用户ID 250Process name:./crasher_cpp <- 进程名称 251Process life time:1s <- 进程存活时间 252Reason:Signal:SIGSEGV(SEGV_MAPERR)@0x00000004 probably caused by NULL pointer dereference <- 故障原因和空指针提示 253Fault thread info: 254Tid:9623, Name:crasher_cpp <- 故障线程号,线程名 255#00 pc 00008d22 /system/bin/crasher_cpp(TestNullPointerDereferenceCrash0()+22)(adfc673300571d2da1e47d1d12f48b44) <- 调用栈 256#01 pc 000064d1 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+160)(adfc673300571d2da1e47d1d12f48b44) 257#02 pc 00006569 /system/bin/crasher_cpp(main+92)(adfc673300571d2da1e47d1d12f48b44) 258#03 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83) 259#04 pc 00004e28 /system/bin/crasher_cpp(_start_c+84)(adfc673300571d2da1e47d1d12f48b44) 260#05 pc 00004dcc /system/bin/crasher_cpp(adfc673300571d2da1e47d1d12f48b44) 261Registers: <- 故障现场寄存器 262r0:ffffafd2 r1:00000004 r2:00000001 r3:00000000 263r4:ffd27e39 r5:0096e000 r6:00000a40 r7:0096fdfc 264r8:f7ba58d5 r9:f7baea86 r10:f7cadd38 265fp:ffd27308 ip:f7cb2078 sp:ffd272a0 lr:f7c7ab98 pc:0096ad22 266Memory near registers: <- 故障现场寄存器附近内存 267r4([stack]): 268 ffd27e30 72656873 269 ffd27e34 7070635f 270 ... 271 ffd27eac 3d73746f 272r5(/system/bin/crasher_cpp): 273 0096dff8 00000000 274 0096dffc 0096717d 275 ... 276 0096e074 00000000 277r7(/system/lib/ld-musl-arm.so.1): 278 f7cabb58 00000000 279 f7cabb5c 0034ba00 280 ... 281 f7cabbd4 00000000 282r8(/system/lib/ld-musl-arm.so.1): 283 f7ba58cc 63637573 284 f7ba58d0 2e737365 285 ... 286 f7ba5948 70206269 287r9(/system/lib/ld-musl-arm.so.1): 288 f7baea7c 20746f6e 289 f7baea80 6e756f66 290 ... 291 f7baeaf8 25206e69 292r10([anon:ld-musl-arm.so.1.bss]): 293 f7cadd30 00000000 294 f7cadd34 00000000 295 ... 296 f7caddac 00000000 297r12([anon:ld-musl-arm.so.1.bss]): 298 f7cb2070 56726562 299 f7cb2074 65756c61 300 ... 301 f7cb20ec 00000000 302sp([stack]): 303 ffd27328 00000000 304 ffd2732c 00966dd0 305 ... 306 ffd273a4 00000004 307pc(/system/bin/crasher_cpp): 308 00966dc8 e1a0d00c 309 00966dcc eb000000 310 ... 311 00966e44 e5907008 312pc(/system/bin/crasher_cpp): 313 00966dc8 e1a0d00c 314 00966dcc eb000000 315 ... 316 00966e44 e5907008 317FaultStack: <- 崩溃线程的栈地址空间 318 ffd27260 00000000 319 ffd27264 f7cac628 320 ... 321 ffd2729c 0096ad1f 322sp0:ffd272a0 0096fdfc <- #00栈顶 323 ffd272a4 009684d3 324sp1:ffd272a8 00000001 325 ffd272ac 73657408 326 ffd272b0 f7590074 327 ... 328 ffd272dc 0096856d 329sp2:ffd272e0 ffd27334 330 ffd272e4 ffd27334 331 ffd272e8 00000002 332 .... 333 ffd272f4 f7bfbb9c 334sp3:ffd272f8 00000000 335 ffd272fc ffd27334 336 337Maps: <- 故障时进程maps 338962000-966000 r--p 00000000 /system/bin/crasher_cpp 339966000-96c000 r-xp 00003000 /system/bin/crasher_cpp 34096c000-96f000 r--p 00008000 /system/bin/crasher_cpp 34196f000-970000 rw-p 0000a000 /system/bin/crasher_cpp 342149f000-14a0000 ---p 00000000 [heap] 34314a0000-14a2000 rw-p 00000000 [heap] 344... 345f7b89000-f7be1000 r--p 00000000 /system/lib/ld-musl-arm.so.1 346f7be1000-f7ca9000 r-xp 00057000 /system/lib/ld-musl-arm.so.1 347f7ca9000-f7cab000 r--p 0011e000 /system/lib/ld-musl-arm.so.1 348f7cab000-f7cad000 rw-p 0011f000 /system/lib/ld-musl-arm.so.1 349f7cad000-f7cbc000 rw-p 00000000 [anon:ld-musl-arm.so.1.bss] 350ffd07000-ffd28000 rw-p 00000000 [stack] 351ffff0000-ffff1000 r-xp 00000000 [vectors] 352OpenFiles: <- 故障时进程打开文件Fd信息 3530->/dev/pts/1 native object of unknown type 0 3541->/dev/pts/1 native object of unknown type 0 3552->/dev/pts/1 native object of unknown type 0 3563->socket:[67214] native object of unknown type 0 357... 35811->pipe:[67219] native object of unknown type 0 35912->socket:[29074] native object of unknown type 0 36025->/dev/ptmx native object of unknown type 0 36126->/dev/ptmx native object of unknown type 0 362``` 363<!--DelEnd--> 364**日志格式 - 栈溢出故障场景** 365该场景会在日志中打印出提示信息,表明故障很有可能是因为栈溢出导致。核心日志如下: 366 367``` 368Generated by HiviewDFX@OpenHarmony 369================================================================ 370Device info:OpenHarmony 3.2 <- 设备信息 371Build info:OpenHarmony 5.0.0.23 <- 版本信息 372Fingerprint:8bc3343f50024204e258b8dce86f41f8fcc50c4d25d56b24e71fe26c0a23e321 <- 标识故障特征 373Module name:crasher_cpp <- 模块名 374Timestamp:2024-05-06 20:18:24.000 <- 故障发生时间戳 375Pid:9838 <- 进程号 376Uid:0 <- 用户ID 377Process name:./crasher_cpp <- 进程名称 378Process life time:2s <- 进程存活时间 379Reason:Signal:SIGSEGV(SEGV_ACCERR)@0xf76b7ffc current thread stack low address = 0xf76b8000, probably caused by stack-buffer-overflow <- 故障原因和栈溢出提示 380... 381``` 382 383**日志格式 - 栈覆盖故障场景** 384在栈覆盖场景下,由于栈上内存被踩,无法成功回溯栈帧,该场景会在日志中打印出提示信息,说明回栈失败并尝试从线程栈里解析获取不可靠的调用栈,尽可能提供开发者信息以分析问题。核心日志如下: 385 386``` 387Generated by HiviewDFX@OpenHarmony 388================================================================ 389Device info:OpenHarmony 3.2 <- 设备信息 390Build info:OpenHarmony 5.0.0.23 <- 版本信息 391Fingerprint:79b6d47b87495edf27135a83dda8b1b4f9b13d37bda2560d43f2cf65358cd528 <- 标识故障特征 392Module name:crasher_cpp <- 模块名 393Timestamp:2024-05-06 20:27:23.2035266415 <- 故障发生时间戳 394Pid:10026 <- 进程号 395Uid:0 <- 用户ID 396Process name:./crasher_cpp <- 进程名称 397Process life time:1s <- 进程存活时间 398Reason:Signal:SIGSEGV(SEGV_MAPERR)@0000000000 probably caused by NULL pointer dereference <- 故障原因 399LastFatalMessage: Failed to unwind stack, try to get unreliable call stack from #02 by reparsing thread stack <- 尝试从线程栈里获取不可靠的堆栈 400Fault thread info: 401Tid:10026, Name:crasher_cpp <- 故障线程号,线程名 402#00 pc 00000000 Not mapped 403#01 pc 00008d22 /system/bin/crasher_cpp(TestNullPointerDereferenceCrash0()+22)(adfc673300571d2da1e47d1d12f48b44) <- 调用栈 404#02 pc 000064d1 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+160)(adfc673300571d2da1e47d1d12f48b44) 405#03 pc 00006569 /system/bin/crasher_cpp(main+92)(adfc673300571d2da1e47d1d12f48b44) 406#04 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83) 407... 408``` 409 410**日志格式 - 异步线程场景故障** 411(目前支持ARM64架构,且在调试应用(HAP_DEBUGGABLE)下开启) 412当异步线程发生崩溃后,把提交该异步任务的线程的栈也打印出来,帮助定位由于异步任务提交者造成的崩溃问题。崩溃线程的调用栈和其提交线程的调用栈用SubmitterStacktrace分割开。核心日志如下: 413 414``` 415Generated by HiviewDFX@OpenHarmony 416================================================================ 417Device info:OpenHarmony 3.2 <- 设备信息 418Build info:OpenHarmony 5.0.0.23 <- 版本信息 419Fingerprint:8bc3343f50024204e258b8dce86f41f8fcc50c4d25d56b24e71fe26c0a23e321 <- 标识故障特征 420Module name:crasher_cpp <- 模块名 421Timestamp:2024-05-06 20:28:24.000 <- 故障发生时间戳 422Pid:9838 <- 进程号 423Uid:0 <- 用户ID 424Process name:./crasher_cpp <- 进程名称 425Process life time:2s <- 进程存活时间 426Reason:Signal:SIGSEGV(SI_TKILL)@0x000000000004750 from:18256:0 <- 故障原因 427Fault thread info: 428Tid:18257, Name:crasher_cpp <- 故障线程号,线程名 429#00 pc 000054e6 /system/bin/ld-musl-aarch64.so.l(raise+228)(adfc673300571d2da1e47d1d12f48b44) <- 调用栈 430#01 pc 000054f9 /system/bin/crasher_cpp(CrashInSubThread(void*)+56)(adfc673300571d2da1e47d1d12f48b50) 431#02 pc 000054f9 /system/bin/ld-musl-aarch64.so.l(start+236)(adfc673300571d2da1e47d1d12f48b44) 432========SubmitterStacktrace======== <- 任务异常时打印任务提交者调用栈 433#00 pc 000094dc /system/bin/crasher_cpp(DfxCrasher::AsyncStacktrace()+36)(adfc673300571d2da1e47d1d12f48b50) 434#01 pc 00009a58 /system/bin/crasher_cpp(DfxCrasher::ParseAndDoCrash(char const*) const+232)(adfc673300571d2da1e47d1d12f48b50) 435#02 pc 00009b40 /system/bin/crasher_cpp(main+140)(adfc673300571d2da1e47d1d12f48b50) 436#03 pc 0000a4e1c /system/bin/ld-musl-aarch64.so.l(libc_start_main_stage2+68)(adfc673300571d2da1e47d1d12f48b44) 437... 438``` 439 440### 基于崩溃栈定位行号 441 442#### 方式一:DevEco Studio 开发者环境下,支持调用栈直接跳转到对应行号 443 444在应用开发场景,对于应用自身的动态库,生成的cppcrash堆栈可直接跳转到代码行处,支持Native栈帧和JS栈帧,无需开发者自行进行解行号操作。对于部分未能解析跳转到对应行号的栈帧,可参考方式二解析。 445 446 447 448#### 方式二:通过SDK llvm-addr2line 工具定位行号 449 4501. 获取符号表 451 获取崩溃栈中so文件对应的带符号版本,保证与应用/系统内运行时的so文件版本一致。 452 对于应用自身的动态库,经DevEco编译构建,生成在工程的 /build/default/intermediates/libs 目录下,默认是带符号的版本。可通过Linux file 命令查询二进制文件的 BuildID 以核对是否匹配。其中,BuildID 是用于标识二进制文件的唯一标识符,通常由编译器在编译时生成,not stripped 表示该动态库是包含符号表的。 453 454 ``` 455 $ file libbabel.so 456 libbabel.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fdb1b5432b9ea4e2a3d29780c3abf30e2a22da9d, with debug_info, not stripped 457 ``` 458 459 **说明:**对于系统动态库符号表,随版本进行归档。 460 4612. 通过 llvm-addr2line 工具定位行号 462 llvm-addr2line 工具归档在:`[SDK DIR PATH]\OpenHarmony\11\native\llvm\bin` 路径下。根据实际的SDK版本路径略有不同,开发者请自行识别或在路径下搜索。 463 例如有堆栈如下(有省略): 464 465 ``` 466 Generated by HiviewDFX@OpenHarmony 467 ================================================================ 468 Device info:OpenHarmony 3.2 469 Build info:OpenHarmony 5.0.0.22 470 Fingerprint:50577c0a1a1b5644ac030ba8f08c241cca0092026b59f29e7b142d5d4d5bb934 471 Module name:com.samples.recovery 472 Version:1.0.0 473 VersionCode:1000000 474 PreInstalled:No 475 Foreground:No 476 Timestamp:2017-08-05 17:03:40.000 477 Pid:2396 478 Uid:20010044 479 Process name:com.samples.recovery 480 Process life time:7s 481 Reason:Signal:SIGSEGV(SEGV_MAPERR)@0000000000 probably caused by NULL pointer dereference 482 Tid:2396, Name:amples.recovery 483 # 00 pc 00003510 /data/storage/el1/bundle/libs/arm/libentry.so(TriggerCrash(napi_env__*, napi_callback_info__*)+24)(446ff75d3f6a518172cc52e8f8055650b02b0e54) 484 # 01 pc 0002b0c5 /system/lib/platformsdk/libace_napi.z.so(panda::JSValueRef ArkNativeFunctionCallBack<true>(panda::JsiRuntimeCallInfo*)+448)(a84fbb767fd826946623779c608395bf) 485 # 02 pc 001e7597 /system/lib/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaInterpreter::RunInternal(panda::ecmascript::JSThread*, unsigned char const*, unsigned long long*)+14710)(106c552f6ce4420b9feac95e8b21b792) 486 # 03 pc 001e0439 /system/lib/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaInterpreter::Execute(panda::ecmascript::EcmaRuntimeCallInfo*)+984)(106c552f6ce4420b9feac95e8b21b792) 487 ... 488 # 39 pc 00072998 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(5b1e036c4f1369ecfdbb7a96aec31155) 489 # 40 pc 00005b48 /system/bin/appspawn(_start_c+84)(cb0631260fa74df0bc9b0323e30ca03d) 490 # 41 pc 00005aec /system/bin/appspawn(cb0631260fa74df0bc9b0323e30ca03d) 491 Registers: 492 r0:00000000 r1:ffc47af8 r2:00000001 r3:f6555c94 493 r4:00000000 r5:f4d90f64 r6:bd8434f8 r7:00000000 494 r8:00000000 r9:ffc48808 r10:ffc47b70 495 fp:f7d8a5a0 ip:00000000 sp:ffc47aac lr:f4d6b0c7 pc:bd843510 496 ``` 497 498 基于SDK llvm-addr2line解析行号如下所示: 499 500 ``` 501 [SDK DIR PATH]\OpenHarmony\11\native\llvm\bin> .\llvm-addr2line.exe -Cfie libentry.so 3150 502 TrggerCrash(napi_env__*, napi_callback_info__*) 503 D:/code/apprecovery-demo/entry/src/main/cpp/hello.cpp:48 504 ``` 505 506 llvm-addr2line 逐行解析的命令为:`llvm-addr2line.exe -fCpie libutils.z.so 偏移量`,偏移量可以多个一起解:`llvm-addr2line.exe -fCpie libxxx.so 0x1bc868 0x1be28c xxx`。使用llvm-addr2line后,如果得出的行号看起来不是很正确,可以考虑对 地址进行微调(如减1),或者考虑关闭一些编译优化。 507 508#### 方式三:通过 DevEco Studio hstack 工具解析堆栈信息 509 510hstack是DevEco Studio为开发人员提供的用于将release应用混淆后的crash堆栈还原为源码对应堆栈的工具,支持Windows、Mac、Linux三个平台。[DevEco Studio hstack使用指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-command-line-hstack-V5) 511 512### 结合业务检视代码 513 514根据基于崩溃栈定位行号章节中介绍的三种方式获取到栈顶对应的行号后,回到代码中,检视上下文。如下图所示,hello.cpp中的48行是一个空指针解引用的代码问题。 515 516 517 518本场景是一个故障构造的应用,实际的场景往往不会这么简单,需要结合实际业务进行分析。 519 520### 反汇编(可选) 521 522一般而言,如果是比较明确的问题,反编译定位到代码行就能够定位;较少数的情况,比如定位到某一行里面调用的方法有多个参数,参数又涉及到结构体等,就需要借助反汇编来进一步分析。 523 524参考案例 525 526CPPCRASH日志头部信息如下: 527 528```text 529Process name:com.ohos.medialibrary.medialibrarydata 530 531Process life time:13402s 532 533Reason:SIGSEGV(SEGV_MAPERR)@0x0000005b3b46c000 534 535Fault thread info: 536 537Tid:48552, Name:UpradeTask 538 539#00 pc 00000000000a87e4 /system/lib/ld-musl-aarch64.so.1(memcpy+356)(3c3e7fb27680dc2ee99aa08dd0f81e85) 540 541... 542``` 543 544分析步骤: 545 5461. 根据pc寄存器地址找到对应的汇编指令,根据汇编指令找到当前操作 547 548 在CPPCRASH日志文件中找到栈顶的PC地址,并反汇编对应的ELF(使用unstrip的so,llvm-objdump -d -l xxx.so)。 549 550 例如参考案例在执行00000000000a87e4地址对应的指令时发生data_abort,反编译对应buildId(3c3e7fb27680dc2ee99aa08dd0f81e85)的libc.so, 551 552 反汇编查看a87e4偏移地址显示的信息: 553 554 ```text 555 xxx/../../third_party/optimized-routines/string/aarch64/memcpy.S:175 556 557 a87e4:a94371aa ldp x10, x11, [x1, #48] 558 ``` 559 560 根据反汇编显示的源码文件位置175行,查看对应memcpy.S源文件代码: 561 562 ```text 563 L(loop64): 564 565 line 170 stp A_l, A_h, [dst, 16] 566 567 line 171 ldp A_l, A_h, [src, 16] 568 569 line 172 stp B_l, B_h, [dst, 32] 570 571 line 173 ldp B_l, B_h, [src, 32] 572 573 line 174 stp C_l, C_h, [dst, 48] 574 575 line 175 ldp C_l, C_h, [src, 48] ----> 崩溃处指令 576 577 line 176 stp D_l, D_h, [dst, 64] 578 579 line 177 ldp D_l, D_h, [src, 64] 580 581 line 178 subs count, count, 64 582 583 line 179 b.hi L(loop64) 584 ``` 585 5862. 根据寄存器值,结合上下文推测当前操作的代码对象 587 588 通常x0寄存器为函数的第一个参数,x1为第二个参数,x2为第三个,依次类推;如果为类的方法,x0为对象的地址指针,其后x1、x2、x3为依次类推,注意函数参数超过5个会压入堆栈中。 589 590 栈顶函数void* memcpy(void* restrict dest, void* restrict src, size_t n)的调用参数,x0为目的地址dest, x1为源地址,x2为拷贝字节数; 591 592 在CPPCRASH日志文件中找到对应的三个寄存器值,结合错误访问地址0x0000005b3b46c000,判断出问题的参数为x1对应的src源地址参数: 593 594 ```text 595 Register: 596 597 x0:000005b50c3e3c4 x1:000005b3b46bfcc x2:0000000000007e88 x3:000005b50c42380 598 599 ... 600 ``` 601 6023. 判断代码对象的故障类型 603 604 通过CPPCRASH日志中Memory near registers查看寄存器附近内存地址值: 605 606 ```text 607 x1(/data/medialibrary/database/kvdb/3ddb6fb8b2fcb38d2f431e86bfb806dab771637860d6e86bb9430fa15df04248/single_ver/main/gen_natural_st): 608 609 0000005b21bb1fb8 8067d0f2e727f00a 610 611 0000005b21bb1fc0 1b10e1e9a1079f7a 612 613 0000005b21bb1fc8 83906d9c18cdb9c1 614 615 0000005b21bb1fd0 627dd75ab9335eb0 616 617 0000005b21bb1fd8 aabe2bb1b00f2c03 618 619 0000005b21bb1fe0 f981e4acb716cbc1 620 621 0000005b21bb1fe8 806b3d5730d281ee 622 623 0000005b21bb1ff0 3e99fedbc0a9b5e9 624 625 0000005b21bb1ff8 a91ab9d327969682 626 627 0000005b21bb2000 ffffffffffffffff -----> 读取越界 628 629 0000005b21bb2008 ffffffffffffffff 630 631 0000005b21bb2010 ffffffffffffffff 632 633 0000005b21bb2018 ffffffffffffffff 634 635 0000005b21bb2020 ffffffffffffffff 636 637 0000005b21bb2028 ffffffffffffffff 638 639 0000005b21bb2030 ffffffffffffffff 640 ``` 641 642 由上判断是一个读取越界的问题,出问题的参数为memcpy的buf和bufSize, 643 644 此时只需要分析代码中调用memcpy时传入的参数逻辑即可。 645 6464. 持续跟踪出问题对象的参数来源,结合代码与流水日志排查问题 647 648 排查方向一:排查参数对象的有效性、范围是否合法,例如buf的实际大小是否与传入的bufSize一致; 649 650 排查方向二:参数对象的生命周期是否合法,例如buf是否已被释放,是否存在多线程操作被踩内存; 651 652 排查方向三:通过参数对象访问函数的上下文,排查参数的不合理操作逻辑,例如跟踪buf和bufsize的操作逻辑,增加调试信息,锁定不合理操作逻辑。 653 654 代码片段: 655 656 ```text 657 static StatusInter xxxFunc(..., const uint8_t *buf, uint32_t bufSize) 658 659 ... 660 661 uint32_t srcSize = bufSize; 662 663 uint32_t srcOffset = cache->appendOffset - bufSize; 664 665 errno_t ret = memcpy_s(cache->buffer + srcOffset, srcSize, buf, bufSize); 666 667 if (ret != EOK) { 668 669 return MEMORY_OPERATE_FAILED_INTER; 670 671 } 672 673 ... 674 ``` 675 676 通过持续追踪buf和bufSize的来源,最终来确定buf与bufSize在连续拷贝后不匹配,bufSize最终大于实际buf大小导致越界读取。 677 678### CppCrash 常见问题分类与原因 679 680- 空指针解引用 NULL pointer dereference 681 形如 SIGSEGV(SEGV_MAPERR)@0x00000000 或 cppcrash日志的Register中打印的r0,r1 等传参寄存器的值为0时,应首先考虑调用时是否传入了空指针。 682 形如 SIGSEGV(SEGV_MAPERR)@0x0000000c 或 cppcrash日志Register中打印的r1 等传参寄存器的值为一个很小的值时应考虑调用入参的结构体成员是否包含空指针。 683- 程序主动终止SIGABRT 684 一般为用户/框架/C库主动触发,大部分场景下跳过C库/abort发起的框架库的第一帧即为崩溃原因,这里主要检测的是资源使用类的问题,如线程创建,文件描述符使用,接口调用时序等。 685- SIGSEGV无效内存访问 686 - 多线程操作集合,std库的集合为非线程安全,如果多线程添加删除,容易出现SIGSEGV类崩溃,如果使用 llvm-addr2line 后的代码行与集合相关,可以考虑这个原因。 687 - 不匹配的对象生命周期,比如使用裸指针(不含有封装、自动内存管理等特性的指针)保存sptr类型以及shared_ptr类型,会导致内存泄漏和悬空指针问题。裸指针是指不含有封装、自动内存管理等特性的指针。它只是一个指向内存地址的简单指针,没有对指针指向的内存进行保护或管理。裸指针可以直接访问指向的内存,但也容易出现内存泄漏、空指针引用等问题。因此,在使用裸指针时需要特别小心,避免出现潜在的安全问题;推荐使用智能指针来管理内存。 688- use after free问题 689 返回临时变量、野指针:比如返回栈变量的引用,释放后未置空继续访问。 690 691 ``` 692 # include <iostream> 693 694 int& getStackReference() { 695 int x = 5; 696 return x; // 返回 x 的引用 697 } 698 699 int main() { 700 int& ref = getStackReference(); // 获取 x 的引用 701 // x 在 getStackReference 函数返回后被释放 702 // ref 现在是悬空引用,继续访问会导致未定义行为 703 std::cout << ref << std::endl; // 试图输出 x 的值,这是未定义行为 704 return 0; 705 } 706 ``` 707 708- 栈溢出:如递归调用,析构函数相互调用,特殊的栈(信号栈)中使用大块栈内存。 709 ``` 710 # include <iostream> 711 712 class RecursiveClass { 713 public: 714 RecursiveClass() { 715 std::cout << "Constructing RecursiveClass" << std::endl; 716 } 717 718 ~RecursiveClass() { 719 std::cout << "Destructing RecursiveClass" << std::endl; 720 // 在析构函数中递归调用 721 RecursiveClass obj; 722 } 723 }; 724 725 int main() { 726 RecursiveClass obj; 727 return 0; 728 } 729 ``` 730 创建一个 RecursiveClass 对象时,它的构造函数被调用。销毁这个对象时,它的析构函数被调用。在析构函数中,创建了一个新的RecursiveClass对象,这会导致递归调用,直到栈溢出。递归调用导致了无限的函数调用,最终导致栈空间耗尽,程序崩溃。 731- 二进制不匹配:通常由ABI(应用程序二进制接口)不匹配引起,如自己编译二进制与实际运行的二进制接口存在差异,数据结构定义存在差异,这种一般会产生随机的崩溃栈。 732- 踩内存:使用有效的野指针,并修改了其中的内存为非法值,访问越界,覆盖了正常的数据这种一般会产生随机的崩溃栈。 733- SIGBUS (Aligment)考虑对指针进行强转之后地址是否已经处于非对齐状态。 734 735## 分析案例 736 737本章节从信号分类、问题场景分类和维测工具分类三个维度来对CppCrash典型问题进行分析和归纳。 738信号分类,侧重对常见崩溃信号覆盖介绍,各类信号提供一个典型案例。 739问题场景分类,侧重归纳目前高频问题背后的通用场景,各类场景提供一个典型案例。 740维测工具分类,侧重总结各类维测工具如果使用类分析相应的问题,各类工具提供一个典型案例。 741 742### 从信号维度分析问题 743 744#### 类型一:SIGSEGV类崩溃问题 745 746SIGSEGV信号伴随着程序发生段错误(Segmentation Fault)故障,其故障场景为`当程序试图访问不被允许访问的内存区域(比如,尝试写一块属于操作系统的内存),或以错误的类型访问内存区域(比如,尝试写一块只读内存)`。概括有如下几点: 747 748- SIGSEGV是在访问内存时发生的错误,它属于内存管理的范畴。 749- SIGSEGV是一个用户态的概念,是操作系统在用户态程序错误访问内存时所做出的处理。 750- 当用户态程序访问(访问表示读、写或执行)不允许访问的内存时,产生SIGSEGV。 751- 当用户态程序以错误的方式访问允许访问的内存时,产生SIGSEGV。 752 753SIGSEGV在很多时候是由于指针越界引起的,但并不是所有的指针越界都会引发SIGSEGV。如果不解引用越界指针,是不会引起SIGSEGV崩溃的。而且即使解引用了一个越界的指针,也不一定会引起SIGSEGV。SIGSEGV涉及到操作系统、C库、编译器、链接器各方面的内容,以如下具体的例子来说明。 754 7551. 错误的访问类型 756 样例代码如下: 757 758 ``` 759 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 760 { 761 char *s = "hello world"; 762 s[1] = 'H'; 763 return 0; 764 } 765 ``` 766 767 这是最常见的一个例子。此例中,"hello world" 作为一个常量字符串,在编译后会被放在 .rodata 节(GCC),最后链接生成目标程序时 .rodata 节会被合并到 text segment 与代码段放在一起,故其所处内存区域是只读的。这就是错误的访问类型引起的 SIGSEGV(SEGV_ACCERR) 崩溃。 768 769  770 7712. 访问不属于进程地址空间的内存 772 773 样例代码如下: 774 775 ``` 776 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 777 { 778 uint64_t* p = (uint64_t*)0xffffffcfc42ae6f4; 779 *p = 10; 780 return 0; 781 } 782 ``` 783 784 在这个例子中,我们访问了一个属于内核的地址。当然很少会有人这样写程序,但程序可能在不经意的情况下做出这样的行为,产生SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4的崩溃。本例中的CppCrash故障日志(仅展示核心日志内容)如下: 785 786 ``` 787 Device info:xxxxxx xxxx xx xxx 788 Build info:xxxxxxx 789 Fingerprint:73a5dcdf3e509605563aa11ac8cb4f3d7f99b9946dc142212246b53b741c4129 790 Module name:com.samples.recovery 791 Version:1.0.0 792 VersionCode:1000000 793 PreInstalled:No 794 Foreground:Yes 795 Timestamp:2024-04-29 14:07:12.082 796 Pid:21374 797 Uid:20020144 798 Process name:com.samples.recovery 799 Process life time:8s 800 Reason:Signal:SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4 <-崩溃地址,即进程在 801 Fault thread info: 802 Tid:21374, Name:amples.recovery 803 # 00 pc 0000000000001ccc /data/storage/el1/bundle/libs/arm64/libentry.so(TriggerCrash(napi_env__*, napi_callback_info__*)+36)(4dd115fa8b8c1b3f37bdb5b7b67fc70f31f0dbac) 804 # 01 pc 0000000000033678 /system/lib64/platformsdk/libace_napi.z.so(ArkNativeFunctionCallBack(panda::JsiRuntimeCallInfo*)+372)(7d6f229764fdd4b72926465066bc475e) 805 # 02 pc 00000000001d7f38 /system/lib64/module/arkcompiler/stub.an(RTStub_PushCallArgsAndDispatchNative+40) 806 # 03 at doTriggerException (entry/src/main/ets/pages/FaultTriggerPage.ets:72:7) 807 # 04 at triggerNativeException (entry/src/main/ets/pages/FaultTriggerPage.ets:79:5) 808 # 05 at anonymous (entry/src/main/ets/pages/FaultTriggerPage.ets:353:19) 809 # 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) 810 ... 811 # 38 pc 00000000000324b0 /system/bin/appspawn(AppSpawnRun+172)(c992404f8d1cf03c84c067fbf3e1dff9) 812 # 39 pc 00000000000213a8 /system/bin/appspawn(main+956)(c992404f8d1cf03c84c067fbf3e1dff9) 813 # 40 pc 00000000000a4b98 /system/lib/ld-musl-aarch64.so.1(libc_start_main_stage2+64)(ff4c94d996663814715bedb2032b2bbc) 814 ``` 815 8163. 访问不存在的内存 817 样例代码如下: 818 819 ``` 820 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 821 { 822 int *a = NULL; 823 *a = 1; 824 return 0; 825 } 826 ``` 827 828 在实际情况中,此例中的空指针可能指向用户态地址空间,但其所指向的页面实际不存在,便是最常见的空指针解引用的场景,这类场景CppCrash日志会识别出来,并在Reason字段打印推断信息 `Reason:Signal:SIGSEGV(SEGV_MAPERR)@000000000000000000 probably caused by NULL pointer dereference`,如下图所示: 829 830  831 8324. 重复free 833 样例代码如下: 834 835 ``` 836 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 837 { 838 void *pc = malloc(1024); 839 free(pc); 840 free(pc); // 重复free 841 printf("free ok!\n"); 842 return 0; 843 } 844 ``` 845 846 重复释放内存的场景,系统会抛出 SIGSEGV(SI_TKILL) 类故障提示为非法的内存操作,如下图所示: 847 848  849 850 以上是 SIGSEGV 类崩溃比较常见的原因,除此之外还有栈溢出内存访问、堆溢出内存访问、访问全局区野指针、函数跳转到一个非法的地址上执行,以及非法的系统调用参数等一些场景都有可能触发 SIGSEGV 。SIGSEGV和操作系统栈分配回收、编译器有着密切的联系。 851 852#### 类型二:SIGABRT类崩溃问题 853 854SIGABRT信号被发送到进程,告诉进程中止。既可以进程自己调用C标准库的abort()函数,信号通常由进程本身发起,也可以跟其他信号一样从外部发送给进程。 855 8561. 执行abort函数 857 样例代码如下: 858 859 ``` 860 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 861 { 862 OH_LOG_FATAL(LOG_APP, "test fatal log."); 863 abort(); 864 return 0; 865 } 866 ``` 867 868 该场景是主动调用 abort() 函数构造,对应的场景是各基础库可能会存在一些安全校验,对于识别为会导致进程无法安全运行性的场景,会主动 abort。对应如下场景如下图所示,会将进程退出前的最后一条fatal级别日志打印到崩溃日志中。 869 870  871 8722. 执行assert函数 873 样例代码如下: 874 875 ``` 876 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 877 { 878 # if 0 //该值为0,则报错;为1,则正常 879 void *pc = malloc(1024); 880 # else 881 void *pc = nullptr; 882 # endif 883 assert(pc != nullptr); 884 return 0; 885 } 886 ``` 887 888 除了调用 abort() 函数外,C++中的另一个异常处理机制还包括 assert() 函数,其他的还有 exit() 函数,异常捕获机制(try-catch)、exception类等。assert用于校验当前函数执行流程中的一些数据,校验失败进程会主动 abort。对应的故障场景如下图所示: 889 890  891 892### 从场景维度分析问题 893 894#### 类型一:内存访问类崩溃问题 895 896**问题背景** 897每次崩溃地址0x7f82764b70都在libace_napi_ark.z.so的可读可执行段上。崩溃原因是需要对地址进行写操作,而对应的maps段只有可读、可执行权限没有写权限,当进程试图访问不被允许访问的内存区域时,进程发生内存访问类崩溃。 898 899``` 9007f82740000-7f8275c000 r--p 00000000 /system/lib64/libace_napi_ark.z.so 9017f8275c000-7f8276e000 r-xp 0001b000 /system/lib64/libace_napi_ark.z.so <-崩溃地址落在该地址区间 9027f8276e000-7f82773000 r--p 0002c000 /system/lib64/libace_napi_ark.z.so 9037f82773000-7f82774000 rw-p 00030000 /system/lib64/libace_napi_ark.z.so 904``` 905 906崩溃调用栈如下图: 907 908 909 910**定位思路** 911每次地址出错都很有规律,但node地址不应该落在libace_napi_ark.z.so,从此类问题的现象来看,很有可能是踩内存问题。踩内存问题可使用[ASAN工具](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-asan-V5)排查问题。于是后续使用ASAN版本进行压测复现,也找到了稳定必现的场景。ASAN版本检测出来的问题也和上面崩溃栈反映的问题一致。堆栈报的是heap-use-after-free,实际上是对同一个address进行重复释放,只是在重复释放那次操作时,使用该地址去访问了其对象成员,进而报出了UAF问题。 912ASAN核心日志如下: 913 914``` 915================================================================= 916==appspawn==2029==ERROR: AddressSanitizer: heap-use-after-free on address 0x003a375eb724 at pc 0x002029ba8514 bp 0x007fd8175710 sp 0x007fd8175708 917READ of size 1 at 0x003a375eb724 thread T0 (thread name) 918 # 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 919(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 920 # 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 921(inlined by) panda::CopyableGlobal<panda::ObjectRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9 922(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 923(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 924(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 925 # 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 926(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 927 # 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 928(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 929 # 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 930(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 931 # 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 932(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 933... 934freed by thread T0 (thread name) here: 935 # 0 0x2024ed3abc (/system/asan/lib64/libclang_rt.asan.so+0xd3abc) 936 # 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 937(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 938(inlined by) panda::ecmascript::JSThread::DisposeGlobalHandle(unsigned long) at arkcompiler/ets_runtime/ecmascript/js_thread.h:604:9 939(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 940 # 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 941(inlined by) panda::CopyableGlobal<panda::FunctionRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9 942(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 943(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 944(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 945 # 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 946(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 947 # 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 948(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 949... 950previously allocated by thread T0 (thread name) here: 951 # 0 0x2024ed3be4 (/system/asan/lib64/libclang_rt.asan.so+0xd3be4) 952 # 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 953 # 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 954(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 955(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 956 # 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 957(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 958(inlined by) panda::ecmascript::JSThread::SetWeak(unsigned long, void*, void (*)(void*), void (*)(void*)) at arkcompiler/ets_runtime/ecmascript/js_thread.h:610:16 959(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 960... 961``` 962 963根据堆栈继续分析, 964JsiWeak析构或重置的时候会触发其成员(类型为JsiObject/JsiValue/JsiFunction)父类JsiType中CopyableGlobal被释放,如下图: 965 966 967 968运行时在GC过程中IterateWeakEcmaGlobalStorage,会对无callback的WeakNode调用DisposeGlobalHandle操作,也对其进行释放,如下图: 969 970 971 972因此,对于同一个WeakNode,可能会存在两个入口释放。如果是GC过程中IterateWeakEcmaGlobalStorage先释放,因为无callback回调通知到JsiWeak进行清理,JsiWeak那边仍保存一个对已释放的WeakNode引用,即CopyableGlobal;当前面讲的WeakNode所在的NodeList被整体释放,归还给操作系统后,JsiWeak处保留的CopyableGlobal再释放,就会存在double-free问题。 973 974 975 976**修改方法** 977JsiWeak调用SetWeakCallback,传入callback,在GC过程中IterateWeakEcmaGlobalStorage释放WeakNode时,通知JsiWeak对其保存的CopyableGlobal进行重置,确保同一个地址不被double-free。 978 979**建议与总结** 980使用内存时应考虑是否存在重复释放或者未释放的可能,另外定位内存访问类崩溃问题(一般是SIGSEGV类型问题)时,如果根据崩溃栈分析问题无头绪时,应优先考虑跑ASAN版本复现问题。 981 982#### 类型二:多线程类问题 983 984**问题背景** 985napi_env释放后仍被使用。 986 987**问题场景** 988napi接口的env传入非法,崩溃栈直接挂在NativeEngineInterface::ClearLastError()中,根据日志打印env地址定位,发现是env被释放后仍然被使用。 989 990 991 992核心崩溃栈如下: 993 994 995 996**修改方法** 997一个线程的创建的env,不要传给另一个线程使用。 998 999**建议与总结** 1000对于多线程类问题可以打开方舟多线程检测功能,能够更加方便定位问题,见工具类方舟多线程检测章节。 1001 1002注:napi接口中的env,是引擎创建时候的arkNativeEngine。 1003 1004#### 类型三:生命周期类问题 1005 1006**问题背景** 1007开发者在写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就会访问已释放的内存,产生问题。 1008 1009**问题场景** 1010napi_value其实是个裸指针(结构体指针),其作用是持有js对象,用于保持js对象的生命周期,保证js对象不被GC当成垃圾对象回收。napi_handle_scope用来管理napi_value,离开napi_handle_scope作用域之后,napi_value由GC回收,napi_value不再持有js对象(不再保护js对象生命周期)。 1011 1012**定位思路** 1013根据崩溃栈反编译找到出现问题的napi接口的上层接口,在上层接口内找到出问题的napi_value,检查napi_value的使用范围是否超出了napi_handle_scope的作用域范围。 1014 1015**案例** 1016napi_value超出NAPI框架的scope,如下: 1017 1018 1019 1020js侧通过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失效。 1021注:NAPI框架的scope即napi_handle_scope,napi开发者可以通过napi_handle_scope来管理napi_value的生命周期。框架层的scope嵌入在js call native的端到端流程中,即进入开发者自己写的native方法前open scope,native方法结束后close scope。 1022 1023#### 类型四:指针类问题 1024 1025**问题背景** 1026智能指针使用之前未判空,造成进程运行时发生空指针解引用崩溃问题。 1027 1028**问题影响** 1029进程发生崩溃,影响进程的稳定运行,非预期退出。 1030 1031**定位思路** 1032 1033 1034空指针类型崩溃可以从故障原因得到提示信息。通过llvm-addr2line解行号发现业务代码中在使用智能指针之前未对智能指针判空,对空地址进行访问导致崩溃产生。 1035 1036**修复方法** 1037对所有使用该指针的地方进行保护性判空。 1038 1039**建议与总结** 1040指针在使用之前应该要进行判空处理,防止访问空指针造成进程崩溃退出。 1041 1042### 配合工具分析问题 1043 1044#### 工具一:ASAN 1045 1046[ASAN使用指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-asan-V5) 1047 1048#### 工具二:方舟多线程检测 1049 1050**基本原理** 1051js是单线程的,操作js对象只允许发生在创建该js线程上,否则将会有多线程安全问题(主线程创建的js对象只能在主线程上操作,worker创建的js对象只能在worker线程上操作)。napi接口会直接涉及到对象的操作,因此绝大部分(95%)的napi接口只允许在js线程上使用。多线程检测机制检测的是:当前线程和使用的vm/env中的js thread id是否一致,若不一致,则表明vm/env被跨线程使用,存在多线程安全问题。常见问题有:1. 非js线程使用napi接口,2. napi接口使用其他线程的env。 1052 1053**使用方法** 1054 1055 1056DevEco勾选Multi Thread Check选项即可开启方舟多线程检测功能。 1057 1058**使用场景** 1059如果crash日志的堆栈难以分析,出现概率也相对比较高,对于此类问题,应该考虑开启多线程检测。 开启多线程检测之后,如果cpp_crash日志中fatal信息为Fatal: ecma_vm cannot run in multi-thread! thread:3096 currentThread:3550,则发生了多线程安全问题,意思是当前线程号为3550,而使用的js thread却是3096线程创建出来的,跨线程使用vm。 1060 1061**案例** 1062打开后重新触发崩溃,如果是多线程问题,会显示fatal 信息,参考如下: 1063 1064``` 1065Fatal: ecma_vm cannot run in multi-thread! thread:xxx currentThread:yyy 1066``` 1067 1068该信息意思是当前线程号为17585,而使用的 js thread 却是17688 线程创建出来的,跨线程使用 vm。vm 就是 js thread 的 napi_env__* ,运行线程代码的环境,一个线程使用一个 vm。 1069崩溃日志核心部分如下所示: 1070 1071``` 1072 1073Reason:Signal:SIGABRT(SI_TKILL)@0x01317b9f000044b1 from:17585: 20020127 1074LastFatalMessage: [default] CheckThread:177 Fatal: ecma_vm cannot run in multi-thread! thread:17688 currentThread:17585 1075Fault thread Info: 1076Tid:17585, Name:xxxxx 1077# 00 pc 00000000000f157c /system/lib/ld-musl-aarch64-asan.so.1(__restore_sigs+52)(38eb4ca904ae601d4b4dca502e948960) 1078# 01 pc 00000000000f1800 /system/lib/ld-musl-aarch64-asan.so.1(raise+112) (38eb4ca904aeó01d4b4dca502e948960) 1079# 02 pc 00000000000adc74 /system/lib/ld-musl-aarch64-asan.so.1(abort.+20) (38eb4ca904ae601d4b4dca502e948960) 1080# 03 pc 0000000000844fdc /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaVM::CheckThread() const+2712)(1df055932338c14060b864435aec88ab) 1081# 04 pc 0000000000f3d930 /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::0bjectRef:: New(panda::ecmascript::EcmaVM const*)+908)(1df055932338c14060b864435aec88 1082# 05 pC 0000000000095048 /sYstem/asan/lib64/platformsdk/libace_napi.z.so(napi_create_object+80)(efc1b3d1378f56b4b800489fb30dcded) 1083# 06 pc 00000000005d9770 /data/ storage/el1/bundle/libs/arm64/xxxxx.so (c0f1735eada49fadc5197745f5afOc0a52246270) 1084``` 1085 1086多线程问题分析步骤: 1087i. 检查 libace_napi.z.so 下面的第一个栈帧,上图为`xxxxx.so`,判断是否把 17688 线程的 napi_env 传给了 17585 线程; 1088ii. 如果 libace_napi.z.so 下面的栈帧没有明显的 napi_env 参数传递,需要检查是否以结构体成员变量的方式传递; 1089 1090#### 工具三:objdump 1091 1092**使用方法** 1093objdump二进制是系统侧工具,开发者需要具备OpenHarmony编译环境,项目代码在gitee上可获取,命令如下: 1094 1095``` 1096repo init -u git@gitee.com:openharmony/manifest.git -b master --no-repo-verify --no-clone-bundle --depth=1 1097repo sync -c 1098./build/prebuilts_download.sh 1099``` 1100 1101工具在工程目录下`prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump`,命令如下: 1102 1103``` 1104prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump -d libark_jsruntime.so > dump.txt 1105``` 1106 1107**使用场景** 1108有些情况下,通过addr2line只能看出代码某一行有问题,无法确认具体是哪个变量异常,此时可以通过objdump反汇编并结合cppcrash寄存器内容,进一步确认具体崩溃原因。 1109 1110**案例** 1111日志内容如下: 1112 1113``` 1114Tid:6655, Name:GC_WorkerThread 1115# 00 pc 00000000004492d4 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkObject(unsigned int, panda::ecmascript::TaggedObject*)+124)(21cf5411626d5986a4ba6383e959b3cc) 1116# 01 pc 000000000044b580 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkValue(unsigned int, panda::ecmascript::ObjectSlot&, panda::ecmascript::Region*, bool)+72)(21cf5411626d5986a4ba6383e959b3cc) 1117# 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) 1118# 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) 1119# 04 pc 0000000000447ccc /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::ProcessMarkStack(unsigned int)+248)(21cf5411626d5986a4ba6383e959b3cc) 1120# 05 pc 0000000000438588 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Heap::ParallelGCTask::Run(unsigned int)+148)(21cf5411626d5986a4ba6383e959b3cc) 1121# 06 pc 00000000004e31c8 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Runner::Run(unsigned int)+144)(21cf5411626d5986a4ba6383e959b3cc) 1122# 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) 1123# 08 pc 000000000014d894 /system/lib/ld-musl-aarch64.so.1 1124# 09 pc 0000000000085d04 /system/lib/ld-musl-aarch64.so.1 1125``` 1126 1127首先先用addr2line查看出错的行,如下: 1128 1129 1130 1131能看出的信息是判断IsYongSpace的时候访问到了空指针挂了,能够大概猜测出来是Region是空指针。 1132继续使用objdump反汇编,搜索出错地址4492d4 , 对应的汇编指令如下。 1133 1134 1135 1136查看x20寄存器,发现为0x000000000000000,x20从上面可以看出是基于x2做位运算(清除掉后18位,典型的Region::ObjectAddressToRange操作)。这样分析之后,就清楚了,x2为MarkObject函数的第二个参数object,x20为变量objectRegion,如下: 1137 1138``` 1139Registers: x0:0000007f0fe31560 x1:0000000000000003 x2:0000000000000000 x3:0000005593100000 1140 x4:0000000000000000 x5:0000000000000000 x6:0000000000000000 x7:0000005596374fa0 1141 x8:0000000000000000 x9:0000000000000000 x10:0000000000000000 x11:0000007f9cb42bb8 1142 x12:000000000000005e x13:000000000061f59e x14:00000005d73d60fb x15:0000000000000000 1143 x16:0000007f9cc5f200 x17:0000007f9f201f68 x18:0000000000000000 x19:0000000000000000 1144 x20:0000000000000000 x21:0000000000000000 x22:0000000000000000 x23:000000559313f860 1145 x24:000000559313f868 x25:0000000000000003 x26:00000055a0e19960 x27:0000007f9cc57b38 1146 x28:0000007f9f21a1c0 x29:00000055a0e19700 lr:0000007f9cb4b584 sp:00000055a0e19700 pc:0000007f9cb492d4 1147``` 1148 1149上面ldrb w8, [x20]对应 packedData_.flags_.spaceFlag_ 是因为,packedData_是region的第一个域,flags_是packedData_的第一个域,spaceFlag_是flags_的第一个域,所以直接取objectRegion地址对应的第一个字节。 1150查看汇编代码需要熟悉常见的汇编指令,以及传参规则,例如对于c++非inline的成员函数r0一般保存的是this指针。另外,由于编译器优化,源码和汇编代码对应关系可能不是很直观,我们可以根据代码中的一些特征值(常量),较快地找到对应关系。