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文件概貌

 

 
  參見本文中的圖片


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM