5 - Debugging Tools for Memory Issues - 内存问题调试工具

Debugging Tools for Memory Issues - 内存问题调试工具

我的博客

程序源码

这里我们对第五章中的代码进行分析。这里只是原文的一部分,详见原文。

工具类型

有两类分析工具:

  • 动态分析工具
  • 静态分析工具

动态分析工具会询问运行的进程,静态分析工具会分析源码。我们这里使用的工具都可以划分到动态分析工具类中。

Valgrind

这是一个开源软件,在 GNU GPL ver.2 下发布,最初由 Julian Seward 开发。实际上,它是一个虚拟机,提供下面的功能:

工具名 功能
cachegrind CPU cache 分析器
callgrind cachegrind 的扩展
drd Pthreads 问题分析器
helgrind 多线程应用数据竞争分析器
massif 堆分析器
Memcheck 内存问题分析器,包括带外访问、未初始化访问、UAF、UAR、内存泄露、多重释放等

这里我们关注 Valgrind 提供的 Memcheck 工具。

使用 Valgrind Memcheck 工具

MemcheckValgrind 默认工具,不需要显式传递参数就可以使用,当然也可以使用 valgrind --tool=memcheck 来使用,下面是使用 valgrind 查看 df 的例子:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal
$ df --version | head -n1
df (GNU coreutils) 8.30
$ valgrind df
==102686== Memcheck, a memory error detector
==102686== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==102686== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==102686== Command: df
==102686==
Filesystem                        1K-blocks     Used Available Use% Mounted on
==102686==
==102686== HEAP SUMMARY:
==102686==     in use at exit: 5,708 bytes in 335 blocks
==102686==   total heap usage: 801 allocs, 466 frees, 42,271 bytes allocated
==102686==
==102686== LEAK SUMMARY:
==102686==    definitely lost: 0 bytes in 0 blocks
==102686==    indirectly lost: 0 bytes in 0 blocks
==102686==      possibly lost: 0 bytes in 0 blocks
==102686==    still reachable: 5,708 bytes in 335 blocks
==102686==         suppressed: 0 bytes in 0 blocks
==102686== Rerun with --leak-check=full to see details of leaked memory
==102686==
==102686== For lists of detected and suppressed errors, rerun with: -s
==102686== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

valgrind 接管了这一过程并运行了 df 进程,询问了它所有的动态内存访问,之后将这一信息打印出来。上面展示中的 102686 是 df 进程的 PID。

上面这个执行过程显示没有发现内存问题。上面的 still reachable 表示在进程退出时,一些内存块没有被应用显式释放(但是在进程结束时被隐式释放), total heap usage: 801 allocs, 466 frees, 42271 bytes allocated 结合 in use at exit: 5708 bytes in 335 blocks 可知有 335 个块没有被释放。

现在,我们运行上一章中的 membug 程序,看一下它在 valgrind 下是什么样子的。

访问未经初始化的内存的情况:

$ valgrind ./membugs_dbg 1
==103211== Memcheck, a memory error detector
==103211== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==103211== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==103211== Command: ./membugs_dbg 1
==103211==
==103211== Conditional jump or move depends on uninitialised value(s)
==103211==    at 0x109FFE: uninit_var (membugs.c:298)
==103211==    by 0x10A142: process_args (membugs.c:342)
==103211==    by 0x10A272: main (membugs.c:398)
==103211==
false case
==103211==
==103211== HEAP SUMMARY:
==103211==     in use at exit: 12 bytes in 1 blocks
==103211==   total heap usage: 4 allocs, 3 frees, 5,604 bytes allocated
==103211==
==103211== LEAK SUMMARY:
==103211==    definitely lost: 0 bytes in 0 blocks
==103211==    indirectly lost: 0 bytes in 0 blocks
==103211==      possibly lost: 0 bytes in 0 blocks
==103211==    still reachable: 12 bytes in 1 blocks
==103211==         suppressed: 0 bytes in 0 blocks
==103211== Rerun with --leak-check=full to see details of leaked memory
==103211==
==103211== Use --track-origins=yes to see where uninitialised values come from
==103211== For lists of detected and suppressed errors, rerun with: -s
==103211== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Conditional jump or move depends on uninitialised value(s)ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) at 0x109FFE: uninit_var (membugs.c:298),可以看到,valgrind 捕捉到了未初始化的内存访问的问题(只有未经编译器优化的编译产物才能被捕捉到,经编译器优化的编译产物则不能捕捉到这一现象,我使用的编译器版本、valgrind 版本与原文都不同,因此执行效果会不一样)。

