Interpreting Sanitizer Messages
Credit due to Professor David SmallbergWhen you build your program with Address Sanitizer, the compiler inserts extra code to check for certain types of undefined behavior. That code can't catch everything, but does a good job of catching most problems. When it detects an issue, it terminates your program and writes a lot of diagnostic output. Let's see how to read it.
Suppose we have the following program:
double myFunction(double a[], int k) { return a[k]; } int main() { double x[10]; double y[20]; double z[30]; x[5] = myFunction(y, 22); z[5] = x[5]; if (x[5] != z[5]) return 1; }
If we run this, we get a wall of text starting with the following perhaps. (If you run it, some of the numbers might be different, but the relationships between them will be the same.)
================================================================= ==14177==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffcfbd6c3f0 at ... READ of size 8 at 0x7ffcfbd6c3f0 thread T0 #0 0x4009bb in myFunction(double*, int) (/tmp/a.out+0x4009bb) #1 0x400a98 in main (/tmp/a.out+0x400a98) #2 0x7ff6f49ec554 in __libc_start_main (/usr/lib64/libc.so.6+0x22554) #3 0x400878 (/tmp/a.out+0x400878) Address 0x7ffcfbd6c3f0 is located in stack of thread T0 at offset 320 in frame #0 0x4009df in main (/tmp/a.out+0x4009df) This frame has 3 object(s): [32, 112) 'x' (line 8) [144, 304) 'y' (line 9) <== Memory access at offset 320 overflows this variable [368, 608) 'z' (line 10)
Let's see what this is telling us. stack-buffer-overflow usually means we tried to access an element past the end of a local array. READ of size 8 means we tried to get the value of some 8-byte object. (WRITE of size 8 would mean we tried to store a value into an 8-byte object.) What follows is a stack trace showing what function we were in when the bad event occurred, what function called it, what function called the function that called it, etc. From
#0 ... in myFunction(double*, int) ... #1 ... in main ... ...
we see that we were in myFunction when the bad event occurred, and myFunction was directly called from main.
Now, we have
... on address 0x7ffcfbd6c3f0 ... ... Address 0x7ffcfbd6c3f0 is located ... at offset 320 in frame ... in main ...
telling us that the address that we tried to access is among the local variables of main, at "offset 320", and the objects in main, with their offsets, are listed here:
This frame has 3 object(s): [32, 112) 'x' (line 8) [144, 304) 'y' (line 9) [368, 608) 'z' (line 10)
This tells us that the array x
occupies offsets 32 up to but not
including 112, a range of 112−32 = 80 bytes. Since x
has
10 elements and doubles are 8 bytes long, this makes sense. Similarly
y
occupies offsets 144 up to but not including 304, which is 160
bytes, which is 20 doubles.
We see now that
READ of size 8 at 0x7ffcfbd6c3f0 ... ... Address 0x7ffcfbd6c3f0 is located ... at offset 320 in frame ... [144, 304) 'y' (line 9) <== Memory access at offset 320 overflows this variable
brings it all together. Offset 320 is 320−144 = 176 bytes from the
start of y
has
only 20 elements.
Here's another example. The program is
#include <iostream> #include <cstring> using namespace std; void anotherFunction(const char* s) { for (int k = strlen(s); k >= 0; k--) cout << s[k-1] << endl; } int main() { anotherFunction("Hi"); }
and it produces something starting with
i H ================================================================= ==46331==ERROR: AddressSanitizer: global-buffer-overflow on address 0x00000040121f ... READ of size 1 at 0x00000040121f ... #0 ... in anotherFunction(char const*) ... #1 ... in main ... ... 0x00000040121f is located 45 bytes to the right of global variable '*.LC0' defined in 'myTestProgram.cpp' (0x4011e0) of size 18 '*.LC0' is ascii string 'myTestProgram.cpp' 0x00000040121f is located 1 bytes to the left of global variable '*.LC1' defined in 'myTestProgram.cpp' (0x401220) of size 3 '*.LC1' is ascii string 'Hi'
Here we tried to read something of size 1 (probably a char) when we were in
anotherFunction
. A
global-buffer-overflow
usually means we tried to access an element past the end of a global array
(as opposed to a local array, which causes a
stack-buffer-overflow).
The global array is either something we declared outside of any function or
something the compiler set up for us, usually an array to hold the text of a
string literal.
From the two green messages, the bad access is 45 bytes beyond the start of
some C string myTestProgram.cpp
(18 bytes counting the zero byte)
the compiler apparently set up with the name of the source file; that bad
access position is also 1 byte before the start of "Hi". Of the two ways of
looking at it, it's a lot likelier the incorrect code asked to look one byte
before the 'H' of "Hi" than look way past the end of a string our code
never even mentions. Indeed, when k
is 0, s[k-1]
tries to access s[-1], one position before the 'H'.
How about this:
int main() { int* p = nullptr; *p = 42; return *p; }
It produces
myBadPtr.cpp:4:5: runtime error: store to null pointer of type 'int' AddressSanitizer:DEADLYSIGNAL ================================================================= ==1225==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 ... ==1225==The signal is caused by a WRITE memory access. ==1225==Hint: address points to the zero page. #0 ... in main ...
Even if it didn't directly tell you about trying to store through a null pointer, you could tell from the fact that you tried to WRITE (as opposed to READ) involving the unknown address 0x000000000000; on most machines, the null pointer is represented by address 0x000000000000 (and the runtime system prevents you from ever actually reading from or storing into that address).
Here's another one:
struct Blah { double x[10]; double y; }; int main() { Blah* bp = new Blah; delete bp; bp->y = 42; }
It produces
==30268==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000070 ... WRITE of size 8 at 0x602000000070 ... ... in main ... 0x602000000070 is located 80 bytes inside of 88-byte region [0x602000000020,0x602000000078) ...
The heap-use-after-free error means you tried to follow a pointer to an object that has already been deleted. The WRITE of size 8 in the code above is consistent with trying to store into a double. The y member of a Blah object comes after the 10-double x member, so is 80 (10*8) bytes from the start of a Blah object.
If the main routine above were instead
int main() { Blah* bp = new Blah; delete bp; delete bp; }
we'd get
==30268==ERROR: AddressSanitizer: ==33125==ERROR: AddressSanitizer: attempting double-free on 0x608000000020 ...
...
indicating the attempt to delete an object that has already been deleted.