有些嵌入式设备内存,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);
}