###################
# 3、目标文件里有什么
###################
# 目标文件的格式
可执行文件:Linux下的ELF(Executable Linkable Format)可执行文件
动态链接库:DLL(Dynamic Linking Library) , linux的.so
静态链接库:.a
ELF格式的文件可以归为以下4类:
(1)可重定位文件(Relocatable File)
这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类。linux中的.o
[xiaoloaw@AONT03 partTwo]$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
(2)可执行文件
这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,一般没有扩展名
[xiaoloaw@AONT03 partTwo]$ file /bin/bash
/bin/bash: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
(3)共享目标文件
这种文件包含了代码和数据,可以在两种情况下使用。linux中的.so
一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件;
第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行;
[xiaoloaw@AONT03 partTwo]$ file /lib/libgcc_s-4.4.4-20100726.so.1
/lib/libgcc_s-4.4.4-20100726.so.1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped
(4)核心转储文件
当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件。linux下的core dump
[xiaoloaw@AONT03 HD_R5601_FDT1356]$ file omciMgr-1970-01-01-00-01-13_pid1461_sig11.core
omciMgr-1970-01-01-00-01-13_pid1461_sig11.core: ELF 32-bit MSB core file, MIPS, MIPS-I version 1 (SYSV), SVR4-style
# 目标文件是什么样的
目标文件中的内容至少有编译后的机器指令代码、数据,还包含了链接时所需要的一些信息(符号表、调试信息、字符串等)。
一般目标文件将这些信息按不同的属性,以节(Section)的形式存储,也叫段(Segment)。
(1).text段:一般C语言的编译后执行语句都编译成机器代码
(2).data段:已初始化的全局变量和已初始化的局部静态变量
(3).bss段:未初始化的全局变量和局部静态变量预留位置而已,默认值为0,并没有内容,所以它在文件中不占据空间。
(4).rodata段:只读数据段
(5).comment段:注释信息段
(6).note.GNU-stack段:堆栈提示段
// SimpleSection.c
int printf( const char *format, ... );
int global_init_var = 84; // 数据段 .data section
int global_uninit_var; // .bss section
void func1(int i)
{
printf("%d\n", i);
}
int main(void) // .text section
{
static int static_var = 85; // 数据段 .data section
static int static_var2; // .bss section
int a = 1;
int b;
func1( static_var + static_var2 + a + b); // .text section
return a;
}
$ gcc -c SimpleSection.c // -c 只编译不链接
// printf语句中用到了字符串常量"%d\n",它是一个只读数据,所有它被放到了".rodata段",这个段的4个字节刚好是这个字符串常量的ASCII字节序,最后以\0结尾。
$ objdump -h SimpleSection.o // 查看目标文件的结构和内容
SimpleSection.o: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000050 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 00000000 00000000 00000084 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 00000000 00000000 0000008c 2**2
ALLOC
3 .rodata 00000004 00000000 00000000 0000008c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002e 00000000 00000000 00000090 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 00000000 00000000 000000be 2**0
CONTENTS, READONLY
// global_init_var 和 static_var 保存在数据段,共8个字节。
$ size SimpleSection.o // size命令用来查看ELF文件的代码段、数据段和BSS段的长度
text data bss dec hex filename
84 8 4 96 60 SimpleSection.o
# ELF文件结构描述
ELF Header
|
.text
|
.data
|
.bss
|
...
other sections
|
Section header table
|
String Tables
Symbol Tables
...
|
ELF 结构
$ readelf -h SimpleSection.o // 查看ELF文件头
ELF Header:
// e_ident
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
// e_type, ELF文件类型
Type: REL (Relocatable file)
// e_machine,ELF文件的CPU平台属性
Machine: Intel 80386
// e_version,ELF版本号,一般为常数1
Version: 0x1
// e_entry,入口虚拟地址
Entry point address: 0x0
// e_phoff
Start of program headers: 0 (bytes into file)
// e_shoff,段表在文件中的偏移,也就是段表从文件的第273个字节开始
Start of section headers: 272 (bytes into file)
// e_flags,ELF标志位
Flags: 0x0
// e_ehsize,ELF文件头本身的大小
Size of this header: 52 (bytes)
// e_phentsize
Size of program headers: 0 (bytes)
// e_phnum
Number of program headers: 0
// e_shentsize,段表描述符的大小
Size of section headers: 40 (bytes)
// e_shnum,段表描述符数量
Number of section headers: 11
// e_shstrndx,段表字符串表所在的段 在段表中的下标
Section header string table index: 8
ELF文件头结构及相关常数被定义在/usr/include/elf.h
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;
(2)段表(Section Header Table )
ELF文件中有各种各样的段,段表就是保存这些段的基本属性的结构。
$ readelf -S SimpleSection.o // 查看完整ELF文件段表的内容
There are 11 section headers, starting at offset 0x110:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000050 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000420 000028 08 9 1 4
[ 3] .data PROGBITS 00000000 000084 000008 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 00008c 000004 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 00008c 000004 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 000090 00002e 01 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 0000be 000000 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0000be 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 0002c8 0000f0 10 10 10 4
[10] .strtab STRTAB 00000000 0003b8 000066 00 0 0 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)
段表的结构比较简单,它是一个以 "Elf32_Shdr" 结构体为元素的数组。数组元素的个数等于段的个数,每个Elf32_Shdr结构体对应一个段。
"Elf32_Shdr"又被称为段描述符
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */ 段名是一个字符串,它位于一个叫做".shstrtab"的字符串表。sh_name是段名字符串在".shstrtab"中的偏移。
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */ 段虚拟地址,如果该段可以被加载,则sh_addr为该段被加载后在进程地址空间中的虚拟地址;否则sh_addr为0。
Elf32_Off sh_offset; /* Section file offset */ 段偏移,如果该段存在于文件中,则表示该段在文件中的偏移;否则无意义,比如sh_offset对于BSS段来说就没有意义。
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 */ 项的长度,有些段包含了一些固定大小的项,比如符号表,它包含的每个符号所占的大小都一样的,对于这种段,sh_entsize表示每个项的大小。如果为0,则表示该段不包含固定大小的项。
} Elf32_Shdr;
(3)重定位表(Relocation Table)
链接器在处理目标文件时,需要对目标文件中某些部位进行重定向,即代码段和数据段中那些绝对地址的引用的位置。
这些重定位的信息都记录在ELF文件的重定位表里面,对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。
SimpleSection.o中的.rel.text就是针对.text段的重定位表,因为.text段中至少有一个绝对地址的引用,那就是
printf函数的调用。
.data段则没有对绝对地址的引用,它只包含了几个常量,所以没有重定位表。
(4)字符串表
.strtab:字符串表(String Table),用来保存普通的字符串,比如符号的名字
.shstrtab:段表字符串表(Section Header String Table),用来保存段表中用到的字符串,最常见的就是段名(sh_name)。
# 链接的接口---符号
在链接中,目标文件之间相互拼接实际上是
目标文件之间对地址的引用,即对函数和变量的地址的引用。
每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间的混淆。
在链接中,将函数和变量统称为符号,函数名或变量名就是符号名。
每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。
可以使用很多工具来查看ELF文件的符号表,比如readelf、objdump、nm等
$ nm SimpleSection.o
00000000 T func1
00000000 D global_init_var
00000004 C global_uninit_var
0000001b T main
U printf
00000004 d static_var.1243
00000000 b static_var2.1244
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;
$ readelf -s SimpleSection.o
Symbol table '.symtab' contains 15 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: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.1243
7: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1244
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
11: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
12: 00000000 27 FUNC GLOBAL DEFAULT 1 func1
13: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
14: 0000001b 53 FUNC GLOBAL DEFAULT 1 main
符号修饰与函数签名
GCC的基本C++名称修饰方法如下:所有的符号都以"_Z"开头,对于嵌套的名称(在名称空间或在类里面的),后面紧跟"N"。
然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以"E"结尾。
对于一个函数来说,它的参数列表紧跟在"E"后面,对于int类型来说,就是字母"i"。
N::C::func(int)函数签名经过修饰为_ZN1N1C4funcEi。
工具c++filt可以用来解析被修饰过的名称
$ c++filt _ZN1N1C4funcEi
N::C::func(int)
$ c++filt _ZN3foo3barE
foo::bar