静态链接之目标文件的内容

编译器编译源代码后生成的文件叫做目标文件,目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有调整。

一、目标文件的格式

现在PC上流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),
它们都是COFF(Common file format)格式的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux下的。o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。动态链接库(DLL,Dynamic Linking Library)(Windows的.dll和Linux的.so)及静态链接库(Static Linking Library)文件都按照可执行文件格式存储。它们在Windows 下都按照PE-COFF格式存储,Linux下按照ELF格式存储。ELF文件标准里面把系统中采用ELF格式的文件归为如下4类:

ELF 文件类型说明实例
可重定位文件(Relocatable File这类文件包含了代码和数据,可以被用来连接成可执行文件或者共享目标问津,静态链接库也可以归这一类Linux的.o Windows的.obj
可执行文件(Executable File)这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,它们一般都没有扩展名比如/bin/bash 文件 Windows的exe
共享目标文件(Shared Object File)这种文件包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行Linux 的.so,如、llib/glibc-2.5.so Windows的DLL
核心转储文件(Core Dump FIle)当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其它信息转储到核心转储文件Linux 下的core dump

我们可以在Linux下使用file命令来查看相应的文件格式。

二、目标文件是什么样的

一般目标文件按照指令、数据等不同属性以“节”(Section)的形式存储,有时候也叫“段”(segment)。 程序源代码编译后的机器指令经常被放在代码段(Code Section)里,代码段常见的名字有“.code”或“.text”;全局变量和局部静态变量数据经常放在数据段(Data Section),数据段的一般名字都叫“.data”。ELF文件的开头是一个“文件头”,它描述了整个文件的文件属性,包括文件是否可以执行、是静态链接还是动态链接的入口地址(如果是可执行文件)、目标 硬件、目标操作系统等信息,文件头还包括一个段表(Section Table ),段表是描述各个段的数组。段表描述了各个段在文件中的偏移以及段的属性等信息,从段表中能获取段的所有信息。文件头后面就是各个段的内容。

三、挖掘SimpleSection.o

我们将SimpleSection.c编译出来的目标文件 作为分析对象:


int printf( const char * format ,...);

int global_init_var = 84;
int global_uninit_var;

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来编译这个文件(参数 -c 表示只编译不链接)

$ gcc -c SimpleSection.c

我们使用binutils的工具objdump来查看object 内部的结构

$ objdump -h SimpleSection.o

输出:

SimpleSection.o:     文件格式 elf64-x86-64

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000054  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000094  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  0000009c  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  0000009c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002c  0000000000000000  0000000000000000  000000a0  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000cc  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000058  0000000000000000  0000000000000000  000000d0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

参数“-h”就是把ELF文件的各个段的信息打印出来。“-x”是详细打印各个参数的值。比较重要的是段的长度(Sized)偏移(File off)

1.代码段

objdump 参数“-s”可将所有段的内容以十六进制的方式打印出来

$ objdump -s SimpleSection.o
SimpleSection.o:     文件格式 elf64-x86-64

Contents of section .text:
 0000 554889e5 4883ec10 897dfc8b 45fc89c6  UH..H....}..E...
 0010 bf000000 00b80000 0000e800 000000c9  ................
 0020 c3554889 e54883ec 10c745f8 01000000  .UH..H....E.....
 0030 8b150000 00008b05 00000000 01c28b45  ...............E
 0040 f801c28b 45fc01d0 89c7e800 0000008b  ....E...........
 0050 45f8c9c3                             E...            
Contents of section .data:
 0000 54000000 55000000                    T...U...        
Contents of section .rodata:
 0000 25640a00                             %d..            
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520342e  .GCC: (Ubuntu 4.
 0010 382e342d 32756275 6e747531 7e31342e  8.4-2ubuntu1~14.
 0020 30342e31 2920342e 382e3400           04.1) 4.8.4.    
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 21000000 00410e10 8602430d  ....!....A....C.
 0030 065c0c07 08000000 1c000000 3c000000  .\..........<...
 0040 00000000 33000000 00410e10 8602430d  ....3....A....C.
 0050 066e0c07 08000000                    .n......   

参数“-d” 将包含指令的段反汇编

$ objdump -d SimpleSection.o

输出:

SimpleSection.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <func1>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   89 7d fc                mov    %edi,-0x4(%rbp)
   b:   8b 45 fc                mov    -0x4(%rbp),%eax
   e:   89 c6                   mov    %eax,%esi
  10:   bf 00 00 00 00          mov    $0x0,%edi
  15:   b8 00 00 00 00          mov    $0x0,%eax
  1a:   e8 00 00 00 00          callq  1f <func1+0x1f>
  1f:   c9                      leaveq 
  20:   c3                      retq   

