如打開'-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轉換。
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
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
比如 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存儲在可執行程序的.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使用 [C++] <wbr>addr2line使用](/image/aHR0cDovL3MxMS5zaW5haW1nLmNuL213NjkwLzAwMjZ1V2ZNenk3Y0tnTGdFS21jYSY2OTA=.png)
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使用 [C++] <wbr>addr2line使用](/image/aHR0cDovL3MyLnNpbmFpbWcuY24vbXc2OTAvMDAyNnVXZk16eTdjS2dJSjA3NzkxJjY5MA==.png)
這里說明機器二進制編碼的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使用 [C++] <wbr>addr2line使用](/image/aHR0cDovL3M5LnNpbmFpbWcuY24vbXc2OTAvMDAyNnVXZk16eTdjS2dISmUzZWM4JjY5MA==.png)
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