编译时内存溢出问题:

$ valgrind ./membugs_dbg 5
==103503== Memcheck, a memory error detector
==103503== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==103503== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==103503== Command: ./membugs_dbg 5
==103503==
arr = aaaaattttttt
==103503==
==103503== HEAP SUMMARY:
==103503==     in use at exit: 12 bytes in 1 blocks
==103503==   total heap usage: 4 allocs, 3 frees, 5,604 bytes allocated
==103503==
==103503== LEAK SUMMARY:
==103503==    definitely lost: 0 bytes in 0 blocks
==103503==    indirectly lost: 0 bytes in 0 blocks
==103503==      possibly lost: 0 bytes in 0 blocks
==103503==    still reachable: 12 bytes in 1 blocks
==103503==         suppressed: 0 bytes in 0 blocks
==103503== Rerun with --leak-check=full to see details of leaked memory
==103503==
==103503== For lists of detected and suppressed errors, rerun with: -s
==103503== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

valgrind 没能捕获到读溢出内存问题,因为 valgrind 自身的限制,它只能询问并捕获动态分配内存的问题,上面的例子是静态分配内存。

静态分配内存的内存溢出问题:

$ valgrind ./membugs_dbg 6
==103577== Memcheck, a memory error detector
==103577== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==103577== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==103577== Command: ./membugs_dbg 6
==103577==
==103577== Invalid write of size 1
==103577==    at 0x109D04: read_overflow_dynmem (membugs.c:221)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==  Address 0x4c5b2f5 is 0 bytes after a block of size 5 alloc'd
==103577==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103577==    by 0x109CB0: read_overflow_dynmem (membugs.c:212)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==
==103577== Invalid read of size 1
==103577==    at 0x483EF54: strlen (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103577==    by 0x4AD8D14: __vfprintf_internal (vfprintf-internal.c:1688)
==103577==    by 0x4AC1D3E: printf (printf.c:33)
==103577==    by 0x109D55: read_overflow_dynmem (membugs.c:223)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==  Address 0x4c5b2f5 is 0 bytes after a block of size 5 alloc'd
==103577==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103577==    by 0x109CB0: read_overflow_dynmem (membugs.c:212)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==
...
arr = aaaaaSecreT
==103577== Use of uninitialised value of size 8
==103577==    at 0x4ABB69B: _itoa_word (_itoa.c:179)
==103577==    by 0x4AD7574: __vfprintf_internal (vfprintf-internal.c:1687)
==103577==    by 0x4AC1D3E: printf (printf.c:33)
==103577==    by 0x109D76: read_overflow_dynmem (membugs.c:226)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==
...
*(arr+100)=0
==103577== Invalid read of size 1
==103577==    at 0x109D81: read_overflow_dynmem (membugs.c:227)
==103577==    by 0x10A174: process_args (membugs.c:357)
==103577==    by 0x10A272: main (membugs.c:398)
==103577==  Address 0x4c5da00 is 8,832 bytes inside an unallocated block of size 4,188,256 in arena "client"
==103577==
*(arr+10000)=0
==103577==
==103577== HEAP SUMMARY:
==103577==     in use at exit: 12 bytes in 1 blocks
==103577==   total heap usage: 5 allocs, 4 frees, 5,609 bytes allocated
==103577==
==103577== LEAK SUMMARY:
==103577==    definitely lost: 0 bytes in 0 blocks
==103577==    indirectly lost: 0 bytes in 0 blocks
==103577==      possibly lost: 0 bytes in 0 blocks
==103577==    still reachable: 12 bytes in 1 blocks
==103577==         suppressed: 0 bytes in 0 blocks
==103577== Rerun with --leak-check=full to see details of leaked memory
==103577==
==103577== Use --track-origins=yes to see where uninitialised values come from
==103577== For lists of detected and suppressed errors, rerun with: -s
==103577== ERROR SUMMARY: 31 errors from 16 contexts (suppressed: 0 from 0)

显然我们可以看到这个程序中存在的内存问题。

释放后使用问题:

$ valgrind ./membugs_dbg 8
...
==103725== Invalid write of size 1
==103725==    at 0x483F260: strncpy (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x109B3D: uaf (membugs.c:180)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)
==103725==  Address 0x4c5b2f0 is 0 bytes inside a block of size 512 free'd
==103725==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x109B1B: uaf (membugs.c:179)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)
==103725==  Block was alloc'd at
==103725==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x109A0E: uaf (membugs.c:165)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)
==103725==
==103725== Invalid read of size 1
==103725==    at 0x483EEF9: strnlen (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x4AD7089: __vfprintf_internal (vfprintf-internal.c:1688)
==103725==    by 0x4AC1D3E: printf (printf.c:33)
==103725==    by 0x109B6E: uaf (membugs.c:182)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)
==103725==  Address 0x4c5b2f0 is 0 bytes inside a block of size 512 free'd
==103725==    at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x109B1B: uaf (membugs.c:179)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)
==103725==  Block was alloc'd at
==103725==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103725==    by 0x109A0E: uaf (membugs.c:165)
==103725==    by 0x10A197: process_args (membugs.c:364)
==103725==    by 0x10A272: main (membugs.c:398)

