文档章节

MIPS32 backtrace

lgl88911
 lgl88911
发布于 2014/04/11 16:27
字数 1114
阅读 552
收藏 1

在嵌入式开发过程中,经常会遇到出问题了想知道函数调用关系,不过目前我用到的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的地址,根据这些地址和运行程序的反汇编,很容易就可以对应出实际函数的调用关系

























© 著作权归作者所有

共有 人打赏支持
lgl88911
粉丝 18
博文 83
码字总数 50237
作品 0
成都
高级程序员
MIPS32模拟器--SPIM

SPIM是一个独立的MIPS32模拟器,可以读取和运行MIPS32汇编程序。同时提供了一个简单的调试器和最小化的操作系统服务,但是它并不能运行编译后的二进制程序。 SPIM几乎完整(除浮点数比较、舍...

漆兴
2015/04/22
2.1K
0
MIPS技术公司官方对linux的支持信息

Linux MIPS Technologies actively supports, develops and improves the Linux kernel for the MIPS® architecture, in particular MIPS Technologies cores, the MIPS32®, microMIPS™ ......

RyaneLuo
2012/07/30
0
0
用Makefile和gccgo来构建mips32下的Go项目报错,求助

由于我的是mips32,go不支持mips32,只能用gccgo来编译够程序,而且我的project组织结构比较复杂,分了许多目录和库呀等等,必须自己写Makefile来构建我的go项目 假设我有源文件/my/path/a....

TymonHuang
2016/10/25
98
0
国产纯Java多核体系结构模拟器--Archimulator

Archimulator 国产纯Java多核体系结构模拟器: Written in 100% Java with the aid of JNA (Java Native Access) for native POSIX syscall implementations, which assures its portabilit......

mcai
2012/09/04
986
0
路由器固件安全分析技术(一)

前言 本文可作为路由器安全的入门学习教程,一起学习从零基础从搭建环境开始入门路由器固件安全分析的技术。 搭建环境篇 演示系统:debian 3.16.0-4-686-pae 本篇重在演示路由器固件分析及运...

广岛秋泽
2017/06/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

kubeadm部署kubernetes集群

一、环境要求 这里使用RHEL7.5 master、etcd:192.168.10.101,主机名:master node1:192.168.10.103,主机名:node1 node2:192.168.10.104,主机名:node2 所有机子能基于主机名通信,编辑...

人在艹木中
今天
2
0
Shell特殊符号总结以及cut,sort,wc,uniq,tee,tr,split命令

特殊符号总结一 * 任意个任意字符 ? 任意一个字符 # 注释字符 \ 脱义字符 | 管道符 # #号后的备注被忽略[root@centos01 ~]# ls a.txt # 备注 a.txt[root@centos01 ~]# a=1[root@centos01...

野雪球
今天
2
0
OSChina 周二乱弹 —— 程序员圣衣

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @达尔文:分享Skeeter Davis的单曲《The End of the World》 《The End of the World》- Skeeter Davis 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
14
0
[ python import module ] 导入模块

import moudle_name ----> import module_name.py ---> import module_name.py文件路径 -----> sys.path (这里进行查找文件) # from app.web import Personimport app.web.Person as Pe......

_______-
昨天
5
0
Redis性能问题排查解决手册

一、性能相关的数据指标 通过Redis-cli命令行界面访问到Redis服务器,然后使用info命令获取所有与Redis服务相关的信息。通过这些信息来分析文章后面提到的一些性能指标。 nfo命令输出的数据可...

IT--小哥
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部