How to detect C/C++ memory leak
Table of contents
AddressSanitizer (or ASan) is a tool developed by Google detect memory access error such as use-after-free and memory leaks. It can be used on both C and C++ codes. Address Sanitizer uses runtime instrumentation to track memory allocations, which mean you must build your code with Address Sanitizer to take advantage of it’s features.
Compiler version
AddressSanitizer is currently implemented in:
- Clang >= 3.12
- CGG >= 4.8
- Xcode >= 7
- MSVC >= 16.9
Instrumentation cost
On average, the instrumentation increases processing time by 73% and memory usage by 240% according to AddressSanitizer: A Fast Address Sanity Checker.
Limitations
AddressSanitizer does not detect any uninitialized memory reads (but this is detected by MemorySanitizer), and only detects some use-after-return bugs.
It is also not capable of detecting all arbitrary memory corruption bugs, nor all arbitrary write bugs due to integer underflow/overflows (when the integer with undefined behavior is used to calculate memory address offsets).
Adjacent buffers in structs and classes are not protected from overflow, in part to prevent breaking backwards compatibility.
Makefile
The code instrumentation is made by adding
-fsanitize=address -g -O
to the compilation flags.
The lines below are used to build and run the code used in this
article. The compiled files files are bin/leak_01
and bin/leak_01.2
# Build both versions (instrumented and non instrumented)
bin/leak_01: leak_01.cpp
$(CXX) -Wall -fsanitize=address -O -g $^ -o $@
$(CXX) -Wall $^ -o $@.2 # non instrumented build
# Save each function output to their respective files
output/leak_01.txt: bin/leak_01
bin/leak_01 0 > $@.0.i 2>&1 || echo "Error ouput expected"
bin/leak_01 1 > $@.1.i 2>&1 || echo "Error ouput expected"
bin/leak_01.2 0 > $@.0.ni
bin/leak_01.2 1 > $@.1.ni
Buggy C++ source code
C++ Delete Mismatch
Bad function which does not deallocate 4bytes (sizeof int) on each call.
#line 100 // for easy reference the line above in this post
void leak()
{
int *ptr = new int{ 42 };
cout << "The value of ptr is " << *ptr << " " << ptr << endl;
// missing delete call here!
}
The main
function will call the leak
function 10 times leaking 40 bytes
(10 sizeof int
).
The debug information printed on standard error output explain the fault.
Direct leak of 40 byte(s) in 10 object(s) allocated from:
Instrumented possible output
The value of ptr is 42 0x602000000010
The value of ptr is 42 0x602000000030
The value of ptr is 42 0x602000000050
The value of ptr is 42 0x602000000070
The value of ptr is 42 0x602000000090
The value of ptr is 42 0x6020000000b0
The value of ptr is 42 0x6020000000d0
The value of ptr is 42 0x6020000000f0
The value of ptr is 42 0x602000000110
The value of ptr is 42 0x602000000130
=================================================================
==127641==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 40 byte(s) in 10 object(s) allocated from:
#0 0x7fd217994498 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
#1 0x5570f76e55fc in leak() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:102
SUMMARY: AddressSanitizer: 40 byte(s) leaked in 10 allocation(s).
Non instrumented possible output
The value of ptr is 42 0x55c4f1431eb0
The value of ptr is 42 0x55c4f14320e0
The value of ptr is 42 0x55c4f1432100
The value of ptr is 42 0x55c4f1432120
The value of ptr is 42 0x55c4f1432140
The value of ptr is 42 0x55c4f1432160
The value of ptr is 42 0x55c4f1432180
The value of ptr is 42 0x55c4f14321a0
The value of ptr is 42 0x55c4f14321c0
The value of ptr is 42 0x55c4f14321e0
Heap use after free
#line 200
void heapUseAfterFree()
{
int *array = new int[100];
delete[] array;
cout << "Heap use after free: " << array[42] << endl; // 💩
}
Instrumented possible output
=================================================================
==127649==ERROR: AddressSanitizer: heap-use-after-free on address 0x6140000000e8 at pc 0x55946fbd58fe bp 0x7ffe201ca9e0 sp 0x7ffe201ca9d0
READ of size 4 at 0x6140000000e8 thread T0
#0 0x55946fbd58fd in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:204
#1 0x55946fbd60d6 in main /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:233
#2 0x7fbcccc69d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x7fbcccc69e3f in __libc_start_main_impl ../csu/libc-start.c:392
#4 0x55946fbd5524 in _start (/home/geraldo/git/Intmain/language_cpp/bin/leak_01+0x2524)
0x6140000000e8 is located 168 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
#0 0x7fbccd25d148 in operator delete[](void*) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:155
#1 0x55946fbd57ee in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:203
previously allocated by thread T0 here:
#0 0x7fbccd25c618 in operator new[](unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:98
#1 0x55946fbd57e3 in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:202
SUMMARY: AddressSanitizer: heap-use-after-free /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:204 in heapUseAfterFree()
Shadow bytes around the buggy address:
0x0c287fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
=>0x0c287fff8010: fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd
0x0c287fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c287fff8030: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
0x0c287fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c287fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c287fff8060: 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
==127649==ABORTING
Non instrumented possible output
Heap use after free: 0
Main function
Each bad function can be called individually by passing its number.
int main( int argc, char **argv )
{
if( argc != 2 ) {
return 1;
}
switch( stoi( argv[1] ) ) {
case 0:
for( int i = 0; i < 10; i++ ) {
leak();
}
break;
case 1:
heapUseAfterFree();
break;
}
return 0;
}