LinuxC目标文件
宗旨:技术的学习是有限的,分享的精神是无限的。
一、目标文件格式(ELF格式)
编译器编译源代码后生成的文件叫做目标文件。目标文件是已经编译后的可执行文件,只是还没有经过链接的过程。
PC平台流行的可执行文件格式:windows下的PE和Linux下的ELF。
动态链接库和静态链接库也是按照可执行文件存储的。
1、ELF文件归于4类:
ELF文讲类型 | 说明 | 举例 |
可重定位文件 | 目标文件.o | Linux的.o, windows下的.obj |
可执行文件 | 直接可执行的文件 | /bin/bash windows的.exe |
共享目标文件 |
| .so DLL |
核心转储文件 | 进程意外终止 | core dump |
Linux下的file命令查看相应的文件格式:
2、目标文件
编译后的机器指令代码、数据、符号表、调试信息、字符串等。
一般目标文件将这些文件信息按不同的属性,以“节”的形式存储。
机器指令放在代码段.text,已初始化全局变量和静态变量放在数据段里.data,未初始化全局变量和静态变量放在数据段里.bss。.bss只是为变量预留的位置而已,并没有内容,不占据空间。
二、剖析目标文件section.o
// section.c:
#include<stdio.h>int init_var = 84;
int uninit_var;void fun(int i)
{printf(" %d \n", i);
}int main(void)
{static int static_var = 85;static int static_var2;int a = 1;int b;fun(init_var + uninit_var + a + b);return 0;
}
gcc -c section.c生成section.o——只编译不链接
objdump -h section.o// ELF文件的各个段的基本信息打印出来。
除了最基本的代码段、数据段、BSS段,还有三个段:只读数据段(.rodata)、注释信息段(.comment)和堆栈提示段(.note.GNU-stack)。
段的属性:最容易理解的就是段的长度Size和段所在的位置File off(偏移量)。每个段第二行的“CONTENTS”表示该段在文件中存在——BSS段没有“CONTENTS “,实际上在ELF中不存在;”note.GNU-stack“有“CONTENTS”但大小为0,奇怪。
size命令查看ELF文件中的代码段、数据段和BSS段长度。
root@colinux:~/mystudy# size section.o
text data bss dec hex filename
88 8 4 100 64 section.o
1、代码段
objdump的“-s”十六进制方式打印,“-d”反汇编。提取出代码段的内容:
“Contents of section.text”就是.text的数据以十六进制方式打印出来的内容,0x58字节,与size命令的长度符合。对照反汇编结果,.text包含两个函数,fun()和main()。.text的第一个字节就是”0x55”就是fun()函数的第一条“push %ebp”指令,而最后一个字节0xc3正是main()函数的最后一条指令“ret”。
2、数据段和只读数据段
.data段保存的是初始化的全局变量和静态变量,section.c中有这样两个变量init_var和static_var,都是int型,刚好8字节。所以.data的大小是8字节。
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 20256420 0a00 %d ..
.data前四个字节,0x54、0x00、0x00、0x00 —— 0x54 = 84;——大端机
3、BSS段
.bss段保存的是未化的全局变量和静态变量,section.c中有这样两个变量uninit_var和static_var2。但是通过size命令看到.bss只有4字节。通过符号表(后面说)看到,只有static_var2被放入了.bss段,uninit_var没有。与不同的语言和不同的编译器有关。
4、其他段
常用段名 | 说明 |
.rodata | 只读数据,如字符串常量,const只读变量 |
.comment | 编译器版本信息, |
.debug | 调试信息 |
.dynamic | 动态链接信息 |
.hash | 符号哈希表 |
.line | 行号表 |
.note | 额外的编译器信息:公司名,发布版本号等 |
.strtab | 字符串表 |
.symtab | 符号表 |
.shstrtab | 段名表 |
.plt .got | 动态链接的跳转表和全局入口表 |
.init .fini | 程序初始化与终结代码段 |
三、ELF文件结构
提取重要的结构:ELFHeader(ELF文件头)、.text、.data、.bss、其他段、段表、字符串表、符号表等。
ELF文件头——描述了整个文件的文件属性:是否可执行、是静态还是动态连接及入口地址、目标硬件、目标操作系统等信息。
段表——所有段的信息:段名、段的长度、在文件中的偏移、读写权限及段的其他属性。
1、文件头(readelf命令)
ELF文件头定义:ELF魔数、文件机器字节长度、文件存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口地址和长度、段表的位置和长度及段的数量等。
ELF文件头结构及相关常数被定义在”/usr/include/elf.h”,32位“ELF32_Ehdr”
#define EI_NIDENT(16)typedef struct
{unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */Elf32_Half e_type; /* Objectfile type */Elf32_Half e_machine; /*Architecture */Elf32_Word e_version; /* Object file version */Elf32_Addr e_entry; /* Entrypoint virtual address */Elf32_Off e_phoff; /* Programheader table file offset */Elf32_Off e_shoff; /* Sectionheader table file offset */Elf32_Word e_flags; /*Processor-specific flags */Elf32_Half e_ehsize; /* ELFheader size in bytes */Elf32_Half e_phentsize; /* Programheader table entry size */Elf32_Half e_phnum; /* Program header table entrycount */Elf32_Half e_shentsize; /* Sectionheader table entry size */Elf32_Half e_shnum; /* Sectionheader table entry count */Elf32_Half e_shstrndx; /* Sectionheader string table index */
} Elf32_Ehdr;
结构与readelf输出的ELF文件头信息相比:只有e_ident对应了readelf输出中的“Class Data Version OS/ABI ABI Version”5个参数,剩下的参数一一对应。
ELF魔数:Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
16字节对应了Elf32_Ehdr的e_ident这个16字节成员。这16字节被ELF标准规定来标识ELF文件的平台属性:如字长、字节序、版本等。16字节含义:
前4字节是所有ELF文件都必须相同的标识码:0x7F、0X45、0X4C、0X46(这四个字节就是ELF文件的魔数)。几乎所有的可执行文件的开始的几个字节都是魔数,如a.out最开始两个字节是0x01、0x07;PE最开始两个字节是0x4d、0x5a。这个魔数用来确认文件类型。第5个字节用来标识ELF文件类的,0x01表示是32位的,0x02表示64位的。第6字节是字节序,规定ELF文件是大端的还是小端的。第7字节规定ELF文件的主版本号,一般是1.后面的九个字节ELF标准没有定义,一般写0。
e_type文件类型:ET_REL—— 1 ——可重定位文件,一般是.o文件;ET_EXEC—— 2 —— 可执行文件; ET_DYN —— 2 ——共享目标文件,一般是.so文件。
e_machine机器类型:ELF文件的平台属性,EM_386 —— 3 —— x86
2、段表
ELF的段结构就是由段表决定的,编译器、连接器和装载器都是靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置由ELF文件头Elf32_Ehdr结构中的” e_shoff” 成员决定。section.o中,段表位于偏移0x104(260字节)处。
前面用”objdump -h”查看ELF文件中的段,此命令只是把ELF文件中的关键段显示出来了,省略了其他辅助性的段:符号表、字符串表、重定位表等。
readelf -S命令
段表是以“Elf32_Shdr”结构体为元素的数组,数组元素的个数等于段的个数,每个“Elf32_Shdr”结构体对应一个段。section.o:11个元素的数组。/usr/include/elf.h:
typedef struct
{Elf32_Word sh_name; /* Section name (string tblindex) */Elf32_Word sh_type; /* Section type */Elf32_Word sh_flags; /* Section flags */Elf32_Addr sh_addr; /* Section virtual addr atexecution */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 sectioninformation */Elf32_Word sh_addralign; /* Section alignment */Elf32_Word sh_entsize; /* Entry size if section holdstable */
} Elf32_Shdr;
总结section.o段表的位置
| 起始地址 | 大小 |
ELF Header e_shoff = 0x104 | 0 | 0x34 |
.text | 0x34 | 0x52 |
.data | 0x88 | 0x08 |
.rodata | 0x90 | 0x06 |
.comment | 0x96 | 0x1d |
.shstrtab | 0xB3 | 0x51 |
Section Table | 0x104 | 0x1b8 |
.symtab | 0x2bc | 0xf0 |
.rel.text | 0x3fc | 0x28 |
长度为0x424 = 1060,这个长度正好是section.o文件的大小。
段的类型(sh_type):段的名字只有在编译和链接的过程中有意义。SHT_NULL – 0 – 无效段, SHT_PROGBITS– 1 – 程序段, SHT_SYMTAB – 2 – 表示该段的内容为符号表, SHT_STRTAB – 3 – 字符串表, SHT_RELA –4 – 重定位表, SHT_HASH – 5 – 符号表的哈希表,SHT_DYNAMIC – 6 – 动态链接信息, SHT_NOTE – 7 – 提示性信息, SHT_NOBITS– 8 –该段在文件中没内容, SHT_REL – 9 –该段包含了重定位信息,SHT_SHLIB – 10 – 保留,SHT_DNYSYM – 11 – 动态链接的符号表。
段的标志位(sh_flag):表示该段在进程虚拟地址空间中的属性,可写可执行等。SHF_WRITE – 1 – 该段在进程空间中可写; SHF_ALLOC– 2 – 在进程空间中要分配空间; SHF_EXECINSTR– 4 –该段在进程空间中可以被执行,一般指代码段。
段的链接信息(sh_link、sh_info):
sh_type | sh_link | sh_info |
SHT_DYNAMIC | 字符串表在段表的下标 | 0 |
SHT_HASH | 符号表在段表中的下标 | 0 |
SHT_REL | 相应符号表在段表中的下标 | 该重定位表所作用的段在段表中的下标 |
SHT_RELA | ||
SHT_SYMTAB | 操作系统相关 | 操作系统相关 |
SHTDYNSYM | ||
other | SHN_UNDEF | 0 |
3、重定位表
section.o中有一个“rel.text”的段,类型是“SHT_REL”——重定位表。
代码段和数据段中那些对绝对地址的引用的位置——相应的重定位表。section.o中的“rel.text”就是对“.text”段的重定位表——printf函数的调用;而“.data”段没有对绝对地址的引用,只包含了几个常量,故没有“.rel.data”。
4、字符串表——段名,变量名等
四、链接的接口——符号
可以使用很多工具查看ELF文件的符号表,readelf,objdump,nm等;
1、ELF符号表结构
ELF符号表是文件中的一个段“.symtab”
/* Symbol table entry. */
typedef struct
{Elf32_Word st_name; /* Symbol name (string tblindex) */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;