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\n");
}
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

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM