c++的符号表的肤浅认识
符号表是编译期产生的一个hash列表,随着可执行文件在一起
示例程序
int a = 10;
int b;
void foo(){
static int c=100;
}
int main(){
int d=1000;
int e;
foo();
}
符号表包括了变量和函数的信息,以及调试信息,可以通过nm 命令查看符号表
$nm -a
0000000000000000 a
0000000000004028 D a
0000000000004034 B b
0000000000004030 b .bss
0000000000004030 B __bss_start
0000000000000000 n .comment
0000000000004030 b completed.7393
0000000000000000 a crtstuff.c
0000000000000000 a crtstuff.c
w __cxa_finalize@@GLIBC_2.2.5
0000000000004018 d .data
0000000000004018 D __data_start
0000000000004018 W data_start
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000000 N .debug_info
0000000000000000 N .debug_line
0000000000000000 N .debug_str
0000000000001050 t deregister_tm_clones
00000000000010c0 t __do_global_dtors_aux
0000000000003e00 d __do_global_dtors_aux_fini_array_entry
0000000000004020 D __dso_handle
0000000000003e08 d .dynamic
0000000000003e08 d _DYNAMIC
00000000000003b8 r .dynstr
0000000000000328 r .dynsym
0000000000004030 D _edata
0000000000002038 r .eh_frame
0000000000002004 r .eh_frame_hdr
0000000000004038 B _end
00000000000011b8 t .fini
00000000000011b8 T _fini
0000000000003e00 d .fini_array
0000000000001110 t frame_dummy
0000000000003df8 d __frame_dummy_init_array_entry
0000000000002104 r __FRAME_END__
0000000000004000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000002004 r __GNU_EH_FRAME_HDR
0000000000000308 r .gnu.hash
000000000000045c r .gnu.version
0000000000000468 r .gnu.version_r
0000000000003fd8 d .got
0000000000004000 d .got.plt
0000000000001000 t .init
0000000000001000 t _init
0000000000003df8 d .init_array
0000000000003e00 d __init_array_end
0000000000003df8 d __init_array_start
0000000000000000 a init.c
00000000000002a8 r .interp
0000000000002000 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
00000000000011b0 T __libc_csu_fini
0000000000001140 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000001120 T main
0000000000000000 a main.cpp
00000000000002e8 r .note.ABI-tag
00000000000002c4 r .note.gnu.build-id
0000000000001080 t register_tm_clones
0000000000000488 r .rela.dyn
0000000000002000 r .rodata
0000000000001020 T _start
0000000000001020 t .text
0000000000004030 D __TMC_END__
0000000000001119 T _Z3foov
000000000000402c d _ZZ3foovE1c
可见这里还包含了位置 , 变量和函数都能看到。 还有debug信息
通过readelf -S a.out 可以查看所有符号表头信息
$readelf -S a.out
There are 32 section headers, starting at offset 0x3c90:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000000002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000000002e8 000002e8
0000000000000020 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 00000308
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000328 00000328
0000000000000090 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000000003b8 000003b8
00000000000000a4 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000045c 0000045c
000000000000000c 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000468 00000468
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000488 00000488
00000000000000c0 0000000000000018 A 5 0 8
[10] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[11] .text PROGBITS 0000000000001020 00001020
0000000000000195 0000000000000000 AX 0 0 16
[12] .fini PROGBITS 00000000000011b8 000011b8
000000000000000d 0000000000000000 AX 0 0 4
[13] .rodata PROGBITS 0000000000002000 00002000
0000000000000004 0000000000000004 AM 0 0 4
[14] .eh_frame_hdr PROGBITS 0000000000002004 00002004
0000000000000034 0000000000000000 A 0 0 4
[15] .eh_frame PROGBITS 0000000000002038 00002038
00000000000000d0 0000000000000000 A 0 0 8
[16] .init_array INIT_ARRAY 0000000000003df8 00002df8
0000000000000008 0000000000000008 WA 0 0 8
[17] .fini_array FINI_ARRAY 0000000000003e00 00002e00
0000000000000008 0000000000000008 WA 0 0 8
[18] .dynamic DYNAMIC 0000000000003e08 00002e08
00000000000001d0 0000000000000010 WA 6 0 8
[19] .got PROGBITS 0000000000003fd8 00002fd8
0000000000000028 0000000000000008 WA 0 0 8
[20] .got.plt PROGBITS 0000000000004000 00003000
0000000000000018 0000000000000008 WA 0 0 8
[21] .data PROGBITS 0000000000004018 00003018
0000000000000018 0000000000000000 WA 0 0 8
[22] .bss NOBITS 0000000000004030 00003030
0000000000000008 0000000000000000 WA 0 0 4
[23] .comment PROGBITS 0000000000000000 00003030
000000000000004c 0000000000000001 MS 0 0 1
[24] .debug_aranges PROGBITS 0000000000000000 0000307c
0000000000000030 0000000000000000 0 0 1
[25] .debug_info PROGBITS 0000000000000000 000030ac
00000000000000ca 0000000000000000 0 0 1
[26] .debug_abbrev PROGBITS 0000000000000000 00003176
0000000000000088 0000000000000000 0 0 1
[27] .debug_line PROGBITS 0000000000000000 000031fe
000000000000004b 0000000000000000 0 0 1
[28] .debug_str PROGBITS 0000000000000000 00003249
0000000000000068 0000000000000001 MS 0 0 1
[29] .symtab SYMTAB 0000000000000000 000032b8
0000000000000690 0000000000000018 30 49 8
[30] .strtab STRTAB 0000000000000000 00003948
000000000000020f 0000000000000000 0 0 1
[31] .shstrtab STRTAB 0000000000000000 00003b57
0000000000000139 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
nm 和readelf 还有很多功能, man 真的值得看看
符号表的生成
符号表在编译的词法分析的时候,一直向符号表里填入符号,例如有重复定义的时候会报错,因为符号表已经存在该符号了。
符号表的使用
- 链接的时候,链接器会去符号表查找引用的符号是否存在
- 对于常量,编译器会向符号表查找const的值,直接替换
符号表中的调试代码
所以说区分debug版本和release 版本的方法就是看符号表里有没有调试符号了
通过objdump -g a.out 可以看到很多调试信息
Contents of the .debug_info section (loaded from a.out):
Compilation Unit @ offset 0x0:
Length: 0xe1 (32-bit)
Version: 4
Abbrev Offset: 0x0
Pointer Size: 8
<0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
<c> DW_AT_producer : (indirect string, offset: 0x34): GNU C++14 9.2.1 20200130 -mtune=generic -march=x86-64 -g
<10> DW_AT_language : 4 (C++)
<11> DW_AT_name : (indirect string, offset: 0x0): main.cpp
<15> DW_AT_comp_dir : (indirect string, offset: 0xe): /home/
<19> DW_AT_low_pc : 0x1119
<21> DW_AT_high_pc : 0x22
<29> DW_AT_stmt_list : 0x0
<1><2d>: Abbrev Number: 2 (DW_TAG_variable)
<2e> DW_AT_name : a
<30> DW_AT_decl_file : 1
<31> DW_AT_decl_line : 1
<32> DW_AT_decl_column : 5
<33> DW_AT_type : <0x41>
<37> DW_AT_external : 1
<37> DW_AT_location : 9 byte block: 3 28 40 0 0 0 0 0 0 (DW_OP_addr: 4028)
<1><41>: Abbrev Number: 3 (DW_TAG_base_type)
<42> DW_AT_byte_size : 4
<43> DW_AT_encoding : 5 (signed)
<44> DW_AT_name : int
<1><48>: Abbrev Number: 4 (DW_TAG_const_type)
<49> DW_AT_type : <0x41>
<1><4d>: Abbrev Number: 2 (DW_TAG_variable)
<4e> DW_AT_name : b
<50> DW_AT_decl_file : 1
<51> DW_AT_decl_line : 2
<52> DW_AT_decl_column : 5
<53> DW_AT_type : <0x41>
<57> DW_AT_external : 1
<57> DW_AT_location : 9 byte block: 3 34 40 0 0 0 0 0 0 (DW_OP_addr: 4034)
<1><61>: Abbrev Number: 5 (DW_TAG_variable)
<62> DW_AT_name : (indirect string, offset: 0x2f): cons
<66> DW_AT_decl_file : 1
<67> DW_AT_decl_line : 3
<68> DW_AT_decl_column : 11
<69> DW_AT_type : <0x48>
<6d> DW_AT_location : 9 byte block: 3 4 20 0 0 0 0 0 0 (DW_OP_addr: 2004)
<1><77>: Abbrev Number: 6 (DW_TAG_subprogram)
<78> DW_AT_external : 1
<78> DW_AT_name : (indirect string, offset: 0x9): main
<7c> DW_AT_decl_file : 1
<7d> DW_AT_decl_line : 7
<7e> DW_AT_decl_column : 5
<7f> DW_AT_type : <0x41>
<83> DW_AT_low_pc : 0x1120
<8b> DW_AT_high_pc : 0x1b
<93> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
<95> DW_AT_GNU_all_tail_call_sites: 1
<95> DW_AT_sibling : <0xb1>
<2><99>: Abbrev Number: 7 (DW_TAG_variable)
分离调试信息
将调试信息保存到a.symbol 中
objcopy --only-keep-debug a.out a.symbol
去除调试信息
objcopy --strip-debug a.out a.bin
可以发现去除符号信息的debug 版本少了一下符号(表头)
[24] .debug_aranges PROGBITS 0000000000000000 0000307c
0000000000000030 0000000000000000 0 0 1
[25] .debug_info PROGBITS 0000000000000000 000030ac
00000000000000e5 0000000000000000 0 0 1
[26] .debug_abbrev PROGBITS 0000000000000000 00003191
00000000000000a0 0000000000000000 0 0 1
[27] .debug_line PROGBITS 0000000000000000 00003231
000000000000004b 0000000000000000 0 0 1
[28] .debug_str PROGBITS 0000000000000000 0000327c
000000000000006d 0000000000000001 MS 0 0 1
[29] .symtab SYMTAB 0000000000000000 000032f0
00000000000006a8 0000000000000018 30 50 8
[30] .strtab STRTAB 0000000000000000 00003998
0000000000000218 0000000000000000
符号表在调试的方法
没有调试信息的符号表是很难调试的,以下是没有调试信息和有调试信息的gdb情况
没有符号表的情况
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007ff3b4f74c60 in __GI___nanosleep (requested_time=0x7ffe32225050, remaining=0x7ffe32225050)
at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28 ../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
(gdb) bt
#0 0x00007ff3b4f74c60 in __GI___nanosleep (requested_time=0x7ffe32225050, remaining=0x7ffe32225050)
at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1 0x0000000000439815 in wait_to_exit(std::shared_ptr<App>&) ()
#2 0x000000000043626a in main ()
(gdb) n
29 in ../sysdeps/unix/sysv/linux/nanosleep.c
有符号表没有源文件的情况
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00007f8247a4cc60 in __GI___nanosleep (requested_time=requested_time@entry=0x7fffdf2fb6e0,
---Type <return> to continue, or q <return> to quit---
remaining=remaining@entry=0x7fffdf2fb6e0) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28 ../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
(gdb) bt
#0 0x00007f8247a4cc60 in __GI___nanosleep (requested_time=requested_time@entry=0x7fffdf2fb6e0,
remaining=remaining@entry=0x7fffdf2fb6e0) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
#1 0x0000000000439a35 in std::this_thread::sleep_for<long, std::ratio<1l, 1l> > (__rtime=...)
at /builds/main.cpp:192
#2 wait_to_exit(std::shared_ptr<App>&) () at /builds/main.cpp:192
#3 0x00000000004364b2 in main () at /builds/SkybilityHA/ha-engine/src/ha-sync/main.cpp:345
#4 0x00007f8245ee7b97 in __libc_start_main (main=0x436130 <main>, argc=3, argv=0x7fffdf2fbd88, init=<optimized out>,
fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffdf2fbd78) at ../csu/libc-start.c:310
#5 0x00000000004372ad in _start () at ../sysdeps/x86_64/elf/start.S:113
(gdb) n
29 in ../sysdeps/unix/sysv/linux/nanosleep.c
(gdb)
wait_to_exit(std::shared_ptr<App>&) () at /builds/main.cpp:193
193 /builds/main.cpp: No such file or directory.
可见,没有调试信息的堆栈信息是比较少的, 另外有调试信息提示没有源文件,所以如果将文件放到指定位置,就可以逐行调试代码了。
生产上用符号文件调试releas 程序
- 我们通常将调试文件放到可执行文件相同的目录,因为gdb会在当前目录查找符号文件。 另外可以通过gdb -s 来指定符号文件的位置。可以加多个符号文件
- 我们可以通过attach 加上-s 来调试运行中的程序