Archive:

x86 Page Fault 处理


x86 中异常处理源代码定义复杂,此日志用于记录源代码分析流程,基于内核版本 5.19.0

通常 Linux 中出现异常时处理流程如下。

  • asm_exc_page_fault() 汇编声明的异常处理入口 @ arch/x86/include/asm/idtentry.h
  • exc_page_fault() C 语言版本,由 asm_exc_page_fault() 调用 @ arch/x86/mm/fault.c
  • do_kernel_page_fault() 异常来自于内核时,进行处理
  • do_user_page_fault() 异常来自于用户时,进行处理

asm_exc_page_fault() 声明

该函数声明比较复杂,由 DECLARE_IDTENTRY_RAW_ERRORCODE 宏展开而成。

该宏分成两部分,一部分由 C 语言编译器处理,另一部分则由汇编器处理。

C 语言部分

#define DECLARE_IDTENTRY_ERRORCODE(vector, func)			\
	asmlinkage void asm_##func(void);				\
	asmlinkage void xen_asm_##func(void);				\
	__visible void func(struct pt_regs *regs, unsigned long error_code)
/* ... */
#define DECLARE_IDTENTRY_RAW_ERRORCODE(vector, func)			\
	DECLARE_IDTENTRY_ERRORCODE(vector, func)

汇编部分

#define DECLARE_IDTENTRY(vector, func)					\
	idtentry vector asm_##func func has_error_code=0

#define DECLARE_IDTENTRY_ERRORCODE(vector, func)			\
	idtentry vector asm_##func func has_error_code=1

其中汇编部分调用了 idtentry 汇编宏进行二次展开,其定义位于 arch/x86/entry/entry_64.S

/**
 * idtentry - Macro to generate entry stubs for simple IDT entries
 * @vector:		Vector number
 * @asmsym:		ASM symbol for the entry point
 * @cfunc:		C function to be called
 * @has_error_code:	Hardware pushed error code on stack
 *
 * The macro emits code to set up the kernel context for straight forward
 * and simple IDT entries. No IST stack, no paranoid entry checks.
 */
.macro idtentry vector asmsym cfunc has_error_code:req
SYM_CODE_START(\asmsym)
	UNWIND_HINT_IRET_REGS offset=\has_error_code*8
	ENDBR
	ASM_CLAC
	cld

	.if \has_error_code == 0
		pushq	$-1			/* ORIG_RAX: no syscall to restart */
	.endif

	.if \vector == X86_TRAP_BP
		/*
		 * If coming from kernel space, create a 6-word gap to allow the
		 * int3 handler to emulate a call instruction.
		 */
		testb	$3, CS-ORIG_RAX(%rsp)
		jnz	.Lfrom_usermode_no_gap_\@
		.rept	6
		pushq	5*8(%rsp)
		.endr
		UNWIND_HINT_IRET_REGS offset=8
.Lfrom_usermode_no_gap_\@:
	.endif

	idtentry_body \cfunc \has_error_code

_ASM_NOKPROBE(\asmsym)
SYM_CODE_END(\asmsym)
.endm

接下来对这段代码进行分析,前四行代码都是对特定特性进行处理。

  • UNWIND_HINT_IRET_REGS 提供栈回溯信息,伪指令,不会被编译进实际二进制。
  • ENDBR Intel x86 CET 扩展,指明此处可以被 jmp 抵达。
  • ASM_CLAC Intel x86 SMAP 扩展,允许内核访问用户空间地址。
  • cld x86 方向寄存器,清空确保循环递增方向一致。

接下来的 .if 伪指令判断是否为断点,如果是来自内核的断点,对栈进行特殊处理。 最后进入 idtentry_body