[toc]
GDB是一个非常强大的调试工具,它可以设置断点,实时查看寄存器、程序变量的值等,甚至还可以远程调试。本文介绍的使用GDB调试Linux就用到的它的远程调试功能。具体来讲就是在启动QEMU的同时,添加gdb参数,这样就会将debug信息通过TCP/IP协议发送出去。在另外一端,只需要启动gdb去监听该端口的信息,即可进行实时调试。另外,本文还将介绍QEMU上运行的kernel启动后,通过VirtFS实现QEMU和宿主机之间文件共享的功能。本文中工具的安装环境完全基于上一篇博客Linux内存管理之一 —— 环境搭建(在QEMU上运行Linux 5.4.0)。
一 使用GDB(aarch64-linux-gnu-gdb)调试kernel
1.1 安装GDB
根据上面介绍的GDB远程调试功能,QEMU提供了调试端口,另外一端才是真正实现GDB调试的窗口,由于我们调试的对象是基于ARM64运行的kernel,因此,需要安装对应的gdb工具——aarch64-linux-gnu-gdb
。
wget http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.xz
- 编译及安装
下载的源码支持多种平台架构的CPU,比如arm,x86等,如果要编译对应版本的GDB,可以在执行configure
命令的时候添加--target=
参数来指定编译特定架构的GDB工具。如果不指定该参数,默认编译所有版本的GDB,只是编译时间会比较长。这里我们选择编译所有架构CPU对应的GDB工具。执行以下命令,解压源码,并且编译及安装:
tar xvf gdb-9.2.tar.xz
cd gdb-9.2
mkdir build
cd build/
../configure --host=x86_64-linux-gnu --target=aarch64-linux-gnu
make -j8 && sudo make install
如果没有错误,则表示aarch64-linux-gnu-gdb
正确安装,也可通过aarch64-linux-gnu-gdb --version
命令确认安装是否成功。如果输出以下信息,则表明安装成功。
$ aarch64-linux-gnu-gdb --version
GNU gdb (GDB) 9.2
Copyright (C) 2020 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.
1.2 启动GDB
根据GDB远程调试原理,我们需要先添加--gdb
参数启动QEMU,然后再启动GDB。具体步骤如下:
- 启动QEMU
qemu-system-aarch64 -gdb tcp::1234 -S -machine virt -cpu cortex-a57 -nographic -smp 1 -m 2048 -kernel out/arch/arm64/boot/Image
其中,--gdb
后面添加了tcp::1234
表示使用tcp协议监听本地的1234
端口,该端口用来作为GDB调试的通信端口。
- 启动GDB
aarch64-linux-gnu-gdb out/vmlinux -ex 'target remote :1234' -ex 'add-symbol-file out/vmlinux 0x40081000 -s .head.text 0x40080000 -s .rodata 0x40910000'
同样,在启动aarch64-linux-gnu-gdb
也指定了'target remote :1234'
,表示启用远程调试模式,远程端口为本地的1234
端口。其中, 'add-symbol-file out/vmlinux 0x40081000 -s .head.text 0x40080000 -s .rodata 0x40910000'
的意义表示从out/vmlinux
文件中读取额外的符号表。之所以需要读取额外符号的原因是,当程序在运行过程中,如果发生了动态链接,显然,动态链接的那部分代码里的符号在原程序编译的时候是不会保存在原程序符号表中的,所以需要手动指定读取符号的地址或section。但是,在这里并非这种情况。在Kernel启动过程中,启动MMU之前的代码(比如head.S
的代码)使用的都是物理地址,而符号表中保存的都是虚拟地址,并且没有包含这段代码的符号。因此,这里需要手动导入这些符号,否则的话,GDB是无法在这部分代码中break,也无法找到其中包含的符号的。关于add-symbol-file
参数的详细解释可以参考参考阅读11。
1.3 使用GDB
aarch64-linux-gnu-gdb
的使用和普通的gdb使用命令是一模一样的。当输入2.2 启动GDB中的启动命令之后,出现以下界面:
按下y
,就进入就可以执行gdb命令了。为了方便对照代码单步调试,按下ctrl + x
, 然后再按a
,就会进入下面的界面:
然后,我们使用b
命令设置断点,再执行c
,就可以使程序运行到第一个端点处,并在上面的窗口显示代码,如下图所示:
关于GDB的用法,网上有很多教程,这里就不再做详细的介绍了。
二 QEMU和宿主机文件共享
在QEMU上运行kernel之后,由于当前Kernel只有一个文件系统——根文件系统,所以当需要把文件传递给kernel的时候,必须将文件先放到根文件系统中,然后重新编译Kernel,重新启动QEMU。这样太繁琐了,因此我们这里使用QEMU提供的VirtFS
机制来实现QEMU和宿主机之间的文件共享。当然,也需要kernel配置对应的CONFIG。完成配置之后,只需要在启动的QEMU的时候指定一个宿主机的文件夹,然后通过VirtFS,就可以将这个文件夹映射到Kernel根文件系统下的某个文件夹了。这样,QEMU和Kernel就可以通过这个文件夹来共享文件了。
2.1 配置根文件系统
在制作根文件系统时,创建一个文件夹用来挂载9p的网络文件系统
(关于9p协议的介绍请参考参考阅读22),并且使用mount
命令将该文件系统挂载到这个文件夹下。具体方法是,修改etc/init.d/rcS
文件,在其中添加下面两行:
mkdir -pv /mnt/net9p
mount -t 9p -o trans=virtio net9p /mnt/net9p
这里,我们在/mnt
下创建了一个net9p
的文件夹,并将9p的网络文件系统挂载到了该文件夹下。
2.2 配置kernel
修改kernel的config,使之支持9P协议。具体方法是使用上一篇博客Linux内存管理之一 —— 环境搭建(在QEMU上运行Linux 5.4.0)的4.2 指定根文件系统的位置
节中提供的方法,在out/my.config
文件中添加下列配置:
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_NET_9P_DEBUG=y (Optional)
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_PCI=y
CONFIG_VIRTIO_PCI=y
CONFIG_PCI_HOST_GENERIC=y
然后,执行merge_config.sh
脚本merge config文件,之后再编译kernel即可。具体如下:
scripts/kconfig/merge_config.sh -m -O out out/.config out/my.config
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out
关于QEMU上配置9p文件系统,更详细的可参考参考阅读33。
2.3 启动命令
完成上述配置之后,就可以使用下面的命令来启动kernel了:
qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -smp 1 -m 2048 -kernel out/arch/arm64/boot/Image --fsdev local,id=share9p,path=$PWD/sharefs,security_model=none -device virtio-9p-device,fsdev=share9p,mount_tag=net9p
上述命令中:
* id
:表示创建的fsdev的名字,这个名字一定要和后面fsdev=xxx
的值保持一致;
* path
:表示共享文件夹的本地路径。在运行命令之前,必须保证本地存在该文件夹;
* mount_tag
:表示kernel中的挂载点名字,这个值必须和2.1 配置根文件系统
中mount
命令的倒数第二个参数保持一致。
在QEMU启动之后,在kernel的命令行中输入touch mount/net9p/test
,然后,在本地的$PWD/share9p
发现创建了一个名为test
的文件,则证明上述配置正确。
“Linux内存管理之二 —— 环境搭建二(使用GDB调试Linux内核)”的一个回复