学号131原创作品转载请注明出处
本实验来源 https://github.com/mengning/linuxkernel/
实验环境
Ubuntu 18.04 虚拟机
VMware Workstation Pro 15.0.2 for Windows
实验要求
举例跟踪分析Linux内核5.0系统调用处理过程
- 编译内核5.0
- qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
- 选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析
- https://github.com/mengning/menu
- 给出相关关键源代码及实验截图,撰写一篇博客(署真实姓名或学号最后3位编号),并在博客文章中注明“原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ ”,博客内容的具体要求如下:
- 题目自拟,内容围绕系统调用进行;
- 博客中需要使用实验截图
- 博客内容中需要仔细分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程
- 总结部分需要阐明自己对系统调用工作机制的理解。
实验步骤
编译内核5.0
- 下载5.0.1版本的linux内核,其下载地址为
并进行解压tar -xvf linux-5.0.1.tarhttps://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
- cd linux-5.0.1
- make menuconfig
为了跟踪内核代码的执行过程,需要先在运行的内核中加入调试信息,这样我们就能用GDB在我们感兴趣的代码行上设置断点,观察程序的执行过程了。添加方法是在linux源代码目录下用make menuconfig命令选中kernel hacking项目的compile the kernel with debug info项
4. make(编译时间较久请耐心等待,推荐使用命令make -j8加速编译)
可能出现的问题
1.
Make menuconfig时出现提示缺少ncurses library,使用命令:
sudo apt-get install libncurses5-dev libncursesw5-dev
进行相应的安装
2.
提示flex与Bison未找到,利用命令
sudo apt-get install flex bison
进行安装
缺少的这两个文件分别为:flex(词法分析器)与bison(语法分析器)
3.
此状况出现在虚拟机显示的页面太小上,可以将虚拟机页面扩大
根文件系统的创建
制作img文件
cd ~/Documents/linux-5.0.1
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
启动Menu os
cd ~/Documents/linux-5.0.1
qemu-system-i386 -kernel arch/x86/boot/bzImage -initrd rootfs.img
此时出现问题
网上提的方法均使用,检查已经打开了主板的CPU虚拟化支持开关,虚拟机中ubuntu已设置为64位。
后经老师提醒是qemu是32位的,无法编译64位的内核。于是将代码进行了修改
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd rootfs.img
该代码成功执行
跟踪调试内核启动
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s
如果出现了start_kernel断点断不住的情况,并且确认在kernel-hack选项勾了[*] compile the kernel with debug info后,可以考虑用hbreak (GDB的硬件断点) 替代普通断点,在这之后就可正常使用break。
这个错误的产生可能是gdb关于虚拟化的一个bug。
打开一个新终端
gdb
(gdb)file linux-5.0.1/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
到这里linux5.0内核就已经编译成功!
选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析
在文件/usr/include/asm/unistd_32.h 中查询得知学号131对应的调用为#define __NR_stty 31。Stty命令用于检查和修改当前注册的终端的通信参数。
但是经过google搜索后,唯一能搜索找到的stty函数的定义如下
而sgttyb结构体的定义则更为复杂。于是决定选取序号为33的中断函数access进行相应的分析。
调用access函数的源文件代码如下:
#include"stdio.h"
#include"unistd.h"
#include "string.h"
#define fileNAME1 "test"
#define fileNAME2 "./liang"
int main(void)
{
char name[BUFSIZ];//文件名字
int flag = 1;//退出标志,0 exit
printf("\t\t\t程序开始\n");
printf("请输入要检查的文件(可包含路径,EOF退出):");
scanf("%s",name);
if( strcmp(name,"EOF") == 0 ){
flag = 0;
}
while(flag){
if(access(name,F_OK)==0){
printf("文件存在\n");
if(access(name,R_OK|W_OK)==0){
printf("文件可读可写\n");
}else
printf("文件不可读或不可写\n");
if(access(name,X_OK)==0){
printf("文件可执行\n");
}else
printf("文件不可执行\n");
}else
printf("文件不存\n");
printf("\n请输入要检查的文件(可包含路径,EOF退出):");
scanf("%s",name);
if( strcmp(name,"EOF") == 0 ){
flag = 0;
}
}//while
printf("\t\t\t程序结束\n");
return 0;
}
利用代码
gcc -g test.c -o test -m32
完成汇编操作,接下来通过gdb程序完成对于代码的分析过程。
gdb -q
file test
list
1.在access处设置断点,并通过命令r(run的简写)运行至断点处
2.通过命令disass观察汇编代码运行至何处(现场的保护与系统的调用)
push %ebx
首先是利用push %ebx的操作,将现场进行相应的保护
call *%gs:0x10
接下来观察到这样一行代码系统调用从这里进入,会调用到int80,而由于是动态链接,于是采用"call *%gs:0x10"指令
3.运用 i r命令观察此时寄存器的值
这里详细介绍下系统调用中寄存器的作用
系统调用号可以在文件unistd_32.h中查询得到
/usr/include/i386-linux-gnu/asm/unistd_32.h
如:
#define __NR_getuid 24
#define __NR_getuid32 199
参数:
EAX指定要调用的函数(系统调用号)
EBX传递函数的第一个参数
ECX传递函数的第二个参数
EDX传递函数的第三个参数
ESI
EDI
EBP
最后调用后的返回值EAX
4.通过ni命令(汇编级别的指令,用于汇编中的单步运行操作),运行至运行至call指令处
观察此时的寄存器的值,发现eax的值变为了33,正是我们所使用的中断函数access
5.继续ni后,观察寄存器的值,发生了变化,原代码中的判断为if(access(name,F_OK)==0),而eax的值和原函数的返回值是相同的,证明了前文的说法。
6.现场的恢复
不断运行ni指令后发现,最终回到了系统调用的main函数之中。
最后整个系统的中断调用过程可以用下面这个流程图来表示。
需要注意的是,首先确定调用哪个中断函数(即调用中断号)与调用完中断函数后返回的值均是由eax寄存器传递的,其次系统调用可能需要参数,但是不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。
实验总结
- 系统中断的调用有三层分别是API、system_call、system_service。
- 系统调用是一个用户态->内核态->用户态的过程
- Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。