Xil_DataAbortHandler,
Xil_PrefetchAbortHandler,
Xil_UndefinedExceptionHandler,
Xil_ExceptionNullHandler
上面这几个异常的问题都可以用我这个方法解决 . 此方案也支持stm32
请允许我将这篇文章设置为vip可见了, 最近创业手头紧张. 需要恰饭. 各位见谅. 本文绝对值得你花点钱看. 上面的这4种异常的处理,在zynq裸机开中,如果排查需要耗费大量的时间. 采用本文的方法绝对能节省你大量的时间. 可以微信 ABCD2730
首先必须得理解 Prefetch Abort 异常是什么异常. 这种异常是如何引起的.
在Zynq中,Prefetch Abort异常是一种与指令预取相关的异常。这种异常通常发生在处理器尝试预取一条指令但未能成功时,具体原因和触发条件如下:
具体原因
- 内存访问问题:如地址错误,即处理器尝试访问一个不存在的或不可执行的内存地址。
- 访问权限不足:处理器尝试访问一个它没有权限访问的内存区域。
- 内存故障:如内存损坏或不可访问。
- 内存重新映射:内存重新映射后,出错的地址处的内容没有被正确初始化。
- 程序计数器(PC)问题:如PC被设置为一个无效的地址,或者由于某种原因(如栈溢出或栈损坏)导致PC被错误地修改。
- 野函数指针: 如:某个函数指针被定义后, 并未正确初始化为0, 指针指向的是某个特殊地址. 执行的时候,无法判断函数指针是否是正确的值. 被调用后就会出现触Prefetch Abort 异常.
触发条件
当处理器在预取指令阶段遇到上述任何问题时,都会触发Prefetch Abort异常。
这通常意味着处理器无法继续执行当前的指令流,因为它无法获取到下一条要执行的指令。
总之Prefetch Abort是系统无法获取到下一条要执行的指令
异常处理
当Prefetch Abort异常发生时,处理器会跳转到相应的异常处理例程。这个例程通常位于操作系统的底层或硬件的异常向量表中。在异常处理例程中,系统会尝试诊断问题的原因,并采取相应的措施来恢复或报告错误。这可能包括记录错误信息、尝试重新访问内存、终止当前任务或重启系统等。
注意事项
- Prefetch Abort异常是一种比较难解决的问题,因为很难定位出错的位置。
- 在进行嵌入式开发或底层系统开发时,需要特别注意内存访问的合法性和权限问题,以避免触发Prefetch Abort异常。
综上所述,Prefetch Abort异常是Zynq处理器中一种与指令预取相关的异常,它通常由内存访问问题、访问权限不足、内存故障等原因触发。当这种异常发生时,处理器会跳转到异常处理例程进行处理。
那么如何才能定位到出问题的这行代码呢?
由于项目是老项目, 不是自己写的, 纯靠猜很难快速定位到有问题的代码处, 这么多的代码, 根本不知道是哪一行代码引起的. 要想解决问题, 首先要定位出问题的行. . 这个异常恰恰难就难在如何定位问题行. 如果源代码不多, 可以考虑一行一行的执行代码. 逐步定位到问题行, 如果代码过多. 也可以考虑二分查找法逐步定位到问题行. 高级语言例如JAVA或者C#在这方面要好的多, 会报错. 提示找不到对象, 提示出问题的行. 对于程序员来讲解决问题相对要简单很多. 找原因要花费大量的时间, 但是C语言就不行了没有任何提示. 裸机开发更难. 必须得自己动手实现异常捕获和异常后代码恢复. GDB也没有捕获这种异常并停止代码继续执行的功能. 如果触发异常时能立即停止代码执行类似C#的调试, 那将会简单很多 . 问题就出在没有办法停止函数的执行.
所以思来想去总不能一行一行的执行吧. 这么多代码, 何时才是头啊. 这必须得解决. 得想个好办法.
经过一顿辛苦的探索最后终于解决了…使用到的技巧是gcc编译器的 -finstrument-functions 功能.
这个功能是在每个函数的入口和出口都默认调用一个代码.
代码如下.
void __attribute__((__no_instrument_function__)) __cyg_profile_func_enter (void *this_fn, void *call_site)
{
// 在此处添加您在函数入口处想要执行的代码,例如打印函数名
printf("进入函数 : %p\n", this_fn);
}
void __attribute__((__no_instrument_function__)) __cyg_profile_func_exit (void *this_fn, void *call_site)
{
// 在此处添加您在函数出口处想要执行的代码
printf("退出函数: %p\n", this_fn);
}
使用时需要给gcc 编译器增加 -finstrument-functions 编译选项.在xilinx sdk中, 如下添加这个编译选项
这样就会在每个函数被调用时都记录下来程序的函数名, 也就形成了函数的调用堆栈…
效果如下…
进入函数: 0x100c6c
进入函数: 0x100bb8
退出函数: 0x100bb8
进入函数: 0x100c30
退出函数: 0x100c30
退出函数: 0x100c6c
得到这个函数的地址后, 剩下的是如何将这个地址转成对应的函数名. 这个其实比较简单的, 使用GDB的调试功能. 在GDB窗口中增加断点, 一定要是调试状态下添加断点. 如下图:
这里注意要多加几个断点, 最近出错的函数名.2-3个函数都加一下. 另外
break *0x100c30
中间要加一个 *号. 意思是100c30 结尾的断点. 前面会自动补零…不加星号无法定位到问题函数.
然后重新开始调试. 等待出错的时候进入异常函数时开始单步执行, 直到找到出问题的代码行. 因为已经定位到最后的内部函数. 此时再找有问题的代码行, 将会大大缩短定位问题行的时间. 此时的调用堆栈等信息也都还可直接在调试窗口看到. 所以要方便的多.
至此, 其它的几个异常也都如法炮制. 解决方案是一样的. 这个方案是万能的.
这个方案的关键技巧就是使用了gcc的 -finstrument-functions 这个编译项. 在每个函数的头部入口加了一段代码. 真是伟大的功能 . . 否则还真不知道如何解决这个问题 …
另外提醒一下, 产品发布时记得要去掉这个选项哦 … 否则影响代码执行速度.
如果没有print 函数或者不方便打印可以考虑使用变量存储最近的几个函数入口地址. NND, 搞了一下午. 以后再也不怕这种异常问题了. 这样就不用打印了.
最后修改后, 代码修改后如下,
/******************************************************************************
*
* Copyright (C) 2009 - 2014 Xilinx, Inc. All rights reserved.
*
*/
#include <stdio.h>
#include