ToB企服应用市场:ToB评测及商务社交产业平台

标题: OpenHarmony(鸿蒙南向开辟)——小型体系内核(LiteOS-A)【用户态内存调 [打印本页]

作者: 我可以不吃啊    时间: 2024-10-11 19:02
标题: OpenHarmony(鸿蒙南向开辟)——小型体系内核(LiteOS-A)【用户态内存调
基本概念

Debug版本的musl-libc库为用户提供内存泄漏检测、堆内存统计、踩内存分析以及backtrace功能等维测本领,可以进步用户态内存相干问题的定位效率。
采用了对malloc/free接口举行插桩,保存关键节点信息,然后步调在申请和释放内存时举行内存节点完备性校验,最后在步调竣事时通过统计节点信息得到内存统计信息并根据统计信息判断内存是否泄漏的设计思想。
运行机制

内存泄漏检查

对于每个进程,内存调测模块维护了128个链表(当前体系的线程最大数量为128个),每个链表的索引为线程ID。
申请内存时:保存关键信息到内存节点控制块,根据当火线程ID将内存节点控制块挂到对应链表;
释放内存时:根据需要释放的内存地址匹配内存节点控制块并将该控制块删除。
图1 堆内存节点信息链表

申请内存时,返回地址会被保存到LR寄存器中。进程运行过程中,体系会在内存节点控制块中添加疑似泄漏点对应的lr等信息。如下图所示:
图2 堆内存节点信息

其中,TID表示线程ID;PID表示进程ID;ptr表示申请的内存地址;size表示申请的内存大小;lr[n]表示函数调用栈地址,变量n的大小可以根据具体场景的需要举行配置。
释放内存时,将free等接口的入参指针与node的ptr字段举行匹配,如果相同则删除该内存节点控制块信息。
用户通过串口或文件等方式,将各个进程内存调测信息导出,利用addr2line工具将导出的信息转换成导致内存泄漏的代码行,便可以解决内存泄露问题。
图3 泄漏点代码行定位流程

堆内存统计

用户态线程堆内存使用统计具有一定的实际意义,统计线程申请的堆内存占比,为用户步调的内存使用优化提供数据支持。用户态堆内存统计模块主要涉及的接口为malloc和free。如上图所示,每个进程维护128个链表,链表索引即线程ID,申请内存时体系将ptr和size信息记录在内存节点控制块中并将节点控制块挂在以线程ID为头信息的链表上,堆内存释放时根据ptr从对应的链表上移除相应的堆内存块信息;同时计算出当火线程所持有的堆内存总的使用量,并更新当前进程的堆内存使用量和堆内存使用峰值。
内存完备性检查


图4 node节颔首信息添加校验值

free堆内存时,不会立即把该内存块释放掉,而是在内存中写入魔术数字0xFE,并放到free队列中(包管在一定时间内不会再被malloc函数分配),当有野指针或use-after-free的情况对该内存举行读取的操纵时,可以或许发现数据非常,但是对于写操纵则无法判断出来。
图5 free流程图


图6 malloc通过mmap机制申请内存的内存布局

使用指导

接口阐明

表1 内存调测功能
接口名形貌mem_check_init初始化内存检测模块。watch_mem获取线程级堆内存使用信息。check_leak检查是否有堆内存泄漏。check_heap_integrity检查堆内存的完备性。backtrace获取调用栈地址信息。backtrace_symbols根据地址信息获取符号信息。print_trace输出函数调用栈信息。 表2 调用栈回溯功能
接口名形貌backtrace获取调用栈地址信息。backtrace_symbols根据地址信息获取符号信息。print_trace输出函数调用栈信息。 使用阐明

编译OpenHarmony工程时默认编译的是debug版本,即libc库已经集成内存调测相干的接口实现,用户可以根据具体需要决定是否使能内存调测功能。
堆内存调测功能提供两种方式供用户使用:接口调用及命令行参数。

   阐明: 内存调测功能使能后,进程退出时会默认举行一次堆内存泄漏和堆内存完备性检查。内存调测功能未使能时,堆内存统计、堆内存泄漏检查、堆内存完备性校验功能不会开启,调用相干调测接口无相应。
  接口调用方式

示例代码

