struct Coff [src]

Fields

data: []const u8
is_loaded: bool
is_image: bool
coff_header_offset: usize
guid: [16]u8 = undefined
age: u32 = undefined

Members

Source

pub const Coff = struct { data: []const u8, // Set if `data` is backed by the image as loaded by the loader is_loaded: bool, is_image: bool, coff_header_offset: usize, guid: [16]u8 = undefined, age: u32 = undefined, // The lifetime of `data` must be longer than the lifetime of the returned Coff pub fn init(data: []const u8, is_loaded: bool) !Coff { const pe_pointer_offset = 0x3C; const pe_magic = "PE\x00\x00"; var stream = std.io.fixedBufferStream(data); const reader = stream.reader(); try stream.seekTo(pe_pointer_offset); const coff_header_offset = try reader.readInt(u32, .little); try stream.seekTo(coff_header_offset); var buf: [4]u8 = undefined; try reader.readNoEof(&buf); const is_image = mem.eql(u8, pe_magic, &buf); var coff = @This(){ .data = data, .is_image = is_image, .is_loaded = is_loaded, .coff_header_offset = coff_header_offset, }; // Do some basic validation upfront if (is_image) { coff.coff_header_offset = coff.coff_header_offset + 4; const coff_header = coff.getCoffHeader(); if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader; } // JK: we used to check for architecture here and throw an error if not x86 or derivative. // However I am willing to take a leap of faith and let aarch64 have a shot also. return coff; } pub fn getPdbPath(self: *Coff) !?[]const u8 { assert(self.is_image); const data_dirs = self.getDataDirectories(); if (@intFromEnum(DirectoryEntry.DEBUG) >= data_dirs.len) return null; const debug_dir = data_dirs[@intFromEnum(DirectoryEntry.DEBUG)]; var stream = std.io.fixedBufferStream(self.data); const reader = stream.reader(); if (self.is_loaded) { try stream.seekTo(debug_dir.virtual_address); } else { // Find what section the debug_dir is in, in order to convert the RVA to a file offset for (self.getSectionHeaders()) |*sect| { if (debug_dir.virtual_address >= sect.virtual_address and debug_dir.virtual_address < sect.virtual_address + sect.virtual_size) { try stream.seekTo(sect.pointer_to_raw_data + (debug_dir.virtual_address - sect.virtual_address)); break; } } else return error.InvalidDebugDirectory; } // Find the correct DebugDirectoryEntry, and where its data is stored. // It can be in any section. const debug_dir_entry_count = debug_dir.size / @sizeOf(DebugDirectoryEntry); var i: u32 = 0; while (i < debug_dir_entry_count) : (i += 1) { const debug_dir_entry = try reader.readStruct(DebugDirectoryEntry); if (debug_dir_entry.type == .CODEVIEW) { const dir_offset = if (self.is_loaded) debug_dir_entry.address_of_raw_data else debug_dir_entry.pointer_to_raw_data; try stream.seekTo(dir_offset); break; } } else return null; var cv_signature: [4]u8 = undefined; // CodeView signature try reader.readNoEof(cv_signature[0..]); // 'RSDS' indicates PDB70 format, used by lld. if (!mem.eql(u8, &cv_signature, "RSDS")) return error.InvalidPEMagic; try reader.readNoEof(self.guid[0..]); self.age = try reader.readInt(u32, .little); // Finally read the null-terminated string. const start = reader.context.pos; const len = std.mem.indexOfScalar(u8, self.data[start..], 0) orelse return null; return self.data[start .. start + len]; } pub fn getCoffHeader(self: Coff) CoffHeader { return @as(*align(1) const CoffHeader, @ptrCast(self.data[self.coff_header_offset..][0..@sizeOf(CoffHeader)])).*; } pub fn getOptionalHeader(self: Coff) OptionalHeader { assert(self.is_image); const offset = self.coff_header_offset + @sizeOf(CoffHeader); return @as(*align(1) const OptionalHeader, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeader)])).*; } pub fn getOptionalHeader32(self: Coff) OptionalHeaderPE32 { assert(self.is_image); const offset = self.coff_header_offset + @sizeOf(CoffHeader); return @as(*align(1) const OptionalHeaderPE32, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeaderPE32)])).*; } pub fn getOptionalHeader64(self: Coff) OptionalHeaderPE64 { assert(self.is_image); const offset = self.coff_header_offset + @sizeOf(CoffHeader); return @as(*align(1) const OptionalHeaderPE64, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeaderPE64)])).*; } pub fn getImageBase(self: Coff) u64 { const hdr = self.getOptionalHeader(); return switch (hdr.magic) { IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().image_base, IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().image_base, else => unreachable, // We assume we have validated the header already }; } pub fn getNumberOfDataDirectories(self: Coff) u32 { const hdr = self.getOptionalHeader(); return switch (hdr.magic) { IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().number_of_rva_and_sizes, IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().number_of_rva_and_sizes, else => unreachable, // We assume we have validated the header already }; } pub fn getDataDirectories(self: *const Coff) []align(1) const ImageDataDirectory { const hdr = self.getOptionalHeader(); const size: usize = switch (hdr.magic) { IMAGE_NT_OPTIONAL_HDR32_MAGIC => @sizeOf(OptionalHeaderPE32), IMAGE_NT_OPTIONAL_HDR64_MAGIC => @sizeOf(OptionalHeaderPE64), else => unreachable, // We assume we have validated the header already }; const offset = self.coff_header_offset + @sizeOf(CoffHeader) + size; return @as([*]align(1) const ImageDataDirectory, @ptrCast(self.data[offset..]))[0..self.getNumberOfDataDirectories()]; } pub fn getSymtab(self: *const Coff) ?Symtab { const coff_header = self.getCoffHeader(); if (coff_header.pointer_to_symbol_table == 0) return null; const offset = coff_header.pointer_to_symbol_table; const size = coff_header.number_of_symbols * Symbol.sizeOf(); return .{ .buffer = self.data[offset..][0..size] }; } pub fn getStrtab(self: *const Coff) error{InvalidStrtabSize}!?Strtab { const coff_header = self.getCoffHeader(); if (coff_header.pointer_to_symbol_table == 0) return null; const offset = coff_header.pointer_to_symbol_table + Symbol.sizeOf() * coff_header.number_of_symbols; const size = mem.readInt(u32, self.data[offset..][0..4], .little); if ((offset + size) > self.data.len) return error.InvalidStrtabSize; return Strtab{ .buffer = self.data[offset..][0..size] }; } pub fn strtabRequired(self: *const Coff) bool { for (self.getSectionHeaders()) |*sect_hdr| if (sect_hdr.getName() == null) return true; return false; } pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader { const coff_header = self.getCoffHeader(); const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header; return @as([*]align(1) const SectionHeader, @ptrCast(self.data.ptr + offset))[0..coff_header.number_of_sections]; } pub fn getSectionHeadersAlloc(self: *const Coff, allocator: mem.Allocator) ![]SectionHeader { const section_headers = self.getSectionHeaders(); const out_buff = try allocator.alloc(SectionHeader, section_headers.len); for (out_buff, 0..) |*section_header, i| { section_header.* = section_headers[i]; } return out_buff; } pub fn getSectionName(self: *const Coff, sect_hdr: *align(1) const SectionHeader) error{InvalidStrtabSize}![]const u8 { const name = sect_hdr.getName() orelse blk: { const strtab = (try self.getStrtab()).?; const name_offset = sect_hdr.getNameOffset().?; break :blk strtab.get(name_offset); }; return name; } pub fn getSectionByName(self: *const Coff, comptime name: []const u8) ?*align(1) const SectionHeader { for (self.getSectionHeaders()) |*sect| { const section_name = self.getSectionName(sect) catch |e| switch (e) { error.InvalidStrtabSize => continue, //ignore invalid(?) strtab entries - see also GitHub issue #15238 }; if (mem.eql(u8, section_name, name)) { return sect; } } return null; } pub fn getSectionData(self: *const Coff, sec: *align(1) const SectionHeader) []const u8 { const offset = if (self.is_loaded) sec.virtual_address else sec.pointer_to_raw_data; return self.data[offset..][0..sec.virtual_size]; } pub fn getSectionDataAlloc(self: *const Coff, sec: *align(1) const SectionHeader, allocator: mem.Allocator) ![]u8 { const section_data = self.getSectionData(sec); return allocator.dupe(u8, section_data); } }