Skip to content

Day 84: std.elf模块:ELF格式解析

1. 引言:Linux与Unix的可执行文件

Section titled “1. 引言:Linux与Unix的可执行文件”

ELF(Executable and Linkable Format)是Linux、BSD以及许多其他类Unix操作系统上的标准文件格式。它用于可执行文件、可重定位的对象文件(.o)和共享库(.so)。

与Mach-O类似,理解ELF的结构对于二进制分析、链接器/加载器开发、调试和逆向工程至关重要。Zig的std.elf模块提供了一个全面的、原生的ELF解析器。

每个ELF文件的开头都有一个ELF头部,它描述了文件的基本属性,如体系结构、字节序、文件类型(可执行、共享库等)以及程序头部表和节头部表的位置。

std.elf.parse函数是解析的入口点,它会首先解析这个头部。

const std = @import("std");
const elf = std.elf;
pub fn main() !void {
const allocator = std.testing.allocator;
const file_bytes = try std.fs.cwd().readFileAlloc(allocator, "/bin/ls", 10 * 1024 * 1024);
defer allocator.free(file_bytes);
var file = try elf.parse(file_bytes, allocator, .{});
defer file.deinit();
std.debug.print("ELF Class (32/64-bit): {any}\n", .{file.header.e_class});
std.debug.print("File Type: {any}\n", .{file.header.e_type});
std.debug.print("Machine: {any}\n", .{file.header.e_machine});
}

对于动态链接的可执行文件或共享库,.dynamic节(或PT_DYNAMIC段)包含了至关重要的信息。它列出了文件所依赖的共享库、所需符号的位置以及重定位信息等。

file.dynamic_symbols和file.dynamic_relocations提供了对这些信息的便捷访问。

通过解析.dynamic节中的DT_NEEDED条目,我们可以找出可执行文件依赖的所有共享库。

// ...接上文
// `getNeeded` 是一个便捷函数,用于提取所有 DT_NEEDED 条目
const needed_libs = try file.getNeeded();
std.debug.print("Shared Library Dependencies:\n", .{});
for (needed_libs) |lib| {
std.debug.print(" - {s}\n", .{lib});
}

遍历file.dynamic_relocations,打印出一些重定位条目。每个条目都描述了链接器在加载时需要“修补”的一个内存位置。例如,一个函数调用的地址在编译时是未知的,需要在运行时由动态链接器根据共享库的加载地址来确定。打印出重定位的类型(r_type)和偏移量(r_offset)。

  • 32位 vs. 64位 ELF格式有32位和64位两个变体。std.elf.parse会自动检测并处理这两种变体,file.header.e_class字段会告诉你它是哪一种。

  • 节(Sections) vs. 段(Segments) ELF文件有两种“视图”:链接视图和执行视图。

    • **节头部表(Section Header Table)**定义了所有的节,如.text, .data, .bss。这些节对于链接器很重要。
    • **程序头部表(Program Header Table)**定义了所有的段,如PT_LOAD, PT_DYNAMIC。这些段告诉操作系统如何将文件加载到内存中并执行。一个段通常包含一个或多个节。
  • 调试信息(DWARF) ELF文件通常将调试信息以DWARF格式存储在专门的节中(如.debug_info, .debug_str)。std.elf本身不解析DWARF,但它可以帮助你定位和提取这些节,以便交给专门的DWARF解析库(如std.dwarf)来处理。

ELF是三种主流可执行格式中设计最为灵活和可扩展的一种。它清晰地区分了链接视图和执行视图,并拥有一个强大的动态链接机制。

  • 与Mach-O对比: Mach-O的加载命令比ELF的程序头部更具描述性,但ELF的节处理更为通用。
  • 与PE对比: PE格式与Windows操作系统的加载器紧密耦合,而ELF的设计则更为通用,适用于多种类Unix系统。

通过为ELF、Mach-O和PE提供统一的高质量解析库,Zig将自身定位为构建下一代跨平台开发工具(如链接器、调试器、编译器和安全分析工具)的理想平台。开发者可以用一种语言,就能深入理解和操作所有主流平台上的二进制文件。