ARM64基础12:ARM64的异常模式及异常向量表

1,249次阅读
一条评论

1.ARM64的异常等级

ARM64包含4个异常等级:
EL0:非特权模式,常用来跑应用程序;
EL1:特权模式,常用来跑内核;
EL2:虚拟化监控程序,例如hypervisor;
EL3:安全模式,例如secure monitor;

2.同步异常和异步异常

同步异常是由正在运行的指令,或指令运行的结果,出错造成的异常;而异步异常则不必由运行的指令造成,可以在程序运行中的任意时刻(异步)发生。

同步异常包括:
1.系统调用,svc, hvc, SMC等;
2.MMU引发的异常;
3.SP和PC对齐检查;
4.未分配的指令;

异步异常
IRQ中断;
FIQ中断;
SError

注:在官方手册D1.12有详细列出那些是同步异常;

3.异常入口

当异常发生时刻:
a.CPU硬件做了哪些事情
(1) PSTATE保存到SPSR_ELx;
(2) 返回地址保存到ELR_ELx;
(3) PSTATE寄存器里的DAIF域都设置为1,相当于把调试异常、系统错误关闭;
(4)更新ESR_ELx寄存器,该寄存器包含同步异常发生的原因;
(5)切换到对应的EL, 然后跳转到异常向量表里执行;

b.操作系统做哪些事情
(1)根据异常发生的类型,跳转到合适的异常向量表
(2)异常向量表的每个表项,保存有一个异常处理的跳转函数,跳转到对应的异常处理函数处理异常

4 异常的返回

操作系统执行一条eret语句;
从ELR_ELx寄存器恢复PC指针;
从SPSR_ELx寄存器恢复处理器的状态;

5.异常返回地址

返回地址的两个寄存器:
x30:子函数的返回地址,使用ret指令来返回;
ELR_Elx:异常返回地址,使用eret返回;

ELR_ELx保存了异常返回地址
(1)对于异步异常,它是中断发生时的下一条指令,或没有执行的第一条指令;
(2)对于不是system call的同步异常,它是触发同步异常的那一条指令;
(3)对于system call, 它是svc指令的下一条指令;

6.异常处理的路由

(1)异常发生时,异常处理可以在当前或更高EL,EL0不能处理异常;
(2)同步异常是可以在当前EL处理的,比如在EL1里发生的同步异常;
(3)对于异步异常,可以路由到EL1/EL2/EL3处理,需要配置HCR以及SCR相关寄存器;

路由方法:

1.选择异常级别:

当异常发生时,PC可以有三个基地址VBAR_EL1、VBAR_EL3、VBAR_EL1(secure)供选择;路由规则如下:
ARM64基础12:ARM64的异常模式及异常向量表

举两例来说明下:
第一个红色框的内容表示:在此种配置(安全模式)下,EL0和EL1状态下产生的异步异常,会导致CPU进入EL1。
第一个红色框的内容表示:在此种配置(非安全模式)下,EL0和EL1状态下产生的异步异常,会导致CPU进入EL1,而EL2状态下产生的异常,不会导致exception level切换。

Linux内核只支持EL0和EL1,EL0对应用户态,EL1对应内核态,当CPU运行在用户态时,产生的异步异常会导致CPU切换到EL1,当CPU运行在内核态时,产生的异步异常不会导致exception level的切换。
这里配置为非安全模式vbar_el1:

//set vector table addr to vbar
    ldr x10, =vectors
    msr vbar_el1, x10
    isb

2.选择异常级别相关的偏移:

配置好基地址后,再根据下表配置异常向量表的偏移地址
ARM64基础12:ARM64的异常模式及异常向量表
说明:
(1)实际上有四张表,每张表有四个异常入口,分别对应同步异常,IRQ,FIQ和出错异常
(2)每一个异常入口占用0x80 bytes(不同于ARMv7之前的4bytes)空间,也就是说,每一个异常入口可以放置多条指令,而不仅仅是一条跳转指令。
四张表类型
(1)如果发生异常并不会导致exception level切换,并且使用的栈指针是SP_EL0,那么使用第一张异常向量表。
(2)如果发生异常并不会导致exception level切换,并且使用的栈指针是SP_EL1/2/3,那么使用第二张异常向量表。
(3)如果发生异常会导致exception level切换,并且比目的exception level低一级的exception level运行在AARCH64模式,那么使用第三张异常向量表。
(4)如果发生异常会导致exception level切换,并且比目的exception level低一级的exception level运行在AARCH32模式,那么使用第四张异常向量表。

在Linux中,用户态EL0, 内核态EL1, 结合上面路由规则,可得到如下结论:
(1)第一章异常向量表,用不到; 因为EL0使用SP_EL0,但发生EL0异常会routing到EL1;
(2)第二张表,用于CPU运行在EL1即内核态,发生异常时,exception level不发生切换;
(3)第三张表用于CPU运行在EL0即用户态的AARCH32模式时,发生异常;
(4)第四张表,用于CPU运行在EL0即用户态的AARCH64时,发生异常;
ARM64基础12:ARM64的异常模式及异常向量表

3.填充异常向量表

根据以上规则,填充异常向量表如下,根据实际发生的不同异常类型,跳转到对应的处理函数;

/*
 * Vector Table
 *
 * ARM64的异常向量表一共占用2048个字节
 * 分成4组,每组4个表项,每个表项占128字节
 * 参见ARMv8 spec v8.6第D1.10节
 * align 11表示2048字节对齐
 */
.align 11
.global vectors
vectors:
    /* Current EL with SP0
       当前系统运行在EL1时使用EL0的栈指针SP
       这是一种异常错误的类型
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Current EL with SPx
       当前系统运行在EL1时使用EL1的栈指针SP
       这说明系统在内核态发生了异常

       Note: 我们暂时只实现IRQ中断
     */
    vtentry el1_sync_invalid
    vtentry el1_irq
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Lower EL using AArch64
       在用户态的aarch64的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

    /* Lower EL using AArch32
       在用户态的aarch32的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

el1_sync_invalid:
    //inv_entry 1, BAD_SYNC
    kernel_entry
    mov x0, sp
    mov x1, 0
    mrs x2, esr_el1
    bl bad_mode
    kernel_exit

el1_irq_invalid:
    inv_entry 1, BAD_IRQ
el1_fiq_invalid:
    inv_entry 1, BAD_FIQ
el1_error_invalid:
    inv_entry 1, BAD_ERROR
el0_sync_invalid:
    inv_entry 0, BAD_SYNC
el0_irq_invalid:
    inv_entry 0, BAD_IRQ
el0_fiq_invalid:
    inv_entry 0, BAD_FIQ
el0_error_invalid:
    inv_entry 0, BAD_ERROR

7.栈的选择

(1)每个异常登记EL都有对应的栈指针寄存器SP:SP_EL0, SP_EL1, SP_EL2, SP_EL3;
(2)栈必须16字节对齐,硬件可以检测栈指针对齐;
(3)当异常发生时,跳转到目标异常等级时,硬件会自动选择SP_ELx;
(3)操作系统负责分配和保证,每个异常等级EL对应的栈,是可用的

8.异常处理的执行模式

(1)异常发生时,切换到更高级别EL,这个EL运行在哪个模式?
HCR_EL2.RW记录EL1要运行的哪个模式,1:aarch64, 0:aarch32;
(2)异常发生后,执行模式可以改变;
一个aarch32模式运行的应用程序,异常出现后,可以在aarch64模式处理异常;

9.异常返回的执行模式

从一个异常返回时,SPSR寄存器记录了:
(1)返回到哪个EL?SPSR.M[3:0]
(2)返回目标EL的执行模式?SPSR.M[4],1表示aarch32,0表示aarch64;

案例1:树莓派开机默认运行在EL2模式, 请切换到EL1模式, 切换后,打印出EL的值为1;

提示: 从EL2切换到EL1,需要做一下事情:
(1)设置HCR_EL2寄存器,Bit31位设置为1,表示EL1要运行在aarch64模式;
(2)设置SCTRL_EL1寄存器,设置小端,关闭MMU;
(3)设置SPSR_EL2寄存器,设置切换模式M域为EL1h, 另外关闭所有DAIF;
(4)设置异常返回寄存器elr_el2, 将返回到EL1时执行的函数;
(5) 调用eret;

其中(3)(4)是一个典型的一场返回操作
主要实现代码如下:

master:
    /* init uart and print string */
    bl __init_uart

    mrs x5, CurrentEL
    cmp x5, #CurrentEL_EL3
    b.eq el3_entry
    b el2_entry

