const std = @import("std"); // Cool website for ELF info: // https://www.man7.org/linux/man-pages/man5/elf.5.html pub const ELFError = error{ InvalidMagic, InvalidClass, InvalidByteOrder, InvalidIdentVersion, InvalidVersion, NotExecutable, NotPowerPC, NoEntrypoint, MissingProgramHeader, InvalidProgramHeaderEntrySize, TextSegmentInvalidMemorySize, TooManyTextSegments, TooManyDataSegments, }; const ELFMagic = "\x7fELF"; const ELFClass_32bit = 1; const ELFDataFormat_BigEndian = 2; const ELFVersion_Current = 1; const ELFType_Executable = 2; const ELFMachine_PowerPC = 20; const MaximumTextSegments = 7; const MaximumDataSegments = 11; const PSFlags_Executable = 1; const PSFlags_Writable = 2; const PSFlags_Readable = 4; const ELFHeader = extern struct { e_ident: [16]u8, e_type: u16, e_machine: u16, e_version: u32, e_entry: u32, e_phoff: u32, e_shoff: u32, e_flags: u32, e_ehsize: u16, e_phentsize: u16, e_phnum: u16, e_shentsize: u16, e_shnum: u16, e_shstrndx: u16, }; const ELFProgramHeader = extern struct { p_type: u32, p_offset: u32, p_vaddr: u32, p_paddr: u32, p_filesz: u32, p_memsz: u32, p_flags: u32, p_align: u32, }; pub const DolHeader = extern struct { text_off: [7]u32, data_off: [11]u32, text_addr: [7]u32, data_addr: [11]u32, text_size: [7]u32, data_size: [11]u32, bss_addr: u32, bss_size: u32, entry: u32, pad: [7]u32, }; pub const DolMap = struct { header: DolHeader, text_cnt: u32, data_cnt: u32, text_elf_off: [7]u32, data_elf_off: [11]u32, flags: u32, }; const DolHasBSS = 1; pub fn readELF(file: std.fs.File) !DolMap { // Create dol map var dolMap = DolMap{ .header = .{ .text_off = .{0} ** 7, .data_off = .{0} ** 11, .text_addr = .{0} ** 7, .data_addr = .{0} ** 11, .text_size = .{0} ** 7, .data_size = .{0} ** 11, .bss_addr = 0, .bss_size = 0, .entry = 0, .pad = .{0} ** 7, }, .text_cnt = 0, .data_cnt = 0, .text_elf_off = undefined, .data_elf_off = undefined, .flags = 0, }; // Read header const header = try file.reader().readStructEndian(ELFHeader, std.builtin.Endian.big); try checkELFHeader(header); // Get entry point dolMap.header.entry = header.e_entry; // Read program headers const phnum = header.e_phnum; const phoff = header.e_phoff; // Sanity checks if (phnum == 0 or phoff == 0) { return ELFError.MissingProgramHeader; } if (header.e_phentsize != @sizeOf(ELFProgramHeader)) { return ELFError.InvalidProgramHeaderEntrySize; } // Read program headers try file.seekTo(phoff); for (0..phnum) |_| { const programHeader = try file.reader().readStructEndian(ELFProgramHeader, std.builtin.Endian.big); // Skip non-loadable segments if (programHeader.p_type != 1) { std.debug.print("Skipping non-loadable segment at 0x{x}\n", .{programHeader.p_vaddr}); continue; } // Skip empty segments if (programHeader.p_memsz == 0) { std.debug.print("Skipping empty segment at 0x{x}\n", .{programHeader.p_vaddr}); continue; } // Check if segment is readable if (programHeader.p_flags & PSFlags_Readable == 0) { std.debug.print("Warning: non-readable segment at 0x{x}\n", .{programHeader.p_vaddr}); } // If the segment is executable, it's a TEXT segment if (programHeader.p_flags & PSFlags_Executable != 0) { // Do we have too many text segments? if (dolMap.text_cnt >= MaximumTextSegments) { return ELFError.TooManyTextSegments; } // Check if segment is writable if (programHeader.p_flags & PSFlags_Writable != 0) { std.debug.print("Warning: segment at 0x{x} is both executable and writable\n", .{programHeader.p_vaddr}); } // Check if segment has valid memory size if (programHeader.p_filesz > programHeader.p_memsz) { return ELFError.TextSegmentInvalidMemorySize; } // Check if there's leftover space if (programHeader.p_filesz < programHeader.p_memsz) { // Add as BSS segment of whatever is left between the file and memory sizes // TODO: why?! add_bss(&dolMap, programHeader.p_paddr + programHeader.p_filesz, programHeader.p_memsz - programHeader.p_filesz); std.debug.print("Found bss segment at 0x{x}\n", .{programHeader.p_paddr + programHeader.p_filesz}); } std.debug.print("Found text segment at 0x{x}\n", .{programHeader.p_vaddr}); dolMap.header.text_addr[dolMap.text_cnt] = programHeader.p_paddr; dolMap.header.text_size[dolMap.text_cnt] = programHeader.p_filesz; dolMap.text_elf_off[dolMap.text_cnt] = programHeader.p_offset; dolMap.text_cnt += 1; } else { // DATA or BSS segment // TODO: ???? if (programHeader.p_filesz == 0) { add_bss(&dolMap, programHeader.p_paddr, programHeader.p_memsz); std.debug.print("Found bss segment at 0x{x}\n", .{programHeader.p_vaddr}); continue; } // Do we have too many data segments? if (dolMap.data_cnt >= MaximumDataSegments) { return ELFError.TooManyDataSegments; } std.debug.print("Found data segment at 0x{x}\n", .{programHeader.p_vaddr}); dolMap.header.data_addr[dolMap.data_cnt] = programHeader.p_paddr; dolMap.header.data_size[dolMap.data_cnt] = programHeader.p_filesz; dolMap.data_elf_off[dolMap.data_cnt] = programHeader.p_offset; dolMap.data_cnt += 1; } } return dolMap; } // I don't understand what this does fn add_bss(map: *DolMap, addr: u32, size: u32) void { if (map.flags & DolHasBSS != 0) { const originalAddr = map.header.bss_addr; const originalSize = map.header.bss_size; if ((originalAddr + originalSize) == addr) { map.header.bss_size = originalSize + size; } } else { map.header.bss_addr = addr; map.header.bss_size = size; map.flags |= DolHasBSS; } } fn checkELFHeader(header: ELFHeader) !void { // Check magic if (!std.mem.eql(u8, header.e_ident[0..4], ELFMagic)) { return ELFError.InvalidMagic; } // Check class if (header.e_ident[4] != ELFClass_32bit) { return ELFError.InvalidClass; } // Check byte order if (header.e_ident[5] != ELFDataFormat_BigEndian) { return ELFError.InvalidByteOrder; } // Check ident version if (header.e_ident[6] != ELFVersion_Current) { return ELFError.InvalidIdentVersion; } // Check version if (header.e_version != ELFVersion_Current) { return ELFError.InvalidVersion; } // Check type if (header.e_type != ELFType_Executable) { return ELFError.NotExecutable; } // Check machine if (header.e_machine != ELFMachine_PowerPC) { return ELFError.NotPowerPC; } // Check entry point if (header.e_entry == 0) { return ELFError.NoEntrypoint; } }