跟踪分析Linux内核5.0系统调用处理过程
学号129,原创作品转载请注明出处
实验内容来源 : https://github.com/mengning/linuxkernel/
实验要求
举例跟踪分析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/ ”,博客内容的具体要求如下:
- 题目自拟,内容围绕系统调用进行
- 博客中需要使用实验截图
- 博客内容中需要仔细分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程
- 总结部分需要阐明自己对系统调用工作机制的理解
- 博客URL提交到https://github.com/mengning/linuxkernel/issues/10
实验环境
- Ubuntu 18.04
- gcc 7.3
实验过程
一.下载Linux内核5.0.1并配置编译
新建一个LinuxKernel文件夹,下载linux5.0.1内核源码并解压编译
注意要下载安装相应的内核编译工具
mkdir LinuxKernel
cd LinuxKernel/
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
sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev
make menuconfig
在编译前选择配置内核使其具备调试功能
->kernel hacking
->Compile-time checks and compiler options
->[*]Compile the kernel with debug info
然后
make -j8
8线程加速编译
二.制作根文件系统
从孟宁老师提供的github上下载menuOS制作根文件系统
cd ~/LinuxKernel/
mkdir rootfs
git clone https://gitbug.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 > …/roootfs.img #手一抖多打了个o
三.启动MenuOS
下载安装好qemu以后启动menuOS
qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd roootfs.img
效果图如下:
四.修改menuOS使用系统调用
根据学号129选择029号系统调用,查系统调用表可知29号系统调用为pause
在test.c
中增加函数PauseTestAsm
和PauseTest
并在main函数中注册
void sig_handler(int num)
{
printf("receive the signal %d.\n", num);
alarm(5);
}
int PauseTest(int argc, char *argv[])
{
signal(SIGALRM, sig_handler);
alarm(2);
int j = 5;
while(j--){
pause();
printf("pause finished\n");
}
return 0;
}
int PauseTestAsm(int argc, char *argv[])
{
int a;
signal(SIGALRM, sig_handler);
alarm(2);
int j=5;
while(j--)
{
asm volatile(
"mov $0x1d,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=m"(a)
);
printf("pauseAsm finished\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",PauseTest);
MenuConfig("pauseasm","Show pause(asm)",PauseTestAsm);
ExecuteMenu();
}
重新编译rootfs.img
测试效果如下:可以看到两种编写方式使用系统调用下都输出了五次,符合预期
五.跟踪调用
设置断点在sys_pause
n
逐步运行并使用info r
查看寄存器数据,disass
显示当前函数的汇编形式随着我们一步步运行,QEMU界面也在发生输出,pause暂停了进程,然后由于alarm发出的SIGALRM,重新唤醒仅存继续执行
我们仔细分析每一步的运行结果可以发现程序执行和函数调用的具体流程
可以看到在使用了int0x80中断后系统调用entry_INT80_32指令,该指令位于arch/x86/entry_32.s中
下面简单分析以下该指令
ENTRY(entry_INT80_32)
ASM_CLAC
pushl %eax /* 保存系统调用号*/
SAVE_ALL pt_regs_ax=$-ENOSYS switch_stacks=1 /* 保护现场,将所有使用的CPU寄存器存在栈中 */
/*
* User mode is traced as though IRQs are on, and the interrupt gate
* turned them off.
*/
TRACE_IRQS_OFF
movl %esp, %eax
call do_int80_syscall_32 /*根据系统调用号调用处理函数*/
.Lsyscall_32_done:
STACKLEAK_ERASE
restore_all: /*返回到用户态,恢复现场,跳到堆栈入口*/
TRACE_IRQS_IRET
SWITCH_TO_ENTRY_STACK
.Lrestore_all_notrace:
CHECK_AND_APPLY_ESPFIX
.Lrestore_nocheck:
/* Switch back to user CR3 */
SWITCH_TO_USER_CR3 scratch_reg=%eax /*将系统调用号存在CR3寄存器中*/
BUG_IF_WRONG_CR3
/* Restore user state */
RESTORE_REGS pop=4 # skip orig_eax/error_code /*逐个从堆栈中弹出数值到寄存器中,恢复现场*/
.Lirq_return:
/*
* ARCH_HAS_MEMBARRIER_SYNC_CORE rely on IRET core serialization
* when returning from IPI handler and when returning from
* scheduler to user-space.
*/
INTERRUPT_RETURN /*中断返回的宏,系统调用到这里结束*/
实验总结
- 系统调用就是一种用户态到内核态最后再到用户态的一种过程
- 调用一个系统调用时,执行int 0x80指令触发系统调用后系统从用户态进入内核态,system_call()函数通过系统调用号查找系统调用表来查找具体系统调用服务进程
- 内核态通过汇编代码直接操作内存和寄存器,由于涉及上下文的切换问题,在进入中断程序前,我们需要利用堆栈保护现场,完成了系统调用之后,从堆栈中获得数据恢复现场
- 用户态与内核态的栈堆不同,所以它们只能使用寄存器来传值,其中eax主要是存放系统调用号以及调用返回值,其他的eax、ebx、ecx、edx、esi和edi寄存器主要存放系统调用的参数
- 除了系统调用号(eax),参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp),若超过6个,可以把某一个寄存器作为指针指向一块内存,在进入内核态后访问该地址空间来传递参数