C++ GDB/LLDB | cout 100개 찍어도 못 찾은 버그, 디버거로 5분 만에 해결

C++ GDB/LLDB | cout 100개 찍어도 못 찾은 버그, 디버거로 5분 만에 해결

이 글의 핵심

C++ GDB/LLDB에 대해 정리한 개발 블로그 글입니다. 세그폴트가 발생하는 버그를 찾고 있었습니다. std::cout을 수십 개 추가했지만 원인을 찾지 못했습니다. 디버거는 "어느 줄에서 멈췄는지, 그때 변수 값과 스택이 어떤지"를 멈춘 상태에서 직접 볼 수 있게 해 줍니다.… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드: C++, …

들어가며: printf 디버깅의 한계

”cout 100개를 찍어도 버그를 못 찾겠어요”

세그폴트가 발생하는 버그를 찾고 있었습니다. std::cout수십 개 추가했지만 원인을 찾지 못했습니다.
디버거는 “어느 줄에서 멈췄는지, 그때 변수 값과 스택이 어떤지”를 멈춘 상태에서 직접 볼 수 있게 해 줍니다. 브레이크포인트(실행을 멈출 지점)·조건부 중단·백트레이스(호출 스택—어떤 함수가 어떤 함수를 불렀는지 순서)만 익혀도 printf 디버깅보다 훨씬 빠르게 버그 위치를 좁힐 수 있고, GDB/LLDB는 리눅스·맥·임베디드에서 공통으로 쓰이므로 한 번 익혀 두면 유용합니다.

요구 환경: GDB(Linux/WSL: apt install gdb 또는 sudo yum install gdb) 또는 LLDB(macOS: Xcode 명령줄 도구 xcode-select --install). 디버그 정보가 있어야 하므로 빌드 시 -g 옵션 사용(g++ -g, CMake에서는 Debug 구성 또는 RelWithDebInfo).

GDB vs LLDB: Linux·WSL에서는 GDB, macOS에서는 LLDB(Xcode 명령줄 도구에 포함)를 쓰는 경우가 많습니다. 사용법은 거의 비슷하고, 이 글의 예시는 GDB 기준으로 적었지만 LLDB에서도 breakpointrunframe variable 같은 흐름으로 대응할 수 있습니다.

printf vs 디버거 비교

flowchart TB
    subgraph printf["printf/cout 디버깅"]
        P1[코드 수정] --> P2[재컴파일]
        P2 --> P3[실행]
        P3 --> P4[출력 확인]
        P4 --> P5{버그 발견?}
        P5 -->|아니오| P1
        P5 -->|예| P6[코드 수정 제거]
    end

    subgraph debugger["디버거 사용"]
        D1[브레이크포인트 설정] --> D2[실행]
        D2 --> D3[멈춘 시점에서 변수/스택 확인]
        D3 --> D4{버그 발견?}
        D4 -->|아니오| D5[조건 변경 후 재실행]
        D5 --> D2
        D4 -->|예| D6[즉시 수정]
    end

문제의 접근:

void processData(int* arr, int size) {
    std::cout << "1\n";
    for (int i = 0; i < size; ++i) {
        std::cout << "2: i=" << i << "\n";
        arr[i] *= 2;
        std::cout << "3\n";
    }
    std::cout << "4\n";
}
// 어디선가 크래시... 하지만 출력이 버퍼링되어 정확한 위치 모름

주의사항: std::endl 남발은 성능·동기화에 영향을 주므로, 디버깅 후에는 '\n' 또는 디버거 사용으로 전환하세요.

GDB로 해결:

$ gdb ./myapp
(gdb) run
Program received signal SIGSEGV

(gdb) backtrace
#0  processData() at main.cpp:15
#1  main() at main.cpp:30

(gdb) print i
$1 = 100

(gdb) print size
$2 = 100

(gdb) print arr[100]  # 범위 밖!

5분 만에 버그 발견: 배열 인덱스 범위 초과

이 글을 읽으면:

  • GDB/LLDB의 기본 명령어를 사용할 수 있습니다.
  • 브레이크포인트를 설정하고 단계별 실행을 할 수 있습니다.
  • 변수와 메모리를 검사할 수 있습니다.
  • 실전에서 버그를 빠르게 찾을 수 있습니다.

