深入理解系统调用

一、实验要求

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

笔者学号最后2位为43,查阅得知为accept()函数。

 

二、实验环境搭建

1. 配置内核选项并编译

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
make -j4
qemu-system-x86_64 -kernel arch/x86/boot/bzImage

2. 构造根文件并提供可执行程序

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
make -j4 && 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脚本添加权限

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh
chmod +x init

打包根文件并测试

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

 

三、编写系统调用程序

 编写程序 accept_43.c,代码如下:

int main() {
    asm volatile(
        "movl $0x2B,%eax\n\t"
        "syscall\n\t"
    );
    printf("Hello accept");
    return 0;
}

用gcc静态编译,生成可执行文件,重新执行

gcc -o test_mincore test_mincore.c -static
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

启动qemu观察结果。

 

四、跟踪accept()内核处理过程

重启 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 __x64_sys_accept

命令中在accept系统调用处打上断点,在qemu中运行调用函数,程序会停在断点等待调试。

在gdb中,可以查看堆栈信息(bt命令),查看对应代码(l命令)。由下图可知,accept是通过syscall来进行的系统调用,系统调用的入口为 entry_SYSCALL_64 。

从终端信息可知:系统先调用entry_SYSCALL_64 (),其次是do_syscall_64(),最后才是__x64_sys_accept() 。

使用n命令进行单步执行,可以在gdb中查看保存现场信息的步骤:

(gdb) n
do_syscall_64 (nr=140730116323824, regs=0xffffc900001b7f58)
    at arch/x86/entry/common.c:300
300        syscall_return_slowpath(regs);
(gdb) n
301    }
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:184
184        movq    RCX(%rsp), %rcx
(gdb) n
185        movq    RIP(%rsp), %r11
(gdb) n
187        cmpq    %rcx, %r11    /* SYSRET requires RCX == RIP */
(gdb) n
188        jne    swapgs_restore_regs_and_return_to_usermode
(gdb) n
205        shl    $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
(gdb) n
206        sar    $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
(gdb) n
210        cmpq    %rcx, %r11
(gdb) n
211        jne    swapgs_restore_regs_and_return_to_usermode
(gdb) n
213        cmpq    $__USER_CS, CS(%rsp)        /* CS must match SYSRET */
(gdb) n
214        jne    swapgs_restore_regs_and_return_to_usermode
(gdb) n
216        movq    R11(%rsp), %r11
(gdb) n
217        cmpq    %r11, EFLAGS(%rsp)        /* R11 == RFLAGS */
(gdb) n
218        jne    swapgs_restore_regs_and_return_to_usermode
(gdb) n
238        testq    $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11
(gdb) n
239        jnz    swapgs_restore_regs_and_return_to_usermode
(gdb) n
243        cmpq    $__USER_DS, SS(%rsp)        /* SS must match SYSRET */
(gdb) n
244        jne    swapgs_restore_regs_and_return_to_usermode
(gdb) n
253        POP_REGS pop_rdi=0 skip_r11rcx=1
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:259
259        movq    %rsp, %rdi
(gdb) n
260        movq    PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:262
262        pushq    RSP-RDI(%rdi)    /* RSP */
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:263
263        pushq    (%rdi)        /* RDI */
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:271
271        SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
(gdb) n
273        popq    %rdi
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:274
274        popq    %rsp
(gdb) n
entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:275
275        USERGS_SYSRET64
(gdb) n
0x0000000000448b97 in ?? ()
(gdb) c
Continuing.
(gdb)

 

五、系统调用分析

sysentersyscall都借助CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,可断处理的思路,压栈关键寄存器、保存现场、恢复现场,最后系统调⽤返回。swapgs指令以类似快照的⽅式将保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复,加快了系统调⽤。进入内核态后,swapgs进行现场的保存,并完成寄存器的压栈。调用do_do_syscall_64,从中断向量表中获得系统调用号并对函数进行调用。 

accept() 函数模型:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  

sockfd:套接字的文件描述符,socket()系统调用返回的文件描述符fd

addr:指向存放地址信息的结构体的首地址

addrlen:存放地址信息的结构体的大小,其实也就是sizof(struct sockaddr)

可以看出,bind(),connect(),以及accept()的参数都是一致的。

内核实现:

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen)
{
    return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

最终调用的是SYSCALL_DEFINE4 。

函数执行完后回到do_do_syscall_64,进行syscall_return_slowpath(regs)的调用,prepare_exit_to_usermode(regs)准备返回用户态。再次回到entry_64.S中,进行最后的现场恢复和堆栈的切换。回到用户态程序,继续执行下一条指令,至此,系统调用过程结束。

 

posted @ 2020-05-27 15:57  真理长眠  阅读(208)  评论(0)    收藏  举报