深入理解Linux系统调用

一、实验内容

  • 找一个系统调用,系统调用号为学号最后2位相同的系统调用
  • 通过汇编指令触发该系统调用
  • 通过gdb跟踪该系统调用的内核处理过程
  • 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

二、实验步骤

由于启动内核需要根文件系统,我们上次的实验制作了简单的内核,但是没有制作根文件系统,内核检测不到初始化文件,

所以内核无法正常启动。

2.1 环境配置

这里和第一个实验类似,这里就不截图了。

安装开发工具

sudo apt install build-essential
sudo apt install qemu # install QEMU 
sudo apt install libncurses5-dev bison ?ex 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 defcon?g # Default con?guration is based on x86_64_defcon?g
make menucon?g  
# 打开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  #  此时应该不能正常运行

 

 

2.2 制作根文件系统

电脑加电启动?先由bootloader(启动加载程序)加载内核,内核紧接着需要挂载内存
根?件系统,其中包含必要的设备驱动和?具, bootloader加载根?
件系统到内存中,内核会将其挂载到根?录/下,然后运?根?件系统
init脚本执??些启动任务,最后才挂载真正的磁盘根?件系统

我们这?为了简化实验环境,仅制作内存根?件系统。这?借助
BusyBox 构建极简内存根?件系统,提供基本的?户态可执?程序。


?先从https://www.busybox.net下载 busybox源代码解压,解压完成
后,跟内核?样先配置编译,并安装。

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 menucon?g 
#记得要编译成静态链接,不?动态链接库。
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系统调用

 

 

2.3 找一个系统调用,系统调用号为学号最后2位(18)相同的系统调用

Linux源代码中的arch/x86/entry/syscalls/syscall_32.tblarch/x86/
entry/syscalls/syscall_64.tbl 分别定义了32x86x86-64的系统调
?内核处理函数
通过查看linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl系统调用表

可以看到18号系统调用为pwrite64,此系统调用功能为

从给定偏移量处读取或写入文件描述符,对应内核处理函数为:__x64_sys_pwrite64。

深入理解Linux系统调用

 

 

 

2.3.1 编写汇编代码调用该系统调用

创建test.c文件

#include <stdio.h>

int main()
{
    asm volatile(     
            "movl $0x12, %eax\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进行调试。

 

2.4 gdb跟踪系统调用

先输入如下命令:

 因为我们不需要看图形界面,所以使用纯命令启动

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

 

然后新开启一个terminal

输入如下命令来开启gdb调试并在我们要调用的系统调用处设置断点:

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

运行如下:

深入理解Linux系统调用

 

 然后在gdb里面输入c使qemu继续运行,在qemu虚拟机里面,

输入./test ,然后就可以在gdb页面看到系统执行到了我们打断点的位置

深入理解Linux系统调用

 

 

继续单步执行:

深入理解Linux系统调用

 

 在系统调用执行完成之后就开始恢复现场,其中entry_SYSCALL_64代码如下

 

 

 深入理解Linux系统调用

继续单步执行,直到恢复现场完成:

深入理解Linux系统调用

 

 

三、实验总结

pwrite64 函数通过系统调用函数__x64_sys_pread64出发 ,其功能由ksys_pread64() 实现;

本次实验通过汇编指令传递系统调用号并触发系统调用,找到函数中断入口。通过do_syscall_64 函数

得到了系统调用号 ,执行系统调用内容。然后程序跳到read_write.c文件,触发了ksys_pread64() 函数,

执行完毕后,又跳回了do_syscall_64 中的syscall_return_slowpath() ,准备进行现场恢复操作,

执行entry_SYSCALL_64() ,在entry_SYSCALL_64() 里通过swapgs 指令保存现场,

并准备跳回用户态。最后执行popq ,完成堆栈切换。

 

深入理解Linux系统调用

上一篇:Vim 配置


下一篇:Linux中的软连接与硬连接