举例跟踪分析Linux内核5.0系统调用处理过程
学号后三位029
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
1.实验目标
1.编译内核5.0
2.选择系统调用进行跟踪分析
2.实验环境
VMware Workstation Pro
Ubuntu 18.04 虚拟机
3.编译内核5.0
- 下载linux5.0
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.tar.xz//使用wget命令下载linux5.0
- 解压并编译
xz -d linux-5.0.1.tar.xz
tar -xvf linux-5.0.1.tar
cd linux-5.0.1
make i386_defconfig
这里出现了问题
于是安装flex与bison
sudo apt-get install flex
sudo apt-get install bison
编译文件
make i386_defconfig
make
出现的问题
这里好像是有一个libssl-dev没有安装
那么来安装
sudo apt-get install libssl-dev
- 制作根文件系统
sudo apt-get install gcc-multilib//这一步是必要的,不然会报错,需要安装gcc编译器
cd ..
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc | gzip -9 > ../rootfs.img
- 编译
qemu -kernel linux-5.0/arch/x86/boot/bzImage -initrd rootfs.img -s -S -append nokaslr
- 进行内核跟踪调试,需要重新编译Linux内核
make menuconfig
这里如果窗口太小可能会报错
qemu -kernel linux-5.0/arch/x86/boot/bzImage -initrd rootfs.img -s -S -append nokaslr
跟踪调试内核启动
使用gdb打开调试工具
gdb
(gdb)file linux-5.0/vmlinux
(gdb)target remote:1234
4.选择系统调用进行跟踪分析
我的学号后三位是029 所以找第29位系统调用号进行使用
函数说明:pause()会令目前的进程暂停(进入睡眠状态),直至信号(signal)所中断。
返回值:只返回-1
于是修改test.c中的代码,添加一个调用pause的代码
#include <signal.h>
#include <unistd.h>
void sigl_name(int num)
{
printf("receive the signal %d.\n", num);
alarm(2);
}
int Pause_test(int argc, char *argv[])
{
signal(SIGALRM, sigl_name);
alarm(2);
while(1){
pause();
printf("pause is over.\n");
}
return 0;
}
int main()
{
PrintMenuOS();
SetPrompt("MenuOS>>");
MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
MenuConfig("quit","Quit from MenuOS",Quit);
MenuConfig("time","Show System Time",Time);
MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
MenuConfig("pause","Show pause",Pause_test);
ExecuteMenu();
}
为了方便观看断点设置,循环了代码,当程序运行时,pause会使当前的进程进入睡眠状态,直到我们由定时器alarm向该 signal发送SIGALRM信号,进程才会被唤醒,并处理信号,处理完信号后pause函数才返回,并继续运行该程序,具体情况如下图所示:
用b sys_pause设置断点,c继续执行
使用n不断进行一步一步进行调试,发现在QEMU界面发生了输出,pause暂停了进程,然后由于alarm发送的发送的SIGALRM信号给signal让程序重新执行
有一步步执行下来可以看出具体流程sys_pause->schedule->sys_pause->do_fast_syscall_32
->entry_sysenter_32->entry_int80_32
再细看,分析sys_pause的具体运行,使用disass与info r
可以看出在每一次从用户态进入内核态,都会有上下文切换的问题,那么就要把用户态寄存器上下文保存起来,从运行结果disass显示出来的可以看出,再每次的进行中都会先进行压栈,再结束过程中再进行出栈,以此来保护原函数的运行现场,再最后寄存器集体出栈时恢复。也就是说保护现场就是进入中断服务程序之前,保存使用的寄存器值,也就是说进行压栈操作; 恢复现场就是进入中断服务程序后,恢复之前保存的寄存器数据,也就是进行出栈操作
系统调用分析:
系统中的调用,就是通过系统调用号来给这些system_call来编号,不同功能给予不同的功能号,如这次使用的就是29号 Pause功能,通过这样让系统明确知道用户想要执行的是哪个系统调用。而这之间的传递主要是通过eax寄存器来传递。
除了系统调用号外,系统调用也可能需要传递参数,系统调用由于是从用户态到内核态,两者的栈堆不一样,所以他们之间只能由寄存器来进行传递值,也就是ebx、ecx、edx、esi和edi寄存器中。
5.总结
系统调用主要就是一种用户态到内核态最后再到用户态的一种过程,用户态可以说成间接操作内存的程序,而内核态也就是通过汇编代码直接操作内存,而他们再转换过程中,由于涉及上下文的切换问题,所以需要对内容进行保护,所以再进入中断程序前,将内核态寄存器的值放入栈中,在程序结束后再进行出栈。
而且由于用户态与内核态的栈堆不同,所以他们之间只能使用寄存器来传递值,也就是eax,ebx、ecx、edx、esi和edi寄存器中,其中eax主要是存放系统调用号,以及调用的返回值,而其他的五个寄存器主要是存放系统调用的参数。
系统调用号是内核为每个系统调用分配的相当于id的事务,用户态进程必须明确的指定使用的系统调用号,来使内核确定使用的服务。