C++ GDB 기초 완벽 가이드 | 브레이크포인트·워치포인트
이 글의 핵심
printf 디버깅의 한계를 넘어서. GDB 브레이크포인트, 워치포인트, backtrace, print, step/next/continue 완전 예제, 흔한 에러, 모범 사례, 프로덕션 패턴까지 실전 코드로 다룹니다.
들어가며: printf 디버깅의 한계
”cout 100개를 찍어도 버그를 못 찾겠어요”
세그폴트가 발생하는 버그를 찾고 있었습니다. std::cout을 수십 개 추가했지만 출력이 버퍼링되어 정확한 크래시 위치를 알 수 없었습니다. GDB(GNU Debugger)는 “어느 줄에서 멈췄는지, 그때 변수 값과 호출 스택이 어떤지”를 멈춘 상태에서 직접 볼 수 있게 해 줍니다. 브레이크포인트(실행을 멈출 지점)·워치포인트(변수 변경 감지)·백트레이스(호출 스택)·print(변수 출력)·step/next/continue(단계별 실행)만 익혀도 printf 디버깅보다 훨씬 빠르게 버그 위치를 좁힐 수 있습니다.
요구 환경: GDB(Linux/WSL: apt install gdb 또는 sudo yum install gdb). 디버그 정보가 있어야 하므로 빌드 시 -g 옵션 사용(g++ -g, CMake에서는 Debug 구성).
이 글을 읽으면:
- GDB의 핵심 명령어(breakpoint, watchpoint, backtrace, print, step/next/continue)를 완전히 익힐 수 있습니다.
- 문제 시나리오별로 GDB를 활용하는 방법을 알 수 있습니다.
- 흔한 에러와 해결법을 파악할 수 있습니다.
- 프로덕션 환경에서의 디버깅 패턴을 적용할 수 있습니다.
목차
- 문제 시나리오
- 디버그 빌드와 GDB 시작
- 브레이크포인트 완전 예제
- 워치포인트 완전 예제
- 백트레이스(backtrace) 완전 예제
- print와 변수 검사 완전 예제
- step/next/continue 완전 예제
- 통합 실습: 버그 찾기 end-to-end
- 자주 발생하는 에러와 해결법
- 모범 사례
- 프로덕션 패턴
1. 문제 시나리오
시나리오 1: 세그폴트—배열 범위 초과
상황: 10만 개 원소를 처리하는 루프에서 가끔 크래시가 납니다. cout으로 i를 찍어봤지만 출력이 버퍼링되어 정확한 i 값을 알 수 없습니다.
// buggy_array.cpp
#include <iostream>
void processData(int* arr, int size) {
for (int i = 0; i <= size; ++i) { // ❌ <= 버그 (i==size일 때 범위 초과)
arr[i] = i * 2;
}
}
int main() {
int arr[100];
processData(arr, 100); // 크래시!
std::cout << "done\n";
return 0;
}
GDB로 해결: run → 크래시 → backtrace → print i → i=100 발견 → i <= size가 i < size여야 함을 확인.
시나리오 2: 널 포인터 역참조
상황: 외부 라이브러리에서 받은 포인터가 가끔 null인데, 어디서 null이 들어오는지 모릅니다.
// null_ptr.cpp
struct Node { int value; Node* next; };
int sumList(Node* head) {
int sum = 0;
while (head != nullptr) {
sum += head->value; // head가 null이면 크래시
head = head->next;
}
return sum;
}
GDB로 해결: break sumList → run → print head → null이면 호출자 확인.
시나리오 3: 무한 루프
상황: 프로그램이 멈춘 것처럼 보입니다. i++를 누락했을 가능성이 있습니다.
// infinite_loop.cpp
void processData() {
int i = 0;
while (i < 100) {
process(i);
// i++ 누락!
}
}
GDB로 해결: Ctrl+C로 중단 → backtrace → print i → i가 변하지 않음 확인.
시나리오 4: 메모리 오염—어디선가 값이 덮어씌워짐
상황: arr[5]가 0이어야 하는데 어딘가에서 999로 바뀝니다. 어디서 바뀌는지 모릅니다.
// memory_corruption.cpp
int arr[10] = {0};
someFunction(); // 이 함수 내부 어딘가에서 arr[5] 오염
GDB로 해결: watch arr[5] → continue → 값이 변경되는 시점에서 멈춤.
시나리오 5: 1000번 중 500번째만 버그
상황: 루프가 1000번 돌 때 500번째에서만 크래시합니다. 매번 next로 500번 진행하는 것은 비효율적입니다.
// conditional_bug.cpp
for (int i = 0; i < 1000; ++i) {
process(i); // i=500일 때만 버그
}
GDB로 해결: break main.cpp:10 if i == 500 → run → 500번째에서만 멈춤.
문제 시나리오 요약 다이어그램
flowchart TB
subgraph problems["문제 유형"]
P1[세그폴트]
P2[널 포인터]
P3[무한 루프]
P4[메모리 오염]
P5[조건부 버그]
end
subgraph gdb_tools["GDB 도구"]
G1[backtrace]
G2[print]
G3[watch]
G4[조건부 break]
end
P1 --> G1
P1 --> G2
P2 --> G2
P3 --> G1
P3 --> G2
P4 --> G3
P5 --> G4
2. 디버그 빌드와 GDB 시작
디버그 빌드
-g 옵션을 주면 실행 파일에 디버그 심볼(소스 줄 번호, 변수 이름)이 들어가서, GDB에서 “어느 줄에서 멈췄는지”, “변수 이름으로 값을 보는 것”이 가능해집니다. **-O0**로 최적화를 끄면 변수가 최적화로 사라지거나 코드 순서가 바뀌는 일이 줄어듭니다.
# 디버그 정보 포함 (-g), 최적화 끄기 (-O0)
g++ -g -O0 main.cpp -o myapp
# CMake 사용 시
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
GDB 시작
# 프로그램 로드
gdb ./myapp
# GDB 프롬프트에서 실행
(gdb) run
# 인자와 함께 실행
(gdb) run arg1 arg2
# 종료
(gdb) quit
GDB 워크플로우
sequenceDiagram
participant Dev as 개발자
participant GDB as GDB
participant App as 대상 프로그램
Dev->>GDB: gdb ./myapp
GDB->>App: 로드 (디버그 심볼)
Dev->>GDB: break main
Dev->>GDB: run
GDB->>App: 실행 시작
App->>GDB: main() 도달 → 중단
GDB->>Dev: 프롬프트 반환
Dev->>GDB: next / step / print
GDB->>Dev: 결과 출력
Dev->>GDB: continue
GDB->>App: 다음 브레이크포인트까지 실행
3. 브레이크포인트 완전 예제
브레이크포인트란?
브레이크포인트는 프로그램 실행이 특정 위치에 도달했을 때 자동으로 멈추게 하는 지점입니다. printf를 여러 개 넣는 대신, 한 번 설정하면 해당 줄에 도달할 때마다 멈춥니다.
기본 브레이크포인트 설정
# 함수에 브레이크포인트
(gdb) break main
Breakpoint 1 at 0x401234: file main.cpp, line 5.
(gdb) break processData
Breakpoint 2 at 0x401256: file main.cpp, line 12.
# 파일:라인에 브레이크포인트
(gdb) break main.cpp:15
Breakpoint 3 at 0x401278: file main.cpp, line 15.
조건부 브레이크포인트
# i가 50일 때만 멈춤
(gdb) break main.cpp:20 if i == 50
# ptr이 null일 때만 멈춤
(gdb) break processData if ptr == nullptr
# size가 0보다 클 때만 멈춤
(gdb) break main.cpp:10 if size > 0
브레이크포인트 관리
# 브레이크포인트 목록
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401234 in main at main.cpp:5
2 breakpoint keep y 0x0000000000401256 in processData at main.cpp:12
# 브레이크포인트 삭제
(gdb) delete 1 # 1번 삭제
(gdb) delete # 모두 삭제
# 브레이크포인트 비활성화/활성화
(gdb) disable 2
(gdb) enable 2
# 위치로 삭제
(gdb) clear main.cpp:15
브레이크포인트 동작 원리
flowchart LR
subgraph normal["Normal Run"]
N1[Execute] --> N2[Next]
N2 --> N1
end
subgraph bp["With Breakpoint"]
B1[Execute] --> B2{BP?}
B2 -->|Yes| B3[Stop]
B3 --> B4[Debug]
B2 -->|No| B1
end
실습: 브레이크포인트로 배열 버그 찾기
// breakpoint_demo.cpp - g++ -g -O0 -o bp_demo breakpoint_demo.cpp
#include <iostream>
void fillArray(int* arr, int size) {
for (int i = 0; i <= size; ++i) { // 버그: <=
arr[i] = i;
}
}
int main() {
int arr[5];
fillArray(arr, 5);
std::cout << "done\n";
return 0;
}
# 1. 빌드
$ g++ -g -O0 -o bp_demo breakpoint_demo.cpp
# 2. GDB 실행
$ gdb ./bp_demo
# 3. fillArray 함수에 브레이크포인트
(gdb) break fillArray
# 4. 실행
(gdb) run
# 5. 브레이크포인트에서 멈춤. next로 한 줄씩 진행
(gdb) next
(gdb) next
# i가 5일 때 arr[5] 접근 → 다음 next에서 세그폴트
# 6. 또는 조건부 브레이크로 i==5에서만 멈춤
(gdb) run
(gdb) break fillArray
(gdb) run
(gdb) break fillArray if i == 5
(gdb) continue
4. 워치포인트 완전 예제
워치포인트란?
워치포인트는 특정 변수나 메모리 주소의 값이 변경될 때 실행을 멈추게 하는 기능입니다. “어디서 이 변수가 바뀌는지” 모를 때 유용합니다.
기본 워치포인트
# 변수 변경 시 멈춤
(gdb) watch variable_name
# 포인터가 가리키는 값 변경 시 멈춤
(gdb) watch *ptr
# 배열 요소 변경 시
(gdb) watch arr[5]
워치포인트 종류
# watch: 쓰기 시 멈춤
(gdb) watch x
# rwatch: 읽기 시 멈춤 (하드웨어 지원 필요)
(gdb) rwatch x
# awatch: 읽기 또는 쓰기 시 멈춤
(gdb) awatch x
실습: 메모리 오염 추적
// watchpoint_demo.cpp - g++ -g -O0 -o wp_demo watchpoint_demo.cpp
#include <iostream>
void corruptData(int* arr) {
arr[5] = 999; // 여기서 arr[5] 변경
}
int main() {
int arr[10] = {0};
std::cout << "arr[5] before: " << arr[5] << "\n";
corruptData(arr); // arr[5]가 여기서 바뀜
std::cout << "arr[5] after: " << arr[5] << "\n";
return 0;
}
# 1. main에 브레이크포인트 후 실행
$ gdb ./wp_demo
(gdb) break main
(gdb) run
# 2. arr[5]에 워치포인트 설정 (main에서 arr이 유효한 상태에서)
(gdb) watch arr[5]
Hardware watchpoint 2: arr[5]
# 3. continue로 진행
(gdb) continue
# 4. arr[5]가 변경되면 멈춤
Hardware watchpoint 2: arr[5]
Old value = 0
New value = 999
corruptData (arr=0x7fff...) at watchpoint_demo.cpp:6
6 arr[5] = 999;
# 5. backtrace로 호출 경로 확인
(gdb) backtrace
#0 corruptData (arr=0x7fff...) at watchpoint_demo.cpp:6
#1 main () at watchpoint_demo.cpp:14
워치포인트 제한
- 하드웨어 워치포인트: CPU가 지원하면 개수 제한 있음(보통 4개). 매우 빠름.
- 소프트웨어 워치포인트: 개수 제한 없지만 매 단계마다 확인하여 느림.
- 지역 변수는 스코프를 벗어나면 워치포인트가 자동 해제될 수 있음.
5. 백트레이스(backtrace) 완전 예제
백트레이스란?
백트레이스(또는 스택 트레이스)는 “현재 실행 위치에 도달하기까지 어떤 함수들이 호출되었는지” 호출 스택을 보여줍니다. 크래시 시 어디서 문제가 발생했는지, 누가 그 함수를 호출했는지 파악하는 데 필수입니다.
기본 백트레이스
# 호출 스택 보기
(gdb) backtrace
(gdb) bt
# 출력 예시:
#0 buggyFunction (arr=0x7fff..., size=10) at main.cpp:5
#1 main () at main.cpp:12
상세 백트레이스 (모든 프레임의 지역 변수)
(gdb) backtrace full
(gdb) bt full
# 출력 예시:
#0 buggyFunction (arr=0x7fff..., size=10) at main.cpp:5
i = 10
size = 10
#1 main () at main.cpp:12
arr = {0, 2, 4, ...}
프레임 이동
# 2번 프레임으로 이동
(gdb) frame 2
(gdb) f 2
# 현재 프레임 정보
(gdb) info frame
# 위/아래 프레임으로 이동
(gdb) up
(gdb) down
실습: 세그폴트에서 백트레이스 활용
// backtrace_demo.cpp - g++ -g -O0 -o bt_demo backtrace_demo.cpp
#include <iostream>
void level3(int* p) {
*p = 42; // p가 null이면 크래시
}
void level2(int* p) {
level3(p);
}
void level1(int* p) {
level2(p);
}
int main() {
int* p = nullptr;
level1(p); // 크래시!
return 0;
}
$ g++ -g -O0 -o bt_demo backtrace_demo.cpp
$ gdb ./bt_demo
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401156 in level3 (p=0x0) at backtrace_demo.cpp:5
5 *p = 42;
# 백트레이스로 호출 경로 확인
(gdb) backtrace
#0 level3 (p=0x0) at backtrace_demo.cpp:5
#1 level2 (p=0x0) at backtrace_demo.cpp:9
#2 level1 (p=0x0) at backtrace_demo.cpp:13
#3 main () at backtrace_demo.cpp:18
# main 프레임으로 이동해 p 확인
(gdb) frame 3
(gdb) print p
$1 = (int *) 0x0
# level3에서 p 확인
(gdb) frame 0
(gdb) print p
$2 = (int *) 0x0
스택 프레임 구조
flowchart TB
subgraph stack["호출 스택 (아래→위)"]
F0["main() - 프레임 3"]
F1["level1() - 프레임 2"]
F2["level2() - 프레임 1"]
F3["level3() - 프레임 0 (현재)"]
end
F0 --> F1
F1 --> F2
F2 --> F3
subgraph vars["프레임 0 지역 변수"]
V1["p = 0x0"]
end
6. print와 변수 검사 완전 예제
print 기본 사용법
# 변수 값 출력
(gdb) print x
(gdb) p x
# 포인터 역참조
(gdb) print *ptr
# 구조체/클래스 멤버
(gdb) print person.name
(gdb) print obj->value
# 배열
(gdb) print arr[0]
(gdb) print arr[0]@10 # 10개 원소 (GDB)
다양한 print 형식
# 16진수
(gdb) print/x ptr
# 10진수
(gdb) print/d x
# 문자
(gdb) print/c c
# 부동소수점
(gdb) print/f f
info locals, info args
# 현재 프레임의 모든 지역 변수
(gdb) info locals
# 현재 함수의 인자
(gdb) info args
메모리 검사 (x 명령)
# x/[개수][형식][크기] 주소
# 형식: x(16진), d(10진), s(문자열), i(명령어)
# 크기: b(1바이트), h(2바이트), w(4바이트), g(8바이트)
# 16진수로 10개 워드
(gdb) x/10x ptr
# 10진수로 10개
(gdb) x/10d ptr
# 문자열로
(gdb) x/10s ptr
실습: print로 버그 원인 확인
// print_demo.cpp - g++ -g -O0 -o print_demo print_demo.cpp
#include <iostream>
struct Point { int x, y; };
void process(Point* p) {
if (p == nullptr) return;
p->x *= 2;
p->y *= 2;
}
int main() {
Point pt = {10, 20};
process(&pt);
std::cout << pt.x << ", " << pt.y << "\n";
return 0;
}
$ gdb ./print_demo
(gdb) break process
(gdb) run
# process 내부에서 p 검사
(gdb) print p
$1 = (Point *) 0x7fffffffe2a0
(gdb) print *p
$2 = {x = 10, y = 20}
(gdb) print p->x
$3 = 10
(gdb) next
(gdb) print p->x
$4 = 20
7. step/next/continue 완전 예제
step vs next vs continue
| 명령 | 동작 |
|---|---|
| next (n) | 다음 소스 줄로 이동. 함수 호출이 있으면 함수 안으로 들어가지 않고 한 번에 실행 |
| step (s) | 다음 소스 줄로 이동. 함수 호출이 있으면 함수 안으로 들어감 |
| continue (c) | 다음 브레이크포인트까지 계속 실행 |
| finish | 현재 함수가 반환될 때까지 실행 |
단계별 실행 흐름
flowchart TD
A[현재 위치] --> B{next vs step?}
B -->|next| C[다음 소스 줄로]
B -->|step| D{함수 호출?}
D -->|예| E[함수 내부로 진입]
D -->|아니오| C
C --> F[다음 명령 대기]
E --> F
F --> G{finish?}
G -->|예| H[현재 함수 반환까지 실행]
G -->|아니오| A
H --> F
실습: step vs next
// stepping_demo.cpp - g++ -g -O0 -o step_demo stepping_demo.cpp
#include <iostream>
int add(int a, int b) {
return a + b;
}
int main() {
int x = 10;
int y = 20;
int z = add(x, y); // 이 줄에서 next vs step 차이
std::cout << z << "\n";
return 0;
}
$ gdb ./step_demo
(gdb) break main
(gdb) run
# next: add() 안으로 들어가지 않음
(gdb) next
(gdb) next
(gdb) next # add(x, y) 호출 후 다음 줄(std::cout)로
(gdb) print z
$1 = 30
# step: add() 안으로 들어감
(gdb) run
(gdb) next
(gdb) next
(gdb) step # add() 함수 내부로 진입
add (a=10, b=20) at stepping_demo.cpp:4
4 return a + b;
(gdb) print a
$2 = 10
(gdb) print b
$3 = 20
(gdb) finish # add() 반환까지 실행
(gdb) print z
$4 = 30
N번 반복 실행
# next 5번 실행
(gdb) next 5
# step 3번 실행
(gdb) step 3
continue 사용
# 브레이크포인트 여러 개 설정 후
(gdb) break main
(gdb) break processData
(gdb) run
# main에서 멈춤
(gdb) continue
# processData에서 멈춤
(gdb) continue
# 다음 브레이크포인트 또는 프로그램 종료까지
8. 통합 실습: 버그 찾기 end-to-end
전체 예제 코드
// full_demo.cpp - g++ -g -O0 -o full_demo full_demo.cpp
#include <iostream>
#include <vector>
void processVector(std::vector<int>& vec) {
for (size_t i = 0; i <= vec.size(); ++i) { // ❌ <= 버그
vec[i] *= 2;
}
}
int main() {
std::vector<int> vec = {1, 2, 3};
processVector(vec);
std::cout << "done\n";
return 0;
}
단계별 GDB 디버깅
# 1. 빌드
$ g++ -g -O0 -o full_demo full_demo.cpp
# 2. GDB 시작
$ gdb ./full_demo
# 3. processVector에 브레이크포인트
(gdb) break processVector
# 4. 실행
(gdb) run
# 5. next로 루프 진행 (또는 조건부 break)
(gdb) next
(gdb) next
# ... i가 3일 때 vec[3] 접근 → 세그폴트
# 6. 크래시 후
(gdb) backtrace
#0 processVector (vec=...) at full_demo.cpp:5
#1 main () at full_demo.cpp:12
(gdb) print i
$1 = 3
(gdb) print vec.size()
$2 = 3
# 7. 원인: i <= vec.size() → i가 3일 때 vec[3] 접근 (범위 초과)
# 수정: i < vec.size()
GDB 명령어 체크리스트
- [ ] break [위치] — 브레이크포인트 설정
- [ ] run — 실행
- [ ] next / step — 단계별 실행
- [ ] continue — 다음 브레이크포인트까지
- [ ] backtrace — 호출 스택
- [ ] print [변수] — 변수 값
- [ ] watch [변수] — 변경 감지
- [ ] frame N — 프레임 이동
- [ ] info locals / info args — 지역 변수/인자
9. 자주 발생하는 에러와 해결법
에러 1: “No symbol table” / “No debugging symbols found”
원인: -g 옵션 없이 빌드함.
해결법:
# 재빌드 시 -g 추가
g++ -g -O0 main.cpp -o myapp
# 기존 바이너리에 심볼 있는지 확인
file myapp
# "not stripped" 또는 "with debug_info" 확인
에러 2: “Cannot access memory at address 0x0”
원인: Null 포인터 역참조.
해결법:
(gdb) backtrace
(gdb) frame 0
(gdb) info args
(gdb) print ptr # 0x0인지 확인
에러 3: “optimized out” (변수 값이 표시되지 않음)
원인: -O2, -O3 등 최적화로 변수가 제거되거나 레지스터에만 있음.
해결법:
# -O0으로 재빌드
g++ -g -O0 main.cpp -o myapp
에러 4: GDB가 “run” 후 즉시 종료
원인: 프로그램이 정상 종료되거나, 자식 프로세스에서 크래시.
해결법:
# 자식 프로세스 추적
(gdb) set follow-fork-mode child
# fork 전에 브레이크포인트
(gdb) break fork
(gdb) run
에러 5: 워치포인트 “Cannot watch constant value”
원인: 상수나 레지스터만 있는 값은 워치 불가.
해결법:
# 메모리에 있는 변수에만 워치 설정
# 지역 변수는 해당 스코프에서 설정
(gdb) break main
(gdb) run
(gdb) watch arr[5]
에러 6: “Program received signal SIGSEGV” — 원인 불명
해결법:
(gdb) backtrace full
(gdb) frame 0
(gdb) info locals
(gdb) info args
# 크래시 직전 상태로 run 후 break로 그 함수에 멈추고 next로 한 줄씩 진행
에러 요약 표
| 에러 | 원인 | 해결 |
|---|---|---|
| No symbol table | -g 없음 | g++ -g -O0 |
| Cannot access 0x0 | Null 포인터 | backtrace, print ptr |
| optimized out | -O2 이상 | -O0 재빌드 |
| run 후 즉시 종료 | fork 등 | follow-fork-mode child |
| 워치 불가 | 상수/레지스터 | 메모리 변수에만 |
10. 모범 사례
핵심 원칙
- 디버그 빌드:
g++ -g -O0 -Wall main.cpp -o myapp - 조건부 브레이크:
break main.cpp:10 if i == 500— 1000번 루프에서 500번째만 확인 - backtrace full: 크래시 시 모든 프레임의 지역 변수 확인
- .gdbinit:
set pagination off,set print pretty on,set print array on - 로그 저장:
set logging file debug.log→set logging on→ 디버깅 →set logging off
디버깅 체크리스트
- [ ] -g -O0로 빌드했는가?
- [ ] backtrace로 크래시 위치 확인
- [ ] frame N으로 해당 프레임 이동
- [ ] info locals, info args로 변수 확인
- [ ] 조건부 브레이크로 특정 케이스만 추적
- [ ] 워치포인트로 메모리 변경 추적
11. 프로덕션 패턴
Core Dump 분석
프로덕션에서 크래시 시 core dump를 저장해 나중에 분석합니다.
# core dump 활성화 (Linux)
ulimit -c unlimited
echo /tmp/core.%e.%p | sudo tee /proc/sys/kernel/core_pattern
# 크래시 후
$ gdb ./myapp /tmp/core.myapp.12345
(gdb) backtrace
(gdb) backtrace full
디버그 심볼 분리
Release 빌드와 디버그 심볼을 분리해 배포합니다.
# Release 빌드 + 별도 .debug 파일
objcopy --only-keep-debug myapp myapp.debug
strip -g myapp
objcopy --add-gnu-debuglink=myapp.debug myapp
# 분석 시
gdb -s myapp.debug -e myapp -c core.12345
원격 디버깅 (gdbserver)
# 대상 머신
gdbserver :1234 ./myapp
# 개발 머신
gdb ./myapp
(gdb) target remote 192.168.1.100:1234
(gdb) continue
배치 모드로 자동 분석
# analyze.gdb
set pagination off
run
backtrace full
quit
# 실행
gdb -batch -x analyze.gdb ./myapp
프로덕션 디버깅 시 주의사항
flowchart TD
A[프로덕션 크래시] --> B{디버그 빌드 있음?}
B -->|예| C[core dump 수집]
B -->|아니오| D[재현 환경 구축]
C --> E[gdbserver 또는 로컬 분석]
D --> E
E --> F[backtrace 분석]
F --> G[원인 파악]
G --> H[수정 및 배포]
주의사항:
- 프로덕션 바이너리와 core dump의 빌드가 정확히 일치해야 함
-O2빌드에서는 변수/줄 번호가 어긋날 수 있음 → RelWithDebInfo 권장- gdbserver는 프로세스를 중단하므로 트래픽 적은 시간에 사용
GDB 명령어 치트시트
| 기능 | 명령 |
|---|---|
| 실행 | run, run arg1 arg2, kill |
| 브레이크포인트 | break, info breakpoints, delete 1 |
| 워치포인트 | watch variable |
| 실행 제어 | next(n), step(s), finish, continue(c) |
| 검사 | print(p), backtrace(bt), backtrace full, frame N, info locals, info args |
| 기타 | list(l), quit(q) |
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ LLDB 기초 완벽 가이드 | macOS·브레이크포인트
- C++ GDB/LLDB | cout 100개 찍어도 못 찾은 버그, 디버거로 5분 만에 해결
- C++ Segmentation fault | core dump
- C++ Sanitizers | ASan·TSan으로 메모리 버그·data race 자동 탐지
이 글에서 다루는 키워드 (관련 검색어)
C++ GDB, GDB 기초, 브레이크포인트, 워치포인트, 백트레이스, GDB print, step next, 디버깅, 세그폴트, core dump 등으로 검색하시면 이 글이 도움이 됩니다.
정리
| 도구 | 용도 |
|---|---|
| break | 특정 위치에서 실행 중단 |
| watch | 변수 변경 시 중단 |
| backtrace | 호출 스택 확인 |
| 변수 값 출력 | |
| step/next | 단계별 실행 |
| continue | 다음 브레이크포인트까지 |
핵심 원칙:
- printf 대신 GDB
- 브레이크포인트 + 조건부 브레이크
- backtrace로 호출 경로 파악
- print로 변수 검증
- 워치포인트로 메모리 오염 추적
- 프로덕션은 core dump + 심볼 분리
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 세그폴트·크래시 원인 파악, 무한 루프 디버깅, 특정 조건에서만 발생하는 버그 추적, 메모리 오염 추적 등 printf로 찾기 어려운 버그를 GDB 기본 명령어로 빠르게 해결할 때 사용합니다.
Q. GDB와 LLDB의 차이는?
A. GDB는 Linux에서, LLDB는 macOS에서 주로 사용합니다. 명령어가 유사하므로 GDB를 익히면 LLDB도 쉽게 사용할 수 있습니다. 자세한 내용은 GDB/LLDB 디버거 가이드를 참고하세요.
Q. 프로덕션에서 GDB를 붙여도 되나요?
A. gdbserver로 붙이면 프로세스가 중단되므로, 가능하면 core dump를 수집해 오프라인에서 분석하는 것이 좋습니다. 트래픽이 적은 시간에만 gdbserver 사용을 권장합니다.
한 줄 요약: GDB 브레이크포인트·워치포인트·백트레이스·print·step/next로 버그를 빠르게 찾을 수 있습니다. 다음으로 GDB/LLDB 디버거 가이드(#16-1)를 읽어보면 좋습니다.
다음 글: [C++ 실전 가이드 #16-1] GDB/LLDB 디버거 완벽 가이드
이전 글: [C++ 실전 가이드 #4] CMake 입문
관련 글
- C++ LLDB 기초 완벽 가이드 | macOS·브레이크포인트
- CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
- C++ 디버깅 기초 완벽 가이드 | GDB·LLDB 브레이크포인트·워치포인트로 버그 5분 만에 찾기
- C++ GDB/LLDB | cout 100개 찍어도 못 찾은 버그, 디버거로 5분 만에 해결
- VS Code C++ 설정 | IntelliSense·빌드·디버깅