zynq的栈监控与Xil_XXXAbortHandler问题排查
参考
UG585Address Map.csdn
stm32的栈监控与HardFault_Handler问题排查.csdn
崩溃现象
程序崩溃后会跳转到
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
# 修改_IRQ_STACK_SIZE=DEFINED(_IRQ_STACK_SIZE)? _IRQ_STACK_SIZE:1024;# 为_IRQ_STACK_SIZE=DEFINED(_IRQ_STACK_SIZE)? _IRQ_STACK_SIZE:4000;lscript.ld
| 分组 | 键 | 值 | 功能 | 解释 |
|---|---|---|---|---|
| 栈/堆配置 | _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 | 放入内存区 |
脚本工具
# 设置环境变量>$env:PATH="D:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin;"+$env:PATH# 查源码位置>arm-none-eabi-addr2line-ezynq_wave_app.elf 0x00109264 axi_test/src/main.cpp:44# 查符号表arm-none-eabi-nm zynq_wave_app.elf# wsl有各种工具更方便>wsl $:arm-none-eabi-nm zynq_wave_app.elf|grepheap 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|grepirq 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 栈为例
#defineIRQ_STACK_TOP((uint32_t*)0x00132300)#defineIRQ_STACK_END((uint32_t*)0x00131360)#defineWATERMARK0xA5A5A5A5volatileuint32_tg_irq_stack_max_usage=0;//irq 栈填充水印voidirq_stack_fill_watermark(void){uint32_t*p=IRQ_STACK_END;while(p<IRQ_STACK_TOP){*p++=WATERMARK;}}// irq 栈最大用量写入全局变量g_irq_stack_max_usagevoidirq_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);}测试
intmain(){staticuint32_ts_ms_tick=0;staticuint32_ts_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_tms=BspGetTickMs();BspGetMillis();if(ms-s_ms_tick>=10){s_ms_tick=ms;intlen=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);}}return0;}打印
因为 _IRQ_STACK_SIZE 配置的只有1024 ,irq栈最大用量是1248
所以发生了各种奇怪现象
[18:52:51.763]收←◆ke268435456IRQ stack max usage=1248bytes