VSCode+GDB+Qemu调试ARM64 linux内核 - 知乎
如有侵权请联系删除。
由于Ubuntu是X86架构,为了编译arm64的文件,需要安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev build-essential git bison flex libssl-dev
linux的启动需要配合根文件系统,这里我们利用busybox来制作一个简单的根文件系统
wget https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -xjf busybox-1.33.1.tar.bz2
cd busybox-1.33.1
打开静态库编译选项
make menuconfig
Settings --->
[*] Build static binary (no shared libs)
指定编译工具
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
编译
make
make install
编译完成,在busybox目录下生成_install目录
为了init进程能正常启动, 需要再额外进行一些配置
根目录添加etc、dev和lib目录
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ mkdir etc dev lib
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ ls
bin dev etc lib linuxrc sbin usr
在etc分别创建文件:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13]
$ cat profile
#!/bin/sh
export HOSTNAME=bryant
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16]
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19]
$ cat fstab
#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26]
$ ls init.d
rcS
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30]
$ cat init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
这里对这几个文件做一点说明:
dev目录:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1
$ sudo mknod null c 1 3
这一步很重要, 没有console这个文件, 用户态的输出没法打印到串口上
lib目录:拷贝lib库,支持动态编译的应用程序运行:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43]
$ cp /usr/aarch64-linux-gnu/lib/*.so* -a .
linux内核源码可以在github上直接下载。
内核版本:linux-5.16.12.tar.gz
下载地址:The Linux Kernel Archives
根据arch/arm64/configs/defconfig 文件生成.config
make defconfig ARCH=arm64
将下面的配置加入.config文件中,前面两项可以在.config中直接修改,后面两项没有,直接添加就好。
CONFIG_DEBUG_INFO=y
CONFIG_INITRAMFS_SOURCE="./root"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0
CONFIG_DEBUG_INFO是为了方便调试
CONFIG_INITRAMFS_SOURCE是指定kernel ramdisk的位置,这样指定之后ramdisk会直接被编译到kernel 镜像中。
我们将之前制作好的根文件系统cp到root目录下:
# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56]
$ cp -r ../busybox-1.33.1/_install root
make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-
这里指定target为Image 会只编译kernel, 不会编译modules, 这样会增加编译速度
需要注意的,qemu最好源码编译, 用apt-get直接安装的qemu可能版本过低,导致无法启动arm64内核。笔者是使用4.2.1版本的qemu
apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
wget https://download.qemu.org/qemu-4.2.1.tar.xz
tar xvJf qemu-4.2.1.tar.xz
cd qemu-4.2.1
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make
sudo make install
编译完成之后,qemu在 /usr/local/bin目录下
$ /usr/local/bin/qemu-system-aarch64 --version
QEMU emulator version 4.2.1
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel arch/arm64/boot/Image -append "rdinit=/linuxrc nokaslr console=ttyAMA0 loglevel=8" -nographic -s
这里对于参数做一些解释:
-m 512M
内存为512M
-smp 4
4核
-cpu cortex-a57
cpu 为cortex-a57
-kernel
kernel镜像文件
-append
传给kernel 的cmdline参数。其中rdinit指定了init进程;nokaslr 禁止内核起始地址随机化,这个很重要, 否则GDB调试可能有问题;console=ttyAMA0指定了串口,没有这一步就看不到linux的输出;
-nographic
禁止图形输出
-s
监听gdb端口, gdb程序可以通过1234这个端口连上来。
这里说明一下console=ttyAMA0是怎么生效的。
查看linux源码可知ttyAMA0对应的是AMBA_PL011
这个驱动:
config SERIAL_AMBA_PL011_CONSOLE
bool "Support for console on AMBA serial port"
depends on SERIAL_AMBA_PL011=y
select SERIAL_CORE_CONSOLE
select SERIAL_EARLYCON
help
Say Y here if you wish to use an AMBA PrimeCell UART as the system
console (the system console is the device which receives all kernel
messages and warnings and which allows logins in single user mode).
Even if you say Y here, the currently visible framebuffer console
(/dev/tty0) will still be used as the system console by default, but
you can alter that using a kernel command line option such as
"console=ttyAMA0". (Try "man bootparam" or see the documentation of
your boot loader (lilo or loadlin) about how to pass options to the
kernel at boot time.)
AMBA_PL011是arm的一个标准串口设备, qemu 的输出就是模拟的这个串口。
在qemu的源码文件中,也可以看到PL011的相关文件:
# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54]
$ find . -name "*pl011*"
./hw/char/pl011.c
成功启动Linux后, 串口打印如下:
[ 2.347988] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[ 2.361616] ALSA device list:
[ 2.363053] No soundcards found.
[ 2.370163] uart-pl011 9000000.pl011: no DMA platform data
[ 2.429632] Freeing unused kernel memory: 18368K
[ 2.431664] Run /linuxrc as init process
[ 2.431870] with arguments:
[ 2.432023] /linuxrc
[ 2.432194] nokaslr
[ 2.432314] with environment:
[ 2.432452] HOME=/
[ 2.432563] TERM=linux
[ 2.555962] 9pnet_virtio: no channels available for device kmod_mount
mount: mounting kmod_mount on /mnt failed: No such file or directory
/etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory
Please press Enter to activate this console.
[root@bryant ]#
[root@bryant ]#
由于我之前已经安装好了,所以显示已经安装。
ubuntu@VM-4-3-ubuntu:~/work/smmu/linux-5.16.12$ sudo apt-get install gdb-multiarch
Reading package lists... Done
Building dependency tree
Reading state information... Done
gdb-multiarch is already the newest version (9.2-0ubuntu1~20.04.1).
The following packages were automatically installed and are no longer required:
dblatex dblatex-doc dh-strip-nondeterminism docbook-utils dwz eatmydata fonts-gfs-baskerville fonts-gfs-porson libalgorithm-c3-perl libarchive-cpio-perl libarchive-zip-perl libb-hooks-endofscope-perl libb-hooks-op-check-perl
libclass-c3-perl libclass-c3-xs-perl libclass-data-inheritable-perl libclass-method-modifiers-perl libclass-xsaccessor-perl libdata-optlist-perl libdebhelper-perl libdevel-callchecker-perl libdevel-caller-perl
libdevel-globaldestruction-perl libdevel-lexalias-perl libdevel-stacktrace-perl libdist-checkconflicts-perl libdynaloader-functions-perl libeatmydata1 libemail-date-format-perl libeval-closure-perl libexception-class-perl
libfile-homedir-perl libfile-stripnondeterminism-perl libipc-shareable-perl liblog-dispatch-perl liblog-log4perl-perl libmime-charset-perl libmime-lite-perl libmime-types-perl libmodule-implementation-perl libmodule-runtime-perl
libmro-compat-perl libnamespace-autoclean-perl libnamespace-clean-perl libpackage-stash-perl libpackage-stash-xs-perl libpadwalker-perl libparams-classify-perl libparams-util-perl libparams-validationcompiler-perl
libreadonly-perl libref-util-perl libref-util-xs-perl librole-tiny-perl libsgmls-perl libsombok3 libspecio-perl libsub-exporter-perl libsub-exporter-progressive-perl libsub-identify-perl libsub-install-perl libsub-override-perl
libsub-quote-perl libunicode-linebreak-perl libvariable-magic-perl libxstring-perl libyaml-tiny-perl python3-jinja2 python3-json-pointer python3-jsonpatch python3-jsonschema python3-pyrsistent sgmlspl squashfs-tools texlive
texlive-bibtex-extra texlive-extra-utils texlive-lang-greek texlive-science xmlto
Use 'sudo apt autoremove' to remove them.
0 upgraded, 0 newly installed, 0 to remove and 145 not upgraded.
在刚刚启动qemu的命令后面加上-S
qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel arch/arm64/boot/Image -append "rdinit=/linuxrc nokaslr console=ttyAMA0 loglevel=8" -nographic -s -S
-S
表示qemu虚拟机冻结cpu,直到gdb输入相应控制命令
此时qemu启动后无法继续往下运行,直到收到GDB的控制命令,此时卡住没有输出是正常现象。
gdb调试结果
后面就可以根据需求修改对应的代码,并调试了,并进行追踪,学习愉快。