gdb

Original🇺🇸 English
Translated
4 scripts

Debug and trace C/C++/Rust programs with the GNU Debugger (GDB) without blocking the agent. Use when you need to set tracepoints, inspect variables, or monitor a running process while staying responsive to the user.

1installs
Added on

NPX Install

npx skill4agent add betab0t/skills gdb

GNU Debugger (GDB) Skill

Non-blocking GDB debugging for AI agents. Trace live processes, set conditional breakpoints, and inspect program state while the agent stays responsive and can multitask.

How It Works

Agents are single-threaded — if an agent runs GDB directly and waits for a breakpoint, it blocks entirely. It can't respond to the user, answer questions, or change strategy until the breakpoint fires (which may take a very long time).
This skill avoids that by running GDB in the background via a named pipe. The agent sends commands by writing to the pipe and reads results from a log file, so it never blocks. Instead of traditional breakpoints, it uses
dprintf
(dynamic printf) — a GDB feature that prints formatted output when hit and automatically continues execution without stopping.
This means the agent can:
  1. Start a debug session and immediately return to the user.
  2. Sample the log file periodically to report progress.
  3. Multitask (e.g., search the web, edit code) while the target process runs.
  4. Interrupt and modify tracepoints dynamically without killing the session.
The skill includes helper scripts that encapsulate all the pipe management, signal handling, and quoting logic — so any agent that can run shell commands can use it.

Core Concepts

  1. Background Execution: GDB runs in the background. You interact with it by writing to its standard input (stdin).
  2. Non-Blocking: Use
    dprintf
    (dynamic printf) instead of
    break
    . This inserts a printf call and continues execution immediately.
  3. Log Sampling: GDB output is redirected to a file (
    set logging file ...
    ). You read this file to see the trace.
  4. Dynamic Updates: proper interrupt handling is required to add/remove tracepoints on a running process.

Prerequisites & Setup

Requirements

  • Linux OS
  • gdb-multiarch
    (recommended) or
    gdb

Installation

bash
# Debian/Ubuntu
sudo apt-get update && sudo apt-get install -y gdb-multiarch

# Fedora/RHEL
sudo dnf install -y gdb

# Arch
sudo pacman -S gdb

# Verify
gdb-multiarch --version || gdb --version
  • Target process is running.
  • If remote:
    gdbserver
    running on target.

Workflow

1. Start a Session (Universal Method)

This method works with any agent that can run shell commands, without needing special stdin handling.
Recommended — use the helper script:
bash
./scripts/gdb_start.sh ./path/to/executable
# Creates: gdb_cmd_pipe (named pipe), .gdb_pid (PID file), trace.log (output)
Manual method (if you need more control):
  1. Create a Named Pipe (FIFO):
    bash
    mkfifo gdb_cmd_pipe
  2. Start GDB reading from the pipe:
    bash
    tail -f gdb_cmd_pipe | gdb-multiarch -q -x scripts/setup.gdb --args ./executable &
    echo $! > .gdb_pid
Interaction Protocol:
  1. Send Commands:
    ./scripts/gdb_send.sh "command"
    (or
    echo "command" > gdb_cmd_pipe
    )
  2. Interrupt:
    kill -INT $(cat .gdb_pid)
  3. Resume:
    ./scripts/gdb_send.sh "continue"

2. Configure Logging (The Easy Way)

Use the provided
scripts/setup.gdb
to automatically configure non-blocking logging.
bash
# Start GDB with the setup script
gdb-multiarch -q -x scripts/setup.gdb -p <PID>
If you need to configure it manually (e.g., if you forgot
-x
):
gdb
set pagination off
set logging file trace.log
set logging enabled on

3. Set Non-Blocking Tracepoints (
dprintf
)

Use
dprintf
to print variables or status without pausing the program.
Format:
dprintf <LOCATION>, "<FORMAT_STRING>", <ARGUMENTS>
Examples:
gdb
dprintf main.c:120, "Iteration %d, Value: %s\n", i, some_string
dprintf process_request, "Got request ID: %d\n", req->id
Send
continue
to let the process run while tracing:
gdb
continue

4. Conditional Tracing

