= Learn to debug code with gdb

:Author: Seth Kenlon
:email: [email protected]

The GNU Debugger, more commonly known as its command `gdb`, is an interactive console to help you step through source code, analyze what gets executed, and essentially reverse engineer what's going wrong in a buggy application.
The trouble with troubleshooting is that it's complex.
Gnu Debugger isn't exactly a complex application itself, but it can be overwhelming if you don't know where to start, or even when and why you might need to turn to gdb to do your troubleshooting.
If you've been using print or echo or https://opensource.com/article/20/8/printf[printf statements] to debug your code, but you're beginning to suspect that there may be something more powerful, then this tutorial is for you.

== Code is buggy

To get started with gdb, you need some code.
Here's a sample application written in C++ (it's OK if you don't typically write in C++, the principles are the same across all languages), derived from one of the examples in the https://opensource.com/article/20/12/learn-c-game[guessing game series] here on opensource.com:

[source,c++]
----
#include <iostream>
#include <stdlib.h> //srand
#include <stdio.h>  //printf

using namespace std;

int main () {

srand (time(NULL));
int alpha = rand() % 8;
cout << "Hello world." << endl;
int beta = 2;

printf("alpha is set to is %s\n", alpha);
printf("kiwi is set to is %s\n", beta);

return 0;
} // main
----

There's a bug in this code sample, but it does compile (at least as of GCC 5).
If you're familiar with C++, you may already see it, but it's a nice simple problem that can help new gdb users understand the debugging process.
Compile it and run it to see the error:

[source,c++]
----
$ g++ -o buggy example.cpp
$ ./buggy
Hello world.
Segmentation fault
----

== Troubleshooting a segmentation fault

From this output, you can surmise that the variable `alpha` got set correctly, because otherwise you wouldn't expect to a line of code that came _after_ it.
That's not always true, of course, but it's a good working theory, and it's essentially the same conclusion you'd likely come to were you just using `printf` as a log and debugger.
From here, you can assume that the bug lies in _some line_ after the one that printed successfully.
It's not clear, however, whether the bug is in the very next line or several lines later.

Gnu Debugger is an interactive troubleshooter, so you can use the `gdb` command to run buggy code.
For best results, though, you should re-compile your buggy application from source code with _debug symbols_ included.
First, take a look at what information gdb can provide without re-compiling.

[source,c++]
----
$ gdb ./buggy
Reading symbols from ./buggy...done.
(gdb) start
Temporary breakpoint 1 at 0x400a44
Starting program: /home/seth/demo/buggy

Temporary breakpoint 1, 0x0000000000400a44 in main ()
(gdb)
----

When you start gdb with a binary executable as the argument, gdb loads the application and then waits for your instructions.
Because this is the first time you're running gdb on this executable, it makes sense to try to repeat the error in hopes that gdb can provide further insight.
The cammand for gdb to launch the application it has loaded is, intuitively enough,  `start`.
By default, there's a _breakpoint_ built into gdb so that when it encounters the `main` function of your application, it pauses execution.
To allow gdb to proceed, use the command `continue`.

[source,c++]
----
(gdb) continue
Continuing.
Hello world.

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6
(gdb)
----

No surprises here: the application has crashed shortly after printing "Hello world", but gdb is able to provide the function call happening when the crash occurred.
This could potentially be all you need to find the bug that's causing the crash, but to get a better idea of gdb's features, and the general debugging process, imagine that the problem hasn't become clear yet, and that you want to dig even deeper into what's happening with this code.

== Compiling code with debug symbols

To get the most out of gdb, you need debug symbols compiled into your executable.
You can generate this with the `-g` option in GCC.

[source,c++]
----
$ g++ -o debuggy example.cpp
$ ./debuggy
Hello world.
Segmentation fault
----

Compiling debug symbols into an executable results in a much larger file, so they're usually not distributed with the added convenience.
However, if you're dubugging open source code, it makes sense to recompile with debug symbols for testing.

[source,bash]
----
$ ls -l *buggy* *cpp
-rw-r--r--    310 Feb 19 08:30 debug.cpp
-rwxr-xr-x  11624 Feb 19 10:27 buggy*
-rwxr-xr-x  22952 Feb 19 10:53 debuggy*
----

== Debugging with gdb

Launch gdb with your new executable (`debuggy` in this example) loaded:

[source,c++]
----
$ gdb ./buggy
Reading symbols from ./buggy...done.
(gdb) start
Temporary breakpoint 1 at 0x400a44
Starting program: /home/seth/demo/debuggy

Temporary breakpoint 1, 0x0000000000400a44 in main ()
(gdb)
----

As before, use the `start` command to proceed:

[source,c++]
----
(gdb) start
Temporary breakpoint 1 at 0x400a48: file debug.cpp, line 9.
Starting program: /home/sek/demo/debuggy

Temporary breakpoint 1, main () at debug.cpp:9
9       srand (time(NULL));
(gdb)
----

