ELF文件格式详解

ELF文件格式详解

什么是ELF

ELF(Executable and Linkable Format)是Linux平台的可执行和可链接文件的文件格式标准。ELF文件以文件开头的ELF字符串为标识,这个Magic String用于标记文件类型。

ELF基本结构

ELF文件主要由四部分组成:

  • ELF Header:文件头,包含文件的元信息
  • ELF Program Header Table(程序头表):执行视角的基本单位
  • ELF Sections(节):链接视角的基本单位
  • ELF Section Header Table(节头表):描述所有节的表

ELF基本结构

两种视角

ELF文件具有两种不同的视角:

  • Linking View(链接视角):从链接的角度考虑,以section为基本单位
  • Execution View(执行视角):从运行加载的角度考虑,以segment为基本单位

.o文件是不可执行的,因此没有program header。

常用指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 查看所有信息
readelf -a file_name

# 查看ELF Header
readelf -h file_name

# 查看程序段(segment) 等价于 readelf -l
readelf --segments file_name

# 查看符号表 --wide 选项用于避免输出被截断 也可以-W
readelf -s --wide file_name

# 查看节(section) 等价于 readelf -S  ,可以显示 ELF 文件的 节区头信息,查看 file_name 是否是 debug 模式编译文件
readelf --sections file_name

# 查看动态段信息,比如共享库依赖、动态符号、重定位信息、程序入口点等
readelf -d file_name

ELF 详细结构


ELF 详细结构

ELF 头结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x2a7d00
  Start of program headers:          64 (bytes into file)
  Start of section headers:          721384 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         41
  Section header string table index: 39

ELF Header是一个Elf_Ehdr结构体,包含以下关键字段:

字段含义说明
e_identMagic魔数标识前4字节为7f 45 4c 46(ELF),后续标识架构、数据格式等
e_type文件类型ET_REL(可重定位)、ET_EXEC(可执行)、ET_DYN(共享对象)、ET_CORE(核心转储)
e_machine目标架构如X86-64、AMD GPU等
e_version版本号一般为常数1
e_entry入口地址程序执行的起始地址
e_phoff程序头表偏移Program Header Table在文件中的偏移
e_shoff节头表偏移Section Header Table在文件中的偏移
e_flagsELF标志位与具体处理器相关
e_ehsize文件头大小ELF Header本身的大小
e_phentsize程序头大小Program Header entry 大小
e_phnum程序头数量Program Header entry的数目
e_shnum节头数量Section Header entry的数目
e_shstrndx节名字符串表索引指向节名字符串表在Section Header Table中的索引

文件类型

通过e_type字段区分:

  • ET_REL(Relocatable File):可重定位文件,.o文件、静态库.a
  • ET_EXEC(Executable File):位置相关的可执行文件
  • ET_DYN(Shared Object File):位置无关的可执行文件或共享目标文件,.so文件
  • ET_CORE(Core file):核心转储文件

Section Header解析

Section Header是一个Elf_Shdr结构体,描述每个节的详细信息:

字段含义说明
sh_name节名指向.shstrtab的偏移值
sh_type节类型标识节的类型(SHT_NULL、SHT_PROGBITS、SHT_SYMTAB等)
sh_addr节地址被加载后的虚拟地址,未加载则为0
sh_offset节偏移在文件中的偏移位置
sh_size节大小节占用的字节数
sh_entsize节项大小有些节包含了一些固定大小的项,如符号表,sh_entsize表示每个项的大小。如果为0,则表示该节不包含固定大小的项。
sh_flags节标志标识节的属性(SHF_WRITE、SHF_ALLOC、SHF_EXECINSTR等)
sh_link、sh_info链接信息相关节的索引或附加信息
sh_addralign地址对齐节的地址对齐方式

节类型(sh_type)

