使用GWP-ASan定位地址越界问题

📅 2026/6/30 12:34:33
使用GWP-ASan定位地址越界问题
本原创文章帖发布在华为开发者联盟社区欢迎开发者前往访问评论交流更多与该内容相关讨论请点击原帖查看使用GWP-ASan定位地址越界问题-华为开发者话题 | 华为开发者联盟一、地址越界的定义与危害地址越界泛指程序对内存的错误访问例如堆内存释放后重用、栈内存退栈后重用、堆或栈的越界访问等。这类问题本质上是程序访问了“不该访问”或“已经失效”的内存区域属于典型的内存安全问题。地址越界问题的危害较大大部分情况下它会直接表现为应用闪退极个别情况下也可能表现为功能异常、数据异常、界面显示异常等功能性问题。相比闪退类问题功能性问题通常更容易被隐藏和忽略导致问题长时间潜伏在线上。同时地址越界问题具有很强的随机性。普通 Crash 日志或 Coredump 捕获到的往往不是内存被破坏的第一现场而是内存被破坏后在后续访问、释放或系统检测时暴露出来的二次现场。因此日志中看到的崩溃点不一定是真正的问题根因难以还原内存从申请、释放到异常访问的完整过程定位成本较高。GWP-ASan 正是用于解决这类问题的轻量级地址越界检测工具。它通过对堆内存分配进行采样在较低性能开销下检测释放后使用、堆越界访问、重复释放等典型内存错误触发的第一现场。相较于 HWASan/ASanGWP-ASan 无需额外插桩适配性能开销低于 5%更适合在大规模现网用户场景下运行用于发现难复现的地址越界类问题。目前GWP-ASan能检测的错误类型有故障类型含义Use After Free释放后使用Heap Buffer Overflow / Underflow堆上/下越界访问Double Free重复释放Invalid Left / Right Free指针非法释放详细检测原理可参考官网文档GWP-ASan检测原理 。二、GWP-ASan使用指南2.1 开启GWP-ASan检测方式一默认参数开启在app.json5中添加GWPAsanEnabled: true配置如下图所示开启GWP-ASan检测后如果应用发生地址越界问题且该内存块正好被GWP-ASan采样监控GWP-ASan会记录地址越界事件。方式二三方灰度支持开发者定制化开启GWP-ASan从API206.0版本开始开发者可通过如下参数去定制化开启GWP-ASan。从API246.1版本开始提供了可恢复模式在该模式下系统检测到地址越界故障后避免因检测机制本身导致进程崩溃。名称默认值是否必填说明APIROMalwaysEnabledfalse否true100%开启GWP-ASan与app.json5中GWPAsanEnabled标签功能一致。false1/128概率开启GWP-ASan在应用冷启动时候会判断是否开启。注意若在app.json5中设置了 GWPAsanEnabled将会覆盖该参数。206.0sampleRate2500否GWP-ASan采样频率。1/sampleRate的概率对分配的内存进行采样。建议值≥1000默认参数下性能开销小于5%。采样频率过小会显著影响性能若调整参数请开发者自行保证用户体验。206.0maxSimutaneousAllocations1000否最大分配的插槽数。当插槽用尽时新分配的内存将不再受监控。释放已使用的内存后其占用的插槽将自动复用以便于后续内存的监控。建议值≤20000每个插槽会额外占用约4.5KB内存默认参数下约占4.5MB过大可能导致VMA超限崩溃。206.0duration7否开启GWP-ASan检测天数默认值为7天。206.0isRecoverapi24 6.1版本false否该参数从 API 24 开始支持。用于控制应用在100% 开启 GWP-ASan 时是否以可恢复模式运行。true当 GWP-ASan 以 100% 概率开启时应用以可恢复模式运行。在该模式下系统检测到地址越界故障后避免因检测机制本身导致进程崩溃但对于已造成非法内存访问的错误应用仍可能发生崩溃。false当 GWP-ASan 以 100% 概率开启时应用以不可恢复模式运行。注意该参数只在“100% 开启 GWP-ASan”场景下生效1/128 概率开启场景默认为可恢复不受isRecover控制。默认值false。246.1接口具体使用方式可查看ohos.hidebug (Debug调试) 。三、使用场景3.1 开发态开发调试阶段以问题发现和复现效率优先。建议将应用配置为100%开启GWP-ASan检测以稳定暴露潜在内存问题。示例import { hidebug } from kit.PerformanceAnalysisKit; import { taskpool } from kit.ArkTS; import { BusinessError } from kit.BasicServicesKit; Concurrent function enableGwpAsanTask(): void { let options: hidebug.GwpAsanOptions { alwaysEnabled: true, sampleRate: 2500, maxSimutaneousAllocations: 5000, isRecover: false, // 不可恢复模式 }; hidebug.enableGwpAsanGrayscale(options, duration); } taskpool.execute(enableGwpAsanTask).then(() { console.info(Succeeded in enabling GWP-ASan.); }).catch((error: BusinessError) { const err: BusinessError error as BusinessError; console.error(Failed to enable GWP-ASan. Code: ${err.code}, message: ${err.message}); })该场景下每次应用启动均会使能GWP-Asan检测其中采样率为1/2500槽位数为5000检测到地址越界问题后应用会崩溃不会继续运行。若默认参数下问题仍较难复现可适当减小 sampleRate或增大maxSimutaneousAllocations以提升检测覆盖率。3.2 运维态当应用发布后出现疑似地址越界问题且默认 1/128 概率开启难以命中时开发者可通过 hidebug 接口并结合 duration 参数临时将应用切换为 100% 开启模式用于线上问题复现和定位。示例import { hidebug } from kit.PerformanceAnalysisKit; import { taskpool } from kit.ArkTS; import { BusinessError } from kit.BasicServicesKit; Concurrent function enableGwpAsanTask(): void { let options: hidebug.GwpAsanOptions { alwaysEnabled: true, sampleRate: 2500, maxSimutaneousAllocations: 1000, isRecover: true, // 可恢复模式 }; let duration: number 2; // GWP-ASan 检测天数 hidebug.enableGwpAsanGrayscale(options, duration); } taskpool.execute(enableGwpAsanTask).then(() { console.info(Succeeded in enabling GWP-ASan.); }).catch((error: BusinessError) { const err: BusinessError error as BusinessError; console.error(Failed to enable GWP-ASan. Code: ${err.code}, message: ${err.message}); })该场景下在指定时长内应用每次启动都会使能GWP-ASan检测其中采样率为1/2500槽位数为1000检测到地址越界问题后不会崩溃继续运行。同样地如果问题难以复现也可以适当的调整sampleRate和maxSimutaneousAllocations。四、GWP-ASan日志4.1 日志获取4.1.1 开发态方式一通过DevEco Studio获取日志DevEco Studio会收集设备/data/log/faultlog/faultlogger/路径下的进程崩溃故障日志到FaultLog下根据进程名和故障和时间分类显示。获取日志的方法参见DevEco Studio使用指南-FaultLog 。方式二通过hdc获取日志需打开开发者选项日志默认都落盘至 /data/log/faultlog/faultlogger 下。在开发者选项打开的情况下开发者可以通过hdc file recv /data/log/faultlog/faultlogger D:\命令导出故障日志到本地。故障日志文件名格式为gwpasan-com.example.sampleapplication-20020209-20260416234001285.log其中 com.example.sampleapplication 表示应用包名20020209 表示应用 UID20260416234001285 表示故障发生时间。4.1.2 运维态方式通过HiAppEvent接口订阅HiAppEvent给开发者提供了故障订阅接口详见HiAppEvent介绍 。参考订阅地址越界事件ArkTS 或订阅地址越界事件C/C 完成地址越界事件订阅并通过事件的external_log 字段读取故障日志文件内容。4.2 日志规格GWP-ASan的日志格式如下会展示越界类型Use After Free、Double Free、Overflow等。以下示例为典型的Use-After-Free问题日志包含内存块的分配、释放及违规访问的调用栈信息。五、实战案例5.1 原始日志*** GWP-ASan detected a memory error *** Use After Free at 0x5b41761010 (16 bytes into a 40-byte allocation at 0x5b41761000) by thread 18502 here: #0 0x5b13f1a6d0 (/system/lib64/libwm.z.so0x1da6d0) (BuildId: xxx) #1 0x5b13f18d5c (/system/lib64/libwm.z.so0x1d8d5c) (BuildId: xxx) #2 0x5b13f1a24c (/system/lib64/libwm.z.so0x1da24c) (BuildId: xxx) ...... 0x5b41761010 was deallocated by thread 18502 here: #0 0x5a859859d4 (/lib/ld-musl-aarch64.so.10x1579d4) (BuildId: xxx) #1 0x5a859853ac (/lib/ld-musl-aarch64.so.10x1573ac) (BuildId: xxx) #2 0x5b13f1a6cc (/system/lib64/libwm.z.so0x1da6cc) (BuildId: xxx) #3 0x5b13f18d5c (/system/lib64/libwm.z.so0x1d8d5c) (BuildId: xxx) ...... 0x5b41761010 was allocated by thread 18502 here: #0 0x5a859859d4 (/lib/ld-musl-aarch64.so.10x1579d4) (BuildId: xxx) #1 0x5a85985110 (/lib/ld-musl-aarch64.so.10x157110) (BuildId: xxx) #2 0x5a859a773c (/lib/ld-musl-aarch64.so.10x17973c) (BuildId: xxx) #3 0x5a86f731a4 (/system/lib64/libc.so0xb31a4) (BuildId: xxx) #4 0x5b13f1a724 (/system/lib64/libwm.z.so0x1da724) (BuildId: xxx) #5 0x5b13f18d5c (/system/lib64/libwm.z.so0x1d8d5c) (BuildId: xxx) ......5.2 问题分析拿到一份 GWP-ASan 日志后首先看日志中的故障类型和关键调用栈。从日志首行可以看出该问题是 Use After Free即释放后使用异常访问地址为 0x5b41761010。继续看释放栈可以发现异常访问和释放动作都发生在线程 18502 中且报错栈和释放栈都指向 libwm.z.so 的相近位置• 报错栈#0 libwm.z.so0x1da6d0• 释放栈#2 libwm.z.so0x1da6cc重点分析这两个偏移对应的代码逻辑。通过 addr2line -Cfpie libwm.z.so 0x1da6d0 0x1da6cc 定位源码行关键代码如下1、对报错栈 libwm.z.so0x1da6d0 进行分析后发现这里对应一处三元运算符逻辑。代码会根据迭代器 it 是否指向 ownPropList.end() 来决定 insertPair 的值。2、对释放栈 libwm.z.so0x1da6cc 进行分析后发现这里做了容器erase的操作。问题此时已经比较明确erase(it) 会删除 it 指向的元素删除后原来的 it 会失效不能再继续使用。但当前代码在 erase(it) 之后又继续使用 it 参与三元判断并在特定分支下解引用 *it从而触发 Use After Free。5.3 修复建议需要避免在 erase(it) 后继续使用原来的迭代器 it。如果后续还需要使用该元素内容应在 erase 前先保存如果需要继续遍历则使用 erase 返回的新迭代器。六、聚类规则应用程序在不同版本或同一版本的不同时间可能因为同一根因产生多份 GWP-ASan 故障日志。但日志中的部分信息会随着版本、时间、地址随机化等因素发生变化导致开发者难以快速判断这些日志是否属于同一类问题。同时GWP-ASan 日志中通常同时包含系统侧和应用侧调用栈。如果不做过滤和归一化处理容易受到系统库栈帧、BuildID 等信息干扰不利于开发者快速聚焦应用侧问题。因此为避免重复分析多份故障信息提高应用故障问题的分析效率可以根据故障类型和业务相关调用栈对日志进行聚类。判断多份日志是否属于同一问题主要基于以下两个维度1、地址越界的故障类型。2、业务相关调用栈过滤基础库后的栈顶前两帧。通过上述信息可以对问题进行初步定界。6.1 步骤一提取故障类型在GWP-ASAN日志中故障类型根据原始日志中包含at的行提取。6.2 步骤二标准化栈信息提取故障类型后需要对日志中的调用栈进行筛选、清洗和标准化避免易变信息影响聚类结果。主要规则如下1. 去除易变信息行号、相同的BuildID和绝对地址。2. 过滤系统栈帧系统库栈帧通常以“/system/lib”或“/system/lib64”为起始字符。3. 保留业务相关栈帧保留业务so路径作为关键特征。例如原始栈帧内容标准化后栈帧内容#0 0x5b181acd48 (/lib/ld-musl-aarch64.so.10x169d48) (BuildId: 2e2fbc21511b76f80bdcc5ad5bcc0e79)忽略基础库#1 0x661be0e870 (/data/storage/el1/bundle/libs/arm64/libentry.so0x4e870) (BuildId: 5bc0684c2c3fc90841c2498efe1af4fd4792e5a8)/data/storage/el1/bundle/libs/arm64/libentry.so0x4e8706.3 步骤三提取聚类特征与聚类在完成标准化后根据不同故障类型提取关键栈作为特征故障类型关键栈帧提取规则Use After Free释放栈过滤基础库后取栈顶的前两帧so名相对偏移Buffer Overflow报错栈Double Free第一次释放栈第二次释放栈Invalid Free报错栈最终聚类特征为故障类型关键栈帧