【鸿蒙开发】第三十四章 DevEco Studio - 故障分析汇总
目录
1 FaultLog
1.1 概述
1.2 查看FaultLog日志
1、查看设备历史抛出的FaultLog日志
2、查看设备实时抛出的FaultLog日志
3、跳转至引起错误的代码行
1.3 导出日志
2 堆栈轨迹分析
2.1 概述
2.2 应用堆栈解析
3 异常堆栈解析原理
3.1 构建产物介绍
1、ArkTS调试产物sourcemap
2、C++调试产物debug so
3、代码混淆产物nameCache
3.2 C++堆栈解析原理
1、编译选项差异
2、release编译带debug信息的so
3、C++堆栈解析流程
4、常见问题
3.3 ArkTS堆栈解析原理
1、sourcemap格式
2、sourcemap解析流程
3.4 反混淆解析原理
1、代码混淆产物介绍
2、代码混淆解析
4 使用ASan检测内存错误
5 使用TSan检测线程错误
6 使用HWASan检测内存错误
7 方舟运行时检测
1 FaultLog
1.1 概述
当应用运行发生错误使应用进程终止时,应用将会抛出错误日志以通知应用崩溃的原因,开发者可通过查看错误日志分析应用崩溃的原因及引起崩溃的代码位置。
若需定位应用中的C++代码导致的内存问题如数组越界、内存泄露、释放已释放的地址等问题,需要在运行调试配置界面勾选Address Sanitizer以打开ASan功能再推包运行,具体请查看ASan检测。
FaultLog由系统自动从设备进行收集,包括如下几类故障信息:
- App Freeze
- CPP Crash
- JS Crash
- System Freeze
- ASan
- TSan
说明
调试模式(debug和attach)下,DevEco Studio会屏蔽当前工程的App Freeze和System Freeze等超时检测,避免调试过程出现超时检测影响开发者调试。
当前支持屏蔽的App Freeze故障类型:
- THREAD_BLOCK_3S/THREAD_BLOCK_6S:应用主线程卡死检测,卡住3秒/6秒。
- APP_INPUT_BLOCK:输入响应超时。
当前支持屏蔽的System Freeze故障类型:
- LIFECYCLE_TIMEOUT:app、ability生命周期切换超时。
1.2 查看FaultLog日志
1、查看设备历史抛出的FaultLog日志
打开FaultLog窗口,将显示当前选中设备抛出的所有FaultLog日志。
FaultLog故障信息左侧按照应用/元服务包名 > 故障类型 > 故障时间结构组成,选中具体的故障日期,则会在右侧展示详细的故障信息,并对部分关键信息进行高亮展示,便于开发者进行故障定位。
2、查看设备实时抛出的FaultLog日志
当设备抛出FaultLog日志时,DevEco Studio将会弹出消息提示框,开发者点击Jump to Log即可跳转至FaultLog窗口查看日志信息。
3、跳转至引起错误的代码行
若抛出的FaultLog中的堆栈信息中的链接或偏移地址指向的是当前工程中的某行代码,该段信息将会被转换为超链接形式,点击后可跳转至对应代码行。
1.3 导出日志
开发者可将当前显示的日志信息保存到本地,以便后续的进一步分析。开发者可根据需要选择保存当前选中节点的日志或保存所有日志。
- 保存当前选中节点的日志:
- 在当前选中节点右键点击Export FaultLog。
- 点击Export FaultLog按钮
,弹出子选项后进一步点击Export Selected FaultLog。
- 在当前选中节点右键点击Export FaultLog。
- 保存所有日志:点击Export FaultLog按钮
,弹出子选项后进一步点击Export All FaultLog。
2 堆栈轨迹分析
2.1 概述
对于发布的应用(Release应用),为减小应用程序大小,提高运行效率,会对代码进行优化,去除其中的debug信息。因此无法直接通过Release应用的堆栈信息定位到源码的具体文件和行位置,不易于开发者快速定位解决问题。
针对该场景,DevEco Studio提供了Release应用堆栈解析功能,开发者可以利用构建产物中包含Debug信息的文件(so文件、sourcemap文件、nameCache文件等),对Release应用中C++堆栈、ArkTS堆栈以及ArkTS堆栈中混淆的方法名和文件名进行还原。
2.2 应用堆栈解析
Release应用堆栈解析功能操作方法如下:
- 单击菜单栏Code > Analyze Stack Trace,或在FaultLog页面异常堆栈信息处右键选择Analyze Stack Trace。
- 在弹出的Analyze Stack Trace对话框中,粘贴Release应用的异常堆栈信息。
- 如果当前工程为堆栈所在应用对应的工程,且存在Release构建产物,点击Start Analyze即可进行解析。
如果当前工程不是堆栈所在应用对应的工程,则需要配置应用对应构建产物:勾选Unscramble stack trace, 在下方的文件选择框中,分别添加应用对应的sourcemap文件、so文件以及nameCache文件,点击Start Analyze进行转换。
DevEco Studio将解析后的堆栈信息显示在右侧的输出框中。
在构建Release应用时,so文件是默认不包含符号表信息的,如果需要在构建Release应用时生成包含符号表的so文件,需要在工程的模块级build-profile.json5文件的buildOption属性中,配置如下信息:
"buildOption": {
"externalNativeOptions": {
"arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
}
}
如果引用release Har包中native方法产生了异常堆栈,解析时请勾选Unscramble stack trace, 并选择har模块中编译出的带有符号信息的so文件,引用方build产物中的har模块so不带有符号信息。so文件在模块中相对路径为build/default/intermediates/libs/default/{cpu类型}/libxxx.so。
3 异常堆栈解析原理
3.1 构建产物介绍
1、ArkTS调试产物sourcemap
release模式编译产物,产物位置:{ProjectPath}/{ModuleName}/build/{product}/cache/default/default@CompileArkTS/esmodule/release/sourceMaps.map
2、C++调试产物debug so
带debug信息的so数据,产物位置:{ProjectPath}/{ModuleName}/build/{product}/intermediates/libs
配置方式请参考release编译带debug信息的so。
3、代码混淆产物nameCache
反混淆映射表,release模式编译产物,产物位置:{ProjectPath}/{ModuleName}/build/{product}/cache/default/default@CompileArkTS/esmodule/release/obfuscation
3.2 C++堆栈解析原理
1、编译选项差异
- Debug:不优化代码,附加调试信息。
- Release:最大化优化代码,但不包含调试信息。
- RelWithDebInfo:近似于Release模式,既进行了代码优化,同时保留部分调试信息。
2、release编译带debug信息的so
通常release的so会经过strip,strip后的so中的符号表、调试信息会被剥离。
若需要保留so文件中的符号表、调试信息,需要在build-profile.json5的buildOption/externalNativeOptions中配置参数:"arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo"。
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"cppFlags": "",
}
},
...
}
- libs:带debug信息的so。
- stripped_native_libs:移除调试信息等冗余数据后的so。
3、C++堆栈解析流程
llvm-addr2line(获取llvm-addr2line工具)是将函数地址解析成文件名或行号的工具。
给出一个可执行文件中的地址或一个可重定位对象中的偏移部分的地址,使用调试信息来找出与之相关的文件名和行号。
常用参数:
参数 | 用途 |
---|---|
-a | 以十六进制形式显示地址 |
-C | 将符号名解码为用户级别的名字 |
-e | 设置需要转换地址的可执行文件名 |
-f | 显示文件名、行号和函数名信息 |
-F | 显示函数名及文件行号 |
-j | 读取指定部分的偏移量,而不是绝对地址 |
-p | 每个地址信息单独占一行 |
参考示例:
查看文件名、行号和函数名相关信息:
llvm-addr2line -f -e File.so
查找指定的地址所对应的代码位置:
llvm-addr2line 0x00000000004005e7 -e test -f -C -s
例如:
llvm-addr2line -e libapplication.so 00003714 -f –C
ASan堆栈解析:
4、常见问题
- 什么是UUID?
每一个可执行程序都有一个build UUID来唯一标识。Crash日志包含发生crash的这个应用(app)的build UUID以及crash发生时应用加载的所有库文件的build UUID。
- 如何获取llvm-addr2line工具?
在DevEco Studio安装目录/deveco-studio/sdk/default/openharmony/native/llvm/bin下即可找到llvm-addr2line.exe。
3.3 ArkTS堆栈解析原理
1、sourcemap格式
图1 源码
图2 编译后产物
实际代码映射关系:
70->29
71->30
72->31
73->32
sourcemap结构:
单个module构建产物sourceMaps.map为merge文件,实际包含该模块的所有文件的映射关系;每个json中key以编译构建产物的唯一路径作为主键,运行程序的abc中保留了对应的key信息,当运行时异常代码归属到该文件时输出信息为该key,sources为实际源码文件信息,用于异常堆栈还原源码;mappings为编码后的行列号映射表,每个文件有独立的映射关系。
- version:目前source map标准的版本为3。
- file:生成的文件名。
- mappings:记录位置信息的字符串。
- sources:源文件地址列表。
- names:转换前的所有变量名和属性名。
- sourceRoot:源文件目录地址,可以用于重新定位服务器上的源文件。
- entry-package-info:"home|1.0.0" 对应module本身的oh-package.json中的name及version,用于关联反混淆nameCache资源版本。
- package-info: "commonlib|1.0.0" 对应非module本身的oh-package.json中的name及version,即dependencies引用的代码,可用于引用三方库二次解析sourcemap。
2、sourcemap解析流程
图3 sourcemap中的key结构化处理
3.4 反混淆解析原理
代码混淆配置请参考代码混淆。
1、代码混淆产物介绍
混淆映射表:$ProjectPath\$ModuleName\build\$product\cache\default\default@CompileArkTS\esmodule\release\obfuscation\nameCache.json
{
"home/src/main/ets/homeability/HomeAbility.ets": {
"IdentifierCache": {
"#AbilityConstant": "AbilityConstant",
"#hilog": "hilog",
"#UIAbility": "UIAbility",
"#Want": "Want",
"#window": "window",
"HomeAbility#onWindowStageCreate#__function": "i"
},
"MemberMethodCache": {
"onCreate:10:16": "onCreate",
"onDestroy:18:20": "onDestroy",
"onWindowStageCreate:22:33": "onWindowStageCreate",
"onWindowStageDestroy:35:38": "onWindowStageDestroy",
"onForeground:40:43": "onForeground",
"onBackground:45:48": "onBackground"
},
"obfName": "home/src/main/ets/homeability/HomeAbility.ets"
},
"compileSdkVersion": "5.0.0.25",
"entryPackageInfo": "home|1.0.0",
"PropertyCache": {
"integratedHsp": "i",
"asanClick": "j",
"Index_Params": "m",
"testNapi": "o",
"Index": "t",
"testObfuscation": "g2"
}
}
- originalfieldname:该字段为每个文件的原始文件路径及名称,例如以上的"home/src/main/ets/homeability/HomeAbility.ets"。
- ObfName:key为固定字段,value为每个文件混淆后的名称,与originalfieldname配对。
"obfName": "home/src/main/ets/pages/a.ts"
- IdentifierCache:该字段对应的值为该文件下的变量名混淆前后的映射关系。
- 变量名分为两类:普通变量、类方法变量。
- 普通变量映射关系的格式如下:
originalvariablename : obfuscatedvariablename
- originalvariablename 表示原始的变量名称。
- obfuscatedvariablename 表示混淆后的变量名称。
类方法变量映射关系的格式如下:
/*--------------------------key---------------------------------- : -----------value----------*/
originalmethodname: originalmethodstartline: originalmethodendline : obfuscatedmethodname
-
- originalmethodname 表示原始的方法名称。
- [:originalmethodstartline:originalmethodendline] 表示原始的方法起始行数与结束行数,左右都是闭区间。
- obfuscatedmethodname 表示混淆后的方法名称。
- MemberMethodCache:该字段对应的值为该文件下的成员方法名混淆前后的映射关系。
开启属性混淆时,成员方法映射关系的格式如下:
-
/*--------------------------key--------------------------------- : -----------value----------*/ originalmethodname:originalmethodstartline:originalmethodendline : obfuscatedmethodname
未开启属性混淆时,成员方法映射关系的格式如下:
/*--------------------------key------------------------------------- : -----------value----------*/
originalmethodname : originalmethodstartline : originalmethodendline : originalmethodname
-
- originalmethodname 表示原始的成员方法名称。
- [:originalmethodstartline :originalmethodendline] 表示原始的成员方法起始行数与结束行数,左右都是闭区间。
- obfuscatedmethodname 表示混淆后的成员方法名称。
- PropertyCache:该字段对应的值为全局所有属性名混淆前后的映射关系,只有在开启属性混淆时才会有值。
属性名映射关系格式如下:
-
/*--------key------- : -----------value----------*/ originalpropertyname : obfuscatedmethodname
- originalpropertyname 表示原始的属性名称。
- obfuscatedmethodname 表示混淆后的属性名称。
2、代码混淆解析
异常堆栈如下:
Pid:58348
Uid:20020156
Reason:RangeError
Error name:RangeError
Error message:The number cannot be converted to a BigInt because it is not an integer
Stacktrace:
Cannot get SourceMap info, dump raw stack:
at g2 (home|home|1.0.0|src/main/ets/pages/a.ts:6:6)
at getVersion (home|home|1.0.0|src/main/ets/pages/a.ts:2:2)
at anonymous (home|home|1.0.0|src/main/ets/pages/Index.ts:61:61)
1. 经过sourcemap映射转码堆栈如下:
at g2 (home/src/main/ets/pages/tool.ts:7:27)
at getVersion (home/src/main/ets/pages/tool.ts:2:30)
at anonymous (home/src/main/ets/pages/Index.ets:23:40)
a.ts通过sourcemap还原为tool.ts。
"home|home|1.0.0|src/main/ets/pages/a.ts": {
"version": 3,
"file": "tool.ts",
"sources": [
"home/src/main/ets/pages/tool.ts"
],
"names": [],
"mappings": "AAAA,MAAM,CAAC,OAAO,UAAU,UAAU,IAAI,MAAM;IAC1C,IAAI,KAAM,IAAiB,CAAA;IAC3B,UAAW;AACb,CAAC;AAED,eAA2B,MAAM;IAC/B,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IACxB,OAAO,GAAG,CAAC;AACb,CAAC",
"sourceRoot": "",
"entry-package-info": "home|1.0.0"
}
2. 函数级文件名映射。
"home/src/main/ets/pages/tool.ts": {
"IdentifierCache": {
"getVersion#res": "h2",
"#testObfuscation:6:9": "g2"
},
"MemberMethodCache": {},
"obfName": "home/src/main/ets/pages/a.ts"
}
该字段的IdentifierCache与MemberMethodCache中保存了方法名混淆前后的映射关系,对应格式为:"源码方法名:该方法起始行号:该方法结束行号":"混淆后方法名"。
源码方法名中的"源码方法名"代表上下级关系,故匹配后可以通过"#"保留最后名称。
第一条堆栈混淆后的方法名为"g2",若存在多个"g2"则需要通过行号范围过滤,故利用上述字段对该方法名进行还原:
- 通过key(home/src/main/ets/pages/tool.ts)查找到映射表。
- 在上述字段中找出所有混淆后方法名为"g2"的条目,该条目为:
"#testObfuscation:6:9": "g2"
3. 找到行号范围包含步骤一中还原后行号的条目,步骤一中得到的行号为7包含在6-9之内,因此可以得到源码对应方法名为"#testObfuscation",经过字符串处理结果为"testObfuscation"。
at testObfuscation (home/src/main/ets/pages/tool.ts:7:27)
at getVersion (home/src/main/ets/pages/tool.ts:2:30)
at anonymous (home/src/main/ets/pages/Index.ets:23:40)
4 使用ASan检测内存错误
5 使用TSan检测线程错误
6 使用HWASan检测内存错误
7 方舟运行时检测
注意:4、5、6、7 点直接跳转链接