MIPS32 backtrace

在嵌入式开发过程中,经常会遇到出问题了想知道函数调用关系,不过目前我用到的toochain的Libc提供的backtrace只能出来2层,所以就只好另外写代码来backtrace。目前嵌入式mips32在gcc编译器上的ABI都遵循o32, 从一段反汇编简单看一下o32 ABI的帧结构

0040064c <Test2>:
  40064c:       27bdffe0        addiu   sp,sp,-32    ##mips不会自动处理sp,这里进行函数堆栈开辟
  400650:       afbf001c        sw      ra,28(sp)    ##将ra入栈
  400654:       afbe0018        sw      s8,24(sp)    ##其它寄存器入栈
  400658:       03a0f021        move    s8,sp
  40065c:       0c100184        jal     400610 <Test1>
  400660:       00000000        nop
  400664:       24420001        addiu   v0,v0,1
  400668:       03c0e821        move    sp,s8
  40066c:       8fbf001c        lw      ra,28(sp)    ##从堆栈取出ra
  400670:       8fbe0018        lw      s8,24(sp)    ##其它寄存器出栈
  400674:       27bd0020        addiu   sp,sp,32     ##恢复堆栈
  400678:       03e00008        jr      ra           ##跳回到函数调用处
  40067c:       00000000        nop

从上面看堆栈中大约保存的就是各个要保护的寄存器内容,但不同的函数保护的内容不一样,因此帧内数据不一定一样,例如下面这个函数就不用保存ra

00400610 <Test1>:
  400610:       27bdffe8        addiu   sp,sp,-24
  400614:       afbe0014        sw      s8,20(sp)
  400618:       03a0f021        move    s8,sp
  40061c:       24020003        li      v0,3
  400620:       afc20008        sw      v0,8(s8)
  400624:       24020004        li      v0,4
  400628:       afc2000c        sw      v0,12(s8)
  40062c:       8fc30008        lw      v1,8(s8)
  400630:       8fc2000c        lw      v0,12(s8)
  400634:       00621021        addu    v0,v1,v0
  400638:       03c0e821        move    sp,s8
  40063c:       8fbe0014        lw      s8,20(sp)
  400640:       27bd0018        addiu   sp,sp,24
  400644:       03e00008        jr      ra
  400648:       00000000        nop

在C/C++函数中,启动程序函数,再向上就是汇编了, 这是能backtrace C/C++的最后一层

00400440 <__start>:
  400440:       3c1c0042        lui     gp,0x42        ##Load GOT
  400444:       279c8920        addiu   gp,gp,-30432
  400448:       0000f821        move    ra,zero
  40044c:       3c040040        lui     a0,0x40
  400450:       248406c0        addiu   a0,a0,1728
  400454:       8fa50000        lw      a1,0(sp)
  400458:       27a60004        addiu   a2,sp,4
  40045c:       2401fff8        li      at,-8
  400460:       03a1e824        and     sp,sp,at
  400464:       27bdffe0        addiu   sp,sp,-32
  400468:       3c070040        lui     a3,0x40
  40046c:       24e70710        addiu   a3,a3,1808
  400470:       3c080040        lui     t0,0x40
  400474:       250807b8        addiu   t0,t0,1976
  400478:       afa80010        sw      t0,16(sp)
  40047c:       afa20014        sw      v0,20(sp)

从上面的分析来看,

1. 进入函数最先做的事情就是开辟堆栈:

27bdffe8        addiu   sp,sp,-24

2.如果ra要保护,则需要入栈

afbf001c        sw      ra,28(sp)

3. 最顶层的C/C++函数是要准备GOT的

 400440:       3c1c0042        lui     gp,0x42

4.一个函数结束,需要跳回到ra指向的地址

400644:       03e00008        jr      ra

有了以上三点,就可以backtrace了:

#include "syscall.h"
#define abs(X) ((X)>=0?(X):(-(X)))

int autobt_mips32(void **btbuffer,int size)
{
    unsigned long *addr;
    unsigned long *ra;
    unsigned long *sp;
    
    unsigned int raoffset;
    unsigned int stacksize;
    
    unsigned int bt;

    if(!btbuffer || size<0)
    {
        return -1;
    }
    
    //获取当前函数的ra和sp
    __asm__ __volatile__(
        "move %0, $ra\n"
        "move %1, $sp\n"
        :"=r"(ra),"=r"(sp)
        );

    //因为当前函数是叶子函数,所以ra不会再被占用,因此不会将ra入栈,所以不用去找ra的在sp中的偏移地址,因此ra中的值就是调用autobt_mips32的下一条指令地址

    stacksize = 0;
    for(addr=(unsigned long*)autobt_mips32;;++addr) //从当前函数的起始地址找堆栈大小
    {
        if((*addr&0xffff0000) == 0x2fbd0000)    //0x2fbd is "addiu sp,sp",前面分析过,这个指令是为函数开辟堆栈的
        {
            //当发现是开辟堆栈的指令时,取出堆栈大小
            stacksize = abs((short)(*addr&0xffff));    //mips堆栈是负增长,所以要取绝对值
            if(stacksize != 0)
            {
                break;
            }
        }
        else if(*addr == 0x3e00008)        //0x3e00008 is "jr ra"
        {
            //发现返回指令,说明已经找到头了,退出查找
            break;
        }
    }

    //找到了autobt_mips32使用堆栈的大小,就可以算出autobt_mips32调用者的堆栈指针
    sp =(unsigned long *)((unsigned long)sp + stacksize);

    //做backtrace
    for(bt=0;bt<size&&ra;bt++)
    {
        btbuffer[bt]=ra;    
        
        raoffset = 0;
        stacksize = 0;
        
        for(addr=ra; raoffset==0||stacksize==0; addr--)    //从ra开始向上找
        {
            switch(*addr&0xffff0000)    //get instruction
            {
                case 0x27bd0000:    //找到开辟堆栈的指令,保存堆栈的值
                    stacksize = abs((short)(*addr&0xffff));
                    break;
                case 0xafbf0000:    //从前面的分析可以知道0xafbf 是"sw ra (XX)sp",这里就是ra存放的偏移地址
                    raoffset = (short)(*addr&0xffff);
                    break;
                case 0x3c1c0000:    //找到C/C++ 函数的最后一层, 停止 backtrace。0x3c1c 是"lui gp"
                    return bt+1;
                    break;
                default:
                    break;
            }
        }
        
        //设置上一层调用者的调用本层函数的返回地址(ra的地址在上一层函数中)和堆栈地址        
        ra =(unsigned long *)((unsigned long)ra + raoffset);
        sp =(unsigned long *)((unsigned long)sp + stacksize);
        
    }

    return bt;
}

当想要知道一个函数的backtrace时,可以在这个函数内call autobt_mips32, 函数的返回值就是backtrace的层数,而btbuffer中保存的是backtrace的地址,根据这些地址和运行程序的反汇编,很容易就可以对应出实际函数的调用关系

























你可能感兴趣的:(MIPS32 backtrace)