GDB 가이드
GDB를 명령어 나열이 아니라 실제 결함 분석 흐름으로 설명합니다. 특히 커널 개발 문맥에 맞춰 vmlinux 심볼 로딩, 모듈 주소 해석, vmcore 후분석, KGDB/QEMU 원격 연결, Python 자동화, DWARF 디버그 정보 해석을 먼저 잡고, 그 위에 일반 GDB 기능을 실전 순서대로 정리합니다.
-g 옵션으로 디버그 정보를 생성하는 방법을 먼저 익히세요.
GDB는 DWARF 형식의 디버그 정보를 활용해 소스 레벨 디버깅(Debugging)을 수행합니다.
핵심 요약
- GDB — GNU Source-Level Debugger. C, C++, Ada, Fortran, Go, Rust 등 다수 언어를 지원하는 소스 레벨 디버거.
- 중단점(Breakpoint) — 특정 위치에서 실행을 일시 정지.
break/tbreak/hbreak/rbreak로 설정. - 감시점(Watchpoint) — 변수나 메모리 주소 값이 변경될 때 정지.
watch/rwatch/awatch로 설정. - 잡기점(Catchpoint) — 예외 발생, 시스템 콜(System Call), fork 등 이벤트 발생 시 정지.
catch명령으로 설정. - 역방향 디버깅 —
record full로 실행을 기록한 뒤reverse-step/reverse-continue로 되감기 가능. - 원격 디버깅 —
gdbserver로 타깃 장치에서 실행하고 GDB로 원격 연결. 임베디드/커널 디버깅에 필수.
단계별 이해
- 디버그 정보 포함 빌드
gcc -g3 -O0 -o prog prog.c로 DWARF 디버그 정보를 포함해 컴파일합니다.-g3은 매크로(Macro) 정보도 포함합니다. - GDB 기동 및 기본 조작
gdb prog로 시작,break main→run→next/step→print→continue흐름을 익힙니다. - 중단점 관리
조건부 중단점(break ... if cond), 임시 중단점(tbreak), 하드웨어 중단점(hbreak)을 상황별로 활용합니다. - 스택 및 데이터 검사
backtrace로 콜 스택,frame N으로 프레임 전환,print/x/display로 변수·메모리를 검사합니다. - 고급 기능 습득
역방향 디버깅(record full), 원격 디버깅(gdbserver), TUI 모드(tui enable), Python 스크립팅을 단계적으로 익힙니다.
커널 디버깅 빠른 시작
이 페이지(Page)는 GDB 일반 기능도 폭넓게 다루지만, 커널 개발자라면 아래 흐름부터 읽는 편이 효율적입니다. 즉 "명령어 사전"보다 "커널에서 어떤 심볼과 아티팩트를 열어야 하는가"를 먼저 고정해야 합니다.
| 상황 | 가장 먼저 할 일 | 바로 이어질 섹션 |
|---|---|---|
| 라이브 커널 KGDB 연결 | vmlinux와 동일 빌드의 심볼 준비 | 리눅스 커널 디버깅 활용 |
| 모듈 함수 주소 해석 | 모듈 로드 주소와 심볼 오프셋(Offset) 확인 | 위치 지정자, 리눅스 커널 디버깅 활용 |
vmcore 후분석 | crash 대신 GDB로 특정 구조체(Struct)를 직접 탐색 | 리눅스 커널 디버깅 활용, Python 확장 |
| QEMU 원격 디버깅 | gdb stub 포트와 심볼 파일 정합성 확인 | 원격 디버깅, 리눅스 커널 디버깅 활용 |
vmlinux, 모듈 심볼, /proc/kallsyms, System.map, vmcore, KGDB transport가 서로 맞아야 하므로, 심볼 정합성을 먼저 확인하는 습관이 중요합니다.
GDB 개요
GDB(GNU Debugger)는 GNU 프로젝트의 표준 소스 레벨 디버거입니다. 1986년 Richard Stallman이 최초 작성했으며, 현재는 Free Software Foundation이 관리합니다. GDB는 다음과 같은 핵심 기능을 제공합니다.
- 프로그램 실행 제어 — 중단점·감시점·잡기점에서 실행 일시 정지, 단계 실행(step/next), 계속 실행(continue)
- 상태 검사 — 변수 값 출력(
print), 메모리 덤프(x), 레지스터(Register) 확인(info registers), 콜 스택(backtrace) - 실행 환경 수정 — 변수 값 변경(
set variable), 메모리 패치(Patch), 함수 호출 강제 - 역방향 디버깅 — 실행 기록 후 되감기(
record full+reverse-step) - 원격 디버깅 — gdbserver 연동, JTAG/OpenOCD 연결, 커널 KGDB
- 코어 덤프(Core Dump) 분석 — 크래시 후 생성된 core 파일로 사후 분석
지원 언어: C, C++, D, Go, Objective-C, Fortran, Ada, Rust, OpenCL C, Pascal, Modula-2, Assembly
지원 아키텍처: x86/x86_64, ARM/AArch64, RISC-V, MIPS, PowerPC, SPARC, S/390 등 다수
실전 샘플 GDB 세션
다음은 버퍼 오버플로(Buffer Overflow)우 버그가 있는 프로그램을 GDB로 처음부터 끝까지 디버깅하는 완전한 세션 예시입니다.
/* buggy.c — 간단한 버퍼 오버플로우 예제 */
#include <stdio.h>
#include <string.h>
void process(const char *input) {
char buf[8];
strcpy(buf, input); /* 위험: 길이 검사 없음 */
printf("result: %s\n", buf);
}
int main(int argc, char **argv) {
if (argc < 2) { fprintf(stderr, "usage: buggy <input>\n"); return 1; }
process(argv[1]);
return 0;
}
$ gcc -g3 -O0 -fno-stack-protector -o buggy buggy.c
$ gdb -q buggy
Reading symbols from buggy...
(gdb) list main ← 소스 확인
8 int main(int argc, char **argv) {
9 if (argc < 2) { fprintf(stderr, "usage: buggy <input>\n"); return 1; }
10 process(argv[1]);
11 return 0;
12 }
(gdb) break process ← process 함수에 중단점
Breakpoint 1 at 0x401142: file buggy.c, line 5.
(gdb) run AAAAAAAAAAAAA ← 긴 입력으로 실행
Starting program: /tmp/buggy AAAAAAAAAAAAA
Breakpoint 1, process (input=0x7fffffffe38a "AAAAAAAAAAAAA") at buggy.c:5
5 char buf[8];
(gdb) info locals ← 지역 변수 확인
buf = "\000\000\000\000\000\000\000\000"
(gdb) info args ← 함수 인자 확인
input = 0x7fffffffe38a "AAAAAAAAAAAAA"
(gdb) next ← 다음 줄로 (strcpy 실행 직전)
6 strcpy(buf, input);
(gdb) next ← strcpy 실행 후
7 printf("result: %s\n", buf);
(gdb) print buf ← buf 내용 확인
$1 = "AAAAAAAA" ← 8바이트까지만 표시 (이미 오버플로우 발생!)
(gdb) x /20xb &buf ← buf 주변 메모리 덤프
0x7fffffffe290: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ← buf[0..7]
0x7fffffffe298: 0x41 0x41 0x41 0x41 0x41 0x00 0x00 0x00 ← 반환 주소 영역 덮임!
0x7fffffffe2a0: 0x01 0x00 0x00 0x00
(gdb) backtrace ← 콜 스택 확인
#0 process (input=0x7fffffffe38a "AAAAAAAAAAAAA") at buggy.c:7
#1 0x4141414141414141 in ?? () ← 반환 주소가 'A'로 오염됨!
(gdb) frame 0
(gdb) info frame ← 현재 프레임 상세
Stack level 0, frame at 0x7fffffffe2a0:
rip = 0x401156 in process (buggy.c:7); saved rip = 0x4141414141414141
...
(gdb) continue
Program received signal SIGSEGV, Segmentation fault.
0x4141414141414141 in ?? () ← 오염된 주소로 점프 시도
(gdb) backtrace
#0 0x4141414141414141 in ?? ()
#1 0x00007fffffffe2a0 in ?? ()
(gdb) quit
info locals로 스택 프레임(Stack Frame) 변수 초기화 직후 확인x /20xb &buf로 buf 주변 20바이트를 1바이트 단위 16진수로 덤프 → 오버플로우 영역 직접 확인- 반환 주소가
0x4141414141414141('A' 8개)로 덮인 것을info frame의saved rip로 확인 backtrace에서??가 나타나면 심볼이 없는 주소(= 오염된 주소)로 반환됐다는 신호
GDB 내부 동작 원리 (ptrace)
GDB가 프로세스(Process)를 제어하는 핵심 메커니즘은 리눅스 커널의 ptrace(2) 시스템 콜입니다. GDB가 중단점을 설정하고 프로그램을 단계 실행하는 방법을 이해하면 더 효과적으로 활용할 수 있습니다.
# ptrace 사용 실제 확인 (strace로 GDB 내부 관찰)
$ strace -e ptrace gdb -q ./buggy
...
ptrace(PTRACE_TRACEME) ← PTRACE_TRACEME (자식)
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACECLONE|...) ← GDB 옵션 설정
ptrace(PTRACE_PEEKDATA, pid, 0x401142, NULL) = 0xe5894855 ← 원본 명령 읽기
ptrace(PTRACE_POKEDATA, pid, 0x401142, 0xe5894855cc) ← 0xCC 삽입 (중단점)
ptrace(PTRACE_CONT, pid, 0, 0) ← 실행 재개
...
ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...) ← 레지스터 읽기
ptrace(PTRACE_PEEKDATA, pid, 0x401142, NULL) ← 원본 바이트 읽기
ptrace(PTRACE_POKEDATA, pid, 0x401142, 0xe5894855) ← 원본 복원
ptrace(PTRACE_SINGLESTEP, pid, 0, 0) ← 1 명령 실행
ptrace(PTRACE_POKEDATA, pid, 0x401142, 0xe5894855cc) ← 0xCC 재설치
| ptrace 요청 | 방향 | GDB 활용 |
|---|---|---|
PTRACE_TRACEME | 자식→커널 | fork 후 자식이 추적 허용 선언 |
PTRACE_ATTACH | GDB→커널 | 실행 중인 프로세스에 attach |
PTRACE_DETACH | GDB→커널 | 프로세스에서 분리 (detach) |
PTRACE_PEEKDATA | GDB→커널 | 대상 메모리 1워드 읽기 |
PTRACE_POKEDATA | GDB→커널 | 대상 메모리 1워드 쓰기 (0xCC 설치) |
PTRACE_GETREGS | GDB→커널 | 대상 레지스터 읽기 |
PTRACE_SETREGS | GDB→커널 | 대상 레지스터 변경 |
PTRACE_CONT | GDB→커널 | 프로세스 계속 실행 |
PTRACE_SINGLESTEP | GDB→커널 | 1 명령 실행 후 정지 (si/ni) |
PTRACE_SYSCALL | GDB→커널 | 시스템 콜 진입/반환 시 정지 |
PTRACE_SETOPTIONS | GDB→커널 | fork/exec/clone 추적 옵션 |
PTRACE_GETREGSET | GDB→커널 | 아키텍처별 레지스터 읽기 (SIMD 포함) |
GDB 기동 및 초기화
기본 기동 방법
GDB는 다양한 방법으로 시작할 수 있습니다.
# 실행 파일만 지정
gdb program
# 실행 파일 + 코어 덤프
gdb program core
# 실행 파일 + 프로세스 PID (attach)
gdb program 1234
gdb -p 1234
# 아무것도 지정하지 않고 시작
gdb
주요 기동 옵션
| 옵션 | 설명 |
|---|---|
-s <em>symfile</em> | 심볼 파일 지정 (실행 파일과 별도) |
-e <em>prog</em> | 실행 파일 지정 (심볼 없이) |
-x <em>file</em> | GDB 명령어 파일 실행 (초기화 스크립트) |
-ex <em>cmd</em> | 기동 시 GDB 명령 즉시 실행 |
-ix <em>file</em> | 초기화 파일 로드 전에 명령 파일 실행 |
-iex <em>cmd</em> | 초기화 파일 로드 전에 명령 실행 |
-p <em>pid</em> | 실행 중인 프로세스에 attach |
-c <em>core</em> | 코어 덤프 파일 지정 |
-q / --quiet / --silent | 기동 메시지 숨김 |
-n / --nx | 초기화 파일(.gdbinit) 무시 |
--nh | 홈 디렉터리 ~/.gdbinit만 무시 |
--batch | 배치 모드 (비대화형): -x 스크립트 실행 후 종료 |
--batch-silent | 배치 모드 + GDB 출력 완전 억제 |
--tui | TUI(Terminal User Interface) 모드로 시작 |
-d <em>dir</em> | 소스 파일 검색 디렉터리 추가 |
-b <em>bps</em> | 직렬 포트(Serial Port) 통신 속도 설정 (원격 디버깅) |
-l <em>timeout</em> | 원격 통신 타임아웃(초) 설정 |
--readnow | 심볼 파일을 즉시 전부 읽음 (느리지만 이후 빠름) |
--readnever | 심볼 파일을 읽지 않음 (속도 최우선) |
--interpreter <em>mi</em> | GDB/MI(Machine Interface) 모드 (IDE 연동용) |
--args <em>prog</em> <em>args...</em> | 프로그램과 인자를 함께 지정 |
--version | GDB 버전 출력 후 종료 |
--configuration | GDB 빌드 설정 출력 후 종료 |
초기화 파일
GDB는 기동 시 다음 순서로 초기화 파일을 처리합니다.
# pretty-printer 활성화 (GCC STL)
python import subprocess
set auto-load safe-path /
# 기본 출력 설정
set print pretty on
set print object on
set print static-members on
set pagination off
# 히스토리 영구 저장
set history save on
set history filename ~/.gdb_history
set history size 10000
# TUI 레이아웃 기본값
# layout src
종료 및 셸 명령
(gdb) quit # 종료 (q, Ctrl-d)
(gdb) exit # quit의 별칭
(gdb) quit 1 # 종료 코드 1로 종료 (배치 모드 유용)
# 셸 명령 실행
(gdb) shell ls -la # !ls -la 와 동일
(gdb) !make
# 로그 기록
(gdb) set logging enabled on
(gdb) set logging file gdb.log # 기본: gdb.txt
(gdb) set logging overwrite on # 매번 덮어쓰기
(gdb) set logging redirect on # GDB 출력을 파일로만 (터미널 출력 안 함)
DWARF 디버그 정보 구조
GDB가 소스 레벨 디버깅을 할 수 있는 이유는 컴파일러가 ELF 바이너리 내에 DWARF(Debugging With Attributed Record Formats) 형식의 메타데이터를 삽입하기 때문입니다. DWARF는 ISO/IEC 국제 표준(현재 DWARF 5)으로, 소스 위치, 타입, 변수 위치, 인라인 정보 등을 기술합니다.
# DWARF 정보 직접 확인 도구
$ readelf --debug-dump=info buggy | head -60
<0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
<c> DW_AT_producer : GNU C17 14.2.0 -g3 -O0
<...> DW_AT_language : DW_LANG_C17
<...> DW_AT_comp_dir : /tmp
<...> DW_AT_low_pc : 0x401136
<...> DW_AT_high_pc : 0x42 (0x401178)
<1><2e>: Abbrev Number: 2 (DW_TAG_subprogram)
<2f> DW_AT_name : (indirect string, offset: 0x25): process
<...> DW_AT_decl_file : 1
<...> DW_AT_decl_line : 4
<...> DW_AT_low_pc : 0x401136
<...> DW_AT_high_pc : 0x38
<...> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
# 라인 번호 테이블 (.debug_line)
$ readelf --debug-dump=decodedline buggy
buggy.c:
File name Line number Starting address
buggy.c 4 0x401136
buggy.c 5 0x401142
buggy.c 6 0x401149
buggy.c 7 0x401155
buggy.c 8 0x401169
# objdump으로 DWARF 확인
$ objdump --dwarf=info buggy
$ objdump --dwarf=loc buggy # 변수 위치 표현식
$ objdump --dwarf=frames buggy # 스택 풀기 정보
# GDB에서 DWARF 관련 정보 확인
(gdb) info source # 현재 소스 파일 정보 (언어, 컴파일러 등)
(gdb) info sources # 모든 소스 파일 목록
(gdb) maint info symtabs # 심볼 테이블 내부 상태
(gdb) maint info psymtabs # 부분 심볼 테이블 상태 (빠른 검색용)
| DWARF 섹션 | 내용 | GDB 활용 |
|---|---|---|
.debug_info | DIE 트리 (타입, 함수, 변수 계층 정보) | ptype, print, info locals |
.debug_line | 소스 라인 ↔ 기계어(Machine Code) 주소 매핑(Mapping) 테이블 | break file.c:42, list |
.debug_str | 공유 문자열 풀 (심볼 이름, 파일 경로) | 모든 심볼 이름 참조 |
.debug_loc | 변수 위치 표현식 (레지스터/스택 오프셋) | print var, info locals |
.debug_frame | CFA(Canonical Frame Address) 규칙, 레지스터 복원 | backtrace, frame |
.eh_frame | 런타임 언와인드 (C++ 예외용, 중복 정보) | backtrace (디버그 없어도 가능) |
.debug_aranges | 주소 범위 → CU 빠른 조회 | 중단점 설정 성능 |
.debug_pubnames | 전역 함수/변수 이름 인덱스 | 심볼 검색 info functions |
.debug_macro | 매크로 정의 정보 (-g3 필요) | print macro, 매크로 확장 표시 |
.debug_types | 타입 단위 (DWARF 4; DWARF 5에서 통합) | ptype struct ... |
디버그 빌드
GDB가 소스 레벨 디버깅을 하려면 실행 파일에 DWARF 형식의 디버그 정보가 포함되어야 합니다.
# 기본 디버그 빌드
gcc -g -O0 -o prog prog.c
# DWARF 레벨 3 (매크로 정보 포함)
gcc -g3 -O0 -o prog prog.c
# 디버깅에 친화적인 최적화 (소스와 어느 정도 대응)
gcc -Og -g -o prog prog.c
# DWARF 버전 명시 (5 권장)
gcc -gdwarf-5 -g -O0 -o prog prog.c
# 심볼 분리 (배포용: 실행 파일은 작게, 심볼은 별도 보관)
gcc -g -O2 -o prog prog.c
objcopy --only-keep-debug prog prog.debug
strip --strip-debug prog
objcopy --add-gnu-debuglink=prog.debug prog
# GDB에서 분리된 심볼 파일 로드
(gdb) symbol-file prog.debug
| GCC 옵션 | DWARF 포함 정보 | 사용 시나리오 |
|---|---|---|
-g | 기본 디버그 정보 (DWARF 5) | 일반 디버깅 |
-g1 | 최소 정보 (라인 번호만) | 스택 트레이스만 필요 시 |
-g2 | -g와 동일 | — |
-g3 | 매크로 정의 포함 | 매크로 디버깅 필요 시 |
-ggdb | GDB 전용 확장 정보 | GDB 전용 환경 |
-gdwarf-4 | DWARF 4 형식 | 구형 도구 호환 |
-gdwarf-5 | DWARF 5 형식 (최신) | 최신 GDB 17.x 활용 |
프로그램 실행
기본 실행 명령
(gdb) run # 프로그램 시작 (r)
(gdb) run arg1 arg2 # 인자 전달하여 시작
(gdb) run < input.txt # 표준 입력 리다이렉션
(gdb) start # main() 첫 줄에 임시 중단점 설정 후 run
(gdb) starti # 첫 번째 기계어 명령에서 정지 (start inferior)
# 인자 설정
(gdb) set args arg1 arg2 arg3
(gdb) show args
# 환경 변수
(gdb) set environment VAR value
(gdb) unset environment VAR
(gdb) show environment VAR
# 작업 디렉터리
(gdb) set cwd /path/to/dir # 프로그램의 작업 디렉터리
(gdb) cd /path/to/dir # GDB 자체 작업 디렉터리
(gdb) pwd
# 입출력 단말
(gdb) tty /dev/pts/1 # 별도 단말로 프로그램 I/O 분리
# 경로 추가
(gdb) path /usr/local/bin # 실행 파일 검색 경로 추가
Attach / Detach / Kill
# 실행 중인 프로세스 디버깅
(gdb) attach 1234 # PID 1234 프로세스에 attach
(gdb) detach # 프로세스에서 분리 (계속 실행)
(gdb) kill # 디버깅 중인 프로세스 종료
# 실행 파일 지정 (attach 전에)
(gdb) file /path/to/prog
(gdb) symbol-file /path/to/prog.debug
(gdb) core-file /path/to/core
다중 Inferior (프로세스)
GDB는 여러 프로세스(inferior)를 동시에 디버깅할 수 있습니다.
(gdb) info inferiors # 현재 inferior 목록
(gdb) inferior 2 # inferior 2로 전환
(gdb) add-inferior # 새 inferior 추가
(gdb) remove-inferior 2 # inferior 2 제거
(gdb) clone-inferior # 현재 inferior 복제
# fork 동작 제어
(gdb) set follow-fork-mode child # fork 후 자식 프로세스 따라감
(gdb) set follow-fork-mode parent # 기본값: 부모 프로세스 유지
(gdb) set detach-on-fork off # fork 후 양쪽 모두 디버깅
# 스레드 목록
(gdb) info threads
(gdb) thread 3 # 스레드 3으로 전환
(gdb) thread apply all bt # 모든 스레드 백트레이스
체크포인트 (Checkpoint)
(gdb) checkpoint # 현재 실행 상태 스냅샷 저장
(gdb) info checkpoints # 체크포인트 목록
(gdb) restart 1 # 체크포인트 1로 복귀
(gdb) delete checkpoint 2 # 체크포인트 2 삭제
# 주소 공간 무작위화 제어
(gdb) set disable-randomization on # ASLR 비활성화 (재현성 확보)
(gdb) set disable-randomization off # ASLR 활성화
중단점 (Breakpoints)
중단점 설정
# 위치 지정 방법
(gdb) break main # 함수 이름
(gdb) break file.c:42 # 파일:라인번호
(gdb) break 42 # 현재 파일의 라인번호
(gdb) break *0x400520 # 주소 (기계어 레벨)
(gdb) break file.c:foo # 파일 내 함수
# 명시적 위치 지정자 (explicit location)
(gdb) break -function foo -label L
(gdb) break -source file.c -line 42
# 조건부 중단점
(gdb) break main if argc > 2
(gdb) break foo if x == 0 && y != 0
# 강제 조건 (조건 오류 무시)
(gdb) break foo -force-condition if *p == 0
# 횟수 지정 중단점 (N번 무시 후 정지)
(gdb) ignore 1 5 # 중단점 1을 5번 무시
# 임시 중단점 (한 번만 정지)
(gdb) tbreak main
# 하드웨어 중단점 (코드 실행 중단, 쓰기 금지 영역 등)
(gdb) hbreak *0x400520
(gdb) thbreak main # 임시 하드웨어 중단점
# 정규식으로 여러 중단점 (rbreak)
(gdb) rbreak ^test_ # test_로 시작하는 모든 함수
(gdb) rbreak file.c:^test_ # file.c 내 test_로 시작하는 함수
보류 중단점 (Pending Breakpoints)
# 아직 로드되지 않은 공유 라이브러리 함수
(gdb) set breakpoint pending on
(gdb) break dlopen_func # 나중에 .so가 로드되면 설정됨
(gdb) set breakpoint pending off # 보류 중단점 거부
(gdb) set breakpoint pending ask # 기본: 물어봄
중단점 관리
(gdb) info breakpoints # 중단점 목록 (info b)
(gdb) info breakpoints 1 2 3 # 특정 중단점만
# 삭제
(gdb) delete # 모든 중단점 삭제
(gdb) delete 1 # 중단점 1 삭제
(gdb) delete 1-5 # 1~5번 삭제
(gdb) clear # 현재 라인 중단점 삭제
(gdb) clear main # 함수 중단점 삭제
(gdb) clear file.c:42 # 특정 위치 삭제
# 활성화/비활성화
(gdb) disable # 모든 중단점 비활성화
(gdb) disable 1 2 # 1, 2번 비활성화
(gdb) enable # 모든 중단점 활성화
(gdb) enable once 1 # 1번을 한 번만 활성화 후 비활성화
(gdb) enable delete 1 # 1번을 한 번 정지 후 삭제
# 조건 변경
(gdb) condition 1 x > 0 # 중단점 1의 조건 변경
(gdb) condition 1 # 중단점 1의 조건 제거
# 중단점 명령 (정지 시 자동 실행)
(gdb) commands 1
> print x
> print y
> continue
> end
중단점 전역 설정
(gdb) set breakpoint auto-hw on # 하드웨어 중단점 자동 선택
(gdb) set breakpoint always-inserted on # 항상 삽입 (멀티스레드 유용)
(gdb) set breakpoint condition-evaluation host # 조건을 GDB 쪽에서 평가
(gdb) set breakpoint condition-evaluation target # 타깃에서 평가 (gdbserver)
dprintf — 정지 없는 동적 printf
dprintf는 중단점과 달리 프로그램을 멈추지 않고 지정 위치에서 형식화 문자열을 출력합니다. 로그 삽입이 어려운 릴리스 바이너리 추적에 유용합니다.
# dprintf 기본 사용법
(gdb) dprintf main,"진입: argc=%d\n", argc
(gdb) dprintf foo,"x=%d, y=%d 합=%d\n", x, y, x+y
(gdb) dprintf file.c:42,"라인42: ptr=%p\n", ptr
(gdb) info breakpoints # dprintf도 중단점 목록에 표시됨
# dprintf 출력 방식 변경
(gdb) set dprintf-style gdb # GDB printf 명령 경유 (기본값)
(gdb) set dprintf-style call # 프로그램의 printf 함수 직접 호출
(gdb) set dprintf-style agent # 타깃에서 직접 실행 (원격 디버깅)
(gdb) set dprintf-function fprintf # call 방식에서 사용할 함수명
(gdb) set dprintf-channel stderr # call 방식에서 FILE* 인자
# 사용 예시: 로그 없는 라이브러리 함수 추적
(gdb) dprintf malloc,"malloc(%lu) called from:\n", $rdi
(gdb) dprintf malloc+0," → returned %p\n", $rax # 반환 직후엔 catch-all 패턴 필요
중단점 명령
# commands 블록 — 중단점 히트 시 자동 실행
(gdb) break foo
(gdb) commands
> silent # "(Breakpoint N, foo...)" 메시지 숨김
> printf "x=%d y=%d\n", x, y
> backtrace 3
> continue # 정지 없이 자동 계속
> end
# silent + continue 패턴: 중단점을 "트레이스포인트"처럼 활용
(gdb) break write_log
(gdb) commands 1
> silent
> printf "[LOG] %s\n", msg
> continue
> end
# 중단점 번호 없이 마지막 설정 중단점에 commands 적용
(gdb) break bar
Breakpoint 3 at ...
(gdb) commands # 번호 생략 = 마지막 중단점
> print $rdi
> continue
> end
# hook-stop: 모든 정지 시 실행
(gdb) define hook-stop
printf "▶ 정지 위치: "
where 1
end
# hook-run: run 명령 전에 실행
(gdb) define hook-run
printf "프로그램 시작\n"
end
감시점 (Watchpoints)
감시점은 변수나 메모리 영역의 값이 변경되거나 읽힐 때 실행을 정지합니다.
# 쓰기 감시 (값 변경 시 정지)
(gdb) watch x # 변수 x 감시
(gdb) watch -l x # 위치(lvalue) 감시 (포인터 역참조 안정)
(gdb) watch *(int *)0xffff8000 # 주소 감시
(gdb) watch global_var # 전역 변수
# 읽기 감시 (값 읽힐 때 정지) — 하드웨어 지원 필요
(gdb) rwatch x
# 읽기+쓰기 감시
(gdb) awatch x
# 조건부 감시
(gdb) watch x if x > 100
(gdb) info watchpoints # 감시점 목록
(gdb) delete 2 # 감시점도 delete로 삭제
x86_64는 DR0–DR3로 최대 4개의 하드웨어 감시점을 지원합니다. 초과 시 소프트웨어 감시점으로 대체되며, 이 경우 매 명령마다 값을 비교하므로 실행이 크게 느려집니다.
set can-use-hw-watchpoints 0으로 하드웨어 감시점 비활성화 가능.
잡기점 (Catchpoints)
잡기점은 특정 이벤트 발생 시 실행을 정지합니다.
# C++ 예외
(gdb) catch throw # 예외 throw 시 정지
(gdb) catch rethrow # 예외 rethrow 시 정지
(gdb) catch catch # 예외 catch 핸들러 진입 시 정지
(gdb) catch throw std::runtime_error # 특정 예외 타입만
# Ada 예외
(gdb) catch exception # Ada 예외
(gdb) catch exception Program_Error
# 시스템 콜
(gdb) catch syscall # 모든 시스템 콜
(gdb) catch syscall read write # read, write 시스템 콜만
(gdb) catch syscall 1 # syscall 번호로 지정
# 프로세스 생성/실행
(gdb) catch fork # fork() 호출 시
(gdb) catch vfork # vfork() 호출 시
(gdb) catch exec # exec() 호출 시 (새 프로그램 로드)
# 공유 라이브러리
(gdb) catch load # .so 로드 시
(gdb) catch load libfoo.so # 특정 .so 로드 시
(gdb) catch unload # .so 언로드 시
# 시그널
(gdb) catch signal SIGSEGV # 시그널 발생 시
(gdb) catch signal all
# 임시 잡기점
(gdb) tcatch throw # 한 번만 정지
단계 실행 및 계속 실행
# 계속 실행
(gdb) continue # 다음 중단점까지 계속 (c)
(gdb) continue 3 # 중단점을 3번 무시하고 계속
# 소스 레벨 단계 실행
(gdb) next # 다음 줄 실행 (함수 호출 건너뜀) (n)
(gdb) next 5 # 5줄 실행
(gdb) step # 함수 안으로 들어감 (s)
(gdb) step 3 # 3단계 실행
# 함수 복귀
(gdb) finish # 현재 함수 실행 완료 후 정지
(gdb) return # 현재 함수 강제 반환
(gdb) return 42 # 반환값 지정
# 범위 실행
(gdb) until # 루프 끝까지 실행 (현재 위치 지나면 정지)
(gdb) until 50 # 라인 50까지 실행
(gdb) advance foo # foo 함수 첫 줄까지 실행
(gdb) advance file.c:80 # 특정 위치까지 실행
# 기계어 레벨 단계 실행
(gdb) stepi # 기계어 명령 1개 실행 (si)
(gdb) nexti # 기계어 명령 1개 실행 (함수 호출 건너뜀) (ni)
# 시그널 처리 제어
(gdb) handle SIGINT stop print # SIGINT 수신 시 GDB가 정지·출력
(gdb) handle SIGPIPE nostop # SIGPIPE는 무시
(gdb) info signals # 시그널 처리 방식 목록
역방향 디버깅 (Reverse Debugging)
GDB는 실행을 기록한 뒤 시간을 거슬러 올라가는 역방향 디버깅을 지원합니다.
# 실행 기록 시작
(gdb) record full # 모든 레지스터/메모리 변경 기록
(gdb) record btrace # 브랜치 기록 (하드웨어 BTS/PT 사용, 경량)
(gdb) record btrace bts # Intel BTS (Branch Trace Store)
(gdb) record btrace pt # Intel PT (Processor Trace)
# 기록 중 실행
(gdb) run
(gdb) continue
# 역방향 실행
(gdb) reverse-continue # 이전 중단점까지 역방향 실행 (rc)
(gdb) reverse-step # 이전 소스 줄로 역단계 실행 (rs)
(gdb) reverse-next # 이전 줄로 역단계 (함수 복귀 포함)
(gdb) reverse-finish # 현재 함수 진입 직전으로 역이동
# 기록 정보
(gdb) info record # 기록 상태
(gdb) record instruction-history 1,20 # 명령어 기록 출력
(gdb) record function-call-history # 함수 호출 기록
(gdb) record function-call-history /c # 호출 횟수 포함
# 기록 제어
(gdb) record stop # 기록 중단
(gdb) record delete # 기록 삭제
(gdb) set record full insn-number-max 200000 # 최대 기록 명령 수
(gdb) set record full stop-at-limit on # 한계 도달 시 정지
스택 검사 (Examining the Stack)
백트레이스
(gdb) backtrace # 콜 스택 출력 (bt)
(gdb) bt # 약어
(gdb) bt 5 # 최상위 5개 프레임만
(gdb) bt -5 # 최하위 5개 프레임만
(gdb) bt full # 각 프레임의 로컬 변수 포함
(gdb) bt no-filters # pretty-printer 필터 적용 안 함
(gdb) bt -frame-info source-and-location # 소스 정보 포함
프레임 탐색
(gdb) frame # 현재 프레임 정보 (f)
(gdb) frame 3 # 프레임 3으로 전환
(gdb) up # 상위 프레임으로 이동 (호출한 함수)
(gdb) up 3 # 3단계 상위로
(gdb) down # 하위 프레임으로 이동 (호출된 함수)
(gdb) down 2
(gdb) select-frame 3 # 프레임 전환 (출력 없음)
(gdb) up-silently 1 # 이동 (출력 없음)
# 프레임 상세 정보
(gdb) info frame # 현재 프레임 상세 (레지스터, 반환 주소 등)
(gdb) info args # 현재 함수 인자 목록
(gdb) info locals # 현재 함수 지역 변수 목록
# 모든 프레임에 명령 적용
(gdb) frame apply all bt # 모든 프레임 백트레이스
(gdb) frame apply 1-3 info locals # 프레임 1~3의 로컬 변수
(gdb) faas print x # 모든 프레임에서 x 출력 (frame apply all --quiet)
소스 파일 검사
# list 명령
(gdb) list # 현재 위치 주변 10줄 출력
(gdb) list 42 # 라인 42 주변
(gdb) list foo # 함수 foo 주변
(gdb) list file.c:42 # file.c의 42번 줄
(gdb) list file.c:foo # file.c의 foo 함수
(gdb) list , # 다음 10줄
(gdb) list - # 이전 10줄
(gdb) list 1,50 # 1~50번 줄
# list 크기 설정
(gdb) set listsize 20
(gdb) show listsize
# 소스 파일 검색 경로
(gdb) directory /path/to/src # 소스 검색 경로 추가
(gdb) show directories
(gdb) directory # 경로 초기화
# 소스 파일 치환 (빌드 경로 다를 때)
(gdb) set substitute-path /build/path /local/path
(gdb) show substitute-path
# 어셈블리 혼합 출력
(gdb) disassemble # 현재 함수 어셈블리
(gdb) disassemble /m foo # 소스와 어셈블리 혼합 (mix)
(gdb) disassemble /s foo # 소스 포함
(gdb) disassemble 0x400520, 0x400560 # 주소 범위
메모리 검색 (find 명령)
find 명령은 지정한 메모리 범위에서 특정 값 또는 패턴을 검색합니다. 문자열 위치 추적, 매직 번호 탐색, 메모리 포렌식에 활용합니다.
# 기본 문법: find [/sn] start, +len|end, val...
# s: 검색 단위 크기 (b=1, h=2, w=4, g=8), n: 최대 결과 수
# 문자열 검색 (기본 바이트 단위)
(gdb) find 0x400000, +0x10000, "Hello" # 코드 세그먼트에서 문자열 탐색
(gdb) find &buf, +256, "SECRET" # buf 주변 256바이트에서 문자열
# 정수 값 검색
(gdb) find /w 0x7ffe0000, +0x10000, 0xdeadbeef # 4바이트 값 탐색
(gdb) find /g 0x600000, +0x1000, 0x7f3a2b1c0000 # 8바이트 포인터 탐색
# 멀티바이트 패턴 검색
(gdb) find /b 0x0, 0x7fffffff, 0x48, 0x89, 0xe5 # mov rbp,rsp (프롤로그) 탐색
# 결과 수 제한
(gdb) find /b/5 0x400000, +0x100000, 0x90 # NOP 최대 5개 탐색
# 검색 결과 활용 ($_ 에 마지막 탐색 주소 저장됨)
(gdb) find /w 0x7ffe0000, +0x8000, 0xcafebabe
0x7ffe1234
1 pattern found.
(gdb) x /4xw $_ # 마지막 발견 주소 내용 확인
# 스택에서 특정 값 찾기 (예: 반환 주소 위치 탐색)
(gdb) find /g $sp, $sp+0x200, main
(gdb) info proc mappings # 메모리 맵 확인 후 범위 설정
# 실전: 힙에서 구조체 인스턴스 탐색
(gdb) info proc mappings
Start Addr End Addr Size Offset File
0x55555... → heap 영역 확인
# 매직 번호로 구조체 탐색
(gdb) find /w 0x555555000000, +0x100000, 0xdeadbeef
# 발견 → 해당 주소로 구조체 캐스팅
(gdb) print *(struct MyStruct *)0x5555551a3c20
데이터 검사 (Examining Data)
print 명령
# 기본 출력
(gdb) print x # 변수 x 출력 (p)
(gdb) print x + y # 표현식 계산
(gdb) print arr[5] # 배열 요소
(gdb) print *ptr # 포인터 역참조
(gdb) print ptr->field # 구조체 필드
(gdb) print (int)x # 타입 캐스팅
# 출력 형식 지정자
(gdb) print /x x # 16진수
(gdb) print /d x # 10진수 (부호 있음)
(gdb) print /u x # 10진수 (부호 없음)
(gdb) print /o x # 8진수
(gdb) print /t x # 2진수
(gdb) print /f x # 부동소수점
(gdb) print /c x # 문자
(gdb) print /s str # 문자열
(gdb) print /a ptr # 주소
# 변수 추적 (display)
(gdb) display x # 매번 정지 시 x 자동 출력
(gdb) display /x ptr # 16진수로 자동 출력
(gdb) info display # display 목록
(gdb) undisplay 1 # display 1 삭제
(gdb) disable display 1
(gdb) enable display 1
# 변수 수정
(gdb) set variable x = 42
(gdb) set variable *ptr = 0
(gdb) set {int}0x8000 = 100 # 주소에 직접 쓰기
# 함수 호출
(gdb) print foo(42) # 함수 호출 및 반환값 출력
(gdb) call bar(x, y) # 반환값 무시
x 명령 (메모리 검사)
# x /NFU addr
# N: 개수, F: 형식, U: 단위 크기
(gdb) x /10xw 0x8000 # 0x8000부터 4바이트 단위 16진수 10개
(gdb) x /20i $pc # PC부터 기계어 명령 20개
(gdb) x /s 0x400800 # 주소의 C 문자열
(gdb) x /4xg &var # var 주소부터 8바이트 단위 16진수 4개
# 단위 크기: b(1), h(2), w(4), g(8)
# 형식: x(hex), d(dec), u(uns), o(oct), t(bin), f(float), c(char), s(str), i(inst), a(addr)
# 히스토리 참조
(gdb) print $ # 최근 출력값
(gdb) print $$ # 이전 출력값
(gdb) print $3 # 히스토리 3번
(gdb) print $_ # 마지막 검사한 주소
(gdb) print $__ # 마지막 검사 결과
레지스터 및 특수 변수
(gdb) info registers # 주요 레지스터 목록
(gdb) info all-registers # 모든 레지스터 (FP/SIMD 포함)
(gdb) print $rax # RAX 레지스터
(gdb) print $pc # 프로그램 카운터
(gdb) print $sp # 스택 포인터
(gdb) set $rax = 0 # 레지스터 변경
(gdb) info float # FPU 상태
(gdb) info vector # SIMD 레지스터 (MMX/SSE/AVX)
타입 및 심볼 정보
(gdb) ptype x # 변수 타입 출력
(gdb) ptype struct task_struct # 구조체 레이아웃
(gdb) whatis x # 간략 타입
(gdb) info variables foo # 이름에 foo를 포함하는 변수 목록
(gdb) info functions foo # 이름에 foo를 포함하는 함수 목록
(gdb) info types int # 타입 검색
(gdb) info symbol 0x400520 # 주소에 해당하는 심볼
(gdb) info address main # 함수 주소
(gdb) demangle _ZN3foo3barEv # C++ 심볼 분해
출력 형식 완전 정리
set print 계열 명령으로 GDB의 데이터 출력 형식을 세밀하게 제어할 수 있습니다. 특히 복잡한 C++ 객체나 중첩 구조체 디버깅 시 필수입니다.
| 설정 명령 | 기본값 | 효과 |
|---|---|---|
set print pretty on | off | 구조체를 들여쓰기 있는 여러 줄로 출력 |
set print array on | off | 배열 원소를 한 줄씩 출력 |
set print array-indexes on | off | 배열 출력 시 인덱스 번호 표시 |
set print elements N | 200 | 배열/문자열 출력 최대 원소 수 (0=무제한) |
set print repeats N | 10 | 동일 값 N회 이상 반복 시 "N times" 약어 표시 |
set print null-stop on | off | char 배열을 '\0' 까지만 출력 (C 문자열처럼) |
set print union on | on | 공용체(union) 내부 모든 멤버 출력 |
set print object on | off | C++ 다형 객체를 실제 파생 타입으로 출력 |
set print vtbl on | off | C++ vtable 내용 출력 |
set print static-members on | on | C++ static 멤버 출력 |
set print demangle on | on | C++ mangled 이름 자동 분해 |
set print asm-demangle on | off | 어셈블리(Assembly) 출력 시 C++ 이름 분해 |
set print symbol-loading off | brief | 심볼 로딩 메시지 억제 |
set print inferior-events off | on | inferior 시작/종료 메시지 억제 |
set print thread-events off | on | 스레드(Thread) 시작/종료 메시지 억제 |
set print max-depth N | 20 | 중첩 구조체 최대 출력 깊이 |
set print characters N | 200 | 문자열 출력 최대 문자 수 |
set print nibbles on | off | 이진수 출력 시 4비트씩 구분 |
# 복잡한 구조체 예쁘게 출력
(gdb) set print pretty on
(gdb) print mystruct
$1 = {
field_a = 42,
nested = {
x = 1,
y = 2
},
flags = 0x3
}
# 긴 배열 전체 출력
(gdb) set print elements 0
(gdb) print bigarray
# char 배열을 C 문자열로 출력
(gdb) set print null-stop on
(gdb) set print elements 512
(gdb) print (char *)buf
# 임시 설정 (with 명령)
(gdb) with print pretty on -- print task_struct_instance
(gdb) with print elements 0 -- print huge_array
C++ 디버깅
GDB는 C++ 특화 기능(가상 함수, 예외, 템플릿, STL 컨테이너(Container) 등)을 위한 다양한 도구를 제공합니다.
vtable 및 다형성 검사
# 가상 함수 테이블 검사
(gdb) set print vtbl on
(gdb) set print object on # 실제 파생 타입으로 출력
(gdb) print *base_ptr # Base* 지만 Derived 내용 출력
# vtable 직접 확인
(gdb) print *(void **)base_ptr # 첫 워드 = vptr
(gdb) info vtbl base_ptr # vtable 내용 (GCC ABI)
(gdb) x /8xg *(void **)base_ptr # vtable 항목들 확인
# 이름 분해 (demangle)
(gdb) demangle _ZN4Base7processEi # C++ 심볼 분해
(gdb) info symbol 0x400abc # 주소의 심볼 (분해된 이름으로)
# 동적 타입 확인
(gdb) print $dynamic_type(base_ptr) # 실제 파생 타입 이름
STL 컨테이너 pretty-printer
GCC libstdc++는 GDB pretty-printer를 내장합니다. Ubuntu/Debian에서 python3-libstdcxx-pris 또는 libstdc++6-<ver>-dbg 패키지로 활성화됩니다.
# pretty-printer 활성화 확인
(gdb) info pretty-printer
global pretty-printers:
builtin
libstdc++-v6
std::string
std::vector
std::map
std::unordered_map
std::list
std::deque
...
# STL 컨테이너 출력
(gdb) print vec # std::vector
$1 = std::vector of length 5, capacity 8 = {1, 2, 3, 4, 5}
(gdb) print m # std::map
$2 = std::map with 3 elements = {["a"] = 1, ["b"] = 2, ["c"] = 3}
(gdb) print str # std::string
$3 = "hello world"
# 특정 pretty-printer 비활성화
(gdb) disable pretty-printer global libstdc++-v6 std::vector
(gdb) print vec # 내부 구조 직접 출력
# pretty-printer 없을 때 std::vector 수동 검사
(gdb) print vec._M_impl._M_start # 시작 포인터
(gdb) print vec._M_impl._M_finish # 끝 포인터
(gdb) print vec._M_impl._M_end_of_storage # 용량 끝 포인터
(gdb) print *(vec._M_impl._M_start)@5 # 원소 5개 출력
C++ 예외 디버깅
# 모든 예외 throw 시 정지
(gdb) catch throw
# 특정 예외 타입만
(gdb) catch throw std::runtime_error
(gdb) catch throw std::bad_alloc
# 예외 catch 시 정지
(gdb) catch catch
# 처리되지 않은 예외 (terminate 호출 시)
(gdb) catch throw
(gdb) commands
> printf "예외 발생: "
> print $exception # 현재 예외 객체 (GCC ABI)
> backtrace
> continue
> end
# 예외 정보 접근 (GCC ABI 기준)
(gdb) catch throw
# ... 정지 후 ...
(gdb) call __cxa_current_exception_type()->name() # 예외 타입 이름
(gdb) print *(std::runtime_error *)__cxa_current_primary_exception() # 예외 객체
템플릿 및 이름 검색
# 템플릿 함수 중단점
(gdb) break 'foo<int>' # 따옴표 필요
(gdb) break 'std::vector<int>::push_back'
# 과부하 함수 중단점 선택
(gdb) break foo
# GDB가 여러 버전 나열:
# [0] cancel
# [1] foo(int) at foo.cpp:10
# [2] foo(double) at foo.cpp:15
# 번호 선택
# 모든 과부하 버전에 중단점
(gdb) break foo # 모두 선택 (all)
(gdb) rbreak ^foo$ # 정규식으로 foo 전체
# 네임스페이스 탐색
(gdb) info functions MyNS::
(gdb) break MyNS::Bar::process
위치 지정자 완전 정리
GDB의 break, list, advance, until, clear 등 위치가 필요한 모든 명령에서 사용하는 위치 지정자(location specification)는 세 가지 형식이 있습니다.
① Linespec (라인 지정자)
# 라인 번호
(gdb) break 42 # 현재 소스 파일의 42번 줄
(gdb) break -5 # 현재 줄에서 5줄 위 (음수 오프셋 지원 안 함: list에서만)
# 파일:라인
(gdb) break file.c:42 # file.c 42번 줄
(gdb) break src/bar.cpp:100 # 경로 포함
# 함수 이름
(gdb) break main # 함수 진입 첫 줄
(gdb) break foo # foo() 함수
(gdb) break 'MyClass::method' # C++ 메서드 (따옴표 권장)
(gdb) break 'T::func<int>' # 템플릿 함수
# 파일:함수
(gdb) break bar.c:foo # bar.c의 foo 함수 (동명 함수 구분)
# 레이블 (C 레이블)
(gdb) break foo:retry_label # foo 함수 내 retry_label: 위치
# 라인 오프셋 (list 등)
(gdb) list +5 # 현재에서 5줄 뒤
(gdb) list -3 # 현재에서 3줄 앞
② Explicit Location (명시적 위치 지정자)
키워드-값 쌍으로 모호함 없이 위치를 지정합니다. 동명 함수나 복잡한 C++ 이름에서 유용합니다.
# 키워드 목록: -source, -function, -line, -label, -qualified
(gdb) break -source file.c -line 42
(gdb) break -source bar.cpp -function foo
(gdb) break -function 'MyClass::method' -label retry
(gdb) break -qualified ::foo # 전역 foo (네임스페이스 없는)
# -qualified: 정확한 이름 매칭 (부분 일치 방지)
(gdb) break -qualified MyNS::Bar::process
# 혼합 사용
(gdb) break -source utils.c -function process -line 25
③ Address Location (주소 지정자)
# * 접두사로 주소 직접 지정
(gdb) break *0x401142 # 16진수 절대 주소
(gdb) break *main+32 # 심볼 + 오프셋
(gdb) break *($pc + 8) # 레지스터 기반
# 표현식 주소
(gdb) break *((char *)&buf + 4) # 변수 주소 오프셋
# 심볼 주소 확인 후 사용
(gdb) info address main # main 함수 주소 출력
(gdb) print &global_var # 전역 변수 주소
(gdb) break *&global_var # 변수 주소에 중단점 (감시점이 더 적합)
④ 정적 탐침 (Static Probes)
# SystemTap 탐침 또는 DTrace 탐침에 중단점
(gdb) info probes # 탐침 목록
(gdb) info probes stap # SystemTap 탐침
(gdb) break -probe stap:libc:malloc # libc malloc 탐침
(gdb) break -probe-stap :free # :provider 생략 가능
# 탐침 인자 접근
# 중단점 히트 후:
(gdb) print $_probe_arg0 # 탐침 인자 0
(gdb) print $_probe_arg1 # 탐침 인자 1
GDB 명령어 체계
명령어 문법
# 약어 사용 가능 (고유하면 OK)
(gdb) b main # break main
(gdb) c # continue
(gdb) n # next
(gdb) s # step
(gdb) p x # print x
(gdb) bt # backtrace
(gdb) f 2 # frame 2
(gdb) i b # info breakpoints
(gdb) i r # info registers
# 빈 줄 입력 → 마지막 명령 반복 (step, next, continue, si, ni)
# Ctrl-c → 실행 중인 프로그램 인터럽트
# # → 주석
# with 명령 (임시 설정)
(gdb) with print pretty on -- print mystruct
(gdb) with scheduler-locking on -- next
# set/show
(gdb) set print pretty on
(gdb) show print pretty
자동 완성
(gdb) break mem<Tab> # memcpy, memset 등 완성
(gdb) info b<Tab> # info breakpoints 완성
(gdb) set max-completions 50 # 최대 완성 후보 수 (기본: 200)
(gdb) set max-completions unlimited
(gdb) complete break me # "break me"로 시작하는 후보 목록 출력
도움말 시스템
(gdb) help # 명령 분류 목록
(gdb) help break # break 명령 도움말
(gdb) help info # info 하위 명령 목록
(gdb) help set print # set print 하위 명령 목록
(gdb) apropos memory # "memory" 관련 명령 검색
(gdb) apropos -r "watch.*var" # 정규식 검색
(gdb) info # info 하위 명령 목록
(gdb) show # show 가능한 설정 목록
원격 디버깅 (Remote Debugging)
gdbserver
gdbserver는 타깃 장치에서 실행하여 GDB와 TCP/직렬 포트로 통신합니다. 임베디드 시스템, 크로스 컴파일(Cross Compilation) 환경, 커널 디버깅에 필수적입니다.
# 타깃 장치에서 (예: ARM 보드)
gdbserver :1234 ./prog # TCP 포트 1234
gdbserver /dev/ttyS0 ./prog # 직렬 포트
gdbserver :1234 --attach 5678 # 실행 중인 프로세스 attach
gdbserver --multi :1234 # 다중 연결 대기 (종료 안 됨)
# 호스트 PC에서 GDB
gdb ./prog # 동일한 실행 파일 (심볼용)
(gdb) target remote 192.168.1.100:1234 # TCP 연결
(gdb) target remote /dev/ttyS0 # 직렬 포트 연결
# extended-remote (gdbserver --multi 사용 시)
(gdb) target extended-remote :1234
(gdb) set remote exec-file /path/on/target/prog
(gdb) run
커널 KGDB
KGDB(Kernel GDB)는 리눅스 커널을 GDB로 직접 디버깅할 수 있게 합니다. 직렬 포트 백엔드(kgdboc)나 네트워크 백엔드(kgdboe)를 통해 호스트 GDB와 GDB Remote Protocol로 통신합니다. 내장 KDB(Kernel Debugger) 셸과 전환하며 사용할 수 있습니다.
maxcpus=1 커널 파라미터를 권장합니다. SMP에서는 다른 CPU가 계속 실행되어 경쟁 조건(Race Condition)이 발생할 수 있습니다. kgdb ↔ kdb 전환은 monitor kdb / KDB> kgdb 명령으로 수행합니다.
# 커널 설정 (CONFIG 옵션)
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y # 직렬 포트 백엔드 (kgdboc)
CONFIG_KGDB_KDB=y # KDB 셸 포함
CONFIG_DEBUG_INFO=y # DWARF 디버그 정보
CONFIG_FRAME_POINTER=y # 백트레이스 품질 향상
# 부트 파라미터
kgdboc=ttyS0,115200 # 직렬 포트 설정
kgdbwait # 부팅 시 즉시 GDB 대기
maxcpus=1 # SMP 제약 완화 (권장)
# 실행 중 KGDB 활성화
echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc
echo g > /proc/sysrq-trigger # 커널을 GDB 대기 상태로 강제
# 호스트에서 GDB 연결 (직렬 백엔드)
gdb ./vmlinux
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyS0
(gdb) lx-symbols /path/to/module/build/ # 모듈 심볼 로드 (scripts/gdb)
# KGDB → KDB 전환 (GDB 세션 도중)
(gdb) monitor kdb # kgdb → kdb 모드 전환
# 타깃 콘솔에서 KDB 프롬프트 사용 후 다시 KGDB로 복귀:
# KDB> kgdb # kdb → kgdb 모드 복귀
(gdb) continue # GDB 세션 재개
# Linux scripts/gdb 활용 (커널 전용 명령)
# vmlinux 로드 시 자동으로 lx-* 명령 등록
(gdb) lx-dmesg # dmesg 출력
(gdb) lx-ps # 프로세스 목록
(gdb) lx-lsmod # 모듈 목록
(gdb) lx-symbols # 모듈 심볼 로드
KDB는 GDB 없이 타깃 콘솔에서 직접 사용하는 간단한 커널 디버거입니다. SysRq 트리거(echo g > /proc/sysrq-trigger)로 KDB 프롬프트에 진입합니다.
| KDB 명령 | 설명 |
|---|---|
go | 실행 재개 (GDB의 continue) |
bt | 현재 CPU 스택 백트레이스 |
btc | 모든 CPU 백트레이스 |
md <addr> <count> | 메모리 덤프 (16진수) |
mm <addr> <value> | 메모리 값 수정 |
lsmod | 로드된 커널 모듈(Kernel Module) 목록 |
dmesg | 커널 링 버퍼(Ring Buffer) 출력 |
ps | 프로세스 목록 (task_struct 기반) |
pid <pid> | 지정 PID로 컨텍스트 전환 |
sr g | SysRq 'g' 전송 → KGDB 모드로 전환 |
kgdb | KDB → KGDB 모드 복귀 |
help | 전체 명령 목록 출력 |
네트워크 백엔드(kgdboe)는 직렬 포트 없이 이더넷으로 KGDB를 사용할 수 있습니다.
# kgdboe 설정 (CONFIG_KGDB_ETHERNET=y 필요)
# 부트 파라미터: 포트@타깃IP/인터페이스,호스트IP
kgdboe=6443@192.168.1.10/eth0,192.168.1.1
# 실행 중 활성화
echo 6443@192.168.1.10/eth0,192.168.1.1 > /sys/module/kgdboe/parameters/kgdboe
echo g > /proc/sysrq-trigger
# 호스트에서 연결
gdb ./vmlinux
(gdb) target remote udp:192.168.1.10:6443
Non-stop 모드 (비정지 멀티스레드)
기본 All-stop 모드에서는 하나의 스레드가 중단점에 걸리면 모든 스레드가 정지합니다. Non-stop 모드에서는 중단점에 걸린 스레드만 정지하고 나머지는 계속 실행합니다. 실시간(Real-time) 서버, 멀티미디어, UI 스레드를 분리해서 디버깅할 때 필요합니다.
# Non-stop 모드 활성화 (.gdbinit 또는 gdb 시작 직후)
(gdb) set non-stop on
(gdb) set target-async on # 비동기 목표 (non-stop에 필요)
(gdb) set pagination off # 비동기 환경에서 페이지네이션 방해
# 프로그램 시작
(gdb) run & # '&'로 백그라운드 실행
(gdb) continue -a & # 모든 스레드 백그라운드 계속 실행
# 특정 스레드에 중단점 설정 후 정지
(gdb) break foo thread 2 # 스레드 2만 foo에서 정지
# ... 스레드 2가 foo에서 정지, 나머지는 계속 실행 중 ...
(gdb) thread 2 # 정지한 스레드 2로 전환
(gdb) backtrace
(gdb) continue & # 스레드 2만 재시작
# 실행 중인 스레드 인터럽트
(gdb) interrupt # 현재 스레드 인터럽트
(gdb) interrupt -a # 모든 스레드 인터럽트
# All-stop vs Non-stop 비교
# All-stop: break → 전체 정지 → 분석 → continue → 전체 재시작
# Non-stop: break → 해당 스레드만 정지 → 분석 → continue & → 해당 스레드만 재시작
# Non-stop에서 scheduler-locking
(gdb) set scheduler-locking on # 현재 스레드만 next/step
(gdb) set scheduler-locking off # 전체 허용 (non-stop 기본)
(gdb) set scheduler-locking replay # record/replay 시 결정론적 재생
GDB/MI 프로토콜 (IDE 연동)
GDB/MI(Machine Interface)는 GDB를 프론트엔드(IDE, 에디터)에서 프로그래밍 방식으로 제어하기 위한 구조화된 텍스트 프로토콜입니다. VS Code(cppdbg), CLion, Eclipse CDT 등 IDE들이 GDB/MI로 GDB를 제어합니다.
# GDB/MI 모드 시작
gdb --interpreter=mi3 prog # mi3 = GDB/MI 버전 3 (최신)
gdb -i mi ./prog
# MI 명령 형식: [token] -command [params]
# 결과 형식: token^result,key=value,...
# 예: 중단점 설정
-break-insert main
→ ^done,bkpt={number="1",type="breakpoint",addr="0x401162",func="main",...}
# 예: 프로그램 실행
-exec-run
→ ^running
→ *stopped,reason="breakpoint-hit",bkptno="1",frame={...}
# 예: 변수 값 읽기
-var-create myvar * x # 변수 객체 생성
→ ^done,name="myvar",numchild="0",value="42",type="int"
-var-evaluate-expression myvar # 값 평가
→ ^done,value="42"
-var-update myvar # 변경 추적
→ ^done,changelist=[{name="myvar",value="99",in_scope="true"}]
# 스택 정보
-stack-list-frames
→ ^done,stack=[frame={level="0",addr="0x401175",func="foo",...},...]
-stack-list-locals --all-values 0 # 지역 변수 (0=현재 프레임)
→ ^done,locals=[{name="x",type="int",value="42"}]
# 비동기 이벤트 (MI 알림)
=thread-created,id="2",group-id="i1" # 스레드 생성
=thread-exited,id="2",group-id="i1" # 스레드 종료
*running,thread-id="all" # 실행 중
*stopped,reason="exited-normally" # 정상 종료
gdb --interpreter=dap으로 DAP 모드를 지원합니다. VS Code의 내장 C/C++ 디버거 또는 codelldb 대안으로 활용 가능합니다.
gdb -q -ex "set mi-async on" --interpreter=dap prog
TUI 모드 (Terminal User Interface)
TUI 모드는 터미널에서 소스 코드, 어셈블리, 레지스터를 동시에 볼 수 있는 분할 화면을 제공합니다.
# TUI 진입/종료
(gdb) tui enable # TUI 활성화
(gdb) tui disable # TUI 비활성화
Ctrl-x Ctrl-a # TUI 토글
gdb --tui prog # TUI 모드로 시작
# 레이아웃 전환
(gdb) layout src # 소스 창
(gdb) layout asm # 어셈블리 창
(gdb) layout split # 소스 + 어셈블리
(gdb) layout regs # 현재 레이아웃 + 레지스터 창
Ctrl-x 1 # 단일 창 (소스)
Ctrl-x 2 # 분할 창 (소스+어셈 또는 어셈+레지)
Ctrl-x s # 싱글키 모드 토글
# 창 크기 조정
(gdb) winheight src +5 # 소스 창 5줄 확대
(gdb) winheight cmd -5 # 명령 창 5줄 축소
# 화면 새로고침
Ctrl-l # TUI 화면 새로고침
# 싱글키 모드 단축키
# c = continue, d = down, f = finish, n = next, o = nexti
# q = quit singkey, r = run, s = step, u = up, v = info locals, w = where (bt)
GDB 스크립팅
GDB 명령어 스크립팅
# 사용자 정의 명령 (define)
(gdb) define mybt
backtrace
info locals
end
(gdb) define hook-stop
# 정지할 때마다 자동 실행
print "stopped!"
end
# 명령 파일 실행
(gdb) source /path/to/cmds.gdb
# 조건 및 루프
(gdb) if x > 0
print "positive"
else
print "non-positive"
end
(gdb) while x > 0
next
print x
end
Python 스크립팅
# 인라인 Python
(gdb) python print("hello from python")
(gdb) python gdb.execute("backtrace")
# Python 스크립트 파일 로드
(gdb) source /path/to/script.py
# Python pretty-printer 예시 (my_printer.py)
import gdb
import gdb.printing
class MyListPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return f"MyList(size={int(self.val['size'])})"
def children(self):
...
def build_pretty_printer():
pp = gdb.printing.RegexpCollectionPrettyPrinter("mylib")
pp.add_printer('MyList', '^MyList$', MyListPrinter)
return pp
gdb.printing.register_pretty_printer(gdb.current_objfile(),
build_pretty_printer())
# Python API 주요 함수
import gdb
frame = gdb.selected_frame()
block = frame.block()
val = frame.read_var("x")
bp = gdb.Breakpoint("foo")
bp.condition = "x > 0"
bp.commands = "backtrace\ncontinue\n"
Python 확장
이벤트 API
import gdb
# 정지 이벤트 (중단점, 시그널, 완료 등)
def on_stop(event):
if isinstance(event, gdb.BreakpointEvent):
bps = event.breakpoints
print(f"[stop] 중단점 {bps[0].number} 히트: {bps[0].location}")
elif isinstance(event, gdb.SignalEvent):
print(f"[stop] 시그널: {event.stop_signal}")
frame = gdb.selected_frame()
print(f" → {frame.name()} at {frame.find_sal().symtab}:{frame.find_sal().line}")
gdb.events.stop.connect(on_stop)
# 중단점 생성/삭제 이벤트
def on_bp_created(bp):
print(f"[BP 생성] #{bp.number}: {bp.location}")
gdb.events.breakpoint_created.connect(on_bp_created)
gdb.events.breakpoint_deleted.connect(lambda bp: print(f"[BP 삭제] #{bp.number}"))
gdb.events.breakpoint_modified.connect(lambda bp: print(f"[BP 변경] #{bp.number}"))
# 새 inferior/스레드 이벤트
gdb.events.new_inferior.connect(lambda e: print(f"[Inferior] 새 프로세스"))
gdb.events.new_thread.connect(lambda e: print(f"[Thread] 새 스레드"))
gdb.events.exited.connect(lambda e: print(f"[종료] 코드: {getattr(e,'exit_code','?')}"))
# 연결 해제
gdb.events.stop.disconnect(on_stop)
Breakpoint 서브클래스
import gdb
class CountingBreakpoint(gdb.Breakpoint):
"""히트 횟수를 기록하는 중단점"""
def __init__(self, spec):
super().__init__(spec)
self.hit_count = 0
def stop(self):
self.hit_count += 1
frame = gdb.selected_frame()
print(f"[BP #{self.number}] 히트 {self.hit_count}회 @ {frame.name()}")
# True 반환 → 정지, False 반환 → 계속 실행 (트레이스포인트)
return self.hit_count % 10 == 0 # 10번에 한 번만 정지
class ConditionalWatchpoint(gdb.Breakpoint):
"""Python 조건부 감시점"""
def __init__(self, expr, threshold):
super().__init__(expr, gdb.BP_WATCHPOINT, gdb.WP_WRITE)
self.threshold = threshold
def stop(self):
val = gdb.parse_and_eval(self.expression)
if int(val) > self.threshold:
print(f"임계값 초과: {val} > {self.threshold}")
return True # 정지
return False # 계속 실행
# 사용
bp = CountingBreakpoint("malloc")
wp = ConditionalWatchpoint("global_counter", 1000)
Frame Filters
import gdb
import gdb.frames
import itertools
class KernelFrameFilter:
"""libc/runtime 프레임을 숨기는 필터"""
name = "KernelFilter"
priority = 100
enabled = True
HIDDEN = frozenset(["__GI___libc_start_main", "__libc_csu_init",
"_start", "__pthread_create_2_1"])
def filter(self, frame_iter):
return (f for f in frame_iter
if f.function() not in self.HIDDEN)
gdb.frame_filters["global"][KernelFrameFilter.name] = KernelFrameFilter()
# Frame Decorator: 출력 형식 변경
class ColorFrameDecorator(gdb.FrameDecorator.FrameDecorator):
def function(self):
name = super().function()
if name and name.startswith("kernel_"):
return f"\033[33m{name}\033[0m" # 노란색
return name
Type Printers / Xmethods
import gdb
import gdb.types
# Type Printer: ptype 출력 커스터마이즈
class MyTypePrinter:
name = "MyLib types"
class _recognizer(gdb.types.TypePrinter):
def recognize(self, type_obj):
if type_obj.tag == "MyList":
return "MyList<{}>".format(type_obj.template_argument(0))
return None
def __call__(self, type_obj):
return self._recognizer()
gdb.types.register_type_printer(gdb.current_objfile(), MyTypePrinter())
# Xmethod: 인라인/최적화로 사라진 멤버 함수 복원
import gdb.xmethod
class VectorSizeXMethod(gdb.xmethod.XMethod):
"""std::vector::size()를 최적화로 인라인된 경우 복원"""
name = "size"
enabled = True
def get_worker(self, method_name):
if method_name == "size":
return self
def __call__(self, obj):
start = obj['_M_impl']['_M_start']
finish = obj['_M_impl']['_M_finish']
return int(finish - start)
사용자 정의 GDB 명령 (Python)
import gdb
class PrintAllLocals(gdb.Command):
"""현재 프레임의 모든 지역 변수를 Python으로 출력"""
def __init__(self):
super().__init__("plocals", gdb.COMMAND_DATA)
def invoke(self, arg, from_tty):
frame = gdb.selected_frame()
block = frame.block()
for sym in block:
if sym.is_variable or sym.is_argument:
try:
val = frame.read_var(sym)
print(f" {sym.name} ({sym.type}) = {val}")
except gdb.error as e:
print(f" {sym.name}: <{e}>")
class HeapWalk(gdb.Command):
"""간단한 힙 워크 명령 (malloc 내부 구조 기반)"""
def __init__(self):
super().__init__("heap-walk", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
# glibc malloc_info 활용
gdb.execute("call malloc_info(0, stdout)")
# 명령 등록
PrintAllLocals()
HeapWalk()
# GDB에서 사용
# (gdb) plocals
# (gdb) heap-walk
시그널(Signal) 처리
GDB는 타겟 프로세스에 전달되는 모든 POSIX 시그널을 가로채고 제어할 수 있습니다. 시그널 기반 디버깅은 SIGSEGV(세그폴트), SIGABRT(abort), SIGFPE(부동소수점 오류)의 원인 분석뿐 아니라, SIGUSR1/SIGUSR2를 활용한 애플리케이션 내부 상태 덤프에도 활용됩니다.
handle 명령 상세
# 시그널 처리 정책 설정
(gdb) handle SIGSEGV stop print pass # 정지, 출력, 전달 (기본)
(gdb) handle SIGPIPE nostop noprint pass # 무시 (서버 디버깅 시 필수)
(gdb) handle SIGUSR1 nostop pass # 앱 자체 시그널 전달만
(gdb) handle SIGINT stop nopass # Ctrl-C → GDB 정지, 앱에는 미전달
(gdb) handle all nostop noprint pass # 모든 시그널 통과 (주의)
# 현재 시그널 처리 설정 확인
(gdb) info signals
Signal Stop Print Pass to program Description
SIGHUP Yes Yes Yes Hangup
SIGINT Yes Yes No Interrupt
SIGQUIT Yes Yes Yes Quit
SIGSEGV Yes Yes Yes Segmentation fault
...
# 특정 시그널만 확인
(gdb) info signals SIGSEGV SIGABRT SIGBUS
# 정지 시 시그널 정보 확인
(gdb) run
# Program received signal SIGSEGV, Segmentation fault.
# 0x0000555555556194 in process_data (ptr=0x0) at prog.c:42
# 시그널 원인 분석
(gdb) print $_siginfo # siginfo_t 구조체 (리눅스)
(gdb) print $_siginfo.si_signo # 시그널 번호
(gdb) print $_siginfo.si_code # 세부 원인 코드
(gdb) print /x $_siginfo.si_addr # 오류 발생 주소 (SIGSEGV)
(gdb) print $_siginfo._sifields._sigfault # fault 상세
# SIGSEGV si_code 해석
# SEGV_MAPERR (1): 매핑되지 않은 주소 (NULL deref 등)
# SEGV_ACCERR (2): 권한 위반 (읽기전용 쓰기 등)
# 시그널 강제 전송
(gdb) signal SIGUSR1 # 타겟에 SIGUSR1 전송 후 실행 재개
(gdb) signal 0 # 시그널 없이 실행 재개 (pending 시그널 삼킴)
(gdb) queue-signal SIGTERM # 시그널 큐잉 (실행 재개 안 함)
# 시그널 핸들러 추적
(gdb) catch signal SIGSEGV # 시그널 핸들러 진입 시 정지
(gdb) catch signal SIGUSR1 SIGUSR2 # 여러 시그널 지정
시그널 디버깅 실전 패턴
# 패턴 1: SIGSEGV 원인 분석 (가장 흔한 크래시)
(gdb) run
# Program received signal SIGSEGV
(gdb) bt # 크래시 콜 스택
(gdb) print /x $_siginfo.si_addr # 접근한 주소
(gdb) x/i $pc # 크래시 명령어
(gdb) info registers # 레지스터 상태
# si_addr=0x0 → NULL deref
# si_addr=0x7f... → use-after-free 가능성
# si_addr=0x41414141 → 버퍼 오버플로우 가능성
# 패턴 2: 서버 디버깅 시 SIGPIPE 무시
# 클라이언트 연결 종료 시 SIGPIPE 발생 → 서버 정지 방지
(gdb) handle SIGPIPE nostop noprint pass
(gdb) handle SIGCHLD nostop noprint pass # 자식 종료 시그널도
# 패턴 3: SIGALRM/타이머 기반 앱 디버깅
# 타이머가 계속 시그널을 보내서 디버깅 방해 시
(gdb) handle SIGALRM nostop noprint pass
# 또는 GDB 진입 시 타이머 비활성화
(gdb) call alarm(0) # 알람 해제
# 패턴 4: 시그널 핸들러 내부 디버깅
(gdb) break my_signal_handler # 시그널 핸들러에 BP
(gdb) handle SIGUSR1 stop pass
(gdb) run
# ... SIGUSR1 수신 → GDB 정지 → continue → 핸들러 BP 히트
(gdb) bt
# #0 my_signal_handler at prog.c:10
# #1 <signal handler called> ← 시그널 프레임
# #2 main at prog.c:50 ← 인터럽트된 코드
# 패턴 5: 코어 덤프 대신 GDB 자동 attach
# /proc/sys/kernel/core_pattern에 GDB 스크립트 설정:
# echo '|/usr/bin/gdb -batch -ex bt -ex quit %e %p' > /proc/sys/kernel/core_pattern
트레이스포인트
트레이스포인트는 프로그램을 정지하지 않고 데이터를 수집하는 비침투적 디버깅 기법입니다. 실시간 시스템, 프로덕션 환경, 타이밍에 민감한 버그에서 일반 중단점 대신 사용합니다. GDB의 트레이스포인트는 gdbserver 또는 in-process agent(IPA)에서 실행되어 오버헤드(Overhead)를 최소화합니다.
트레이스포인트 기본 사용
# 트레이스포인트는 gdbserver 환경에서 동작
# 타겟: gdbserver :1234 ./prog
# 호스트: gdb ./prog → target remote :1234
# 트레이스포인트 설정
(gdb) trace foo # 함수 foo에 트레이스포인트
(gdb) trace prog.c:42 # 소스 줄에 트레이스포인트
(gdb) trace *0x401234 # 주소에 트레이스포인트
(gdb) ftrace foo # fast 트레이스포인트 (IPA 사용)
# 데이터 수집 액션 설정
(gdb) actions
> collect $regs # 모든 레지스터
> collect $locals # 모든 지역 변수
> collect $args # 모든 함수 인자
> collect buffer, length # 특정 변수
> collect *(char*)ptr@100 # 메모리 영역 (ptr부터 100바이트)
> collect $_sdata # 정적 트레이스 데이터 (strace mark용)
> teval counter++ # 표현식 평가만 (수집 안 함)
> end
# 조건부 트레이스포인트
(gdb) trace foo if x > 100 # 조건 만족 시만 수집
(gdb) passcount 50 1 # 트레이스포인트 1에서 50회만 수집
# 트레이스 실행
(gdb) tstart # 트레이스 시작
(gdb) continue # 프로그램 실행 (트레이스 수집 중)
# ... 프로그램 실행, 트레이스 데이터 자동 수집 ...
(gdb) tstop # 트레이스 중지
(gdb) tstatus # 수집 상태 (프레임 수, 버퍼 사용량)
# 수집된 데이터 탐색 (tfind)
(gdb) tfind start # 첫 번째 프레임으로
(gdb) tfind # 다음 프레임
(gdb) tfind 42 # 프레임 42로 이동
(gdb) tfind tracepoint 2 # 트레이스포인트 2의 프레임
(gdb) tfind pc 0x401234 # 특정 PC의 프레임
(gdb) tfind end # 탐색 종료 (라이브 모드 복귀)
# 프레임 내에서 데이터 확인 (수집된 것만 조회 가능)
(gdb) print x # 수집된 변수 값
(gdb) print $trace_frame # 현재 프레임 번호
(gdb) info registers # 수집된 레지스터
(gdb) bt # 수집된 스택 (collect $regs 필요)
# 트레이스 데이터 저장/로드
(gdb) tsave trace_data.tfile # 파일로 저장
(gdb) target tfile trace_data.tfile # 저장된 데이터 로드 (오프라인 분석)
정적 트레이스포인트 (Static Tracepoints)
# 정적 트레이스포인트: 소스 코드에 마커 삽입
# UST (User-Space Tracing) 또는 SDT (Static Defined Tracing) 마커 사용
# SDT 프로브 포인트 확인
(gdb) info static-tracepoint-markers
Cnt ID Addr Name
1 stap/libc 0x7ffff7e12340 malloc_entry
2 stap/libc 0x7ffff7e12345 malloc_return
# SDT 마커에 정적 트레이스포인트 설정
(gdb) strace -m malloc_entry
(gdb) actions
> collect $regs
> collect $_sdata
> end
# SystemTap SDT 프로브를 GDB에서 사용
# 소스 코드에 SDT 마커 삽입 (sys/sdt.h)
# #include <sys/sdt.h>
# DTRACE_PROBE2(myapp, request_start, req_id, req_size);
# → GDB에서 'strace -m myapp:request_start'로 연결
# GDB의 while-stepping 액션 (트레이스포인트에서 N단계 추적)
(gdb) trace critical_function
(gdb) actions
> collect $regs, $locals
> while-stepping 20 # 히트 후 20스텝 추가 수집
> collect $regs, $locals
> end
> end
dprintf를 사용할 수 있습니다. 프로그램을 정지하지 않고 printf 스타일 출력을 삽입합니다.
(gdb) dprintf foo, "foo called: x=%d ptr=%p\n", x, ptr
(gdb) set dprintf-style call — 타겟의 printf 호출 (가장 빠름)
(gdb) set dprintf-style gdb — GDB의 printf 사용 (안전하지만 느림)
(gdb) set dprintf-style agent — gdbserver 에이전트 사용 (원격)
고급 주제
코어 덤프 분석
# 코어 덤프 활성화
ulimit -c unlimited # 코어 덤프 크기 제한 해제
echo '/tmp/core.%e.%p' > /proc/sys/kernel/core_pattern
# GDB로 코어 분석
gdb ./prog core.prog.1234
(gdb) backtrace # 크래시 시 콜 스택
(gdb) info registers # 레지스터 상태
(gdb) frame 0
(gdb) info locals # 지역 변수
(gdb) list # 소스 위치
멀티스레드 디버깅
(gdb) info threads # 스레드 목록
(gdb) thread 3 # 스레드 3으로 전환
(gdb) thread apply all bt # 모든 스레드 백트레이스
(gdb) thread apply 1 3 5 bt # 특정 스레드만
# 스레드 잠금 (scheduler-locking)
(gdb) set scheduler-locking on # 현재 스레드만 실행
(gdb) set scheduler-locking off # 기본: 모든 스레드 실행
(gdb) set scheduler-locking step # step/next 시만 잠금
# 스레드 중단점
(gdb) break foo thread 2 # 스레드 2에서만 정지
(gdb) break foo thread 2 if x > 0
Fork 디버깅
(gdb) set follow-fork-mode child # fork 후 자식 추적
(gdb) set follow-fork-mode parent # fork 후 부모 유지 (기본)
(gdb) set detach-on-fork off # fork 후 양쪽 모두 디버깅
(gdb) info inferiors # inferior (프로세스) 목록
(gdb) inferior 2 # 자식 프로세스로 전환
(gdb) inferior 1 # 부모 프로세스로 전환
공유 라이브러리(Shared Library)
(gdb) info sharedlibrary # 로드된 공유 라이브러리 목록
(gdb) sharedlibrary libfoo # 심볼 수동 로드
(gdb) nosharedlibrary # 공유 라이브러리 심볼 해제
(gdb) set auto-solib-add on # 자동 심볼 로드 (기본)
(gdb) set auto-solib-add off # 비활성화 (대형 프로그램에서 속도 향상)
(gdb) set solib-search-path /path/to/libs # 라이브러리 검색 경로
편의 변수 및 함수
# 편의 변수 ($로 시작)
(gdb) set $i = 0
(gdb) set $addr = (char *)0x8000
# 편의 함수 (built-in)
(gdb) print $_thread # 현재 스레드 번호
(gdb) print $_inferior # 현재 inferior 번호
(gdb) print $_exitcode # 종료 코드
(gdb) print $bpnum # 최근 설정된 중단점 번호
# 구조체 배열 순회 예시
(gdb) set $i = 0
(gdb) while $i < 10
print arr[$i]
set $i = $i + 1
end
프로세스 상태 분석
# /proc 인터페이스 활용
(gdb) info proc # PID, 실행 파일, 상태
(gdb) info proc mappings # 가상 메모리 맵 (mmap 목록)
(gdb) info proc status # /proc/PID/status 내용
(gdb) info proc stat # /proc/PID/stat 내용
(gdb) info proc cmdline # 명령줄 인자
(gdb) info proc cwd # 작업 디렉터리
(gdb) info proc exe # 실행 파일 경로
(gdb) info proc files # 열린 파일 디스크립터
(gdb) info proc all # 위 모든 정보
# 메모리 맵 확인 예시
(gdb) info proc mappings
Start Addr End Addr Size Offset Perms File
0x555555554000 0x555555556000 0x2000 0x0 r--p /tmp/buggy
0x555555556000 0x555555557000 0x1000 0x2000 r-xp /tmp/buggy
...
0x7ffff7dd1000 0x7ffff7df3000 0x22000 0x0 r--p /lib/x86_64-linux-gnu/libc.so.6
# 메모리 영역 직접 덤프
(gdb) dump binary memory /tmp/heap.bin 0x555555559000 0x55555556a000
(gdb) append binary memory /tmp/stack.bin $sp $sp+0x10000
JIT 컴파일 코드 디버깅
# JIT 디버깅 인터페이스 (GDB JIT API)
# JIT 엔진은 __jit_debug_register_code() 호출로 GDB에 새 코드 알림
# __jit_debug_descriptor 구조체에 ELF 이미지 정보 등록
# GDB 자동 JIT 감지 설정
(gdb) set jit-reader-load /path/to/jit_reader.so # 커스텀 JIT reader
(gdb) info jit # 등록된 JIT 코드 목록
# Python JIT (CPython) 디버깅
# python-dbg 또는 python3-dbg 패키지 필요
gdb python3
(gdb) py-bt # Python 스택 트레이스 (python-gdb.py 필요)
(gdb) py-print var # Python 변수 출력
(gdb) py-locals # Python 지역 변수
(gdb) py-list # Python 소스 위치
(gdb) py-up / py-down # Python 프레임 이동
ARM MTE (Memory Tagging Extension)
# ARM MTE: 각 16바이트 메모리 청크에 4비트 태그 부착
# use-after-free, 힙 오버플로우를 하드웨어 레벨에서 감지
# MTE 지원 GDB 확인
(gdb) show memory-tag-violations # 태그 위반 처리 설정
# 메모리 태그 검사
(gdb) memory-tag check ptr # ptr의 논리 태그 vs 할당 태그 일치 확인
(gdb) memory-tag print-logical-tag ptr # 포인터의 논리 태그 출력
(gdb) memory-tag print-allocation-tag ptr # 주소의 할당 태그 출력
(gdb) memory-tag set-allocation-tag ptr, length, tag # 태그 강제 설정
(gdb) memory-tag with-logical-tag ptr, tag # 태그된 포인터 생성
# 커널에서 MTE 활성화 필요
# /proc/sys/vm/tag_stack 및 mmap flags (PROT_MTE)
Sanitizer 통합 디버깅
컴파일러 기반 Sanitizer(ASan, TSan, MSan, UBSan)는 실행 시 메모리 오류, 레이스 컨디션, 미초기화 읽기, 미정의 동작을 감지합니다. GDB와 연동하면 감지 시점에서 즉시 정지하여 정밀 분석이 가능합니다.
ASan + GDB 실전
# ASan 빌드
gcc -g -O1 -fsanitize=address -fno-omit-frame-pointer -o prog prog.c
# GDB에서 실행 (ASan 환경변수 설정)
gdb ./prog
(gdb) set env ASAN_OPTIONS=abort_on_error=1:detect_leaks=1:halt_on_error=1
(gdb) run
# ASan이 오류 감지 → abort() → GDB에서 자동 정지
# =================================================================
# ==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000ef50
# ... ASAN 보고서 출력 ...
# GDB로 상세 분석
(gdb) bt # 오류 발생 콜 스택
(gdb) frame 3 # 사용자 코드 프레임으로 이동
(gdb) info locals # 지역 변수 확인
(gdb) print ptr # 문제의 포인터 확인
# ASan 내부 함수에 직접 중단점 설정
(gdb) break __asan_report_load8 # 8바이트 읽기 오류 감지 시
(gdb) break __asan_report_store4 # 4바이트 쓰기 오류 감지 시
# ASan shadow 메모리 직접 검사
# shadow_addr = (addr >> 3) + 0x7fff8000 (x86_64 기본 오프셋)
(gdb) print /x *(char*)((0x60200000ef50 >> 3) + 0x7fff8000)
# 0xfd = freed heap 0xfa = heap left redzone
# 0xf1 = stack left 0xf3 = stack right redzone
# 0x00 = 접근 가능 0x01-0x07 = 부분 접근 가능
# 주요 ASAN_OPTIONS
# abort_on_error=1 : SIGABRT로 종료 (GDB에서 잡기 용이)
# halt_on_error=1 : 첫 번째 오류에서 정지
# detect_leaks=1 : 종료 시 메모리 누수 보고
# quarantine_size_mb=256: 해제된 메모리 격리 크기 (UAF 감지율 ↑)
# malloc_context_size=30: 할당/해제 스택 깊이
# fast_unwind_on_malloc=0: 정확한 스택 추적 (느림)
TSan + GDB
# TSan 빌드 (ASan과 동시 사용 불가)
gcc -g -O1 -fsanitize=thread -o prog prog.c -lpthread
# GDB에서 실행
gdb ./prog
(gdb) set env TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1
(gdb) run
# TSan 레이스 감지 시 보고서
# ==================
# WARNING: ThreadSanitizer: data race (pid=12345)
# Write of size 4 at 0x... by thread T2:
# #0 worker_func prog.c:42
# Previous read of size 4 at 0x... by thread T1:
# #0 reader_func prog.c:28
# TSan 내부 함수 중단점
(gdb) break __tsan_on_report # 레이스 보고 시 정지
(gdb) break __tsan_mutex_pre_lock # 뮤텍스 잠금 전 추적
# 모든 스레드 동시 분석
(gdb) thread apply all bt # 레이스 발생 시 모든 스레드 스택
UBSan + GDB
# UBSan 빌드 (ASan과 동시 사용 가능)
gcc -g -fsanitize=undefined,address -o prog prog.c
# UBSan 세부 검사 옵션
gcc -g -fsanitize=signed-integer-overflow,null,alignment,\
shift,unreachable,vla-bound,float-divide-by-zero,\
float-cast-overflow,bounds,bool,enum -o prog prog.c
# GDB에서 UBSan 오류 잡기
gdb ./prog
(gdb) set env UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
(gdb) run
# UBSan 오류 감지 시:
# prog.c:15:5: runtime error: signed integer overflow:
# 2147483647 + 1 cannot be represented in type 'int'
# → GDB에서 즉시 정지
# UBSan 핸들러에 중단점
(gdb) break __ubsan_handle_add_overflow # 정수 오버플로우
(gdb) break __ubsan_handle_type_mismatch # NULL/정렬 오류
(gdb) break __ubsan_handle_shift_out_of_bounds # 시프트 오류
| Sanitizer | GCC 옵션 | 핵심 환경변수 | 동시 사용 |
|---|---|---|---|
| ASan | -fsanitize=address | ASAN_OPTIONS | UBSan과 가능 |
| TSan | -fsanitize=thread | TSAN_OPTIONS | 단독 사용 |
| MSan | -fsanitize=memory (Clang) | MSAN_OPTIONS | 단독 사용 |
| UBSan | -fsanitize=undefined | UBSAN_OPTIONS | ASan과 가능 |
| LSan | -fsanitize=leak | LSAN_OPTIONS | ASan에 포함 |
다중 프로세스(Inferior) 디버깅
GDB의 Inferior는 디버깅 대상 프로세스 하나를 추상화합니다. 다중 Inferior를 사용하면 fork(), exec(), 독립 프로세스를 하나의 GDB 세션에서 동시에 디버깅할 수 있습니다. 클라이언트-서버 프로그램이나 IPC 통신 디버깅에 필수적입니다.
Inferior 관리 명령
# fork 디버깅: 양쪽 모두 유지
(gdb) set detach-on-fork off # fork 시 자식도 유지 (기본: detach)
(gdb) set follow-fork-mode parent # fork 후 부모 추적 (기본)
(gdb) run
# ... fork() 발생 ...
# [New inferior 2 (process 1001)]
# Inferior 목록 확인
(gdb) info inferiors
Num Description Connection Executable
* 1 process 1000 1 (native) /tmp/server
2 process 1001 1 (native) /tmp/server
# Inferior 간 전환
(gdb) inferior 2 # 자식 프로세스로 전환
(gdb) bt # 자식의 콜 스택
(gdb) inferior 1 # 부모로 복귀
# 독립 프로세스를 새 Inferior로 추가
(gdb) add-inferior # 빈 Inferior 3 추가
(gdb) inferior 3
(gdb) attach 2000 # 클라이언트 프로세스에 attach
(gdb) bt # 클라이언트 콜 스택
# 다른 실행 파일을 새 Inferior로 로드
(gdb) add-inferior -exec /tmp/client
(gdb) inferior 3
(gdb) run # 클라이언트 시작
# 모든 Inferior에 명령 적용
(gdb) thread apply all -ascending bt # 모든 Inferior의 모든 스레드
# 특정 Inferior의 스레드만 선택
(gdb) thread 2.1 # Inferior 2의 Thread 1로 전환
(gdb) break handle_request inferior 2 # Inferior 2에서만 중단
# exec 이벤트 추적
(gdb) set follow-exec-mode new-inferior # exec() 시 새 Inferior 생성
(gdb) catch exec # exec() 시점에 정지
(gdb) catch fork # fork() 시점에 정지
(gdb) catch vfork # vfork() 시점에 정지
# Inferior 제거
(gdb) remove-inferiors 3 # Inferior 3 제거
(gdb) kill inferiors 2 # Inferior 2 프로세스 종료
IPC 디버깅 패턴
# 서버-클라이언트 동시 디버깅 예시
# 터미널 1: 서버 시작
gdb ./server
(gdb) break handle_client
(gdb) run
# 터미널 2: 클라이언트를 같은 GDB에 추가 (GDB/MI 또는 다른 방법)
# 또는 서버 GDB 내에서:
(gdb) add-inferior -exec ./client
(gdb) inferior 2
(gdb) break send_request
(gdb) run --server=localhost:8080
# 클라이언트가 send_request에서 정지
# → Inferior 1(서버)로 전환하여 handle_client 확인
(gdb) inferior 1
(gdb) continue # 서버가 요청 수신 → handle_client 정지
(gdb) info locals # 수신된 데이터 확인
# 파이프/소켓 버퍼 확인
(gdb) info proc files # 열린 FD 목록
(gdb) call (int)fcntl(4, 1) # F_GETFL: FD 4의 플래그 확인
(gdb) call (int)recv(4, buf, 100, MSG_PEEK) # 소켓 버퍼 엿보기
리눅스 커널 디버깅 활용
maxcpus=1 커널 파라미터와 함께 사용합니다.
QEMU + GDB 커널 디버깅
# QEMU + GDB로 커널 디버깅
# -s = -gdb tcp::1234 (기본 포트 단축), -S = 시작 즉시 정지
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "nokaslr console=ttyS0" \
-serial tcp::1234,server,nowait \
-S -gdb tcp::1235 # -S: 시작 시 정지, GDB 포트 1235
# 호스트에서 GDB 연결
gdb vmlinux
(gdb) target remote :1235
(gdb) break start_kernel
(gdb) continue
# 멀티코어 환경 (-smp 4): GDB thread 명령으로 각 CPU 확인
# qemu-system-x86_64 -smp 4 -S -gdb tcp::1235 ...
(gdb) info threads # CPU별 스레드 목록 (Thread 1~4)
(gdb) thread 2 # CPU 1로 전환
(gdb) backtrace # 해당 CPU 스택
(gdb) thread apply all bt # 전체 CPU 스택 한 번에
# 부팅 초기 단계 디버깅 (start_kernel 이전, head.S)
(gdb) break startup_64 # x86_64 어셈블리 진입점 (arch/x86/kernel/head_64.S)
(gdb) break x86_64_start_kernel # C 코드 전환 직전
(gdb) break start_kernel # 커널 메인 C 진입점
# scripts/gdb 활용 (커널 빌드 디렉터리에서)
(gdb) source scripts/gdb/vmlinux-gdb.py
(gdb) lx-dmesg # 커널 로그 출력
(gdb) lx-ps # task_struct 기반 프로세스 목록
(gdb) lx-lsmod # 모듈 목록
(gdb) lx-symbols # 모듈 심볼 로드
(gdb) lx-mounts # 마운트 목록
# 커널 자료구조 탐색
(gdb) p init_task # 첫 번째 task_struct
(gdb) ptype struct task_struct # task_struct 레이아웃
(gdb) p $lx_current() # 현재 태스크
# 모듈 디버깅
(gdb) add-symbol-file drivers/foo/foo.ko 0x$(cat /sys/module/foo/sections/.text)
lx-* 명령
scripts/gdb/vmlinux-gdb.py를 로드하면 커널 전용 lx-* 명령이 등록됩니다. 이 명령들은 커널 자료구조를 Python으로 순회해 GDB에서 직접 커널 상태를 조회합니다.
| 명령 | 설명 |
|---|---|
lx-dmesg | 커널 링 버퍼 출력 (printk 로그) |
lx-ps | 프로세스 목록 (task_struct 기반) |
lx-lsmod | 로드된 모듈 목록 |
lx-symbols | 모듈 심볼 로드 (add-symbol-file 자동화) |
lx-mounts | 마운트(Mount) 목록 (/proc/mounts 대응) |
lx-clk-summary | 클록 트리 요약 (클록 주파수·상태) |
lx-device-list-bus | 버스별 디바이스 목록 |
lx-device-list-class | 클래스별 디바이스 목록 |
lx-device-list-tree | 디바이스 트리(Device Tree) 계층 출력 |
lx-fdtdump | 디바이스 트리 블록 덤프 (임베디드) |
lx-timerlist | 커널 타이머(Timer) 목록 (/proc/timer_list 대응) |
lx-genpd-summary | 전원 도메인(Generic PM Domain) 요약 |
lx-iomem | I/O 메모리 맵 (/proc/iomem 대응) |
lx-ioports | I/O 포트 맵 (/proc/ioports 대응) |
lx-version | 커널 버전 정보 출력 |
커스텀 lx-* 명령은 gdb.Command를 상속해 Python으로 작성합니다.
import gdb
class LxTaskWalk(gdb.Command):
"""모든 task_struct를 순회하는 커스텀 lx 명령"""
def __init__(self):
super().__init__("lx-taskwalk", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
init = gdb.parse_and_eval("init_task")
# for_each_process: tasks.next → 다음 task_struct (container_of)
# scripts/gdb/linux/tasks.py의 task_lists() 함수 참조
pid = int(init["pid"])
comm = init["comm"].string()
print(f"PID {pid}: {comm}")
# ... (링크드 리스트 순회 구현)
LxTaskWalk()
# (gdb) source lx_taskwalk.py
# (gdb) lx-taskwalk
커널 자료구조 GDB 탐색
GDB로 커널의 핵심 자료구조를 직접 탐색하는 실전 패턴입니다.
# 태스크 / 스케줄러
(gdb) p init_task.pid # init 프로세스 PID (0)
(gdb) p $lx_current()->comm # 현재 실행 중인 태스크 이름
(gdb) p $lx_current()->mm->mmap_base # 사용자 공간 mmap 베이스
(gdb) p *$lx_current()->sched_class # 스케줄러 클래스 구조체
(gdb) ptype struct task_struct # task_struct 전체 레이아웃
# 메모리 관리
(gdb) p/x init_task.mm->pgd # 페이지 디렉터리 기본 주소 (pgd_t)
(gdb) p init_mm.total_vm # 커널 VM 영역 크기 (페이지 수)
(gdb) p *virt_to_page(0xffffffff81000000) # 가상 주소 → page 구조체
(gdb) p ((struct page*)mem_map)[0] # 첫 번째 page 구조체
# VFS (가상 파일시스템)
(gdb) p $lx_current()->fs->root.dentry->d_iname # 루트 경로 이름
(gdb) p init_task.files->fdt->max_fds # 파일 디스크립터 최대 수
(gdb) p *init_task.files->fdt->fd[0] # fd 0번 (stdin) file 구조체
# 네트워크
(gdb) p init_net.dev_base_head # net_device 링크드 리스트 헤드
(gdb) p ((struct net_device*)init_net.dev_base_head.next)->name # 첫 번째 인터페이스
# 인터럽트 / IRQ
(gdb) p *irq_desc[0] # IRQ 0 디스크립터
(gdb) p irq_desc[0].action->name # IRQ 0 핸들러 이름
KASLR 처리 및 심볼 재베이스
KASLR(Kernel Address Space Layout Randomization)이 활성화된 커널에서는 심볼 주소가 매 부팅마다 달라집니다. GDB에서 올바른 심볼 조회를 위해 심볼 파일을 재베이스해야 합니다.
# KASLR 활성화 상태에서 커널 텍스트 오프셋 확인
$ cat /proc/kallsyms | grep ' _text$'
ffffffff91000000 T _text # 실제 적재 주소 (기준: 0xffffffff81000000)
# 오프셋: 0xffffffff91000000 - 0xffffffff81000000 = 0x10000000
# GDB에서 심볼 파일 재베이스 (-s 옵션으로 섹션별 주소 지정)
(gdb) add-symbol-file vmlinux \
-s .text 0xffffffff91000000 \
-s .data 0xffffffff92800000
# 자동화 Python 스크립트 (GDB 세션에서 실행)
python
kallsyms = open('/proc/kallsyms').readlines()
line = next(l for l in kallsyms if ' _text\n' in l)
actual = int(line.split()[0], 16)
offset = actual - 0xffffffff81000000
gdb.execute(f"add-symbol-file vmlinux -s .text {actual:#x}")
print(f"KASLR offset: {offset:#x}")
end
# QEMU 개발 환경: nokaslr로 KASLR 비활성화 권장
# -append "nokaslr console=ttyS0 ..."
KASAN / KCSAN + GDB 분석
커널 빌드 시 KASAN(Kernel Address Sanitizer)·KCSAN(Kernel Concurrency Sanitizer)을 활성화하면 메모리 오류와 경쟁 조건을 런타임에 감지합니다. GDB와 연계해 오류를 정밀 분석합니다.
# KASAN 빌드 설정
CONFIG_KASAN=y
CONFIG_KASAN_OUTLINE=y # 인스트루멘테이션 모드 (INLINE보다 디버깅 용이)
CONFIG_KASAN_GENERIC=y # 일반 KASAN (QEMU x86_64 지원)
# KCSAN 빌드 설정
CONFIG_KCSAN=y
CONFIG_KCSAN_REPORT_ONCE_IN_MS=3000 # 중복 보고 억제
# KASAN 오류 발생 시 GDB로 분석
(gdb) lx-dmesg # BUG: KASAN: use-after-free in ... 확인
(gdb) break kasan_report # KASAN 보고 함수에 중단점
(gdb) continue # 오류 발생 시 정지
# 오류 주소의 page 구조체 검사
(gdb) p *virt_to_page(0xffff888003b00000) # page 구조체 상태
(gdb) p ((struct page*)0xffff888003b00000)->_refcount # 참조 카운트
# KCSAN 경쟁 조건 감지 후 전체 CPU 스택 확인
(gdb) thread apply all bt # 경쟁하는 코드 경로 파악
# KASAN shadow 메모리 직접 확인 (x86_64, CONFIG_KASAN_GENERIC)
# shadow_addr = (addr >> 3) + 0xdffffc0000000000
python
addr = 0xffff888003b00000
shadow = (addr >> 3) + 0xdffffc0000000000
val = int(gdb.parse_and_eval(f"*(char*){shadow:#x}"))
print(f"Shadow: {val:#x} (0=접근가능, 음수=해제/무효)")
end
crash 도구와 병행 사용
# kdump로 생성된 vmcore 분석
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/vmcore
# GDB로 vmcore 직접 분석 (crash 대안)
gdb vmlinux vmcore
(gdb) target kdump vmcore # 일부 GDB 버전에서 지원
프로세스 메모리 레이아웃 검사
GDB로 프로세스의 가상 메모리(Virtual Memory) 구조를 시각적으로 이해하고 검사하는 것은 메모리 버그, 보안 분석, 성능 최적화의 기초입니다. 리눅스 x86_64 프로세스의 메모리 레이아웃과 GDB 검사 방법을 심층 정리합니다.
섹션별 상세 검사
# 전체 메모리 맵 확인
(gdb) info proc mappings
Start Addr End Addr Size Offset Perms File
0x555555554000 0x555555556000 0x2000 0x0 r--p /tmp/prog
0x555555556000 0x555555558000 0x2000 0x2000 r-xp /tmp/prog # .text
0x555555558000 0x555555559000 0x1000 0x4000 r--p /tmp/prog # .rodata
0x55555555a000 0x55555555b000 0x1000 0x5000 rw-p /tmp/prog # .data .bss
0x55555555b000 0x55555557c000 0x21000 0x0 rw-p [heap]
0x7ffff7d90000 0x7ffff7f22000 0x192000 0x0 r-xp libc.so.6
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack]
# ELF 섹션 정보
(gdb) maintenance info sections
[0] 0x555555554318->0x555555554334 .interp
[1] 0x555555554338->0x555555554358 .note.gnu.build-id
...
[13] 0x555555556000->0x555555557a52 .text ALLOC LOAD READONLY CODE
[14] 0x555555558000->0x555555558120 .rodata ALLOC LOAD READONLY DATA
# 스택 프레임 상세 검사
(gdb) info frame
Stack level 0, frame at 0x7fffffffde10:
rip = 0x555555556194 in main (prog.c:42)
Arglist at 0x7fffffffde00, args: argc=1, argv=0x7fffffffdf08
Locals at 0x7fffffffde00, Previous frame's sp is 0x7fffffffde10
Saved registers: rbp at 0x7fffffffde00, rip at 0x7fffffffde08
# 스택 내용 직접 검사 (16바이트 단위, 32줄)
(gdb) x/32gx $rsp
0x7fffffffdda0: 0x0000000000000001 0x00007fffffffdf08 # argc, argv
0x7fffffffddb0: 0x0000555555556194 0x00007fffffffde00 # rip, rbp
# 스택 canary 확인 (fs_base + 0x28)
(gdb) print /x *(long*)($fs_base + 0x28)
$1 = 0x1a2b3c4d5e6f7080 # stack canary 값
# ASLR 상태 확인
(gdb) info auxv
33 AT_SYSINFO_EHDR 0x7ffff7fc1000 # vDSO 주소
25 AT_RANDOM 0x7fffffffdf79 # 랜덤 시드 주소
3 AT_PHDR 0x555555554040 # 프로그램 헤더 (PIE 베이스)
# GOT/PLT 검사 (동적 링킹 관련)
(gdb) info functions @plt # PLT 함수 목록
(gdb) x/gx 0x55555555a018 # GOT 엔트리 확인 (printf@got)
(gdb) disassemble 0x555555556020,+16 # PLT 스텁 확인
힙 구조 심층 분석
# 힙 청크 직접 검사
(gdb) print ptr # 0x55555555b260 (malloc 반환 포인터)
(gdb) x/4gx (char*)ptr - 16 # 청크 헤더부터 확인
0x55555555b250: 0x0000000000000000 # prev_size
0x55555555b258: 0x0000000000000031 # size=0x30(48바이트) | P=1
0x55555555b260: 0x4141414141414141 # user data
0x55555555b268: 0x0000000000000000
# tcache 확인 (glibc 2.26+)
# tcache_perthread_struct는 각 스레드의 힙 첫 부분에 위치
(gdb) print *(tcache_perthread_struct*)0x55555555b010
# main_arena 확인 (멀티스레드 힙)
(gdb) print main_arena
(gdb) print main_arena.top # top 청크 (brk 경계)
(gdb) print main_arena.bins[0] # unsorted bin
Record & Replay
GDB의 기본 record full 외에도, rr(Mozilla)과 Intel PT(Processor Trace)를 활용한 고급 Record & Replay 기법이 있습니다. 비결정적 버그(레이스 컨디션, 하이젠버그)를 결정론적으로 재현합니다.
rr (Record and Replay)
# rr 설치 (Ubuntu/Debian)
apt install rr
# 또는 최신 버전
git clone https://github.com/rr-debugger/rr && cd rr && mkdir build && cd build
cmake .. && make -j$(nproc)
# perf_event_paranoid 설정 (필수)
echo 1 > /proc/sys/kernel/perf_event_paranoid
# 기록 (Record)
rr record ./prog arg1 arg2
# → Recording... (실행 완료까지 기록)
# → 기록 파일: ~/.local/share/rr/prog-N/
# 재생 (Replay) — GDB 인터페이스로 자동 연결
rr replay
# → (rr) 프롬프트 = GDB 명령 사용 가능
# 역방향 디버깅 (rr의 핵심 기능)
(rr) break main
(rr) continue # → main에서 정지
(rr) continue # → 프로그램 끝 (또는 크래시)
# 크래시 지점에서 역방향 추적
(rr) reverse-continue # 이전 중단점까지 역방향
(rr) reverse-step # 이전 소스 줄로
(rr) reverse-next # 이전 줄 (함수 건너뜀)
(rr) reverse-finish # 호출자로 역방향 복귀
# 감시점 + 역방향 = 언제 변경되었는지 추적
(rr) watch -l *0x601050 # 주소 감시
(rr) reverse-continue # → 이 주소를 마지막으로 수정한 지점으로 이동!
# 체크포인트 (기록 내 특정 시점 저장)
(rr) checkpoint # 현재 시점 저장
(rr) info checkpoints
(rr) restart 1 # 체크포인트 1로 이동
# rr의 이벤트 개념
(rr) when # 현재 이벤트 번호 출력
(rr) run 5000 # 이벤트 5000으로 이동
# rr 기록 목록 관리
rr ls # 기록 목록
rr replay -p PID # 특정 PID 재생 (fork 후)
rr pack # 기록을 공유 가능하게 패키징
Intel PT + GDB
# Intel PT 지원 확인
cat /proc/cpuinfo | grep intel_pt
# 또는
dmesg | grep "Intel PT"
# GDB에서 Intel PT 기록
(gdb) record btrace pt # Intel PT 기반 기록 시작
(gdb) continue
# ... 프로그램 실행 (브랜치 트레이스 하드웨어 기록) ...
Ctrl-c # 중단
# 기록된 실행 이력 탐색
(gdb) record instruction-history # 실행된 명령어 이력
(gdb) record function-call-history # 함수 호출 이력
# 역방향 실행
(gdb) reverse-step
(gdb) reverse-next
(gdb) reverse-continue
# record btrace vs record full 비교
# record btrace pt: 하드웨어 기록, 빠름, 분기만 기록 (데이터 변경 기록 안 됨)
# record full: 소프트웨어 기록, 느림, 모든 레지스터/메모리 변경 기록
# perf를 통한 Intel PT 수집 + GDB 분석
perf record -e intel_pt//u ./prog # 유저 모드 트레이스 기록
perf script --itrace=bep # 분기/이벤트/파워 디코딩
perf_event_paranoid ≤ 1 설정이 필요하며, 일부 하드웨어 기능(AVX-512, GPU 접근)은 지원되지 않습니다. VM 환경에서는 PMU 가상화(Virtualization)가 필요합니다.
실전 팁 모음
자주 쓰는 패턴
# 세그폴트 발생 지점 찾기
(gdb) run
# ... Segmentation fault ...
(gdb) backtrace # 크래시 지점 콜 스택
(gdb) frame 0
(gdb) print *ptr # null 포인터 확인
# 무한 루프 탈출
(gdb) Ctrl-c # 실행 중단
(gdb) backtrace # 어디에 갇혔는지 확인
(gdb) until 50 # 루프 밖으로 강제 이동
# 메모리 누수 추적 (감시점 활용)
(gdb) break malloc
(gdb) commands
> print $rdi # malloc 크기 (x86_64 ABI: 1번 인자)
> bt 3 # 호출 스택
> continue
> end
# 특정 변수 변경 추적
(gdb) watch global_flag
(gdb) continue # 변경될 때마다 정지
# 함수 호출 카운트
(gdb) break foo
(gdb) ignore 1 999 # 1000번째부터 정지
(gdb) continue
.gdbinit 프로젝트 설정 예시
# .gdbinit (프로젝트 루트)
set auto-load safe-path /
# 공통 설정
set print pretty on
set print array on
set print array-indexes on
set pagination off
set confirm off
# 소스 경로
directory /path/to/src
# 커스텀 명령
define bta
thread apply all backtrace
end
document bta
모든 스레드의 백트레이스를 출력합니다.
end
define runto
tbreak $arg0
continue
end
document runto
지정한 함수까지 한 번만 실행합니다.
사용법: runto <function_name>
end
GDB 명령어 빠른 참조
| 범주 | 명령 | 설명 |
|---|---|---|
| 실행 제어 | run / r | 프로그램 시작 |
continue / c | 계속 실행 | |
next / n | 다음 줄 (함수 건너뜀) | |
step / s | 다음 줄 (함수 진입) | |
finish | 현재 함수 완료 후 정지 | |
| 중단점 | break / b | 중단점 설정 |
tbreak | 임시 중단점 | |
watch | 감시점 (쓰기) | |
catch throw | 예외 잡기점 | |
| 데이터 검사 | print / p | 변수/표현식 출력 |
x /NFU addr | 메모리 덤프 | |
display | 자동 출력 등록 | |
info registers | 레지스터 목록 | |
| 스택 | backtrace / bt | 콜 스택 |
frame / f N | 프레임 전환 | |
up / down | 프레임 이동 | |
info locals | 지역 변수 | |
| 소스 | list / l | 소스 출력 |
disassemble | 어셈블리 출력 | |
directory | 소스 경로 추가 | |
| 역방향 | record full | 실행 기록 시작 |
reverse-step | 역단계 실행 | |
reverse-continue | 역방향 계속 실행 | |
| 원격 | target remote :1234 | gdbserver 연결 |
load | 타깃에 파일 업로드 | |
monitor reset | 타깃 리셋 (JTAG) |
실전 버그 케이스 스터디
① Use-After-Free (UAF) 디버깅
/* UAF 예제 */
struct Node { int val; struct Node *next; };
struct Node *create(int v) {
struct Node *n = malloc(sizeof(*n));
n->val = v; n->next = NULL; return n;
}
void process(struct Node *n) { printf("%d\n", n->val); } /* UAF 발생 가능 */
// 디버깅 세션
$ gdb -q ./uaf_prog
(gdb) start
// 방법 1: malloc/free에 중단점 → 추적
(gdb) break malloc
(gdb) commands
> silent
> set $malloc_ptr = $rax /* 반환값 = 할당 주소 (ABI: rax) */
> printf "[malloc] %lu bytes → %p\n", $rdi, $rax
> continue
> end
(gdb) break free
(gdb) commands
> silent
> printf "[free] %p\n", $rdi
> bt 5
> continue
> end
(gdb) run
// 방법 2: AddressSanitizer (ASAN) + GDB
$ gcc -g -fsanitize=address -o prog prog.c
$ gdb prog
(gdb) run
// ... ASAN이 UAF 감지하면 정지 → bt로 위치 확인
// 방법 3: 감시점으로 해제된 메모리 쓰기 감지
(gdb) break free
(gdb) commands
> silent
> watch -l *$rdi /* 해제될 메모리 감시 */
> continue
> end
② 데드락 디버깅
// 데드락 발생 시 Ctrl-c로 인터럽트 후
(gdb) ^C
(gdb) info threads
Id Target Id Frame
1 Thread 0x... pthread_cond_wait @ glibc
2 Thread 0x... __lll_lock_wait @ glibc
3 Thread 0x... __lll_lock_wait @ glibc
// 각 스레드 스택 확인
(gdb) thread apply all bt
// 스레드 1 스택 분석
(gdb) thread 1
(gdb) bt
#0 pthread_cond_wait → 조건 변수 대기 중
#1 worker_thread (arg=0x0) → 어느 mutex를 기다리는지 확인
// 잠금 상태 확인 (glibc pthread_mutex_t 내부)
(gdb) print mutex_a # mutex_a.__data.__lock = 1 → 잠긴 상태
(gdb) print mutex_a.__data.__owner # 잠근 스레드 TID
// 스레드 TID ↔ GDB 스레드 ID 대응
(gdb) info threads # TID 열 확인
// Python으로 데드락 분석 자동화
python
import gdb
for t in gdb.selected_inferior().threads():
t.switch()
frame = gdb.selected_frame()
while frame:
if "lll_lock_wait" in (frame.name() or ""):
print(f"Thread {t.num} 잠금 대기 중")
frame = frame.older()
end
③ 레이스 컨디션 디버깅
// 방법 1: 감시점으로 공유 변수 접근 감지
(gdb) watch -l shared_counter // shared_counter 쓰기 감지
(gdb) awatch shared_counter // 읽기+쓰기 모두 감지
(gdb) continue
// ... 쓰기 발생 시 정지 → bt로 어느 스레드에서 수정했는지 확인
// 방법 2: scheduler-locking으로 재현
(gdb) set scheduler-locking on // 현재 스레드만 실행
(gdb) thread 1
(gdb) next // 스레드 1을 임계 구역 직전까지 이동
(gdb) set scheduler-locking off // 스레드 2가 실행되도록 해제
(gdb) thread 2
(gdb) next // 스레드 2가 먼저 임계 구역 진입
(gdb) set scheduler-locking on // 다시 잠금
(gdb) thread 1
(gdb) next // 레이스 발생 조건 재현
// 방법 3: Thread Sanitizer (TSan)
$ gcc -g -fsanitize=thread -o prog prog.c
$ gdb prog
(gdb) run
// TSan이 레이스 감지 → 중단 → 상세 보고서
④ 힙 손상 (Heap Corruption) 디버깅
// glibc malloc 무결성 검사 활성화
(gdb) call mallopt(M_CHECK_ACTION, 3) // 힙 손상 즉시 abort+core
// 힙 상태 확인 함수 호출
(gdb) call malloc_info(0, stdout) // 힙 통계
(gdb) call malloc_stats() // 힙 사용 요약
// 할당/해제 추적
(gdb) set env MALLOC_CHECK_=3 // 환경 변수로 검사 활성화
(gdb) run
// mtrace 활용 (프로그램에 mtrace() 삽입 필요)
$ MALLOC_TRACE=/tmp/mtrace.log ./prog
$ mtrace ./prog /tmp/mtrace.log // 누수 분석
// Valgrind + GDB 연동
$ valgrind --vgdb=yes --vgdb-error=0 ./prog &
$ gdb ./prog
(gdb) target remote | vgdb // Valgrind에 연결
(gdb) monitor leak_check full // 메모리 누수 검사
(gdb) monitor get_vbits addr len // 초기화 여부 비트맵
임베디드/JTAG 디버깅
GDB는 JTAG/SWD 디버그 프로브(Probe)를 통해 베어메탈 펌웨어(Firmware), RTOS, 부트로더(Bootloader)를 디버깅하는 표준 도구입니다. OpenOCD, J-Link GDB Server, pyOCD 등이 GDB Remote Protocol로 프로브를 추상화합니다.
OpenOCD + GDB
# OpenOCD 서버 시작 (ST-Link + STM32F4)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# → Info : Listening on port 3333 for gdb connections
# → Info : Listening on port 4444 for telnet connections
# GDB 연결 (ARM 크로스 컴파일러 GDB 사용)
arm-none-eabi-gdb firmware.elf
(gdb) target remote :3333
# Flash 프로그래밍
(gdb) monitor reset halt # 리셋 후 정지
(gdb) monitor flash write_image erase firmware.bin 0x08000000
(gdb) load # ELF에서 자동 Flash 기록
# 임베디드 디버깅 기본 워크플로우
(gdb) monitor reset halt
(gdb) break main
(gdb) continue
(gdb) info registers # ARM 레지스터 확인
(gdb) print /x *SCB # System Control Block 레지스터
# OpenOCD monitor 명령
(gdb) monitor reset init # 리셋 + 초기화 스크립트 실행
(gdb) monitor halt # CPU 정지
(gdb) monitor resume # CPU 재개
(gdb) monitor reg # 레지스터 (OpenOCD 형식)
(gdb) monitor mdw 0x40021000 4 # 메모리 워드 읽기 (RCC 레지스터 등)
(gdb) monitor mww 0x40021000 0x01 # 메모리 워드 쓰기
(gdb) monitor bp 0x08001234 4 hw # 하드웨어 중단점 (Flash)
# 세미호스팅 (타겟의 printf를 GDB 콘솔로 리다이렉트)
(gdb) monitor arm semihosting enable
# 타겟 코드에서 printf() 호출 → GDB 콘솔에 출력
J-Link + GDB
# J-Link GDB Server 시작
JLinkGDBServer -device STM32F407VG -if SWD -speed 4000 -port 2331
# GDB 연결
arm-none-eabi-gdb firmware.elf
(gdb) target remote :2331
(gdb) monitor reset # 타겟 리셋
(gdb) monitor halt
(gdb) load # Flash 프로그래밍
# J-Link RTT (Real-Time Transfer) — UART 없이 고속 로깅
# 타것 코드에서 SEGGER_RTT_printf() → J-Link 버퍼 → 호스트
(gdb) monitor exec SetRTTAddr 0x20000000 # RTT 컨트롤 블록 주소
# SWO (Serial Wire Output) — ITM 트레이스 출력
(gdb) monitor SWO EnableTarget 4000000 0 1 0 # SWO 활성화
RISC-V + GDB
# OpenOCD + RISC-V (FTDI 기반 프로브)
openocd -f interface/ftdi/olimex-arm-usb-tiny-h.cfg \
-f target/riscv.cfg
# RISC-V GDB 연결
riscv64-unknown-elf-gdb firmware.elf
(gdb) target remote :3333
# RISC-V 트리거 (하드웨어 중단점/감시점)
(gdb) hbreak *0x80000000 # 하드웨어 BP (tdata1 CSR 사용)
(gdb) watch *0x80001000 # 하드웨어 WP (match control)
# RISC-V CSR(Control and Status Register) 접근
(gdb) info registers csr # 모든 CSR 레지스터
(gdb) print /x $mstatus # Machine Status Register
(gdb) print /x $mcause # Machine Cause (예외/인터럽트 원인)
(gdb) print /x $mepc # Machine Exception PC
# 다중 hart(코어) 디버깅
(gdb) info threads # hart별 스레드 목록
(gdb) thread 2 # hart 1로 전환
cortex-debug 확장으로 VS Code에서 GDB+OpenOCD를 GUI로 사용할 수 있습니다. launch.json에서 "servertype": "openocd"와 "configFiles"를 설정하면 Flash 프로그래밍·브레이크포인트·변수 감시를 GUI에서 수행합니다.
GDB 확장 도구
GDB의 기능을 크게 확장하는 서드파티 플러그인들이 있습니다. 특히 보안 연구, CTF(Capture The Flag), 저수준 디버깅에서 필수적으로 활용됩니다.
pwndbg
익스플로잇 개발과 CTF에 특화된 GDB 플러그인입니다. 힙 분석, ROP 가젯 탐색, 메모리 맵 시각화 등을 제공합니다.
# 설치
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
# 주요 추가 명령
(pwndbg) context # 레지스터·스택·어셈블리·백트레이스 종합 출력
(pwndbg) heap # glibc 힙 청크 목록
(pwndbg) bins # free 청크 bin 목록 (fastbin/unsorted/small/large/tcache)
(pwndbg) arena # malloc_state (arena) 구조체
(pwndbg) vis_heap_chunks # 힙 시각화
(pwndbg) got # GOT(Global Offset Table) 항목 및 실제 주소
(pwndbg) plt # PLT 항목 목록
(pwndbg) checksec # 바이너리 보안 설정 확인 (ASLR/NX/PIE/RELRO/Stack Canary)
(pwndbg) rop # ROP 가젯 탐색
(pwndbg) ropper # ropper 도구 연동
(pwndbg) search -s "AAAA" # 메모리에서 패턴 탐색 (find 확장)
(pwndbg) cyclic 100 # De Bruijn 패턴 생성 (오프셋 계산)
(pwndbg) cyclic -l 0x6161616161616166 # 패턴에서 오프셋 역산
(pwndbg) vmmap # 메모리 맵 (권한 색상 표시)
(pwndbg) stack 30 # 스택 내용 30줄
(pwndbg) telescope $rsp # 포인터 체인 재귀 역참조
(pwndbg) retaddr # 현재 함수 반환 주소 위치
GEF (GDB Enhanced Features)
pwndbg와 유사한 기능을 제공하는 단일 파일 플러그인입니다. 설치가 간단하며 AARCH64/MIPS 지원이 강점입니다.
# 설치 (단일 Python 파일)
bash -c "$(wget -q -O- https://gef.blah.cat/sh)"
# 또는
wget -O ~/.gdbinit-gef.py https://github.com/hugsy/gef/raw/main/gef.py
echo "source ~/.gdbinit-gef.py" >> ~/.gdbinit
# 주요 추가 명령
(gef) context # 종합 컨텍스트 출력
(gef) heap chunks # 힙 청크
(gef) heap bins # free bin
(gef) got # GOT 테이블
(gef) checksec # 보안 설정
(gef) pattern create 100 # De Bruijn 패턴
(gef) pattern search 0x6161616161616166
(gef) elf-info # ELF 헤더 정보
(gef) xinfo 0x400520 # 주소 상세 정보 (섹션, 심볼, 권한)
(gef) format-string-helper # 포맷 스트링 취약점 탐색
(gef) scan section1 section2 # 섹션1에서 섹션2의 포인터 탐색
gdb-dashboard
TUI 대안으로 Python으로 구현된 대시보드형 GDB 플러그인입니다. 모듈별 패널 구성이 가능합니다.
# 설치
wget -P ~ git.io/.gdbinit
# 설정 (.gdbinit 또는 대화형)
(gdb) dashboard -enabled yes
(gdb) dashboard -style main_bacground '#111111'
# 패널 선택
(gdb) dashboard -layout source assembly registers stack threads expressions history
# 개별 모듈 설정
(gdb) dashboard source -style height 15
(gdb) dashboard registers -style list 'rax rbx rcx rdx rsi rdi rsp rbp rip eflags'
| 도구 | 특징 | 주요 용도 | 설치 복잡도 |
|---|---|---|---|
| pwndbg | 힙 분석·ROP·CTF 특화, 활발한 개발 | 익스플로잇 개발, CTF | 중 (setup.sh) |
| GEF | 단일 파일, ARM/MIPS 강점, 원격 디버깅 | 크로스 아키텍처 디버깅 | 쉬움 (wget) |
| peda | 오래된 플러그인, 단순하고 안정적 | 기본 보안 분석 | 쉬움 |
| gdb-dashboard | 모듈형 TUI 대안, 커스터마이즈 자유도 높음 | 일반 개발 디버깅 | 쉬움 (wget) |
| Voltron | 멀티 창 분할 (tmux/iTerm2 연동) | 복잡한 UI 환경 | 중 |
GDB 내부 아키텍처
GDB는 단순한 명령줄 도구가 아니라, 복잡한 계층형 아키텍처를 가진 프레임워크입니다. 내부 구조를 이해하면 확장(Python API, MI 프로토콜)과 문제 해결에 큰 도움이 됩니다.
타겟 스택 (Target Stack)
GDB의 핵심 추상화인 타겟 스택은 여러 타겟 백엔드를 계층적으로 쌓아 기능을 조합합니다. 각 타겟은 struct target_ops를 구현하며, 상위 타겟이 처리하지 못하는 요청은 하위로 위임됩니다.
# 현재 타겟 스택 확인
(gdb) maint print target-stack
The current target stack is:
- record-full (Process record and target replay)
- remote (Remote serial target in gdb-specific protocol)
- exec (Local exec file)
중단점 내부 메커니즘
GDB 중단점은 내부적으로 소프트웨어 중단점(명령어 패치)과 하드웨어 중단점(디버그 레지스터)으로 나뉩니다.
| 구분 | 소프트웨어 중단점 | 하드웨어 중단점 |
|---|---|---|
| 메커니즘 | 명령어를 INT3(0xCC)로 패치 | CPU 디버그 레지스터(DR0-DR3) 설정 |
| 개수 제한 | 제한 없음 (메모리 공간만큼) | x86: 4개, ARM: 2-16개 |
| 실행 영향 | 최소 (명령 1바이트 교체) | 없음 (CPU 내장) |
| ROM/Flash | 불가 (쓰기 불가 메모리) | 가능 |
| 조건부 | 히트 시 GDB가 조건 평가 | 주소 매치만 (조건은 GDB 측) |
| 감시점 | 전체 메모리 단일스텝 (극히 느림) | DR0-DR3으로 고속 감시 |
| GDB 명령 | break (기본) | hbreak / watch (하드웨어) |
# 하드웨어 중단점 직접 사용
(gdb) hbreak *0x401000 # ROM/Flash 코드에 하드웨어 BP
(gdb) info break
Num Type Disp Enb Address What
1 hw breakpoint keep y 0x0000000000401000
# 하드웨어 감시점 내부
(gdb) watch -l *(int*)0x601050 # 주소 직접 지정 감시
(gdb) show can-use-hw-watchpoints # 하드웨어 감시점 사용 가능 여부
(gdb) maint info break # 내부 중단점 목록 (숨겨진 것 포함)
# 중단점 명령어 패치 확인
# break main 설정 전: 원래 명령어
(gdb) x/1bx main # 0x55: push rbp
# break main 설정 후: INT3으로 패치됨
# 실제로는 GDB가 shadow copy를 유지하여 사용자에게는 원래 명령 표시
# 중단점 히트 처리 흐름:
# 1. CPU가 INT3 실행 → SIGTRAP 발생
# 2. 커널이 ptrace로 GDB에 전달
# 3. GDB가 주소로 중단점 매칭
# 4. 조건 평가 (있으면)
# 5. 원래 명령어 복원 → 단일스텝 → 다시 INT3 삽입
표현식 평가 엔진
GDB의 print, call, 조건부 중단점에서 사용되는 표현식 평가 엔진은 대상 언어의 문법을 이해합니다.
# 표현식에서 타겟 함수 호출 (inferior call)
(gdb) call strlen("hello") # 타겟 프로세스에서 strlen 실행
(gdb) print malloc(100) # 타겟 힙에서 100바이트 할당
# inferior call 동작 원리:
# 1. 현재 레지스터/스택 상태 저장
# 2. 인자를 레지스터/스택에 설정 (ABI 규약)
# 3. PC를 함수 주소로 변경
# 4. 반환 주소에 중단점 설정
# 5. resume → 함수 실행 → 반환 시 중단
# 6. 반환값 읽기 → 원래 상태 복원
# inferior call 위험성
(gdb) set unwindonsignal on # call 중 시그널 → 자동 복구
(gdb) set unwind-on-terminating-exception on # 예외 → 자동 복구
# 표현식 타입 캐스팅
(gdb) print (struct task_struct *)0xffff888003b00000
(gdb) print {int}0x601050 # 주소를 int로 해석 (C에 없는 GDB 문법)
(gdb) print *array@10 # 인공 배열: array부터 10개 요소
# 현재 언어 확인/변경
(gdb) show language # auto; currently c++
(gdb) set language rust # 강제 언어 변경 (표현식 문법 변경)
비동기 실행 모델
# GDB 이벤트 루프 (main event loop)
# - stdin (사용자 명령) 감시
# - target fd (ptrace/remote 이벤트) 감시
# - timer (타임아웃, 폴링) 처리
# 동기 vs 비동기 모드
(gdb) show target-async # 비동기 타겟 모드 확인
(gdb) set target-async on # non-stop에 필요
# 비동기 모드에서 명령 실행
(gdb) continue & # 백그라운드 실행 (프롬프트 즉시 반환)
(gdb) interrupt # 실행 중단
# 내부 디버깅 (GDB 자체 디버깅)
(gdb) set debug infrun 1 # 실행 제어 엔진 디버그 출력
(gdb) set debug target 1 # 타겟 계층 디버그 출력
(gdb) set debug remote 1 # 원격 프로토콜 패킷 출력
(gdb) set debug event 1 # 이벤트 루프 디버그
(gdb) set debug lin-lwp 1 # Linux LWP (경량 프로세스) 디버그
# GDB 원격 프로토콜 패킷 예시 (debug remote 1 출력)
# → $m7fffffffe000,100#xx (메모리 읽기: 주소, 길이)
# ← $xx...xx#xx (데이터 응답)
# → $Z0,401000,1#xx (소프트웨어 BP 삽입)
# ← $OK#xx (성공)
# → $c#xx (continue)
# ← $T05thread:01;#xx (SIGTRAP, 스레드 1)
GDB/MI 인터페이스 내부 구조
GDB/MI(Machine Interface)는 IDE, 프론트엔드, 자동화 스크립트가 GDB와 구조화된 방식으로 통신하기 위한 프로토콜입니다. CLI와 달리 파싱하기 쉬운 형식으로 출력하며, 모든 현대 디버거 프론트엔드(VS Code, Eclipse CDT, CLion, Emacs GUD)가 이 인터페이스를 사용합니다.
MI 명령 체계
MI 명령은 -<category>-<operation> 형식을 따릅니다. CLI 명령과 1:1 대응되지만, 출력이 구조화되어 파싱이 용이합니다.
| MI 카테고리 | 주요 명령 | CLI 대응 |
|---|---|---|
-exec-* | -exec-run, -exec-continue, -exec-next, -exec-step | run, continue, next, step |
-break-* | -break-insert, -break-delete, -break-condition | break, delete, condition |
-data-* | -data-evaluate-expression, -data-read-memory, -data-list-register-values | print, x, info registers |
-stack-* | -stack-list-frames, -stack-list-locals, -stack-list-arguments | bt, info locals, info args |
-thread-* | -thread-info, -thread-select | info threads, thread N |
-var-* | -var-create, -var-update, -var-evaluate-expression | (MI 전용 변수 객체) |
-target-* | -target-select, -target-attach | target remote, attach |
-file-* | -file-exec-and-symbols, -file-list-exec-source-files | file, info sources |
# MI 모드로 GDB 기동
gdb -i=mi ./prog
# MI 명령 예시 (토큰 번호 + 명령)
1-break-insert main
# 응답: 1^done,bkpt={number="1",type="breakpoint",disp="keep",...
2-exec-run
# 응답: 2^running
# 비동기: *stopped,reason="breakpoint-hit",bkptno="1",frame={...}
3-data-evaluate-expression "argc"
# 응답: 3^done,value="2"
4-stack-list-frames
# 응답: 4^done,stack=[frame={level="0",addr="0x401142",func="main",...}]
5-var-create - * "argv[0]"
# 응답: 5^done,name="var1",numchild="0",value="0x7fffffffe38a",type="char *"
# CLI 명령을 MI에서 실행 (호환용)
6-interpreter-exec console "info proc mappings"
# 비동기 통지 예시
# =thread-created,id="2",group-id="i1"
# =library-loaded,id="/lib/x86_64-linux-gnu/libc.so.6",...
# *stopped,reason="signal-received",signal-name="SIGSEGV"
VS Code + GDB/MI 설정
// .vscode/launch.json — C/C++ Extension (cppdbg)
{
"version": "0.2.0",
"configurations": [
{
"name": "GDB 로컬 디버깅",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/prog",
"args": ["--config", "test.cfg"],
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{ "text": "set print pretty on" },
{ "text": "set pagination off" }
]
},
{
"name": "QEMU 커널 디버깅",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/vmlinux",
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerServerAddress": "localhost:1234",
"setupCommands": [
{ "text": "set pagination off" },
{ "text": "source scripts/gdb/vmlinux-gdb.py" }
]
}
]
}
심볼 테이블과 DWARF 심층 분석
GDB가 소스 레벨 디버깅을 수행하려면 바이너리의 DWARF 디버그 정보를 파싱하여 내부 심볼 테이블을 구축해야 합니다. 이 과정의 내부 구조를 이해하면 "심볼 없음" 문제, 심볼 로딩 지연, 대규모 프로젝트의 디버깅 성능 문제를 해결할 수 있습니다.
# 심볼 로딩 성능 진단
(gdb) set verbose on # 심볼 로딩 과정 출력
(gdb) maint print statistics # 심볼 테이블 통계
(gdb) maint print objfiles # 로드된 오브젝트 파일 목록
(gdb) maint info symtabs # 심볼 테이블 상세 (partial/full)
(gdb) maint expand-symtabs # 모든 partial symtab 확장 (느림)
# .gdb_index 생성 (빌드 후)
gdb-add-index ./my_program # .gdb_index 섹션 추가
# 효과: GDB 시작 시 심볼 검색 속도 10~100배 향상
# debuginfod 설정 (자동 디버그 정보 다운로드)
export DEBUGINFOD_URLS="https://debuginfod.elfutils.org/"
(gdb) set debuginfod enabled on # GDB 내부에서 활성화
(gdb) set debuginfod verbose 1 # 다운로드 진행 표시
# 분리 디버그 정보 수동 로드
(gdb) symbol-file /usr/lib/debug/.build-id/ab/cdef1234.debug
(gdb) set debug-file-directory /usr/lib/debug # 디버그 파일 검색 경로
# DWARF 디버그 정보 직접 확인 (readelf/llvm-dwarfdump)
readelf --debug-dump=info prog | head -50 # .debug_info DIE 목록
readelf --debug-dump=line prog # 행번호 매핑
llvm-dwarfdump --statistics prog # DWARF 통계
# GDB 내부에서 DWARF DIE 직접 탐색
(gdb) maint print dwarf-attribute DW_AT_name # 특정 속성 검색
(gdb) set debug dwarf-die 1 # DWARF DIE 파싱 디버그
(gdb) set debug dwarf-line 1 # 행번호 매핑 디버그
커널 GDB 스크립트 (lx-*) 완전 가이드
리눅스 커널 소스의 scripts/gdb/ 디렉터리에는 GDB Python 확장으로 구현된 lx-* 명령 모음이 있습니다. 이 스크립트들은 커널 자료구조를 직접 순회하여 GDB에서 커널 상태를 조회합니다. vmlinux-gdb.py가 진입점이며, 각 기능은 scripts/gdb/linux/ 하위 모듈로 구현되어 있습니다.
lx-dmesg 내부 동작
lx-dmesg는 커널 링 버퍼(struct printk_ringbuffer)를 직접 순회하여 커널 로그를 출력합니다. /proc/kmsg나 dmesg 명령 없이도 GDB만으로 커널 메시지를 확인할 수 있습니다.
# lx-dmesg 내부 동작 원리 (scripts/gdb/linux/dmesg.py 기반)
# 1. prb (printk_ringbuffer) 전역 변수 접근
# 2. desc_ring에서 각 descriptor 순회
# 3. text_data_ring에서 메시지 텍스트 추출
# 4. 타임스탬프(nsec) → 사람이 읽을 수 있는 형식 변환
import gdb
class LxDmesgDetailed(gdb.Command):
"""lx-dmesg 확장: 로그 레벨 필터링 지원"""
def __init__(self):
super().__init__("lx-dmesg-filter", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
level_filter = int(arg) if arg else 7 # 0=EMERG ~ 7=DEBUG
log_buf = gdb.parse_and_eval("prb")
# printk_ringbuffer → desc_ring → text_data_ring 순회
# 각 레코드의 level 필드 확인 후 필터링
print(f"[lx-dmesg-filter] 레벨 {level_filter} 이하 메시지만 출력")
# ... (링 버퍼 순회 구현)
LxDmesgDetailed()
lx-symbols 상세 사용법
lx-symbols는 커널 모듈의 심볼을 자동으로 로드합니다. 모듈이 동적으로 로드/언로드되는 환경에서 각 모듈의 .text 섹션 주소를 자동 감지하여 add-symbol-file을 실행합니다.
# lx-symbols 기본 사용
(gdb) lx-symbols
loading vmlinux
# scanning for modules in /path/to/kernel/build
# loading @0xffffffffa0000000: /path/to/module/foo.ko
# 모듈 빌드 디렉터리 지정 (여러 경로 가능)
(gdb) lx-symbols /path/to/modules/ /another/path/
# 모듈 심볼이 자동 로드된 후 모듈 함수에 중단점 설정
(gdb) break my_module_init
(gdb) break my_module_ioctl
# 수동으로 모듈 심볼 로드 (lx-symbols 실패 시)
# 1. 타겟에서 모듈 섹션 주소 확인
(gdb) p/x ((struct module *)$lx_module_by_name("foo"))->core_layout.base
# 또는 /sys/module/foo/sections/.text 에서 확인
# 2. add-symbol-file로 수동 로드
(gdb) add-symbol-file /path/to/foo.ko 0xffffffffa0001000
# 모듈 로드 이벤트 자동 감지 (lx-symbols 내부)
# scripts/gdb/linux/symbols.py는 do_init_module()에
# 내부 중단점을 설정하여 새 모듈 로드 시 자동 심볼 추가
# 현재 로드된 모듈 확인
(gdb) lx-lsmod
# Address Module Size Used by
# 0xffffffffa0000000 foo 16384 0
# 0xffffffffa0010000 bar 24576 1 foo
# 모듈 의존성 확인
(gdb) p ((struct module *)$lx_module_by_name("foo"))->source_list
(gdb) p ((struct module *)$lx_module_by_name("foo"))->target_list
lx-* 고급 활용 패턴
# lx-ps 확장: 특정 상태의 프로세스만 필터링
(gdb) lx-ps
# TASK_RUNNING(0), TASK_INTERRUPTIBLE(1), TASK_UNINTERRUPTIBLE(2)
# Python으로 D 상태(TASK_UNINTERRUPTIBLE) 프로세스만 추출
python
import gdb
from linux import tasks
for t in tasks.task_lists():
state = int(t["__state"])
if state == 2: # TASK_UNINTERRUPTIBLE
pid = int(t["pid"])
comm = t["comm"].string()
print(f"[D state] PID {pid}: {comm}")
end
# 특정 태스크의 커널 스택 덤프
python
task = gdb.parse_and_eval("init_task")
stack = task["stack"]
print(f"Stack base: {stack}")
# 스택 포인터에서 thread_info 추출
end
# lx-mounts 활용
(gdb) lx-mounts
# mount: /dev/sda1 -> / type: ext4
# mount: proc -> /proc type: proc
# lx-iomem / lx-ioports 로 I/O 리소스 맵 확인
(gdb) lx-iomem
# 00000000-0009ffff : System RAM
# 000a0000-000bffff : PCI Bus 0000:00
# ...
# lx-version 으로 커널 빌드 정보 확인
(gdb) lx-version
# Linux version 6.8.0-rc1 (gcc 13.2.0) #1 SMP PREEMPT_DYNAMIC ...
# lx-device-list-bus 로 PCI/USB 디바이스 확인
(gdb) lx-device-list-bus pci
(gdb) lx-device-list-bus usb
# lx-clk-summary 로 클록 트리 확인 (임베디드/SoC)
(gdb) lx-clk-summary
QEMU + GDB 커널 디버깅
QEMU는 GDB stub을 내장하고 있어 GDB Remote Protocol로 직접 연결할 수 있습니다. 실제 하드웨어 없이 커널을 완전히 제어할 수 있으므로, 커널 개발에서 가장 효율적인 디버깅 환경입니다.
QEMU 커널 디버깅 환경 구축
# 1. 디버깅용 커널 빌드 설정
make menuconfig
# 필수 설정:
CONFIG_DEBUG_INFO=y # DWARF 디버그 정보
CONFIG_DEBUG_INFO_DWARF5=y # DWARF 5 (권장)
CONFIG_GDB_SCRIPTS=y # scripts/gdb 활성화
CONFIG_FRAME_POINTER=y # 정확한 백트레이스
CONFIG_RANDOMIZE_BASE=n # KASLR 비활성화 (디버깅 편의)
CONFIG_DEBUG_INFO_REDUCED=n # 디버그 정보 축소 비활성화
CONFIG_KGDB=y # KGDB 지원 (선택)
CONFIG_STRICT_KERNEL_RWX=n # 코드 패치 허용 (SW BP용)
# 2. 최소 initramfs 생성 (BusyBox 기반, 상세: busybox.html)
mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,tmp}
# BusyBox 정적 바이너리 복사
cp busybox initramfs/bin/
cd initramfs/bin && ln -s busybox sh && ln -s busybox ls && cd ../..
# init 스크립트
cat > initramfs/init <<'INITEOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
echo "=== Debug Kernel Booted ==="
exec /bin/sh
INITEOF
chmod +x initramfs/init
# cpio 아카이브 생성
cd initramfs && find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz && cd ..
# 3. QEMU 기동 (디버그 모드)
qemu-system-x86_64 \
-kernel arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "nokaslr console=ttyS0 loglevel=7" \
-serial stdio \
-m 1G \
-smp 4 \
-enable-kvm \
-S -gdb tcp::1234 \
-nographic # 그래픽 없이 콘솔만
# 4. GDB 연결 (별도 터미널)
gdb vmlinux
(gdb) target remote :1234
(gdb) source scripts/gdb/vmlinux-gdb.py
(gdb) break start_kernel
(gdb) continue
# → Breakpoint 1, start_kernel () at init/main.c:...
# 5. virtio 디바이스 디버깅
(gdb) break virtio_dev_probe # virtio 디바이스 프로브 감시
(gdb) break virtnet_open # virtio-net 열기 감시
(gdb) break virtblk_make_request # virtio-blk I/O 요청 감시
# 6. 9p 파일시스템으로 호스트-게스트 파일 공유
# QEMU 옵션 추가:
# -virtfs local,path=/path/to/shared,mount_tag=shared,security_model=mapped-xattr
# 게스트에서 마운트:
# mount -t 9p -o trans=virtio shared /mnt
QEMU 환경 모듈 디버깅
# 모듈을 게스트에 전달 (9p 또는 initramfs에 포함)
# 게스트에서 모듈 로드
# (guest) insmod /mnt/my_module.ko
# GDB에서 모듈 심볼 자동 로드
(gdb) lx-symbols /path/to/module/build/
# loading @0xffffffffa0000000: my_module.ko
# 모듈 함수에 중단점 설정
(gdb) break my_module_init
(gdb) break my_module_ioctl
(gdb) continue
# 모듈 로드 시점 자동 감시 (do_init_module에 잡기)
(gdb) break do_init_module
(gdb) commands
silent
lx-symbols
continue
end
# 모듈의 특정 구조체 검사
(gdb) p *((struct module *)$lx_module_by_name("my_module"))
(gdb) p/x ((struct module *)$lx_module_by_name("my_module"))->core_layout
# → {base = 0xffffffffa0000000, size = 0x4000, text_size = 0x1000, ...}
QEMU 초기 부팅 디버깅
# BIOS → 부트로더 → 커널 진입까지 추적
# -S 옵션으로 QEMU는 첫 명령 실행 전에 정지
(gdb) target remote :1234
# 현재 위치: BIOS 코드 (리얼 모드)
# x86_64 부팅 순서별 중단점
(gdb) break *0x7c00 # 부트로더 진입 (MBR)
(gdb) hbreak startup_64 # 64비트 모드 전환 (head_64.S)
(gdb) break x86_64_start_kernel # C 코드 전환점
(gdb) break start_kernel # 커널 메인 C 진입점
# 서브시스템 초기화 추적
(gdb) break mm_core_init # 메모리 관리 초기화
(gdb) break sched_init # 스케줄러 초기화
(gdb) break rest_init # init 프로세스 생성 직전
(gdb) break kernel_init # PID 1 (init) 진입
# 커널 커맨드라인 확인
(gdb) break start_kernel
(gdb) continue
(gdb) p boot_command_line
# → "nokaslr console=ttyS0 loglevel=7"
코어덤프(Core Dump) 분석
코어덤프(Core Dump)는 프로세스가 비정상 종료될 때 메모리 상태를 파일로 저장한 것입니다. GDB로 코어 파일을 로드하면 크래시 시점의 정확한 상태를 사후 분석할 수 있습니다. 커널 코어덤프(vmcore)는 시스템 패닉 시 kdump가 생성하며, crash 도구 또는 GDB로 분석합니다.
유저 공간 코어덤프 분석
# 코어덤프 활성화
ulimit -c unlimited # 현재 셸에서 코어 크기 무제한
echo "core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern # 파일명 패턴
# 코어 파일로 GDB 분석
gdb ./prog core.prog.12345
# Core was generated by `./prog --input large_file.dat'.
# Program terminated with signal SIGSEGV, Segmentation fault.
# #0 0x0000000000401234 in process_data (buf=0x0) at prog.c:42
# 크래시 원인 분석 절차
(gdb) bt # 1. 콜 스택 확인
(gdb) bt full # 2. 로컬 변수 포함 스택
(gdb) info registers # 3. 레지스터 상태 (RIP, RSP 등)
(gdb) frame 0 # 4. 크래시 프레임 선택
(gdb) list # 5. 크래시 위치 소스 코드
(gdb) info locals # 6. 지역 변수 값
(gdb) info args # 7. 함수 인자
(gdb) x/16gx $rsp # 8. 스택 메모리 덤프
# gcore: 실행 중인 프로세스의 코어 파일 생성
gcore 12345 # PID 12345의 코어 덤프 (core.12345 생성)
# 또는 GDB 내에서:
(gdb) attach 12345
(gdb) generate-core-file mycore.dump # 커스텀 파일명
(gdb) detach
# coredumpctl (systemd 환경)
coredumpctl list # 최근 코어덤프 목록
coredumpctl info PID # 특정 코어덤프 상세 정보
coredumpctl debug PID # GDB로 직접 분석 시작
coredumpctl dump PID -o core.out # 코어 파일 추출
# 코어 파일 구조 확인 (readelf)
readelf -n core.prog.12345 # PT_NOTE 세그먼트 (레지스터, siginfo)
readelf -l core.prog.12345 # PT_LOAD 세그먼트 (메모리 매핑)
eu-readelf --notes core.prog.12345 # elfutils 상세 노트
커널 코어덤프 (vmcore) 분석
# kdump 설정 (커널 코어덤프 캡처)
# 1. crashkernel 메모리 예약 (부트 파라미터)
# crashkernel=256M 또는 crashkernel=256M,high
# 2. kdump 서비스 활성화
systemctl enable kdump
systemctl start kdump
# 3. 패닉 테스트 (주의: 시스템 크래시 발생!)
# echo c > /proc/sysrq-trigger
# vmcore 분석 (crash 도구)
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/vmcore
crash> bt # 패닉 시점 콜 스택
crash> log # 커널 로그
crash> ps # 프로세스 목록
crash> struct task_struct ffff88800abcd000 # 구조체 덤프
# GDB로 vmcore 직접 분석
gdb vmlinux vmcore
(gdb) bt # 패닉 CPU 콜 스택
(gdb) info threads # CPU별 스레드 (각 CPU = 1 thread)
(gdb) thread apply all bt # 모든 CPU 스택
# vmcore에서 커널 자료구조 탐색
(gdb) p panic_cpu # 패닉 발생 CPU
(gdb) p init_task.comm # swapper 이름
(gdb) p system_state # 시스템 상태
# makedumpfile로 vmcore 필터링 (크기 축소)
makedumpfile -c -d 31 vmcore vmcore.filtered # 불필요한 페이지 제거
# -d 플래그: 1=제로페이지, 2=캐시, 4=사용자, 8=프리, 16=감시
역방향 디버깅
역방향 디버깅(Reverse Debugging)은 프로그램 실행을 되감아 버그의 근본 원인을 추적하는 기법입니다. GDB의 record full, Mozilla의 rr, Intel PT 각각의 내부 동작 원리와 실전 활용 패턴을 심층 분석합니다.
rr 고급 활용
# rr로 멀티스레드 레이스 컨디션 디버깅
# 1. 레이스가 발생하는 프로그램 기록
rr record ./race_program
# 여러 번 실행하여 레이스가 발생하는 기록 확보
# 2. 재생 시작
rr replay
(rr) break crash_function
(rr) continue # → 크래시 지점
# 3. 데이터 레이스 역추적
# 문제가 되는 변수의 주소를 확인
(rr) print &shared_counter
(rr) watch -l *0x604040 # 하드웨어 감시점
(rr) reverse-continue # → 마지막으로 수정한 지점!
# Inferior stopped because watchpoint 2 was triggered.
# Old value = 42, New value = 41
(rr) bt # 어떤 스레드가 수정했는지 확인
(rr) info threads # 활성 스레드 목록
# 4. rr의 chaos 모드 (레이스 유발 극대화)
rr record --chaos ./race_program # 스케줄링 무작위화
# 5. rr 기록 공유 (팀 협업)
rr pack ~/.local/share/rr/race_program-0/
# → 기록 디렉터리를 다른 머신으로 전송 가능
# 수신 측: rr replay /path/to/packed/trace
# 6. rr + 감시점 역추적 패턴 (use-after-free 추적)
(rr) break main
(rr) continue
# ... 크래시까지 진행 ...
(rr) print ptr # 댕글링 포인터 주소 확인
(rr) watch -l *(void**)&ptr # ptr 변수 감시
(rr) reverse-continue # → ptr가 마지막으로 설정된 시점
# 여기서 free() 이후 ptr이 재사용되는 패턴 확인
# 7. rr 타임라인 내비게이션
(rr) when # 현재 이벤트 번호 (예: event 5000)
(rr) run 1000 # 이벤트 1000으로 점프
(rr) run 9999 # 이벤트 9999로 점프
record full 실전 패턴
# record full로 메모리 오염 원인 추적
(gdb) break main
(gdb) run
(gdb) record full # main에서 기록 시작
(gdb) continue # → 크래시 또는 이상 동작
# 크래시 시점에서 역추적
(gdb) print corrupted_var
(gdb) watch corrupted_var # 감시점 설정
(gdb) reverse-continue # → 마지막 수정 지점으로 되감기
# 기록 한계 설정
(gdb) set record full insn-number-max 500000 # 최대 명령어 수
(gdb) set record full stop-at-limit off # 한계 시 순환 버퍼 (오래된 기록 삭제)
# 기록 상태 확인
(gdb) info record
# Active record target: record-full
# Record mode: normal (중단 없이 기록 중)
# Lowest recorded instruction number is 1.
# Highest recorded instruction number is 150000.
# Log contains 150000 instructions.
# record full 주의사항
# - 시스템 콜 결과는 기록되지만 부작용(파일 I/O)은 복원 안 됨
# - 멀티스레드에서 다른 스레드의 변경은 기록 안 됨
# - 메모리 사용량이 크게 증가할 수 있음
GDB Python 스크립팅
GDB의 Python API는 디버깅 자동화의 핵심입니다. Pretty Printer, 커스텀 명령, 이벤트 핸들러, Frame Filter를 넘어서 커널 자료구조 분석 자동화, CI/CD 통합, 조건부 로깅까지 실현할 수 있습니다.
Pretty Printer 실전 작성
import gdb
import gdb.printing
# 커널 list_head Pretty Printer
class ListHeadPrinter:
"""struct list_head를 읽기 쉬운 형태로 출력"""
def __init__(self, val):
self.val = val
def to_string(self):
head = self.val
next_ptr = head["next"]
prev_ptr = head["prev"]
if next_ptr == head.address:
return "list_head [empty]"
# 리스트 요소 수 세기 (최대 100개까지)
count = 0
ptr = next_ptr
while ptr != head.address and count < 100:
count += 1
ptr = ptr["next"]
suffix = "+" if count == 100 else ""
return f"list_head [{count}{suffix} entries]"
# sk_buff Pretty Printer (네트워크 패킷)
class SkBuffPrinter:
"""struct sk_buff를 네트워크 패킷 요약으로 출력"""
def __init__(self, val):
self.val = val
def to_string(self):
skb = self.val
length = int(skb["len"])
data_len = int(skb["data_len"])
protocol = int(skb["protocol"])
return f"sk_buff [len={length}, data_len={data_len}, proto=0x{protocol:04x}]"
def children(self):
skb = self.val
yield ("head", skb["head"])
yield ("data", skb["data"])
yield ("tail", skb["tail"])
yield ("end", skb["end"])
# Pretty Printer 등록
def build_kernel_pp():
pp = gdb.printing.RegexpCollectionPrettyPrinter("kernel")
pp.add_printer("list_head", "^list_head$", ListHeadPrinter)
pp.add_printer("sk_buff", "^sk_buff$", SkBuffPrinter)
return pp
gdb.printing.register_pretty_printer(gdb, build_kernel_pp())
디버깅 자동화 스크립트
import gdb
import json
import os
class AutoCrashAnalyzer(gdb.Command):
"""코어 파일 자동 분석: bt, registers, locals를 JSON으로 저장"""
def __init__(self):
super().__init__("crash-analyze", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
report = {"frames": [], "registers": {}, "signal": ""}
# 시그널 정보
try:
sig = gdb.execute("info program", to_string=True)
report["signal"] = sig.strip()
except:
pass
# 레지스터 수집
try:
frame = gdb.selected_frame()
for reg in ["rip", "rsp", "rbp", "rax", "rbx",
"rcx", "rdx", "rsi", "rdi"]:
val = frame.read_register(reg)
report["registers"][reg] = str(val)
except:
pass
# 스택 프레임 수집
frame = gdb.newest_frame()
while frame is not None:
info = {
"level": str(frame.level()),
"function": frame.name() or "??",
"pc": hex(frame.pc()),
}
sal = frame.find_sal()
if sal.symtab:
info["file"] = sal.symtab.filename
info["line"] = sal.line
# 로컬 변수 수집
try:
frame.select()
block = frame.block()
locals_dict = {}
for sym in block:
if sym.is_variable or sym.is_argument:
try:
locals_dict[sym.name] = str(frame.read_var(sym))
except:
locals_dict[sym.name] = "<unavailable>"
info["locals"] = locals_dict
except:
pass
report["frames"].append(info)
frame = frame.older()
# JSON 저장
output = arg or "crash_report.json"
with open(output, "w") as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"[crash-analyze] 보고서 저장: {output}")
AutoCrashAnalyzer()
# 사용법:
# gdb -batch -ex "source crash_analyzer.py" -ex "crash-analyze report.json" prog core
커널 자료구조 순회 자동화
import gdb
def container_of(ptr, type_name, member):
"""커널 container_of 매크로의 Python 구현"""
t = gdb.lookup_type(type_name)
for f in t.fields():
if f.name == member:
offset = f.bitpos // 8
return (int(ptr) - offset)
raise ValueError(f"member {member} not found in {type_name}")
def list_for_each_entry(head_val, type_name, member):
"""커널 list_for_each_entry 매크로의 Python 구현"""
t = gdb.lookup_type(type_name).pointer()
head_addr = int(head_val.address)
node = head_val["next"]
while int(node) != head_addr:
entry_addr = container_of(node, type_name, member)
entry = gdb.Value(entry_addr).cast(t).dereference()
yield entry
node = node["next"]
class LxNetDevices(gdb.Command):
"""모든 네트워크 디바이스 나열 (list_head 순회)"""
def __init__(self):
super().__init__("lx-netdevs", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
init_net = gdb.parse_and_eval("init_net")
head = init_net["dev_base_head"]
print(f"{'Name':16} {'MTU':8} {'Flags':12} Address")
print("-" * 50)
for dev in list_for_each_entry(head, "struct net_device", "dev_list"):
name = dev["name"].string()
mtu = int(dev["mtu"])
flags = int(dev["flags"])
addr = dev.address
print(f"{name:16} {mtu:8} 0x{flags:08x} {addr}")
LxNetDevices()
# (gdb) lx-netdevs
# Name MTU Flags Address
# --------------------------------------------------
# lo 65536 0x00000049 0xffff88800...
# eth0 1500 0x00001043 0xffff88800...
멀티스레드/멀티프로세스 디버깅
멀티스레드 프로그램에서 데이터 레이스(Data Race), 데드락(Deadlock), 라이브락(Livelock)을 GDB로 진단하는 것은 난도가 높습니다. GDB의 All-stop/Non-stop 모드, scheduler-locking, fork follow 정책을 적절히 조합해야 합니다.
데드락 진단
# 데드락 상태 프로그램에 GDB attach
gdb -p $(pidof deadlock_prog)
# 1. 모든 스레드 백트레이스 확인
(gdb) thread apply all bt
# Thread 2 (LWP 12345):
# #0 __lll_lock_wait () from /lib/libc.so.6
# #1 pthread_mutex_lock (mutex=0x604060) at mutex.c:...
# #2 worker_a () at prog.c:15
# Thread 3 (LWP 12346):
# #0 __lll_lock_wait () from /lib/libc.so.6
# #1 pthread_mutex_lock (mutex=0x604040) at mutex.c:...
# #2 worker_b () at prog.c:25
# 2. 뮤텍스 소유자 확인
(gdb) print *(pthread_mutex_t*)0x604040
# → __data = {__lock = 2, __owner = 12345, ...}
# Thread 2(LWP 12345)가 뮤텍스 A를 보유
(gdb) print *(pthread_mutex_t*)0x604060
# → __data = {__lock = 2, __owner = 12346, ...}
# Thread 3(LWP 12346)가 뮤텍스 B를 보유
# → Thread 2: 뮤텍스 A 보유 + 뮤텍스 B 대기
# → Thread 3: 뮤텍스 B 보유 + 뮤텍스 A 대기
# → 순환 대기 → 데드락!
# 3. 스레드별 잠금 상태 Python 자동 분석
python
import gdb
for thr in gdb.selected_inferior().threads():
thr.switch()
frame = gdb.newest_frame()
while frame:
if frame.name() and "lock" in frame.name():
print(f"Thread {thr.global_num}: blocked in {frame.name()}")
try:
mutex = frame.read_var("mutex")
print(f" mutex addr: {mutex}, owner: {mutex['__data']['__owner']}")
except:
pass
break
frame = frame.older()
end
scheduler-locking 실전 활용
# 단일 스레드 집중 분석 (다른 스레드 동결)
(gdb) set scheduler-locking on
(gdb) thread 2 # 분석할 스레드 선택
(gdb) step # 이 스레드만 한 줄 실행
(gdb) next # 이 스레드만 다음 줄
# 레이스 컨디션 재현 (모든 스레드 자유 실행)
(gdb) set scheduler-locking off
(gdb) continue # 모든 스레드 함께 실행
# 스레드별 중단점 설정
(gdb) break worker thread 2 # Thread 2에서만 중단
(gdb) break process_data if $_thread == 3 # Thread 3에서만 조건부
# fork 후 추적 대상 선택
(gdb) set follow-fork-mode child # fork 후 자식 프로세스 추적
(gdb) set follow-fork-mode parent # fork 후 부모 프로세스 추적 (기본)
(gdb) set detach-on-fork off # fork 후 양쪽 모두 유지
# exec 후 처리
(gdb) set follow-exec-mode new-inferior # exec 시 새 Inferior 생성
(gdb) set follow-exec-mode same-inferior # exec 시 같은 Inferior 재사용
# 스레드 스케줄링 관찰
(gdb) set print thread-events on # 스레드 생성/종료 이벤트 출력
(gdb) catch syscall clone # clone() 시점에 정지 (스레드 생성)
(gdb) catch syscall futex # futex 호출 감시 (뮤텍스/조건변수)
원격 디버깅 설정
원격 디버깅(Remote Debugging)은 GDB와 디버깅 대상이 다른 시스템에서 실행되는 환경입니다. 임베디드 시스템, 크로스 컴파일 환경, 컨테이너 내부 프로세스, 클라우드 서버 디버깅에 필수적입니다. GDB Remote Serial Protocol(RSP)이 통신 프로토콜입니다.
gdbserver 고급 설정
# gdbserver 다중 연결 모드 (서버 계속 실행)
gdbserver --multi :1234 # 다중 연결 대기
# 호스트에서:
(gdb) target extended-remote :1234
(gdb) set remote exec-file /path/on/target/prog
(gdb) run # 원격에서 프로그램 시작
# SSH 터널을 통한 안전한 원격 디버깅
# 로컬에서 SSH 터널 생성:
ssh -L 1234:localhost:1234 user@target_host
# 타깃에서 gdbserver 시작:
gdbserver :1234 ./prog
# 호스트에서 localhost 연결 (터널 경유):
(gdb) target remote localhost:1234
# Docker 컨테이너 디버깅
# 컨테이너 내 gdbserver 실행:
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
-p 1234:1234 myimage \
gdbserver :1234 ./prog
# 호스트에서 연결:
(gdb) target remote localhost:1234
# sysroot 설정 (크로스 컴파일 환경)
(gdb) set sysroot /path/to/target/rootfs
(gdb) set solib-search-path /path/to/target/lib
(gdb) set solib-absolute-prefix /path/to/target/rootfs
# 이렇게 하면 타깃의 공유 라이브러리 심볼도 올바르게 해석
# 원격 파일 전송
(gdb) remote put local_file /tmp/remote_file # 호스트→타깃
(gdb) remote get /tmp/remote_file local_file # 타깃→호스트
(gdb) remote delete /tmp/remote_file # 타깃 파일 삭제
# 직렬 포트 원격 디버깅 (임베디드)
# 타깃:
gdbserver /dev/ttyUSB0 ./prog
# 호스트:
(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
# gdbserver 제한 사항과 해결책
# - 리버스 디버깅 미지원 → rr record 사용
# - Python 확장 실행 불가 → 호스트 GDB에서만 실행
# - 파일 접근은 원격 프로토콜 경유 → sysroot 설정 권장
원격 프로토콜 디버깅
# GDB Remote Protocol 패킷 추적
(gdb) set debug remote 1 # RSP 패킷 로그 출력
# 패킷 로그 예시:
# Sending packet: $qSupported:..#xx
# Packet received: PacketSize=47ff;...
# Sending packet: $Hg0#xx ← 스레드 0 선택
# Packet received: OK
# Sending packet: $g#xx ← 레지스터 읽기
# Packet received: 0000000000... ← 레지스터 값 (hex)
# Sending packet: $m7fffffffe000,100#xx ← 메모리 읽기
# 원격 타임아웃 설정
(gdb) set remotetimeout 30 # 30초 타임아웃 (느린 연결 시)
(gdb) set remote hardware-watchpoint-limit 4 # HW 감시점 개수
(gdb) set remote hardware-breakpoint-limit 4 # HW 중단점 개수
# 원격 메모리 접근 최적화
(gdb) set remote memory-read-packet-size 4096 # 패킷당 읽기 크기
(gdb) set remote memory-write-packet-size 4096 # 패킷당 쓰기 크기
# 원격 디버깅 트러블슈팅
# 1. "Remote communication error" → 네트워크 연결 확인
# 2. "Remote 'g' packet reply is too long" → set architecture 확인
# 3. "Cannot access memory" → ASLR/PIE 주소 불일치
# 4. "Target halted" → JTAG 연결 불안정
버전별 주요 변경사항
| GDB 버전 | 주요 신기능 |
|---|---|
| 17.1 (2025) | Intel PT 역방향 디버깅 개선, DWARF 5 완전 지원, Python 3.x API 업데이트, AArch64 SVE/SME 레지스터 지원 |
| 15.x | ROCm/HIP GPU 디버깅, 개선된 MinGW/Windows 지원 |
| 14.x | RISC-V 개선, 개선된 record btrace (Intel PT), Ada 2022 |
| 13.x | DAP(Debug Adapter Protocol) 지원 추가, 개선된 TUI |
| 12.x | 개선된 thread-local storage, GDB/MI 개선 |
| 10.x | DWARF 5 초기 지원, Rust 지원 개선 |
| 8.x | Intel MPX 지원, C++17 초기 지원 |
참고자료
공식 문서
- Debugging with GDB — GNU Project — GDB 공식 매뉴얼 최신판으로, 모든 명령어와 옵션을 포괄적으로 설명합니다.
- GDB: The GNU Project Debugger — sourceware.org — GDB 프로젝트 홈페이지로 릴리스 노트, 다운로드, 위키 링크를 제공합니다.
- Extending GDB using Python — GDB Manual — GDB Python API 공식 문서로, pretty-printer, command, event 확장 방법을 설명합니다.
- Remote Debugging — GDB Manual — gdbserver 원격 디버깅 프로토콜과 설정 방법을 다룹니다.
- Reverse Execution — GDB Manual —
reverse-continue,reverse-step등 역방향 디버깅 기능을 설명합니다.
커널 디버깅 문서
- Debugging kernel and modules via GDB — Linux Kernel Documentation — QEMU/KVM 환경에서 GDB로 커널을 디버깅하는 공식 가이드입니다.
- Using kgdb, kdb and the kernel debugger internals — Linux Kernel Documentation — KGDB/KDB 커널 디버거의 설정, 사용법, 내부 구조를 설명합니다.
- Development tools for the kernel — Linux Kernel Documentation — GDB, KASAN, UBSAN, lockdep 등 커널 개발 도구 색인 페이지입니다.
- Debugging via FireWire (OHCI-1394) — Linux Kernel Documentation — FireWire를 통한 물리 메모리 직접 접근 기반 커널 디버깅 기법입니다.
LWN.net 기사
- Debugging the Linux kernel with KGDB — LWN.net — KGDB를 활용한 커널 디버깅 실전 가이드입니다.
- Debugging kernel modules with QEMU and GDB — LWN.net — QEMU 가상 머신에서 커널 모듈을 GDB로 디버깅하는 워크플로를 설명합니다.
- KGDB/KDB merge and the kernel debugger front-ends — LWN.net — KGDB와 KDB의 통합 과정 및 프론트엔드 설계를 다룹니다.
- GDB and the kernel — LWN.net — GDB의 커널 인식 기능과 scripts/gdb/ Python 헬퍼를 소개합니다.
QEMU + GDB 커널 디버깅
- GDB usage — QEMU Documentation — QEMU의 GDB 스텁(stub) 설정과
-s -S옵션 사용법을 설명합니다. - GDB stub — QEMU Wiki — QEMU GDB 스텁의 기능과 프로토콜 구현 세부사항을 다룹니다.
- Debugging kernel and modules via GDB — kernel.org —
vmlinux-gdb.py스크립트와lx-dmesg,lx-ps등 커널 전용 GDB 명령어를 설명합니다.
서적 및 심화 자료
- Debugging with GDB: The GNU Source-Level Debugger, Tenth Edition (Richard Stallman, Roland Pesch, Stan Shebs 외, Free Software Foundation) — GDB 공식 매뉴얼의 인쇄본으로, 모든 명령어와 내부 동작을 체계적으로 설명합니다.
- Linux Kernel Debugging (Kaiwan N Billimoria, Packt, 2022) — KGDB, QEMU+GDB, crash 도구를 활용한 커널 디버깅 실무 가이드입니다.
- Linux Kernel Development, 3rd Edition (Robert Love, Addison-Wesley) — 커널 개발 전반을 다루며, 디버깅 기법과 oops 분석 장을 포함합니다.
- The Art of Debugging with GDB, DDD, and Eclipse (Norman Matloff, Peter Jay Salzman, No Starch Press) — GDB 기초부터 다중 스레드 디버깅, 메모리 오류 추적까지 실습 중심으로 설명합니다.
- linux/scripts/gdb/ — GitHub — 커널 소스 트리에 포함된 GDB Python 헬퍼 스크립트 모음으로,
lx-symbols,lx-dmesg,lx-lsmod등의 구현을 확인할 수 있습니다. - gdb(1) — Linux manual page — GDB 명령줄 옵션과 기본 사용법을 요약한 맨 페이지입니다.
- GCC 완전 가이드 —
-g,-g3,-Og디버그 빌드 옵션 - 디버깅 & 트러블슈팅 — printk, KASAN, lockdep, kdump 등 커널 디버깅 전반
- ftrace / Tracepoints — 커널 동적 추적, kprobe, 함수 그래프
- 크래시 분석 — kdump vmcore 분석, crash 도구, Call Trace 읽기
- 커널 심볼(Kernel Symbol) — kallsyms, System.map, 모듈 주소 해석
- 커널 개발 도구 — QEMU, sparse, Coccinelle, KUnit
관련 문서
- C 언어 완전 가이드 & 커널 C 관용어 — GNU C 확장과 커널 관용어를 중심으로 container_of·READ_ONCE·barr
- GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치(Relocation) 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문
- Linux Containers & Docker 커널 내부 — 컨테이너 런타임, Docker 데몬 아키텍처, libcontainer, OCI, seccomp, CRIU