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

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

이 글의 핵심

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

실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  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 값을 알 수 없습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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이 들어오는지 모릅니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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++를 누락했을 가능성이 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 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번 진행하는 것은 비효율적입니다. 다음은 간단한 cpp 코드 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// conditional_bug.cpp
for (int i = 0; i < 1000; ++i) {
    process(i);  // i=500일 때만 버그
}

GDB로 해결: break main.cpp:10 if i == 500run → 500번째에서만 멈춤.

문제 시나리오 요약 다이어그램

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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로 최적화를 끄면 변수가 최적화로 사라지거나 코드 순서가 바뀌는 일이 줄어듭니다. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 디버그 정보 포함 (-g), 최적화 끄기 (-O0)
g++ -g -O0 main.cpp -o myapp
# CMake 사용 시
cmake -DCMAKE_BUILD_TYPE=Debug ..
make

GDB 시작

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 프로그램 로드
gdb ./myapp
# GDB 프롬프트에서 실행
(gdb) run
# 인자와 함께 실행
(gdb) run arg1 arg2
# 종료
(gdb) quit

GDB 워크플로우

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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를 여러 개 넣는 대신, 한 번 설정하면 해당 줄에 도달할 때마다 멈춥니다.

기본 브레이크포인트 설정

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 함수에 브레이크포인트
(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.

조건부 브레이크포인트

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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

브레이크포인트 관리

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 브레이크포인트 목록
(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

브레이크포인트 동작 원리

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

실습: 브레이크포인트로 배열 버그 찾기

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 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. 워치포인트 완전 예제

워치포인트란?

워치포인트는 특정 변수나 메모리 주소의 값이 변경될 때 실행을 멈추게 하는 기능입니다. “어디서 이 변수가 바뀌는지” 모를 때 유용합니다.

기본 워치포인트

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 변수 변경 시 멈춤
(gdb) watch variable_name
# 포인터가 가리키는 값 변경 시 멈춤
(gdb) watch *ptr
# 배열 요소 변경 시
(gdb) watch arr[5]

워치포인트 종류

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# watch: 쓰기 시 멈춤
(gdb) watch x
# rwatch: 읽기 시 멈춤 (하드웨어 지원 필요)
(gdb) rwatch x
# awatch: 읽기 또는 쓰기 시 멈춤
(gdb) awatch x

실습: 메모리 오염 추적

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 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) 완전 예제

백트레이스란?

백트레이스(또는 스택 트레이스)는 “현재 실행 위치에 도달하기까지 어떤 함수들이 호출되었는지” 호출 스택을 보여줍니다. 크래시 시 어디서 문제가 발생했는지, 누가 그 함수를 호출했는지 파악하는 데 필수입니다.

기본 백트레이스

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 호출 스택 보기
(gdb) backtrace
(gdb) bt
# 출력 예시:
#0  buggyFunction (arr=0x7fff..., size=10) at main.cpp:5
#1  main () at main.cpp:12

상세 백트레이스 (모든 프레임의 지역 변수)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

(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, ...}

프레임 이동

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 2번 프레임으로 이동
(gdb) frame 2
(gdb) f 2
# 현재 프레임 정보
(gdb) info frame
# 위/아래 프레임으로 이동
(gdb) up
(gdb) down

실습: 세그폴트에서 백트레이스 활용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ 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

스택 프레임 구조

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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와 변수 검사 완전 예제

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 변수 값 출력
(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 형식

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 16진수
(gdb) print/x ptr
# 10진수
(gdb) print/d x
# 문자
(gdb) print/c c
# 부동소수점
(gdb) print/f f

info locals, info args

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 현재 프레임의 모든 지역 변수
(gdb) info locals
# 현재 함수의 인자
(gdb) info args

메모리 검사 (x 명령)

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 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로 버그 원인 확인

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ 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현재 함수가 반환될 때까지 실행

단계별 실행 흐름

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ 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번 반복 실행

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# next 5번 실행
(gdb) next 5
# step 3번 실행
(gdb) step 3

continue 사용

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 브레이크포인트 여러 개 설정 후
(gdb) break main
(gdb) break processData
(gdb) run
# main에서 멈춤
(gdb) continue
# processData에서 멈춤
(gdb) continue
# 다음 브레이크포인트 또는 프로그램 종료까지

8. 통합 실습: 버그 찾기 end-to-end

전체 예제 코드

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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 디버깅

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 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 명령어 체크리스트

아래 코드는 markdown를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

- [ ] break [위치] — 브레이크포인트 설정
- [ ] run — 실행
- [ ] next / step — 단계별 실행
- [ ] continue — 다음 브레이크포인트까지
- [ ] backtrace — 호출 스택
- [ ] print [변수] — 변수 값
- [ ] watch [변수] — 변경 감지
- [ ] frame N — 프레임 이동
- [ ] info locals / info args — 지역 변수/인자

9. 자주 발생하는 에러와 해결법

에러 1: “No symbol table” / “No debugging symbols found”

원인: -g 옵션 없이 빌드함. 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 재빌드 시 -g 추가
g++ -g -O0 main.cpp -o myapp
# 기존 바이너리에 심볼 있는지 확인
file myapp
# "not stripped" 또는 "with debug_info" 확인

에러 2: “Cannot access memory at address 0x0”

원인: Null 포인터 역참조. 해결법: 다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

(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” 후 즉시 종료

원인: 프로그램이 정상 종료되거나, 자식 프로세스에서 크래시. 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 자식 프로세스 추적
(gdb) set follow-fork-mode child
# fork 전에 브레이크포인트
(gdb) break fork
(gdb) run

에러 5: 워치포인트 “Cannot watch constant value”

원인: 상수나 레지스터만 있는 값은 워치 불가. 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 메모리에 있는 변수에만 워치 설정
# 지역 변수는 해당 스코프에서 설정
(gdb) break main
(gdb) run
(gdb) watch arr[5]

에러 6: “Program received signal SIGSEGV” — 원인 불명

해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

(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

디버깅 체크리스트

아래 코드는 markdown를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

- [ ] -g -O0로 빌드했는가?
- [ ] backtrace로 크래시 위치 확인
- [ ] frame N으로 해당 프레임 이동
- [ ] info locals, info args로 변수 확인
- [ ] 조건부 브레이크로 특정 케이스만 추적
- [ ] 워치포인트로 메모리 변경 추적

11. 프로덕션 패턴

Core Dump 분석

프로덕션에서 크래시 시 core dump를 저장해 나중에 분석합니다. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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 빌드와 디버그 심볼을 분리해 배포합니다. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 대상 머신
gdbserver :1234 ./myapp
# 개발 머신
gdb ./myapp
(gdb) target remote 192.168.1.100:1234
(gdb) continue

배치 모드로 자동 분석

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# analyze.gdb
set pagination off
run
backtrace full
quit
# 실행
gdb -batch -x analyze.gdb ./myapp

프로덕션 디버깅 시 주의사항

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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++ 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 입문

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3