分析system

分析system,第1张

分析system

分析system_call中断处理过程
  • 前言
  • 给MenuOS增加命令
  • 使用gdb跟踪调用内核函数sys_getpid
    • gdb调试
    • 查找sys_getpid和system_call代码
    • 画出流程图
  • 总结

前言

之前我们使用了两种方法进行了系统调用,一种是使用库函数(调用API),第二种是C语言嵌入内嵌汇编。详见博客

给MenuOS增加命令

首先可以删除实验楼自带的系统,然后再git clone一个新的,这样减少了编译过程,一个make搞定

我们进入menu中的test.c,新增我上一次博客中的两个功能的代码(一个API,一个内嵌汇编)-注意要加头文件**#include**

之前我们编译这个内核需要的命令是

gcc -pthread -o init linktable.c menu.c test.c -m32 -static
mv init ../rootfs
cd ../rootfs
find . | cpio -o -Hnewc | gzip -9 > ../rootfs.img
cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

这四行命令意思是将三个C语言一起编译成可执行文件Init,将Init移动到rootfs(没有就自己建)文件夹下,进入rootfs文件夹,第四行是将这个文件夹下的init打包成rootfs.img镜像。最后一行使用qemu虚拟机模拟rootfs.img镜像
但是现在只需要在menu文件夹下执行如下命令即可

make rootfs

可以使用函数(pid为1-对应的是init)

使用gdb跟踪调用内核函数sys_getpid gdb调试

首先返回父目录,再启动镜像,但一定要把镜像冻结起来,命令如下

cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

这里-s是在1234端口创建一个gdb-server,-S是将其冻结起来,方便gdb调试

再打开一个窗口,打开gdb,然后输入

file linux-3.18.6/vmlinux
target remote:1234

将内核加载进来

然后可以设置两个断点

b start_kernel
b sys_getpid

查找sys_getpid和system_call代码

可以看到这个sys_getpid函数在kernel/sys.c里,查找代码如下

SYSCALL_DEFINE0(getpid)
{
	return task_tgid_vnr(current);
}


SYSCALL_DEFINE0(gettid)
{
	return task_pid_vnr(current);
}


SYSCALL_DEFINE0(getppid)
{
	int pid;

	rcu_read_lock();
	pid = task_tgid_vnr(rcu_dereference(current->real_parent));
	rcu_read_unlock();

	return pid;
}

SYSCALL_DEFINE0(getuid)
{
	
	return from_kuid_munged(current_user_ns(), current_uid());
}

SYSCALL_DEFINE0(geteuid)
{
	
	return from_kuid_munged(current_user_ns(), current_euid());
}

SYSCALL_DEFINE0(getgid)
{
	
	return from_kgid_munged(current_user_ns(), current_gid());
}

SYSCALL_DEFINE0(getegid)
{
	
	return from_kgid_munged(current_user_ns(), current_egid());
}

实际的system_call在linux-3.18.6/arch/x86/kernel/entry_32.S里

ENTRY(system_call)
	RING0_INT_frame			# can't unwind into user space anyway
	ASM_CLAC
	pushl_cfi %eax			# 保存系统调用号
	SAVE_ALL             #保存现场,将用到的所有CPU寄存器保存到栈中
	GET_THREAD_INFO(%ebp)  #ebp用于存放当前进程thread_info结构的地址
					# system call tracing in operation / emulation
	testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
	jnz syscall_trace_entry
	cmpl $(NR_syscalls), %eax #检查系统调用号(系统调用号应小于nr_syscalls)
	jae syscall_badsys #不合法 跳入异常处理
syscall_call:
	call *sys_call_table(,%eax,4) #通过系统调用号在系统调用表中找到相应的系统调用内核处理函数 -这一行很重要
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,这是常见进程调度时机

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
画出流程图


至于流程图中的syscall_exit_work,working_pending与work_resched在linux-3.18.6/arch/x86/kernel/entry_32.S里的593到665行。

总结

上次实验是通过API和内嵌汇编来系统调用,这次是具体分析了系统调用的内核处理过程,比较重要的是call *sys_call_table(,%eax,4),这一段汇编是通过系统调用号,比如我这次实验中的0x14(变为十进制就是20),来查找与之匹配的系统调用函数,20对应的系统调用函数是getpid,其他系统调用原理与之相似,详细对应关系见对应关系,在流程图中SAVE_ALL将所有CPU寄存器保存到栈中,找到syscall_call,sys_call_table与call *sys_call_table(,%eax,4)调用系统调用函数。

以上有很多是我个人见解,如果有错误,欢迎各位大佬指正。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4948446.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-13
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存