多精彩内容在公众号。
ELF(Executable and Linkable Format)文件格式是一种用于表示可执行文件、目标文件、共享库和核心转储的标准文件格式。它被广泛应用于Linux和许多类Unix操作系统中。ELF文件格式的设计允许程序和数据的高效存储,同时也支持程序的链接和加载。以下是ELF文件格式的详细介绍:
ELF文件的主要组成部分:
ELF头 (ELF Header):
位于文件的开始位置,主要用来定位文件的其他部分。
包含文件的魔数(Magic Number)、文件类型、目标架构、入口点地址、节头表和程序头表的位置等信息。
节头表 (Section Header Table):
描述文件中的各个节(sections)的信息,如节的名称、类型、属性、内存地址、文件偏移、大小等。
节是ELF文件中存储数据的基本单位,例如代码、数据、符号表、重定位信息等。
程序头表 (Program Header Table):
描述文件中的各个段(segments),这些段在程序加载到内存时会被映射到虚拟内存中。
包含段的类型、属性、文件偏移、虚拟地址、物理地址、大小等信息。
ELF文件的结构细节:
ELF头:
魔数:用于识别文件是否为ELF格式,以及文件的字节序(大端或小端)。
文件类型:指示文件是可执行文件、共享对象还是核心转储等。
目标架构:指明文件适用的CPU架构,如x86、ARM等。
入口点地址:程序的执行入口点。
节头表和程序头表的偏移量、大小和数量:用于在文件中定位这些表的具体位置。
节头表:
节名称:节的名称,通常是一个字符串。
节类型:指示节的类型,如代码节、数据节等。
属性:节的访问权限(读、写、执行)。
内存地址和文件偏移:节在内存和文件中的起始位置。
节大小:节的长度。
链接信息:与其他节的链接关系。
地址对齐:节的对齐要求。
表项大小:如果节包含表项,其大小信息。
程序头表:
段类型:指示段的类型,如加载时需要映射到内存的段。
属性:段的访问权限和是否需要加载到内存。
文件偏移和虚拟地址:段在文件中的位置和加载到内存后的地址。
物理地址:段在物理内存中的地址(通常与虚拟地址相同)。
文件大小和内存大小:段在文件和内存中的大小。
对齐:段的对齐要求。
首先来看一段c代码
#include <stdio.h>
unsigned int test=2;
void para_add()
{
test+=1;
printf("test=%d\n",test);
}
void main()
{
int i=1;
printf("i=%d\n",i);
para_add();
}
执行gcc -o test test.c后会生成ELF的目标文件test
执行file test可以看到test文件的属性
用readelf -h test命令可以看到test文件的头信息
如果用objdump -x test 可以看到test中section中的具体信息
现在我们用python的pyelftools库来解析这个文件。代码如下
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
def parse_elf_file(elf_file_path):
# 打开ELF文件
print(elf_file_path)
with open(elf_file_path, 'rb') as f:
elffile = ELFFile(f)
# 检查是否是ELF文件
if not elffile:
print('This is not a valid ELF file.')
return
# 获取ELF头部信息
header = elffile.header
# 打印ELF文件的头部信息
print(' Magic:', header.e_ident['EI_MAG'])
print(' Class:', '64-bit' if header.e_ident['EI_CLASS'] == 3 else '32-bit')
print(' Data:', '2\'s complement, little-endian' if header.e_ident['EI_DATA'] == 1 else 'not little-endian')
print(' Entry point address:', hex(header.e_entry))
print(' Section header offset:', hex(header.e_shoff))
print(' Section header size:', header.e_shentsize)
print(' Number of section headers:', header.e_shnum)
print(' version:',header.e_version)
print(' Section header string table index:', header.e_shstrndx)
# 遍历节(section)头
for section in elffile.iter_sections():
if isinstance(section, SymbolTableSection):
# 如果是符号表节,则打印符号信息
print('\nSymbol Table Section:', section.name)
for symbol in section.iter_symbols():
# print(dir(symbol.entry),symbol.entry.st_info)
pass
print(' Symbol name:', symbol.entry.st_name)
print(' Symbol binding:', 'global' if symbol.entry.st_info['bind'] == 0 else 'local')
print(' Symbol type:', 'function' if symbol.entry.st_info['type'] == 2 else 'object')
print(' Symbol section index:', symbol.entry.st_shndx)
print(' Symbol value:', hex(symbol.entry.st_value))
print(' Symbol size:', hex(symbol.entry.st_size))
# 替换为你的ELF文件路径
elf_file_path = 'home/linux_network/test'
parse_elf_file(elf_file_path)
执行结果如下。首先是header部分的解析。和前面命令的结果也能对上。然后是26个section的信息统计