深入理解Linux系统调用

一、实验内容

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

二、实验步骤

安装开发工具

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)
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 menuconfig
Settings --->
 [*] Build static binary (no shared libs)
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

内核启动成功

 

 

 99号系统调用

 

 查阅arch/x86/entry/syscalls/syscall_64.tbl可知,99号系统调用为sysinfo,其代码入口为__x64_sys_sysinfo。sysinfo()系统调用返回有关内存、交换区使用以及平均负载的统计信息,对应内核处理函数为:__x64_sys_sysinfo。

创建汇编代码进行系统调用

int main()
{
  asm volatile (
      "movq $0x63, %rax\n\t"
      "syscall\n\t"
      );
  return 0;
}

编译程序,并打包到根文件系统

gcc syscall99.c -o syscall99 --static
cp syscall99 rootfs/home/
find . -print0 | cpio --null -ov --format=newc | gzip -9 > rootfs.cpio.gz

3.跟踪和分析

启动qemu

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

运行gdb,并执行以下命令

file linux-5.4.34/vmlinux
target remote:1234
b start_kernel

在内核函数__x64_sys_sysinfo处打断点,执行程序,可以看到断点被成功捕捉到。

系统调用的实现,其核心代码如下

ENTRY(entry_SYSCALL_64)
    /* ... */
​
    swapgs
    
    /* ... *//* 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 *//* ... *//* IRQs are off. */
    movq    %rax, %rdi
    movq    %rsp, %rsi
    call    do_syscall_64       /* returns with IRQs disabled *//* ... */
    
    USERGS_SYSRET64
END(entry_SYSCALL_64)

现场的保存的主要工作是由swapgs指令完成的,现代处理器对系统调用的实现做了大量的优化。之后将部分寄存器暂存到栈内形成pt_regs结构体。接下来执行do_call_64函数。它根据ax寄存器中的系统调用号,在sys_call_table中定位对应的内核处理函数

#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);

 

posted @ 2020-05-27 17:25  蒋学成  阅读(243)  评论(0)    收藏  举报