valgrind 可以捕获到这一问题。

返回后使用问题:

$ valgrind ./membugs_dbg 9
==103882== Memcheck, a memory error detector
==103882== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==103882== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==103882== Command: ./membugs_dbg 9
==103882==
res: (null)
==103882==
==103882== HEAP SUMMARY:
==103882==     in use at exit: 12 bytes in 1 blocks
==103882==   total heap usage: 4 allocs, 3 frees, 5,604 bytes allocated
==103882==
==103882== LEAK SUMMARY:
==103882==    definitely lost: 0 bytes in 0 blocks
==103882==    indirectly lost: 0 bytes in 0 blocks
==103882==      possibly lost: 0 bytes in 0 blocks
==103882==    still reachable: 12 bytes in 1 blocks
==103882==         suppressed: 0 bytes in 0 blocks
==103882== Rerun with --leak-check=full to see details of leaked memory
==103882==
==103882== For lists of detected and suppressed errors, rerun with: -s
==103882== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

这个问题未能捕获到。

内存泄露:

$ valgrind --leak-resolution=high --num-callers=50 --leak-check=full ./membugs_dbg 13
==103929== Memcheck, a memory error detector
==103929== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==103929== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==103929== Command: ./membugs_dbg 13
==103929==

## Leakage test: case 3: "lib" API: runtime cond = 0
mypath = /home/arv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

## Leakage test: case 3: "lib" API: runtime cond = 1
mypath = /home/arv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
==103929==
==103929== HEAP SUMMARY:
==103929==     in use at exit: 4,108 bytes in 2 blocks
==103929==   total heap usage: 6 allocs, 4 frees, 13,796 bytes allocated
==103929==
==103929== 4,096 bytes in 1 blocks are definitely lost in loss record 2 of 2
==103929==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==103929==    by 0x109482: silly_getpath (membugs.c:45)
==103929==    by 0x10955F: leakage_case3 (membugs.c:63)
==103929==    by 0x10A21E: process_args (membugs.c:383)
==103929==    by 0x10A272: main (membugs.c:398)
==103929==
==103929== LEAK SUMMARY:
==103929==    definitely lost: 4,096 bytes in 1 blocks
==103929==    indirectly lost: 0 bytes in 0 blocks
==103929==      possibly lost: 0 bytes in 0 blocks
==103929==    still reachable: 12 bytes in 1 blocks
==103929==         suppressed: 0 bytes in 0 blocks
==103929== Reachable blocks (those to which a pointer was found) are not shown.
==103929== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==103929==
==103929== For lists of detected and suppressed errors, rerun with: -s
==103929== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

definitely lost: 4,096 bytes in 1 blocks

总结

测试编号 测试案例 是否能够通过 valgrind 检测到
1 未初始化内存访问
2 界外访问(编译时内存)
3 写上溢(动态内存)
4 写下溢(动态内存)
5 读上溢(编译时内存)
6 读上溢(动态内存)
7 读下溢(动态内存)
8 释放后使用
9 返回后使用
10 双重释放
11 内存泄露

