《程序员的自我修养》第3章---目标文件里有什么

x33g5p2x  于2021-11-13 转载在 其他  
字(15.2k)|赞(0)|评价(0)|浏览(644)

第3章 目标文件里有什么

3.1 目标文件的格式:

编译器编译源代码后生成的文件叫做 “目标文件”

目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。其实它本身就是按照可执行文件格式存储的,只是跟真正的可执行文件在结构上稍有不同。

现在PC平台流行的 “可执行文件格式”(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是 COFF(Common File Format)格式的变种。

目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的 .obj 和 Linux下的 .o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件一起采用一种格式存储。

从广义上可以将目标文件与可执行文件看成是一种类型的文件,在Windows下统称它们为 PE-COFF 文件格式,在Linux下它们统称为 ELF文件

不只是“可执行文件”按照ELF文件格式存储,“动态链接库”以及“静态链接库”文件也都按照ELF格式存储。

动态链接库:DLL,Dynamic Linking Library。Windows下的 .dll 和 Linux下的 .so 文件;
静态链接库:Static Linking Library。Windows下的 .lib 和 Linux下的 .a 文件;

静态链接库稍有不同,它是把很多文件捆绑在一起形成一个文件,再加上一些索引,你可以简单的把它理解为一个包含很多目标文件的文件包。

ELF文件标准里面把系统中采用ELF格式的文件归为以下4类:

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

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

# gcc -E demo.c -o demo.i //
# gcc -S demo.i -o demo.s //生成汇编文件
# gcc -c demo.s -o demo.o //生成目标文件
# gcc demo.c -o demo //生成可执行文件

# file demo.c
demo.c: C source, ASCII text

# file demo.i
demo.i: C source, ASCII text

# file demo.s
demo.s: assembler source, ASCII text

# file demo.o
demo.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

# file demo
demo: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9bee5fbd55540c9ab45e68ac1af1569a9abde37c, for GNU/Linux 3.2.0, not stripped

目标文件与可执行文件格式的小历史:

COFF 是由 Unix System V Release 3 首先提出并且使用的格式规范,后来微软公司基于COFF格式指定了PE格式标准,并将其用于Windows NT系统。
System V Release 4 在 COFF 的基础上引入了 ELF格式,目前流行的Linux系统也以ELF作为基本可执行文件格式。
这也是为什么目前PE和ELF如此相似的主要原因,因为它们都是源于同一种可执行文件格式COFF。

3.2 目标文件是什么样的:

目标文件中的内容包括:编译后的机器指令代码、数据,以及用于链接时所需要的一些信息,例如符号表、调试信息、字符串等。

目标文件将这些信息按不同的属性,以“段”(Segment)的方式存储,例如代码段、数据段。

例如上图中的ELF文件:
ELF文件的开头是一个 “文件头”,它描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息;
文件头还包括一个**“段表”**(Section Table),段表其实是一个描述文件中各个段的数组,段表描述了文件中各个段在文件中的偏移位置及段的属性等,从段表里面可以得到每个段的所有信息。

.bss段只是为未初始化的全局变量和静态局部变量预留位置而已,它并没有内容,所以它在文件中也不占据空间。

总体来说,程序源代码被编译以后主要分成两种段: 程序指令 和 程序数据。
代码段(.text)属于程序指令,而数据段(.data) 和 .bss端属于程序数据。

为什么要将代码段和数据段分开存放,而不是简单的混杂放在一个段里面?因为将它们分开存放有以下几点好处:

  1. 防止代码段中的程序指令被有意或无意的改写:
    当程序被装载后,数据和指令分别被映射到两个虚拟内存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚拟内存区域的权限可以被分别设置为读写和只读,用以防止程序的指令被有意或无意的改写;
  2. 提高CPU的缓存命中率:
    对于现代CPU来说,它们有着极为强大的缓存体系(Cache)。CPU的缓存一般都被设计成“数据缓存”和“指令缓存”分离,而程序的“指令区”和“数据区”分离有利于提高程序的局部性,因此程序的指令和数据分开存放对CPU的缓存命令率提升有好处;
  3. (最重要的原因)多个程序副本共享指令区:
    当系统中运行着多个程序的副本时,它们的指令都是一样的,所以内存中只需要保存一份该程序的指令部分,特别是在有动态链接的系统中,可以节省大量的内存。

3.3 挖掘 SimpleSection.o :

示例程序 SimipleSection.c

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

int global_init_var = 64;
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生成目标文件:

[linux] gcc -c SimpleSection.c	
//生成目标文件 SimpleSection.o

使用 objdump -h 命令可查看.o目标文件的ELF头中内容:
(ELF文件头中的内容描述了整个文件的基本属性,例如:ELF文件版本、体系结构、起始地址等)

[linux] objdump -h SimpleSection.o

//objdump:
//-h, --[section-]headers 	Display the contents of the section headers
//objdump的参数“-h”就是把ELF文件的各个段的基本信息打印出来。
//也可以使用“-x”打印更多信息

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

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000005f  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  000000a0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  000000a8  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  000000a8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002b  0000000000000000  0000000000000000  000000ac  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000d7  2**0
                  CONTENTS, READONLY
  6 .note.gnu.property 00000020  0000000000000000  0000000000000000  000000d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame     00000058  0000000000000000  0000000000000000  000000f8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

.o目标文件中的各个段在ELF文件中的结构如下图所示:

<br.>

3.3.1 代码段:

.text代码段通过 objdump -s -d 命令进行反汇编后,可以看出与C源程序的代码内容完全一致。

3.3.2 数据段和只读数据段:

.data段 保存的是 已初始化的全局静态变量和已初始化的局部静态变量。

使用 objdump -s 可以看到目标文件中的 .data段的内容:

[linux] objdump -s SimpleSection.o

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

Contents of section .data:
 0000 40000000 55000000                    @...U...

x86 CPU的字节序是小端序,高地址存放低字节,所以 0x40 是第一个字节,十进制为64,即对应的是源代码中的 global_init_var 变量,40000000长度为4个字节,32比特,刚好对应一个int型;
55000000第一个字节 0x50 = 85,对应源代码中的 static_var

使用 size 命令可以看到 SimpleSection.o 目标文件中 .data 段的大小为 8个字节:

[linux] size SimpleSection.o

   text	   data	    bss	    dec	    hex	filename
    219	      8	      4	    231	     e7	SimpleSection.o

.rodata段存放的是只读数据,一般是程序里const关键字修饰的只读变量和 字符串常量

有时候编译器会把 字符串常量 放在 .data段,而不会放在 .rodata段。这种行为因编译器而异。

3.3.3 BSS段:

使用 size 命令可以看到 .bss 段的大小为4,但是使用 objdump -s 命令却发现目标文件中没有 .bss 段的内容。
因为 .bss段中存放的是未初始化的全局变量和未初始化的局部静态变量,它们当前是没有初始化值的,所以没有必要在ELF文件中开辟空间为其存放值,更确切的说法是 “.bss段为它们预留了空间”。

另外,SimpleSection.c中有两个变量 global_uninit_varstatic_var2都属于未初始化的静态变量,为什么目标文件 SimpleSection.o 中的 .bss段的大小是 4字节而不是 8字节?

这与编译器有关,有些编译器会将 “全局未初始化变量”存放在 .bss段,有些则不放。

3.4 ELF文件结构描述:

!!! 重要:ELF文件结构:

文件头:(ELF Header)

用于描述整个ELF文件的基本属性,例如:ELF文件版本、目标机器型号、程序入口地址等;

段表:(Section Header Table)

段表是一个结构体数组,用于描述ELF文件中的所有段的信息,包括每个段的段名、段长度、偏移量、读写权限等属性。

3.4.1 文件头:

ELF Header :
描述整个文件的基本属性,例如:ELF文件版本、目标机器型号、程序入口地址 等。

使用 objdump -x 就能看到 ELF“文件头”中的所有信息:

[linux] objdump -x SimpleSection.o

SimpleSection.o:     文件格式 elf64-x86-64
SimpleSection.o
体系结构:i386:x86-64, 标志 0x00000011:
HAS_RELOC, HAS_SYMS
起始地址 0x0000000000000000

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000005f  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  000000a0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  000000a8  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  000000a8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002b  0000000000000000  0000000000000000  000000ac  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000d7  2**0
                  CONTENTS, READONLY
  6 .note.gnu.property 00000020  0000000000000000  0000000000000000  000000d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .eh_frame     00000058  0000000000000000  0000000000000000  000000f8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 SimpleSection.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000004 l     O .data	0000000000000004 static_var.1920
0000000000000000 l     O .bss	0000000000000004 static_var2.1921
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .note.gnu.property	0000000000000000 .note.gnu.property
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data	0000000000000004 global_init_var
0000000000000004       O *COM*	0000000000000004 global_uninit_var
0000000000000000 g     F .text	0000000000000028 func1
0000000000000000         *UND*	0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000         *UND*	0000000000000000 printf
0000000000000028 g     F .text	0000000000000037 main

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
0000000000000017 R_X86_64_PC32     .rodata-0x0000000000000004
0000000000000021 R_X86_64_PLT32    printf-0x0000000000000004
000000000000003d R_X86_64_PC32     .data
0000000000000043 R_X86_64_PC32     .bss-0x0000000000000004
0000000000000056 R_X86_64_PLT32    func1-0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE 
0000000000000020 R_X86_64_PC32     .text
0000000000000040 R_X86_64_PC32     .text+0x0000000000000028

文件头的内容存放在 /usr/include/elf.h 头文件中的 Elf32_Ehdr 结构体中:

typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */	//Magic, Class, Data, Version, OS/ABI, AB Version
  Elf32_Half    e_type;                 /* Object file type */	//ELF文件类型: ET_REL=1, 可重定位文件(.o); ET_EXEC=2, 可执行文件; ET_DYN=3, 共享目标文件(.so);
  Elf32_Half    e_machine;              /* Architecture */	//该ELF文件支持的运行平台,ELF文件格式被设计成可以在多个平台下使用,但不意味着同一个ELF文件可以在多个平台下运行,而是不同平台下的ELF文件都遵循同一套ELF标准。以“EM_”开头: EM_M32: AT&T; EMP_SPARC: SPARC; EM_386: Intel x86; EM_860: Intel 80860; EM_68K: Motorola 68000; EM_88K: Motorola 88000.
  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;

使用 readelf -h 命令查看 SimpleSection.o目标文件中的内容:

readelf -h SimpleSection.o 

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          1184 (bytes into file)
  标志:             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         14
  Section header string table index: 13

其中,Elf32_Ehdr结构体中的e_ident 成员对应了 readelf输出结果中的 ClassDataVersionOS/ABIABI Version 这5个参数,其中还包括 Magic魔数,其余的 Elf32_Ehdr结构体中的成员与 readelf输出结果的参数一一对应。

3.4.2 段表:

段表用于描述ELF文件中的各个段(.text, .data, .bss 等)的信息,例如:每个段的 段名、段的长度、在文件中的偏移量、读写权限、其他属性。

段表的结构:

它是一个以 Elf32_Shdr 结构体为元素的数组,数组元素个数等于段的个数,每个 Elf32_Shdr 结构体对应一个段,又称为 “段描述符”(Section Descriptor)。

Elf32_Shdr 结构体的结构:(用于描述一个段的属性)

typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */	//段的名字,如: .text, .data, .rodata, .bss 等
  Elf32_Word    sh_type;                /* Section type */		//段的类型,以“SHT_”开头,如: SHT_NULL:无效段; SHT_PROBITS:程序段、代码段、数据段都是这种类型; SHT_SYMTAB:符号表; SHT_RELA:字符串表; SHT_DNYSYM:动态链接的符号表;
  Elf32_Word    sh_flags;               /* Section flags */	//段的标志位:SHF_WRITE:表示该段在进程空间中可写; SHF_ALLOC:表示该段需要在进程虚拟空间中分配空间,代码段、数据段都需要这个标志位; SHF_EXCINSTR:表示该段可执行,一般指代码段;
  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;

3.5 链接的接口 ---- 符号:

链接过程的本质就是要把多个不同的目标文件相互“粘”在一起,组成一个整体。
为了使不同的目标文件之间能够相互粘合,这些目标文件之间需要有固定的规则才行。

在链接中,目标文件之间相互拼合实际上是目标文件回见对 地址 的引用,即对函数和变量地址的引用。

例如目标文件B引用了目标文件A中的函数foo(),那么就称目标文件A中 “定义”(Define) 了函数foo(),称目标文件B中 “引用”(Reference) 了目标文件A中的函数foo()。

“定义”和“引用”这两个概念也同样适用于变量。

每个函数和变量都要有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。

在链接中,将函数和变量统称为 “符号”(Symbol),函数名和变量名就是 “符号表”(Symbol Name)

我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能够正确完成。

链接过程中很关键的一部分就是符号的管理,每一个目标文件都有一个相应的 “符号表”(Symbol Table),表中记录目标文件所用到的所有符号。

每个定义的符号有一个对应的值,称为 “符号值”(Symbol Value),对于变量和函数来说,符号值就是它们的地址。

符号表中的符号可以分为以下几类:

  1. (定义在本目标文件中的)全局符号:
  2. (本文件引用其他目标文件的外部的)全局符号:
  3. 段名:(由编译器产生)
  4. 局部符号:
  5. 行号信息:

对于链接过程来说,最值得关注的就是 “全局符号”,因为链接过程只关心全局符号的相互粘合,局部符号、段名、行号等都是次要的,它们对于目标文件来说是不可见的,在链接过程中也是无关紧要的。

可用于查看ELF文件符号的工具:
readelfobjdumpnm 等。

使用 nm命令列出 SimpleSection.o目标文件中的所有符号:
(大写表示全局,如T;小写表示局部,如d)

nm SimpleSection.o
0000000000000000 T func1					//T: 表示该符号位于 代码段(.text)
0000000000000000 D global_init_var			//D: 表示该符号位于 数据段(.data)
0000000000000004 C global_uninit_var		//C: 表示该符号为 COMMON
0000000000000028 T main						
                 U printf					//U: 表示该符号在当前文件中是“未定义的”,即该符号定义在其他的文件中
0000000000000004 d static_var.1920			
0000000000000000 b static_var2.1921

使用 objdump -x命令查看SimpleSection.o目标文件中的符号:

[linux] objdump -x SimpleSection.o

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 SimpleSection.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000004 l     O .data	0000000000000004 static_var.1920
0000000000000000 l     O .bss	0000000000000004 static_var2.1921
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .note.gnu.property	0000000000000000 .note.gnu.property
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data	0000000000000004 global_init_var
0000000000000004       O *COM*	0000000000000004 global_uninit_var
0000000000000000 g     F .text	0000000000000028 func1
0000000000000000         *UND*	0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000         *UND*	0000000000000000 printf
0000000000000028 g     F .text	0000000000000037 main

使用 readelf -s 查看ELF文件中的符号:

[linux] readelf -s SimpleSection.o

Symbol table '.symtab' contains 18 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.1920
     7: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1921
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
    12: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    13: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM global_uninit_var
    14: 0000000000000000    40 FUNC    GLOBAL DEFAULT    1 func1
    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    17: 0000000000000028    55 FUNC    GLOBAL DEFAULT    1 main

Num : 表示符号表数组的下标
Value : 符号值
Size : 符号大小
Type : 符号类型
Bind : 绑定信息
Vis : 目前在C/C++中未使用,暂时忽略
Ndx : 表示该符号所属的段
Name : 符号名称

3.5.1 ELF符号表结构:

ELF符号表示一个结构体数组,数组中的每个元素对应一个 Elf32_Sym结构体,一个结构体对应一个符号:

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;

3.5.4 extern “C” :

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

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

extern "C" { } 大括号括起来的部分的代码,C编译器会将其当作C语言代码来处理,此时C的名称修饰机制(name-manling)将不会起作用。

9. Linux下用于查看二进制文件的几个实用工具:

9.1 file命令:查看文件格式(源文件、汇编文件、ELF文件)

例如:

[linux] file SimpleSection.o

SimpleSection.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

9.2 objdump命令:查看目标文件内部结构

objdump命令有点像那种快速查看之类的工具,就是以一种可阅读的格式让你更多的了解 二进制文件 可能带有的附加信息。

对于一个只想让自己的程序跑起来的程序员,这个命令没有更多意义;对于想进一步了解系统的程序员,应该掌握这个工具。

objdump -h, --[section-]headers  Display the contents of the section headers
	//查看目标文件的“文件头”,可以看到目标文件的代码段、数据段的大小、属性等信息

objdump -x, --all-headers        Display the contents of all headers
	//“-h”只是查看目标文件的“文件头”信息,内容精简;“-x”用于查看目标文件的所有信息,内容丰富复杂

objdump -s, --full-contents      Display the full contents of all sections requested
	//将所有的段内容以十六进制的方式打印出来

objdump -d, --disassemble        Display assembler contents of executable sections
	//将所有包含指令的段进行反汇编(将目标文件由机器语言格式 反汇编 成为汇编语言格式,在这里是将ELF格式文件反汇编成为汇编语言文件)

objdump -r, --reloc              Display the relocation entries in the file
	//查看ELF文件的重定位表,即查看每个重定位入口

9.3 size命令:查看ELF文件的代码段、数据段和BSS段的长度

例如:

[linux] size SimpleSection.o

   text	   data	    bss	    dec	    hex	filename
    219	      8	      4	    231	     e7	SimpleSection.o

9.4 readelf命令:查看ELF文件内容

理解:

objdump -h SimpleSection.o 命令看到的东西更倾向于文件的 “内容”“结构”

readelf -h SimpleSection.o 命令看到的东西更倾向于文件的 “属性”

readelf   -h --file-header       Display the ELF file header
	//查看ELF文件的“文件头”信息

readelf   -s --syms              Display the symbol table
     --symbols           An alias for --syms
	//查看ELF文件的“符号表”信息

readelf  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
     //查看ELF文件的“段表头”信息

9.5 nm命令:

“nm” 是 names的缩写,nm命令用于列出某些文件中的符号(函数、全局变量等)。

例如:

nm SimpleSection.o
0000000000000000 T func1
0000000000000000 D global_init_var
                 U _GLOBAL_OFFSET_TABLE_
0000000000000004 C global_uninit_var
0000000000000028 T main
                 U printf
0000000000000004 d static_var.1920
0000000000000000 b static_var2.1921

相关文章