ARMv8 Linux内核异常处理过程分析


看了Linaro提供的开源ARMv8 Linux内核源码,发现ARMv8异常处理与ARMv7及之前的架构有所不同,简单分析。

LinaroARMv8工程:http://www.linaro.org/engineering/engineering-projects/armv8

1.1 Linux内核异常处理相关文件

Linux内核中,异常处理主要由两个文件完成,entry.S和traps.c,当然还有一些其它异常处理函数分布于fault.c, memory.c等等。entry.S包含异常的入口、进入异常处理C函数前的压栈、退出C函数前的出栈、一些fork函数相关的处理代码(暂不分析)、任务切换汇编处理过程(cpu_switch_to函数,暂不分析)。traps.c主要包含异常处理C函数。

本文主要分析entry.S,对于traps.c作简要介绍。

1.2 执行kernel_entry之前的栈

ARMv8 Linux内核异常处理过程分析_第1张图片

1.3 执行kernel_entry时的栈

ARMv8 Linux内核异常处理过程分析_第2张图片

1.4 执行kernel_exit 时的栈

ARMv8 Linux内核异常处理过程分析_第3张图片

1.5 entry.s代码分析

/*
 * Low-level exception handling code
 *
 * Copyright (C) 2012 ARM Ltd.
 * Authors:	Catalin Marinas <[email protected]>
 *		Will Deacon <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/init.h>
#include <linux/linkage.h>

#include <asm/assembler.h>
#include <asm/asm-offsets.h>
#include <asm/errno.h>
#include <asm/thread_info.h>
#include <asm/unistd.h>
#include <asm/unistd32.h>

/*
 * Bad Abort numbers
 *-----------------
 */
#define BAD_SYNC		0
#define BAD_IRQ		1
#define BAD_FIQ		2
#define BAD_ERROR	3

//根据该结构体内容
/*
struct pt_regs {			
	union {
		struct user_pt_regs user_regs;//结构体user_pt_regs和结构体pt_regs内容一样
		struct {					//共用体存储31个通用寄存器,外加sp,pc,pstate三个特殊寄存器
									//该结构体用于异常处理的压栈弹栈操作
			u64 regs[31];
			u64 sp;
			u64 pc;
			u64 pstate;
		};
	};
	u64 orig_x0;
	u64 syscallno;
};
*/


