Quickstart - Debugging C and C++ memory errors

February 14, 2022 | View Comments

https://danielnouri.org/media/quickstart---debugging-c-and-c++-memory-errors.jpg

When programming C or C++, memory access violations resulting in so-called segmentation faults (segfaults) are a very common thing. These will usually crash your program, leaving you with only an unhelpful error message, and no further clue as to where to look for the problem.

This page introduces two tools that'll help you debug these types of errors quickly. They are AddressSanitizer and GDB.

So let's consider this simple example program that looks up a letter in the alphabet based on the user's input:

#include <stdio.h>
#include <stdlib.h>

const char* alphabet = "abcdefghijklmnopqrstuvwxyz";

int main(int argc, char** argv) {
    int pos = atoi(argv[1]);
    char letter = alphabet[pos-1];
    printf("The %dth letter of the alphabet is %c\n", pos, letter);
}

Here's how we can compile it and run it:

$ gcc alphabet.c -o alphabet
$ ./alphabet 4
The 4th letter of the alphabet is d

And here's two ways how we can crash our program, by causing it to access array elements that are out of bounds (resulting in buffer overflow):

$ ./alphabet
Segmentation fault (core dumped)
$ ./alphabet 123123
Segmentation fault (core dumped)

Post-mortem debugging with GNU Debugger

Another way of finding out where and why our program crashed is using the GNU Debugger. For GDB to work properly, we need to add debugging symbols when compiling using the -g flag:

$ gcc alphabet.c -g -o alphabet

We then start our program using GDB. First we call gdb alphabet to start the debugger itself. Then at the (gdb) prompt, we run the program with any arguments. Below we'll run our program with the problematic 123123 argument again:

$ gdb alphabet
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from alphabet...
(gdb) run 123123
Starting program: ./alphabet 123123

Program received signal SIGSEGV, Segmentation fault.
main (argc=2, argv=0x7fffffffe388) at alphabet.c:8
8         char letter = alphabet[pos-1];
(gdb)

Here, GDB not only shows us where the program crashed (8 char letter = alphabet[pos-1];), it also allows us to perform post-mortem debugging: it enables us to inspect the state of our program at the point where it crashed. So we can for instance print out the value of the pos variable at the time of the crash:

(gdb) print pos
$1 = 123123

Another useful GDB feature is that it allows us to stop the execution of our program at any time to introspect variables, not only after a crash. For this, we can set a breakpoint, before we call run in the GDB prompt. In the following example, we'll set a breakpoint at line 7 and then run our alphabet program without any arguments:

$ gdb alphabet
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from alphabet...
(gdb) b alphabet.c:7
Breakpoint 1 at 0x117c: file alphabet.c, line 7.
(gdb) run
Starting program: ./alphabet

Breakpoint 1, main (argc=1, argv=0x7fffffffe3a8) at alphabet.c:7
7         int pos = atoi(argv[1]);
(gdb)

With this feature we're free to print out the value of variables at arbitrary points in our program, to try to better understand what's going on.