编译器编译源代码后生成的文件叫目标文件;
从结构上来说与可执行文件一致,只是还没有经过动态链接的过程,有符号还没有被调整。与真正可执行文件稍有区别。
可执行文件格式涵盖了程序的编译、链接、装载和执行的各个方面。
windows下的PE和Linux下的ELF,都是COFF格式的变种。
目标文件(Linux下的.o win下的.obj)与可执行文件就差了个静态链接过程,一般存储格式是一样的,在Linux下为ELF文件。
动态链接库(DLL .dll和.so )以及静态链接库(Static Linking Library .lib和.a)也都是按照可执行文件格式存储的。
ELF文件类型
可重定位文件(Relocatable File)包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态库也可以归为此类
可执行文件(Executable File)ELF可执行文件
共享目标文件(Shared Object File)包含了代码和数据,两种情况:一种是静态链接器可以使用进行重定位产生新的目标文件,二是动态链接器
核心转储文件(Core Dump File)
COFF的主要贡献是在目标文件中引入段的机制,不同的目标文件可以拥有不同数量以及不同类型的段,另外还定义了调试数据格式。
目标文件内容
内容包括编译后的机器指令代码、数据,以及一些链接时需要的信息:符号表、调试信息、字符串等。按照不同的属性,以“节”的形式进行存储,有时候也称“段”。
文件头:File Header (描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接以及入口地址(如果是可执行文件)、目标硬件、目标操作系统 )
(段表:属于文件头中一部分,段表是一个描述文件中各个段的数组,描述了文件中各段在文件的偏移位置以及段的属性)
代码段:.code .text 机器代码 汇编
数据段:.data 以及初始化的全局变量和局部静态变量
未初始化段: .bss 未初始化的全局变量和局部静态变量预留位置, 默认值为0, 在.data段中分配空间并存放数据0是没有必要的,elf文件中只是记录了需要分配内存的总和,该段不占用空间。
可执行文件必须记录所有全局变量和局部静态变量的大小总和。
BSS(Block Started by Symbol)
程序代码编译后主要分为两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
数据和指令分开的好处:1:读写权限;2:CPU cache缓存问题,提升cache命中率;3:指令可以复用。
64位Ubuntu系统
代码示例
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 SimpleSection.c
objdump -h SimpleSection.o

代码段、数据段、.bss段、只读数据段(.rodata)、注释信息段(.comment)和堆栈提示段(.note.GNU-stack)
size SimpleSection.o
![]()
objdump -s -d SimpleSection.o

一般而言bss是未初始化的全局变量和局部静态变量预留位置 ,这里有两个global_uninit_var和 static_var2,应该是8,实际才是4。实际这里只存了static_var2;
这跟强符号和弱符号有关系,global_uninit_var是等到最终链接成可执行文件时才在bss段分配标记。
自定义段:
有时候可能希望变量或者部分代码能够放到你所指定的段中去,以实现某些特定的功能。比如为了满足某些硬件的内存和I/O的地址布局,或者像Linux内核中用来完成一些初始化和用户空间复制时出现页错误异常等。
__attribute__((section("FOO"))) int global = 42;
在全局变量或函数之前加上“__attribute__((section("name")))”属性就可以把相应的变量或者函数放在以name作为段名的段中。
ELF文件的总体结构:
| ELF Header |
| .text |
| .data |
| .bss |
| … Other sections |
| Section header Table |
| String Tables Symbol Tables |
readelf -h 查看命令的帮助信息,需要正确区分文件头、section头、程序头(可执行程序才有)。
程序头是可执行程序load和exec时使用的,在汇编和链接过程中用不到;
相应的Section头在load时也用不上。

ELF file Header : 描述了整个文件的基本属性,比如ELF文件版本,目标芯片类型,程序入口地址(Entry point address)

readelf -S 显示段表(Section Header Table)段表是elf文件除了文件头以外最重要的结构,描述了各个段的信息,比如各个段的段名,段的长度,在文件中的偏移、读写权限及段的其他属性。
编译器、链接器和装载器都是依靠段来定位和访问各个段的属性的,对比objdump -h也可以查看段表信息,但是不是完整的。
这里起始地址0x430等于上面的1072。

ELF相关的结构体定义在 “usr/include/elf.h”中,对应ndk的位置:./toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/linux/elf.h
关于段表的描述为一个结构体(Elf64_Shdr sizeof=64 (size of section header))数组,如上,对应长度为13的数组;

最终的文件结构顺序以及长度如下:
| 序号 | 段内容 | 长度 | 偏移 | 对齐 |
| ELF file Header | 0x40 | 0 | ||
| 1 | .text | 0x55 | 0x40 | 1 |
| 2 | .data | 0x8 | 0x98 | 4 |
| 3 | .bss | 0 | 0xa0 | |
| 4 | .rodata | 0x4 | 0xa0 | 1 |
| 5 | .comment | 0x36 | 0xa4 | 1 |
| 6 | .note.GNU-stack | 0 | 0xda | |
| 7 | .eh_frame | 0x58 | 0xe0 | 8 |
| 8 | .symtab 符号表 | 0x180 | 0x138 | 8 |
| 9 | .strtab | 0x66 | 0x2b8 | 1 |
| 10 | .rela.text | 0x78 | 0x320 | 8 |
| 11 | .rela.eh_frame | 0x30 | 0x398 | 8 |
| 12 | .shstrtab | 0x61 | 0x3c8 | 1 |
| 段表 | Section header | 0x340 = 0x40*13 | 0x430 | 4? |
| 文件长度: | 0x770=1904 |
这与文件大小是可以对应上的:

readelf参考:
readelf 命令,Linux readelf 命令详解:用于显示elf格式文件的信息 - Linux 命令搜索引擎















