Chinaunix首页 | 论坛 | 博客
  • 博客访问: 775313
  • 博文数量: 247
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 501
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-12 21:53
个人简介

系统未建立

文章分类

全部博文(247)

文章存档

2021年(1)

2020年(3)

2019年(5)

2018年(3)

2017年(44)

2016年(75)

2015年(52)

2014年(63)

2013年(1)

我的朋友

分类: LINUX

2016-08-09 16:45:43

最近需要对mips架构的堆栈回溯进行开发,查询了一些资料,发现“根据pc和sp进行逆向遍历指令”相对比较简单有效。后来找到如下文章,与我所想一致,转载记录下
ZZ: http://my.oschina.net/lgl88911/blog/221036
摘要
提供一种mips32 的backtrace方式用于嵌入式调试

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

0040064c :
  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 
  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 :
  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;btsize&&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的地址,根据这些地址和运行程序的反汇编,很容易就可以对应出实际函数的调用关系



阅读(1750) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~