动态库单独添加Address Sanitizer

发布时间 2023-07-09 11:23:22作者: liqinglucky

原文地址:https://www.cnblogs.com/liqinglucky/p/address-sanitizer-in-library.html

Address Sanitizer集成的原理是在汇编过程中(参考:程序编译过程与运行时内存 - liqinglucky - 博客园 (cnblogs.com))编译出.o文件时就将AddressSanitizer的运行时库替换malloc()/free()实现内存检测功能的。所以可以单独在执行程序(main executable)加Address Sanitizer,也可以在链接库中单独加Address Sanitizer。

main executable sanitized as well or only the shared library

也就是说当软件是由一个main函数调用很多动态库(也是自己源码)生成的可执行程序,那编译时main函数有编译进address senitizer库检测内存,那些被调用的动态库源码GCC编译时也要单独 加address senitizer参数编译才能检测动态库的内存。如果只在main程序加了address senitizer参数编译,就会出现main程序内存问题能发现,有些调用的动态库内存问题不会发现。为了验证这一想法我用一个由main程序调用动态库的代码做演示。

源代码:Address-Sanitizer-C-Language: 内存泄漏/越界检查工具 - Gitee.com

一、主程序加Address Sanitizer参数编译

代码

how_to_integration/CMakeLists.txt加上编译参数

project( app )

//加上编译参数
add_compile_options(-fsanitize=address -fsanitize-recover=all -fsanitize=leak)
add_link_options(-fsanitize=address -fsanitize-recover=all -fsanitize=leak) 

1 主程序的测试代码

/Address-Sanitizer-C-Language# git diff how_to_integration/main.c
diff --git a/how_to_integration/main.c b/how_to_integration/main.c
index 0010f95..0dabebe 100755
--- a/how_to_integration/main.c
+++ b/how_to_integration/main.c
@@ -7,7 +7,7 @@ int main(void)
   printf("add:%d \n", add(a,b));
   printf("sub:%d \n", sub(a,b));

+  int a1[10] = {0};
+  printf("%s a1 %d\n", __FUNCTION__, a1[11]);
   return 0;
 }

2 库程序的测试代码

/Address-Sanitizer-C-Language# git diff how_to_integration/lib/add.c
diff --git a/how_to_integration/lib/add.c b/how_to_integration/lib/add.c
index 96d8ea7..ed007f8 100755
--- a/how_to_integration/lib/add.c
+++ b/how_to_integration/lib/add.c
@@ -2,5 +2,8 @@

 int add(int a, int b)
 {
+  int a1[10] = {0};
+  printf("%s a1 %d\n", __FUNCTION__, a1[11]);

   return a+b;
 }

编译

build# cmake ..
build# make
build# ./app

测试

1 检测主程序的测试代码

/Address-Sanitizer-C-Language/how_to_integration/build# ./app
add:6
sub:2
=================================================================
==352812==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffee86702fc at pc 0x5591df96445c bp 0x7ffee8670280 sp 0x7ffee8670270
READ of size 4 at 0x7ffee86702fc thread T0
    #0 0x5591df96445b in main (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x145b)
    #1 0x7f3d2a8a6082 in __libc_start_main ../csu/libc-start.c:308
    #2 0x5591df9641cd in _start (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x11cd)

Address 0x7ffee86702fc is located in stack of thread T0 at offset 92 in frame
    #0 0x5591df964298 in main (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x1298)

  This frame has 1 object(s):
    [48, 88) 'a1' (line 10) <== Memory access at offset 92 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 (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x145b) in main
Shadow bytes around the buggy address:
  0x10005d0c6000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10005d0c6050: 00 00 00 00 f1 f1 f1 f1 f1 f1 00 00 00 00 00[f3]
  0x10005d0c6060: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c6090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d0c60a0: 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
==352812==ABORTING

2 检测库程序的测试代码

/Address-Sanitizer-C-Language/how_to_integration/build# ./app
=================================================================
==352873==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffee9e7d24c at pc 0x7fa8c086b397 bp 0x7ffee9e7d1d0 sp 0x7ffee9e7d1c0
READ of size 4 at 0x7ffee9e7d24c thread T0
    #0 0x7fa8c086b396 in add (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1396)
    #1 0x558b93e3f231 in main (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x1231)
    #2 0x7fa8c069c082 in __libc_start_main ../csu/libc-start.c:308
    #3 0x558b93e3f14d in _start (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x114d)

Address 0x7ffee9e7d24c is located in stack of thread T0 at offset 92 in frame
    #0 0x7fa8c086b228 in add (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1228)

  This frame has 1 object(s):
    [48, 88) 'a1' (line 5) <== Memory access at offset 92 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 (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1396) in add
Shadow bytes around the buggy address:
  0x10005d3c79f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
=>0x10005d3c7a40: f1 f1 f1 f1 00 00 00 00 00[f3]f3 f3 f3 f3 00 00
  0x10005d3c7a50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10005d3c7a90: 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
==352873==ABORTING

二、库程序加Address Sanitizer参数编译

代码

how_to_integration/lib/CMakeLists.txt加上编译参数

//加上编译参数
add_compile_options(-fsanitize=address -fsanitize-recover=all -fsanitize=leak)
add_link_options(-fsanitize=address -fsanitize-recover=all -fsanitize=leak) 

1 主程序的测试代码

