一般进行开发工作时,会有debug版本和release版本之分,debug版本方便调试,但与release版本相比,体积臃肿,运行速度慢。一般debug版本会带有符号表与调试信息,而release版本会把符号表和调试信息等strip掉。这时候,如果release版本出现core,而又不具备在debug版本复现的条件,就需要直接在release版本定位,由于没有符号表与调试信息,定位起来会比较麻烦。
先补充一下基础知识,涉及以下几个方面:
1.常用命令
2.obj文件与可执行文件段结构
3.进程内存映像
4.符号表与调试信息
5.gdb命令
一、常用命令
以下介绍的命令都是gnu binutils套件的一部分,手册地址链接。包含工具的概览如下:
命令 | 用处 |
---|---|
ar | 创建、修改和提取存档 |
nm | 列出对象文件中的符号 |
objcopy | 复制和翻译对象文件 |
objdump | 显示对象文件中的信息 |
ranlib | 生成索引以存档内容 |
size | 列出节大小和总大小 |
strings | 列出文件中的可打印字符串 |
strip | 丢弃符号 |
c++filt | 用于对编码C++符号进行反处理的筛选器,其实就是反编译 |
cxxfilt | MS-DOS name for c++filt |
addr2line | 将地址转换为文件和行 |
windmc | Windows 消息资源的生成器 |
windres | 操作Windows 资源 |
dlltool | 创建生成和使用 DLL 所需的文件 |
readelf | 显示 ELF 格式文件的内容 |
elfedit | 更新 ELF 文件的 ELF 标头和属性 |
1.ar
ar - create, modify, and extract from archives
ar命令最常见的用法是将目标文件打包为静态链接库。
指令参数:
-d 删除备存文件中的成员文件。
-r 将文件插入备存文件中。
-t 显示备存文件中所包含的文件。
-x 自备存文件中取出成员文件。
选项参数:
c 建立备存文件。
v 程序执行时显示详细的信息。
使用实例:
1.创建静态库
$ ar -crv libmakefile_test.a cli.o lib.o resource.o
r - cli.o
r - lib.o
r - resource.o
2.使用静态库编译
$ gcc -o app_test_static ./app/main.c -I ./include -L ./ -lmakefile_test -static
$ gcc -o app_test_dynamic ./app/main.c -I ./include -L ./ -lmakefile_test
-I选项指定头文件搜索路径
-L选项指定库文件搜索路径
-l选项指明需要链接库文件路径下的哪一个库
-static 在支持动态链接的系统,这个参数将覆盖-pie选项并且防止链接到共享库。在其他系统,这个参数无用。
查看编译的文件:
$ file app_test_static
app_test_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=2f7ba0b5feb0bee3acd3113608215c8b3fa25c43, for GNU/Linux 3.2.0, not stripped
$ file app_test_dynamic
app_test_dynamic: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b8f4aae218ee51b8559f737609d156d4dccd111c, for GNU/Linux 3.2.0, not stripped
关于静态库和动态库编译及使用,可查看这篇文章
2.nm
nm - list symbols from object files
常用参数:
-A: 在输出时每行前加上文件名;
-a: 输出所有符号,包含debugger-only symbols;
-B: BSD码显示,兼容MIPS nm;
-C: 将低级符号名解析为用户级名字,可以使得C++函数名更具可读性;
-D: 显示动态符号。该选项只对动态目标(如特定类型的共享库)有意义;
-f format/–format=format 使用format格式输出。format可以选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd
-g: 只显示外部符号;
-l: 对于每个符号,使用debug信息找到文件名和行号;
-n: 按符号对应地址的顺序排序,而非按符号名字字符顺序排序;
-P: 按照POSIX2.0标准格式输出,等同于使用 -f posix;
-p: 按照目标文件中遇到的符号顺序显示,不排序;
-r: 反转排序;
-s: 当列出库成员符号时,包含索引。索引的内容:模块和其包含名字的映射;
-u: 只显示未定义符号;
–defined-only: 只显示定义了的符号。
$ cat app/main.c
#include<stdio.h>
#include"../include/cli.h"
#include"../include/lib.h"
#include"../include/resource.h"
int main()
{
installcmd();
openlib();
initresource();
return 0;
}
$ nm -n main.o
U _GLOBAL_OFFSET_TABLE_
U initresource
U installcmd
U openlib
0000000000000000 T main
第二列的符号含义
符号 | 含义 |
---|---|
A | 该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。 |
B | 该符号的值出现在非初始化数据段(bss)中。例如,在一个文件中定义全局static int test。则该符号test的类型为b,位于bss section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中。 |
C | 该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B。 |
D | 该符号位于初始化数据段中。一般来说,分配到data section中。例如定义全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。 |
G | 该符号也位于初始化数据段中。主要用于small object提高访问small data object的一种方式。 |
I | 该符号是对另一个符号的间接引用。 |
N | 该符号是一个debugging符号。 |
R | 该符号位于只读数据区。例如定义全局const int test[] = {123, 123};则test就是一个只读数据区的符号。注意在cygwin下如果使用gcc直接编译成MZ格式时,源文件中的test对应_test,并且其符号类型为D,即初始化数据段中。但是如果使用m6812-elf-gcc这样的交叉编译工具,源文件中的test对应目标文件的test,即没有添加下划线,并且其符号类型为R。一般而言,位于rodata section。值得注意的是,如果在一个函数中定义const char *test = “abc”, const char test_int = 3。使用nm都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test在rodata section中,大小为4。 |
S | 符号位于非初始化数据区,用于small object。 |
T | 该符号位于代码区text section。 |
U | 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。 |
V | 该符号是一个weak object。 |
W | 该符号是没有被明确标记为weak object的弱符号类型。 |
- | 该符号是a.out格式文件中的stabs symbol。 |
? | 该符号类型没有定义。 |
3.objcopy
objcopy - copy and translate object files
常用参数:
–set-start val
设定新文件的起始地址为val,并不是所有格式的目标文件都支持设置起始地址。
–change-start incr
–adjust-start incr
通过增加incr量来调整起始地址,并不是所有格式的目标文件都支持设置起始地址。
–change-address incr
–adjust-vma incr
通过增加incr量调整所有sections的VMA(virtual memory address)和LMA(linear memory address),以及起始地址。
–only-keep-debug
对文件进行strip,移走所有不会被–strip-debug移走的section,并且保持调试相关的section原封不动。
–add-gnu-debuglink= 增加 .gnu_debuglink到文件中
-S 移除所有符号表
-g 移除调试符号和节
$objcopy --only-keep-debug algorithm algorithm.dbg
$ls
algorithm algorithm.dbg
$strip -s algorithm
$objcopy --add-gnu-debuglink=$(pwd)/algorithm.dbg algorithm
$readelf -S algorithm
...
[28] .gnu_debuglink PROGBITS 0000000000000000 0000303c
0000000000000014 0000000000000000 0 0 4
...
4.objdump
objdump - display information from object files
常用参数:
-a :显示档案库的成员信息,类似ls -l将lib*.a的信息列出。
-C :将底层的符号名解码成用户级名字,除了去掉所开头的下划线之外,还使得C++函数名以可理解的方式显示出来。
-g :显示调试信息。企图解析保存在文件中的调试信息并以C语言的语法显示出来。仅仅支持某些类型的调试信息。有些其他的格式被readelf -w支持。
-e :类似-g选项,但是生成的信息是和ctags工具相兼容的格式。
-d
:从objfile中反汇编那些特定指令机器码的section。可用于查看对应汇编的行号。
-D :与 -d 类似,但反汇编所有section.
–endian={big|little} :指定目标文件的大小端。这个项将影响反汇编出来的指令。在反汇编的文件没描述小端信息的时候用。例如S-records.
-f :显示objfile中每个文件的整体头部摘要信息。
-h
:显示目标文件各个section的头部摘要信息。 如果想看.text段在文件中的偏移量(字节),可以查看File off字段。需要注意的是,偏移量可能是16进制的,但是里面显示没带0x
-i :显示对于 -b 或者 -m 选项可用的架构和目标格式列表。
-I
, --include=DIR 将指定文件夹添加到源文件搜索列表
-j name:仅仅显示指定名称为name的section的信息
-l
:用文件名和行号标注相应的目标代码。
-m machine :指定反汇编目标文件时使用的架构,当待反汇编文件本身没描述架构信息的时候(比如S-records),这个选项很有用。可以用-i选项列出这里能够指定的架构.
-t :显示文件的符号表。类似于nm -s提供的信息
-T :显示文件的动态符号表,仅仅对动态目标文件意义,比如某些共享库。它显示的信息类似于 nm -D|–dynamic 显示的信息。
-r :显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇编后的格式显示出来。
-R :显示文件的动态重定位入口,仅仅对于动态目标文件意义,比如某些共享库。
-s :显示指定section的完整内容。默认所有的非空section都会被显示。
-S
:尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。 使用-Sld
参数可以进行汇编行和c代码的对应。
–show-raw-insn :反汇编的时候,显示每条汇编指令对应的机器码,如不指定–prefix-addresses,这将是缺省选项。
–no-show-raw-insn :反汇编时,不显示汇编指令的机器码,如指定–prefix-addresses,这将是缺省选项。
–start-address=address :从指定地址开始显示数据,该选项影响-d、-r和-s选项的输出。
–stop-address=address :显示数据直到指定地址为止,该项影响-d、-r和-s选项的输出。
-x :显示所可用的头信息,包括符号表、重定位入口。-x 等价于-a -f -h -r -t 同时指定。
-z :一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编。
@file 可以将选项集中到一个文件中,然后使用这个@file将选项载入。
$objdump -h out/usr/local/bin/algorithm
out/usr/local/bin/algorithm: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000000318 0000000000000318 00000318 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.gnu.property 00000030 0000000000000338 0000000000000338 00000338 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000000368 0000000000000368 00000368 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .note.ABI-tag 00000020 000000000000038c 000000000000038c 0000038c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .gnu.hash 00000028 00000000000003b0 00000000000003b0 000003b0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynsym 00000108 00000000000003d8 00000000000003d8 000003d8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .dynstr 000000ca 00000000000004e0 00000000000004e0 000004e0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version 00000016 00000000000005aa 00000000000005aa 000005aa 2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .gnu.version_r 00000030 00000000000005c0 00000000000005c0 000005c0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.dyn 000000d8 00000000000005f0 00000000000005f0 000005f0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .rela.plt 00000060 00000000000006c8 00000000000006c8 000006c8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
11 .init 0000001b 0000000000001000 0000000000001000 00001000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt 00000050 0000000000001020 0000000000001020 00001020 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .plt.got 00000010 0000000000001070 0000000000001070 00001070 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .plt.sec 00000040 0000000000001080 0000000000001080 00001080 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .text 00000151 00000000000010c0 00000000000010c0 000010c0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
16 .fini 0000000d 0000000000001214 0000000000001214 00001214 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
17 .rodata 0000002d 0000000000002000 0000000000002000 00002000 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .eh_frame_hdr 0000003c 0000000000002030 0000000000002030 00002030 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
19 .eh_frame 000000bc 0000000000002070 0000000000002070 00002070 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
20 .init_array 00000008 0000000000003d80 0000000000003d80 00002d80 2**3
CONTENTS, ALLOC, LOAD, DATA
21 .fini_array 00000008 0000000000003d88 0000000000003d88 00002d88 2**3
CONTENTS, ALLOC, LOAD, DATA
22 .dynamic 00000210 0000000000003d90 0000000000003d90 00002d90 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .got 00000060 0000000000003fa0 0000000000003fa0 00002fa0 2**3
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000010 0000000000004000 0000000000004000 00003000 2**3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000010 0000000000004010 0000000000004010 00003010 2**4
ALLOC
26 .comment 0000002b 0000000000000000 0000000000000000 00003010 2**0
CONTENTS, READONLY
27 .debug_aranges 00000060 0000000000000000 0000000000000000 0000303b 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
28 .debug_info 000004a6 0000000000000000 0000000000000000 0000309b 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
29 .debug_abbrev 00000244 0000000000000000 0000000000000000 00003541 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
30 .debug_line 0000013a 0000000000000000 0000000000000000 00003785 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
31 .debug_str 000002a8 0000000000000000 0000000000000000 000038bf 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
32 .debug_line_str 0000011c 0000000000000000 0000000000000000 00003b67 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
33 .debug_loclists 00000028 0000000000000000 0000000000000000 00003c83 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
34 .debug_rnglists 00000017 0000000000000000 0000000000000000 00003cab 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
$objdump -a out/usr/local/lib/libparallelmenu.a
In archive out/usr/local/lib/libparallelmenu.a:
parallelmenu.o: file format elf64-x86-64
rw-r--r-- 0/0 7216 Jan 1 08:00 1970 parallelmenu.o
parallelmenu_api.o: file format elf64-x86-64
rw-r--r-- 0/0 25872 Jan 1 08:00 1970 parallelmenu_api.o
5.strip
strip - discard symbols and other data from object files
常用参数:
-s:去除所有符号表
-g:只去除debug符号表
-o:将strip的内容存储到另外的指定文件
$ strip a.out -o a_strip.out
$ ls
a.out a.out.dbg a_strip.out test.c
如果使用了-g,那么调试时还是能看到函数名的,只不过可能看不到行号了。
实例:在使用-g前有38个节,debug就有8个
$readelf -S algorithm|grep debug
[28] .debug_aranges PROGBITS 0000000000000000 0000303b
[29] .debug_info PROGBITS 0000000000000000 0000309b
[30] .debug_abbrev PROGBITS 0000000000000000 00003541
[31] .debug_line PROGBITS 0000000000000000 00003785
[32] .debug_str PROGBITS 0000000000000000 000038bf
[33] .debug_line_str PROGBITS 0000000000000000 00003b67
[34] .debug_loclists PROGBITS 0000000000000000 00003c83
[35] .debug_rnglists PROGBITS 0000000000000000 00003cab
$readelf -p .strtab algorithm
String dump of section '.strtab':
[ 1] Scrt1.o
[ 9] __abi_tag
[ 13] main.c
[ 1a] crtstuff.c
[ 25] deregister_tm_clones
[ 3a] __do_global_dtors_aux
[ 50] completed.0
[ 5c] __do_global_dtors_aux_fini_array_entry
[ 83] frame_dummy
[ 8f] __frame_dummy_init_array_entry
[ ae] interface.c
[ ba] __FRAME_END__
[ c8] _DYNAMIC
[ d1] __GNU_EH_FRAME_HDR
[ e4] _GLOBAL_OFFSET_TABLE_
[ fa] __libc_start_main@GLIBC_2.34
[ 117] _ITM_deregisterTMCloneTable
[ 133] puts@GLIBC_2.2.5
[ 144] stdin@GLIBC_2.2.5
[ 156] _edata
[ 15d] _fini
[ 163] interface
[ 16d] __data_start
[ 17a] __gmon_start__
[ 189] __dso_handle
[ 196] _IO_stdin_used
[ 1a5] _end
[ 1aa] __bss_start
[ 1b6] main
[ 1bb] BST
[ 1bf] __TMC_END__
[ 1cb] parallelmenu
[ 1d8] _ITM_registerTMCloneTable
[ 1f2] __cxa_finalize@GLIBC_2.2.5
[ 20d] _init
[ 213] getc@GLIBC_2.2.5
$readelf -p .dynstr algorithm
String dump of section '.dynstr':
[ 1] _ITM_deregisterTMCloneTable
[ 1d] __gmon_start__
[ 2c] _ITM_registerTMCloneTable
[ 46] parallelmenu
[ 53] BST
[ 57] __cxa_finalize
[ 66] __libc_start_main
[ 78] getc
[ 7d] puts
[ 82] stdin
[ 88] libparallelmenu.so.0
[ 9d] libbst.so.0
[ a9] libc.so.6
[ b3] GLIBC_2.2.5
[ bf] GLIBC_2.34
$ readelf -s algorithm
Symbol table '.dynsym' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND BST
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND parallelmenu
7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getc@GLIBC_2.2.5 (3)
9: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (3)
10: 0000000000004010 8 OBJECT GLOBAL DEFAULT 26 stdin@GLIBC_2.2.5 (3)
Symbol table '.symtab' contains 42 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS Scrt1.o
2: 000000000000038c 32 OBJECT LOCAL DEFAULT 4 __abi_tag
3: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
4: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
5: 0000000000001110 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
6: 0000000000001140 0 FUNC LOCAL DEFAULT 16 register_tm_clones
7: 0000000000001180 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
8: 0000000000004018 1 OBJECT LOCAL DEFAULT 26 completed.0
9: 0000000000003d88 0 OBJECT LOCAL DEFAULT 22 __do_global_dtor[...]
10: 00000000000011c0 0 FUNC LOCAL DEFAULT 16 frame_dummy
11: 0000000000003d80 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_in[...]
12: 0000000000000000 0 FILE LOCAL DEFAULT ABS interface.c
13: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
14: 0000000000002128 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
15: 0000000000000000 0 FILE LOCAL DEFAULT ABS
16: 0000000000003d90 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
17: 0000000000002030 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
18: 0000000000003fa0 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]
20: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
21: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5
23: 0000000000004010 8 OBJECT GLOBAL DEFAULT 26 stdin@GLIBC_2.2.5
24: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
25: 0000000000001214 0 FUNC GLOBAL HIDDEN 17 _fini
26: 00000000000011d0 65 FUNC GLOBAL DEFAULT 16 interface
27: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
28: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
29: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
30: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
31: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 26 _end
32: 00000000000010e0 38 FUNC GLOBAL DEFAULT 16 _start
33: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
34: 00000000000010c0 22 FUNC GLOBAL DEFAULT 16 main
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND BST
36: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND parallelmenu
38: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
39: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@G[...]
40: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init
41: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getc@GLIBC_2.2.5
$strip -g algorithm
$file algorithm
algorithm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9b9abe6929571743b9a7f78b269953a53224a6b5, for GNU/Linux 3.2.0, not stripped
此时仍显示未被strip,但此时就只有30个节了。而且使用gdb已经无法看到行号了。
再次使用-s,则只剩下28个节
$strip -s algorithm
$file algorithm
algorithm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9b9abe6929571743b9a7f78b269953a53224a6b5, for GNU/Linux 3.2.0, stripped
.symtab和 .strtab已经被去除, .shstrtab还在(专门存储Section名字的Section(Section Header String Table Section,简写为shstrtab)),.dynsym和.dynstr也都还在。因此大多数函数都不能断了,但是外部函数还能断,因为.dynsym还存在。
6.addr2line
addr2line - convert addresses into file names and line numbers
常用参数:
-a:在函数名、文件名和行号信息之前,以十六进制形式显示地址。
-C:将低级别的符号名解码为用户级别的名字。
-e:指定需要转换地址的可执行文件名,默认文件是a.out。
-f:在显示文件名、行号信息的同时显示函数名。
-s:仅显示每个文件名(the base of each file name)去除目录名。
-i:如果需要转换的地址是一个内联函数,则还将打印返回第一个非内联函数的信息。
-j:读取指定section的偏移而不是绝对地址。
-p:使打印更加人性化:每个地址(location)的信息都打印在一行上。
-r:启用或禁用递归量限制。
编译进程如下,执行后段错误:
int main()
{
int *p = NULL;
*p=0;
printf("hello world!\n");
}
dmesg信息如下:
[ 2022.242578] a.out[6050]: segfault at 0 ip 0000558c30b8f161 sp 00007ffd40f0e6f0 error 6 in a.out[558c30b8f000+1000]
[ 2022.242606] Code: 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 83 ec 10 48 c7 45 f8 00 00 00 00 48 8b 45 f8 <c7> 00 00 00 00 00 48 8d 3d 96 0e 00 00 e8 dd fe ff ff b8 00 00 00
[ 2022.242619] potentially unexpected fatal signal 11.
0000558c30b8f161 - 558c30b8f000 = 0x161
需要注意有的系统可以直接使用报错地址查到,有的系统需要减去基地址
7.readelf
-a 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I.
-h 显示elf文件开始的文件头信息.
-l - 显示程序头(段头)信息(如果有的话)。
-S
显示节头信息(如果有的话)。 如果想看.text段在文件中的偏移量(字节),可以查看Offset字段。需要注意的是,偏移量可能是16进制的,但是里面显示没带0x
-g 显示节组信息(如果有的话)。
-t 显示节的详细信息(-S的)。
-s
显示符号表段中的项(如果有的话),使用-Ws
选项来查看符号表。
-e 显示全部头信息,等价于: -h -l -S -n --notes 显示note段(内核注释)的信息。
-r 显示可重定位段的信息。
-u 显示unwind段信息。当前只支持IA64 ELF的unwind段信息。
-d
显示动态段的信息。 .dynamic段
-V 显示版本段的信息。
-A 显示CPU构架信息。
-D 使用动态段中的符号表显示符号,而不是使用符号段。
-x 以16进制方式显示指定段内内容。number指定段表中段的索引,或字符串指定文件中的段名。
-p
dump出指定节的内容,看指定节的内容就用他
-w[liaprmfFsoR] or --debug-dump[=line,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges] 显示调试段中指定的内容。
-I 显示符号的时候,显示bucket list长度的柱状图。
-W 宽行输出。
@file 可以将选项集中到一个文件中,然后使用这个@file选项载入。
二、obj文件与可执行文件段结构
linux下的obj文件和可执行文件都是elf格式,其结构如下:
ELF头部(ELF Header):ELF头部是ELF文件的第一个部分,包含了文件类型、机器类型、入口地址、程序头表
偏移量、节头表
偏移量等信息。
程序头表(Program Header Table):程序头表描述了可执行文件或共享库的段信息,包括段类型、段的虚拟地址、文件偏移量、段大小等信息。
节头表(Section Header Table):节头表描述了ELF文件中的所有节,包括代码段、数据段、符号表、重定位表等。每个节头表项包含了节的名称、类型、大小、偏移量等信息。
节区(Section):ELF文件的数据被组织成许多节,每个节都有一个唯一的名称和类型。常见的节区包括.text(代码段)、.data(数据段)、.bss(未初始化的数据段)等。
符号表(Symbol Table):符号表包含了ELF文件中的所有符号信息,包括函数名、变量名、常量等。每个符号表项包含了符号的名称、类型、值、大小等信息。
字符串表(String Table):字符串表包含了ELF文件中使用的所有字符串,包括节名、符号名、库名等。
重定位表(Relocation Table):重定位表包含了ELF文件中的所有重定位信息,用于修正代码和数据的地址。每个重定位表项包含了需要修正的地址、修正方式等信息。
三、进程内存映像
调试core时加载带调试信息so时需要加地址的原因:
参见《深入理解计算机系统笔记–备忘》:代码段与数据段之间有空隙(为了对齐要求),而栈,共享模块,堆由于安全性还会使用地址空间布局随机化,每次程序运行时,他们的地址都会改变。
core中info proc mappings得到地址为当时动态库加载的地址,而add-symbol-file需要指定文件的text段地址,因此我们的目标是调试时加载的地址就是当时动态库.text段的地址,debug信息也要加载在这。
四、符号表与调试信息
符号表包含.dynsym节和.symtab节
.dynsym保存了引用来自外部文件符号的全局符号。如printf库函数。.dynsym保存的符号是.symtab所保存符合的子集,.symtab中还保存了可执行文件的本地符号。如全局变量,代码中定义的本地函数等。
.dynsym对于动态链接可执行文件的执行是必需的,而.symtab只是用来进行调试和链接的。
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
6: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (3)
Symbol table '.symtab' contains 36 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS Scrt1.o
2: 000000000000038c 32 OBJECT LOCAL DEFAULT 4 __abi_tag
3: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
4: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
5: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
6: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
7: 0000000000004010 1 OBJECT LOCAL DEFAULT 26 completed.0
8: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtor[...]
9: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
10: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_in[...]
11: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
12: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
13: 00000000000020f0 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
14: 0000000000000000 0 FILE LOCAL DEFAULT ABS
15: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
16: 0000000000002014 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
17: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]
19: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
20: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5
22: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
23: 0000000000001168 0 FUNC GLOBAL HIDDEN 17 _fini
24: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
25: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
26: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
27: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
28: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end
29: 0000000000001060 38 FUNC GLOBAL DEFAULT 16 _start
30: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
31: 0000000000001149 30 FUNC GLOBAL DEFAULT 16 main
32: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
33: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
34: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@G[...]
35: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init
Linux进程的调试信息包含在ELF文件中的多个段中,这些段通常以.debug开头,例如:
.debug_info:包含了程序的调试信息,例如类型定义、变量定义、函数定义等。
.debug_line:包含了程序的源代码行号信息,用于在调试器中显示源代码。
.debug_str:包含了程序中使用的所有字符串,例如变量名、函数名等。
.debug_abbrev:包含了调试信息的缩写表,用于压缩调试信息。
.debug_loc:包含了程序中局部变量的地址信息,用于在调试器中显示局部变量的值。
.debug_ranges:包含了程序中全局变量和静态变量的地址范围信息,用于在调试器中显示变量的值。
因此当符号表全部被去除,nm -a无内容时,还可以查看.dynsym来确认库对外提供的外部接口,可以使用objdump的-T选项:
-T, --dynamic-syms Display the contents of the dynamic symbol table;
或者可以使用readelf的-s选项:
-s --syms Display the symbol table
关于backtrace_symbols符号解析,后续再研究
-g 选项:
-g 选项告诉编译器生成调试信息,这些信息用于在调试工具(如 gdb)中调试程序。它将提供源代码的位置映射到生成的机器代码,使得调试时可以使用变量名、函数名、行号等。
使用此选项编译的程序通常会包含更详细的调试信息,便于调试,但它并不影响调用栈信息的符号解析。
-rdynamic 选项:
-rdynamic 选项则是用于把程序中的所有符号(包括静态和动态的)添加到可执行文件的符号表中。这对于在运行时获得符号信息(如通过 backtrace_symbols)是必须的。 使用 -rdynamic
后,程序在运行时能更方便地访问函数名和其他符号信息,与调用栈跟踪等功能兼容。
五、GLibc版本查看
有时排查问题需要明确c库的版本,方法列举如下:
ldd --version
strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC
getconf GNU_LIBC_VERSION
/lib/x86_64-linux-gnu/libc.so.6 #直接执行
六、gdb相关命令
1.add-symbol-file
(gdb) help add-symbol-file
从FILE中加载符号,并假定FILE已经被动态加载。
用法:add-symbol-file FILE [-readnow | -readnever] [-o OFF] [ADDR] [-s SECT-NAME SECT-ADDR]…
ADDR是文件.text的起始地址。
每个-s
,参数指定了一个段名和段地址,如果数据段和bss段与文本不连续,则应指定该参数。
SECT-NAME 是一个被加载在 SECT-ADDR的段的名字.
OFF是一个可选的偏移量,它被添加到默认加载地址中未指定其他地址的所有节的。
(OFF is an optional offset which is added to the default load addresses of all sections for which no other address was specified.)
'-readnow’选项将导致GDB立即读取整个符号文件。这可能使命令执行变慢,但可能使未来的操作更快。
'-readnever’选项将阻止GDB读取符号文件的符号调试信息。
2.info proc mappings
List memory regions mapped by the specified process.
列出特定进程的内存区域
3. info sharedlibrary
Status of loaded shared object libraries.
已加载共享库的状态
4.info proc mappings和info sharedlibrary的差异
wsl@My-win:~/project/algorithm$ objdump -h /usr/local/lib/libparallelmenu.so.0.0.0
/usr/local/lib/libparallelmenu.so.0.0.0: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.property 00000020 00000000000002a8 00000000000002a8 000002a8 2**3
...
...
13 .text 00000928 00000000000012c0 00000000000012c0 000012c0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
通过上述命令可以看到.text段相对于动态库的偏移是0x12c0.
如果直接运行进程,然后attach,查看命令:
(gdb) info proc mappings
process 9775
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x561bf6f6f000 0x561bf6f70000 0x1000 0x0 /usr/local/bin/algorithm
...
...
0x561bf6f73000 0x561bf6f74000 0x1000 0x3000 /usr/local/bin/algorithm
0x561bf7300000 0x561bf7321000 0x21000 0x0 [heap]
0x7fe23e052000 0x7fe23e055000 0x3000 0x0
0x7fe23e055000 0x7fe23e07d000 0x28000 0x0 /usr/lib/x86_64-linux-gnu/libc.so.6
...
...
0x7fe23e26e000 0x7fe23e270000 0x2000 0x218000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7fe23e270000 0x7fe23e27d000 0xd000 0x0
0x7fe23e27d000 0x7fe23e27e000 0x1000 0x0 /usr/local/lib/libparallelmenu.so.0.0.0
0x7fe23e27e000 0x7fe23e27f000 0x1000 0x1000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7fe23e27f000 0x7fe23e280000 0x1000 0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7fe23e280000 0x7fe23e281000 0x1000 0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7fe23e281000 0x7fe23e282000 0x1000 0x3000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7fe23e287000 0x7fe23e289000 0x2000 0x0
0x7fe23e289000 0x7fe23e28b000 0x2000 0x0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
...
...
0x7fe23e2c3000 0x7fe23e2c5000 0x2000 0x39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffd49381000 0x7ffd493a2000 0x21000 0x0 [stack]
0x7ffd493d9000 0x7ffd493dd000 0x4000 0x0 [vvar]
0x7ffd493dd000 0x7ffd493df000 0x2000 0x0 [vdso]
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007fe23e27e2c0 0x00007fe23e27ebe8 Yes /usr/local/lib/libparallelmenu.so.0
0x00007fe23e07d700 0x00007fe23e20fabd Yes (*) /lib/x86_64-linux-gnu/libc.so.6
0x00007fe23e28b090 0x00007fe23e2b4335 Yes (*) /lib64/ld-linux-x86-64.so.2
(*): Shared library is missing debugging information.
如果从gdb启动,显示如下:
(gdb) info proc mappings
process 9789
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x555555554000 0x555555555000 0x1000 0x0 /usr/local/bin/algorithm
...
...
0x555555558000 0x555555559000 0x1000 0x3000 /usr/local/bin/algorithm
0x7ffff7d86000 0x7ffff7d89000 0x3000 0x0
0x7ffff7d89000 0x7ffff7db1000 0x28000 0x0 /usr/lib/x86_64-linux-gnu/libc.so.6
...
...
0x7ffff7fa2000 0x7ffff7fa4000 0x2000 0x218000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7fa4000 0x7ffff7fb1000 0xd000 0x0
0x7ffff7fb1000 0x7ffff7fb2000 0x1000 0x0 /usr/local/lib/libparallelmenu.so.0.0.0
0x7ffff7fb2000 0x7ffff7fb3000 0x1000 0x1000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7ffff7fb3000 0x7ffff7fb4000 0x1000 0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7ffff7fb4000 0x7ffff7fb5000 0x1000 0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7ffff7fb5000 0x7ffff7fb6000 0x1000 0x3000 /usr/local/lib/libparallelmenu.so.0.0.0
0x7ffff7fbb000 0x7ffff7fbd000 0x2000 0x0
0x7ffff7fbd000 0x7ffff7fc1000 0x4000 0x0 [vvar]
0x7ffff7fc1000 0x7ffff7fc3000 0x2000 0x0 [vdso]
0x7ffff7fc3000 0x7ffff7fc5000 0x2000 0x0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
...
...
0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007ffff7fc5090 0x00007ffff7fee335 Yes (*) /lib64/ld-linux-x86-64.so.2
0x00007ffff7fb22c0 0x00007ffff7fb2be8 Yes /usr/local/lib/libparallelmenu.so.0
0x00007ffff7db1700 0x00007ffff7f43abd Yes (*) /lib/x86_64-linux-gnu/libc.so.6
(*): Shared library is missing debugging information.
总结:
info proc mappings查看的是动态库运行时加载的地址,如果是直接启动的,这个地址是随机的,如果是gdb启动的,这个地址是固定的。
info sharedlibrary 命令查看的是动态库代码段的起始地址。
4.x与disassemble的异同
(gdb) help x
检查内存:x/FMT ADDRESS。
ADDRESS是要检查的内存地址的表达式。
FMT是一个重复计数,后面跟着一个格式字母和一个大小字母。
格式字母有o(八进制),x(十六进制),d(十进制),u(无符号十进制),
T(二进制),f(浮点),a(地址),i(指令),c(char), s(字符串)
z(十六进制,0在左边填充)
大小字母是b(字节),h(半字),w(字),g(巨型,8字节)。
打印指定大小的指定数量的对象
根据格式。如果指定的是负数,则内存为从地址往后查。
字母的格式和大小的默认值是以前使用的。
默认计数为1。默认地址在最后打印的内容后面用这个命令或者"print"。
(gdb) help disassemble
反汇编内存的指定部分。
默认是所选帧的pc周围的函数。
如果使用/m修饰符,那么将包括源代码行(如果可用)。
这种视图是“以源代码为中心”的:输出按源行顺序排列,而不是优化的代码。只有主源文件
被显示,而不是别的,例如,任何内联函数。这个修饰符在实践中没有被证明是有用的,因此不建议使用
使用/s修饰符,那么将包括源代码行(如果可用)。这与/m在两个重要方面不同:
-输出仍然是PC地址顺序
-显示所有相关源文件的文件名和内容。
使用/r修饰符,将包括十六进制的原始指令。
如果只有一个参数,围绕该地址的函数将被转储。两个参数(以逗号分隔)作为要转储的内存范围,以“start,end”或“start,+length”的形式。
注意,地址被解释为一个表达式,而不是一个位置,就像"break"命令一样。
因此,例如,如果你想在文件foo.c中反汇编函数bar,你必须输入“disassemble ‘foo.c’::bar”而不是“disassemble 'foo.c:bar”。
七、coredump
coredump是一个内存映像,可以是主动生成,也可以是崩溃自动生成。
查看coredump开启情况
查看coredump开启情况:
$ ulimit -c
0
coredump功能在linux是默认关闭的。
打开 coredump 功能
(1)临时打开 coredump 功能
ulimit -c unlimited 命令:生成 coredump 文件,文件大小不受限制。 或ulimit -c 1024 命令: 生成固定大小的 coredump 文件,单位为 blocks(KB)。
注意:这种修改为临时限制,只存活于当前 shell 会话,仅影响当前 shell。
wsl@My-win:~$ ulimit -c unlimited
wsl@My-win:~$ ulimit -c
unlimited
(2)永久打开 coredump 功能
更改 /etc/security/limits.conf 文件中的内容。
去掉 soft core 0 一行前面的注释 ,同时,将 0 改为 unlimited
#/etc/security/limits.conf
...
#
#* soft core 0
#root hard core 100000
#* hard rss 10000
设置coredump路径
默认生成路径:输入可执行文件运行命令的同一路径下
默认生成名字:默认命名为core。新的core文件会覆盖旧的core文件
以下命令分别添加了进程号和日期作为core文件名字
echo 1 > /proc/sys/kernel/core_uses_pid
echo "/corefile/core-%e-%p-%t" > core_pattern
测试core文件生成情况
kill -s SIGSEGV $$
离线查看
离线查看core是很常见的,因为出问题的设备很可能资源有限。把源文件直接放到core文件同路径就可以看函数以及行号了。离线调试core文件需要对应的交付件与符号表,交付件可以是出问题的设备拷贝出来的,也可以是重编的带符号表的同参数编译的交付件,而符号表可以使用objcopy单独剥离的。
八、符号表剥离与附加
查看.text段在文件中的偏移量(字节)
1.objdump -h 的File off字段
Idx Name Size VMA LMA File off Algn
12 .text 00145e2b 0000000000022320 0000000000022320 00022320 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
2.readelf -S 的Offset字段
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[13] .text PROGBITS 0000000000022320 00022320
0000000000145e2b 0000000000000000 AX 0 0 16
需要注意的是,偏移量可能是16进制的,但是里面显示没带0x
在使用add-symbol-file时,需要指定.text段的地址,即info proc mappings对应的起始地址+.text段偏移量,需要注意的是,如果在看偏移量时发现地址和偏移不一致,可以直接使用地址,不需要再加.text段偏移了,具体原因未知,可能和x86地址随机化有关
同样的代码,在x86_64:
(gdb) add-symbol-file test 0x559129f91000+0x1060
add symbol table from file "test" at
.text_addr = 0x559129f92060
(y or n) y
Reading symbols from test...done.
(gdb) bt
#0 0x0000559129f92171 in main () at test.c:13
#1 0x00007f113bf9409b in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x0000559129f9208a in ?? ()
在arm64:
(gdb) add-symbol-file test_arm64 0x000000004004f0
add symbol table from file "test_arm64" at
.text_addr = 0x4004f0
(y or n) y
Reading symbols from test_arm64...
(gdb) bt
#0 0x0000000000400624 in main () at test.c:13
如果是动态库,可以直接 info sharedlibrary查看库的地址,然后直接使用add-sys mbol-file 加载
剥离符号表流程
$ aarch64-none-linux-gnu-objcopy --only-keep-debug test_arm64 test_arm64.dbg
$ file test_arm64.dbg
test_arm64.dbg: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter *empty*, for GNU/Linux 3.7.0, with debug_info, not stripped
$ aarch64-none-linux-gnu-readelf -S test_arm64.dbg
There are 27 section headers, starting at offset 0x3b0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp NOBITS 0000000000400238 00000238
000000000000001b 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .hash NOBITS 0000000000400278 00000274
000000000000002c 0000000000000004 A 5 0 8
[ 4] .gnu.hash NOBITS 00000000004002a8 00000274
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym NOBITS 00000000004002c8 00000274
0000000000000090 0000000000000018 A 6 1 8
[ 6] .dynstr NOBITS 0000000000400358 00000274
000000000000004b 0000000000000000 A 0 0 1
[ 7] .gnu.version NOBITS 00000000004003a4 00000274
000000000000000c 0000000000000002 A 5 0 2
[ 8] .gnu.version_r NOBITS 00000000004003b0 00000274
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn NOBITS 00000000004003d0 00000274
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt NOBITS 00000000004003e8 00000274
0000000000000078 0000000000000018 A 5 22 8
[11] .init NOBITS 0000000000400460 00000274
0000000000000014 0000000000000000 AX 0 0 4
[12] .plt NOBITS 0000000000400480 00000274
0000000000000070 0000000000000010 AX 0 0 16
[13] .text NOBITS 00000000004004f0 00000274
00000000000001cc 0000000000000000 AX 0 0 8
gdb中:
(gdb) add-symbol-file test_arm64.dbg 0x000000004004f0
add symbol table from file "test_arm64.dbg" at
.text_addr = 0x4004f0
(y or n) y
Reading symbols from test_arm64.dbg...
(gdb) bt
#0 0x0000000000400624 in main () at test.c:13
需要注意 test_arm64.dbg与test_arm64中的.text偏移是一样的
使用效果
(gdb) bt
#0 0x0000ffffae295c84 in epoll_pwait () from target:/lib64/libc.so.6
#1 0x0000000000e169cc in thread_fetch3 ()
#2 0x00000000005286c0 in cml_start ()
#3 0x000000000050aee0 in main ()
(gdb) add-symbol-file cmld.dbg 0x0000000050df10
add symbol table from file "cmld.dbg" at
.text_addr = 0x50df10
(y or n) y
Reading symbols from cmld.dbg...
(gdb) bt
#0 0x0000ffffae295c84 in epoll_pwait () from target:/lib64/libc.so.6
#1 0x0000000000e169cc in nsm_encode_cfm_status_msg (pnt=0xfffffffffffffffc, size=0xfffffc30af00, msg=0x42) at nsm_message.c:17442
#2 0x00000000005286c0 in cml_check_if_default_value_set_in_db (header=0x1, tranObj=<optimized out>) at cml_main.c:17847
#3 0x000000000050aee0 in main ()
远程gdb使用效果
(gdb) target extended-remote 172.14.49.68:6000
(gdb) bt
#0 0x0000ffffae295c84 in epoll_pwait () from target:/lib64/libc.so.6
#1 0x0000000000e169cc in thread_fetch3 ()
#2 0x00000000005286c0 in cml_start ()
#3 0x000000000050aee0 in main ()
(gdb) add-symbol-file cmld.dbg 0x0000000050df10
add symbol table from file "cmld.dbg" at
.text_addr = 0x50df10
(y or n) y
Reading symbols from cmld.dbg...
(gdb) bt
#0 0x0000ffffae295c84 in epoll_pwait () from target:/lib64/libc.so.6
#1 0x0000000000e169cc in nsm_encode_cfm_status_msg (pnt=0xfffffffffffffffc, size=0xfffffc30af00, msg=0x42) at nsm_message.c:17442
#2 0x00000000005286c0 in cml_check_if_default_value_set_in_db (header=0x1, tranObj=<optimized out>) at cml_main.c:17847
#3 0x000000000050aee0 in main ()
(gdb)
参考文章:
Linux环境Release版本的符号表剥离及调试方法
Why does the same shared library show up multiple times when using info proc mappings
in gdb?
为什么 ‘ldd’ 和 ‘(gdb) info sharedlibrary’ 显示不同的动态库基地址?
Thread: addr2line doesn’t work - returns ??:0
如何在没有core文件的情况下用dmesg+addr2line定位段错误
Linux下离线调试之coredump文件介绍
计算机那些事(4)——ELF文件结构
strip,eu-strip 及其符号表,gdb调试strip过的程序
Linux调试工具 | Addr2line
addr2line 输出为?:0可能原因