본문으로 건너뛰기
Previous
Next
C++ Debugging Basics — GDB & LLDB: Breakpoints, Watchpoints,

C++ Debugging Basics — GDB & LLDB: Breakpoints, Watchpoints,

C++ Debugging Basics — GDB & LLDB: Breakpoints, Watchpoints,

이 글의 핵심

C++ debugging basics: GDB and LLDB breakpoints and watchpoints to narrow bugs quickly—limits of printf debugging and problem scenarios.

Introduction: limits of printf debugging

“I added 100 couts and still can’t find the bug”

You were chasing a segfault. You added dozens of std::cout statements but still could not pinpoint the cause. Output was buffered, so you never saw the exact crash location, and recompiling and rerunning cost a lot of time. A debugger lets you stop and inspect which line you are on, variable values, and the stack at that moment. Learning breakpoints (where to stop), watchpoints (stop when a variable changes), conditional stops, and backtraces (call stack) alone lets you narrow bugs much faster than printf debugging.

Analogy: printf debugging is like taking photos one frame at a time and guessing what happened; a debugger is like freezing time and inspecting the full state directly.

Environment: GDB (Linux/WSL: apt install gdb) or LLDB (macOS: xcode-select --install). Always build with -g.

After reading this article you will be able to:

  • Use core GDB/LLDB commands in real debugging sessions.
  • Set breakpoints and watchpoints appropriately.
  • Inspect variables, memory, and the stack and trace root causes.
  • Apply production-oriented debugging patterns.

Practical note: This article is based on real issues and fixes from large C++ projects. It includes practical pitfalls and tips that books and manuals often skip.

Table of contents

  1. Problem scenarios
  2. Starting the debugger
  3. Breakpoints — full guide
  4. Tracing memory with watchpoints
  5. Stepping
  6. Inspecting variables and memory
  7. GDB/LLDB complete examples
  8. Common errors and fixes
  9. Debugging best practices
  10. Production debugging patterns

1. Problem scenarios

Scenario 1: “It segfaults but I don’t know where”

"The program dies with Segmentation fault. The log shows nothing."
"I have no idea which function is at fault."

Situation: Crashes from out-of-bounds indices, null pointer dereference, etc. You sprinkled printf everywhere but output was buffered or the last log before the crash never printed, so you could not narrow the location.

What to do: Run under GDB/LLDB → on crash use backtraceframe N to the right frame → print variables to find the cause.

Scenario 2: “Only the 500th iteration of 1000 fails”

"Usually it works; sometimes the result is wrong."
"Hard to reproduce, so printf is painful."

Situation: The bug only appears on a specific iteration or input. Printing all 1000 iterations is wasteful; conditional printf clutters the code.

What to do: Conditional breakpoint — e.g. break main.cpp:20 if i == 500 so you only stop when it matters.

Scenario 3: “A variable changes somewhere and I can’t find where”

"A variable I initialized later has a bogus value."
"I have no idea what overwrites it."

Situation: Memory corruption, buffer overruns, bad pointers change values unexpectedly. Hard to trace across hundreds of functions.

What to do: Watchpointwatch variable_name stops when the value changes.

Scenario 4: “I’m stuck in an infinite loop”

"The program looks hung. I hit Ctrl+C but don’t know why."

Situation: Wrong while condition, missing i++, etc. Too much printf output hides the signal.

What to do: Interrupt with Ctrl+C → backtraceprint loop variables.

Scenario 5: “Deadlock in a multi-threaded program”

"Two threads seem to wait for each other."
"I don’t know which thread holds which mutex."

Situation: Inconsistent lock ordering causes deadlock; may not reproduce on a single thread.

What to do: Ctrl+C → thread apply all backtrace to see every thread’s stack.

Scenario 6: “It only crashes in production sometimes”

"Never on my dev machine; occasionally dies on the server."
"No core dump, so I can’t analyze."

Situation: Load- or data-dependent bugs; hard to reproduce; attaching a debugger in production is often impractical.

What to do: Enable core dumps → collect core on crash → analyze with GDB on the core file.

Choosing a tool by scenario

flowchart TD
    A[Bug] --> B{Type?}
    B -->|Crash| C[GDB/LLDB run → backtrace]
    B -->|Conditional| D[Conditional breakpoint]
    B -->|Variable corruption| E[Watchpoint]
    B -->|Infinite loop| F[Ctrl+C → backtrace]
    B -->|Deadlock| G[thread apply all bt]
    B -->|Production| H[Core dump analysis]

2. Starting the debugger

Debug build (required)

-g embeds debug symbols (source lines, names). -O0 disables optimization so variables are less often “optimized out” and line mapping stays faithful.

# Debug info (-g), no optimization (-O0)
g++ -g -O0 main.cpp -o myapp
# With CMake
cmake -DCMAKE_BUILD_TYPE=Debug ..
make

Starting GDB

# Load the program
gdb ./myapp
# Run
(gdb) run
# Run with arguments
(gdb) run arg1 arg2
# Set env then run
(gdb) set env LD_LIBRARY_PATH=/path/to/libs
(gdb) run
# Quit
(gdb) quit

Note: PIE binaries may load at different addresses; only use ASLR-disabling options in dedicated dev setups for security reasons.

Starting LLDB (macOS)

# Load
lldb ./myapp
# Run
(lldb) run
# With arguments
(lldb) run arg1 arg2
# Quit
(lldb) quit

Debugging workflow

sequenceDiagram
    participant Dev as Developer
    participant GDB as GDB/LLDB
    participant App as Target program
    Dev->>GDB: gdb ./myapp
    GDB->>App: Load (debug symbols)
    Dev->>GDB: break main
    Dev->>GDB: run
    GDB->>App: Start execution
    App->>GDB: Reach main() → stop
    GDB->>Dev: Return prompt
    Dev->>GDB: next / step / print
    GDB->>Dev: Print results
    Dev->>GDB: continue
    GDB->>App: Run to next breakpoint

3. Breakpoints — full guide

What is a breakpoint?

A place where execution stops so you can inspect variables, stack, and memory.

How software breakpoints work (conceptually):

Software breakpoint:

1. Debugger sets a breakpoint:

   (gdb) break main.cpp:20

   Debugger steps:
   a. Map source file:line → machine address
      Debug symbol lookup:
      main.cpp:20 → 0x400540

   b. Save the original instruction at that address:
      0x400540: mov rax, [rbp-8]  # original
      → backup: saved_instruction[0x400540] = 0x48 8b 45 f8

   c. Replace with int3 (1 byte):
      0x400540: int3  # 0xCC
      → write 0xCC to memory

2. Program runs:

   CPU hits 0x400540:
   → executes int3
   → SIGTRAP
   → kernel notifies the debugger

3. Debugger handles SIGTRAP:

   a. Stop the program (pause PTRACE_CONT)
   b. Restore original instruction:
      0x400540: mov rax, [rbp-8]
   c. Rewind PC by 1 byte:
      RIP = 0x400540 (before int3)
   d. Return to user prompt:
      (gdb)

   User types continue:
   → run original instruction once
   → reinstall int3 (reusable)
   → PTRACE_CONT to continue

ptrace usage:

Program start:
fork() → child process

child: ptrace(PTRACE_TRACEME, 0, NULL, NULL)
      → "trace me"
      exec(myapp)

parent (GDB): ptrace(PTRACE_CONT, child_pid, ...)
          → controls child execution

Common ptrace ops:
- PTRACE_TRACEME: child asks to be traced
- PTRACE_PEEKTEXT: read memory (print)
- PTRACE_POKETEXT: write memory (insert int3)
- PTRACE_GETREGS: read registers
- PTRACE_SETREGS: write registers
- PTRACE_CONT: continue
- PTRACE_SINGLESTEP: single instruction (step)

Hardware breakpoint:

Uses CPU debug registers (x86: DR0–DR7):

(gdb) hbreak main.cpp:20

DR0 = 0x400540  # breakpoint address
DR7 = 0x00000001  # enable DR0

CPU accesses 0x400540:
→ hardware detects
→ Debug Exception
→ notify debugger

Pros:
- No code patch (no int3)
- Works in ROM / execute-only memory
- Limited to 4 (DR0–DR3)

Conditional breakpoint implementation:

(gdb) break main.cpp:20 if i == 500

Internally:
1. Unconditional breakpoint (int3)
2. On SIGTRAP:
   a. Evaluate condition:
      read i (PTRACE_PEEKDATA)
      i == 500? → false
   b. If false:
      - execute original instruction
      - reinstall int3
      - PTRACE_CONT
   c. If true:
      - return to user prompt

→ condition checked many times (overhead)
→ slow if usually false

Performance comparison:

Software BP:
- Unlimited count
- Patches code (int3)
- Slightly slower (SIGTRAP handling)

Hardware BP:
- Max 4
- No code patch
- Fast (CPU)

Conditional BP:
- Repeated condition eval
- Can be very slow

Concrete example:

Original code:
int main() {
    int x = 10;  // 0x400540
    int y = 20;  // 0x400545
    return 0;
}

Without debugger:
0x400540: mov dword ptr [rbp-4], 10
0x400545: mov dword ptr [rbp-8], 20
0x40054c: xor eax, eax
0x40054e: ret

After break main.cpp:2:
0x400540: int3  # 0xCC (was C7 45 FC 0A)
0x400545: mov dword ptr [rbp-8], 20
0x40054c: xor eax, eax
0x40054e: ret

Execution:
1. CPU hits 0x400540
2. int3 → SIGTRAP
3. kernel → GDB
4. GDB: stop
5. User: (gdb) prompt
6. continue
7. GDB: restore & run original
8. reinstall int3
9. continue

GDB breakpoint commands

# Break on function
(gdb) break main
(gdb) break processData
(gdb) b main  # short
# Break on file:line
(gdb) break main.cpp:15
(gdb) b main.cpp:15
# Conditional (stop only when i == 50)
(gdb) break main.cpp:20 if i == 50
# Only when pointer is null
(gdb) break main.cpp:25 if ptr == nullptr
# List breakpoints
(gdb) info breakpoints
(gdb) i b
# Delete
(gdb) delete 1        # by number
(gdb) delete          # all
(gdb) clear main.cpp:15  # by location
# Disable / enable
(gdb) disable 1
(gdb) enable 1
# Ignore first N hits
(gdb) ignore 1 99    # ignore breakpoint 1 ninety-nine times, then stop

LLDB breakpoint commands

# Function
(lldb) breakpoint set --name main
(lldb) b main  # short
# File:line
(lldb) breakpoint set --file main.cpp --line 15
(lldb) b main.cpp:15
# Conditional
(lldb) breakpoint set --name processData --condition 'i == 50'
(lldb) breakpoint set -f main.cpp -l 20 -c 'ptr == nullptr'
# List
(lldb) breakpoint list
(lldb) br list
# Delete
(lldb) breakpoint delete 1
(lldb) breakpoint delete  # all
# Disable / enable
(lldb) breakpoint disable 1
(lldb) breakpoint enable 1

Conditional breakpoint example

// conditional_bug.cpp — bug only on iteration 500 of 1000
for (int i = 0; i < 1000; ++i) {
    process(i);  // wrong behavior only when i == 500
}
# GDB: stop at main.cpp line 10 only when i == 500
(gdb) break main.cpp:10 if i == 500
(gdb) run
# LLDB
(lldb) breakpoint set -f main.cpp -l 10 -c 'i == 500'
(lldb) run

4. Tracing memory with watchpoints

What is a watchpoint?

Stops execution when a variable or memory address changes. Use it to answer “where does this value get overwritten?”

GDB watchpoints

# Stop on write
(gdb) watch variable_name
# Value pointed to by ptr changes
(gdb) watch *ptr
# Stop on read (rwatch)
(gdb) rwatch variable_name
# Stop on read or write (awatch)
(gdb) awatch variable_name
# Often: break main, run, then watch when in scope
(gdb) break main
(gdb) run
(gdb) watch arr[5]   # arr[5] in scope now
(gdb) continue

LLDB watchpoints

# Stop on write
(lldb) watchpoint set variable variable_name
(lldb) w s v variable_name  # short
# Expression (pointer deref)
(lldb) watchpoint set expression -- ptr
(lldb) watchpoint set expression -w write -- *(int*)0x7fff1234
# List
(lldb) watchpoint list
# Delete
(lldb) watchpoint delete 1

Watchpoint example: finding memory corruption

// memory_corruption.cpp
#include <iostream>
int global_counter = 0;
void suspiciousFunction() {
    int buffer[10] = {0};
    // Buffer overrun: writing buffer[10] may overwrite global_counter
    for (int i = 0; i <= 10; ++i) {
        buffer[i] = i;  // i==10 is out of range!
    }
}
int main() {
    std::cout << "Before: " << global_counter << "\n";
    suspiciousFunction();
    std::cout << "After: " << global_counter << "\n";  // unexpected value
    return 0;
}
# Find where global_counter changes with GDB
$ g++ -g -O0 -o corrupt memory_corruption.cpp
$ gdb ./corrupt
(gdb) break main
(gdb) run
(gdb) watch global_counter
(gdb) continue
# Stops when global_counter changes
Hardware watchpoint 2: global_counter
Old value = 0
New value = 10
suspiciousFunction () at memory_corruption.cpp:10

Everyday analogy: Think of memory as an apartment building. The stack is like an elevator—fast but limited. The heap is like a warehouse—roomy but slower to navigate. A pointer is the note that says “3rd floor, unit 302.”

5. Stepping

next vs step

ActionGDBLLDBBehavior
Next line (step over calls)next / nnext / nRun current line, go to next; do not enter callee
Step intostep / sstep / sEnter function when current line is a call
Until returnfinishfinishRun until current function returns
Continuecontinue / ccontinue / cRun to next breakpoint

GDB execution control

# Next line (don’t enter functions)
(gdb) next
(gdb) n
# Step into
(gdb) step
(gdb) s
# Run until current function returns
(gdb) finish
# Continue to next breakpoint
(gdb) continue
(gdb) c
# Next/step N times
(gdb) next 5
(gdb) step 3
# Show source around PC
(gdb) list
(gdb) l

LLDB execution control

(lldb) next
(lldb) n
(lldb) step
(lldb) s
(lldb) finish
(lldb) continue
(lldb) c
(lldb) list
(lldb) l

Stepping flow

flowchart TD
    A[Current location] --> B{next vs step?}
    B -->|next| C[Next source line]
    B -->|step| D{Function call?}
    D -->|Yes| E[Enter function]
    D -->|No| C
    C --> F[Wait for next command]
    E --> F
    F --> G{finish?}
    G -->|Yes| H[Run until current function returns]
    G -->|No| A
    H --> F

6. Inspecting variables and memory

Printing variables

# GDB
(gdb) print x
(gdb) p x
# Dereference pointer
(gdb) print *ptr
# Ten array elements
(gdb) print arr[0]@10
# Struct
(gdb) print person
(gdb) print person.name
# STL vector (GDB 7.0+)
(gdb) print vec
(gdb) print vec.size()
# LLDB
(lldb) frame variable
(lldb) fr v
(lldb) frame variable x
(lldb) p x
(lldb) expression ptr->member
(lldb) expr vec.size()

Examining memory (x in GDB)

# GDB: examine memory
# Form: x/[count][format][size] address
(gdb) x/10x ptr   # 10 words in hex (4 bytes each by default)
(gdb) x/10d ptr   # 10 in decimal
(gdb) x/10s ptr   # strings
(gdb) x/20xb ptr  # 20 bytes, hex
# Formats: x hex, d decimal, s string, i instruction
# Sizes: b 1, h 2, w 4, g 8 bytes

