各位为什么在调试的时候进入了stm32 hardfault定位 中断处理函数

查看: 9425|回复: 3
stm32 HardFault_Handler调试及问题查找方法
本帖最后由 piaolin 于
17:20 编辑
&&STM32出现HardFault_Handler故障的原因主要有两个方面:1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。2、堆栈溢出。增加堆栈的大小。出现问题时排查的方法:发生异常之后可首先查看LR寄存器中的值,确定当前使用堆栈为MSP或PSP,然后找到相应堆栈的指针,并在内存中查看相应堆栈里的内容。由于异常发生时,内核将R0~R3、R12、Returnaddress、PSR、LR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址。注意:寄存器均是32位,且STM32是小端模式。(参考Cortex-M3权威)编写问题代码如下: void StackFlow(void)
int a[3],i;
for(i=0; i&10000; i++)
void SystemInit(void)
RCC-&CR |= (uint32_t)0x;
RCC-&CFGR = 0x;
RCC-&CR &= (uint32_t)0xFEF6FFFF;
RCC-&PLLCFGR = 0x;
StackFlow();
RCC-&CR &= (uint32_t)0xFFFBFFFF;
。。。。。。。。。。。。。。
DEBUG如下图SP值为0x,查看堆栈里面的值依次为R0~R3、R12、Return address、PSR、LR, 例如R0(), 显然堆栈后第21个字节到24字节即为Returnaddress,该地址0x08001FFD即为异常前PC将要执行的下一条指令地址(即StackFlow()后面的语句处RCC-&CR &= (uint32_t)0xFFFBFFFF)另一种方法:默认的HardFault_Handler处理方法不是B .这样的死循环么?楼主将它改成BXLR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿
& && &Cortex-M3/4的Fault异常是由于非法的存储器访问(比如访问0地址、写只读存储位置等)和非法的程序行为(比如除以0等)等造成的。常见的4种异常及产生异常的情况如下:
BusFault:在fetch指令、数据读写、fetch中断向量或中断时存储恢复寄存器栈情况下,检测到内存访问错误则产生BusFault。
Memory ManagementFault:访问了内存管理单元(MPU)定义的不合法的内存区域,比如向只读区域写入数据。
UsageFault:检测到未定义指令或在存取内存时有未对齐。还可以通过软件配置是否检测到除0和其它未对齐内存访问也产生该异常,默认关闭,需要在工程初始化时配置:
[cpp] viewplaincopyprint?
SCB-&CCR |= 0x18; // enable div-by-0 and unaligned fault&&
HardFault:在调试程序过程中,这种异常最常见。上面三种异常发生任何一种异常都会引起HardFault,在上面的三种异常未使能的情况下,默认发生异常时进入HardFault中断服务程序。使能前三种异常也要在初始化时配置:
[cpp] viewplaincopyprint?
SCB-&SHCSR |= 0x;& &// enable Usage Fault, Bus Fault, and MMU Fault&&
在默认复位初始化时,HardFault使能,其它三者不使能,因此当程序中出现不合法内存访问(一般是指针错误引起)或非法的程序行为(一般就是数学里面常见的除0)时都将产生HardFault中断。
[url=]2 HardFault调试方法[/url]假设IDE环境为Keil,芯片为STM32F103。
在stm32f10x_it.c中,添加软件断点,一旦调试时出现Hard Fault则会在停在__breakpoint(0)处。
&&void HardFault_Handler(void)&&{&&& & &&if (CoreDebug-&DHCSR & 1) {&&//check C_DEBUGEN == 1 -& Debugger Connected&&& && &__breakpoint(0);&&// halt program execution here& && && &&&}&&&&while (1)&&&&{&&&&}&&}&&
当进入HardFault断点后,菜单栏Peripherals &Core Peripherals &FaultReports打开异常发生的报告,查看发生异常的原因。
上面的报告发生了BUS FAULT,并将Fault的中断服务转向Hard Fault。
相对于检测发生了什么异常,定位异常发生位置显得更重要。
(1)打开Call Stack窗口(如下图左侧,断点停在Hard Fault服务程序中)
(2)在Call Stack的HardFault_Handler上右键Show CallerCode(有的Keil版本也可以直接双击)
这时将跳转到发生异常的源代码位置(如上图),异常发生在p-&hour=0这一行。这里错误很明显:指针p尚未为成员变量分配内存空间,直接访问未分配的内粗空间肯定出错。
再说明2点:
[1] 在复杂的情况下,即使定位了异常发生位置也很难容易的改正错误,要学会使用Watch窗口对发生错误的指针变量进行跟踪;
[2]在问题不明晰的情况下,尝试分析反汇编代码,就自己遇到的,部分情况下的异常发生在BL等跳转指令处,BL跳转到了不合法的内存地址产生异常
Refrences:
[1] Application Note209. Using Cortex-M3 and Cortex-M4 FaultExceptions.
[2] Cortex-M3权威指南
 看到有朋友遇到Hard Fault 异常错误,特地找到一篇飞思卡尔工程师写的一片经验帖,定位Hard Fault 异常。
Kinetis MCU 采用 Cortex-M4 的内核,该内核的 Fault 异常可以捕获非法的内存访问和非法的编程行为。Fault异常能够检测到以下几类非法行为:·& && &&&总线 Fault:&&在取址、数据读/写、取中断变量、进入/退出中断时寄存器堆栈操作(入栈/出栈)时检测到内存访问错误。·& && &&&存储器管理 Fault: 检测到内存访问违反了内存保护单元(MPU, MemoryProtection Unit)定义的区域。·& && &&&用法 Fault:&&检测到未定义的指令异常,未对其的多重加载/存储内存访问。如果使能相应控制位,还可以检测出除数为零以及其他未对齐的内存访问。
·& && &&&硬 Fault:&&如果上述的总线 Fault、存储器管理 Fault、用法 Fault 的处理程序不能被执行(例如禁能了总线 Fault、存储器管理Fault、用法Fault 的异常或者在这些异常处理程序中又出现了新的Fault)则触发硬Fault。& && & 在 MQX 操作系统启动的时候会安装上默认的异常中断处理函数,当系统异常时会产生一个“unexpected”中断,内核就会自动调用异常处理函数,同时也将运行用户自定义的处理函数,来实现特殊故障的定位方法。& && & 默认情况下,MQX把出现异常的任务挂起,避免故障进一步扩大。通过TAD 任务感知调试插件的Task summary 功能,我们可以观察到出现异常的任务情况。
dmda9a9la2ad.png.thumb.jpg (273.83 KB, 下载次数: 7)
17:22 上传
开发人员在调试期间,需要弄清楚系统异常触发了哪类Fault,由什么原因触发了Fault 以及定位触发Fault 的代码。在这种情况下,可以利用自定义的Fault 中断处理程序来分析Fault 出错原因。& &&&为了解释所述的 Fault 中断处理程序的原理,这里重述一下当系统产生异常时 MCU 的处理过程:·& && &&&有一个压栈的过程,若产生异常时使用 PSP(进程栈指针),就压入到 PSP 中,若产生异常时使用MSP(主栈指针),就压入MSP 中。·& && &&&会根据处理器的模式和使用的堆栈,设置 LR 的值(当然设置完的LR 的值再压栈)。·& && &&&异常保存,硬件自动把 8 个寄存器的值压入堆栈(8 个寄存器依次为 xPSR、PC、LR、R12以及 R3~R0)。如果异常发生时,当前的代码正在使用PSP,则上面8 个寄存器压入PSP; 否则就压入MSP。& && & 当系统产生异常时,我们需要两个关键寄存器值,一个是 PC ,一个是 LR (链接寄存器),通过 LR找到相应的堆栈,再通过堆栈找到触发异常的PC 值。将产生异常时压入栈的 PC 值取出,并与反汇编的代码对比就能得到哪条指令产生了异常。& && &&&这里解释一下关于 LR 寄存器的工作原理。如上所述,当 Cortex-M4 处理器接受了一个异常后,寄存器组中的一些寄存器值会被自动压入当前栈空间里,这其中就包括链接寄存器(LR )。这时的 LR 会被更新为异常返回时需要使用的特殊值(EXC_RETURN)。关于EXC_RETURN 的定义如下,其为 32 位数值,高 28 位置 1,第 0 位到第三位则提供了异常返回机制所需的信息,如下表所示。可见其中第 2 位标示着进入异常前使用的栈是 MSP还是PSP。在异常处理过程结束时,MCU 需要根据该值来分配 SP 的值。这也是本方法中用来判断所使用堆栈的原理,其实现方法可以从后面_init_hardfault_isr 中看到。
gw5v22awlaabza.jpg.thumb.jpg (14.05 KB, 下载次数: 8)
17:22 上传
& &另外,我们可以利用 MQX 的控制台串口输出Fault 异常信息来帮助调试。编写Fault 处理程序时,将启动代码中默认的Fault 处理程序跟换成自己需要的Fault 处理程序。需要注意的是,由于是在中断中进行打印输出,MQX的控制台串口只能使用POLL 轮询模式的驱动,不能使用中断模式的驱动。& &&&用户可以编写自定义的硬 Fault 处理程序_int_hardfault_isr,修改 MQX 的中断向量定义vector.c,把里面的DEFAULT_VECTOR 代码段换成下面的代码。当系统出现硬Fault 异常时,将会调用自定义的Fault 处理_int_hardfault_isr函数。在这个函数,我们可以通过StackTrace-back 回溯出现问题的代码。
220232ixlrzbjt8fhlllxy.png.thumb.jpg (230.03 KB, 下载次数: 6)
17:22 上传
我们可以在_int_hardfault_isr 函数里将出现异常时的寄存器、堆栈、状态寄存器等信息打印出来。如果系统出现异常时,一般情况都会通过串口控制台打印出LR,PC的值。然后根据编译器生成的map 文件,找到出现问题的具体函数。
l2eiixxelvbcb.png.thumb.jpg (168.56 KB, 下载次数: 7)
17:22 上传
& &&&从上图的串口输出我们可以看到 PC 和 LR 寄存器值,PC 的值为 0x56c6,我们根据汇编代码可以找到出现问题的指令。从而大大缩小了查找出现问题的范围,可以帮助开发人员快速定位问题的根本原因。
附录Fault异常中断处理代码:// hard fault handler in C,
// with stack frame location as input parameter
void hard_fault_handler_c (unsigned int * hardfault_args)
{
&&unsigned int stacked_r0;
&&unsigned int stacked_r1;
&&unsigned int stacked_r2;
&&unsigned int stacked_r3;
&&unsigned int stacked_r12;
&&unsigned int stacked_
&&unsigned int stacked_
&&unsigned int stacked_
&&stacked_r0 = ((unsigned long)hardfault_args[0]);
&&stacked_r1 = ((unsigned long)hardfault_args[1]);
&&stacked_r2 = ((unsigned long)hardfault_args[2]);
&&stacked_r3 = ((unsigned long)hardfault_args[3]);
&&stacked_r12 = ((unsigned long)hardfault_args[4]);
&&stacked_lr = ((unsigned long)hardfault_args[5]);
&&stacked_pc = ((unsigned long)hardfault_args[6]);
&&stacked_psr = ((unsigned long) hardfault_args[7]);
&&printf (&\n\n[Hard faulthandler - all numbers in hex]\n&);
&&printf (&R0 = %x\n&,stacked_r0);
&&printf (&R1 = %x\n&,stacked_r1);
&&printf (&R2 = %x\n&,stacked_r2);
&&printf (&R3 = %x\n&,stacked_r3);
&&printf (&R12 = %x\n&,stacked_r12);
&&printf (&LR [R14] = %x&&subroutine call return address\n&,stacked_lr);
&&printf (&PC [R15] = %x&&program counter\n&, stacked_pc);
&&printf (&PSR = %x\n&,stacked_psr);
&&/******************* Add yourdebug trace here ***********************/
&&_int_kernel_isr();
}
/* hard fault interrupt handler */
void _int_hardfault_isr( )
{
&&__asm(&TST LR, #4&);
&&__asm(&ITE EQ&);
&&__asm(&MRSEQ R0,MSP&);
&&__asm(&MRSNE R0,PSP&);
&&__asm(&Bhard_fault_handler_c&);
}复制代码
17:24 上传
点击文件名下载附件
下载积分: 黑币 -5
1.42 KB, 下载次数: 4, 下载积分: 黑币 -5
17:24 上传
点击文件名下载附件
下载积分: 黑币 -5
3.43 KB, 下载次数: 2, 下载积分: 黑币 -5
17:24 上传
点击文件名下载附件
下载积分: 黑币 -5
362.58 KB, 下载次数: 12, 下载积分: 黑币 -5
 正在调试STM32F205,OS操作系统调度USB功能,楼主的方法正中,非常感谢!&&
谢谢楼主分享
Powered by嵌入式(18)
STM32出现HardFault_Handler故障的原因主要有两个方面:
1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。
2、堆栈溢出。增加堆栈的大小。
出现问题时排查的方法:
1、发生异常之后可首先查看LR寄存器中的值,确定当前使用堆栈为MSP或PSP,然后找到相应堆栈的指针,并在内存中查看相应堆栈里的内容。由于异常发生时,内核将R0~R3、R12、Return address、PSR、LR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址,因此在堆栈中反数第三个字即为出错位置。
2、默认的HardFault_Handler处理方法是B .将它改成BX LR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿。
这个有时候可能需要在反汇编模式下调试,因为可以是程序跑飞一会儿才出现HardFault_Handler。
3、还是将中断函数修改,打印中断时的一些信息:
HardFault_Hander()定义如下:
void HardFault_Handler(void)
&&uint32_t&r_
&&r_sp = __get_PSP();&//获取SP的值
&&PERROR(ERROR,Memory Access Error!);
&&Panic(r_sp);
&&while (1);
实际中Keil调试模式时,若已烧入stm32的代码与待调试的代码不一致,则调试时会异常进入硬件错误。
通常进入调试模式时会将待调试代码的.axf文件烧入stm32,但有时烧入不正确导致不一致。
解决办法:重新烧录待调试代码生成的.axf文件
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:7167次
排名:千里之外
原创:13篇
转载:71篇
(5)(5)(3)(1)(10)(6)(2)(15)(13)(5)(1)(6)(11)(1)产生segment fault的原因及调试方法总结 - 推酷
产生segment fault的原因及调试方法总结
段错误(以下定义摘自C专家编程)
段错误通常是由于解除引用一个未初始化或非法值的指针引起的。以发生频率为序,最终可能导致段错误的常见编程错误是:
坏指针错误
:在指针赋值之前就用它来引用内存;或者向库函数传递一个坏指针
如果调试器显示系统程序中出现了段错误,很可能并不是系统程序引起的段错误,问题可能就出现在自己的代码中
;或者指针被释放后还继续访问它的内容。
:越过数组边界写入数据,在动态分配的内存空间以外写入数据,或改写一些堆管理数据结构
在动态分配的内存之前的区域写入数据就很容易发生这种情况
指针释放引起的错误
:释放同一块内存两次,或释放一块未曾使用
分类的内存,或释放一个无效的指针。一个极为常见的与释放内存有关的错误就是在&
for(p=p;p=p-&next)&
这样的循环中迭代一个链表,并在循环体内使用&
这样的语句。这样,在下一次循环迭代时,程序就会对已经释放的指针进行解除引用操作,从而导致不可预料的结果。
问: 以下这
段C++代码会有问题吗?有几个printf能正常输出呢?
#include &stdio.h&
#include &stdlib.h&
class Data {
& public :
& & void test_data() {
& & & printf(&test_dtata\n&);&
&// printf2
& & & printf(&test_dtata:%d\n&, data_); //printf3
& private :
& & int data_;
class Container {
& public :
& & void test_container() {
& & & printf(&test_container\n&); &// printf1
& & & data_.test_data();
& private :
& & Data data_;
int main()
& Container *c = NULL;
& c-&test_container();
& return 0;
答案是有两个可以输出,第三个会引起segment fault!
test_container
test_dtata
问: 为什么第一第二个printf能输出,第三个不行?
& c-&test_container();最终等价于
&test_container(NULL);然后调用
test_data(NULL) ,结果在
& printf(&test_dtata:%d\n&, data_); //printf3 &访问NULL-&data_,显然这是非法访问,所以出错。
问:为什么我要举这个例子?
能输出,直觉就是指针不可能是空的,否则调用不了函数。
我在调试的时候遇到segment fault,具体的项目比这个小例子复杂的多,
一开始是以为线程同步没处理好多次释放了内存导致segment fault,最后
发现是未赋值,仅初始化为NULL)。
如何调试segment fault:
1、 gdb 直接调试,用bt来看segment fault的栈情况
2、 对已产生 core dump的,用gdb program.exe core调试,同样用bt来看,默认不产生coredump,如何开启见:http://blog.csdn.net/huangkq1989/article/details/7032776
3、 通过dmesg来看:【引用于:&http://zengji.li./blog/static//】
&kernel&:&***&:&segfault&at&0011&rip&0454&rsp&00
fd30&error&4
这种信息一般都是由内存访问越界造成的,不管是用户态程序还是内核态程序访问越界都会出
并在系统日志里面输出一条这样的信息。这条信息的前面分别是访问越界的程序名,进程
号,访问越界的地址以及当时进程堆栈地址等信息,比较有用的信息是&最后的
error&number.&
在上面的信息中,
error&number
下面详细介绍一下
error&number
在上面的例子中,
error&number
转成二进制就是
bit2=1,&bit1=1,&bit0=0,&
按照上面的解释,我们可以得出这条信息是由于用户态程序读操作访问越界造成的。
error&number
是由三个字位组成的,从高到底分别为
所以它的取值范围是
表示是用户态程序内存访问越界,值为
表示是内核态程序内存访问越界
表示是写操作导致内存访问越界,值为
表示是读操作导致内存访问越界
表示没有足够的权限访问非法地址的内容,值为
表示访问的非法地址根本没有对应的页面,也就是无效地址
addr2line -e testseg 0470 -f可得到出问题的函数及对应的行。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 hardfault 的文章

 

随机推荐