概述

在嵌入式程序发生错误时,准确的错误现场记录有助于高效定位和分析问题。Ameba SoC 提供专用的错误处理机制,包括崩溃转储(Crash Dump)和回溯(Backtrace)功能:

  • 崩溃转储(Crash Dump):自动保存异常发生时的通用寄存器、系统寄存器及堆栈信息,用于还原错误现场。

  • 回溯(Backtrace):基于崩溃位置,分析并还原导致异常的函数调用路径。

不同指令集架构 CPU 异常处理特点

特性

ARM v8M

RISC-V

回溯起点PC

由硬件压栈到栈上,需从栈中读取

存储在 mepc 寄存器中

寄存器保存

硬件自动保存部分核心寄存器(8个)

硬件不保存通用寄存器,需由软件在 Trap Handler 完成

帧指针 (FP)

通常是 R7

通常是 x8 (s0/fp)

返回地址 (RA)

LR (Link Register)

ra (Return Address, x1)

核心依赖

依赖硬件压栈机制和 Fault_Handler

依赖 Trap Handler 必须正确地保存所有寄存器

自动化工具

addr2line、GDB 等工具,结合 ELF/内存转储分析

addr2line、GDB 等工具同样适用,原理一致

小心

为了提升 SDK 的实际运行的性能,编译时,GCC 编译等级是 O2 或者 Os,这将默认开启 fomit-frame-pointer 编译选项。在开启该选项后, fp 寄存器 不会被保存,这意味着无法通过 fp 寄存器进行栈回溯。

崩溃转储

崩溃转储的核心原理是利用 CPU 自身的异常检测机制——当发生异常后,CPU 硬件自动保存异常发生时刻的关键信息,然后自动跳转到异常处理函数。其主要步骤如下:

  • 异常现场捕获:当系统发生异常(如硬 Fault、非法指令、访问异常等)时,CPU 会自动保存当前执行现场的关键信息,包括部分通用寄存器、程序计数器(PC)以及异常相关状态寄存器。

  • 特殊寄存器采集:可通过平台提供的专用异常寄存器(如 ARM 的 SCB 寄存器:CFSR、HFSR、MMFAR、BFAR,或 RISC-V 的 mepc、mcause、mtval 等)提取错误类型、发生地址等核心排障信息。

  • 堆栈采集:通过当前栈指针(ARM: MSP/PSP;RISC-V: sp)读取异常触发点的现场堆栈快照,为后续函数回溯及调用链分析提供基础数据。

  • 转储实现:在异常(Fault/Trap)处理流程中,将通用寄存器、特殊寄存器和堆栈数据结构化存储或输出(如串口/日志/闪存),供离线或远程错误分析使用。

提示

崩溃转储利用 CPU 自身的异常检测机制,这意味着以下程序异常不能通过该方法检测到:

  • 程序死循环或卡死:常见于逻辑错误、访问不存在的地址空间或者访问未使能时钟的 IP 等

  • 资源耗尽:常见于 Heap 空间耗尽导致 malloc 失败

发生异常时:

  • ARM Cortex-M 在硬件层面会保存 R0~R3,R12,LR,PC,xPSR 等寄存器。如果有 FPU,且在浮点数上下文中发生异常,还会保存浮点数相关寄存器。如图 ARM-v8M 异常栈 所示。

  • RISCV 在发生异常后,硬件并不会自动保存寄存器,需要在 Trap Handler 中自行保存寄存器。Ameba SDK 已实现 X0-X31,MSTATUS 寄存器的保存,如图 RISCV-IMACF 异常栈 所示。

../../rst_rtos/9_crash/figures/crash_arm_v8m_exception_stack.svg

ARM-v8M 异常栈

../../rst_rtos/9_crash/figures/crash_riscv_exception_stack.svg

RISCV-IMACF 异常栈

备注

stack 向下生长,且 以 4Byte 对齐。

回溯

函数回溯的核心原理是利用栈帧(stack Frame)结构——每次函数调用时,编译器会自动生成一段代码,在栈上开一个栈帧,用于保存返回地址、被调用函数要保存的参数以及局部变量等。其主要步骤如下:

  • 异常现场捕获:异常发生后,需要保存异常指令地址(PC)、异常发生的栈(Stack)

  • 寻找返回地址:根据函数当前压栈大小以及操作返回地址地址寄存器的指令,找出上一层调用函数。重复该过程,直到达到指定的回溯层数或者 stack 到底时,停止回溯。

如图 函数调用栈 所示,展示了在不同指令集架构下,函数的栈帧结构:

../../rst_rtos/9_crash/figures/crash_stack_frame.svg

函数调用栈

函数栈结合 ELF/AXF/BIN 文件中具体的栈操作指令,找到每一层栈帧的返回地址,即可回溯出完整函数调用路径。

小心

以下情况,回溯可能出错:

  • 执行中断过程中发生崩溃,此时由于缺少栈调用关系,无法完成栈回溯,只能打印异常发生时的寄存器。换言之,中断函数应避免调用关系不能太深。

  • 当开启 Secure 功能后,由于部分地址区间受到保护,回溯可能因此出错

解析与问题定位

用户在使用过程中发生异常,崩溃信息和栈回溯信息默认从 LOGUART 打印:

ARMv8-M

当 ARMv8M CPU 发生异常时,系统会输出详细的崩溃日志。如下 LOG 展示了 访问不存在的地址 产生的异常:

访问不存在的地址
========== Crash Dump ==========
------------Task Info------------
Fault on task <shell_task>
Task ID: 1
Task TCB:0x2000e720
Current State: 0 (Running)
Base Priority: 5
Current Priority: 5
Run Time Counter: 2
StackTop: 0x2000e630, StackBase: 0x2000d620, StackEnd: 0x2000e6e0, StackSize=1073(word)
Stack High WaterMark: 1001(word)
------------Task Info------------
Exception caught on 0e00ed0e
BFSR: [0x00000004] -> Bus fault is caused by imprecise data access violation
========== Register Dump ==========
[  LR] 0x0e00ed07
[  PC] 0x0e00ed0e
[xPSR] 0x21000000
[EXCR] 0xfffffffd
[ R0] 0x0000000b
[ R1] 0x0e01becb
[ R2] 0x00000041
[ R3] 0xdeadbeef
[ R4] 0x00000000
[ R5] 0x2ffffffc
[ R6] 0x00000001
[ R7] 0x30000000
[ R8] 0x0e01becb
[ R9] 0x0e01c202
[ R10] 0x0e01c204
[ R11] 0x0e01d920
[ R12] 0x00045418
==========KM4 Stack Dump ==========
Current Stack Pointer = 0x2000e630, and dump stack depth = 128
[0x2000e630] 0000000b 0e01becb 00000041 deadbeef
[0x2000e640] 00045418 0e00ed07 0e00ed0e 21000000
[0x2000e650] 2ffffffc 00000001 00000001 e000ed00
[0x2000e660] 20004080 00000000 00011721 20004161
[0x2000e670] 00011671 0e00196b 00000002 0e01d930
[0x2000e680] 2000407c 00000001 00000006 0e00bc77
[0x2000e690] 00000041 200040bc 20004161 00010219
[0x2000e6a0] 2000c3f0 20004160 00000000 2000c470
[0x2000e6b0] 11111111 0e00be41 00000000 01010101
[0x2000e6c0] 04040404 05050505 06060606 07070707
[0x2000e6d0] 08080808 09090909 10101010 0e01040b
[0x2000e6e0] a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[0x2000e6f0] a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5
[0x2000e700] 00000000 800001a0 25447cfa d2e11067
[0x2000e710] f5ffef97 38213187 53b359bc c8cadae8
[0x2000e720] 2000e630 79b46159 2000c570 2000c570
[0x2000e730] 2000e720 2000c568 00000006 2000c638
[0x2000e740] 2000c638 2000e720 00000000 00000005
[0x2000e750] 2000d620 6c656873 61745f6c 38006b73
[0x2000e760] 14c74143 3fc569e7 00ad1c45 2000e6e0
[0x2000e770] 00000001 09d35ecf 00000005 00000000
[0x2000e780] 00000000 00000000 00000000 00000002
[0x2000e790] 00000000 2000d00c 2000d074 2000d0dc
[0x2000e7a0] 00000000 00000000 00000000 00000000
[0x2000e7b0] 00000000 00000000 00000000 00000000
[0x2000e7c0] 00000000 00000000 00000000 00000000
[0x2000e7d0] 00000000 00000000 00000000 00000000
[0x2000e7e0] 00000000 00000000 00000000 00000000
[0x2000e7f0] 00000000 00000000 00000000 00000000
[0x2000e800] 00000000 00000000 00000000 00000000
[0x2000e810] 00000000 00000000 00000000 00000000
[0x2000e820] 00000000 00000000 00000000 00000000
========== Stack Trace ==========
Start stack backtracing for sp 0x2000e650, pc 0x0e00ed0e, lr 0x0e00ed07
/opt/rtk-toolchain/asdk-10.3.1-4354/linux/newlib/bin/arm-none-eabi-addr2line -e /home/user_name/sdk/amebalite_gcc_project/project_km4/asdk/image/target_img2.axf -afpiC 0x0e00ed0e 0x0e001966 0x0e00bc74 0x0e00be3c
========== End of Stack Trace ==========
========== End of Crash Dump ==========