This time, the automatic `main` breakpoint is able to specify what line number gdb has paused on, and what code the line contains.
You could resume normal operation with `continue`, but you already know that the application crashes before completion, so instead you can step through your code line by line using the `next` keyword.


[source,c++]
----
(gdb) next
10  int alpha = rand() % 8;
(gdb) next
11  cout << "Hello world." << endl;
(gdb) next
Hello world.
12  int beta = 2;
(gdb) next
14      printf("alpha is set to is %s\n", alpha);
(gdb) next

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6
(gdb)
----

From this process, you can confirm that the crash didn't happen when the `beta` variable was getting set, but when the `printf` line was executed.
The bug has been exposed several times over during the course of this article (spoiler: the wrong data type is being provided to `printf`), but assume for a moment that the solution remains unclear, and that further investigation is required.

== Setting breakpoints

Once your code is loaded into gdb, you have the ability to ask gdb about the data that's been produced by the code so far.
To try some data introspection, first restart your application by issuing the `start` command again, and then proceed to line 11.
An easy way to get to 11 quickly is to set a breakpoint that looks for a specific line number:

[source,c++]
----
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x400a48: file debug.cpp, line 9.
Starting program: /home/sek/demo/debuggy

Temporary breakpoint 2, main () at debug.cpp:9
9       srand (time(NULL));
(gdb) break 11
Breakpoint 3 at 0x400a74: file debug.cpp, line 11.
----

With the breakpoint established, continue the execution with `continue`:

[source,c++]
----
(gdb) continue
Continuing.

Breakpoint 3, main () at debug.cpp:11
11      cout << "Hello world." << endl;
(gdb)
----

You're now paused at line 11, just after the `alpha` variable has been set, and just before `beta` gets set.

== Variable introspection with gdb

To see the value of a variable, use the `print` command.
The value of `alpha` is random in this example code, so your actual results may vary from mine.

[source,c++]
----
(gdb) print alpha
$1 = 3
(gdb)
----

Of course, you can't see the value of a variable that has not yet been established:

[source,c++]
----
(gdb) print beta
$2 = 0
----

== Flow control

To proceed, you could step through the lines of code to get to the point that `beta` is set to a value.

[source,c++]
----
(gdb) next
Hello world.
12  int beta = 2;
(gdb) next
14  printf("alpha is set to is %s\n", alpha);
(gdb) print beta
$3 = 2
----

Alternatively, you could set a watchpoint.
A watchpoint, like a breakpoint, is a way to control the flow of how gdb executes the code.
In this case, you know that the `beta` variable should be set to `2`, so you could set a watchpoint to alert you when the value of `beta` changes:

[source,c++]
----
(gdb) watch beta > 0
Hardware watchpoint 5: beta > 0
(gdb) continue
Continuing.

Breakpoint 3, main () at debug.cpp:11
11      cout << "Hello world." << endl;
(gdb) continue
Continuing.
Hello world.

Hardware watchpoint 5: beta > 0

Old value = false
New value = true
main () at debug.cpp:14
14      printf("alpha is set to is %s\n", alpha);
(gdb)
----

You can step through the code execution manually with `next`, or you can control how the code executes with breakpoints, watchpoints, and catchpoints.

== Analyzing data with gdb

You can see data in different formats.
For instance, to see the value of `beta` as an octal value:

[source,c++]
----
(gdb) print /o beta
$4 = 02
----

To see its address in memory:

[source,c++]
----
(gdb) print /o beta
$5 = 0x2
----

You can also see the data type of a variable:

[source,c++]
----
(gdb) whatis beta
type = int
----

== Solving bugs with gdb

This kind of introspection better informs you about not only what code is getting executed, but how it's getting executed.
In this example, the `whatis` command on a variable gives you the clue that your `alpha` and `beta` variables are integers, which might jog your memory about `printf` syntax, making you realize that instead of `%s` in your `printf` statements, you must use the `%d` designator.
Making that change causes the application to run as expected, with no more obvious bugs present.

It's especially frustrating when code compiles but then reveals that there are bugs present, but that's how the trickiest of bugs work.
If they were easy to catch, they wouldn't be bugs.
Using gdb is one way to hunt them down and eliminate them.

== Download our cheatsheet

It's a fact of life, in even the most basic forms of programming, that code has bugs.
Not all bugs are so crippling that they stop an application from running (or even from compiling), and not all bugs are caused by incorrect code.
Sometimes bugs happen intermittently based on an unexpected comibination of choices made by a particularly creative user.
Sometimes programmers inherit bugs from the libraries they use in their own code.
Whatever the cause, bugs are basically everywhere, and it's part of the programmer's job to find and neutralize them.

Gnu Debugger is a useful tool in finding bugs.
There's a lot more you can do with it than demonstrated in this article.
You can read about its many functions with the Gnu Info reader:

[source,bash]
----
$ info gdb
----

Whether you're just learning gdb or you're a pro at it, it never hurts to have a reminder of what commands are available to you, and what the syntax for those commands are.
Download our cheatsheet for gdb today.