当前位置:   article > 正文

【ARMv8/v9 异常模型入门及渐进4 - ARM64 异常概念/异常处理流程/异常向量/汇编代码】_arm 异常serror

arm 异常serror


请阅读【ARMv8/v9 ARM64 System Exception】



上篇文章:ARMv8/v9 异常模型系列 3 - Process State介绍
下篇文章:ARMv8/v9 异常模型系列 5 - IRQ 异常处理流程

1. ARMv8 异常向量表

ARM芯片手册通常使用 ELn 来表示第 n个异常等级。
在 ARMv8 架构的AArch64时,每种 EL 都具有独立的16个Entires, 这16个 Entires分为四类异常:

IRQ 
FIQ 
SError 
Synchronous
  • 1
  • 2
  • 3
  • 4

根据触发一种异常时是否会产生EL级别的迁移和使用产生迁移时使用的是AArch64还是AArch32的指令集又具有四种不同的区域。

  • EL0 是用户态。
  • EL1 是内核态也就是特权模式。
  • EL2 是虚拟化的hypevisor模式。
  • EL3 是安全 monitor。

当在EL0产生异常而未产生EL的迁移时,使用的是SP0保存的栈空间地址。
而在除EL0之外的ELn产生异常而未导致EL的迁移时使用的是对应的SPn保存的栈空间地址。
在AArch64中,EL的迁移只能是从低EL向高EL进行迁移,如果使用AArch64指令集和AArch32指令集时产生了异常并且也发生了EL的迁移时,则使用的是将迁移到的EL对应的SPn

1.1 ARMv8-A Exception Handling

异常主要是分成2大类,一类是同步,另外一类是异步

  • 同步异常
    导致异常发生的原因是执行了某个指令,异常返回地址就指明了是因为执行了那个“圈套”指令导致的,这个异常发生是精确的。
    • 同步异常的产生是和cpu core执行的指令异常或试图改变执行权限引起的异常
    • 返回地址是硬件保存下来并提供给handler,以便进行异常返回现场的处理。
    • 同步异常分两种:
      • 一是abort类,例如未定义的指令、data abortprefetch instruction abort、SP未对齐异常,debug exception等等。
      • 另一种是正常指令执行造成的,包括SVC/HVC/SMC指令,这些指令的使命就是产生异常,改变执行权限。

  • 异步异常
    这个异常的发生不是因为执行了某个指令而中的圈套,异常返回地址没法指明是中了哪条指令的圈套,这个异常发生是不精确的。
    • 异步异常可以理解为中断,CPU是不可预知的。
    • 异常和 CPU 执行的指令无关。
    • 返回地址是硬件保存下来并提供给 handler,以便进行异常返回现场的处理。
    • 根据这个定义 IRQ、FIQ 和 SError interrupt 属于 asynchronous exception。

同步异常就是说中了指令的圈套,这样的,这些异常的发生是比较精准的。
而异步异常是和运行的指令没有关系,因为它是被中圈套。在ARMv7的那些异常,比如 data abort等,就符合这里的同步异常,而IRQ 和 FIQ 中断就符合异步异常。

1.1.1 什么是精确异常

先来说说什么是非精确异常?:在多发射乱序执行的流水线 CPU 上,从指令进入流水线到异常事件的发生,期间要经过若干流水级,此时 PC 的值已指向其后的某条指令,在实现非精确异常的 CPU 上就把此时的 PC 值作为引起异常指令的所在,也就是记录异常指令的PC并非真正的引起异常的指令所在,而是其后面的某条指令所在。

精确异常(precise exception),也就是记录异常指令的PC并非真正的引起异常的指令所在,而是其后面的某条指令所在。实现精确异常的 CPU,在最后指令提交时 (commit) 按指令流的顺序提交,异常的抛出也在该指令提交时,这样就能精确计算出引起异常的指令相对于当前 PC 的偏移,从而保证精确异常。不管是何类异常,记录异常指令的PC之前的所有指令都会被执行完成 (commit),之后的指令不会被执行。

在 AArch64中,除了SError interrupt 这种 exception,其他的 exception 都是 precise exception。

1.1.2 SPSR_ELn Register

进入异常时会将 PSTATE,即PE状态放在异常等级对应 的 SPSR_ELn

SP位: 栈指针寄存器选择位,对于Aarch64,栈指针寄存器有四个,EL0/LE1/EL2/EL3,但是SP位只有1bit, 只能指示使用SP_EL0还是SP_ELx,所以就需求配合其他域来使用,这时就出现了SP_ELx域。

对于EL1/EL2/EL3三个栈指针是怎么选择的当异常发生时,现根据PSTATE中的SP位选决定使用是否使用SP_EL0,然后再根据PSTATE 中的 EL 2 bit决定到对应的异常等级的栈指针SP_ELx(x>0)。

一般异常等级里执行程序时,都会再将SP位清零,也就是继续用SP_EL0

为什么不直接用SP_ELx
如果当ELx的异常发生了,然后用上了栈指针寄存器SP_ELx,然后就开始执行处理的指令流,然后正在处理过程中,又发生了一个同级的异常。栈指针又立马选到了对应的异常等级的栈指针,这就是为何EL0的SP栈指针寄存器单独用了一个标志位来表示。

AIF 位: 分别为 SErrorIRQFIQ 屏蔽位,置位后对应中断不能发生。
在AArch64中,调用ERET从一个异常返回时,会将SPSR_ELn 恢复到 PSTATE中。

1.1.3 Exception Handling Flow

  • 处理器的状态保存到对应(target)的异常等级的 SPSR_ELn 寄存器里, 如果当前的异常发生在EL1,则将 PSTATE 的状态保存到SPSR_EL1中;
  • 返回地址(PC)保存到对应的异常等级的 ELR_ELn 寄存器里,n 对应异常等级;
  • PSTATE 寄存器里的 D,A,I,F 域都设置为1,关掉其他异常,以防止处理异常时被打断了;
  • 如果是同步异常,根据 ESR_ELn 寄存器的值找到对应的原因;
  • 设置一下栈指针,指向对应异常等级里的栈;
  • 迁移到对应的异常等级,然后跑到异常向量表里;
  • ELR_ELn 从取出地址放到 PC、从 SPSR_ELn 取出状态放到 PSTATE
AArch64 state异常处理流程说明
1、保存 PSTATE 数据到SPSR_ELx,(x = 1,2,3)异常返回时需要从SPSR_ELx中恢复 PSTATE
2、保存异常进入地址到 ELR_ELx同步异常(und/abt等)是当前地址,而异步异常(irq/fiq等)是下一条指令地址64位架构 LR 和 ELR 是独立分开的,这点和 32 位架构有所差别
3、保存异常原因信息到ESR_ELxESR_ELx.EC代表 Exception Class,关注这个bit
4、PE 根据目标 EL 的异常向量表中定义的异常地址强制跳转到异常处理程序跳转到哪个 EL 使用哪个向量偏移地址又路由关系决定
5、堆栈指针 SP 的使用由目标 EL 决定(SPSR_ELx.M[0] == 1) ? h(ELx): t(EL0)

1.2 Excepiton Vectors

ARMv7 的异常向量表,每个表项只有 4 个字节,只能存放一条跳转指令,但 ARMv8 里升级了,每个表项需要 128个字节,这样可以存放 32 条指令。ARMv8 指令集里一条指令的位宽是 32bit 的,而不是64bit

Q1: 异常向量表里要包含 4 组,每一组包含4个表项。这怎么理解?
A:ARMv8 有 4 个异常级别,每一个异常级别对应一个 VBAR(Vector Base Address Register) 寄存器(VABR_EL1, VABR_EL2 ,VABR_EL3),用来指向异常向量表的基地址,每一个异常向量表的大小为128个字节,也即可以存放32条指令;

同时每一个异常向量表会分为4组,每一组包含4 种异常,如下表 1-1所示。

在这里插入图片描述

表 1-1

Q2:current EL with SP_EL0是啥意思呢?
A:当系统运行在EL1的时候,然后使用EL0SP,貌似还没有这种场景。
current EL with SP_EL1,这是说系统运行在EL1里,SP也是使用EL1,那就是在内核态里发生了异常。

