概述
在嵌入式程序发生错误时,准确的错误现场记录有助于高效定位和分析问题。Ameba SoC 提供专用的错误处理机制,包括崩溃转储(Crash Dump)和回溯(Backtrace)功能:
崩溃转储(Crash Dump):自动保存异常发生时的通用寄存器、系统寄存器及堆栈信息,用于还原错误现场。
回溯(Backtrace):基于崩溃位置,分析并还原导致异常的函数调用路径。
特性 |
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 异常栈 所示。
ARM-v8M 异常栈
RISCV-IMACF 异常栈
备注
stack 向下生长,且 以 4Byte 对齐。
回溯
函数回溯的核心原理是利用栈帧(stack Frame)结构——每次函数调用时,编译器会自动生成一段代码,在栈上开一个栈帧,用于保存返回地址、被调用函数要保存的参数以及局部变量等。其主要步骤如下:
异常现场捕获:异常发生后,需要保存异常指令地址(PC)、异常发生的栈(Stack)
寻找返回地址:根据函数当前压栈大小以及操作返回地址地址寄存器的指令,找出上一层调用函数。重复该过程,直到达到指定的回溯层数或者 stack 到底时,停止回溯。
如图 函数调用栈 所示,展示了在不同指令集架构下,函数的栈帧结构:
函数调用栈
函数栈结合 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。
异常号 |
名称 / 英文 |
原因简述 |
---|---|---|
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) 来标识和定位错误。常见错误涵盖访问非法内存、堆栈溢出、非法指令、环境调用等。
异常号/编号 |
名称 |
原因简述 |
---|---|---|
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 架构下,写数据(如 sb
、 sh
、 sw
、 sd
)时没有满足数据宽度对齐要求时,发生此类错误。
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异常例子 |
---|---|---|
地址不对齐 |
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 |