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 真的值得看看

符号表的生成

符号表在编译的词法分析的时候,一直向符号表里填入符号,例如有重复定义的时候会报错,因为符号表已经存在该符号了。

符号表的使用

  1. 链接的时候,链接器会去符号表查找引用的符号是否存在
  2. 对于常量,编译器会向符号表查找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 程序

  1. 我们通常将调试文件放到可执行文件相同的目录,因为gdb会在当前目录查找符号文件。 另外可以通过gdb -s 来指定符号文件的位置。可以加多个符号文件
  2. 我们可以通过attach 加上-s 来调试运行中的程序
posted @ 2020-02-28 00:27  SnailRush  阅读(6176)  评论(1编辑  收藏  举报