1# Faultlogger开发指导
2
3
4## 概述
5
6
7### 功能简介
8
9Faultlogger是OpenHarmony为开发者提供的一个维测日志框架,能够为应用、元能力、系统服务进程崩溃故障提供统一检测、日志采集、日志存储、日志上报功能,为应用崩溃故障提供详细的维测日志用以辅助故障定位。本章节内容适用于标准系统以及Linux内核的小型系统。
10
11FaultLogger承载OpenHarmony系统上的故障记录功能,按照服务对象不同分别运行在两个部件中:
12
13- Hiview部件中的服务:服务于应用层和native层的功能模块,功能是分类管理系统中发生的各类故障信息,并为模块提供查询故障的API。
14
15- Faultloggerd部件中的服务:服务于崩溃进程,功能是收集C/C++运行时异常的守护进程和获取进程调用栈。
16
17基于Faultlogger服务,进程崩溃的处理流程如下图所示:
18
19  **图1** 进程崩溃处理流程图
20
21![zh-cn_image_0000001261812333](figures/zh-cn_image_0000001267089002.jpg)
22
231. 进程运行时崩溃,内核抛出崩溃信号,用户态SignalHandler响应(进程启动时硬编码注册);
24
252. SignalHandler收集好崩溃现场信息(context、fatal message等),设置当前进程ptracer和dumpable状态后,fork出ProcessDump进程进行remote抓栈;
26
273. ProcessDump获取到signalhandler通过pipe传输过来的进程crash现场相关信息后,向Faultloggerd申请crash文件句柄;
28
294. 基于libunwinder自研回栈能力进行回栈,崩溃日志写入到`/data/log/faultlog/temp` 目录下的原始crash故障文件;
30
315. ProcessDump完成crash文件内容写入后,将精简的crash故障信息打包上报到hiview;
32
336. hiview faultlogger插件接收到添加的crash信息后,额外获取hilog信息,在`/data/log/faultlog/faultlogger`目录下生成面向开发者开发的日志。同时上报到hisysevent和hiappevent;
34
35### 使用场景
36
37Faultloggerd意在为开发者在开发测试过程中遇到的崩溃或卡死问题提供一种简单轻量的定位手段。
38
39主要包含以下应用场景:
40
41  **表1** Faultloggerd模块应用场景
42
43| 场景描述 | 使用工具 | 使用方式 |
44| -------- | -------- | -------- |
45| 了解函数的调用顺序 | DumpCatcher API | 参见:[使用DumpCatcher接口获取调用栈](#使用dumpcatcher接口获取调用栈) |
46| 应用卡死/CPU占用高 | DumpCatcher Command Tool | 参见:[使用DumpCatcher命令获取调用栈](#使用dumpcatcher命令获取调用栈) |
47| 崩溃问题定位 | 崩溃日志和llvm-addr2line工具 | 参见:[基于崩溃日志定位问题](#基于崩溃日志定位问题) |
48
49
50## 使用DumpCatcher接口获取调用栈
51
52
53### 接口说明
54
55DumpCatcher可以抓取OpenHarmony指定进程(线程)的调用栈。
56
57  **表2** DumpCatcher接口说明
58
59| 类 | 方法 | 描述 |
60| -------- | -------- | -------- |
61| DfxDumpCatcher | bool DumpCatch(const int pid, const int tid, std::string&amp; msg, size_t maxFrameNums, bool isJson) |   接口返回值:<br/>- true:回栈成功,回栈信息存储在msg字符串对象中;<br/>- false:回栈失败。<br/>  输入参数:<br/>- pid:目标进程号;<br/>- tid:目标线程号,如果需要回栈进程中的所有线程,则tid设定为0;<br/>- maxFrameNums:回栈最大帧数,如果pid不等于调用方pid,则忽略这个参数,该参数默认值为256;<br/>- isJson:堆栈消息是否为JSON格式化,该参数默认值为false;<br/>  输出参数:<br/>- msg:如果回栈成功,则通过msg返回调用栈信息。 |
62| DfxDumpCatcher | bool DumpCatchMix(const int pid, const int tid, std::string&amp; msg) |   接口返回值:<br/>- true:回栈成功,回栈信息存储在msg字符串对象中;<br/>- false:回栈失败。<br/>  输入参数:<br/>- pid:目标进程号;<br/>- tid:目标线程号,如果需要回栈进程中的所有线程,则tid设定为0;<br/>  输出参数:<br/>- msg:如果回栈成功,则通过msg返回混合栈信息。 |
63| DfxDumpCatcher | bool DumpCatchFd(const int pid, const int tid, std::string&amp; msg, int fd, size_t maxFrameNums) |   接口返回值:<br/>- true:回栈成功,回栈信息存储在msg字符串对象中;<br/>- false:回栈失败。<br/>  输入参数:<br/>- pid:目标进程号;<br/>- tid:目标线程号,如果需要回栈进程中的所有线程,则tid设定为0;<br/>- fd:指定写入的文件句柄号;<br/>- maxFrameNums:回栈最大帧数,如果pid不等于调用方pid,则忽略这个参数,该参数默认值为256;<br/>  输出参数:<br/>- msg:如果回栈成功,则通过msg返回调用栈信息。 |
64| DfxDumpCatcher | bool DumpCatchMultiPid(const std::vector\<int> pidV, std::string&amp; msg) |   接口返回值:<br/>- true:回栈成功,回栈信息存储在msg字符串对象中;<br/>- false:回栈失败。<br/>  输入参数:<br/>- pidV:目标进程号列表;<br/>  输出参数:<br/>- msg:如果回栈成功,则通过msg返回调用栈信息。 |
65
66> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
67> 当调用此接口的进程id与目标pid不一致时需要调用者是管理员(system,root)用户。
68
69
70### 开发实例
71
72
73系统应用开发者可以用DumpCatcher在自己的应用中获取指定进程(线程)的调用栈。下文以dumpcatcherdemo模块使用DumpCatcher基础接口获取调用栈作为实例进行讲解。
74
75
761. 编译构建文件添加dumpcatcher依赖:以`/base/hiviewdfx/faultloggerd/example/BUILD.gn`为例,在include_dirs中添加DfxDumpCatcher头文件路径,并在deps中添加`//base/hiviewdfx/faultloggerd/interfaces/innerkits/dump_catcher:lib_dfx_dump_catcher`模块依赖。
77
78   ```
79   import("//base/hiviewdfx/faultloggerd/faultloggerd.gni")
80
81   config("dumpcatcherdemo_config") {
82     visibility = [ ":*" ]
83
84     include_dirs = [
85       ".",
86      "$faultloggerd_common_path/dfxutil",
87      "$faultloggerd_interfaces_path/common",  # 添加dumpcatcher头文件路径
88     ]
89   }
90
91   ohos_executable("dumpcatcherdemo") {
92    sources = [ "dump_catcher_demo.cpp" ]
93    configs = [ ":dumpcatcherdemo_config" ]
94    deps = [
95      "$faultloggerd_common_path/dfxlog:dfx_hilog",
96      "$faultloggerd_interfaces_path/innerkits/dump_catcher:libdfx_dumpcatcher", # 添加dumpcathcer模块依赖
97      "$faultloggerd_interfaces_path/innerkits/formatter:libjson_stack_formatter",
98    ]
99    external_deps = [
100      "c_utils:utils",
101      "hilog:libhilog",
102      "jsoncpp:jsoncpp",
103    ]
104    install_enable = true
105    part_name = "faultloggerd"
106    subsystem_name = "hiviewdfx"
107   }
108   ```
109
110    备注(如果其他仓想要依赖该模块,要通过faultloggerd部件进行依赖)
111    ```
112    external_deps += [
113        "faultloggerd:libdfx_dumpcatcher",
114        "faultloggerd:libjson_stack_formatter",
115    ]
116    ```
117
1182. 头文件定义用到的函数:以`/base/hiviewdfx/faultloggerd/example/dump_catcher_demo.h`为例,本样例代码中,通过调用栈深度测试的测试函数来构造一个指定深度的调用栈。
119
120   ```
121   #ifndef DUMP_CATCHER_DEMO_H
122   #define DUMP_CATCHER_DEMO_H
123
124   #define NOINLINE __attribute__((noinline))
125
126   // 定义该宏函数用于自动生成函数调用链
127   #define GEN_TEST_FUNCTION(FuncNumA, FuncNumB)          \
128       __attribute__((noinline)) int TestFunc##FuncNumA() \
129       {                                                  \
130           return TestFunc##FuncNumB();                   \
131       }
132
133   // 调用栈深度测试的测试函数
134   int TestFunc0(void);
135   int TestFunc1(void);
136   int TestFunc2(void);
137   int TestFunc3(void);
138   int TestFunc4(void);
139   int TestFunc5(void);
140   int TestFunc6(void);
141   int TestFunc7(void);
142   int TestFunc8(void);
143   int TestFunc9(void);
144
145   #endif // DUMP_CATCHER_DEMO_H
146   ```
147
1483. 在源文件中调用DumpCatch接口:以`/base/hiviewdfx/faultloggerd/example/dump_catcher_demo.cpp`为例,引用dfx_dump_catcher.h头文件,声明DfxDumpCatcher对象,通过宏函数构造函数调用链,并最后调用DumpCatch接口方法,传入需要抓取调用栈的进程号、线程号。
149
150   ```
151    #include "dump_catcher_demo.h"
152
153    #include <cstdint>
154    #include <cstdlib>
155    #include <cstring>
156    #include <iostream>
157    #include <string>
158    #include <unistd.h>
159    #include "dfx_define.h"
160    #include "dfx_dump_catcher.h"
161    #include "dfx_json_formatter.h"
162    #include "elapsed_time.h"
163
164    static NOINLINE int TestFuncDump(int32_t pid, int32_t tid, bool isJson)
165    {
166        OHOS::HiviewDFX::DfxDumpCatcher dumplog;
167        std::string msg = "";
168        OHOS::HiviewDFX::ElapsedTime counter;
169        bool ret = dumplog.DumpCatch(pid, tid, msg, OHOS::HiviewDFX::DEFAULT_MAX_FRAME_NUM, isJson); // 调用DumpCatch接口获取调用栈
170        time_t elapsed1 = counter.Elapsed();
171        if (ret) {
172            std::cout << msg << std::endl;
173            if (isJson) {
174                std::string outStr = "";
175                OHOS::HiviewDFX::DfxJsonFormatter::FormatJsonStack(msg, outStr);
176                std::cout << outStr << std::endl;
177            }
178        }
179        time_t elapsed2 = counter.Elapsed();
180        std::cout << "elapsed1: " << elapsed1 << " ,elapsed2: " << elapsed2 << std::endl;
181        return ret;
182    }
183
184    static NOINLINE int TestFunc10(void)
185    {
186        return TestFuncDump(getpid(), gettid(), false);
187    }
188
189   // 通过宏函数自动生成函数调用链
190    GEN_TEST_FUNCTION(0, 1)
191    GEN_TEST_FUNCTION(1, 2)
192    GEN_TEST_FUNCTION(2, 3)
193    GEN_TEST_FUNCTION(3, 4)
194    GEN_TEST_FUNCTION(4, 5)
195    GEN_TEST_FUNCTION(5, 6)
196    GEN_TEST_FUNCTION(6, 7)
197    GEN_TEST_FUNCTION(7, 8)
198    GEN_TEST_FUNCTION(8, 9)
199    GEN_TEST_FUNCTION(9, 10)
200
201    static bool ParseParameters(int argc, char *argv[], int32_t &pid, int32_t &tid)
202    {
203        switch (argc) {
204            case 3:
205                if (!strcmp("-p", argv[1])) {
206                    pid = atoi(argv[2]);
207                    return true;
208                }
209                if (!strcmp("-t", argv[1])) {
210                    pid = getpid();
211                    tid = atoi(argv[2]);
212                    return true;
213                }
214                break;
215            case 5:
216                if (!strcmp("-p", argv[1])) {
217                    pid = atoi(argv[2]);
218
219                    if (!strcmp("-t", argv[3])) {
220                        tid = atoi(argv[4]);
221                        return true;
222                    }
223                } else if (!strcmp("-t", argv[1])) {
224                    tid = atoi(argv[2]);
225
226                    if (!strcmp("-p", argv[3])) {
227                        pid = atoi(argv[4]);
228                        return true;
229                    }
230                }
231                break;
232            default:
233                break;
234        }
235        return false;
236    }
237
238    int main(int argc, char *argv[])
239    {
240        int32_t pid = 0;
241        int32_t tid = 0;
242        if (ParseParameters(argc, argv, pid, tid)) {
243            TestFuncDump(pid, tid, true);
244        } else {
245            TestFunc0();
246        }
247
248        return 0;
249    }
250   ```
251
252
253## 使用DumpCatcher命令获取调用栈
254
255
256### 工具说明
257
258DumpCatcher Command Tool是一个抓取调用栈的命令行工具,在OpenHarmony系统中可直接使用,该工具通过-p、-t参数指定进程和线程,命令执行后在命令行窗口打印指定进程的线程栈信息。还可通过添加-m参数来抓取应用进程的JS Native混合栈。
259
260  **表3** DumpCatcher Command Tool使用说明
261
262| 工具名称 | 命令行工具路径 | 执行命令 | 描述 |
263| -------- | -------- | -------- | -------- |
264| dumpcatcher | /system/bin | - dumpcatcher -p [pid]<br/>- dumpcatcher -p [pid] -t [tid]<br/>- dumpcatcher -m -p [pid]<br/>- dumpcatcher -m -p [pid] -t [tid]<br/> | **参数说明:**<br/>- -p [pid]:打印指定进程下面的所有线程栈信息。<br/>- -p [pid] -t [tid]:打印指定进程下面的指定线程信息。<br/>- -m -p [pid]:打印指定进程下面的所有线程混合栈信息。<br/>- -m -p [pid] -t [tid]:打印指定进程下面的指定线程混合栈信息。<br/>**返回值说明:**<br/>如果栈信息解析成功,则将信息显示到标准输出;失败则打印错误信息。 |
265
266
267### 使用实例
268
269通过dumpcatcher命令打印hiview进程的调用栈。
270
271
272```
273# ps -ef |grep hiview
274hiview        2035     1 4 14:15:51 ?     00:08:24 hiview
275root          6428  6425 12 17:31:51 pts/1 00:00:00 grep hiview
276# dumpcatcher -p 2035 -t 2035
277Result: 0 ( no error )
278Timestamp:2024-05-06 17:32:05.000
279Pid:2035
280Uid:1201
281Process name:/system/bin/hiview
282Tid:2035, Name:hiview
283#00 pc 000c3cac /system/lib/ld-musl-arm.so.1(ioctl+72)(d820b1827e57855d4f9ed03ba5dfea83)
284#01 pc 0000efdf /system/lib/chipset-pub-sdk/libipc_common.z.so(OHOS::BinderConnector::WriteBinder(unsigned long, void*)+14)(cd26d6fe1883c088fa265a1eb112cd38)
285#02 pc 000129b1 /system/lib/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::TransactWithDriver(bool)+216)(0d33aaefbd1542c521978a6b2a5e3591)
286#03 pc 00012aab /system/lib/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::StartWorkLoop()+34)(0d33aaefbd1542c521978a6b2a5e3591)
287#04 pc 00013abf /system/lib/chipset-pub-sdk/libipc_single.z.so(OHOS::IPC_SINGLE::BinderInvoker::JoinThread(bool)+34)(0d33aaefbd1542c521978a6b2a5e3591)
288#05 pc 00028df7 /system/bin/hiview(OHOS::HiviewDFX::HiviewService::StartService()+14)(9db8e6c0a2e9e1f6f489bd2b0ddb432c)
289#06 pc 00012d8d /system/bin/hiview(main+140)(9db8e6c0a2e9e1f6f489bd2b0ddb432c)
290#07 pc 00072b98 /system/lib/ld-musl-arm.so.1(libc_start_main_stage2+56)(d820b1827e57855d4f9ed03ba5dfea83)
291#08 pc 00012ce8 /system/bin/hiview(_start_c+84)(9db8e6c0a2e9e1f6f489bd2b0ddb432c)
292#09 pc 00012c8c /system/bin/hiview(9db8e6c0a2e9e1f6f489bd2b0ddb432c)
293```
294
295
296## 基于崩溃日志定位问题
297
298开发者可以通过faultloggerd生成的崩溃堆栈日志进行问题定位。本章节将主要介绍如何利用llvm-addr2line工具进行崩溃问题定位。
299
3001. 程序自崩溃或构造崩溃。
301   例如将如下代码植入自己的代码中,调用触发一个无效内存访问故障(SIGSEGV)。
302
303
304   ```
305   NOINLINE int TriggerSegmentFaultException()
306   {
307       printf("test TriggerSegmentFaultException \n");
308       // 为构造崩溃,强制进行类型转换
309       int *a = (int *)(&RaiseAbort);
310       *a = SIGSEGV;
311       return 0;
312   }
313   ```
314
3152. 获取崩溃函数调用栈日志。
316   因为存在未处理的异常,进程会在`/data/log/faultlog/temp`路径下生成临时的日志文件,其命名规则为:
317
318
319   ```
320   cppcrash-pid-time
321   ```
322
323   获取其生成的调用栈如下:
324
325
326   ```
327   Timestamp:2017-08-05 17:35:03.000
328   Pid:816
329   Uid:0
330   Process name:./crasher_c
331   Process life time:1s
332   Reason:Signal:SIGSEGV(SEGV_ACCERR)@0x0042d33d
333   Fault thread Info:
334   Tid:816, Name:crasher
335   #00 pc 0000332c /data/crasher(TriggerSegmentFaultException+15)(8bc37ceb8d6169e919d178fdc7f5449e)
336   #01 pc 000035c7 /data/crasher(ParseAndDoCrash+277)(8bc37ceb8d6169e919d178fdc7f5449e)
337   #02 pc 00003689 /data/crasher(main+39)(8bc37ceb8d6169e919d178fdc7f5449e)
338   #03 pc 000c3b08 /system/lib/ld-musl-arm.so.1(__libc_start_main+116)
339   #04 pc 000032f8 /data/crasher(_start_c+112)(8bc37ceb8d6169e919d178fdc7f5449e)
340   #05 pc 00003284 /data/crasher(_start+32)(8bc37ceb8d6169e919d178fdc7f5449e)
341   Registers:
342   r0:0042d33d r1:0000000b r2:1725d4c4 r3:b6f9fa84
343   r4:bec97e69 r5:b6fc0268 r6:0042d661 r7:bec97d60
344   r8:00000000 r9:00000000 r10:00000000
345   fp:bec97d20 ip:00000020 sp:bec97cd0 lr:b6f9fae4 pc:0042d32c
346   ```
347
3483. 利用llvm-addr2line工具进行调用栈分析。
349   使用llvm-addr2line工具根据偏移地址解析行号:
350
351
352   ```
353   root:~/OpenHarmony/out/hi3516dv300/exe.unstripped/hiviewdfx/faultloggerd$ llvm-addr2line -e crasher 0000332c
354   base/hiviewdfx/faultloggerd/tools/crasher/dfx_crasher.c:57
355   ```
356
357   这个崩溃是由赋值给一块不可写的区域导致的,代码行为dfx_crasher.c文件的57行,修改后可以避免发生此崩溃。
358