首页 > 其他 > 详细

深入理解系统调用

时间:2020-05-25 13:54:27      阅读:48      评论:0      收藏:0      [点我收藏+]

 一、实验原理

       系统调?的库函数就是我们使?的操作系统提供的 API(应?程序编程接?),API 只是函数定义。系统调?是通过特定的软件中断(陷阱 trap)向内核发出服务请求,int $0x80和syscall指令的执?就会触发?个系统调?。C库函数内部使?了系统调?的封装例程,其主要?的是发布系统调?,使程序员在写代码时不需要?汇编指令和寄存器传递参数来触发系统调?。?般每个系统调?对应?个系统调?的封装例程,函数库再?这些封装例程定义出给程序员调?的 API,这样把系统调?最终封装成?便程序员使?的C库函数。
       当?户态进程调??个系统调?时,CPU切换到内核态并开始执?system_call(entry_INT80_32或entry_SYSCALL_64)汇编代码,其中根据系统调?号调?对应的内核处理函数。具体来说,在Linux中通
过执?int $0x80或syscall指令来触发系统调?的执?,其中这条int $0x80汇编指令是产?中断向量为128的编程异常(trap)。另外Intel处理器中还引?了sysenter指令(快速系统调?),因为Intel专?
AMD并不?持,在此不再详述。我们只关注int指令和syscall指令触发的系统调?,进?内核后,开始执?对应的中断服务程序entry_INT80_32或entry_SYSCALL_64。
      内核通过给每个系统调??个编号来区分,即系统调?号,将API函数xyz()和系统调?内核函数sys_xyz()关联起来了。内核实现了很多不同的系统调?,?户态进程必须指明需要执?哪个系统调?,这需要使?EAX寄存器传递?个名为系统调?号的参数。除了系统调?号外,系统调?也可能需要传递参数。由于压栈的?式需要读写内存,函数调?速度较慢,64位x86体系结构下普通的函数调?和系统调?都是通过寄存器传递参数,RDI、RSI、RDX、RCX、R8、R9这6个寄存器?作函数/系统调?参数传递,依次对应第 1 参数到第 6 个参数。

二、实验准备

下载内核源码

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  #  此时应该不能正常运行

配置过程如下图所示:

技术分享图片

制作根文件系统

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 "Welcome My OS!"
 echo "-------------------" 
 cd home
 /bin/sh 

给init脚本增加可执行权限

chmod +x init

输入下面代码查看qemu运行情况: 

#打包成内存根?件系统镜像 
?nd . -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  

由下图可知qemu正常运行,可以着手开始做实验:

技术分享图片 

 三、实验过程

1.触发系统调用准备工作

本人学号末尾两位为69,故通过查看打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl可知69号调用为msgsnd,该调用用于向消息队列发送消息,对应的内核处理函数为__x64_sys_msgsnd。

技术分享图片

我们使用下面的代码触发系统调用

//test_msgsnd.c
int main() {   asm volatile( //使?EAX传递系统调?号69   "movl $0x45,%eax\n\t" //触发系统调用   "syscall\n\t"
);   return 0; }  

写好test_msgsnd.c后,使用命令 

gcc -o test_msgsnd msgsnd.c -static 

形成可执行文件,并将文件放至rootfs/home路径下,重新执行

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

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

从如下结果可以看到触发代码test_msgsnd.c对应的可执行程序已经移入系统中。

技术分享图片

 2.gdb调试过程

首先使用如下命令启动qemu模拟器:

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

再打开?个窗?,启动gdb,把内核符号表加载进来,输入以下命令建?连接:

cd linux-5.4.34/
gdb vmlinux
(gdb) target remote:1234
(gdb) b x86_sys_msgsnd  

 执行过程如下图所示:

技术分享图片

技术分享图片

技术分享图片

然后在qemu模拟器内执行test_msgsnd触发代码,之后便可以在gdb处查看断点信息:

技术分享图片

技术分享图片

使用bt命令查看堆栈,l命令查看对应代码,由下图可知msgsnd通过syscall进行系统调用,entry_SYSCALL_64是系统调用的入口。

技术分享图片

技术分享图片

技术分享图片

 

技术分享图片

技术分享图片技术分享图片

 

在gdb中进行单步执行,可以发现保存现场信息的步骤:0

技术分享图片

其中的 swapgs指令用于保存现场。这是在x86-64中引入的指令,将保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复。

继续往下执行,可以发现如下恢复现场和系统调用返回的执行过程:

技术分享图片

技术分享图片

其中,popq %rdi 和popq %rsp恢复了寄存器和堆栈现场,而最后执行的宏USERGS_SYSRET64,做了两部分工作:swapgs——恢复现场和sysretq——系统调用返回。

3.实验总结

从上述实验过程中,可大致了解系统调用执行过程:首先,msgsnd函数底层触发系统调用,在对应的系统调用入口entry_SYSCALL_64中,执行swapgs保存现场。之后,do_syscall_64函数根据rax寄存器中的系统调用号找到对应的系统调用__x64_sys_msgsnd,在系统调用表sys_call_table中找到相应的函数进行调用并将寄存器中保存的参数取出来,作为函数参数,然后陷入内核。系统调用函数执行完毕后,通过popq %rdi 、popq %rsp和宏USERGS_SYSRET64恢复现场并返回。

深入理解系统调用

原文:https://www.cnblogs.com/lwis/p/12953704.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!