• Linux程序调试查看二进制文件


    一,二进制文件的类型

     

        Linux下的二进制文件是ELF格式的,主要有目标文件、静态链接库文件、动态链接库文件、可执行文件和core dump文件。可以使用如下命令查看其类型:

     

        file  文件名。

     

        我们还是以之前的例子test.c举例,test.c的源代码和之前的文章一样:

     

     


        int sub(int a,int b,int c){

              *(int *)a=16;
               return 0;
        }

        int main()
       {
           int a=0;
           int b=1;
           int c=2;
           sub(a,b,c);
           return 0;
       }

     

     

       a)使用gcc生成目标文件: gcc -c -o test.obj test.c

     

       使用file查看:

     

       file test.obj
       test.obj: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

     

     

     

       b)使用gcc 和ar生成静态库文件:

       gcc -c -o test.o test.c

       ar rcs libtest.a test.o

     

       使用file查看:

     

        file libtest.a
        libtest.a: current ar archive

       c)使用gcc生成动态链接库文件:

       gcc -fPIC -c -o test.o test.c

       gcc -shared -o libtest.so test.o

     

       使用file查看:

     

       file libtest.so
       libtest.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped

     

       d)使用gcc生成可执行文件

        gcc -o test test.c

     

        使用file查看:

     

       file test
       test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), not stripped

     

       e)运行产生core dump 

       ./test

     

     

        使用file查看:

     

         file test-29728.core
         test-29728.core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './test'

     

     

    二,查看二进制文件段的信息

     

        为了能够在查看二进制文件的同时,看到二进制文件中段的意义,采用的源代码如下所示:

     

    *Linux:
       gcc -c SimpleSection.c
     *
     *Windows:
     * cl SimpleSection.c /c/Za
     */
    int printf(const char*format,...);

    int global_init_var=84;
    int global_uninit_var;
    static int global_static_var;
    static int global_static_var1=1;
    static int global_static_var0=0;
    void func1(int i)
    {

    printf("%d/n",i);
    }

    int main(void){

     static int static_var=85;
     static int static_var2;
     int a=1;
     int b;

     func1(static_var+static_var2+a+b);

     return a;





    }

     

       使用gcc 编译出目标文件: gcc -c -o SimpleObject.o SimpleObject.c

     

       使用binutils工具包中的objdump查看该二进制文件,-h表示查看段头:

     

     

    objdump -h SimpleSection.o

    SimpleSection.o:     file format elf32-i386

    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         0000005b  00000000  00000000  00000034  2**2
                      CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
      1 .data         0000000c  00000000  00000000  00000090  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      2 .bss          0000000c  00000000  00000000  0000009c  2**2
                      ALLOC
      3 .rodata       00000004  00000000  00000000  0000009c  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      4 .comment      0000002e  00000000  00000000  000000a0  2**0
                      CONTENTS, READONLY
      5 .note.GNU-stack 00000000  00000000  00000000  000000ce  2**0
                      CONTENTS, READONLY

     

    注解:

     

      VMA即 Virtual Memory Address,即虚拟地址

      LMA即 Load Memory Address即加载地址

     

      正常情况下这两个地址一样,有些嵌入式系统这两个值不同。

     

     

      .text是代码段,其大小为5b,在文件中的偏移是34

     

      .data是数据段,大小是0c,在文件中的偏移是90

     

      .bss是BSS段,大小是0c,文件中的偏移是9c

     

      .bss是存储未初始化的全局变量和静态局部变量。其实仅仅是给这些变量预留空间。此处便是:
    static int global_static_var;static int global_static_var0=0;static int static_var2,共12字节。由于static int global_static_var0=0相当于没有初始化(没有初始化的值就是0),因而被编译器优化到了.bss,因为这样不占用磁盘空间。

     

    int global_uninit_var则没有被放到任何段,而是作为未定义的COMMON符号。这个和不同语言、编译器实现有关,有的编译器放到.bss 段,有的仅仅是预留一个COMMON符号,在链接的时候再在.bss段分配预留空间。编译单元内部可见的静态变量,比如在上述中加上static的 static int global_static_var则确实被放到了.bss,是因为这个仅仅是编译单元内部可见。

     

     

      .rodata是只读数据段,大小是4,文件中偏移是9c。单独设立.rodata段,不仅仅直接在语义上支持了c++的const关键字,而且操作系统 加载的时候,可将其映射会只读,防止对只读数据的修改。在嵌入式平台下,有些时候使用ROM进行存储。有的编译器把字符串常量防到.data,而不是放 到.rodata,例如MSVC编译器就在编译C++的时候把字符串常量放置到.data段。

     

      .comment是注释信息段,大小是2e,文件中的偏移是a0

     

      .note.GNU-stack是GNU栈提示段,大小事0,文件中的偏移是ce

     

    其中的属性 CONTENTS表示在文件中存在内容,没有该属性则表示在文件中不存在内容

     

    这样,其结构如图:

     

     

     

     

    也可使用size命令查看各个段的大小、地址信息,-format表示使用的输出格式:

     

    size --format=SysV SimpleSection.o
    SimpleSection.o  :
    section           size   addr
    .text               91      0
    .data               12      0
    .bss                12      0
    .rodata                  0
    .comment            46      0
    .note.GNU-stack          0
    Total              165

     

    三,查看段的内容

     

     

       使用 objdump的-s查看任何需要的段的内容,如果不指定段,则显示所有的非空段的内容,-d表示将代码段反汇编(disassemble)。

     

     

     Contents of section .text:
     0000 5589e583 ec088b45 08894424 04c70424  U......E..D$...$
     0010 00000000 e8fcffff ffc9c38d 4c240483  ............L$..
     0020 e4f0ff71 fc5589e5 5183ec14 c745f401  ...q.U..Q....E..
     0030 0000008b 15080000 00a10400 00008d04  ................
     0040 020345f4 0345f889 0424e8fc ffffff8b  ..E..E...$......
     0050 45f483c4 14595d8d 61fcc3             E....Y].a..    

    Contents of section .data:
     0000 54000000 01000000 55000000           T.......U...   
    Contents of section .rodata:
     0000 25640a00                             %d..           
    Contents of section .comment:
     0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
     0010 32203230 30383037 30342028 52656420  2 20080704 (Red
     0020 48617420 342e312e 322d3434 2900      Hat 4.1.2-44). 
    Disassembly of section .text:

    00000000 <func1>:
       0:   55                      push   �p
       1:   89 e5                   mov    %esp,�p
       3:   83 ec 08                sub    $0x8,%esp
       6:   8b 45 08                mov    0x8(�p),�x
       9:   89 44 24 04             mov    �x,0x4(%esp)
       d:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
      14:   e8 fc ff ff ff          call   15 <func1+0x15>
      19:   c9                      leave 
      1a:   c3                      ret   

    0000001b <main>:
      1b:   8d 4c 24 04             lea    0x4(%esp),�x
      1f:   83 e4 f0                and    $0xfffffff0,%esp
      22:   ff 71 fc                pushl  0xfffffffc(�x)
      25:   55                      push   �p
      26:   89 e5                   mov    %esp,�p
      28:   51                      push   �x
      29:   83 ec 14                sub    $0x14,%esp
      2c:   c7 45 f4 01 00 00 00    movl   $0x1,0xfffffff4(�p)
      33:   8b 15 08 00 00 00       mov    0x8,�x
      39:   a1 04 00 00 00          mov    0x4,�x
      3e:   8d 04 02                lea    (�x,�x,1),�x
      41:   03 45 f4                add    0xfffffff4(�p),�x
      44:   03 45 f8                add    0xfffffff8(�p),�x
      47:   89 04 24                mov    �x,(%esp)
      4a:   e8 fc ff ff ff          call   4b <main+0x30>
      4f:   8b 45 f4                mov    0xfffffff4(�p),�x
      52:   83 c4 14                add    $0x14,%esp
      55:   59                      pop    �x
      56:   5d                      pop    �p
      57:   8d 61 fc                lea    0xfffffffc(�x),%esp
      5a:   c3                      ret

     

     

     a)摘出.text段查看。

     

     

     Contents of section .text:
     0000 5589e583 ec088b45 08894424 04c70424  U......E..D$...$
     0010 00000000 e8fcffff ffc9c38d 4c240483  ............L$..
     0020 e4f0ff71 fc5589e5 5183ec14 c745f401  ...q.U..Q....E..
     0030 0000008b 15080000 00a10400 00008d04  ................
     0040 020345f4 0345f889 0424e8fc ffffff8b  ..E..E...$......
     0050 45f483c4 14595d8d 61fcc3             E....Y].a..

     

     

     该段总共0x5b(十进制为91)个字节。

     

     

     00000000 <func1>:
       0:   55                      push   �p
       1:   89 e5                   mov    %esp,�p
       3:   83 ec 08                sub    $0x8,%esp
       6:   8b 45 08                mov    0x8(�p),�x
       9:   89 44 24 04             mov    �x,0x4(%esp)
       d:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
      14:   e8 fc ff ff ff          call   15 <func1+0x15>
      19:   c9                      leave 
      1a:   c3                      ret   

    0000001b <main>:
      1b:   8d 4c 24 04             lea    0x4(%esp),�x
      1f:   83 e4 f0                and    $0xfffffff0,%esp
      22:   ff 71 fc                pushl  0xfffffffc(�x)
      25:   55                      push   �p
      26:   89 e5                   mov    %esp,�p
      28:   51                      push   �x
      29:   83 ec 14                sub    $0x14,%esp
      2c:   c7 45 f4 01 00 00 00    movl   $0x1,0xfffffff4(�p)
      33:   8b 15 08 00 00 00       mov    0x8,�x
      39:   a1 04 00 00 00          mov    0x4,�x
      3e:   8d 04 02                lea    (�x,�x,1),�x
      41:   03 45 f4                add    0xfffffff4(�p),�x
      44:   03 45 f8                add    0xfffffff8(�p),�x
      47:   89 04 24                mov    �x,(%esp)
      4a:   e8 fc ff ff ff          call   4b <main+0x30>
      4f:   8b 45 f4                mov    0xfffffff4(�p),�x
      52:   83 c4 14                add    $0x14,%esp
      55:   59                      pop    �x
      56:   5d                      pop    �p
      57:   8d 61 fc                lea    0xfffffffc(�x),%esp
      5a:   c3                      ret

     

       对照反汇编结果,函数func1中的第一个指令push   �p的十六进制即是第一个字节0x55,而最后一个字节c3,恰恰是main函数中的ret。

     

     

      b)摘出.data段,该段存储的是已经初始化的全局变量和静态局部变量

     

      Contents of section .data:
      0000 54000000 01000000 55000000           T.......U...      

    (其实是分别是int global_init_var=84;static int global_static_var1=1;static int static_var=85;采用的字节序是LITTLE-ENDIAN,所以对于84,54在前,000000在后。)static int global_static_var0=0被优化到了.bss段预留空间,请参见“二,查看二进制文件段的信息”中对.bss段的描述。

     

      c)摘出.rodata段,该段存储的是只读数据,一般是const修饰的变量和字符串常量

     

      Contents of section .rodata:
      0000 25640a00                             %d..              这个便是printf中的"%d/n"然后加上/0组成字符串。

     

      d)摘出.comment段

     

     Contents of section .comment:
     0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
     0010 32203230 30383037 30342028 52656420  2 20080704 (Red
     0020 48617420 342e312e 322d3434 2900      Hat 4.1.2-44).

     

    四,其他可能存在的段

     

       其他可能存在的段有:

     

       .rodata1,与.rodata类似

       .comment 编译器版本信息

       .debug 调试信息

       .dynamic 动态链接信息

       .hash 符号哈希表

       .line 调试时的行号表,即源代码和编译后指令的对照表

       .note   额外编译器信息

       .strtab  String Table,字符串表

       .symtab Symbol Table,段名表

       .shstrtab Section String Table 段名表

       .plt .got 动态链接的跳转表和全局入口表

       .init .fini 程序初始化和终结代码段,与c++全局构造和析构有关。

     

       这些以.开头,是系统保留的,自己也可以定义,不能使用.开头,还有一些因为历史原因留下的段名,已经被废弃,如:.sbss、liblist、conflict等。另外,一个ELF中可以包含多个相同段名的段。

     

       自定义段:

     

       gcc提供了拓展机制。

     

       __attribute__((section("FOO") )) int global=32;

       __attribute__((section("BAR"))) void foo(){

     

      }

     

      这样,就将全局量或者函数放置到指定的自定义段中了。

     

       我们将一个二进制文件,比如图片、MP3放入一个目标文件的段,可以使用objcopy。比如image.jpg,大小0x82100字节。

       objcopy -I binary -o elf32-i386 -B i386 image.jpg image.o。结果请使用objdump -ht查看,其里边的符号代表图片的起始、终止地址和大小可以在程序中声明、使用。

     

     

    五,ELF文件的头

     

     

       ELF文件中主要顺序包含了ELF Header、.text、.data、.bss、其他段、Section header table、String Tables、Symbol Tables等。

     

       ELF文件头中描述了整个文件的基本属性,比如版本、目标机器类型、程序入口地址。

     

       使用readelf查看ELF文件头,如下:

     

      readelf -h SimpleSection.o

     

      ELF Header:
      Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              REL (Relocatable file)
      Machine:                           Intel 80386
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          0 (bytes into file)
      Start of section headers:          288 (bytes into file)
      Flags:                             0x0
      Size of this header:               52 (bytes)
      Size of program headers:           0 (bytes)
      Number of program headers:         0
      Size of section headers:           40 (bytes)
      Number of section headers:         11
      Section header string table index: 8

     

     其内容有 ELF魔数、文件机器字长长度、字节序、版本、运行平台、ABI版本、文件类型、硬件机器类型、硬件机器版本、入口地址、程序头入口和长度、段表位置和长 度、段的数量等。ELF文件结构的头的结构定义在"/usr/include/elf.h",其有Elf32_Ehdr和Elf64_Ehdr两个版本。 两个版本的成员大小不一样。readelf结果与该结构体定义的字段类似,但有所不同。

     

    /usr/include/elf.h中的定义:

     

     

    typedef struct
    {

      unsigned char e_ident[EI_NIDENT];      
      Elf32_Half    e_type;                
      Elf32_Half    e_machine;             
      Elf32_Word    e_version;             
      Elf32_Addr    e_entry;               
      Elf32_Off     e_phoff;               
      Elf32_Off     e_shoff;               
      Elf32_Word    e_flags;               
      Elf32_Half    e_ehsize;              
      Elf32_Half    e_phentsize;           
      Elf32_Half    e_phnum;               
      Elf32_Half    e_shentsize;           
      Elf32_Half    e_shnum;               
      Elf32_Half    e_shstrndx;            
    } Elf32_Ehdr;

    e_ident对应的是:

     

      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0

     

      上述五个即Magic(魔数)。此处7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00的前4个字节是固定的标识码,如果这四个字节不正确,则操作系统会拒绝加载。接下来的01表示32位,下一个是字节序,之后是ELF文件主版本一般是 1,后边一般是0,没意义,有的平台用以进行拓展。

     

    e_type对应的是:

        Type:                              REL (Relocatable file)

     

      ET_REL,可重定位,一般为目标文件.o;

      ET_EXEC,可执行;

      ET_DYN,共享目标文件.so。

     

    e_machine对应的是:

       Machine:                           Intel 80386

     

    该ELF文件格式的支持的平台。这里就是说Intel 80386平台支持该ELF文件格式。

     

    e_version对应的是:

         Version:                           0x1

     

    e_entry对应的是:

          Entry point address:               0x0

    操作系统在加载完可执行文件后,从该入口地址开始执行。可定位文件(目标文件是其一种)通常没有入口地址,所以为0。

     

    e_phoff对应的是:

         Start of program headers:          0 (bytes into file)

     

    e_shoff对应的是:

         Start of section headers:          288 (bytes into file)

    段表在文件中的偏移。此处即从289开始是段表。

     

    e_flags对应的是:

        Flags:                             0x0

    表示ELF文件平台相关属性。

     

    e_ehsize对应的是:

        Size of this header:               52 (bytes)

    ELF文件头本身大小。

     

    e_phentsize对应的是:

        Size of program headers:           0 (bytes)

     

    e_phnum对应的是:

       Number of program headers:         0

     

    e_shentsize对应的是:

       Size of section headers:           40 (bytes)

       段表描述符大小。一般为40。

     

    e_shnum对应的是:

       Number of section headers:         11

       段表描述符数量,此处为11。

     

    e_shstrndx对应的是:

       Section header string table index: 8

        段表字符串表所在段,在段表中的下标。

     

    六,ELF文件的段表

     

       使用readelf -S 查看所有的段表结构:

     

       readelf -S SimpleSection.o

     

       There are 11 section headers, starting at offset 0x120:

    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00       0
      [ 1] .text             PROGBITS        00000000 000034 00005b 00  AX   4
      [ 2] .rel.text         REL             00000000 000498 000028 08       4
      [ 3] .data             PROGBITS        00000000 000090 00000c 00  WA   4
      [ 4] .bss              NOBITS          00000000 00009c 00000c 00  WA   4
      [ 5] .rodata           PROGBITS        00000000 00009c 000004 00    1
      [ 6] .comment          PROGBITS        00000000 0000a0 00002e 00       1
      [ 7] .note.GNU-stack   PROGBITS        00000000 0000ce 000000 00       1
      [ 8] .shstrtab         STRTAB          00000000 0000ce 000051 00       1
      [ 9] .symtab           SYMTAB          00000000 0002d8 000120 10     10  13  4
      [10] .strtab           STRTAB          00000000 0003f8 00009e 00       1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings)
      I (info), L (link order), G (group), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)

     

     

    实际上文件中包含11个段表描述符。

     

       段描述符的数据结构为:

     

      

    typedef struct
    {
      Elf32_Word    sh_name;               
      Elf32_Word    sh_type;               
      Elf32_Word    sh_flags;              
      Elf32_Addr    sh_addr;               
      Elf32_Off     sh_offset;             
      Elf32_Word    sh_size;               
      Elf32_Word    sh_link;               
      Elf32_Word    sh_info;               

      Elf32_Word    sh_addralign;          


      Elf32_Word    sh_entsize;            


    } Elf32_Shdr;

     

    第一个段描述符是无效的,所以SimpleSection.o共有10个有效的段描述符。

     

    段类型:

    SHT_NULL 无效段

    SHT_PROGBITS 程序段、代码段、数据段

    SHT_SYMTAB 该段内容为符号表

    SHT_STRTAB 该段为字符串表

    SHT_RELA 该段为重定位表,包含重定位信息。

    SHT_HASH该段为符号表的hash表

    SHT_DYNAMIC该段为动态链接信息

    SHT_NOTE该段是提示性信息

    SHT_NOBITS该段在文件中没有内容,如.bss

    SHT_REL该段包含重定位信息

    SHT_SHLIB  保留

    SHT_DNYSYM动态链接符号表

     

    段标识位:

     

    SHF_WRITE 该段在进程中可写

    SHF_ALLOC  该段在进程中要分配空间

    SHF_EXECINSTR 该段在进程中可执行

     

    段的链接信息:

    只有类型是链接相关的时候,sh_link和sh_info才有意义,如下表对应意义:

     

    类型                            sh_link                                                  sh_info

    SHT_DYNAMIC           该段所使用的字符串表在段表中的下标          0

    SHT_HASH                该段所使用的符号表在段表中的下标            0

    SHT_REL                     该段使用的相应符号表在段表的下标          该重定位表所作用的段在段表中的下表
    SHT_RELA                    该段使用的相应符号表在段表的下标          该重定位表所作用的段在段表中的下表
    SHT_SYMTAB                操作系统相关                                        操作系统相关
    SHT_DYNSYM               操作系统相关                                         操作系统相关
    other                            SHN_UNDEF                                        0

     

    七,重定位表

     

        需要重定位的代码段或数据段,即对绝对地址引用的位置需要重定位。比如这里的printf,就是绝对地址的引用。所以需要对.text段进行重定位,所以使用了段.rel.text。

     

     

    八,字符串表

     

         段名、变量名等都是字符串。引用字符串仅需给出该字符串在字符串的字符表格中的开始的下标。字符串表用以存储普通字符串(.strtab),而段名等段表 中用到的字符串存储于段表字符串(Section Header String Table,.shstab)。

     

        由于文件头信息中的e_shstrndx是段表字符串表在段表中的下表,所以使用ELF文件头,可以得到段表和段表字符串表的位置,进而解析整个ELF文件。

     

    九,符号表

     

       链接是基于符号表进行的。符号表中记录了目标文件用到的所有符号,包括:

     

       定义在本目标文件中的全局符号。例如main、func1。

       在本目标文件中引用的全局符号,即外部符号或符号引用,例如printf。

       段名,编译器产生,值就是段的起始位置。例如.text,.data。

       局部符号,如static_var和static_var2,编译单元内部可见。调试器使用这些符号以分析程序和形成转储文件。对于链接没有用处。

       行号信息,目标文件指令和源代码行的对应关系,也是可选的。

     

       对于链接过程,第一类和第二类是重要的。

     

      使用readelf、objdump、nm查看符号:

     

       例如使用nm,

     

       [root@swtich1 mylinuxc]# nm SimpleSection.o
    00000000 T func1
    00000000 D global_init_var
    00000008 b global_static_var
    00000000 b global_static_var0
    00000004 d global_static_var1
    00000004 C global_uninit_var
    0000001b T main
             U printf
    00000008 d static_var.1292
    00000004 b static_var2.1293

     

     ELF符号表的项的数据结构如下:

     

     

     



    typedef struct
    {
      Elf32_Word    st_name;               
      Elf32_Addr    st_value;              
      Elf32_Word    st_size;               
      unsigned char st_info;               
      unsigned char st_other;              
      Elf32_Section st_shndx;              
    } Elf32_Sym;

     

    符号绑定信息:

     

     

       STB_LOCAL            局部符号,外部不可见

       STB_GLOBAL          全局符号,外部可见
       STB_WEAK             弱符号

     

    符号类型:

     

      STT_NOTYPE          未知

      STT_OBJECT           数据对象

      STT_FUNC              函数、可执行代码

     STT_SECTION         段的符号,同时必须是STB_LOCAL的

     STT_FILE                文件名。一般是源文件名。一定是STB_LOCAL的,并且st_shndx==SHN_ABS。

     

     

    符号所在段:

     

    SHN_ABS    该符号包含一个绝对的值,比如文件名

    SHN_COMMON 该符号是一个COMMON块类型的符号。比如未初始化的全局符号,SimpleSection.o中的global_uninit_var。

    SHN_UNDEF      符号未定义,在目标文件中引用到,但是定义在其他目标文件。

     

    符号的值:

     

      符号不是COMMON块,且被定义在目标文件: 则值是符号在段中的偏移,段由st_shndx指定。

      符号是COMMON块,在目标文件,则st_value表示符号的对其属性。

                                  可执行文件中,st_value是符号的虚拟地址。

     

     

    使用readelf -s SimpleSection.o查看:

     

    readelf -s SimpleSection.o

    Symbol table '.symtab' contains 18 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 00000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
         2: 00000000     0 SECTION LOCAL  DEFAULT    1
         3: 00000000     0 SECTION LOCAL  DEFAULT    3
         4: 00000000     0 SECTION LOCAL  DEFAULT    4
         5: 00000004     4 OBJECT  LOCAL  DEFAULT    3 global_static_var1
         6: 00000000     4 OBJECT  LOCAL  DEFAULT    4 global_static_var0
         7: 00000000     0 SECTION LOCAL  DEFAULT    5
         8: 00000004     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1293
         9: 00000008     4 OBJECT  LOCAL  DEFAULT    3 static_var.1292
        10: 00000008     4 OBJECT  LOCAL  DEFAULT    4 global_static_var
        11: 00000000     0 SECTION LOCAL  DEFAULT    7
        12: 00000000     0 SECTION LOCAL  DEFAULT    6
        13: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
        14: 00000000    27 FUNC    GLOBAL DEFAULT    1 func1
        15: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
        16: 0000001b    64 FUNC    GLOBAL DEFAULT    1 main
        17: 00000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var

     

    对于类型为STT_SECTION的符号,它们的符号名没有显示,因为符号名就是段名,可以通过Ndx这个下表去看段的名字以得知。使用objdump -t可以清楚看到段名符号。

     

     

    特殊符号:

     

     

    使用ld链接时,它会为我们定义很多特殊符号,虽然这些符号没有在你的代码中定义,但是你可以直接声明、引用它们。这些符号在ld链接脚本中定义。ld会在链接形成可执行文件的时候将它们解析成正确值。例如:

     

      __executable_start 程序开始地址,不是入口地址。

      __etext/_etext/etext 代码段结束地址

      _edata/edata 数据段结束地址

      _end/end 程序结束地址

     

     以上都是进程虚拟地址。

     

     

     

    符号修饰与函数签名:

     

      现在,对于c语言,gcc Linux下默认不会加前缀,可以通过编译参数进行控制。

      Windows下的Visual C++则对c语言源代码所有全局量和函数编译后在符号名前加上前缀"_"。

     

     

      C++符号修饰:

        C++拥有类、继承、虚函数、重载、名称空间等特性。编译器、链接器使用符号修饰来区分函数、变量。

     

        gcc编译器对C++函数、全局变量、静态变量:

     

          1)所有符号以“_Z”开头

          2)对于在名字空间或者类内的名字,首先是加上N,然后应该跟名字空间名字,然后是类的名字,之后是该符号名字、最后加上E。

              但是这些名字前都会加上名字的长度。

          3)对于函数,之后是参数的首字母

     

         例如:N::C::func(int)----------> _ZN1N1C4funcEi。

     

                 foo空间中的bar------>_ZN3foo3braE。可见,变量类型信息并没有在符号修饰中。

     

           名字修饰也用以防止静态变量的冲突:例如main中的foo,和func()中的foo分别为_ZZ4mainE3foo和_ZZ4funcvE3foo。

     

     

       使用“c++filt”工具可以用来解析被修饰过的名称。

     

    例如:

    $ c++filt _ZN1N1C4funcEi
    N::C::func(int)

     

      不同编译器产生的是不同的。在Visual C++下,使用UnDecoreateSymbolName() API可以对修饰后的名称转化成函数。

     

      这是导致不同编译器产生的目标文件不能互相编译的主要原因之一。

     

     extern “C”{} 可以使得其中的代码当作C代码处理,这样就没有了C++的名字修饰。

     

     很多时候,在C++代码中使用C的头文件,这样,编译器会将包含的头文件中的函数进行修饰,链接器无法链接指定的c库。因此,对于使用C库中的函 数的c++代码,应该用extern对函数进行修饰。C++编译器会默认定义C++的宏__cplusplus,使用判断该宏是否定义的方法可以知道当前 编译的是C++的代码还是C语言的代码,如果判断出是C++代码,则对于使用C函数的代码,应该使用extern处理。

     

     

    十,弱符号和强符号

     

        符号重复定义的错误在写程序中经常遇到。多个目标文件有相同名字的全局符号的定义,就会出现上述错误。

        这种符号定义成为强符号。

        对于C/C++语言,编译器默认初始化的全局变量为强符号,未初始化的全局变量为弱符号。可以使用GCC的__attribute__((weak))指 定任何强符号为弱符号。强弱符号都是针对定义,而不是引用说的。因此 extern int ext,这个符号不是强符号,也不是弱符号,因为它是外部符号的引用。链接器对于多次定义的全局符号,针对强弱如此处理:

     

        1)不允许强符号多次定义

        2)一个符号在一个目标文件中是强符号,其他文件中都是弱符号,则选择强符号

        3)所有目标文件中是弱符号,则选择其中占用空间大的使用,当然这样多种不同类型弱符号容易导致难以发现的程序错误。

     

    十一,弱引用和强引用

     

        如果对外部符号的引用,在链接时,找不到定义则会报错,称为强引用。否则如果符号没有定义,链接器不报错,称为弱引用。链接器只是对于弱引用的符号,在没 有决议情况下不认为是个错误。这种引用链接器会默认其为0或者某个特殊值。弱符号与链接器的COMMON块概念紧密相连。

       使用GCC中的__attribute__((weakref))拓展关键字来声明一个外部函数的引用为弱引用。

     

      例如 __attribute__((weakref))  void foo();

      int main()

    {

      foo();

    }

     

    编译链接不报错,但是运行的时候,发生错误。因为foo的地址为0.发生非法地址访问。改进方法为:

     

     

    __attribute__((weakref))  void foo();

      int main()

    {

      if (foo) foo();

    }

     

    这样编译链接执行都不会有错了。

     

     

    强弱引用对应库很重要,自定义版本的库函数可以通过强符号,覆盖掉通用库中的弱符号。或者程序对于拓展功能使用弱引用,这样,拓展模块去掉,程序可以正常链接。

     

       Linux中,一个程序可以判断支持的多线程还是单线程模式,即链接的是单线程还是多线程Glibc(编译时是否有-Ipthread选项),从而执行单线程版本或者多线程版本。

       例如:我们可以定义一个pthread_create函数的弱引用,因为如果链接的是多线程版本,则pthread_create值不会是0了,而单线程 则导致该弱符号依然是未决议的默认值:0。这样可以在函数运行时判断是否链接到pthread库来决定执行单线程版本还是多线程版本。

     

     

    十一,调试信息的去除

     

      使用strip可以去除调试信息:

     

      strip SimpleSection.o

     

     

    十二,ELF文件概貌

     

     
     参见本文中的图片

  • 相关阅读:
    记一次Redis+Getshell经验分享
    冰蝎动态二进制加密WebShell基于流量侧检测方案
    ubuntu16下安装mongodb 3.6
    centos安装sass环境必看
    CLR 调试体系结构
    CLR 调试概述
    CLR Exception---E0434352
    关于System.MissingMethodException异常
    关于异常System.ArgumentException
    从.NET/CLR返回的hresult:0x8013XXXX的解释
  • 原文地址:https://www.cnblogs.com/androidme/p/3008615.html
Copyright © 2020-2023  润新知