GNU Debugger
The GNU Debugger (GDB) is a free and open-source debugger developed as part of the GNU Project, enabling programmers to inspect and control the execution of computer programs written in various languages, including C, C++, Fortran, Ada, and Rust. Originally authored by Richard Stallman around 1985, GDB allows users to set breakpoints, examine variables, step through code, and analyze program state during execution or after a crash on native, remote, or simulated systems.[1] It was first publicly released in 1986 and has since evolved into a powerful tool under the GNU General Public License (GPL), granting freedoms to copy, modify, and distribute it with source code access.[2][1]
GDB supports debugging on most popular UNIX-like systems, Microsoft Windows, and macOS, with capabilities for both hosted and embedded environments. It accommodates a wide range of programming languages such as Assembly, D, Go, Objective-C, OpenCL C, and Pascal, making it versatile for software development across diverse architectures. Key contributors over the years include Michael Tiemann for C++ support, John Gilmore for early releases and architectural redesigns like the BFD library in 1990, and Stan Shebs for later versions, with ongoing maintenance by a collaborative team using public repositories.[2][1]
Among its notable features are watchpoints for monitoring variable changes, reversible debugging for replaying execution backward, and integration with scripting via a Python API, alongside support for the Debugger Adapter Protocol to enhance compatibility with integrated development environments. GDB's codebase has grown from a few thousand lines of C code to over half a million, incorporating advanced elements like explicit frame objects for stack analysis introduced in 2002 and extensive testing with over 18,000 cases as of its early 2010s development phase.[1] The latest stable release, version 16.3, was issued on April 20, 2025, with the GDB 17 development branch created in September 2025 to address ongoing enhancements and bug fixes.[3]
Overview
Purpose and Core Functionality
The GNU Debugger (GDB) is a portable and extensible source-level debugger designed for inspecting and controlling the execution of programs at the source code level.[4] As part of the GNU Project, it enables developers to observe a program's internal behavior during runtime or analyze its state at the point of a crash, facilitating the identification and diagnosis of software bugs.[5] GDB supports debugging in languages such as C and C++, allowing users to interact with programs compiled from these sources.[5]
At its core, GDB provides functionality to attach to running processes, load executable files, and examine key elements of program state, including memory contents, CPU registers, and stack traces.[5] This is achieved by leveraging debugging information embedded in the executable, which maps low-level machine instructions back to high-level source code constructs.[6] GDB commonly utilizes the DWARF debugging format for this purpose, as it offers expressive support for describing program structure, variables, and execution flow in a compiler-independent manner.[6]
GDB can be invoked in several basic ways to initiate a debugging session, such as running gdb [program](/page/Program) to load and start an executable directly, or gdb [program](/page/Program) [core](/page/Core_dump) to analyze a core dump file generated from a crashed process.[7] Additionally, it supports attaching to an already running process using gdb [program](/page/Program) [pid](/page/PID), where pid is the process identifier, enabling non-intrusive inspection without restarting the program.[7] These methods ensure flexibility in debugging scenarios, from live executions to post-mortem analysis.[5]
Importance in Software Development
The GNU Debugger (GDB) plays a pivotal role in software development by accelerating the detection and resolution of bugs, enabling developers to efficiently trace execution paths and inspect program states in real-time or post-mortem scenarios. As a source-level debugger, it supports the analysis of complex codebases, including those with millions of symbols, by leveraging symbolic information to map low-level machine instructions back to high-level source code, enabling startup times for symbol loading to be reduced from minutes to seconds in large projects like Emacs or Firefox.[1] Furthermore, GDB's free and open-source nature under the GNU General Public License (GPL) ensures widespread accessibility, allowing developers worldwide to modify, extend, and distribute it without licensing barriers, fostering collaboration in open-source ecosystems.[8]
GDB is particularly valuable in diverse use cases, such as debugging crashes in embedded systems via remote connections with tools like GDBserver, optimizing performance bottlenecks in server applications on multicore architectures, and identifying logic errors in desktop software written in compiled languages like C and C++.[9] In embedded environments, it facilitates cross-debugging between host and target architectures, crucial for resource-constrained devices, while in server contexts, it aids in analyzing multithreaded behaviors and core dumps from production failures.[1] For desktop applications, GDB's ability to handle optimized code helps uncover subtle issues that might otherwise require extensive logging or printf-style instrumentation.
Compared to non-source-level debuggers, which operate directly on machine code and demand expertise in assembly language, GDB's symbolic approach is preferred for languages supporting debug symbols, as it provides intuitive views of variables, functions, and control flow without delving into hexadecimal disassembly, thereby lowering the skill barrier and improving productivity in modern development workflows.[10] This advantage is amplified through its integration with compilers like GCC, where the -g flag embeds debug information during compilation, enabling GDB to correlate executable binaries with original source files for precise inspections.[9]
History
Origins and Early Development
The GNU Debugger (GDB) originated in 1986 as a key component of the GNU Project, initiated by Richard Stallman to develop a free software symbolic debugger for the emerging GNU operating system. This effort was motivated by the need for an open-source alternative to proprietary debugging tools prevalent in commercial Unix environments, such as dbx from Sun Microsystems, ensuring that developers could freely inspect and control program execution without licensing restrictions. Stallman, already known for GNU Emacs, prioritized GDB after stabilizing Emacs, viewing it as essential for debugging the growing suite of GNU tools.[11][1]
Written primarily in C, GDB's early implementation focused on Unix-like systems, leveraging low-level system calls for process control. Development began around 1985–1986, with the initial codebase emphasizing portability and source-level debugging capabilities for languages like C. The first public release appeared in 1988, though changes before version 2.0 are not well documented, reflecting rapid iteration in the project's nascent phase. GDB drew conceptual influences from established Unix debugging mechanisms, including the ptrace system call for tracing and manipulating processes, as well as command syntax from BSD-derived debuggers like dbx, adapting these for a more extensible, machine-independent design.[12][13][1]
Key early contributors shaped GDB's foundation, with Stallman leading the core implementation and releases through version 2.8, assisted by collaborators such as Peter TerMaat, Chris Hanson, and Richard Mlynarik. John Gilmore played a pivotal role in the late 1980s and early 1990s, redesigning the target interface for better cross-platform support and integrating the Binary File Descriptor (BFD) library in 1990 to handle diverse object formats. These efforts also facilitated tight integration with the GNU Compiler Collection (GCC), enabling seamless debugging of code compiled with GCC's output, which was crucial for the GNU ecosystem's cohesion during its formative years up to the 1990s.[14][1]
Major Releases and Milestones
GDB follows an annual major release cadence on a slip-for-slip schedule, where delays in branching propagate to release dates to accommodate essential fixes and features.[15]
In 1999, GDB's development shifted to a public source code repository hosted on Sourceware.org, a collaborative platform established in 1998 to manage open-source projects including binutils and GCC, enhancing community-driven maintenance while retaining FSF stewardship.[1]
Significant milestones include GDB 6.0, released on October 6, 2003, which marked the start of the 6.x series with improvements in multi-architecture support and remote debugging capabilities.[16]
GDB 7.0, released on October 6, 2009, introduced full Python scripting integration via a new API, enabling users to extend GDB with custom commands, pretty-printers, and automation scripts.[17]
GDB 10.1, released on October 24, 2020, enhanced handling of DWARF 5 debug information format, including support for indexed strings and new attributes, improving compatibility with modern compilers.[18]
More recently, GDB 14.1, released on December 3, 2023, bolstered RISC-V architecture support with better vector extension handling and multi-core debugging features.[19]
GDB 16.1, released on January 18, 2025, added watchpoint support for tagged data pointers, facilitating debugging of memory tagging extensions in architectures like ARM.[20]
The latest milestone, GDB 16.3, released on April 20, 2025, focused on bug fixes and stability enhancements as a corrective update over 16.2, addressing regressions in scripting and target descriptions. In September 2025, the GDB 17 branch was created for ongoing development and enhancements.[21][22]
Programming Languages
The GNU Debugger (GDB) provides comprehensive support for debugging programs written in C and C++, including advanced handling of language-specific constructs such as templates and the Standard Template Library (STL). In C++, GDB demangles names, supports pretty-printing for STL containers like std::vector and std::map, and allows inspection of template instances through commands like ptype and print.[23] For Fortran, GDB offers built-in support for array slicing and intrinsic functions, enabling users to display and manipulate multi-dimensional arrays directly in expressions, though variable names may require a trailing underscore depending on the compiler.[24][25] Ada receives full semantic support in GDB, particularly for tasking features, with dedicated commands like info tasks to list and switch between concurrent tasks, and catchpoints for Ada exceptions.[26][27]
Beyond these primary languages, GDB supports assembly language debugging by disassembling code and examining registers at the machine level.[28] For Rust, integration is provided through the rust-gdb wrapper, which loads Rust-specific pretty-printers for types like enums and traits, though some value-printing behaviors differ from native Rust tools due to GDB's expression parser.[29] Go programs can be debugged with support for goroutines and channels, while D and Objective-C receive parsing for their object-oriented features, including Objective-C's method calls and categories.[28] GDB also supports OpenCL C for kernel debugging, Pascal, and Modula-2. Python support is limited to scripting and extending GDB itself via embedded Python code, rather than full source-level debugging of Python applications, which typically requires tools like the Python Debugger (pdb).[30][28]
GDB handles language-specific runtime behaviors, such as C++ exceptions, through catchpoints that pause execution on throw, catch, or unhandled exceptions, accessible via the catch throw command.[31] To enable these features, programs must be compiled with debugging information, usually by passing the -g flag to compilers like GCC or Clang, which generates DWARF or STABS symbol tables for GDB to parse.[32] Platform dependencies may influence the completeness of language support, such as Ada tasking availability in core dumps.[33]
Operating Systems and Architectures
GDB is primarily designed for Unix-like operating systems, with extensive native support on Linux distributions, where it serves as the standard debugger integrated into development toolchains like GCC. It also provides robust compatibility with BSD variants, including FreeBSD, NetBSD, and OpenBSD, enabling native debugging on these platforms across various hardware configurations. macOS, based on Darwin, has native support, allowing debugging of Mach-O executables, though it requires codesigning the GDB binary for process control due to macOS security policies.[4][34][35] On Windows, GDB operates via POSIX emulation layers such as MinGW, Cygwin, or the Windows Subsystem for Linux (WSL), facilitating debugging of native Windows applications or cross-compiled binaries without requiring a full Unix environment. Furthermore, GDB extends to embedded real-time operating systems (RTOS) like FreeRTOS, supporting debugging of microcontroller-based applications on platforms such as STM32, often in conjunction with hardware interfaces.[4][34]
In terms of processor architectures, GDB offers comprehensive support for widely used instruction sets, including x86 and x86-64 for general-purpose computing, ARM in both 32-bit (AArch32) and 64-bit (AArch64) modes for mobile and embedded devices, RISC-V for open-source hardware initiatives, PowerPC for legacy and high-performance systems, and MIPS for networking and embedded applications. Support for LoongArch, a Chinese-developed architecture, was introduced in GDB 13.1, including floating-point debugging capabilities on Linux targets, broadening its applicability in diverse global hardware ecosystems.[36][37][38] This multi-architecture capability is achieved through configurable builds that include architecture-specific backends, ensuring accurate symbol resolution and register handling across targets.
Cross-debugging is a core strength of GDB, facilitated by gdbserver, a lightweight companion program that runs on the target system to communicate with the host GDB instance over serial or TCP connections using the GDB remote serial protocol. This setup allows developers to debug executables compiled for one architecture on a host machine of a different architecture, such as examining an ARM application running on an embedded Linux device from an x86-64 desktop. gdbserver's portability makes it suitable for resource-constrained environments, supporting multi-process execution and attachment to running programs without embedding debug stubs in the target binary.[39][40]
Despite its broad portability, GDB has certain limitations tied to host and target environments; for instance, it lacks a built-in native graphical user interface on Windows, requiring third-party frontends like DDD or Eclipse for visual debugging. GDB's functionality depends on standard executable formats, including ELF for Linux and Unix-like systems, Mach-O for macOS, and PE/COFF for Windows via MinGW or Cygwin, with debug information typically in DWARF format to ensure compatibility across these platforms. Language support, such as for C++ or Fortran, may further depend on OS-specific debug info extensions within these formats.[41][42]
Core Debugging Features
Stepping and Execution Control
The GNU Debugger (GDB) provides essential commands for controlling program execution, enabling users to start, resume, or advance through code line by line or function by function. These mechanisms form the foundation for interactive debugging, allowing precise navigation of program flow without full automation. The primary commands include run, which initiates program execution from the beginning or resumes from the last stop if already running, creating an inferior process and passing any specified arguments or environment settings.[43] Similarly, continue (or c) resumes execution from the point of interruption until completion, a breakpoint, or a signal, with an optional ignore-count to bypass initial hits at the current location.[44]
For granular control, GDB offers stepping commands that advance execution incrementally. The step (or s) command executes the program until the next source line, entering function calls if debug information is available, while next (or n) steps over function calls to the subsequent line in the current function, treating called functions as single units.[44] The finish (or fin) command runs the program until the current function returns, displaying the return value upon completion, which is particularly useful for tracing subroutine behavior.[44] These commands support a count argument to repeat the action multiple times, and execution halts at breakpoints or watchpoints as needed.[45]
GDB supports two execution modes: synchronous (foreground), where the debugger waits for the program to stop before accepting further input, and asynchronous (background), enabled by appending & to commands like continue& or step&, allowing immediate prompting for additional commands during ongoing execution.[46] Asynchronous mode requires target support and is ideal for multi-threaded debugging, with interruptions handled via the interrupt command to stop threads selectively or globally.[46]
Signal handling integrates seamlessly with execution control, enabling resumption after interruptions like SIGINT (keyboard interrupt) or SIGSEGV (segmentation fault). By default, GDB stops on erroneous signals such as SIGSEGV and prints details, but users can continue with signal 0 to ignore the signal and proceed, or use handle to configure behaviors like passing the signal to the program without stopping.[47] For SIGINT, GDB typically halts execution, but continue resumes normally unless reconfigured.[47]
Watchpoints enhance conditional stepping by pausing execution when a watched expression changes, effectively creating data-dependent flow control during step or continue operations.[48] Conditions can be added to watchpoints (e.g., for specific threads), triggering stops only under defined circumstances, which complements stepping by avoiding unnecessary pauses in complex code paths. Software watchpoints achieve this via single-stepping with value checks, though hardware watchpoints offer faster performance by monitoring memory access directly.[48]
Breakpoints and Watchpoints
Breakpoints in the GNU Debugger (GDB) allow users to pause program execution at specified points, such as lines of source code or function entries, facilitating detailed examination of program behavior. The primary command to set a breakpoint is break, which can target a function name, like break main to halt at the start of the main function, or a specific line, such as break file.c:42. Temporary breakpoints, set with tbreak, function similarly but are automatically deleted after the first hit, useful for one-time pauses without manual cleanup.
Conditional breakpoints extend this functionality by pausing execution only when a specified Boolean expression evaluates to true, invoked via break with an if clause, for example, break loop.c:10 if i > 5 to stop when variable i exceeds 5. This enables targeted debugging of rare events or specific states without repeated manual intervention.
Watchpoints monitor changes to variables or memory locations, pausing execution upon read, write, or access. Hardware-assisted watchpoints, set with watch, leverage processor debug registers for efficient monitoring of simple variables, such as watch counter to detect modifications to counter. For more complex expressions or when hardware resources are exhausted, GDB falls back to software watchpoints, which insert breakpoints at potential modification sites in the code, though this approach is slower and less precise for large programs.
Breakpoint and watchpoint management in GDB includes commands like info breakpoints to list all active points with details such as location, status, and hit counts; delete to remove specific ones by number; and disable or enable to toggle them without deletion. A notable enhancement in GDB 16.1 introduced support for tagged data watchpoints, allowing monitoring of memory regions with associated metadata tags, which aids debugging in systems with tagged architectures such as ARM's Memory Tagging Extension (MTE).[22]
Hardware watchpoints are limited by the number of available debug registers on the target architecture; for instance, x86 processors typically support only four such slots, restricting simultaneous watchpoints to that count unless software emulation is used. Upon hitting a breakpoint or watchpoint, GDB enters a stopped state from which stepping commands can resume controlled execution.
Variable Inspection and Modification
In the GNU Debugger (GDB), variable inspection allows users to examine the current state of program variables, expressions, and memory during a debugging session, providing insights into data values, types, and structures without altering execution flow.[49] This is typically performed after the program has paused, such as at a breakpoint, enabling developers to verify logic and identify issues like incorrect values or overflows. Core inspection features support both simple scalars and complex data types, with commands that output formatted results for clarity.
The primary command for inspecting variables is print, abbreviated as p, which evaluates and displays the value and type of an expression.[49] For example, print x prints the value of variable x, while print /d x formats it as a signed decimal integer; other formats include /x for hexadecimal, /f for floating-point, /a for addresses, and /s for strings.[49] To automatically display expressions on each stop (e.g., during stepping), the display command is used, such as display x, which lists the expression in subsequent outputs until removed with undisplay.[49] For local variables and function arguments in the current stack frame, info locals lists all locals with their values, and info args does the same for arguments, facilitating quick overviews of scope-specific state.[49]
Handling complex data types is supported through formatted printing and syntax extensions. Arrays can be inspected with print /a array to show elements in array format, or using the artificial array operator @, as in print *array@10 to display the first 10 elements starting from array.[49] Structures and C++ objects are accessed via member notation, e.g., print struct_var.member or print object->method(), with options like set print pretty on enabling indented output for nested types to improve readability.[49] Stack frames, which represent function call contexts, are examined using backtrace (or bt), which prints a summary of the call stack with one line per frame, including function names and arguments; bt 5 limits to the innermost five frames.[50] Selecting a frame with frame n (where n is the frame number) allows inspection of its locals and args via the aforementioned info commands.[50]
For low-level memory inspection, the x (examine) command displays contents at a specified address in various formats and sizes. Its syntax is x/nfu addr, where n is the repeat count, f the format (e.g., x for hex, s for string, i for machine instructions), u the unit size (b for byte, h for halfword, w for word), and addr the starting address.[49] An example is x/4xw $sp, which examines four words in hexadecimal from the stack pointer $sp. Convenience variables like $_ (last examined address) aid iterative inspection.[49]
Variable modification alters program state directly in the current frame using assignment expressions via the set command, which avoids printing the result unlike print. The syntax is set var = expression, such as set var x = 42 to assign 42 to x; this evaluates the expression in the program's context and updates memory accordingly.[51] For convenience variables (starting with $), set $foo = *object stores a value without affecting the program. Modifications take effect immediately upon resumption, allowing tests of boundary conditions or fixes during debugging.[51]
GDB also supports postmortem analysis of crashed programs through core dump files, which capture the memory image at termination. To load a core dump, invoke gdb program core, where program is the executable and core the dump file; this enables inspection of variables, stack frames, and memory using the same commands as live sessions, such as print, backtrace, and info locals, without executing the program.[52] This feature is essential for diagnosing segmentation faults or other failures in production environments where rerunning is impractical.[52]
Advanced Debugging Capabilities
Remote and Cross-Debugging
The GNU Debugger (GDB) supports remote debugging, allowing users to debug programs executing on a separate machine or device from the host running GDB, which is particularly useful for resource-constrained or inaccessible targets.[39] This capability extends to cross-debugging, where the host and target architectures differ, such as debugging ARM binaries from an x86_64 host.[53] GDB achieves this through lightweight stubs like gdbserver on the target, which communicate with the host GDB instance using the Remote Serial Protocol (RSP).[54]
To set up remote debugging, gdbserver is run on the target system to launch or attach to the program, while a multi-architecture variant of GDB, such as gdb-multiarch, is used on the host to handle cross-architecture symbols and binaries.[39] For example, on the target, the command gdbserver :2345 /path/to/program starts the program and listens on TCP port 2345; on the host, GDB connects via target remote target-host:2345 after loading the executable with file /path/to/program.[53] For cross-debugging, the host specifies the target's system root or library paths using set sysroot to resolve shared libraries correctly.[53] Serial connections are also supported, such as gdbserver /dev/ttyS0 program on the target and target remote /dev/ttyS0 on the host.[39]
The RSP serves as the underlying protocol, enabling packet-based communication over serial lines, TCP/IP, or USB for controlling execution, reading memory, and managing registers without requiring a full GDB installation on the target.[54] It uses checksums for error detection and supports commands like stepping, breakpoints, and data transfer, making it suitable for low-bandwidth links.[54]
Common use cases include debugging embedded devices via serial or TCP connections, where gdbserver's small footprint is advantageous, and native Android applications, which can be debugged by running gdbserver on the device and connecting from a host GDB instance.[39][55] Cross-compilation scenarios, such as an x86 host debugging ARM targets, leverage this setup to inspect binaries built for the remote architecture.[53]
Security is a critical concern, as gdbserver lacks built-in authentication or encryption, providing full access to the target's memory and potentially other resources upon connection.[39] Users must restrict access using host firewalls to allow only trusted IP addresses on the chosen port and avoid exposing gdbserver to public networks; for added protection, connections can be tunneled over SSH.[39][53]
Reversible Debugging
Reversible debugging in the GNU Debugger (GDB) enables users to record the execution trace of a program and replay it forward or backward, facilitating the inspection of program states at any point in the execution history. This feature, also known as record-and-replay or time-travel debugging, is particularly valuable for analyzing complex program behaviors without restarting from the beginning each time.[56]
The mechanism operates by first activating recording mode, which logs the effects of each machine instruction executed by the inferior process. Users initiate recording with commands such as record full start, which employs software emulation to capture a complete trace including memory modifications and register changes, or record btrace, which leverages hardware branch tracing for more efficient logging of control-flow events on supported processors like Intel's Processor Trace. Once recording begins—typically after starting the program with run or start—GDB maintains an execution log that can be navigated during replay. Key commands for backward execution include reverse-step (or reverse-stepi for instructions), which executes the program backward one step at a time, and reverse-continue, which runs the program backward until a breakpoint or watchpoint is hit. Additional navigation is possible via record goto to jump to specific points in the log, such as the beginning (begin) or end (end), or by instruction number. This integration allows seamless use alongside standard stepping commands for bidirectional control.[56][57]
Built-in support for reversible debugging was introduced in GDB 7.0 in 2009 through the initial process record and replay target, providing foundational software-based recording. Subsequent enhancements include the addition of hardware-assisted btrace recording in GDB 7.12, which reduces overhead by focusing on branch traces rather than full instruction emulation, and further improvements like Python API support for clearing trace data in GDB 11.1. For scenarios requiring even greater efficiency or scalability, extensions such as UDB (from Undo.io) build upon GDB's framework to offer optimized record-and-replay capabilities, though these are not part of the core GDB distribution.[58][59]
Common use cases for reversible debugging include diagnosing non-deterministic bugs, such as those arising from race conditions in multithreaded applications, where replaying and rewinding execution allows developers to isolate the exact sequence leading to a failure. By recording a session once and then exploring it iteratively, users can set breakpoints in the past or step backward from a crash to pinpoint root causes that might otherwise require multiple reruns with varied inputs. This approach is especially effective for intermittent issues that are difficult to reproduce in forward-only debugging.[56][57]
Despite its utility, reversible debugging has notable limitations, primarily high resource consumption in software-based modes. The record full method, for instance, can consume significant memory—defaulting to a limit of about 200,000 instructions (configurable via set record full insn-number-max)—as it stores detailed state changes, making it impractical for long-running or memory-intensive programs. Hardware btrace mitigates this by using processor ring buffers (e.g., 64KB default for Intel BTS), but it only captures branches, omitting data accesses, and requires compatible hardware. Platform support is restricted to architectures like x86, ARM, AArch64, PowerPC, RISC-V, and others primarily on GNU/Linux, with no native support on Windows or macOS. Recording also pauses or alters execution timing, which may affect timing-sensitive bugs.[56][60]
Multi-Process and Thread Support
The GNU Debugger (GDB) provides comprehensive support for debugging multi-threaded programs, allowing users to manage and inspect threads within a single process or inferior.[61] Threads in GDB are identified by unique per-inferior IDs and global IDs, sharing the same address space but maintaining separate registers and stacks.[61] The debugger designates a "current thread" for focused control, while enabling observation of all threads during execution.[61]
Key commands facilitate thread management. The info threads command lists all threads, displaying their IDs, global IDs, system tags, names, and current frames, with options to filter by stopped or running state.[61] To switch the current thread, users employ thread thread-id, where thread-id is the local ID from the info threads output.[61] For applying commands across multiple threads, thread apply [thread-id-list | all] command executes the specified command on selected or all threads, supporting flags like -c for continuing on errors and -s for silent operation.[61] Thread naming via thread name [name] and searching with thread find [regexp] further aid in organization.[61]
GDB operates in all-stop mode by default, where the entire program halts when any thread hits a breakpoint or receives a signal, simplifying synchronization but potentially slowing execution.[62] In contrast, non-stop mode allows individual threads to continue running independently, enabling examination of stopped threads while others proceed, which is useful for asynchronous or real-time applications.[62] To control thread scheduling, the set scheduler-locking [mode](/page/Mode) command restricts which threads resume during execution: off permits all threads, on limits to the current thread (halting new threads at entry), step behaves like on only during stepping to avoid preemption, and replay applies on in replay mode.[63]
Thread-specific breakpoints enhance targeted debugging by triggering only for designated threads.[64] Set using break locspec thread thread-id, where locspec is the location and thread-id the target thread's ID, these breakpoints ignore hits from other threads and are automatically deleted if the specified thread exits.[64] Conditional variants, such as break locspec thread thread-id if condition, further refine control, for example, break frik.c:13 thread 28 if bartab > lim.[64] GDB also supports thread-local storage (TLS) access, such as inspecting errno per thread on GNU/Linux and FreeBSD platforms.[61]
For multi-process debugging, GDB treats each process as an "inferior," supporting attachment to multiple processes within a session.[65] The info inferiors command lists all inferiors, showing their GDB-assigned numbers, system process IDs, connection types, and executables.[65] Switching occurs via inferior inf-no, while add-inferior creates new entries and remove-inferiors deletes non-running ones.[65] Attachment to a running process uses attach pid, and detachment employs detach inferior inf-no, leaving the inferior entry intact but disconnected.[65] Killing is handled by kill inferiors inf-no-list.[65]
When programs fork child processes, GDB's set follow-fork-mode directs debugging focus: parent (default) continues with the parent, while child switches to the child.[66] The set detach-on-fork setting determines handling of the non-followed process: on (default) detaches it to run freely, and off maintains control over both, allowing management via info inferiors and inferior commands.[66] This support is available on GNU/Linux kernels from version 2.5.46 onward, in both native and gdbserver modes.[66]
Variable inspection in GDB can span threads by switching the current thread or using thread-specific commands, revealing per-thread values like TLS variables.[61] Since GDB 12, enhancements to non-stop mode and asynchronous event handling have improved support for debugging threads involved in async I/O operations, reducing interruptions in concurrent environments.[38]
User Interfaces
Command-Line Interface
The GNU Debugger's command-line interface (CLI) serves as the primary mode for text-based interaction, where users enter commands at a prompt indicated by (gdb) to control program execution and examine state. This interface leverages the GNU Readline library for enhanced usability, allowing seamless input editing and navigation.[67]
Command history enables recalling prior inputs efficiently; pressing the up arrow key cycles through previously entered commands, while the history is saved to a file such as .gdb_history upon exit if configured with set history save on. Tab completion further streamlines operation by suggesting and auto-filling command names, subcommands, program symbols, and filenames when the Tab key is pressed, with repeated presses displaying available options.[68][67][69]
The built-in help system provides comprehensive guidance directly within the CLI. The help command, abbreviated as h, lists all command classes such as "aliases" or "breakpoints" when invoked without arguments; help class details commands in a specific category, and help command offers syntax and usage for individual commands. Complementing this, info help outlines categories of the info subcommands, which query the program's current state, such as breakpoints or registers.[70]
Customization enhances the CLI's flexibility through initialization files. The user's ~/.gdbinit file, executed automatically at startup unless disabled with options like -nx, allows defining command aliases via the define directive—for instance, define adder followed by a sequence of commands and end creates a shorthand for common operations, accessible by name thereafter. Local .gdbinit files in the current working directory support project-specific setups and are auto-loaded by default, controlled by set auto-load local-gdbinit on and secured via set auto-load safe-path to prevent loading from untrusted locations.[71][72][73]
Output formatting options tailor display for clarity and efficiency. The set print elements 0 command disables the default limit of 200 array elements, allowing unlimited printing to avoid truncation and provide fuller data views without additional pagination prompts.[74]
Graphical User Interfaces
The GNU Debugger (GDB) supports a range of third-party graphical user interfaces (GUIs) that enhance usability by providing visual tools for debugging tasks, often built atop GDB's command-line interface (CLI) via its machine interface (MI) protocol. These GUIs abstract complex CLI commands into intuitive point-and-click interactions, making them particularly valuable for developers seeking integrated debugging within development environments.
One of the most established standalone GUIs is the Data Display Debugger (DDD), developed under the GNU Project, which offers advanced visualization of data structures as interactive graphs alongside source code viewing and execution control. DDD supports GDB (version 4.16 and later) as its primary backend, enabling features like visual breakpoints, expression watches, and call stack navigation through graphical representations. It also accommodates other debuggers such as DBX and JDB, though development for non-GDB interfaces has been inactive since version 3.4.[75]
Integrations within integrated development environments (IDEs) further extend GDB's graphical capabilities. Eclipse CDT, the C/C++ Development Tooling plugin for Eclipse, embeds GDB for full-featured debugging, including visual breakpoint setting in the editor margin, real-time variable inspection in a dedicated watch window, and graphical call stack traces. Similarly, Code::Blocks IDE incorporates GDB via its built-in debugger plugin, supporting watch expressions, memory views, and disassembly alongside source-level stepping. Qt Creator provides GDB integration through configurable preferences, allowing users to set visual breakpoints, monitor threads, and examine locals in a multi-pane interface tailored for Qt and C++ projects.[76][77]
For Visual Studio Code, the official Microsoft C/C++ extension provides robust GDB integration, enabling features such as inline variable hovers, conditional breakpoint editors, and call stack visualizations directly in the editor. Another option is the third-party Native Debug extension, which also leverages GDB to deliver browser-like debugging with similar capabilities. Emacs users benefit from the built-in GDB mode, invoked via M-x gdb, which creates a multi-buffer layout for source code, breakpoints, stack frames, and locals, supporting mouse-driven interactions like clicking to set breakpoints. These tools collectively provide call stack graphs (prominent in DDD and VS Code) and persistent variable watches, streamlining common workflows without leaving the IDE.[78][79][80]
Despite their advantages, GDB GUIs generally act as wrappers around the CLI backend, inheriting its strengths while introducing limitations such as incomplete support for advanced or platform-specific features—like stepping into dynamic libraries on certain systems—requiring users to fall back to direct GDB commands for full control.
Machine Interface for Integration
The GNU Debugger's Machine Interface (GDB/MI) provides a structured, line-based protocol for programmatic interaction with GDB, enabling seamless integration into external applications such as integrated development environments (IDEs) and automated testing frameworks.[81] This interface is activated by invoking GDB with the --interpreter=mi option, transforming its output into a machine-readable format rather than human-oriented console responses.[82] Designed primarily for embedding GDB as a backend component, GDB/MI facilitates real-time control over debugging sessions, including execution, breakpoint management, and variable inspection, without relying on parseable text from the command-line interface.[81]
GDB/MI has evolved through three major versions to enhance reliability and expressiveness. MI Version 1 (MI1), introduced in GDB 5.3, offered basic synchronous command-response interactions.[82] MI Version 2 (MI2), available since GDB 6.0, added asynchronous event reporting, allowing notifications for events like stop or running states without blocking command execution.[82] The current MI Version 3 (MI3), implemented starting with GDB 9.1, incorporates annotations for complex outputs, such as multi-location breakpoints, and refines syntax for more robust parsing by frontends.[82] Earlier versions like MI1 and MI2 remain supported for compatibility but are deprecated in favor of MI3.[82]
The protocol's output is organized into result records prefixed with tokens like ^done for successful completions, ^error for failures, or ^running for asynchronous starts, followed by key-value pairs in a format resembling name="value".[81] Commands are issued with a leading hyphen, such as -exec-run to initiate program execution or -break-insert -f main to set a breakpoint at the main function, each eliciting structured responses that include details like thread states or stack frames.[81] Out-of-band records, marked with ~ for console output or @ for logs, interleave with results to provide contextual notifications, ensuring frontends can synchronize events accurately.[81]
Common use cases for GDB/MI center on IDE backends, where it powers debugging features in tools like Eclipse CDT, which leverages the protocol to visualize variables, control execution, and handle multi-threaded sessions.[83] Similarly, extensions in Visual Studio Code and other editors use GDB/MI to automate remote debugging and integrate with build pipelines for continuous integration/continuous deployment (CI/CD) environments, allowing scripted analysis of crashes without manual intervention.[84] This integration supports cross-platform development by abstracting GDB's core functionality into extensible APIs, reducing the need for custom parser implementations in frontend applications.
Scripting and Extensions
Built-in Command Scripting
The GNU Debugger (GDB) provides built-in command scripting capabilities through user-defined commands and command files, enabling users to automate sequences of debugging tasks without relying on external scripting languages.[85] This facility allows for the creation of macros that encapsulate repetitive operations, such as setting multiple breakpoints or inspecting variables in a loop, directly within GDB's command-line interface.[72] These features are particularly useful for streamlining workflows in batch or interactive debugging sessions.
User-defined commands are created using the define command, which assigns a new name to a sequence of existing GDB commands. The syntax is define command-name followed by the commands to execute, terminated by end.[72] For example, to define a command that prints the values of two variables, one might use:
define print_vars
print var1
print var2
end
define print_vars
print var1
print var2
end
This command can then be invoked simply as print_vars.[72] If the command name already exists, GDB prompts for confirmation before redefining it. Documentation for user-defined commands can be added with the document command, using similar syntax, and viewed via help command-name.[72]
User-defined commands support arguments, allowing dynamic behavior. Arguments are passed as whitespace-separated tokens and accessed via $arg0 through $argN, with $argc indicating the number of arguments.[72] For instance, a command to add numbers could be defined as:
define adder
if $argc == 0
echo No arguments provided\n
else
set $sum = 0
set $i = 0
while $i < $argc
set $sum = $sum + $arg$i
set $i = $i + 1
end
print $sum
end
end
define adder
if $argc == 0
echo No arguments provided\n
else
set $sum = 0
set $i = 0
while $i < $argc
set $sum = $sum + $arg$i
set $i = $i + 1
end
print $sum
end
end
This example demonstrates argument handling combined with control structures.[72]
Command files extend scripting by allowing sequences of commands to be stored in text files, with lines starting with # treated as comments.[85] These files are executed using the source command, with syntax source filename, where filename typically ends in .gdb.[85] Options include -s to search the source path and -v for verbose execution, displaying each command as it runs. GDB searches for the file in the current directory first, then along the source path set by the directory command.[85] This mechanism supports batch automation, such as loading symbols for multiple shared libraries via repeated add-symbol-file commands in a single file.[85]
GDB's scripting includes basic control flow for loops and conditionals. The while command implements loops with syntax while expression followed by commands and end, repeating as long as the expression evaluates to nonzero.[85] Within loops, loop_continue skips to the next iteration, and loop_break exits early. The if command handles conditionals: if expression executes commands if true, optionally followed by else for the alternative branch, all ending with end.[85] Stepping commands can be conditional, such as step if expression, which advances only when the condition holds. These constructs enable scripts to iterate over data structures or perform repetitive inspections, like printing array elements until a sentinel value is reached.[85]
Logging captures session transcripts for review or automation verification. The set logging enabled on command starts logging GDB's output to a file, defaulting to gdb.txt, while set logging enabled off stops it.[86] By default, output appends to the file and appears both on the terminal and in the log; set logging redirect on sends output solely to the file, and set logging overwrite on replaces the file instead of appending.[86] The set logging file filename command specifies a custom log path. This feature is valuable for recording scripted sessions, such as automated symbol loading or variable inspections, to analyze results offline.[86]
Common use cases include batch loading of symbols for complex programs with many shared libraries, achieved by scripting multiple add-symbol-file invocations in a command file sourced at startup.[85] Repetitive inspections, such as monitoring a variable across loop iterations in the debugged program, leverage while loops to execute print commands conditionally until a breakpoint or exit condition.[85] For more advanced needs, such as full programmatic control, GDB offers integration with Python scripting.[85]
Python and Guile Integration
The GNU Debugger (GDB) integrates Python scripting starting with version 7.0, allowing users to extend its functionality through the gdb Python module, which provides access to GDB's internals for creating custom commands, automating tasks, and enhancing data visualization.[87] This integration requires GDB to be configured with the --with-python option during compilation and enables importing the gdb module within Python scripts to interact with debugging sessions.[30] Key features include pretty-printers, which customize the display of complex data types, and event hooks via the gdb.events module, which allow scripts to respond to debugging events such as stops or continuations.[88]
Pretty-printers in GDB's Python API enable tailored output for specific types, such as improving the readability of Standard Template Library (STL) containers or user-defined structures by overriding default printing behavior.[89] For instance, a pretty-printer can be implemented as a subclass of gdb.DefaultPrettyPrinter to parse and format a custom linked list structure, displaying its nodes recursively without manual traversal commands. Event hooks, accessed through gdb.events, facilitate reactive scripting; an example is registering a callback on gdb.events.Stop to automatically inspect variables upon breakpoint hits, enhancing workflow automation.[88]
Custom commands can be defined by subclassing gdb.Command, providing a way to encapsulate logic for parsing intricate data structures directly in GDB's command-line interface. For example, a Python script might define a dump_struct command that takes a variable name as input, dereferences pointers in a custom binary tree structure, and prints node values with indentation to reflect hierarchy:
class DumpStruct (gdb.Command):
def __init__ (self):
super (DumpStruct, self).__init__ ("dump_struct", gdb.COMMAND_DATA)
def invoke (self, arg, from_tty):
# Parse arg as gdb.Value and traverse structure
val = gdb.parse_and_eval (arg)
# Custom logic to print tree nodes
print_tree (val)
DumpStruct ()
class DumpStruct (gdb.Command):
def __init__ (self):
super (DumpStruct, self).__init__ ("dump_struct", gdb.COMMAND_DATA)
def invoke (self, arg, from_tty):
# Parse arg as gdb.Value and traverse structure
val = gdb.parse_and_eval (arg)
# Custom logic to print tree nodes
print_tree (val)
DumpStruct ()
This approach integrates seamlessly with GDB, allowing invocation like dump_struct my_tree during a session.[88] Python scripts support auto-loading, where modules placed in GDB's data directory (e.g., data-directory/python) are automatically imported upon startup or when relevant object files are loaded, streamlining extension deployment.
GDB also supports Guile, an implementation of the Scheme programming language, for scripting since version 7.8, offering a functional paradigm for extensions that complements Python's imperative style. This requires configuration with --with-guile and Guile version 2.0 or later (including 3.0), with scripts installed in data-directory/guile for automatic path inclusion.[90] Users interact via the guile command to evaluate Scheme expressions or guile-repl for an interactive REPL, enabling concise functional scripts for tasks like data transformation during debugging.[91] For example, a Guile script could define a procedure to filter and display inferior process variables matching a pattern, leveraging Scheme's list-processing primitives for elegant extensions.
Starting with GDB 13, Python 2 support has been fully removed, mandating Python 3 and enhancing compatibility with modern versions like 3.8 and above for improved scripting performance and security features.[22] These updates include refined auto-loading mechanisms and API stability, ensuring robust integration for contemporary development environments.
Customization and Plugins
GDB supports customization through initialization files that allow users to configure the debugger's behavior upon startup. The primary user-specific file is .gdbinit, located in the user's home directory ($HOME/.gdbinit), which executes GDB commands automatically when the debugger starts. System-wide configurations are handled by /etc/gdbinit and the directory /etc/gdbinit.d/, where additional script files can be placed for global settings on Red Hat-based systems and similar distributions.[92] For security, GDB disables automatic loading of local .gdbinit files by default; users must enable it explicitly with set auto-load local-gdbinit on or configure safe paths using set auto-load safe-path to specify trusted directories for auto-loading scripts.[93]
GDB extends its functionality via plugins, primarily through auto-loading mechanisms that integrate external scripts when object files are read. One key method is the objfile-gdb.ext file, where objfile matches the name of the binary being debugged (e.g., program-gdb.py for a Python extension); GDB automatically loads this script if it exists in a safe path, enabling application-specific commands without manual intervention. Popular community plugins built on this system include GEF (GDB Enhanced Features), a Python-based extension that enhances the command-line interface with contextual information, memory visualization, and exploit development tools like heap analysis. Another example is Pwndbg, a Python module designed for exploit development and reverse engineering, which adds utilities for register visualization, disassembly enhancements, and compatibility with emulators like QEMU.[94]
Users can further customize GDB's appearance and interactions through themes, aliases, and Text User Interface (TUI) bindings defined in initialization files. Custom prompts can be set with commands like set prompt "$$\033[01;32m$$\u@\h$$\033[00m$$:\w\$ ", enabling colored or context-aware displays. Aliases simplify complex commands via define, such as define pq\nprintf "%#x", $pc\nend, for quick disassembly lookups. In TUI mode, key bindings can be remapped (e.g., using Ctrl+X A to toggle the interface), and styling options like set style tui-border normal control colors and borders for better readability.
Recent community extensions have incorporated AI for debugging assistance, leveraging plugins to provide intelligent hints. For instance, gdb-ollama integrates local large language models via Ollama into GDB, offering real-time insights on code states, variable analysis, and bug suggestions directly within the debugger session as of 2025.[95] Similarly, the GDB Debugger MCP Server enables AI-driven workflows for C++ debugging, allowing assistants to query and manipulate GDB states through natural language interfaces.[96] These tools build on Python as a base for plugin development, enhancing GDB's extensibility for modern, AI-augmented debugging.
Internals
Architectural Components
The GNU Debugger (GDB) employs a layered architecture that separates concerns into symbol handling, target interaction, and expression evaluation to facilitate debugging across diverse platforms and languages. At its core, GDB's symbol side manages debugging information from object files, interpreting formats to build symbol tables that map program elements like functions and variables to memory addresses. This layer includes the symtab-and-line mapping mechanism, which uses partial and full symbol tables along with line number tables to enable efficient source code correlation with execution points, supporting lookups from addresses to source lines and vice versa.[1]
The value and expression parser forms another foundational layer, processing user-specified expressions in supported languages such as C and C++ through a Yacc-generated grammar that constructs abstract syntax trees. These trees are evaluated to compute values from the inferior process's memory, assuming flexible typing to handle operations like dereferencing pointers or array slicing without strict type enforcement during parsing. Inferior management, handled via the target subsystem, oversees the debugged program's lifecycle using a stack of target vectors—over 40 implementations for hosts like Linux or embedded systems such as Xilinx MicroBlaze—allowing layered operations from high-level process control (e.g., starting execution) down to low-level register access.[1]
Key modules integrate with these layers to support file I/O and operations. The Binary File Descriptor (BFD) library provides a uniform interface for reading and writing object files in formats like ELF and COFF, enabling GDB to load executables and libraries regardless of the underlying binary structure, a capability introduced in 1990 for cross-development tools. GDB's event loop, restructured in 1999 to an event-driven model, processes asynchronous events such as signals from the inferior or responses from the Machine Interface (MI) through queues and the wait_for_inferior function, ensuring responsive handling without blocking the main thread.[97][1]
GDB's build system relies on Autotools, generating a configure script that detects host and target configurations to enable or disable features like threading support or specific target backends via options such as --enable-tui for terminal user interface. This setup produces Makefile fragments and customized headers, allowing compilation for over 20 host architectures and numerous targets while integrating dependencies like BFD from the Binutils suite.[1]
The GNU Debugger (GDB) primarily relies on the DWARF format for processing debugging information, supporting versions 2 through 5 as generated by modern compilers like GCC.[98] DWARF provides a structured, extensible representation of symbols, types, and source-level mappings embedded in executables and shared objects. Legacy support includes the STABS format, which encapsulates debugging data as symbol table strings within object files like a.out, COFF, or ELF, though it is largely superseded by DWARF due to its less efficient structure.[99] For Windows environments, GDB offers limited handling of Microsoft's PDB format through extensions or third-party integrations, enabling symbol resolution in cross-platform scenarios but without full native parity to DWARF.[100]
GDB manages symbol tables in a multi-tiered approach to balance startup speed and detail access for large programs. Partial symbol tables (psymtabs) are loaded first, containing minimal symbols such as global functions, variables, and source file names, which allow quick lookups without parsing the entire debug data.[101] Full symbol tables, with complete type information and local symbols, are expanded on demand from these partials when commands like breakpoints or variable inspections require them; this lazy loading is crucial for executables with millions of symbols.[102] For massive files, partial symtabs mitigate memory overhead by deferring full reads, with options like set symbol-loading off or -readnever to skip even partial loading if needed.[101]
Symbol handling begins with reading debug sections from executables and dynamic shared objects (DSOs) using the Binary File Descriptor (BFD) library to access diverse binary formats like ELF or PE.[101] GDB scans the debug info incrementally: for DWARF, it processes compilation units in .debug_info to build symtabs, while automatically adding symbols from loaded DSOs via add-symbol-file or during execution with set auto-solib-add on.[101] Line number tables, typically from DWARF's .debug_line section, map instruction addresses to source lines and files, enabling accurate stack traces and stepping; these are indexed during partial symtab creation for efficient queries.[101]
In GDB 14 and later, optimizations enhance DWARF 5 processing, including better support for compressed debug sections using zlib (for .zdebug_* variants) and improved indexing to reduce parsing time for large, compressed inputs.[99] These updates leverage DWARF 5's native compression features, such as skeleton units and address ranges, to minimize memory use while maintaining full symbol resolution.[103]
Practical Usage
Essential Commands
To use the GNU Debugger (GDB) effectively, programs must first be compiled with debugging information enabled. This is achieved using the GCC compiler with the -g flag, which embeds symbol tables and source line mappings into the executable without altering its functionality. The typical syntax is gcc -g source.c -o program, where source.c is the source file and program is the output executable name.[104]
Among the core commands for initiating and managing a debugging session, the file command specifies the executable to debug by loading its symbol table and memory layout. Its syntax is file filename, which searches the PATH environment variable if no directory is provided; omitting the argument discards prior file information. The set args command defines command-line arguments for the program, using the syntax set args arglist (or set args to clear them), which take effect on the next execution; show args displays the current settings. To start program execution, the run command is used with syntax run [arglist], creating a new process and passing arguments via the shell if available (default /bin/sh); it requires a prior file specification and stops at breakpoints or the program's end. Finally, the quit command exits GDB, with syntax quit [expression] (abbreviated q), where an optional expression sets the exit status; alternatively, exit [expression] or an end-of-file character (e.g., Ctrl-D) achieves the same.[101][105][43][106]
For navigation during debugging, the list command displays source code context, defaulting to 10 lines around the current position or main if none exists. Syntax includes list linenum to center on a line number, list function to show around a function's start, list first,last for a range, or list to continue from the previous display; set listsize count adjusts the line count (unlimited with unlimited), and show listsize views the setting. The disassemble command outputs machine instructions for a memory range, using syntax disassemble [start[,end]] (defaulting to the function at the program counter); modifiers like /m or /s mix source and disassembly, /r shows raw hex in instruction order, and addresses can be expressions (e.g., $pc - 8). To inspect hardware state, info registers lists register names and values in the current stack frame, with syntax info registers [regname ...] for specifics or info all-registers to include floating-point and vector registers; values are frame-relative, and <not saved> indicates unsaved caller registers.[107][108][109]
Error handling commands allow control over runtime events. The handle command configures GDB's response to signals, with syntax handle signal [keywords ...] where signal can be a name (e.g., SIGINT), number (1-15), range (e.g., 1-5), or all (excluding SIGINT and SIGTRAP); keywords include stop/nostop to pause/resume on signal, print/noprint for messaging, and pass/nopass to propagate/ignore to the program—erroneous signals default to stop, print, pass, while others default to nostop, noprint, pass. For catching specific events like exceptions, the catch command sets catchpoints to halt execution on occurrences such as C++ throw/catch, exec, fork, vfork, library loads/unloads, or Ada assertions, using syntax catch event (e.g., catch throw); supported events vary by language and target.[47][31]
Sample Debugging Session
To illustrate a practical debugging session with GDB, consider a simple C program that computes the sum of an array's elements but contains a bug in the loop condition, causing an array overflow and eventual segmentation fault when accessing invalid memory beyond the array bounds.
c
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
int i;
for (i = 0; i < 10; i++) { // Bug: loop limit should be 5, not 10
sum += arr[i];
}
printf("Sum: %d\n", sum);
return 0;
}
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
int i;
for (i = 0; i < 10; i++) { // Bug: loop limit should be 5, not 10
sum += arr[i];
}
printf("Sum: %d\n", sum);
return 0;
}
First, compile the program with debugging symbols enabled using GCC: gcc -g -o buggy buggy.c. This generates an executable named buggy with symbol information for GDB to use.
Launch GDB on the executable: gdb ./buggy. GDB starts and displays its version and prompt, such as (gdb). Set a breakpoint at the beginning of the loop for inspection: (gdb) break 8, where line 8 is sum += arr[i];. GDB confirms: Breakpoint 1 at 0x40113e: file buggy.c, line 8.
Run the program: (gdb) run. Execution halts at the breakpoint: Breakpoint 1, main () at buggy.c:8 followed by 8 sum += arr[i];. Step through iterations using (gdb) next or (gdb) step to observe the loop. After several steps, when i reaches 5, GDB reports: Program received signal SIGSEGV, [Segmentation fault](/page/Segmentation_fault). 0x0000555555555169 in main () at buggy.c:8 8 sum += arr[i];. This indicates the invalid memory access.
Inspect the state with (gdb) print i, yielding $1 = 5, and (gdb) print [sum](/page/Sum), yielding $2 = 15, and (gdb) print arr@5, showing the valid elements {1, 2, 3, 4, 5}. The backtrace (gdb) bt confirms the fault occurred in main, with output like #0 0x0000555555555169 in main () at buggy.c:8. To temporarily skip the faulty access and exit the loop, use (gdb) set i = 10 then (gdb) continue. This sets i beyond the loop condition, allowing the program to print the correct sum of 15 and exit. The session ends with (gdb) quit. This process reveals the off-by-five error in the loop bound (should be i < 5).
For postmortem analysis without rerunning the program, enable core dumps by setting ulimit -c unlimited in the shell, then execute ./buggy outside GDB, producing a core file upon segfault. Load it into GDB: gdb ./buggy core. GDB reads the dump and stops at the fault: Program terminated with signal SIGSEGV, Segmentation fault. Use the same inspection commands like bt and print i to diagnose the overflow at i=5.
In remote debugging scenarios, such as on an embedded target, start gdbserver :1234 buggy on the remote machine, then connect from the host: gdb ./buggy followed by (gdb) target remote target-ip:1234 and (gdb) run. The session proceeds similarly, halting at the segfault for inspection.