> 文章列表 > arm64异常向量表

arm64异常向量表

arm64异常向量表

arm64异常量表

  • 1 arm64异常向量表
  • 2 linux arm64异常向量表
  • 3 kernel_ventry宏
  • 4 异常向量表的保存
    • 4. VBAR_ELx寄存器
    • 4.2 __primary_switched
    • 4.3 __primary_switched

1 arm64异常向量表

arm64异常向量表
When an exception occurs, the processor must execute handler code which corresponds to the exception. The location in memory where the handler is stored is called the exception vector. In the ARM architecture, exception vectors are stored in a table, called the exception vector table. Each Exception level has its own vector table, that is, there is one for each of EL3, EL2 and EL1. The table contains instructions to be executed, rather than a set of addresses. Vectors for individual exceptions are located at fixed offsets from the beginning of the table. The virtual address of each table base is set by the Vector Based Address Registers VBAR_EL3, VBAR_EL2 and VBAR_EL1.
Each entry in the vector table is 16 instructions long. This in itself represents a significant change compared to ARMv7, where each entry was 4 bytes. This spacing of the ARMv7 vector table meant that each entry would almost always be some form of branch to the actual exception handler elsewhere in memory. In AArch64, the vectors are spaced more widely, so that the
top-level handler can be written directly in the vector table.
The base address is given by VBAR_ELn and then each entry has a defined offset from this base address. Each table has 16 entries, with each entry being 128 bytes (32 instructions) in size. The table effectively consists of 4 sets of 4 entries.
Which entry is used depends upon a number of factors:

  • The type of exception (SError, FIQ, IRQ or Synchronous)
  • If the exception is being taken at the same Exception level, the Stack Pointer to be used (SP0 or SPx)
  • If the exception is being taken at a lower Exception level, the execution state of the next lower level (AArch64 or AArch32)
    Considering an example might make this easier to understand.
    If kernel code is executing at EL1 and an IRQ interrupt is signaled, an IRQ exception occurs. This particular interrupt is not associated with the hypervisor or secure environment and is also handled within the kernel, also at SP_EL1, and the SPSel bit is set, so you are using SP_EL1. Execution is therefore from address VBAR_EL1 + 0x280.
    In the absence of LDR PC, [PC, #offset] in the ARMv8-A architecture, you must use more instructions to enable the destination to be read from a table of registers. The choice of spacing of the vectors is designed to avoid cache pollution for typical sized instruction cache lines from vectors that are not being used. The Reset Address is a completely separate address, which is
    IMPLEMENTATION DEFINED, and is typically set by hardwired configuration within the core. This address is visible in the RVBAR_EL1/2/3 register.
    Having a separate exception vector for each exception, either from the current Exception level or from the lower Exception level, gives the flexibility for the OS or hypervisor to determine the AArch64 and AArch32 state of the lower Exception levels. The SP_ELn is used for exceptions generated from lower levels. However, the software can switch to use SP_EL0 inside the
    handler. When you use this mechanism, it facilitates access to the values from the thread in the handler.

2 linux arm64异常向量表

/*                          * Exception vectors.       */                         .pushsection ".entry.text", "ax" .align  11          
SYM_CODE_START(vectors)    kernel_ventry   1, t, 64, sync          // Synchronous EL1t kernel_ventry   1, t, 64, irq           // IRQ EL1t kernel_ventry   1, t, 64, fiq           // FIQ EL1t kernel_ventry   1, t, 64, error         // Error EL1t kernel_ventry   1, h, 64, sync          // Synchronous EL1h kernel_ventry   1, h, 64, irq           // IRQ EL1h kernel_ventry   1, h, 64, fiq           // FIQ EL1h kernel_ventry   1, h, 64, error         // Error EL1h kernel_ventry   0, t, 64, sync          // Synchronous 64-bit EL0kernel_ventry   0, t, 64, irq           // IRQ 64-bit EL0kernel_ventry   0, t, 64, fiq           // FIQ 64-bit EL0kernel_ventry   0, t, 64, error         // Error 64-bit EL0                                                                                                                                        kernel_ventry   0, t, 32, sync          // Synchronous 32-bit EL0kernel_ventry   0, t, 32, irq           // IRQ 32-bit EL0kernel_ventry   0, t, 32, fiq           // FIQ 32-bit EL0kernel_ventry   0, t, 32, error         // Error 32-bit EL0
SYM_CODE_END(vectors)
  • .pushsection “.entry.text”, “ax” 表示要将当前的异常向量表放到.entry.text段,属性为"ax"
  • .align 11表示异常向量表的起始地址要2K对齐(2^11 = 2048),这和VBAR_ELx的寄存器定义是一致的。
  • SYM_CODE_START(vectors) 声明异常向量表

3 kernel_ventry宏

  • kernel_ventry是异常向量表的一部分通用处理宏,会依据参数跳转到不同的异常处理函数,对于kernel_ventry 1, h, 64, irq,则会跳转到el1h_64_irq处理函数流程中。
  • .align 7 表示当前的入口要按照128B对齐的方式去存放,这和异常向量表每个entry的大小为128B是匹配的
	.macro kernel_ventry, el:req, ht:req, regsize:req, label:req.align 7
.Lventry_start\\@:.if	\\el == 0/ This must be the first instruction of the EL0 vector entries. It is* skipped by the trampoline vectors, to trigger the cleanup.*/b	.Lskip_tramp_vectors_cleanup\\@.if	\\regsize == 64mrs	x30, tpidrro_el0msr	tpidrro_el0, xzr.elsemov	x30, xzr.endif
.Lskip_tramp_vectors_cleanup\\@:.endifsub	sp, sp, #PT_REGS_SIZE
#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 + x0sub	x0, sp, x0			// x0' = sp' - x0 = (sp + x0) - x0 = sptbnz	x0, #THREAD_SHIFT, 0fsub	x0, sp, x0			// x0'' = sp' - x0' = (sp + x0) - sp = x0sub	sp, sp, x0			// sp'' = sp' - x0 = (sp + x0) - x0 = spb	el\\el\\ht\\()_\\regsize\\()_\\label0:/ 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 PT_REGS_SIZE) in tpidr_el0. */msr	tpidr_el0, x0/* Recover the original x0 value and stash it in tpidrro_el0 */sub	x0, sp, x0msr	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 contextsub	x0, sp, x0			// delta with top of overflow stacktst	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, x0mrs	x0, tpidrro_el0
#endifb	el\\el\\ht\\()_\\regsize\\()_\\label
.org .Lventry_start\\@ + 128	// Did we overflow the ventry slot?.endm

4 异常向量表的保存

异常向量表的保存分为两个部分,一个是主核的启动流程,另一个是副核的启动流程处理的。其处理函数分别为__primary_switched和__primary_switched

4. VBAR_ELx寄存器

VBAR_EL1, Vector Base Address Register (EL1),Holds the vector base address for any exception that is taken to EL1.
arm64异常向量表

  • Bits [63:11]
    • Vector Base Address. Base address of the exception vectors for exceptions taken to EL1.
    • If the implementation does not support ARMv8.2-LVA, then:
      • If tagged addresses are being used, bits [55:48] of VBAR_EL1 must be the same or else the use of the vector address will result in a recursive exception.
      • If tagged addresses are not being used, bits [63:48] of VBAR_EL1 must be the same or else the use of the vector address will result in a recursive exception.
    • If the implementation supports ARMv8.2-LVA, then:
      • If tagged addresses are being used, bits [55:52] of VBAR_EL1 must be the same or else the use of the vector address will result in a recursive exception.
      • If tagged addresses are not being used, bits [63:52] of VBAR_EL1 must be the same or else the use of the vector address will result in a recursive exception.
        其访问方式如下所示:
  • MRS , VBAR_EL1 读取VBAR_EL1寄存器中保存的异常向量表地址到xt寄存器中
  • MSR VBAR_EL1, 将xt寄存器中保存的异常向量表地址写入VBAR_EL1寄存器中。

4.2 __primary_switched

对于主核的启动流程来说,其处理是如下两步,首先将vectors地址加载到x8寄存器中,随后通过msr指令,将其值写入vbar_el1寄存器中。

  • adr_l x8, vectors // load VBAR_EL1 with virtual
  • msr vbar_el1, x8 // vector table address
/ The following fragment of code is executed with the MMU enabled.   x0 = __pa(KERNEL_START)*/
SYM_FUNC_START_LOCAL(__primary_switched)adr_l	x4, init_taskinit_cpu_task x4, x5, x6adr_l	x8, vectors			// load VBAR_EL1 with virtualmsr	vbar_el1, x8			// vector table addressisbstp	x29, x30, [sp, #-16]!mov	x29, spstr_l	x21, __fdt_pointer, x5		// Save FDT pointerldr_l	x4, kimage_vaddr		// Save the offset betweensub	x4, x4, x0			// the kernel virtual andstr_l	x4, kimage_voffset, x5		// physical mappingsmov	x0, x20bl	set_cpu_boot_mode_flag// Clear BSSadr_l	x0, __bss_startmov	x1, xzradr_l	x2, __bss_stopsub	x2, x2, x0bl	__pi_memsetdsb	ishst				// Make zero page visible to PTW#if VA_BITS > 48adr_l	x8, vabits_actual		// Set this early so KASAN early initstr	x25, [x8]			// ... observes the correct valuedc	civac, x8			// Make visible to booting secondaries
#endif#ifdef CONFIG_RANDOMIZE_BASEadrp	x5, memstart_offset_seed	// Save KASLR linear map seedstrh	w24, [x5, :lo12:memstart_offset_seed]
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)bl	kasan_early_init
#endifmov	x0, x21				// pass FDT address in x0bl	early_fdt_map			// Try mapping the FDT earlymov	x0, x20				// pass the full boot statusbl	init_feature_override		// Parse cpu feature overridesmov	x0, x20bl	finalise_el2			// Prefer VHE if possibleldp	x29, x30, [sp], #16bl	start_kernelASM_BUG()
SYM_FUNC_END(__primary_switched)

4.3 __primary_switched

对于副核的启动流程来说,其处理是如下两步,首先将vectors地址加载到x5寄存器中,随后通过msr指令,将其值写入vbar_el1寄存器中。

  • adr_l x5, vectors
  • msr vbar_el1, x5
SYM_FUNC_START_LOCAL(__secondary_switched)mov	x0, x20bl	set_cpu_boot_mode_flagstr_l	xzr, __early_cpu_boot_status, x3adr_l	x5, vectorsmsr	vbar_el1, x5isbadr_l	x0, secondary_dataldr	x2, [x0, #CPU_BOOT_TASK]cbz	x2, __secondary_too_slowinit_cpu_task x2, x1, x3#ifdef CONFIG_ARM64_PTR_AUTHptrauth_keys_init_cpu x2, x3, x4, x5
#endifbl	secondary_start_kernelASM_BUG()
SYM_FUNC_END(__secondary_switched)