代码功能:显式调用调测模块的相干接口对用户代码举行内存校验。
  1. #include <pthread.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <debug.h> // 包含提供内存调测接口声明的头文件
  5. #define MALLOC_LEAK_SIZE  0x300
  6. void func(void)
  7. {
  8.     char *ptr = malloc(MALLOC_LEAK_SIZE);
  9.     memset(ptr, '3', MALLOC_LEAK_SIZE);
  10. }
  11. int main()
  12. {
  13.     mem_check_init(NULL); // 通过串口输出内存调测信息,必须在用户程序第一次申请堆内存之前调用(一般在main函数入口调用),否则调测信息不准确。
  14.     // mem_check_init("/storage/mem_debug.txt"); // 内存调测信息输出到/storage/mem_debug.txt文件中,如果该文件创建失败,则信息通过串口输出。
  15.     char *ptr = malloc(MALLOC_LEAK_SIZE);
  16.     memset(ptr, '1', MALLOC_LEAK_SIZE);
  17.     watch_mem(); // 在当前代码逻辑处查看线程级内存统计信息。
  18.     func();
  19.     check_heap_integrity(); // 检查堆内存节点完整性。
  20.     check_leak(); // 在当前代码逻辑处检查堆内存是否泄漏(一般在程序退出之前校验比较准确,若在malloc和free调用逻辑之间做校验,则结果不准确)。
  21.     return 0;
  22. }
  23. c
复制代码
编译

  1. $ clang -o mem_check mem_check.c -funwind-tables -rdynamic -g -mfloat-abi=softfp -mcpu=cortex-a7 -mfpu=neon-vfpv4 -target arm-liteos --sysroot=/home/<user-name>/directory/out/hispark_taurus/ipcamera_hispark_taurus/sysroot $(clang -mfloat-abi=softfp -mcpu=cortex-a7 -mfpu=neon-vfpv4 -target arm-liteos -print-file-name=libunwind.a)
复制代码
  阐明:
  
  调测信息

  1. OHOS # ./mem_check
  2. OHOS #
  3. ==PID:4== Heap memory statistics(bytes): // 堆内存统计信息
  4.     [Check point]: // check点调用栈
  5.         #00: <main+0x38>[0x86c] -> mem_check
  6.         #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  7.     [TID: 18, Used: 0x320] // 18号线程堆内存占用,当前进程仅一个线程
  8. ==PID:4== Total heap: 0x320 byte(s), Peak: 0x320 byte(s)
  9. Check heap integrity ok! // 堆内存完整性检查
  10. ==PID:4== Detected memory leak(s): // 内存泄漏信息及调用栈
  11.     [Check point]:
  12.         #00: <check_leak+0x1c4>[0x2da4c] -> /lib/libc.so
  13.         #01: <main+0x44>[0x878] -> mem_check
  14.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  15.         #00: <main+0x1c>[0x850] -> mem_check
  16.         #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  17.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  18.         #00: <func+0x14>[0x810] -> mem_check
  19.         #01: <main+0x3c>[0x870] -> mem_check
  20.         #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  21. ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
  22. ==PID:4== Detected memory leak(s):
  23.     [Check point]:
  24.         #00: <check_leak+0x1c4>[0x2da4c] -> /lib/libc.so
  25.         #01: <exit+0x28>[0x111ec] -> /lib/libc.so
  26.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  27.         #00: <main+0x1c>[0x850] -> mem_check
  28.         #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  29.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  30.         #00: <func+0x14>[0x810] -> mem_check
  31.         #01: <main+0x3c>[0x870] -> mem_check
  32.         #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  33. ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
  34. Check heap integrity ok!
复制代码
调用栈解析

提供parse_mem_info.sh脚本可以对调用栈举行解析,解析脚本存放的路径:kernel/liteos_a/tools/scripts/parse_memory/parse_mem_info.sh。利用脚本可以将相应的调测信息转换成具体的源码行号,如下命令所示,mem_debug.txt保存的是内存调测信息,elf1、elf2等文件是需要解析的elf文件。
  1. $ ./parse_mem_info.sh mem_debug.txt elf1 elf2 elf3 ...
复制代码
例如:
  1. $ ./parse_mem_info.sh mem_debug.txt mem_check
  2. Compiler is [gcc/llvm]: llvm
  3. Now using addr2line ...
  4. ==PID:4== Heap memory statistics(bytes):
  5.     [Check point]:
  6.         #00: <main+0x38>[0x86c] at /usr1/xxx/TEST_ELF/mem_check.c:22
  7.         #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  8.     [TID: 18, Used: 0x320]
  9. ==PID:4== Total heap: 0x320 byte(s), Peak: 0x320 byte(s)
  10. Check heap integrity ok!
  11. ==PID:4== Detected memory leak(s):
  12.     [Check point]:
  13.         #00: <check_leak+0x1c4>[0x2da4c] -> /lib/libc.so
  14.         #01: <main+0x44>[0x878] at /usr1/xxx/TEST_ELF/mem_check.c:28
  15.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  16.         #00: <main+0x1c>[0x850] at /usr1/xxx/TEST_ELF/mem_check.c:17
  17.         #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  18.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  19.         #00: <func+0x14>[0x810] at /usr1/xxx/TEST_ELF/mem_check.c:9
  20.         #01: <main+0x3c>[0x870] at /usr1/xxx/TEST_ELF/mem_check.c:24
  21.         #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so
  22. ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
