您的位置:首页 > 其它

ARM系统中, 当crash发生时的back trace调试输出

2007-08-08 22:48 260 查看
[align=left]转载时请注明出处和作者联系方式:http://blog.csdn.net/mimepp [/align]
ARM系统中, 当crash发生时的back trace调试输出
作者:余涛(yut616_at_sohu.com)
关键字:一般保护错 back trace

在ARM target board上, 如果发生异常,如内存访问越界等情况,有时会非常难debug到底是哪里出错,近来看了一下back trace回溯的功能及实现,在这里做个笔记。

首先,back trace要涉及到一堆寄存器, 不过不用担心, 实际上没你想象的那么难。

在linux kernel的代码中可以看到关于ARM寄存器的定义,在这里列一下:
linux/include/asm-arm/proc-armv/ptrace.h




struct pt_regs ...{


long uregs[18];


};




#define ARM_cpsr uregs[16] //当前程序状态寄存器


#define ARM_pc uregs[15] //程序计数器 指令计数器,下一条将要执行的指令


#define ARM_lr uregs[14] //链接link寄存器,是退出时要装载到pc中的值,返回回来时就是到这个里面去执行之前的代码


#define ARM_sp uregs[13] //栈指针


#define ARM_ip uregs[12] //指令指针,指向下一指令


#define ARM_fp uregs[11] //帧指针


#define ARM_r10 uregs[10]


#define ARM_r9 uregs[9]


#define ARM_r8 uregs[8]


#define ARM_r7 uregs[7]


#define ARM_r6 uregs[6]


#define ARM_r5 uregs[5]


#define ARM_r4 uregs[4]


#define ARM_r3 uregs[3]


#define ARM_r2 uregs[2]


#define ARM_r1 uregs[1]


#define ARM_r0 uregs[0]


#define ARM_ORIG_r0 uregs[17]



这里比较重要的几个寄存器是fp,lr,sp,继续往后讨论。

我们可以先想象一个情景,某个调用过程

A(…)
{
…;
B(…);
printf("aaa ");

}

在A调B的时候,大体的过程会是A在执行,执行到B,进入B执行,B执行完后,在A中的B下面的部分继续执行。
这样的话,在转到B去执行的时候,要准备好一些东西,来保证B完成时还回到A里来继续往下执行。
怎么来保证上面的这个问题呢,那就是要用到lr寄存器来存A处的信息,这个是link register链接寄存器的缩写,它就好比一个链条一样,把一堆调用给串起来。
所以上面的过程就会是,要执行到B时,把B的地址放入pc中,这样下面就要执行B了。Pc是指令计数器,放在其中的地址就将被执行。这个时候还要保存刚才我们提到的A的信息,把需要返回的值放在lr中。

在ARM的过程调用中,有个结构叫回溯结构,还有两个概念叫帧和栈,它们的关系是:
回溯结构
|-----------|------------------------------| 帧
栈 … 帧
… 帧

回溯结构在帧的高端,通过这个回溯结构,就可以把一个一个的帧连接成栈。
帧指针fp,frame pointer就是指向把栈给串起来的一个一个回溯结构所组成的列表中的最后一个结构。这样就可以通过这个fp来追溯函数的调用顺序,在系统崩溃时就可以得到最后调用的函数都是哪些。


回溯结构是:


高地址


保存代码指针 [fp] fp指向这里


返回lr值 [fp, #-4]


返回sp值 [fp, #-8]


返回fp值 [fp, #-12] 指向下一个结构





低地址

从上面的内容,我们只要循环打印上面lr中的值就可以得到我们想要的一系列的地址了。


long fp, nextfp;


fp = regs->ARM_fp;


long lr;


for(XXX;XXX;XXX) //循环多少,可以自己决定




...{


lr = *(( long *)(fp - 4));


nextfp = *(( long *) fp - 12));


fp = nextfp;




printk(“call address: 0x%lx ”, lr- current->mm->start_code - 4);


}



上面的current->mm->start_code是当前进程的数据段基地址,再减4,就能得到返回处前面的地址。打印出来的内容也就是调用函数的地址。
如:


call address: 0x2849cc


call address: 0x3b8b70


call address: 0x2c270


call address: 0x2c5ec


call address: 0x2b298


call address: 0x283934


call address: 0x3c41a8


你可以用objdump得到你的执行文件的sym文件,如:
arm-elf-objdump -SD test.gdb > /tmp/test.gdb.sym
然后就可以拿这个循环打印出来的地址,来搜索出错的位置了。

上面的代码只是一个参考,具体还要看你的系统实际的情况。

另外还看到一个start_thread,列一下做个记载。


#define start_thread(regs,pc,sp)




(...{


unsigned long *stack = (unsigned long *)sp;


set_fs(USER_DS);


memzero(regs->uregs, sizeof(regs->uregs));


if (current->personality & ADDR_LIMIT_32BIT)


regs->ARM_cpsr = USR_MODE;


else


regs->ARM_cpsr = USR26_MODE;




regs->ARM_pc = pc; /**//* pc */




regs->ARM_sp = sp; /**//* sp */




regs->ARM_r2 = stack[2]; /**//* r2 (envp) */




regs->ARM_r1 = stack[1]; /**//* r1 (argv) */




regs->ARM_r0 = stack[0]; /**//* r0 (argc) */




regs->ARM_r10 = current->mm->start_data; /**//* data segment base */


})
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: