android 性能分析工具(04)Asan 内存检测工具
1 Asan工具简介
1.1 Asan工具历史背景
AddressSanitizer(ASan)最初由Google开发,并作为LLVM项目的一部分。ASan的设计目的是帮助开发者检测并修复内存错误,如堆栈和全局缓冲区溢出、使用已释放的内存等,这些问题可能会影响程序的正确性和安全性。ASan通过在编译时插入额外的代码来检查每次内存访问,并在运行时监控这些访问,从而发现错误。
ASan的实现基于"shadow memory"技术,它为程序的内存分配创建了一个影子区域,用于跟踪内存的使用情况。当程序试图访问未初始化或已释放的内存时,ASan能够检测到这些非法访问并报告错误。这项技术使得ASan能够在不牺牲太多性能的情况下,有效地发现难以察觉的内存错误。
随着时间的推移,ASan已经成为GCC和Clang编译器的标准组成部分,广泛用于提高软件质量,特别是在内存安全性方面。此外,ASan的集成和易用性也得到了不断改进,例如在Visual Studio中,从2019版本16.9开始,MSVC编译器和IDE支持AddressSanitizer,并且提供了与IDE的集成,使得开发者可以更容易地在Windows平台上使用ASan。
1.2 Asan工具软件定位
AddressSanitizer(ASan)主要用于C和C++程序。它通过在编译时插入额外的检查代码,运行时监控程序的内存访问,来定位常见的内存错误,包括但不限于:
- 堆缓冲区溢出:检测堆上分配的内存是否被越界访问。
- 栈缓冲区溢出:检测栈上分配的局部变量是否超出其声明的范围。
- 全局变量溢出:检测全局或静态变量的内存是否被越界访问。
- 使用已释放的内存:检测程序是否尝试访问已经被释放的内存。
- 内存泄漏:实验性地检测内存泄漏问题。
- 初始化顺序问题:检测由于不正确的初始化顺序导致的问题。
- 内存越界访问:检测对内存的读写操作是否超出了其分配的范围。
- 函数返回局部变量:检测函数返回局部变量的引用,这通常会导致悬挂指针问题。
Asan的工作原理是在编译时对代码进行插桩,即在内存访问点周围添加额外的检查代码。运行时,Asan的运行时库会接管标准的内存管理函数,如malloc
和free
,以跟踪内存分配和释放的状态。当检测到内存错误时,Asan会立即报告错误并提供详细的错误信息,包括错误类型、发生错误的代码位置、调用栈信息等。
Asan的使用可以显著提高软件的内存安全性,帮助开发者在开发阶段就发现并修复潜在的内存问题。然而,由于ASan会引入额外的性能开销,它通常用于调试和测试阶段,而不推荐在生产环境中启用。
总的来说,ASan是一个功能强大且广泛使用的内存错误检测工具,能够有效地帮助开发者定位和解决C/C++程序中的内存问题,提高软件的稳定性和安全性。
接下来我们看看如何使用Asan工具。
2 Asan工具使用
2.1 如何使用Asan工具
使用ASan,你需要使用支持ASan的编译器。比如Clang或GCC,并开启ASan相关的编译选项。
如果使用Clang编译器,在终端执行以下命令:
$clang -fsanitize=address -g hellp.c -o hello
如果使用GCC编译器,在终端执行以下命令:
$gcc -fsanitize=address -g hello.c -o hello
在上述命令中,-fsanitize=address
是ASan的编译选项,用于开启ASan。但在大型工程(使用makefile方式进行编译)中我们一般通过环境变量的方式加入ASan编译选项,然后编译额时候需要加上环境变量,一般是CFLAGS
或者CXXFLAGS
。
2.2 -fsanitize选项常见的sanitizer解读
以下是一些常见的sanitizer选项及其含义:
- -fsanitize=address:这个选项启用了地址sanitizer,它是一个运行时检测工具,用于检测C/C++程序中的内存错误,如缓冲区溢出、使用后释放(use-after-free)错误等。地址sanitizer会在程序运行时监控内存操作,当检测到错误时,它会提供详细的错误报告,包括错误类型、发生位置和堆栈跟踪信息。
- -fsanitize-recover=address:这个选项与-fsanitize=address一起使用,它告诉地址sanitizer在检测到内存错误时,尝试恢复程序的执行,而不是立即终止程序。这可以使得程序在检测到某些内存错误后继续运行,这对于调试和分析程序的行为非常有用。
- -fsanitize=memory:MemorySanitizer(MSan)用于检测未初始化的内存读取。它通过为每个值关联一个“起源”来工作,如果一个值被读取时其起源是未知的,MSAN就会报告一个错误。
- -fsanitize=undefined:UndefinedBehaviorSanitizer(UBSan)用于检测未定义行为,例如整数溢出、空指针解引用、除以零等问题。
- -fsanitize=thread:ThreadSanitizer(TSan)用于检测多线程程序中的数据竞争和死锁问题。
- -fsanitize=leak:这个选项已经被弃用,取而代之的是使用-fsanitize=address,它现在包含了内存泄漏检测功能。
- -fsanitize=coverage:CoverageSanitizer(CovSan)用于生成代码覆盖率报告,检测程序中哪些部分被执行过。
- -fsanitize=kernel-address:Kernel Address Sanitizer(KASan)是AddressSanitizer的一个变种,专门用于检测Linux内核中的内存错误。
- -fsanitize=dataflow:这个选项允许开发者定义自己的数据流问题,并在运行时检测它们。
- -fsanitize=cfi:Control Flow Integrity(CFI)Sanitizer用于检测控制流相关的安全问题,比如虚拟函数表破坏攻击。
- -fsanitize=safe-stack:SafeStack通过将安全关键的栈数据移动到堆上来保护程序,从而防止栈溢出攻击。
- -fsanitize=hwaddress:Hardware Address Sanitizer(HWASAN)用于检测硬件级别的内存错误,比如加载无效的地址。
这些sanitizer工具是查找隐藏bug的利器,它们可以帮助开发者在开发和测试阶段发现和修复潜在的错误。需要注意的是,这些工具会增加程序的运行时间和内存开销,因此主要用于调试和测试阶段,不推荐在生产环境中启用。
3 asan部分实战解读
这里给出一些asan的具体使用方法,当然,限于常用的一些场景。其他场景参考即可。分析问题和解决问题的思路是类似的。
3.1 堆缓冲区溢出(Heap Buffer Overflow)
案例描述:检测对堆分配的缓冲区边界以外的内存写入操作。
参考代码如下:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *buffer = (char *)malloc(5 * sizeof(char));
if (buffer == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
sprintf(buffer, "Hello, World!");
printf("Buffer contents: %s\n", buffer);
free(buffer);
return 0;
}
使用以下命令编译程序,并启用AddressSanitizer:
$gcc -fsanitize=address -g -o heap_overflow heap_overflow.c
然后使用gdb运行编译出的程序:
$gdb ./heap_overflow
执行后 得到如下调试信息:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
=================================================================
==1346454==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000015 at pc 0x7ffff745f04f bp 0x7fffffffdaf0 sp 0x7fffffffd280
WRITE of size 14 at 0x602000000015 thread T0
#0 0x7ffff745f04e in __interceptor_vsprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1687
#1 0x7ffff745f27e in __interceptor_sprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1730
#2 0x5555555552f2 in main /media/wangdaosheng/data/backup/tmp/test2/heap_overflow.c:14
#3 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7ffff7029e3f in __libc_start_main_impl ../csu/libc-start.c:392
#5 0x5555555551a4 in _start (/media/wangdaosheng/data/backup/tmp/test2/heap_overflow+0x11a4)
0x602000000015 is located 0 bytes to the right of 5-byte region [0x602000000010,0x602000000015)
allocated by thread T0 here:
#0 0x7ffff74b4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x55555555527e in main /media/wangdaosheng/data/backup/tmp/test2/heap_overflow.c:6
#2 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1687 in __interceptor_vsprintf
Shadow bytes around the buggy address:
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[05]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1346454==ABORTING
[Inferior 1 (process 1346454) exited with code 01]
这个错误信息是由 AddressSanitizer(ASan)提供的。根据提供的信息,我们可以分析出以下关键信息:
-
错误类型:
AddressSanitizer: heap-buffer-overflow
表示发生了堆缓冲区溢出。 -
错误位置:错误发生在地址
0x602000000015
,这是一个堆内存地址。 -
出错指令:
WRITE of size 14 at 0x602000000015
表示在地址0x602000000015
处尝试写入 14 个字节的数据。 -
程序计数器(PC)和基指针(BP):
pc 0x7ffff745f04f
和bp 0x7fffffffdaf0
提供了出错时的程序计数器和基指针的值,这对于调试器来说是有用的。 -
出错函数:错误发生在
__interceptor_vsprintf
函数中,这是 ASan 对vsprintf
函数的拦截实现。 -
调用栈:调用栈显示了错误发生时的函数调用顺序。在这里,我们看到
sprintf
函数(第2行)被main
函数(第3行)调用,这是错误发生的源头。 -
内存分配位置:出错的内存是在
main
函数的第6行分配的,由__interceptor_malloc
拦截。 -
Shadow Bytes:Shadow Bytes 是 ASan 使用的一块内存区域,用于跟踪实际内存的状态。这里的 Shadow Bytes 显示了出错地址附近的内存状态。
-
错误摘要:
SUMMARY: AddressSanitizer: heap-buffer-overflow
再次总结了错误类型。 -
程序退出:
==1346454==ABORTING
表示由于这个严重错误,程序被ASan中止执行。
在这个案例中,错误是由于 sprintf
写入的数据超出了分配的5字节大小的缓冲区,导致堆缓冲区溢出。
3.2 栈缓冲区溢出(Stack Buffer Overflow)
案例描述:检测对栈分配的缓冲区边界以外的内存写入操作。
参考代码如下:
#include <stdio.h>
#include <string.h>
int main() {
char buffer[4];
strcpy(buffer, "Hello");
printf("Buffer contents: %s\n", buffer);
return 0;
}
使用以下命令编译程序,并启用AddressSanitizer:
$gcc -fsanitize=address -g -o stack_overflow stack_overflow.c
然后使用gdb运行编译出的程序:
$gdb ./stack_overflow
执行后 得到如下调试信息:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
=================================================================
==1680994==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffffffdba4 at pc 0x7ffff743a2c3 bp 0x7fffffffdb70 sp 0x7fffffffd318
WRITE of size 6 at 0x7fffffffdba4 thread T0
#0 0x7ffff743a2c2 in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827
#1 0x5555555552ca in main /media/wangdaosheng/data/backup/tmp/test2/stack_overflow.c:6
#2 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x7ffff7029e3f in __libc_start_main_impl ../csu/libc-start.c:392
#4 0x555555555164 in _start (/media/wangdaosheng/data/backup/tmp/test2/stack_overflow+0x1164)
Address 0x7fffffffdba4 is located in stack of thread T0 at offset 36 in frame
#0 0x555555555238 in main /media/wangdaosheng/data/backup/tmp/test2/stack_overflow.c:4
This frame has 1 object(s):
[32, 36) 'buffer' (line 5) <== Memory access at offset 36 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy
Shadow bytes around the buggy address:
0x10007fff7b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10007fff7b70: f1 f1 f1 f1[04]f3 f3 f3 00 00 00 00 00 00 00 00
0x10007fff7b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7b90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7ba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7bb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10007fff7bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1680994==ABORTING
[Inferior 1 (process 1680994) exited with code 01]
这个错误信息是由 AddressSanitizer(ASan)提供的。根据提供的信息,我们可以分析出以下关键信息:
- 错误类型:
AddressSanitizer: stack-buffer-overflow
表示发生了栈缓冲区溢出。 - 错误位置:错误发生在地址
0x7fffffffdba4
,这是一个栈内存地址。 - 出错指令:
WRITE of size 6 at 0x7fffffffdba4
表示在地址0x7fffffffdba4
处尝试写入 6 个字节的数据。 - 程序计数器(PC)和基指针(BP)以及栈指针(SP):
pc 0x7ffff743a2c3
,bp 0x7fffffffdb70
,sp 0x7fffffffd318
提供了出错时的程序计数器、基指针和栈指针的值,这对于调试器来说是有用的。 - 出错函数:错误发生在
__interceptor_memcpy
函数中,这是 ASan 对memcpy
函数的拦截实现。 - 调用栈:调用栈显示了错误发生时的函数调用顺序。在这里,我们看到
memcpy
函数(第1行)被main
函数(第2行)调用,这是错误发生的源头。 - 内存分配位置:出错的内存是在
main
函数的第4行分配的,由栈帧信息指出。 - Shadow Bytes:Shadow Bytes 是 ASan 使用的一块内存区域,用于跟踪实际内存的状态。这里的 Shadow Bytes 显示了出错地址附近的内存状态。
- 错误摘要:
SUMMARY: AddressSanitizer: stack-buffer-overflow
再次总结了错误类型。 - 程序退出:
==1680994==ABORTING
表示由于这个严重错误,程序被ASan中止执行。
在这个案例中,错误是由于 memcpy
写入的数据超出了分配的缓冲区大小,导致栈缓冲区溢出。
3.3 全局缓冲区溢出(Global Buffer Overflow)
案例描述:检测对全局变量边界以外的内存写入操作。
参考代码如下:
#include <stdio.h>
#include <string.h>
char globalBuffer[5];
int main() {
strcpy(globalBuffer, "Hello, World!");
printf("Global Buffer contents: %s\n", globalBuffer);
return 0;
}
使用以下命令编译程序,并启用AddressSanitizer:
$gcc -fsanitize=address -g -o global_overflow global_overflow.c
然后运行编译出的程序:
$gdb ./global_overflow
执行后 得到如下调试信息:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
=================================================================
==1681772==ERROR: AddressSanitizer: global-buffer-overflow on address 0x555555558125 at pc 0x7ffff743a2c3 bp 0x7fffffffdbf0 sp 0x7fffffffd398
WRITE of size 14 at 0x555555558125 thread T0
#0 0x7ffff743a2c2 in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827
#1 0x55555555520e in main /media/wangdaosheng/data/backup/tmp/test2/global_overflow.c:7
#2 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x7ffff7029e3f in __libc_start_main_impl ../csu/libc-start.c:392
#4 0x555555555124 in _start (/media/wangdaosheng/data/backup/tmp/test2/global_overflow+0x1124)
0x555555558125 is located 0 bytes to the right of global variable 'globalBuffer' defined in 'global_overflow.c:4:6' (0x555555558120) of size 5
SUMMARY: AddressSanitizer: global-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy
Shadow bytes around the buggy address:
0x0aab2aaa2fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa2fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa2ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa3000: 00 00 00 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9
0x0aab2aaa3010: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
=>0x0aab2aaa3020: 00 00 00 00[05]f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0aab2aaa3030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa3040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa3050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa3060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0aab2aaa3070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1681772==ABORTING
[Inferior 1 (process 1681772) exited with code 01]
这个错误信息是由 AddressSanitizer(ASan)提供的。根据提供的信息,我们可以分析出以下关键信息:
- 错误类型:
AddressSanitizer: global-buffer-overflow
表示发生了全局缓冲区溢出。 - 错误位置:错误发生在地址
0x555555558125
,这是一个全局变量的地址。 - 出错指令:
WRITE of size 14 at 0x555555558125
表示在地址0x555555558125
处尝试写入 14 个字节的数据。 - 程序计数器(PC)、基指针(BP)和栈指针(SP):
pc 0x7ffff743a2c3
,bp 0x7fffffffdbf0
,sp 0x7fffffffd398
提供了出错时的程序计数器、基指针和栈指针的值,这对于调试器来说是有用的。 - 出错函数:错误发生在
__interceptor_memcpy
函数中,这是 ASan 对memcpy
函数的拦截实现。 - 调用栈:调用栈显示了错误发生时的函数调用顺序。在这里,我们看到
memcpy
函数(第1行)被main
函数(第2行)调用,这是错误发生的源头。 - 内存分配位置:出错的内存是全局变量
globalBuffer
,定义在global_overlap.c:4:6
,大小为 5 字节。 - Shadow Bytes:Shadow Bytes 是 ASan 使用的一块内存区域,用于跟踪实际内存的状态。这里的 Shadow Bytes 显示了出错地址附近的内存状态。
- 错误摘要:
SUMMARY: AddressSanitizer: global-buffer-overflow
再次总结了错误类型。 -
程序退出:
==1681772==ABORTING
表示由于这个严重错误,程序被ASan中止执行。
在这个案例中,错误是由于 memcpy
写入的数据超出了全局变量 globalBuffer
分配的大小,导致全局缓冲区溢出。
3.4 使用后释放(Use After Free)
案例描述:检测已经释放的内存被再次访问的情况。
参考代码如下:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
*ptr = 42;
printf("Value at ptr: %d\n", *ptr);
free(ptr);
printf("Value at ptr after free: %d\n", *ptr);
return 0;
}
使用以下命令编译程序,并启用AddressSanitizer:
$gcc -fsanitize=address -g -o use_after_free use_after_free.c
然后运行编译出的程序:
$gdb ./use_after_free
执行后 得到如下调试信息:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Value at ptr: 42
=================================================================
==1682910==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x55555555539b bp 0x7fffffffd7a0 sp 0x7fffffffd790
READ of size 4 at 0x602000000010 thread T0
#0 0x55555555539a in main /media/wangdaosheng/data/backup/tmp/test2/use_after_free.c:14
#1 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#2 0x7ffff7029e3f in __libc_start_main_impl ../csu/libc-start.c:392
#3 0x5555555551c4 in _start (/media/wangdaosheng/data/backup/tmp/test2/use_after_free+0x11c4)
0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
#0 0x7ffff74b4537 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127
#1 0x555555555363 in main /media/wangdaosheng/data/backup/tmp/test2/use_after_free.c:13
#2 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
previously allocated by thread T0 here:
#0 0x7ffff74b4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x55555555529e in main /media/wangdaosheng/data/backup/tmp/test2/use_after_free.c:5
#2 0x7ffff7029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-use-after-free /media/wangdaosheng/data/backup/tmp/test2/use_after_free.c:14 in main
Shadow bytes around the buggy address:
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[fd]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1682910==ABORTING
[Inferior 1 (process 1682910) exited with code 01]
这个错误信息是由 AddressSanitizer(ASan)提供的。根据提供的信息,我们可以分析出以下关键信息:
- 错误类型:
AddressSanitizer: heap-use-after-free
表示发生了堆使用后释放的错误。 - 错误位置:错误发生在地址
0x602000000010
,这是一个堆内存地址。 - 出错指令:
READ of size 4 at 0x602000000010
表示在地址0x602000000010
处尝试读取 4 个字节的数据。 - 程序计数器(PC)、基指针(BP)和栈指针(SP):
pc 0x55555555539b
,bp 0x7fffffffd7a0
,sp 0x7fffffffd790
提供了出错时的程序计数器、基指针和栈指针的值,这对于调试器来说是有用的。 - 出错函数:错误发生在
main
函数的第14行。 - 内存释放位置:出错的内存是在
main
函数的第13行被释放的。 - 内存分配位置:出错的内存是在
main
函数的第5行被分配的。 - Shadow Bytes:Shadow Bytes 是 ASan 使用的一块内存区域,用于跟踪实际内存的状态。这里的 Shadow Bytes 显示了出错地址附近的内存状态,其中
fd
表示已释放的堆内存区域。 - 错误摘要:
SUMMARY: AddressSanitizer: heap-use-after-free
再次总结了错误类型。 - 程序退出:
==1682910==ABORTING
表示由于这个严重错误,程序被ASan中止执行。
3.5 多线程数据竞争(Data Race):
案例描述:检测多线程程序中不同线程对同一数据的竞态访问。
参考代码如下:
#include <stdio.h>
#include <pthread.h>
int global_var = 0;
void* increment(void* arg) {
int i;
for (i = 0; i < 100; ++i) {
global_var++; // 此处存在数据竞争
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, increment, NULL);
pthread_create(&thread2, NULL, increment, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Result: %d\n", global_var);
return 0;
}
使用以下命令编译程序,并启用AddressSanitizer:
$gcc -fsanitize=thread -g -o data_race data_race.c -lpthread
然后运行编译出的程序:
$gdb ./data_race
执行后 得到如下调试信息:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff51ff640 (LWP 2156986)]
[New Thread 0x7ffff49fe640 (LWP 2156987)]
[Thread 0x7ffff49fe640 (LWP 2156987) exited]
[New Thread 0x7ffff41fd640 (LWP 2156988)]
==================
WARNING: ThreadSanitizer: data race (pid=2156978)
Read of size 4 at 0x555555558014 by thread T2:
#0 increment /media/wangdaosheng/data/backup/tmp/test2/data_race.c:9 (data_race+0x129d)
Previous write of size 4 at 0x555555558014 by thread T1:
#0 increment /media/wangdaosheng/data/backup/tmp/test2/data_race.c:9 (data_race+0x12b5)
Location is global 'global_var' of size 4 at 0x555555558014 (data_race+0x000000004014)
Thread T2 (tid=2156988, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
#1 main /media/wangdaosheng/data/backup/tmp/test2/data_race.c:18 (data_race+0x133a)
Thread T1 (tid=2156987, finished) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8)
#1 main /media/wangdaosheng/data/backup/tmp/test2/data_race.c:17 (data_race+0x131d)
SUMMARY: ThreadSanitizer: data race /media/wangdaosheng/data/backup/tmp/test2/data_race.c:9 in increment
==================
Result: 200
ThreadSanitizer: reported 1 warnings
[Thread 0x7ffff41fd640 (LWP 2156988) exited]
[Thread 0x7ffff51ff640 (LWP 2156986) exited]
[Inferior 1 (process 2156978) exited with code 0102]
这个错误信息是由 ThreadSanitizer(TSan)提供的,它帮助检测多线程程序中的数据竞争问题。根据提供的信息,我们可以分析出以下关键点:
- 错误类型:
WARNING: ThreadSanitizer: data race
表示检测到数据竞争。 - 竞争位置:数据竞争发生在地址
0x555555558014
,这是全局变量global_var
的地址。 - 出错指令:
Read of size 4 at 0x555555558014 by thread T2
和Previous write of size 4 at 0x555555558014 by thread T1
表示线程 T2 读取了global_var
,而线程 T1 在之前写了global_var
。 - 竞争变量:竞争的变量是全局变量
global_var
,大小为 4 字节。 - 线程信息:线程 T2(tid=2156988)和线程 T1(tid=2156987)都参与了这次数据竞争。
- 线程创建:两个线程都是由主线程(main thread)创建的,创建位置在
main
函数中。 - 错误摘要:
SUMMARY: ThreadSanitizer: data race
再次总结了错误类型。 - 程序退出:
[Inferior 1 (process 2156978) exited with code 0102]
表示程序由于TSan报告的错误而退出。
在这个案例中,错误是由于两个线程同时访问和修改全局变量 global_var
导致的。