目录
[1 Fault类异常](#1 Fault类异常)
[1.1 总线Fault](#1.1 总线Fault)
[1.2 存储器管理Fault](#1.2 存储器管理Fault)
[1.3 用法Fault](#1.3 用法Fault)
[1.4 硬件Fault](#1.4 硬件Fault)
[2 Core Registers](#2 Core Registers)
[2.1 R14(LR)寄存器](#2.1 R14(LR)寄存器)
[2.2 R15(PC)寄存器](#2.2 R15(PC)寄存器)
[3 HardFault处理原理](#3 HardFault处理原理)
[4 真实项目调试](#4 真实项目调试)
1 Fault类异常
Cortex-M3的Fault异常分为:
- 总线Fault
- 存储器管理Fault
- 用法Fault
- 硬件Fault
1.1 总线Fault
这里引用《Cortex-M3权威指南》说法:
可以通过地址0xe000ed29查看具体是什么异常。
1.2 存储器管理Fault
这里引用《Cortex-M3权威指南》说法:
可以通过地址0xe000ed28查看具体是什么异常。
1.3 用法Fault
这里引用《Cortex-M3权威指南》说法:
可以通过地址0xe000ed2a查看具体是什么异常。
1.4 硬件Fault
硬件Fault也就是我们所说的HardFault,只要产生了总线、存储器管理、用法Fault其中之一,就会上报给硬件Fault,所以,最终的Falut我们都可以通过HardFault查看。
可以通过地址0xe000ed2c查看具体是什么异常。
2 Core Registers
讲了几种Fault之后,我们需要了解一些基本的内核寄存器,后续查找Fault提供帮助。
2.1 R14(LR)寄存器
R14(LR):连接寄存器,当呼叫一个子程序时,由R14存储返回地址。
当只有一级子程序的时候:把返回地址直接存储在R14寄存器中,这样就无需访问内存(堆栈内存,进栈和出栈),从而提高子程序的调用效率;
当子程序多于一级的时候:前一级存储在R14的返回地址值压到堆栈里,保留最后一级的返回地址在R14里面;
注意:在RAM上编程时,应尽量只使用寄存器保存中间结果,迫不得已时才访问内存。
- 如图所示,当程序执行到0x08001224地址的时候,CM3内核会把0x08001228存到LR中(实际存入的是0x08001229,因为是Thumb-2指令),并在进入子程序之后把LR压栈(因为子程序里面存在子程序,如果子程序不存在子程序,就不会压栈);
- 当程序执行到0x08001400的时候,由于LR已经压栈,所以LR的值就会重新存储0x08001404,当执行完NVIC_Init程序之后,BL LR(LR的值为0x08001404)就会无条件跳转到0x08001404处继续执行(此时需要把LR出栈,出栈的值是0x08001228);
- 最后执行完NVIC_Configuration之后,就会执行BL LR返回到0x08001228处继续执行代码。
2.2 R15(PC)寄存器
R15:程序计数寄存器,指向下一次要执行的程序地址。必须保证PC的数值是奇数,用于表示在Thumb状态下执行。
当中断来临的时候,R0-R3、R12、LR、PC、xPRS寄存器会自动的压栈,如果中断中使用了其它寄存器,在进入中断之前,也会对使用到的寄存器自动的压栈。
3 HardFault处理原理
如果程序进入hardfault,那么首先找到SP指针的值,然后根据SP指针的值,找出对应地址所存放的寄存器(CM0和CM3目前这些地址存放的寄存器相同)。
然后主要是定位LR和PC的值,LR指的是发生异常前本来要执行的下一条指令的地址,但是由于异常的产生,不会执行这条指令(只需要在这个地址之前打一个断点,根据条件来找寻出错的点),PC是发生异常前紧接着执行的地址。
4 真实项目调试
真实项目中,我们大多不必关心Fault状态寄存器的值,因为只要我们定位到错误的代码的位置,我们大致都能知道是什么错误了。
- 首先工程跑到hardfault了;
- 通过SP指针找到PC的值为0x08000046,LR的值为0x08001DEB,可知程序在执行了0x08000046指令之后就进入了HardFault,此时程序不会执行0x08001DEB这条指令。如果程序没有进入HardFault,那么子函数就会返回0x08001DEB这条指令。
- 找到LR和PC指针指向地址的程序位置,可知道PC指针的值是中断向量表,这里又没用开PVD中断,所以断定是这里出错了;
- 在LR地址的前一条指令的位置打上断点,确定程序在这里执行完之后,就出错了;
- 通过发现是(*pEpInt_IN[EPindex-1])();函数的位置出错,所以把watch窗口打开,查看这个函数的地址和返回值,发现返回值是0x08000046,所以这个值出错了,因为返回值成功是0,失败不可能是0x08000046(从调试信息可知,此函数的地址是0x20000018,所以返回值写入的是0x20000018);
- 增加条件断点,当0x20000018地址的值被修改了之后产生断点,捕捉哪里把0x20000018的值修改为了0x08000046,通过断点可知,在下面代码的时候导致了hardfault,进一步得知是数组溢出导致。