如何使用Kgdb调试内核源码?

1 编译内核,去优化编译

(1)Makefile 中-O2 变成-O1

# 进入内核源码目录
cd linux-4.10.1
# 编辑Makefile
vim Makefile
# 修改Makefile中的优化级别
# 将-O2改为-O1
641 ifdef CONFIG_PROFILE_ALL_BRANCHES
642 KBUILD_CFLAGS   += -O1 $ (call cc-disable-warning,maybe-uninitialized,)
643 else
644 KBUILD_CFLAGS   += -O1
645 endif
# Esc :wq! 保存文件

(2)修改配置文件.config

可以将当前系统的配置文件复制到源码目录下:

cp /boot/config-4.10.0-28-generic .config
# config-4.10.0-28-generic为我的内核版本,到了这一目录的时候输入config,然后使用Tab键补全即可。

然后执行 make oldconfig.

make oldconfig
# 如果有新的配置项,系统将会提示,直接回车选择默认项即可

推荐的配置

需要注意的是:
为了方便 kgdb 调试,需要将.config 中的一些项这样设置(其实make oldconfig后这些项大多数已经设置好了,没有设置好的将在后面小节中介绍修改方法):

# 使用gdb
CONFIG_KGDB = y
# 使用串口进行通信
CONFIG_KGDB_SERIAL_CONSOLE = y
# CONFIG_DEBUG_RODATA是将内核的一些内存区域空间设置为只读,这样可能导致kgdb的设置软断点功能失效。所以推荐将该选项关闭。
CONFIG_DEBUG_RODATA = n
# 使得编译的内核包含一些调试信息
CONFIG_DEBUG_INFO = y
# 使得内核使用帧指针寄存器来维护堆栈,从而就可以正确地执行堆栈回溯,即函数调用栈信息
CONFIG_FRAME_POINTER = y
# 激活"魔术 SysRq"键. 该选项对kgdboc调试非常有用,kgdb向其注册了‘g’魔术键来激活kgdb
CONFIG_MAGIC_SYSRQ = y
# CONFIG_DEBUG_SET_MODULE_RONX会将内核模块空间设置为只读,这样会导致调试内核模块时设置断点功能失效,设置断点时出现以下错误,Cannot insert breakpoint 1.
# 这一项在内核为4.11及以上,将RONX变为了RWX
# 这一项为4.10版本及之前版本
CONFIG_DEBUG_SET_MODULE_RONX = n
# 这四项为4.11版本及以上版本
CONFIG_ARCH_OPTIONAL_KERNEL_RWX = n
CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT = n
CONFIG_STRICT_KERNEL_RWX = n
CONFIG_STRICT_MODULE_RWX = n

一般在执行make oldconfig后,上面的许多项已经成功设置了。可以通过查看.config 文件确定上述配置已正确!

(3)若内核为 4.11 之前版本 命令:uname -r

(3-1)确定 CONFIG_DEBUG_RODATA=n

答主的系统为 Ubuntu 16.04,在执行 make oldconfig 后,发现.config 文件中的 CONFIG_DEBUG_RODATA 的设置仍旧为 y(推荐配置为 n)。

如果这一项为 y,则会将内核的一些内存区域空间设置为只读,这样可能导致 kgdb 的设置软断点功能失效。表现为编译器报错:Cannot insert breakpoint 1.

控制台输入命令:

make menuconfig
# 进入menuconfig后 输入'/'来搜索配置:DEBUG_RODATA

回车发现:

Symbol: DEBUG_RODATA [=y]
Type  : boolean
Defined at arch/x86/Kconfig:312
# Exit退出

可以看到 DEBUG_RODATA 配置是在 arch/x86/Kconfig 文件的 312 行定义的。

因此:

vim arch/x86/Kconfig
# 编辑Kconfig文件,定位到312行
# 将 def_bool y 改成 def_bool n
312 config DEBUG_RODATA
313         def_bool n
# wq! 保存文件
# 返回源代码目录
cd ../../
make oldconfig

(3-2)确定 CONFIG_DEBUG_SET_MODULE_RONX = n

CONFIG_DEBUG_SET_MODULE_RONX 会将内核模块空间设置为只读,这样会导致调试内核模块时设置断点功能失效,设置断点时出现以下错误,Cannot insert breakpoint 1.

与(3)一样,执行:

make menuconfig
# 进入kernel hacking
# 找到:Set loadable kernel module data as NX and text as RO
# 将其设置为N
# 移动下面的选项,Save之后exit

请再次去.config 文件查看配置是否正确。尤其是(3-1)和(3-2)的这两项配置。

下面是我的配置:

cat .config|grep -E 'RODATA|RONX'
# CONFIG_DEBUG_RODATA is not set
# CONFIG_DEBUG_RODATA_TEST is not set
# CONFIG_DEBUG_SET_MODULE_RONX is not set

不清楚 menuconfig 中设置为 n 后,为什么.config 文件中会显示为”is not set”,不过只要不是 y 就行。目前没有发现会影响后期的调试。

(4)若内核为 4.11 版本及以上

CONFIG_DEBUG_SET_MODULE_RONX 会将内核模块空间设置为只读,这样会导致调试内核模块时设置断点功能失效,设置断点时出现以下错误,Cannot insert breakpoint 1. 这一项在内核为 4.11 及以上,将 RONX 变为了 RWX

