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
=================================================================
==14635==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 40 byte(s) in 10 object(s) allocated from:
#0 0x5559182002fd in operator new(unsigned long) (/data/home/geraldo/git/Intmain/language_cpp/bin/leak_01+0xf22fd) (BuildId: e3be81e16211953e9c0eb772e463da0f3429b315)
#1 0x5559182028dd in leak() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:101:14
#2 0x7f214de4ad09 in __libc_start_main csu/../csu/libc-start.c:308:16
SUMMARY: AddressSanitizer: 40 byte(s) leaked in 10 allocation(s).
Non instrumented possible output
The value of ptr is 42 0x563a57eb2eb0
The value of ptr is 42 0x563a57eb3ee0
The value of ptr is 42 0x563a57eb3f00
The value of ptr is 42 0x563a57eb3f20
The value of ptr is 42 0x563a57eb3f40
The value of ptr is 42 0x563a57eb3f60
The value of ptr is 42 0x563a57eb3f80
The value of ptr is 42 0x563a57eb3fa0
The value of ptr is 42 0x563a57eb3fc0
The value of ptr is 42 0x563a57eb3fe0
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
=================================================================
==14642==ERROR: AddressSanitizer: heap-use-after-free on address 0x6140000000e8 at pc 0x55ed7456ac4b bp 0x7fff11336a90 sp 0x7fff11336a88
READ of size 4 at 0x6140000000e8 thread T0
#0 0x55ed7456ac4a in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:203:38
#1 0x55ed7456b070 in main /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:231:7
#2 0x7ff3a441fd09 in __libc_start_main csu/../csu/libc-start.c:308:16
#3 0x55ed744953f9 in _start (/data/home/geraldo/git/Intmain/language_cpp/bin/leak_01+0x1f3f9) (BuildId: e3be81e16211953e9c0eb772e463da0f3429b315)
0x6140000000e8 is located 168 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
#0 0x55ed74568c5d in operator delete[](void*) (/data/home/geraldo/git/Intmain/language_cpp/bin/leak_01+0xf2c5d) (BuildId: e3be81e16211953e9c0eb772e463da0f3429b315)
#1 0x55ed7456aaf8 in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:202:3
#2 0x7ff3a441fd09 in __libc_start_main csu/../csu/libc-start.c:308:16
previously allocated by thread T0 here:
#0 0x55ed7456840d in operator new[](unsigned long) (/data/home/geraldo/git/Intmain/language_cpp/bin/leak_01+0xf240d) (BuildId: e3be81e16211953e9c0eb772e463da0f3429b315)
#1 0x55ed7456aaed in heapUseAfterFree() /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:201:16
#2 0x7ff3a441fd09 in __libc_start_main csu/../csu/libc-start.c:308:16
SUMMARY: AddressSanitizer: heap-use-after-free /home/geraldo/git/Intmain/language_cpp/leak_01.cpp:203:38 in heapUseAfterFree()
Shadow bytes around the buggy address:
0x613ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x613ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x613fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x613fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x614000000000: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
=>0x614000000080: fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd
0x614000000100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x614000000180: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
0x614000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x614000000300: 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
==14642==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;
}