el2_entry:
    bl print_el
    /*
     * set arm64 from EL2 to EL1, need to do list
     * 1.set HCR_EL2, bit31=1?aarch64:aarch32;
     * 2.set SCTLR_EL1, big/little endian and disable MMU;
     * 3.set SPSR_EL2, set M filed to EL1h, and disable all DAIF;
     * 4.set ELR_EL2, return to el1_entry;
     */

    /* The execution state for EL1 is AArch64 */
    ldr x0, =HCR_HOST_NVHE_FLAGS
    msr hcr_el2, x0

    /* set big/little endian, disable MMU*/
    ldr x0, = SCTLR_VALUE_MMU_DISABLED
    msr sctlr_el1, x0

    /*
     * 1.set M_field to EL1h;
     * 2.disable all DAIF;
     */
    ldr x0, = SPSR_EL1
    msr spsr_el2, x0

    /*
     * set return function
     */
    adr x0, _el1_entry
    msr elr_el2, x0
    eret

el3_entry:
    eret

.globl _el1_entry
_el1_entry:
    bl print_el

    adr x0, _bss
    adr x1, _ebss
    sub x1, x1, x0
    bl memzero

    mov sp, #LOW_MEMORY 
    bl  kernel_main
    b   proc_hang       // should never come here

单步调式,观察PC寄存器值变化如下:
ARM64基础12:ARM64的异常模式及异常向量表
执行eret后,CPU自动从ELR_el2获取返回地址,继续执行
ARM64基础12:ARM64的异常模式及异常向量表

10 异常向量表

(1)每个异常等级EL都有自己的异常向量表,EL0除外;
(2)异常向量表的基地址需要设置到VBAR_ELx寄存器中;
(3)VBAR_EL1寄存器必须以2KB对齐;
(4)每个表项可存放32条指令,一共128字节
(5)大的异常类型分类有4类,分别是同步/IRQ/FIQ/Serror;

ARM64的异常向量表格式如下

地址偏移(基地址为VBAR_ELn) 异常类型 描述
+0x000 同步 Current EL with SP0
+0x080 IRQ/vIRQ Current EL with SP0
+0x100 FIQ/vFIQ Current EL with SP0
+0x180 SError/vSError Current EL with SP0
+0x200 同步 Current EL with SP1
+0x280 IRQ/vIRQ Current EL with SP1
+0x300 FIQ/vFIQ Current EL with SP1
+0x380 SError/vSError Current EL with SP1
+0x400 同步 Current EL with SP2
+0x480 IRQ/vIRQ Current EL with SP2
+0x500 FIQ/vFIQ Current EL with SP2
+0x580 SError/vSError Current EL with SP2
+0x600 同步 Current EL with SP3
+0x680 IRQ/vIRQ Current EL with SP3
+0x700 FIQ/vFIQ Current EL with SP3
+0x780 SError/vSError Current EL with SP3

内核里的异常向量表实现arch/arm64/kernel/entry.S

/*
 * Exception vectors.
 */
    .pushsection ".entry.text", "ax"

    .align  11
SYM_CODE_START(vectors)
    kernel_ventry   1, sync_invalid         // Synchronous EL1t
    kernel_ventry   1, irq_invalid          // IRQ EL1t
    kernel_ventry   1, fiq_invalid          // FIQ EL1t
    kernel_ventry   1, error_invalid        // Error EL1t

    kernel_ventry   1, sync             // Synchronous EL1h
    kernel_ventry   1, irq              // IRQ EL1h
    kernel_ventry   1, fiq              // FIQ EL1h
    kernel_ventry   1, error            // Error EL1h

    kernel_ventry   0, sync             // Synchronous 64-bit EL0
    kernel_ventry   0, irq              // IRQ 64-bit EL0
    kernel_ventry   0, fiq              // FIQ 64-bit EL0
    kernel_ventry   0, error            // Error 64-bit EL0

#ifdef CONFIG_COMPAT
    kernel_ventry   0, sync_compat, 32      // Synchronous 32-bit EL0
    kernel_ventry   0, irq_compat, 32       // IRQ 32-bit EL0
    kernel_ventry   0, fiq_compat, 32       // FIQ 32-bit EL0
    kernel_ventry   0, error_compat, 32     // Error 32-bit EL0
#else
    kernel_ventry   0, sync_invalid, 32     // Synchronous 32-bit EL0
    kernel_ventry   0, irq_invalid, 32      // IRQ 32-bit EL0
    kernel_ventry   0, fiq_invalid, 32      // FIQ 32-bit EL0
    kernel_ventry   0, error_invalid, 32        // Error 32-bit EL0
#endif
SYM_CODE_END(vectors)

kernel_ventry宏定义实现:

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

    .macro kernel_ventry, el, label, regsize = 64
    .align 7
    #...
    sub sp, sp, #PT_REGS_SIZE
    #...
    b   el\()\el\()_\label
    .endm

案例,实现一个简单的异常向量表,用一个数据对齐异常,PC指针的跳转;


#define BAD_SYNC        0
#define BAD_IRQ         1
#define BAD_FIQ         2
#define BAD_ERROR       3

/*
   处理无效的异常向量
 */
    .macro inv_entry el, reason
    //kernel_entry el
    mov x0, sp
    mov x1, #\reason
    mrs x2, esr_el1
    b bad_mode
    .endm

/*
   vector table entry
   每个表项是128字节, align 7表示128字节对齐
 */
    .macro vtentry label
    .align 7
    b \label
    .endm

/*
 * Vector Table
 *
 * ARM64的异常向量表一共占用2048个字节
 * 分成4组,每组4个表项,每个表项占128字节
 * 参见ARMv8 spec v8.6第D1.10节
 * align 11表示2048字节对齐
 */
.align 11
.global vectors
vectors:
    /* Current EL with SP0
       当前系统运行在EL1时使用EL0的栈指针SP
       这是一种异常错误的类型
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Current EL with SPx
       当前系统运行在EL1时使用EL1的栈指针SP
       这说明系统在内核态发生了异常

       Note: 我们暂时只实现IRQ中断
     */
    vtentry el1_sync_invalid
    vtentry el1_irq_invalid
    vtentry el1_fiq_invalid
    vtentry el1_error_invalid

    /* Lower EL using AArch64
       在用户态的aarch64的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

    /* Lower EL using AArch32
       在用户态的aarch32的程序发生了异常
     */
    vtentry el0_sync_invalid
    vtentry el0_irq_invalid
    vtentry el0_fiq_invalid
    vtentry el0_error_invalid

el1_sync_invalid:
    inv_entry 1, BAD_SYNC
el1_irq_invalid:
    inv_entry 1, BAD_IRQ
el1_fiq_invalid:
    inv_entry 1, BAD_FIQ
el1_error_invalid:
    inv_entry 1, BAD_ERROR
el0_sync_invalid:
    inv_entry 0, BAD_SYNC
el0_irq_invalid:
    inv_entry 0, BAD_IRQ
el0_fiq_invalid:
    inv_entry 0, BAD_FIQ
el0_error_invalid:
    inv_entry 0, BAD_ERROR

string_test:
    .string "t"

.global trigger_alignment
trigger_alignment:
    ldr x0, =0x80002
    ldr x1, [x0]
    ret

单步调试如下
ARM64基础12:ARM64的异常模式及异常向量表
异常执行完后,返回
ARM64基础12:ARM64的异常模式及异常向量表
串口打印:

qemu-system-aarch64 -machine raspi4 -nographic -kernel benos.bin
booting at EL2
booting at EL1
printk init done
<0x800880> func_c
image layout:
  .text.boot: 0x00080000 - 0x000800d8 (   216 B)
  .text: 0x000800d8 - 0x0008329c ( 12740 B)
  .rodata: 0x0008329c - 0x000834ee (   594 B)
  .data: 0x000834ee - 0x00083828 (   826 B)
  .bss: 0x000838d8 - 0x000a3ce8 (132112 B)
Bad mode for Sync Abort handler detected, far:0x0 esr:0x2000000
test and:p=0x2
test or:p=0x3
test andnot:p=0x1
el=1

11 同步异常的解析

查看异常综合信息寄存器:ESR_ELx

ARM64基础12:ARM64的异常模式及异常向量表
当异常发生时,在异常向量表处,对应处理函数里,读取ESR的EC域,根据EC域分类,进一步解析ISS域具体异常原因:
ARM64基础12:ARM64的异常模式及异常向量表

失效地址寄存器FAR

FAR寄存器保存发生异常时刻的虚拟地址;
异常解析部分代码:

static const char * const bad_mode_handler[] = {
      "Sync Abort",
      "IRQ",
      "FIQ",
      "SError"
};

static const char *data_fault_code[] = {
    [0] = "Address size fault, level0",
    [1] = "Address size fault, level1",
    [2] = "Address size fault, level2",
    [3] = "Address size fault, level3",
    [4] = "Translation fault, level0",
    [5] = "Translation fault, level1",
    [6] = "Translation fault, level2",
    [7] = "Translation fault, level3",
    [9] = "Access flag fault, level1",
    [10] = "Access flag fault, level2",
    [11] = "Access flag fault, level3",
    [13] = "Permission fault, level1",
    [14] = "Permission fault, level2",
    [15] = "Permission fault, level3",
    [0x21] = "Alignment fault",
    [0x35] = "Unsupported Exclusive or Atomic access",
};

static const char *esr_get_dfsc_string(unsigned int esr)
{
    return data_fault_code[esr & 0x3f];
}

static const char *esr_class_str[] = {
    [0 ... ESR_ELx_EC_MAX]      = "UNRECOGNIZED EC",
    [ESR_ELx_EC_UNKNOWN]        = "Unknown/Uncategorized",
    [ESR_ELx_EC_WFx]        = "WFI/WFE",
    [ESR_ELx_EC_CP15_32]        = "CP15 MCR/MRC",
    [ESR_ELx_EC_CP15_64]        = "CP15 MCRR/MRRC",
    [ESR_ELx_EC_CP14_MR]        = "CP14 MCR/MRC",
    [ESR_ELx_EC_CP14_LS]        = "CP14 LDC/STC",
    [ESR_ELx_EC_FP_ASIMD]       = "ASIMD",
    [ESR_ELx_EC_CP10_ID]        = "CP10 MRC/VMRS",
    [ESR_ELx_EC_CP14_64]        = "CP14 MCRR/MRRC",
    [ESR_ELx_EC_ILL]        = "PSTATE.IL",
    [ESR_ELx_EC_SVC32]      = "SVC (AArch32)",
    [ESR_ELx_EC_HVC32]      = "HVC (AArch32)",
    [ESR_ELx_EC_SMC32]      = "SMC (AArch32)",
    [ESR_ELx_EC_SVC64]      = "SVC (AArch64)",
    [ESR_ELx_EC_HVC64]      = "HVC (AArch64)",
    [ESR_ELx_EC_SMC64]      = "SMC (AArch64)",
    [ESR_ELx_EC_SYS64]      = "MSR/MRS (AArch64)",
    [ESR_ELx_EC_IMP_DEF]        = "EL3 IMP DEF",
    [ESR_ELx_EC_IABT_LOW]       = "IABT (lower EL)",
    [ESR_ELx_EC_IABT_CUR]       = "IABT (current EL)",
    [ESR_ELx_EC_PC_ALIGN]       = "PC Alignment",
    [ESR_ELx_EC_DABT_LOW]       = "DABT (lower EL)",
    [ESR_ELx_EC_DABT_CUR]       = "DABT (current EL)",
    [ESR_ELx_EC_SP_ALIGN]       = "SP Alignment",
    [ESR_ELx_EC_FP_EXC32]       = "FP (AArch32)",
    [ESR_ELx_EC_FP_EXC64]       = "FP (AArch64)",
    [ESR_ELx_EC_SERROR]     = "SError",
    [ESR_ELx_EC_BREAKPT_LOW]    = "Breakpoint (lower EL)",
    [ESR_ELx_EC_BREAKPT_CUR]    = "Breakpoint (current EL)",
    [ESR_ELx_EC_SOFTSTP_LOW]    = "Software Step (lower EL)",
    [ESR_ELx_EC_SOFTSTP_CUR]    = "Software Step (current EL)",
    [ESR_ELx_EC_WATCHPT_LOW]    = "Watchpoint (lower EL)",
    [ESR_ELx_EC_WATCHPT_CUR]    = "Watchpoint (current EL)",
    [ESR_ELx_EC_BKPT32]     = "BKPT (AArch32)",
    [ESR_ELx_EC_VECTOR32]       = "Vector catch (AArch32)",
    [ESR_ELx_EC_BRK64]      = "BRK (AArch64)",
};

static const char *esr_get_class_string(unsigned int esr)
{
    return esr_class_str[esr >> ESR_ELx_EC_SHIFT];
}

void parse_esr(unsigned int esr)
{
    unsigned int ec = ESR_ELx_EC(esr);

    printk("ESR info:\n");
    printk("  ESR = 0x%08x\n", esr);
    printk("  Exception class = %s, IL = %u bits\n", esr_get_class_string(esr), (esr & ESR_ELx_IL) ? 32 : 16);

    if (ec == ESR_ELx_EC_DABT_LOW || ec == ESR_ELx_EC_DABT_CUR) {
        printk("  Data abort:\n");
        if ((esr & ESR_ELx_ISV)) {
            printk("  Access size = %u byte(s)\n", 1U << ((esr & ESR_ELx_SAS) >> ESR_ELx_SAS_SHIFT));
            printk("  SSE = %lu, SRT = %lu\n", (esr & ESR_ELx_SSE) >> ESR_ELx_SSE_SHIFT, (esr & ESR_ELx_SRT_MASK) >> ESR_ELx_SRT_SHIFT);
            printk("  SF = %lu, AR = %lu\n", (esr & ESR_ELx_SF) >> ESR_ELx_SF_SHIFT, (esr & ESR_ELx_AR) >> ESR_ELx_AR_SHIFT);
        }

        printk("  SET = %lu, FnV = %lu\n", (esr >> ESR_ELx_SET_SHIFT) & 3, (esr >> ESR_ELx_FnV_SHIFT) & 1);
        printk("  EA = %lu, S1PTW = %lu\n", (esr >> ESR_ELx_EA_SHIFT) & 1, (esr >> ESR_ELx_S1PTW_SHIFT) & 1);
        printk("  CM = %lu, WnR = %lu\n", (esr & ESR_ELx_CM) >> ESR_ELx_CM_SHIFT, (esr & ESR_ELx_WNR) >> ESR_ELx_WNR_SHIFT);
        printk("  DFSC = %s\n", esr_get_dfsc_string(esr));
    }
}

void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
{
    printk("Bad mode for %s handler detected, far:0x%x esr:0x%x- %s\n",
                      bad_mode_handler[reason], read_sysreg(far_el1),
                      esr, esr_get_class_string(esr));
    parse_esr(esr);  

}

正文完
 0
admin
版权声明:本站原创文章,由 admin 于2021-11-02发表,共计11652字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(一条评论)
粤ICP备2021172357号-1