SIGSEGV时自动打印栈帧(2014.02.28 更新)

有些嵌入式设备内存,flash 小,根本不能跑gdb, 连带调试符号的软件flash都装不下。这时调试死机问题就比较麻烦,下面我介绍一种方法:

让程序死时自动把栈帧打印出来。

 

/*backtrace.c -- print back trace on linux.
 *
 * test:
 *   gcc -g -D LINUX_X86 backtrace.c -o bt
 *   ./bt | gawk '{system("addr2line -f -s -e bt "  $4);}'
 *
 * author: ludi 2013.12
 * */
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

static uintptr_t get_max_stack(void)
{
    static const char file_name[] = "/proc/self/maps";
    char line[1024];
    int line_number;
    FILE *f;

    f = fopen(file_name, "r");
    if (f == NULL) {
        printf("opening %s failed", file_name);
        return -1;
    }

    for (line_number = 1; fgets(line, sizeof line, f); line_number++) {
		
        if (strstr(line, "[stack]")) {
            uintptr_t end;
            if (sscanf(line, "%*x-%"SCNxPTR, &end) != 1) {
                printf("%s:%d: parse error", file_name, line_number);
                continue;
            }
            fclose(f);
            return end;
        }
    }
    fclose(f);

    printf("%s: no stack found", file_name);
    return -1;
}

#if defined(LINUX_ARM)
/*arm stack address from high to low:
 * PC LR SP FP params locals PC LR SP FP params locals
 * ^                                  |
 * |__________________________________|
 * */
int backtrace( int size)
{
	uintptr_t fp, lr;
	uintptr_t low = (uintptr_t)&fp, high = get_max_stack();
	int i;
	__asm__ __volatile__(" \
              mov %0, r11"
              :"=r" (fp)
              :
       );

	for(i = 0; i < size; ++i){
		if(!(low <= fp  && fp < get_max_stack() ))break;

		lr = *((uintptr_t*)fp - 1);
		fp = *((uintptr_t*)fp - 3);
		printf("backtrace #%02d at 0x%x\n", i, lr);
	}
	
	return i;
}
#endif

#if defined(LINUX_X86)
/* x86 stack address from hight to low:
* params eip ebp locals
*            ebp    esp
* */

int backtrace(int size)
{
       uintptr_t ebp, eip;
	   int i;
	   uintptr_t low = (uintptr_t)&ebp, high = get_max_stack();

       __asm__ __volatile__(" \
              movl %%ebp, %0"
              :"=g" (ebp)
              :
              :"memory"
       );

       for(i = 0; i < size; i++){
			  if(!(low <= ebp  && ebp < get_max_stack() ))break;
              eip = (uintptr_t)((uintptr_t*)ebp + 1);
              eip = *(uintptr_t*)eip;
              ebp = *(uintptr_t*)ebp;

			  printf("backtrace #%02d at 0x%x\n", i, eip);
       }

       return i;
}
#endif

func3()
{
	backtrace(100);
}

func2()
{
	func3();
}

void func1()
{
	func2();
}

int main(int argc, char*argv[])
{
	func1();
	return 0;
}



 

我实测

像这样的可以正确打印:

void func_segv()
{
    * ((volatile int *) 0x0) = 0xDEAD;
}

 

但是像一些复杂的死机问题, 连gdb打印不出来。(gdb 也不能准确定位,有时运气好只给出有关的文件)

 

注记:

实际运用中,放到flash上的bt是strip过的,没有strip 的bt 还要保留给addr2line用。

 

2014.02.28 更新:

如果没有带调试符号的, 应该还有map 文件。$S 给驱动开发人员就是提供调试符号文件的。

但是map文件中地址是函数定义地址,栈帧打出来的是调用点地址,如何对应回去呢? 其实注意观察map 文件发现地址都是递增的,并且栈帧地址位于两个高定义地址中间,

我们就可以使用这两点就够了,详细的见下面:

/*search_map_file.c -- search name of a backtrace address in a map file.

how to generate a map file?
 gcc -Wl,-Map,a.def a.c
 
*/
#include <stdio.h>
#include <string.h>


char*  search_map_file(FILE *mapfile, char *key)
{
	char buf[1024*2];
	static char field[3][512], last[3][512];
	char key2[16];
	int lineno = 0, is_text_part = 0;
	
	sprintf(key2, "0x%08x", strtol(key, NULL, 16));
	

	while(fgets(buf, sizeof(buf)-1, mapfile)){
		lineno++;
		if((2 == sscanf(buf, "%s%s%s", field[0], field[1], field[2]))
			&&(field[0][0] == '0' && field[0][1] == 'x')
			&&(is_text_part)){
			if((strcmp(key2, field[0]) < 0)
				&&(strcmp(key2, last[0]) > 0)){
				//printf("key2 %s f0 %s lineno %d buf %s\n", key2, field[0], lineno, buf);
				return last[1];
			}
			memcpy(last, field, sizeof(last));
		}
		if(!is_text_part){is_text_part = !strcmp(field[0], ".text") ;}

		
		memset(buf, 0, sizeof(buf));
	}

	return NULL;
}

int main(int ac, char **av)
{
	FILE *fp = NULL;

	if(ac < 3){return printf("usage: ./search_map_file a.def hex_addr\n");}
	
	fp = fopen(av[1], "r");
	if(!fp){return printf("can't open %s\n", av[1]);}
	
	printf("%s %s\n", av[2], search_map_file(fp, av[2]) );

	fclose(fp);
	return 0;
}


另外,打印栈帧还有一个应用,就是检查谁引起内存泄露:

static int alloc_en(void *addr, unsigned int size);

void* malloc_wrapper(unsigned int size){

	void *ptr = (void*)malloc(size);
	 alloc_en(ptr, size);
	 return ptr;
}

static int alloc_en(void *addr, unsigned int size)
{

 for(i = 0; i < MAX_CALL; ++i){
  if(0 == s_array[i].addr)break;
 }

 if(i >= MAX_CALL){
  printf("no free slot");
  return -1;
 }

 s_array[i].addr = (U32)addr;
 s_array[i].size = size;
 s_array[i].caller = backtrace(3);
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值