Stack trace (backtrace)

# GDB
(gdb) backtrace
(gdb) bt
# Locals in all frames
(gdb) backtrace full
(gdb) bt full
# Select frame
(gdb) frame 2
(gdb) f 2
# Current frame info
(gdb) info frame
# Locals in current frame
(gdb) info locals
# Arguments
(gdb) info args
# LLDB
(lldb) thread backtrace
(lldb) bt
(lldb) frame select 2
(lldb) f 2
(lldb) frame variable

Stack frame picture

flowchart TB
    subgraph stack["Call stack (bottom → top)"]
        F0["main() — frame 2"]
        F1["processData() — frame 1"]
        F2["buggyFunction() — frame 0 (current)"]
    end
    F0 --> F1
    F1 --> F2
    subgraph vars["Frame 0 locals"]
        V1["i = 10"]
        V2["size = 10"]
        V3["arr = 0x7fff..."]
    end

7. GDB/LLDB complete examples

Example 1: Array out of bounds (segfault)

// buggy_array.cpp — g++ -g -O0 -o buggy buggy_array.cpp
#include <iostream>
void buggyFunction(int* arr, int size) {
    for (int i = 0; i <= size; ++i) {  // ❌ <= bug (i==size is OOB)
        arr[i] = i * 2;
    }
}
int main() {
    int arr[10];
    buggyFunction(arr, 10);  // crash!
    std::cout << "done\n";
    return 0;
}

GDB session:

$ g++ -g -O0 -o buggy buggy_array.cpp
$ gdb ./buggy
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
(gdb) backtrace
#0  buggyFunction (arr=0x7fffffffe2a0, size=10) at buggy_array.cpp:5
#1  main () at buggy_array.cpp:12
(gdb) frame 0
(gdb) print i
$1 = 10   # ← bug: valid indices are 0–9
(gdb) print size
$2 = 10
(gdb) print arr[10]
Cannot access memory at address 0x...

Fix:

// ✅ use i < size
for (int i = 0; i < size; ++i) {
    arr[i] = i * 2;
}

Example 2: Null pointer dereference

// null_ptr.cpp
struct Node { int value; Node* next; };
int sumList(Node* head) {
    int sum = 0;
    while (head != nullptr) {
        sum += head->value;  // crashes if head is null
        head = head->next;
    }
    return sum;
}
(gdb) break sumList
(gdb) run
(gdb) next
(gdb) print head
$1 = (Node *) 0x0   # null pointer!
(gdb) backtrace full

Example 3: Infinite loop

// infinite_loop.cpp
void processData() {
    int i = 0;
    while (i < 100) {
        process(i);
        // missing i++!
    }
}
# Interrupt with Ctrl+C
(gdb) run
^C
Program received signal SIGINT, Interrupt.
(gdb) backtrace
#0  processData () at main.cpp:5
(gdb) print i
$1 = 0   # never changes → find missing i++

Example 4: Conditional break (500th of 1000)

(gdb) break main.cpp:10 if i == 500
(gdb) run
# After stop
(gdb) print i
$1 = 500
(gdb) step
(gdb) backtrace

Example 5: Watchpoint for corruption

(gdb) break main
(gdb) run
(gdb) watch arr[5]
(gdb) continue
# When arr[5] changes
Hardware watchpoint 2: arr[5]
Old value = 0
New value = 999
someFunction () at memory_corruption.cpp:15

Example 6: STL vector out of range

// vector_bug.cpp
std::vector<int> vec = {1, 2, 3};
for (size_t i = 0; i <= vec.size(); ++i) {  // ❌ <= bug
    std::cout << vec[i] << "\n";
}
(gdb) run
Program received signal SIGSEGV
(gdb) print i
$1 = 3
(gdb) print vec.size()
$2 = 3
# vec[3] is out of range

