zoukankan      html  css  js  c++  java
  • Linux下手动获取当前调用栈

    被问到如何手动获取当前的调用栈,之前碰到过一时没记起来,现在回头整理一下。

    其原理是:使用backtrace()从栈中获取当前调用各层函数调用的返回地址,backtrace_symbols()将对应地址翻译成对应的符号信息,这两个函数在execinfo.h中声明。详细用法见后面的example。这里强调几处需要注意的地方,在man里头也有说明

    1,inline函数无返回地址,因此在结果中不显示

    2,需要给linker指定对应的参数,才能保证有对应的符号名称信息,GNU工具链是指定-rdynamic

    3,尾调优化会使当前栈帧被新的栈帧覆盖,因此查询的到的信息,会与代码里调用关系不能一一对应

    4,static函数由于其符号信息未输出,因此不能获取到具体的名称

    example代码,编译指令gcc backtrace.c -o backtrace -g -rdynamic

     1 #include <execinfo.h>
     2 #include <stdio.h>
     3 #include <stdlib.h>
     4 
     5 void bt(void)
     6 {
     7     #define MAX_DEPTH (20)
     8     void *buffer[MAX_DEPTH];
     9     int nptrs = backtrace(buffer, MAX_DEPTH);
    10     char **stack = backtrace_symbols(buffer, nptrs);
    11     int i;
    12     
    13     if (stack)
    14     {
    15         for (i = 0; i < nptrs; ++i)
    16         {
    17             printf("%s
    ", stack[i]);
    18         }
    19         
    20         free(stack);
    21     }
    22 
    23     return;
    24 }
    25 
    26 static void func2(void)
    27 {
    28     bt(); 
    29 }
    30 
    31 inline void func1(void)
    32 {
    33     func2();
    34 }
    35 
    36 void func(void)
    37 {
    38     func1();
    39 }
    40 
    41 int main(int argc, char *argv[])
    42 {
    43     func();
    44     
    45     return 0;
    46 }

    Linux arch 2.6.30-ARCH #1 SMP PREEMPT Fri Jul 31 18:10:38 UTC 2009 i686 Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz GenuineIntel GNU/Linux

    gcc4.4.1 环境之行结果如下

    [root@arch code]# make backtrace
    gcc backtrace.c -o backtrace -g -rdynamic
    [root@arch code]# ./backtrace
    ./backtrace(bt+0x19) [0x80486ed]
    ./backtrace [0x804874b]
    ./backtrace(func1+0xb) [0x8048758]
    ./backtrace(func+0xb) [0x8048765]
    ./backtrace(main+0xb) [0x8048772]
    /lib/libc.so.6(__libc_start_main+0xe6) [0xb7f8da36]
    ./backtrace [0x8048641]
    [root@arch code]# addr2line -e ./backtrace 0x8048765
    /root/code/backtrace.c:40
    [root@arch code]# addr2line -e ./backtrace 0x8048758
    /root/code/backtrace.c:35
    [root@arch code]# addr2line -e ./backtrace 0x804874b
    /root/code/backtrace.c:30
    [root@arch code]# addr2line -e ./backtrace 0x80486ed
    /root/code/backtrace.c:10
    [root@arch code]#

    从实际验证结果可以看出static函数的确没有解析出对应的符号名,但是inline函数仍然有自己的调用栈,这应该是gcc没有实际将其优化展开,仍然将其当作普通函数所致。

    并且根据addre2line的结果,我们可以看出backtrace()调用获取到的其实是各个函数调用的返回地址,可以自己根据行号进行一一比对。这里就不多重复了。

    不过在Raspbian环境(Linux raspberrypi 3.10.25+ #622 PREEMPT Fri Jan 3 18:41:00 GMT 2014 armv6l GNU/Linux gcc 4.6.3)里,编译执行均没有问题,但是无任何输出,gdb跟踪的结果是backtrace()调用返回0,很奇怪。stackoverflow上有人说是根据GCC ARM Options documentation需要加上-mapcs-frame参数,以让gcc在ARM平台上产生栈帧,可是编译时加上该参数仍然无效。strace跟踪其执行过程发现其执行过程没有任何backtrace字样,如下

    pi@raspberrypi ~/code $ strace ./backtrace
    execve("./backtrace", ["./backtrace"], [/* 16 vars */]) = 0
    brk(0)                                  = 0x1082000
    uname({sys="Linux", node="raspberrypi", ...}) = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0c000
    access("/etc/ld.so.preload", R_OK)      = 0
    open("/etc/ld.so.preload", O_RDONLY)    = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=44, ...}) = 0
    mmap2(NULL, 44, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0xb6f0b000
    close(3)                                = 0
    open("/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so", O_RDONLY) = 3
    read(3, "177ELF1113(12704004"..., 512) = 512
    lseek(3, 7276, SEEK_SET)                = 7276
    read(3, ""..., 1080) = 1080
    lseek(3, 7001, SEEK_SET)                = 7001
    read(3, "A.aeabi1$05666101	1
    222424125"..., 47) = 47
    fstat64(3, {st_mode=S_IFREG|0755, st_size=10170, ...}) = 0
    mmap2(NULL, 39740, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6ee0000
    mprotect(0xb6ee2000, 28672, PROT_NONE)  = 0
    mmap2(0xb6ee9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xb6ee9000
    close(3)                                = 0
    munmap(0xb6f0b000, 44)                  = 0
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=43581, ...}) = 0
    mmap2(NULL, 43581, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6ed5000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY) = 3
    read(3, "177ELF1113(1214y1004"..., 512) = 512
    lseek(3, 1194784, SEEK_SET)             = 1194784
    read(3, ""..., 1360) = 1360
    lseek(3, 1194348, SEEK_SET)             = 1194348
    read(3, "A.aeabi1$05666101	1
    222424125"..., 47) = 47
    fstat64(3, {st_mode=S_IFREG|0755, st_size=1196144, ...}) = 0
    mmap2(NULL, 1238312, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6da6000
    mprotect(0xb6ec8000, 28672, PROT_NONE)  = 0
    mmap2(0xb6ecf000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x121) = 0xb6ecf000
    mmap2(0xb6ed2000, 9512, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6ed2000
    close(3)                                = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0b000
    set_tls(0xb6f0b4c0, 0xb6f0bb98, 0xb6f10048, 0xb6f0b4c0, 0xb6f10048) = 0
    mprotect(0xb6ecf000, 8192, PROT_READ)   = 0
    mprotect(0xb6f0f000, 4096, PROT_READ)   = 0
    munmap(0xb6ed5000, 43581)               = 0
    open("/etc/ld.so.cache", O_RDONLY)      = 3
    fstat64(3, {st_mode=S_IFREG|0644, st_size=43581, ...}) = 0
    mmap2(NULL, 43581, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6ed5000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/arm-linux-gnueabihf/libgcc_s.so.1", O_RDONLY) = 3
    read(3, "177ELF1113(1`364004"..., 512) = 512
    lseek(3, 130212, SEEK_SET)              = 130212
    read(3, ""..., 1160) = 1160
    lseek(3, 129880, SEEK_SET)              = 129880
    read(3, "A2aeabi1(05666101	1
    222424125"..., 51) = 51
    brk(0)                                  = 0x1082000
    brk(0x10a3000)                          = 0x10a3000
    fstat64(3, {st_mode=S_IFREG|0644, st_size=131372, ...}) = 0
    mmap2(NULL, 162704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6d7e000
    mprotect(0xb6d9e000, 28672, PROT_NONE)  = 0
    mmap2(0xb6da5000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f) = 0xb6da5000
    close(3)                                = 0
    munmap(0xb6ed5000, 43581)               = 0
    exit_group(0)                           = ?
    pi@raspberrypi ~/code $
    View Code

    对其原因待有待深究。

    ===== update 2019/5/5 ====

    aarch64 linux 版本的 gcc有 -funwind-tables 编译参数,可以实现 backtrace()/backtrace_symbols 正常功能,需要新的 gcc 版本(>= gcc-4.5) 提供支持。原理是记录每个函数的 入栈指令(一般比APCS的入栈要少的多)到特殊的段.ARM.unwind_idx .ARM.unwind_tab。

    详情见 http://www.alivepea.me/prog/how-backtrace-work/

  • 相关阅读:
    某个周六加班日的划水记
    如何保证消息的可靠性传输
    PHP面向对象学习六 多态
    PHP面向对象学习五 类中接口的应用
    PHP面向对象学习四 类的关键字
    PHP面向对象学习三 类的抽象方法和类
    PHP面向对象学习二
    PHP面向对象学习一
    高级ql
    mysql 方法
  • 原文地址:https://www.cnblogs.com/lanyuliuyun/p/3815266.html
Copyright © 2011-2022 走看看