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

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

이 글의 핵심

C++ macOS에서 printf 디버깅의 한계를 넘어서. LLDB 브레이크포인트, 워치포인트, backtrace, frame variable, step/next/continue 완전 예제, GDB와 비교, 흔한 에러, 모범 사례, 프로덕션 패턴까지 실전 코드로 다룹니다.

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

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

macOS에서 세그폴트가 발생하는 버그를 찾고 있었습니다. std::cout수십 개 추가했지만 출력이 버퍼링되어 정확한 크래시 위치를 알 수 없었습니다. LLDB(Low Level Debugger)는 macOS·iOS의 기본 디버거로, “어느 줄에서 멈췄는지, 그때 변수 값과 호출 스택이 어떤지”를 멈춘 상태에서 직접 볼 수 있게 해 줍니다. 브레이크포인트(실행을 멈출 지점)·워치포인트(변수 변경 감지)·백트레이스(호출 스택)·frame variable(변수 출력)·step/next/continue(단계별 실행)만 익혀도 printf 디버깅보다 훨씬 빠르게 버그 위치를 좁힐 수 있습니다. 요구 환경: LLDB(macOS: Xcode 명령줄 도구 xcode-select --install). Linux에서도 apt install lldb로 설치 가능. 디버그 정보가 있어야 하므로 빌드 시 -g 옵션 사용(clang++ -g, CMake에서는 Debug 구성). 이 글을 읽으면:

  • LLDB의 핵심 명령어(breakpoint, watchpoint, backtrace, frame variable, step/next/continue)를 완전히 익힐 수 있습니다.
  • 문제 시나리오별로 LLDB를 활용하는 방법을 알 수 있습니다.
  • GDB와 LLDB의 명령어 차이를 파악할 수 있습니다.
  • 흔한 에러와 해결법을 적용할 수 있습니다.
  • 프로덕션 환경에서의 디버깅 패턴을 적용할 수 있습니다.

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

목차

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

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

LLDB로 해결: run → 크래시 → btframe variable 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;
}

LLDB로 해결: breakpoint set -n sumListrunframe variable head → null이면 호출자 확인.

시나리오 3: 무한 루프

상황: 프로그램이 멈춘 것처럼 보입니다. i++를 누락했을 가능성이 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

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

시나리오 4: 메모리 오염—어디선가 값이 덮어씌워짐

상황: arr[5]가 0이어야 하는데 어딘가에서 999로 바뀝니다. 어디서 바뀌는지 모릅니다.

// memory_corruption.cpp
int arr[10] = {0};
someFunction();  // 이 함수 내부 어딘가에서 arr[5] 오염

LLDB로 해결: watchpoint set variable 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일 때만 버그
}

LLDB로 해결: breakpoint set -f main.cpp -l 10 -c 'i == 500'run → 500번째에서만 멈춤.

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

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

flowchart TB
    subgraph problems[문제 유형]
        P1[세그폴트]
        P2[널 포인터]
        P3[무한 루프]
        P4[메모리 오염]
        P5[조건부 버그]
    end
    subgraph lldb_tools[LLDB 도구]
        L1[backtrace]
        L2[frame variable]
        L3[watchpoint]
        L4[조건부 breakpoint]
    end
    P1 --> L1
    P1 --> L2
    P2 --> L2
    P3 --> L1
    P3 --> L2
    P4 --> L3
    P5 --> L4

2. 디버그 빌드와 LLDB 시작

디버그 빌드

-g 옵션을 주면 실행 파일에 디버그 심볼(소스 줄 번호, 변수 이름)이 들어가서, LLDB에서 “어느 줄에서 멈췄는지”, “변수 이름으로 값을 보는 것”이 가능해집니다. -O0로 최적화를 끄면 변수가 최적화로 사라지거나 코드 순서가 바뀌는 일이 줄어듭니다. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

LLDB 시작

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

# 프로그램 로드
lldb ./myapp
# LLDB 프롬프트에서 실행
(lldb) run
# 인자와 함께 실행
(lldb) run arg1 arg2
# 환경 변수 설정 후 실행
(lldb) settings set target.env-vars LD_LIBRARY_PATH=/path/to/libs
(lldb) run
# 종료
(lldb) quit

