GDB is the GNU project debugger. It can be used to see what a program is doing or what it was doing when it crashed. GDB can be used with a variety of languages. Because I’m learning C++, I’m going to explain it in the context of C++.

Adding debugging symbols

One of the stages of the compilation of a C++ program is to generate an object file (file.o). This object file contains what is called a symbol table, which contains each identifier in the code with information associated with it (type, constness, etc…).

If we want to be able to use GDB in one of our programs we need to add debugging information to this table (debug symbols). To add debug symbols to our binary we use the -g flag:

1
g++ -g main.cpp -o program

A program to debug

To make it easy to understand what we are doing. Lets create a simple program to debug.

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "math_stuff.h"

void printSomeNumbers() {
  for (int i = 0; i < 10; i++) {
    std::cout << i << '\n';
  }
}

int main()
{
  std::cout << "Start of the program\n";

  printSomeNumbers();
  int num = math_stuff::addNumbers(4, 5);
  std::cout << math_stuff::printNumberPlus5(num) << '\n';

  std::cout << "End of the program\n";
}

math_stuff.h

1
2
3
4
namespace math_stuff {
  int addNumbers(int a, int b);
  int printNumberPlus5(int number);
}

math_stuff.cpp

1
2
3
4
5
6
7
8
9
namespace math_stuff {
  int addNumbers(int a, int b) {
    return a + b;
  }

  int printNumberPlus5(int number) {
    return number + 3;
  }
}

To compile this program with debugging symbols we can use:

1
g++ -g main.cpp math_stuff.cpp -o program

Debugging a program

Lets say there is an issue in our program and we want to debug it. We know there is something wrong in line 16 of main.cpp, so let’s set a breakpoint.

We start by opening our program with GDB:

1
gdb program

Once in GDB we can add a breakpoint (b is short for breakpoint):

1
b main.cpp:16

And run the program (r is short for run):

1
r

The program will execute until the line where we set the breakpoint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) r
Starting program: /home/adrianancona/repos/c++/program
Start of the program
0
1
2
3
4
5
6
7
8
9

Breakpoint 1, main () at main.cpp:16
16    std::cout << math_stuff::printNumberPlus5(num) << '\n';

GDB stops before executing line 16. One thing we might want to do here is see the current value of num (p is short for print):

1
p num

Will output something like this:

1
$1 = 9

You can use p to print the result of any valid expression. For example, we can see what printNumberPlus5 returns:

1
p math_stuff::printNumberPlus5(num)

By looking at the output, we can see that printNumberPlus5 is not working as expected

1
$2 = 12

It should be printing 14 (because num is 9, and 9 + 5 = 14), not 12.

We can step into the function at the current breakpoint (s is short for step):

1
s

And we will see the first line of the function:

1
2
math_stuff::printNumberPlus5 (number=9) at math_stuff.cpp:7
7       return number + 3;

It’s adding 3 instead of 5. We found the bug!

There are more things we can do while we are here. We could for example see the call stack to find how we got where we are (bt is short for backtrace):

1
bt

Output:

1
2
#0  math_stuff::printNumberPlus5 (number=9) at math_stuff.cpp:7
#1  0x00000000004008a8 in main () at main.cpp:16

It happens often that seeing just the current line is not enough to figure out what the problem is. We can tell GDB to give us a little more context (l is short for list):

1
l 7

7 is the line number were we last stopped. The command will show lines sorrounding the given line in the current file (by default 10 lines):

1
2
3
4
5
6
7
8
2     int addNumbers(int a, int b) {
3       return a + b;
4     }
5
6     int printNumberPlus5(int number) {
7       return number + 3;
8     }
9   }

We can step out of the current function using the finish command:

1
finish

This takes us to the next line in our main function:

1
2
3
4
5
Run till exit from #0  math_stuff::printNumberPlus5 (number=9) at math_stuff.cpp:7
0x00000000004008a8 in main () at main.cpp:16
16    std::cout << math_stuff::printNumberPlus5(num) << '\n';
1: num = 9
Value returned is $4 = 12

Since we are done debugging we can continue the program execution (c is short for continue):

1
c

This will continue the execution of the program until it reaches another endpoint, or (in our case) until the program ends.

These are some of the most common use cases for debugging a simple program. I’ll cover more advanced cases in another post.

[ c++  debugging  programming  ]
Error Handling in Rust
Configuring ESP32 to act as Access Point
Aligned and packed data in C and C++
ESP32 Non-Volatile Storage (NVS)
Making HTTP / HTTPS requests with ESP32