深入理解系统调用
一、准备实验环境
由于我的学号最后两位是54,故这里选择54号系统调用。
打开第一个实验下载的linux-5.4.34,找到目录arch/x86/entry/syscalls/
下的syscall_64.tbl
文件并打开,该文件包含了64位x86系统对应的系统调用号。找到54号对应的系统调用
54号对应的系统调用为setsockopt,经查资料,该系统调用的作用是设置与某个套接字关联的选项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。由于我们是为了理解系统调用的详细过程,故这里不对该系统调用的具体功能过分深究,仅做粗略介绍即可。
配置内核
这里内核使用实验一下载的linux-5.4.34,不过去除了mykernel。
将下载好的linux内核重新解压,进入解压目录,打开shell,输入以下命令:
make defconfig make menuconfig
shell会跳转到这个界面:
选中Kernel hacking
选中Compile-time checks and compiler options
将光标移动到Compile the kernel with debug info 和 Provide GDB scripts for kernel debugging上,按Y勾选。
按四次Esc键返回到首页,选中Processor type and features,关闭其中的关闭Randomize the address of the kernel image (KASLR)
全部配置好后保存并退出。
接着使用以下命令对内核进行编译并尝试运行内核:
make -j$(nproc) qemu-system-x86_64 -kernel arch/x86/boot/bzImage
内核目前无法正常加载,不过这是正常现象,我们接下来来制作根文件系统。
制作根文件系统
使用命令 axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2 来下载busybox-1.31.1,解压busybox并进入其目录。
输入 make menuconfig 来更改设置
选择setting--->Build static binary (no shared libs)
然后使用命令 make -j$(nproc) && make install
编译并安装
这里busybox第一次安装出现rdate和date模块安装错误,遂去make menuconfig中关闭了这两个模块后安装成功。
使用以下命令创建根目录,注意rootfs文件夹与busybox-1.31.1平行:
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/
在rootfs文件夹下创建init文件
touch init
编辑init的内容如下:
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome MyOS!" echo "--------------------" cd home /bin/sh
给init文件加上执行权限:
sudo chmod +x init
接着回到linux-5.4.34目录下,输入以下命令打包成内存根文件系统镜像(时间可能较长)
:
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
出现了我们在init中自定义的欢迎标语,运行成功(另外一开始这一步总是找不到init文件,几番尝试之下我发现在给init改变权限的时候需要使用sudo chmod,之后便成功)。
二、编写系统调用程序
写一小段程序syscall54.c调用54号系统调用,由于;这里仅仅是为了观察系统调用的过程,故程序写的较为简单:
int main() { asm volatile( "movl $0x36,%eax\n\t" //将系统调用号54转换为16进制传给eax "syscall\n\t" //触发系统调⽤ );
printf("Hello World!\n"); return 0; }
使用gcc编译该代码,注意由于这里是静态链接,所以要带上-static参数。
将编译好的文件放入rootfs/home路径下,并重新打包镜像。
启动qemu,使用ls命令查看是否有文件:
三、进行gdb调试
使用以下命令启动qemu,此时qemu处于暂停状态:
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
再开一个shell,输入以下命令给我们需要调试的函数打上断点:
请注意这里有个天坑!在下面打开第二个shell进行gdb调试的时候有的同学(我)为了图省事直接在linux-5.4.34目录下打开shell,这样看似会省略掉cd linux-5.4.34这一步,但是会导致gdb无法与qemu关联起来从而导致调试失败!本人在这上面折腾了半天才发现这个天坑。至于为啥不能直接在文件夹中打开shell而必须要在根目录中打开shell然后cd到linux目录下,这我也说不清楚。只能说linux真是一个神奇的操作系统......
cd linux-5.4.34/ gdb vmlinux (gdb) target remote:1234 (gdb) b __x64_sys_setsockopt
断点打上后如下:
在gdb调试界面输入c以继续运行qemu。
运行我们home目录下的syscall54文件:
gdb调试会捕捉到断点:
在gdb中使用bt命令查看堆栈信息,l命令查看对应代码。
可以看到系统调用入口为entry_SYSCALL_64,其又返回了__sys_setsockopt的调用入口。
使用n单步执行,查看每一步的具体操作:
系统首先通过entry_SYSCALL_64进入系统调用,do_syscall_64获得了系统调用号,跳转至linux内核net文件夹下的socket文件2100行去执行具体的__sys_setsockopt函数,swapgs用类似快照的方式来保存现场和恢复现场,交换用户态与内核态,最后通过sysret指令恢复堆栈。