复制代码
命令行参数方式

对用户态进程举行内存相干的检查时,除了接口调用方式还可以通过命令行方式举行内存统计、内存泄漏或内存完备性检查。
  1. --mwatch:初始化内存调测功能,注册信号。内存调测信息将从串口输出;
  2. --mrecord <f_path>:初始化内存调测功能,注册信号。内存调测信息将保存至f_path文件,若f_path创建失败,则内存调测信息将从串口输出
复制代码
在待调测的进程未退出时可使用信号机制获取对应信息:
  1. kill -35 <pid> # 查看线程级堆内存占用
  2. kill -36 <pid> # 检查是否存在堆内存泄漏
  3. kill -37 <pid> # 检查堆内存头节点是否完整
复制代码
示例代码

代码功能:构造内存问题利用命令行方式举行内存调测。
  1. #include <pthread.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #define MALLOC_LEAK_SIZE  0x300
  5. void func(void)
  6. {
  7.     char *ptr = malloc(MALLOC_LEAK_SIZE);
  8.     memset(ptr, '3', MALLOC_LEAK_SIZE);
  9. }
  10. int main()
  11. {
  12.     char *ptr = malloc(MALLOC_LEAK_SIZE);
  13.     memset(ptr, '1', MALLOC_LEAK_SIZE);
  14.     func();
  15.     while (1);
  16. }
  17. c
复制代码
编译

参考接口调用章节里的编译。
使用mwatch参数命令

  1. OHOS # ./mem_check --mwatch // 利用task命令可以查到mem_check进程的pid为4
  2. OHOS #
  3. OHOS # kill -35 4 // 查看堆内存统计信息
  4. OHOS #
  5. ==PID:4== Heap memory statistics(bytes):
  6.     [Check point]:
  7.         #00: <arm_signal_process+0x5c>[0x58dfc] -> /lib/libc.so
  8.     [TID: 18, Used: 0x640]
  9. ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s)
  10. OHOS # kill -36 4 // 检查是否存在堆内存泄漏
  11. OHOS #
  12. ==PID:4== Detected memory leak(s):
  13.     [Check point]:
  14.         #00: <check_leak+0x1c4>[0x2da4c] -> /lib/libc.so
  15.         #01: <arm_signal_process+0x5c>[0x58dfc] -> /lib/libc.so
  16.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  17.         #00: <main+0x14>[0x724] -> mem_check
  18.         #01: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so
  19.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  20.         #00: <func+0x14>[0x6ec] -> mem_check
  21.         #01: <main+0x30>[0x740] -> mem_check
  22.         #02: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so
  23. ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
  24. OHOS # kill -37 4 // 检查堆内存头节点的完整性
  25. OHOS #
  26. Check heap integrity ok!
复制代码
调用栈解析

将调测信息保存至test.txt文件中,利用脚本举行解析,获取内存泄漏的具体行号。
  1. $ ./parse_mem_info.sh test.txt mem_check
  2. Compiler is [gcc/llvm]: llvm
  3. Now using addr2line ...
  4. ==PID:4== Detected memory leak(s):
  5.     [Check point]:
  6.         #00: <check_leak+0x1c4>[0x2da4c] -> /lib/libc.so
  7.         #01: <arm_signal_process+0x5c>[0x58dfc] -> /lib/libc.so
  8.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  9.         #00: <main+0x14>[0x724] at /usr1/xxx/TEST_ELF/mem_check.c:14
  10.         #01: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so
  11.     [TID:18 Leak:0x320 byte(s)] Allocated from:
  12.         #00: <func+0x14>[0x6ec] at /usr1/xxx/TEST_ELF/mem_check.c:8
  13.         #01: <main+0x30>[0x740] at /usr1/xxx/TEST_ELF/mem_check.c:19
  14.         #02: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so
  15. ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
复制代码
使用mrecord参数命令

  1.     OHOS # ./mem_check --mrecord /storage/check.txt
复制代码
  1.     OHOS # kill -35 4
  2.     OHOS # Memory statistics information saved in /storage/pid(4)_check.txt
  3.     OHOS # cat /storage/pid(4)_check.txt
  4.     ==PID:4== Heap memory statistics(bytes):
  5.         [Check point]:
  6.             #00: <arm_signal_process+0x5c>[0x5973c] -> /lib/libc.so
  7.         [TID: 18, Used: 0x640]
  8.     ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s)