목차

  1. 디버거 시작하기
  2. 브레이크포인트
  3. 단계별 실행
  4. 변수 검사
  5. 실전 디버깅 시나리오
  6. 자주 발생하는 에러와 해결법
  7. 디버깅 팁과 단축키
  8. 프로덕션 환경 디버깅 패턴

1. 디버거 시작하기

디버깅 워크플로우 개요

sequenceDiagram
    participant Dev as 개발자
    participant GDB as GDB/LLDB
    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: 다음 브레이크포인트까지 실행

디버그 빌드

-g 옵션을 주면 실행 파일에 디버그 심볼(소스 줄 번호, 변수 이름)이 들어가서, GDB/LLDB에서 “어느 줄에서 멈췄는지”, “변수 이름으로 값을 보는 것”이 가능해집니다. **-O0**로 최적화를 끄면, 변수가 최적화로 사라지거나 코드 순서가 바뀌는 일이 줄어들어 디버깅이 훨씬 수월합니다. Release 빌드에서만 재현되는 버그가 아니라면 보통 -g -O0로 빌드해 디버거에 붙입니다.

# 디버그 정보 포함 (-g)
g++ -g -O0 main.cpp -o myapp

# 최적화 끄기 (-O0) 중요!
# 최적화 켜면 변수가 사라지거나 코드 순서가 바뀜

# CMake 사용 시
cmake -DCMAKE_BUILD_TYPE=Debug ..
# 또는 RelWithDebInfo (최적화 + 디버그 심볼)
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..

GDB 시작

**gdb ./myapp으로 실행 파일을 로드한 뒤, run**으로 프로그램을 실행합니다. 인자가 필요하면 run arg1 arg2처럼 씁니다. 크래시가 나면 그 시점에서 멈추고, backtrace(또는 bt)로 호출 스택을 보고, **print 변수명으로 현재 프레임의 변수 값을 확인할 수 있습니다. 문제를 다 보면 quit**로 GDB를 종료합니다.

# 프로그램 로드
gdb ./myapp

# 실행
(gdb) run

# 인자와 함께 실행
(gdb) run arg1 arg2

# 환경 변수 설정 후 실행
(gdb) set env LD_LIBRARY_PATH=/path/to/libs
(gdb) run

# 종료
(gdb) quit

LLDB 시작

# 프로그램 로드
lldb ./myapp

# 실행
(lldb) run

# 인자와 함께 실행
(lldb) run arg1 arg2

# 종료
(lldb) quit

2. 브레이크포인트

브레이크포인트 동작 원리

flowchart LR
    subgraph normal["일반 실행"]
        N1[명령 실행] --> N2[다음 명령]
        N2 --> N1
    end

    subgraph bp["브레이크포인트 도달 시"]
        B1[명령 실행] --> B2{브레이크포인트?}
        B2 -->|예| B3[실행 중단]
        B3 --> B4[개발자 제어]
        B2 -->|아니오| B1
    end

기본 브레이크포인트

# 함수에 브레이크포인트
(gdb) break main
(gdb) break processData

# 파일:라인에 브레이크포인트
(gdb) break main.cpp:15

# 조건부 브레이크포인트
(gdb) break main.cpp:20 if i == 50

# 브레이크포인트 목록
(gdb) info breakpoints

# 브레이크포인트 삭제
(gdb) delete 1  # 번호로 삭제
(gdb) clear main.cpp:15  # 위치로 삭제

# 브레이크포인트 비활성화/활성화
(gdb) disable 1
(gdb) enable 1

LLDB 브레이크포인트

# 함수에 브레이크포인트
(lldb) breakpoint set --name main
(lldb) b main  # 단축

# 파일:라인
(lldb) breakpoint set --file main.cpp --line 15
(lldb) b main.cpp:15

# 조건부
(lldb) breakpoint set --name processData --condition 'i == 50'

# 목록
(lldb) breakpoint list

# 삭제
(lldb) breakpoint delete 1

워치포인트 (메모리 변경 감지)

# GDB: 변수 변경 시 멈춤
(gdb) watch variable_name
(gdb) watch *ptr  # 포인터가 가리키는 값

# 하드웨어 워치포인트 (지원 시 더 빠름)
(gdb) rwatch variable_name  # 읽기 시
(gdb) awatch variable_name  # 읽기/쓰기 시

# LLDB
(lldb) watchpoint set variable variable_name
(lldb) watchpoint set expression -- ptr

3. 단계별 실행

단계별 실행 흐름

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

GDB 실행 제어

# 다음 줄 실행 (함수 안으로 들어가지 않음)
(gdb) next
(gdb) n

# 다음 줄 실행 (함수 안으로 들어감)
(gdb) step
(gdb) s

# 현재 함수 끝까지 실행
(gdb) finish

# 계속 실행 (다음 브레이크포인트까지)
(gdb) continue
(gdb) c

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

# 현재 위치 보기
(gdb) list
(gdb) l

LLDB 실행 제어

# 다음 줄
(lldb) next
(lldb) n

# 함수 안으로
(lldb) step
(lldb) s

# 함수 끝까지
(lldb) finish

# 계속 실행
(lldb) continue
(lldb) c

# 현재 위치
(lldb) list
(lldb) l

4. 변수 검사

스택 프레임 구조

flowchart TB
    subgraph stack["호출 스택 (아래→위)"]
        F0["main() - 프레임 2"]
        F1["processData() - 프레임 1"]
        F2["buggyFunction() - 프레임 0 (현재)"]
    end

    F0 --> F1
    F1 --> F2

    subgraph vars["프레임 0 지역 변수"]
        V1["i = 10"]
        V2["size = 10"]
        V3["arr = 0x7fff..."]
    end

변수 출력

# 변수 값 출력
(gdb) print x
(gdb) p x

# 포인터 역참조
(gdb) print *ptr

# 배열 출력
(gdb) print arr[0]
(gdb) print arr[0]@10  # 10개 원소

# 구조체
(gdb) print person
(gdb) print person.name

# STL 컨테이너 (GDB 7.0+)
(gdb) print vec
(gdb) print *vec._M_impl._M_start  # vector 내부

타입 확인

# 변수 타입
(gdb) ptype x

# 구조체 정의
(gdb) ptype Person

메모리 검사

# 메모리 내용 보기
(gdb) x/10x ptr  # 16진수로 10개
(gdb) x/10d ptr  # 10진수로 10개
(gdb) x/10s ptr  # 문자열로 10개

# 형식
# x/[개수][형식][크기] 주소
# 형식: x(16진), d(10진), s(문자열), i(명령어)
# 크기: b(1바이트), h(2바이트), w(4바이트), g(8바이트)

스택 추적

# 호출 스택 보기
(gdb) backtrace
(gdb) bt

# 상세 정보 (모든 프레임의 지역 변수)
(gdb) backtrace full

# 특정 프레임으로 이동
(gdb) frame 2
(gdb) f 2

# 현재 프레임 정보
(gdb) info frame

# 모든 지역 변수
(gdb) info locals

# 함수 인자
(gdb) info args

LLDB 변수 검사

# 현재 프레임의 모든 변수
(lldb) frame variable
(lldb) fr v

# 특정 변수
(lldb) frame variable x
(lldb) p x

# 표현식 평가
(lldb) expression x + 1
(lldb) expr ptr->member

5. 실전 디버깅 시나리오

시나리오 1: 세그폴트 디버깅 (배열 범위 초과)

완전한 예제 코드:

// 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) {  // ❌ <= 버그 (i==size일 때 범위 초과)
        arr[i] = i * 2;
    }
}

int main() {
    int arr[10];
    buggyFunction(arr, 10);  // 크래시!
    std::cout << "done\n";
    return 0;
}

단계별 디버깅 과정:

# 1. 디버그 빌드
$ g++ -g -O0 -o buggy buggy_array.cpp

# 2. GDB로 실행
$ gdb ./buggy
(gdb) run
Program received signal SIGSEGV, Segmentation fault.

# 3. 크래시 위치 확인
(gdb) backtrace
#0  buggyFunction (arr=0x7fffffffe2a0, size=10) at buggy_array.cpp:5
#1  main () at buggy_array.cpp:12

# 4. 현재 프레임에서 변수 검사
(gdb) print i
$1 = 10  # ← 버그 발견! (유효 인덱스는 0-9)

