zl程序教程

您现在的位置是:首页 >  系统

当前栏目

linux下利用backtrace追踪函数调用堆栈以及定位段错误

Linux错误定位 利用 以及 追踪 堆栈 函数调用
2023-09-27 14:29:31 时间

该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小

在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址

注意:某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会导致无法正确解析堆栈内容

 


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

现在,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的符号给链接器,以能支持函数名功能(比如,在使用GNU ld链接器的系统中,你需要传递(-rdynamic), -rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果你的链接器支持-rdynamic的话,建议将其加上!)

该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针.

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

 


backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况

 

下面是glibc中的实例(稍有修改):


/* A dummy function to make the backtrace more interesting. */   void dummy_function (void)       print_trace ();   int main (int argc, char *argv[])       dummy_function ();       return 0;  
./execinfo() [0x8048556]   /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x70a113]  

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

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

举例如下:


    size = backtrace(buffer, 30);       fprintf(stdout, "Obtained %zd stack frames.nm\n", size);       strings = backtrace_symbols(buffer, size);       if (strings == NULL)       {           perror("backtrace_symbols.");           exit(EXIT_FAILURE);       }              for (i = 0; i   size; i++)       {           fprintf(stdout, "%s\n", strings[i]);       }       free(strings);       strings = NULL;       exit(0);   void func_c()       *((volatile char *)0x0) = 0x9999;   void func_b()       func_c();   void func_a()       func_b();   int main(int argc, const char *argv[])       if (signal(SIGSEGV, dump) == SIG_ERR)           perror("cant catch SIGSEGV");       func_a();       return 0;  
./backstrace_debug(main+0x33)[0x80488cb]   /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113]  

 (这里有个疑问: 多次运行的结果是/lib/i368-linux-gnu/libc.so.6和[0x468400]的返回地址是变化的,但不变的是后三位, 不知道为什么)

接着:

objdump -d test   test.s

在test.s中搜索804888c如下:

 


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

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

我们也可以通过addr2line来查看


《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》——1.3 开始向32位模式转变,为main函数的调用做准备 本节书摘来自华章计算机《Linux内核设计的艺术:图解Linux操作系统架构设计与实现原理》一书中的第1章,第1.3节,作者:新设计团队著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
在Linux中打印函数调用堆栈【原创】 本人学习笔记,代码参考如下网址 参考http://www.cnblogs.com/dma1982/archive/2012/02/08/2342215.html zhangbh@prolin-srv: gcc -rdynamic -o my a.
一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。
Linux的进程调度时机(Schedule函数何时调用) Linux在众多进程中是怎么进行调度的,这个牵涉到Linux进程调度时机的概念,由Linux内核中Schedule()的函数来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等等。