前言
略.
确定当前内核开启调试
在调试前我们需要确定该系统是否开启了支持内核调试:
# 使用如下命令
zcat /proc/config.gz
# 确认是否有如下配置:
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y# 部分版本需要依赖于下面的开启,具体需要使用make nenuconfig查看依赖情况
CONFIG_DEBUG_INFO_DWARF5=y
# 或者
CONFIG_DEBUG_INFO_DWARF4=y或者:
### 以下几个方式都可以###
# 查看是否有任何信息
dmesg | grep -i kgdb
# 挂载debugfs,然后在看看debug下是否有KGDB相关文件
mount -t debugfs none /sys/kernel/debug
ls /proc/sys/kernel/debug
# 查看模块
lsmod | grep kgdboc获取源码
如果(非嵌入式)设备没有开启KGDB的话,我们需要编译源码来开启。 这里主要指的是如
ubuntu系统
下载
sudo apt search linux-source
uname -r
# 寻找一个合适的源码包
sudo apt install linux-source-5.10.160 #版本选自己合适的解压源码:
# 解压
cd /usr/src/linux-source-xxx
tar -xvf linux-source-xxx
# 处理,如果有debain等目录在其中,我们需要把解压的内容移动到上一层目录中
mv linux-source-xxx/* ./
# 复制本系统的配置到源码中
cp -v /boot/config-$(uname -r) .config检查是否开启了上面的KGDB配置:
# 期间的错误,可以安装下面的软件(报什么错,就安装什么)
sudo apt install libncurses-dev
# /bin/sh: 1: flex: not found,解决如下:
sudo apt-get install flex
# /bin/sh: 1: bison: not found,解决如下:
sudo apt-get install bison
# 查看配置
make menuconfig
# 实际上我看到服务器版本是默认开启的,后面编译可以跳过编译:
# 编译时会有些头文件没有
sudo apt install libelf-dev libssl-dev bc pahole
make -j$(npro)
sudo make modules_install
sudo make install
sudo update-grub配置进入调试
进入调试分两类:
- 启动时进入
- 运行时进入
启动时进入
启动就进入调试模式有两种方式:
- 修改
/etc/default/grub - 开机进入高级设置
方法1
这需要修改CMD LINE:
sudo vim /etc/default/grub
# 写入:
#GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX_DEFAULT="kgdboc=ttyXXXX,115200 kgdbwait"方法2
在开机时,按住shift,然后进入高级设置,再次按下e,然后在配置中加入:
kgdboc=ttyXXXX,115200 kgdbwait运行时进入
方法1
echo "ttyXXX" > /sys/module/kgdboc/parameters/kgdboc当然有些系统可能没有这个文件夹,你需要编译源码时开启KGDBOC。
方法2
# 查看是否开启了sysrq
echo 1 | sudo tee /proc/sys/kernel/sysrq
# 查看它支持的事件
cat /proc/sys/kernel/sysrq
# 进入调试KGDB
echo g > /proc/sysrq-trigger
主机设备配置
我们需要将目标机上源码编译好的vmlinux复制到另外一台设备上,然后使用:
sudo gdb vmlinux
(gdb) set serail baud 115200
(gdb) target remote /dev/ttyS0调试操作
调试的话有两种:
- 使用
host进行远程gdb - 调试串口进行
KGDB调试
远程调试kgdb
查看^39124e 这种调试与
gdb操作无异
本地串口调试
进入调试后状态如下:
我们使用help就可以看到命令列表:
Command Usage Description
----------------------------------------------------------
md <vaddr> Display Memory Contents, also mdWcN, e.g. md8c1
mdr <vaddr> <bytes> Display Raw Memory
mdp <paddr> <bytes> Display Physical Memory
mds <vaddr> Display Memory Symbolically
mm <vaddr> <contents> Modify Memory Contents
go [<vaddr>] Continue Execution
rd Display Registers
rm <reg> <contents> Modify Registers
ef <vaddr> Display exception frame
bt [<vaddr>] Stack traceback
btp <pid> Display stack for process <pid>
bta [D|R|S|T|C|Z|E|U|I|M|A]
Backtrace all processes matching state flag
btc Backtrace current process on each cpu
btt <vaddr> Backtrace process given its struct task address
env Show environment variables
set Set environment variables
help Display Help Message
? Display Help Message
cpu <cpunum> Switch to new cpu
kgdb Enter kgdb mode
ps [<flags>|A] Display active task list
pid <pidnum> Switch to another task
reboot Reboot the machine immediately
lsmod List loaded kernel modules
sr <key> Magic SysRq key
dmesg [lines] Display syslog buffer
defcmd name "usage" "help" Define a set of commands, down to endefcmd
kill <-signal> <pid> Send a signal to a process
summary Summarize the system
per_cpu <sym> [<bytes>] [<cpu>]
Display per_cpu variables
grephelp Display help on | grep
bp [<vaddr>] Set/Display breakpoints
bl [<vaddr>] Display breakpoints
bc <bpnum> Clear Breakpoint
be <bpnum> Enable Breakpoint
bd <bpnum> Disable Breakpoint
ss Single Step
dumpcommon Common kdb debugging
dumpall First line debugging
dumpcpu Same as dumpall but only tasks on cpus示例1(ubuntu启动KGDB)
使用如下环境操作
virtual box虚拟机ubuntu22.04,Server(服务器)版本作为目标机器(target)ubuntu22.04,Server(服务器)版本作为主机调试(host)
target指的是被调试的设备,host设备则指的是监视,和控制调试进程的设备。 注意:因为在调试时希望可以看源码,我们在配置和编译好target源码后复制该虚拟机。
目标调试机器配置
安装系统
可以参考:安装ubuntu-server。
准备一些软件
sudo apt install vim net-tools iputils-ping samba make gcc nano安装源码
配置静态网络
sudo nano /etc/netplan/01-installer-config.yaml
# 配置示例
network:
version: 2
ethernets:
enp0s3:
addresses:
- 192.168.1.12/24
gateway4: 192.168.1.1
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4如果想配置动态:
network:
version: 2
ethernets:
enp0s3:
dhcp4:true下载源码
我们先查看一下内核版本:
uname -r
# 我这里是:
5.15.158
# 搜索一下
apt search linux-source-5.15只找到一个版本:
版本大概差不多,当然有更匹配的选择更精确。 安装源码:
sudo apt install linux-source-5.15.0解压
cd /usr/src/linux-source-5.15.0
ls
这里我已经解压后的,实际上只有只有框中的3个文件。解压:
tar -xvf linux-source-5.15.0.tar.bz2
mv linux-source-5.15.0/* ./
rm -r linux-source-5.15.0/编译源码
我们需要提前安装一些软件,作为环境,具体可以查看上面的流程。
配置
# 复制本系统的配置到源码中
cp -v /boot/config-$(uname -r) .config
#
make menuconfig
# 配置config,请查看前面的:确定当前内核开启调试编译与安装
make -j$(npro)
sudo make modules_install
sudo make install
sudo update-grub之后重启。
调试
复制虚拟机

配置串口
target设置如下: 
host设置如下:
启动系统时,要想两个串口可以在两个虚拟机中通信,需要先开启未勾选:连接到至现有通道或套接字的系统然后再启动勾选的。
测试串口
按照顺序启动两个系统,然后target设备
dmesg |grep tty
#发现log中显示物理串口被加载到ttyS0,我们记住这个
stty -F /dev/ttyS0 speed 115200
sudo cat /dev/ttyS0然后在host上:
# 同上,查看串口被加载到那个tty上,我这里还是ttyS0
stty -F /dev/ttyS0 speed 115200
sudo su
echo Hello_uart > /dev/ttyS0查看一些target是否有数据出现。
启动调试
target启动后,我们设置调试串口:
echo "ttyS0" > sys/module/kgdboc/parameters/kgdboc
# 进入调试KGDB
echo g > /proc/sysrq-trigger当然也可以设置开机就进入调试,不过启动很慢。 target设置如下:
在这里设备就会卡住,我们需要操作host端。 host端设置如下:
这样便进入了KGDB调试。 
示例2(嵌入式系统启动KGDB)
这是一个嵌入式的调试过程,使用设备与软件如下:
A40i,Kernel-5.10.149, Qt5.10ubuntu+vmware- 串口工具
配置
- 先试用
deconfig或./build.sh menuconfig开启KGDB和DEBUG。 - 然后编译,烧录。
链接调试
在目标机器使用:
#先看看tty,在串口调试输入
#打印如下:
/dev/ttyS0
#我们设置
echo "ttyS0" > /sys/module/kgdboc/parameters/kgdboc
#也可以
echo "ttyS0,115200" > /sys/module/kgdboc/parameters/kgdboc
#然后设置
echo g > /proc/sysrq-trigger接着会进入:
我们输入help:
这表示可以开始调试了。
Host接入调试
我们打开ubuntu虚拟机,开启后,将串口接入到虚拟机:
通过:
dmesg | grep tty
可以看到ttyUSB0是该调试串口 我们进入到源码目录:
cd SDK_SOURCE
cd out/a40i_h/kernel/build接着输入(注意sudo):
sudo ../../../toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb vmlinux
我们设置波特率和串口:
set serial baud 115200
target remote /dev/ttyUSB0
可以看到,当前断点在kgdb_breakpoint;
示例3(调试示例)
^71dde3
^39124e
这里使用的设备是:
A40i,Kernel5.10,QT5.10 (target)Ubuntu22 (Host)- 串口调试
target先进入KGDB 我们简单的进行调试一个驱动,这里挑选的是i2c,代码以及函数如下:
# SDK_SOURCE/kernel/linux-5.10/drivers/i2c/busses/i2c-sunxi.c
# 选择函数:
sunxi_i2c_status_show我们阅读源码发现,该函数式通过读取class节点下的status触发进入。 我们在host端设置如下:
(gdb) b sunxi_i2c_status_show
(gdb) c这样target则会继续执行,我们这样操作target:
cd /sys/class/i2c-adapter/i2c-0/device
cat status执行上面命令后target会卡住。 我们看看host端,发现触发中断了: 
gdb中输入命令list可以查看一下源码:
对比实际的源码:
一模一样。 好了,我们开始单步调试:
(gdb) n
(gdb) n
(gdb) list
我们查看一下指针i2c的地址,但是: 这里是被优化了,我们来看看
i2c_status:
我们直接使用:
(gdb) rtarget显示:
我们可以print *attr:
或打印print *dev: 
示例4(调试用户定义模块)
在这里,我们需要编写一个简单的模块,然后进行调试。 这里使用的设备是: +
飞凌 A40i,Kernel5.10,QT5.10 (target)
Ubuntu22 (Host)- 串口调试
编译及加载
在此,我们使用[[lesson_4.c|文件节点操作]]来进行实验,我们先将其放入内核源码进行交叉编译,然后在上传到target设备:
# makefile修改
#KERNEL_DIR=/SDK_SOUR/OKA40i-linux-sdk/kernel/linux-5.10
CROSS_COMPILE=/SDK_SOUR/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
PWD=$(shell pwd)
ARCH=arm
obj-m := lesson_4.o
module-objs := lesson_4.o
MAKE=make
all:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(CROSS_COMPILE_PATH) M=$(PWD) modules
.PHONY:clean
clean:
rm -rf ./*o ./*.ko ./.+ ./*.order ./Module.* ./*.mod.c ./.*.cmd -d ./.\w+然后载入模块:
insmod lesson_4.ko准备调试
我们通过以下方式获取模块的加载位置:
获取模块加载地址
方式1
# 方式1
cat /proc/modules
# 结果如下:
lesson_4 16384 0 - Live 0xbf024000 (PO)方式2
# 方式2
cat /proc/kallsyms | grep lesson_4| sort
# 地址最小的那个就是
方式3
# cd cat /sys/module/<module_name>/sections/
cat /sys/module/lesson_4/sections
ls -all # 如果是ls,"."开头的文件会隐藏,看不到
cat .text
进入调试
target进入
我们依照^71dde3方法进入调试模式。
Host进入
同样我们依照^71dde3方法进入调试模式。 我们进入驱动模块(文件节点)的源码目录,在里面启动KGDB:
# vmlinux在内核源码的输出目录,我们现需要使用vmlinux进入调试
# SDK_SOURCE/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb
# sudo <交叉gdb位置> <内核vmlinx位置>
sudo /home/forlinx/work2/OKA40i-linux-sdk/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-gdb /home/forlinx/work2/OKA40i-linux-sdk/out/a40i_h/kernel/build/vmlinux
然后设置波特率参数,连接TTY(串口),接着载入当前驱动的模块文件:
#地址是上面获取的
add-symbol-file lesson_4.ko 0xbf024000
我们设置一个断点:
(gdb) b scull_write然后使用tty连接调试设备,并继续运行设备:c。
接着在target上操作:
# 驱动这里并没有显示的创建文件节点,所以我们先看看scull设备的主设备号
dmesg
然后再创建一个文件节点:
mknode /dev/scull0 c 241 0
接着:
echo 9 > /dev/scull0之后就会进入中断,我们可以看看host端的中断状态。
示例5(嵌入式系统开机启动KGDB)
上面演示的都是开机后使用
sys Rq进入的调试,但如果我们需要调试开机过程中的代码,则需要在开机后就进入KGDB。
RK3588 kernel-6.1.75ubuntu-22.0
注意:ARM设备似乎无法在更早的时候进入KGDB,而X86可以更早进入KGDB,所以RK3588无法调试比较早的代码,具体原因可以查看Early_KGDB。
配置
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
CONFIG_KGDB_KDB=y
# 记得关闭优化,不然很多局部变量都会被优化掉,调试查看不到任何数值
# 1. 进行优化,同时保留调试信息
# 2. 关闭减少调试信息选项
CONFIG_CC_OPTIMIZE_FOR_DEBUGGING=y
CONFIG_DEBUG_INFO_REDUCED=n这里的CONFIG_KGDB_KDB是可选选项,它的作用是,在你进入KGDB中断时(错误,主动进入),你可以通过串口命令行进行基本的调试。 
启动
我们可以将启动命令行加入到开机command line,可以通过以下方式:
uboot 命令行
setenv bootargs ${bootargs} kgdboc_earlycon=uart kgdbwait
saveenv
boot设备树
# 注意记得将原来的bootargs,完全复制过来,再加上kgdboc_earlycon=uart kgdbwait
&chosen {
bootargs = "kgdboc_earlycon=uart kgdbwait earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 root=PARTUUID=614e0000-0000 rw rootwait";
};这样我们就可以在开机时进入KGDB:
接下来的调试过程可以参考上面的示例。 注意:这里的启动命令行与上面的kgdboc=ttyXXXX,115200 kgdbwait不一样,这是因为在开机初期,TTY模块还未加载,只有基础的uart,所以如果RK3588进入系统后,还是可以像上面一样使用kgdboc=ttyXXXX,115200 kgdbwait进入KGDB模式。
其他
当然我们也可以通过在代码中注入kgdb_breakpoint();,比如在按键中加入该中断: 