深入理解Linux系统调用

一、实验内容

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

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

深入理解Linux系统调用_第1张图片

 

 

打印并执行成功,内存根文件系统制作完毕

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

首先查看要选择进行实验的系统调用--94号系统调用

 

可知94号系统调用为lchown。对应的内核处理函数为__x64_sys_lchown。作用:修改文件所有者和组别。触发这一系统调用,我们需要写一个小程序。使用内联汇编小程序new_test.c如下:

int main()
{
    asm volatile(
    "movl $0x5E,%eax\n\t" //使⽤EAX传递系统调⽤号94
    "syscall\n\t" //触发系统调⽤ 
    );
    return 0;
}

 

 
  

由于我们搭建的系统不支持动态链接,因此这里我们在使用gcc编译时要用-static静态编译参数)。

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

    

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

打包完毕后,我们的构建系统的根目录下应该已经有new_test这一可执行文件了,用qemu运行,检查一下。如下图所示: 

 

接下来我们首先需要检查是否成功触发了94号系统调用,然后逐步跟踪了解内核的处理过程、系统调用入口的保存现场、恢复现场和系统调用返回等流程

四:gdb调试

1. 纯命令行启动qemu

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

运行gdb,进入调试环境

 

gdb捕获到了断点 —— __x64_sys_lchown函数,94号系统调用触发成功。如下图:

深入理解Linux系统调用_第2张图片

 

 

gdb单步调试

深入理解Linux系统调用_第3张图片

深入理解Linux系统调用_第4张图片

四:结果分析

内核的初始化完成了以下的函数调用过程:

start_kernel > trap_init > cpu_init > syscall_init, 并在syscall_init中完成将entry_SYSCALL_64的入口地址存入msr寄存器中,我们用gdb来调试验证以下,分别给这几个函数打上断点,检测断点捕获情况和执行顺序。

深入理解Linux系统调用_第5张图片

 

汇编指令syscall 触发系统调用,通过MSR寄存器找到了中断函数入口

系统调用的保存现场和恢复现场

entry_SYSCALL_64是系统调用的入口点,它完成了保存现场,调用对应的内核处理函数、恢复现场、系统调用返回等工作。

在linux5.4.34/arch/x86/kernel/entry中找到enrty_64.s,它是enrty_SYSCALL_64的所在

深入理解Linux系统调用_第6张图片

结合代码

可以看到,它没有使用sava_all命令保存现场,而使用了特殊的swapgs,来快照式地保存现场,加快了系统调用的速度。

swapgs之后push了一堆寄存器,将其入内核堆栈,然后转换成pt_regs结构体......如下图所示: 深入理解Linux系统调用_第7张图片

 

 然后,执行了do_syscall_64这一内核函数

跳转获得系统调用号,执行系统调用的内容

 深入理解Linux系统调用_第8张图片

 调用结束,恢复到用户态执行syscall_return_slowpath 函数要为恢复现场做准备。

深入理解Linux系统调用_第9张图片深入理解Linux系统调用_第10张图片

swapgs——恢复现场

 深入理解Linux系统调用_第11张图片

 

 

 

 

 

 

你可能感兴趣的:(深入理解Linux系统调用)