STM调试技巧之栈逆向
HardFault_Handler说明STM32出现了硬件错误,为了解决HardFault_Handler问题,可以使用到栈回溯调试技巧。
先看个例子
A函数调用了函数B,B函数调用了函数C,但B函数在调用函数C时传入参数0,使得除数为0,发生HardFault_Handler。
一、函数调用过程
要搞懂逆向栈,也可以说回溯,首先要知道正向是怎样的一个过程:
当A函数调用B函数时,要将返回地址存放到LR寄存器中,这其实很好理解,简单来讲就是B函数干完了以后要回来继续干A,LR寄存器就存放了回来的地址。
ok,很简单对不对,让我们继续。A调用了B,LR寄存器中存了返回到A函数的地址;那么我们继续在B中调用C呢?返回到B函数的地址存在哪里?也存到LR寄存器吗?显然不行,里面已经放了返回到A函数的地址了,不能把它给覆盖了。
因此,我们需要在栈中先把LR寄存器里的值存进去,这是被调用函数第一步要做的事情。就拿A、B举例,A调用B后,B函数第一件要做的是不是去执行自己的代码,而是要在栈中保存好A函数的现场,可以看先反汇编文件。
反汇编文件中先将lr和r4入栈,然后再去执行其他指令,同样的,B调用C,C函数第一件事也是将lr以及其他寄存器的值入栈。
二、栈逆向的前置准备
当发生HardFault_Handler时,硬件还会自动将当前时刻的部分寄存器入栈,包括PSR寄存器、PC寄存器、LR寄存器等:
我们可以在做一些手脚,将r4-r11也存到栈里面去,方法就是修改启动文件中HardFault_Handler代码,它现在只做死循环实在是太浪费了:
汇编语言不太熟悉的可以让GPT去解释每一行的意思,上面片段的大体意思就是先判断使用的时MSP还是PSP,然后将r4-r11,exc_return入栈,跳转到rt_hw_hard_fault_exception。
rt_hw_hard_fault_exception相关代码可以参考如下:
exception_info结构体如下:
可以看出exception_info从exc_return到psr,就是按栈顶指针从低地址到高地址顺序排列的,这样的话我们通过访问此结构体就可以方便的知道各个寄存器的值。
三、栈逆向
好了,前置工作终于做完了,我们下载我们的代码,开打串口,不出意外我们可以看到如下信息,表示出现了HardFault_Handler错误:
ok,我们先看pc寄存器的值:0x008020e0,意思就是运行到这一地址时候出现了错误,我们打开dis反汇编文件,搜索0x008020e0(什么!反汇编文件不知道怎么生成?好吧,自己去搜寻一下吧,其实编译完成后的一条指令,根据axf文件生成dis文件)
可以看出是在C函数中出现了此错误,我们查看一下C函数的代码,发现没有什么问题,只是return了一个除数
既然发现不了问题那就继续往前追责吧,看看谁调用了C函数。从第一节内容我们知道这时候应该找返回地址,从C的放汇编我们也能看到第一句话就是PUSH {r4,lr},也就是说存放了两个寄存器(32bit)到栈中,一个是r4,一个是lr,就是这两:
显然0080020af是返回地址,我们继续在dis文件中搜寻,注意这个时候应该是搜0x008020ae,表示使用的是THUMB指令集。
好的,可以看到是C函数的返回地址指向了B函数,那么就说明B调用了C,C中出现了HardFault_Handler错误,我们再来看看B调用C有没有什么问题:
破案了,原来B调用C时,传入的参数是0,0是不可以做除数的,这才导致出现了HardFault_Handler。
当然,你可以继续向上追溯,看看谁调用了B,原理都是一样的。