深入理解Linux系统调用
Posted sa19225518朱帅帅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Linux系统调用相关的知识,希望对你有一定的参考价值。
一、实验内容
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验步骤
由于启动内核需要根文件系统,我们上次的实验制作了简单的内核,但是没有制作根文件系统,内核检测不到初始化文件,
所以内核无法正常启动。
2.1 环境配置
这里和第一个实验类似,这里就不截图了。
安装开发工具
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 # 此时应该不能正常运行
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 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
可以看到内核成功启动
2.3 找一个系统调用,系统调用号为学号最后2位(18)相同的系统调用
Linux源代码中的arch/x86/entry/syscalls/syscall_32.tbl和arch/x86/
entry/syscalls/syscall_64.tbl 分别定义了32位x86和x86-64的系统调
⽤内核处理函数
通过查看linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl系统调用表
可以看到18号系统调用为pwrite64,此系统调用功能为
从给定偏移量处读取或写入文件描述符,对应内核处理函数为:__x64_sys_pwrite64。
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
运行如下:
然后在gdb里面输入c使qemu继续运行,在qemu虚拟机里面,
输入./test ,然后就可以在gdb页面看到系统执行到了我们打断点的位置
继续单步执行:
在系统调用执行完成之后就开始恢复现场,其中entry_SYSCALL_64代码如下
继续单步执行,直到恢复现场完成:
三、实验总结
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系统调用的主要内容,如果未能解决你的问题,请参考以下文章