鸿蒙 faultlog 崩溃日志分析实战:cppcrash / jscrash / appfreeze 三类故障怎么读

本文为「鸿蒙系统测试新人笔记」系列第 5 弹。适用对象:需要分析鸿蒙应用/系统崩溃、卡死根因的新人。
文档定位:崩溃日志在哪 + 三类故障怎么看 + 真实日志长啥样 + 直接抄的获取/分析命令。读完能独立完成一次崩溃/冻屏根因定位。

系列导航:① hdc 环境配置 → ② wukong 压测 → ③ hilog 日志 → ④ keycodeType 控键 → ⑤ faultlog 崩溃分析(本文) → ⑥ uitest 自动化 → ⑦ DevEco 图形化 → ⑧ 性能功耗 → ⑨ 专项平台 → ⑩ DFX 全家桶 → ⑪ 分布式协同 → ⑫ 合规红线 → ⑬ 方法论

本文关联篇hdc 配置 · wukong 测试 · hilog 指南 · keycodeType · DevEco 图形化 · DFX 全家桶。系列文章均已在个人主页发布,可在主页目录查看。

摘要:faultlog 是鸿蒙系统自动生成的崩溃/冻屏现场记录,相当于 Android 的 tombstone/ANR。本文系统讲解 faultlog 落盘路径、获取方式,并以真实日志样例逐段拆解 cppcrash(Native 崩溃)、jscrash(ArkTS 崩溃)、appfreeze(应用冻屏)三类故障的分析方法,附首次分析决策树、常见崩溃模式速查表、符号化步骤、术语表与 FAQ,新人照着做即可独立完成根因定位。


一、faultlog 是什么(30 秒理解)

faultlog(故障日志)是鸿蒙 DFX 子系统自动生成的崩溃/卡死现场记录。当应用或进程发生异常(崩溃、冻屏)时,系统会把"案发现场"存成文件,开发者拿来分析根因。

一句话记忆法:faultlog = 鸿蒙版 tombstone / ANR 日志。wukong 压测发现的崩溃,最终证据都落在这里。

1.1 日志是怎么生成的(知其所以然)

进程崩溃/卡死
   │
   ├─ Native 崩溃(信号触发) ──→ 内核捕获信号 ──→ ProcessDump 守护进程
   │                                              ├─ dump 寄存器/栈/内存
   │                                              ├─ 先写 /data/log/faultlog/temp(临时)
   │                                              └─ 转存 /data/log/faultlog/faultlogger(正式)
   │
   ├─ JS 崩溃(未捕获异常) ──→ ArkTS 运行时 ──→ 同上落盘
   │
   └─ 应用冻屏(watchdog 检测) ──→ AppFreeze 模块 ──→ 落盘

关键点:崩溃发生 → 进程已被系统杀死,日志是"死后现场"。所以分析时看到的是"尸体报告",不是"病程记录"。要看病程得配 hilog(见第3弹)。

