大部分人觉得C/C++比较难,主要是因为指针的灵活性以及内存的使用,C和C++需要程序员自己来控制内存,自己申请,自己释放,很容易就会出现各种头疼难搞的内存问题从而导致系统core dump,这类问题,除了平时自己写code注意,也要在出现问题的时候懂得借鉴“巨人”为我们提供的定位方法,比如比较好用的clang引入,gcc4.8后支持的功能Address Sanitizer和Thread Sanitizer。
Address Sanitizer: 顾名思义,就是用来检查非法内存错误的,这个工具比较适合检查如下几种类型的bugs:
1.越界问题
2.
使用释放后的内存
3.
double free
一个访问越界的例子:
#include <iostream>
using namespace std;
int main() {
int *arr = new int[5];
for (int i = 0; i < 6; ++i) { // out-of-bounds access
arr[i] = i;
}
delete [] arr;
return 0;
}
compile:
clang++ -g -fsanitize=address out-of-bounds.cpp -o result(gcc使用方法一样)
execute:
./result
=================================================================
==69004==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6030000002c4 at pc 0x000109f7eec5 bp 0x7ffee5c81750 sp 0x7ffee5c81748
WRITE of size 4 at 0x6030000002c4 thread T0
#0 0x109f7eec4 in main out-of-bounds.cpp:6
#1 0x7fff76c9a014 in start (libdyld.dylib:x86_64+0x1014)
0x6030000002c4 is located 0 bytes to the right of 20-byte region [0x6030000002b0,0x6030000002c4)
allocated by thread T0 here:
#0 0x109fea122 in wrap__Znam (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x63122)
#1 0x109f7ee4a in main out-of-bounds.cpp:4
#2 0x7fff76c9a014 in start (libdyld.dylib:x86_64+0x1014)
SUMMARY: AddressSanitizer: heap-buffer-overflow out-of-bounds.cpp:6 in main
Shadow bytes around the buggy address:
0x1c0600000000: fa fa 00 00 00 fa fa fa 00 00 00 00 fa fa 00 00
0x1c0600000010: 00 00 fa fa 00 00 00 00 fa fa fd fd fd fa fa fa
0x1c0600000020: fd fd fd fd fa fa 00 00 00 00 fa fa 00 00 00 fa
0x1c0600000030: fa fa 00 00 00 fa fa fa 00 00 00 00 fa fa fd fd
0x1c0600000040: fd fa fa fa fd fd fd fd fa fa 00 00 02 fa fa fa
=>0x1c0600000050: 00 00 00 00 fa fa 00 00[04]fa fa fa fa fa fa fa
0x1c0600000060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c0600000070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c0600000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c0600000090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c06000000a0: 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
==69004==ABORTING
Abort trap: 6
打印出来的信息非常详细,在代码的哪行code何种原因导致了非法内存访问,以及这块内存在哪里被申请了多少等等信息。
Thread Sanitizer:是用来检查数据竞争(Data Race)的,数据竞争指多个线程在没有正确加锁的情况下,同时访问同一块内存数据,并且至少有一个线程是写操作,对数据的读取和修改产生了竞争,从而导致各种不可预计的问题。Data Race的问题比较难查,一旦发生,结果是不可预期的,也许直接就Crash了,也许导致执行流程错乱了,也许把内存破坏导致之后某个时刻突然Crash了。
一个数据竞争的例子:
#include <pthread.h>
#include <iostream>
int Global;
pthread_mutex_t mutex_x = PTHREAD_MUTEX_INITIALIZER;
void *Thread1(void *x) {
// pthread_mutex_lock(&mutex_x);
++Global;
std::cout << "thread1 deal with global data" << std::endl;
// pthread_mutex_unlock(&mutex_x);
return NULL;
}
void *Thread2(void *x) {
// pthread_mutex_lock(&mutex_x);
++Global;
std::cout << "thread2 deal with global data" << std::endl;
// pthread_mutex_unlock(&mutex_x);
return NULL;
}
int main() {
pthread_t thr[2];
pthread_create(&thr[0], NULL, Thread1, NULL);
pthread_create(&thr[1], NULL, Thread2, NULL);
pthread_join(thr[0], NULL);
pthread_join(thr[1], NULL);
return 0;
}
在不加锁的情况下,这两个线程就会同时去修改Global变量,从而导致数据竞争。
compile:
clang++ -g -fsanitize=thread data-race.cpp -o result(gcc使用方法一样)
execute:
./result
thread1 deal with global data
==================
WARNING: ThreadSanitizer: data race (pid=69339)
Write of size 4 at 0x0001052a4198 by thread T2:
#0 Thread2(void*) data-race.cpp:17 (result:x86_64+0x100001776)
Previous write of size 4 at 0x0001052a4198 by thread T1:
#0 Thread1(void*) data-race.cpp:9 (result:x86_64+0x100001486)
Location is global 'Global' at 0x0001052a4198 (result+0x000100003198)
Thread T2 (tid=1839532, running) created by main thread at:
#0 pthread_create <null>:1600656 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:26 (result:x86_64+0x10000184e)
Thread T1 (tid=1839531, finished) created by main thread at:
#0 pthread_create <null>:1600656 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:25 (result:x86_64+0x10000182f)
SUMMARY: ThreadSanitizer: data race data-race.cpp:17 in Thread2(void*)
==================
==================
WARNING: ThreadSanitizer: data race (pid=69339)
Read of size 4 at 0x7fffaefd66f8 by thread T2:
#0 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long) ostream:732 (result:x86_64+0x100001c30)
#1 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) ostream:864 (result:x86_64+0x100001553)
#2 Thread2(void*) data-race.cpp:18 (result:x86_64+0x100001792)
Previous write of size 4 at 0x7fffaefd66f8 by thread T1:
#0 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long) ostream:732 (result:x86_64+0x100001d52)
#1 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) ostream:864 (result:x86_64+0x100001553)
#2 Thread1(void*) data-race.cpp:10 (result:x86_64+0x1000014a2)
Location is global 'std::__1::cout' at 0x7fffaefd6660 (libc++.1.dylib+0x00003a3ee6f8)
Thread T2 (tid=1839532, running) created by main thread at:
#0 pthread_create <null>:1599920 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:26 (result:x86_64+0x10000184e)
Thread T1 (tid=1839531, finished) created by main thread at:
#0 pthread_create <null>:1599920 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:25 (result:x86_64+0x10000182f)
SUMMARY: ThreadSanitizer: data race ostream:732 in std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long)
==================
==================
WARNING: ThreadSanitizer: data race (pid=69339)
Read of size 8 at 0x7fffaefd6680 by thread T2:
#0 std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) locale:1388 (result:x86_64+0x10000222b)
#1 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long) ostream:725 (result:x86_64+0x100001dc1)
#2 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) ostream:864 (result:x86_64+0x100001553)
#3 Thread2(void*) data-race.cpp:18 (result:x86_64+0x100001792)
Previous write of size 8 at 0x7fffaefd6680 by thread T1:
#0 std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) locale:1420 (result:x86_64+0x100002943)
#1 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long) ostream:725 (result:x86_64+0x100001dc1)
#2 std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) ostream:864 (result:x86_64+0x100001553)
#3 Thread1(void*) data-race.cpp:10 (result:x86_64+0x1000014a2)
Location is global 'std::__1::cout' at 0x7fffaefd6660 (libc++.1.dylib+0x00003a3ee680)
Thread T2 (tid=1839532, running) created by main thread at:
#0 pthread_create <null>:1598912 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:26 (result:x86_64+0x10000184e)
Thread T1 (tid=1839531, finished) created by main thread at:
#0 pthread_create <null>:1598912 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2936d)
#1 main data-race.cpp:25 (result:x86_64+0x10000182f)
SUMMARY: ThreadSanitizer: data race locale:1388 in std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char)
==================
thread2 deal with global data
ThreadSanitizer: reported 3 warnings
Abort trap: 6
信息同样也很详细,会指出哪些线程会如何产生data race, 以及这两个线程是在何处create出来的。
参考链接: