描述
当 PE 将异常处理到使用 AArch64 的异常级别时,将强制执行到作为异常的异常向量的地址。 异常向量存在于异常被处理到的异常级别的向量表中。
从向量基地址开始,向量表在内存中占用多个连续的字对齐地址。
每个异常级别都有一个关联的向量基地址寄存器 (VBAR),它定义了该异常级别的表的异常基地址。
对于 AArch64 状态的异常,向量表提供以下信息:
1.异常是否为以下之一:
- Synchronous exception
- SError
- IRQ
- FIQ
2.有关异常来自的异常级别的信息,结合有关正在使用的堆栈指针的信息,以及寄存器的状态。
Vector offsets from vector table base address
VBAR寄存器
Exception level的Vector Base Address Register (VBAR)寄存器,该寄存器保存了各个exception level的异常向量表的基地址。该寄存器有三个,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。
linux初始化的时候会配置每一个PE的vbar_el1设置为linux的中断向量表。
SYM_FUNC_START_LOCAL(__primary_switched)
adr_l x8, vectors // load VBAR_EL1 with virtual
msr vbar_el1, x8 // vector table address
isb
SYM_FUNC_START_LOCAL(__secondary_switched)
adr_l x5, vectors
msr vbar_el1, x5
isb
涉及文件
linux/arch/arm64/kernel/head.S
中断向量表在kernel中的实现
495 /*
496 * Exception vectors.
497 */
498 .pushsection ".entry.text", "ax" //表明编译到.entry.text 段 ax的意思是a (可分配)、 w(可写)、r (可读)、x (可执行),
499
500 .align 11 //2K地址对齐
501 SYM_CODE_START(vectors) //中断向量表,主要通过宏kernel_ventry来实现。
502 kernel_ventry 1, sync_invalid // Synchronous EL1t EL1t的中断向量为invalid
503 kernel_ventry 1, irq_invalid // IRQ EL1t
504 kernel_ventry 1, fiq_invalid // FIQ EL1t
505 kernel_ventry 1, error_invalid // Error EL1t
506
507 kernel_ventry 1, sync // Synchronous EL1h EL1h的同步异常
508 kernel_ventry 1, irq // IRQ EL1h EL1h的irq
509 kernel_ventry 1, fiq_invalid // FIQ EL1h EL1h的fiq为invalid
510 kernel_ventry 1, error // Error EL1h EL1h的error
511
512 kernel_ventry 0, sync // Synchronous 64-bit EL0 EL0的同步异常
513 kernel_ventry 0, irq // IRQ 64-bit EL0 EL0的irq
514 kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 EL0的fiq为invalid
515 kernel_ventry 0, error // Error 64-bit EL0 EL0的error
516
517 #ifdef CONFIG_COMPAT //El0运行在aarch32状态的中断的支持
518
519 kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
520 kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
521 kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
522 #else
523 kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
524 kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
525 kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
526 kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
527 #endif
528 SYM_CODE_END(vectors)
关于该向量表的解释参考armv8手册
D1.10.2章节
其中EL1t和ELL1h的差别描述如下:
kernel_ventry
kernel_ventry是个宏实现如下,主要做了:
- sub sp, sp, #S_FRAME_SIZE—sp减去S_FRAME_SIZE用 保存当前pt_regs 用来恢复callstack。注意对于运行在用户态的用户进程来说这个sp是sp_el1,sp_el1这个时候指向用户进程内核栈的最低端(地址最大处)。对于内核进程,或者运行在内核态的用户进程sp指向内核栈的使用的栈顶。
- b el()\el()_\label —跳转到对应异常类型的处理。
.macro kernel_ventry, el, label, regsize = 64 //el代表异常等级。lable中断触发的方式irq fiq error sync还有invalid。 regsize默认是64位。
.align 7 //对齐
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0 //KPTI方案的补丁,这里略过
.if \el == 0
alternative_if ARM64_UNMAP_KERNEL_AT_EL0
.if \regsize == 64
mrs x30, tpidrro_el0
msr tpidrro_el0, xzr
.else
mov x30, xzr
.endif
alternative_else_nop_endif
.endif
#endif
sub sp, sp, #S_FRAME_SIZE //sp减去S_FRAME_SIZE用 保存当前pt_regs 用来恢复callstack
#ifdef CONFIG_VMAP_STACK //虚拟映射栈,这里略过。
/*
* Test whether the SP has overflowed, without corrupting a GPR.
* Task and IRQ stacks are aligned so that SP & (1 << THREAD_SHIFT)
* should always be zero.
*/
add sp, sp, x0 // sp' = sp + x0
sub x0, sp, x0 // x0' = sp' - x0 = (sp + x0) - x0 = sp
tbnz x0, #THREAD_SHIFT, 0f
sub x0, sp, x0 // x0'' = sp' - x0' = (sp + x0) - sp = x0
sub sp, sp, x0 // sp'' = sp' - x0 = (sp + x0) - x0 = sp
b el()\el()_\label
0:
/*
* Either we've just detected an overflow, or we've taken an exception
* while on the overflow stack. Either way, we won't return to
* userspace, and can clobber EL0 registers to free up GPRs.
*/
/* Stash the original SP (minus S_FRAME_SIZE) in tpidr_el0. */
msr tpidr_el0, x0
/* Recover the original x0 value and stash it in tpidrro_el0 */
sub x0, sp, x0
msr tpidrro_el0, x0
/* Switch to the overflow stack */
adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0
/*
* Check whether we were already on the overflow stack. This may happen
* after panic() re-enables interrupts.
*/
mrs x0, tpidr_el0 // sp of interrupted context
sub x0, sp, x0 // delta with top of overflow stack
tst x0, #~(OVERFLOW_STACK_SIZE - 1) // within range?
b.ne __bad_stack // no? -> bad stack pointer
/* We were already on the overflow stack. Restore sp/x0 and carry on. */
sub sp, sp, x0
mrs x0, tpidrro_el0
#endif
b el()\el()_\label //跳转到 eln_lable 例如el1_irq,第一个el()是字符串,\el()是传递的参数,\label也是参数。
.endm
可知,根据kernel的中断向量表根据中断来源的不同,实现了以下异常类型
涉及文件
linux/arch/arm64/kernel/entry.S
参考 armv8手册 :DDI0487D_a_armv8_arm-20181102S
D1.10 Exception entry
D1.10.2 Exception vectors