1.2 三类核心故障(对应 FaultType 枚举,来自 @ohos.faultLogger

FaultType名称含义日常叫法
NO_SPECIFIC0未区分通用故障
CPP_CRASH2C++ 进程崩溃Native 层崩溃(信号崩)cppcrash
JS_CRASH3JS 进程崩溃ArkTS/JS 未捕获异常jscrash
APP_FREEZE4应用冻屏主线程卡死 / 输入无响应appfreeze

注意:值没有 1,1 是预留位,代码里别写错。
还有 SYSTEM_FREEZE(系统冻屏)、ASAN(地址越界检测)等,在 DevEco Studio 的 FaultLog 面板可见(见第7弹)。


二、日志落在哪(必记路径)

目录内容何时看
/data/log/faultlog/faultlogger正式故障日志(cppcrash / jscrash / appfreeze 都在这)分析时看这
/data/log/faultlog/temp崩溃原始临时文件(ProcessDump 先写这,再转存正式目录)极少直接看

文件命名规则:

<故障类型>-<进程名>-<进程UID>-<毫秒级时间戳>.log

示例:

cppcrash-com.example.myapplication-20020205-1705812215000.log
jscrash-com.example.myapplication-20020205-1705812215000.log
appfreeze-com.example.myapplication-20020205-1705812215000.log

注意:同一进程最多保留 10 份 cppcrash,超出从最早的一批开始删。抓日志要趁早,压测完立刻导出,别等第二天。

2.1 一份 cppcrash 日志的内部结构(打开后从头到尾长这样)

┌─ Build info          ← 系统版本
├─ Module name         ← 崩溃应用的包名
├─ Version / VersionCode
├─ Pid / Uid           ← 进程号 / 用户号(UID 20020205 = 三方应用)
├─ ProcessName
├─ Reason              ← 【重点1】信号类型 + 可能原因提示
├─ Fault thread Info   ← 【重点2】崩溃线程的调用栈(#00 是崩溃点)
│     Tid / Name
│     #00 pc ... so(func+offset)
│     #01 pc ... so(func+offset)
│     ...
├─ Registers           ← 【重点3】崩溃瞬间 CPU 寄存器值(查空指针)
├─ Memory near registers ← 寄存器附近的内存(看是否乱码/全0)
├─ Stack               ← 栈内存 dump
├─ Other thread info   ← 其他线程堆栈(看锁竞争/死锁时有用)
└─ Fingerprint         ← 【重点4】标准化栈帧指纹(去重/聚类用)

新手分析顺序:Reason → #00 帧 → Registers → Fingerprint(详见第四章)。


三、获取方式(三种任选)

3.0 环境准备清单(崩溃发生前就该备好)

准备项说明没备会怎样
hdc 已配通见第 1 弹《hdc 环境配置》导不出日志
设备开 USB 调试开发者选项里hdc 连不上
带符号的 so / 符号表debug 包自带;release 包需单独保留构建产物cppcrash 只有偏移,看不到行号
debug 包(查 jscrash 行号时)含 sourcemapjscrash 只能 dump raw stack
release 包(查 appfreeze 时)AppFreeze 仅 release 生效appfreeze 抓不到
DevEco Studio(可选)5.1.0+ 支持自动符号化只能手工 addr2line

经验:正式测试前,把带符号的构建产物(so/sourcemap)存档。等崩溃了再去翻构建产物,常常已被 CI 清掉。

3.1 方式 1:DevEco Studio FaultLog 面板(最省事,推荐)

开发态下,Studio 自动收集 CppCrash / App Freeze / JS Crash / System Freeze / ASANFaultLog 面板,按进程和故障类型分好类,还能自动符号化。新手首选。

入口:View → Tool Windows → FaultLog。版本门槛:符号化需 5.1.0+,AppFreeze 结构化需 6.0.0 Beta2+。详见第 7 弹《DevEco Studio 图形化测试》。

3.2 方式 2:hdc 导出(命令行/无 Studio 时)

# 导出整个故障日志目录到本地
hdc file recv /data/log/faultlog/faultlogger ./faultlogs

# 只看某一类
ls ./faultlogs | grep cppcrash
ls ./faultlogs | grep jscrash
ls ./faultlogs | grep appfreeze

# 先确认目录在不在(找不到时的第一动作)
hdc shell ls /data/log/faultlog/

3.3 方式 3:HiAppEvent 订阅(代码内实时捕获)

应用里订阅崩溃事件,从 external_log 字段读取日志文件路径,再读内容:

import { hiAppEvent } from '@kit.PerformanceAnalysisKit';

hiAppEvent.addWatcher({
  name: 'crashWatcher',
  appEventFilters: [{ eventType: hiAppEvent.EventType.FAULT }],
  onReceive: (domain: string, stage: number, eventInfos: hiAppEvent.AppEventInfo[]) => {
    for (const info of eventInfos) {
      // params.external_log 是数组,元素是崩溃日志文件的绝对路径
      const logs: string[] = info.params.external_log ?? [];
      console.info(`捕获到故障: ${info.params.event_name}, 日志路径: ${logs}`);
      // 可按需读取文件内容上报到自建后台
    }
  }
});

用途:线上应用的崩溃监控、自建埋点上报。本地测试一般用方式 1/2 即可。详见第 10 弹《DFX 全家桶》。


四、首次分析决策树(拿到日志先看什么)

新手最怕的是打开一个几百行的日志不知道从哪下眼。按下面顺序走,3 分钟锁定方向。

打开 .log 文件
   │
   ├─ 第 1 步:看文件名前缀 → 确定故障类型
   │     cppcrash-* → Native 崩溃      → 走【五、CPP_CRASH】
   │     jscrash-*  → ArkTS 崩溃       → 走【六、JS_CRASH】
   │     appfreeze-*→ 应用冻屏         → 走【七、APP_FREEZE】
   │
   ├─ 第 2 步:找 "Reason:" 行 → 一句话定性
   │     Signal:SIGSEGV(SEGV_MAPERR)@0x0  → 空指针解引用
   │     Signal:SIGABRT                     → 主动 abort()/断言失败
   │     TypeError / RangeError            → JS 异常类型
   │     THREAD_BLOCK_6S                    → 主线程卡死 6 秒
   │
   ├─ 第 3 步:找崩溃点
   │     cppcrash → "#00 pc ..."        → 崩溃所在 so + 函数 + 偏移
   │     jscrash  → "Stacktrace:" 块    → 文件:行:列 直接定位
   │     appfreeze→ "main thread stack"→ 主线程卡在哪一行
   │
   ├─ 第 4 步:补全行号(cppcrash 才需要)
   │     有符号 so + llvm-addr2line  →  还原源码行号
   │     或丢进 DevEco FaultLog 面板自动符号化
   │
   └─ 第 5 步:复测验证
         修完后用相同 wukong 种子(见第2弹)复测,确认不再复现

90% 的崩溃靠前 3 步就能定位到方向。第 4 步是 Native 崩溃的"临门一脚"。


五、CPP_CRASH 详解(Native 崩溃)

5.1 完整日志样例(先看真实长啥样)

Build info: OpenHarmony 5.0.0
Module name: com.example.myapplication
Version: 1.0.0
VersionCode: 1000000
PreInstall: No
RestartTimes: 0
Pid: 579
Uid: 20020205
ProcessName: com.example.myapplication
Reason: Signal:SIGSEGV(SEGV_MAPERR)@0x0000000000000000 probably caused by NULL pointer dereference
Fault thread Info:
Tid: 579, Name: com.example.myapp
#00 pc 0x0000000000008a4c /data/app/el1/bundle/public/com.example.myapplication/.../libs/arm64/libentry.so(OHOS_NativeCrash+84)(BuildId: a40044d0acb68107cfc4adb5049c0725)
#01 pc 0x0000000000008b0c /data/app/el1/bundle/public/com.example.myapplication/.../libs/arm64/libentry.so(NativeCrashCall+32)(BuildId: a40044d0acb68107cfc4adb5049c0725)
#02 pc 0x0000000000008bc8 /data/app/el1/bundle/public/com.example.myapplication/.../libs/arm64/libentry.so(main+120)(BuildId: a40044d0acb68107cfc4adb5049c0725)
#03 pc 0x0000000000004a8c /system/lib/ld-musl-aarch64.so.1(__libc_start_main+88)(BuildId: ...)
#04 pc 0x00000000000049fc /system/lib/ld-musl-aarch64.so.1(__libc_start_init+80)(BuildId: ...)
Registers:
x0: 0x0000000000000000  x1: 0x0000000000000001  x2: 0x0000007ffefff4e0  x3: 0x0000007ffefff528
x4: 0x0000000000000000  x5: 0x0000000000000000  x6: 0x0000000000000000  x7: 0x000000000000006f
x8: 0x0000000000000000  x9: 0x0000000000000000  x10: 0x0000000000000001  x11: 0x0000000000000000
...
pc: 0x0000007fa6b08a4c  sp: 0x0000007ffefff4d0  pstate: 0x60000000
Memory near registers:
x0:[+0]: 0x0 0x0 0x0 0x0
        [+8]: 0x0 0x0 0x0 0x0
        [+16]: 0x0 0x0 0x0 0x0
Stack:
0x0000007ffefff4d0: 0x0000007fa6b08b0c 0x0000000000000001 ...
...
Other thread info:
Tid: 581, Name: os.async.hiapp
#00 pc 0x... /system/lib/...
Fingerprint: @0x0000000000008a4c 0x0000000000008b0c 0x0000000000008bc8...

真实日志可能比这长 2-3 倍(栈深、其他线程多),但骨架就是这些。

5.2 系统支持的崩溃信号表(看 Reason 时对着查)

signo信号含义常见触发原因
4SIGILL非法指令执行未知/特权指令、代码损坏
5SIGTRAP断点/陷阱调试断点、单步
6SIGABRT进程终止调用 abort()、断言失败
7SIGBUS非法内存访问地址未对齐、访问不存在物理地址
8SIGFPE浮点异常除 0、算术溢出
11SIGSEGV无效内存访问空指针解引用、越界、UAF(最常见)
16SIGSTKFLT栈错误栈操作异常(不支持 minidump)
31SIGSYS错误系统调用非法/错误参数的 syscall

常见二级分类:

  • SIGSEGVSEGV_MAPERR(访问不存在地址,多半空指针)、SEGV_ACCERR(无权限地址)
  • SIGABRT:通常由 abort() 引发,常见于 C++ 异常未 catch、断言失败
  • SIGFPEFPE_INTDIV(整数除 0)

经验:90% 的 cppcrash 是 SIGSEGV。看到 @0x0000000000000000 基本就是空指针。

5.3 调用栈(Backtrace)帧格式

#00 pc 000e8400 /system/lib/ld-musl-arm.so.1(raise+176)(a40044d0acb68107cfc4adb5049c0725)
含义
#00栈帧序号,#00 是崩溃点,向上回溯调用链(#01 调用了 #00,以此类推)
pc 000e8400程序计数器在该文件内的偏移字节数
/system/lib/...soso 库名 / 二进制文件
(raise+176)函数名 + 函数内偏移(无符号表时只有偏移无函数名)
(a40044...)BuildID(二进制唯一标识,符号化时要匹配)

JS/Native 混合栈帧(64 位支持跨语言):

#05 at onPageShow (entry|har1|1.0.0|src/main/ets/pages/Index.ts:7:13)

→ 函数名 + 模块 + 源文件 行:列号

5.4 寄存器怎么读(判断空指针的利器)

Registers 段,重点盯崩溃指令用到的寄存器

现象大概率原因
某寄存器值 = 0x0 或极小(如 0x18空指针/野指针解引用
pc 指向的 so 不在进程加载范围代码被破坏/JIT 异常
寄存器值像合法地址但 Memory near 全是 0x0/乱码访问已释放内存(UAF)

配合 Memory near registers 段看:若该地址附近内存全 0 或不可读 → 进一步佐证空指针/野指针。

新手不用全看懂 30 个寄存器,只看有没有 0 就能定性 80% 的 SIGSEGV。

5.5 符号化详细步骤(偏移 → 源码行号)

cppcrash 默认只有偏移(pc 0x8a4c),看不到源码行。两种方式还原:

方式 A:DevEco Studio 自动符号化(推荐)

  1. 把带符号的构建产物(so)准备好(debug 包自带)
  2. FaultLog 面板 → 上传符号表 → 自动还原行号

方式 B:命令行 llvm-addr2line(无 Studio 时)

# 工具在 HarmonyOS SDK 的 llvm/bin 目录下,需配 PATH
# -e 指定带符号的 so,-f 显示函数名,-C 显示完整符号,最后是偏移地址(去掉 pc 前缀)

llvm-addr2line -e libentry.so -f -C 0x8a4c
# 输出示例:
# OHOS_NativeCrash
# /path/to/src/native_crash.cpp:42

注意:so 的 BuildID 必须和日志里的一致,否则符号化结果错位。日志里 (BuildId: a40044...) 要和本地 so 的 BuildID 对上。
验证 BuildID:llvm-readelf -n libentry.so | grep "Build ID"

5.6 定位步骤小结

  1. Reason:信号类型(如 SIGSEGV(SEGV_MAPERR)),提示里常带 probably caused by NULL pointer dereference
  2. #00 帧:崩溃所在函数与偏移。
  3. 符号化:用带符号的 so + llvm-addr2line 还原行号(或交给 DevEco Studio 自动做)。
  4. Registers / Memory near registers:确认是否空指针(寄存器值为 0 或极小值)。
  5. 相同 Fingerprint(标准化栈帧)表示同一故障,可用于聚类去重、统计崩溃率。

六、JS_CRASH 详解(ArkTS 崩溃)

6.1 完整日志样例

Build info: OpenHarmony 5.0.0
Module name: com.example.myapplication
Version: 1.0.0
VersionCode: 1000000
PreInstall: No
Pid: 579
Uid: 0
Reason: TypeError
Error message: Cannot read property c of undefined
Cannot get SourceMap info, dump raw stack:
SourceCode: var a = b.c;
                       ^
Stacktrace:
    at onPageShow (entry/src/main/ets/pages/Index.ets:7:13)
    at Index (entry/src/main/ets/pages/Index.ets:5:1)
    at ... (框架内部栈)
字段含义
Reason异常类型(TypeError / RangeError / ReferenceError / SyntaxError 等)
Error message具体错误信息(如"Cannot read property c of undefined")
SourceCode出错的源码行,箭头 ^ 指到具体出错列
Stacktrace调用栈,文件:行:列直接定位(最关键)

6.2 常见 JS 异常类型对照

Reason含义典型场景
TypeError类型错误undefined/null 上取属性、调非函数
RangeError范围错误数组越界、递归过深栈溢出
ReferenceError引用错误用了未定义的变量
SyntaxError语法错误eval() 执行非法代码
Error通用错误主动 throw new Error()

典型根因:undefined/null 上取属性、数组越界、异步回调里访问已释放对象。
注意:release 包不含 sourcemapError message 可能只能 dump raw stack(行列号对不上源码)。调试用 debug 包更易看行号,或用 DevEco FaultLog 面板解析。


七、APP_FREEZE 详解(应用冻屏)

7.1 完整日志样例

Build info: OpenHarmony 5.0.0
Module name: com.example.myapplication
Version: 1.0.0
Pid: 579
Uid: 20020205
Reason: THREAD_BLOCK_6S
EVENTNAME: THREAD_BLOCK_6S
eventHandler:
  runner thread id: 0
  queue size: 8
  pending task count: 5
  duration of longest event in queue: 6234ms
main thread stack:
  #00 pc 0x... /system/lib/...so(func+...)
  #01 pc 0x... /data/app/.../libentry.so(HeavyWork+...)
  ...
 binder call trace:
  ...

7.2 两类故障(看 EVENTNAME 字段)

EVENTNAME含义阈值
THREAD_BLOCK_6S主线程卡死超时watchdog 3 秒警告、6 秒触发
APP_INPUT_BLOCK用户输入响应超时输入事件长时间未处理

检测原理:watchdog 线程定期向主线程插入"判活检测任务",主线程若被阻塞则无法按时完成 → 触发故障。触发后系统会杀死应用以恢复

重要约束:AppFreeze 检测仅对 release 版本应用生效,debug 版本不生效。开发自测冻屏问题必须用 release 包。

7.3 日志字段解读

字段看什么
EVENTNAME故障子类型(THREAD_BLOCK_6S / APP_INPUT_BLOCK
eventHandler.queue size主线程任务队列堆积数(越大越堵)
pending task count待处理任务数
duration of longest event队列里最耗时的任务时长(定位元凶)
main thread stack主线程当前卡在哪一行(最关键)
binder call trace跨进程调用链(看是否卡在系统服务)

7.4 常见根因 & 解决方向

根因表现方向
主线程耗时操作磁盘 I/O、网络请求、大 JSON 解析、锁竞争、过度绘制挪到 TaskPool / Worker 异步执行
系统 API 未捕获异常调用系统接口挂起加 try/catch、超时处理
生命周期回调阻塞onCreate/onPageShow 干重活拆分、延迟、异步
死锁主线程等子线程锁、子线程等主线程锁避免主线程持锁、用无锁结构

定位重点:日志里的 eventHandler 任务队列 + 主线程堆栈 + binder 调用链,对比 3 秒与 6 秒的任务堆积。


八、常见崩溃模式速查表(典型签名 → 根因 → 方向)

把这几张表贴墙上,看到签名就能条件反射出方向。

8.1 CPP_CRASH 常见模式

日志特征根因修复方向
SIGSEGV(SEGV_MAPERR)@0x0 + 寄存器全 0空指针解引用加空判断;检查对象是否已释放
SIGSEGV(SEGV_ACCERR) + 内存乱码野指针/UAF检查生命周期;用智能指针
SIGABRT + abort()C++ 异常未 catch / 断言失败加 try-catch;查 ASSERT 触发点
SIGFPE FPE_INTDIV整数除 0除数加非 0 判断
SIGILL代码损坏/跳转到非代码区检查函数指针、回调签名
崩在 ld-musl/libc内存被踩坏用 ASAN 复测定位越界点

8.2 JS_CRASH 常见模式

Reason + Error message根因修复方向
TypeError: Cannot read property X of undefined链式取属性遇 undefined加可选链 ?. 或空判断
TypeError: X is not a function调了非函数/this 丢失箭头函数绑 this;检查导入
RangeError: Maximum call stack无限递归加终止条件
ReferenceError: X is not defined变量未声明/导入错检查 import/export
异步回调里崩访问已释放对象检查组件是否已销毁、加 flag

8.3 APP_FREEZE 常见模式

主线程栈特征根因修复方向
卡在文件读写(read/write同步 I/O挪到 TaskPool/Worker
卡在 JSON.parse 大字符串大数据解析分块、流式、子线程
卡在 binder 调用系统服务响应慢加超时、减少调用频率
卡在锁(pthread_mutex_lock死锁/锁竞争避免主线程持锁
队列堆积数百任务短时高频任务过载合并、节流、降频

九、实战模板(直接抄)

# 1. 跑 wukong 压测时,另开终端实时盯崩溃/冻屏
hdc shell hilog -L E | grep -iE "jscrash|cppcrash|appfreeze|fault"

# 2. 压测结束,导出全部故障日志
hdc file recv /data/log/faultlog/faultlogger ./faultlogs

# 3. 分类查看
ls ./faultlogs | grep -E "cppcrash|jscrash|appfreeze"

# 4. 看某个 cppcrash 的崩溃点与信号
grep -E "Reason|#00|#01" ./faultlogs/cppcrash-*.log

# 5. 看某个 jscrash 的异常信息与调用栈
grep -E "Reason|Error message|Stacktrace" ./faultlogs/jscrash-*.log

# 6. 看 appfreeze 的故障类型与卡死线程
grep -E "EVENTNAME|THREAD_BLOCK|APP_INPUT_BLOCK" ./faultlogs/appfreeze-*.log

# 7. 找不到目录时的确认动作
hdc shell ls /data/log/faultlog/

# 8. cppcrash 符号化(把偏移还原行号)
llvm-addr2line -e <带符号的.so> -f -C <偏移地址,如 0x8a4c>

十、与 wukong / hilog 闭环(完整排障流)

1. hdc 配通(见第1弹)
2. 跑 wukong 稳定性压测(见第2弹)
   hdc shell
   wukong exec -b com.example.myapp -a 0.3 -t 0.7 -T 30
3. 另开终端实时盯异常
   hdc shell hilog -L E | grep -iE "crash|freeze"
4. 发现崩溃 → 导出 faultlog
   hdc file recv /data/log/faultlog/faultlogger ./faultlogs
5. 按类型定位:
   - CPP_CRASH → 看 Reason 信号 + #00 帧 + 符号化
   - JS_CRASH  → 看 Error message + SourceCode + Stacktrace
   - APP_FREEZE→ 看 EVENTNAME + 主线程堆栈(release 包才生效)
6. 修复后用相同 wukong 种子复测(同种子=可复现,见第2弹)

十一、新手必踩的 8 个坑

  1. 找不到 faultlog 目录 → 默认路径是 /data/log/faultlog/faultlogger(不是 /data/faultlog,后者老版本可能用)。先 hdc shell ls /data/log/faultlog/ 确认。
  2. appfreeze 怎么都抓不到 → 用的是 debug 包,AppFreeze 只对 release 包生效。改打 release 包再测。
  3. cppcrash 只有偏移没有行号 → 需要带符号的 so + llvm-addr2line 符号化,或直接在 DevEco Studio 里看(自动符号化)。
  4. jscrash 看不到源码行 → release 包无 sourcemap,换 debug 包跑,或用 DevEco FaultLog 面板解析。
  5. 同一崩溃反复刷屏 → 同一进程最多留 10 份,旧的全被顶掉;分析要趁早导出
  6. FaultType 值对不上CPP_CRASH=2 / JS_CRASH=3 / APP_FREEZE=4(没有 1,1 是预留),代码里别写错。
  7. 符号化行号对不上/错位 → so 的 BuildID 和日志不一致。重新用崩溃发生时那次构建的带符号产物;验证:llvm-readelf -n xxx.so | grep "Build ID"
  8. 崩溃栈全是 ld-musl/libc 看不懂 → 大概率是内存被踩坏导致栈乱。换 ASAN 包复测(DevEco FaultLog 面板可看 ASAN 报告),能精确报越界点。

记忆口诀:崩溃落 faultlogger,cpp看信号、js看栈、freeze看主线程;debug 不出 freeze,release 才灵;符号化对 BuildID。


十二、标准作业流(SOP)

1. 设备开 USB 调试,hdc 连上(第1弹)
2. 【测试前】备好带符号构建产物 + 确认包类型(debug 查 jscrash/release 查 freeze)
3. 跑压测/复现异常
4. hdc shell hilog | grep -iE "crash|freeze"  实时观察
5. hdc file recv /data/log/faultlog/faultlogger ./faultlogs  导出
6. 按文件前缀分类型:cppcrash / jscrash / appfreeze
7. cppcrash → Reason(信号) + #00帧 + 符号化(对BuildID)
   jscrash  → Error message + SourceCode + Stacktrace
   appfreeze→ EVENTNAME + 主线程堆栈(release 包)
8. 查【第八章 常见模式速查表】对号入座
9. 修复 → 同种子 wukong 复测验证

十三、一句话速记卡(贴显示器上)

faultlog = 鸿蒙版 tombstone/ANR
路径: /data/log/faultlog/faultlogger
类型: CPP_CRASH=2  JS_CRASH=3  APP_FREEZE=4(无1)

获取:
  DevEco FaultLog 面板(自动符号化,推荐)
  hdc file recv /data/log/faultlog/faultlogger ./logs
  HiAppEvent 订阅 FAULT 事件(线上监控)

分析顺序: 文件名→Reason→崩溃点→符号化→复测
  cppcrash → Reason(信号SIGSEGV=11/SIGABRT=6) + #00帧 + addr2line
             寄存器有0=空指针; BuildID要对上
  jscrash  → Error message + SourceCode + Stacktrace(文件:行:列)
             release无sourcemap, 查行号用debug包
  appfreeze→ EVENTNAME(THREAD_BLOCK_6S/APP_INPUT_BLOCK) + 主线程栈
             ⚠ 仅 release 包生效!

盯日志: hdc shell hilog -L E | grep -iE "crash|freeze"
口诀: cpp看信号 js看栈 freeze看主线程; debug不出freeze release才灵

十四、术语表(看日志遇到不懂的词来这查)

术语含义
faultlog鸿蒙故障日志统称,对应 Android 的 tombstone/ANR
ProcessDump系统守护进程,崩溃时由它 dump 现场(寄存器/栈/内存)并落盘
FaultType故障类型枚举,@ohos.faultLogger 定义,CPP=2/JS=3/FREEZE=4
Reason日志里的"故障原因"行,cppcrash 是信号,jscrash 是异常类型,appfreeze 是事件名
Backtrace / 调用栈崩溃瞬间的函数调用链,#00 是崩溃点,向上是调用者
pc程序计数器,崩溃指令在 so 内的偏移地址
BuildID二进制文件的唯一标识,符号化时 so 的 BuildID 必须和日志一致
Fingerprint标准化栈帧指纹,相同指纹=同一崩溃,用于去重/统计崩溃率
watchdog看门狗线程,定期向主线程发判活任务,超时未响应→appfreeze
sourcemapJS 源码映射表,release 包不含,导致 jscrash 看不到行列号
SEGV_MAPERRSIGSEGV 子类,访问不存在的地址(多半空指针)
SEGV_ACCERRSIGSEGV 子类,访问无权限地址
UAFUse-After-Free,使用已释放内存,常表现为野指针崩
ASANAddressSanitizer,地址越界检测工具,DevEco FaultLog 面板可看报告
binder鸿蒙跨进程调用机制,appfreeze 日志里的 binder trace 看是否卡在系统服务
THREAD_BLOCK_6S主线程卡死 6 秒触发的 appfreeze 事件名
APP_INPUT_BLOCK输入事件响应超时触发的 appfreeze 事件名

十五、FAQ(新人高频疑问)

Q1:wukong 跑了半小时没崩,但用户反馈偶发崩溃,怎么复现?
A:faultlog 是"死后现场",要复现得靠 hilog 看"病程"。建议:① 用相同种子重跑 wukong(见第2弹);② 加大压测时长/提高点击比例;③ 接 HiAppEvent 订阅做线上监控,收集真实用户崩溃。

Q2:cppcrash 日志里 #00 帧是 libc/ld-musl,不是我的代码,怎么定位?
A:往上看 #01、#02……找到第一个你的业务 so(如 libentry.so)。系统库崩溃多半是被业务代码踩坏了内存,建议用 ASAN 包复测。

Q3:同一进程崩了好几次,日志只剩最新 10 份,前面的怎么找回?
A:找不回。同进程最多保留 10 份 cppcrash,超出的会被删。压测完立刻 hdc file recv 导出,别攒着。

Q4:jscrash 的 Stacktrace 行列号和我的源码对不上?
A:大概率是 release 包无 sourcemap,显示的是编译后的行列号。换 debug 包跑,或用 DevEco FaultLog 面板上传 sourcemap 解析。

Q5:appfreeze 日志里主线程栈是空的/只有几行系统调用?
A:说明卡在系统调用里(如 binder、I/O)。看 binder call trace 找是哪个系统服务没及时返回;也可能是死锁,看其他线程是否持有主线程需要的锁。

Q6:FaultLog 面板里看到 ASAN 报告,和 cppcrash 啥区别?
A:ASAN 是编译期插桩的内存检测,能精确报"在哪一行越界/UAF",比 cppcrash 的 SIGSEGV 信息更准。复测内存问题时优先用 ASAN 包。但 ASAN 有性能开销,只用于测试,不能上线

Q7:崩溃日志里 UID 是 20020205,是啥意思?
A:鸿蒙里 20020205 开头的 UID 表示三方应用;系统应用 UID 较小(如 1000 段)。看到这个值说明是用户态三方应用崩溃。

Q8:修完 bug 怎么验证确实修好了?
A:用相同 wukong 种子复测(见第2弹),跑相同或更长时间,确认不再复现同一 Fingerprint 的崩溃。


参考来源

本文为本人实践整理的原创笔记,技术内容参考华为官方文档与社区实践,日志样例为脱敏/示意格式。


版权与转载声明

本文为原创内容,首发于 CSDN,作者保留全部权利。

  • 转载请注明出处并附上原文链接,禁止删改本声明。
  • 文中所有日志样例均为脱敏/示意格式,不含任何真实业务代码与生产环境数据。
  • 系统命令与 API 以华为官方文档为准,本文基于 OpenHarmony 5.0 版本整理,后续版本如有差异请以官方文档为准。
  • 配套系列其余文章(共 13 篇)已在个人主页发布,欢迎关注获取更新。

CSDN 发布标签建议鸿蒙 · HarmonyOS · faultlog · 崩溃日志 · 移动开发 · 软件测试 · OpenHarmony

推荐专栏分类:移动开发 / 软件测试 / HarmonyOS


觉得有帮助就点个赞 + 收藏,遇到问题欢迎评论区交流。系列持续更新中。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