(gdb) print size
$2 = 10

(gdb) print arr[10]
Cannot access memory at address 0x...

# 5. 원인: i <= size → i가 10일 때 arr[10] 접근 (범위 초과)

수정:

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

시나리오 2: Null 포인터 역참조

// null_ptr.cpp - head가 null일 때 head->value 접근
int sumList(Node* head) {
    int sum = 0;
    while (head != nullptr) {
        sum += head->value;  // head가 null이면 크래시
        head = head->next;
    }
    return sum;
}

디버깅:

(gdb) break sumList
(gdb) run
(gdb) next
(gdb) print head
$1 = (Node *) 0x0  # null 포인터!
(gdb) backtrace full

시나리오 3: 무한 루프

// infinite_loop.cpp
void processData() {
    int i = 0;
    while (i < 100) {
        process(i);
        // i++를 깜빡함!
    }
}

디버깅:

# 1. Ctrl+C로 중단
(gdb) run
^C
Program received signal SIGINT, Interrupt.

# 2. 현재 위치 확인
(gdb) backtrace
#0  processData () at main.cpp:5

# 3. 변수 확인
(gdb) print i
$1 = 0  # 변하지 않음!

# 4. i++ 누락 발견

시나리오 4: 조건부 브레이크 (1000번 중 500번째만 버그)

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

디버깅:

# i=500일 때만 멈춤
(gdb) break main.cpp:10 if i == 500
(gdb) run

# 멈춘 후 검사
(gdb) print i
$1 = 500
(gdb) step  # process() 함수 안으로
(gdb) backtrace  # 호출 스택 확인

시나리오 5: 메모리 오염 (워치포인트)

// memory_corruption.cpp
int arr[10] = {0};
// 어딘가에서 arr[5]를 잘못된 값으로 덮어씀
someFunction();  // 이 함수 내부에서 오염 발생

디버깅:

# 워치포인트: arr[5] 변경 시 멈춤
(gdb) break main
(gdb) run
(gdb) watch arr[5]
(gdb) continue

# arr[5]가 변경되면 멈춤
Hardware watchpoint 2: arr[5]
Old value = 0
New value = 999
someFunction () at memory_corruption.cpp:15

시나리오 6: STL vector 범위 초과

// vector_bug.cpp
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3};
    for (size_t i = 0; i <= vec.size(); ++i) {  // ❌ <= 버그
        std::cout << vec[i] << "\n";
    }
    return 0;
}

디버깅:

(gdb) run
Program received signal SIGSEGV

(gdb) print i
$1 = 3

(gdb) print vec.size()
$2 = 3

(gdb) print vec
$3 = std::vector of length 3, capacity 3 = {1, 2, 3}
# vec[3] 접근 → 범위 초과

시나리오 7: 멀티스레드 데드락

// deadlock.cpp
#include <thread>
#include <mutex>

std::mutex m1, m2;

void thread1() {
    m1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    m2.lock();  // thread2가 m2를 잡고 m1 대기 중이면 데드락
    m2.unlock();
    m1.unlock();
}

void thread2() {
    m2.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    m1.lock();
    m1.unlock();
    m2.unlock();
}

int main() {
    std::thread t1(thread1), t2(thread2);
    t1.join();
    t2.join();  // 데드락으로 영원히 대기
    return 0;
}

디버깅:

# Ctrl+C로 중단 후
(gdb) info threads
  Id   Target Id                    Frame
* 1    Thread 0x7f... "deadlock"    __lll_lock_wait ()
  2    Thread 0x7f... "deadlock"    __lll_lock_wait ()

(gdb) thread apply all backtrace
Thread 1:
#0  __lll_lock_wait () at ...
#1  thread1 () at deadlock.cpp:10
...
Thread 2:
#0  __lll_lock_wait () at ...
#1  thread2 () at deadlock.cpp:20
...

# 두 스레드가 서로의 뮤텍스를 기다림 → 데드락 확인

시나리오 8: 재귀 호출 스택 오버플로우

// stack_overflow.cpp
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // 종료 조건 없이 음수 전달 시 무한 재귀
}

int main() {
    int result = factorial(-5);  // ❌ 음수 → 무한 재귀
    return 0;
}

디버깅:

