Skip to content

GDB Setup, Commands and Configurations

Disable printf when gdb is active

Debugging in C Programming

Creating a debug.h file with the following:

  • This will help us to disable the printf function when debugging
#ifndef DEBUGING_H
#define DEBUGING_H

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600  // Ensure fileno and related functions are declared.
#endif

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L  // Use this only if not already defined.
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>



// Global variable to store the original stdout file descriptor.
static int saved_stdout_fd = -1;

// Function to check if a debugger (e.g., GDB) is attached
static inline bool is_debugger_attached(void) {
    FILE *f = fopen("/proc/self/status", "r");
    if (!f) return false;

    char line[256];
    bool attached = false;
    while (fgets(line, sizeof(line), f)) {
        if (strncmp(line, "TracerPid:", 10) == 0) {
            int tracer_pid = atoi(line + 10);
            if (tracer_pid > 0) {
                attached = true;
            }
            break;
        }
    }
    fclose(f);
    return attached;
}

// Disables the printf function if gdb is active.
#undef printf
#define printf(...) ( is_debugger_attached() ? (void)0 : fprintf(stdout, __VA_ARGS__) )

// Function to disable stdout by redirecting it to /dev/null if a debugger is attached.
static inline void disable_stdout(void) {
    if (is_debugger_attached()) {
        if (saved_stdout_fd == -1) {
            // Save the current stdout file descriptor.
            saved_stdout_fd = dup(fileno(stdout));
        }
        // Redirect stdout to /dev/null.
        freopen("/dev/null", "w", stdout);
    }
}

// Function to re-enable stdout by restoring the original file descriptor.
static inline void enable_stdout(void) {
    if (saved_stdout_fd != -1) {
        fflush(stdout);
        dup2(saved_stdout_fd, fileno(stdout));
        close(saved_stdout_fd);
        saved_stdout_fd = -1;
    }
}

#endif // DEBUGING_H

To use it

  • Simply add this under all your #include in every document you are printing in
//--------------------------------------//
// This will initialize the changes     //
// within that file, this has to be     //
// done in every file you where want    //
// to disable the printf function.      //
//--------------------------------------//
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif

#include <stdio.h>
#include "debug.h"
//--------------------------------------//

Debugging in CPP

Creating a debug.h file

#ifndef DEBUGGING_H
#define DEBUGGING_H

#include <iostream>
#include <fstream>
#include <streambuf>
#include <string>
#include <unistd.h>

struct NullBuffer : public std::streambuf {
    int overflow(int c) override { return c; }
};

struct NullStream : public std::ostream {
    NullStream() : std::ostream(&m_sb) {}
private:
    NullBuffer m_sb;
};

// Function to check if GDB is attached
inline bool is_debugger_attached() {
    std::ifstream status_file("/proc/self/status");
    if (!status_file.is_open()) return false;

    std::string line;
    while (std::getline(status_file, line)) {
        if (line.find("TracerPid:") == 0) {
            int tracer_pid = std::stoi(line.substr(10));
            return tracer_pid > 0;  // If TracerPid > 0, a debugger is attached.
        }
    }
    return false;
}

// Function to disable stdout if running in gdb
inline void disable_stdout() {
    if (is_debugger_attached()) {  // Now disables output only if GDB is attached.
        static NullStream null_stream;
        std::cout.rdbuf(null_stream.rdbuf());
    }
}

// Function to enable stdout
inline void enable_stdout() {
    static std::streambuf *orig_buf = std::cout.rdbuf();
    std::cout.rdbuf(orig_buf);
}

#endif // DEBUGGING_H

To use it

#include "debug.h"

int main(){
    disable_stdout(); // Disable prints when using gdb
    return 0;
}

GDB Commands

Setting up the debugger:

  1. gdb ./<executable> (starts the debugger)
  2. lay next and then press Enter until you see only the C code
  3. b main sets a breakpoint inside the main function (can be set in other functions too)
  4. run (executes the program)

Using the debugger:

  1. s = Step in (enters a function)
  2. n = Next (moves to the next line)
  3. p size = Prints the value of the variable size
  4. info locals = Prints all locally accessible variables
  5. info variables <var-name-substring> = Prints all global variables that start with <var-name-substring>

Printing Variables in Different Formats:

  • Decimal (default): p var
  • Hexadecimal: p/x var
  • Binary: p/t var
  • Octal: p/o var
  • Character (if applicable): p/c var
  • Unsigned decimal: p/u var
  • Floating point: p/f var
  • Dereferencing pointers: p *ptr
  • Printing memory in different formats: x/<format> <address>
  • Example: x/4xb &var prints 4 bytes of memory at var in hex

This allows you to inspect variables and memory in different representations while debugging.

GDB Config File

  • This you can set in ~/.gdbinit to apply to your gdb.
Config file
# ~/.gdbinit - Custom GDB Configuration

# -----------------------------------------------------
# General GDB settings
# -----------------------------------------------------
# Disable pagination for smooth scrolling
set pagination off

# Enable TUI mode with layout next to show C source code
layout next
focus source

# Suppress extra GDB prints and confirmations
set verbose off
set print elements 0
set print repeats 0
set print pretty off
set print static-members off
set print object on
set confirm off

# -----------------------------------------------------
# Suppress the inferior's output via freopen() at main()
# -----------------------------------------------------
# Note: This redirect must occur before any output is flushed.
break main
commands
    # Redirect stdout and stderr to /dev/null to suppress printf/puts output.
    call freopen("/dev/null", "w", stdout)
    call freopen("/dev/null", "w", stderr)
    # Continue execution after setting up the redirection.
    continue
end

# -----------------------------------------------------
# Define a silent_run command as an alternative method
# -----------------------------------------------------
# Note: This method sets the inferior's tty to /dev/null.
#       It may not work as expected in TUI mode or on all systems.
define silent_run
    set inferior-tty /dev/null
    run
end
document silent_run
    Runs the program with its output redirected to /dev/null.
    (If this doesn’t work as expected, the break-at-main with freopen() is your best bet.)
end

# Inform the user that the configuration is loaded.
echo Custom GDB configuration loaded.\n

Additional GDB Configs

Switching between child and parent processes

When debugging programs that use fork(), GDB, by default, follows the parent process. You can configure GDB to follow the child process or manually switch between them.

Set GDB to follow the child process after a fork:

set follow-fork-mode child

Set GDB to follow the parent process after a fork:

set follow-fork-mode parent

List all processes in the debugging session:

info inferiors

Switch between parent and child process manually:

inferior <id>
Replace <id> with the process ID shown in info inferiors.