Debugging (GDB) — Breakpoints, Backtraces, Memory Inspection, Conditional Breakpoints, Core Dumps
In this tutorial, you will learn about Debugging (GDB). We cover key concepts, practical examples, and best practices to help you master this topic.
GDB (GNU Debugger) enables interactive debugging of C++ programs — setting breakpoints, stepping through code, inspecting variables and memory, and analyzing core dumps to diagnose crashes.
What You'll Learn
You will compile C++ programs with debugging symbols (-g), set breakpoints on functions, lines, and conditions, step through code with next, step, continue, and finish, inspect variables, registers, and memory, use backtraces to find crash locations, analyze core dumps for post-mortem debugging, and use GDB's TUI and reverse debugging features.
Why It Matters
Debugging is where developers spend most of their time. Printf debugging is slow and limited — GDB lets you inspect any variable at any point without recompiling. For complex bugs (memory corruption, race conditions, crashes), GDB is essential. Every professional C++ developer should know the basic GDB workflow.
Learning Path
graph LR
A["66: Unit Testing"] --> B["67: Debugging (GDB)"]
B --> C["68: Performance Profiling"]
C --> D["69: Best Practices"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Compiling for Debugging
Debug symbols must be enabled during compilation.
# Debug build with symbols
g++ -g -O0 -o program main.cpp calculator.cpp
# With address sanitizer (catches memory errors)
g++ -g -O0 -fsanitize=address -o program main.cpp
# With undefined behavior sanitizer
g++ -g -O0 -fsanitize=undefined -o program main.cpp
# Debug + no optimizations + all warnings
g++ -g -O0 -Wall -Wextra -o program main.cpp
// buggy.cpp — program with bugs for debugging
#include <iostream>
#include <vector>
#include <cstring>
std::vector<int> createData() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v;
}
int computeSum(const std::vector<int>& data) {
int sum = 0;
for (size_t i = 0; i <= data.size(); ++i) { // Bug: off-by-one
sum += data[i];
}
return sum;
}
void bufferOverflow() {
char buffer[8];
strcpy(buffer, "This string is too long for the buffer!");
std::cout << buffer << "\n";
}
int main() {
auto data = createData();
int result = computeSum(data);
std::cout << "Sum: " << result << "\n";
// bufferOverflow(); // Uncomment for crash
return 0;
}
GDB Session Walkthrough
# Start GDB
gdb ./program
# Run without arguments
(gdb) run
# Run with arguments
(gdb) run arg1 arg2
# Run with input redirection
(gdb) run < input.txt
Setting Breakpoints
# Break on function
(gdb) break main
(gdb) break computeSum
# Break on specific line
(gdb) break buggy.cpp:15
# Conditional breakpoint (trigger when condition is true)
(gdb) break buggy.cpp:16 if i > 3
# Break at memory address
(gdb) break *0x4005c0
# Break on C++ method (quoted)
(gdb) break 'std::vector<int>::push_back(int)'
# List all breakpoints
(gdb) info breakpoints
# Delete breakpoint
(gdb) delete 1
# Disable/enable
(gdb) disable 2
(gdb) enable 2
Stepping Through Code
# Run to next breakpoint
(gdb) continue
# Step into function
(gdb) step
# Step over function (don't enter)
(gdb) next
# Step one assembly instruction
(gdb) stepi
# Finish current function (return to caller)
(gdb) finish
# Continue until current line
(gdb) until
# Continue to specific line
(gdb) advance buggy.cpp:20
Inspecting State
# Print variable value
(gdb) print sum
(gdb) print data
(gdb) print data.size()
# Print with format
(gdb) print/x 42 # hexadecimal
(gdb) print/t 42 # binary
(gdb) print/c 65 # character
(gdb) print/f 3.14 # float
# Print all local variables
(gdb) info locals
# Print function arguments
(gdb) info args
# Print registers
(gdb) info registers
# Print stack trace
(gdb) backtrace
(gdb) bt full # with local variables
(gdb) bt 5 # first 5 frames
# Switch to frame
(gdb) frame 2
(gdb) up # up one frame
(gdb) down # down one frame
Memory Inspection
# Examine memory at address
(gdb) x buffer # default format
(gdb) x/10xb buffer # 10 hex bytes
(gdb) x/10xw buffer # 10 hex words (4-byte)
(gdb) x/s buffer # as string
(gdb) x/i $rip # instruction at instruction pointer
(gdb) x/10i main # disassemble first 10 instructions
# Examine pointer
(gdb) print ptr
(gdb) print *ptr
(gdb) x/20xb ptr # memory dump
# Find what value is at an address
(gdb) info symbol 0x7fffffffe000
Analyzing Crashes (Segfaults)
# Run until crash, GDB shows backtrace automatically
(gdb) run
# Program received signal SIGSEGV, Segmentation fault.
# 0x00005555555552a4 in computeSum (data=...) at buggy.cpp:16
# 16 sum += data[i];
(gdb) backtrace
# #0 0x... in computeSum (data=...) at buggy.cpp:16
# #1 0x... in main () at buggy.cpp:27
(gdb) print i
# $1 = 5
(gdb) print data.size()
# $2 = 5
# Bug: reading data[5] which is out of bounds!
(gdb) frame 1 # Go to main
(gdb) print data
# Shows main's data variable
Post-Mortem Debugging with Core Dumps
# Enable core dumps
ulimit -c unlimited
# Run the program (crashes, creates core dump)
./program
# Analyze with GDB
gdb ./program core
# GDB shows where it crashed automatically
(gdb) bt
(gdb) print variable
(gdb) list
Watchpoints (Data Breakpoints)
# Break when variable changes
(gdb) watch sum
# Break when variable is read
(gdb) rwatch sum
# Break on read or write
(gdb) awatch sum
# Conditional watchpoint
(gdb) watch sum if i > 5
Reverse Debugging (GDB Record)
# Start recording execution
(gdb) record
# Run to find bug
(gdb) continue
# Now you can reverse-step
(gdb) reverse-step
(gdb) reverse-next
(gdb) reverse-continue
# Go back to see where a variable was set
(gdb) reverse-step
(gdb) print variable
GDB TUI (Text User Interface)
# Start with TUI
gdb -tui ./program
# Or toggle during session
(gdb) tui enable
# Keyboard shortcuts in TUI:
# Ctrl+X A — toggle TUI
# Ctrl+P/N — prev/next command
# Ctrl+X O — switch focus between windows
GDB with C++ Specifics
# Call functions during debugging
(gdb) call strlen(buffer)
(gdb) call std::vector<int>(5, 10)
# Print STL containers (with pretty-printing)
(gdb) print my_vector
(gdb) print my_map
# Set variables
(gdb) set var sum = 0
(gdb) set var i = 0
# Demangle C++ names
(gdb) set print demangle on
(gdb) info functions std::vector
GDB Scripting
# save_commands.gdb — GDB script
break main
break computeSum
run
print "Starting debugging session"
print data
continue
print sum
quit
# Run with script:
# gdb -x save_commands.gdb ./program
Common Mistakes
Mistake 1: Debugging optimized code
-O2 optimizes variables into registers, making them invisible. Always debug with -O0.
Mistake 2: Missing -g flag
Without debug symbols, GDB shows raw assembly and memory addresses, not source code and variable names.
Mistake 3: Forgetting to compile with address sanitizer
g++ -g -O0 -fsanitize=address -fno-omit-frame-pointer program.cpp
ASAN catches out-of-bounds, use-after-free, and memory leaks.
Mistake 4: Using step instead of next when you don't want to enter functions
step enters every function call. Use next to stay at the same level.
Mistake 5: Not using conditional breakpoints for loops
break buggy.cpp:16 if i == 1000000 # Much faster than hitting break 1M times
Practice Questions
What is the GDB command to set a breakpoint on line 42 of main.cpp? Answer:
break main.cpp:42What is the difference between
nextandstepin GDB? Answer:nextexecutes the current line without stepping into function calls.stepenters functions.How do you inspect all local variables in the current function? Answer:
info locals(orbt fullfor locals in all frames).What compile flags are needed for debugging? Answer:
-g(debug symbols) and-O0(no optimizations). Add-fsanitize=addressfor memory error detection.How do you analyze a core dump after a crash? Answer:
ulimit -c unlimited(enable), run the program, thengdb ./program coreand usebtfor backtrace.
FAQ
Mini Project
Given this buggy program, debug it with GDB and identify all bugs:
// debug_me.cpp
#include <iostream>
#include <vector>
#include <cstring>
std::vector<int> extractPositive(const std::vector<int>& input) {
std::vector<int> result;
for (size_t i = 0; i <= input.size(); ++i) { // Bug 1
if (input[i] > 0) { // Bug 2
result.push_back(input[i]);
}
}
return result;
}
int* createArray() {
int arr[5] = {10, 20, 30, 40, 50}; // Bug 3
return arr; // Returns pointer to local array!
}
int main() {
std::vector<int> data = {-1, 2, -3, 4, -5, 6};
auto positive = extractPositive(data);
std::cout << "Positive count: " << positive.size() << "\n";
int* arr = createArray();
std::cout << "First: " << arr[0] << "\n"; // Undefined behavior
return 0;
}
Bugs to find with GDB:
- Off-by-one in loop condition
- Out-of-bounds vector access
- Returning pointer to stack-allocated array
Use GDB breakpoints, watchpoints, and memory inspection to identify each issue. This exercise mirrors real C++ debugging scenarios that every developer encounters.
What's Next
You now debug C++ programs with GDB. Next, you will learn performance profiling — identifying bottlenecks, measuring cache misses, and optimizing C++ code with profilers like perf, Valgrind, and Intel VTune.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro