C++ GDB | "디버거" 가이드
이 글의 핵심
C++ GDB에 대한 실전 가이드입니다.
들어가며
GDB(GNU Debugger)는 C/C++ 프로그램 디버깅의 필수 도구입니다. 중단점 설정, 변수 검사, 스택 추적 등 강력한 기능을 제공하여 버그를 빠르게 찾고 수정할 수 있습니다.
1. GDB 기본
설치
# Ubuntu/Debian
sudo apt install gdb
# macOS (LLDB 권장)
brew install gdb
# Windows (MinGW)
# MinGW 설치 시 gdb 포함
컴파일 및 실행
# 디버그 정보 포함 (-g 플래그)
g++ -g program.cpp -o program
# 최적화 끄기 (디버깅 용이)
g++ -g -O0 program.cpp -o program
# 또는 디버깅용 최적화
g++ -g -Og program.cpp -o program
# GDB 실행
gdb ./program
# 프로그램 실행
(gdb) run
# 인자와 함께 실행
(gdb) run arg1 arg2
핵심 개념:
- -g: 디버그 정보(심볼, 라인 번호) 포함
- -O0: 최적화 끄기 (변수가 최적화로 제거되지 않음)
- -Og: 디버깅에 적합한 최적화 레벨
2. 기본 명령어
실행 제어
# 프로그램 실행
(gdb) run # 처음부터 실행
(gdb) run arg1 arg2 # 인자와 함께 실행
(gdb) continue (c) # 다음 중단점까지 계속
(gdb) next (n) # 다음 줄 (함수 넘김)
(gdb) step (s) # 다음 줄 (함수 진입)
(gdb) finish # 현재 함수 끝까지
(gdb) until # 현재 루프 끝까지
(gdb) quit (q) # GDB 종료
중단점 (Breakpoint)
# 중단점 설정
(gdb) break main # 함수에 설정
(gdb) break file.cpp:42 # 파일:라인에 설정
(gdb) break MyClass::method # 메서드에 설정
(gdb) break +5 # 현재 위치에서 5줄 후
# 조건부 중단점
(gdb) break factorial if n == 3
(gdb) condition 1 x > 100 # 중단점 1에 조건 추가
# 중단점 관리
(gdb) info breakpoints # 목록 확인
(gdb) delete 1 # 중단점 1 삭제
(gdb) delete # 모든 중단점 삭제
(gdb) disable 1 # 중단점 1 비활성화
(gdb) enable 1 # 중단점 1 활성화
변수 검사
# 변수 출력
(gdb) print var # 변수 값 출력
(gdb) print &var # 주소 출력
(gdb) print *ptr # 포인터가 가리키는 값
(gdb) print arr[0] # 배열 원소
(gdb) print obj.member # 멤버 변수
# 자동 출력 (매 중단마다)
(gdb) display var # 자동 출력 설정
(gdb) undisplay 1 # 자동 출력 해제
# 정보 확인
(gdb) info locals # 지역 변수 목록
(gdb) info args # 함수 인자 목록
(gdb) info variables # 전역 변수 목록
스택 추적
# 스택 추적
(gdb) backtrace (bt) # 전체 스택
(gdb) backtrace 5 # 최근 5개 프레임
(gdb) frame 0 # 프레임 0으로 이동
(gdb) up # 상위 프레임
(gdb) down # 하위 프레임
(gdb) info frame # 현재 프레임 정보
3. 실전 예제
예제 1: 기본 디버깅
// program.cpp
#include <iostream>
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
int result = factorial(5);
std::cout << "결과: " << result << std::endl;
return 0;
}
GDB 세션:
# 컴파일
$ g++ -g program.cpp -o program
# GDB 실행
$ gdb ./program
# 중단점 설정
(gdb) break factorial
Breakpoint 1 at 0x1189: file program.cpp, line 5.
# 프로그램 실행
(gdb) run
Starting program: ./program
Breakpoint 1, factorial (n=5) at program.cpp:5
# 변수 확인
(gdb) print n
$1 = 5
# 다음 중단점까지 계속
(gdb) continue
Breakpoint 1, factorial (n=4) at program.cpp:5
# 스택 추적
(gdb) backtrace
#0 factorial (n=4) at program.cpp:5
#1 0x0000555555555195 in factorial (n=5) at program.cpp:6
#2 0x00005555555551b5 in main () at program.cpp:10
# 종료
(gdb) quit
예제 2: 조건부 중단점
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int i = 0; i < numbers.size(); ++i) {
int value = numbers[i] * 2;
std::cout << value << std::endl;
}
return 0;
}
GDB 세션:
# 조건부 중단점: i가 5일 때만
(gdb) break 7 if i == 5
(gdb) run
# i가 5일 때만 중단됨
# 조건 확인
(gdb) print i
$1 = 5
(gdb) print value
$2 = 12
예제 3: 워치포인트 (변수 감시)
#include <iostream>
int main() {
int counter = 0;
for (int i = 0; i < 10; ++i) {
counter += i;
if (counter > 20) {
counter = 0; // 버그: 여기서 리셋됨
}
}
std::cout << "최종: " << counter << std::endl;
return 0;
}
GDB 세션:
# 워치포인트 설정 (counter 값 변경 시 중단)
(gdb) watch counter
(gdb) run
# counter가 변경될 때마다 중단
Hardware watchpoint 2: counter
Old value = 0
New value = 1
# 계속 실행하면서 변경 추적
(gdb) continue
예제 4: 코어 덤프 분석
#include <iostream>
void crash() {
int* ptr = nullptr;
*ptr = 42; // Segmentation fault!
}
int main() {
crash();
return 0;
}
코어 덤프 분석:
# 코어 덤프 활성화
$ ulimit -c unlimited
# 프로그램 실행
$ ./program
Segmentation fault (core dumped)
# GDB로 코어 덤프 분석
$ gdb ./program core
# 크래시 위치 확인
(gdb) backtrace
#0 0x0000555555555189 in crash () at program.cpp:5
#1 0x00005555555551a5 in main () at program.cpp:10
# 크래시 프레임으로 이동
(gdb) frame 0
#0 0x0000555555555189 in crash () at program.cpp:5
5 *ptr = 42;
# 변수 확인
(gdb) print ptr
$1 = (int *) 0x0
4. 고급 기능
메모리 검사
# 메모리 내용 확인 (x = examine)
(gdb) x/10x address # 16진수로 10개 워드
(gdb) x/10d address # 10진수로 10개 워드
(gdb) x/10c address # 문자로 10개
(gdb) x/s address # 문자열로 출력
(gdb) x/10i address # 명령어 10개 (어셈블리)
# 예제
(gdb) print &var
$1 = (int *) 0x7fffffffe3fc
(gdb) x/4x 0x7fffffffe3fc
0x7fffffffe3fc: 0x0000000a 0x00000000 0xf7dc2620 0x00007fff
타입 정보
# 타입 확인
(gdb) ptype var # 상세한 타입 정보
(gdb) whatis var # 간단한 타입
# 예제
(gdb) ptype std::vector<int>
type = class std::vector<int, std::allocator<int>> {
...
}
멀티스레드 디버깅
# 스레드 목록
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fc0740 (LWP 12345) main () at main.cpp:10
2 Thread 0x7ffff6fbf700 (LWP 12346) worker () at worker.cpp:5
# 스레드 전환
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff6fbf700)]
# 현재 스레드 스택
(gdb) backtrace
# 모든 스레드 스택
(gdb) thread apply all backtrace
역방향 디버깅 (Reverse Debugging)
# 기록 시작
(gdb) record
(gdb) continue
# 역방향 실행
(gdb) reverse-step # 이전 줄로
(gdb) reverse-next # 이전 줄로 (함수 넘김)
(gdb) reverse-continue # 이전 중단점까지
(gdb) reverse-finish # 함수 시작까지
5. 자주 발생하는 문제
문제 1: 디버그 정보 없음
# ❌ 디버그 정보 없음
$ g++ program.cpp -o program
$ gdb ./program
(gdb) list
No symbol table is loaded.
# ✅ -g 플래그 추가
$ g++ -g program.cpp -o program
$ gdb ./program
(gdb) list
1 #include <iostream>
2
3 int factorial(int n) {
...
해결책: 항상 -g 플래그로 컴파일하세요.
문제 2: 최적화로 인한 변수 제거
#include <iostream>
int main() {
int x = 10;
int y = x * 2;
int z = y + 5;
std::cout << z << std::endl;
return 0;
}
# ❌ -O3 최적화
$ g++ -g -O3 program.cpp -o program
$ gdb ./program
(gdb) break main
(gdb) run
(gdb) print x
$1 = <optimized out> # 변수가 최적화로 제거됨
# ✅ -O0 또는 -Og
$ g++ -g -O0 program.cpp -o program
$ gdb ./program
(gdb) print x
$1 = 10 # 정상 출력
해결책: 디버깅 시 -O0 또는 -Og 사용하세요.
문제 3: 심볼 제거 (strip)
# ❌ strip 실행 후
$ strip program
$ gdb ./program
(gdb) break main
Function "main" not defined.
# ✅ strip 하지 않기
# 또는 별도 심볼 파일 유지
$ objcopy --only-keep-debug program program.debug
$ strip program
$ objcopy --add-gnu-debuglink=program.debug program
해결책: 디버그 빌드는 strip하지 마세요.
문제 4: 멀티스레드 디버깅
#include <iostream>
#include <thread>
void worker(int id) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << id << ": " << i << std::endl;
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}
GDB 세션:
# 중단점 설정
(gdb) break worker
# 실행
(gdb) run
[New Thread 0x7ffff6fbf700 (LWP 12346)]
Thread 2 "program" hit Breakpoint 1, worker (id=1) at program.cpp:5
# 스레드 목록
(gdb) info threads
Id Target Id Frame
* 2 Thread 0x7ffff6fbf700 (LWP 12346) worker (id=1) at program.cpp:5
1 Thread 0x7ffff7fc0740 (LWP 12345) 0x00007ffff7bc0a9d in __pthread_join
# 다른 스레드로 전환
(gdb) thread 1
(gdb) backtrace
6. TUI 모드
TUI(Text User Interface)는 소스 코드를 화면에 표시하는 모드입니다.
# TUI 모드로 시작
$ gdb -tui ./program
# 또는 실행 중 활성화
(gdb) tui enable
(gdb) tui disable
# 레이아웃 변경
(gdb) layout src # 소스 코드
(gdb) layout asm # 어셈블리
(gdb) layout split # 소스 + 어셈블리
(gdb) layout regs # 레지스터 + 소스
# 창 전환
Ctrl+X, A # TUI 모드 토글
Ctrl+X, O # 활성 창 전환
Ctrl+L # 화면 새로고침
7. 실전 예제: 버그 찾기
#include <iostream>
#include <vector>
double average(const std::vector<int>& numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum / numbers.size(); // 버그: 정수 나눗셈!
}
int main() {
std::vector<int> scores = {85, 92, 78, 95, 88};
double avg = average(scores);
std::cout << "평균: " << avg << std::endl;
return 0;
}
GDB로 버그 찾기:
# 컴파일 및 실행
$ g++ -g bug.cpp -o bug
$ ./bug
평균: 87 # 예상: 87.6
# GDB 실행
$ gdb ./bug
# average 함수에 중단점
(gdb) break average
(gdb) run
Breakpoint 1, average (numbers=...) at bug.cpp:5
# 변수 확인
(gdb) next
(gdb) next
...
(gdb) print sum
$1 = 438
(gdb) print numbers.size()
$2 = 5
# 나눗셈 전 타입 확인
(gdb) ptype sum
type = int
(gdb) ptype numbers.size()
type = std::size_t
# 문제 발견: int / size_t = int (정수 나눗셈)
# 해결: static_cast<double>(sum) / numbers.size()
8. GDB 명령어 요약
| 카테고리 | 명령어 | 설명 |
|---|---|---|
| 실행 | run | 프로그램 실행 |
continue (c) | 다음 중단점까지 | |
next (n) | 다음 줄 (함수 넘김) | |
step (s) | 다음 줄 (함수 진입) | |
finish | 현재 함수 끝까지 | |
| 중단점 | break | 중단점 설정 |
watch | 워치포인트 설정 | |
info breakpoints | 중단점 목록 | |
delete | 중단점 삭제 | |
| 검사 | print | 변수 출력 |
display | 자동 출력 | |
info locals | 지역 변수 | |
backtrace (bt) | 스택 추적 | |
| 메모리 | x | 메모리 검사 |
ptype | 타입 정보 | |
| 스레드 | info threads | 스레드 목록 |
thread | 스레드 전환 |
정리
핵심 요약
- GDB: C/C++ 디버깅 도구
- -g 플래그: 디버그 정보 포함 필수
- 중단점:
break명령으로 설정 - 변수 검사:
print,display로 확인 - 스택 추적:
backtrace로 호출 스택 확인 - 조건부 중단:
break ... if조건 지정 - 워치포인트:
watch로 변수 변경 감시 - 코어 덤프: 크래시 원인 분석
실전 팁
컴파일:
- 디버깅 시
-g -O0또는-g -Og사용 - 릴리스 빌드는
-O2또는-O3 strip은 릴리스 빌드에만 사용
디버깅:
- 조건부 중단점으로 특정 상황만 검사
watch로 예상치 못한 변수 변경 추적- TUI 모드로 소스 코드 확인하며 디버깅
backtrace로 크래시 위치 빠르게 파악
효율:
- 중단점을 최소화하여 빠르게 문제 위치 좁히기
display로 관심 변수 자동 출력- 멀티스레드는
info threads로 전체 상태 파악
다음 단계
- C++ Valgrind
- C++ Sanitizers
- C++ 예외 성능
관련 글
- C++ 디버깅 완벽 가이드 | GDB·Sanitizer·메모리 누수·멀티스레드 디버깅 실전
- C++ 디버깅 실전 가이드 | gdb, LLDB, Visual Studio 완벽 활용
- C++ Sanitizers |
- C++ Valgrind |
- C++ Benchmarking |