跟踪分析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 info
  • make

在这里插入图片描述
在这里插入图片描述
可能出现的问题

/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. 启动MenuOS
qemu-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
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
      trap_init();
      
      \arch\x86\kernel\traps.c
      #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
      
    系统调用的工作机制,一旦start_kernel初始化好之后,在代码中一旦出现int 0x80的指令,就会立即跳转到system_call这个位置。
    • 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的处理过程


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值