程序进入handle_trap的解决方法(一)
1、异常现象
- 程序出现了下列trap的打印(打印右边的注释,是根据程序编译的.map和.lst文件得到的内容)
ra = 0x1ff04eb4 //_printf_i 函数内的一个代码地址
sp = 0x1ff0e4c0 //栈指针,指向当前栈顶位置
gp = 0x1ff0a7a0 //_sp地址(栈顶的地址)
tp = 0xa5a5a5a5
t0 = 0x1ff02056
t1 = 0xfffffff0
t2 = 0xa5a5a5a5
fp = 0x1ff0e610 //帧指针,指向当前栈帧的基址
s1 = 0x1ff09250
a0 = 0xfffe1265 //非法地址
a1 = 0x0
a2 = 0xfffe1264
a3 = 0x1ff0e748
a4 = 0x1ff0e60c
a5 = 0x1ff0e744
a6 = 0x0
a7 = 0x64
s2 = 0x1ff0e6a8
s3 = 0x1ff06634
s4 = 0x1ff09250
s5 = 0xfffe1265 //非法地址
s6 = 0xa
s7 = 0x25
s8 = 0x1
s9 = 0x7
s10 = 0xa5a5a5a5
s11 = 0xa5a5a5a5
t3 = 0xa5a5a5a5
t4 = 0xa5a5a5a5
t5 = 0xa5a5a5a5
t6 = 0xa5a5a5a5
mstatus = 0x9880
mepc = 0x1ff05e78 //memchr 函数内的一个代码地址
msubm = 0x80
mcause = 0x38000005 //这是一个异常
mdcause = 0x2
mtval = 0xfffe1265 //非法地址
stack trace:0x1ff067c8 //_svfiprintf_r 函数内的一个代码地址
stack trace:0x1ff01d3e
stack trace:0x1ff06994 //_svfiprintf_r 函数内的一个代码地址
stack trace:0x1ff04f8e //_vsniprintf_r 函数内的一个代码地址
stack trace:0x1ff01064
stack trace:0x1ff0209a //_puts 函数内的一个代码地址
stack trace:0x1ff020d4 //_printf 函数内的一个代码地址
stack trace:0x1ff03334 //tc_run_test_case 函数内的一个代码地址
stack trace:0x1ff038d6 //fpga_test_case_task 函数内的一个代码地址
stack trace:0x1ff01d3e
stack trace:0x1ff01422
stack trace:0x1ff01848
stack trace:0x1ff0104e
stack trace:end
2、名词解释
2.1、Stack Trace(堆栈跟踪)
-
是程序发生异常(或主动打印)时,输出的一系列方法调用链记录。它告诉你“程序在执行到哪一行代码时出了问题”,以及“这个方法是经由怎样的调用路径被触发的”。
-
阅读顺序: 从下往上读,就是调用的历史;从上往下读,就是异常传播的路径。
-
结合以上理论,可以知道异常的代码调用顺序如下:
fpga_test_case_task
tc_run_test_case
_printf
_puts
_vsniprintf_r
_svfiprintf_r
2.2、Exception Register(异常寄存器)
-
Exception Register 是当程序发生异常时,CPU或操作系统保存的异常现场信息,记录了异常发生瞬间的处理器状态。它与 Stack Trace 是互补的两种调试信息:
-
Stack Trace:程序的调用链路(逻辑层面的执行路径)
-
Exception Register:CPU的硬件状态(物理层面的执行现场)
(1)、mepc,记录异常发生时正在执行的指令地址
- 最重要!直接告诉你代码执行到了哪个内存地址
(2)、sp,栈指针
- 指向当前栈顶位置用于恢复函数调用栈、局部变量等
(3)、fp,帧指针
- 指向当前栈帧的基址,用于回溯调用链(Stack Trace 的基础)
(4)、mstatus,(需要查RISCV的手册)
- 状态寄存器 / 标志位
(5)、mcause,是 RISC-V 架构中的一个异常/中断原因寄存器
mcause = 0x38000005
- 最高位第31bit为0,表示这是一个异常
- 第0bit为1,指令地址未对齐
- 第2bit为1,非法指令
- 第27bit为1,加载虚拟地址未对齐
- 第28bit为1,加载虚拟访问故障
- 第29bit为1,指令虚拟地址未对齐
(6)、mtval,异常相关的额外信息(如错误地址)
- mcause 只告诉"发生了什么",mtval 告诉"具体是什么"(如错误地址、非法指令值)
mtval = 0xfffe1265
- 这个表示错误的地址,即程序运行过程中,发生了地址被篡改的行为
3、解决错误
- 根据程序的打印,先到程序异常前的位置打个断点,可以观察到group指针,name指针,case_list指针的地址都是正常的sram地址。

-
程序异常后,再观察,name地址、case_list等地址已经变成异常值,且恰好和mtval的值相等

-
可以观察到,group指针指向name指针,name指针指向"fpga_test_case_list"内容,
-
group指向的地址为0x1ff09afc,name指向的地址为0x1ff08d14
-
即0x1ff09afc地址存放的是name指针变量,0x1ff08d14地址存放的是"fpga_test_case_list"内容
-
name指针变量的值被篡改了,可以用 *(int *)0x1ff09afc的方式来得到name指针变量的值
-
在程序异常前,先打数据断点,再执行程序,调试时程序就会在异常的地方停下来,就能找到是哪段代码导致的异常。
-exec watch *(int *)0x1ff09afc
4、最终原因
- 代码流程类似下面
volatile uint8_t flag = 0;
uint32_t value[10] = {0};
//中断复位函数
void isr()
{
value[flag] = 10;
flag++;
}
void main()
{
while(flag != 10);
flag = 0;
}
(1)、数据竞争(Data Race)
- 主循环和ISR同时访问共享变量 flag,虽然使用了 volatile,但缺乏原子性保证:
// 主线程中
while(flag != 10); // 读取flag
flag = 0; // 写入flag
// ISR中
flag++; // 读取-修改-写入操作,非原子
(2)、flag++ 不是原子操作
ldrb r0, [flag_addr] ; 读取
add r0, r0, #1 ; 修改
strb r0, [flag_addr] ; 写入
-
如果主循环在这三条指令之间读取flag,可能读到中间状态。
-
volatile 只能防止编译器优化,不能保证原子性,也不能防止CPU乱序执行。
-
所以当主程序读到flag等于10的时候,中断可能还在写入11,就会发生数组越界的问题。
-
可以修改中断服务函数如下
//中断复位函数
void isr()
{
if(flag < 10)
{
value[flag] = 10;
flag++;
}
}
- 或者使用原子操作,临界区关中断,使用双缓冲/环形缓冲区等方式解决。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)