为什么我不能像 C++ 中的 Little Endian 文件一样读取 Big Endian ELF 文件?

Why can I not read Big Endian ELF files the same way as Little Endian files in C++?

本质上,我正在做类似于 https://wiki.osdev.org/ELF_Tutorial 的事情,我将数据加载到结构中并通过偏移量读取各个部分。主机是小端,我正在尝试分析为大端目标交叉编译的文件。我尝试对这些大端文件执行与小端文件相同的代码序列,但是在尝试访问这些部分时代码出现段错误。

int fd = open(filename, O_RDONLY);
char *header_start = (char *)mmap(0, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
Elf32_Ehdr* elf_ehdr = (Elf32_Ehdr *)header_start;
Elf32_Shdr* elf_shdrs = (Elf32_Shdr *)((int)header_start + elf_ehdr->e_shoff);
Elf32_Shdr* sh_strtab = &elf_shdrs[elf_ehdr->e_shstrndx];
// code segfaults here when trying to access sh_strtab->sh_offset for big endian
// files, but works just fine for little endian files

为什么大端文件的代码会失败?

在大端文件中,elf_ehdr->e_shoff 将是大端整数,需要遵守大端字节顺序。

假设我们处理的是 32 位,e_shoff 是一个不错的小数字,例如 64。在大端中,它将作为 0x00000040 记录在文件中。但是你正在阅读这个文件似乎是一个小字节序 CPU,所以 0x00000040 被作为二进制 blob 从文件中读出并且将被 CPU 解释为 1073741824。

Elf32_Shdr* elf_shdrs = (Elf32_Shdr *)((int)header_start + elf_ehdr->e_shoff);

解析为

Elf32_Shdr* elf_shdrs = (Elf32_Shdr *)((int)header_start + 1073741824);

没有

Elf32_Shdr* elf_shdrs = (Elf32_Shdr *)((int)header_start + 64);

并且将大大偏离目标。尝试访问结果 elf_shdrs 的成员会陷入未定义的行为。

快速修复是

Elf32_Shdr* elf_shdrs = (Elf32_Shdr *)(header_start + ResolveEndian(elf_ehdr->e_shoff));

其中 ResolveEndian 是一系列重载函数,它们要么完全不执行任何操作,因为文件字节序与系统字节序匹配,要么翻转字节顺序。有关如何执行此操作的许多示例,请参阅 How do I convert between big-endian and little-endian values in C++?

较长的修复不会使用内存映射文件,而是 deserialize the file 考虑 32 位和 64 位程序以及端序之间的变量大小差异(以及由此产生的偏移差异)。这将产生一个更健壮和可移植的解析器,无论源 ELF 和用于构建解析器的编译器实现如何,它始终可以工作。