Valgrind 优点与缺点

优点:

  • 可以捕获常见的动态分配内存问题
    • 未初始化变量使用
    • 界外访问
    • 释放后使用/返回后使用
    • 双重释放
    • 内存泄露
  • 不需要修改源码

缺点:

  • 使用 valgrind 运行软件性能很低
  • 耗费资源
  • 不能捕获静态内存问题

Sanitizer 工具

sanitizer 是谷歌提供的开源工具,像其他的内存分析工具那样,它可以用来查找程序的常见内存问题,包括界外访问,释放后使用,返回后使用,双重释放以及内存泄露等问题。它的其中一个工具也能够处理 C/C++ 代码的数据竞争问题。

sanitizer 工具通过编译器传递一些指令给代码(CTI: Compile-time instrumentation)。

sannitizer 工具集

工具(简写) 用途
AddressSanitizer(ASan) 查找一般的内存错误
Kernel AddressSanitizer(KASAN) Linux 内核空间的 ASan
MemorySanitizer(MSan) 未初始化内存检测
ThreadSanitizer(TSan) 数据竞争检测
LeakSanitizer(LSan) 内存泄露检测
UndefinedBehaviorSanitizer(UBSan) 未定义行为检测

上面的工具并非所有平台都支持。更多资料可见原书的本章节介绍。

构建使用 ASan 的程序

我们需要在编译时给到特殊的指令,才能使用这个工具。推荐使用 clang,gcc 可能不支持这个工具要使用的一些编译选项(可以修改这个源程序的 Makefile,指定 CC 为 clang)。可以看一下源代码中的 Makefile 里面有相关的选项。

未初始化内存访问:

$ ./membugs_dbg_asan 1
false case

它没能检测到这个问题,这是 ASan 的一个缺点,它不能够捕捉到静态分配内存的未初始化内存的访问问题。但是使用 MSan 工具是能够发现这个问题的:

$ ./membugs_dbg_msan 1
==106593==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x497e60 in uninit_var /home/arv/xudong/prog/hand-on/Hands-on-System-Programming-with-Linux-master/ch5/membugs.c:298:6
    #1 0x4954b8 in process_args /home/arv/xudong/prog/hand-on/Hands-on-System-Programming-with-Linux-master/ch5/membugs.c:342:4
    #2 0x49501b in main /home/arv/xudong/prog/hand-on/Hands-on-System-Programming-with-Linux-master/ch5/membugs.c:398:2
    #3 0x7f193cbcd082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #4 0x41c2cd in _start (/home/arv/xudong/prog/hand-on/Hands-on-System-Programming-with-Linux-master/ch5/membugs_dbg_msan+0x41c2cd)

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/arv/xudong/prog/hand-on/Hands-on-System-Programming-with-Linux-master/ch5/membugs.c:298:6 in uninit_var
Exiting

编译内存写上溢:

$ ./membugs_dbg_asan 2
=================================================================
==182984==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffc897f494 at pc 0x0000004c3630 bp 0x7fffc897f450 sp 0x7fffc897f448
WRITE of size 4 at 0x7fffc897f494 thread T0
    #0 0x4c362f in write_overflow_compilemem /home/arv/ch5/membugs.c:276:10
    #1 0x4c335d in process_args /home/arv/ch5/membugs.c:345:4
    #2 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #3 0x7f407cb6e082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #4 0x41b34d in _start (/home/arv/ch5/membugs_dbg_asan+0x41b34d)

Address 0x7fffc897f494 is located in stack of thread T0 at offset 52 in frame
    #0 0x4c34ff in write_overflow_compilemem /home/arv/ch5/membugs.c:272

  This frame has 1 object(s):
    [32, 52) 'arr' (line 273) <== Memory access at offset 52 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 /home/arv/ch5/membugs.c:276:10 in write_overflow_compilemem
Shadow bytes around the buggy address:
  0x100079127e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127e50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127e80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
=>0x100079127e90: 00 00[04]f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x100079127ea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127ec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100079127ee0: 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

我们可以看到,它连同调用过程也打印了出来:_start --> __libc_start_main --> main --> process_args --> write_overflow_compilemem

动态内存的写上溢:

$ ./membugs_dbg_asan 3
=================================================================
==184947==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000018 at pc 0x00000047fbce bp 0x7fff4f0f2db0 sp 0x7fff4f0f2570
WRITE of size 10 at 0x602000000018 thread T0
    #0 0x47fbcd in strcpy (/home/arv/ch5/membugs_dbg_asan+0x47fbcd)
    #1 0x4c3803 in write_overflow_dynmem /home/arv/ch5/membugs.c:266:2
    #2 0x4c3367 in process_args /home/arv/ch5/membugs.c:348:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7facd0057082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #5 0x41b34d in _start (/home/arv/ch5/membugs_dbg_asan+0x41b34d)

0x602000000018 is located 0 bytes to the right of 8-byte region [0x602000000010,0x602000000018)
allocated by thread T0 here:
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c37a8 in write_overflow_dynmem /home/arv/ch5/membugs.c:262:9
    #2 0x4c3367 in process_args /home/arv/ch5/membugs.c:348:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7facd0057082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

释放后使用:

$ ./membugs_dbg_asan 8
uaf():170: arr = 0x615000000300:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
=================================================================
==185557==ERROR: AddressSanitizer: heap-use-after-free on address 0x615000000300 at pc 0x000000480560 bp 0x7ffd00192e10 sp 0x7ffd001925c8
WRITE of size 22 at 0x615000000300 thread T0
    #0 0x48055f in strncpy (/home/arv/ch5/membugs_dbg_asan+0x48055f)
    #1 0x4c43c9 in uaf /home/arv/ch5/membugs.c:180:2
    #2 0x4c33a5 in process_args /home/arv/ch5/membugs.c:364:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f8e8542d082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #5 0x41b34d in _start (/home/arv/ch5/membugs_dbg_asan+0x41b34d)

0x615000000300 is located 0 bytes inside of 512-byte region [0x615000000300,0x615000000500)
freed by thread T0 here:
    #0 0x49380d in free (/home/arv/ch5/membugs_dbg_asan+0x49380d)
    #1 0x4c43a2 in uaf /home/arv/ch5/membugs.c:179:2
    #2 0x4c33a5 in process_args /home/arv/ch5/membugs.c:364:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f8e8542d082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

previously allocated by thread T0 here:
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c41ef in uaf /home/arv/ch5/membugs.c:165:8
    #2 0x4c33a5 in process_args /home/arv/ch5/membugs.c:364:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f8e8542d082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: heap-use-after-free (/home/arv/ch5/membugs_dbg_asan+0x48055f) in strncpy

ASan 不仅能够发现释放后使用的问题存在,它还能发现内存在哪里分配在哪里释放。

返回后使用:

$ ./membugs_dbg_asan 9
res:

上面使用情况没能发现返回后使用的问题,但是我们可以像下面这样使用:

$ ASAN_OPTIONS=detect_stack_use_after_return=1 ./membugs_dbg_asan 9
=================================================================
==185823==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f9d1f358020 at pc 0x000000439beb bp 0x7ffcf6f5c040 sp 0x7ffcf6f5b7c8
READ of size 23 at 0x7f9d1f358020 thread T0
    #0 0x439bea in printf_common(void*, char const*, __va_list_tag*) (/home/arv/ch5/membugs_dbg_asan+0x439bea)
    #1 0x43af2e in printf (/home/arv/ch5/membugs_dbg_asan+0x43af2e)
    #2 0x4c33c8 in process_args /home/arv/ch5/membugs.c:368:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f9d228e7082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #5 0x41b34d in _start (/home/arv/ch5/membugs_dbg_asan+0x41b34d)

Address 0x7f9d1f358020 is located in stack of thread T0 at offset 32 in frame
    #0 0x4c448f in uar /home/arv/ch5/membugs.c:149
    ...

双重释放:

$ ./membugs_dbg_asan 10
doublefree(): cond 0
doublefree(): cond 1
=================================================================
==186538==ERROR: AddressSanitizer: requested allocation size 0xffffffffffffffff (0x800 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0)
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c47d9 in doublefree /home/arv/ch5/membugs.c:137:11
    #2 0x4c33de in process_args /home/arv/ch5/membugs.c:372:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7fa805481082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

