avatarElNiak

Summarize

source

Mastering GDB: A Beginner’s Tutorial for Effective Debugging

Unlock the full potential of GDB with our in-depth tutorial. From installation to advanced debugging techniques, learn how to efficiently debug your applications with GNU Debugger.

Free version

Ever found yourself staring at a screen full of cryptic error messages, wondering where it all went wrong?

You’re not alone. Debugging is an integral part of the development process, and GDB (GNU Debugger) is the Swiss Army knife for tackling these challenges head-on.

Whether you’re a seasoned developer or just starting out, understanding how to leverage GDB can transform your debugging skills from guesswork to precision.

Summary

In this tutorial, we’ll embark on a journey through the realms of GDB, exploring its vast capabilities from the ground up.

We’ll start with the basics: installing GDB and getting familiar with its core commands. Then, we’ll dive into a hands-on example, debugging a simple yet buggy program to unveil the practical power of GDB.

By the end, you’ll not only grasp the theory but also gain the confidence to apply these techniques in your own development endeavors.

Why GDB? In the world of software development, bugs are inevitable. GDB offers a robust set of features that allow you to observe the inner workings of your program in real time, understand its behavior, and pinpoint the exact location of bugs. From simple syntax errors to complex runtime anomalies, GDB empowers you to take control of the situation and steer your project back on track.

Ready to turn your bug-induced frustrations into victories? Let’s get started.

And hey, if you enjoyed this journey through the intricacies of memory safety, don’t forget to give a clap (or 50) and follow for more tales from the front lines of cybersecurity !! 👏👏👏

What is GDB?

The GNU Debugger, or GDB, is the go-to tool for Unix-like systems when it comes to debugging applications. At its core, GDB allows programmers to see what is going on ‘inside’ another program while it executes — or what another program was doing at the moment it crashed.

GDB can do four main kinds of things (plus other things in support of these) to help you catch bugs in the act:

  • Start your program, specifying anything that might affect its behavior.
  • Make your program stop on specified conditions.
  • Examine what has happened when your program has stopped.
  • Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.

The tool supports numerous programming languages, including C, C++, Rust, Go, Objective-C, Ada, Assembly, Fortran, and more. It’s not just for catching bugs. GDB also offers facilities for program control and inspection, making it versatile for tasks such as automated testing and script-based debugging.

GDB’s functionality can be extended with the use of scripts, and its capabilities can be integrated into various development environments, offering a flexible and powerful debugging toolkit that can handle simple to complex debugging scenarios.

Installing GDB

Before we can unleash the power of GDB, we need to ensure it’s installed on our machine. GDB is available on most Unix-like operating systems, including Linux and macOS, and there’s also a version for Windows users.

Linux:

Most Linux distributions come with GDB pre-installed. If not, you can easily install it using your distribution’s package manager. For example, on Ubuntu or Debian-based systems, you can use sudo apt-get install gdb.

macOS:

You can install GDB using Homebrew, a package manager for macOS. Simply run brew install gdb in your terminal.

Windows:

GDB can be used on Windows through the MinGW or Cygwin environments. You can download MinGW from MinGW’s official website and select the GDB package during installation.

Basic Commands

GDB might seem daunting at first, but its power lies in a set of simple commands that form the backbone of most debugging tasks.

GDB is designed to be user-friendly, and one aspect of this is the ability to use shortcuts for many of its commands, speeding up the debugging process.

Before debugging, I advice you to (1) disable ASLR (randomization of the memory) with echo 0 | sudo tee /proc/sys/kernel/randomize_va_space. And (2) to run a first time you program without any breakpoint to initialize the address space.

Note that for most command with arguments, you can use tab to have a list of possible information ! You can also use enter to rerun the last command.

Here’s a quick overview of the command I use the most:

  • run(r): Starts your program under GDB.
(gdb) run
Starting program: /path/to/your_program
  • set args/variable(s): Set argument list to give program being debugged when it is started.
(gdb) set args arg1 arg2
(gdb) set variable i = 10
(gdb) print i
$1 = 10
(gdb) set {int}0x83040 = 4
  • break [location](b): Sets a breakpoint at the specified location. You can use function names, line numbers, or even memory addresses. The location can be a function name, an +offset or -offset from the position at which execution stopped in the currently selected stack frame, linenum for a specific line number of the source file, *address for an address.
(gdb) b main
Breakpoint 1 at 0x4011f4: file main.c, line 10.

(gdb) b 15
Breakpoint 2 at 0x401200: file main.c, line 15.

(gdb) b +3
Breakpoint 3 at 0x401210: file main.c, line 18.

(gdb) b *0x4011f4
Breakpoint 4 at 0x4011f4: file main.c, line 10.
  • delete [breakpoint_id/address] (d): delete a breakpoint
  • continue(c): Resumes the execution of your program after it has been stopped (e.g., at a breakpoint).
  • next[i](n[i]): Executes the next program line after stopping, but treats function calls as a single step. ni allow to step through a single x86 instruction
(gdb) next
11      int a = 5;
  • step[i] (s[i]): Like next, but steps into function calls to debug them line by line. si allow to step through a single x86 instruction
(gdb) step
factorial (n=5) at factorial.c:4
4       if (n == 0)
(gdb) stepi
0x... <+34>:    mov    $0x0,%eax
  • print [expression](p): Evaluates and prints the value of an expression without altering the program's execution.
Breakpoint 1, main () at example.c:5
5       int a = 5, b = 10;
(gdb) print a+b
$1 = 15
  • watch [variable](w): Sets a watchpoint on a variable to pause execution whenever the variable's value changes.
Breakpoint 1, main () at example.c:5
5       int i = 0;
(gdb) watch i
Hardware watchpoint 2: i
(gdb) continue
Continuing.
Hardware watchpoint 2: i

Old value = 0
New value = 1
main () at example.c:6
  • info [var/reg/command/symbol] (i): Describe where the data for symbol is stored.

Example with:

#include <stdio.h>
int global_var = 10;
int main() {
    int local_var = 5;
    printf("Hello, GDB!\n");
    return 0;
}
Breakpoint 1, main () at example.c:6
6       int local_var = 5;
(gdb) info locals
local_var = 5
(gdb) info variables global_var
All variables matching regular expression "global_var":
File example.c:
int global_var;
(gdb) info registers
rax            0x0  0
rbx            0x0  0
rcx            0x0  0
...
(gdb) info commands
Type "help" followed by command name for full documentation.
Command class is "aliases": alias, unalias
Command class is "breakpoints": break, tbreak, hbreak, ...
...
(gdb) info symbol global_var
global_var in section .data of /path/to/examples
  • x[/[length][format][address]]: Displays the memory contents at a given address using the specified format. The length have following value b — byte, h — halfword (16-bit value), w — word (32-bit value), g — giant word (64-bit value). If specified, the format allows overriding the output format used by the command. Valid format specifiers are: o — octal, x — hexadecimal, d — decimal, u — unsigned decimal, t — binary, f — floating point, a — address, c — char, s — string, i — instruction

Example with:

#include <stdio.h>
int main() {
    char *str = "Hello, GDB!";
    int num = 12345;
    printf("%s %d\n", str, num);
    return 0;
}
Breakpoint 1, main () at memory_example.c:5
5       printf("%s %d\n", str, num);
# Address of str and num:
(gdb) print &str
$1 = (char **) 0x7fffffffdf58
(gdb) print &num
$2 = (int *) 0x7fffffffdf4c
# Hexadecimal Format:
(gdb) x/4xb 0x7fffffffdf58
0x7fffffffdf58: 0x48    0x65    0x6c    0x6c
# Decimal Format:
(gdb) x/4wd 0x7fffffffdf4c
0x7fffffffdf4c: 12345   0   0   0
# Character String Format:
(gdb) x/s 0x7fffffffdf58
0x7fffffffdf58: "Hello, GDB!"
# Instruction format:
(gdb) x/3i 0x7fffffffdf58
0x7fffffffdf58: mov    $0x0,%eax
0x7fffffffdf5d: leaveq
0x7fffffffdf5e: retq

