Learn to debug code with the GNU Debugger | Opensource.com

Learn to debug code with the GNU Debugger

Troubleshoot your code with the GNU Debugger. Download our new cheat sheet.

magnifying glass on computer screen, finding a bug in the code
Image by : 
Opensource.com
x

Subscribe now

Get the highlights in your inbox every week.

The GNU Debugger, more commonly known by 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, 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, echo, or printf statements to debug your code, but you're beginning to suspect 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 guessing game series here on Opensource.com:

#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 simple problem that can help new GDB users understand the debugging process. Compile it and run it to see the error:

$ g++ -o buggy example.cpp
$ ./buggy
Hello world.
Segmentation fault

Troubleshooting a segmentation fault

From this output, you can surmise that the variable alpha was set correctly because otherwise, you wouldn't expect the 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 if you were 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. However, it's not clear 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, you should recompile your buggy application from source code with debug symbols included. First, take a look at what information GDB can provide without recompiling:

$ 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. GDB's command 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:

(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 crashed shortly after printing "Hello world," but GDB can provide the function call that was 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 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:

$ 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 debugging open source code, it makes sense to recompile with debug symbols for testing:

$ 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:

$ gdb ./debuggy
Reading symbols from ./debuggy...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:

(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 can specify what line number GDB 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:

(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 being set but when the printf line was executed. The bug has been exposed several times in 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 can ask GDB about the data that the code has produced so far. To try some data introspection, 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:

(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:

(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.

Doing 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:

(gdb) print alpha
$1 = 3
(gdb)

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

(gdb) print beta
$2 = 0

Using flow control

To proceed, you could step through the lines of code to get to the point where beta is set to a value:

(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:

(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:

(gdb) print /o beta
$4 = 02

To see its address in memory:

(gdb) print /o beta
$5 = 0x2

You can also see the data type of a variable:

(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 a 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 combination 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 I demonstrated in this article. You can read about its many functions with the GNU Info reader:

$ 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.

magnifying glass on computer screen, finding a bug in the code

The GNU Project Debugger is a powerful tool for finding bugs in programs.
Dump truck rounding a turn in the road

Check out Microsoft's open source tool for getting process information.
Person drinking a hot drink at the computer

Once you understand the general principles, C++ method pointers become less intimidating.

Topics

About the author

Seth Kenlon
Seth Kenlon - Seth Kenlon is a UNIX geek, free culture advocate, independent multimedia artist, and D&D nerd. He has worked in the film and computing industry, often at the same time. He is one of the maintainers of the Slackware-based multimedia production project Slackermedia.