0000000000000021 <main>:
  21:   55                      push   %rbp
  22:   48 89 e5                mov    %rsp,%rbp
  25:   48 83 ec 10             sub    $0x10,%rsp
  29:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  30:   8b 15 00 00 00 00       mov    0x0(%rip),%edx        # 36 <main+0x15>
  36:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 3c <main+0x1b>
  3c:   01 c2                   add    %eax,%edx
  3e:   8b 45 f8                mov    -0x8(%rbp),%eax
  41:   01 c2                   add    %eax,%edx
  43:   8b 45 fc                mov    -0x4(%rbp),%eax
  46:   01 d0                   add    %edx,%eax
  48:   89 c7                   mov    %eax,%edi
  4a:   e8 00 00 00 00          callq  4f <main+0x2e>
  4f:   8b 45 f8                mov    -0x8(%rbp),%eax
  52:   c9                      leaveq 
  53:   c3                      retq  

2.数据段和只读数据段

.data段存放已经 初始化的全局变量和局部静态变量。如global_init_varabal与static_var。对应“.data”段的8个字节。
这里linux系统采用的是小端(Little-endian)字节序。

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

“.rodata”段存放的是只读数据。如“const”修饰的变量和字符串常量(如“%d\n”)。

有些编译器会将字符串常量存放到“.data”段。

3. BSS

.bss段存放的是未初始化的全局变量局部静态变量。global_uninit_var和static_var2就是被存放在.bss段。通过符号表(Symbol Table)我们知道,global_uninit_var确没有被存放在任何段只是一个未定义的“COMMON符号”。这其实是跟不同的语言与不同的编译器有关。有的编译器会将全局未初始化的变量存放在目标文件.bss段,有些则不存放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss段分配空间。编译单元内部未初始化的变量确实是放在.bss段。初始化为0的变量也会被优化放在.bss段。

4.其它段

常用段名说明
.rodata1Read only Datra ,这种段里存放的是只读数据,比如字符串常量、全局const变量。跟“.rodata”一样
.comment存放的是编译器版本信息,比如字符串 :”GCC:(GNU)4.2.0”
.debug调试信息
.dynamic动态调试信息
.hash符号哈希表
.line调试时的行号表,即源代码行号与编译后指令的对应表
.note额外的编译器信息。比如程序的公司名、发布版本号等
.strtabString Table.用于存储ELF文件中用到的各种字符串
.symtabSymbol Table.符号表
.plt .got动态链接的跳转表和全局入口表
.init .fini程序初始化与终结代码段。

由“.”作为前缀的段名是系统保留的段。你也可以用非系统保留的段作为段名,但应用程序自定义的段名不能使用“.”作为前缀,以防止跟系统使用的段名冲突。ELF文件允许段名相同。
自定义段
我们在全局变量或函数之前加上_attribute_((section("name")))属性就可以把相应的变量或函数放到以“name”作为段名的段中。

_attribute_((section("FOO"))) int global = 42;
_attribute_((section("BAR"))) void foo(){}

四、 ELF文件结构描述

ELF文件总体结构:
这里写图片描述

1.文件头

查看ELF文件头:

readelf -h SimpleSection.o

输出:

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (可重定位文件)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          400 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         64 (字节)
  节头数量:         13
  字符串表索引节头: 10

ELF文件头结构及相关常数被定义在“/user/include/elf.h”。ELF文件分为32位版本和64位版本,ELF文件内容一样,有些成员变量不一样。

ELF标准类型

/* Standard ELF types.  */

#include <stdint.h>

/* Type for a 16-bit quantity.  */
typedef uint16_t Elf32_Half;
typedef uint16_t Elf64_Half;

/* Types for signed and unsigned 32-bit quantities.  */
typedef uint32_t Elf32_Word;
typedef int32_t  Elf32_Sword;
typedef uint32_t Elf64_Word;
typedef int32_t  Elf64_Sword;

/* Types for signed and unsigned 64-bit quantities.  */
typedef uint64_t Elf32_Xword;
typedef int64_t  Elf32_Sxword;
typedef uint64_t Elf64_Xword;
typedef int64_t  Elf64_Sxword;

/* Type of addresses.  */
typedef uint32_t Elf32_Addr;
typedef uint64_t Elf64_Addr;

/* Type of file offsets.  */
typedef uint32_t Elf32_Off;
typedef uint64_t Elf64_Off;

/* Type for section indices, which are 16-bit quantities.  */
typedef uint16_t Elf32_Section;
typedef uint16_t Elf64_Section;

/* Type for version symbol information.  */
typedef Elf32_Half Elf32_Versym;
typedef Elf64_Half Elf64_Versym;

ELF文件头结构类型(32位和64位)

/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
  unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  Elf32_Half    e_type;         /* Object file type */
  Elf32_Half    e_machine;      /* Architecture */
  Elf32_Word    e_version;      /* Object file version */
  Elf32_Addr    e_entry;        /* Entry point virtual address */
  Elf32_Off e_phoff;        /* Program header table file offset */
  Elf32_Off e_shoff;        /* Section header table file offset */
  Elf32_Word    e_flags;        /* Processor-specific flags */
  Elf32_Half    e_ehsize;       /* ELF header size in bytes */
  Elf32_Half    e_phentsize;        /* Program header table entry size */
  Elf32_Half    e_phnum;        /* Program header table entry count */
  Elf32_Half    e_shentsize;        /* Section header table entry size */
  Elf32_Half    e_shnum;        /* Section header table entry count */
  Elf32_Half    e_shstrndx;     /* Section header string table index */
} Elf32_Ehdr;