常量含义
SHT_NULL无效节
SHT_PROGBITS程序节(代码、数据等)
SHT_SYMTAB符号表
SHT_STRTAB字符串表
SHT_HASH符号表的哈希表
SHT_DYNAMIC动态链接信息
SHT_NOTE提示性信息
SHT_NOBITS文件中无内容(如.bss
SHT_RELA / SHT_REL重定位表
SHT_DNYSYM动态链接的符号表

节标志位(sh_flags)

常量含义
SHF_WRITE (W)执行期间可被写入
SHF_ALLOC (A)执行期间需要分配内存
SHF_EXECINSTR (X)包含可执行指令
SHF_MERGE (M)可被合并
SHF_STRINGS (S)内容为字符串
SHF_GROUP (G)属于section group
SHF_TLS (T)线程本地存储
SHF_COMPRESSED (C)含有压缩数据

常见Section详解

代码和数据段

节名作用
.text代码节,存放可执行指令
.data数据节,存放已初始化的数据
.rodata只读数据节,存放只读数据
.bss未初始化数据节,不占据文件空间

动态链接相关

节名作用
.dynsym动态链接符号表
.dynstr动态链接字符串表
.plt过程链接表,用于延迟加载
.got全局偏移表,保存外部符号地址
.got.pltPLT专用的全局偏移表
.dynamic动态链接器信息
.hash符号查找的哈希表

重定位表

节名作用
.rel.a_section节的重定位信息

程序执行控制

节名作用
.init程序初始化代码(main之前)
.fini程序结束代码(main之后)
.init_array多个初始化动作
.fini_array多个结束动作
.interp动态链接器路径

符号和字符串表

节名作用
.symtab符号表(包含所有符号)
.strtab字符串表
.shstrtab节头表字符串表

异常处理和调试信息

节名作用
.eh_frameC++异常处理和栈回退信息
.eh_frame_hdr二分查找表,加速eh_frame查找
.debug_*调试信息节(编译时加-g产生)

符号表(Symbol Table)

符号表用于记录程序中的符号信息(函数、变量等)。

符号属性

属性类型含义
TypeNOTYPE未指定类型
OBJECT数据对象(变量、数组等)
FUNC可执行代码(函数)
COMMON未初始化的数据块
BindLOCAL只能被当前文件可见
GLOBAL可被所有文件可见
Weak类似于GLOBAL,但可被覆盖
Visdefault可被其他组件可见
protected可见但不可覆盖
hidden不可被其他组件可见
NdxABS绝对地址,不随重定位改变
UND未定义符号

查看符号表

1
readelf -s --wide file_name

注意:C++符号名是经过重整的,可以使用c++filt查看原始名称。

Section Name解析示例

解析section name的步骤:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// step 1: 读取ELF Header
memcpy(&hdr, data, sizeof(hdr));

// step 2: 读取节名字符串表的位置
spot = hdr.e_shoff;  // section header偏移
strndx = hdr.e_shstrndx;  // 字符串表索引
stroff = spot + strndx * hdr.e_shentsize;  // 字符串表实际偏移
memcpy(&shdr, (char*)data + stroff, hdr.e_shentsize);

// step 3: 读取字符串表
strtable = (char*)malloc(shdr.sh_size);
memcpy(strtable, (char*)data + shdr.sh_offset, shdr.sh_size);

// step 4: 遍历所有section header
for (i = 0; i < hdr.e_shnum; ++i) {
    memcpy(&shdr, (char*)data + spot, hdr.e_shentsize);
    spot += hdr.e_shentsize;
    sh_name = &strtable[shdr.sh_name];
    printf("[%d] %s\n", i, sh_name);
}

输出示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[0]
[1] .interp
[2] .note.gnu.property
[3] .note.gnu.build-id
[4] .note.ABI-tag
...
[21] .init_array
[22] .fini_array
[23] .dynamic
[24] .got
[25] .data
[26] .bss
[27] .comment
[28] .symtab
[29] .strtab
[30] .shstrtab

异构ELF(Fat Binary)

异构程序(如HIP/ROCm)将CPU代码和GPU代码打包在同一个ELF文件中,形成fat binarymultiarchitecture binary

编译流程

  1. CPU主机代码编译为.cui.bc.out
  2. GPU设备代码按架构编译(gfx906、gfx926等)
  3. 使用clang-offload-bundler打包成.hipfb文件
  4. 通过clang -fcuda-include-gpubinary.hipfb插入主机ELF的.hip_fatbin

异构ELF的特有节

节名作用
.hip_fatbin存储打包的设备端代码
.hipFatBinSegment设备端代码段

hipfb文件结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Magic String
Number of Bundle Entries
Bundle Struct 1
Bundle Struct 2
...
Bundle Struct N
Code Object 1
Code Object 2
...
Code Object N

每个Bundle Struct包含:

  • Code Object Offset:代码对象在文件中的偏移
  • Code Object Size:代码对象大小
  • Entry ID Length:入口ID长度
  • Entry ID:入口标识

设备端代码对象

设备端ELF(如gfx906)的特点:

  • Machine:AMD GPU
  • OS/ABI:unknown(设备端专用)
  • ABI Version:1(HSA_V3)、2(HSA_V4)、3(HSA_V5)
  • Entry point:通常为0x0
  • Flags:包含GPU架构信息(如EF_AMDGPU_MACH_AMDGCN_GFX906

设备端常见的section:

  • .note:metadata
  • .AMDGPU.csdata:记录kernel信息(FloatMode、IeeeMode等)
  • 注意:设备端没有.eh_frame节,不支持异常处理

异构ELF的Symbol解析

异构程序的符号表存在特殊性:

主机端符号表

1
2
93: 0000000000208d78     8 OBJECT    GLOBAL DEFAULT   21 _Z15matrixTransposePfS_i
95: 00000000000010e0   122 FUNC    GLOBAL DEFAULT   13 _Z30__device_stub__matrixTransposePfS_i

设备端符号表

1
2
3
Num:    Value          Size Type    Bind   Vis      Ndx Name
1: 0000000000001000   148 FUNC    GLOBAL PROTECTED    7 _Z15matrixTransposePfS_i
2: 0000000000000540    64 OBJECT    GLOBAL PROTECTED    6 _Z15matrixTransposePfS_i.kd

关键点:

  • _Z30__device_stub__matrixTransposePfS_i:编译器生成的辅助函数,用于启动设备端函数
  • _Z15matrixTransposePfS_i.kd:kernel descriptor,包含kernel执行必要信息
  • 主机端的_Z15matrixTransposePfS_i(OBJECT类型)实际指向设备端的kernel descriptor

Kernel Descriptor

设备端的kernel descriptor(.kd)包含kernel执行所需的所有信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.amdhsa_kernel _Z15matrixTransposePfS_i
    .amdhsa_group_segment_fixed_size 0
    .amdhsa_private_segment_fixed_size 0
    .amdhsa_kernarg_size 20          // 内核参数大小
    .amdhsa_user_sgpr_count 6        // 用户SGPR数量
    .amdhsa_user_sgpr_dispatch_ptr 1
    .amdhsa_user_sgpr_kernarg_segment_ptr 1
    .amdhsa_user_sgpr_flat_scratch_init 1
    .amdhsa_system_sgpr_workgroup_id_x 1
    .amdhsa_system_sgpr_workgroup_id_y 1
    ...
.end_amdhsa_kernel

解析异构ELF的步骤

  1. 解析ELF Header,找到Section Header Table位置
  2. 通过Section Header Table找到.hip_fatbin的位置和大小
  3. 读取clang_offload_bundle_header,校验Magic String
  4. 按顺序读取每个Bundle Struct,解析code object
  5. 解析code object的metadata

实践工具

查看设备端代码

1
llvm-amdgpu-objdump --inputs=MatrixTranspose

该指令会根据GPU架构生成ISA文件,包含metadata、kernel descriptor和kernel函数的反汇编。

反汇编操作

1
2
3
llvm-objdump -d # 反汇编命令,默认cpu, 
extractkernel -i # 反汇编命令,dcu
llvm-amdgpu-objdump --inputs= # 反汇编命令,amdgpu

参考阅读

0%