STM32H7xx 调试HardFault的一次记录

STM32H7是ST新推出的一款cotex-m7核心的高性能产品,核心频率达到了400Mhz。虽然是M7核心,但是和M3一样都是属于M家族成员,所以各个寄存器和地址以及硬核的事务流程处理上基本没有什么大的变化。

下面说调试的过程,在HardFaultHandler里打一个断点,出现hardfault时会停止在这里。你可以在hardfault处理函数里加上一句汇编语句asm("bx lr")。这样可以快速返回到hardfault之前语句,仔细看下当前的程序是否有问题,hardfault大部分是由于内存的非法访问引起的,一般可以很容易找到错误。

STM32H7xx 调试HardFault的一次记录_第1张图片

如果想一步步的找到原因,就需要按照寄存器中的值来慢慢查找。我们知道LR链接寄存器存放的是从子程序返回的地址,这个也同样适用于异常中断程序,不过异常中断程序的返回地址并不是具体程序的地址,而是特殊的值。参见M7用户指导中的异常返回值:

STM32H7xx 调试HardFault的一次记录_第2张图片

再看我们的寄存器的值是0xFFFFFFE9,表中说返回线程模式,使用MSP堆栈。

STM32H7xx 调试HardFault的一次记录_第3张图片

从寄存器值中可以看到MSP的值是0x20000530,当进入中断时,CPU依次将xPSR,PC,LR,R12,R3-R0压入堆栈。现在我们来看看中断之前的这些寄存器的值是多少。

STM32H7xx 调试HardFault的一次记录_第4张图片

M7的堆栈是从高地址向低地址生长的,从上图可以看到PC为0x080021dc,LR为0x080021b5,xPSR为0x81000000.有了这个我们就可以找到进入hardfault之前的语句了,在汇编窗口中查找该地址是:

STM32H7xx 调试HardFault的一次记录_第5张图片

这是在发送数据时,往串口的发送数据寄存器TDR中写数据时,进入的hardfault。我们也可以查看是什么原因导致的hardfault,cotex-M7中的HFSR寄存器描述了与hardfault相关的信息。HFSR的寄存器地址是0xE000ED2C,我们看一下具体的值。

STM32H7xx 调试HardFault的一次记录_第6张图片

可以从内存中看到HFSR的值是0x40000000。查看HFSR的位定义:

STM32H7xx 调试HardFault的一次记录_第7张图片

FORCED被置位,从说明中可以看到这是因为其它异常上访成为hardfault的,具体原因需要进一步查看其它状态寄存器。这里插一下cotex-m的异常处理机制,cotex-m处理器支持多个异常处理,分别是reset,NMI,hardfault,memfault,busfault,usagefault,debug monitor,SVCall,interrupts。如果memfault,busfault,usagefault对应的fault mask未被置位,则这些异常都将上访成为hardfault。本例中就是因为上访被置位的,所以我们要继续查看CFSR寄存器,CFSR表明了与USAGE,MEM,BUS相关的fault信息,地址是0xE000ED28,我们从前面的图中可以看到其值为0x00008200,从寄存器定义可以查到是BFARVALID和PRECISERR置位。PRECISERR表示数据总线错误,是因为PC指向的异常返回指令导致的。BFARVALID置位表示当发生了busfault时,会将错误的地址保存到BFAR寄存器中,本例中BFAR的当前值为0x2A0D53E7。

     从上面的分析我们来具体跟踪一下,前面压入堆栈的PC值是0x080021DC,其指令是LDRB R0,[R4]。表示要将R4中的地址所在的值载入到R0中。我们可以看到R4中的值正是0x2A0D53E7。STM32H7的SRAM总共有1M bytes,分布于0x24000000,0x30000000等几个地方。所以0x2A0D53E7这个地址中的值根本不存在或者是未知的,反正就是非法的,CPU无法访问,所以导致的buffault,再上访为hardfault。到此为止,导致hardfault的原因就分析清楚了。

STM32H7xx 调试HardFault的一次记录_第8张图片

分析了引起错误的原因以后,我们再来寻找代码中的错误。上面说了是因为LDRB R0,[R4]这句指令出现的错误,因为R4中值表示的地址是非法的,那么为什么R4会变成这个值呢。我们从程序往前寻找R4被操作的语句,在当前函数开始找到了对R4的操作:MOVS R4,R1;R4是装载的R1寄存器。那么继续往前找R1寄存器的值是如何操作的。

STM32H7xx 调试HardFault的一次记录_第9张图片

在跳转到函数

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

之前的指令是这样的:

STM32H7xx 调试HardFault的一次记录_第10张图片

可以看到R1的值是R9和R0的值相加得来的。实际调用这个函数是这样的            

HAL_UART_Transmit(&Uart1Handle,&uart7.buf[uart7.tail],1,HAL_MAX_DELAY);

上面的R0的值是uart7.tail的地址,R9的值是uart7.buf[ ]的首地址,R1的值是uart7.buf[uart7.tail]的地址。到这里答案就呼之欲出了。uart7.buf[ ]的首地址是直接编译出来的不会错,所以只可能是uart7.tail的值的改变导致了uart7.buf[uart7.tail]的地址错误。这里因为uart7.tail的值的错的比较大,进入到非法地址范围,导致了hardfault,如果是一个很小值,在可访问地址范围内,就会导致其它地址的内容改变,但是不会报错。使程序出现不可预料的结果,所以大家使用指针操作内存时一定要小心。

你可能感兴趣的:(arm)