To trace only when a condition is true, use the helper script:
bash
./scripts/gdb_trace.sh loop_function '"Hit 10: %d\n", iteration' 'iteration == 10'
Or manually via the pipe:
  1. Set the
    dprintf
    :
    bash
    ./scripts/gdb_send.sh 'dprintf loop_function, "Hit 10: %d\n", iteration'
    # Output in trace.log: Dprintf 2 at ...
  2. Apply the condition. Use
    $bpnum
    (GDB's convenience variable for the last breakpoint number). Important: Escape the
    $
    so the shell doesn't expand it:
    bash
    ./scripts/gdb_send.sh 'condition $bpnum iteration == 10'

5. Sampling the Trace

Read the log file to see what's happening.
bash
tail -n 20 trace.log

6. Modifying Tracepoints (The Interrupt Pattern)

Use the helper script to replace all tracepoints in one call:
bash
# Replaces all existing tracepoints with a new one:
./scripts/gdb_trace.sh new_func '"New trace: %d\n", x'

# Or clear all tracepoints without setting new ones:
./scripts/gdb_trace.sh --clear
Manual method (if you need finer control):
  1. Interrupt
    kill -INT $(cat .gdb_pid)
  2. Modify
    ./scripts/gdb_send.sh "delete"
    then
    ./scripts/gdb_send.sh 'dprintf ...'
  3. Resume
    ./scripts/gdb_send.sh "continue"

Helper Scripts

The
scripts/
directory provides helper scripts that handle quoting, timing, and PID tracking. Use these instead of raw
echo
commands — they save tokens and avoid shell quoting errors.
ScriptPurposeUsage
gdb_start.sh
Create pipe, start GDB, save PID
./scripts/gdb_start.sh ./my_app
gdb_send.sh
Send a single GDB command to the pipe
./scripts/gdb_send.sh "info breakpoints"
gdb_trace.sh
Interrupt + delete + set dprintf + continue
./scripts/gdb_trace.sh func '"fmt\n"' 'cond'
gdb_trace.sh --clear
Remove all tracepoints, keep running
./scripts/gdb_trace.sh --clear
gdb_stop.sh
Kill processes, wait, remove session files
./scripts/gdb_stop.sh

Example Session

Here is the complete workflow using the helper scripts. These are plain shell commands that work with any agent (Cursor, Claude Code, Gemini CLI, Antigravity, Aider, etc.).

1. Start GDB

bash
./scripts/gdb_start.sh ./demo_app
# Output: GDB started. PID=12345, pipe=gdb_cmd_pipe, log=trace.log

2. Set a conditional tracepoint

bash
./scripts/gdb_trace.sh loop_function '"iteration=%d\n", iteration' 'iteration == 10'

3. Run the program

bash
./scripts/gdb_send.sh "run"

4. Sample the trace

bash
tail -n 20 trace.log

5. Modify the tracepoint dynamically

bash
./scripts/gdb_trace.sh loop_function '"new trace: %d\n", iteration' 'iteration == 50'

6. Clear all tracepoints (keep process running)

bash
./scripts/gdb_trace.sh --clear

7. Stop GDB and clean up

bash
./scripts/gdb_stop.sh
# Kills GDB + target + tail feeder, waits for exit, removes session files.

Agent Compatibility

The named pipe method was designed to work with any agent that can execute shell commands. It requires only three primitives: (1) write to a file, (2) send POSIX signals, (3) read a file.
AgentShell ToolBackground CommandsNotes
Cursor
Shell
block_until_ms: 0
or
&
Stateful shell across calls
Claude Code
bash
&
Stateful shell
Gemini CLIShell
&
Similar to Claude Code
AntigravityShell/exec
&
Varies by config
AiderShell
&
May need
--yes
for non-interactive
Key design choice: The helper scripts (
gdb_start.sh
,
gdb_send.sh
,
gdb_trace.sh
) encapsulate all quoting and timing logic, so agents don't need to reason about shell escaping or sleep intervals. Every agent calls the same scripts the same way.

Limitations

  1. Linux Only: This workflow relies on POSIX signals (
    SIGINT
    ) and Linux process management. It will not work on Windows.
  2. Permissions (
    ptrace
    )
    :
    • Attaching to a running process (
      -p PID
      ) often requires
      sudo
      or adjusting
      /proc/sys/kernel/yama/ptrace_scope
      (set to 0).
    • Workaround: Start the process via GDB (
      --args ./exe
      ) to avoid this.
  3. Signal Handling: If the target process masks
    SIGINT
    , interrupting it might be difficult. You may need to use
    kill -STOP
    /
    kill -CONT
    cautiously, but that stops GDB logic too.
  4. Blind Spots: Since you are not seeing
    stdout
    in real-time (only the log file), you might miss immediate errors if you don't check the log frequently.

Common Pitfalls

These are real issues discovered in practice. Read them before starting a session.

1. Conditional tracepoint on a value that already passed

If the program has a monotonically increasing counter (e.g.,
iteration++
) and you set
condition $bpnum iteration == 20
after the counter is already at 50, the tracepoint will never fire. The counter will never be 20 again.
Fix: Before setting a conditional tracepoint on a changing value, interrupt and inspect the current value first. Then pick a target value that hasn't been reached yet.
bash
# Interrupt, check current state, then set a future target
kill -INT $(cat .gdb_pid) && sleep 1
echo "frame 3" > gdb_cmd_pipe && sleep 0.5   # navigate to the right frame
echo "print counter" > gdb_cmd_pipe && sleep 1
tail -n 5 trace.log                           # read the value
# Now set condition for a value AHEAD of where the program is

2. Cleanup race: removing the pipe before GDB exits

If you delete
gdb_cmd_pipe
before GDB has finished processing
kill
/
quit
commands sent through that pipe, GDB and its child processes will linger as orphans. The pipe is gone, so GDB can no longer read commands and the
tail -f
feeder also hangs.
Fix: Use
./scripts/gdb_stop.sh
which kills processes first, waits for them to die, and only then removes session files. Never
rm
the pipe as a first step.

3. Commands dropped after SIGINT

After sending
kill -INT
to GDB, it needs time to stop the inferior and return to the
(gdb)
prompt. Commands sent too soon (e.g., 0.3s later) may be silently dropped.
Fix: Wait at least 1 second after SIGINT before sending the next command. The helper scripts (
gdb_trace.sh
) already account for this.

4. GDB cannot process commands while the inferior is running

In all-stop mode (the default), GDB blocks on the running inferior. Commands like
info breakpoints
sent via the pipe are queued but not executed until the inferior stops.
Fix: Always interrupt first, then send your query command, read the log, and then
continue
. Example:
bash
kill -INT $(cat .gdb_pid) && sleep 1
./scripts/gdb_send.sh "info breakpoints"
sleep 1 && tail -n 10 trace.log
./scripts/gdb_send.sh "continue"

5. Log output appears delayed

GDB's logging (
set logging enabled on
) can buffer output. After a
dprintf
fires or a command runs, the result may not appear in
trace.log
immediately.
Fix: Wait 1–2 seconds before reading the log after sending a command. Don't assume the log is broken just because it looks stale — check again after a brief delay.

