举例跟踪分析Linux内核5.0系统调用处理过程

原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
学号 181
本次实验主要内容如下:

  1. 编译内核5.0
  2. 选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析

一、编译内核5.0
方法 下载内核、解压、make
内核下载地址https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
使用代码如下

cd ~/Lkernel/
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
xz -d linux-5.0.1.tar.xz
tar -xvf linux-5.0.1.tar
cd linux-5.0.1
make i386_defconfig
make -j

遇到的问题如下

/bin/sh: 1: flex: not found 
scripts/Makefile.lib:202: recipe for target 'scripts/kconfig/zconf.lex.c' failed 
make[2]: *** [scripts/kconfig/zconf.lex.c] Error 127

解决方法

sudo apt-get install flex

这个时候又会出现

E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源临时不可用)
E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?

解决方法

sudo rm /var/lib/dpkg/lock

sudo rm /var/cache/apt/archive/lock

这下就可以安心make了,直到结束。
make成功
将git上的一个根文件系统下载下来打包成img文件

cd ~/Lkernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git  
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread

这里出现问题
在这里插入图片描述
解决方法如图中所示 不用lpthread
缺少文件 补全即可

紧接着执行下面代码制作img文件

cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

启动Menu os

cd ~/Lkernel/
qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img

此时报错
在这里插入图片描述
安装 qemu 且使用命令qemu-system-i386代替qemu
之后再重新编译一下内核
如图
在这里插入图片描述在这里插入图片描述
保存之后make一次就可以使之携带调试信息了,然后就是利用gdb进行跟踪调试了。

现在一个shell里面启动menuos:

qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -s -S

# -S freeze CPU at startup (use ’c’ to start execution)
# -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项

在这里插入图片描述
然后开启另外一个窗口,注意要进入Lkernel文件夹

gdb
(gdb)file linux-5.0.1/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

在这里插入图片描述在这里插入图片描述
到这里linux5.0内核就已经编译成功!

二、选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析

我的学号后两位是81 ——setgroups

名称:getgroups/setgroups/initgroups
功能:get group file entry
头文件:#include <grp.h> #include <unistd.h>
函数原形:int getgroups(int gidsetsize,gid_t grouplist[]);int setgroups(int ngroups,const gid_t grouplist[]);int initgrops(const char *username,gid_t basegid);

在test.c 中加入

int setgroupstest(int argc, char *argv[])
{
    gid_t list[100];
    int x = getgroups(0,list);
    int t;
    getgroups(x,list);
    t = setgroups(x,list);
    printf("%d\n",t);
    return 0;
}



int main()
{
    PrintMenuOS();
    SetPrompt("MenuOS>>");
    MenuConfig("version","MenuOS V1.0(Based on Linux 5.0.1)",NULL);
    MenuConfig("quit","Quit from MenuOS",Quit);
    MenuConfig("time","Show System Time",Time);
    MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
    MenuConfig("setgroupstest","Show setgroupstest",setgroupstest);
    ExecuteMenu();
}

重新编译后
在这里插入图片描述
输出为0 setgroups调用成功
调试如下
在这里插入图片描述
但是效果不太明显,于是使用相近的80号——getgroups再做一次

先写一个.c文件 181.c

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

 
int main()

{

		gid_t list[500];
		int x,i;
		x = getgroups(0,list);
		getgroups(x,list);
		for(i=0;i<x;i++)
			printf("%d:%d\n",i,list[i]);
}

将181.c文件编译运行并查看输出结果
在这里插入图片描述
使用gdb断点,分析系统调用
在这里插入图片描述
1.在getgroups处设置断点,运行到断点处
111
2. 使用ni命令,对指令逐条运行,并且通过info r 查看值
在这里插入图片描述
前面的清理寄存器的值此处先不讨论,直接一直ni运行到传递系统调用号处(箭头所指的为下一条将要运行的指令,所以此时系统调用号还未传入)。
在这里插入图片描述
我们可以发现此时eax寄存器里的值还未改变

3.当箭头指向call时
eax的值发生了变化
在这里插入图片描述
此时eax的值发生了变化,变成了0xcd——205(十进制),但是和我们用的系统调用号不一样,再次查表发现205号是getgroups32,在看看上面的代码发现gcc -m32是用的32 位,所以默认将getgroups变成了getgroups32。

4 再ni 一下,此时该系统调用的实际工作已经完成了,结果保存在eax中
在这里插入图片描述
5、一直ni,直到从系统调用里回到main中。可以看到,系统调用的结果确实由eax传出,然后交给printf函数打印。
在这里插入图片描述
6、系统调用、保护现场与恢复现场、系统调用号及参数传递过程如下图所示
在这里插入图片描述
我们使用int 0x80触发中断,然后中断处理程序保存现场,我们的进程陷入内核态。
系统调用的工作机制是:当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,由API、中断向量和中断处理程序协调完成。
而在系统调用的过程中,系统调用号使用eax寄存器传递参数。

三、总结

系统调用的三层皮(参考):

API、中断向量对应的system_call、中断服务程序system_service

当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常。内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数,使用eax寄存器

系统调用的参数传递方法:

system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
进入sys_call之后,立即将eax的值压入内核堆栈

寄存器在传递参数时的限制:
1)每个参数的长度不能超过寄存器的长度,即32位。
2)在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp),若超过6个,就将某一个寄存器作为指针,指向一块内存,在进入内核态之后,可以访问所有的地址空间,通过内存来传递参数。

从实验中我们可以发现
通过eax传递系统调用号,然后由system_call交给system_service完成工作;system_service完成工作后,结果又由eax传递给用户态堆栈。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值