参考
stm32的栈监控与HardFault_Handler问题排查.csdn
崩溃现象
程序崩溃后会跳转到
bash
ps7_cortexa9_0\standalone_domain\bsp\ps7_cortexa9_0\libsrc\standalone_v7_3\src\xil_exception.c
异常总览表
| 异常类型 | 默认处理函数(Xilinx BSP) | CPU进入时机 | 典型触发场景 | 向量表默认绑定 | 关键寄存器/信息 | 默认行为 | 常见根因(重点) | 架构说明 |
|---|---|---|---|---|---|---|---|---|
| Reset(复位) | 启动汇编入口 | 上电 / 硬复位瞬间 | 上电、复位按键、看门狗复位 | Boot ROM / Reset Vector | - | 从头启动系统 | 硬复位、看门狗复位 | ARM32/64均有,最高优先级 |
| Undefined Instruction | Xil_UndefinedExceptionHandler() |
指令译码阶段(ID) | 执行非法机器码、跑飞执行数据区 | 默认绑定该函数 | UndefinedExceptionAddr | 打印地址 + 死循环 | PC跑飞、Flash损坏、函数指针错误、代码被破坏 | ARM32/64均支持(ARM64为同步异常) |
| SVC / SWI(系统调用) | Xil_ExceptionNullHandler()(裸机) |
执行 SVC 指令瞬间 | OS系统调用、特权级切换 | 默认空处理 | - | 裸机死循环 / OS接管 | 正常系统调用 / 误执行SVC | ARM32独立 / ARM64同步异常 |
| Prefetch / Instruction Abort | Xil_PrefetchAbortHandler() |
取指阶段(IF) | PC跳飞、执行非法代码地址 | 默认绑定该函数 | PrefetchAbortAddr / IFSR | 打印寄存器 + 死循环 | 栈溢出、函数指针错误、return地址破坏、跳转非法地址、MMU禁止执行 | ARM32为Prefetch Abort / ARM64为Instruction Abort |
| Data Abort | Xil_DataAbortHandler() |
访存阶段(MEM) | 读写非法内存或外设地址 | 默认绑定该函数 | DataAbortAddr / DFSR | 打印地址 + 死循环 | 野指针、堆损坏、栈溢出、free后使用、访问未映射外设 | ARM32/64均存在(ARM64为同步异常) |
| IRQ(普通中断) | 用户注册 handler(默认NullHandler) | GIC中断分发后 | UART、Timer、GPIO等外设中断 | 未注册则NullHandler | GIC CPU Interface寄存器 | 未注册→死循环 | 中断未清、未注册handler | ARM32/64均有 |
| FIQ(快速中断) | 用户注册 handler(默认NullHandler) | GIC高优先级通道 | 高实时控制、电机控制 | 未注册则NullHandler | GIC高优先级路径 | 未注册→死循环 | 高优先级实时任务 | ARM32/64均有 |
| SError(系统错误) | Xil_SErrorAbortHandler()(AArch64) |
异步触发(与指令无关) | ECC错误、AXI总线错误、cache一致性错误 | AArch64向量表绑定 | 系统错误状态寄存器 | 打印日志 + 死循环 | DDR ECC损坏、AXI bus fault、硬件一致性错误 | ARM64常见;ARM32通常归入Abort类 |
问题1
中断专用栈被破坏了
在中断里调用 AT_println("ke %d", code); 崩溃进入 Xil_DataAbortHandler
改成 AT_println("ke"); 正常
主循环 注释 BspGetMillis 又不崩
把BspGetMillis 代码拷贝到主循环也不崩
解决
修改 链接脚本 lscript.ldx
bash
# 修改
_IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 1024;
# 为
_IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 4000;
| 分组 | 键 | 值 | 功能 | 解释 |
|---|---|---|---|---|
| 栈/堆配置 | _STACK_SIZE |
0x2000 |
主栈大小 | System/User 模式栈大小(8KB) |
_HEAP_SIZE |
0x2000 |
堆大小 | malloc/new 动态内存 | |
_ABORT_STACK_SIZE |
1024 |
Abort栈 | Data Abort / Prefetch Abort | |
_SUPERVISOR_STACK_SIZE |
2048 |
SVC栈 | Supervisor模式 | |
_IRQ_STACK_SIZE |
4000 |
IRQ栈 | 中断模式 | |
_FIQ_STACK_SIZE |
1024 |
FIQ栈 | 快速中断模式 | |
_UNDEF_STACK_SIZE |
1024 |
Undefined栈 | 未定义指令异常 | |
| MEMORY内存区域 | ps7_ddr_0 |
0x00100000~0x3FFFFFFF |
DDR区域 | 主程序运行内存 |
ps7_qspi_linear_0 |
0xFC000000~0xFCFFFFFF |
QSPI线性映射 | Flash线性访问 | |
ps7_ram_0 |
0x00000000~0x0002FFFF |
OCM低地址 | On-Chip Memory | |
ps7_ram_1 |
0xFFFF0000~0xFFFFFE00 |
OCM高地址 | 高地址映射OCM | |
| 程序入口 | ENTRY(_vector_table) |
_vector_table |
程序入口 | CPU复位后跳转地址 |
| 代码段 | .text |
> ps7_ddr_0 |
代码段 | 函数机器码 |
.vectors |
异常向量 | 中断入口 | ARM异常向量表 | |
.boot |
Boot代码 | 启动代码 | reset/startup | |
.init |
初始化代码 | CRT初始化 | main前执行 | |
.fini |
结束代码 | 程序退出清理 | main后执行 | |
| 只读数据 | .rodata |
const数据 | 只读常量 | 字符串/查表 |
.rodata1 |
扩展rodata | 编译器扩展 | 次级只读区 | |
.sdata2 |
小只读数据 | GP优化 | 小常量快速访问 | |
.sbss2 |
小只读BSS | GP优化 | 小未初始化数据 | |
| 数据段 | .data |
已初始化变量 | 全局静态变量 | 启动时复制 |
.data1 |
扩展data | 编译器扩展 | 次级data | |
.sdata |
小数据区 | GP优化 | 小变量快速访问 | |
.sbss |
小BSS区 | GP优化 | 小未初始化变量 | |
.bss |
未初始化变量 | 零初始化区 | 启动时清零 | |
COMMON |
公共变量 | 旧式全局变量 | GCC兼容 | |
| TLS线程局部存储 | .tdata |
TLS初始化数据 | 线程局部变量 | Thread Local |
.tbss |
TLS未初始化数据 | TLS BSS | Thread Local | |
| C++初始化 | .ctors |
构造函数表 | 全局对象构造 | main前执行 |
.dtors |
析构函数表 | 全局对象析构 | main后执行 | |
.preinit_array |
预初始化数组 | libc前初始化 | 极少使用 | |
.init_array |
初始化数组 | GCC现代构造机制 | 替代ctors | |
.fini_array |
析构数组 | GCC现代析构机制 | 替代dtors | |
| 异常/调试 | .eh_frame |
异常展开表 | C++异常 | stack unwind |
.eh_framehdr |
异常头 | unwind索引 | backtrace辅助 | |
.gcc_except_table |
GCC异常表 | throw/catch | C++异常 | |
.ARM.exidx |
ARM异常索引 | ARM unwind | backtrace | |
.ARM.extab |
ARM unwind表 | ARM异常处理 | unwind数据 | |
| MMU相关 | .mmu_tbl |
MMU页表 | 一级页表 | Cortex-A9 MMU |
ALIGN(16384) |
16KB对齐 | 页表对齐 | ARM硬件要求 | |
| 地址符号 | __rodata_start |
地址符号 | rodata开始 | 边界标记 |
__rodata_end |
地址符号 | rodata结束 | 边界标记 | |
__data_start |
地址符号 | data开始 | 数据复制起点 | |
__data_end |
地址符号 | data结束 | 数据复制终点 | |
__bss_start |
地址符号 | bss开始 | 清零起点 | |
__bss_end |
地址符号 | bss结束 | 清零终点 | |
| SDA小数据 | _SDA_BASE_ |
sdata中点 | SDA基址 | GP小数据寻址 |
_SDA2_BASE_ |
sdata2中点 | SDA2基址 | 小只读寻址 | |
| Heap区域 | .heap |
heap段 | 动态内存 | malloc/new |
_heap_start |
heap起点 | heap开始 | libc使用 | |
_heap_end |
heap终点 | heap结束 | heap边界 | |
HeapBase |
heap基址 | heap起始 | 调试/库 | |
HeapLimit |
heap结束 | heap上限 | heap边界 | |
| Stack区域 | .stack |
stack段 | CPU栈 | 所有模式栈 |
_stack_end |
栈底 | 主栈开始 | 向上分配 | |
_stack |
主栈顶 | SP初值 | User/System | |
__stack |
主栈符号 | SP引用 | BSP使用 | |
__irq_stack |
IRQ栈顶 | IRQ SP | IRQ模式 | |
__supervisor_stack |
SVC栈顶 | SVC SP | Supervisor模式 | |
__abort_stack |
Abort栈顶 | Abort SP | Data Abort | |
__fiq_stack |
FIQ栈顶 | FIQ SP | Fast IRQ | |
__undef_stack |
Undefined栈顶 | Undefined SP | Undefined模式 | |
| 程序结束 | _end |
最终地址 | 程序结束 | heap边界参考 |
| GNU LD语法 | KEEP() |
强制保留 | 防止gc删除 | 向量表常用 |
ALIGN(x) |
地址对齐 | cache/MMU要求 | 硬件对齐 | |
NOLOAD |
不装载 | 不生成bin | BSS/Stack | |
SORT() |
section排序 | ctor顺序 | GCC使用 | |
EXCLUDE_FILE() |
排除文件 | 避免重复 | ctor/dtor | |
> |
section映射 | 指定MEMORY | 放入内存区 |
脚本工具
bash
# 设置环境变量
> $env:PATH = "D:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin;" + $env:PATH
# 查源码位置
> arm-none-eabi-addr2line -e zynq_wave_app.elf 0x00109264
axi_test/src/main.cpp:44
# 查符号表
arm-none-eabi-nm zynq_wave_app.elf
bash
# wsl有各种工具更方便
> wsl
$:arm-none-eabi-nm zynq_wave_app.elf | grep heap
0012d360 B _heap
0012f360 B _heap_end
0012d360 B _heap_start
0012d310 b heap.5721
# 查看IRQ栈大小
$: arm-none-eabi-nm zynq_wave_app.elf | grep _IRQ_STACK_SIZE
00000fa0 A _IRQ_STACK_SIZE
# IRQ 栈地址 0x00131360:0x00132300 向下增长
$: arm-none-eabi-nm zynq_wave_app.elf | grep irq
0012ccd8 b _ZL12s_pl_irq_cnt
00107448 t _ZL14pl_irq_handlerPv
00107a20 t _ZL16uart_irq_handlerPvmj
00132300 B __irq_stack
00131360 B _irq_stack_end
栈监控
这里以__irq_stack 栈为例
c
#define IRQ_STACK_TOP ((uint32_t *)0x00132300)
#define IRQ_STACK_END ((uint32_t *)0x00131360)
#define WATERMARK 0xA5A5A5A5
volatile uint32_t g_irq_stack_max_usage = 0;
//irq 栈填充水印
void irq_stack_fill_watermark(void)
{
uint32_t *p = IRQ_STACK_END;
while (p < IRQ_STACK_TOP)
{
*p++ = WATERMARK;
}
}
// irq 栈最大用量写入全局变量g_irq_stack_max_usage
void irq_stack_update_usage(void)
{
uint32_t *p = IRQ_STACK_END;
while (p < IRQ_STACK_TOP)
{
if (*p != WATERMARK)
{
break;
}
p++;
}
g_irq_stack_max_usage = (uint32_t)((uint8_t*)IRQ_STACK_TOP - (uint8_t*)p);
}
测试
c
int main(){
static uint32_t s_ms_tick=0;
static uint32_t s_pt_tick=0;
irq_stack_fill_watermark();
BspInit();
at_init(Bsp_shell_write);
AT_printf("ke");
at_app_init();
at_show_version();
//启动PT协程
Protothread::AllStart();
while (1) {
uint32_t ms= BspGetTickMs();
BspGetMillis();
if(ms-s_ms_tick>=10){
s_ms_tick=ms;
int len= BspUartRead((uint8_t *)AT_m_buf, -1);
at_import((uint8_t *)AT_m_buf, len, ms);
}
if(g_bt.key_flag){
g_bt.key_flag=0;
irq_stack_update_usage();
AT_println("IRQ stack max usage = %lu bytes\n", g_irq_stack_max_usage);
}
}
return 0;
}
打印
因为 _IRQ_STACK_SIZE 配置的只有1024 ,irq栈最大用量是1248
所以发生了各种奇怪现象
bash
[18:52:51.763]收←◆ke 268435456
IRQ stack max usage = 1248 bytes