代码改变世界

深入理解Linux系统调用

2020-05-27 11:53  微光~  阅读(312)  评论(0编辑  收藏  举报

一、实验内容

1.学号末尾为98,故采用98号系统调用

2.通过汇编指令触发系统调用

3.通过gdb跟踪该系统调用的内核处理过程

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

二、环境准备

安装开发工具

sudo apt install build-essential
sudo apt install qemu # install QEMU 
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev

下载内核源代码

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 "Wellcome TestOS!" echo "--------------------"
cd home
/bin/sh

#给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


内存根文件系统制作完毕

三:查看系统调用,编写调用汇编代码

根据学号后两位在/arch/x86/entry/syscalls/syscall_64.tbl 中找到对应的系统调用函数__x64_sys_getrusage

 

 

查阅可以得知,getrusage()用来获得进程的相关资源信息。如:用户开销时间,系统开销时间,接收的信号量等等;其数据结构如下:

struct rusage {
    struct timeval ru_utime; /* user time used 用户态使用的时间 */
    struct timeval ru_stime; /* system time used 内核态使用的时间 */
    long   ru_maxrss;        /* maximum resident set size  */
    long   ru_ixrss;         /* integral shared memory size */
    long   ru_idrss;         /* integral unshared data size */
    long   ru_isrss;         /* integral unshared stack size */
    long   ru_minflt;        /* page reclaims */
    long   ru_majflt;        /* page faults */
    long   ru_nswap;         /* swaps */
    long   ru_inblock;       /* block input operations */
    long   ru_oublock;       /* block output operations */
    long   ru_msgsnd;        /* messages sent */
    long   ru_msgrcv;        /* messages received */
    long   ru_nsignals;      /* signals received */
    long   ru_nvcsw;         /* voluntary context switches */
    long   ru_nivcsw;        /* involuntary context switches */
};

并且提供wait3,wait4和getrusage来获取该结构。

#include <sys/time.h>
#include <sys/resource.h>

int getrusage(int who, struct rusage usage);

汇编进行系统调用

写一个test.c的测试文件

int main()
{
  asm volatile (
      "movq $0x62, %rax\n\t"
      "syscall\n\t"
      );
  return 0;
}

使用下面命令将test.c进行静态编译

gcc -o test test.c -static

将形成的可执行文件放到rootfs/home/目录下,然后重新打包rootfs文件夹,由于我们对系统做了修改,需要重新打包成内存根文件系统镜像。因此要再次使用命令:

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

这样,我们就完成了系统调用的准备,接下来使用gdb进行调试。

纯命令行启动qemu

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

在linux-5.4.34目录下启动gdb调试

cd linux-5.4.34/
gdb vmlinux
target remote:1234
b __x64_sys_pwrite64 

可以使用bt来观察方法的堆栈从而知道 entry_SYSCALL_64是系统调用的入口,它在系统调用之前已经初始化了该方法对应的内核堆栈

 

 继续调试 直到最后结束,可以看到恢复现场以及系统调用返回,以及从内核态返回用户态的步骤:

 

 可以看到该系统调用涉及到do_syscall_64和entry_SYSCALL_64两个内核函数。

四、总结

本次实验中,使用gdb工具断点调试,查看了98号系统调用的调用过程,了解了系统调用的保存和恢复现场。

整个系统调用,保存和恢复现场过程:syscall指令触发系统调用,通过MSR寄存器找到了中断函数入口

然后进如 entry_SYSCALL_64( )执行现场保存

do_syscall_64( )查找调用入口并执行

准备恢复现场 调用entry_SYSCALL_64( )最后完成现场恢复