C++ GDB 기초 완벽 가이드 | 브레이크포인트·워치포인트

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를 활용하는 방법을 알 수 있습니다.
  • 흔한 에러와 해결법을 파악할 수 있습니다.
  • 프로덕션 환경에서의 디버깅 패턴을 적용할 수 있습니다.

목차

  1. 문제 시나리오
  2. 디버그 빌드와 GDB 시작
  3. 브레이크포인트 완전 예제
  4. 워치포인트 완전 예제
  5. 백트레이스(backtrace) 완전 예제
  6. print와 변수 검사 완전 예제
  7. step/next/continue 완전 예제
  8. 통합 실습: 버그 찾기 end-to-end
  9. 자주 발생하는 에러와 해결법
  10. 모범 사례
  11. 프로덕션 패턴

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 → 크래시 → backtraceprint ii=100 발견 → i <= sizei < 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 sumListrunprint head → null이면 호출자 확인.

시나리오 3: 무한 루프

상황: 프로그램이 멈춘 것처럼 보입니다. i++를 누락했을 가능성이 있습니다.

// infinite_loop.cpp
void processData() {
    int i = 0;
    while (i < 100) {
        process(i);
        // i++ 누락!
    }
}

GDB로 해결: Ctrl+C로 중단 → backtraceprint ii가 변하지 않음 확인.

시나리오 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 == 500run → 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와 변수 검사 완전 예제

# 변수 값 출력
(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 0x0Null 포인터backtrace, print ptr
optimized out-O2 이상-O0 재빌드
run 후 즉시 종료fork 등follow-fork-mode child
워치 불가상수/레지스터메모리 변수에만

10. 모범 사례

핵심 원칙

  1. 디버그 빌드: g++ -g -O0 -Wall main.cpp -o myapp
  2. 조건부 브레이크: break main.cpp:10 if i == 500 — 1000번 루프에서 500번째만 확인
  3. backtrace full: 크래시 시 모든 프레임의 지역 변수 확인
  4. .gdbinit: set pagination off, set print pretty on, set print array on
  5. 로그 저장: set logging file debug.logset 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호출 스택 확인
print변수 값 출력
step/next단계별 실행
continue다음 브레이크포인트까지

핵심 원칙:

  1. printf 대신 GDB
  2. 브레이크포인트 + 조건부 브레이크
  3. backtrace로 호출 경로 파악
  4. print로 변수 검증
  5. 워치포인트로 메모리 오염 추적
  6. 프로덕션은 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·빌드·디버깅