Using GDB Effectively: A Step-by-Step Demonstration

4 minute read

Published:

Using GDB Effectively: A Step-by-Step Demonstration

Debugging is an essential skill for developers, and GDB (GNU Debugger) is one of the most powerful tools available. Why settle for print statements when you can step through code, inspect variables, and analyze program flow in real-time? Debugging lets you do all this and more.

This post walks you through using GDB effectively with a more complex C program. We’ll explore features like breakpoints, stepping, backtraces, watches, and conditional debugging.

A Sample C Program with Nested Functions

Let’s consider this program to demonstrate GDB’s power:

#include <stdio.h>

void helper_function(int x) {
    printf("Helper received: %d\n", x);
    if (x < 0) {
        printf("Error: Negative number\n");
    }
}

int compute_sum(int a, int b) {
    helper_function(a);
    helper_function(b);
    return a + b;
}

int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

int main() {
    int x = 5, y = -2;
    printf("Factorial of %d is %d\n", x, factorial(x));
    int sum = compute_sum(x, y);
    printf("Sum of %d and %d is %d\n", x, y, sum);
    return 0;
}

Save this code in a file called nested_demo.c. Compile it with debugging information:

gcc -g nested_demo.c -o nested_demo

Launching GDB

Start GDB with the compiled program:

gdb ./nested_demo

Exploring Key GDB Features

1. Breakpoints

Set breakpoints at specific functions or lines to pause execution. For instance, set a breakpoint at compute_sum:

break compute_sum

Run the program:

run

When the breakpoint hits, GDB pauses at the beginning of compute_sum.

2. Stepping Through Code

To understand the flow inside a function, use:

step

This executes the current line and steps into any function calls. If you want to skip function calls:

next

3. Backtracing

To see how the program reached the current function, use:

backtrace

Here’s an example: If the program pauses in helper_function during a call from compute_sum, the backtrace might look like:

#0  helper_function (x=-2) at nested_demo.c:5
#1  compute_sum (a=5, b=-2) at nested_demo.c:12
#2  main () at nested_demo.c:21

This stack trace shows the chain of calls that led to the current point, making it easier to debug nested or recursive functions.

4. Watches

Monitor variables for changes dynamically. Add a watch for x:

watch x

Continue execution:

continue

GDB halts whenever x changes, even if no breakpoint is set.

5. Conditional Breakpoints

Set a breakpoint that activates only when certain conditions are met. For instance, break when b is negative in compute_sum:

break compute_sum if b < 0

Run the program, and GDB will pause only when b is negative.

6. Examining Variables and Locals

While paused, inspect variables using:

print a

View all local variables:

info locals

7. Stepping Out of Functions

To skip the rest of the current function and return to the caller:

finish

This is especially helpful in recursive calls, like factorial.

8. Viewing Code Layout

Enable source code view to track where execution is paused:

layout src

This splits the terminal, showing your code alongside GDB commands.

9. Continuing Until Specific Points

Use until to continue execution until a specific line or loop iteration:

until 15

Debugging the Nested Program

  1. Tracking Recursive Calls:
    • Set a breakpoint in factorial.
    • Use backtrace to view the recursive call stack.
    • Use info args to examine the arguments at each level of recursion.
  2. Debugging Nested Function Calls:
    • Break at compute_sum and step into helper_function.
    • Use backtrace to trace how helper_function was invoked.
    • Watch variable x to monitor its value during execution.
  3. Diagnosing Errors:
    • Use a conditional breakpoint in helper_function for negative values:
      break helper_function if x < 0
      
    • Step through the error-handling logic.

Wrapping Up

GDB is a versatile and powerful debugger that simplifies identifying bugs and understanding program flow. By experimenting with breakpoints, backtraces, watches, and stepping commands, you can gain a deep insight into your code. Debugging programs with nested and recursive functions, as shown here, showcases the real strength of GDB.

Dive into your own projects and explore GDB’s potential. Debugging is not just about fixing errors; it’s about mastering your code.