Agent Guidance (Reducing Tool Calls)

Follow these patterns to handle common GDB tasks in fewer round-trips.

Before setting a conditional tracepoint on a running program

If the condition depends on a changing value (counter, timestamp, etc.), you MUST check the current value first to avoid setting a condition that will never fire. Do this in one shell call:
bash
kill -INT $(cat .gdb_pid) && sleep 1 \
  && echo "frame 3" > gdb_cmd_pipe && sleep 0.5 \
  && echo "print counter" > gdb_cmd_pipe && sleep 1 \
  && tail -n 5 trace.log
Then set the tracepoint for a value ahead of the current one, and continue — again in one call:
bash
./scripts/gdb_trace.sh loop_function '"trace: %d\n", iteration' 'iteration == <FUTURE_VALUE>'

Setting a tracepoint and immediately running

When the program hasn't been started yet (
run
not called), you can set the tracepoint and run in one call since there's nothing to interrupt:
bash
./scripts/gdb_trace.sh loop_function '"iteration=%d\n", iteration' 'iteration == 10' \
  && ./scripts/gdb_send.sh "run"

Sampling the log after a tracepoint

Don't check the log immediately — give the program time to reach the tracepoint. Estimate the wait from the loop interval and current position, then combine the wait and the read into one call:
bash
sleep 5 && tail -n 20 trace.log

Inspecting state (interrupt + query + resume)

Batch all three steps into a single shell call:
bash
kill -INT $(cat .gdb_pid) && sleep 1 \
  && ./scripts/gdb_send.sh "info breakpoints" \
  && sleep 1 && tail -n 15 trace.log \
  && ./scripts/gdb_send.sh "continue"

Shutdown and cleanup

Always use the dedicated stop script — one call handles everything:
bash
./scripts/gdb_stop.sh

Best Practices

  • Use the helper scripts instead of raw
    echo
    commands. They handle quoting, timing, and PID tracking.
  • Always use
    gdb-multiarch
    if available (auto-detected by
    gdb_start.sh
    ).
  • Trace lightly: High-frequency tracepoints (e.g., inside tight loops) will slow down execution significantly.
  • Avoid blocking commands: Never issue a GDB command that waits for user input (like
    command
    without
    end
    ) unless you are sure you can provide it.
  • Check
    trace.log
    frequently
    : Since stdout isn't visible in real-time, the log is your only window into the process.
  • Clean up with
    gdb_stop.sh
    : Always use the stop script instead of manually removing files. It kills processes first, avoiding orphaned GDB/tail processes.