HarmonyOS Next 开发工具 DevEco Studio 介绍:ASan 与 TSan 检测根治你的 C++ 恐惧症
1、背景介绍
很多开发者面对 C++都很犯怵,其中主要的一块就是内存操作。不合理的内存操作,比如数组越界、内存泄露、释放已释放的地址,可能会引起程序性能问题:内存消耗大,卡顿,更严重的会导致程序出现崩溃。当应用运行发生错误使应用进程终止时,应用将会抛出错误日志以通知应用崩溃的原因,开发者可通过查看错误日志分析应用崩溃的原因及引起崩溃的代码位置。FaultLog 由系统自动从设备进行收集,包括如下几类故障信息:
App Freeze
CPP Crash
JS Crash
System Freeze
ASan
TSan
其他工具我们日常使用较多,不做过多介绍。由于 ArkTS 是单线程,提供的多线程机制也是基于非共享内存,很多多线程场景可能会放到 C++层实现,所以本文主要介绍 DevEco Studio 提供的内存调试和线程调试工具:AScan 检测与 TSan 检测。
2、 ASan 检测
C++内存问题主要体现在访问越界、内存未释放、内存释放后再次释放,出于性能考虑,编译器和运行框架不会对内存操作进行安全检查,DevEco Studio 提供了 ASan 帮助开发者检测地址越界问题。ASan 全称 Address-Sanitizer,首先我们先了解工程中配置 ASan 的方式。
2.1 ASan 配置
ASan 主要由 ASAN_OPTIONS 参数控制,主要设置检测级别、输出格式、内存错误报告的详细程度等。参数配置主要有两个地方:
工程的 app.json5 文件中(优先级较高)
Run/Debug Configurations 中
2.1.1 在 app.json5 中配置
AppScope > app.json5 文件中:
2.1.2 在 Run/Debug Configurations 中配置
点击加号按钮新增名称为 ASAN_OPTIONS 的配置,value 值可以设置为:log_exe_name=true abort_on_error=0 print_cmdline=true
2.1.3 可配置参数说明
2.2 启用和启动 ASan
有两种方式使用 ASan:
运行调试窗口,点击 Diagnostics,勾选 Address Sanitizer。
AppScope/app.json5,添加 ASan 配置开关。
建议可以在 IDE 配置中修改的就尽量不要在代码中改动。
如果有依赖的本地 library,需在 library 模块的 build-profile.json5 文件中,配置 arguments 字段值为“-DOHOS_ENABLE_ASAN=ON”,表示以 ASan 模式编译 so 文件。
接下来我们运行或调试当前应用时,当程序出现内存错误时,弹出 ASan log 信息,点击信息中的链接即可跳转至引起内存错误的代码处。
下面是模拟数组越界异常崩溃后的 asan 日志,点击错误直接跳转到引起崩溃位置,而且会提示 UNKNOWN memory access,这种日志对我们定位日志帮助很大。
2.3 ASan 检测异常码
ASan 会提示具体错误码,下面列出插件错误码和对应原因。
2.3.1 heap-buffer-overflow
原因和影响:访问越界,导致程序存在安全漏洞,并有崩溃风险。优化建议:已知大小的集合注意访问不要越界,位置大小的集合访问前先判断大小。错误代码示例:
2.3.2 stack-buffer-underflow
原因和影响:访问越下界,导致程序存在安全漏洞,并有崩溃风险。优化建议:访问索引不应小于下界。错误代码示例:
2.3.3 stack-use-after-scope
原因和影响:栈变量在作用域之外被使用。导致程序存在安全漏洞,并有崩溃风险。优化建议:注意变量的作用域。错误代码示例:
2.3.4 attempt-free-nonallocated-memory
原因和影响:尝试释放了非堆对象(non-heap object)或未分配内存。导致程序存在安全漏洞,并有崩溃风险。优化建议:不要对非堆对象或未分配的内存使用 free 函数。错误代码示例:
2.3.5 double-free
原因和影响:重复释放内存。导致程序存在安全漏洞,并有崩溃风险。优化建议:变量定义声明时初始化为 NULL,释放内存后也应立即将变量重置为 NULL,这样每次释放之前都可以通过判断变量是否为 NULL 来判断是否可以释放。错误代码示例:
2.3.6 heap-use-after-free
原因和影响:当指针指向的内存被释放后,仍然通过该指针访问已经被释放的内存,就会触发 heap-use-after-free。优化建议:实现一个 free()的替代版本或者 delete 析构器来保证指针的重置。错误代码示例:
2.4 其他注意事项
如果应用内的任一模块使能 ASan,那么 entry 模块需同时使能 ASan。如果 entry 模块未使能 ASan,该应用在启动时将闪退,出现 CPP Crash 报错。
3、 TSan 检测
C++开发中的另一个问题是线程问题,引入多线程会导致代码的执行顺利不可预测,导致调试和定位问题困难。幸好 DevEco Studio 提供了 TSan 工具,TSan 全称 ThreadSanitizer,是一个检测数据竞争的工具,它包含一个编译器插桩模块和一个运行时库。
3.1 主要应用场景
多线程开发中常见的问题包括线程安全、死锁、资源竞争和线程池管理问题,这些问题都可以通过 TSan 帮助我们检查,TSan 能够检测出如下问题:
数据竞争检测:数据竞争(Data Race)是指两个或多个线程在没有适当的同步机制情况下同时访问相同的内存位置,其中至少有一个线程在写入。数据竞争是导致多线程程序行为不可预测的主要原因之一。
锁错误检测:TSan 不仅能检测数据竞争,还能检测与锁相关的错误:
死锁(Deadlock):死锁是指两个或多个线程互相等待对方释放锁,导致程序无法继续执行。
双重解锁(Double Unlock):同一线程尝试解锁已经解锁的锁。
未持有锁解锁:一个线程尝试解锁一个它未持有的锁。
条件变量错误检测,条件变量用于线程之间的通信和同步,常见错误包括:
未持有锁等待:一个线程在未持有相关锁的情况下调用 wait。
未持有锁唤醒:一个线程在未持有相关锁的情况下调用 signal 或 broadcast。
3.2 TSan 配置
有两种方式配置开启 TSan:
在运行调试窗口,点击 Diagnostics,勾选 Thread Sanitizer。
修改工程目录下 AppScope/app.json5,添加 TSan 配置开关。
注意: 如果有引用本地 library,需在 library 模块的 build-profile.json5 文件中,配置 arguments 字段值为“-DOHOS_ENABLE_TSAN=ON”,表示以 TSan 模式编译 so 文件。
3.3 开启 TSan
运行或调试当前应用,当程序出现线程错误时,弹出 TSan log 信息,点击信息中的链接即可跳转至引起线程错误的代码处。
当 TSan 检测到错误时,它会生成详细的报告,包括:
错误类型:例如数据竞争、死锁等。
内存地址:涉及的内存地址。
线程信息:涉及的线程 ID 和线程创建的堆栈跟踪。
源代码位置:每一个内存访问的源代码位置和堆栈跟踪。
上下文信息:访问类型(读/写)、访问大小等。
注意:当前使用 call_once 接口会存在 TSan 误报的现象,开发者可以在调用该接口的函数前添加__attribute__((no_sanitize("thread")))来屏蔽该问题。
3.4 其他注意事项
TSan 开启后,会使性能降低 5 到 15 倍,同时使内存占用率提高 5 到 10 倍,其他申请大虚拟内存的功能(如 gpu 图形渲染)可能会受影响。
ASan 与 TSan 不可同时开启。
TSan 仅支持 API 12 及以上版本。
4、总结
本文主要介绍了 DevEco Studio 提供了两种内存调试和线程调试工具:ASan(Address Sanitizer)和 TSan(Thread Sanitizer)。这两个工具可以帮助开发者检测并解决 C++程序中的内存错误和线程问题。ASan 用于检测地址越界、未释放的内存和释放后再次释放的问题,而 TSan 则用于检测数据竞争、锁错误以及条件变量错误。
DevEco Studio 相当给力,对开发者很友好,把 C++开发中遇到的问题都通过工具化来协助解决和定位,通过内存和线程检测工具,开发者在面对 C++开发任务时可以得心应手,手到擒来。推荐大家可以熟悉下这两个工具,在面对复杂的 C++开发场景,可以做到胸有成竹,借助工具消除隐患。
版权声明: 本文为 InfoQ 作者【轻口味】的原创文章。
原文链接:【http://xie.infoq.cn/article/210c077f3da89dc8215a3672b】。文章转载请联系作者。
评论