深入理解系统调用

-、实验内容

1、触发系统调用。

2、通过gdb跟踪该系统调用的内核处理过程。

3、分析系统调用入口的保存现场、恢复现场和系统调用返回,以及关注系统调用过程中内核堆栈状态的变化。

 

二、环境

沿用了第一次的压缩包,

xz -d linux-5.4.34.tar.xz 
tar -xvf linux-5.4.34.tar cd linux-5.4.34

 

配置内核编译选项 

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 
# 关闭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
测试
qemu-system-x86_64 -kernel arch/x86/boot/bzImage  

因为没有⽂件系统终会kernel panic 

 

接下来就要制作根文件系统了

#下载
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


制作内存根文件系统镜像
这里的目录要与busy的平行 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目录下)

touch init

然后将这些文件内容写进去
#!/bin/sh
mount -t proc none /proc mount -t sysfs none /sys
echo "Wellcome TestOS!" echo "--------------------"
cd home
/bin/sh

写入文件的指令:
vi init
按i进入编辑模式
将上方的脚本内容复制进去
按esc退出编辑模式 按:然后wq 最后回车保存推出
#给init脚本添加可执行文件
chmod +x init


#打包成内存根文件系统镜像
 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

 

 

 

三、编写系统调用程序

由于我的学号是46结尾,所以打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,查看要选择进行实验的系统调用。

 

 

 46号系统调用是sendmsg,这个是一个最通用的I/O函数,可以将各种输出函数调用替换成sendmsg调用。

大部分参数封装在msghdr结构中

struct msghdr{
    void          *msg_name;
    socklen_t    msg_namelen;
    struct         iovec  *msg_iov;
    int              msg_iovlen;
    void           *msg_control;
    socklen_t     msg_controllen;
    int             msg_flags;
}                

 

为了触发这个系统调用,需要写一个小程序

 

 

 

四,调试

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

 在另一个终端:

    

   捕获到了断点 —— __x64_sys_lchown函数,系统调用触发成功

   

 

 

  再进行单步调试:

 

 

 

 

 

 五、分析:

1.触发系统调用后,代码执行了/linux-5.4.34/arch/x86/entry/entry_64.S 目录下的ENTRY(entry_SYSCALL_64)入口,然后开始通过swapgs 和压栈动作保存现场:

 

 

 

 2.然后跳转到了/linux-5.4.34/arch/x86/entry/common.c 目录下的 do_syscall_64 函数,在ax寄存器中获取到系统调用号,然后去执行系统调用

 

 

 

3.查看entry_syscall_64后续的代码,在完成执行现场的恢复,最后的两个popq出栈指令恢复原 rdi 和 rsp的内容,也就是完成了堆栈的切换。

 

 

 

从系统调用的整个过程来看,主要有以下几个阶段:

(1)用户态程序,发生syscall,触发系统调用;

(2)进入内核态,完成内核初始化后,调用entry_SYSCALL_64 ()

(3)完成现场的保存,将关键寄存器压栈,并从CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,更改CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝ ,调用do_syscall_64()

(4)do_syscall_64()函数中得到系统调用号,调用相关的函数gettimeofday()

(5)调用结束后,保存现场和恢复现场时的CPU存器也通过CPU内部的存储器快速保存和恢复 

(6)系统调用返回,回到用户态程序

 

posted @ 2020-05-26 12:56  sktt1mata川  阅读(235)  评论(0编辑  收藏  举报