在嵌入式设备开发中,内核为内核模块的函数栈追踪已经提供了很好的支持,但用户层的函数栈追踪确没有很好的提供支持。在网上收集学习函数栈跟踪大部分都是描述INTER体系架构支持栈帧的实现机制,或者直接给出glibc的现成库函数。但如果开发环境是broadcom相关方案,通常使用的是MIPS32的体系架构,并且C库使用的是更小的uclibc,虽然MIPS32体系架构中也定义了栈帧寄存器s8(类似于Inter体系架构中常见的ebp寄存器),但经过GCC编译器的优化选项控制后,通常在O1以上的优化就已经去除了s8栈帧的使用,所以给函数栈追踪的实现就带来了一点点小麻烦。
通常情况下,函数的返回值还是使用压栈实现,所以只要知道了函数每次调用时,返回值(ra寄存器)与当前栈顶(sp寄存器)的偏移量,就可以实现函数栈追踪,对函数反汇编可以了解到这个偏移量通过sw ra, xxxx(sp)可以得到,另一个难题是怎么得到函数每次调用时的当前栈顶(sp寄存器),通过函数反汇编可以了解到addiu sp, sp, xxxx指令就是每次给函数分配当前栈帧大小的,所以只要得到这个栈帧大小然后用sp进行差值计算就可以往回推出上一个sp的值了,还剩下最后一个问题,一步步获取上一个函数的栈顶,什么时候结束?答案就是只要栈顶sp的寄存器为0就追踪到头了,通过这些分析我们可以了解到当前实现机制根本没用到程序的栈段内容,对,完全从程序指令段做为线索,获取程序运行时指令一步步找出函数栈的调用,看起来很酷,但现实也比较残酷,比如我们上面分析说要得到栈顶sp的寄存器为0表示追踪到头,我当前调试环境在__start(可以参考一些链接加载的相关技术资料了解,其它main并不是c的起始函数,__start才是c语言的起始函数)函数中使得sp为0的汇编指令为addu ra, zero, zero,我还不清楚其它编译器是否会使用其它指令方法进行设置,所以我们当前实现的这个函数栈追跟踪功能的实现代码并不是很标准的,如果读者有幸参考该代码,请一定要在理解上面描述的原理基础上,另外需要在您自己的编译开发环境进行调试,确保这个追踪功能代码自己不会引发Crash再使用,否则那玩笑就开的大了。
剩下就不多说了,直接付上我当前的开发环境及实现源码,以及最终在broadcom6838板上运行的测试程序的效果图。
开发环境:
实现源码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <ucontext.h>
#define abs(s) ((s) < 0 ? -(s) : (s))
#define CALL_TRACE_MAX_SIZE (10)
#define MIPS_ADDIU_SP_SP_XXXX (0x27bd0000) /* instruction code for addiu sp, sp, xxxx */
#define MIPS_SW_RA_XXXX_SP (0xafbf0000) /* instruction code for sw ra, xxxx(sp) */
#define MIPS_ADDU_RA_ZERO_ZERO (0x0000f821) /* instruction code for addu ra, zero, zero */
void getCodeIn(unsigned long codeAddr, char *pCodeIn, int iCodeInSize, unsigned long *pOffset)
{
FILE *pFile = NULL;
char szLine[1000] = {0};
pFile = fopen("/proc/self/maps", "r");
if ( pFile != NULL )
{
while (fgets(szLine, sizeof(szLine), pFile))
{
char *pTmp = NULL;
char szAddr[500] = {0};
char szCodeIn[500] = {0};
unsigned long begin = 0;
unsigned long end = 0;
sscanf(szLine, "%s %*s %*s %*s %*s %s", szAddr, szCodeIn);
pTmp = strchr(szAddr, '-');
if ( pTmp != NULL )
{
*pTmp++ = '\0';
begin = strtoul(szAddr, NULL, 16);
end = strtoul(pTmp, NULL, 16);
}
if ( codeAddr >= begin && codeAddr <= end )
{
strncpy(pCodeIn, szCodeIn, iCodeInSize);
*pOffset = codeAddr - begin;
return;
}
}
fclose(pFile);
}
strncpy(pCodeIn, "unknown", iCodeInSize);
*pOffset = 0;
}
int backtrace_mips32(void **buffer, int size, ucontext_t *uc)
{
unsigned long *tmpl = NULL;
unsigned long *addr = NULL;
unsigned long *ra = NULL;
unsigned long *sp = NULL;
unsigned long *pc = NULL;
size_t ra_offset = 0;
size_t stack_size = 0;
int depth = 0;
if (size == 0)
{
return 0;
}
if (buffer == NULL || size < 0 || uc == NULL)
{
return -1;
}
pc = (unsigned long *)(unsigned long)uc->uc_mcontext.pc;
ra = (unsigned long *)(unsigned long)uc->uc_mcontext.gregs[31];
sp = (unsigned long *)(unsigned long)uc->uc_mcontext.gregs[29];
buffer[0] = pc;
if ( size == 1 )
{
return 1;
}
for ( addr = pc; !ra_offset || !stack_size; --addr )
{
if ( ((*addr) & 0xffff0000) == MIPS_SW_RA_XXXX_SP)
{
ra_offset = (short)((*addr) & 0xffff);
}
else if ( ((*addr) & 0xffff0000) == MIPS_ADDIU_SP_SP_XXXX)
{
stack_size = abs((short)((*addr) & 0xffff));
}
else if ( (*addr) == MIPS_ADDU_RA_ZERO_ZERO )
{
return 1;
}
}
if ( ra_offset > 0 )
{
tmpl = (unsigned long *)((char *)sp + ra_offset);
ra = (unsigned long *)(*tmpl);
}
if ( stack_size > 0 )
{
sp = (unsigned long *)((unsigned long)sp + stack_size);
}
for (depth = 1; depth < size && ra; ++depth)
{
buffer[depth] = ra;
ra_offset = 0;
stack_size = 0;
for (addr = ra; !ra_offset || !stack_size; --addr)
{
if ( ((*addr) & 0xffff0000) == MIPS_SW_RA_XXXX_SP)
{
ra_offset = (short)((*addr) & 0xffff);
}
else if ( ((*addr) & 0xffff0000) == MIPS_ADDIU_SP_SP_XXXX)
{
stack_size = abs((short)((*addr) & 0xffff));
}
else if ( (*addr) == MIPS_ADDU_RA_ZERO_ZERO )
{
return depth + 1;
}
}
tmpl = (unsigned long *)((char *)sp + ra_offset);
ra = (unsigned long *)(*tmpl);
sp = (unsigned long *)((unsigned long)sp + stack_size);
}
return depth;
}
void signal_process(int sig_no, siginfo_t *sig_info, void *ucontext)
{
int i = 0;
unsigned long *callStack[CALL_TRACE_MAX_SIZE] = {0};
printf("\r\n*******************************************\r\n");
printf("recv signo %d\r\n", sig_no);
backtrace_mips32((void **)callStack, CALL_TRACE_MAX_SIZE, (ucontext_t *)ucontext);
printf("\r\ncall tracing:\r\n");
for ( i = 0; i < CALL_TRACE_MAX_SIZE; i++ )
{
char szCodeIn[100] = {0};
unsigned long addrOffset = 0;
if ( callStack[i] == 0 )
{
break;
}
getCodeIn((unsigned long)callStack[i], szCodeIn, sizeof(szCodeIn), &addrOffset);
printf("\t[%02d] addr 0x%08x(offset 0x%08x), in %s\r\n", i, callStack[i], addrOffset, szCodeIn);
}
printf("*******************************************\r\n");
if (sig_no == SIGSEGV)
{
signal(sig_no, SIG_DFL);
}
sleep(3);
}
void c(void)
{
int *p = 0;
printf("I am function [c]\r\n");
*p = 888;
}
void b(void)
{
printf("I am function [b]\r\n");
c();
}
void a(void)
{
printf("I am function [a]\r\n");
b();
}
int main(int argc, char *argv[])
{
struct sigaction act = {0};
act.sa_sigaction = signal_process;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &act, NULL);
printf("I am function [main]\r\n");
a();
return 0;
}
运行效果图: