深入理解系统调用

一、准备实验环境

由于我的学号最后两位是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指令恢复堆栈。

posted @ 2020-05-25 23:10  darz233  阅读(256)  评论(0编辑  收藏  举报