如打开'-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
");
}
int main() {
test();
return 0;
}
编译时使用-g选项包含调试符号条,使用-Wl,-Map=testAddr2line.map选项输出MapFile。
gcc -Wl,-Map=testAddr2line.map -g -o testAddr2line testAddr2line.c
testAddr2line.map部分内容如下:
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的调试信息。
这里说明机器二进制编码的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命令查找函数的指令指针地址:
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