作者:刘磊
参考代码出处:《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000
由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。
本次实验,主要目的是编译目前最近的Linux内核,并借助工具qemu进行系统调用的跟踪分析。
实验环境:Ubuntu 16虚拟机、VMware 14
1 编译内核
1.1 下载解压内核
目前比较新的内核版本号是5.x,本次实验我们采用5.0.1的内核进行(下载地址)。
下载完内核的压缩包后,我们在根据以下命令解压到当前目录。
1 # tar -xvf linux-5.0.1.tar
1.2 配置编译选项并编译内核
首先我们进入到解压的linux-5.0.1目录下,敲入以下命令对其编译信息进行配置。
1 # make defconfig 2 # make i386_defconfig
输入以下命令,开启文本菜单选项,并找到kernel hacking -> Compile-time checks and compiler options,勾选 [*]compile the kernel with debug info
1 # make menuconfig
在生成配置文件后,我们开始进行编译。
1 # make 或 make -j* # *为cpu核⼼心数
编译成功后会看到如下图所示的信息:
注:在编译过程中会遇到种种问题,根据错误命令的提示依次安装相应的软件包即可解决问题。
2 制作根文件系统并启动qemu
2.1 构造MenuOS
下载menu文件,并进入该目录下:
1 # git clone https://github.com/mengning/menu.git 2 # cd menu
由于我的实验环境是64位的,编译32的文件需要安装以下软件包:
1 # sudo apt-get install libc6-dev-i386
进行menu文件编译:
1 # make rootfs
制作rootfs.img:
1 # cd ../rootfs 2 # cp ../menu/init ./ 3 # find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
2.3 启动qemu
通过以下命令启动qemu:
1 # qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
在qemu正常启动后会另外弹出一个窗口,如下图所示:
3 跟踪系统调用
由于我的学号后三位是242,我就以42号系统调用为例进行跟踪。
在内核源文件中42号系统调用位:
1 #define __NR_pipe 42
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次都需要提供两个文件描述符来操作管道。其中一个对管道进行写操作,另一个对管道进行读操作。对管道的读写与一般的IO系统函数一致,使用write()函数写入数据,使用read()读出数据。
3.1 在test.c下添加系统调用
在menu目录下的test.c中加入如下函数:
1 int Pipe(int argc, char *argv[]) 2 { 3 int result=-1; 4 int fd[2]; 5 result = pipe(fd); 6 if(-1 == result) 7 { 8 printf("fail to create pipe\n"); 9 } 10 else 11 { 12 printf("successfully create pipe\n"); 13 } 14 return 0; 15 }
在此文件中的main函数里进行登记,即加入以下代码,并重新编译制作rootfs.img文件:
1 MenuConfig("pipe","Create Pipe",Pipe);
3.2 在gbd下跟踪系统调用
打开两个中断,在其中一个输入:
1 # qemu -kernel linux-5.0/arch/x86/boot/bzImage -initrd rootfs.img -s -S -append nokaslr
在运行如上命令后,会弹出一个处于stop状态的qemu窗口:
再在另外一个终端下,进入linux-5.0.1目录,并输入以下命令进入gdb的脚本模式:
1 # sudo gdb
在gdb脚本模式下,输入以下内容:
1 # file vmlinux 2 # target remote:1234 3 # b sys_pipe 4 # c
在运行完以上命令后,处于stop状态的qemu终端就会开始运行,然后在其脚本模式下,输入pipe:
在gdb脚本模式下依次输入ni,disass,info r三条命令,逐步跟踪系统调用,可以得到如下的显示:
从图上可以看出,系统在对原始状态进行压栈操作,以保护原函数的”现场“。在程序运行到最后可以发现一些出栈操作,来恢复现场:
4 总结
系统调用实际上就是一个从用户态到管态再恢复到用户态的过程。在第一次切换时,执行system_call和系统调用处理函数。在CPU中使用eax寄存器来传递系统调用号,且通过寄存器的方式来传递参数。若参数过多,可以使用间接寻址的方式来获取对应数据在内存中的偏移地址。