老话说,工欲善其事必先利其器。linuxkernel是一个十分复杂的系统,初学者会很难入门。
假如有一个便捷的调试环境,学习效率起码能有5-10倍的提高。
为了学习linux内核,一般有这两个须要
可以甩掉硬件,便捷的编译和运行linux可以使用图形化的工具来调试linux
笔者使用VSCode+GDB+Qemu完成了这两个需求
qemu作为虚拟机linux运维博客,拿来启动linux。
VSCode+GDB作为调试工具,拿来图形化地DEBUG。
最终疗效大致如下:
qemu运行界面:
vscode调试界面:
下边将一步一步介绍怎么搭建上述环境。
本文所有操作都在VmwareUbuntu16虚拟机上进行。
安装编译工具链
因为Ubuntu是X86构架,为了编译arm64的文件如何用虚拟光驱安装linux系统,须要安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev build-essential git bison flex libssl-dev
制做根文件系统
linux的启动须要配合根文件系统,这儿我们借助busybox来制做一个简单的根文件系统
编译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进程能正常启动如何用虚拟光驱安装linux系统,须要再额外进行一些配置
根目录添加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
这儿对这几个文件做一点说明:
busybox作为linuxrc启动后,会读取/etc/profile,这儿面设置了一些环境变量和shell的属性按照/etc/fstab提供的挂载信息,进行文件系统的挂载busybox会从/etc/inittab中读取sysinit并执行,这儿sysinit指向了/etc/init.d/rcS/etc/init.d/rcS中linux内核,mdev-s这条命令很重要,它会扫描/sys目录,查找字符设备和块设备,并在/dev下mknod
dev目录:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1
这一步很重要,没有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上直接下载。
按照arch/arm64/configs/defconfig文件生成.config
make defconfig ARCH=arm64
将下边的配置加入.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是指定kernelramdisk的位置,这样指定以后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下载qemu
须要注意的,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
启动linux内核
/usr/local/bin/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
这儿对于参数做一些解释:
-m512M显存为512M
-smp44核
-cpucortex-a57cpu为cortex-a57
-kernelkernel镜像文件
-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后,并口复印如下:
[ 3.401567] usbcore: registered new interface driver usbhid
[ 3.404445] usbhid: USB HID core driver
[ 3.425030] NET: Registered protocol family 17
[ 3.429743] 9pnet: Installing 9P2000 support
[ 3.435439] Key type dns_resolver registered
[ 3.440299] registered taskstats version 1
[ 3.443685] Loading compiled-in X.509 certificates
[ 3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[ 3.473163] ALSA device list:
[ 3.474432] No soundcards found.
[ 3.485283] uart-pl011 9000000.pl011: no DMA platform data
[ 3.541376] Freeing unused kernel memory: 10752K
[ 3.545897] Run /linuxrc as init process
[ 3.548390] with arguments:
[ 3.550279] /linuxrc
[ 3.551073] nokaslr
[ 3.552216] with environment:
[ 3.554396] HOME=/
[ 3.555898] TERM=linux
[ 3.985835] 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 ]#
VSCode+GDB
vscode中集成了GDB功能,我们可以用它来图形化的调试linuxkernel
首先我们添加vscode的gdb配置文件(.vscode/launch.json):
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "kernel debug",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/vmlinux",
"cwd": "${workspaceFolder}",
"MIMode": "gdb",
"miDebuggerPath":"/usr/bin/gdb-multiarch",
"miDebuggerServerAddress": "localhost:1234"
}
]
}
这儿对几个重点参数做一些说明:
program:调试的符号文件
miDebuggerPath:gdb的路径,这儿须要注意的是,因为我们是arm64内核,因而须要用gdb-multiarch来进行调试
miDebuggerServerAddress:对端地址,qemu会默认使用1234这个端口
配置完成以后,可以直接启动GDB,联接上linuxkernel
在vscode中,可以设置断点,进行单步调试