LLDB 워크플로우

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

sequenceDiagram
    participant Dev as 개발자
    participant LLDB as LLDB
    participant App as 대상 프로그램
    Dev->>LLDB: lldb ./myapp
    LLDB->>App: 로드 (디버그 심볼)
    Dev->>LLDB: breakpoint set -n main
    Dev->>LLDB: run
    LLDB->>App: 실행 시작
    App->>LLDB: main() 도달 → 중단
    LLDB->>Dev: 프롬프트 반환
    Dev->>LLDB: next / step / frame variable
    LLDB->>Dev: 결과 출력
    Dev->>LLDB: continue
    LLDB->>App: 다음 브레이크포인트까지 실행

3. 브레이크포인트 완전 예제

브레이크포인트란?

브레이크포인트는 프로그램 실행이 특정 위치에 도달했을 때 자동으로 멈추게 하는 지점입니다. printf를 여러 개 넣는 대신, 한 번 설정하면 해당 줄에 도달할 때마다 멈춥니다.

기본 브레이크포인트 설정

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

# 함수에 브레이크포인트
(lldb) breakpoint set --name main
(lldb) b main
(lldb) breakpoint set --name processData
(lldb) b processData
# 파일:라인에 브레이크포인트
(lldb) breakpoint set --file main.cpp --line 15
(lldb) b main.cpp:15
# 정규식으로 여러 함수에 브레이크포인트
(lldb) breakpoint set --name-re 'process.*'

조건부 브레이크포인트

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

# i가 50일 때만 멈춤
(lldb) breakpoint set --file main.cpp --line 20 --condition 'i == 50'
# ptr이 null일 때만 멈춤
(lldb) breakpoint set --name processData --condition 'ptr == nullptr'
# size가 0보다 클 때만 멈춤
(lldb) breakpoint set --file main.cpp --line 10 --condition 'size > 0'

브레이크포인트 관리

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

# 브레이크포인트 목록
(lldb) breakpoint list
(lldb) br list
# 출력 예시:
# 1: name = 'main', locations = 1
#    resolved = 1, hit count = 0
# 2: file = 'main.cpp', line = 15, locations = 1
#    resolved = 1, hit count = 0
# 브레이크포인트 삭제
(lldb) breakpoint delete 1
(lldb) br del 1
# 모든 브레이크포인트 삭제
(lldb) breakpoint delete --all
# 브레이크포인트 비활성화/활성화
(lldb) breakpoint disable 2
(lldb) breakpoint enable 2
# 위치로 삭제
(lldb) breakpoint delete --file main.cpp --line 15

브레이크포인트 동작 원리

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

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

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

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

// breakpoint_demo.cpp - clang++ -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. 빌드
$ clang++ -g -O0 -o bp_demo breakpoint_demo.cpp
# 2. LLDB 실행
$ lldb ./bp_demo
# 3. fillArray 함수에 브레이크포인트
(lldb) breakpoint set --name fillArray
(lldb) b fillArray
# 4. 실행
(lldb) run
# 5. 브레이크포인트에서 멈춤. next로 한 줄씩 진행
(lldb) next
(lldb) next
# i가 5일 때 arr[5] 접근 → 다음 next에서 세그폴트
# 6. 또는 조건부 브레이크로 i==5에서만 멈춤
(lldb) run
(lldb) run
(lldb) breakpoint set --name fillArray --condition 'i == 5'
(lldb) continue

4. 워치포인트 완전 예제

워치포인트란?

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

기본 워치포인트

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

# 변수 변경 시 멈춤
(lldb) watchpoint set variable variable_name
(lldb) watchpoint set expression -- variable_name
# 포인터가 가리키는 값 변경 시 멈춤
(lldb) watchpoint set expression -- *ptr
# 배열 요소 변경 시
(lldb) watchpoint set variable arr[5]

워치포인트 종류

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

# watch: 쓰기 시 멈춤 (기본)
(lldb) watchpoint set variable x
# 읽기 시 멈춤
(lldb) watchpoint set variable x -w read
# 읽기 또는 쓰기 시 멈춤
(lldb) watchpoint set variable x -w read_write

실습: 메모리 오염 추적

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

// watchpoint_demo.cpp - clang++ -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에 브레이크포인트 후 실행
$ lldb ./wp_demo
(lldb) breakpoint set --name main
(lldb) run
# 2. arr[5]에 워치포인트 설정 (main에서 arr이 유효한 상태에서)
(lldb) watchpoint set variable arr[5]
# 3. continue로 진행
(lldb) continue
# 4. arr[5]가 변경되면 멈춤
Watchpoint 2 hit:
old value: 0
new value: 999
corruptData (arr=0x7fff...) at watchpoint_demo.cpp:6
6       arr[5] = 999;
# 5. backtrace로 호출 경로 확인
(lldb) bt
* frame #0: ....corruptData(...) at watchpoint_demo.cpp:6
  frame #1: ....main at watchpoint_demo.cpp:14

워치포인트 제한

  • 하드웨어 워치포인트: CPU가 지원하면 개수 제한 있음(보통 4개). 매우 빠름.
  • 소프트웨어 워치포인트: 개수 제한 없지만 매 단계마다 확인하여 느림.
  • 지역 변수는 스코프를 벗어나면 워치포인트가 자동 해제될 수 있음.

5. 백트레이스(backtrace) 완전 예제

백트레이스란?

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

기본 백트레이스

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

# 호출 스택 보기
(lldb) backtrace
(lldb) bt
# 출력 예시:
# * frame #0: ....buggyFunction(...) at main.cpp:5
#   frame #1: ....main at main.cpp:12

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

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

(lldb) thread backtrace --extended
(lldb) bt all
# 또는 각 프레임에서 frame variable
(lldb) bt
(lldb) frame select 0
(lldb) frame variable
(lldb) frame select 1
(lldb) frame variable

프레임 이동

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

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

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

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

// backtrace_demo.cpp - clang++ -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를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ clang++ -g -O0 -o bt_demo backtrace_demo.cpp
$ lldb ./bt_demo
(lldb) run
Process ....stopped with signal SIGSEGV
* frame #0: ....level3 (p=0x0) at backtrace_demo.cpp:5
5       *p = 42;
# 백트레이스로 호출 경로 확인
(lldb) bt
* frame #0: ....level3 (p=0x0) at backtrace_demo.cpp:5
  frame #1: ....level2 (p=0x0) at backtrace_demo.cpp:9
  frame #2: ....level1 (p=0x0) at backtrace_demo.cpp:13
  frame #3: ....main at backtrace_demo.cpp:18
# main 프레임으로 이동해 p 확인
(lldb) frame select 3
(lldb) frame variable p
(int *) p = 0x0000000000000000
# level3에서 p 확인
(lldb) frame select 0
(lldb) frame variable p
(int *) p = 0x0000000000000000

스택 프레임 구조

다음은 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. frame variable과 변수 검사 완전 예제

frame variable 기본 사용법

LLDB에서는 frame variable(또는 fr v)이 GDB의 print보다 더 직관적입니다. 현재 프레임의 모든 변수를 한 번에 보여주거나, 특정 변수만 지정할 수 있습니다. 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 현재 프레임의 모든 지역 변수
(lldb) frame variable
(lldb) fr v
# 특정 변수만
(lldb) frame variable x
(lldb) fr v x
# 포인터 역참조
(lldb) frame variable *ptr
(lldb) expr *ptr

expression (expr) — 표현식 평가

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

# C++ 표현식 평가 (print와 유사)
(lldb) expression x
(lldb) expr x
(lldb) p x
# 포인터 역참조
(lldb) expr *ptr
# 구조체/클래스 멤버
(lldb) expr person.name
(lldb) expr obj->value
# 배열
(lldb) expr arr[0]
(lldb) expr arr[0]@10   # 10개 원소 (LLDB)

다양한 출력 형식

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