[FAULT-A] SHCSR = 0x000f0002
[FAULT-A] AIRCR = 0xfa054000
[FAULT-A] CONTROL = 0x00000000

Bus Fault:
Secure State: 1

Stacked:
R0 = 0x0000000b
R1 = 0x0e01becb
R2 = 0x00000041
R3 = 0xdeadbeef
R12 = 0x00045418
LR = 0x0e00ed07
PC = 0x0e00ed0e
PSR = 0x21000000

Current:
EXC_RETURN = 0xfffffffd
MSP = 0x20003fe0
PSP = 0x2000e630
xPSR = 0xa0000005
CFSR  = 0x00000400
HFSR  = 0x00000000
DFSR  = 0x00000000
MMFAR = 0x00000000
BFAR  = 0x00000000
AFSR  = 0x00000000
PriMask = 0x00000000
SVC priority: 0x00
PendSVC priority: 0xe0
Systick priority: 0xe0
MSP_NS = 0x20004000
PSP_NS = 0x00000000
CFSR_NS  = 0x00000000
HFSR_NS  = 0x00000000
DFSR_NS  = 0x00000000
MMFAR_NS = 0x00000000
BFAR_NS  = 0x00000000
AFSR_NS  = 0x00000000
SVC priority NS: 0x00
PendSVC priority NS: 0x00
Systick priority NS: 0x00

LOG 信息分类及排查:

  • 任务信息:如果在任务执行过程中发生异常,才会打印任务控制块信息;裸机状态下不会打印任务信息。通过任务名可以确认出错的具体任务,另外根据其堆栈使用情况(如 High WaterMakrk),可以判断是否发生栈溢出。上述错误发生在 shell task 任务中。

  • 异常类型及原因:用户可根据打印信息确认当前发生的具体错误类型,如上述例子中打印结果是 BFSR: [0x00000004] -> Bus fault is caused by imprecise data access violation ,表明发生了总线错误。

  • 通用寄存器:发生异常时,通用寄存器记录了 CPU 正在访问的内容。其中 PC 寄存器指向出错的指令的地址,LR 寄存器指向上一层调用函数。根据 PC 值``e00ed0e`` ,寻找对应 asm 文件中的所在位置,可以发现该异常发生在 rtk_log_memory_dump_word()