复制代码
  1.     OHOS # kill -36 4
  2.     OHOS # Leak check information saved in /storage/pid(4)_check.txt
  3.     OHOS # cat /storage/pid(4)_check.txt
  4.     ==PID:4== Heap memory statistics(bytes):
  5.         [Check point]:
  6.             #00: <arm_signal_process+0x5c>[0x5973c] -> /lib/libc.so
  7.         [TID: 18, Used: 0x640]
  8.     ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s)
  9.     ==PID:4== Detected memory leak(s):
  10.         [Check point]:
  11.             #00: <check_leak+0x1c4>[0x2e38c] -> /lib/libc.so
  12.             #01: <arm_signal_process+0x5c>[0x5973c] -> /lib/libc.so
  13.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  14.             #00: <main+0x14>[0x724] -> mem_check
  15.             #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  16.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  17.             #00: <func+0x14>[0x6ec] -> mem_check
  18.             #01: <main+0x30>[0x740] -> mem_check
  19.             #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  20.     ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
复制代码
  1.     OHOS # kill -9 4
  2.     OHOS # Leak check information saved in /storage/pid(4)_check.txt
  3.     Check heap integrity ok!
  4.     OHOS # cat /storage/pid(4)_check.txt
  5.     OHOS #
  6.     ==PID:4== Heap memory statistics(bytes):
  7.         [Check point]:
  8.             #00: <arm_signal_process+0x5c>[0x5973c] -> /lib/libc.so
  9.         [TID: 18, Used: 0x640]
  10.     ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s)
  11.     ==PID:4== Detected memory leak(s):
  12.         [Check point]:
  13.             #00: <check_leak+0x1c4>[0x2e38c] -> /lib/libc.so
  14.             #01: <arm_signal_process+0x5c>[0x5973c] -> /lib/libc.so
  15.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  16.             #00: <main+0x14>[0x724] -> mem_check
  17.             #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  18.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  19.             #00: <func+0x14>[0x6ec] -> mem_check
  20.             #01: <main+0x30>[0x740] -> mem_check
  21.             #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  22.     ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
  23.     ==PID:4== Detected memory leak(s):
  24.         [Check point]:
  25.             #00: <check_leak+0x1c4>[0x2e38c] -> /lib/libc.so
  26.             #01: <exit+0x28>[0x11b2c] -> /lib/libc.so
  27.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  28.             #00: <main+0x14>[0x724] -> mem_check
  29.             #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  30.         [TID:18 Leak:0x320 byte(s)] Allocated from:
  31.             #00: <func+0x14>[0x6ec] -> mem_check
  32.             #01: <main+0x30>[0x740] -> mem_check
  33.             #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so
  34.     ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s).
复制代码
  阐明: 上述连续记录的信息会逐步追加到初始化时所指定的文件中,故最后cat文件时,文件中还包罗历史记录的信息内容。
  常见问题

UAF(Use after free)


   阐明: free之后的堆内存不会立即释放进堆内存池,会先放至固定长度的队列中,并置魔术数字0xFE,队列满后会将先放至队列中的内存块释放进堆内存池
  写操纵:无法校验。

Double free

Double free时,用户步调将会非常退出。
堆内存节点被踩


堆内存节点被踩时,用户步调将会非常退出,并输出破坏被踩节点的可能的堆内存申请调用栈,对于野指针踩内存情况无法校验出来。例如用户步调mem_check中存在堆内存越界踩的情况,利用命令行方式可以获得踩内存的可能的具体位置。
  1.     OHOS # ./mem_check --mwatch
  2.     OHOS #
  3.     ==PID:6== Memory integrity information:
  4.         [TID:28 allocated addr: 0x272e1ea0, size: 0x120] The possible attacker was allocated from:
  5.             #00: <malloc+0x808>[0x640e8] -> /lib/libc.so
  6.             #01: <threadFunc1+0x7c>[0x21d0] -> mem_check
复制代码
可以通过调用栈解析脚本对调用栈信息举行解析。

堆内存由malloc通过mmap接口申请,申请得到的堆内存块前后各置一个size为PAGE_SIZE大小的区间,设置无读写权限,读写操纵会触发用户步调非常。
如果各人想更加深入的学习 OpenHarmony(鸿蒙南向) 开辟的全栈内容,不妨可以参考以下相干学习文档举行学习,助你快速提升自己:
OpenHarmony 开辟情况搭建:https://qr18.cn/CgxrRy


《OpenHarmony源码解析》:https://qr18.cn/CgxrRy


体系架构分析:https://qr18.cn/CgxrRy



OpenHarmony 设备开辟学习手册:https://qr18.cn/CgxrRy


OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy


写在最后