您的位置:首页 > 其它

分析system_call中断处理过程

2015-03-28 12:58 441 查看
第一章 环境

Ubuntu 14.10

Linux Kernel 3.18.6

第二章 源代码

ENTRY(system_call)
	RING0_INT_FRAME			# can't unwind into user space anyway
	ASM_CLAC
	pushl_cfi %eax			# save orig_eax
	S***E_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)
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

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
.section .fixup,"ax"
ENTRY(iret_exc)
	pushl $0			# no error code
	pushl $do_iret_error
	jmp error_code
.previous
	_ASM_EXTABLE(irq_return,iret_exc)

#ifdef CONFIG_X86_ESPFIX32
	CFI_RESTORE_STATE
ldt_ss:
#ifdef CONFIG_PAR***IRT
	/*
	 * The kernel can't run on a non-flat stack if paravirt mode
	 * is active.  Rather than try to fixup the high bits of
	 * ESP, bypass this code entirely.  This may break DOSemu
	 * and/or Wine support in a paravirt VM, although the option
	 * is still available to implement the setting of the high
	 * 16-bits in the INTERRUPT_RETURN paravirt-op.
	 */
	cmpl $0, pv_info+PAR***IRT_enabled
	jne restore_nocheck
#endif

/*
 * Setup and switch to ESPFIX stack
 *
 * We're returning to userspace with a 16 bit stack. The CPU will not
 * restore the high word of ESP for us on executing iret... This is an
 * "official" bug of all the x86-compatible CPUs, which we can work
 * around to make dosemu and wine happy. We do this by preloading the
 * high word of ESP with the high word of the userspace ESP while
 * compensating for the offset by changing to the ESPFIX segment with
 * a base address that matches for the difference.
 */
#define GDT_ESPFIX_SS PER_CPU_VAR(gdt_page) + (GDT_ENTRY_ESPFIX_SS * 8)
	mov %esp, %edx			/* load kernel esp */
	mov PT_OLDESP(%esp), %eax	/* load userspace esp */
	mov %dx, %ax			/* eax: new kernel esp */
	sub %eax, %edx			/* offset (low word is 0) */
	shr $16, %edx
	mov %dl, GDT_ESPFIX_SS + 4 /* bits 16..23 */
	mov %dh, GDT_ESPFIX_SS + 7 /* bits 24..31 */
	pushl_cfi $__ESPFIX_SS
	pushl_cfi %eax			/* new kernel esp */
	/* Disable interrupts, but do not irqtrace this section: we
	 * will soon execute iret and the tracer was already set to
	 * the irqstate after the iret */
	DISABLE_INTERRUPTS(CLBR_EAX)
	lss (%esp), %esp		/* switch to espfix segment */
	CFI_ADJUST_CFA_OFFSET -8
	jmp restore_nocheck
#endif
	CFI_ENDPROC
ENDPROC(system_call)


第三章 分析

由于这个是无法用gdb调试的,所以不能用截图表达。网上的文章说可以用kdb,kgdb来调试,但是复杂程度超过我的能力范围,所以无法截图分析。只总结并画出流程图。

S***E_ALL:将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,从而实现从汇编代码向C程序传递参数。

struct pt_regs 位于:linux/arch/x86/include/asm/ptrace.h。

GET_THREAD_INFO 宏获得当前进程的thread_info结构的地址,获取当前进程的信息。(后面会有文章讲task_struct 和 thread_info等内核线程的数据结构和相关内容) 525行的jnz syscall_trace_entry比较结果不为零的时候跳转。对用户态进程传递过来的系统调用号的合法性进行检查。如果不合法则跳转到syscall_badsys标记的命令处。

比较结果大于或者等于最大的系统调用号的时候跳转,合法则跳转到相应系统调用号所对应的服务例程当中,也就是在sys_call_table表中找到了相应的函数入口点。由于sys_call_table表的表项占4字节,因此获得服务例程指针的具体方法是将由eax保存的系统调用号乘以4再与sys_call_table表的基址相加。

接下来,会进入到系统调用表查找到系统调用服务程序的入口函数的地址,再进行跳转,整个过程大概是这样:system_call -> 4x%eax ->sys_call_table ->sys_xxx

sys_call_table位于:linux/arch/x86/kernel/syscall_table_32.S

此部分来源于:http://www.cnblogs.com/zhuyp1015/archive/2012/05/29/2524936.html

jne syscall_exit_work //退出系统调用之前,检查是否需要处理信号


参考资料:http://blog.chinaunix.net/uid-27767798-id-3507139.html

如果遇到了错误,则要通过

jmp error_code
进行异常处理,以便及时纠正。

参考资料:http://read.pudn.com/downloads83/ebook/321730/%D6%D0%B6%CF.pdf

RESTORE_ALL:恢复现场。

irq_return:
	INTERRUPT_RETURN


在linux-3.18.6/arch/x86/kernel/head_64.S中定义了一个宏INTERRUPT_RETURN:

#define INTERRUPT_RETURN iretq
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
上述代码是为了能过处理内核中进程的信号量问题。

参考资料:http://blog.chinaunix.net/uid-27767798-id-3507139.html

接着转向:

jmp restore_nocheck


RESTORE_REGS 4                  //忽略orig_eax和error_code


接着便是退出

CFI_ENDPROC


从而不再进行内核处理过程,返回用户态。

第五章 总结



查阅相关资料后,我明白:

1、基本上用户态到内核态需要int 0x80进行中断,产生中断向量后才可以转换;

2、系统内部调用涉及CPU架构等内容,不同的CPU对于系统调用的汇编具体代码是不一样的。

3、之所以要有高级语言,是因为汇编语言对于底层而言太过于依赖,没有跨平台可移植性。这也加深了我对高级语言的理解。高级语言通过对系统底层细节的屏蔽完成了跨平台实现,其中最出名的例子可以说是Java了。而C则是第一个跨平台的语言。对此,对前辈们的艰苦努力表示敬意。

附录

卢晅 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: