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