typedef struct
{
  unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  Elf64_Half    e_type;         /* Object file type */
  Elf64_Half    e_machine;      /* Architecture */
  Elf64_Word    e_version;      /* Object file version */
  Elf64_Addr    e_entry;        /* Entry point virtual address */
  Elf64_Off e_phoff;        /* Program header table file offset */
  Elf64_Off e_shoff;        /* Section header table file offset */
  Elf64_Word    e_flags;        /* Processor-specific flags */
  Elf64_Half    e_ehsize;       /* ELF header size in bytes */
  Elf64_Half    e_phentsize;        /* Program header table entry size */
  Elf64_Half    e_phnum;        /* Program header table entry count */
  Elf64_Half    e_shentsize;        /* Section header table entry size */
  Elf64_Half    e_shnum;        /* Section header table entry count */
  Elf64_Half    e_shstrndx;     /* Section header string table index */
} Elf64_Ehdr;

ELF文件头结构成员含义

成员readelf 输出结果与含义
e_ident[16]Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
e_typeType:REL (可重定位文件)ELF 文件类型
e_machineMachine: Advanced Micro Devices X86-64 ELF文件CPU平台属性
e_versionVersion: 0x1 ELF版本号
e_entry入口点地址: 0x0 ELF文件程序入口虚地址,可重定位文件入口地址为0
e_phoff程序头起点: 0 (bytes into file)
e_shoffStart of section headers:400 (bytes into file) 段表在文件中的偏移
e_flags标志: 0x0
e_ehsize本头的大小: 64 (字节) ELF文件头大小
e_phentsize程序头大小: 0 (字节)
e_phnumNumber of program headers: 0
e_shentsize节头大小: 64 (字节) 段表大小
e_shnum节头数量: 13 段表的数量
e_shstrndx字符串表索引节头: 10段表字符串表所在的段在段表中的下标

e_ident数组各个元素的含义及取值范围

/* Fields in the e_ident array.  The EI_* macros are indices into the
   array.  The macros under each EI_* macro are the values the byte
   may have.  */

#define EI_MAG0     0       /* File identification byte 0 index */
#define ELFMAG0     0x7f        /* Magic number byte 0 */

#define EI_MAG1     1       /* File identification byte 1 index */
#define ELFMAG1     'E'     /* Magic number byte 1 */

#define EI_MAG2     2       /* File identification byte 2 index */
#define ELFMAG2     'L'     /* Magic number byte 2 */

#define EI_MAG3     3       /* File identification byte 3 index */
#define ELFMAG3     'F'     /* Magic number byte 3 */

/* Conglomeration of the identification bytes, for easy testing as a word.  */
#define ELFMAG      "\177ELF"
#define SELFMAG     4

#define EI_CLASS    4       /* File class byte index */
#define ELFCLASSNONE    0       /* Invalid class */
#define ELFCLASS32  1       /* 32-bit objects */
#define ELFCLASS64  2       /* 64-bit objects */
#define ELFCLASSNUM 3

#define EI_DATA     5       /* Data encoding byte index */
#define ELFDATANONE 0       /* Invalid data encoding */
#define ELFDATA2LSB 1       /* 2's complement, little endian */
#define ELFDATA2MSB 2       /* 2's complement, big endian */
#define ELFDATANUM  3

#define EI_VERSION  6       /* File version byte index */
                    /* Value must be EV_CURRENT */

#define EI_OSABI    7       /* OS ABI identification */
#define ELFOSABI_NONE       0   /* UNIX System V ABI */
#define ELFOSABI_SYSV       0   /* Alias.  */
#define ELFOSABI_HPUX       1   /* HP-UX */
#define ELFOSABI_NETBSD     2   /* NetBSD.  */
#define ELFOSABI_GNU        3   /* Object uses GNU ELF extensions.  */
#define ELFOSABI_LINUX      ELFOSABI_GNU /* Compatibility alias.  */
#define ELFOSABI_SOLARIS    6   /* Sun Solaris.  */
#define ELFOSABI_AIX        7   /* IBM AIX.  */
#define ELFOSABI_IRIX       8   /* SGI Irix.  */
#define ELFOSABI_FREEBSD    9   /* FreeBSD.  */
#define ELFOSABI_TRU64      10  /* Compaq TRU64 UNIX.  */
#define ELFOSABI_MODESTO    11  /* Novell Modesto.  */
#define ELFOSABI_OPENBSD    12  /* OpenBSD.  */
#define ELFOSABI_ARM_AEABI  64  /* ARM EABI */
#define ELFOSABI_ARM        97  /* ARM */
#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */

#define EI_ABIVERSION   8       /* ABI version */

#define EI_PAD      9       /* Byte index of padding bytes */

e_type 文件类型取值范围

/* Legal values for e_type (object file type).  */

#define ET_NONE     0       /* No file type */
#define ET_REL      1       /* Relocatable file */
#define ET_EXEC     2       /* Executable file */
#define ET_DYN      3       /* Shared object file */
#define ET_CORE     4       /* Core file */
#define ET_NUM      5       /* Number of defined types */
#define ET_LOOS     0xfe00      /* OS-specific range start */
#define ET_HIOS     0xfeff      /* OS-specific range end */
#define ET_LOPROC   0xff00      /* Processor-specific range start */
#define ET_HIPROC   0xffff      /* Processor-specific range end */

e_machine 机器类型

/* Legal values for e_machine (architecture).  */

#define EM_NONE      0      /* No machine */
#define EM_M32       1      /* AT&T WE 32100 */
#define EM_SPARC     2      /* SUN SPARC */
#define EM_386       3      /* Intel 80386 */
#define EM_68K       4      /* Motorola m68k family */
#define EM_88K       5      /* Motorola m88k family */
#define EM_860       7      /* Intel 80860 */
#define EM_MIPS      8      /* MIPS R3000 big-endian */
#define EM_S370      9      /* IBM System/370 */
#define EM_MIPS_RS3_LE  10      /* MIPS R3000 little-endian */

#define EM_PARISC   15      /* HPPA */
#define EM_VPP500   17      /* Fujitsu VPP500 */
#define EM_SPARC32PLUS  18      /* Sun's "v8plus" */
#define EM_960      19      /* Intel 80960 */
#define EM_PPC      20      /* PowerPC */
#define EM_PPC64    21      /* PowerPC 64-bit */
#define EM_S390     22      /* IBM S390 */

#define EM_V800     36      /* NEC V800 series */
#define EM_FR20     37      /* Fujitsu FR20 */
#define EM_RH32     38      /* TRW RH-32 */
#define EM_RCE      39      /* Motorola RCE */
#define EM_ARM      40      /* ARM */
#define EM_FAKE_ALPHA   41      /* Digital Alpha */
#define EM_SH       42      /* Hitachi SH */
#define EM_SPARCV9  43      /* SPARC v9 64-bit */
#define EM_TRICORE  44      /* Siemens Tricore */
#define EM_ARC      45      /* Argonaut RISC Core */
#define EM_H8_300   46      /* Hitachi H8/300 */
#define EM_H8_300H  47      /* Hitachi H8/300H */
#define EM_H8S      48      /* Hitachi H8S */
#define EM_H8_500   49      /* Hitachi H8/500 */
#define EM_IA_64    50      /* Intel Merced */
#define EM_MIPS_X   51      /* Stanford MIPS-X */
#define EM_COLDFIRE 52      /* Motorola Coldfire */
#define EM_68HC12   53      /* Motorola M68HC12 */
#define EM_MMA      54      /* Fujitsu MMA Multimedia Accelerator*/
#define EM_PCP      55      /* Siemens PCP */
#define EM_NCPU     56      /* Sony nCPU embeeded RISC */
#define EM_NDR1     57      /* Denso NDR1 microprocessor */
#define EM_STARCORE 58      /* Motorola Start*Core processor */
#define EM_ME16     59      /* Toyota ME16 processor */
#define EM_ST100    60      /* STMicroelectronic ST100 processor */
#define EM_TINYJ    61      /* Advanced Logic Corp. Tinyj emb.fam*/
#define EM_X86_64   62      /* AMD x86-64 architecture */
#define EM_PDSP     63      /* Sony DSP Processor */

#define EM_FX66     66      /* Siemens FX66 microcontroller */
#define EM_ST9PLUS  67      /* STMicroelectronics ST9+ 8/16 mc */
#define EM_ST7      68      /* STmicroelectronics ST7 8 bit mc */
#define EM_68HC16   69      /* Motorola MC68HC16 microcontroller */
#define EM_68HC11   70      /* Motorola MC68HC11 microcontroller */
#define EM_68HC08   71      /* Motorola MC68HC08 microcontroller */
#define EM_68HC05   72      /* Motorola MC68HC05 microcontroller */
#define EM_SVX      73      /* Silicon Graphics SVx */
#define EM_ST19     74      /* STMicroelectronics ST19 8 bit mc */
#define EM_VAX      75      /* Digital VAX */
#define EM_CRIS     76      /* Axis Communications 32-bit embedded processor */
#define EM_JAVELIN  77      /* Infineon Technologies 32-bit embedded processor */
#define EM_FIREPATH 78      /* Element 14 64-bit DSP Processor */
#define EM_ZSP      79      /* LSI Logic 16-bit DSP Processor */
#define EM_MMIX     80      /* Donald Knuth's educational 64-bit processor */
#define EM_HUANY    81      /* Harvard University machine-independent object files */
#define EM_PRISM    82      /* SiTera Prism */
#define EM_AVR      83      /* Atmel AVR 8-bit microcontroller */
#define EM_FR30     84      /* Fujitsu FR30 */
#define EM_D10V     85      /* Mitsubishi D10V */
#define EM_D30V     86      /* Mitsubishi D30V */
#define EM_V850     87      /* NEC v850 */
#define EM_M32R     88      /* Mitsubishi M32R */
#define EM_MN10300  89      /* Matsushita MN10300 */
#define EM_MN10200  90      /* Matsushita MN10200 */
#define EM_PJ       91      /* picoJava */
#define EM_OPENRISC 92      /* OpenRISC 32-bit embedded processor */
#define EM_ARC_A5   93      /* ARC Cores Tangent-A5 */
#define EM_XTENSA   94      /* Tensilica Xtensa Architecture */
#define EM_AARCH64  183     /* ARM AARCH64 */
#define EM_TILEPRO  188     /* Tilera TILEPro */
#define EM_MICROBLAZE   189     /* Xilinx MicroBlaze */
#define EM_TILEGX   191     /* Tilera TILE-Gx */
#define EM_NUM      192

