요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
QEMU 또는 KGDB target 연결
gdb-kernel-debugging.rst:22-70QEMU는 -s -S로 gdb stub을 열고 boot 전 CPU를 멈출 수 있다. GDB에서 vmlinux를 열고 target remote :1234로 연결한다. Real hardware는 KGDB transport를 사용한다. Address가 runtime randomization과 어긋나지 않도록 debugging boot에서 nokaslr을 사용하거나 relocation offset을 반영한다.
gdb vmlinux
(gdb) target remote :1234
(gdb) lx-symbols
(gdb) break start_kernel
(gdb) continue
Linux GDB helper 사용
gdb-kernel-debugging.rst:71-157scripts/gdb가 제공하는 lx-symbols는 load된 module symbol을 찾아 추가하고, lx-dmesg는 kernel log buffer를 읽는다. lx-ps와 task helper로 process를 순회하고 list·hlist macro를 Python command로 안전하게 따라갈 수 있다.
Module이 load·unload되면 symbol address가 바뀌므로 breakpoint 전에 lx-symbols를 다시 실행한다. Optimized-out local과 inline frame은 compiler 결과이므로 disassembly와 register를 source와 함께 읽는다.
Helper command 범주
gdb-kernel-debugging.rst:158-176- lx-symbols와 module symbol loading
- lx-dmesg log extraction
- lx-ps와 task lookup
- Kernel list, hlist와 rb-tree traversal
- per-CPU variable와 current task 접근
- Kernel configuration과 timer inspection
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. highlight:: none
Debugging kernel and modules via gdb
====================================
The kernel debugger kgdb, hypervisors like QEMU or JTAG-based hardware
interfaces allow to debug the Linux kernel and its modules during runtime
using gdb. Gdb comes with a powerful scripting interface for python. The
kernel provides a collection of helper scripts that can simplify typical
kernel debugging steps. This is a short tutorial about how to enable and use
them. It focuses on QEMU/KVM virtual machines as target, but the examples can
be transferred to the other gdb stubs as well.
Requirements
------------
- gdb 7.2+ (recommended: 7.4+) with python support enabled (typically true
for distributions)
Setup
-----
- Create a virtual Linux machine for QEMU/KVM (see www.linux-kvm.org and
www.qemu.org for more details). For cross-development,
https://landley.net/aboriginal/bin keeps a pool of machine images and
toolchains that can be helpful to start from.
- Build the kernel with CONFIG_GDB_SCRIPTS enabled, but leave
CONFIG_DEBUG_INFO_REDUCED off. If your architecture supports
CONFIG_FRAME_POINTER, keep it enabled.
- Install that kernel on the guest, turn off KASLR if necessary by adding
"nokaslr" to the kernel command line.
Alternatively, QEMU allows to boot the kernel directly using -kernel,
-append, -initrd command line switches. This is generally only useful if
you do not depend on modules. See QEMU documentation for more details on
this mode. In this case, you should build the kernel with
CONFIG_RANDOMIZE_BASE disabled if the architecture supports KASLR.
- Build the gdb scripts (required on kernels v5.1 and above)::
make scripts_gdb
- Enable the gdb stub of QEMU/KVM, either
- at VM startup time by appending "-s" to the QEMU command line
or
- during runtime by issuing "gdbserver" from the QEMU monitor
console
- cd /path/to/linux-build
- Start gdb: gdb vmlinux
Note: Some distros may restrict auto-loading of gdb scripts to known safe
directories. In case gdb reports to refuse loading vmlinux-gdb.py, add::
add-auto-load-safe-path /path/to/linux-build
to ~/.gdbinit. See gdb help for more details.
- Attach to the booted guest::
(gdb) target remote :1234
Examples of using the Linux-provided gdb helpers
------------------------------------------------
- Load module (and main kernel) symbols::
(gdb) lx-symbols
loading vmlinux
scanning for modules in /home/user/linux/build
loading @0xffffffffa0020000: /home/user/linux/build/net/netfilter/xt_tcpudp.ko
loading @0xffffffffa0016000: /home/user/linux/build/net/netfilter/xt_pkttype.ko
loading @0xffffffffa0002000: /home/user/linux/build/net/netfilter/xt_limit.ko
loading @0xffffffffa00ca000: /home/user/linux/build/net/packet/af_packet.ko
loading @0xffffffffa003c000: /home/user/linux/build/fs/fuse/fuse.ko
...
loading @0xffffffffa0000000: /home/user/linux/build/drivers/ata/ata_generic.ko
- Set a breakpoint on some not yet loaded module function, e.g.::
(gdb) b btrfs_init_sysfs
Function "btrfs_init_sysfs" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (btrfs_init_sysfs) pending.
- Continue the target::
(gdb) c
- Load the module on the target and watch the symbols being loaded as well as
the breakpoint hit::
loading @0xffffffffa0034000: /home/user/linux/build/lib/libcrc32c.ko
loading @0xffffffffa0050000: /home/user/linux/build/lib/lzo/lzo_compress.ko
loading @0xffffffffa006e000: /home/user/linux/build/lib/zlib_deflate/zlib_deflate.ko
loading @0xffffffffa01b1000: /home/user/linux/build/fs/btrfs/btrfs.ko
Breakpoint 1, btrfs_init_sysfs () at /home/user/linux/fs/btrfs/sysfs.c:36
36 btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj);
- Dump the log buffer of the target kernel::
(gdb) lx-dmesg
[ 0.000000] Initializing cgroup subsys cpuset
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] Linux version 3.8.0-rc4-dbg+ (...
[ 0.000000] Command line: root=/dev/sda2 resume=/dev/sda1 vga=0x314
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
....
- Examine fields of the current task struct(supported by x86 and arm64 only)::
(gdb) p $lx_current().pid
$1 = 4998
(gdb) p $lx_current().comm
$2 = "modprobe\000\000\000\000\000\000\000"
- Make use of the per-cpu function for the current or a specified CPU::
(gdb) p $lx_per_cpu(runqueues).nr_running
$3 = 1
(gdb) p $lx_per_cpu(runqueues, 2).nr_running
$4 = 0
- Dig into hrtimers using the container_of helper::
(gdb) set $leftmost = $lx_per_cpu(hrtimer_bases).clock_base[0].active.rb_root.rb_leftmost
(gdb) p *$container_of($leftmost, "struct hrtimer", "node")
$5 = {
node = {
node = {
__rb_parent_color = 18446612686384860673,
rb_right = 0xffff888231da8b00,
rb_left = 0x0
},
expires = 1228461000000
},
_softexpires = 1228461000000,
function = 0xffffffff8137ab20 <tick_nohz_handler>,
base = 0xffff888231d9b4c0,
state = 1 '\001',
is_rel = 0 '\000',
is_soft = 0 '\000',
is_hard = 1 '\001'
}
List of commands and functions
------------------------------
The number of commands and convenience functions may evolve over the time,
this is just a snapshot of the initial version::
(gdb) apropos lx
function lx_current -- Return current task
function lx_module -- Find module by name and return the module variable
function lx_per_cpu -- Return per-cpu variable
function lx_task_by_pid -- Find Linux task by PID and return the task_struct variable
function lx_thread_info -- Calculate Linux thread_info from task variable
lx-dmesg -- Print Linux kernel log buffer
lx-lsmod -- List currently loaded modules
lx-symbols -- (Re-)load symbols of Linux kernel and currently loaded modules
Detailed help can be obtained via "help <command-name>" for commands and "help
function <function-name>" for convenience functions.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
GDB로 kernel과 module debug하기
1-19kgdb, QEMU 같은 hypervisor, JTAG 기반 hardware interface가 제공하는 GDB stub을 이용하면 running Linux kernel과 module을 GDB로 debug할 수 있다. GDB에는 강력한 Python scripting interface가 있으며 kernel source는 반복적인 kernel debugging 절차를 단순화하는 helper script를 제공한다.
이 문서는 QEMU/KVM virtual machine을 target으로 helper를 enable하고 사용하는 짧은 tutorial이지만 같은 예를 다른 GDB stub에도 적용할 수 있다. Python support를 enable한 GDB 7.2 이상이 필요하고 7.4 이상을 권장한다.
Target과 kernel build 준비
22-45- QEMU/KVM용 Linux virtual machine을 만든다. 자세한 내용은 www.linux-kvm.org와 www.qemu.org를 참고한다. Cross development의 시작 image와 toolchain은 https://landley.net/aboriginal/bin 에서 구할 수 있다.
- Kernel을 CONFIG_GDB_SCRIPTS=y로 build하고 CONFIG_DEBUG_INFO_REDUCED는 끈다. Architecture가 CONFIG_FRAME_POINTER를 지원하면 enable한다.
- Guest에 kernel을 설치하고 필요하면 kernel command line에 nokaslr를 추가해 KASLR을 끈다.
- Module에 의존하지 않는 경우 QEMU의 -kernel, -append, -initrd option으로 kernel을 직접 boot할 수도 있다. 이 방식에서 architecture가 KASLR을 지원한다면 CONFIG_RANDOMIZE_BASE를 disable한다.
- Linux v5.1 이상에서는 make scripts_gdb로 GDB helper script를 build한다.
make scripts_gdb
QEMU GDB stub과 연결
46-69VM을 시작할 때 QEMU command line에 -s를 추가하거나 running VM의 QEMU monitor console에서 gdbserver를 실행해 GDB stub을 enable한다.
cd /path/to/linux-build
gdb vmlinux
(gdb) target remote :1234
일부 distribution은 안전하다고 등록된 directory에서만 GDB script auto-loading을 허용한다. GDB가 vmlinux-gdb.py load를 거부하면 ~/.gdbinit에 다음 설정을 추가한다.
add-auto-load-safe-path /path/to/linux-build
Kernel과 module symbol load
71-86(gdb) lx-symbols
loading vmlinux
scanning for modules in /home/user/linux/build
loading @0xffffffffa0020000: /home/user/linux/build/net/netfilter/xt_tcpudp.ko
loading @0xffffffffa0016000: /home/user/linux/build/net/netfilter/xt_pkttype.ko
loading @0xffffffffa0002000: /home/user/linux/build/net/netfilter/xt_limit.ko
loading @0xffffffffa00ca000: /home/user/linux/build/net/packet/af_packet.ko
loading @0xffffffffa003c000: /home/user/linux/build/fs/fuse/fuse.ko
...
loading @0xffffffffa0000000: /home/user/linux/build/drivers/ata/ata_generic.ko
lx-symbols는 vmlinux symbol을 load하고 build directory를 scan해 target에서 현재 load된 module의 .ko file을 실제 load address에 연결한다.
아직 load되지 않은 module에 breakpoint 걸기
87-107(gdb) b btrfs_init_sysfs
Function "btrfs_init_sysfs" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (btrfs_init_sysfs) pending.
(gdb) c
loading @0xffffffffa0034000: /home/user/linux/build/lib/libcrc32c.ko
loading @0xffffffffa0050000: /home/user/linux/build/lib/lzo/lzo_compress.ko
loading @0xffffffffa006e000: /home/user/linux/build/lib/zlib_deflate/zlib_deflate.ko
loading @0xffffffffa01b1000: /home/user/linux/build/fs/btrfs/btrfs.ko
Breakpoint 1, btrfs_init_sysfs () at /home/user/linux/fs/btrfs/sysfs.c:36
36 btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj);
GDB가 아직 symbol을 모르는 module function에 pending breakpoint를 만들고 target을 계속 실행하면 module load 시 lx-symbols가 symbol을 연결한 뒤 해당 source line에서 멈출 수 있다.
Kernel log와 current task 확인
109-127(gdb) lx-dmesg
[ 0.000000] Initializing cgroup subsys cpuset
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] Linux version 3.8.0-rc4-dbg+ (...
[ 0.000000] Command line: root=/dev/sda2 resume=/dev/sda1 vga=0x314
[ 0.000000] e820: BIOS-provided physical RAM map:
...
(gdb) p $lx_current().pid
$1 = 4998
(gdb) p $lx_current().comm
$2 = "modprobe\000\000\000\000\000\000\000"
lx-dmesg는 target kernel의 log buffer를 출력한다. x86과 arm64에서는 $lx_current()로 현재 task_struct를 얻어 pid와 comm 같은 field를 조사할 수 있다.
Per-CPU 자료와 container_of helper
128-155(gdb) p $lx_per_cpu(runqueues).nr_running
$3 = 1
(gdb) p $lx_per_cpu(runqueues, 2).nr_running
$4 = 0
(gdb) set $leftmost = $lx_per_cpu(hrtimer_bases).clock_base[0].active.rb_root.rb_leftmost
(gdb) p *$container_of($leftmost, "struct hrtimer", "node")
$lx_per_cpu(symbol)는 current CPU의 per-CPU variable을 반환하고 두 번째 인자로 CPU 번호를 주면 특정 CPU 값을 반환한다. 예시는 runqueue의 nr_running을 읽는다.
Hrtimer 예시는 per-CPU hrtimer_bases에서 active rbtree의 leftmost node를 얻은 뒤 $container_of()로 struct hrtimer를 복원한다. 출력에서는 rb node pointer, expires와 _softexpires, callback function tick_nohz_handler, base, state, relative/soft/hard flag를 직접 확인할 수 있다.
제공되는 command와 convenience function
158-175| 이름 | 기능 |
|---|---|
| lx_current | current task를 반환한다 |
| lx_module | 이름으로 module을 찾아 module variable을 반환한다 |
| lx_per_cpu | per-CPU variable을 반환한다 |
| lx_task_by_pid | PID로 task를 찾아 task_struct variable을 반환한다 |
| lx_thread_info | task variable에서 Linux thread_info를 계산한다 |
| lx-dmesg | Linux kernel log buffer를 출력한다 |
| lx-lsmod | 현재 load된 module을 나열한다 |
| lx-symbols | kernel과 현재 load된 module symbol을 다시 load한다 |
Command의 세부 도움말은 help <command-name>, convenience function의 도움말은 help function <function-name>으로 확인한다. Helper 수와 기능은 kernel version에 따라 달라질 수 있으며 이 목록은 문서 작성 당시의 snapshot이다.
Debug symbol과 Python 지원 GDB
gdb-kernel-debugging.rst:3-21CONFIG_GDB_SCRIPTS와 debug information을 포함해 kernel을 build하고 strip되지 않은 vmlinux를 사용한다. GDB는 Python support가 필요하며 target kernel과 source·vmlinux가 정확히 같은 build여야 type과 address가 일치한다.