深入理解系统调用
一、实验要求:
- 本人学号后两位为17,则寻找系统调用号为17的系统调用
-
通过汇编指令触发该系统调用
-
通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、环境搭建:
2.1按照实验一中的步骤来进行初始环境搭建
sudo apt install build-essential sudo apt 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
2.2配置内核选项
make defconfig make menuconfig
进入 Kernel hacking的Compile-time checks and compiler options:


进入Processor type and features:

2.3编译内核:
make -j$(nproc) qemu-system-x86_64 -kernel arch/x86/boot/bzImage

由于没有⽂件系统最终会kernel panic,这属于正常现象。
2.4利用busybox制作根文件系统
下载并制作根文件系统
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 -j$(nproc) && make install
在配置选项时进入Settings,选择Build static binary (no shared libs)

制作内存根文件系统镜像:
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脚本,并修改权限:
vi init #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome WangbaOS!" cd home /bin/sh 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

三、汇编指令触发该系统调用
因为学号后两位是82,在/home/zd/linux-5.4.34/arch/x86/entry/syscalls目录下查阅syscall_64.tbl文件,82号系统调用为__x64_sys_gettimeofday,对应的函数是gettimeofday。
在网上查询关于gettimeofday函数的使用方法,编写测试函数,使用汇编指令触发该系统调用。
(1)在C语言中可以使用函数gettimeofday()函数来得到时间。它的精度可以达到微秒
(2)函数原型
#include<sys/time.h>
int gettimeofday(struct timeval*tv,struct timezone *tz )
(3)返回值:gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中。
C代码调用
#include<stdio.h>
#include<sys/time.h>
int main()
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv,&tz);
printf("tv_sec:%ld\n",tv.tv_sec);
printf("tv_usec:%ld\n",tv.tv_usec);
printf("tz_minuteswest:%d\n",tz.tz_minuteswest);
printf("tz_dsttime:%d\n",tz.tz_dsttime);
return 0;
}
内嵌汇编调用
#include<stdio.h>
#include<sys/time.h>
int main()
{
struct timeval tv;
struct timezone tz;
int flag;
asm volatile(
"movl %1, %%edi\n\t"
"movl %2, %%esi\n\t"
"movl $0x4E, %%eax\n\t"
"syscall\n\t"
"movl %%eax, %0\n\t"
:"=m"(flag)
:"b"(&tv),"c"(&tz)
);
printf("tv_sec:%ld\n",tv.tv_sec);
printf("tv_usec:%ld\n",tv.tv_usec);
printf("tz_minuteswest:%d\n",tz.tz_minuteswest);
printf("tz_dsttime:%d\n",tz.tz_dsttime);
return 0;
}
运行结果如下

四、使用gdb跟踪该系统调用的内核处理过程
1、启动qemu:
1 qemu-system-x86_64 -kernel ~/lab2-linux-5.4.34/arch/x86/boot/bzImage -initrd ~/rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
此时终端界面会停止

2开启一个新的终端,设置断点
1 cd lab2-linux-5.4.34 2 gdb vmlinux 3 target remote:1234 4 b __x64_sys_socketpair 5 c

在之前停止的终端下执行socketpair

在设置的断点处停下

接下来进行单步调试:可以看到有系统调用entry_SYSCALL_64()


五、总结
从这次实验中,学习了通过汇编指令来触发系统调用,并用gdb调试跟踪了整个系统调用的过程,让我对linux内核的系统调用过程有了深刻而且系统的理解。下面是对整个过程的总结。
从系统调用的整个过程来看,主要有以下几个阶段:
(1)用户态程序,发生syscall,触发系统调用;
(2)进入内核态,完成内核初始化后,调用entry_SYSCALL_64 ()
(3)完成现场的保存,将关键寄存器压栈,并从CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,更改CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝ ,调用do_syscall_64()
(4)do_syscall_64()函数中得到系统调用号,调用相关的函数gettimeofday()
(5)调用结束后,保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复
(6)系统调用返回,回到用户态程序


浙公网安备 33010602011771号