Example 7: Multi-threaded deadlock

// deadlock.cpp
std::mutex m1, m2;
void thread1() { m1.lock(); /* ... */ m2.lock(); /* ... */ }
void thread2() { m2.lock(); /* ... */ m1.lock(); /* ... */ }
# After Ctrl+C
(gdb) thread apply all backtrace
Thread 1:
#0  __lll_lock_wait ()
#1  thread1 () at deadlock.cpp:10
Thread 2:
#0  __lll_lock_wait ()
#1  thread2 () at deadlock.cpp:20
# Each thread waits on the other’s mutex → deadlock

Example 8: Recursive stack overflow

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
int main() {
    int result = factorial(-5);  // ❌ negative → non-terminating recursion
    return 0;
}
(gdb) run
Program received signal SIGSEGV
(gdb) backtrace
#0  factorial (n=-1048576) at stack_overflow.cpp:3
#1  factorial (n=-1048575) at stack_overflow.cpp:4
...
#1048575  factorial (n=-5) at stack_overflow.cpp:4
#1048576  main () at stack_overflow.cpp:8
# Negative n never satisfies termination → stack overflow

8. Common errors and fixes

“No symbol table” / “No debugging symbols found”

Cause: Built without -g.

Fix:

g++ -g -O0 main.cpp -o myapp
file myapp
# Look for "not stripped" or debug_info

“Cannot access memory at address 0x0”

Cause: Null pointer dereference.

Fix:

(gdb) backtrace
(gdb) frame 0
(gdb) info args
(gdb) print ptr   # check for 0x0

“optimized out”

Cause: -O2/-O3 removed or register-allocated the variable.

Fix:

g++ -g -O0 main.cpp -o myapp
# Or volatile for one variable (last resort)
volatile int debug_var = value;

“Program received signal SIGSEGV” — unclear cause

Fix:

(gdb) backtrace full
(gdb) break [crashing function]
(gdb) run
(gdb) next   # step and watch variables

GDB exits immediately after run

Cause: Normal exit, or crash in a child process.

Fix:

(gdb) set follow-fork-mode child
(gdb) break fork
(gdb) run
(gdb) continue

LLDB “variable not found”

Cause: Optimization or out of scope.

Fix:

(lldb) frame variable
(lldb) expr *(int*)0x7fff...

Hardware watchpoint limit

Cause: x86 often allows only four hardware watchpoints.

Fix: GDB may fall back to software watchpoints (slower, unlimited). Delete unused watchpoints if needed.

(gdb) delete 2

9. Debugging best practices

1. Always -g -O0 for debug builds

g++ -g -O0 main.cpp -o myapp

With optimization on, variables disappear, line numbers drift, and stepping misleads.

2. On crash, backtrace first

(gdb) run
# After SIGSEGV
(gdb) backtrace full
(gdb) info locals
(gdb) info args

3. Save time with conditional breakpoints

(gdb) break main.cpp:20 if i == 500

4. Watchpoints for mystery mutations

(gdb) watch suspicious_var
(gdb) continue

5. Use .gdbinit

# ~/.gdbinit or project/.gdbinit
set pagination off
set print pretty on
set print array on

6. Log the session

(gdb) set logging file debug.log
(gdb) set logging on
(gdb) run
# ... debug ...
(gdb) set logging off

Checklist

- [ ] Built with -g -O0?
- [ ] backtrace to locate crash
- [ ] frame N to select frame
- [ ] info locals, info args
- [ ] Conditional breakpoint for rare cases
- [ ] Watchpoint for memory changes
- [ ] thread apply all bt for multi-threaded issues

10. Production debugging patterns

Analyzing core dumps

Enable core dumps so crashes leave a file you can open later.

# Linux
ulimit -c unlimited
echo /tmp/core.%e.%p | sudo tee /proc/sys/kernel/core_pattern
# After crash
$ gdb ./myapp /tmp/core.myapp.12345
(gdb) backtrace
(gdb) backtrace full

Remote debugging (gdbserver)

# Target machine
gdbserver :1234 ./myapp
# Dev machine
gdb ./myapp
(gdb) target remote 192.168.1.100:1234
(gdb) continue

Split debug symbols

Keep debug info in a separate file for release binaries.

objcopy --only-keep-debug myapp myapp.debug
strip -g myapp
objcopy --add-gnu-debuglink=myapp.debug myapp
# Later
gdb -s myapp.debug -e myapp -c core.12345

Production flow

flowchart TD
    A[Production crash] --> B{Have matching debug build?}
    B -->|Yes| C[Collect core dump]
    B -->|No| D[Reproduce elsewhere]
    C --> E[gdbserver or local GDB]
    D --> E
    E --> F[Analyze backtrace]
    F --> G[Find root cause]
    G --> H[Fix and deploy]

Notes:

  • The binary that produced the core must match the symbols you load.
  • -O2 builds may show wrong or missing locals → RelWithDebInfo is a good compromise.
  • gdbserver pauses the process—use during low traffic.

Practical workflow

  1. Reproduce with minimal input.
  2. Isolate with breakpoints.
  3. Hypothesize from variable values.
  4. Verify after the fix.
  5. Document cause and resolution.

Command reference

GDB cheat sheet

run, r                    # run
run arg1 arg2             # with args
kill                      # kill inferior
break, b                  # breakpoint
info breakpoints          # list
delete 1                  # delete
next, n                   # next line
step, s                   # step in
finish                    # finish function
continue, c               # continue
print, p                  # print
ptype                     # type
backtrace, bt             # stack
info locals
info args
watch var
list, l                   # source
quit, q

GDB vs LLDB

FeatureGDBLLDB
Runrunrun
Breakbreak mainbreakpoint set -n main
Conditionalbreak f.c:10 if i==5breakpoint set -f f.c -l 10 -c 'i==5'
Nextnextnext
Stepstepstep
Printprint xframe variable x
Stackbacktracebt
Watchwatch varwatchpoint set variable var
Quitquitquit

Summary

ToolPlatformNotes
GDBLinuxGNU debugger; gdbserver for remote
LLDBMac/LinuxLLVM debugger; fast
Visual StudioWindowsGUI debugger

Principles:

  1. Prefer a debugger over printf.
  2. Use breakpoints and conditional breakpoints.
  3. Use watchpoints for memory issues.
  4. Use the stack trace to locate cause.
  5. In production: core dumps + gdbserver.

Next: [C++ In Practice #16-2] Sanitizers: tools that find memory bugs automatically


FAQ

Q. When do I use this in practice?

A. Whenever printf is too slow or misleading: GDB/LLDB breakpoints, watchpoints, variable inspection, and stack traces to find bugs quickly—from scenarios above through production patterns. Use the examples and guides in this article.

Q. What should I read first?

A. Follow previous post links at the bottom of each article. See the C++ series index for the full sequence.

Q. Where can I go deeper?

A. cppreference and official library docs. Use the reference links at the end of articles as well.

Previous: [C++ In Practice #15-3] Compile-time optimization


Keywords

C++, debugging, GDB, LLDB, breakpoint, watchpoint, debugger, bugfix, segfault — searches like these should surface this article.


See also

  • C++ GDB/LLDB — find in minutes what 100 couts could not
  • C++ logging and assertions
  • C++ GDB basics — breakpoints and watchpoints
  • C++ Sanitizers
  • C++ LLDB basics — macOS and breakpoints

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ GDB/LLDB | cout 100개 찍어도 못 찾은 버그, 디버거로 5분 만에 해결
  • C++ GDB 기초 완벽 가이드 | 브레이크포인트·워치포인트
  • C++ LLDB 기초 완벽 가이드 | macOS·브레이크포인트

이 글에서 다루는 키워드 (관련 검색어)

C++, Debugging, GDB, LLDB, Breakpoint, Watchpoint, Debugger 등으로 검색하시면 이 글이 도움이 됩니다.