嵌入式之栈回溯backtrace篇

程序发生段错误借助gdb工具去定位是非常好用而精准的。但如果有些段错误是概率性发生,那么有时使用gdb工具去定位就显得那么好用了。如果在代码里去监控段错误,段错误一旦发生,就能很好得记录段错误信息,就能排查问题。所以gdb工具一般只适用于程序调试阶段,而如果想要在程序维护阶段去监控段错误的发生,只能在代码里实现段错误的监控措施,即栈回溯。

int backtrace(void **buffer,int size)

该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存堆栈的个数,如果size 不够,小于栈回溯的长度,怎么将无法完整打印栈回溯。所以size需要保证大于栈回溯的长度。 函数返回值是实际获取的堆栈的个数,最大不超过size大小。在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。

char ** backtrace_symbols (void *const *buffer, int size);

backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。函数返回值是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址。

注意:该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针.如果不能为字符串获取足够的空间函数的返回值将会为NULL。

段错误例程:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <execinfo.h>

typedef struct 
{
	const char *name;
	int signum;
	int flags;		//0:default handler, >0:catch, <0:ignore
}SigTable_S;

static SigTable_S sigTable[] = 
{
	{"zero", 0, 0},
	{"SIGHUP", SIGHUP, -1},
	{"SIGINT", SIGINT, 1},
	{"SIGQUIT", SIGQUIT, -1},
	{"SIGILL", SIGILL, 1},
	{"SIGTRAP", SIGTRAP, -1},
	{"SIGABRT", SIGABRT, 1},
	{"SIGBUS", SIGBUS, 1},
	{"SIGFPE", SIGFPE, -1},
	{"SIGKILL", SIGKILL, 0},		// reserved
	{"SIGUSR1", SIGUSR1, 2},		// for light flicker
	{"SIGSEGV", SIGSEGV, 1},
	{"SIGUSR2", SIGUSR2, -1},
	{"SIGPIPE", SIGPIPE, -1},
	{"SIGALRM", SIGALRM, -1},
	{"SIGTERM", SIGTERM, 1},		// for kill
	{"SIGSTKFLT", SIGSTKFLT, -1},
	//{"SIGCHLD", SIGCHLD, -1},
	{"SIGCONT", SIGCONT, -1},
	{"SIGSTOP", SIGSTOP, 0},		// reserved
	{"SIGTSTP", SIGTSTP, -1},
	{"SIGTTIN", SIGTTIN, -1},
	{"SIGTTOU", SIGTTOU, -1},
	{"SIGURG", SIGURG, -1},
	{"SIGXCPU", SIGXCPU, -1},
	{"SIGXFSZ", SIGXFSZ, -1},
	{"SIGVTALRM", SIGVTALRM, -1},
	{"SIGPROF", SIGPROF, -1},
	{"SIGWINCH", SIGWINCH, -1},
	{"SIGIO", SIGIO, -1},
	{"SIGPWR", SIGPWR, -1},
	{"SIGSYS", SIGSYS, -1},
};

void Fun_Print_BackTrace(int signum)
{
	if ((signum > 0) && (signum <= SIGSYS)) 
    {
		void *array[30]={0};
		size_t size=0,i=0;
		char** strings=NULL;
		char logbuf[256];
		char processname[20];
		pid_t pid = getpid();
		//Fun_GetNameByPid(pid,processname);
		sprintf(logbuf, "Signal Catched!process is :%s, signal is %s(%d)\n",processname,sigTable[signum].name,signum);
		printf("%s\n",logbuf);
		//UserLog_Error_Local1_API(logbuf);
		size=backtrace(array,30);
		printf("size =%ld\n",size);
		strings=backtrace_symbols(array,size);
		for(i=0;i<size;i++)
		{	
			sprintf(logbuf,"%s\n",strings[i]);
			printf("%s\n",logbuf);
			//UserLog_Error_Local1_API(logbuf);
		}
		free(strings);
		strings = NULL;
	}
	else 
    {
        printf("\n\n\n@@@@@@@@@@@@@@@@@@@@@ signum is :%d line[%d]\n\n\n",signum, __LINE__);
    }
	exit(0);
	
}

int SetupSignalHandlers()
{
	struct sigaction sigsetup;
	unsigned int i;

	//set signal handler
	for(i=0; i<sizeof(sigTable)/sizeof(sigTable[0]); i++)
	{
		//exit on system signal
		if(sigTable[i].flags == 1)
		{
			memset(&sigsetup, 0, sizeof(sigsetup));		
			sigsetup.sa_sigaction = Fun_Print_BackTrace;
			sigsetup.sa_flags = SA_SIGINFO;
			sigemptyset(&sigsetup.sa_mask);
			sigaction(sigTable[i].signum, &sigsetup, NULL);
		}
		//ignore these
		if(sigTable[i].flags < 0)
		{
			memset(&sigsetup, 0, sizeof(sigsetup));		
			sigsetup.sa_handler = SIG_IGN;
			sigsetup.sa_flags = 0;
			sigemptyset(&sigsetup.sa_mask);
			sigaction(sigTable[i].signum, &sigsetup, NULL);
		}
	}
	
	return 0;
}

int fun()
{
	int a;
	int b=1+5;
	return 0;
}

int sub(int c)
{
	int a;
	char *b2="chen";
	*b2='1';
	fun();
	return c;
}

int aad(int a , int b)
{
	
	sub(a);
	return a+b;
}

int add(int a , int b)
{
	int t = 2,d,g;
	d = aad( a , b); 
	g= t+d;
	return g;
}
int main()
{
	SetupSignalHandlers();
	int ab = 100;
	int abc = 20;
	int c;
	c = add(ab,abc);
	printf("c = %d\n",c);
	return 0;
}

编译选项:
编译的时候一定加上-g -rdynamic,-rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果不加rdynamic选项,则发生段错误是,无法将函数名打印出来。

gcc -o strace strace.c -g -rdynamic

测试结果如下:
在这里插入图片描述
通过栈回溯,可以将发生段错误的函数名,函数的偏移地址,和实际的返回地址打印出来,并且可以回溯打印。

详细定位吨错误的位置(在文件的哪一行)
唯一不足的地方就是无法打印行号,就无法详细定位段错误的地方,这时可以借助addr2line工具去定位段错误的详细地方。

addr2line

addr2line 0x400f3c -e srace -f

