一、实验内容
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验环境配置
1、安装开发工具
sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
sudo apt install qemu
2、下载安装多线程下载工具axel并使用axel下载内核源代码
sudo apt install axel axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
3、解压内核源代码
xz -d linux-5.4.34.tar.xz tar -xvf linux-5.4.34.tar cd linux-5.4.34
4、编译内核并配置编译选项
make defconfig
make menuconfig #打开配置选项,如下图所示
修改相关选项如下:
Kernel hacking ---> Compile-time checks and compiler options ---> [*] Compile the kernel with debug info [*] Provide GDB scripts for kernel debugging [*] Kernel debugging Processor type and features ----> [] Randomize the address of the kernel image (KASLR)
5、编译
make -j$(nproc)
#在linux-5.4.34文件夹下使用qemu测试内核是否能加载,结果如下图所示显示“kernel panic”
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
6、使用busybox制作根文件系统,我直接在linux-5.4.34文件夹下下载并解压的
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
#打开debug选项,选择静态链接 Settings ---> [*] Build static binary (no shared libs)
编译安装到busybox-1.31.1下的_install目录下
make -j$(nproc) && make install
7、制作内存根文件镜像,在busybox-1.31.1目录下新键rootfs文件并将busybox-1.31.1下的_install文件中的内容复制到rootfs中,新建四个文件夹,在dev文件下新建七个文档。
mkdir rootfs cd rootfs cp ../_install/* ./ -rf mkdir dev proc sys home sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
8、在rootfs文件夹下新建一个init脚本文件,并在init中添加如下内容:
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome MengningOS!" echo "--------------------" cd home /bin/sh
9、给init文件赋予可执行权限,此时init文件名变为绿色,说明其为可执行文件了
chmod +x init
10、在rootfs文件中打包成内存根文件系统镜像,打包完后在busybox-1.31.1文件夹中就生成了rootfs.cpio.gz镜像文件,在busybox-1.31.1文件中用qemu测试挂载根文件系统,显示“Wellcome MengningOS!”
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
qemu-system-x86_64 -kernel ../arch/x86/boot/bzImage -initrd rootfs.cpio.gz
11、结果
三、编写汇编指令触发系统调用
1、查看学号后两位所对应的系统调用函数
从inux-5.4.34/arch/x86/entry/syscalls/syscall_32.tbl中找到要调用的学号后两位的63号系统调用。(由于我的虚拟机为32位所以我查找syscall_32.tbl文件,如果是64位的虚拟机则查找syscall_64.tbl文件)
由下文件截图可以看出63号对应的系统调用为sys_dup2。
2、32位Linux系统调用dup2()功能
dup2()函数可以用来复制一个文件描述符,它的原型为:
int dup2(int oldfd, int newfd);
函数执行成功返回新的文件描述符,失败则返回-1。同时它可以用newfd来指定新描述符的数值,如果newfd指向的文件已经被打开则会先将其关闭,如果newfd等于oldfd,就不关闭newfd,newfd和oldfd共同指向同一份文件。这也是linux系统的重定向实现方法。
3、编写调用代码
为了能够触发sys_dup2,新建一个test.c文件,内容编写如下:
int main() { asm volatile( "movl $0x3F,%eax\n\t" "syscall\n\t" ); return 0; }
使用gcc对文件进行编译,生成可执行文件test,注意要使用静态编译。
gcc test.c -o test -static
将生成的文件复制到rootfs下的home文件夹中
4、在rootfs文件中重新打包在busybox-1.31.1中生成内存根文件系统镜像rootfs.cpio.gz
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
四、gdb跟踪该系统调用的内核处理过程
1、启动qemu
在busybox-1.31.1文件中纯命令行启动qemu
qemu-system-x86_64 -kernel ../arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
此时会停留在如下图界面不动。
2、启动gdb
在linux-5.4.34中打开另一个新的终端,运行如下代码,查看sys_dup2的断点在fs/file.c的第948行处。
gdb vmlinux (gdb) target remote:1234 (gdb) b sys_dup2
输入c继续,回到初试终端运行home下刚编译好的可执行文件test
3、调试结果
不停输入n进行单步gdb调试,由结果可得触发的是函数__se_sys_dup2()。
五、总结
通过以上gdb调试,entry_sysenter_332()是整个系统调用的入口处,它负责保存现场,调用对应的内核处理函数,并恢复现场,完成系统调用的返回等一系列工作。并且它的保存现场主要是通过swapgs和压栈完成的。
do_syscall_32函数可以在相应寄存器中获取到系统调用号。
然后执行dup2()函数。
最后两个pop指令恢复了相应寄存器