KGDB调试ARM-LINUX内核

目标机内核配置

1.配置kgdb

1
2
3
4
5
6
7
8
9
Kernel hacking  --->
[*] KGDB: kernel debugger --->
--- KGDB: kernel debugger
<*> KGDB: use kgdb over the serial console
[ ] KGDB: internal test suite
[*] KGDB_KDB: include kdb frontend for kgdb
(0x1) KDB: Select kdb command functions to be enabled by default
[ ] KGDB_KDB: keyboard as input device
(0) KDB: continue after catastrophic errors

2.配置vmlinux符号信息

1
2
3
Kernel hacking  --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info

gdb移植

下载gdb源码(以8.0.1版本为例):

1
wget http://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.xz

安装libexpat:

1
apt install libexpat1-dev

解压并配置gdb源码:

1
2
3
tar -xf gdb-8.0.1.tar.xz
cd gdb-8.0.1
./configure --target=arm-linux --prefix=/root/work/arm/gdb/gdb-8.0.1/_install --with-expat

编译和安装gdb:

1
2
make
make install

安装完成后在配置指定的prefix目录中的bin目录下可找到目标程序arm-linux-gdb。

调试实例

Qemu ARM虚拟机调试(通过tcp调试)

启动ARM虚拟机,启动内核gdb服务,并将gdb服务绑定到本地tcp的1234端口,启动参数如下:

1
qemu-system-arm -M vexpress-a9 -m 512M -kernel zImage -dtb vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" -sd virtsd.ext2 -net nic,vlan=0 -net tap,vlan=0 -S -s

上面启动虚拟机的参数中,-S表示启动后冻结CPU,在gdb客户端通过c命令启动,-s表示-gdb tcp::1234,表示默认端口1234,如果不想用默认端口,则可以使用-gdb tcp::1234选项,修改后面的1234。

接着在宿主机内执行gdb程序调试vmlinux(参数根据绝对路径或相对路径设置):

1
arm-linux-gdb vmlinux

也可以在启动gdb的时候不传入vmlinux,而在启动gdb后通过下面的命令指定调试文件:

1
file ./vmlinux

成功进入gdb后输出内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GNU gdb (GDB) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /root/work/arm/kernel/linux-5.3.4/vmlinux...done.
(gdb)

在gdb终端输入以下命令连接gdb服务器:

1
target remote::1234

连接到gdb服务器后输出内容如下:(本地调试可以默认不填ip,即remote::1234,或者填写ip,即remote localhost:1234)

1
2
3
4
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x60000000 in ?? ()
(gdb)

此时即可使用gdb命令调试内核。下例在start_kernel处打断点,查看代码,直接执行代码到断点,然后单步执行,然后直接运行代码到系统启动:(在gdb调试终端进行单步执行时,在qemu启动内核终端可看到同步执行信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) b start_kernel     //在start_kernel函数开始地址加入断点
Breakpoint 1 at 0x80a00a34: file init/main.c, line 576.
(gdb) l //查看当前文件代码
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * linux/init/main.c
4 *
5 * Copyright (C) 1991, 1992 Linus Torvalds
6 *
7 * GK 2/5/95 - Changed to support mounting root fs via NFS
8 * Added initrd & change_root: Werner Almesberger & Hans Lermen, Feb '96
9 * Moan early if gcc is old, avoiding bogus kernels - Paul Gortmaker, May '96
10 * Simplified starting of init: Michael A. Griffith <grif@acm.org>
(gdb) c //直接运行到当前执行的最近一个断点处
Continuing.

