GDB 커널 디버깅

리눅스 커널 GDB 디버깅에 특화된 내용을 정리합니다. KGDB/QEMU 원격 연결, vmlinux 심볼 로딩, 모듈 주소 해석, 커널 GDB 스크립트(lx-*), vmcore 코어덤프 분석, 임베디드 JTAG 디버깅, 역방향 디버깅을 다룹니다. GDB 기본 기능은 GDB 가이드를 참조하세요.

전제 조건: GDB의 기본 명령어(중단점, 감시점, 스택 검사, 원격 디버깅 등)에 익숙해야 합니다. GDB 가이드에서 기본 사용법을 먼저 익히세요.

커널 디버깅 빠른 시작

커널 개발자라면 아래 흐름부터 읽는 편이 효율적입니다. 즉 "명령어 사전"보다 "커널에서 어떤 심볼과 아티팩트를 열어야 하는가"를 먼저 고정해야 합니다. GDB 기본 명령어는 GDB 가이드를 참조하세요.

상황가장 먼저 할 일바로 이어질 섹션
라이브 커널 KGDB 연결vmlinux와 동일 빌드의 심볼 준비리눅스 커널 디버깅 활용
모듈 함수 주소 해석모듈 로드 주소와 심볼 오프셋(Offset) 확인위치 지정자, 리눅스 커널 디버깅 활용
vmcore 후분석crash 대신 GDB로 특정 구조체(Struct)를 직접 탐색리눅스 커널 디버깅 활용, Python 확장
QEMU 원격 디버깅gdb stub 포트와 심볼 파일 정합성 확인원격 디버깅, 리눅스 커널 디버깅 활용
커널 문맥 우선: 유저 공간 프로그램처럼 "바이너리 하나 열고 main에 중단점"으로 끝나지 않습니다. 커널에서는 vmlinux, 모듈 심볼, /proc/kallsyms, System.map, vmcore, KGDB transport가 서로 맞아야 하므로, 심볼 정합성을 먼저 확인하는 습관이 중요합니다.

리눅스 커널 디버깅 활용

주의: 커널 디버깅 시 CONFIG_RANDOMIZE_BASE=n (KASLR 비활성화), CONFIG_DEBUG_INFO=y, CONFIG_FRAME_POINTER=y 를 설정하는 것이 권장됩니다. KGDB 기반 디버깅은 SMP 환경에서 제한이 있으며, 일반적으로 단일 CPU 또는 maxcpus=1 커널 파라미터와 함께 사용합니다.
호스트 GDB gdb vmlinux target remote :1235 scripts/gdb 플러그인 vmlinux-gdb.py 로드 lx-* 명령 GDB에 등록 GDB 원격 연결로 커널 메모리 조회 (source scripts/gdb/vmlinux-gdb.py) QEMU VM -S -gdb tcp::1235 커널 실행 (bzImage) (방식 ①: 개발 · 테스트) GDB Remote Protocol (TCP) 타깃 커널 (KGDB) kgdboc / kgdboe 실제 장치 또는 VM (방식 ②: 실제 타깃) 직렬 / 이더넷

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-iomemI/O 메모리 맵 (/proc/iomem 대응)
lx-ioportsI/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 검사 방법을 심층 정리합니다.

Linux x86_64 프로세스 가상 메모리 레이아웃 0x7FFF... 커널 공간 (접근 불가) 스택 (Stack) ↓ 성장 지역변수 · 함수인자 · 반환주소 · canary $rsp → ↕ 가변 간격 (ASLR) 공유 라이브러리 / mmap libc.so · ld-linux.so · mmap() 영역 ↕ 가변 간격 힙 (Heap) ↑ 성장 malloc() · brk/sbrk · arena · tcache .bss (초기화되지 않은 전역) 0으로 초기화됨 .data (초기화된 전역) 전역변수 · static 변수 .text (코드) / .rodata (읽기전용) 실행 코드 · 문자열 리터럴 · const 0x0000... NULL 페이지 (접근 → SIGSEGV) GDB 검사 명령 스택: bt · info frame · x/32gx $rsp info stack · print $rbp-$rsp 라이브러리/mmap: info sharedlibrary · info proc mappings info files · maintenance info sections 힙: x/32gx <heap_addr> · call malloc_info(0,stdout) pwndbg: heap · bins · vis_heap_chunks 데이터/BSS: info variables · print &global_var x/s <rodata_addr> 코드: disassemble main · info functions info symbol <addr> · info line 보안 검사: checksec (pwndbg) · info auxv print $fs_base (TLS/canary) x/gx $rbp+8 (return address 확인) dump binary memory file.bin start end