2.段表

ELF文件中有很多各种各样的段,这个段表(Section Header Table) 就是保存了这些段的基本属性的结构。段表是ELF文件中除了文件头以外最重要的结构。编译器、连接器和装载器都是依靠段表来定位和访问各个段的属性的。

共有 13 个节头,从偏移量 0x190 开始:

节头:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000054  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000006b8
       0000000000000078  0000000000000018          11     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000094
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  0000009c
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  0000009c
       0000000000000004  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000a0
       000000000000002c  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000cc
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000d0
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000730
       0000000000000030  0000000000000018          11     8     8
  [10] .shstrtab         STRTAB           0000000000000000  00000128
       0000000000000061  0000000000000000           0     0     1
  [11] .symtab           SYMTAB           0000000000000000  000004d0
       0000000000000180  0000000000000018          12    11     8
  [12] .strtab           STRTAB           0000000000000000  00000650
       0000000000000066  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

段表的结构比较简单,它是一个以“ELF32_Shdr”或“ELF64_Shdr”为元素的数组。每一个结构体对应一个段。段表数组的第一个元素是无效的段的段描述符,它的类型为“NULL”
段描述符的结构:

/* Section header.  */

typedef struct
{
  Elf32_Word    sh_name;        /* Section name (string tbl index) */
  Elf32_Word    sh_type;        /* Section type */
  Elf32_Word    sh_flags;       /* Section flags */
  Elf32_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf32_Off sh_offset;      /* Section file offset */
  Elf32_Word    sh_size;        /* Section size in bytes */
  Elf32_Word    sh_link;        /* Link to another section */
  Elf32_Word    sh_info;        /* Additional section information */
  Elf32_Word    sh_addralign;       /* Section alignment */
  Elf32_Word    sh_entsize;     /* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
  Elf64_Word    sh_name;        /* Section name (string tbl index) */
  Elf64_Word    sh_type;        /* Section type */
  Elf64_Xword   sh_flags;       /* Section flags */
  Elf64_Addr    sh_addr;        /* Section virtual addr at execution */
  Elf64_Off sh_offset;      /* Section file offset */
  Elf64_Xword   sh_size;        /* Section size in bytes */
  Elf64_Word    sh_link;        /* Link to another section */
  Elf64_Word    sh_info;        /* Additional section information */
  Elf64_Xword   sh_addralign;       /* Section alignment */
  Elf64_Xword   sh_entsize;     /* Entry size if section holds table */
} Elf64_Shdr;

段表各个成员的含义:

成员含义
sh_nameSection name段名
sh_typeSection type 段的类型
sh_flagsSection flag 段的标志位
sh_addr该段被加载后在进程地址空间中的虚拟地址,否者为0
sh_offset该段在文件中的偏移。sh_offset对于BSS段来说就没有意义
sh_sizeSection Size 段的长度
sh_link和sh_info段链接信息
sh_addralign段地址对齐
sh_entsize项的长度

段的类型(sh_type)
段的名字只是在链接和编译过程中有意义,但它不能真正地表示段的类型。对于编译器和链接器来说,主要决定段的属性的是段的类型(sh_type)段的标准位(sh_flags)。段的相关常量以SHT_开头。