//S_FRAME_SIZE定义在asm-offsets.c中,DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));
//即结构体pt_regs的大小,结构体pt_regs的定义见上面
//S_LR定义:DEFINE(S_LR,offsetof(struct pt_regs, regs[30]));
//即31号寄存器在结构体pt_regs中的偏移量
//阅读以下内容请参考图1 和图2
	.macro	kernel_entry, el, regsize = 64
	sub	sp, sp, #S_FRAME_SIZE - S_LR	// room for LR, SP, SPSR, ELR,见图2中sp'指向的位置			
	.if	\regsize == 32
	mov	w0, w0				// zero upper 32 bits of x0
	.endif
	/*
	 *.macro	push, xreg1, xreg2	//压栈两个寄存器
	 *stp	\xreg1, \xreg2, [sp, #-16]!	//注意!!!push指令也改变sp的值!!!
	 *.endm
	 */
	push	x28, x29		//进行压栈操作,push也是一个宏定义,因为ARMv8没有push指令,用stp代替
	push	x26, x27	
	push	x24, x25
	push	x22, x23
	push	x20, x21
	push	x18, x19
	push	x16, x17
	push	x14, x15
	push	x12, x13
	push	x10, x11
	push	x8, x9
	push	x6, x7
	push	x4, x5
	push	x2, x3
	push	x0, x1		//此时sp指向位置见图2中sp''
	.if	\el == 0		//如果异常级是el0,把el0的sp栈指针给x21寄存器
	mrs	x21, sp_el0
	.else
	add	x21, sp, #S_FRAME_SIZE	//如果异常级不是el0,把sp指针指向的地方加上pt_regs大小后的地址放入x21,
								//即指向没进入kernel_entry函数钱的sp指向的位置,见图2中x21指向的地址
	.endif
	mrs	x22, elr_el1			//把el1的lr寄存器给x22
	mrs	x23, spsr_el1			//把spsr给x23
	stp	lr, x21, [sp, #S_LR]	//把lr,x21寄存器存入sp+S_LR指向的地方
	stp	x22, x23, [sp, #S_PC]	//把lr,存入sp+s_PC指向的位置,用于异常返回

	/*
	 * Set syscallno to -1 by default (overridden later if real syscall).
	 */
	.if	\el == 0
	mvn	x21, xzr
	str	x21, [sp, #S_SYSCALLNO]
	.endif
	/*
	 * Registers that may be useful after this macro is invoked:
	 *
	 * x21 - aborted SP
	 * x22 - aborted PC
	 * x23 - aborted PSTATE
	*/
	.endm

	
	.macro	kernel_exit, el, ret = 0
	//把此时sp(即图2中sp'')+S_PC位置处开始的16字节内容分别给x21,x22
	//即把栈中存的x21和x22内容取出来
	ldp	x21, x22, [sp, #S_PC]		// load ELR, SPSR
	.if	\el == 0
	ldr	x23, [sp, #S_SP]		// load return stack pointer,取出
	.endif
	.if	\ret
	ldr	x1, [sp, #S_X1]			// preserve x0 (syscall return),如果ret=1,则保存x0,用于系统调用,暂不分析
	add	sp, sp, S_X2
	.else
	pop	x0, x1				//如果ret=0,弹出x0,x1
	.endif
	pop	x2, x3				// load the rest of the registers
	pop	x4, x5
	pop	x6, x7
	pop	x8, x9
	msr	elr_el1, x21			// set up the return data,把前面弹出的x21,x22分别赋值给elr_el1,spsr_el1
	msr	spsr_el1, x22
	.if	\el == 0
	msr	sp_el0, x23
	.endif
	pop	x10, x11
	pop	x12, x13
	pop	x14, x15
	pop	x16, x17
	pop	x18, x19
	pop	x20, x21
	pop	x22, x23
	pop	x24, x25
	pop	x26, x27
	pop	x28, x29
	ldr	lr, [sp], #S_FRAME_SIZE - S_LR	// load LR and restore SP,把lr弹出
	eret					// return to kernel,异常返回,该指令会把lr给pc,完成跳转
	.endm

	.macro	get_thread_info, rd
	mov	\rd, sp
	and	\rd, \rd, #~((1 << 13) - 1)	// top of 8K stack
	.endm

/*
 * These are the registers used in the syscall handler, and allow us to
 * have in theory up to 7 arguments to a function - x0 to x6.
 *
 * x7 is reserved for the system call number in 32-bit mode.
 */
sc_nr	.req	x25		// number of system calls
scno	.req	x26		// syscall number
stbl	.req	x27		// syscall table pointer
tsk	.req	x28		// current thread_info

/*
 * Interrupt handling.
 */
	.macro	irq_handler
	ldr	x1, handle_arch_irq
	mov	x0, sp
	blr	x1
	.endm

	.text

/*
 * Exception vectors.
 */
	.macro	ventry	label	//这里是2^7对齐,即对齐到内存地址的0x80
	.align	7
	b	\label
	.endm

	.align	11
	
/* ENTRY也是一个宏,定义在include/linkage.h中
* #ifndef ENTRY
* #define ENTRY(name) \
* .globl name; \
* ALIGN; \
* name:
* #endif
*/

ENTRY(vectors)
	ventry	el1_sync_invalid		// Synchronous EL1t,ventry 是一个宏,见上面定义
	ventry	el1_irq_invalid			// IRQ EL1t,这个版本的内核,对于XXX_invalid类异常都是不真正处理的。这里以el1_irq_invalid进行分析
	ventry	el1_fiq_invalid			// FIQ EL1t
	ventry	el1_error_invalid		// Error EL1t

	ventry	el1_sync			// Synchronous EL1h,以el1级发生同步异常为例,详细分析内核异常处理过程
	ventry	el1_irq				// IRQ EL1h
	ventry	el1_fiq_invalid			// FIQ EL1h
	ventry	el1_error_invalid		// Error EL1h

	ventry	el0_sync			// Synchronous 64-bit EL0
	ventry	el0_irq				// IRQ 64-bit EL0
	ventry	el0_fiq_invalid			// FIQ 64-bit EL0
	ventry	el0_error_invalid		// Error 64-bit EL0

#ifdef CONFIG_COMPAT
	ventry	el0_sync_compat			// Synchronous 32-bit EL0
	ventry	el0_irq_compat			// IRQ 32-bit EL0
	ventry	el0_fiq_invalid_compat		// FIQ 32-bit EL0
	ventry	el0_error_invalid_compat	// Error 32-bit EL0
#else
	ventry	el0_sync_invalid		// Synchronous 32-bit EL0
	ventry	el0_irq_invalid			// IRQ 32-bit EL0
	ventry	el0_fiq_invalid			// FIQ 32-bit EL0
	ventry	el0_error_invalid		// Error 32-bit EL0
#endif
END(vectors)

/*
 * Invalid mode handlers
 */
	.macro	inv_entry, el, reason, regsize = 64
	kernel_entry el, \regsize		//kernel_entry是宏,主要完成寄存器压栈操作。
	mov	x0, sp						//x0,x1,x2是传给函数bad_mode函数的参数。sp是当前栈指针。
	mov	x1, #\reason				//x1是发生异常的原因,用于读取一个结构体,在函数bad_mode中会介绍
	mrs	x2, esr_el1					//通过分析bad_mode及其他函数,确定esr_el1是el1级异常分类寄存器,
									//用于在一个大类异常(例如syc异常)中细分异常类型
	b	bad_mode
	.endm

el0_sync_invalid:
	inv_entry 0, BAD_SYNC
ENDPROC(el0_sync_invalid)

el0_irq_invalid:
	inv_entry 0, BAD_IRQ
ENDPROC(el0_irq_invalid)

el0_fiq_invalid:
	inv_entry 0, BAD_FIQ
ENDPROC(el0_fiq_invalid)

el0_error_invalid:
	inv_entry 0, BAD_ERROR
ENDPROC(el0_error_invalid)

#ifdef CONFIG_COMPAT
el0_fiq_invalid_compat:
	inv_entry 0, BAD_FIQ, 32
ENDPROC(el0_fiq_invalid_compat)

el0_error_invalid_compat:
	inv_entry 0, BAD_ERROR, 32
ENDPROC(el0_error_invalid_compat)
#endif

el1_sync_invalid:
	inv_entry 1, BAD_SYNC
ENDPROC(el1_sync_invalid)

el1_irq_invalid:
	inv_entry 1, BAD_IRQ	//inv_entry是一个宏定义,主要工作就是将寄存器压栈后跳到bad_mode函数运行。
							//后面紧跟的1代表异常级是el1,即内核态。
							//BAD_IRQ定义在前面,值为1,代表发生异常的原因
ENDPROC(el1_irq_invalid)

el1_fiq_invalid:
	inv_entry 1, BAD_FIQ
ENDPROC(el1_fiq_invalid)

el1_error_invalid:
	inv_entry 1, BAD_ERROR
ENDPROC(el1_error_invalid)

/*
 * EL1 mode handlers.
 */
	.align	6
el1_sync:
	kernel_entry 1			//把寄存器信息压栈
	
	//读异常类型寄存器
	mrs	x1, esr_el1			// read the syndrome register
	//逻辑右移26位,取31-27位
	lsr	x24, x1, #26			// exception class
	//判断异常类型
	cmp	x24, #0x25			// data abort in EL1
	//如果是el1的数据中止(data_abort)异常,跳转到el1_da标号处
	b.eq	el1_da
	cmp	x24, #0x18			// configurable trap
	b.eq	el1_undef
	cmp	x24, #0x26			// stack alignment exception
	b.eq	el1_sp_pc
	cmp	x24, #0x22			// pc alignment exception
	b.eq	el1_sp_pc
	cmp	x24, #0x00			// unknown exception in EL1
	b.eq	el1_undef
	cmp	x24, #0x30			// debug exception in EL1
	b.ge	el1_dbg
	b	el1_inv
el1_da:
	/*
	 * Data abort handling,数据中止异常处理函数
	 */
	mrs	x0, far_el1		//看过函数do_mem_abort内容后确定,far_el1寄存器是异常地址寄存器
	
	/* 该宏定义在arm64/include/asm/assembler.h中:
	 * .macro	enable_dbg_if_not_stepping, tmp
	 * mrs	\tmp, mdscr_el1	//通过该宏名称确定,mdscr_el1寄存器是关于硬件debug的,不影响异常处理不分析
	 * tbnz	\tmp, #1, 9990f
	 * enable_dbg
	 * 9990:
	 * .endm
	 */ 
	 //通过以上内容及该宏名称可以猜测,其作用只是根据条件决定是否开启dbg模式,不影响异常执行,不做分析
	enable_dbg_if_not_stepping x2
	
	// re-enable interrupts if they were enabled in the aborted context
	//根据x23(在kernel_entry中定义,存储spsr的值)判断是否开启中断
	tbnz	x23, #7, 1f			// PSR_I_BIT
	enable_irq
1:
	mov	x2, sp				// struct pt_regs,sp中存储的是执行完kernel_entry后的值,其指向压栈后的栈顶,作为参数传给函数do_mem_abort
	//do_mem_abort函数在arm64/mm/fault.c中,分析见代码最后面
	bl	do_mem_abort		//传给该函数的x0发生异常的地址信息,x1是异常类型,x2就是压入栈中的寄存器堆首地址。

	// disable interrupts before pulling preserved data off the stack
	disable_irq
	kernel_exit 1		//异常返回,把所有压入栈中的寄存器弹出。对应于kernel_entry。
el1_sp_pc:
	/*
	 * Stack or PC alignment exception handling
	 */
	mrs	x0, far_el1
	mov	x1, x25
	mov	x2, sp
	b	do_sp_pc_abort
el1_undef:
	/*
	 * Undefined instruction
	 */
	mov	x0, sp
	b	do_undefinstr
el1_dbg:
	/*
	 * Debug exception handling
	 */
	tbz	x24, #0, el1_inv		// EL1 only
	mrs	x0, far_el1
	mov	x2, sp				// struct pt_regs
	bl	do_debug_exception

	kernel_exit 1
el1_inv:
	// TODO: add support for undefined instructions in kernel mode
	mov	x0, sp
	mov	x1, #BAD_SYNC
	mrs	x2, esr_el1
	b	bad_mode
ENDPROC(el1_sync)

	.align	6
el1_irq:
	kernel_entry 1
	enable_dbg_if_not_stepping x0
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif
#ifdef CONFIG_PREEMPT
	get_thread_info tsk
	ldr	x24, [tsk, #TI_PREEMPT]		// get preempt count
	add	x0, x24, #1			// increment it
	str	x0, [tsk, #TI_PREEMPT]
#endif
	irq_handler
#ifdef CONFIG_PREEMPT
	str	x24, [tsk, #TI_PREEMPT]		// restore preempt count
	cbnz	x24, 1f				// preempt count != 0
	ldr	x0, [tsk, #TI_FLAGS]		// get flags
	tbz	x0, #TIF_NEED_RESCHED, 1f	// needs rescheduling?
	bl	el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif
	kernel_exit 1
ENDPROC(el1_irq)

#ifdef CONFIG_PREEMPT
el1_preempt:
	mov	x24, lr
1:	enable_dbg
	bl	preempt_schedule_irq		// irq en/disable is done inside
	ldr	x0, [tsk, #TI_FLAGS]		// get new tasks TI_FLAGS
	tbnz	x0, #TIF_NEED_RESCHED, 1b	// needs rescheduling?
	ret	x24
#endif

/*
 * EL0 mode handlers.
 */
	.align	6
el0_sync:
	kernel_entry 0
	mrs	x25, esr_el1			// read the syndrome register
	lsr	x24, x25, #26			// exception class
	cmp	x24, #0x15			// SVC in 64-bit state
	b.eq	el0_svc
	adr	lr, ret_from_exception
	cmp	x24, #0x24			// data abort in EL0
	b.eq	el0_da
	cmp	x24, #0x20			// instruction abort in EL0
	b.eq	el0_ia
	cmp	x24, #0x07			// FP/ASIMD access
	b.eq	el0_fpsimd_acc
	cmp	x24, #0x2c			// FP/ASIMD exception
	b.eq	el0_fpsimd_exc
	cmp	x24, #0x18			// configurable trap
	b.eq	el0_undef
	cmp	x24, #0x26			// stack alignment exception
	b.eq	el0_sp_pc
	cmp	x24, #0x22			// pc alignment exception
	b.eq	el0_sp_pc
	cmp	x24, #0x00			// unknown exception in EL0
	b.eq	el0_undef
	cmp	x24, #0x30			// debug exception in EL0
	b.ge	el0_dbg
	b	el0_inv

#ifdef CONFIG_COMPAT
	.align	6
el0_sync_compat:
	kernel_entry 0, 32
	mrs	x25, esr_el1			// read the syndrome register
	lsr	x24, x25, #26			// exception class
	cmp	x24, #0x11			// SVC in 32-bit state
	b.eq	el0_svc_compat
	adr	lr, ret_from_exception
	cmp	x24, #0x24			// data abort in EL0
	b.eq	el0_da
	cmp	x24, #0x20			// instruction abort in EL0
	b.eq	el0_ia
	cmp	x24, #0x07			// FP/ASIMD access
	b.eq	el0_fpsimd_acc
	cmp	x24, #0x28			// FP/ASIMD exception
	b.eq	el0_fpsimd_exc
	cmp	x24, #0x00			// unknown exception in EL0
	b.eq	el0_undef
	cmp	x24, #0x30			// debug exception in EL0
	b.ge	el0_dbg
	b	el0_inv
el0_svc_compat:
	/*
	 * AArch32 syscall handling
	 */
	adr	stbl, compat_sys_call_table	// load compat syscall table pointer
	uxtw	scno, w7			// syscall number in w7 (r7)
	mov     sc_nr, #__NR_compat_syscalls
	b	el0_svc_naked

	.align	6
el0_irq_compat:
	kernel_entry 0, 32
	b	el0_irq_naked
#endif

el0_da:
	/*
	 * Data abort handling
	 */
	mrs	x0, far_el1
	disable_step x1
	isb
	enable_dbg
	// enable interrupts before calling the main handler
	enable_irq
	mov	x1, x25
	mov	x2, sp
	b	do_mem_abort
el0_ia:
	/*
	 * Instruction abort handling
	 */
	mrs	x0, far_el1
	disable_step x1
	isb
	enable_dbg
	// enable interrupts before calling the main handler
	enable_irq
	orr	x1, x25, #1 << 24		// use reserved ISS bit for instruction aborts
	mov	x2, sp
	b	do_mem_abort
el0_fpsimd_acc:
	/*
	 * Floating Point or Advanced SIMD access
	 */
	mov	x0, x25
	mov	x1, sp
	b	do_fpsimd_acc
el0_fpsimd_exc:
	/*
	 * Floating Point or Advanced SIMD exception
	 */
	mov	x0, x25
	mov	x1, sp
	b	do_fpsimd_exc
el0_sp_pc:
	/*
	 * Stack or PC alignment exception handling
	 */
	mrs	x0, far_el1
	disable_step x1
	isb
	enable_dbg
	// enable interrupts before calling the main handler
	enable_irq
	mov	x1, x25
	mov	x2, sp
	b	do_sp_pc_abort
el0_undef:
	/*
	 * Undefined instruction
	 */
	mov	x0, sp
	b	do_undefinstr
el0_dbg:
	/*
	 * Debug exception handling
	 */
	tbnz	x24, #0, el0_inv		// EL0 only
	mrs	x0, far_el1
	disable_step x1
	mov	x1, x25
	mov	x2, sp
	b	do_debug_exception
el0_inv:
	mov	x0, sp
	mov	x1, #BAD_SYNC
	mrs	x2, esr_el1
	b	bad_mode
ENDPROC(el0_sync)

	.align	6
el0_irq:
	kernel_entry 0
el0_irq_naked:
	disable_step x1
	isb
	enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif
	get_thread_info tsk
#ifdef CONFIG_PREEMPT
	ldr	x24, [tsk, #TI_PREEMPT]		// get preempt count
	add	x23, x24, #1			// increment it
	str	x23, [tsk, #TI_PREEMPT]
#endif
	irq_handler
#ifdef CONFIG_PREEMPT
	ldr	x0, [tsk, #TI_PREEMPT]
	str	x24, [tsk, #TI_PREEMPT]
	cmp	x0, x23
	b.eq	1f
	mov	x1, #0
	str	x1, [x1]			// BUG
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif
	b	ret_to_user
ENDPROC(el0_irq)

/*
 * This is the return code to user mode for abort handlers
 */
ret_from_exception:
	get_thread_info tsk
	b	ret_to_user
ENDPROC(ret_from_exception)

/*
 * Register switch for AArch64. The callee-saved registers need to be saved
 * and restored. On entry:
 *   x0 = previous task_struct (must be preserved across the switch)
 *   x1 = next task_struct
 * Previous and next are guaranteed not to be the same.
 *
 */
ENTRY(cpu_switch_to)
	add	x8, x0, #THREAD_CPU_CONTEXT
	mov	x9, sp
	stp	x19, x20, [x8], #16		// store callee-saved registers
	stp	x21, x22, [x8], #16
	stp	x23, x24, [x8], #16
	stp	x25, x26, [x8], #16
	stp	x27, x28, [x8], #16
	stp	x29, x9, [x8], #16
	str	lr, [x8]
	add	x8, x1, #THREAD_CPU_CONTEXT
	ldp	x19, x20, [x8], #16		// restore callee-saved registers
	ldp	x21, x22, [x8], #16
	ldp	x23, x24, [x8], #16
	ldp	x25, x26, [x8], #16
	ldp	x27, x28, [x8], #16
	ldp	x29, x9, [x8], #16
	ldr	lr, [x8]
	mov	sp, x9
	ret
ENDPROC(cpu_switch_to)

/*
 * This is the fast syscall return path.  We do as little as possible here,
 * and this includes saving x0 back into the kernel stack.
 */
ret_fast_syscall:
	disable_irq				// disable interrupts
	ldr	x1, [tsk, #TI_FLAGS]
	and	x2, x1, #_TIF_WORK_MASK
	cbnz	x2, fast_work_pending
	tbz	x1, #TIF_SINGLESTEP, fast_exit
	disable_dbg
	enable_step x2
fast_exit:
	kernel_exit 0, ret = 1

/*
 * Ok, we need to do extra processing, enter the slow path.
 */
fast_work_pending:
	str	x0, [sp, #S_X0]			// returned x0
work_pending:
	tbnz	x1, #TIF_NEED_RESCHED, work_resched
	/* TIF_SIGPENDING or TIF_NOTIFY_RESUME case */
	ldr	x2, [sp, #S_PSTATE]
	mov	x0, sp				// 'regs'
	tst	x2, #PSR_MODE_MASK		// user mode regs?
	b.ne	no_work_pending			// returning to kernel
	enable_irq				// enable interrupts for do_notify_resume()
	bl	do_notify_resume
	b	ret_to_user
work_resched:
	enable_dbg
	bl	schedule

/*
 * "slow" syscall return path.
 */
ENTRY(ret_to_user)
	disable_irq				// disable interrupts
	ldr	x1, [tsk, #TI_FLAGS]
	and	x2, x1, #_TIF_WORK_MASK
	cbnz	x2, work_pending
	tbz	x1, #TIF_SINGLESTEP, no_work_pending
	disable_dbg
	enable_step x2
no_work_pending:
	kernel_exit 0, ret = 0
ENDPROC(ret_to_user)

/*
 * This is how we return from a fork.
 */
ENTRY(ret_from_fork)
	bl	schedule_tail
	get_thread_info tsk
	b	ret_to_user
ENDPROC(ret_from_fork)

/*
 * SVC handler.
 */
	.align	6
el0_svc:
	adrp	stbl, sys_call_table		// load syscall table pointer
	uxtw	scno, w8			// syscall number in w8
	mov	sc_nr, #__NR_syscalls
el0_svc_naked:					// compat entry point
	stp	x0, scno, [sp, #S_ORIG_X0]	// save the original x0 and syscall number
	disable_step x16
	isb
	enable_dbg
	enable_irq

	get_thread_info tsk
	ldr	x16, [tsk, #TI_FLAGS]		// check for syscall tracing
	tbnz	x16, #TIF_SYSCALL_TRACE, __sys_trace // are we tracing syscalls?
	adr	lr, ret_fast_syscall		// return address
	cmp     scno, sc_nr                     // check upper syscall limit
	b.hs	ni_sys
	ldr	x16, [stbl, scno, lsl #3]	// address in the syscall table
	br	x16				// call sys_* routine
ni_sys:
	mov	x0, sp
	b	do_ni_syscall
ENDPROC(el0_svc)

	/*
	 * This is the really slow path.  We're going to be doing context
	 * switches, and waiting for our parent to respond.
	 */
__sys_trace:
	mov	x1, sp
	mov	w0, #0				// trace entry
	bl	syscall_trace
	adr	lr, __sys_trace_return		// return address
	uxtw	scno, w0			// syscall number (possibly new)
	mov	x1, sp				// pointer to regs
	cmp	scno, sc_nr			// check upper syscall limit
	b.hs	ni_sys
	ldp	x0, x1, [sp]			// restore the syscall args
	ldp	x2, x3, [sp, #S_X2]
	ldp	x4, x5, [sp, #S_X4]
	ldp	x6, x7, [sp, #S_X6]
	ldr	x16, [stbl, scno, lsl #3]	// address in the syscall table
	br	x16				// call sys_* routine

__sys_trace_return:
	str	x0, [sp]			// save returned x0
	mov	x1, sp
	mov	w0, #1				// trace exit
	bl	syscall_trace
	b	ret_to_user

/*
 * Special system call wrappers.
 */
ENTRY(sys_execve_wrapper)
	mov	x3, sp
	b	sys_execve
ENDPROC(sys_execve_wrapper)

ENTRY(sys_clone_wrapper)
	mov	x5, sp
	b	sys_clone
ENDPROC(sys_clone_wrapper)

ENTRY(sys_rt_sigreturn_wrapper)
	mov	x0, sp
	b	sys_rt_sigreturn
ENDPROC(sys_rt_sigreturn_wrapper)

ENTRY(sys_sigaltstack_wrapper)
	ldr	x2, [sp, #S_SP]
	b	sys_sigaltstack
ENDPROC(sys_sigaltstack_wrapper)

ENTRY(handle_arch_irq)
	.quad	0

	
	
/*
 * Dispatch a data abort to the relevant handler.
 */
 /*
asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
					 struct pt_regs *regs)
{
	const struct fault_info *inf = fault_info + (esr & 63);//取esr所有有效位,用于选择fault_info数组中的相应处理函数,该数组定义在后面
	struct siginfo info;

	if (!inf->fn(addr, esr, regs))		//如果处理成功(返回0),则直接返回,否则继续执行。
		return;

	//异常处理不成功,打印出错信息,进一步处理,不做分析。这里假设异常处理正常返回。
	pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
		 inf->name, esr, addr);

	info.si_signo = inf->sig;
	info.si_errno = 0;
	info.si_code  = inf->code;
	info.si_addr  = (void __user *)addr;
	arm64_notify_die("", regs, &info, esr);
}
*/
/*
static struct fault_info {
	int	(*fn)(unsigned long addr, unsigned int esr, struct pt_regs *regs);//相应的异常处理函数
	int	sig;
	int	code;
	const char *name;
} fault_info[] = {
	{ do_bad,		SIGBUS,  0,		"ttbr address size fault"	},
	{ do_bad,		SIGBUS,  0,		"level 1 address size fault"	},
	{ do_bad,		SIGBUS,  0,		"level 2 address size fault"	},
	{ do_bad,		SIGBUS,  0,		"level 3 address size fault"	},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"input address range fault"	},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 1 translation fault"	},
	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 2 translation fault"	},
	{ do_page_fault,	SIGSEGV, SEGV_MAPERR,	"level 3 translation fault"	},
	{ do_bad,		SIGBUS,  0,		"reserved access flag fault"	},
	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"level 1 access flag fault"	},
	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"level 2 access flag fault"	},
	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 3 access flag fault"	},
	{ do_bad,		SIGBUS,  0,		"reserved permission fault"	},
	{ do_bad,		SIGSEGV, SEGV_ACCERR,	"level 1 permission fault"	},
	{ do_sect_fault,	SIGSEGV, SEGV_ACCERR,	"level 2 permission fault"	},
	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 3 permission fault"	},
	{ do_bad,		SIGBUS,  0,		"synchronous external abort"	},
	{ do_bad,		SIGBUS,  0,		"asynchronous external abort"	},
	{ do_bad,		SIGBUS,  0,		"unknown 18"			},
	{ do_bad,		SIGBUS,  0,		"unknown 19"			},
	{ do_bad,		SIGBUS,  0,		"synchronous abort (translation table walk)" },
	{ do_bad,		SIGBUS,  0,		"synchronous abort (translation table walk)" },
	{ do_bad,		SIGBUS,  0,		"synchronous abort (translation table walk)" },
	{ do_bad,		SIGBUS,  0,		"synchronous abort (translation table walk)" },
	{ do_bad,		SIGBUS,  0,		"synchronous parity error"	},
	{ do_bad,		SIGBUS,  0,		"asynchronous parity error"	},
	{ do_bad,		SIGBUS,  0,		"unknown 26"			},
	{ do_bad,		SIGBUS,  0,		"unknown 27"			},
	{ do_bad,		SIGBUS,  0,		"synchronous parity error (translation table walk" },
	{ do_bad,		SIGBUS,  0,		"synchronous parity error (translation table walk" },
	{ do_bad,		SIGBUS,  0,		"synchronous parity error (translation table walk" },
	{ do_bad,		SIGBUS,  0,		"synchronous parity error (translation table walk" },
	{ do_bad,		SIGBUS,  0,		"unknown 32"			},
	{ do_bad,		SIGBUS,  BUS_ADRALN,	"alignment fault"		},
	{ do_bad,		SIGBUS,  0,		"debug event"			},
	{ do_bad,		SIGBUS,  0,		"unknown 35"			},
	{ do_bad,		SIGBUS,  0,		"unknown 36"			},
	{ do_bad,		SIGBUS,  0,		"unknown 37"			},
	{ do_bad,		SIGBUS,  0,		"unknown 38"			},
	{ do_bad,		SIGBUS,  0,		"unknown 39"			},
	{ do_bad,		SIGBUS,  0,		"unknown 40"			},
	{ do_bad,		SIGBUS,  0,		"unknown 41"			},
	{ do_bad,		SIGBUS,  0,		"unknown 42"			},
	{ do_bad,		SIGBUS,  0,		"unknown 43"			},
	{ do_bad,		SIGBUS,  0,		"unknown 44"			},
	{ do_bad,		SIGBUS,  0,		"unknown 45"			},
	{ do_bad,		SIGBUS,  0,		"unknown 46"			},
	{ do_bad,		SIGBUS,  0,		"unknown 47"			},
	{ do_bad,		SIGBUS,  0,		"unknown 48"			},
	{ do_bad,		SIGBUS,  0,		"unknown 49"			},
	{ do_bad,		SIGBUS,  0,		"unknown 50"			},
	{ do_bad,		SIGBUS,  0,		"unknown 51"			},
	{ do_bad,		SIGBUS,  0,		"implementation fault (lockdown abort)" },
	{ do_bad,		SIGBUS,  0,		"unknown 53"			},
	{ do_bad,		SIGBUS,  0,		"unknown 54"			},
	{ do_bad,		SIGBUS,  0,		"unknown 55"			},
	{ do_bad,		SIGBUS,  0,		"unknown 56"			},
	{ do_bad,		SIGBUS,  0,		"unknown 57"			},
	{ do_bad,		SIGBUS,  0,		"implementation fault (coprocessor abort)" },
	{ do_bad,		SIGBUS,  0,		"unknown 59"			},
	{ do_bad,		SIGBUS,  0,		"unknown 60"			},
	{ do_bad,		SIGBUS,  0,		"unknown 61"			},
	{ do_bad,		SIGBUS,  0,		"unknown 62"			},
	{ do_bad,		SIGBUS,  0,		"unknown 63"			},
};
*/

1.6 traps.c代码分析

//该文件中代码原理很简单,目前暂不分析,若需要,后续再添上。

/*
 * bad_mode handles the impossible case in the exception vector.
 */
//三个参数从左到右分别对应x0~x3,该函数的作用就是打印出错原因,跳转到panic()函数
asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
{
	console_verbose();

	pr_crit("Bad mode in %s handler detected, code 0x%08x\n",
		handler[reason], esr);

	die("Oops - bad mode", regs, 0);
	local_irq_disable();
	panic("bad mode");
}






你可能感兴趣的:(linux,异常处理,内核)