(gdb) run
Program received signal SIGSEGV, Segmentation fault.

(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

# 스택이 가득 참. n이 음수로 전달되어 종료 조건 미도달

시나리오 9: Use-After-Free

// use_after_free.cpp - 지역 변수 주소 반환
int* createValue() {
    int x = 42;
    return &x;  // ❌ 스택 메모리 반환
}
// main에서 *ptr 접근 시 미정의 동작. AddressSanitizer와 함께 사용 권장.

시나리오 10: 포인터 산술 오류

// char*인데 buf += sizeof(int) → 4바이트씩 건너뛰어 범위 초과
void processBuffer(char* buf, int len) {
    char* end = buf + len;
    while (buf < end) {
        *buf = toupper(*buf);
        buf += sizeof(int);  // ❌ char*에 int 크기
    }
}

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

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

원인: -g 옵션 없이 빌드함.

해결법:

# 재빌드 시 -g 추가
g++ -g -O0 main.cpp -o myapp

# 기존 바이너리에 심볼 있는지 확인
file myapp
# myapp: ELF 64-bit LSB executable, x86-64, not stripped
# "not stripped" 또는 "with debug_info" 확인

“Cannot access memory at address 0x0”

원인: Null 포인터 역참조.

해결법:

(gdb) backtrace  # 크래시 위치 확인
(gdb) frame 0
(gdb) info args  # 인자 중 null 확인
(gdb) print ptr  # 0x0인지 확인

“optimized out” (변수 값이 표시되지 않음)

원인: -O2, -O3 등 최적화로 변수가 레지스터에만 있거나 제거됨.

해결법:

# -O0으로 재빌드
g++ -g -O0 main.cpp -o myapp

# 또는 volatile 사용 (해당 변수만)
volatile int debug_var = value;

“Program received signal SIGSEGV” - 원인 불명

해결법:

# 1. backtrace로 크래시 위치 확인
(gdb) backtrace full

# 2. 크래시 직전 상태로 재실행
(gdb) run
# 크래시 후
(gdb) break [크래시 함수]
(gdb) run
# 브레이크포인트에서 next로 한 줄씩 진행하며 변수 확인

GDB가 “run” 후 즉시 종료

원인: 프로그램이 정상 종료되거나, 자식 프로세스에서 크래시.

해결법:

# 자식 프로세스 추적
(gdb) set follow-fork-mode child

# fork 전에 브레이크포인트
(gdb) break fork
(gdb) run
(gdb) continue  # 자식으로 전환 후

LLDB “variable not found”

원인: 최적화로 변수 제거, 또는 스코프 밖.

해결법:

# frame variable로 현재 프레임 변수 확인
(lldb) frame variable

# expression으로 주소 직접 접근
(lldb) expr *(int*)0x7fff...

7. 디버깅 팁과 단축키

GDB 초기화 스크립트 (.gdbinit)

# ~/.gdbinit 또는 프로젝트/.gdbinit
set pagination off
set print pretty on
set print array on

# STL 프린터 (설치 시)
# source /usr/share/gdb/python/libstdcxx/__init__.py

유용한 GDB 명령어 조합

# 크래시 시 자동 backtrace
(gdb) run
# SIGSEGV 수신 후
(gdb) bt full
(gdb) info registers  # 레지스터 상태

# 특정 시그널 무시
(gdb) handle SIGPIPE nostop noprint

# 로그 파일로 저장
(gdb) set logging file debug.log
(gdb) set logging on
(gdb) run
# ... 디버깅 ...
(gdb) set logging off

조건부 브레이크 고급 활용

# 포인터가 null이 될 때
(gdb) break main.cpp:20 if ptr == nullptr

# 문자열 비교
(gdb) break main.cpp:30 if strcmp(name, "buggy") == 0

# 반복 N번째에만
(gdb) break process if ++count == 100
# (count는 (gdb) set var count=0 으로 초기화)

디버깅 체크리스트

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

8. 프로덕션 환경 디버깅 패턴

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
# 디버그 심볼이 있는 빌드와 매칭되어야 함

원격 디버깅 (gdbserver)

# 대상 머신 (프로덕션/임베디드)
gdbserver :1234 ./myapp

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

디버그 심볼 분리 (Debug Info 패키지)

# Release 빌드 + 별도 .debug 파일
objcopy --only-keep-debug myapp myapp.debug
strip -g myapp  # myapp에서 디버그 정보 제거
objcopy --add-gnu-debuglink=myapp.debug myapp

# 분석 시
gdb -s myapp.debug -e myapp -c core.12345

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

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는 프로세스를 중단하므로 트래픽 적은 시간에 사용

실무 디버깅 워크플로우

  1. 재현: 최소 입력으로 버그 재현
  2. 격리: 브레이크포인트로 범위 좁히기
  3. 가설: 변수 값으로 원인 추측
  4. 검증: 수정 후 재테스트
  5. 문서화: 원인과 해결법 기록

유용한 명령어 정리

GDB 치트시트

# 실행
run, r                    # 실행
run arg1 arg2            # 인자와 함께 실행
kill                     # 프로그램 종료

# 브레이크포인트
break, b                 # 브레이크포인트 설정
info breakpoints         # 목록
delete 1                 # 삭제

# 실행 제어
next, n                  # 다음 줄
step, s                  # 함수 안으로
finish                   # 함수 끝까지
continue, c              # 계속

# 검사
print, p                 # 변수 출력
ptype                    # 타입 확인
backtrace, bt            # 스택 추적
info locals              # 지역 변수
info args                # 함수 인자

# 기타
list, l                  # 소스 코드
quit, q                  # 종료

GDB vs LLDB 명령어 대응표

기능GDBLLDB
실행runrun
브레이크포인트break mainbreakpoint set -n main
다음 줄nextnext
함수 진입stepstep
변수 출력print xframe variable x
스택backtracebt
종료quitquit

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

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

  • C++ Segmentation fault 원인 5가지와 디버깅 방법 | GDB로 추적하기
  • C++ 로깅·Assertion | 프로덕션 간헐적 크래시, 로그 없이 재현 불가일 때
  • C++ Sanitizers | ASan·TSan으로 메모리 버그·data race 자동 탐지

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

C++ 디버거, GDB LLDB, 브레이크포인트, 스택 추적, 디버깅, 버그 수정, 세그폴트, core dump 등으로 검색하시면 이 글이 도움이 됩니다.

정리

도구플랫폼특징
GDBLinuxGNU 디버거, gdbserver 원격 지원
LLDBMac/LinuxLLVM 디버거, 빠른 성능
Visual StudioWindowsGUI 디버거

핵심 원칙:

  1. printf 대신 디버거
  2. 브레이크포인트 활용
  3. 조건부 브레이크포인트
  4. 스택 추적으로 원인 파악
  5. 워치포인트로 메모리 추적
  6. 프로덕션은 core dump + gdbserver

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. GDB와 LLDB 디버거의 기본 사용법, 브레이크포인트, 변수 검사, 스택 추적, 그리고 실전에서 버그를 빠르게 찾는 방법을 다룹니다. 세그폴트·크래시 원인 파악, 무한 루프, 조건부 버그, 멀티스레드 데드락 분석 등 printf로 찾기 어려운 상황에서 활용합니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. Sanitizers와 함께 사용하면 메모리 버그 탐지에 더 효과적입니다.

한 줄 요약: GDB/LLDB로 브레이크포인트·백트레이스로 버그 위치를 좁힐 수 있습니다. 다음으로 Sanitizers(#16-2)를 읽어보면 좋습니다.

다음 글: [C++ 실전 가이드 #16-2] Sanitizers: 메모리 버그를 자동으로 찾는 도구

이전 글: [C++ 실전 가이드 #15-3] 컴파일 타임 최적화: constexpr과 템플릿 메타프로그래밍


관련 글

  • C++ 디버깅 기초 완벽 가이드 | GDB·LLDB 브레이크포인트·워치포인트로 버그 5분 만에 찾기
  • C++ 로깅·Assertion | 프로덕션 간헐적 크래시, 로그 없이 재현 불가일 때
  • C++ Sanitizers | ASan·TSan으로 메모리 버그·data race 자동 탐지
  • C++ 디버깅 실전 가이드 | gdb, LLDB, Visual Studio 완벽 활용
  • C++ Segmentation fault 원인 5가지와 디버깅 방법 | GDB로 추적하기