#define SHT_NULL      0     /* Section header table entry unused *///无效段
#define SHT_PROGBITS      1     /* Program data *///程序段、代码段、数据段都是这种类型的
#define SHT_SYMTAB    2     /* Symbol table *///符号表
#define SHT_STRTAB    3     /* String table *///字符串表
#define SHT_RELA      4     /* Relocation entries with addends *///重定位表
#define SHT_HASH      5     /* Symbol hash table *///符号表的哈希表
#define SHT_DYNAMIC   6     /* Dynamic linking information *///动态链接信息
#define SHT_NOTE      7     /* Notes *///提示信息
#define SHT_NOBITS    8     /* Program space with no data (bss) *///表示该段在文件中没内容,比如.bss段
#define SHT_REL       9     /* Relocation entries, no addends */该段包含了重定位信息
#define SHT_SHLIB     10        /* Reserved *///保留
#define SHT_DYNSYM    11        /* Dynamic linker symbol table *///动态链接的符号表表。
#define SHT_INIT_ARRAY    14        /* Array of constructors */
#define SHT_FINI_ARRAY    15        /* Array of destructors */
#define SHT_PREINIT_ARRAY 16        /* Array of pre-constructors */
#define SHT_GROUP     17        /* Section group */
#define SHT_SYMTAB_SHNDX  18        /* Extended section indeces */
#define SHT_NUM       19        /* Number of defined types.  */
#define SHT_LOOS      0x60000000    /* Start OS-specific.  */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5   /* Object attributes.  */
#define SHT_GNU_HASH      0x6ffffff6    /* GNU-style hash table.  */
#define SHT_GNU_LIBLIST   0x6ffffff7    /* Prelink library list */
#define SHT_CHECKSUM      0x6ffffff8    /* Checksum for DSO content.  */
#define SHT_LOSUNW    0x6ffffffa    /* Sun-specific low bound.  */
#define SHT_SUNW_move     0x6ffffffa
#define SHT_SUNW_COMDAT   0x6ffffffb
#define SHT_SUNW_syminfo  0x6ffffffc
#define SHT_GNU_verdef    0x6ffffffd    /* Version definition section.  */
#define SHT_GNU_verneed   0x6ffffffe    /* Version needs section.  */
#define SHT_GNU_versym    0x6fffffff    /* Version symbol table.  */
#define SHT_HISUNW    0x6fffffff    /* Sun-specific high bound.  */
#define SHT_HIOS      0x6fffffff    /* End OS-specific type */
#define SHT_LOPROC    0x70000000    /* Start of processor-specific */
#define SHT_HIPROC    0x7fffffff    /* End of processor-specific */
#define SHT_LOUSER    0x80000000    /* Start of application-specific */
#define SHT_HIUSER    0x8fffffff    /* End of application-specific */

段的标志位(sh_flag)

/* Legal values for sh_flags (section flags).  */

#define SHF_WRITE        (1 << 0)   /* Writable */
#define SHF_ALLOC        (1 << 1)   /* Occupies memory during execution *///代码段、数据段和.bss段都会有这个标志位
#define SHF_EXECINSTR        (1 << 2)   /* Executable */
#define SHF_MERGE        (1 << 4)   /* Might be merged */
#define SHF_STRINGS      (1 << 5)   /* Contains nul-terminated strings */
#define SHF_INFO_LINK        (1 << 6)   /* `sh_info' contains SHT index */
#define SHF_LINK_ORDER       (1 << 7)   /* Preserve order after combining */
#define SHF_OS_NONCONFORMING (1 << 8)   /* Non-standard OS specific handling
                       required */
#define SHF_GROUP        (1 << 9)   /* Section is member of a group.  */
#define SHF_TLS          (1 << 10)  /* Section hold thread-local data.  */
#define SHF_MASKOS       0x0ff00000 /* OS-specific.  */
#define SHF_MASKPROC         0xf0000000 /* Processor-specific */
#define SHF_ORDERED      (1 << 30)  /* Special ordering requirement
                       (Solaris).  */
#define SHF_EXCLUDE      (1 << 31)  /* Section is excluded unless
                       referenced or allocated (Solaris).*/

3.重定位表

重定位表(Relocation Table)记录了代码段和数据段中那些绝对地址的引用的位置。这些位置需要链接器在处理目标文件时,对这些位置进行重定位。

4.字符串表

段名、变量名都是字符串。字符串被集中起来存放到一个表中,然后使用字符串在吧表中的偏移来引用字符串。在ELF文件中引用字符串只需要一个数字下标即可。“.strtab”为字符串表(String Table),保存普通字符串。”.shstrtab”为段表字符串表(Section Header String Table),保存段名字符串。
“e_shstrndx”是文件头的最后一个成员,它是“Section header string table index” 的缩写。段表字符串表是ELF文件中一个普通的段,它的名字叫做“.shstrtab”。“e_shstmdx“就表示”.shstrtab”在段表中的下标。即段表字符串表在段表中的下标。

5.符号

目标文件之间相互拼合拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。例如目标文件B要用到了目标文件A中的函数”foo”,那么我们就称目标文件A**定义(Define)了函数“foo”,目标文件B引用(Reference)了目标文件A中的函数“foo”。变量也一样,我们将函数和变量统称为符号(Symbol)**,函数名或者变量名就是(Symbol Name)。
链接过程是基于符号完成的。每一个目标文件都会有一个相应的符号表(Symbol Table)。 表里记录了目标文件中所用到的所有符号。每个定义的符号都有一个值,叫做符号值(Symbol Value)。对于变量和函数名来说,符号值就是它们的地址。
除了函数和变量外,还存在其他几种不太常用的符号

  • 定义在本目标文件的全局符号,可以被其它目标文件引用。
    比如SimpleSection.o里面的“func1”、“global_init_var”。
  • 在目标文件中引用的全局符号,却没有定义在本目标文件,这一般叫做外部符号(External Symbal),也就是符号引用。例如SimpleSection里面的“printf”。
  • 段名,这种符号由编译器产生,它的值就是该段的起始地址。比如 SimpleSection.o里面的“.text”、“.data”等。
  • 局部符号,这类符号只在编译单元内部可见。比如SimpleSection.o里面的“static_var2”。局部符号对于链接过程没有作用,链接器往往也忽略它们。
  • 行号信息,即目标文件指令与源代码中代码行的对应关系。
    其中第一类和第二类在链接过程中比较重要,我们只需要关注它们。