Breakpoint 1, start_kernel () at init/main.c:576
576 {
(gdb) n //单步执行
580 set_task_stack_end_magic(&init_task);
(gdb) c //继续运行 此处由于之后未设置断点,将直接启动系统

开发板调试(通过串口调试)

正常启动目标机系统后通过以下命令进入kdb终端(假设串口是ttyAMA0):

1
2
echo ttyAMA0 > /sys/module/kgdboc/parameters/kgdboc
echo g > /proc/sysrq-trigger

如果需要开机即进入kgdb状态,则在目标板的启动参数中添加如下参数即可:

1
kgdboc=ttyAMA0,115200 kgdbwait

在本实验中,要开机进入kgdb的启动参数部分为:

1
"root=/dev/mmcblk0 rw console=ttyAMA0 kgdboc=ttyAMA0,115200 kgdbwait"

进入kdb终端后输入kgdb命令按回车进入kgdb模式,如果内核未开启gdb,则不需要kgdb命令。

(如果目标机只有一个输出串口,则在调试机操作目标机和运行gdb使用同一个串口,在目标机上完成上述操作后该串口将被gdb用来连接目标机的gdb服务)

在调试机中首先将串口波特率设置和目标板一致:

1
stty ispeed 115200 ospeed 115200 -F /dev/ttyUSB0

接着运行gdb调试vmlinux:

1
arm-linux-gdb vmlinux

进入gdb终端后输入以下指令连接到目标及kgdb:

1
target remote /dev/ttyUSB0

成功连接到目标板后输出和Qemu ARM虚拟机调试中一致。

KDB调试

先决条件

和kgdb不同,kdb不是源码级别的调试工具,Kdb的主要目的是做一些分析,以帮助开发或诊断内核问题。但是kdb仍然可以使用简单的断点。

kdb可以通过名字访问内核中的一些内核中内建的或模块中的符号,但需要内核开启CONFIG_KALLSYMS设置。

kdb内核配置:

主功能配置:CONFIG_KGDB_KDB

串口配置:CONFIG_KGDB_SERIAL_CONSOLE

键盘配置:CONFIG_KDB_KEYBOARD

SYSRQ配置:CONFIG_MAGIC_SYSRQ

进入和退出kdb

1.通过kgdboc配置串口

1
echo ttyAMA0 > /sys/module/kgdboc/parameters/kgdboc

2.配置进入debug模式

1
echo g > /proc/sysrq-trigger

3.退出kdb命令行

1
kdb> g

kdb获取help信息

输入help命令,得到kdb命令列表说明:(kdb>是命令提示符)

1
kdb>help

kdb命令说明

命令 参数 说明
md <vaddr> 显示指定内存地址的内容
mdr <vaddr> <bytes> 显示原始内存
mdp <paddr> <bytes> 显示物理内存
mds <vaddr> 显示内存符号
mm <vaddr> <contents> 修改指定地址的内存内同
go [<vaddr>] 继续执行,或跳到指定地址执行
rd 显示寄存器的值
rm <reg> <contents> 修改寄存器的值
ef <vaddr> 显示异常帧
bt [<vaddr>] 栈回溯
btp <pid> 指定地址上的栈显示
bta [D/R/S/T/C/Z/E/U/I/M/A] 栈回溯所有匹配到状态标记的进程
btc 当前进程在每个cpu上的栈回溯
btt <vaddr> 栈回溯指定struct task地址的进程
env 显示环境变量
set 设置环境变量
cpu <cpunum> 切换到指定cpu上
kgdb 进入kgdb模式
ps [<flags> / A] 显示活动进程列表
pid <pidnum> 切换到另一个进程
reboot 立即重启
lsmod 查看加载的内核模块
sr <key> Magic SysRq key
dmesg [lines] 显示syslog缓冲区
defcmd name “usage” “help” 定义命令合集,
kill <-signal> <pid> 向进程发送信号
summary 系统总计和概况
per_cpu <sym> [<bytes>] [<cpu>] 显示每一个cpu上的变量
grephelp 通过管道和grep显示help信息
bp [<vaddr>] 设置/显示断点
bl [<vaddr>] 显示断点
bc <bpnum> 清除断点
be 使能断点
bd 禁止断点
ss 单步执行
dumpcommon Common kdb debugging
dumpall First line debugging
dumpcpu same as dumpall but only task on cpus
ftdump [skip_#entries] [cpu] Dump ftrace log; -skip dumps last #entries