Q3:Current Exeception level 是什么意思?
A:比如当前系统只运行Linux内核不包括虚拟化或者安全特性,那最高的EL等级是EL1,那么它就一定有EL0。所以上面说的Current EL就是说的当前系统最高的EL等级。

如果没有使用 secure monitor 状态和 hvc 状态时,OS 运行在内核态时(EL1)发生了 IRQ 中断,这时候应该跳转到异常表里的0x280处。

Q4:如果 OS 运行在用户态并且正在执行32bit的app,发生了 IRQ 中断,它应该跳转到哪里呢?
A:应该这时应该跳转到0x0x680处。

ARMv8 有4张向量表,每张向量表有4中异常:同步异常、irq异常、fiq异常、系统错误异常,而4张表分别对应:

  • 发生中断时,异常等级不发生变化,并且不管怎么异常模式,sp只用SP_EL0
  • 发生中断时,异常等级不发生变化,并且sp用对应异常私有的SP_ELn
  • 发生中断时,异常等级发生变化,这种情况一般是用户态向内核态发生迁移,当前表示64位用户态向64位内核态发生迁移;
  • 发生中断时,异常等级发生变化,这种情况一般是用户态向内核态发生迁移,当前表示32位用户态向64位/32位内核发生迁移。

1.2.1 向量表的配置

Q:关于异常向量表,还有一个问题需要考虑,那就是异常向量表放在哪里?
A:ARM v8里提供了VBAR(Vector Base address register), ARMv8中,EL1,EL2,EL3都具有独立的 VBAR寄存器,该寄存器就是用于存放各VBAR_ELn 的异常向量表的基地址。当发生异常时,如果该异常产生了EL的迁移,那么完成迁移操作之后会到迁移到的 EL 中的VBAR寄存器中找到向量表的基地址,然后命中对应的 handler.

Linux内核代码(以runninglinux_4.0为例)。代码路径是在arch/arm64/kernel/entry.S文件里。

       .align  11
ENTRY(vectors)
    /* 第1处 */
    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
   
    /* 第 2 处 */
    kernel_ventry   1, sync                         // Synchronous EL1h
    kernel_ventry   1, irq                          // IRQ EL1h
    kernel_ventry   1, fiq_invalid                  // FIQ EL1h
    kernel_ventry   1, error_invalid                // Error EL1h
    
     /* 第 3 处 */
     kernel_ventry   0, sync                    // Synchronous 64-bit EL0
     kernel_ventry   0, irq                     // IRQ 64-bit EL0
     kernel_ventry   0, fiq_invalid             // FIQ 64-bit EL0
     kernel_ventry   0, error_invalid           // 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_invalid_compat, 32    // FIQ 32-bit EL0
    kernel_ventry   0, error_invalid_compat, 32  // Error 32-bit EL0
#else
    /*第 4 处*/
    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
END(vectors)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

kernel_ventry 是宏,翻译后的函数名是(以第2处为例):


/*
 * 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
.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 == 64
        mrs     x30, tpidrro_el0
        msr     tpidrro_el0, xzr
        .else
        mov     x30, xzr
        .endif
.Lskip_tramp_vectors_cleanup\@:
        .endif

        sub     sp, sp, #S_FRAME_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 + 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

上面汇编代码 b el\()\el\()_\label 会转化成下面内容:

el1_sync
el1_irq
el1_fiq_invalid
el1_error_invalid
  • 1
  • 2
  • 3
  • 4

上图中 4 处对应EL1表项的 4 组。上文中说过每个表项是128个字节吗,但是从上图中并卡不出来,注意上面“ENTRY(vectors)”, 这里使用的是align伪指令,它使用align 7,表示按照2的7次方来对齐,2的7次方是128。关于ARM伪指令的详细介绍见 【ARM64 常见汇编指令学习系列文章】

\* Vectory entry *\
.macro ventry label
    .align 7
    b \label
.endm
  • 1
  • 2
  • 3
  • 4
  • 5

上篇文章:ARMv8/v9 异常模型系列 3 - Process State介绍
下篇文章:ARMv8/v9 异常模型系列 5 - IRQ 异常处理流程

推荐阅读
http://blog.chinaunix.net/uid-69947851-id-5830546.html

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号