GDB·LLDB 실전 팁 — STL·최적화 코드·시간 역행 디버깅
마지막 장에서는 매일의 답답함을 줄여 주는 작은 팁을 모았습니다. STL이 줄줄이 보이지 않는 문제, -O2 코드의 “value optimized out”, .gdbinit 합리적 기본값, 그리고 GDB의 가장 새로운 무기인 시간 역행 디버깅.
#STL — 알아서 예쁘게
최신 GDB(>= 7.0)는 libstdc++ pretty-printer를 자동으로 활성화합니다. 보통은 별 설정 없이 다음처럼 나옵니다.
(gdb) print v$1 = std::vector of length 3, capacity 4 = {1, 2, 3}
(gdb) print m$2 = std::map with 2 elements = {[1] = "one", [2] = "two"}
(gdb) print s$3 = "hello"배포판이 자동 로드를 안 했다면 Ch 9의 ~/.gdbinit 설정으로 직접 등록.
#벡터 내용을 N개만
(gdb) print *v._M_impl._M_start@10$4 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}@n 연산자가 연속한 n개 요소를 배열처럼 표시. pretty-printer가 막혔거나, 길이 5000짜리 벡터의 머리만 보고 싶을 때 씁니다.
#큰 컨테이너의 머리만
pretty-printer 출력 길이 제한.
(gdb) set print elements 20(gdb) print huge_vector$5 = std::vector of length 10000, capacity 10000 = {0, 1, 2, ..., 19, ...}set print elements 0이면 무제한.
#깊은 중첩
(gdb) set print pretty on(gdb) print *graphpretty on으로 들여쓰기 보기. set print depth N으로 nested 깊이 제한.
#”value optimized out” — 최적화 코드
-O2로 빌드된 코드에서 변수를 찍으면 자주 만나는 메시지.
(gdb) print x$6 = <optimized out>컴파일러가 변수를 레지스터에 두지 않거나, 식을 인라인 후 없애 버린 결과입니다. 해법 셋.
#1. -Og로 다시 빌드
-O0은 “변수가 다 살아 있지만 너무 느림”, -O2는 “빠르지만 변수가 사라짐”. -Og가 디버깅 친화적 최적화 — 변수 보존을 우선합니다.
$ gcc -Og -g3 -fno-omit-frame-pointer ...프로덕션 빌드 그대로 디버깅해야 한다면 다음 단계.
#2. -g3로 더 많은 정보
-g는 기본 레벨, -g3는 매크로 정의·인라인 함수 정보까지 포함. 인라인된 함수도 디버거가 인지합니다.
#3. 레지스터 직접 보기
<optimized out>이지만 실제 값은 어디엔가 있습니다. 디스어셈블로 어느 레지스터에 있는지 찾아 그 레지스터를 직접 봅니다.
(gdb) disas=> 0x55... <+24>: mov %rdi,%r12 0x55... <+27>: mov %esi,%ebp(gdb) print $r12(gdb) print (int)$ebp귀찮지만 강력. ARM이면 $r0-$r3이 인자.
#4. 인라인 함수 step-in
-O2로 인라인된 함수는 같은 줄에 여러 호출이 압축됩니다. step이 어디로 갈지 헷갈리면 info line으로 PC ↔ 소스 매핑을 확인.
(gdb) info line *0x55555558a3a2#frame info — 진짜 어디서 멈췄나
인라인 호출이 깊게 쌓이면 bt가 한 줄에 두세 함수를 같이 보여 줍니다 (inlined by ...).
#0 0x55... in foo (inlined by bar at f.cpp:30) (inlined by baz at f.cpp:50) at f.cpp:10info frame으로 PC·SP·FP를 직접 확인하면 어디인지 모호함이 줄어듭니다 (Ch 4 참고).
#.gdbinit 추천
다음을 ~/.gdbinit에 넣어 두면 매번 입력하지 않아도 됩니다.
# 히스토리set history save onset history filename ~/.gdb_historyset history size 10000
# 출력set print pretty onset print object onset print array onset print array-indexes onset print elements 200
# 자동 정지 동작set pagination offset confirm off
# 라이브러리 자동 로드 허용 (pretty-printer 등)set auto-load safe-path /
# 색set style sources on
# follow-forkset follow-fork-mode parentset detach-on-fork on
# C++ STL pretty-printer (배포판이 자동 등록하지 않은 경우)pythonimport syssys.path.insert(0, '/usr/share/gcc-13/python')try: from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers(None)except ImportError: passendLLDB는 ~/.lldbinit.
settings set target.skip-prologue falsesettings set stop-line-count-after 5settings set stop-line-count-before 5settings set thread-format "thread #${thread.index}: tid=${thread.id}, '${thread.name}'\n"#빠른 작업 — 한 줄 디버깅
# 핵심 함수까지 자동으로 가서 멈춤$ gdb -ex 'break main' -ex 'run' --args ./my_prog arg1
# 배치 모드 — 한 번 실행 후 종료$ gdb -batch -ex 'run' -ex 'bt' --args ./my_prog[자동으로 콜스택 출력]
# 스크립트로$ gdb -batch -x my_script.gdb ./my_progCI에서 segfault난 바이너리를 자동으로 디버깅해 콜스택만 뽑는 데 유용합니다.
# 사후 분석 자동화$ gdb -batch -ex 'bt' -ex 'info locals' -ex 'quit' \ ./my_prog /tmp/core > /tmp/postmortem.log#reverse-* — GDB 자체의 시간 역행
GDB는 자체적으로도 record 기능이 있습니다 (rr 없이).
(gdb) target record-full(gdb) continue(gdb) reverse-step(gdb) reverse-continue원리는 모든 명령어 실행 전후를 기록 — 그래서 엄청 느림. 짧은 구간에서만 실용적입니다. 멀리 갈 거면 rr이 거의 모든 면에서 앞섭니다.
#rr — 진짜 time-travel
Ch 6에서도 언급한 rr. 다시 짧게.
$ rr record ./my_prog$ rr replay(rr) continue(rr) watch -l my_var(rr) reverse-continue # my_var 마지막 쓰기까지 거꾸로같은 명령어 흐름이 완전히 재현되므로 비결정적 버그·race를 잡는 거의 유일한 도구. x86 Linux 전용 제약은 있지만 그만한 가치는 있습니다.
#자주 만나는 함정 모음
| 증상 | 원인 / 해법 |
|---|---|
?? 함수 이름 | 디버그 심볼 없음 → -g 추가, stripped면 별도 debuginfo |
value optimized out | -Og 또는 레지스터 직접 보기 |
No symbol "x" in current context | 스코프 밖 (블록 종료 또는 인라인) |
| 브레이크포인트가 무시됨 | 다른 단위·다른 인라인 사본에 걸림 — info breakpoints로 위치 확인 |
콜스택 끝이 0x0 | 스택 손상 또는 -fno-omit-frame-pointer 없음 |
(gdb) 명령에 색이 없다 | set style sources on / GDB 8.0+ 필요 |
| GDB 자체가 무한 정지 | Ctrl-C 한 번, 그래도 안 풀리면 kill -USR2 |
| ASLR로 주소가 매번 달라짐 | set disable-randomization on(기본 on) |
| signals가 그냥 빠져나감 | handle SIGPIPE nostop noprint 등으로 무시 (Ch 5) |
#빌드 옵션 권장 정리
# 평소 디버깅 빌드-O0 -g3 -fno-omit-frame-pointer -fno-inline -fno-optimize-sibling-calls
# 프로덕션이지만 디버깅도 가능-Og -g3 -fno-omit-frame-pointer
# 프로덕션 + 별도 debuginfo-O2 -g # 컴파일$ objcopy --only-keep-debug a.out a.debug$ strip --strip-debug a.out$ objcopy --add-gnu-debuglink=a.debug a.out마지막 패턴이 배포 빌드의 표준. 출시본은 stripped, debuginfo는 서버 보관 → core dump 시 build-id로 매칭.
#마지막으로 — 더 깊게
이 시리즈 다음에 보면 좋은 것들.
- Sanitizer(
AddressSanitizer/UBSan/TSan) — 디버거의 대체가 아닌 짝. 메모리·UB·race 자동 검출. - Valgrind Memcheck — 검출 정밀도는 ASan 이상, 속도는 더 느림.
- perf — 디버거가 아니라 사후 통계. hot path / cache miss / branch miss.
- eBPF + bpftrace — 커널·사용자 공간 trace.
- rr / Pernosco — 시간 역행 + 클라우드 분석.
#정리 (시리즈 전체)
- GDB·LLDB는 어떻게 실행 중인 프로세스를 들여다보고 왜 그렇게 됐는지 묻는 도구.
- 브레이크포인트·워치포인트는 정적 검사로 못 잡는 동적 사실을 봅니다.
- 콜스택·프레임은 “지금 어떻게 여기까지 왔나”의 지도.
- 멀티스레드·core dump·원격·임베디드는 같은 GDB가 형태만 바꿔 다룹니다.
- Python 확장이 디버거의 진짜 천장입니다.
- 적절한 빌드 플래그(
-Og -g3 -fno-omit-frame-pointer)가 디버거의 행복을 결정합니다.
이 시리즈로 일상 디버깅의 80%를 덮었습니다. 나머지 20%는 직접 손을 더럽혀야 알게 됩니다.
#관련 항목 (시리즈 전체)
- Ch 1: 소개와 설치
- Ch 2: 기본 명령 — 10가지
- Ch 3: 상태 검사
- Ch 4: 콜스택과 프레임
- Ch 5: 브레이크포인트와 워치포인트
- Ch 6: 멀티스레드 / 멀티프로세스
- Ch 7: core dump 분석
- Ch 8: 원격 / 임베디드 디버깅
- Ch 9: Python 스크립팅
- Ch 10: TUI / 프런트엔드
- 다음: Ch 12: DWARF 디버그 정보
#외부 자료
GDB and LLDB · 11 of 12
- 1GDB vs LLDB 분석 — 두 디버거의 설치·차이·선택 기준
- 2GDB·LLDB 기본 명령 — break·step·next·print 동작 비교
- 3디버거로 상태 들여다보기 — 변수·메모리·레지스터·STL 추적
- 4GDB·LLDB Backtrace와 프레임 이동 — Call Stack 분석
- 5Breakpoint와 Watchpoint 분석 — Conditional·Hardware·Catchpoint
- 6멀티스레드·멀티프로세스 디버깅 — Non-Stop·Scheduler-Locking·Fork
- 7Core Dump 분석 기법 — gcore·coredumpctl·디버거 활용
- 8GDB 원격 디버깅 — gdbserver·OpenOCD·J-Link 통합
- 9GDB·LLDB Python 스크립팅 — Pretty-Printer·Custom Command
- 10GDB·LLDB TUI와 프런트엔드 — gdb-dashboard·gef·pwndbg·VS Code
- 11GDB·LLDB 실전 팁 — STL·최적화 코드·시간 역행 디버깅
- 12DWARF 디버그 정보 — 디버거가 변수와 라인을 찾는 방식
관련 글
디버거로 상태 들여다보기 — 변수·메모리·레지스터·STL 추적
print 만으로 부족한 자리들 — x/memory examine, ptype, STL 컨테이너, pretty printer.
DWARF 디버그 정보 — 디버거가 변수와 라인을 찾는 방식
DWARF 표준, DIE / abbrev / line / location, expression VM, CFI, split-DWARF.
GDB·LLDB TUI와 프런트엔드 — gdb-dashboard·gef·pwndbg·VS Code
TUI 모드, cgdb, gdb-dashboard, gef/pwndbg, VSCode, nvim-dap, DAP 프로토콜.