跟踪分析Linux内核5.0系统调用处理过程
学号320原创作品转载请注明出处
本实验来源 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/ ”,博客内容的具体要求如下:
- 题目自拟,内容围绕系统调用进行;
- 博客中需要使用实验截图
- 博客内容中需要仔细分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程
- 总结部分需要阐明自己对系统调用工作机制的理解。
实验环境
- Ubuntu 18
- gcc 4.8
编译内核5.0
1.下载内核5.0内核代码,配置编译Linux内核,使之携带调试信息
mkdir LinuxKernel
- 下载内核源码:Linux内核5.0 source code
- 解压到LinuxKernel目录下,
cd linux-5.0.1
make menuconfig
,找到kernel hacking,->Compile-time checks and compiler options,选择 [*]compile the kernel with debug infomake
可能出现的问题
/bin/sh: 1: bison: not found
scripts/Makefile.lib:217: recipe for target ‘scripts/kconfig/zconf.tab.c’ failed
make[2]: *** [scripts/kconfig/zconf.tab.c] Error 127
Makefile:514: recipe for target ‘silentoldconfig’ failed
/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
Makefile:514: recipe for target ‘silentoldconfig’ failed
解决方法: 缺啥安啥就完事了sudo apt-get install bison
sudo apt-get install flex
2. 制作根文件系统
cd ~/LinuxKernel/
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
可能出现问题: -m32 64位gcc编译32位程序
/usr/bin/ld: 当搜索用于 /usr/lib/gcc/x86_64-linux-gnu/5/libgcc.a 时跳过不兼容的 -lgcc
/usr/bin/ld: 找不到 -lgcc
/usr/bin/ld: 当搜索用于 /usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so 时跳过不兼容的 -lgcc_s
/usr/bin/ld: 找不到 -lgcc_s
collect2: error: ld returned 1 exit status
解决方法: 根据自己gcc版本安装库 就完事了
sudo apt install gcc-4.8 gcc-4.8-multilib g++-4.8 g++-4.8-multilib
3. 启动MenuOSqemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
4. 跟踪调试内核启动
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr
注意:如果不加-append nokaslr
选项,start_kernel断点有可能断不住!!!加上就完事了。 见知乎
cd LinuxKernel/linux-5.0.1
gdb vmlinux
(gdb) target remote:1234
5. 分析
首先,几乎所有的内核模块均会在start_kernel进行初始化。在start_kernel中,会对各项硬件设备进行初始化,包括一些page_address、tick等等,直到最后需要执行的rest_init中,会开始让系统跑起来。
那rest_init这个过程中,会调用kernel_thread()来创建内核线程kernel_init,它创建用户的init进程,初始化内核,并设置成1号进程,这个进程会继续做相关的系统初始化。
然后,start_kernel会调用kernel_thread并创建kthreadd,负责管理内核中得所有线程,然后进程ID会被设置为2。
最后,会创建idle进程(0号进程),不能被调度,并利用循环来不断调号空闲的CPU时间片,并且从不返回。
参考自:pianogirl123
跟踪系统调用
-
增加系统调用
- 根据学号后两位20,在
/usr/include/asm/unistd_32.h
中可查得#define __NR_getpid 20
。 - getpid()没有参数,返回当前进程的ID。(有参数的系统调用参数传递方式见分析)
- 在test.c中增加函数,GetPID()。
- 重新编译制作
rootfs.img
- 根据学号后两位20,在
int GetPID(int argc, char *argv[])
{
pid_t pid; /* pid_t 需include <sys/types.h> 实际为int型*/
asm volatile(
"mov $0x14,%%eax\n\t" /* 用eax传系统调用号0x14 */
"int $0x80\n\t"
"mov %%eax,%0\n\t" /* eax存放返回值,送给%0,即pid变量 */
: "=m" (pid)
);
printf("pid: %d\n",pid);
return 0;
}
int main()
{
...
...
MenuConfig("getpid","Show current pid",GetPID);
}
-
跟踪调试系统调用
实验分析及总结
-
系统调用的触发及参数传递
- 当调用一个系统调用时,CPU从用户态切换到内核态并开始执行一个system_call和系统调用内核函数。在Linux中通过执行int 0x80来触发系统调用,内核为每个系统调用分配一个系统调用号,用户态进程必须明确指明系统调用号,需要使用EAX寄存器来传递。
- 系统调用可能需要参数,但是不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。
- 概括:EAX用来传递系统调用号,EBX、ECX、EDX、ESI、EDI、EBP用来传递参数,若参数较多,则把指向内存的指针存入寄存器。
-
系统调用流程
- 系统调用机制初始化:
\init\main.c start_kernel
\arch\x86\kernel\traps.ctrap_init();
#ifdef CONFIG_X86_32 //系统调用的中断向量和system_call汇编代码的入口 //一旦执行int 0x80,CPU就跳转到system_call这个位置来执行 set_system_trap_gate(SYSCALL_VECTOR, &system_call); set_bit(SYSCALL_VECTOR, used_vectors); #endif
- entry_32.S部分代码:
//这段代码就是系统调用处理的过程,其它的中断过程也是与此类似 //系统调用就是一个特殊的中断,也存在保护现场和回复现场 ENTRY(system_call)//这是0x80之后的下一条指令 RING0_INT_FRAME # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # save orig_eax SAVE_ALL//保护现场 GET_THREAD_INFO(%ebp) # system call tracing in operation / emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys syscall_call: // 调用了系统调用处理函数,实际的系统调用服务程序 call *sys_call_table(,%eax,4)//定义的系统调用的表,eax传递过来的就是系统调用号 syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work//退出之前,syscall_exit_work //进入到syscall_exit_work里边有一个进程调度时机 restore_all: //恢复现场 TRACE_IRQS_IRET restore_all_notrace://返回到用户态 #ifdef CONFIG_X86_ESPFIX32 movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS # Warning: PT_OLDSS(%esp) contains the wrong/random values if we # are returning to the kernel. # See comments in process.c:copy_thread() for details. movb PT_OLDSS(%esp), %ah movb PT_CS(%esp), %al andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax CFI_REMEMBER_STATE je ldt_ss # returning to user-space with LDT SS #endif restore_nocheck: RESTORE_REGS 4 # skip orig_eax/error_code irq_return: INTERRUPT_RETURN//iret(宏),系统调用过程到这里结束
- 系统调用机制初始化:
参考内容及此图片来源: 5系统调用system_call的处理过程