ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

深入理解系统调用

2020-05-27 21:59:02  阅读:266  来源: 互联网

标签:kernel 调用 系统 init 理解 深入 内核 64


1、实验内容

1)系统调用号后两位为21的系统调用

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

3)通过gdb跟踪该系统调用的内核处理过程

4)重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化

 

2、环境配置

#安装开发⼯具
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

3、制作内存根文件系统

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.
#制作根文件系统
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

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

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

4、系统调用

1)Linux系统调用

系统调⽤的库函数就是使⽤的操作系统提供的 API(应⽤程序编程接⼝)。系统调⽤是通过特定的软件中断(陷阱 trap)向内核发出服务请求,int $0x80 和syscall指令的执⾏就会触发⼀个系统调⽤。C库函数内部使⽤了系统调⽤的封装例程, 其主要⽬的是发布系统调⽤,使程序员在写代码时不需要⽤汇编指令和寄存器传递参数来触发系统调⽤。⼀般每个系统调⽤对应⼀个系统调⽤的封装例程,函数库再⽤这些封装例程定义出给程序员调⽤的API,这样把系统调⽤终封装成⽅便程序员使⽤的C库函数。

如上图所示,系统调用执行的流程如下:

1. 应用程序代码调用 xyz(),该函数是一个包装系统调用的库函数;
2. 库函数 xyz() 负责准备向内核传递的参数,并触发软中断以切换到内核;
3. CPU 被软中断打断后,执行中断处理函数,即系统调用处理函数(system_call);
4. 系统调用处理函数调用系统调用服务例程(sys_xyz ),真正开始处理该系统调用。

2)Syscall

在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植,只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号,比很多操作系统要少很多。

3)查找21号系统调用

 

Tips 1: 用户空间的方法access,对应系统调用层方法则是sys_access
Tips 2: 查找系统调用中断号的信息。

Tips 3: 查看对应的方法sys_access

asmlinkage是gcc标签,代表函数读取的参数来自于栈中,而非寄存器。由此可以看出,该系统调用用于检查调用进程是否可以对指定的文件执行某种操作。

参数:
filename: 需要测试的文件路径名。
mode: 需要测试的操作模式,可能值是一个或多个R_OK(可读?), W_OK(可写?), X_OK(可执行?) 或 F_OK(文件存在?)组合体。   5、汇编代码调用 编写测试代码test.c
int main()
{
    asm volatile(
    "movl $0x15,%eax\n\t" //使⽤EAX传递系统调⽤号21
    "syscall\n\t" //触发系统调⽤
    );
    return 0;
}

使用gcc进行静态编译

gcc test.c -o test -static

重新制作根文件系统

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
  6、通过gdb进行调试
cd linux-5.4.34/
gdb vmlinux 

(gdb) target remote:1234 
# 在access调用处打断点
b __64x_sys_access

 

 7、系统调用过程分析

系统调用的初始化过程为:start_kernel --> trap_init --> cpu_init --> syscall_init。主要分为两步,第一步是中断初始化,第二步是系统调用初始化。

在初始化内核的时候,会执行中断初始化函数trap_init,此函数拷贝中断异常向量表到指定位置,系统就能根据中断向量号跳转到对应的中断处理。而系统调用对应的中断是软件中断,其向量号为0x80。在第一步中,通过将软件中断的处理程序system_call(entry_SYSCALL_64)与0x80绑定,所以执行int 0x80时系统就会跳转到中断处理函数system_call。其次,中断处理函数还需要根据系统调用号来执行相应的系统调用,这就需要初始化系统调用,将系统调用中断向量与服务例程绑定。内核维护一张系统调用表system call table,系统调用表是Linux内核源码文件 arch/x86/entry/syscall_64.c中定义的数组sys_call_table的对应。在第二步中,cpu_init函数调用syscall_init完成per-cpu状态初始化。

1)start_kernel

 start_kernel是过了引导阶段,进入到了内核启动阶段的入口。这个函数是设置操作系统的第一个进程init。printk(linux_banner)函数打出
系统版本号。每个进程创建的时候,系统会为这个进程创建2个页大小的内核栈。这个内核栈底下是thread_info结构。高位是栈。

2)syscall_init 

 

第一个特殊模块集寄存器- MSR_STAR 为用户代码的代码段。这些数据将加载至 CS 和 SS 段选择符,由提供将系统调用返回至相应特权级的用户代码功能的 sysret 指令使用。 第二行代码中将使用系统调用入口entry_SYSCALL_64 填充 MSR_LSTAR 寄存器。 entry_SYSCALL_64 在arch/x86/entry/syscall_64.S汇编文件中定义,包含系统调用执行前的准备。

3)do_syscall_64 entry_syscall_64

当⽤户态进程调⽤⼀个系统调⽤时,CPU切换到内核态并开始执⾏ system_call(entry_INT80_32或entry_SYSCALL_64)汇编代码,其中根据系统调⽤号调⽤对应的内核处理函数。在do_syscall_64中根据系统调用号执行对应的系统调用。

 

 

 

 

 

标签:kernel,调用,系统,init,理解,深入,内核,64
来源: https://www.cnblogs.com/mia-blog/p/12965992.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有