5.1ELF符号表结构

ELF文件中的符号表往往是文件中的一个段,段名一般叫“.symtab”。符号表的结构比较简单,它是一个ELF32_Sym结构(32位的ELF文件)的数组,每个ELF32_Sym结构对应一个符号。这个数组的第一个元素,也就是下标0的元素为无效的“未定义”符号。
符号表的数据结构

/* Symbol table entry.  */

typedef struct
{
  Elf32_Word    st_name;        /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;       /* Symbol value */
  Elf32_Word    st_size;        /* Symbol size */
  unsigned char st_info;        /* Symbol type and binding */
  unsigned char st_other;       /* Symbol visibility */
  Elf32_Section st_shndx;       /* Section index */
} Elf32_Sym;

typedef struct
{
  Elf64_Word    st_name;        /* Symbol name (string tbl index) */
  unsigned char st_info;        /* Symbol type and binding */
  unsigned char st_other;       /* Symbol visibility */
  Elf64_Section st_shndx;       /* Section index */
  Elf64_Addr    st_value;       /* Symbol value */
  Elf64_Xword   st_size;        /* Symbol size */
} Elf64_Sym;

符号类型和绑定信息(st_info),该成员(unsigned char)低四位表示符号的类型(Symbol Type),高四位表示符号绑定信息。

/* How to extract and insert information held in the st_info field.  */

#define ELF32_ST_BIND(val)      (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val)      ((val) & 0xf)
#define ELF32_ST_INFO(bind, type)   (((bind) << 4) + ((type) & 0xf))

/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field.  */
#define ELF64_ST_BIND(val)      ELF32_ST_BIND (val)
#define ELF64_ST_TYPE(val)      ELF32_ST_TYPE (val)
#define ELF64_ST_INFO(bind, type)   ELF32_ST_INFO ((bind), (type))

/* Legal values for ST_BIND subfield of st_info (symbol binding).  */

#define STB_LOCAL   0       /* Local symbol */
#define STB_GLOBAL  1       /* Global symbol */
#define STB_WEAK    2       /* Weak symbol */
#define STB_NUM     3       /* Number of defined types.  */
#define STB_LOOS    10      /* Start of OS-specific */
#define STB_GNU_UNIQUE  10      /* Unique symbol.  */
#define STB_HIOS    12      /* End of OS-specific */
#define STB_LOPROC  13      /* Start of processor-specific */
#define STB_HIPROC  15      /* End of processor-specific */

/* Legal values for ST_TYPE subfield of st_info (symbol type).  */

#define STT_NOTYPE  0       /* Symbol type is unspecified */
#define STT_OBJECT  1       /* Symbol is a data object *///变量、数组等
#define STT_FUNC    2       /* Symbol is a code object *///函数或其它可执行代码
#define STT_SECTION 3       /* Symbol associated with a section *///段
#define STT_FILE    4       /* Symbol's name is file name *///文件名
#define STT_COMMON  5       /* Symbol is a common data object */
#define STT_TLS     6       /* Symbol is thread-local data object*/
#define STT_NUM     7       /* Number of defined types.  */
#define STT_LOOS    10      /* Start of OS-specific */
#define STT_GNU_IFUNC   10      /* Symbol is indirect code object */
#define STT_HIOS    12      /* End of OS-specific */
#define STT_LOPROC  13      /* Start of processor-specific */
#define STT_HIPROC  15      /* End of processor-specific */

符号所在段(st_shndx),如果符号定义在本本目标文件中,那么这个成员表示符号所在段在段表中的下标。如果该符号不是定义在本目标文件中,或者对于有些特殊符号,sh_shndx的值有些特殊
这里写图片描述
符号值(st_value) 如果符号是一个函数或变量的定义,那么符号的值就是这个函数或变量的地址。确切的讲分下面几种情况区别对待。
- 在目标文件中,如果是符号的定义并且该符号不是”COMMON”类型,即(即st_shndx不为SHN_COMMON),则st_value表示该符号在段中的偏移。即符号所对应的函数或变量位于由st_shndx指定的段,偏移st_value的位置。这也是目标文件中定义全局变量的符号的最常见情况,比如SimpleSection.o中的“func1”、“func2”、“global_init_var”
- 在目标文件中,如果符号是“COMMON块”(即st_shndx为SHN_COMMON),则st_value表示该符号的对齐属性。
- 在可执行文件中,st_value表示符号的虚拟地址。
符号大小(st_size)符号的大小,对于数据类型的符号,是该数据类型的大小,对于函数,是该函数所占空间的大小。

用readelf工具查看SimpleSection中符号表的状态:
输入:

readelf -s SimpleSection.o

输出:

Symbol table '.symtab' contains 16 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1731
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1732
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var
    13: 0000000000000000    33 FUNC    GLOBAL DEFAULT    1 func1
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    15: 0000000000000021    51 FUNC    GLOBAL DEFAULT    1 main

说明:

  • func1和main函数都是定义在SimpleSection.c里面,它们所在的代码段(.text)下标为1,即Ndx为1.它们是函数,所以类型是STT_FUNC,它们是全局可见的,所以是STB_GLOBAL;Size表示函数指令所占的字节数;Value表示函数相对于代码段起始位置的偏移量。
  • prinf符号在SimpleSection.c里面被引用,但是没有被定义,所以它的Ndx是UND。
  • glocal_init_var=89 是已经初始化的全局变量,它被定义在.data段,即下标为3.
  • global_uninit_var是未初始化的全局变量,它是一个SHN_COMMON类型的符号,本身没有存在于BSS段
  • static_var=85和static_var2是两个局部静态变量,它们绑定属性是LOCAL,即只是编译单元内部可见。static_var符号Ndx的值为3,static_var2的值为4
  • 对于那些SECTION类型的符号,它们表示下位为Ndx的段的段名。它们的符号名没有显示,即它们的符号名即它们的段名。
    -”SimpleSection.c“这个符号表示编译单元的源文件名。

5.2 特殊符号

当我们使用ld作链接器来链接生产可执行文件时,它会为我们定义很多特殊的符号,我们无需定义就可直接声明并且引用它,这些便是特殊符号。只有使用ld链接生成最终可执行文件的时候这些符号才会存在。

5.3符号修饰与函数签名

20世纪70年代以前,编译器编译源代码产生目标文件时,符号名与相应的变量和和函数的名字是一样的。
为了防止类似的符号名冲突,UNIX下的C语言规定,C语言源代码文件中的所有全局变量和函数经过编译后,相对应的符号名前加上下划线_,而Fortran语言的源代码经过编译后,所有的符号名前加上_,后面也加上_。比如一个C语言函数foo,那么它编译后的符号名就是_foo;如果是Fortran语言,就是_foo_
C++语言设计过程引入了名称空间(Namespace)来解决多模块的符号冲突问题。
现在的Linux下的GCC编译其中,默认情况下已经去掉了在C语言符号前加_的这种方式。window下编译器仍保留。GCC在Windows平台下的版本(cygwin、mingw)也会加_
C++符号修饰
C++可以函数重载,为了实现这个特性,人们发名了符号修饰(Name Decoration)
例:

int func(int);
float func(float);
class C{
    int func(int);
     class C2{
      int func(int);
     };
};
namespace N{
    int func(int);
        class C{
          int func(int);
        };
}

函数签名(Function Signature)包含了一个函数的信息。包括函数名、参数类型、它所在的类和名称空间以及返回值。编译器及连接器处理符号时,它们使用某种名称修饰方法,使得每个函数签名都对应一个修饰落后名称(Decorated Name)
上述函数在GCC编译器下,修饰后名称如下:
这里写图片描述
GCC的基本C++名称修饰方法如下:
所有的符号都以_Z开头,对于嵌套的名字(在名称空间或在类里面的),_Z后面紧跟N.然后跟名称空间和类名的名字和方法名,名字前是名字字符串的长度,再以E结尾 .再跟参数类型。
全局变量和静态变量也可以这样修饰,只是没有变量类型。

5.4 extern “C”

C++为了与C兼容,在符号管理上,C++有一个用来声明或定义一个C的符号的extern "C"关键字用法:

extern "C"{
      int func(int);
      int var;
}

C++编译器会在extern “C” 的大括号内部的代码当作C语言处理。
也可以单独声明。

extern "C" int func(int);
extern "C"  int var;

使用c++宏__cplusplus

#ifdef __cplusplus
extern "C" {
#endif

void * memset (void *,int,size_t);
#ifdef __cplusplus
}
#endif

5.5 弱符号与强符号

弱符号和强符号解决的是符号重复定义的问题

对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号(Strong Symbol), 未初始化的全局变量为弱符号。我们也可以通过GCC的__attribute_((weak)) 来定义任何一个强符号为弱符号。注意强符号和弱符号都是针对定义来说。

extern int ext;
int weak;
int strong =1;
__attribute__((weak)) weak2 =2;
int  main(){
    return 0;
}

上面这段程序中,weakweak2是弱符号,strongmain是强符号,而ext既非强符号也非弱符号,因为它是一个外部变量的引用。按以下规则处理与选择多次定义的全局符号。

  • 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器报符号重复定义错误
  • 规则2:如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号。
  • 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间较大的一个。
  • 无序列表3

强引用和弱引用 目前我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们需要被正确决议,如果没有找到该符号的定义,链接器就会报符号未定义错误,这种被称为强引用(String Reference)。与之相对应还有一种弱引用(Weak Reference),在处理弱引用时,如果该符号有定义,则链接器将该符号的引用决议。如果该符号未被定义,则链接器对于该引用不报错。链接器对未定义的弱引用默认其为0,或者是一个特殊的值。弱引用和弱符号主要用于库的链接过程。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值