Debugging The Linux Kernel With GDB
Debugging The Linux Kernel With GDB
Kieran Bingham
Debugging the Linux Kernel with GDB
● Many of us need to debug the Linux kernel
● Proprietary tools like Trace32 and DS-5 are $$$
● Open source debuggers like GDB lack ‘kernel awareness’ features
found in proprietary tools
http://lwn.net/2000/0914/a/lt-debugger.php3
Why?
● Jan Kizka has led the way, adding Kernel support for GDB
● Target options
○ KGDB
○ QEmu/KVM/UML
○ JTAG
○ Core Dumps
● Linux Awareness
○ Thread Awareness
○ Module Support
○ Data retrieval
○ Extending with Python
● Q+A
Targets for debugging Linux with GDB
KGDB JTAG
Good Better
Qemu
Targets: KGDB with GDB
● Debug stub in the kernel compliant with gdbremote protocol
○ Enable with CONFIG_KGDB
Qemu
Linux Kernel
Targets : Qemu (Example)
qemu-system-arm -kernel ./zImage -dtb ./vexpress-v2p-ca15-tc1.dtb -M vexpress-a15 -smp 2 -m 1024 -append 'root=/dev/nfs nfsroot=10.0.2.2:/opt/root/armv7/,tcp,v3 rw
ip=dhcp mem=1024M raid=noautodetect rootwait console=ttyAMA0,38400n8 devtmpfs.mount=0' -nographic -gdb tcp::32770
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 4.6.0-rc1 (kbingham@CookieMonster) (gcc version 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu1) ) #13 SMP Thu Mar 31 10:33:19 BST 2016
[ 0.000000] CPU: ARMv7 Processor [412fc0f1] revision 1 (ARMv7), cr=10c5387d
[ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
[ 0.000000] Machine model: V2P-CA15
[ ….. ]
[ 3.989042] IP-Config: Got DHCP answer from 10.0.2.2, my address is 10.0.2.15
[ 3.991451] IP-Config: Complete:
[ 3.991672] device=eth0, hwaddr=52:54:00:12:34:56, ipaddr=10.0.2.15, mask=255.255.255.0, gw=10.0.2.2
[ 3.991900] host=10.0.2.15, domain=, nis-domain=(none)
[ 3.992039] bootserver=10.0.2.2, rootserver=10.0.2.2, rootpath= nameserver0=10.0.2.3
$ cat./linux/vmlinux
arm-linux-gdb /etc/exports -iex 'add-auto-load-safe-path ./linux' -ex 'target remote localhost:32770'
Remote debugging using localhost:32770
/opt/rootfs
cpu_v7_do_idle *(rw,sync,no_subtree_check,no_root_squash,insecure)
() at /home/lkd/sources/linux/arch/arm/mm/proc-v7.S:74
74 ret lr
(gdb) info threads
Id Target Id Frame
*1 Thread 1 (CPU#0 [halted ]) cpu_v7_do_idle () at /home/lkd/sources/linux/arch/arm/mm/proc-v7.S:74
2 Thread 2 (CPU#1 [halted ]) cpu_v7_do_idle () at /home/lkd/sources/linux/arch/arm/mm/proc-v7.S:74
(gdb) bt
#0 cpu_v7_do_idle () at /home/lkd/sources/linux/arch/arm/mm/proc-v7.S:74
#1 0xc0308728 in arch_cpu_idle () at /home/lkd/sources/linux/arch/arm/kernel/process.c:72
#2 0xc0376b28 in cpuidle_idle_call () at /home/lkd/sources/linux/kernel/sched/idle.c:151
#3 cpu_idle_loop () at /home/lkd/sources/linux/kernel/sched/idle.c:242
#4 cpu_startup_entry (state=<optimized out>) at /home/lkd/sources/linux/kernel/sched/idle.c:291
#5 0xc0ae8a30 in rest_init () at /home/lkd/sources/linux/init/main.c:408
#6 0xc0f00c5c in start_kernel () at /home/lkd/sources/linux/init/main.c:661
Targets: JTAG
+ OpenOCD is open source
+ Supports gdbremote protocol
+ Supports many ARM/MIPS CPUs
+ Supports many FTDI based JTAG
probes http://openocd.org
http://elinux.org/JTAG
Host
OpenOCD Target System
Telnet telnet
JTAG JTAG
GDB Client gdbserver
Targets: Core Dumps
● CONFIG_PROC_KCORE
○ sudo gdb vmlinux /proc/kcore
○ Virtual ELF core file of live kernel
○ No modifications can be made
● CONFIG_PROC_VMCORE
○ /proc/vmcore
○ Used in conjunction with kexec, kdump
and the crash utility from RedHat
○ py-crash, and libkdumpfile support
coming to GDB from SUSE https://en.wikipedia.org/wiki/Kdump_(Linux) @V4711
https://www.suse.com/documentation/sles-12/book_sle_tuning/data/part_tuning_dumps.html
Linux Awareness
● Provide the debugger with additional knowledge of the underlying
operating system to enable a better debugging experience.
○ Where is the Task List?
○ What is in the Kernel Log Buffer?
○ What modules are loaded? Where?
3. OS Helper Commands
● Interacting with the debugger to obtain useful information
GDB C Extension - Linux Kernel Debugger (LKD)
● Original tools written at ST Micro provide
“Linux Awareness”
● ST-LKD based on GDB 7.6
● Developed for STMC2 JTAG debugger
Linux Source 1
Python Plugin 2 gdbremote
3
GDB Stub
GDB Client
Kgdb / OpenOCD
Guile Plugin
Kernel Awareness
M
or
Linux Source
e
kn
ux
ow
Lin
led
of
ge
ge
of
led
KGDB
Ta
ow
GDB Client
rg
e
kn
t
e
or
M
t->to_shortname = "linux-kthreads";
t->to_longname = "linux kernel-level threads";
t->to_doc = "Linux kernel-level threads"; LKD-C
t->to_close = linux_kthread_close;
t->to_mourn_inferior = linux_kthread_mourn_inferior;
t->to_fetch_registers = linux_kthread_fetch_registers;
t->to_store_registers = linux_kthread_store_registers;
t->to_wait = linux_kthread_wait;
t->to_resume = linux_kthread_resume;
t->to_thread_alive = linux_kthread_thread_alive;
t->to_update_thread_list = linux_kthread_update_thread_list;
t->to_extra_thread_info = linux_kthread_extra_thread_info;
t->to_pid_to_str = linux_kthread_pid_to_str;
t->to_stratum = thread_stratum;
t->to_magic = OPS_MAGIC;
return t;
}
Task Awareness
qemu-system-arm -kernel ./zImage -dtb ./vexpress-v2p-ca15-tc1.dtb -M vexpress-a15 -smp 2 -m 1024 -append 'root=/dev/nfs nfsroot=10.0.2.2:/opt/root/armv7/,tcp,v3 rw
ip=dhcp mem=1024M raid=noautodetect rootwait console=ttyAMA0,38400n8 devtmpfs.mount=0' -nographic -gdb tcp::32770
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 4.6.0-rc1 (kbingham@CookieMonster) (gcc version 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu1) ) #13 SMP Thu Mar 31 10:33:19 BST 2016
[ 0.000000] CPU: ARMv7 Processor [412fc0f1] revision 1 (ARMv7), cr=10c5387d
[ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
[ 0.000000] Machine model: V2P-CA15
[ ….. ]
threads now
appear in the
lkd/bin/arm-linux-gdb ./linux/vmlinux -iex 'add-auto-load-safe-path ./linux' -ex 'target remote localhost:32770'
Remote debugging using localhost:32770 inferior
(gdb) info threads
Id Target Id Frame
*1 [swapper/0] (TGID:0 <C0>) cpu_v7_do_idle () at ../linux/arch/arm/mm/proc-v7.S:74
2 [swapper/1] (TGID:0 <C1>) cpu_v7_do_idle () at ../linux/arch/arm/mm/proc-v7.S:74
3 init (TGID:1) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
4 [kthreadd] (TGID:2) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
5 [ksoftirqd/0] (TGID:3) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
6 [kworker/u4:0] (TGID:6) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
7 [rcu_sched] (TGID:7) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
8 [rcu_bh] (TGID:8) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
9 [migration/0] (TGID:9) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
10 [watchdog/0] (TGID:10) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
11 [cpuhp/0] (TGID:11) context_switch (next=<optimized out>, prev=<optimized out>, rq=<optimized out>) at ../linux/kernel/sched/core.c:2734
[...]
Extending GDB with Python
● Commands
● Functions
● Pretty Printing objects
● Frame Filters / Unwinders
● Breakpoints
● … and more ...
https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html
Thread awareness in Python
def to_update_thread_list(self):
inferior = gdb.selected_inferior() LKD-Python
threads = inferior.threads()
def __init__(self):
super(LxCmdLine, self).__init__("lx-cmdline", gdb.COMMAND_DATA)
LxCmdLine()
Extending GDB with Python
gdb.Command : lx-cmdline
class LxCmdLine(gdb.Command):
""" Report the Linux Commandline used in the current kernel.
(gdb) lx-cmdline
Equivalent to cat /proc/cmdline on a running target"""
root=/dev/nfs nfsroot=10.0.2.2:/opt/root/armv7/,tcp,v3 rw ip=dhcp mem=1024M
raid=noautodetect rootwait console=ttyAMA0,38400n8 devtmpfs.mount=0
def __init__(self):
super(LxCmdLine, self).__init__("lx-cmdline", gdb.COMMAND_DATA)
(gdb) help lx-cmdline
Report the Linux Commandline used in the current kernel.
Equivalent to catarg,
def invoke(self, /proc/cmdline
from_tty): on a running target
gdb.write(gdb.parse_and_eval("saved_command_line").string() + "\n")
LxCmdLine()
Extending GDB with Python
gdb.Function : lx_task_by_pid
class LxTaskByPidFunc(gdb.Function):
"""Find Linux task by PID and return the task_struct variable.
$lx_task_by_pid(PID): Given PID, iterate over all tasks of the target and
return that task_struct variable which PID matches."""
def __init__(self):
super(LxTaskByPidFunc, self).__init__("lx_task_by_pid")
LxTaskByPidFunc()
Extending GDB with Python
gdb.Function : lx_task_by_pid
class LxTaskByPidFunc(gdb.Function):
"""Find Linux task by PID and return the task_struct variable.
$lx_task_by_pid(PID): Given PID, iterate over all tasks of the target and
return that task_struct variable which PID matches."""
(gdb) lx-ps
0xeeea5500 1163 lircd
def __init__(self): ## Output trimmed ….
super(LxTaskByPidFunc, self).__init__("lx_task_by_pid")
(gdb) set $task = $lx_task_by_pid(1163)
(gdb) print $task.comm
def invoke(self, pid):
task = get_task_by_pid(pid)
$5 = "lircd\000" if task:
(gdb) print $task.return <tab task.dereference()
completion available>
else:
raise gdb.GdbError("No task of PID " + str(pid))
LxTaskByPidFunc()
Extending GDB with Python
gdb.Function : lx_radix_tree_lookup (not in ML)
(gdb)class
print LxRadixTree(gdb.Function):
irq_desc_tree
""" Lookup and return a node from a RadixTree.
$1 = {
$lx_radix_tree_lookup(root_node [, index]): Return the node at the given index.
height = 1,
If index is omitted, the root node is dereferenced and returned."""
gfp_mask = 37748928,
def __init__(self):
rnode = 0xee000001 super(LxRadixTree, self).__init__("lx_radix_tree_lookup")
}
def invoke(self, root, index=0):
(gdb) print *irq_desc_tree.rnode
result = lookup(root, index)
$2 = { if result is None:
raise gdb.GdbError("No entry in tree at index {}".format(index))
path = 855638016,
count = 0, return result
…. LxRadixTree()
slots = {0xc0ee8030, 0x80ee8030, 0x40ee8031, 0xee8032, 0xc0ee8033, …...
Extending GDB with Python
gdb.Function : lx_radix_tree_lookup (not in ML)
class LxRadixTree(gdb.Function):
""" Lookup and return a node from a RadixTree.
def __init__(self):
super(LxRadixTree, self).__init__("lx_radix_tree_lookup")
return result
LxRadixTree()
[PATCHv4 10/12] scripts/gdb: Add a Radix Tree Parser
https://lkml.org/lkml/2016/3/30/277
Extending GDB with Python
gdb.Function : lx_radix_tree_lookup (not in ML)
class LxRadixTree(gdb.Function):
""" Lookup
(gdb) print and return
((struct a node from a RadixTree.
irq_desc)$lx_radix_tree_lookup(irq_desc_tree, 18)).irq_data
$3 = $lx_radix_tree_lookup(root_node
{ [, index]): Return the node at the given index.
maskIf index
= 0,is omitted, the root node is dereferenced and returned."""
irq = 18,
def __init__(self):
hwirq = 27, super(LxRadixTree, self).__init__("lx_radix_tree_lookup")
common = 0xee803d80,
def invoke(self, root, index=0):
result = lookup(root, index)
chip = 0xc100285c <gic_data>,
if result is None:
domain = 0xee808000, raise gdb.GdbError("No entry in tree at index {}".format(index))
parent_data = return 0x0,result
chip_data = 0xc100285c <gic_data>
LxRadixTree()
}
Extending GDB with Python : Accessing data
● GDB provides accessors to read memory
def module_list():
modules = gdb.parse_and_eval("modules")
entry = modules['next']
end_of_list = modules.address
And then ?
● IDE integration
● Userspace debug extensions?
● Page table walks?
● The world ...
Summary