浅析Linux平台下的目标文件
1. 使用到的工具
1.1 readelf
readelf是一个用于显示ELF文件信息的工具,可以通过不同选项来控制显示的内容。
1.2 objdump
objdump是一个用于显示目标文件信息的工具,可以通过不同的选项来控制显示的内容
2. 预备知识
2.1 ELF Header
Linux中目标文件的格式为ELF格式,ELF文件的开始是一个文件头(ELF Header),里面包含了整个文件的属性,包括文件是否可执行,静态链接/动态链接,入口地址(如果是可执行文件),目标硬件,目标操作系统和段相关的信息等。段相关的信息包括section header的大小,数量和文件内偏移起始地址。
2.2 section header
section header用于描述每个section的属性,如名称,大小,类型等。
2.3 program header
program header用于描述segement的属性。目标文件(.o文件)不存在program header,因为它不能运行。可执行文件中,存在program header。一个segment包含一个或多个现有的section。
2. 测试代码
本文使用的测试代码如下:
/*
* file: helloworld.c
*/
#include <stdio.h>
int global_init_var = 12;
int global_uninit_var;
void func1(int i)
{
printf("%d\n", i);
}
int main(void)
{
static int static_var = 100;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return 0;
}
使用gcc编译代码,分别编译出目标文件(不进行链接的.o文件)和可执行文件
gcc -c helloword.c -o helloword.o
gcc helloword.c -o helloword
3. 使用readelf查看目标文件结构信息
3.1 查看ELF Header信息
查看未链接的目标文件simpleSection.o的ELF header,结果如下:
readelf -h simpleSection.o
我们关注如下信息:
- Type:REL (Relocatable file),说明这是一个可重定位文件。
- Entry point address:说明了程序执行的入口地址,由于.o文件并不是最终的可执行文件,此处入口地址无意义。
- Start of program headers:Program Header在文件中的偏移位置
- Start of section headers:段表在文件中的偏移位置
- Size of this header:ELF Header的大小
- Size of program headers:Program Header的大小
- Number of program headers:Program Header的数量
- Size of section headers:Section Header的大小,即段表中每一项的大小
- Number of sections headers:Section Header的数量,即段表中有多少项
- Section header string table index:这个索引表明了段字符串表在段表中的索引,通过段字符串表可以获得每个段的名称
查看可执行文件的ELF Header,结果如下:
readelf -h simpleSection
对比之前未进行链接的目标文件的ELF Header,可以发现:可执行文件ELF Header中的Type字段为DYN (Position-Independent Executable file),表明这是一个可执行文件,并且Entry point address字段指明了入口地址。同时,该文件中还包含Program Header,其数量和大小不再是0。并且,和目标文件(.o文件)相比,可执行文件中的section数量要多一些。
3.2 查看section header信息
查看目标文件(simpleSection.o)的段表信息(section header):
readelf -S simpleSection.o
共显示有14个段,从0x408(十进制1032)字节处开始,与之前ELF Header显示的信息一致。Section Header中各字段的含义如下:
名称 | 含义 |
---|---|
Name | 段名。段名是一个字符串,位于一个叫“.shstrtab”的字符串表中,Name实际存储的是段名在“.shstrtab”中的偏移 |
Type | 段的类型 |
Address | 段虚拟地址。如果该段是可以加载的,则表示该段加载后在虚拟内存空间中的地址 |
Offset | 苏果该段存在于文件中,则表示该段在文件中的偏移。否则无意义,Offset字段对于.bss段来说就没有意义 |
Size | 段的大小 |
EntSize | 有些段包含了固定大小的项,比如符号表。EntSize则为这些包含固定大小的项的段中每一项的大小 |
Flag | 段的标志为 |
Link | 段的链接信息 |
Info | 段的链接信息 |
Align | 段地址对齐 |
常见的类型(Type)有如下几种:
名称 | 含义 |
---|---|
NULL | 无效段 |
PROGBITS | 程序段。代码段,数据段都为这种类型 |
NOBITS | 表示该段在文件中无内容。如.bss段 |
RELA | 重定位表。该段包含了重定位信息 |
DYNAMIC | 动态链接信息 |
SYMTAB | 表示该段的内容为符号表 |
STRTAB | 表示该段的内容为字符串表 |
3.3 小结:ELF文件结构
根据前面两小节中介绍的ELF文件中的相关信息,我们可以大致画出ELF文件的结构,如下图所示:
4. 使用objdump查看目标文件信息
4.1 查看目标文件结构和内容**
objdump -h SimpleSection.o
同样对于目标文件simpleSection.o,使用objdump查看段信息,只显示了8个段。这是因为,objdump只显示了部分主要的段,而忽略了部分段。
4.2 查看各个段的内容和汇编指令
objdump使用-s选项可以将各个段的内容以十六进制的方式显示出来,使用-d选项可以将包含指令的段反汇编。对目标文件simpleSection.o运行如下指令:
objdump -s -d simpleSection.o