让程序死时自动把栈帧打印出来。
/*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); }