【鸿蒙开发】第三十章 应用稳定性-检测、分析、优化、运维汇总

news2025/2/15 14:24:59

目录​​​​​​​

1 概述

2  使用Asan检测内存错误

2.1 背景

2.2 原理概述

2.3 使用约束

2.4 配置参数

2.4.1 在app.json5中配置环境变量

2.4.2 在Run/Debug Configurations中配置环境变量

2.5 Asan使能

方式一

方式二

运行ASan

2.6 ASan异常检测类型

heap-buffer-overflow

stack-buffer-overflow

stack-buffer-underflow

heap-use-after-free

stack-use-after-scope

attempt-free-nonallocated-memory

double-free

3 使用HWAsan检测内存错误

3.1 原理概述

3.2 功能介绍

3.3 使用约束

3.4 配置HWASan

方式一

方式二

方式三

3.5 运行HWASan

3.6 HWasan异常检测类型

stack tag-mismatch

heap-buffer-overflow

Use-after-free

4 使用UBSan检测未定义行为

4.1 原理概述

4.2 使用约束

4.3 UBsan使能

方式一

方式二

4.4 UBSan异常检测类型

store to misaligned address

member access within misaligned address

not a valid value for type 'bool'

index xxx out of bounds for type xxx

xxx is outside the range of representable values of type 'int'

division by zero

5 使用GWP-Asan检测内存错误

5.1 背景

5.2 原理概述

5.3 使用约束

5.4 GWP-Asan使能

5.5 Gwp-Asan异常检测类型

double free

use_after_free

invalid free left

invalid free right

Buffer Underflow

6 内存泄漏检测

6.1 使用Snapshot检测内存泄漏

6.1.1 查看快照详情

6.1.2 节点属性与引用链

6.1.3 节点跳转

6.1.4 历史节点前进/后退

6.1.5 比较快照差异

6.1.6 Heap Snapshot离线导入

6.2 ArkTS内存泄漏分析

分析步骤

录制Snapshot模板数据

分析Snapshot数据

7 基础内存检测

7.1 内存分析及优化

分析数据筛选

通过内存状态筛选

通过统计方式筛选

通过so库名筛选

通过搜索筛选

筛选内存分配堆栈

分析启动内存

8 使用Tsan检测线程问题

8.1 原理概述

8.2 功能介绍

应用场景

8.3 错误报告

8.4 使用约束

8.5 使能Tsan

方式一

方式二

8.6 Tsan异常检测类型

Data race

data race on vptr

Use After Free

Signal Check

signal-unsafe call inside of a signal

Mutex Check

9 方舟运行时检测

9.1 方舟多线程检测

1、原理介绍

2、常见多线程安全问题

3、使用约束

4、使能方舟多线程检测

5、启用方舟多线程检测

6、方舟异常检测码

10 使用方舟异常信息增强检测

10.1 概述

10.2 使能方舟native模块加载异常信息增强

10.3 启用方舟native模块加载异常信息增强


1 概述

应用稳定性是影响用户体验的重要因素之一,常见的稳定性问题包括:崩溃、应用Freeze、内存泄漏、内存越界等。HarmonyOS系统提供了完善的稳定性治理框架,围绕着稳定性治理活动,系统提供了丰富的工具,工具涵盖范围包括开发、调试、应用上线以及线上运维等完整生命阶段。

具体用于稳定性治理活动的工具有日志、应用事件、调用链跟踪、故障管理、观测信息剖析等。HarmonyOS生态厂家可以通过工具之一的应用事件获取相应的故障信息,进一步可以基于应用事件构造在线运维系统APM(Application Performance Management)。在开发阶段,开发者可以通过IDE调试调优工具进行快速的稳定性问题定界定位。在应用上线后,开发者可以基于APM系统进行故障分析与处理、指标度量、应用质量分析等各项运维活动,提升应用稳定性。

以下稳定性最佳实践,结合HarmonyOS生态实践要求,按照故障稳定性检测、稳定性分析、稳定性优化、稳定性运维等内容,介绍HarmonyOS生态稳定性治理的完整方案。

2  使用Asan检测内存错误

内存问题检测线程问题检测

使用方舟异常信息增强检测

  • 使用Asan检测内存错误

  • 使用HWAsan检测内存错误

  • 使用UBSan检测未定义行为

  • 使用GWP-Asan检测内存错误

  • 内存泄漏检测

  • 基础内存检测

  • 使用Tsan检测线程问题

  • 方舟运行时检测

使用方舟异常信息增强检测

2.1 背景

为追求C/C++的高性能,编译器和OS(Windows/Linux/Mac)运行框架不会对内存操作进行安全检测。针对该场景,DevEco Studio集成ASan(Address-Sanitizer)为开发者提供面向C/C++的地址越界检测能力,并通过FaultLog展示错误的堆栈详情及导致错误的代码行。常见的Asan异常检测类型有:heap-buffer-overflow、stack-buffer-overflow/underflow、heap-use-after-free和double-free等,详情请参考ASan异常检测类型部分。

2.2 原理概述

Asan工具主要由插桩模块和动态运行库模块构成。

插桩模块主要使能为:

  • 对于每次内存分配(如malloc或new),Asan会插入代码来分配额外的“红区”(redzones)作为边界检查,以及可能的“影子内存”来跟踪内存的可访问性。
  • 对于每次内存释放(如free或delete),Asan将内存放入隔离区,会插入代码来标记内存为已释放,防止使用已释放的内存。
  • 对于每次内存访问(读/写),Asan会插入代码来检查访问的内存是否有效,比如是否越界或是否访问了未初始化的内存。

动态运行库主要使能为:

  • 将路径中的malloc/free函数进行了替换,在malloc函数中增加了分配redzone内存的部分,将redzone对应的shadow memory进行加锁(poison),主要的内存区域对应的影子内存不进行加锁。
  • 将free/delete将所有分配的内存区域加锁(poison),并放入隔离区队列(FIFO)中,以确保一段时间内不会再被分配。

总结:Asan工具在编译时对代码进行插桩,在运行时关注相关内存的shadow memory值,从而判断是否有内存错误的产生。

2.3 使用约束

  • 如果应用内的任一模块使能ASan,那么entry模块需同时使能ASan。如果entry模块未使能ASan,该应用在启动时将闪退,出现CPP Crash报错。
  • ASan和其他内存检测工具能力互斥,不能同时开启,TSan、UBSan、HWASan、GWP-Asan五个只能开启其中一个。

2.4 配置参数

ASAN_OPTIONS:在运行时配置ASan的行为,包括设置检测级别、输出格式、内存错误报告的详细程度等。常用参数请查看表1。

ASAN_OPTIONS支持在app.json5中配置,也支持在Run/Debug Configurations中配置。app.json5的优先级较高,即两种方式都配置后,以app.json5中的配置为准。

2.4.1 在app.json5中配置环境变量

打开AppScope > app.json5文件,添加配置示例如下。

{
  "app": {
    "appEnvironments": [
      {
        "name": "ASAN_OPTIONS",
        "value": "log_exe_name=true abort_on_error=0 print_cmdline=true" // 示例仅供参考,具体以实际为准
      },
    ],
    ...
  }
}

 配置Asan参数时,建议带上以下各项,并设置成默认值,然后按需进行修改。

allow_user_segv_handler=1
detect_odr_violation=0
alloc_dealloc_mismatch=0
allocator_may_return_null=1
detect_container_overflow=0
abort_on_error=0
halt_on_error=0
report_globals=0
handle_abort=0
allow_user_poisoning=1
log_exe_name=true
handle_segv=0
detect_stack_use_after_return=0
print_module_map=2
handle_sigbus=0

2.4.2 在Run/Debug Configurations中配置环境变量

在Run/Debug Configurations中配置环境变量

具体请查看配置环境变量。

表1 常用参数

参数

默认值

是否必填

含义

log_exe_name

true

不可修改。指定内存错误日志中是否包含执行文件的名称。

log_path

/dev/asanlog/asan.log

ROM版本小于NEXT.0.0.68时必填,值不可修改;NEXT.0.0.68及以上版本不再需要该参数。

abort_on_error

0

指定在打印错误报告后调用abort()或_exit()。

  • false(1):打印错误报后使用_exit()结束进程
  • true(0):打印错误报后使用abort()结束进程

strip_path_prefix

-

内存错误日志的文件路径中去除所配置的前缀。

如:/data/storage/el1

detect_stack_use_after_return

0

指定是否检查访问指向已被释放的栈空间。

  • true(1):检查。
  • false(0):不检查。

halt_on_error

0

检测内存错误后是否继续运行。

  • 0表示继续运行。
  • 1表示结束运行。

malloc_context_size

-

内存错误发生时,显示的调用栈层数。

suppressions

""

屏蔽文件名。

handle_segv

-

检查段错误。

handle_sigill

-

检查SIGILL信号。

quarantine_size_mb

256

指定检测访问指向已被释放的栈空间错误的隔离区大小。

更多可配置参数请参见asan_flags。

2.5 Asan使能

可通过以下两种方式使能ASan。每种方式分为IDE场景和流水线场景。

方式一

IDE场景

  1. 在运行调试窗口,点击Diagnostics,勾选Address Sanitizer

如果有引用本地library,需在library模块的build-profile.json5文件中,配置arguments字段值为“-DOHOS_ENABLE_ASAN=ON”,表示以ASan模式编译so文件。

流水线场景

在hvigorw命令后加上ohos-debug-asan=true的选项,执行hvigorw命令,更多options参考hvigorw文档

 
  1. hvigorw [taskNames...] ohos-debug-asan=true <options>

同上,如果有引用本地library,需在library模块的build-profile.json5文件中,配置arguments字段值为“-DOHOS_ENABLE_ASAN=ON”,表示以ASan模式编译so文件。

方式二

IDE场景

1. 修改工程目录下AppScope/app.json5,添加ASan配置开关

  1. "asanEnabled": true

2. 设置模块级构建ASan插桩。

在需要使能ASan的模块中,通过添加构建参数开启ASan检测插桩,在对应模块的模块级build-profile.json5中添加命令参数:

 
  1. "asanEnabled": true

说明

该参数未配置不会报错,但是除包含malloc和free函数等少数内存错误外,出现其他需要插桩检测的内存错误时,ASan无法检测到错误。

流水线场景

在AppScope/app.json5和模块build-profile.json5配置对应asan项后,可直接执行hvigorw命令,更多options参考hvigorw文档

 
  1. hvigorw [taskNames...] <options>

说明

如果按方式一勾选以后,配置app.json5中的为false,Asan仍生效

运行ASan

1. 运行或调试当前应用。

2. 当程序出现内存错误时,弹出ASan log信息,点击信息中的链接即可跳转至引起内存错误的代码处(非release版本)。release版本本地无工程代码,可以使用AnalyzeStackTrace功能,提供要解析堆栈的so,解析结果为源码地址。

2.6 ASan异常检测类型

当前提供案例在debug应用中可产生ASan,release应用因为在编译构建期间会进行代码优化,不一定会产生异常。

说明

对于release版本,本地无工程代码,可以使用AnalyzeStackTrace功能,提供要解析堆栈的so,解析结果为源码地址。

常见Asan检测异常码

说明

可能的Crash信号

heap-buffer-overflow

超出堆上分配的缓冲区范围

SIGSEGV(段错误)、SIGABRT(异常终止)、SIGILL(非法指令)、SIGFPE(浮点异常)、SIGTRAP(陷阱)

stack-buffer-overflow/underflow

超出栈上分配的缓冲区范围

SIGSEGV(段错误)、SIGABRT(异常终止)、SIGILL(非法指令)、SIGFPE(浮点异常)、SIGTRAP(陷阱)

heap-use-after-free

使用了释放后的堆内存

SIGSEGV(段错误)、SIGABRT(异常终止)

stack-use-after-scope

栈变量在作用域外被使用

SIGSEGV(段错误)、SIGABRT(异常终止)

attempt-free-nonallocated-memory

尝试释放了非堆对象(non-heap object)或未分配内存

SIGSEGV(段错误)、SIGABRT(异常终止)

double-free

重复释放内存

SIGSEGV(段错误)、SIGABRT(异常终止)

heap-buffer-overflow

背景

访问堆内存越界(上下界)

错误代码实例

int heapBufferOverflow() {
    char *buffer;
    buffer = (char *)malloc(10);
    *(buffer + 11) = 'n';
    *(buffer + 12) = 'n';
    free(buffer);
    return buffer[1];
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:AddressSanitizer:heap-buffer-overflow

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:heap-buffer-overflow
Fault thread info:
==appspawn==17140==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0060019ca8da at pc 0x005ec33c3250 bp 0x007fe9c392f0 sp 0x007fe9c392e8
WRITE of size 1 at 0x0060019ca8da thread T0 (easandemo_api12)
    #0 0x5ec33c324c  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x324c) (BuildId: 4f31be36da7e9bc00c9b7bad563e7ccfec4d0347)
    #1 0x5ec33c38e0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x38e0) (BuildId: 4f31be36da7e9bc00c9b7bad563e7ccfec4d0347)
    #2 0x7f850b3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
0x0060019ca8da is located 0 bytes to the right of 10-byte region [0x0060019ca8d0,0x0060019ca8da)
allocated by thread T0 (easandemo_api12) here:
    #0 0x7f82652758  (/system/lib64/libclang_rt.asan.so+0xd2758) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ec33c31ec  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x31ec) (BuildId: 4f31be36da7e9bc00c9b7bad563e7ccfec4d0347)
    #2 0x5ec33c38e0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x38e0) (BuildId: 4f31be36da7e9bc00c9b7bad563e7ccfec4d0347)
    #3 0x7f850b3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
    #4 0x5ec6a1bcd8  (/system/lib64/module/arkcompiler/stub.an+0x1dccd8)
    #5 0x5ec6847f4c  (/system/lib64/module/arkcompiler/stub.an+0x8f4c)

修改方法

注意数组的长度,不要访问越界

推荐建议

已知大小的数组注意访问不要越界,访问已知大小数组前先判断访问位置是否落在边界外

stack-buffer-overflow

背景

访问越栈内存上界

错误代码实例

int stackBufferOverflow() {
    int subscript = 43;
    char buffer[42];
    buffer[subscript] = 42;
    return 0;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:AddressSanitizer:stack-buffer-overflow

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:ASAN
=================================================================
==appspawn==8518==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdbb8a1deb at pc 0x7f4d58ec95a6 bp 0x7ffdbb8a1d90 sp 0x7ffdbb8a1d88
WRITE of size 1 at 0x7ffdbb8a1deb thread T0 (e.mycppasandemo)
    #0 0x7f4d58ec95a5  (/data/storage/el1/bundle/libs/x86_64/libentry.so+0x95a5) (BuildId: 5f94771f88ac6f3ed4c63d5c52598c94dc7bca66)
    #1 0x7f4d58eca203  (/data/storage/el1/bundle/libs/x86_64/libentry.so+0xa203) (BuildId: 5f94771f88ac6f3ed4c63d5c52598c94dc7bca66)
    #2 0x7f4dc9c0378f  (/system/lib64/platformsdk/libace_napi.z.so+0x4378f) (BuildId: 88b8b49edb64385b2d6b854950877489)

Address 0x7ffdbb8a1deb is located in stack of thread T0 (e.mycppasandemo) at offset 75 in frame
    #0 0x7f4d58ec948f  (/data/storage/el1/bundle/libs/x86_64/libentry.so+0x948f) (BuildId: 5f94771f88ac6f3ed4c63d5c52598c94dc7bca66)

修改方法

访问索引不应大于上界。

推荐建议

访问索引不应大于上界。

stack-buffer-underflow

背景

访问越栈内存下界

错误代码实例

int stackBufferUnderflow() {
    int subscript = -1;
    char buffer[42];
    buffer[subscript] = 42;
    return 0;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:AddressSanitizer:stack-buffer-underflow

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:stack-buffer-underflow
Fault thread info:
==appspawn==17039==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x007e07c6027f at pc 0x007f1bdc3994 bp 0x007e07c60250 sp 0x007e07c60248
WRITE of size 1 at 0x007e07c6027f thread T0 (easandemo_api12)
    #0 0x7f1bdc3990  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3990) (BuildId: e34349d8024d23ca83c7c7c3b9f69505d2beb3a0)
    #1 0x7f1bdc3fa8  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3fa8) (BuildId: e34349d8024d23ca83c7c7c3b9f69505d2beb3a0)
    #2 0x7e838339a8  (/system/lib64/platformsdk/libace_napi.z.so+0x339a8) (BuildId: f48b24ee6f099a2107ef30b4ace050de)
Address 0x007e07c6027f is located in stack of thread T0 (easandemo_api12) at offset 31 in frame
    #0 0x7f1bdc3820  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3820) (BuildId: e34349d8024d23ca83c7c7c3b9f69505d2beb3a0)

修改方法

访问索引不应小于下界。

推荐建议

访问索引不应小于下界。

heap-use-after-free

背景

当指针指向的内存被释放后,仍然通过该指针访问已经被释放的内存,就会触发heap-use-after-free。

错误代码实例

#include <stdlib.h>
int main() {
    int *array = new int[5];
    delete[] array;
    return array[5]; 
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,显示reason为AddressSanitizer:heap-use-after-free

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:heap-use-after-free
Fault thread info:
==appspawn==10126==ERROR: AddressSanitizer: heap-use-after-free on address 0x006121870ce4 at pc 0x005ee1ec321c bp 0x007ff5959310 sp 0x007ff5959308
READ of size 4 at 0x006121870ce4 thread T0 (easandemo_api12)
    #0 0x5ee1ec3218  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3218) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #1 0x5ee1ec3714  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3714) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #2 0x7fa9133780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
0x006121870ce4 is located 0 bytes to the right of 20-byte region [0x006121870cd0,0x006121870ce4)
freed by thread T0 (easandemo_api12) here:
    #0 0x7fa569f0c4  (/system/lib64/libclang_rt.asan.so+0xdf0c4) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ee1ec31b8  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x31b8) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #2 0x5ee1ec3714  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3714) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #3 0x7fa9133780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
    #4 0x5ee571bcd8  (/system/lib64/module/arkcompiler/stub.an+0x1dccd8)
    #5 0x5ee5547f4c  (/system/lib64/module/arkcompiler/stub.an+0x8f4c)
previously allocated by thread T0 (easandemo_api12) here:
    #0 0x7fa569e888  (/system/lib64/libclang_rt.asan.so+0xde888) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ee1ec3194  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3194) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #2 0x5ee1ec3714  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3714) (BuildId: 3b906822a911c973ab89188662a589eeedf639a4)
    #3 0x7fa9133780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
    #4 0x5ee571bcd8  (/system/lib64/module/arkcompiler/stub.an+0x1dccd8)
    #5 0x5ee5547f4c  (/system/lib64/module/arkcompiler/stub.an+0x8f4c)

修改方法

已经释放的指针不要再使用,将指针设置为NULL/nullptr。

推荐建议

使用智能指针,或实现一个free()函数的替代版本或者 delete析构器来保证指针的重置。

stack-use-after-scope

背景

栈变量在作用域之外被使用。

错误代码实例

int *gp;
bool b = true;
int stackUseAfterScope() {
    if (b) {
        int x[5];
        gp = x + 1;
    }
    return *gp;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:AddressSanitizer:stack-use-after-scope

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:stack-use-after-scope
Fault thread info:
==appspawn==7494==ERROR: AddressSanitizer: stack-use-after-scope on address 0x007ffa213b44 at pc 0x005ebf0431e4 bp 0x007ffa213b10 sp 0x007ffa213b08
READ of size 4 at 0x007ffa213b44 thread T0 (easandemo_api12)
    #0 0x5ebf0431e0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x31e0) (BuildId: cf28a04a79da128bc344416e8d5f860e3e22f495)
    #1 0x5ebf0437f4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x37f4) (BuildId: cf28a04a79da128bc344416e8d5f860e3e22f495)
    #2 0x7f868b3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
Address 0x007ffa213b44 is located in stack of thread T0 (easandemo_api12) at offset 36 in frame
    #0 0x5ebf043024  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3024) (BuildId: cf28a04a79da128bc344416e8d5f860e3e22f495)

修改方法

在作用域内使用该变量。

推荐建议

注意变量的作用域。

attempt-free-nonallocated-memory

背景

尝试释放了非堆对象(non-heap object)或未分配内存。

错误代码实例

int main() {
    int value = 42;
    free(&value);
    return 0;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:

AddressSanitizer: attempting free on address which was not malloc()-ed

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:attempting
Fault thread info:
==appspawn==20382==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x007fd59ae8c0 in thread T0 (easandemo_api12)
    #0 0x7f83a92630  (/system/lib64/libclang_rt.asan.so+0xd2630) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ec45c3120  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3120) (BuildId: 743109db136e66f875a7bc47db74a8095758d4ff)
    #2 0x5ec45c3720  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3720) (BuildId: 743109db136e66f875a7bc47db74a8095758d4ff)
    #3 0x7f8a2f3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
Address 0x007fd59ae8c0 is located in stack of thread T0 (easandemo_api12) at offset 32 in frame
    #0 0x5ec45c2fbc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2fbc) (BuildId: 743109db136e66f875a7bc47db74a8095758d4ff)

修改方法

不要对非堆对象或未分配的内存使用free()函数。

推荐建议

不要对非堆对象或未分配的内存使用free()函数。

double-free

背景

重复释放内存

错误代码实例

int main() {
    int *x = new int[42];
    delete [] x;
    delete [] x;
    return 0;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报ASan,包含字段:AddressSanitizer: attempting double-free

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:AddressSanitizer:attempting
Fault thread info:
==appspawn==9596==ERROR: AddressSanitizer: attempting double-free on 0x0061303ecc10 in thread T0 (easandemo_api12):
    #0 0x7fb3292630  (/system/lib64/libclang_rt.asan.so+0xd2630) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ef0b82ef4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2ef4) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #2 0x5ef0b834bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x34bc) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #3 0x7fb4af3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
0x0061303ecc10 is located 0 bytes inside of 32-byte region [0x0061303ecc10,0x0061303ecc30)
freed by thread T0 (easandemo_api12) here:
    #0 0x7fb3292630  (/system/lib64/libclang_rt.asan.so+0xd2630) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ef0b82eec  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2eec) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #2 0x5ef0b834bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x34bc) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #3 0x7fb4af3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
    #4 0x5ef459bcd8  (/system/lib64/module/arkcompiler/stub.an+0x1dccd8)
    #5 0x5ef43c7f4c  (/system/lib64/module/arkcompiler/stub.an+0x8f4c)
previously allocated by thread T0 (easandemo_api12) here:
    #0 0x7fb3292758  (/system/lib64/libclang_rt.asan.so+0xd2758) (BuildId: aeec20776cc4e8f96db6c6b5603bb49748cc20ff)
    #1 0x5ef0b82ee0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2ee0) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #2 0x5ef0b834bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x34bc) (BuildId: 5b44777ffb29e6665852feeb6f23712aef424077)
    #3 0x7fb4af3780  (/system/lib64/platformsdk/libace_napi.z.so+0x33780) (BuildId: 25f88248f530c20439061db9eb4ed152)
    #4 0x5ef459bcd8  (/system/lib64/module/arkcompiler/stub.an+0x1dccd8)
    #5 0x5ef43c7f4c  (/system/lib64/module/arkcompiler/stub.an+0x8f4c)

修改方法

已经释放一次的指针,不要再重复释放。

推荐建议

变量定义声明时初始化为NULL,释放内存后也应立即将变量重置为NULL,这样每次释放之前都可以通过判断变量是否为NULL来判断是否可以释放。

3 使用HWAsan检测内存错误

3.1 原理概述

HWASan是Hardware-Assisted Address Sanitizer的简称,它是Clang LLVM提供的一套内存错误检测系统,用来检测C/C++中常见的内存访问错误,相比之前的Asan(Address Sanitizer),它在性能、内存上有不小提升,依赖于编译器的Address Tagging特性,该特性允许应用程序自定义数据存储到虚拟地址的最高8位,当CPU操作该虚拟地址时会自动忽略它。HWASan工具检测地址越界问题的原理如下,

  1. 将整个虚拟内存区间按照16:1的比例,划分为user memory和shadow memory;同时,无论是堆上、栈上还是全局对象,其内存起始地址都按照16字节对齐,即保证每16字节的user memory都能映射到1字节的shadow memory;
  2. 分配对象的时候,随机分配一个8位的随机tag标记到该对象的虚拟地址最高8位,同时该tag也会保存到其映射的shadow memory中;
  3. 编译器在每个内存地址的load/store之前都会插入检查指令,用于确认操作地址的最高8位保存的tag与其映射的shadow memory中的tag值是否一致;
  4. 对象回收后也会重新分配一个随机值,保存到其映射的shadow memory中,当出现内存越界行为时,就会检测到tag值不一致的异常;

注意,当分配的对象小于16字节时,多余的内存不会再分配给其它对象,此时shadow memory中保存的是对象所占内存的实际字节数,而tag值则保存在16字节的最后一个字节里面。

常见的HWASan异常检测类型有stack-buffer-overflow/underflow,stack-use-after-scope,stack-use-after-return,heap-buffer-overflow等,详见HWasan异常检测类型。

3.2 功能介绍

HWASan能检测到ASan所能检测到的同一系列错误:

  • 堆栈和堆缓冲区上溢/下溢。
  • 释放之后的堆使用情况。
  • 重复释放/错误释放。

和ASan相比,HWASan具有以下优点:

  • HWASan不需要安全区来检测buffer overflow,既极大地降低了工具对于内存的消耗,也不会出现ASan中某些overflow检测不到的情况。
  • HWASan不需要隔离区来检测UseAfterFree,因此不会出现ASan中某些UseAfterFree检测不到的情况。
  • 此外,HWASan还可以检测返回之后的堆栈使用情况。

3.3 使用约束

  • ROM版本5.0.0.107及以上支持。
  • ASan、TSan、HWASan、UBsan不能同时开启,四个只能开启其中一个。

3.4 配置HWASan

可通过方式一和方式二使能HWAsan,每种方式分为IDE场景和流水线场景。若无法通过方式一或方式二使能HWAsan,请参考方式三。

方式一

IDE场景

点击Run > Edit Configurations >Diagnostics,勾选Hardware-Assisted Address Sanitizer开启检测。

流水线场景

在hvigorw命令后加上ohos-enable-hwasan=true的选项,执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...] ohos-enable-hwasan=true  <options> 

方式二

IDE场景

1. 修改工程目录下的AppScope/app.json5文件,添加HWASan配置开关。

"hwasanEnabled": true

2. 在需要使能HWASan的模块中,通过添加构建参数开启HWASan检测插桩,在对应模块的模块级build-profile.json5中添加命令参数:

"arguments": "-DOHOS_ENABLE_HWASAN=ON"

 

流水线场景

在AppScope/app.json5和模块build-profile.json5配置对应HWASan项后,可直接执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...]  <options> 

说明

如果按方式一勾选以后,配置app.json5中的为false,HWASan仍生效

方式三

对于5.0.5.200版本之前的DevEco Studio,开启HWAsan功能的步骤如下:

(1)参考方式二的字段配置方式,配置arguments字段值为“-DOHOS_ENABLE_HWASAN=ON”,表示以Asan模式编译so文件

(2)确认sdk目录选ohos.toolchain.cmake中是否存在如下信息,将OHOS_ENABLE_HWASAN下面的信息修改到如下选项。

//文件路径:ohos/ndk/cmake/ohos.toolchain.cmake
if (OHOS_ENABLE_HWASAN STREQUAL ON AND OHOS_ARCH STREQUAL arm64-v8a)
    list(APPEND OHOS_C_COMPILER_FLAGS
        -shared-libasan
        -fsanitize=hwaddress
        -mllvm -hwasan-globals=0
        -fno-emulated-tls
        -fno-omit-frame-pointer)
    if (DEFINED OHOS_ASAN_BLACKLIST)
        list(APPEND OHOS_C_COMPILER_FLAGS -fsanitize-blacklist="${OHOS_ASAN_BLACKLIST}")
    endif()
endif()

3.5 运行HWASan

  1. 运行或调试当前应用。

2. 当程序出现内存错误时,弹出HWASan log信息,点击信息中的链接即可跳转至引起内存错误的代码处。

3.6 HWasan异常检测类型

stack tag-mismatch

背景

"stack tag-mismatch"在HWASan中指的是栈内存标签不匹配错误。这种错误通常发生在访问栈内存时,指针携带的标签与栈内存中存储的标签不一致,触发其异常检测码字的异常类型有:stack-buffer-overflow/underflow、stack-use-after-return和stack-use-after-scope

错误代码实例

// stack-buffer-overflow
int stackBufferOverflow() {
    int subscript = 43;
    char buffer[42];
    buffer[subscript] = 42;
    return 0;
}

// stack-buffer-underflow
int stackBufferUnderflow() {
    int subscript = -1;
    char buffer[42];
    buffer[subscript] = 42;
    return 0;
}

// stack-use-after-return
int *ptr;
__attribute__((noinline))
void FunctionThatEscapesLocalObject() {
  int local[100];
  ptr = &local[0];
}
int main(int argc, char **argv) {
  FunctionThatEscapesLocalObject();
  return ptr[argc];
}



// stack-use-after-scope
int *gp;
bool b = true;
int stackUseAfterScope() {
    if (b) {
        int x[5];
        gp = x + 1;
    }
    return *gp;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启ASan检测后,触发demo中的函数,应用闪退报HWASAN,包含字段:

HWAddressSanitizer: tag-mismatch on address

Cause: stack tag-mismatch

定位思路

如果有工程代码,直接开启HWAsan检测,debug模式运行后复现该错误,可以触发WAsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:HWASAN
==appspawn==61390==ERROR: HWAddressSanitizer: tag-mismatch on address 0x007eb11cc05f at pc 0x005acf446438
WRITE of size 1 at 0x007eb11cc05f tags: f1/00 (ptr/mem) in thread T0
    #0 0x5acf446438  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6438) (BuildId: 4b0b8d2189a7eb99fff81c6bc8889dfefd4af4a1)
    #1 0x5acf446c08  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6c08) (BuildId: 4b0b8d2189a7eb99fff81c6bc8889dfefd4af4a1)
    #2 0x5ab397cdc8  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)

Cause: stack tag-mismatch
Address 0x007eb11cc05f is located in stack of thread T0
Thread: T0 0x005b00002000 stack: [0x007eb09d2000,0x007eb11d1000) sz: 8384512 tls: [0x005aacd0fa98,0x005aacd10279)
Previously allocated frames:
  record_addr:0x5ab053c788 record:0xcc0a005acf4463c0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x63c0) (BuildId: 4b0b8d2189a7eb99fff81c6bc8889dfefd4af4a1)
  record_addr:0x5ab053c780 record:0xcc1a005acf446a68  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6a68) (BuildId: 4b0b8d2189a7eb99fff81c6bc8889dfefd4af4a1)
  record_addr:0x5ab053c778 record:0xcb81005acf4866ac  (/data/storage/el1/bundle/libs/arm64/libmainpage.so+0x66ac) (BuildId: 10ffa0d04cbc27e12a59f658b5932674185d63ee)
  record_addr:0x5ab053c770 record:0xcc87005acf4465b4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x65b4) (BuildId: 4b0b8d2189a7eb99fff81c6bc8889dfefd4af4a1)

修改方法

stack-buffer-overflow/underflow访问索引要落在给定的范围内

stack-use-after-scope:在作用域内使用定义的变量。

stack-use-after-return:在作用域内使用局部变量

推荐建议

stack-buffer-overflow/underflow:访问索引不要超过给定的上界/下界

stack-use-after-scope: 避免在作用域外使用局部变量

stack-use-return: 如果需要在函数返回后继续使用某些数据,考虑将它们存储在静态或全局变量中

heap-buffer-overflow

背景

访问堆内存越界(上下界),触发其异常检测码字的异常类型有:heap-buffer-overflow、heap-buffer-underflow

错误代码实例

// heap-buffer-overflow
void heapBufferOverflow() {
    char *buffer;
    buffer = (char *)malloc(10);
    *(buffer + 11) = 'n';
    *(buffer + 12) = 'n';
    free(buffer);
}

// heap-buffer-underflow
void heapBufferUnderflow() {
    char *buffer;
    buffer = (char *)malloc(10);
    *(buffer - 11) = 'n';
    *(buffer - 12) = 'n';
    free(buffer);
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启HWASan检测后,触发demo中的函数,应用闪退报HWASan,包含字段:

HWAddressSanitizer: tag-mismatch

Cause: heap-buffer-overflow

定位思路

如果有工程代码,直接开启HWASan检测,debug模式运行后复现该错误,可以触发HWASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:HWASAN
==appspawn==8344==ERROR: HWAddressSanitizer: tag-mismatch on address 0x000100760332 at pc 0x005adb286570
WRITE of size 1 at 0x000100760332 tags: cb/08(44) (ptr/mem) in thread T0
    #0 0x5adb286570  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6570) (BuildId: 4724eac5a66f4994a03c023a9958da3897de5f16)
    #1 0x5adb286c8c  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6c8c) (BuildId: 4724eac5a66f4994a03c023a9958da3897de5f16)
    #2 0x5abd63cdc8  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)

[0x000100760320,0x000100760340) is a small allocated heap chunk; size: 32 offset: 18

Cause: heap-buffer-overflow
0x000100760332 is located 110 bytes to the left of 10-byte region [0x0001007603a0,0x0001007603aa)
allocated here:
    #0 0x5a394625ec  (/system/lib64/libclang_rt.hwasan.so+0x225ec) (BuildId: 2b2455ae77181bfdfbfa9561b4e6e3ea376ec93b)
    #1 0x5adb286548  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6548) (BuildId: 4724eac5a66f4994a03c023a9958da3897de5f16)
    #2 0x5adb286c8c  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6c8c) (BuildId: 4724eac5a66f4994a03c023a9958da3897de5f16)
    #3 0x5abd63cdc8  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)
    #4 0x5ad4373b6c  (/system/lib64/module/arkcompiler/stub.an+0x3f3b6c)
    #5 0x5ad3f8be8c  (/system/lib64/module/arkcompiler/stub.an+0xbe8c)


修改方法

注意数组长度,不能越界

heap-buffer-overflow: 数组访问位置不要越上界

heap-buffer-underflow: 访问数组位置不要越下界

推荐建议

已知大小的数组注意访问不要越界,访问已知大小数组前先判断访问位置是否落在边界外

Use-after-free

背景

触发其异常检测码字的异常类型有:heap-use-after-free、double-free

heap-use-after-free: 当指针指向的内存被释放后,仍然通过该指针访问已经被释放的内存,就会触发heap-use-after-free

double-free: 重复释放内存

错误代码实例

// heap-use-after-free
int useAfterFree(int argc) {
    int *array = new int[100];
    delete[] array;
    return array[argc];
}

// double free
void doubleFree() {
    char *p = (char *)malloc(32 * sizeof(char));
    free(p);
    free(p);
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启HWAsan检测后,触发demo中的函数,应用闪退报HWAsan,包含字段:

HWAddressSanitizer: tag-mismatch

Cause: use-after-free

定位思路

如果有工程代码,直接开启ASan检测,debug模式运行后复现该错误,可以触发ASan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:HWASAN
==appspawn==10741==ERROR: HWAddressSanitizer: tag-mismatch on address 0x000d00036a68 at pc 0x005ab24864c4
READ of size 4 at 0x000d00036a68 tags: ae/6e (ptr/mem) in thread T0
    #0 0x5ab24864c4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x64c4) (BuildId: e073772c81ab52894a21e4190a263680198f3e9c)
    #1 0x5ab2486c84  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6c84) (BuildId: e073772c81ab52894a21e4190a263680198f3e9c)
    #2 0x5a96b7cdc8  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)

[0x000d00036a60,0x000d00036c00) is a small unallocated heap chunk; size: 416 offset: 8

Cause: use-after-free
0x000d00036a68 is located 8 bytes inside of 400-byte region [0x000d00036a60,0x000d00036bf0)
freed by thread T0 here:
    #0 0x5a90a6844c  (/system/lib64/libclang_rt.hwasan.so+0x2844c) (BuildId: 2b2455ae77181bfdfbfa9561b4e6e3ea376ec93b)
    #1 0x5ab2486494  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6494) (BuildId: e073772c81ab52894a21e4190a263680198f3e9c)
    #2 0x5ab2486c84  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x6c84) (BuildId: e073772c81ab52894a21e4190a263680198f3e9c)
    #3 0x5a96b7cdc8  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)
    #4 0x5aab6b3b6c  (/system/lib64/module/arkcompiler/stub.an+0x3f3b6c)
    #5 0x5aab2cbe8c  (/system/lib64/module/arkcompiler/stub.an+0xbe8c)

修改方法

heap-use-after-free: 已经释放的指针不要再使用,将指针设置为NULL/nullptr。

double-free: 已经释放一次的指针,不要再重复释放。

推荐建议

heap-use-after-free: 使用智能指针,或实现一个free()函数的替代版本或者 delete析构器来保证指针的重置。

double-free: 变量定义声明时初始化为NULL,释放内存后也应立即将变量重置为NULL,这样每次释放之前都可以通过判断变量是否为NULL来判断是否可以释放。

4 使用UBSan检测未定义行为

4.1 原理概述

代码中出现未定义行为,最初可能不会产生任何问题,但是随着代码的复杂度提高,未定义行为可能造成程序崩溃或发生错误,检测出根源会变得更加困难。UBSan(Undefined Behavior Sanitizer)可以检测代码中出现的未定义行为,帮助用户清除未定义行为引起的运行时错误。

常见未定义UBSan异常检测类型store to misaligned address,member access within misaligned address,not a valid value for type 'bool',division by zero等,详见UBSan异常检测类型。

4.2 使用约束

ASan、TSan、UBSan、HWASan不能同时开启,四个只能开启其中一个。

4.3 UBsan使能

可通过以下两种方式使能UBSan。每种方式分为IDE场景和流水线场景。

方式一

IDE场景

点击Run > Edit Configurations > Diagnostics,勾选UndefinedBehaviorSanitizer开启检测。

流水线场景

在hvigorw命令后加上ohos-enable-ubsan=true的选项,执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...] ohos-enable-ubsan=true  <options> 

方式二

IDE场景

在需要使能UBSan的模块中,通过添加构建参数开启UBSan检测插桩,在对应模块的模块级build-profile.json5中添加命令参数:

"arguments": "-DOHOS_ENABLE_UBSAN=ON"

流水线场景

在AppScope/app.json5和模块build-profile.json5配置对应UBsan项后,可直接执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...]  <options> 

说明

如果按方式一勾选以后,配置app.json5中的为false,UBsan仍生效

4.4 UBSan异常检测类型

store to misaligned address

背景

变量使用了不对齐的指针,或未对齐的引用。

错误代码实例

int8_t *buffer = static_cast<int8_t*> (malloc(64)) ;
int32_t *pointer = (int32_t *)(buffer + 1);
*pointer = 42; 

影响

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数,faultlog报UBSAN,包含字段:runtime error: store to misaligned address

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:8:5: runtime error: store to misaligned address 0x005acba55c01 for type 'int32_t' (aka 'int'), which requires 4 byte alignment
0x005acba55c01: note: pointer points here
 00 00 00  00 00 00 00 00 00 00 00  1f dd b2 e1 57 0f 44 58  08 6e 61 6d 65 00 00 00  a0 86 01 00 00
              ^ 
    #0 0x5bd3f020e8  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x20e8) (BuildId: cb738bedadc1fbdf663f3584c3546b3a16cf896d)
    #1 0x5ab8a7d908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5acfc6ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5acf84be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:8:5 in 
==com.example.mycppubsan==25467==Process memory map follows:
    0x001a60000000-0x001a90000000    [anon:ArkTS MemPoolCache]
    0x002890000000-0x002890080000    [anon:ArkTS Heap25467non movable space]

修改方法

确保指针指向的内存地址是正确对齐的。例如,如果一个类型应该4字节对齐,那么它的地址应该是4的倍数。

推荐建议

尽量不要用地址加偏移量的方式给指针赋值,确保所得指针字节对齐

member access within misaligned address

背景

成员(如struct)使用了不对齐的指针,或未对齐的引用。

错误代码实例

struct A {
    int32_t i32;
    int64_t i64;
};

int8_t *buffer = static_cast<int8_t*>(malloc(32));
struct A *pointer = (struct A *)(buffer + 1);
pointer->i32 = 7;

影响

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数,faultlog报UBSAN,包含字段:runtime error: member access within misaligned address

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:14: runtime error: member access within misaligned address 0x005be8bbb061 for type 'struct A', which requires 8 byte alignment
0x005be8bbb061: note: pointer points here
 00 00 00  00 00 00 00 00 00 00 00  6e 6e 65 63 74 69 6f 6e  00 00 00 00 00 00 00 00  00 00 00 00 00
              ^ 
    #0 0x5cf0b42128  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2128) (BuildId: d6de121f4d1e5fef552a5ff31b02d78637bd108f)
    #1 0x5bdc73d908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5bec62ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5bec20be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:14 in 
==com.example.mycppubsan==34776==Process memory map follows:
    0x001d00000000-0x001d30000000    [anon:ArkTS MemPoolCache]

修改方法

确保指针指向的内存地址是正确对齐的。例如,如果一个类型应该4字节对齐,那么它的地址应该是4的倍数。

推荐建议

尽量不要用地址加偏移量的方式给指针赋值,确保所得指针字节对齐

not a valid value for type 'bool'

背景

使用既不是 true 也不是 false 的 bool 值。通常是由不恰当的类型转换导致的,比如把整数或指针当作 bool 来用

错误代码实例

int res = 2;
bool *predicate = (bool *)&res;
if (*predicate) { // Error: variable is not a valid Boolean
    res+=2;
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数,faultlog报UBSAN,包含字段:runtime error: member access within misaligned address

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:9: runtime error: load of value 2, which is not a valid value for type 'bool'
    #0 0x5bf7842114  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2114) (BuildId: bdc801021450256f3247301024c66ba35759bd8e)
    #1 0x5adb1bd908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5af332ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5af2f0be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:9 in 
==com.example.mycppubsan==61094==Process memory map follows:
0x001bb0000000-0x001be0000000    [anon:ArkTS MemPoolCache]

修改方法

使用恰当的类型转换

推荐建议

不要使用不恰当的类型转换

index xxx out of bounds for type xxx

背景

固定长度的数组越界

错误代码实例

int array[5];
for (int i = 0; i <= 5; ++i) {
    array[i] += 1; // Error: out-of-bounds access on the last iteration
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数应用闪退,faultlog报UBSAN,包含字段:runtime error: member access within misaligned address

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:9: runtime error: index 5 out of bounds for type 'int[5]'
    #0 0x5c2e6820f0  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x20f0) (BuildId: ee21ab192e41eb5f030d33e86321164eb1171fae)
    #1 0x5b1217d908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5b2a46ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5b2a04be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:14:9 in 
==com.example.mycppubsan==555==Process memory map follows:
    0x001c60000000-0x001c90000000    [anon:ArkTS MemPoolCache]

修改方法

确保访问数组的位置不超过固定数组的最大长度

推荐建议

可以在访问数组前做一个访问位置和数组长度的校验

xxx is outside the range of representable values of type 'int'

背景

浮点数转换导致的溢出

错误代码实例

double n = 10e50;
int m = (int)n;

影响

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数,faultlog报UBSAN,包含字段:runtime error: 1e+51 is outside the range of representable values of type 'int'

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:13:18: runtime error: 1e+51 is outside the range of representable values of type 'int'
    #0 0x5bd2602080  (/data/storage/el1/bundle/patch_3000001/libs/arm64/libentry.so+0x2080) (BuildId: 17cfd563ada1f6699d9c7369d90e109ffae4ee1b)
    #1 0x5ab6cbd908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5ace06ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5acdc4be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:13:18 in 
==com.example.mycppubsan==9054==Process memory map follows:
    0x001ab0000000-0x001ae0000000    [anon:ArkTS MemPoolCache]

修改方法

使用更大范围的数据类型

推荐建议

强转之前检查值,确保不会溢出

division by zero

背景

÷ 0操作

错误代码实例

int sum = 10;
for (int i = 0; i < 64; ++i) {
    sum /= i; 
}

影响报错

导致程序存在安全漏洞,并有崩溃风险。

开启UBsan检测后,触发demo中的函数,faultlog报UBSAN,包含字段:runtime error: division by zero

定位思路

如果有工程代码,直接开启UBSAN检测,debug模式运行后复现该错误,可以触发UBSAN,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置

Reason:UBSAN
E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:12:14: runtime error: division by zero
    #0 0x5ba7a01ff4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x1ff4) (BuildId: 4d6b1a9e31b1ab325be3aabdd184a22476349eaf)
    #1 0x5a8edbd908  (/system/lib64/platformsdk/libace_napi.z.so+0x3d908) (BuildId: fbb88ca45aa4ffefe148b9838dfd0db7)
    #2 0x5aa366ca98  (/system/lib64/module/arkcompiler/stub.an+0x42ca98)
    #3 0x5aa324be54  (/system/lib64/module/arkcompiler/stub.an+0xbe54)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior E:/MyCppUbsan/entry/src/main/cpp/napi_init.cpp:12:14 in 
==com.example.mycppubsan==24346==Process memory map follows:
    0x001170000000-0x0011a0000000    [anon:ArkTS MemPoolCache]

修改方法

确保除数不为0

推荐建议

增加对除数的非零判断

5 使用GWP-Asan检测内存错误

5.1 背景

当前在系统上定位内存的一些非法行为的时候,可以开启Asan版本来定位此类问题,但鉴于Asan开启会对性能和内存有很大的影响,所以Asan的能力不能部署到正式生产环境中,GWP-Asan可以帮助开发者在性能影响很小的情况下检查到部分内存使用的非法行为,正式因为性能影响较小,GWP-Asan会被部署到正式环境中,避免了正式环境中出现内存问题时再去使用Asan版本做二次复现。

5.2 原理概述

GWP-Asan 是一种原生内存分配器功能,可帮助查找释放后使用和堆缓冲区溢出 bug,启用后,GWP-Asan 会拦截随机选择的堆分配子集,并将其放入特殊区域,以便捕获难以检测到的堆内存损坏错误。只要用户足够多,即使是在低采样率,也可以发现常规测试未能发现的堆内存安全错误。常见的GWP-Asan异常检测类型有:double free,user_after_free,invalid free left等,详见GWPAsan异常检测类型部分。

5.3 使用约束

ASan、TSan、UBSan、HWASan、GWP-Asan不能同时开启,五个只能开启其中一个。

5.4 GWP-Asan使能

在app.json5中

5.5 Gwp-Asan异常检测类型

下面给出异常检测码和相关faultlog

double free

*** GWP-ASan detected a memory error ***
Double Free at 0x7f9c31efe0 (a 20-byte allocation) by thread 11725 here:
  #0 0x7f9c548958 (/lib/ld-musl-aarch64.so.1+0x1ea958)
  #1 0x7f9c548730 (/lib/ld-musl-aarch64.so.1+0x1ea730)
  #2 0x7f9c4810f4 (/lib/ld-musl-aarch64.so.1+0x1230f4)
  #3 0x7f9c4018d8 (/lib/ld-musl-aarch64.so.1+0xa38d8)
  #4 0x7f9c547944 (/lib/ld-musl-aarch64.so.1+0x1e9944)
  #5 0x7f9c418c64 (/lib/ld-musl-aarch64.so.1+0xbac64)
  #6 0x555dab4f24 (/data/local/tmp/double_free+0x1f24)
  #7 0x7f9c4c1668 (/lib/ld-musl-aarch64.so.1+0x163668)
  #8 0x555dab4c14 (/data/local/tmp/double_free+0x1c14)
0x7f9c31efe0 was deallocated by thread 11725 here:
  #0 0x7f9c5480f8 (/lib/ld-musl-aarch64.so.1+0x1ea0f8)
  #1 0x7f9c54799c (/lib/ld-musl-aarch64.so.1+0x1e999c)
  #2 0x7f9c418c64 (/lib/ld-musl-aarch64.so.1+0xbac64)
  #3 0x555dab4f1c (/data/local/tmp/double_free+0x1f1c)
  #4 0x7f9c4c1668 (/lib/ld-musl-aarch64.so.1+0x163668)
  #5 0x555dab4c14 (/data/local/tmp/double_free+0x1c14)
0x7f9c31efe0 was allocated by thread 11725 here:
  #0 0x7f9c5480f8 (/lib/ld-musl-aarch64.so.1+0x1ea0f8)
  #1 0x7f9c547780 (/lib/ld-musl-aarch64.so.1+0x1e9780)
  #2 0x7f9c41882c (/lib/ld-musl-aarch64.so.1+0xba82c)
  #3 0x555dab4f10 (/data/local/tmp/double_free+0x1f10)
  #4 0x7f9c4c1668 (/lib/ld-musl-aarch64.so.1+0x163668)
  #5 0x555dab4c14 (/data/local/tmp/double_free+0x1c14)
*** End GWP-ASan report ***

use_after_free

*** GWP-ASan detected a memory error ***
Use After Free at 0x7fa2ab6000 (0 bytes into a 10-byte allocation at 0x7fa2ab6000) by thread 3594 here:
  #0 0x7fa4781f18 (/lib/ld-musl-aarch64.so.1+0x1e9f18)
  #1 0x7fa4781cf0 (/lib/ld-musl-aarch64.so.1+0x1e9cf0)
  #2 0x7fa46ba6bc (/lib/ld-musl-aarch64.so.1+0x1226bc)
  #3 0x7fa463b298 (/lib/ld-musl-aarch64.so.1+0xa3298)
  #4 0x5562e886ac (/data/local/tmp/gwp_asan_use_after_free_test+0x16ac)
  #5 0x7fa46fac28 (/lib/ld-musl-aarch64.so.1+0x162c28)
  #6 0x5562e88654 (/data/local/tmp/gwp_asan_use_after_free_test+0x1654)
0x7fa2ab6000 was deallocated by thread 3594 here:
  #0 0x7fa47816b8 (/lib/ld-musl-aarch64.so.1+0x1e96b8)
  #1 0x7fa4780f5c (/lib/ld-musl-aarch64.so.1+0x1e8f5c)
  #2 0x7fa46522cc (/lib/ld-musl-aarch64.so.1+0xba2cc)
  #3 0x5562e886ac (/data/local/tmp/gwp_asan_use_after_free_test+0x16ac)
  #4 0x7fa46fac28 (/lib/ld-musl-aarch64.so.1+0x162c28)
  #5 0x5562e88654 (/data/local/tmp/gwp_asan_use_after_free_test+0x1654)
0x7fa2ab6000 was allocated by thread 3594 here:
  #0 0x7fa47816b8 (/lib/ld-musl-aarch64.so.1+0x1e96b8)
  #1 0x7fa4780d40 (/lib/ld-musl-aarch64.so.1+0x1e8d40)
  #2 0x7fa4652010 (/lib/ld-musl-aarch64.so.1+0xba010)
  #3 0x5562e886a4 (/data/local/tmp/gwp_asan_use_after_free_test+0x16a4)
  #4 0x7fa46fac28 (/lib/ld-musl-aarch64.so.1+0x162c28)
  #5 0x5562e88654 (/data/local/tmp/gwp_asan_use_after_free_test+0x1654)
*** End GWP-ASan report ***

invalid free left

*** GWP-ASan detected a memory error ***
Invalid (Wild) Free at 0x7f8551ffff (1 byte to the left of a 1-byte allocation at 0x7f85520000) by thread 11708 here:
  #0 0x7f856746b8 (/lib/ld-musl-aarch64.so.1+0x1286b8)
  #1 0x7f85674268 (/lib/ld-musl-aarch64.so.1+0x128268)
  #2 0x7f856cfbc0 (/lib/ld-musl-aarch64.so.1+0x183bc0)
  #3 0x7f855ea1b4 (/lib/ld-musl-aarch64.so.1+0x9e1b4)
  #4 0x7f8567349c (/lib/ld-musl-aarch64.so.1+0x12749c)
  #5 0x556c5c67a8 (/data/local/tmp/gwp_asan_invalid_free_left_test+0x17a8)
  #6 0x7f855ecd74 (/lib/ld-musl-aarch64.so.1+0xa0d74)
  #7 0x556c5c6754 (/data/local/tmp/gwp_asan_invalid_free_left_test+0x1754)
0x7f8551ffff was allocated by thread 11708 here:
  #0 0x7f85673f20 (/lib/ld-musl-aarch64.so.1+0x127f20)
  #1 0x7f85673298 (/lib/ld-musl-aarch64.so.1+0x127298)
  #2 0x7f856891b4 (/lib/ld-musl-aarch64.so.1+0x13d1b4)
  #3 0x556c5c67a0 (/data/local/tmp/gwp_asan_invalid_free_left_test+0x17a0)
  #4 0x7f855ecd74 (/lib/ld-musl-aarch64.so.1+0xa0d74)
  #5 0x556c5c6754 (/data/local/tmp/gwp_asan_invalid_free_left_test+0x1754)
*** End GWP-ASan report ***

invalid free right

*** GWP-ASan detected a memory error ***
Invalid (Wild) Free at 0x7fa4e96ff1 (1 byte to the right of a 1-byte allocation at 0x7fa4e96ff0) by thread 11852 here:
  #0 0x7fa4fec6b8 (/lib/ld-musl-aarch64.so.1+0x1286b8)
  #1 0x7fa4fec268 (/lib/ld-musl-aarch64.so.1+0x128268)
  #2 0x7fa5047bc0 (/lib/ld-musl-aarch64.so.1+0x183bc0)
  #3 0x7fa4f621b4 (/lib/ld-musl-aarch64.so.1+0x9e1b4)
  #4 0x7fa4feb49c (/lib/ld-musl-aarch64.so.1+0x12749c)
  #5 0x55625737a8 (/data/local/tmp/gwp_asan_invalid_free_right_test+0x17a8)
  #6 0x7fa4f64d74 (/lib/ld-musl-aarch64.so.1+0xa0d74)
  #7 0x5562573754 (/data/local/tmp/gwp_asan_invalid_free_right_test+0x1754)
0x7fa4e96ff1 was allocated by thread 11852 here:
  #0 0x7fa4febf20 (/lib/ld-musl-aarch64.so.1+0x127f20)
  #1 0x7fa4feb298 (/lib/ld-musl-aarch64.so.1+0x127298)
  #2 0x7fa50011b4 (/lib/ld-musl-aarch64.so.1+0x13d1b4)
  #3 0x55625737a0 (/data/local/tmp/gwp_asan_invalid_free_right_test+0x17a0)
  #4 0x7fa4f64d74 (/lib/ld-musl-aarch64.so.1+0xa0d74)
  #5 0x5562573754 (/data/local/tmp/gwp_asan_invalid_free_right_test+0x1754)
*** End GWP-ASan report ***

Buffer Underflow

*** GWP-ASan detected a memory error ***
Buffer Underflow at 0x7f8db1aff1 (4063 bytes to the left of a 48-byte allocation at 0x7f8db1bfd0) by thread 12086 here:
  #0 0x7f8dc716b8 (/lib/ld-musl-aarch64.so.1+0x1286b8)
  #1 0x7f8dc71268 (/lib/ld-musl-aarch64.so.1+0x128268)
  #2 0x7f8dcccbc0 (/lib/ld-musl-aarch64.so.1+0x183bc0)
  #3 0x7f8dbe71b4 (/lib/ld-musl-aarch64.so.1+0x9e1b4)
  #4 0x55801287f8 (/data/local/tmp/gwp_asan_buffer_overflow_test+0x17f8)
  #5 0x7f8dbe9d74 (/lib/ld-musl-aarch64.so.1+0xa0d74)
  #6 0x558012879c (/data/local/tmp/gwp_asan_buffer_overflow_test+0x179c)
0x7f8db1aff1 was allocated by thread 12086 here:
  #0 0x7f8dc70f20 (/lib/ld-musl-aarch64.so.1+0x127f20)
  #1 0x7f8dc70298 (/lib/ld-musl-aarch64.so.1+0x127298)
  #2 0x7f8dc861b4 (/lib/ld-musl-aarch64.so.1+0x13d1b4)
  #3 0x7f8d4ef4fc (/system/lib64/libc++.so+0xaf4fc)
  #4 0x7f8da76818 (/system/lib64/chipset-pub-sdk/libhilog.so+0x36818)
  #5 0x7f8da76af8 (/system/lib64/chipset-pub-sdk/libhilog.so+0x36af8)
  #6 0x7f8da6f228 (/system/lib64/chipset-pub-sdk/libhilog.so+0x2f228)
  #7 0x7f8dbd4d18 (/lib/ld-musl-aarch64.so.1+0x8bd18)
  #8 0x7f8dbd8cc4 (/lib/ld-musl-aarch64.so.1+0x8fcc4)
  #9 0x7f8dd00370 (/lib/ld-musl-aarch64.so.1+0x1b7370)
  #10 0x7f8dbd4d18 (/lib/ld-musl-aarch64.so.1+0x8bd18)
  #11 0x7f8dbd4b28 (/lib/ld-musl-aarch64.so.1+0x8bb28)
  #12 0x7f8dbe9d58 (/lib/ld-musl-aarch64.so.1+0xa0d58)
  #13 0x558012879c (/data/local/tmp/gwp_asan_buffer_overflow_test+0x179c)
*** End GWP-ASan report ***

6 内存泄漏检测

6.1 使用Snapshot检测内存泄漏

6.1.1 查看快照详情

1. 创建Snapshot场景调优分析任务,操作方法可参考性能问题定位:深度录制。

说明

  • 在任务分析窗口,可以通过“Ctrl+鼠标滚轮”缩放时间轴,通过“Shift+鼠标滚轮”左右移动时间轴。或使用快捷键W/S放大或缩小时间轴,使用A键/D键可以左右移动时间轴。
  • 将鼠标悬停在泳道任意位置,可以通过M键添加单点时间标签。
  • 鼠标框选要关注的时间段,可以通过“Shift+M”添加时间段时间标签。
  • 在任务分析窗口,可以通过“ctrl+, ”向前选中单点时间标签,通过“ctrl+. ”向后选中单点时间标签。
  • 在任务分析窗口,可以通过“ctrl+[ ”向前选中时间段时间标签,通过“ctrl+]”向后选中时间段时间标签。

2. 设置Snapshot泳道。

单击任务左上角的进行泳道的新增和删除,再次单击此按钮可关闭设置并生效。

3. 开始录制后可观察Memory泳道的内存使用情况,在需要定位的时刻单击任务左上角的启动一次快照。

“ArkTS Snapshot”泳道的紫色区块表示一次快照完成。

说明

  • 在任务录制过程中,单击分析窗口左上角的可启动内存回收机制。
  • 当方舟虚拟机的调优对象的某个程序/进程占用的部分内存空间在后续的操作中不再被该对象访问时,内存回收机制会自动将这部分空间归还给系统,降低程序错误概率,减少不必要的内存损耗。

在“Statistics”页签中显示当前快照的详细信息:

  • Constructor:构造器。
  • Distance:从GC Root到这个对象的距离。
  • Shallow Size:该对象的实际大小。
  • Retained Size:当前对象释放时,总共可以释放的内存大小。
  • Native Size:该对象所引用的Native内存大小。
  • Retained Native Size:当前对象释放时,总共可以释放的Native内存大小。
  • 构造函数名称后的“x数字”,表示该类型对象的数量,可单击折叠按钮展开。
  • 单击列表中任一对象,右侧区域会显示从GC roots到这个对象的路径,通过这些路径可以看到该对象的句柄被谁持有,从而方便定位问题产生的原因。
  • 标识的对象,表示其为全局对象,可以通过全局window对象直接访问。

6.1.2 节点属性与引用链

在“Snapshot”的“Statistics”页签和“Comparison”页签中,所有实例对象节点展开后会显示"<fields>"以及"<references>",这两项节点分别代表该实例对象的属性以及该实例对象的引用链信息。

在“Snapshot”的More区域则展示“Fields”和“References”两个页签,分别代表Detail区域所选择对象的属性以及引用链信息,方便快捷查看所选中对象的属性等详细信息,而不需要跳转至对应对象。

6.1.3 节点跳转

在“Snapshot”的“Comparison”页签中,查看内存对象、对象属性及其引用链时,若要查看某一对象的详细信息,可以单击该对象所在行行尾的跳转图标跳转至该对象所在的“Statistics”页签并定位至该对象所在的位置,以查看该对象的详细信息。

6.1.4 历史节点前进/后退

当在“Comparison”和“Statistics”之间进行节点跳转后,单击详情区域左下角的左右箭头可以前进或者后退至下一个或上一个历史节点,以便快速在多个历史节点之间跳转查看。当箭头为激活状态时,表示前进/后退功能可用,当箭头为灰色状态时则代表无法使用该功能。

6.1.5 比较快照差异

在“Snapshot”的“Comparison”页签中,以当前选择的快照为base,下拉框选择的快照为Target,即可得到两次快照信息的比较结果。

在“Snapshot”的“Comparison”页签中,可进行两次快照的差异比较,比较内容包括新增数、删除数、个数增量、分配大小、释放大小、大小增量等等。通过不断对比,可快速分析和定位内存问题的具体位置。

6.1.6 Heap Snapshot离线导入

DevEco Profiler提供Heap Snapshot离线导入能力,可导入一个或多个.heapsnapshot文件。

您可以在DevEco Profiler主界面的“Create Session”区域中,单击“Open File”,导入.heapsnapshot文件。

说明

  • .heapsnapshot文件为方舟虚拟机堆内存dump生成的原始文件。
  • 导入的单个文件大小不超过150M。
  • 批量导入的文件数量不超过10个。

可以导入与heapsnapshot文件匹配的.jsleaklist文件,展示jsleakwatcher监控采集到的内存泄漏对象。

说明

  • .jsleaklist文件由JSLeakWatcher内存泄漏检测框架生成。
  • 导入的单个jsleaklist文件大小不超过30M。
  • 导入的jsleaklist文件通过文件中的hash值与已导入的heapsnapshot文件匹配。
  • 可多次导入不同的jsleaklist文件,也可同时导入多个不同的jsleaklist文件,重复导入不会覆盖已导入的匹配上的jsleaklist文件。总的导入匹配成功的文件数量不超过导入的heapsnapshot文件。

6.2 ArkTS内存泄漏分析

分析步骤

分析内存泄漏问题步骤如下:

  1. 在内存泄漏前拍摄快照;
  2. 触发内存泄漏操作后,再次拍摄快照;
  3. 对比两次快照的数据,可快速找到泄漏对象并做进一步分析;
  4. 当有多个对象在比较视图都存在时,可以重复多次步骤2的操作,分别和未进行操作时对比,观察是否有对象出现明显的线性变化趋势,进一步缩小泄漏对象的范围。

录制Snapshot模板数据

1. 连接好设备后启动应用,点击应用选择框(下图中①处)选择需要录制的应用,选择Snapshot模板(下图中②处),点击Create Session或双击Snapshot图标即可创建一个Snapshot的录制模板。

2. 创建好模板后,点击三角按钮即开始录制。

3. 待右侧泳道全部显示recording后则表明正在录制中,此时点击下图中方块按钮或者左侧暂停按钮都可结束录制。

4. 拍摄快照:开始录制后,待右侧泳道全部显示recording后点击图中①处拍摄按钮,待②处显示出紫色条块表示快照拍摄完成。

5. 录制完成后可点击下图①处按钮将录制文件导出,而点击下图②处的按钮即可导入之前录制好的导出件。

分析Snapshot数据

常见虚拟机内存对象介绍

JSObject

JSObject展开后为内部的各个属性如下:

以下通过具体代码来介绍下实例化对象、声明对象、构造函数间的关系:

class People {
  old: number
  name: string
  constructor(old: number, name: string) {
    this.old = old;
    this.name = name;
  }
  printOld() {
    console.log("old = ", this.old);
  }
  printName() {
    console.log("name = ", this.name);
  }
};
let p = new People(20, "Tom");

采集到的snapshot数据如下:

92729对象对应的是People,其主要声明了对象的属性和方法。

实例化对象的_proto_属性指向声明时的对象,声明对象里则会有constructor()构造函数。当实例化多个对象时,实例化对象会有多个,但是声明对象和构造函数只有一个。

JSFunction

目前所有JSFunction都在(closure)标签中,展开即可看到所有JSFunction:

每个函数展开后为函数内的各个属性:

其中HomeObject表示父类对象,即该方法属于哪个对象;_proto_表示原型对象;LexicalEnv表示该函数的闭包上下文;name是内置属性访问器,可获取函数名;FunctionExtraInfo表示额外信息,比如一些napi接口会在这里记录函数地址;ProtoOrHClass表示原型或者隐藏类。

如果函数显示为anonymous(),则表示为匿名函数;如果函数显示为JSFunction(),则表示该函数可能为框架层函数,创建函数的时候未设置函数名。对于这两种函数名不可见的情况,可以通过查看其引用来间接确认其名称:

LexicalEnv

闭包变量上下文;闭包是一个链状结构,如下所示:

733这个节点本身是一个闭包数组,其中0号元素是调用者(或者再往上的调用者,以此类推)的闭包;1号元素存储的是调试信息;2号及以后的元素存储的就是闭包传递的变量,上例传递了一个变量。

InternalAccessor

内置属性访问器,会有getter和setter方法,通过getter、setter可以获取、设置该属性。

分析方法

查看对象名称

对于声明对象,可以通过constructor属性来确定对象名称。

对于实例化对象,一般没有constructor,则需要展开_proto_属性后查找constructor;

若对象里有一些标志性属性,可以通过在代码里搜索属性名称来找到具体是哪个对象。

如果对象间有继承关系,则可以继续展开_proto_:

如上图则表明Man对象继承自People对象。

7 基础内存检测

7.1 内存分析及优化

应用在开发过程中,可能会因为API使用错误、变量未及时释放、异常频繁创建/释放内存等情况引发各种内存问题。

DevEco Profiler提供了基础的内存场景分析Allocation,您可以使用Allocation来分析应用或元服务在运行时的内存分配及使用情况,识别和定位内存泄漏、内存抖动以及内存溢出等问题,对应用或元服务的内存使用进行优化。

在设备连接完成后,可按照如下方法查看内存分析结果:

1.请参考模块级build-profile.json5文件,增加strip字段并赋值为false(false值表示附带调试和符号信息,待发布上线版本建议恢复为true)。采集函数栈解析符号需要附带符号表信息,无符号表信息可能采集不到函数名称,因此请录制模板前按照下图进行配置。

2. 创建Allocation分析任务并录制相关数据,操作方法可参考性能问题定位:深度录制,或在会话区选择Open File,导入历史数据。

说明

  • 在任务分析窗口,可以通过“Ctrl+鼠标滚轮”缩放时间轴,通过“Shift+鼠标滚轮”左右移动时间轴。或使用快捷键W/S放大或缩小时间轴,使用A键/D键可以左右移动时间轴。
  • 将鼠标悬停在泳道任意位置,可以通过M键添加单点时间标签。
  • 鼠标框选要关注的时间段,可以通过“Shift+M”添加时间段时间标签。
  • 在任务分析窗口,可以通过“ctrl+, ”向前选中单点时间标签,通过“ctrl+. ”向后选中单点时间标签。
  • 在任务分析窗口,可以通过“ctrl+[ ”向前选中时间段时间标签,通过“ctrl+]”向后选中时间段时间标签。
  • Allocation分析支持离线符号解析能力,请参见离线符号解析。

Allocation分析任务支持在录制前单击指定要录制的泳道:

  • Memory泳道:显示当前进程的物理内存使用情况,其度量方式包含:

PSS:进程独占内存和按比例分配共享库占用内存之和。

RSS:进程独占内存和相关共享库占用内存之和。

USS:进程独占内存。

默认只显示PSS的统计图,如需要查看USS或RSS,需要在Memory泳道的右上角点选相关数据类型。

展开Memory泳道,子泳道展示的是按照内存类型将进程PSS值拆分开的各个维度的内存信息,类型包含ArkTS Heap/Native Heap/GL/Graph/Gurad/AnonPage Other/FilePage Other/Dev/Stack/.hap/.so/.ttf。默认展示其中的五个子泳道,如要显示其他子泳道,可以点击主泳道的options标签并勾选其他泳道来查看。

说明

  • ArkTS Heap:ArkTS堆的内存占用。
  • Native Heap:Native层(主要是应用依赖的so库的C/C++代码)使用new/malloc分配的堆内存。
  • GL:应用:纹理内存,RS:纹理+图形渲染内存。
  • Graph:该进程按去重规则统计的dma内存占用,包括直接通过接口申请的dma buffer和通过allocator_host申请的dma buffer。
  • Gurad:保护段所占内存。
  • AnonPage Other:其他所有匿名页所占内存(非heap、anon:native_heap、anon:ArkTS heap开头的匿名页)。
  • FilePage Other:其它映射到文件页但不能被归类到.so/.db/.ttf类型的内存占用。
  • Dev:进程加载的以/dev开头的文件所占内存。
  • Stack:栈内存。
  • .hap:进程加载的.hap文件所占内存
  • .so:进程加载的.so动态库所占内存。
  • .ttf:进程加载的.ttf字体文件所占内存。
  • ArkTS Allocation泳道:显示方舟虚拟机上的内存分配信息。该泳道默认不展示,如需录制该泳道数据,在录制前单击左上角菜单栏图标,勾选ArkTS Allocation泳道。由于隐私安全政策,已上架应用市场的应用不支持录制此泳道。

说明

由于较大的性能开销可能导致卡顿/卡死问题,暂不支持同时录制ArkTS Allocation和Native Allocation两条泳道。

  • Native Allocation泳道:显示具体的Native内存分配情况,包括静态统计数据、分配栈、每层函数栈消耗的Native内存等信息。由于隐私安全政策,已上架应用市场的应用不支持录制此泳道。

单击工具控制栏中的按钮,可以设置是否为统计模式、统计间隔、最小跟踪内存、回栈模式、JS回栈、JS回栈深度和Native回栈深度。默认采用统计模式,统计间隔只在统计模式下才需要设置,可设置范围为1s~3600s,默认为10s,默认最小跟踪内存为1024Bytes。FP回栈模式下需要设置JS回栈深度和Native回栈深度,DWARF回栈模式下仅需要设置回栈深度。默认Native回栈深度为10层,JS回栈深度可配置范围为0-128,默认10层。设置完成后,在录制期间小于此大小的内存分配将被忽略,最大回栈深度将达到设置的值。

说明

  • 设置的最小跟踪内存数值越小、回栈深度越大,对应用造成的影响就越大,可能会导致DevEco Profiler卡顿。请根据应用实际的调测情况进行合理设置。
  • 统计模式用于不关注单次分配、关注应用较长时间的内存变化情况的场景,将指定的采样间隔内的数据做合并统计,以达到降低处理数据量,提高录制效率和时长的目的。设置的Sampling Interval为近似值,即尽可能地在接近这个时间内做统计汇总,存在一定的偏差,偏差不超过1s,这个偏差不会对内存分配的正确性产生影响。

说明

  • 在任务录制过程中,单击分析窗口左上角的可启动内存回收机制。
  • 当方舟虚拟机的调优对象的某个程序/进程占用的部分内存空间在后续的操作中不再被该对象访问时,内存回收机制会自动将这部分空间归还给系统,降低程序错误概率,减少不必要的内存损耗。

3. 在目标泳道上长按鼠标左键并拖拽,框选要展示分析的时间段。

Details区域中显示此时间段内指定类型的内存分析统计信息:

Memory泳道:

  • 主泳道的详情区域显示当前框选时间段内各采样点的应用内存PSS总和以及各种内存页面状态的内存占用总和。

  • 子泳道的详情区域显示该泳道所代表的内存类型的框选时间段内各采样点的PSS总和以及各种内存页面状态的实际占用情况。

注意

Graph字段统计方式为:计算/proc/process_dmabuf_info节点下该进程使用的内存大小。

  • ArkTS Allocation泳道:显示被选择进程所使用的所有ArkTS内存总和,框选后展示此时段内录制到的所有方舟实例的对象分配信息。框选子泳道后显示当前框选时段内运行对象的内存使用情况,包括层级、对象自身内存大小、对象关联内存大小等。

“Details”区域中带标识的对象,表示其可以通过窗口访问。每个时段内已释放的内存大小在柱子上置灰,未释放的内存保持绿色。

Native Allocation泳道:框选子泳道后显示具体的内存分配,包括静态统计数据、分配栈等。

  • Statistics页签中显示该段时间内的静态分配情况,包括分配方式(Malloc或Mmap)、总分配内存大小、总分配次数、尚未释放的内存大小、尚未释放次数、已释放的内存大小、已释放次数。

    点击任意对象上的跳转按钮,可跳转至此类对象的详细占用/分配信息。当前统计模式下不支持跳转。

  • Call Trees页签显示线程的内存分配栈情况,包括函数地址或符号、分配大小、占比以及函数栈帧的类别等。单击任一行栈帧,“More”区域将显示经过该栈帧的分配内存最大的调用栈。
  • Allocations List显示内存分配的详细信息,包括内存块起始地址、时间戳、当前活动状态、大小、调用的库、调用库的具体函数、事件类型(与Statistics页签的分配方式对应)等。

说明

统计模式(Statistics Mode)下不存在Allocations List信息。

选择任一对象,右侧会展示与该对象相关的所有库和调用者

4.(可选)根据分析结果,双击可能存在问题的调用栈,跳转至相关代码。开发者可根据实际需要进行优化。

说明

Release应用暂不支持跳转到用户侧Native代码。

分析数据筛选

Allocation分析过程中提供多种数据筛选方式,方便开发者缩小分析范围,更精确地定位问题所在。

通过内存状态筛选

在Allocation分析过程中,对“Native Allocation”泳道的内存状态信息进行过滤,便于开发者定位内存问题。

在“Native Allocation”泳道的“Detail”区域左下方的下拉框中,可以选择过滤内存状态:

  • All Allocations:详情区域展示当前框选时间段内的所有内存分配信息。
  • Created & Existing:详情区域展示当前框选时间段内分配未释放的内存。
  • Created & Released:详情区域展示当前框选时间段内分配已释放的内存。

通过统计方式筛选

在“Native Allocation”泳道的“Statistics”页签中,可以打开“Native Size”选择统计方式以过滤统计数据:

  • Native Size:详情区域按照对象的原生内存进行展示。
  • Native Library:详情区域按照对象的so库进行展示。

通过so库名筛选

在“Native Allocation”泳道的“Allocations List”页签中,可以单击“Click to choose”选择要筛选的so库以过滤出与目标so库相关的数据:

通过搜索筛选

Native Allocation泳道的页签中, 根据界面提示信息输入需要搜索的项目,可定位到相关内容位置,使用搜索框的<、>按键可依次显示搜索结果的详细内容。

筛选内存分配堆栈

在Native Allocation泳道的Call Trees页签中,可以通过底部的“Call Trees”和“Constraints”选择框来过筛选和过滤内存分配栈。

Call Trees选择框包含两种过滤条件:

  • Separate by Allocated Size:在内存分配栈完全相同的情况下,会按照每次分配栈申请的内存大小将栈分开;
  • Hide System Libraries:隐藏内存分配栈中的系统堆栈。

Constraints选择框也包含了两种过滤条件:

  • Count:根据指定的内存申请次数过滤内存分配栈信息;
  • Bytes:根据指定的内存申请大小过滤内存分配栈信息。

在Call Trees页签的More区域,单击“Heaviest Stack”旁的隐藏按钮可以单独控制是否显示More区域最大内存分配栈中的系统堆栈。

在Call Trees页签,可以通过底部的“Flame Chart”切换到火焰图视图。

分析启动内存

应用/元服务在启动过程中对内存资源的占用情况,是开发者较为关心的问题。DevEco Profiler的Allocation分析任务,提供了启动内存分析能力,协助开发者优化启动过程的内存占用。

针对调测应用的当前运行情况,DevEco Profiler对其做如下处理:

  • 如选择的是已安装但未启动的应用,在启动该分析任务时,会自动拉起应用,进行数据录制,结束录制后可正常进入解析阶段。
  • 如选择的是正在运行的应用,在启动该分析任务时,会先将应用关停,再自动拉起应用,进行数据录制,结束录制后可正常进入解析阶段。

具体操作方法为:在任务列表中单击Allocation任务后的按钮

在分析结束后,呈现出的数据类型以及相应的处理方法,与非启动过程的分析相同。

8 使用Tsan检测线程问题

8.1 原理概述

TSan(ThreadSanitizer)是一个检测数据竞争的工具。它包含一个编译器插桩模块和一个运行时库。TSan开启后,会使性能降低5到15倍,同时使内存占用率提高5到10倍。

Tsan使能分为两个阶段,Tsan Instrumentation阶段完成对用户代码的插装,Tsan Runtime阶段负责对竞争情况做判断,然后输出对应的报告。

8.2 功能介绍

应用场景

TSan能够检测出如下问题:

  • 数据竞争检测数据竞争(Data Race)是指两个或多个线程在没有适当的同步机制情况下同时访问相同的内存位置,其中至少有一个线程在写入。数据竞争是导致多线程程序行为不可预测的主要原因之一。
  • 锁错误检测TSan 不仅能检测数据竞争,还能检测与锁相关的错误:
    • 死锁(Deadlock):死锁是指两个或多个线程互相等待对方释放锁,导致程序无法继续执行。
    • 双重解锁(Double Unlock):同一线程尝试解锁已经解锁的锁。
    • 未持有锁解锁:一个线程尝试解锁一个它未持有的锁。
  • 条件变量错误检测条件变量用于线程之间的通信和同步,常见错误包括:
    • 未持有锁等待:一个线程在未持有相关锁的情况下调用 wait。
    • 未持有锁唤醒:一个线程在未持有相关锁的情况下调用 signal 或 broadcast。

常见TSan异常检测类型有data race,heap-use-after-free,signal handler spoils errno等,详见Tsan异常检测类型部分。

8.3 错误报告

当 TSan 检测到错误时,它会生成详细的报告,包括:

  • 错误类型:例如数据竞争、死锁等。
  • 内存地址:涉及的内存地址。
  • 线程信息:涉及的线程ID和线程创建的堆栈跟踪。
  • 源代码位置:每一个内存访问的源代码位置和堆栈跟踪。
  • 上下文信息:访问类型(读/写)、访问大小等。

8.4 使用约束

  • TSan仅支持API 12及以上版本。
  • ASan、TSan、UBSan、HWASan、GWP-Asan不能同时开启,五个只能开启其中一个。
  • TSan开启后会申请大量虚拟内存,其他申请大虚拟内存的功能(如gpu图形渲染)可能会受影响。
  • TSan不支持静态链接libc或libc++库。

8.5 使能Tsan

可通过以下两种方式使能TSan。每种方式分为IDE场景和流水线场景。

方式一

IDE场景

1. 点击Run > Edit Configurations > Diagnostics,勾选Thread Sanitizer

2. 如果有引用本地library,需在library模块的build-profile.json5文件中,配置arguments字段值为“-DOHOS_ENABLE_TSAN=ON”,表示以TSan模式编译so文件。

流水线场景

在hvigorw命令后加上ohos-debug-tsan=true的选项,执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...] ohos-debug-tsan=true  <options> 

同上,如果有引用本地library,需在library模块的build-profile.json5文件中,配置arguments字段值为“-DOHOS_ENABLE_TSAN=ON”,表示以TSAN模式编译so文件。

方式二

IDE场景

1. 修改工程目录下AppScope/app.json5,添加TSan配置开关。

"tsanEnabled": true

2. 设置模块级构建TSan插桩。

在需要使能TSan的模块中,通过添加构建参数开启TSan检测插桩,在对应模块的模块级build-profile.json5中添加命令参数:

"arguments": "-DOHOS_ENABLE_TSAN=ON"

流水线场景

在hvigorw命令后加上ohos-debug-tsan=true的选项,执行hvigorw命令,更多options参考hvigorw文档

hvigorw [taskNames...] ohos-debug-tsan=true  <options> 

同上,如果有引用本地library,需在library模块的build-profile.json5文件中,配置arguments字段值为“-DOHOS_ENABLE_TSAN=ON”,表示以TSAN模式编译so文件。

8.6 Tsan异常检测类型

Data race

背景

多个线程在没有正确加锁的情况下,同时访问同一块数据,并且至少有一个线程是写操作,对数据的读取和修改产生了竞争,从而导致各种不可预计的问题

错误代码实例

int Global = 12;

void Set1() {
    *(char *)&Global = 4;
}

void Set2() {
    Global=43;
}

void *Thread1(void *x){
    Set1();
    return x;
}

static napi_value Add(napi_env env, napi_callback_info info){
    ...
    pthread_t t;
    pthread_create(&t, NULL, Thread1, NULL);
    Set2();
    pthread_join(t, NULL);
    ...
}

影响

对数据的读取和修改产生了竞争,从而导致各种不可预计的问题

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: data race

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:TSAN
==appspawn==54331==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: data race (pid=54331)
  Write of size 1 at 0x007f1b9c4f84 by thread T32:
    #0 0x7f1b9c22cc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x22c8) (BuildId: fe67e101795e12687f395b21194511c9d69851c2)
    #1 0x7f1b9c2354  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2350) (BuildId: fe67e101795e12687f395b21194511c9d69851c2)
    #2 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

  Previous write of size 4 at 0x007f1b9c4f84 by main thread:
    #0 0x7f1b9c2310  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x230c) (BuildId: fe67e101795e12687f395b21194511c9d69851c2)
    #1 0x7f1b9c25ac  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x25a8) (BuildId: fe67e101795e12687f395b21194511c9d69851c2)
    #2 0x7e8df3cb8c  (/system/lib64/platformsdk/libace_napi.z.so+0x3cb88) (BuildId: 57a073cc8fa34c10cb354df9ed7e2e4b)

修改方法

加锁或者其它线程同步的方法

推荐建议

多线程访问同一内存时,需要注意线程同步机制,必要时加锁

data race on vptr

背景

一个线程在删除某个对象(obj)、一个线程在调用虚函数(obj->vcall)

错误代码实例

#include <semaphore.h>
#include <pthread.h>

struct A {
  A() {
    sem_init(&sem_, 0, 0);
  }
  virtual void F() {
  }
  void Done() {
    sem_post(&sem_);
  }
  virtual ~A() {
    sem_wait(&sem_);
    sem_destroy(&sem_);
  }
  sem_t sem_;
};

struct B : A {
  virtual void F() {
  }
  virtual ~B() { }
};

static A *obj = new B;

void *Thread1(void *x) {
  obj->F();
  obj->Done();
  return NULL;
}

void *Thread2(void *x) {
  delete obj;
  return NULL;
}

static napi_value Add(napi_env env, napi_callback_info info){
    ...
    pthread_t t[2];
    pthread_create(&t[0], NULL, Thread1, NULL);
    pthread_create(&t[1], NULL, Thread2, NULL);
    pthread_join(t[0], NULL);
    pthread_join(t[1], NULL);
    ...
}

影响

线程行为发生冲突,程序崩溃

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: data race on vptr (ctor/dtor vs virtual call)

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:TSAN
==appspawn==26789==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: data race on vptr (ctor/dtor vs virtual call) (pid=26789)
  Write of size 8 at 0x007f078bb140 by thread T31:
    #0 0x7f1b943304  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3300) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #1 0x7f1b943258  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3254) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #2 0x7f1b943298  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3294) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #3 0x7f1b943138  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3134) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #4 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

  Previous read of size 8 at 0x007f078bb140 by thread T30:
    #0 0x7f1b94301c  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x3018) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #1 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

  Location is heap block of size 40 at 0x007f078bb140 allocated by main thread:
    #0 0x7e012b68d4  (/system/lib64/libclang_rt.tsan.so+0x768d0) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #1 0x7f1b8b2438  (/data/storage/el1/bundle/libs/arm64/libc++_shared.so+0xb2434) (BuildId: cdf97be9396a35e8f4806f252f90a11320d26ec6)
    #2 0x7f1b942f34  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2f30) (BuildId: 313582257cb30f740bec5d3616a5588b41b7de89)
    #3 0x7e0009125c  (/lib/ld-musl-aarch64.so.1+0x91258) (BuildId: ec44c498ff1525a6f5d1a1a23c105a8b)
    #4 0x7e8bf786ac  (/system/lib64/platformsdk/libace_napi.z.so+0x386a8) (BuildId: 57a073cc8fa34c10cb354df9ed7e2e4b)

修改方法

设置合适的线程同步机制,如锁

推荐建议

确保设置合适的线程同步机制,来保证线程执行逻辑先后的准确性

Use After Free

heap-use-after-free

背景

使用了释放的内存(多线程层面)

错误代码实例

#include <pthread.h>

int *mem;
pthread_mutex_t mtx;

void *Thread1(void *x) {
  pthread_mutex_lock(&mtx);
  free(mem);
  pthread_mutex_unlock(&mtx);
  return NULL;
}

__attribute__((noinline)) void *Thread2(void *x) {
  pthread_mutex_lock(&mtx);
  mem[0] = 42;
  pthread_mutex_unlock(&mtx);
  return NULL;
}

static napi_value Add(napi_env env, napi_callback_info info){
    ...
    mem = (int*)malloc(100);
    pthread_mutex_init(&mtx, 0);
    pthread_t t;
    pthread_create(&t, NULL, Thread1, NULL);
    Thread2(0);
    pthread_join(t, NULL);
    pthread_mutex_destroy(&mtx);
    ...
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: heap-use-after-free

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:TSAN
==appspawn==4830==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: heap-use-after-free (pid=4830)
  Write of size 4 at 0x007f0764e420 by main thread (mutexes: write M0):
    #0 0x7f1b8026bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x26b8) (BuildId: cf997f64a72b3a470193dd0b38a782fc3aef5075)
    #1 0x7f1b80297c  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2978) (BuildId: cf997f64a72b3a470193dd0b38a782fc3aef5075)
    #2 0x7e8f77cb8c  (/system/lib64/platformsdk/libace_napi.z.so+0x3cb88) (BuildId: 57a073cc8fa34c10cb354df9ed7e2e4b)

  Previous write of size 8 at 0x007f0764e420 by thread T31 (mutexes: write M0):
    #0 0x7e012b6f94  (/system/lib64/libclang_rt.tsan.so+0x76f90) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #1 0x7f1b802630  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x262c) (BuildId: cf997f64a72b3a470193dd0b38a782fc3aef5075)
    #2 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

修改方法

已释放的内存不要使用,释放的内存需要标记,方便其它线程判断

推荐建议

使用合理的线程同步机制

Signal Check

signal handler spoils errno

背景

信号处理函数中修改了errno变量

错误代码实例

#include "napi/native_api.h"
#include <signal.h>
#include <sys/types.h>
#include <errno.h>
#include <malloc.h>
#include <pthread.h>

static void MyHandler(int, siginfo_t *s, void *c) {
  errno = 1;
  done = 1;
}

static void* sendsignal(void *p) {
  pthread_kill(mainth, SIGPROF);
  return 0;
}

static __attribute__((noinline)) void loop() {
  while (done == 0) {
    volatile char *p = (char*)malloc(1);
    p[0] = 0;
    free((void*)p);
  }
}

static napi_value Add(napi_env env, napi_callback_info info){
    ...
    mainth = pthread_self();
    struct sigaction act = {};
    act.sa_sigaction = &MyHandler;
    sigaction(SIGPROF, &act, 0);
    pthread_t th;
    pthread_create(&th, 0, sendsignal, 0);
    loop();
    pthread_join(th, 0);
    ...
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: signal handler spoils errno

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

Reason:TSAN
==appspawn==42144==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: signal handler spoils errno (pid=42144)
  Signal 27 handler invoked at:
    #0 0x7f1b7c2730  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x272c) (BuildId: 6c5d69edd55e55ece087ccc4dadb8ffd2712681d)
    #1 0x7f1b7c2868  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x2864) (BuildId: 6c5d69edd55e55ece087ccc4dadb8ffd2712681d)
    #2 0x7f1b7c26b4  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x26b0) (BuildId: 6c5d69edd55e55ece087ccc4dadb8ffd2712681d)
    #3 0x7e8f03cdcc  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)

修改方法

不要在信号处理函数中修改error变量

推荐建议

将MyHandler中的error赋值语句去掉

signal-unsafe call inside of a signal

背景

信号处理函数中调用了非信号安全的函数(比如malloc)

错误代码实例

#include "napi/native_api.h"
#include <signal.h>
#include <sys/types.h>
#include <malloc.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h> 

pthread_t mainth;
volatile int done;

static void handler(int, siginfo_t*, void*) {
  volatile char *p = (char*)malloc(1);
  p[0] = 0;
  free((void*)p);
}

static napi_value Add(napi_env env, napi_callback_info info)
{
    ...
    struct sigaction act = {};
    act.sa_sigaction = &handler;
    sigaction(SIGPROF, &act, 0);
    kill(getpid(), SIGPROF);
    sleep(1); 
    fprintf(stderr, "DONE\n");
    ...
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: signal-unsafe call inside of a signal

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置

Reason:TSAN
==appspawn==54255==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: signal-unsafe call inside of a signal (pid=54255)
    #0 0x7e012b68d4  (/system/lib64/libclang_rt.tsan.so+0x768d0) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #1 0x7f1b9025fc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x25f8) (BuildId: 872d8f3822a374492bda4a455431bcc95f290ad1)
    #2 0x7e012bfb34  (/system/lib64/libclang_rt.tsan.so+0x7fb30) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #3 0x7f1b902530  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x252c) (BuildId: 872d8f3822a374492bda4a455431bcc95f290ad1)
    #4 0x7e8d47cdcc  (/system/lib64/platformsdk/libace_napi.z.so+0x3cdc8) (BuildId: dc293a22b36a4db17dc689ff9413b64e)

修改方法

将信号处理函数中的malloc去掉,在其外部预先分配内存。

推荐建议

建议信号处理程序之外预先分配内存,或者尽可能避免在信号处理程序中进行内存分配和复杂的操作。如果需要在程序中替换malloc,可以考虑使用__malloc_hook或者宏定义等方法

Mutex Check

unlock of an unlocked mutex (or by a wrong thread)

背景

解锁一个已经解锁/自己不拥有的锁

错误代码实例

#include "napi/native_api.h"
#include <pthread.h>
#include <iostream>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* unlocker(void* arg) {
    pthread_mutex_unlock(&mutex);
    return nullptr;
}

static napi_value Add(napi_env env, napi_callback_info info){
    ...
    pthread_t tid;
    pthread_create(&tid, nullptr, unlocker, nullptr);
    pthread_join(tid, nullptr);
    ...
}

影响

导致程序存在安全漏洞,并有崩溃风险。

开启Tsan检测后,触发demo中的函数,应用闪退报Tsan,包含字段:ThreadSanitizer: unlock of an unlocked mutex (or by a wrong thread)

Reason:TSAN
==appspawn==30577==ThreadSanitizer: WARNING: unexpected format specifier in printf interceptor: %{ (reported once per process)
==================
WARNING: ThreadSanitizer: unlock of an unlocked mutex (or by a wrong thread) (pid=30577)
    #0 0x7e012d4cd8  (/system/lib64/libclang_rt.tsan.so+0x94cd4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #1 0x7f1b8421bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x21b8) (BuildId: 4221fdd9cad8fe19e56e4a38ccf49ffc5c637855)
    #2 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

  Location is global '<null>' at 0x000000000000 (libentry.so+0x4db0)

  Mutex M0 (0x007f1b844db0) created at:
    #0 0x7e012d4cd8  (/system/lib64/libclang_rt.tsan.so+0x94cd4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)
    #1 0x7f1b8421bc  (/data/storage/el1/bundle/libs/arm64/libentry.so+0x21b8) (BuildId: 4221fdd9cad8fe19e56e4a38ccf49ffc5c637855)
    #2 0x7e012b84f8  (/system/lib64/libclang_rt.tsan.so+0x784f4) (BuildId: ae7f86a9a081faf0a0a327001ad1c8b20bf33783)

定位思路

如果有工程代码,直接开启Tsan检测,debug模式运行后复现该错误,可以触发Tsan,直接点击堆栈中的超链接定位到代码行,能看到错误代码的位置。

修改方法

先使用try_lock()接口获取锁,再使用unlock()接口解锁

推荐建议

尽量不要释放自己线程未持有的锁

9 方舟运行时检测

9.1 方舟多线程检测

在JS运行时环境中,多线程的安全问题是一个重要的考虑因素。由于JavaScript本身是单线程的,对JS对象的任何操作都必须在创建该JS线程的原始线程上进行。如果违反了这一规则,就会导致多线程安全问题。以下是关于如何判断和处理这些问题的一些详细说明。

1、原理介绍

  • 单线程执行

    JavaScript是单线程执行的语言,这意味着它一次只能在一个线程上执行代码。任何JavaScript对象都只能在创建它们的线程上进行操作。

  • N-API(Node-API)接口

    N-API接口直接涉及到JavaScript对象的操作。绝大多数N-API接口(约95%)只能在创建这些对象的JavaScript线程上调用。

  • 多线程检测机制

    多线程检测机制会检测当前线程和正在使用的JS虚拟机环境(vm/env)中的JS线程ID是否一致。如果不一致,就表明虚拟机环境被跨线程使用,存在多线程安全问题。

2、常见多线程安全问题

  • 非JS线程使用N-API接口

    非JavaScript线程尝试调用N-API接口,可能会导致未定义的行为或崩溃。

  • N-API接口使用其他线程的env

    一个线程尝试使用另一个线程创建的env(JavaScript环境),这也会导致多线程安全问题。

如何判断是否发生了多线程安全问题

如果在运行时遇到以下致命错误信息,这意味着已经发生了多线程安全问题:

Fatal: ecma_vm cannot run in multi-thread! thread:3096 currentThread:3550

其中,thread:3096 表示创建并拥有这个JavaScript环境的线程ID。currentThread:3550 表示当前正在尝试操作这个JavaScript环境的线程ID。

当前线程号为3550,而使用的JavaScript线程是由3096线程创建的,这表明虚拟机环境(vm/env)被跨线程使用,从而导致了多线程安全问题。

3、使用约束

方舟多线程检测通过命令行参数开启,点击桌面图标无效。

4、使能方舟多线程检测

可通过以下两种方式使能方舟多线程检测。

  • 方式一

点击Run > Edit Configurations > Diagnostics,勾选Multi Thread Check

  • 方式二

通过命令行开启。

aa start -a {abilityName} -b {bundleName} -R

5、启用方舟多线程检测

  1. 运行或调试当前应用。
  2. 当程序出现多线程安全问题时,会弹出Crash log信息,点击信息中的链接即可跳转至引起多线程安全问题的代码处。

6、方舟异常检测码

若fatal信息为Fatal: ecma_vm cannot run in multi-thread! thread:1804 currentThread:2057,则发生了多线程安全问题,意为:当前线程号为2057,而使用的js thread是1804创建出来的,跨线程使用VM

10 使用方舟异常信息增强检测

10.1 概述

在进行ArkTS项目开发中可能存在需要加载native模块的场景,开启方舟native模块加载异常信息增强功能后,可以丰富ArkTS项目中因加载native模块导致的报错信息,以便更准确地进行native问题定位。

10.2 使能方舟native模块加载异常信息增强

可以通过以下两种方式使能方舟native模块加载异常信息增强

  • 方式一

点击Run > Edit Configurations > Diagnostics,勾选Enhanced Error Info

  • 方式二

通过命令行开启。

aa start {abilityName} {bundleName} -E

10.3 启用方舟native模块加载异常信息增强

  1. 运行或调试当前应用。
  2. 当程序出现因native模块加载导致的报错信息时,会显示更详细准确的错误信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2298695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux软件编程:IO编程

IO&#xff08;linux输入输出&#xff09; 1. IO概念&#xff1a; I&#xff1a;输入 O&#xff1a;输出 Linux 一切皆是文件 屏幕 -> /dev/tty 磁盘 -> /dev/sda 网卡 键盘 -> /dev/event 鼠标-> /dev/mice 都是一个文件 2. IO操作的对象&#xff1a; 文件 3. 文…

javaEE2

maven 搭建 前后端交互 HTML servlet 后台和数据库交互 servlet jdbc 未来 servlet-->springmvc jdbc-->mybatis-->mybatisplus/jpa javaee-->spring-->springboot SERVLET tomcat ~Apache 服务 Apache(音译为阿帕奇)是世界上使用排名第一的Web服务器…

2025最新深度学习pytorch完整配置:conda/jupyter/vscode

从今天开始&#xff0c;开始一个新的专栏&#xff0c;更新深度学习相关的内容&#xff0c;从入门到精通&#xff0c;首先的首先是关于环境的配置指南&#xff1a;工欲善其事必先利其器&#xff01; PyTorch 是由 Facebook&#xff08;现 Meta&#xff09;开发的 开源深度学习框…

华为小艺助手接入DeepSeek,升级鸿蒙HarmonyOS NEXT即可体验

小艺助手接入DeepSeek的背景与意义 随着人工智能技术的不断发展&#xff0c;大模型成为推动智能交互升级的关键力量。DeepSeek在自然语言处理等领域具有出色的表现&#xff0c;其模型在语言理解、生成等方面展现出强大的能力。华为小艺助手接入DeepSeek&#xff0c;旨在借助其先…

Git 查看修改记录 二

Git 查看修改记录 二 续接 Git 查看一个文件的修改记录 一 一、修改 A.txt 修改 A.txt number6执行命令 git add . git commit -a -m "修改 number6" # git commit -a -m "修改 number6" 执行 输出如下 # $ git commit -a -m "修改 number6"…

【STM32】增量型旋钮编码器

1.增量型旋钮编码器原理 该编码器有A&#xff0c;B两相&#xff0c;当顺时针旋转时 B相会提前A相90度&#xff08;匀速转的时候&#xff09;&#xff0c;也就是A相上升沿时&#xff0c;B相对应高电平&#xff0c;计数器会1&#xff0c;A相下降沿时&#xff0c;B相为低电平时&…

电动汽车电池监测平台系统设计(论文+源码+图纸)

1总体设计 本次基于单片机的电池监测平台系统设计&#xff0c;其整个系统架构如图2.1所示&#xff0c;其采用STC89C52单片机作为控制器&#xff0c;结合ACS712电流传感器、TLC1543模数转换器、LCD液晶、DS18B20温度传感器构成整个系统&#xff0c;在功能上可以实现电压、电流、…

DeepSeek助力:打造属于你的GPTs智能AI助手

文章目录 一、环境准备1.安装必要的工具和库2. 选择合适的开发语言 二、核心技术选型1. 选择适合的AI框架 三、功能实现1. 文本生成与对话交互2. 代码生成与自动补全3. 数据分析与报告生成 四、案例实战1. 搭建一个简单的聊天机器人2. 创建一个代码生成器 五、总结与展望1. 当前…

C语言基础系列【12】运算符

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…

服务器之连接简介(Detailed Explanation of Server Connection)

一台服务器最大能支持多少连接&#xff1f;一台客户端机器最多能发起多少条连接&#xff1f;&#xff1f; 我们知道TCP连接&#xff0c;从根本上看其实就是client和server端在内存中维护的一组【socket内核对象】&#xff08;这里也对应着TCP四元组&#xff1a;源IP、源端口、…

华为2288H V5服务器无法启动问题处理

问题&#xff1a;通电后服务器前面显示888&#xff0c;点击电源键没有反应 一.通过管理口管理服务器硬件设备 华为2288H V5它默认的IP是192.168.2.100 网关是255.255.255.0 2.将网线一头连接服务器的Mgmt口&#xff0c;另一头来连接笔记本的网口&#xff0c;将笔记本的的本地…

华宇TAS应用中间件与因朵科技多款产品完成兼容互认证

在数字化浪潮澎湃向前的当下&#xff0c;信息技术的深度融合与协同发展成为推动各行业创新变革的关键力量。近日&#xff0c;华宇TAS应用中间件携手河北因朵科技有限公司&#xff0c;完成了多项核心产品的兼容互认证。 此次兼容性测试的良好表现&#xff0c;为双方的进一步深入…

第36天:安全开发-JavaEE应用第三方组件Log4j日志FastJson序列化JNDI注入

时间轴&#xff1a; 演示案例&#xff1a; Java-三方组件-Log4J&JNDI Java-三方组件-FastJson&反射 Maven的下载及配置&#xff1a; IDEA配置Maven的超详细步骤_java_脚本之家 Java-三方组件-Log4J&JNDI JNDI 注入&#xff1a; ( 见图 ) Java Naming and Dire…

21爬虫:使用playwright接管本地已经登录淘宝的浏览器并查找python相关店铺信息

1.playwright如何接管本地浏览器 &#xff08;1&#xff09;首先找到电脑上安装的Chrome浏览器可执行程序的完整路径&#xff1a; Mac电脑上可执行程序的完整路径为&#xff1a; /Applications/Google Chrome.app/Contents/MacOS/Google Chrome windows系统的电脑上查找可执行…

Redis——优惠券秒杀问题(分布式id、一人多单超卖、乐悲锁、CAS、分布式锁、Redisson)

#想cry 好想cry 目录 1 全局唯一id 1.1 自增ID存在的问题 1.2 分布式ID的需求 1.3 分布式ID的实现方式 1.4 自定义分布式ID生成器&#xff08;示例&#xff09; 1.5 总结 2 优惠券秒杀接口实现 3 单体系统下一人多单超卖问题及解决方案 3.1 问题背景 3.2 超卖问题的…

【现代深度学习技术】深度学习计算 | GPU

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…

USB Flash闪存驱动器安全分析(第一部分)

翻译原文链接&#xff1a;Hacking Some More Secure USB Flash Drives (Part I) | SySS Tech Blog 文章翻译总结&#xff1a;文章对一些具有AES硬件加密的USB闪存驱动器的网络安全分析研究。研究由SySS的IT安全专家Matthias Deeg进行&#xff0c;他在2022年初发现了几个安全漏…

【Linux】--- 基础开发工具之yum/apt、vim、gcc/g++的使用

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Linux网络编程 本篇博客我们来认识一下Linux中的一些基础开发工具 --- yum,vim,gcc/g。 &#x1f3e0; yum &#x1f3b8; 什么是yum 当用户想下载软…

Python + WhisperX:解锁语音识别的高效新姿势

大家好&#xff0c;我是烤鸭&#xff1a; 最近在尝试做视频的质量分析&#xff0c;打算利用asr针对声音判断是否有人声&#xff0c;以及识别出来的文本进行进一步操作。asr看了几个开源的&#xff0c;最终选择了openai的whisper&#xff0c;后来发现性能不行&#xff0c;又换了…

redis 缓存击穿问题与解决方案

前言1. 什么是缓存击穿?2. 如何解决缓存击穿?怎么做?方案1: 定时刷新方案2: 自动续期方案3: 定时续期 如何选? 前言 当我们使用redis做缓存的时候,查询流程一般是先查询redis,如果redis未命中,再查询MySQL,将MySQL查询的数据同步到redis(回源),最后返回数据 流程图 为什…