# 16진수
(lldb) expr -f x -- ptr
# 10진수
(lldb) expr -f d -- x
# 문자
(lldb) expr -f c -- c
# 부동소수점
(lldb) expr -f f -- f

memory read (메모리 검사)

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

# x/[개수][형식][크기] 주소
# 형식: x(16진), d(10진), s(문자열)
# 크기: b(1바이트), h(2바이트), w(4바이트), g(8바이트)
# 16진수로 10개 워드
(lldb) memory read -c 10 -f x ptr
# 10진수로 10개
(lldb) memory read -c 10 -f d ptr
# 문자열로
(lldb) memory read -c 10 -f s ptr

실습: frame variable으로 버그 원인 확인

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

// print_demo.cpp - clang++ -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를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ lldb ./print_demo
(lldb) breakpoint set --name process
(lldb) run
# process 내부에서 p 검사
(lldb) frame variable p
(Point *) p = 0x00007ff7bfeff2a0
(lldb) expr *p
(Point) $0 = (x = 10, y = 20)
(lldb) frame variable p->x
(int) p->x = 10
(lldb) next
(lldb) frame variable p->x
(int) p->x = 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 - clang++ -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를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

$ lldb ./step_demo
(lldb) breakpoint set --name main
(lldb) run
# next: add() 안으로 들어가지 않음
(lldb) next
(lldb) next
(lldb) next   # add(x, y) 호출 후 다음 줄(std::cout)로
(lldb) frame variable z
(int) z = 30
# step: add() 안으로 들어감
(lldb) run
(lldb) next
(lldb) next
(lldb) step   # add() 함수 내부로 진입
add (a=10, b=20) at stepping_demo.cpp:4
4       return a + b;
(lldb) frame variable a
(int) a = 10
(lldb) frame variable b
(int) b = 20
(lldb) finish  # add() 반환까지 실행
(lldb) frame variable z
(int) z = 30

N번 반복·continue

(lldb) next -c 5    # next 5번
(lldb) step -c 3    # step 3번
(lldb) continue     # 다음 브레이크포인트까지

8. LLDB vs GDB 명령어 비교

핵심 명령어 대응표

기능GDBLLDB
실행runrun
브레이크포인트 설정break mainbreakpoint set -n main 또는 b main
파일:라인break main.cpp:15b main.cpp:15 또는 breakpoint set -f main.cpp -l 15
조건부 브레이크break main.cpp:20 if i == 50breakpoint set -f main.cpp -l 20 -c 'i == 50'
브레이크포인트 목록info breakpointsbreakpoint list
브레이크포인트 삭제delete 1breakpoint delete 1
워치포인트watch variable_namewatchpoint set variable variable_name
다음 줄nextnext
함수 진입stepstep
함수 끝까지finishfinish
계속 실행continuecontinue
변수 출력print xframe variable x 또는 expr x
모든 지역 변수info localsframe variable
함수 인자info argsframe variable (인자 포함)
스택 추적backtracebacktrace 또는 bt
프레임 이동frame 2frame select 2 또는 f 2
소스 보기listlist
종료quitquit

플랫폼별 선택 가이드

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

flowchart TD
    A[디버깅 환경] --> B{플랫폼?}
    B -->|macOS| C[LLDB 권장]
    B -->|Linux| D[GDB 권장]
    B -->|iOS/embedded| C
    C --> E[Xcode CLI: xcode-select --install]
    D --> F[apt install gdb]
  • macOS: LLDB가 기본. Xcode 명령줄 도구에 포함.
  • Linux: GDB가 표준. LLDB도 apt install lldb로 사용 가능.
  • iOS/embedded: LLDB 전용.

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

전체 예제 코드

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

// full_demo.cpp - clang++ -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;
}

단계별 LLDB 디버깅

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

