深入理解系统调用

一、实验要求

1.找一个系统调用,系统调用号为学号最后2位相同的系统调用

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

3.通过gdb跟踪该系统调用的内核处理过程,重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

4.总结分析系统调用的工作机制。

二、实验环境准备

1.   安装一些必要的开发工具

sudo apt install build-essential

sudo apt install qemu # install QEMU

sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev

2.   下载内核源码

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

3.   配置内核选项

make defconig # 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)

 

KASLR让加载到内核时是随机一个地址,如果加载内核一直同一个地址,容易被黑客攻击,这里为了实验方便,把它关闭。

到这里,内核还不能够正常加载运⾏,因为没有⽂件系统,最终会kernel panic。

4.   制作根件系统

我们这⾥为了简化实验环境,仅制作内存根⽂件系统。这⾥借助 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脚本添加可执⾏权限

 chmod +x init

 打包成内存根⽂件系统镜像

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

5.测试挂载根件系统,看内核启动完成后是否执init脚本

执行qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

6.接下来测试能不能跟踪调试

执行qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S –s

再打开⼀个窗⼝,启动gdb,把内核符号表加载进来,建⽴连接

可以看到能够使⽤gdb跟踪调试Linux内核

到这里,环境已经准备好啦!

三、实验步骤

1. 找一个系统调用,系统调用号为学号最后2位相同的系统调用

我的学号后两位是71,打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,这里我选择171号系统调用__x64_sys_setdomainname

setdomaainname是设置主机的域名,用法如下:

第一个参数是主机域名,第二个参数主机域名的长度.

需要注意的是需要在root权限下才能设置域名,返回0则设置成功,返回1则设置错误。

编写程序setdomianname.c,代码如下

编译运行结果为(注意:这里需要用静态编译):

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

把程序反汇编之后查找关键代码

最终通过汇编指令触发系统调用的程序setdomainname_asm.c代码如下:

编译运行结果如下图所示:

3.通过gdb跟踪该系统调用的内核处理过程,重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化。

将生成的可执行文件文件拷贝至rootfs/home文件夹下,然后重新打包成内存根⽂件系统镜像

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S –s

⽤以上命令先启动,然后可以看到虚拟机⼀启动就暂停了

新创建一个终端开启调试,在系统调用入口加一个断点,发现运行setdomainname程序后停止在此处

 bt查看此时的堆栈,可以看到__x64_sys_setdomainname系统调用函数,do_syscall_64 获取系统调用号, 前往系统调用函数,entry_syscall_64 中断入口。

端点定位到了kernel/sys.c的1358行,如下图所示

执行完这个函数,回到了do_sys_call_64 中 ,要执行的 syscall_return_slowpath 函数要为恢复现场做准备。

继续执行到entry_SYSCALL_64()

最后两个pop指令恢复rdi和rsp寄存器

系统调用完成,设置主机域名成功,结果为:

四、实验总结(分析系统调用的工作机制)

  系统调用主要分为以下五步,指令触发系统调用(这里是syscall),保存现场,中断处理,恢复现场,中断返回,第一步在用户态,剩下四步在内核态。

  syscall指令触发系统调⽤机制会在堆栈上保存当前执⾏程序的栈顶地址(ESP、RSP)、当时的状态字(EFlags、RFlags)、当时 的 CS:EIP/RIP 的值。同时会将当前进程内核态的栈顶地址、内核态的状态字放⼊ CPU 对应 的寄存器,并且 CS:EIP/RIP 寄存器的值会指向中断处理程序的⼊⼝,对于系统调⽤来讲是指 向系统调⽤处理的⼊⼝。系统调⽤发⽣之后进⼊中断处理程序,当中断处 理程序结束后,(中断处理结束前的最后⼀件事是恢复现场)就会执⾏ restore_all 和 INTERRUPT_RETURN(iret),负责把中断时保存的⽤户态寄存器再进⾏出栈操作到当前的 CPU ⾥⾯。最后的 iret与中断信号(包括 int 指令)发⽣时的 CPU 做的动作正好相反,之前是保存,这⾥就是恢复。

posted @ 2020-05-26 19:53  hajiao  阅读(524)  评论(0)    收藏  举报