linux的用户态程序栈的跟踪方法

Posted 王玉成的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux的用户态程序栈的跟踪方法相关的知识,希望对你有一定的参考价值。

linux的用户态程序栈的跟踪方法

  • GNU C中的libc相关函数介绍
  • 实例分析

GNU C中的libc相关函数介绍

这一部分文章来源于GNU C的在线帮助文档

实例分析

将下面的代码保存为main.c文件,然后用gcc编译

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)

  void *array[10];
  size_t size;
  char **strings;
  size_t i;

  size = backtrace (array, 10);
  strings = backtrace_symbols (array, size);

  printf ("Obtained %zd stack frames.\\n", size);

  for (i = 0; i < size; i++)
     printf ("%s\\n", strings[i]);

  free (strings);


/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)

  print_trace ();


int
main (void)

  dummy_function ();
  return 0;

然后编译并运行:

[ycwang@ycwang-desktop:backtrace-test]$ gcc -g main.c

[ycwang@ycwang-desktop:backtrace-test]$ ./a.out
Obtained 5 stack frames.
./a.out() [0x4006fe]
./a.out() [0x400795]
./a.out() [0x4007a1]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f06a6ef2ac0]
./a.out() [0x400609]

可以看到,程序运行之后,把stack frames完全打印出来了。
我们可以定位各个栈了。
然后用objdump来把反汇编文件导出到一个文件中

[ycwang@ycwang-desktop:backtrace-test]$ objdump -D a.out > dump

然后打开dump文件,查找函数在内存中的虚拟地址。

450 00000000004006d6 <print_trace>:
 451   4006d6:   55                      push   %rbp
 452   4006d7:   48 89 e5                mov    %rsp,%rbp
 453   4006da:   48 83 c4 80             add    $0xffffffffffffff80,%rsp
 454   4006de:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
 455   4006e5:   00 00 
 456   4006e7:   48 89 45 f8             mov    %rax,-0x8(%rbp)
 457   4006eb:   31 c0                   xor    %eax,%eax
 458   4006ed:   48 8d 45 a0             lea    -0x60(%rbp),%rax
 459   4006f1:   be 0a 00 00 00          mov    $0xa,%esi
 460   4006f6:   48 89 c7                mov    %rax,%rdi
 461   4006f9:   e8 92 fe ff ff          callq  400590 <backtrace@plt>
 462   4006fe:   48 98                   cltq   
 463   400700:   48 89 45 90             mov    %rax,-0x70(%rbp)
 464   400704:   48 8b 45 90             mov    -0x70(%rbp),%rax

可以看出:0x4006fe在print_trace函数里面,这也是函数栈的栈顶。
接下来查找另外几个地址。得到:

地址0x400795:

 503 000000000040078c <dummy_function>:
 504   40078c:   55                      push   %rbp
 505   40078d:   48 89 e5                mov    %rsp,%rbp
 506   400790:   e8 41 ff ff ff          callq  4006d6 <print_trace>
 507   400795:   90                      nop
 508   400796:   5d                      pop    %rbp
 509   400797:   c3                      retq

地址0x4007a1:

 511 0000000000400798 <main>:
 512   400798:   55                      push   %rbp
 513   400799:   48 89 e5                mov    %rsp,%rbp
 514   40079c:   e8 eb ff ff ff          callq  40078c <dummy_function>
 515   4007a1:   b8 00 00 00 00          mov    $0x0,%eax
 516   4007a6:   5d                      pop    %rbp
 517   4007a7:   c3                      retq
 518   4007a8:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
 519   4007af:   00

接下来的两个栈对于我们的程序分析没有意义,暂时不讨论。

然后我们可以得到栈的层次为

print_trace()
dummy_function()
main()

跟我们写出的函数的栈完全相同。

这种调试方法适用于不能打断点的一些程序的调试。

以上是关于linux的用户态程序栈的跟踪方法的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核态用户态简介与IntelCPU特权级别--Ring0-3

内核栈溢出

内核栈溢出

关于用户态协议栈的思考

用户空间程序的函数跟踪器 (Function Tracer)

Linux:用户态与内核态