0e00ecc8 <rtk_log_memory_dump_word>:
e00ecc8:     e92d 47f3       stmdb   sp!, {r0, r1, r4, r5, r6, r7, r8, r9, sl, lr}
...
e00ed0e:     2001            movs    r0, #1
...
  • 异常栈:记录了异常发生时的函数调用栈。函数栈内包含发生异常时被压栈的 R0~R3,R12,LR,PC,xPSR 等寄存器。

  • 栈回溯信息:利用栈回溯机制回溯到函数调用关系,用户可以直接复制该信息到编译 SDK 的环境下,直接获取导致出错的函数名及其调用关系。

>>> $ /opt/rtk-toolchain/asdk-10.3.1-4354/linux/newlib/bin/arm-none-eabi-addr2line -e /home/user_name/sdk/amebalite_gcc_project/project_km4/asdk/image/target_img2.axf -afpiC 0x0e00ed0e 0x0e001966 0x0e00bc74 0x0e00be3c
0x0e00ed0e: rtk_log_memory_dump_word at /home/user_name/sdk/component/soc/amebalite/swlib/log.c:152 (discriminator 3)
0x0e001966: cmd_dump_word at /home/user_name/sdk/component/at_cmd/monitor.c:136
0x0e00bc74: shell_cmd_exec_ram at /home/user_name/sdk/component/soc/amebalite/app/monitor/ram/shell_ram.c:68
0x0e00be3c: shell_task_ram at /home/user_name/sdk/component/soc/amebalite/app/monitor/ram/shell_ram.c:236
  • 系统寄存器:系统寄存器记录了栈指针、系统 Fault 功能、优先级设定等辅助信息。

小心

Ameba SoC 中的 KM4 CPU 支持栈回溯功能,而 KM0 暂不支持。

RISCV

当 RISCV CPU 发生异常时,系统会输出详细的崩溃日志。如下 LOG 展示了 命中调试断点 的异常:

命中调试断点
------------------------------------
Have a test on crash_dump and back trace
crash_SysBreak()
crash_SysBreak1()
crash_SysBreak2()==> Issue BREAK instruction
========== Crash Dump ==========
------------Task Info------------
Fault on task <shell_task>
Task ID: 1
Task TCB:0x2006ba40
Current State: 0 (Running)
Base Priority: 5
Current Priority: 5
Run Time Counter: 2
StackTop: 0x2006b5b8, StackBase: 0x2006b480, StackEnd: 0x2006ba00, StackSize=353(word)
Stack High WaterMark: 78(word)
------------Task Info------------
Exception caught on 0x0c00961e with reason [0x3] -> [Breakpoint]
========== Register Dump ==========
[mscratch] 0x00000000
[mepc]     0x0c00961e
[mcause]   0x00000003
[mtval]    0x00000000
[x0 -> zero] 0x00000000
[x1 -> ra] 0x0c00961e
[x2 -> sp] 0x2006b970
[x3 -> gp] 0x20068f24
[x4 -> tp] 0xffffffff
[x5 -> t0] 0xa5a5a5a5
[x6 -> t1] 0x000bee33
[x7 -> t2] 0xa5a5a5a5
[x8 -> s0/fp] 0x2006b980
[x9 -> s1] 0x00000002
[x10 -> a0] 0x0000002e
[x11 -> a1] 0x00000000
[x12 -> a2] 0x00000000
[x13 -> a3] 0x2006ba40
[x14 -> a4] 0x00000000
[x15 -> a5] 0x00000000
[x16 -> a6] 0x00200000
[x17 -> a7] 0x41014000
[x18 -> s2] 0x20000b04
[x19 -> s3] 0x00000005
[x20 -> s4] 0x00000006
[x21 -> s5] 0x20000be9
[x22 -> s6] 0x0c016374
[x23 -> s7] 0x0c013000
[x24 -> s8] 0xa5a5a5a5
[x25 -> s9] 0xa5a5a5a5
[x26 -> s10] 0xa5a5a5a5
[x27 -> s11] 0xa5a5a5a5
[x28 -> t3] 0x0000000f
[x29 -> t4] 0xa5a5a5a5
[x30 -> t5] 0xa5a5a5a5
[x31 -> t6] 0xa5a5a5a5
========== Stack Trace ==========
Start stack backtracing for sp 0x2006b970, pc 0x0c00961e
[frame #0] sp-> 0x2006b970, pc-> 0x0c00961e, stack_size-> 16, ra-> 0x0c00964a
[frame #1] sp-> 0x2006b980, pc-> 0x0c00964a, stack_size-> 16, ra-> 0x0c009674
[frame #2] sp-> 0x2006b990, pc-> 0x0c009674, stack_size-> 16, ra-> 0x0c0096a2
[frame #3] sp-> 0x2006b9a0, pc-> 0x0c0096a2, stack_size-> 16, ra-> 0x0c004544
========== End of Stack Trace ==========
========== End of Crash Dump ==========

LOG 信息分类及排查:

  • 任务信息:任务执行过程中发生异常,才会打印任务控制块信息;裸机状态下不会打印任务信息。通过任务名可以确认上述错误(调试断点是 RISCV 编号为 3 的异常)发生在 shell task 任务中。

  • 异常类型及原因:用户可根据打印信息确认当前发生的具体错误类型,如上述例子中打印结果是 Exception caught on 0x0c00961e with reason [0x3] -> [Breakpoint] ,表明发生了调试异常,该异常由 ebreak 指令触发。

  • 通用寄存器:通用寄存器记录了 CPU 发生异常前正在访问的内容。其中 MEPC 寄存器指向出错的指令的地址,X1(RA) 寄存器指向上一层调用函数。根据 PC 值 0x0c00961e ,寻找对应 asm 文件中的所在位置,可以发现该异常发生在 crash_SysBreak2()

0c009602 <crash_SysBreak2>:
c009602:     1141                    c.addi  sp,-16
c009604:     c606                    c.swsp  ra,12(sp)
c009606:     c422                    c.swsp  s0,8(sp)
c009608:     0800                    c.addi4spn      s0,sp,16
c00960a:     0c0157b7                lui     a5,0xc015
c00960e:     cd878593                addi    a1,a5,-808 # c014cd8 <__func__.2>
c009612:     0c0157b7                lui     a5,0xc015
c009616:     bbc78513                addi    a0,a5,-1092 # c014bbc <pmap_func+0x648>
c00961a:     ebefe0ef                jal     ra,c007cd8 <__wrap_printf>
c00961e:     9002                    c.ebreak  # Exception Location
c009620:     0001                    c.addi  zero,0
c009622:     40b2                    c.lwsp  ra,12(sp)
c009624:     4422                    c.lwsp  s0,8(sp)
c009626:     0141                    c.addi  sp,16
c009628:     8082                    c.jr    ra
  • 异常栈:记录异常发生时的函数调用栈。此处并未打印,用户可修改 CONFIG_DEBUG_BACK_TRACE#undef 来打印栈。

  • 栈回溯信息:利用栈回溯机制回溯函数调用关系,用户可以利用分析出的 PC 值,在 asm 文件中寻找出错的函数名及其调用关系。

常见错误

ARMv8M 常见错误

ARMv8-M 常见的 FAULT 有总线错误、用法错误、内存管理错误等。当 CPU 实现 Secure 扩展后,使用过程中也会出现 Secure Fault。

ARMv8-M 常见错误

异常号

名称 / 英文

原因简述

3

HardFault

所有不可屏蔽的意外错误,无法由其他fault覆盖

4

MemManage Fault

内存管理错误,代码/数据访问权限出错

5

BusFault

总线访问出错(无效地址等)

6

UsageFault

用法错误(非对齐、非法指令、栈溢出等)

7

SecureFault

安全错误(非法访问受到保护的地址)

备注

  • KM0 不支持总线错误、用法错误、内存管理错误,所有 Fault 统一由 Hard Fault 处理

  • 如果在 SCHSR(System Handler Control and State Register)中未开启总线错误、用法错误、内存管理错误的使能位,则发生的 Fault 全部由 Hard Fault 处理

  • 如果在处理用法错误、内存管理错误时再次产生 Fault,则会升级为 Hard Fault 进行处理。

  • 内存管理错误一般发生在 MPU 使能后,访问受到保护的地址空间

总线错误

总线错误中比较常见的错误只有一个,即非精确的数据访问违例(Imprecise data access violation)。该错误通常是因为访问不存在的地址而产生,可能的场景如下:

  • SRAM 的地址区间仅有 0x2000_0000 ~ 0x2008_0000,共 512KB,但是却访问超过该地址区间的地址(如 0x2FFF_FFFF)

  • 从存储器中读取到的错误的 地址 (比如 0xA54E_D25A,该地址未定义)导致 CPU 在访问该地址时产生错误。存储器中读到错误地址,常见原因为:

    • 存储器未上电:例如代码在 SRAM 中,但是需要从 FLASH 读取指令,FLASH 却未上电

    • 存储器时钟不匹配:例如存储器需要 20MHz 时钟,但是实际设置却是 10MHZ

    • 存储器本身存在坏块

  • 栈内存储的地址被破坏,指向未定义的地址区间

用法错误

用法错误中常见的错误如下:

  • 未定义的指令(undefined instruction)。从存储器中读取到的 指令 不符合指令集约定,常见原因为:

    • 存储器未上电:例如代码在 SRAM 中执行,但是下一条指令需要从 FLASH 读取指令,FLASH 却未上电

    • 存储器时钟不匹配:例如存储器需要 20MHz 时钟,但是实际设置却是 10MHZ

    • 存储器本身存在坏块

__ASM volatile(".hword 0xde00\n");
  • 栈溢出(stack overflow)。栈溢出通常是因为某个函数内部有较大的局部变量,函数调用加上局部变量的大小已经超过分配的栈大小。例如 Audio 播放过程中可能使用较大的数组,该数组可使用全局变量,否则产生栈溢出。

void crash_U_StackOverflowTask(void *param)
{
      (void)param;
      uint8_t CPU_RunInfo[512];//Local variable
      /* Infinite loop */
      while (1) {
              memset(CPU_RunInfo, 0, 512);

              rtos_time_delay_ms(200); /* delay 500 tick */

      }
}

void crash_U_StackOverflow(void)
{
      if (rtos_task_create(NULL, ((const char *)"example_player_thread"), crash_U_StackOverflowTask, NULL, 32, 1) != RTK_SUCCESS) {
              DiagPrintf("StackOverflowTest task creates failed!\r\n");
      }
}
  • 无效的状态(invalid state)。该错误通常是因为 PC 指令不正确,BIT0 为 0,强行从 Thumb 状态跳转到 ARM 状态。

__ASM volatile("MOV     R0,   0x20000000        \n\t"
                             "BX      R0                      \n\t"); //From Thumb state to Arm state
  • 非对齐访问(unaligned access):开启非对齐访问 TRAP 功能(0xE000ED14[3] = 1)后,访问非对齐地址会产生 Fault。

volatile int *p;
volatile int value;
/* bit3: UNALIGN_TRP */
volatile int *SCB_CCR = (volatile int *) 0xE000ED14;  // SCB->CCR
*SCB_CCR |= (1 << 3);

p = (int *) 0x20000001;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);
p = (int *) 0x20000004;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);
p = (int *) 0x20000003;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);

备注

KM4 默认支持非对齐访问,KM0 不支持非对齐访问

内存管理错误

内存管理错误一般发生在 MPU 功能开启后。常见的内存管理错误是数据访问违例(data access violation)。例如,MPU 设置地址区间[0x20050020,0x20050200]的区间为只读,如果强行向该区间地址写入内容,触发数据访问违例。

安全错误

安全错误一般发生在支持 secure 扩展的 CPU 上(KM4),且 Trustzone 功能启用后。安全错误中最常见的错误是属性单元违例(Attribution unit violation),可能发生该错误的场景有

  • 从非安全世界显式地访问受 Trustzone 保护的地址区间。例如,地址区间[0, 0x20000000]受到 Trustzone 保护,直接访问该地址区间将会触发属性单元违例。

小心

  • 空指针:KM4 开启 Trustzone 后,地址 0x00000000 会受到 Trustzone 保护,如果某个函数执行过程中,指针为空,此时直接指向 0x00000000,此时将触发属性单元违例。

RISC-V 常见错误

RISC-V 架构在异常处理机制上与 ARM 有较大不同。RISC-V 使用 异常号(Exception Code)陷入原因(Cause) 来标识和定位错误。常见错误涵盖访问非法内存、堆栈溢出、非法指令、环境调用等。

RISC-V 常见异常与错误

异常号/编号

名称

原因简述

0

Instruction address misaligned

指令地址未对齐访问

1

Instruction access fault

指令访问错误(查找指令时访问了无效物理内存)

2

Illegal instruction

非法或未实现的指令

3

Breakpoint

断点异常,通常用于调试

4

Load address misaligned

数据加载操作时地址未对齐

5

Load access fault

数据加载(读取)时访问无效或禁止的内存

6

Store/AMO address misaligned

数据存储/原子操作时地址未对齐

7

Store/AMO access fault

数据存储/原子操作访问非法或禁止的内存

8

Environment call from U-mode

用户模式下环境调用(如ecall指令)

9

Environment call from S-mode

超级用户模式下环境调用

11

Environment call from M-mode

管理员(Machine)模式下环境调用

12

Instruction page fault

指令访问虚拟页出错(如页不存在、无权限等)

13

Load page fault

数据加载虚拟页出错

15

Store/AMO page fault

数据存储/原子操作虚拟页出错

备注

KR4 是 RISCV-IMACF 架构的 CPU,仅实现了用户模式和管理员模式,超级用户模式未实现。

指令非对齐访问

指令未对齐访问发生在 PC 寄存器的值的 BIT0 不为 0 的情况下,也就是尝试访问非 2Byted 对齐的地址。例如尝试跳转到 crash_unaligned1 + 1 的位置:

__ASM volatile(
        "crash_unaligned0:                                              \n\t"
        "la     t1, crash_unaligned1                    \n\t"
        "addi   t1, t1, 1                                               \n\t" // Set PC unaligned
        "jalr   t1                                                              \n\t"
        "crash_unaligned1:                                              \n\t"
);
  • 如果实现了压缩指令集(16BIT 指令集),PC 地址可以 2Byte 或者 4Byte 对齐

  • 如果仅实现的是基本整数指令集,PC 地址必须 4Byte 对齐

小心

编译器一般不会产生非对齐的 PC 地址,除非特地制造此类 case。

指令访问错误

指令访问错误可能发生在以下场景:

  • 尝试强制切换模式,执行非当前模式的代码。如下示例展示了强制切换到 User 模式,但是执行 Machine 模式下的代码

    __ASM volatile(
            "csrrw  t5, mstatus,zero                        \n\t" // Read mstatus
            "la     t1, 0x00                                        \n\t"
            "slli   t1, t1, 11                                  \n\t"
            "xor    t5, t5, t1                                      \n\t" // Clear Mstatus.MPP
            "csrrw  zero,mstatus,t5                     \n\t"
            "la     t0, crash_temp                          \n\t"
            "csrw   mepc, t0                                        \n\t"
            "mret                                                       \n\t" // Return
            "crash_temp:                                            \n\t"
    );
    
  • 跳转到仅支持数据访问的地址区间,例如跳转到 UART 等寄存器配置的地址区间。

非法指令错误

非法指令错误可能发生的场景如下:

  • 执行不支持的指令集。例如在不支持浮点数的 CPU 上执行浮点指令

  • 存储器损坏、时钟设置异常或者无效,导致读出的指令不合法或者无效

以下示例展示了一条不合法指令:

__ASM volatile(".word 0xFFFFFFFF       \n\t");

调试断点异常

RISCV 使用 ebreak 指令触发断点异常,主要用程序调试。

__ASM volatile("EBREAK");

数据加载非对齐

在 RISC-V 架构下,对多 Byte 数据的加载(读操作,如 lw/ld 等指令)要求目标地址按照数据宽度对齐,否则会产生 Load address misaligned 异常。

如下例子展示了从非对齐的地址读取数值,前两个都未对齐会产生 Fault:

int a =*((volatile u16 *) 0x20000FFD);   // addr not 4-byte aligned
u16 b =  *((volatile u16 *) 0x20000FFD); // addr not 2-byte aligned
u8 c =  *((volatile u8 *) 0x20000FFD);   //OK

从内存中读取数据,数据类型宽度一定要和地址对齐,比如:

  • 4 字节宽度的数据(uint32_t/32 位 int),其地址要是 4 的倍数(0x0, 0x4, 0x8, 0xC, …)。

  • 2 字节宽度(uint16_t/16 位 int),其地址要是 2 的倍数(0x0, 0x2, 0x4, …)。

数据访问错误

数据访问错误发生在读操作时,地址无效或者访问错误引发的错误, mcause = 5

uint32_t *p = (uint32_t *)0x90000000;  // 0x90000000 未被映射
uint32_t  v = *p; // 此操作会导致 load access fault

访问一些未映射的地址区间同样会产生此类错误。

数据存储非对齐

在 RISC-V 架构下,写数据(如 sbshswsd )时没有满足数据宽度对齐要求时,发生此类错误。

  • sw 指令要求写的地址 4Byte 对齐

  • sh 指令要求写的地址 2Byte 对齐

  • sd 指令要求写的地址 8Byte 对齐

    uint8_t buffer[10];
    uint32_t *a = (uint32_t *)(buffer + 1); // a=buffer+1, 假设 buffer=0x1000,则 a=0x1001
    uint16_t *b = (uint16_t *)(buffer + 1); // b=buffer+1, 假设 buffer=0x1000,则=0x1001
    uint8_t *c = (uint8_t *)(buffer + 1);
    
    *a = 0x12345678; // 此处会触发 store address misaligned 异常
    *b = 0x1234; // 此处会触发 store address misaligned 异常
    *c = 0x12; // OK
    

数据存储错误

数据存储错误发生在写操作时,地址无效、不合法(无权限)或者总线错误,就会产生该异常。

uint32_t *p = (uint32_t *)0x90000000; // 0x90000000 无映射
*p = 0x12345678; // 触发 Store access fault

备注

mtval 通常保存异常发生时的虚假或非法地址,但非对齐异常时一定是未对齐指令/数据的地址,访问故障类异常时大多也是出错地址(但有的实现可能填 0)。

两种架构的异同点

RISC-V 和 ARMv8-M 异同点

类型

RISC-V异常例子

ARMv8-M异常例子

地址不对齐

Instruction/Load/Store misaligned

UsageFault: Unaligned Access

访问违规/无效地址

Load/Store Access Fault

MemManage/BusFault

非法/未定义指令

Illegal instruction

UsageFault: Undefined Instr.

系统调用/软件中断

Environment call (ecall)

SVCall(SVC)

调试中断

Breakpoint

Debug Monitor/Breakpoint

页表错误

Page Fault系列

N/A(Cortex-M无MMU,仅MPU)

栈溢出

访问违规引发的Fault

UsageFault: Stack overflow