# 1. 빌드
$ clang++ -g -O0 -o full_demo full_demo.cpp
# 2. LLDB 시작
$ lldb ./full_demo
# 3. processVector에 브레이크포인트
(lldb) breakpoint set --name processVector
# 4. 실행
(lldb) run
# 5. next로 루프 진행 (또는 조건부 break)
(lldb) next
(lldb) next
# ....i가 3일 때 vec[3] 접근 → 세그폴트
# 6. 크래시 후
(lldb) bt
#0  processVector (vec=...) at full_demo.cpp:5
#1  main () at full_demo.cpp:12
(lldb) frame variable i
(size_t) i = 3
(lldb) expr vec.size()
(size_t) $0 = 3
# 7. 원인: i <= vec.size() → i가 3일 때 vec[3] 접근 (범위 초과)
#    수정: i < vec.size()

LLDB 명령어 체크리스트

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

- [ ] breakpoint set -n [함수] — 브레이크포인트 설정
- [ ] run — 실행
- [ ] next / step — 단계별 실행
- [ ] continue — 다음 브레이크포인트까지
- [ ] backtrace — 호출 스택
- [ ] frame variable — 변수 값
- [ ] watchpoint set variable — 변경 감지
- [ ] frame select N — 프레임 이동
- [ ] expr — 표현식 평가

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

에러 1: “No symbol table” / “No debug symbols”

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

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

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

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

(lldb) bt
(lldb) frame select 0
(lldb) frame variable
(lldb) expr ptr   # 0x0인지 확인

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

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

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

에러 4: LLDB가 “run” 후 즉시 종료

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

# 자식 프로세스 추적
(lldb) settings set target.process.follow-fork-mode child
# fork 전에 브레이크포인트
(lldb) breakpoint set --name fork
(lldb) run
(lldb) continue

에러 5: 워치포인트 “Cannot create watchpoint”

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

# 메모리에 있는 변수에만 워치 설정
# 지역 변수는 해당 스코프에서 설정
(lldb) breakpoint set --name main
(lldb) run
(lldb) watchpoint set variable arr[5]

에러 6: “variable not found” / “no variable named ‘x’”

원인: 최적화로 변수 제거, 또는 스코프 밖. 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# frame variable로 현재 프레임 변수 확인
(lldb) frame variable
# expression으로 주소 직접 접근
(lldb) expr *(int*)0x7fff...

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

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

(lldb) thread backtrace --extended
(lldb) frame select 0
(lldb) frame variable
# 크래시 직전 상태로 run 후 break로 그 함수에 멈추고 next로 한 줄씩 진행

에러 8: macOS에서 “Unable to find process” (코드 서명)

원인: macOS 보안 정책으로 인한 디버거 제한. 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 개발자 도구 권한 부여
# 시스템 설정 → 개인 정보 보호 및 보안 → 개발자 도구 → 터미널 허용
# 또는 codesign으로 자체 서명
codesign -s - ./myapp

에러 요약 표

에러원인해결
No symbol table-g 없음clang++ -g -O0
Cannot access 0x0Null 포인터bt, frame variable ptr
optimized out-O2 이상-O0 재빌드
run 후 즉시 종료fork 등follow-fork-mode child
워치 불가상수/레지스터메모리 변수에만
variable not found최적화/스코프frame variable
Unable to find processmacOS 코드 서명codesign 또는 개발자 도구 권한

11. 모범 사례

핵심 원칙

  1. 디버그 빌드: clang++ -g -O0 -Wall main.cpp -o myapp
  2. 조건부 브레이크: breakpoint set -f main.cpp -l 10 -c 'i == 500' — 1000번 루프에서 500번째만 확인
  3. thread backtrace —extended: 크래시 시 모든 프레임의 지역 변수 확인
  4. .lldbinit: settings set target.process.stop-on-sharedlibrary-events false
  5. 로그 저장: log enable lldb command → 디버깅 → log disable lldb command

디버깅 체크리스트

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

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

12. 프로덕션 패턴

Core Dump 분석 (Linux)

Linux에서 LLDB로 core dump를 분석할 수 있습니다. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# core dump 활성화 (Linux)
ulimit -c unlimited
echo /tmp/core.%e.%p | sudo tee /proc/sys/kernel/core_pattern
# 크래시 후
$ lldb ./myapp -c /tmp/core.myapp.12345
(lldb) bt
(lldb) thread backtrace --extended

