学号后三位:069
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
一、实验内容
主要分为两部分:
- 升级内核为5.0
- 分析系统调用的处理过程
二、升级内核
升级前版本为:4.19.4
下载内核源码:https://github.com/mengning/linux/tree/v5.0
将文件解压,进入Linux-5.0目录下,输入如下指令:
sudo apt install libssl-dev(可能还需安装其他依赖)
输入命令make mrproper和sudo make menuconfig配置内核选项,出现内核配置图形界面:
可根据需求裁剪相关模块,保存后退出;编译:
sudo make bzImage -j8
进入Linux-5.0/arch/x86/boot目录下,可看到编译好的文件:
make modules_install #把编译出的内核模块复制到/lib/modules/${KERNEL_VERSION}
make install 安装内核:
内核升级完成,修改grub,将新内核作为默认启动项:
sudo ln -s System.map-5.0.2 System.map
生成文件系统:
sudo mkinitramfs -o /boot/initrd.img-5.0.2
更改grub:
sudo gedit /etc/default/grub
作出如下修改:
保存后退出, sudo update-grub.
重启(按住shift),可选择新内核——5.0.2版本(官网最新版)
至此,内核升级部分完成。
三、ASM和C代码使用系统调用
根据要求,我需要分析的系统调用为第69号系统调用:ssetmask
首先配置环境:
cd LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
修改test.c,加入ssetmask函数代码(C版本和asm版本):
增加命令:
make rootfs
下面以调试方式启动qemu:
qemu -kernel linux-5.0.2/arch/x86/boot/bzImage -initrd rootfs.img -s -S
再打开一个终端,进行gdb调试:(中间,实验楼奔溃了,又重新来了一次)
file linux-5.0.2/vmlinux
target remote:1234
分析system_call()函数:
首先把系统调用号和这个异常处理程序可以用到的所有CPU寄存器保存到相应的栈中,不包括由控制单元已自动保存的eflags、cs、eip、ss和esp寄存器。
pushl %eax
SAVE_ALL
movl $0xffffe000, %ebx /* or 0xfffff000 for 4-KB stacks */
andl %esp, %ebx
接下来检查thread_info结构flag字段的TIF_SYSCALL_TRACE和TIF_SYSCALL_AUDIT标志之一是否被置为1,即检查是否有某一调试程序正在跟踪执行程序对系统调用的调用。如果系统调用号无效则把-ENOSYS值存放在栈中曾保存eax寄存器的单元中,当进程恢复在用户态的执行时会在eax中得到一个负的返回码。
cmpl $NR_syscalls, %eax
jb nobadsys
movl $(-ENOSYS), 24(%esp)
jmp resume_userspaces
最后调用与eax中所包含的系统调用号对应的特定服务例程。
call *sys_call_table(0, %eax, 4)
四、分析系统调用
保护现场与恢复现场
当调用系统调用时,会陷入内核态,此时就涉及了上下文切换问题。当用户切换到内核态时,就要把用户态寄存器上下文保存起来,同时把内核态寄存器的值放到当前CPU中。int指令触发中断机制会在堆栈上保存一些寄存器的值,会保存用户态栈顶地址、当时的状态字、当时的CS:EIP的值。并且CS:EIP寄存器的值会指向中断处理程序的入口,对于系统调用来讲是指向system_call等等如此事务。
最后的iret与中断信号发生时的动作相反,也就是恢复现场的操作。
系统调用号及参数传递过程
Linux操作系统有200多个系统调用,内核就是通过系统调用号来给这些systemcall来编号,加以区分,以明确知道用户想要执行的是哪个系统调用。用户在使用系统调用时需要利用EAX寄存器传递一个名为系统调用号的参数。
除了系统调用号外,系统调用也可能需要传递参数,普通函数调用时通过将参数入栈的方式传递的。而系统调用,涉及从用户态到内核态,堆栈不同,无法用此方式传递参数。所以,系统调用的参数传递方式是通过寄存器来传递的。主要有EBX\ECX\EDX\ESI\EDI\EBP,六个寄存器。如果所需传递的参数超过6个,则可用寄存器存储内存地址,用内存来存储更多参数。
五、总结
系统调用工作机制
如下图所示:
User Mode表示用户态,Kernel Mode表示内核态。
xyz()史一个API函数,封装了一个系统调用,会触发int 0x80中断,对应system_call内核代码的起点,也就是中断服务历程的入口,内部有sys_xyz ()处理函数,执行完后会ret_from_sys_call,这里是进程调度的时机。总的来说,系统调用的三层机制为:
- xyz()
- system_call
- sys_xyz()