섹션별 상세 검사

# 전체 메모리 맵 확인
(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 스텁 확인

힙 구조 심층 분석

glibc malloc 청크 구조 (x86_64) prev_size (이전 청크가 free일 때 유효) 8 bytes size | flags (A|M|P) 하위 3비트: P=1, M=2, A=4 8 bytes ← chunk ptr data (malloc 반환 주소) 사용자 데이터 영역 ... ← user ptr P = PREV_INUSE (1) M = IS_MMAPPED (2) A = NON_MAIN_ARENA (4) -0x10 -0x08 +0x00
# 힙 청크 직접 검사
(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

커널 GDB 스크립트 (lx-*) 완전 가이드

리눅스 커널 소스의 scripts/gdb/ 디렉터리에는 GDB Python 확장으로 구현된 lx-* 명령 모음이 있습니다. 이 스크립트들은 커널 자료구조를 직접 순회하여 GDB에서 커널 상태를 조회합니다. vmlinux-gdb.py가 진입점이며, 각 기능은 scripts/gdb/linux/ 하위 모듈로 구현되어 있습니다.

커널 lx-* 스크립트 아키텍처 vmlinux-gdb.py (진입점) source scripts/gdb/vmlinux-gdb.py utils.py container_of() gdb_eval_or_none() CachedType 캐시 tasks.py lx-ps (프로세스 목록) task_lists() 제너레이터 $lx_current() 편의 함수 symbols.py lx-symbols (모듈 심볼) 자동 add-symbol-file 모듈 로드 이벤트 감지 dmesg.py lx-dmesg (커널 로그) printk 링 버퍼 파싱 타임스탬프 변환 modules.py lx-lsmod (모듈 목록) struct module 순회 timerlist.py lx-timerlist (타이머) hrtimer/timer_list 순회 clk.py / device.py lx-clk-summary lx-device-list-* lists.py / rbtree.py list_for_each_entry() rb_first/rb_next 순회 GDB Remote Protocol (커널 메모리 접근) gdb.parse_and_eval() → $m 패킷 → QEMU/KGDB → 커널 메모리 읽기 핵심 패턴: CachedType("struct task_struct") → gdb.parse_and_eval("init_task") → container_of(ptr, type, member) → list_for_each_entry() 순회 소스: scripts/gdb/linux/*.py (커널 소스 트리 내장)

lx-dmesg 내부 동작

lx-dmesg는 커널 링 버퍼(struct printk_ringbuffer)를 직접 순회하여 커널 로그를 출력합니다. /proc/kmsgdmesg 명령 없이도 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 + GDB 커널 디버깅 환경 호스트 시스템 GDB gdb vmlinux target remote :1234 lx-* 스크립트 scripts/gdb/ vmlinux-gdb.py 빌드 산출물 vmlinux (심볼) bzImage (부팅용) *.ko (모듈) initramfs.cpio.gz System.map .config .vmlinux.cmd 공유 폴더: 9p / virtiofs / NFS QEMU 가상 머신 QEMU 프로세스 -S -gdb tcp::1234 GDB stub 내장 KVM / TCG 가속 게스트 커널 bzImage 부팅 nokaslr 권장 console=ttyS0 virtio 디바이스 virtio-net | virtio-blk | virtio-console | 9pfs rootfs: initramfs / virtio-blk / 9p shared GDB Remote Protocol TCP :1234 주요 QEMU 옵션 정리 디버깅 필수 -S (시작 즉시 정지) -s (-gdb tcp::1234 축약) -gdb tcp::PORT (포트 지정) -append "nokaslr" -kernel / -initrd 성능/환경 -enable-kvm (HW 가속) -smp N (멀티코어) -m SIZE (메모리) -serial stdio / mon:stdio -virtfs / -drive / -netdev

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로 분석합니다.

코어덤프 생성 및 분석 흐름 유저 공간 코어덤프 크래시 발생 SIGSEGV/SIGABRT 커널 코어 핸들러 do_coredump() core_pattern 참조 core 파일 (ELF) coredumpctl GDB 사후 분석 gdb prog core bt / info registers / x print / ptype / frame ELF 코어 파일 구조 ELF Header (ET_CORE) PT_NOTE: 프로세스 상태 NT_PRSTATUS (레지스터) NT_PRPSINFO (프로세스 정보) PT_LOAD: 메모리 세그먼트 .text (코드) .data/.bss (데이터) stack / heap / mmap 커널 코어덤프 (vmcore) 커널 패닉 BUG() / Oops kdump 캡처 커널 예약 메모리로 전환 makedumpfile 실행 vmcore /var/crash/ ELF/makedumpfile crash 도구 GDB crash vmlinux vmcore 또는 gdb vmlinux vmcore ulimit -c unlimited (코어 크기 제한 해제) | /proc/sys/kernel/core_pattern (코어 경로 패턴) systemd: coredumpctl list / coredumpctl debug PID (자동 관리)

유저 공간 코어덤프 분석

# 코어덤프 활성화
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 각각의 내부 동작 원리와 실전 활용 패턴을 심층 분석합니다.

역방향 디버깅 내부 메커니즘 record full 동작 1. 각 명령어 실행 전 레지스터/메모리 스냅샷 저장 2. 단일스텝으로 명령어 실행 3. 변경된 값 기록 (undo log) 4. reverse-* 시 undo log 역적용 오버헤드: 100~10,000x rr 동작 1. ptrace로 프로세스 제어 2. 비결정적 이벤트만 기록: syscall 결과, signal, rdtsc, 스레드 스케줄 순서, CPUID 3. HW 성능 카운터로 분기점 추적 4. replay: 같은 입력 → 같은 결과 오버헤드: 1.2~2x Intel PT 동작 1. CPU 하드웨어가 분기 기록 2. TNT (Taken/Not-Taken) 패킷 3. TIP (Target IP) 간접 분기 4. PSB (동기화 바운더리) 5. 바이너리 + 트레이스 → 재구성 6. 메모리 값은 기록 안 됨 오버헤드: ~5% 방식별 비교 기능 record full rr Intel PT 멀티스레드 단일 스레드만 완전 지원 완전 지원 데이터 감시 완전 (모든 변경) 완전 (재실행) 분기만 (데이터 없음) 설치 요구 없음 (GDB 내장) rr 설치 필요 Intel CPU 필요 적합 사례 짧은 단일스레드 레이스 컨디션 성능 분석 + 흐름 GDB 연동 내장 (record) rr replay (GDB) record btrace 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)은 복원 안 됨
# - 멀티스레드에서 다른 스레드의 변경은 기록 안 됨
# - 메모리 사용량이 크게 증가할 수 있음

임베디드/JTAG 디버깅

GDB는 JTAG/SWD 디버그 프로브(Probe)를 통해 베어메탈 펌웨어(Firmware), RTOS, 부트로더(Bootloader)를 디버깅하는 표준 도구입니다. OpenOCD, J-Link GDB Server, pyOCD 등이 GDB Remote Protocol로 프로브를 추상화합니다.

GDB + JTAG/SWD 디버깅 체인 GDB arm-none-eabi-gdb target remote :3333 RSP 디버그 서버 OpenOCD / J-Link GDB Server pyOCD / ST-LINK GDB Server GDB RSP ↔ JTAG/SWD 프로토콜 변환 USB 프로브 J-Link / ST-Link CMSIS-DAP / FTDI JTAG SWD 타겟 MCU ARM Cortex-M RISC-V Debug Unit (DAP) 주요 JTAG/SWD 기능 • HW 중단점 (Flash/ROM) • Flash 프로그래밍 • 리셋 제어 • 세미호스팅 (printf → 호스트) • 실시간 메모리 접근 • CoreSight 트레이스 (ETM) • SWO 출력 • ITM 스트림 • Watchpoint (DWT)

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 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로 전환
VS Code 임베디드 디버깅: cortex-debug 확장으로 VS Code에서 GDB+OpenOCD를 GUI로 사용할 수 있습니다. launch.json에서 "servertype": "openocd""configFiles"를 설정하면 Flash 프로그래밍·브레이크포인트·변수 감시를 GUI에서 수행합니다.
관련 문서:
  • GDB 가이드 — 중단점·감시점·조건식 추적, 스레드/코어덤프 분석, gdbserver 원격 세션, Python 자동화, DWARF 심볼 해석 등 범용 GDB 기능
  • 디버깅 & 트러블슈팅 — printk, KASAN, lockdep, kdump 등 커널 디버깅 기법 전반
  • 크래시 분석 — kdump vmcore 분석, crash 도구, Call Trace 읽기