디버그 심볼 분리

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

# Release 빌드 + 별도 .dSYM (macOS)
# clang++ -g -O2 ....→ myapp + myapp.dSYM
# dSYM은 자동 생성됨. strip으로 바이너리에서 제거
strip ./myapp
# myapp.dSYM은 별도 보관
# 분석 시
lldb -o "target create -d ./myapp.dSYM ./myapp" -o "target modules add -c core.12345"

원격 디버깅 (lldb-server)

# 대상: lldb-server platform --listen *:1234 --server
# 개발: process connect connect://192.168.1.100:1234

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

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

flowchart TD
    A[프로덕션 크래시] --> B{디버그 빌드 있음?}
    B -->|예| C[core dump 수집]
    B -->|아니오| D[재현 환경 구축]
    C --> E[lldb-server 또는 로컬 분석]
    D --> E
    E --> F[backtrace 분석]
    F --> G[원인 파악]
    G --> H[수정 및 배포]

주의사항:

  • 프로덕션 바이너리와 core dump의 빌드가 정확히 일치해야 함
  • -O2 빌드에서는 변수/줄 번호가 어긋날 수 있음 → RelWithDebInfo 권장
  • lldb-server는 프로세스를 중단하므로 트래픽 적은 시간에 사용

LLDB 명령어 치트시트

기능명령
실행run, run arg1 arg2, process kill
브레이크포인트breakpoint set -n main, breakpoint list, breakpoint delete 1
워치포인트watchpoint set variable var
실행 제어next(n), step(s), finish, continue(c)
검사frame variable(fr v), expr, backtrace(bt), frame select N
기타list(l), quit

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

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


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

C++ LLDB, LLDB 기초, macOS 디버깅, 브레이크포인트, 워치포인트, 백트레이스, frame variable, step next, 디버깅, 세그폴트, core dump, GDB LLDB 비교 등으로 검색하시면 이 글이 도움이 됩니다.

정리

도구용도
breakpoint set특정 위치에서 실행 중단
watchpoint set변수 변경 시 중단
backtrace호출 스택 확인
frame variable변수 값 출력
step/next단계별 실행
continue다음 브레이크포인트까지
핵심 원칙:
  1. printf 대신 LLDB
  2. 브레이크포인트 + 조건부 브레이크
  3. backtrace로 호출 경로 파악
  4. frame variable으로 변수 검증
  5. 워치포인트로 메모리 오염 추적
  6. 프로덕션은 core dump + dSYM 분리

자주 묻는 질문 (FAQ)

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

A. macOS에서 세그폴트·크래시 원인 파악, 무한 루프 디버깅, 특정 조건에서만 발생하는 버그 추적, 메모리 오염 추적 등 printf로 찾기 어려운 버그를 LLDB 기본 명령어로 빠르게 해결할 때 사용합니다.

Q. GDB와 LLDB의 차이는?

A. LLDB는 macOS·iOS의 기본 디버거로, GDB와 명령어가 유사합니다. Linux에서는 GDB, macOS에서는 LLDB를 주로 사용합니다. 이 글의 LLDB vs GDB 비교표를 참고하세요.

Q. 프로덕션에서 LLDB를 붙여도 되나요?

A. lldb-server로 붙이면 프로세스가 중단되므로, 가능하면 core dump를 수집해 오프라인에서 분석하는 것이 좋습니다. 트래픽이 적은 시간에만 lldb-server 사용을 권장합니다. 한 줄 요약: LLDB 브레이크포인트·워치포인트·백트레이스·frame variable·step/next로 macOS에서 버그를 빠르게 찾을 수 있습니다. 다음으로 GDB 기초(#4-1) 또는 GDB/LLDB 디버거 가이드(#16-1)를 읽어보면 좋습니다. 다음 글: [C++ 실전 가이드 #16-1] GDB/LLDB 디버거 완벽 가이드 이전 글: [C++ 실전 가이드 #4-1] GDB 기초 완벽 가이드

관련 글

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