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``` 525objdump -S xxx.so > xxx.txt 526objdump -d xxxx 对 xxxx 文件反汇编 527objdump -S -l xxxx 对 xxxx 文件反汇编,同时将指令对应的源码行显示出来 528``` 529 530### CppCrash 常见问题分类与原因 531 532- 空指针解引用 NULL pointer dereference 533 形如 SIGSEGV(SEGV_MAPERR)@0x00000000 或 cppcrash日志的Register中打印的r0,r1 等传参寄存器的值为0时,应首先考虑调用时是否传入了空指针。 534 形如 SIGSEGV(SEGV_MAPERR)@0x0000000c 或 cppcrash日志Register中打印的r1 等传参寄存器的值为一个很小的值时应考虑调用入参的结构体成员是否包含空指针。 535- 程序主动终止SIGABRT 536 一般为用户/框架/C库主动触发,大部分场景下跳过C库/abort发起的框架库的第一帧即为崩溃原因,这里主要检测的是资源使用类的问题,如线程创建,文件描述符使用,接口调用时序等。 537- SIGSEGV无效内存访问 538 - 多线程操作集合,std库的集合为非线程安全,如果多线程添加删除,容易出现SIGSEGV类崩溃,如果使用 llvm-addr2line 后的代码行与集合相关,可以考虑这个原因。 539 - 不匹配的对象生命周期,比如使用裸指针(不含有封装、自动内存管理等特性的指针)保存sptr类型以及shared_ptr类型,会导致内存泄漏和悬空指针问题。裸指针是指不含有封装、自动内存管理等特性的指针。它只是一个指向内存地址的简单指针,没有对指针指向的内存进行保护或管理。裸指针可以直接访问指向的内存,但也容易出现内存泄漏、空指针引用等问题。因此,在使用裸指针时需要特别小心,避免出现潜在的安全问题;推荐使用智能指针来管理内存。 540- use after free问题 541 返回临时变量、野指针:比如返回栈变量的引用,释放后未置空继续访问。 542 543 ``` 544 # include <iostream> 545 546 int& getStackReference() { 547 int x = 5; 548 return x; // 返回 x 的引用 549 } 550 551 int main() { 552 int& ref = getStackReference(); // 获取 x 的引用 553 // x 在 getStackReference 函数返回后被释放 554 // ref 现在是悬空引用,继续访问会导致未定义行为 555 std::cout << ref << std::endl; // 试图输出 x 的值,这是未定义行为 556 return 0; 557 } 558 ``` 559 560- 栈溢出:如递归调用,析构函数相互调用,特殊的栈(信号栈)中使用大块栈内存。 561 ``` 562 # include <iostream> 563 564 class RecursiveClass { 565 public: 566 RecursiveClass() { 567 std::cout << "Constructing RecursiveClass" << std::endl; 568 } 569 570 ~RecursiveClass() { 571 std::cout << "Destructing RecursiveClass" << std::endl; 572 // 在析构函数中递归调用 573 RecursiveClass obj; 574 } 575 }; 576 577 int main() { 578 RecursiveClass obj; 579 return 0; 580 } 581 ``` 582 创建一个 RecursiveClass 对象时,它的构造函数被调用。销毁这个对象时,它的析构函数被调用。在析构函数中,创建了一个新的RecursiveClass对象,这会导致递归调用,直到栈溢出。递归调用导致了无限的函数调用,最终导致栈空间耗尽,程序崩溃。 583- 二进制不匹配:通常由ABI(应用程序二进制接口)不匹配引起,如自己编译二进制与实际运行的二进制接口存在差异,数据结构定义存在差异,这种一般会产生随机的崩溃栈。 584- 踩内存:使用有效的野指针,并修改了其中的内存为非法值,访问越界,覆盖了正常的数据这种一般会产生随机的崩溃栈。 585- SIGBUS (Aligment)考虑对指针进行强转之后地址是否已经处于非对齐状态。 586 587## 分析案例 588 589本章节从信号分类、问题场景分类和维测工具分类三个维度来对CppCrash典型问题进行分析和归纳。 590信号分类,侧重对常见崩溃信号覆盖介绍,各类信号提供一个典型案例。 591问题场景分类,侧重归纳目前高频问题背后的通用场景,各类场景提供一个典型案例。 592维测工具分类,侧重总结各类维测工具如果使用类分析相应的问题,各类工具提供一个典型案例。 593 594### 从信号维度分析问题 595 596#### 类型一:SIGSEGV类崩溃问题 597 598SIGSEGV信号伴随着程序发生段错误(Segmentation Fault)故障,其故障场景为`当程序试图访问不被允许访问的内存区域(比如,尝试写一块属于操作系统的内存),或以错误的类型访问内存区域(比如,尝试写一块只读内存)`。概括有如下几点: 599 600- SIGSEGV是在访问内存时发生的错误,它属于内存管理的范畴。 601- SIGSEGV是一个用户态的概念,是操作系统在用户态程序错误访问内存时所做出的处理。 602- 当用户态程序访问(访问表示读、写或执行)不允许访问的内存时,产生SIGSEGV。 603- 当用户态程序以错误的方式访问允许访问的内存时,产生SIGSEGV。 604 605SIGSEGV在很多时候是由于指针越界引起的,但并不是所有的指针越界都会引发SIGSEGV。如果不解引用越界指针,是不会引起SIGSEGV崩溃的。而且即使解引用了一个越界的指针,也不一定会引起SIGSEGV。SIGSEGV涉及到操作系统、C库、编译器、链接器各方面的内容,以如下具体的例子来说明。 606 6071. 错误的访问类型 608 样例代码如下: 609 610 ``` 611 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 612 { 613 char *s = "hello world"; 614 s[1] = 'H'; 615 return 0; 616 } 617 ``` 618 619 这是最常见的一个例子。此例中,"hello world" 作为一个常量字符串,在编译后会被放在 .rodata 节(GCC),最后链接生成目标程序时 .rodata 节会被合并到 text segment 与代码段放在一起,故其所处内存区域是只读的。这就是错误的访问类型引起的 SIGSEGV(SEGV_ACCERR) 崩溃。 620 621  622 6232. 访问不属于进程地址空间的内存 624 625 样例代码如下: 626 627 ``` 628 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 629 { 630 uint64_t* p = (uint64_t*)0xffffffcfc42ae6f4; 631 *p = 10; 632 return 0; 633 } 634 ``` 635 636 在这个例子中,我们访问了一个属于内核的地址。当然很少会有人这样写程序,但程序可能在不经意的情况下做出这样的行为,产生SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4的崩溃。本例中的CppCrash故障日志(仅展示核心日志内容)如下: 637 638 ``` 639 Device info:xxxxxx xxxx xx xxx 640 Build info:xxxxxxx 641 Fingerprint:73a5dcdf3e509605563aa11ac8cb4f3d7f99b9946dc142212246b53b741c4129 642 Module name:com.samples.recovery 643 Version:1.0.0 644 VersionCode:1000000 645 PreInstalled:No 646 Foreground:Yes 647 Timestamp:2024-04-29 14:07:12.082 648 Pid:21374 649 Uid:20020144 650 Process name:com.samples.recovery 651 Process life time:8s 652 Reason:Signal:SIGSEGV(SEGV_MAPERR)@0xffffffcfc42ae6f4 <-崩溃地址,即进程在 653 Fault thread info: 654 Tid:21374, Name:amples.recovery 655 # 00 pc 0000000000001ccc /data/storage/el1/bundle/libs/arm64/libentry.so(TriggerCrash(napi_env__*, napi_callback_info__*)+36)(4dd115fa8b8c1b3f37bdb5b7b67fc70f31f0dbac) 656 # 01 pc 0000000000033678 /system/lib64/platformsdk/libace_napi.z.so(ArkNativeFunctionCallBack(panda::JsiRuntimeCallInfo*)+372)(7d6f229764fdd4b72926465066bc475e) 657 # 02 pc 00000000001d7f38 /system/lib64/module/arkcompiler/stub.an(RTStub_PushCallArgsAndDispatchNative+40) 658 # 03 at doTriggerException (entry/src/main/ets/pages/FaultTriggerPage.ets:72:7) 659 # 04 at triggerNativeException (entry/src/main/ets/pages/FaultTriggerPage.ets:79:5) 660 # 05 at anonymous (entry/src/main/ets/pages/FaultTriggerPage.ets:353:19) 661 # 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) 662 ... 663 # 38 pc 00000000000324b0 /system/bin/appspawn(AppSpawnRun+172)(c992404f8d1cf03c84c067fbf3e1dff9) 664 # 39 pc 00000000000213a8 /system/bin/appspawn(main+956)(c992404f8d1cf03c84c067fbf3e1dff9) 665 # 40 pc 00000000000a4b98 /system/lib/ld-musl-aarch64.so.1(libc_start_main_stage2+64)(ff4c94d996663814715bedb2032b2bbc) 666 ``` 667 6683. 访问不存在的内存 669 样例代码如下: 670 671 ``` 672 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 673 { 674 int *a = NULL; 675 *a = 1; 676 return 0; 677 } 678 ``` 679 680 在实际情况中,此例中的空指针可能指向用户态地址空间,但其所指向的页面实际不存在,便是最常见的空指针解引用的场景,这类场景CppCrash日志会识别出来,并在Reason字段打印推断信息 `Reason:Signal:SIGSEGV(SEGV_MAPERR)@000000000000000000 probably caused by NULL pointer dereference`,如下图所示: 681 682  683 6844. 重复free 685 样例代码如下: 686 687 ``` 688 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 689 { 690 void *pc = malloc(1024); 691 free(pc); 692 free(pc); // 重复free 693 printf("free ok!\n"); 694 return 0; 695 } 696 ``` 697 698 重复释放内存的场景,系统会抛出 SIGSEGV(SI_TKILL) 类故障提示为非法的内存操作,如下图所示: 699 700  701 702 以上是 SIGSEGV 类崩溃比较常见的原因,除此之外还有栈溢出内存访问、堆溢出内存访问、访问全局区野指针、函数跳转到一个非法的地址上执行,以及非法的系统调用参数等一些场景都有可能触发 SIGSEGV 。SIGSEGV和操作系统栈分配回收、编译器有着密切的联系。 703 704#### 类型二:SIGABRT类崩溃问题 705 706SIGABRT信号被发送到进程,告诉进程中止。既可以进程自己调用C标准库的abort()函数,信号通常由进程本身发起,也可以跟其他信号一样从外部发送给进程。 707 7081. 执行abort函数 709 样例代码如下: 710 711 ``` 712 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 713 { 714 OH_LOG_FATAL(LOG_APP, "test fatal log."); 715 abort(); 716 return 0; 717 } 718 ``` 719 720 该场景是主动调用 abort() 函数构造,对应的场景是各基础库可能会存在一些安全校验,对于识别为会导致进程无法安全运行性的场景,会主动 abort。对应如下场景如下图所示,会将进程退出前的最后一条fatal级别日志打印到崩溃日志中。 721 722  723 7242. 执行assert函数 725 样例代码如下: 726 727 ``` 728 static napi_value TriggerCrash(napi_env env, napi_callback_info info) 729 { 730 # if 0 //该值为0,则报错;为1,则正常 731 void *pc = malloc(1024); 732 # else 733 void *pc = nullptr; 734 # endif 735 assert(pc != nullptr); 736 return 0; 737 } 738 ``` 739 740 除了调用 abort() 函数外,C++中的另一个异常处理机制还包括 assert() 函数,其他的还有 exit() 函数,异常捕获机制(try-catch)、exception类等。assert用于校验当前函数执行流程中的一些数据,校验失败进程会主动 abort。对应的故障场景如下图所示: 741 742  743 744### 从场景维度分析问题 745 746#### 类型一:内存访问类崩溃问题 747 748**问题背景** 749每次崩溃地址0x7f82764b70都在libace_napi_ark.z.so的可读可执行段上。崩溃原因是需要对地址进行写操作,而对应的maps段只有可读、可执行权限没有写权限,当进程试图访问不被允许访问的内存区域时,进程发生内存访问类崩溃。 750 751``` 7527f82740000-7f8275c000 r--p 00000000 /system/lib64/libace_napi_ark.z.so 7537f8275c000-7f8276e000 r-xp 0001b000 /system/lib64/libace_napi_ark.z.so <-崩溃地址落在该地址区间 7547f8276e000-7f82773000 r--p 0002c000 /system/lib64/libace_napi_ark.z.so 7557f82773000-7f82774000 rw-p 00030000 /system/lib64/libace_napi_ark.z.so 756``` 757 758崩溃调用栈如下图: 759 760 761 762**定位思路** 763每次地址出错都很有规律,但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问题。 764ASAN核心日志如下: 765 766``` 767================================================================= 768==appspawn==2029==ERROR: AddressSanitizer: heap-use-after-free on address 0x003a375eb724 at pc 0x002029ba8514 bp 0x007fd8175710 sp 0x007fd8175708 769READ of size 1 at 0x003a375eb724 thread T0 (thread name) 770 # 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 771(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 772 # 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 773(inlined by) panda::CopyableGlobal<panda::ObjectRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9 774(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 775(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 776(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 777 # 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 778(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 779 # 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 780(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 781 # 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 782(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 783 # 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 784(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 785... 786freed by thread T0 (thread name) here: 787 # 0 0x2024ed3abc (/system/asan/lib64/libclang_rt.asan.so+0xd3abc) 788 # 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 789(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 790(inlined by) panda::ecmascript::JSThread::DisposeGlobalHandle(unsigned long) at arkcompiler/ets_runtime/ecmascript/js_thread.h:604:9 791(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 792 # 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 793(inlined by) panda::CopyableGlobal<panda::FunctionRef>::Reset() at arkcompiler/ets_runtime/ecmascript/napi/include/jsnapi.h:189:9 794(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 795(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 796(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 797 # 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 798(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 799 # 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 800(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 801... 802previously allocated by thread T0 (thread name) here: 803 # 0 0x2024ed3be4 (/system/asan/lib64/libclang_rt.asan.so+0xd3be4) 804 # 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 805 # 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 806(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 807(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 808 # 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 809(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 810(inlined by) panda::ecmascript::JSThread::SetWeak(unsigned long, void*, void (*)(void*), void (*)(void*)) at arkcompiler/ets_runtime/ecmascript/js_thread.h:610:16 811(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 812... 813``` 814 815根据堆栈继续分析, 816JsiWeak析构或重置的时候会触发其成员(类型为JsiObject/JsiValue/JsiFunction)父类JsiType中CopyableGlobal被释放,如下图: 817 818 819 820运行时在GC过程中IterateWeakEcmaGlobalStorage,会对无callback的WeakNode调用DisposeGlobalHandle操作,也对其进行释放,如下图: 821 822 823 824因此,对于同一个WeakNode,可能会存在两个入口释放。如果是GC过程中IterateWeakEcmaGlobalStorage先释放,因为无callback回调通知到JsiWeak进行清理,JsiWeak那边仍保存一个对已释放的WeakNode引用,即CopyableGlobal;当前面讲的WeakNode所在的NodeList被整体释放,归还给操作系统后,JsiWeak处保留的CopyableGlobal再释放,就会存在double-free问题。 825 826 827 828**修改方法** 829JsiWeak调用SetWeakCallback,传入callback,在GC过程中IterateWeakEcmaGlobalStorage释放WeakNode时,通知JsiWeak对其保存的CopyableGlobal进行重置,确保同一个地址不被double-free。 830 831**建议与总结** 832使用内存时应考虑是否存在重复释放或者未释放的可能,另外定位内存访问类崩溃问题(一般是SIGSEGV类型问题)时,如果根据崩溃栈分析问题无头绪时,应优先考虑跑ASAN版本复现问题。 833 834#### 类型二:多线程类问题 835 836**问题背景** 837napi_env释放后仍被使用。 838 839**问题场景** 840napi接口的env传入非法,崩溃栈直接挂在NativeEngineInterface::ClearLastError()中,根据日志打印env地址定位,发现是env被释放后仍然被使用。 841 842 843 844核心崩溃栈如下: 845 846 847 848**修改方法** 849一个线程的创建的env,不要传给另一个线程使用。 850 851**建议与总结** 852对于多线程类问题可以打开方舟多线程检测功能,能够更加方便定位问题,见工具类方舟多线程检测章节。 853 854注:napi接口中的env,是引擎创建时候的arkNativeEngine。 855 856#### 类型三:生命周期类问题 857 858**问题背景** 859开发者在写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就会访问已释放的内存,产生问题。 860 861**问题场景** 862napi_value其实是个裸指针(结构体指针),其作用是持有js对象,用于保持js对象的生命周期,保证js对象不被GC当成垃圾对象回收。napi_handle_scope用来管理napi_value,离开napi_handle_scope作用域之后,napi_value由GC回收,napi_value不再持有js对象(不再保护js对象生命周期)。 863 864**定位思路** 865根据崩溃栈反编译找到出现问题的napi接口的上层接口,在上层接口内找到出问题的napi_value,检查napi_value的使用范围是否超出了napi_handle_scope的作用域范围。 866 867**案例** 868napi_value超出NAPI框架的scope,如下: 869 870 871 872js侧通过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失效。 873注:NAPI框架的scope即napi_handle_scope,napi开发者可以通过napi_handle_scope来管理napi_value的生命周期。框架层的scope嵌入在js call native的端到端流程中,即进入开发者自己写的native方法前open scope,native方法结束后close scope。 874 875#### 类型四:指针类问题 876 877**问题背景** 878智能指针使用之前未判空,造成进程运行时发生空指针解引用崩溃问题。 879 880**问题影响** 881进程发生崩溃,影响进程的稳定运行,非预期退出。 882 883**定位思路** 884 885 886空指针类型崩溃可以从故障原因得到提示信息。通过llvm-addr2line解行号发现业务代码中在使用智能指针之前未对智能指针判空,对空地址进行访问导致崩溃产生。 887 888**修复方法** 889对所有使用该指针的地方进行保护性判空。 890 891**建议与总结** 892指针在使用之前应该要进行判空处理,防止访问空指针造成进程崩溃退出。 893 894### 配合工具分析问题 895 896#### 工具一:ASAN 897 898[ASAN使用指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-asan-V5) 899 900#### 工具二:方舟多线程检测 901 902**基本原理** 903js是单线程的,操作js对象只允许发生在创建该js线程上,否则将会有多线程安全问题(主线程创建的js对象只能在主线程上操作,worker创建的js对象只能在worker线程上操作)。napi接口会直接涉及到对象的操作,因此绝大部分(95%)的napi接口只允许在js线程上使用。多线程检测机制检测的是:当前线程和使用的vm/env中的js thread id是否一致,若不一致,则表明vm/env被跨线程使用,存在多线程安全问题。常见问题有:1. 非js线程使用napi接口,2. napi接口使用其他线程的env。 904 905**使用方法** 906 907 908DevEco勾选Multi Thread Check选项即可开启方舟多线程检测功能。 909 910**使用场景** 911如果crash日志的堆栈难以分析,出现概率也相对比较高,对于此类问题,应该考虑开启多线程检测。 开启多线程检测之后,如果cpp_crash日志中fatal信息为Fatal: ecma_vm cannot run in multi-thread! thread:3096 currentThread:3550,则发生了多线程安全问题,意思是当前线程号为3550,而使用的js thread却是3096线程创建出来的,跨线程使用vm。 912 913**案例** 914打开后重新触发崩溃,如果是多线程问题,会显示fatal 信息,参考如下: 915 916``` 917Fatal: ecma_vm cannot run in multi-thread! thread:xxx currentThread:yyy 918``` 919 920该信息意思是当前线程号为17585,而使用的 js thread 却是17688 线程创建出来的,跨线程使用 vm。vm 就是 js thread 的 napi_env__* ,运行线程代码的环境,一个线程使用一个 vm。 921崩溃日志核心部分如下所示: 922 923``` 924 925Reason:Signal:SIGABRT(SI_TKILL)@0x01317b9f000044b1 from:17585: 20020127 926LastFatalMessage: [default] CheckThread:177 Fatal: ecma_vm cannot run in multi-thread! thread:17688 currentThread:17585 927Fault thread Info: 928Tid:17585, Name:xxxxx 929# 00 pc 00000000000f157c /system/lib/ld-musl-aarch64-asan.so.1(__restore_sigs+52)(38eb4ca904ae601d4b4dca502e948960) 930# 01 pc 00000000000f1800 /system/lib/ld-musl-aarch64-asan.so.1(raise+112) (38eb4ca904aeó01d4b4dca502e948960) 931# 02 pc 00000000000adc74 /system/lib/ld-musl-aarch64-asan.so.1(abort.+20) (38eb4ca904ae601d4b4dca502e948960) 932# 03 pc 0000000000844fdc /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::ecmascript::EcmaVM::CheckThread() const+2712)(1df055932338c14060b864435aec88ab) 933# 04 pc 0000000000f3d930 /system/asan/libó4/platformsdk/libark_jsruntime.so(panda::0bjectRef:: New(panda::ecmascript::EcmaVM const*)+908)(1df055932338c14060b864435aec88 934# 05 pC 0000000000095048 /sYstem/asan/lib64/platformsdk/libace_napi.z.so(napi_create_object+80)(efc1b3d1378f56b4b800489fb30dcded) 935# 06 pc 00000000005d9770 /data/ storage/el1/bundle/libs/arm64/xxxxx.so (c0f1735eada49fadc5197745f5afOc0a52246270) 936``` 937 938多线程问题分析步骤: 939i. 检查 libace_napi.z.so 下面的第一个栈帧,上图为`xxxxx.so`,判断是否把 17688 线程的 napi_env 传给了 17585 线程; 940ii. 如果 libace_napi.z.so 下面的栈帧没有明显的 napi_env 参数传递,需要检查是否以结构体成员变量的方式传递; 941 942#### 工具三:objdump 943 944**使用方法** 945objdump二进制是系统侧工具,开发者需要具备OpenHarmony编译环境,项目代码在gitee上可获取,命令如下: 946 947``` 948repo init -u git@gitee.com:openharmony/manifest.git -b master --no-repo-verify --no-clone-bundle --depth=1 949repo sync -c 950./build/prebuilts_download.sh 951``` 952 953工具在工程目录下`prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump`,命令如下: 954 955``` 956prebuilts/clang/ohos/linux-x86_64/llvm/bin/llvm-objdump -d libark_jsruntime.so > dump.txt 957``` 958 959**使用场景** 960有些情况下,通过addr2line只能看出代码某一行有问题,无法确认具体是哪个变量异常,此时可以通过objdump反汇编并结合cppcrash寄存器内容,进一步确认具体崩溃原因。 961 962**案例** 963日志内容如下: 964 965``` 966Tid:6655, Name:GC_WorkerThread 967# 00 pc 00000000004492d4 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkObject(unsigned int, panda::ecmascript::TaggedObject*)+124)(21cf5411626d5986a4ba6383e959b3cc) 968# 01 pc 000000000044b580 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::MarkValue(unsigned int, panda::ecmascript::ObjectSlot&, panda::ecmascript::Region*, bool)+72)(21cf5411626d5986a4ba6383e959b3cc) 969# 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) 970# 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) 971# 04 pc 0000000000447ccc /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::NonMovableMarker::ProcessMarkStack(unsigned int)+248)(21cf5411626d5986a4ba6383e959b3cc) 972# 05 pc 0000000000438588 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Heap::ParallelGCTask::Run(unsigned int)+148)(21cf5411626d5986a4ba6383e959b3cc) 973# 06 pc 00000000004e31c8 /system/lib64/platformsdk/libark_jsruntime.so(panda::ecmascript::Runner::Run(unsigned int)+144)(21cf5411626d5986a4ba6383e959b3cc) 974# 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) 975# 08 pc 000000000014d894 /system/lib/ld-musl-aarch64.so.1 976# 09 pc 0000000000085d04 /system/lib/ld-musl-aarch64.so.1 977``` 978 979首先先用addr2line查看出错的行,如下: 980 981 982 983能看出的信息是判断IsYongSpace的时候访问到了空指针挂了,能够大概猜测出来是Region是空指针。 984继续使用objdump反汇编,搜索出错地址4492d4 , 对应的汇编指令如下。 985 986 987 988查看x20寄存器,发现为0x000000000000000,x20从上面可以看出是基于x2做位运算(清除掉后18位,典型的Region::ObjectAddressToRange操作)。这样分析之后,就清楚了,x2为MarkObject函数的第二个参数object,x20为变量objectRegion,如下: 989 990``` 991Registers: x0:0000007f0fe31560 x1:0000000000000003 x2:0000000000000000 x3:0000005593100000 992 x4:0000000000000000 x5:0000000000000000 x6:0000000000000000 x7:0000005596374fa0 993 x8:0000000000000000 x9:0000000000000000 x10:0000000000000000 x11:0000007f9cb42bb8 994 x12:000000000000005e x13:000000000061f59e x14:00000005d73d60fb x15:0000000000000000 995 x16:0000007f9cc5f200 x17:0000007f9f201f68 x18:0000000000000000 x19:0000000000000000 996 x20:0000000000000000 x21:0000000000000000 x22:0000000000000000 x23:000000559313f860 997 x24:000000559313f868 x25:0000000000000003 x26:00000055a0e19960 x27:0000007f9cc57b38 998 x28:0000007f9f21a1c0 x29:00000055a0e19700 lr:0000007f9cb4b584 sp:00000055a0e19700 pc:0000007f9cb492d4 999``` 1000 1001上面ldrb w8, [x20]对应 packedData_.flags_.spaceFlag_ 是因为,packedData_是region的第一个域,flags_是packedData_的第一个域,spaceFlag_是flags_的第一个域,所以直接取objectRegion地址对应的第一个字节。 1002查看汇编代码需要熟悉常见的汇编指令,以及传参规则,例如对于c++非inline的成员函数r0一般保存的是this指针。另外,由于编译器优化,源码和汇编代码对应关系可能不是很直观,我们可以根据代码中的一些特征值(常量),较快地找到对应关系。