linux C用户态调试追踪函数调用堆栈以及定位段错误

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解linux C用户态调试追踪函数调用堆栈以及定位段错误问题。 通常

大家好,我是本期栏目编辑小友,现在为大家讲解linux C用户态调试追踪函数调用堆栈以及定位段错误问题。

通常,外部调试器(如GDB(bt)命令)用于查看函数的运行时堆栈。但是,有时候为了分析程序的bug(主要是针对长时间运行的程序的分析),当程序出现问题时,打印出函数的调用栈是非常有用的。

在glibc头文件‘exec info . h’中,声明了三个函数来获取当前线程的函数调用栈。

int back trace(void * *缓冲区,int大小)

这个函数用来获取当前线程的调用栈,获取的信息会存储在buffer中,buffer是一个指针列表。参数大小用于指定缓冲区中可以存储多少void*元素。函数返回值是实际获得的指针数,最大值不超过大小的大小。

缓冲区中的指针实际上是从堆栈中获取的返回地址,每个堆栈帧都有一个返回地址。

请注意,一些编译器优化选项会干扰获取正确的调用堆栈,并且内联函数没有堆栈框架。删除帧指针也会导致堆栈内容被错误解析。

char * * back trace _ symbols(void * const * buffer,int size)

Backtrace_symbols将从Backtrace函数获得的信息转换为字符串数组。参数buffer应该是从backtrace函数获得的指针数组,size是数组中的元素个数(backtrace的返回值)。

函数返回值是一个指向字符串数组的指针,该数组的大小与缓冲区相同。每个字符串都包含与缓冲区中相应元素相关的可打印信息。它包括函数名、函数的偏移地址和实际返回地址。

目前只有使用ELF二进制格式的程序才能获得函数名和偏移量地址。在其他系统中,只能获得十六进制的返回地址。此外,您可能需要将相应的符号传递给链接器以支持函数名函数(例如,在使用GNU ld链接器的系统中,您需要传递(-rddynamic),-rddynamic可用于通知链接器将所有符号添加到动态符号表中,如果。)

这个函数的返回值是malloc函数请求的空间,所以调用者必须使用free函数来释放指针。

注意:如果不能为字符串获取足够的空间函数,返回值将为空。

void back trace _ symbols _ FD(void * const * buffer,int size,int fd)

Backtrace_symbols_fd的功能与Backtrace_symbols相同,只是它不向调用者返回字符串数组,而是将结果写入一个带有fd文件描述符的文件中,每个函数对应一行。它不需要调用malloc函数,所以适合调用这个函数可能失败的情况。

以下是glibc中的一个示例(略有修改):

1 #包含2 #包含3 #包含4 5 /*获取回溯并将其打印到@code{stdout}。*/6 void print _ trace(void)7 { 8 void * array[10];9尺寸_t尺寸;10个字符**字符串;11号t _ I;12 13 size=backtrace(数组,10);14个字符串=backtrace_symbols(数组,大小);15 if(NULL==strings)16 { 17 perror(' backtrace _ syn bols ');18退出(退出_失败);19 }20 21 printf('获得%zd堆栈帧。\n ',大小);22 23为(I=0;一、尺寸;i )24 printf ('%s\n ',字符串[I]);25 26免费(字符串);27个字符串=空;28 }29 30 /*一个虚拟函数,使回溯更加有趣。*/31 void dummy _ FuncTion(void)32 { 33 print _ trace();34 }35 3

6 int main (int argc, char *argv[])37 {38 dummy_funcTIon ();39 return 0; 40 }

输出如下:

Obtained 4 stack frames. ./execinfo() [0x80484dd] ./execinfo() [0x8048549] ./execinfo() [0x8048556] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x70a113]

我们还可以利用这backtrace来定位段错误位置。

通常情况系,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function);函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。

举例如下:

1 #include 2 #include 3 #include 4 #include 5 #include 6 7 void dump(int signo) 8 { 9 void *buffer[30] = {0};10 size_t size;11 char **strings = NULL;12 size_t i = 0;13 14 size = backtrace(buffer, 30);15 fprintf(stdout, "Obtained %zd stack frames.nm\n", size);16 strings = backtrace_symbols(buffer, size);17 if (strings == NULL)18 {19 perror("backtrace_symbols.");20 exit(EXIT_FAILURE);21 }22 23 for (i = 0; i < size; i++)24 {25 fprintf(stdout, "%s\n", strings[i]);26 }27 free(strings);28 strings = NULL;29 exit(0);30 }31 32 void func_c()33 {34 *((volatile char *)0x0) = 0x9999;35 }36 37 void func_b()38 {39 func_c();40 }41 42 void func_a()43 {44 func_b();45 }46 47 int main(int argc, const char *argv[])48 {49 if (signal(SIGSEGV, dump) == SIG_ERR)50 perror("can't catch SIGSEGV");51 func_a();52 return 0;53 }

编译程序: gcc -g -rdynamic test.c -o test; ./test 输出如下:

Obtained6stackframes.nm ./backstrace_debug(dump+0x45)[0x80487c9] [0x468400] ./backstrace_debug(func_b+0x8)[0x804888c] ./backstrace_debug(func_a+0x8)[0x8048896] ./backstrace_debug(main+0x33)[0x80488cb] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]

接着: objdump -d test > test.s 在test.s中搜索804888c如下:

8048884 : 8048884: 55 push %ebp 8048885: 89 e5 mov %esp, %ebp 8048887: e8 eb ff ff ff call 8048877 804888c: 5d pop %ebp 804888d: c3 ret

其中80488c时调用(call 8048877)C函数后的地址,虽然并没有直接定位到C函数,通过汇编代码, 基本可以推出是C函数出问题了(pop指令不会导致段错误的)。 我们也可以通过addr2line来查看

addr2line 0x804888c -e backstrace_debug -f

输出:

func_b /home/astrol/c/backstrace_debug.c:57

 

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。