一、实验原理
二、实验准备
下载内核源码
sudo apt install axel axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/ linux-5.4.34.tar.xz xz -d linux-5.4.34.tar.xz tar -xvf linux-5.4.34.tar cd linux-5.4.34
配置内核编译选项并编译内核
make defconfig # Default configuration is based on 'x86_64_defconfig' make menuconfig # 打开debug相关选项 Kernel hacking ---> Compile-time checks and compiler options ---> [*] Compile the kernel with debug info [*] Provide GDB scripts for kernel debugging [*] Kernel debugging # 关闭KASLR,否则会导致打断点失败 Processor type and features ----> [] Randomize the address of the kernel image (KASLR) make -j$(nproc) # nproc gives the number of CPU cores/threads available # 测试⼀下内核能不能正常加载运⾏,因为没有⽂件系统终会kernel panic qemu-system-x86_64 -kernel arch/x86/boot/bzImage # 此时应该不能正常运行
配置过程如下图所示:
制作根文件系统
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2 tar -jxvf busybox-1.31.1.tar.bz2 cd busybox-1.31.1 make menuconfig #记得要编译成静态链接,不⽤动态链接库。 Settings ---> [*] Build static binary (no shared libs) #然后编译安装,默认会安装到源码⽬录下的 _install ⽬录中。 make -j$(nproc) && make install
配置过程如下:
制作内核根文件系统镜像
mkdir rootfs cd rootfs cp ../busybox-1.31.1/_install/* ./ -rf mkdir dev proc sys home sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
准备init脚本⽂件放在根⽂件系统跟⽬录下(rootfs/init),添加如下内容到init⽂件
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Welcome My OS!" echo "-------------------" cd home /bin/sh
给init脚本增加可执行权限
chmod +x init
输入下面代码查看qemu运行情况:
#打包成内存根⽂件系统镜像 find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz #测试挂载根⽂件系统,看内核启动完成后是否执⾏init脚本 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
由下图可知qemu正常运行,可以着手开始做实验:
三、实验过程
1.触发系统调用准备工作
本人学号末尾两位为69,故通过查看打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl可知69号调用为msgsnd,该调用用于向消息队列发送消息,对应的内核处理函数为__x64_sys_msgsnd。
我们使用下面的代码触发系统调用
//test_msgsnd.c int main() { asm volatile( //使⽤EAX传递系统调⽤号69 "movl $0x45,%eax\n\t" //触发系统调用 "syscall\n\t" ); return 0; }
写好test_msgsnd.c后,使用命令
gcc -o test_msgsnd msgsnd.c -static
形成可执行文件,并将文件放至rootfs/home路径下,重新执行
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
从如下结果可以看到触发代码test_msgsnd.c对应的可执行程序已经移入系统中。
2.gdb调试过程
首先使用如下命令启动qemu模拟器:
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S –s
再打开⼀个窗⼝,启动gdb,把内核符号表加载进来,输入以下命令建⽴连接:
cd linux-5.4.34/ gdb vmlinux (gdb) target remote:1234 (gdb) b x86_sys_msgsnd
执行过程如下图所示:
然后在qemu模拟器内执行test_msgsnd触发代码,之后便可以在gdb处查看断点信息:
使用bt命令查看堆栈,l命令查看对应代码,由下图可知msgsnd通过syscall进行系统调用,entry_SYSCALL_64是系统调用的入口。
在gdb中进行单步执行,可以发现保存现场信息的步骤:
其中的 swapgs指令用于保存现场。这是在x86-64中引入的指令,将保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复。
继续往下执行,可以发现如下恢复现场和系统调用返回的执行过程:
其中,popq %rdi 和popq %rsp恢复了寄存器和堆栈现场,而最后执行的宏USERGS_SYSRET64,做了两部分工作:swapgs——恢复现场和sysretq——系统调用返回。
3.实验总结
从上述实验过程中,可大致了解系统调用执行过程:首先,msgsnd函数底层触发系统调用,在对应的系统调用入口entry_SYSCALL_64中,执行swapgs保存现场。之后,do_syscall_64函数根据rax寄存器中的系统调用号找到对应的系统调用__x64_sys_msgsnd,在系统调用表sys_call_table中找到相应的函数进行调用并将寄存器中保存的参数取出来,作为函数参数,然后陷入内核。系统调用函数执行完毕后,通过popq %rdi 、popq %rsp和宏USERGS_SYSRET64恢复现场并返回。