With these commands at your disposal, you’re well-equipped to start tackling bugs. GDB is a very complete and complex program, so I recommend you to check the documentation for more details !

But let’s put theory into practice by diving into a hands-on example.

Program with a Bug

In this section, we’ll walk through a step-by-step guide to debugging a simple C program that calculates the factorial of a number. The program works fine for positive numbers but crashes when given a negative input, due to a logic error. Our goal is to use GDB to find and fix this bug.

//gcc -g -o factorial factorial.c
#include <stdio.h>
int factorial(int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}
int main() {
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);
    printf("Factorial of %d is %d\n", num, factorial(num));
    return 0;
}

Step 1: Start GDB

gdb ./factorial

Step 2: Set a Breakpoint and Run

(gdb) break factorial
(gdb) run
Enter a number: -5

Step 3: Step Through the Code

(gdb) next
...
(gdb) next
...
(gdb) next
...

As we step through the factorial function, we notice that there's no base case for negative numbers, leading to infinite recursion and eventually a stack overflow.

Advanced Features

GDB is not limited to simple debugging scenarios. It offers a wealth of advanced features that can significantly enhance your debugging capabilities:

  • Reverse Debugging: Allows you to run your program backward, which is invaluable for understanding the sequence of events leading up to a bug.
  • Remote Debugging: Debug programs running on a different machine, useful for embedded systems development or when working with applications in a different environment.
  • Scripting with Python: Extend GDB’s functionality or automate repetitive tasks by scripting with Python.

GDB Enhanced Features (GEF)

GEF, short for GDB Enhanced Features, is a dynamic and powerful extension for GDB that aims to make debugging with GDB more intuitive and feature-rich.

It’s especially geared towards exploit developers and reverse engineers but is also incredibly useful for anyone looking to enhance their debugging capabilities in GDB. GEF is designed to be architecture agnostic, meaning it supports a wide range of architectures including x86, ARM, MIPS, PowerPC, and SPARC, making it a versatile tool for debugging across different platforms​​​.

Key Features of GEF

  • Architecture Agnostic: Works with any GDB-supported architecture, providing a consistent debugging experience across different platforms.
  • Battery Included: GEF is designed to work out of the box with a single GDB script, without any external dependencies, ensuring an easy setup and immediate use.
  • Enhanced Debugging Commands: Offers a suite of commands that extend GDB’s functionality, making it easier to perform complex debugging tasks and analyses.
  • Extensibility: Easily extendable with new commands thanks to its comprehensive API, allowing for customization and extension of GEF’s capabilities.
  • Community Contributions: Benefits from a wide range of community-contributed commands available through GEF-Extras, further expanding its utility.

More on GEF here:

Tell me if you want a tutorial on GEF !!!

Conclusion

Mastering GDB is a journey that can dramatically improve your debugging efficiency and broaden your understanding of your programs’ inner workings. By starting with basic commands and progressing to more advanced features, you gain a powerful toolkit for tackling a wide range of software bugs.

Remember, the key to effective debugging is patience and practice. Use GDB to explore, experiment, and learn from each debugging session. With time, you’ll find yourself identifying and solving even the most cryptic bugs with confidence.

If this story was helpful and you wish to show a little support, you could:

If this story was helpful and you wish to show a little support, you could:

  • Clap 50 times for this story 👏👏👏
  • Leave a comment telling me what you think
  • Highlight the parts in this story that resonate with you

Follow me on Medium (it helps :D) with:

My Twitter to follow

My LinkedIn

My GitHub account to follow:

Gdb
Debugging
Programming
Software Development
Technology
Recommended from ReadMedium