zoukankan      html  css  js  c++  java
  • 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 来调试运行中的程序
  • 相关阅读:
    Kubernetes-一文详解ServiceAccount与RBAC权限控制
    删除无用的docker镜像与容器
    How do I write one to many query in Dapper.Net?
    c# 使用反射Reflection的Emit实现动态创建元数据及可执行文件
    IE浏览器下bootStrap form-control input输入框不显示兼容性问题
    WPF控件从一个窗口移动到另一个窗口,特别适合实时刷新的
    添加/扫描显示二维码中的换行之【另类视野】
    各浏览器官方离线版下载地址
    CentOS挂载NTFS
    System.Data.SQLite.Core for .NET 5 Core manual reference
  • 原文地址:https://www.cnblogs.com/hustcpp/p/12375416.html
Copyright © 2011-2022 走看看