==186538==HINT: if you don't care about these errors you may set allocator_may_return_null=1
SUMMARY: AddressSanitizer: allocation-size-too-big (/home/arv/xudong/prog/hand-on/orz/Hands-on-System-Programming-with-Linux-master/ch5/membugs_dbg_asan+0x493a8d) in malloc
==186538==ABORTING
$ ASAN_OPTIONS=allocator_may_return_null=1 ./membugs_dbg_asan 10
doublefree(): cond 0
doublefree(): cond 1
==186618==WARNING: AddressSanitizer failed to allocate 0xffffffffffffffff bytes
membugs.c:doublefree:140: malloc failed
=================================================================
==186618==ERROR: AddressSanitizer: attempting double-free on 0x615000000580 in thread T0:
    #0 0x49380d in free (/home/arv/ch5/membugs_dbg_asan+0x49380d)
    #1 0x4c4851 in doublefree /home/arv/ch5/membugs.c:141:4
    #2 0x4c33de in process_args /home/arv/ch5/membugs.c:372:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f038b29e082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
    #5 0x41b34d in _start (/home/arv/ch5/membugs_dbg_asan+0x41b34d)

0x615000000580 is located 0 bytes inside of 512-byte region [0x615000000580,0x615000000780)
freed by thread T0 here:
    #0 0x49380d in free (/home/arv/ch5/membugs_dbg_asan+0x49380d)
    #1 0x4c47c0 in doublefree /home/arv/ch5/membugs.c:134:2
    #2 0x4c33de in process_args /home/arv/ch5/membugs.c:372:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f038b29e082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

previously allocated by thread T0 here:
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c4741 in doublefree /home/arv/ch5/membugs.c:130:8
    #2 0x4c33de in process_args /home/arv/ch5/membugs.c:372:4
    #3 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #4 0x7f038b29e082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: double-free (/home/arv/xudong/prog/hand-on/orz/Hands-on-System-Programming-with-Linux-master/ch5/membugs_dbg_asan+0x49380d) in free
==186618==ABORTING

内存泄露:

$ ./membugs_dbg_asan 11
leakage_case1(): will now leak 32 bytes (0 MB)
leakage_case1(): will now leak 1048576 bytes (1 MB)

=================================================================
==186842==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1048576 byte(s) in 1 object(s) allocated from:
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c4d54 in amleaky /home/arv/ch5/membugs.c:74:8
    #2 0x4c4911 in leakage_case1 /home/arv/ch5/membugs.c:119:2
    #3 0x4c33f7 in process_args /home/arv/ch5/membugs.c:376:4
    #4 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #5 0x7f6a91b6c082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

Direct leak of 32 byte(s) in 1 object(s) allocated from:
    #0 0x493a8d in malloc (/home/arv/ch5/membugs_dbg_asan+0x493a8d)
    #1 0x4c4d54 in amleaky /home/arv/ch5/membugs.c:74:8
    #2 0x4c4911 in leakage_case1 /home/arv/ch5/membugs.c:119:2
    #3 0x4c33ed in process_args /home/arv/ch5/membugs.c:375:4
    #4 0x4c31a1 in main /home/arv/ch5/membugs.c:398:2
    #5 0x7f6a91b6c082 in __libc_start_main /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: 1048608 byte(s) leaked in 2 allocation(s).

ASan 使用情况总结表

案例 说明 是否能工作
1 未初始化内存访问
2 编译时分配内存写上溢
3 动态分配内存写上溢
4 动态分配内存写下溢
5 编译时分配内存读上溢
6 动态分配内存读上溢
7 动态分配内存读下溢
8 释放后使用内存
9 返回后使用内存
10 双重释放
11 内存泄露

ASan 优缺点

优点:

  • 能够捕捉到大部分静态、动态内存问题
    • 界外内存访问
    • 释放后内存访问
    • 返回后内存访问
    • 双重释放
    • 内存泄露
  • 性能强
  • 不需要修改源码
  • 支持多线程应用

缺点:

  • 针对某种特定问题使用特定的工具,比如 ASan、TSan、LSan
  • 依赖编译器与编译器选项
posted @ 2023-06-05 23:05  ArvinDu  阅读(213)  评论(0编辑  收藏  举报