puck tools — test your egress policy against real C2 traffic →
#linux#gdb

Getting started with GDB

This course is designed to be hands on and assumes you are following along in a shell. We will be walking through the basics of gdb and how to get it configured and ready for more advanced debug sessions.

What is GDB and why is it useful?

GDB is a debugger: a program whose whole job is to take control of another program so you can stop it and look inside while it runs.

Without one, working out why a program does something means adding print statements, rebuilding, and running it again. You are guessing from the outside, through a keyhole you cut yourself.

A debugger inverts that. The program runs, you freeze it at the moment you want to investigate. Now you can see the arguments a function was handed, what variables are set, return codes, the raw bytes in memory and the registers the CPU is holding. Nothing to rebuild, nothing to guess.

The part that matters for us is that none of it needs source code or modifying the binary. Adding print statements require a program you can edit and recompile. A debugger attaches to whatever is in front of you.

The way you tell it where you want to stop is a breakpoint.

Think of a breakpoint as a stop sign. You name a place. That can be a function, a line, or an address. Then you say “stop here.” The program runs at full speed until it reaches that spot. Then it freezes with everything intact and hands control back to you. Nothing is being simulated and nothing runs slowly in between. It is the real program, in its real memory, paused mid-stride.

Lets get started.

Get it installed

$ sudo apt install gdb gcc
$ gdb --version | head -1
GNU gdb (Ubuntu 15.1-1ubuntu1~24.04.1) 15.1

On Fedora that is sudo dnf install gdb gcc. On Arch it is sudo pacman -S gdb gcc. Ask for the compiler explicitly on Arch, it is not in the base install.

Something to debug

First things first we need something to debug. Let’s start out with a very simple program that takes input and checks it against a “hidden” value. This is obviously a very simple example.

#include <stdio.h>
#include <string.h>

int check(const char *name) {
    return strcmp(name, "CONTEXT") == 0;
}

int main(int argc, char **argv) {
    if (argc < 2) { puts("usage: hello NAME"); return 2; }
    if (check(argv[1])) { printf("Hello, %s. Access granted.\n", argv[1]); return 0; }
    printf("Hello, %s. Access denied.\n", argv[1]);
    return 1;
}

Save that as hello.c and build it:

$ gcc -g -O0 -no-pie -o hello hello.c
$ ./hello Evan
Hello, Evan. Access denied.
$ ./hello CONTEXT
Hello, CONTEXT. Access granted.

For this example we are using some flags on the binary to make our lives easier:

  • -g emits debug information. That is the mapping from machine code back to source lines and variable names. Without it GDB still works, you just get addresses instead of names. Getting those names back for a binary that shipped without them is a course of its own.
  • -O0 turns optimisation off. At -O2 the compiler inlines check, reorders your statements and keeps variables in registers that it reuses for something else halfway through the function. Stepping through optimised code makes it look like the debugger is lying to you. It isn’t. The code genuinely does that.
  • -no-pie fixes the load address so the numbers you see match the numbers printed here. Real binaries in 2026 are all position-independent and get a fresh random base on every run, which is correct and also makes a tutorial impossible to follow. Drop it once you are comfortable and learn to work in offsets from a module base instead.

Your first breakpoint

First we want to run our command instead of running this to start our program with one argument:

$ ./hello Evan

We run it with gdb:

$ gdb -q ./hello
(gdb) break check
Breakpoint 1 at 0x401186: file hello.c, line 5.
(gdb) run Evan
Breakpoint 1, check (name=0x7fffffffe0f7 "Evan") at hello.c:5
5	    return strcmp(name, "CONTEXT") == 0;

First we ran gdb and told it what program to start. We then ran the break check command to set a breakpoint and finally we used run Evan to start the hello program with one argument Evan

The program stopped, one instruction into check, and it will sit there indefinitely. Lets look at what the program already did:

(gdb) bt
#0  check (name=0x7fffffffe0f7 "Evan") at hello.c:5
#1  0x00000000004011e8 in main (argc=2, argv=0x7fffffffdd78) at hello.c:10

bt is a backtrace. It shows the chain of calls that got you here, innermost first. Frame #0 is where you are stopped right now. Frame #1 is main, which called check from line 10.

Now ask what this function was actually handed:

(gdb) info args
name = 0x7fffffffe0f7 "Evan"

One argument, name, holding the pointer 0x7fffffffe0f7. GDB followed that pointer and printed what was at the other end, "Evan". That is the value you typed on the command line, read straight out of live memory.

info locals is the companion command for variables declared inside the function. check does not have any, so it says so:

(gdb) info locals
No locals.

Now let it finish and watch what it returns:

(gdb) finish
Run till exit from #0  check (name=0x7fffffffe0f7 "Evan") at hello.c:5
0x00000000004011e8 in main (argc=2, argv=0x7fffffffdd78) at hello.c:10
10	    if (check(argv[1])) { printf("Hello, %s. Access granted.\n", argv[1]); return 0; }
Value returned is $1 = 0

Value returned is $1 = 0. That zero is why the program says access denied, and you watched it happen rather than guessing.

The five commands that cover most days

Command Short What it does
break <where> b Stop at a function, file.c:42, or *0x401186
run <args> r Start the program under the debugger
continue c Let it go until the next breakpoint
next / step n / s One line, over calls or into them
backtrace bt How did I get here

Press Enter on its own to repeat the last command, which is what makes n tolerable. Ctrl-D or quit gets you out.

That is a working debugger. It is also an unpleasant one. There is no source on screen, no history, and it forgets everything you configure the moment you quit. The next lesson fixes that.

After that, what actually happened when you typed break? The answer explains half the odd behaviour you will hit later.