zoukankan      html  css  js  c++  java
  • objdump & nm & addr2line&readelf

    GCC把C语言源文件('.c')编译成汇编语言文件('.s'),汇编器把汇编语言文件翻译成目标文件('.o'),最后由链接器链接所有的目标文件和有关的库生成可执行文件('a.out')。
    如打开'-g'选项,GCC编译'.c'文件时,把附加的调试信息插进'.s'文件,这些调试信息经汇编器和链接器稍加转换一直传到可执行文件中。这些调试信息包括行号、变量的类型和作用域、函数名字、函数参数和函数的作用域等源文件的特性。
    在 某些目标文件中,调试信息用'.stab'打头的一类汇编指导命令表示,这些指导命令穿插在汇编代码中,这种调试信息格式叫'Stab',即符号表 (Symbol table)。XCOFF和a.out目标文件格式采用Stab调试信息格式。此外,GCC也能在COFF和ECOFF目标文件格式中产生Stab。如要 生成Stab调试信息,在GCC编译源文件时,打开编译选项'-gstabs+'(此选项将产生GNU调试器扩展的Stab的调试信息)或'- gstabs'。
    汇编器处理'.stab'打头指导命令,把Stab中的调试信息填入'.o'文件的符号表和串表(string table)中,链接器合并所有'.o'文件生成只含有一个符号表和一个串表的可执行文件。调试器通过检索可执行文件中的符号表和串表来获得程序的调试信 息,下面分别介绍Stab的格式,GCC生成Stab和汇编链接器对Stab转换。
     
    gcc在加了-g选项时,编译出来的elf可执行文件含有符号表,addr2line和objdump可以读取该符号表,且objdump还可以反汇编
    如何生成不含符号表的elf文件
        1.一个方式是编译的时候使用 选项
    root@root2768:~/test_c>gcc -s test1.c
    
     root@root2768:~/test_c> objdump -t a.out   
    
     a.out:     file format elf64-x86-64
    
     SYMBOL TABLE:
     no symbols
        2.另外一个方式是,生成elf文件以后,使用strip命令从elf文件中去掉符号表

    root@root2768:~/test_c> objdump -t a.out
    
    
    a.out:     file format elf64-x86-64
    
    
    SYMBOL TABLE:
    0000000000400238 l    d  .interp 0000000000000000              .interp
    0000000000400254 l    d  .note.ABI-tag 0000000000000000              .note.ABI-tag
    0000000000400274 l    d  .note.SuSE 0000000000000000              .note.SuSE
    000000000040028c l    d  .note.gnu.build-id 0000000000000000              .note.gnu.build-id
    00000000004002b0 l    d  .hash 0000000000000000              .hash
    00000000004002c8 l    d  .gnu.hash 0000000000000000              .gnu.hash
    00000000004002e8 l    d  .dynsym 0000000000000000              .dynsym
    0000000000400330 l    d  .dynstr 0000000000000000              .dynstr
    0000000000400368 l    d  .gnu.version 0000000000000000              .gnu.version
    0000000000400370 l    d  .gnu.version_r 0000000000000000              .gnu.version_r
    0000000000400390 l    d  .rela.dyn 0000000000000000              .rela.dyn
    00000000004003a8 l    d  .rela.plt 0000000000000000              .rela.plt
    00000000004003c0 l    d  .init 0000000000000000              .init
    00000000004003e0 l    d  .plt 0000000000000000              .plt
    0000000000400400 l    d  .text 0000000000000000              .text
    00000000004005d8 l    d  .fini 0000000000000000              .fini
    00000000004005f0 l    d  .rodata 0000000000000000              .rodata
    00000000004005f4 l    d  .eh_frame_hdr 0000000000000000              .eh_frame_hdr
    0000000000400628 l    d  .eh_frame 0000000000000000              .eh_frame
    0000000000600e30 l    d  .ctors 0000000000000000              .ctors
    0000000000600e40 l    d  .dtors 0000000000000000              .dtors
    0000000000600e50 l    d  .jcr 0000000000000000              .jcr
    0000000000600e58 l    d  .dynamic 0000000000000000              .dynamic
    0000000000600ff8 l    d  .got 0000000000000000              .got
    0000000000601000 l    d  .got.plt 0000000000000000              .got.plt
    0000000000601020 l    d  .data 0000000000000000              .data
    0000000000601038 l    d  .bss 0000000000000000              .bss
    0000000000000000 l    d  .comment 0000000000000000              .comment
    0000000000000000 l    d  .comment.SUSE.OPTs 0000000000000000              .comment.SUSE.OPTs
    0000000000000000 l    d  .debug_aranges 0000000000000000              .debug_aranges
    0000000000000000 l    d  .debug_pubnames 0000000000000000              .debug_pubnames
    0000000000000000 l    d  .debug_info 0000000000000000              .debug_info
    0000000000000000 l    d  .debug_abbrev 0000000000000000              .debug_abbrev
    0000000000000000 l    d  .debug_line 0000000000000000              .debug_line
    0000000000000000 l    d  .debug_frame 0000000000000000              .debug_frame
    0000000000000000 l    d  .debug_str 0000000000000000              .debug_str
    0000000000000000 l    d  .debug_loc 0000000000000000              .debug_loc
    0000000000000000 l    d  .debug_ranges 0000000000000000              .debug_ranges
    0000000000000000 l    df *ABS* 0000000000000000              init.c
    0000000000000000 l    df *ABS* 0000000000000000              
    0000000000000000 l    df *ABS* 0000000000000000              initfini.c
    000000000040042c l     F .text 0000000000000000              call_gmon_start
    00000000004005e8 l       .fini 0000000000000000              _real_fini
    0000000000000000 l    df *ABS* 0000000000000000              crtstuff.c
    0000000000600e30 l     O .ctors 0000000000000000              __CTOR_LIST__
    0000000000600e40 l     O .dtors 0000000000000000              __DTOR_LIST__
    0000000000600e50 l     O .jcr 0000000000000000              __JCR_LIST__
    0000000000400450 l     F .text 0000000000000000              __do_global_dtors_aux
    0000000000601038 l     O .bss 0000000000000001              completed.6159
    0000000000601040 l     O .bss 0000000000000008              dtor_idx.6161
    00000000004004c0 l     F .text 0000000000000000              frame_dummy
    0000000000000000 l    df *ABS* 0000000000000000              crtstuff.c
    0000000000600e38 l     O .ctors 0000000000000000              __CTOR_END__
    0000000000400700 l     O .eh_frame 0000000000000000              __FRAME_END__
    0000000000600e50 l     O .jcr 0000000000000000              __JCR_END__
    00000000004005a0 l     F .text 0000000000000000              __do_global_ctors_aux
    0000000000000000 l    df *ABS* 0000000000000000              initfini.c
    0000000000000000 l    df *ABS* 0000000000000000              test1.c
    0000000000000000 l    df *ABS* 0000000000000000              elf-init.c
    0000000000000000 l    df *ABS* 0000000000000000              
    0000000000600e2c l       .ctors 0000000000000000              __init_array_end
    0000000000600e58 l     O .dynamic 0000000000000000              _DYNAMIC
    0000000000600e2c l       .ctors 0000000000000000              __init_array_start
    0000000000601000 l     O .got.plt 0000000000000000              _GLOBAL_OFFSET_TABLE_
    0000000000400500 g     F .text 0000000000000002              __libc_csu_fini
    0000000000601048 g     O .bss 0000000000000004              global_x
    0000000000601020  w      .data 0000000000000000              data_start
    0000000000601034 g       .data 0000000000000000              _edata
    00000000004005d8 g     F .fini 0000000000000010              _fini
    0000000000600e48 g     O .dtors 0000000000000000              .hidden __DTOR_END__
    0000000000000000       F *UND* 0000000000000000              __libc_start_main@@GLIBC_2.2.5
    0000000000601020 g       .data 0000000000000000              __data_start
    0000000000000000  w      *UND* 0000000000000000              __gmon_start__
    0000000000601028 g     O .data 0000000000000000              .hidden __dso_handle
    00000000004005f0 g     O .rodata 0000000000000004              _IO_stdin_used
    0000000000601030 g     O .data 0000000000000004              global_y
    0000000000400510 g     F .text 0000000000000089              __libc_csu_init
    0000000000601050 g       .bss 0000000000000000              _end
    0000000000400400 g     F .text 0000000000000000              _start
    0000000000601034 g       .bss 0000000000000000              __bss_start
    00000000004004ec g     F .text 0000000000000012              main
    0000000000000000  w      *UND* 0000000000000000              _Jv_RegisterClasses
    00000000004003c0 g     F .init 0000000000000000              _init
    
    
    root@root2768:~/test_c> strip a.out
    root@root2768:~/test_c> objdump -t a.out
    
    
    a.out:     file format elf64-x86-64
    
    
    SYMBOL TABLE:
    no symbols
     
     
     
    当elf文件不含符号表时,只能通过.o文件查看符号表,使用objdump -t test1.o 查看符号表
    如何查看目标文件.o的符号表 :1.一个方式是使用 objdump (objdump -t test1.o)  2.另外一个方式是使用 nm (nm -S test1.o )  3.gdb下使用disassemble 函数名
     
     
     
    addr2line个人理解,它还是像objdump一样,读出elf文件的文件头里的符号表,
    比如 objdump -t  ./test| grep text

    0000000000400500 l    d  .text  0000000000000000              .text
    000000000040052c l     F .text  0000000000000000              call_gmon_start
    0000000000400550 l     F .text  0000000000000000              __do_global_dtors_aux
    00000000004005c0 l     F .text  0000000000000000              frame_dummy
    00000000004006a0 l     F .text  0000000000000000              __do_global_ctors_aux
    0000000000400600 g     F .text  0000000000000002              __libc_csu_fini
    0000000000400500 g     F .text  0000000000000000              _start
    0000000000400610 g     F .text  0000000000000089              __libc_csu_init
    00000000004005ec g     F .text  0000000000000012              main

    第一项是这个函数在文件的起始地址,第5项是大小,
    所以给定一个正文区的地址,它总能算出它是那个函数地址区间里的。

    希望这样能使你明白。

    |
    因为你用-g选项生成的可执行文件,最终的elf文件多生成了几个段,
    可以使用 readelf  --sections  a.out 看的出来
    00000000 l    d  .debug_aranges 00000000              .debug_aranges
    00000000 l    d  .debug_pubnames 00000000              .debug_pubnames
    00000000 l    d  .debug_info 00000000              .debug_info
    00000000 l    d  .debug_abbrev 00000000              .debug_abbrev
    00000000 l    d  .debug_line 00000000              .debug_line
    00000000 l    d  .debug_frame 00000000              .debug_frame
    00000000 l    d  .debug_str 00000000              .debug_str
    00000000 l    d  .debug_loc 00000000              .debug_loc
    00000000 l    d  .debug_pubtypes 00000000              .debug_pubtypes


    .debug_line 这个段里面就包含了每条汇编指令的地址和对应的源代码行数,源代码名字等信息,
    使用readelf -wl a.out  命令就可以查看这个段的内容,比如


    readelf -wl a.out
    Raw dump of debug contents of section .debug_line:

      Offset:                      0x0
      Length:                      277
      DWARF Version:               2
      Prologue Length:             29
      Minimum Instruction Length:  1
      Initial value of 'is_stmt':  1
      Line Base:                   -5
      Line Range:                  14
      Opcode Base:                 13

     Opcodes:
      Opcode 1 has 0 args
      Opcode 2 has 1 args
      Opcode 3 has 1 args
      Opcode 4 has 1 args
      Opcode 5 has 1 args
      Opcode 6 has 0 args
      Opcode 7 has 0 args
      Opcode 8 has 0 args
      Opcode 9 has 1 args
      Opcode 10 has 0 args
      Opcode 11 has 0 args
      Opcode 12 has 1 args

     The Directory Table is empty.

     The File Name Table:
      Entry Dir Time Size Name
      1 0 0 0 main.c   //////////////这个是文件名

     Line Number Statements:
      Extended opcode 2: set Address to 0x8048444
      Advance Line by 12 to 13
      Copy
      Special opcode 90: advance Address by 6 to 0x804844a and Line by 1 to 14
      Special opcode 202: advance Address by 14 to 0x8048458 and Line by 1 to 15
      Special opcode 174: advance Address by 12 to 0x8048464 and Line by 1 to 16
      Special opcode 90: advance Address by 6 to 0x804846a and Line by 1 to 17
      Special opcode 132: advance Address by 9 to 0x8048473 and Line by 1 to 18
      Advance PC by constant 17 to 0x8048484
      Special opcode 76: advance Address by 5 to 0x8048489 and Line by 1 to 19
      Advance PC by constant 17 to 0x804849a
      Special opcode 146: advance Address by 10 to 0x80484a4 and Line by 1 to 20
      Special opcode 62: advance Address by 4 to 0x80484a8 and Line by 1 to 21
      Special opcode 160: advance Address by 11 to 0x80484b3 and Line by 1 to 22

    等等,应该是每条指令的都有了
    这里有一个对格式简单的说明 http://hi.baidu.com/piaoling_sky/blog/item/f9654ad21a6ed43d970a169c.html


    addr2line 应该也是使用了这几个段的信息的,objdump gdb等也都可以解析这个段的。

    使用
    strip -d a.out
    命令去除掉 这个几个debug 段的话,addr2line 也就无能为力了  此时只能用.o文件,可以使用objdump -t test1.o 查看符号表
     
    在可执行程序中都包含有调试信息,其中很重要的一份数据就是程序源程序的行号和编译后的机器代码之间的对应关系Line Number Table。DWARF格式的Line  Number Table是一种高度压缩的数据,存储的是表格前后两行的差值,在解析调试信息时,需要按照规则在内存里重建Line Number  Table才能使用。

    Line Number Table存储在可执行程序的.debug_line域,使用命令

    $ readelf -w test1

    可以输出DWARF的调试信息,其中有两行

    Special opcode 146: advance Address by 10 to 0x4004fe and Line by 1 to 5  

    Special opcode 160: advance Address by 11 to 0x400509 and Line by 1 to 6  

    这里说明机器二进制编码的0x4004fe位置开始,对应于源码中的第5行,0x400509开始就对应与源码的第6行了,所以400506这个地址对应的是源码第5行位置。

    ------------------------------------------------------------------------

    GNU Binutils的Addr2line工具是一个可以将指令的地址和可执行程序转换成文件名、函数名和源代码行数的工具。这种功能对于将跟踪地址转换成更有意义的内容来说简直是太棒了。

    下面是一个小示例testAddr2line.c:

    #include "stdio.h"
    
    void test() {
        printf("Hello Addr2line
    ");
    }
    int main() {
        test();
        return 0;
    }
    

    编译时使用-g选项包含调试符号条,使用-Wl,-Map=testAddr2line.map选项输出MapFile。

    gcc -Wl,-Map=testAddr2line.map -g -o testAddr2line testAddr2line.c
    

    testAddr2line.map部分内容如下:

    [C++] <wbr>addr2line使用

    testAddr2line中也包含符号表信息,因而可以使用objdump查找:

    hadoop@node51054:~/ctest$ objdump -t testAddr2line | grep 'main|test'
    testAddr2line:     file format elf64-x86-64
    0000000000000000 l    df *ABS*  0000000000000000  testAddr2line.c
    0000000000000000       F *UND*  0000000000000000  __libc_start_main@@GLIBC_2.2.5
    0000000000400547 g     F .text  0000000000000015  main
    0000000000400536 g     F .text  0000000000000011  test
    

    使用addr2line:

    hadoop@node51054:~/ctest$ addr2line -e testAddr2line 400536
    /home/hadoop/ctest/testAddr2line.c:3
    hadoop@node51054:~/ctest$ addr2line -e testAddr2line -f 400536
    test
    /home/hadoop/ctest/testAddr2line.c:3
    hadoop@node51054:~/ctest$ addr2line -e testAddr2line 400547
    /home/hadoop/ctest/testAddr2line.c:6
    hadoop@node51054:~/ctest$ addr2line -e testAddr2line -f 400547
    main
    /home/hadoop/ctest/testAddr2line.c:6
    hadoop@node51054:~/ctest$
    hadoop@node51054:~/ctest$ addr2line -e testAddr2line -f 0x0000000000400547
    main
    /home/hadoop/ctest/testAddr2line.c:6
    

    addr2line如何找到的源代码行数的呢?在可执行程序中都包含有调试信息,其中很重要的一份数据就是程序源文件与编译后的机器代码之间的对应关系目录表、文件名表和行数语句。 上述信息存储在可执行程序的.debug_line域,使用命令readelf -w testAddr2line可以输出DWARF的调试信息。

    [C++] <wbr>addr2line使用

    这里说明机器二进制编码的0x400536位置开始对应于源码中的第3行,0x400547开始就对应与源码的第6行了。

    addr2line也可以用于对系统segfault日志信息定位错误位置。下面为一个日志信息示例:

    /home/mryqu/ctest/mydrv.so(mytracex+0x2e) [0x7f713e0c696e]
    /home/mryqu/ctest/mydrv.so(exceptionHandler+0x13a) [0x7f713e08428a]
    /home/mryqu/ctest/mymk.so(myExcept+0x5b) [0x7f71433b55bb]
    /home/mryqu/ctest/mymk.so(my_signal_handler+0x160) [0x7f71433b5c00]
    /lib64/libpthread.so.0(+0xf790) [0x7f71448ad790]
    /home/mryqu/ctest/mytable.so(__XXXX_avx_rep_memcpy+0x130) [0x7f713a340030]
    /home/mryqu/ctest/mytable.so(+0x4652e) [0x7f713a1bb52e]
    /home/mryqu/ctest/mytable.so(+0x42829) [0x7f713a1b7829]
    /home/mryqu/ctest/mydrv.so(+0x1f4d3) [0x7f713e0944d3]
    /home/mryqu/ctest/mydrv.so(+0x2c850) [0x7f713e0a1850]
    /home/mryqu/ctest/mydrv.so(+0x29fb1) [0x7f713e09efb1]
    /home/mryqu/ctest/mymk.so(myMain+0x8d) [0x7f71433b367d]
    /home/mryqu/ctest/mymk.so(myMain+0x6f) [0x7f71433b53ff]
    /lib64/libpthread.so.0(+0x7a51) [0x7f71448a5a51]
    /lib64/libc.so.6(clone+0x6d) [0x7f7143f339ad]
    

    当指令指针位置为动态库偏移量时,如mydrv.so(+0x2c850),则可以直接使用addr2line查看源码位置。

    当指令指针位置为函数偏移量时,如mydrv.so(exceptionHandler+0x13a),则需要先查找函数(如exceptionHandler)相对动态库的偏移量,之后与相对偏移量相加以用于addr2line命令。

    可以通过objdump -S命令查找函数的指令指针地址:

    [C++] <wbr>addr2line使用

    1. objdump

    objdump 命令是Linux下的反汇编目标文件或者可执行文件的命令.

    a. 反汇编test文件中需要执行指令的section

    objdump –d test

    b. 反汇编test文件中所有section

    objdump –D test

    c. 显示test文件的section header信息

    objdump –h test

    d. 反汇编test文件中需要执行指令的section,并且保留c源代码作为参照

    objdump –S test

    e. 指定反汇编的指令架构i386,  i386:x86-64

    objdump –d –m i386 test

    e. 查看.o文件的符号表

    objdump –t xxx.o

    2. nm

    nm 用来列出一个目标文件中的各种符号

    #cat test.c

    static int uninit_static_global;

    static int init_static_global = 2;

    int unit_global;

    char *init_global = "hello, world";

    const readOnly = 10;

    extern int extern_global;

    void function()

    {

            printf("Hello");

    }

    int get_local()

    {

                int local;

                static int uninit_local_static;

                static int init_local_static = 10;                      

                local = 33;

                return local;

    }

    #gcc  -c test.c –g

    # nm -A -l -n test.o

    test.o:                                 U printf    /home/cr7/test/test.c:31

    test.o:0000000000000000 T function /home/cr7/test/test.c:29

    test.o:0000000000000000 d init_static_global           /home/cr7/test/test.c:24

    test.o:0000000000000000 b uninit_static_global       /home/cr7/test/test.c:23

    test.o:0000000000000004 C uninit_global

    test.o:0000000000000004 b uninit_local_static.4246            /home/cr7/test/test.c:31

    test.o:0000000000000008 D init_global        /home/cr7/test/test.c:26

    test.o:0000000000000010 d init_local_static.4247

    test.o:0000000000000010 R readOnly           /home/cr7/test/test.c:27

    test.o:0000000000000018 T get_local            /home/cr7/test/test.c:33

    T: text段代表函数

    D: 已初始化data段全局数据

    d: 已初始化bss段(static)数据

    R: 只读数据

    C: 未初始化data段全局数据

    b: 未初始化bss段(static)数据

    -A: 显示符号所属文件

    -l: 显示符号所属源文件行号

    -n: 所有符号从低地址到高地址排序

    Others: –u 只列出未定义符号; --defined-only将只列出已定义符号

     

    3. addr2line

    Addr2line 根据一个代码地址,定位到对应的源文件与代码行

    #cat test.c

    static int global = 2;

    void function()

    {

                printf("Hello");

                global = 10;    

    }

    int main()

    {

                function();

                return 0;

    }

    #gcc  -o test test.c  –g

    #nm test

    00000000004004f4 T function

    0000000000601020 d global

    0000000000400516 T main

                                     U printf@@GLIBC_2.2.5

    #addr2line -a 4004f4 -e test

    0x00000000004004f4

    /home/cr7/test/test.c:26

    -a 文件中地址, -e可执行文件

    #addr2line -f 4004f4 -e test

    function

    /home/cr7/test/test.c:26

    -f 显示文件中地址所在函数

    1. addr2line
        将地址转换为地址所在的文件及行数(显示所在函数)
        使用方法:arm-linux-androideabi-addr2line [option(s)] [addr(s)]
    (1). 参数 (常用)
        -e --exe=<executable>: 设置要查询地址的文件(默认: a.out)
            一般是*.so/*.a和可执行程序
            此文件必须带有debug信息,在android codebase里是放在out/target/product/$project/symbols目录下
        -f –functions: 显示地址所在的函数名
        -C --demangle[=style]: 反重整函数名为可读方式
            自动识别格式,C++函数才需要此参数
    (2). 例子
        arm-linux-androideabi-addr2line -e libc.so -f -C 0x23234
        wcscoll
        alps/bionic/libc/wchar/wcscoll.c:37
    (3). 什么情况下需要用到?
        发 生NE后,会生成tombstones/tombstones_xx文件或aee_exp里的db解开之后的__exp_main.txt ,里面有backtrace信息,就可以通过addr2line分析出哪个文件哪行哪个函数(注意用-e 载入的文件必须和手机的bin档同一次编译生成,否则地址和符号可能不一致)
    2. nm
        arm-linux-androideabi-nm [option(s)] [file(s)]
        列出该文件的符号(函数,变量,文件等),包含名字、地址、大小
    (1). 参数 (常用)
        -C, --demangle[=STYLE]: 反重整符号为可读方式
            自动识别格式
        -e --exe=<executable>: 设置要查询地址的文件(默认: a.out)
            一般是*.so和可执行程序
        -D, --dynamic: 只显示动态符号
        -g, --extern-only: 只显示外部符号
        -l, --line-numbers:多显示符号所在文件和行数
        -S, --print-size: 多显示符号的大小
        -u, --undefined-only: 只显示未定义的符号
    (2). 符号类型 (常用)
        小写表示是本地符号,大写表示全局符号(external)
        A: 符号值是绝对的。在进一步的连接中,不会被改变(absolute)
        B: 符号位于未初始化数据段(BSS section)
        C: 共用(common)符号. 共用符号是未初始化的数据。在连接时,多个共用符号可能采用一个同样的名字,如果这个符号在某个地方被定义,共用符号被认为是未定义的引用
        D: 已初始化数据段的符号(data section)
        F: 源文件名称符号
        R: 只读数据段符号.(定义为const的变量)
        T: 代码段的符号 (text section)
        U: 未定义符号
        ?: 未知符号类型,或者目标文件特有的符号类型
    (3). 例子
        arm-linux-androideabi-nm -g test
            00009018 D __CTOR_LIST__
            00009010 T __FINI_ARRAY__
            00009008 T __INIT_ARRAY__
                     U __aeabi_unwind_app_pr0
            ......
    3. objdump
        arm-linux-androideabi-objdump <option(s)> <file(s)>
        查看对象文件(*.so/*.a或应用程序)的内容信息
    (1). 参数 (常用)
        至少需要一个以下的参数
            -a, --archive-headers: 显示库(*.a)成员信息
            -f, --file-headers:显示obj中每个文件的整体头部摘要信息
            -h, --[section-]headers:显示目标文件各个section的头部摘要信息
            -x, --all-headers: 显示所有头部摘要信息
            -d, --disassemble:反汇编代码段
            -D, --disassemble-all: 反汇编所有段
            -S, --source:反汇编出源代码,额外有debug信息,隐含-d,如果编译时有-g,效果更明显
            -t, --syms: 显示符号表
            -r, --reloc: 显示重定位记录
        -C, --demangle[=STYLE]: 反重整符号为可读方式
            自动识别格式
    (2). 例子
        arm-linux-androideabi-objdump -S libstdc++.so > disas.txt
    (3). 什么情况下需要用到?
        当发生NE后,拿到backtrace可以查看对应地址的汇编代码。
    4. readelf
        arm-linux-androideabi-readelf <option(s)> elf-file(s)
        查看elf文件(*.so/*.a或应用程序)的内容信息
    (1). 参数 (常用)
        -a, --all: 显示所有可显示的内容
        -h --file-header: 显示ELF文件头
        -l --segments: 显示程序头组
        -S --sections: 显示节头组
        -t: 显示节头细节
        -e --headers: 等效于-h -l -S
        -s --syms: 显示符号表
        -n --notes: 显示内核说明
        -r --relocs: 显示重定位信息
        -u --unwind: 显示解栈信息
        -d --dynamic: 显示动态节
        -p –string-dump=<num|name>: 以字符串的方式显示节
        -W --wide: 允许一行显示超过80个字符
    (2). 例子
        arm-linux-androideabi-readelf -a -W adb > 1.txt
    (3). 什么情况下需要用到?
        学习/查看ELF结构。

    利用backtrace和backtrace_symbols打印函数的调用关系

    backtrace和backtrace_symbols

    一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。

    在glibc头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈。

    int backtrace(void **buffer,int size) 

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

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

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

    char ** backtrace_symbols (void *const *buffer, int size) 

    backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)

    函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址

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

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

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

    void backtrace_symbols_fd (void *const *buffer, int size, int fd) 

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

    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    
    typedef uint32_t UINT32;
    
    void fun3(void)
    {
      void* array[10] = {0};
      UINT32 size = 0;
      char **strframe = NULL;
      UINT32 i = 0, j = 0;
     
      size = backtrace(array, 10);
      strframe = (char **)backtrace_symbols(array, size);
     
      printf("print call frame now:/n");
      for(i = 0; i < size; i++){
        printf("frame %d -- %s/n", i, strframe[i]);
      }
     
      if(strframe)
      {
        free(strframe);
        strframe = NULL;
      }
    }
    
    void fun2(void)
    {
      fun3();
    }
    
    void fun1(void)
    {
      fun2();
    }
    
    int main(void)
    {
      fun1();
      return 0;
    }
    linux-xms:/data/test # gcc  test.c
    
    linux-xms:/data/test # ./a.out
    
    print call frame now:
    frame 0 -- ./a.out [0x80484fe]
    frame 1 -- ./a.out [0x8048582]
    frame 2 -- ./a.out [0x804858f]
    frame 3 -- ./a.out [0x80485a7]
    frame 4 -- /lib/libc.so.6(__libc_start_main+0xdc) [0xb7e188ac]
    frame 5 -- ./a.out [0x8048431]
    ————————————————
    版权声明:本文为CSDN博主「wind19」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wind19/article/details/6105617

    只能看到地址

    修改编译参数

    linux-xms:/data/test # gcc -rdynamic  test.c
    linux-xms:/data/test # ./a.out
    print call frame now:
    frame 0 -- ./a.out(fun3+0x4a) [0x80486de]
    frame 1 -- ./a.out(fun2+0xb) [0x8048762]
    frame 2 -- ./a.out(fun1+0xb) [0x804876f]
    frame 3 -- ./a.out(main+0x16) [0x8048787]
    frame 4 -- /lib/libc.so.6(__libc_start_main+0xdc) [0xb7e588ac]
    frame 5 -- ./a.out [0x8048611]

    现在可以看到函数名了,但没有行号,不过没关系addr2line提供了这个功能

     

    然后我们试图用addr2line来看地址对应的函数和行号

    linux-xms:/data/test # addr2line 0x80486de -e ./a.out -f
    fun3
    ??:0

    失败了,别急,我们再次修改编译参数

    linux-xms:/data/test # gcc -g -rdynamic test.c
    linux-xms:/data/test # ./a.out
    print call frame now:
    frame 0 -- ./a.out(fun3+0x4a) [0x80486de]
    frame 1 -- ./a.out(fun2+0xb) [0x8048762]
    frame 2 -- ./a.out(fun1+0xb) [0x804876f]
    frame 3 -- ./a.out(main+0x16) [0x8048787]
    frame 4 -- /lib/libc.so.6(__libc_start_main+0xdc) [0xb7dcb8ac]
    frame 5 -- ./a.out [0x8048611]
    linux-xms:/data/test # addr2line 0x80486de -e ./a.out -f
    fun3
    /data/test/test.c:14
  • 相关阅读:
    超级女声杭州赛区7进5
    究竟怎么了?
    最近发现
    S2SH基于角色权限拦截
    基于S2SH的电子商务网站系统性能优化
    TSQL复习笔记(一)
    用户sa登录失败,该用户与可信sql server连接无关联
    SQL附加数据库报5120的错误的解决办法
    DotNet中配置文件的使用(一)
    JQuery中使用AJAX $.ajax(prop)方法详解
  • 原文地址:https://www.cnblogs.com/yipianchuyun/p/13130777.html
Copyright © 2011-2022 走看看