在这里插入图片描述
addr2line简介
Addr2line (它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。
用法:addr2line [选项] [地址]
将地址转换成文件名/行号对。
如果没有在命令行中给出地址,就从标准输入中读取它们
选项是:
-a --addresses 显示地址
-b --target= 设置二进位文件格式
-e --exe= 设置输入文件名称(默认为 a.out)
-i --inlines 解开内联函数
-j --section= 读取相对于段的偏移而非地址
-p --pretty-print 让输出对人类更可读
-s --basenames 去除目录名
-f --functions 显示函数名
-C --demangle[=style] 解码函数名
-h --help 显示本帮助

注*static 函数 是无法·导出符号表的,所以栈回溯是无法打印静态函数的函数名,函数的偏移地址,和实际的返回地址。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 以下是 VxWorks 中获取栈回溯信息的源码示例: ```c void stackTraceTaskPrint (int tid) { REG_SET regSet; WIND_TCB *pTcb = taskIdVerify (tid); if (pTcb == NULL) return; if (taskRegsGet (tid, &regSet) != OK) return; printf ("Stack trace for task %#x (%s):\n", (int) pTcb, pTcb->name); stackTracePrint ((char *) pTcb->name, tid, (char *) regSet.spReg, (char *) regSet.pc); } ``` 此函数获取给定任务 ID 的任务控制块,并通过 taskRegsGet 函数获取该任务的寄存器值,然后调用 stackTracePrint 函数打印该任务的栈回溯信息。 ```c void stackTracePrint (char *name, int tid, char *sp, char *pc) { int count; char **list; list = (char **) malloc (MAX_TRACE_DEPTH * sizeof (char *)); if (list == NULL) return; count = stackTrace (tid, sp, pc, list, MAX_TRACE_DEPTH); printf ("Stack trace for task %#x (%s):\n", tid, name); stackTraceDepthPrint (list, count); free (list); } ``` 此函数分配堆内存以存储栈回溯信息,并调用 stackTrace 函数获取栈回溯信息。然后它将回溯信息作为参数传递给 stackTraceDepthPrint 函数,以打印栈回溯信息。 ```c int stackTrace (int tid, char *sp, char *pc, char **pList, int maxDepth) { int i = 0; int depth = 0; FUNCPTR funcPtr; char *nextPc; char **list = pList; while (i < maxDepth) { if (taskIdVerify (tid) == NULL) break; if (i == 0) { if (pc == 0) break; funcPtr = (FUNCPTR) pc; } else { if (nextPc == 0) break; funcPtr = (FUNCPTR) (nextPc - CALL_INSTR_SIZE); } if (funcPtr == NULL) break; *list++ = (char *) funcPtr; depth++; i++; if (taskRegsStackProbe (tid, (char *) funcPtr, &nextPc) != OK) break; } return (depth); } ``` 此函数根据给定的任务 ID、堆栈指针和程序计数器获取栈回溯信息。它使用 taskRegsStackProbe 函数来确定每个函数调用的下一个程序计数器值,并将每个函数的地址存储在列表中。如果达到最大深度或无法确定下一个程序计数器,则停止回溯。 ```c void stackTraceDepthPrint (char **list, int depth) { int i; for (i = 0; i < depth; i++) printf ("%s\n", symFindByValue ((int) list[i], N_TEXT, (char **) NULL, (char **) NULL)); } ``` 此函数用于打印回溯列表中的每个函数地址对应的符号名称。它使用 symFindByValue 函数查找每个地址对应的符号名称,并将其打印到控制台上。 ### 回答2: vxWorks是一款实时操作系统,它的栈回溯功能可以帮助开发人员定位程序的异常或错误。栈回溯是指在程序发生异常或错误时,通过分析程序运行时的调用栈来定位出错的源码位置。 在vxWorks中,栈回溯功能是通过函数backtrace()实现的。backtrace()函数会返回当前函数调用栈的信息,包括每个函数的返回地址和参数值。使用该函数可以获取当前栈的信息,然后通过解析栈信息,可以找到调用栈中每个函数的源码位置。 具体的栈回溯源码如下: 1. 定义一个结构体存储函数调用栈信息: ```c #define MAX_STACK_FRAMES 50 typedef struct { void *returnAddr; void *args[MAX_ARGS]; } StackFrame; ``` 2. 定义栈回溯函数backtrace(),该函数会获取当前函数调用栈信息: ```c void backtrace(StackFrame *frames, int maxFrames) { int frameCount = 0; // 使用汇编指令获取栈帧信息 // 将返回地址和参数值存储到StackFrame结构体中 while (frameCount < maxFrames && frameCount < MAX_STACK_FRAMES) { frames[frameCount].returnAddr = __builtin_return_address(frameCount); frameCount++; } } ``` 3. 在需要进行栈回溯的地方,调用backtrace()函数,并解析栈信息: ```c StackFrame frames[MAX_STACK_FRAMES]; backtrace(frames, MAX_STACK_FRAMES); for (int i = 0; i < MAX_STACK_FRAMES; i++) { void *returnAddr = frames[i].returnAddr; // 解析返回地址和参数值,找到对应的源码位置 // 打印出错函数、源码文件名和行号等信息 } ``` 通过以上的源码实现,我们可以获取程序运行时的调用栈信息,从而快速定位程序的异常或错误源码位置。这对于调试程序非常有帮助,可以提高开发效率。 ### 回答3: vxWorks是一个实时操作系统,它提供了栈回溯功能,可以帮助开发人员快速定位程序中的问题。 在vxWorks中,栈回溯源码的实现是基于任务控制块(Task Control Block, TCB)和堆栈溢出检测技术。 首先,每个任务在TCB中都有一个指向堆栈顶部的指针,通过这个指针可以访问任务的堆栈。当任务运行时,它的函数调用和局部变量会存储在堆栈中。当任务出现问题时,可以通过访问任务的堆栈来获取栈回溯信息。 在vxWorks中,堆栈溢出检测技术可以帮助检测任务是否发生了栈溢出。当任务的堆栈溢出时,会触发一个硬件中断。当这个中断发生时,系统会保存当前的寄存器状态,并调用栈回溯函数。 栈回溯函数是vxWorks中栈回溯功能的核心。它会逐级遍历任务的调用栈,获取每一级函数的返回地址和参数,并将这些信息打印输出。这样,开发人员就可以根据栈回溯信息来定位程序中的问题。 栈回溯函数的源码在vxWorks的源代码中可以找到,一般位于目录"vxworks\kernel\lib"中。栈回溯函数会使用汇编语言和C语言混合编程实现,通过访问任务的堆栈来获取栈回溯信息,然后将这些信息格式化输出。 总之,vxWorks提供了栈回溯源码,开发人员可以通过它来定位程序中的问题。栈回溯的实现依赖于任务的堆栈和堆栈溢出检测技术,通过遍历任务的调用栈获取栈回溯信息,并将其打印输出。这样,开发人员就可以根据栈回溯信息来进行程序调试和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值