GDB (GNU Debugger) 완전 가이드
GDB를 명령어 나열이 아니라 실제 결함 분석 흐름으로 설명합니다. 기동/심볼 로딩 전략, 조건부 중단점과 하드웨어 감시점, 멀티스레드 상태 추적, 코어 덤프 후분석, `gdbserver` 원격 디버깅, Python 스크립트로 반복 작업 자동화, record/replay 기반 역방향 추적, DWARF 디버그 정보 해석, KGDB 연결 시 주의사항까지 실전 기준으로 상세히 정리합니다.
-g 옵션으로 디버그 정보를 생성하는 방법을 먼저 익히세요.
GDB는 DWARF 형식의 디버그 정보를 활용해 소스 레벨 디버깅을 수행합니다.
핵심 요약
- GDB — GNU Source-Level Debugger. C, C++, Ada, Fortran, Go, Rust 등 다수 언어를 지원하는 소스 레벨 디버거.
- 중단점(Breakpoint) — 특정 위치에서 실행을 일시 정지.
break/tbreak/hbreak/rbreak로 설정. - 감시점(Watchpoint) — 변수나 메모리 주소 값이 변경될 때 정지.
watch/rwatch/awatch로 설정. - 잡기점(Catchpoint) — 예외 발생, 시스템 콜, fork 등 이벤트 발생 시 정지.
catch명령으로 설정. - 역방향 디버깅 —
record full로 실행을 기록한 뒤reverse-step/reverse-continue로 되감기 가능. - 원격 디버깅 —
gdbserver로 타깃 장치에서 실행하고 GDB로 원격 연결. 임베디드/커널 디버깅에 필수.
단계별 이해
- 디버그 정보 포함 빌드
gcc -g3 -O0 -o prog prog.c로 DWARF 디버그 정보를 포함해 컴파일합니다.-g3은 매크로 정보도 포함합니다. - 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 스크립팅을 단계적으로 익힙니다.
GDB 개요
GDB(GNU Debugger)는 GNU 프로젝트의 표준 소스 레벨 디버거입니다. 1986년 Richard Stallman이 최초 작성했으며, 현재는 Free Software Foundation이 관리합니다. GDB는 다음과 같은 핵심 기능을 제공합니다.
- 프로그램 실행 제어 — 중단점·감시점·잡기점에서 실행 일시 정지, 단계 실행(step/next), 계속 실행(continue)
- 상태 검사 — 변수 값 출력(
print), 메모리 덤프(x), 레지스터 확인(info registers), 콜 스택(backtrace) - 실행 환경 수정 — 변수 값 변경(
set variable), 메모리 패치, 함수 호출 강제 - 역방향 디버깅 — 실행 기록 후 되감기(
record full+reverse-step) - 원격 디버깅 — gdbserver 연동, JTAG/OpenOCD 연결, 커널 KGDB
- 코어 덤프 분석 — 크래시 후 생성된 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 세션
다음은 버퍼 오버플로우 버그가 있는 프로그램을 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로 스택 프레임 변수 초기화 직후 확인x /20xb &buf로 buf 주변 20바이트를 1바이트 단위 16진수로 덤프 → 오버플로우 영역 직접 확인- 반환 주소가
0x4141414141414141('A' 8개)로 덮인 것을info frame의saved rip로 확인 backtrace에서??가 나타나면 심볼이 없는 주소(= 오염된 주소)로 반환됐다는 신호
GDB 내부 동작 원리 (ptrace)
GDB가 프로세스를 제어하는 핵심 메커니즘은 리눅스 커널의 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 symfile | 심볼 파일 지정 (실행 파일과 별도) |
-e prog | 실행 파일 지정 (심볼 없이) |
-x file | GDB 명령어 파일 실행 (초기화 스크립트) |
-ex cmd | 기동 시 GDB 명령 즉시 실행 |
-ix file | 초기화 파일 로드 전에 명령 파일 실행 |
-iex cmd | 초기화 파일 로드 전에 명령 실행 |
-p pid | 실행 중인 프로세스에 attach |
-c core | 코어 덤프 파일 지정 |
-q / --quiet / --silent | 기동 메시지 숨김 |
-n / --nx | 초기화 파일(.gdbinit) 무시 |
--nh | 홈 디렉터리 ~/.gdbinit만 무시 |
--batch | 배치 모드 (비대화형): -x 스크립트 실행 후 종료 |
--batch-silent | 배치 모드 + GDB 출력 완전 억제 |
--tui | TUI(Terminal User Interface) 모드로 시작 |
-d dir | 소스 파일 검색 디렉터리 추가 |
-b bps | 직렬 포트 통신 속도 설정 (원격 디버깅) |
-l timeout | 원격 통신 타임아웃(초) 설정 |
--readnow | 심볼 파일을 즉시 전부 읽음 (느리지만 이후 빠름) |
--readnever | 심볼 파일을 읽지 않음 (속도 최우선) |
--interpreter mi | GDB/MI(Machine Interface) 모드 (IDE 연동용) |
--args prog args... | 프로그램과 인자를 함께 지정 |
--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 | 소스 라인 ↔ 기계어 주소 매핑 테이블 | 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 | 어셈블리 출력 시 C++ 이름 분해 |
set print symbol-loading off | brief | 심볼 로딩 메시지 억제 |
set print inferior-events off | on | inferior 시작/종료 메시지 억제 |
set print thread-events off | on | 스레드 시작/종료 메시지 억제 |
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 컨테이너 등)을 위한 다양한 도구를 제공합니다.
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- 패키지로 활성화됩니다.
# 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/직렬 포트로 통신합니다. 임베디드 시스템, 크로스 컴파일 환경, 커널 디버깅에 필수적입니다.
# 타깃 장치에서 (예: 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가 계속 실행되어 경쟁 조건이 발생할 수 있습니다. 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 | 로드된 커널 모듈 목록 |
dmesg | 커널 링 버퍼 출력 |
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 모드에서는 중단점에 걸린 스레드만 정지하고 나머지는 계속 실행합니다. 실시간 서버, 멀티미디어, 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
시그널 처리
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)에서 실행되어 오버헤드를 최소화합니다.
트레이스포인트 기본 사용
# 트레이스포인트는 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 # 부모 프로세스로 전환
공유 라이브러리
(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 | 마운트 목록 (/proc/mounts 대응) |
lx-clk-summary | 클록 트리 요약 (클록 주파수·상태) |
lx-device-list-bus | 버스별 디바이스 목록 |
lx-device-list-class | 클래스별 디바이스 목록 |
lx-device-list-tree | 디바이스 트리 계층 출력 |
lx-fdtdump | 디바이스 트리 블록 덤프 (임베디드) |
lx-timerlist | 커널 타이머 목록 (/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로 프로세스의 가상 메모리 구조를 시각적으로 이해하고 검사하는 것은 메모리 버그, 보안 분석, 성능 최적화의 기초입니다. 리눅스 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 가상화가 필요합니다.
실전 팁 모음
자주 쓰는 패턴
# 세그폴트 발생 지점 찾기
(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 디버그 프로브를 통해 베어메탈 펌웨어, RTOS, 부트로더를 디버깅하는 표준 도구입니다. 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 버전 | 주요 신기능 |
|---|---|
| 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 초기 지원 |
- GCC 완전 가이드 —
-g,-g3,-Og디버그 빌드 옵션 - 디버깅 & 트러블슈팅 — printk, KASAN, lockdep, kdump 등 커널 디버깅 전반
- ftrace / Tracepoints — 커널 동적 추적, kprobe, 함수 그래프
- 크래시 분석 심화 — kdump vmcore 분석, crash 도구, Call Trace 읽기
- 커널 개발 도구 — QEMU, sparse, Coccinelle, KUnit
관련 문서
- C 언어 완전 가이드 & 커널 C 관용어 — GNU C 확장과 커널 관용어를 중심으로 container_of·READ_ONCE·barr
- GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문
- Docker 커널 내부 심화 — Docker 데몬 아키텍처(dockerd→containerd→runc), clone()/u