ASAN中无崩溃测试方案实现

# 背景

为提高版本的稳定性,在app灰度时做asan测试

其中一个技术点是实现asan的无崩溃检测方案。但按文档操作发现不生效。

正常ASAN的配置方法查看 <https://developer.android.com/ndk/guides/asan>

为了实现asan的无崩溃测试方案,跟着文档开启halt_on_error=0,但是测试中发现

halt_on_error=0不生效,故深入调查。可直接看结论

# 文档的错误说明

阅读文档说明

<https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html>

要开启无崩溃方案,需要在so编译时添加sanitize-recover参数,但文档说默认是打开recover的

![](https://tech-proxy.bytedance.net/tos/images/1644921785042_7fa914498481debf54cd98863cee2b49)

<https://github.com/google/sanitizers/wiki/AddressSanitizerFlags>

但是配置这编译参数发现还是有区别

## 不同参数的汇编差异对比

在demo中编译过asan so,看看不同参数的区别

## noasan

没有开启asan工具,测试代码不崩溃,没有插桩,没有主动abort的代码

![](https://tech-proxy.bytedance.net/tos/images/1644921784953_0da4c81f6e7ea8925c9b654570629f05)

## asan-recover

demo加了sanitize-recover,发现也会崩溃

![](https://tech-proxy.bytedance.net/tos/images/1644921784855_5c7b8ead7edb9a791e22e65618fac3ee)

这个崩溃和文档说明不符,明明是跳转到__asan_report_load1_noabort函数了,为什么还崩呢。

![](https://tech-proxy.bytedance.net/tos/images/1644921784848_d562dc8323e35c09d45bc0614af675dd)

跟着崩溃堆栈找到就运行了41行,但是我们不应该跑到41行的,因为halt_on_error已经被设置为0了,检查发现是wrap.sh没有打包到apk里,导致值配置失效。

解决了这个问题后后面还崩,反编译看汇编代码

![](https://tech-proxy.bytedance.net/tos/images/1644921784860_94d2f88c87303fb0d8f9d4478c8cbe25)

原来写文件失败也会崩

配置正确的路径后-fsanitize-recover=address终于能按正确的逻辑继续跑asan而不崩溃了 SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer")

![](https://tech-proxy.bytedance.net/tos/images/1644921785070_5a4fc670f512a510502fe273bab8739d)

### 下面是汇编推导找到不崩溃分支的代码过程(可以忽略)

#### 新版本配不配置cover参数都崩溃,从0行开始定位

demo崩溃堆栈,加了cover参数

```
    #00 pc 000000000002237c  /system/lib64/libc.so (abort+116)
    #01 pc 000000000008ce88  /lib/arm64/libclang_rt.asan-aarch64-android.so BL .abort
    #02 pc 0000000000092d40  /lib/arm64/libclang_rt.asan-aarch64-android.so BL _ZN11__sanitizer5AbortEv
    #03 pc 0000000000079b84  /lib/arm64/libclang_rt.asan-aarch64-android.so BL _ZN11__sanitizer3DieEv
    #04 pc 000000000007abb8  /lib/arm64/libclang_rt.asan-aarch64-android.so BL _ZN6__asan19ScopedInErrorReportD2Ev 
    #05 pc 0000000000065f30  /lib/arm64/libclang_rt.asan-aarch64-android.so (__asan_memset+552)
    #06 pc 0000000000013d2c  /lib/arm64/libmodule-lib.so (_Z3usrv+132)
    #07 pc 0000000000013de0  /lib/arm64/libmodule-lib.so (Java_com_example_mylibrary_Module_loadttf+108)
```

手动转了指令

下面就是找到__asan_report_load1_noabort崩和不崩的判断分支

#### _ZN11__sanitizer3DieEv

看堆栈符号就是79b84里出事,有die字眼

编译一路看下来,在跳转 _ZN11__sanitizer5AbortEv 时有个 CBNZ的判断,是不是不跳92d40就可以不崩了呢?为什么要跳呢,往上找逻辑

![](https://tech-proxy.bytedance.net/tos/images/1644921785005_b3485581489a019534501dd5796f76c4)

以防万一,CBNZ翻书看定义

![](https://tech-proxy.bytedance.net/tos/images/1644921784986_537042d307946a967f7cfdc2ae33ffa8)

在这里就是对比W9是不是0,不是0就跳到崩溃代码

那现在是w9不为0了,那w9是啥

![](https://tech-proxy.bytedance.net/tos/images/1644921785257_b405fb9b3606412d8d013a44f24f6451)

地址上看是个栈内存,搜到内存没有打印

x8是去读取common flags配置

![](https://tech-proxy.bytedance.net/tos/images/1644921784949_f3182415965af9773be74d905e1d1f40)

![](https://tech-proxy.bytedance.net/tos/images/1644921785275_4999df1cc2807d7a93090096b5d1aaf1)

这块逻辑太难推,继续努力

![](https://tech-proxy.bytedance.net/tos/images/1644921785452_d728f8e65ad0d5a4ae7573c30c3dc248)

手推

.text:0000000000092D00 ADRP X8, #_ZN11__sanitizer21common_flags_dont_useE_ptr@PAGE

.text:0000000000092D04 LDR X8, [X8,#_ZN11__sanitizer21common_flags_dont_useE_ptr@PAGEOFF]

拿的就是 common_flags_dont_use 的指针

LDRB W9, [X8,#(byte_553CAC - 0x553BE8)] = LDRB W9, [X8,0xC4]

C4=196

就是拿common_flags_dont_use偏移 C4位的变量

这个变量是什么,和版本的类有关

我拿21版本看看

<https://code.woboq.org/llvm/compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp.html#__sanitizer::common_flags_dont_use>

![](https://tech-proxy.bytedance.net/tos/images/1644921784896_af57777e0a23807c52e5756996c2da3f)

```
namespace __sanitizer {
19
20enum HandleSignalMode {
21  kHandleSignalNo,
22  kHandleSignalYes,
23  kHandleSignalExclusive,
24};
25
26struct CommonFlags {
27#define COMMON_FLAG(Type, Name, DefaultValue, Description) Type Name;
28#include "sanitizer_flags.inc"
29#undef COMMON_FLAG
30
31  void SetDefaults();
32  void CopyFrom(const CommonFlags &other);
33};
34
35 // Functions to get/set global CommonFlags shared by all sanitizer runtimes: 36extern CommonFlags common_flags_dont_use;
37inline const CommonFlags *common_flags() {
38  return &common_flags_dont_use;
39}
40
41inline void SetCommonFlagsDefaults() {
42  common_flags_dont_use.SetDefaults();
43}
44
45 // This function can only be used to setup tool-specific overrides for 46 // CommonFlags defaults. Generally, it should only be used right after 47 // SetCommonFlagsDefaults(), but before ParseCommonFlagsFromString(), and 48 // only during the flags initialization (i.e. before they are used for 49 // the first time). 50inline void OverrideCommonFlags(const CommonFlags &cf) {
51  common_flags_dont_use.CopyFrom(cf);
52}
```

<https://code.woboq.org/llvm/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc.html>

这个就是配置变量,说真手推我是没信心确认是哪个,太大了

![](https://tech-proxy.bytedance.net/tos/images/1644921785320_69980a77401720552175602b46cb32a3)

![](https://tech-proxy.bytedance.net/tos/images/1644921785118_21b0aabb109a1abb17974ce0086b5801)

看分支是abort和exit的分支变量,

我可以确认这里没有halt_on_error变量。到这个_ZN11__sanitizer3DieEv函数里就应该是必崩的了,那往上一个堆栈找

#### __asan::ScopedInErrorReport::~ScopedInErrorReport

79A38

![](https://tech-proxy.bytedance.net/tos/images/1644921784989_d207713c84df215dacb3035b53bbc852)

![](https://tech-proxy.bytedance.net/tos/images/1644921784983_1336f28154ff8db72c18c4759cc841e8)

拿锁成功就会跑到abort代码,另外一条路是做unmap等兼容性操作,一般不跑,这样的话就需要再往上看一层

#### __asan_memset

65f30

![](https://tech-proxy.bytedance.net/tos/images/1644921785159_d014d9679bd51bad70c7c03fb8a381fe)

终于看到能跳出来的分支了,看看什么条件才能跳出来

1 asan还在初始化时不崩

![](https://tech-proxy.bytedance.net/tos/images/1644921784946_ef3290514deb136291879248ab4f1de8)

2

__asan28asan_flags_dont_use_directly

![](https://tech-proxy.bytedance.net/tos/images/1644921784943_e62d5ceef9f0b47d7e7b3e331ca9fa94)

__asan_region_is_poisoned 这里有个跳转,判断有没有破坏asan的区域,破坏了就会崩

也就是说不能跑到

由于手推真辛苦,效率也低,安装了反编译插件提高效率

#### __asan_report_load1_noabort

![](https://tech-proxy.bytedance.net/tos/images/1644921784853_8063c73881d81dc49d6595442c9b1366)

#### ReportGenericError

![](https://tech-proxy.bytedance.net/tos/images/1644921784870_898597f6afd63e26edc7d955560feb5e)

#### __asan::ScopedInErrorReport::~ScopedInErrorReport

![](https://tech-proxy.bytedance.net/tos/images/1644921784801_0debd6b3eb85b880591d5bb245899c8c)

终于找到了,看到这个字眼和文档相关就引起了注意,再看分支判断就是崩和不崩的关键所在

## asan

开启asan工具测试代码崩溃

![](https://tech-proxy.bytedance.net/tos/images/1644921784838_56caf76aa9d912d4e341524927d97e99)

插桩的函数已经不一样了,实现就是会崩掉

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fsanitize=address -fno-omit-frame-pointer")

![](https://tech-proxy.bytedance.net/tos/images/1644921784992_eb75a163d310b036e694f806f8c007ca)

# 结论

官方文档有误,无崩溃方案so编译时需要配置sanitize-recover参数

*SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer")*

配置 wrap.sh脚本增加 halt_on_error=0 参数

其他配置和正常的ASAN一样,就可以实现无崩溃的ASAN测试了

# 参考文档

正常ASAN的配置方法查看 <https://developer.android.com/ndk/guides/asan>

文档中的ida工具使用和基础知识 [运行时修改so汇编代码的方法](https://bytedance.feishu.cn/wiki/wikcnQ6vvLyxfKwtnVfeU4WAbOe)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值