/Address-Sanitizer-C-Language# git diff how_to_integration/main.c
diff --git a/how_to_integration/main.c b/how_to_integration/main.c
index 0010f95..0dabebe 100755
--- a/how_to_integration/main.c
+++ b/how_to_integration/main.c
@@ -7,7 +7,7 @@ int main(void)
   printf("add:%d \n", add(a,b));
   printf("sub:%d \n", sub(a,b));

+  int a1[10] = {0};
+  printf("%s a1 %d\n", __FUNCTION__, a1[11]);
   return 0;
 }

2 库程序的测试代码

/Address-Sanitizer-C-Language# git diff how_to_integration/lib/add.c
diff --git a/how_to_integration/lib/add.c b/how_to_integration/lib/add.c
index 96d8ea7..ed007f8 100755
--- a/how_to_integration/lib/add.c
+++ b/how_to_integration/lib/add.c
@@ -2,5 +2,8 @@

 int add(int a, int b)
 {
+  int a1[10] = {0};
+  printf("%s a1 %d\n", __FUNCTION__, a1[11]);

   return a+b;
 }

编译

在库程序单独加编译参数-fsanitize=address编译时需要在编译环境上加环境变量

参考:【解决】asan runtime does not come first in initial library list - C/C++ - 清泛网 - 专注C++内核技术 (tsingfun.com)

build# export ASAN_OPTIONS=verify_asan_link_order=0

然后编译

build# cmake ..
build# make
Scanning dependencies of target math
[ 20%] Building C object bin/CMakeFiles/math.dir/add.o
[ 40%] Building C object bin/CMakeFiles/math.dir/sub.o
[ 60%] Linking C shared library libmath.so
[ 60%] Built target math
[ 80%] Linking C executable app
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tempnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam_r' is dangerous, better use `mkstemp'
[100%] Built target app
root@ubuntu:/var/lte/Address-Sanitizer-C-Language/how_to_integration/build# ./app

测试

1 检测主程序的测试代码

/Address-Sanitizer-C-Language/how_to_integration/build# cmake ..
/Address-Sanitizer-C-Language/how_to_integration/build# make
Scanning dependencies of target math
[ 20%] Building C object bin/CMakeFiles/math.dir/add.o
[ 40%] Building C object bin/CMakeFiles/math.dir/sub.o
[ 60%] Linking C shared library libmath.so
[ 60%] Built target math
[ 80%] Linking C executable app
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tempnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam_r' is dangerous, better use `mkstemp'
[100%] Built target app

/Address-Sanitizer-C-Language/how_to_integration/build# ./app
add:6
sub:2
main a1 1382831681   <<< 主程序不检测, 说明库程序的编译参数并未在主程序生效

2 检测库程序的测试代码

/Address-Sanitizer-C-Language/how_to_integration/build# ./app
=================================================================
==352965==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc0218bfcc at pc 0x7f3b9539e397 bp 0x7ffc0218bf50 sp 0x7ffc0218bf40
READ of size 4 at 0x7ffc0218bfcc thread T0
    #0 0x7f3b9539e396 in add (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1396)
    #1 0x55a7479e11e0 in main (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x11e0)
    #2 0x7f3b951bd082 in __libc_start_main ../csu/libc-start.c:308
    #3 0x55a7479e10ed in _start (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/app+0x10ed)

Address 0x7ffc0218bfcc is located in stack of thread T0 at offset 92 in frame
    #0 0x7f3b9539e228 in add (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1228)

  This frame has 1 object(s):
    [48, 88) 'a1' (line 5) <== Memory access at offset 92 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 (/var/lte/Address-Sanitizer-C-Language/how_to_integration/build/bin/libmath.so+0x1396) in add
Shadow bytes around the buggy address:
  0x1000004297a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000004297b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000004297c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000004297d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000004297e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
=>0x1000004297f0: f1 f1 f1 f1 00 00 00 00 00[f3]f3 f3 f3 f3 00 00
  0x100000429800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100000429810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100000429820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100000429830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100000429840: 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
==352965==ABORTING

遇到的问题

1 程序运行失败:ASan runtime does not come first in initial library list

/Address-Sanitizer-C-Language/how_to_integration/build# make
Scanning dependencies of target math
[ 20%] Building C object bin/CMakeFiles/math.dir/add.o
[ 40%] Building C object bin/CMakeFiles/math.dir/sub.o
[ 60%] Linking C shared library libmath.so
[ 60%] Built target math
Scanning dependencies of target app
[ 80%] Building C object CMakeFiles/app.dir/main.o
[100%] Linking C executable app
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tempnam' is dangerous, better use `mkstemp'
/usr/bin/ld: /lib/x86_64-linux-gnu/libasan.so.5: warning: the use of `tmpnam_r' is dangerous, better use `mkstemp'
[100%] Built target app

/Address-Sanitizer-C-Language/how_to_integration/build# ./app
==343885==ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD.

调查:

1 错误:ASan runtime does not come first in initial library list; you should either link runtime to_w_唯_w的博客-CSDN博客

2 c - valgrind asan runtime does not come first in initial library list - Stack Overflow

3 c++ - CLion wont run binary with address sanitizer - Stack Overflow

4 单独在库里编译Address Sanitizer:gcc - Asan : Issue with asan library loading - Stack Overflow

main executable sanitized as well or only the shared library

建议方法:

  1. build main executable with -fsanitize=address
  2. get rid of /etc/ld.so.preload on your test machine
  3. disable the check (need recent GCC) with export ASAN_OPTIONS=verify_asan_link_order=0; but you have to be sure that libraries from /etc/ld.so.preload do not intercept symbols important for Asan e.g. malloc, free, etc., otherwise things will start breaking