深入理解系统调用

Posted yhhh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解系统调用相关的知识,希望对你有一定的参考价值。

一、实验要求

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

二、实验环境

VMware Workstation Pro 15

ubuntu18.04.4

三、实验准备

配置内核

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 
qemu-system-x86_64 -kernel arch/x86/boot/bzImage # 测试?下内核能不能正常加载运?,因为没有?件系统最终会kernel panic 

技术图片

制作根文件系统

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 

启动内核:

技术图片

四、实验内容

1.查询30号系统调用是shmat函数

技术图片

shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数,shm_id是由shmget()函数返回的共享内存标识。

第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

 

为了观察该系统调用,在rootfs/home目录下写一个程序来触发系统调用

#include <sys/ipc.h>

#include <sys/shm.h>

#include <stdio.h>


int main(){
    int shmid;
    asm volatile(
            "movl %1, %%ebx
	"      //将第一个参数放入edi
            "movl %2, %%ecx
	"      //将第二个参数放入rsi
            "movl %3, %%edx
	"      //将第三个参数放入esi
            "movl $0x1E, %%eax
	"   //中断号30放入eax
            "syscall
	"            //触发系统调?
            "movl %%eax, %0
	"
            :"=m"(shmid)              //将结果放入shmid中
        );
    if(shmid<0){
        printf("error");
        return -1;
    }
    printf("%d
",shmid);
    return 0;
}

 

对该程序进行静态编译

gcc testSys.c -o testSys -static

重新打包内存跟文件系统镜像

?nd . -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 -S -s

2.在linux-5.4.34目录下启动gdb调试

gdb vmlinux
target remote:1234
b __64x_sys_shmat
c

 GDB调试

技术图片

寄存器eax保存系统调用号30、ebx,ecx和edx分别保存三个栈顶参数。

调用中断指令int 0x80进入内核态。找到函数中断入口,执行 entry_SYSCALL_64() ,然后开始通过swapgs 和压栈动作保存现场。

ENTRY(entry_SYSCALL_64)
    UNWIND_HINT_EMPTY
    /*
     * Interrupts are off on entry.
     * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
     * it is too small to ever cause noticeable irq latency.
     */

    swapgs  /*swapgs指令切换gs寄存器从用户态到内核态*/
    /* tss.sp2 is scratch space. */
    movq    %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /*保存中断上下文中的rsp寄存器的值*/
    SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp      /*切换到内核堆栈空间*/
    movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp  

    /* Construct struct pt_regs on stack */
    pushq    $__USER_DS                /* pt_regs->ss */
    pushq    PER_CPU_VAR(cpu_tss_rw + TSS_sp2)    /* pt_regs->sp */
    pushq    %r11                    /* pt_regs->flags */
    pushq    $__USER_CS                /* pt_regs->cs */
    pushq    %rcx                    /* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
    pushq    %rax                    /* pt_regs->orig_ax */

    PUSH_AND_CLEAR_REGS rax=$-ENOSYS

    TRACE_IRQS_OFF          

    /* IRQs are off. */
    movq    %rax, %rdi      
    movq    %rsp, %rsi      
    call    do_syscall_64        /* returns with IRQs disabled */

之后执行do_syscall_64(),此时根据eax查询系统调用表,获得系统调用号,执行系统调用的内容。

#ifdef CONFIG_X86_64
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
    struct thread_info *ti;

    enter_from_user_mode();
    local_irq_enable();
    ti = current_thread_info();
    if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
        nr = syscall_trace_enter(regs);

    if (likely(nr < NR_syscalls)) {
        nr = array_index_nospec(nr, NR_syscalls);
        regs->ax = sys_call_table[nr](regs);  /*获得系统调用号*/
#ifdef CONFIG_X86_X32_ABI
    } else if (likely((nr & __X32_SYSCALL_BIT) &&
              (nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
        nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
                    X32_NR_syscalls);
        regs->ax = x32_sys_call_table[nr](regs);
#endif
    }

    syscall_return_slowpath(regs);
}

服务结束后, 调用entry_SYSCALL_64( )最后完成现场恢复,回到用户态,返回结果。

 

以上是关于深入理解系统调用的主要内容,如果未能解决你的问题,请参考以下文章

深入理解系统调用

深入理解系统调用

深入理解计算机操作系统(笔记)

深入理解系统调用

深入理解系统调用

深入理解系统调用