vim arch/Kconfig

# 编辑Kconfig文件,定位到907行,编辑为:
907 config ARCH_OPTIONAL_KERNEL_RWX
908         def_bool n
909         default n

# 定位到911行,编辑为:
911 config ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
912         def_bool n
913         default n

# 定位到915行,将关于config ARCH_HAS_STRICT_KERNEL_RWX的代码注释掉:
915 # config ARCH_HAS_STRICT_KERNEL_RWX
916 #       def_bool n
917 #       default n

# 定位到919行,将depends行和default行注释掉,然后加一行default n
919 config STRICT_KERNEL_RWX
920         bool "Make kernel text and rodata read-only" if ARCH_OPTIONAL_KERNEL    _RWX
921         # depends on ARCH_HAS_STRICT_KERNEL_RWX
922         # default !ARCH_OPTIONAL_KERNEL_RWX || ARCH_OPTIONAL_KERNEL_RWX_DEFA    ULT
923         default n

# 定位到933行,将关于config ARCH_HAS_STRICT_MODULE_RWX的代码注释掉:
933 # config ARCH_HAS_STRICT_MODULE_RWX
934 #       def_bool n
935 #       default n

# 定位到937行,将depends行和default行注释掉,加一行default n
937 config STRICT_MODULE_RWX
938         bool "Set loadable kernel module data as NX and text as RO" if ARCH_    OPTIONAL_KERNEL_RWX
939         # depends on ARCH_HAS_STRICT_MODULE_RWX && MODULES
940         # default !ARCH_OPTIONAL_KERNEL_RWX || ARCH_OPTIONAL_KERNEL_RWX_DEFA    ULT
941         default n

# wq! 保存文件
# 返回到源代码目录
cd ../
make oldconfig

请确认有关 RWX 的设置都为 n

cat .config|grep RWX
# CONFIG_ARCH_OPTIONAL_KERNEL_RWX is not set
# CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT is not set
# CONFIG_STRICT_KERNEL_RWX is not set
# CONFIG_STRICT_MODULE_RWX is not set

(5)编译系统

sudo apt install kernel-package
sudo apt-get install libssl-dev
touch REPORTING-BUGS
# 编译过程中可能还需要安装其他组件,看错误信息中缺少什么组件,apt-get install 对应组件即可
make-kpkg clean
make-kpkg --initrd kernel-headers kernel_image
# 等待大约3-4小时后,会在源码的上级目录生成deb文件
cd ../
# 可以看到上级目录有文件:
# linux-headers-4.10.1_4.10.1-10.00.Custom_amd64.deb
# linux-image-4.10.1_4.10.1-10.00.Custom_amd64.deb
dpkg -i *.deb
# 安装完成后会看到系统更新grub
reboot
# 重启后进入刚刚编译好的内核版本

如果重启后,若没有显示 grub,应该是将 grub 隐藏了。
可以查看这篇文章将其显示:后续:为什么编译完成后重启没有看到 grub 呢?

2 利用 VM clone 虚拟机,变成两个 Linux 系统

一个是服务器,一个是客户端。
答主使用的是 VM 虚拟机。
虚拟机右键,点击管理->克隆

3 为两个虚拟机配置串口

虚拟上右键,进入设置。
如果没有串口设置,可以点击 add->串口

服务器串口配置

记得选上是服务器,和轮询。

客户端串口配置

记得选上是客户端,和轮询。

4 服务器配置 grub.cfg 文件

vim /etc/default/grub
# 主要是看GRUB_CMDLINE_LINUX这一行
# 如果GRUB_CMDLINE_LINUX没有nokaslr rootdelay=90quiet splash text kgdboc=ttyS1,115200,则加上
# nokaslr 是 禁止内核起始地址随机化,如果不设置这个,将会无法插入软断点
GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US nokaslr rootdelay=90quiet splash text kgdboc=ttyS1,115200"
# 更新grub
sudo update-grub
reboot
# 重后后进入刚刚编译好的内核版本

5 测试两个虚拟机的串口通信

服务器端

# 服务器等待串口发送的数据
cat /dev/ttyS1

客户端

# 客户端发送数据
echo "sss" > /dev/ttyS1

之后会发现服务器接收到了客户端发送的数据。


说明两个窗口通信成功!

6 双机联调实战

(1)服务器进入假死状态,将控制权交给客户端

服务器:

echo ttyS1 > /sys/module/kgdboc/parameters/kgdboc
# 将控制权限交给Kgdb,目标机进入假死状态
echo g > /proc/sysrq-trigger
# 此时回车后发现shell不动了

(2)客户端使用 gdb 调试服务器

客户端,进入源码目录(即 步骤 1 中下载的源码目录):

gdb ./vmlinux
# 读完符号信息后,设置波特率:
# 设置波特率
set serial baud 115200
# 通过串口连接kgdb
target remote /dev/ttyS1

如图所示:

# 在克隆函数中打个断点
break sys_clone
# 继续运行
c

如图所示:

可以看到程序在 sys_clone 停止。
删除断点后继续运行代码:

# 也可以step看下克隆函数是如何运行的
# 删除断点
delete 1
# 继续运行
c

此时,观察服务器已经不是处于假死状态了!

当然也可以给其他的函数打断点,然后调试!

0

Leave a Reply

Your email address will not be published.