深入解析栈回溯技术:如何通过异常处理精准定位程序崩溃点

一、栈回溯

1.1 栈回溯的原理

调试程序时,经常发生这类错误:

1.读写某个地址,导致程序崩溃
2.调用某个空函数,导致程序崩溃

在异常处理函数中,可以打印出”发生错误瞬间”的所有寄存器。

我们调试时,可以根据这些寄存器,知道发生错误的位置。

但是,光知道发生错误的位置还不够! 比如,根据打印信息知道在C函数里发生错误,但是你无法确定是在哪个调用链上出错:

1.A > B > C时出错?
2.D > C时出错?

image.png

C语言函数的返回地址,保存在栈里:

image.png

栈内容示例:

image.png

1.2 修改异常处理函数打印栈内容

发生错误时,异常函数如何处理?

image.png

发生错误时,栈的使用情况:

image.png

修改HardFault_Handler:

IMPORT rt_hw_hard_fault_exception
    EXPORT HardFault_Handler
HardFault_Handler    PROC

    ; get current context
    TST     lr, #0x04               ; if(!EXC_RETURN[2])
    ITE     EQ
    MRSEQ   r0, msp                 ; [2]=0 ==> Z=1, get fault context from handler.
    MRSNE   r0, psp                 ; [2]=1 ==> Z=0, get fault context from thread.

    STMFD   r0!, {r4 - r11}         ; push r4 - r11 register
    STMFD   r0!, {lr}               ; push exec_return register

    TST     lr, #0x04               ; if(!EXC_RETURN[2])
    ITE     EQ
    MSREQ   msp, r0                 ; [2]=0 ==> Z=1, update stack pointer to MSP.
    MSRNE   psp, r0                 ; [2]=1 ==> Z=0, update stack pointer to PSP.

    PUSH    {lr}
    BL      rt_hw_hard_fault_exception
    POP     {lr}

    ORR     lr, lr, #0x04
    BX      lr
    ENDP

打印寄存器和栈:

image.png

image.png

1.3 分析栈找出函数调用关系

要分析栈,需要得到程序的反汇编码:

image.png

fromelf  --text  -a -c  --output=all.dis    F103_Moduel\F103_Moduel.axf

根据PC值在反汇编文件中找到发生错误的位置:

image.png

发现函数C太简单,它根本没有使用栈,函数C执行完,直接返回到LR。

在打印的信息中,LR=0x08000383,去掉bit0,就是:0x08000382

根据这个值,在反汇编文件中找到函数C的调用者,是函数B:

image.png

在函数B的入口处,发现使用了8字节的栈,并且保存了LR:

image.png

分析函数B的栈:确定返回地址LR=0x08000355,bit0清零后就是0x08000354:

image.png

image.png

在反汇编中搜0x08000354 ,确定函数B的返回地址是函数A:

image.png

在函数A的入口处,发现使用了8字节的栈,并且保存了LR:

image.png

分析函数A的栈:确定返回地址LR=0x08001d99,bit0清零后就是0x08001d98。

这是TestDebug函数。

image.png

最终,调用链为:

image.png

二、修改bin文件实现断点