2018-07-21 11:30:11 -07:00
|
|
|
const builtin = @import("builtin");
|
2019-03-02 13:46:04 -08:00
|
|
|
const std = @import("std.zig");
|
2018-07-21 11:30:11 -07:00
|
|
|
const io = std.io;
|
|
|
|
const mem = std.mem;
|
|
|
|
const os = std.os;
|
2019-05-24 19:52:07 -07:00
|
|
|
const File = std.fs.File;
|
2018-07-21 11:30:11 -07:00
|
|
|
|
|
|
|
const ArrayList = std.ArrayList;
|
|
|
|
|
|
|
|
// CoffHeader.machine values
|
|
|
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx
|
2018-09-12 11:26:21 -07:00
|
|
|
const IMAGE_FILE_MACHINE_I386 = 0x014c;
|
|
|
|
const IMAGE_FILE_MACHINE_IA64 = 0x0200;
|
|
|
|
const IMAGE_FILE_MACHINE_AMD64 = 0x8664;
|
2018-07-21 11:30:11 -07:00
|
|
|
|
|
|
|
// OptionalHeader.magic values
|
|
|
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx
|
|
|
|
const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
|
|
|
|
const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
|
|
|
|
|
|
|
|
const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
|
2019-07-28 10:03:36 -07:00
|
|
|
const IMAGE_DEBUG_TYPE_CODEVIEW = 2;
|
2018-07-21 11:30:11 -07:00
|
|
|
const DEBUG_DIRECTORY = 6;
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
pub const CoffError = error{
|
2018-07-21 11:30:11 -07:00
|
|
|
InvalidPEMagic,
|
|
|
|
InvalidPEHeader,
|
|
|
|
InvalidMachine,
|
|
|
|
MissingCoffSection,
|
|
|
|
};
|
|
|
|
|
2019-07-28 10:03:36 -07:00
|
|
|
// Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
|
2018-11-13 05:08:37 -08:00
|
|
|
pub const Coff = struct {
|
2019-05-24 19:52:07 -07:00
|
|
|
in_file: File,
|
2018-07-21 11:30:11 -07:00
|
|
|
allocator: *mem.Allocator,
|
|
|
|
|
|
|
|
coff_header: CoffHeader,
|
|
|
|
pe_header: OptionalHeader,
|
|
|
|
sections: ArrayList(Section),
|
|
|
|
|
|
|
|
guid: [16]u8,
|
|
|
|
age: u32,
|
|
|
|
|
2019-06-29 11:56:23 -07:00
|
|
|
pub fn init(allocator: *mem.Allocator, in_file: File) Coff {
|
|
|
|
return Coff{
|
|
|
|
.in_file = in_file,
|
|
|
|
.allocator = allocator,
|
|
|
|
.coff_header = undefined,
|
|
|
|
.pe_header = undefined,
|
|
|
|
.sections = ArrayList(Section).init(allocator),
|
|
|
|
.guid = undefined,
|
|
|
|
.age = undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-07-21 11:30:11 -07:00
|
|
|
pub fn loadHeader(self: *Coff) !void {
|
|
|
|
const pe_pointer_offset = 0x3C;
|
|
|
|
|
2018-09-30 14:23:42 -07:00
|
|
|
var file_stream = self.in_file.inStream();
|
2018-07-21 11:30:11 -07:00
|
|
|
const in = &file_stream.stream;
|
|
|
|
|
|
|
|
var magic: [2]u8 = undefined;
|
|
|
|
try in.readNoEof(magic[0..]);
|
2019-11-29 18:55:27 -08:00
|
|
|
if (!mem.eql(u8, &magic, "MZ"))
|
2018-07-21 11:30:11 -07:00
|
|
|
return error.InvalidPEMagic;
|
|
|
|
|
|
|
|
// Seek to PE File Header (coff header)
|
|
|
|
try self.in_file.seekTo(pe_pointer_offset);
|
2018-12-12 17:19:46 -08:00
|
|
|
const pe_magic_offset = try in.readIntLittle(u32);
|
2018-07-21 11:30:11 -07:00
|
|
|
try self.in_file.seekTo(pe_magic_offset);
|
|
|
|
|
|
|
|
var pe_header_magic: [4]u8 = undefined;
|
|
|
|
try in.readNoEof(pe_header_magic[0..]);
|
2019-11-29 18:55:27 -08:00
|
|
|
if (!mem.eql(u8, &pe_header_magic, &[_]u8{ 'P', 'E', 0, 0 }))
|
2018-07-21 11:30:11 -07:00
|
|
|
return error.InvalidPEHeader;
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
self.coff_header = CoffHeader{
|
2018-12-12 17:19:46 -08:00
|
|
|
.machine = try in.readIntLittle(u16),
|
|
|
|
.number_of_sections = try in.readIntLittle(u16),
|
|
|
|
.timedate_stamp = try in.readIntLittle(u32),
|
|
|
|
.pointer_to_symbol_table = try in.readIntLittle(u32),
|
|
|
|
.number_of_symbols = try in.readIntLittle(u32),
|
|
|
|
.size_of_optional_header = try in.readIntLittle(u16),
|
|
|
|
.characteristics = try in.readIntLittle(u16),
|
2018-07-21 11:30:11 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
switch (self.coff_header.machine) {
|
2018-09-12 11:26:21 -07:00
|
|
|
IMAGE_FILE_MACHINE_I386, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_IA64 => {},
|
2018-07-21 11:30:11 -07:00
|
|
|
else => return error.InvalidMachine,
|
|
|
|
}
|
|
|
|
|
|
|
|
try self.loadOptionalHeader(&file_stream);
|
|
|
|
}
|
|
|
|
|
2019-05-24 19:52:07 -07:00
|
|
|
fn loadOptionalHeader(self: *Coff, file_stream: *File.InStream) !void {
|
2018-07-21 11:30:11 -07:00
|
|
|
const in = &file_stream.stream;
|
2018-12-12 17:19:46 -08:00
|
|
|
self.pe_header.magic = try in.readIntLittle(u16);
|
2018-07-21 11:30:11 -07:00
|
|
|
// For now we're only interested in finding the reference to the .pdb,
|
|
|
|
// so we'll skip most of this header, which size is different in 32
|
|
|
|
// 64 bits by the way.
|
|
|
|
var skip_size: u16 = undefined;
|
|
|
|
if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
|
|
|
skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 18 * @sizeOf(u32);
|
2018-09-12 11:26:21 -07:00
|
|
|
} else if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
2018-07-21 11:30:11 -07:00
|
|
|
skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 12 * @sizeOf(u32) + 5 * @sizeOf(u64);
|
2018-09-12 11:26:21 -07:00
|
|
|
} else
|
2018-07-21 11:30:11 -07:00
|
|
|
return error.InvalidPEMagic;
|
|
|
|
|
2019-05-24 15:27:18 -07:00
|
|
|
try self.in_file.seekBy(skip_size);
|
2018-07-21 11:30:11 -07:00
|
|
|
|
2018-12-12 17:19:46 -08:00
|
|
|
const number_of_rva_and_sizes = try in.readIntLittle(u32);
|
2018-07-21 11:30:11 -07:00
|
|
|
if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
|
|
|
|
return error.InvalidPEHeader;
|
|
|
|
|
|
|
|
for (self.pe_header.data_directory) |*data_dir| {
|
2018-11-13 05:08:37 -08:00
|
|
|
data_dir.* = OptionalHeader.DataDirectory{
|
2018-12-12 17:19:46 -08:00
|
|
|
.virtual_address = try in.readIntLittle(u32),
|
|
|
|
.size = try in.readIntLittle(u32),
|
2018-07-21 11:30:11 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
|
|
|
|
try self.loadSections();
|
2019-07-28 10:03:36 -07:00
|
|
|
|
2019-07-27 15:50:44 -07:00
|
|
|
const header = blk: {
|
|
|
|
if (self.getSection(".buildid")) |section| {
|
|
|
|
break :blk section.header;
|
|
|
|
} else if (self.getSection(".rdata")) |section| {
|
|
|
|
break :blk section.header;
|
|
|
|
} else {
|
|
|
|
return error.MissingCoffSection;
|
|
|
|
}
|
|
|
|
};
|
2018-07-21 11:30:11 -07:00
|
|
|
|
|
|
|
const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY];
|
|
|
|
const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data;
|
|
|
|
|
2018-09-30 14:23:42 -07:00
|
|
|
var file_stream = self.in_file.inStream();
|
2018-07-21 11:30:11 -07:00
|
|
|
const in = &file_stream.stream;
|
2019-07-28 10:03:36 -07:00
|
|
|
try self.in_file.seekTo(file_offset);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
blk: while (i < debug_dir_entry_count) : (i += 1) {
|
|
|
|
const debug_dir_entry = try in.readStruct(DebugDirectoryEntry);
|
|
|
|
if (debug_dir_entry.type == IMAGE_DEBUG_TYPE_CODEVIEW) {
|
|
|
|
for (self.sections.toSlice()) |*section| {
|
|
|
|
const section_start = section.header.virtual_address;
|
|
|
|
const section_size = section.header.misc.virtual_size;
|
|
|
|
const rva = debug_dir_entry.address_of_raw_data;
|
|
|
|
const offset = rva - section_start;
|
|
|
|
if (section_start <= rva and offset < section_size and debug_dir_entry.size_of_data <= section_size - offset) {
|
|
|
|
try self.in_file.seekTo(section.header.pointer_to_raw_data + offset);
|
|
|
|
break :blk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-21 11:30:11 -07:00
|
|
|
|
|
|
|
var cv_signature: [4]u8 = undefined; // CodeView signature
|
|
|
|
try in.readNoEof(cv_signature[0..]);
|
|
|
|
// 'RSDS' indicates PDB70 format, used by lld.
|
2019-11-29 18:55:27 -08:00
|
|
|
if (!mem.eql(u8, &cv_signature, "RSDS"))
|
2018-07-21 11:30:11 -07:00
|
|
|
return error.InvalidPEMagic;
|
|
|
|
try in.readNoEof(self.guid[0..]);
|
2018-12-12 17:19:46 -08:00
|
|
|
self.age = try in.readIntLittle(u32);
|
2018-07-21 11:30:11 -07:00
|
|
|
|
|
|
|
// Finally read the null-terminated string.
|
|
|
|
var byte = try in.readByte();
|
2019-07-28 10:03:36 -07:00
|
|
|
i = 0;
|
2018-07-21 11:30:11 -07:00
|
|
|
while (byte != 0 and i < buffer.len) : (i += 1) {
|
|
|
|
buffer[i] = byte;
|
|
|
|
byte = try in.readByte();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (byte != 0 and i == buffer.len)
|
|
|
|
return error.NameTooLong;
|
|
|
|
|
2019-11-19 08:02:01 -08:00
|
|
|
return @as(usize, i);
|
2018-07-21 11:30:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn loadSections(self: *Coff) !void {
|
2019-06-28 15:59:34 -07:00
|
|
|
if (self.sections.len == self.coff_header.number_of_sections)
|
2018-07-21 11:30:11 -07:00
|
|
|
return;
|
|
|
|
|
2019-06-28 15:59:34 -07:00
|
|
|
try self.sections.ensureCapacity(self.coff_header.number_of_sections);
|
2018-07-21 11:30:11 -07:00
|
|
|
|
2018-09-30 14:23:42 -07:00
|
|
|
var file_stream = self.in_file.inStream();
|
2018-07-21 11:30:11 -07:00
|
|
|
const in = &file_stream.stream;
|
|
|
|
|
|
|
|
var name: [8]u8 = undefined;
|
|
|
|
|
|
|
|
var i: u16 = 0;
|
|
|
|
while (i < self.coff_header.number_of_sections) : (i += 1) {
|
|
|
|
try in.readNoEof(name[0..]);
|
2018-11-13 05:08:37 -08:00
|
|
|
try self.sections.append(Section{
|
|
|
|
.header = SectionHeader{
|
2018-07-21 11:30:11 -07:00
|
|
|
.name = name,
|
2019-07-28 10:03:36 -07:00
|
|
|
.misc = SectionHeader.Misc{ .virtual_size = try in.readIntLittle(u32) },
|
2018-12-12 17:19:46 -08:00
|
|
|
.virtual_address = try in.readIntLittle(u32),
|
|
|
|
.size_of_raw_data = try in.readIntLittle(u32),
|
|
|
|
.pointer_to_raw_data = try in.readIntLittle(u32),
|
|
|
|
.pointer_to_relocations = try in.readIntLittle(u32),
|
|
|
|
.pointer_to_line_numbers = try in.readIntLittle(u32),
|
|
|
|
.number_of_relocations = try in.readIntLittle(u16),
|
|
|
|
.number_of_line_numbers = try in.readIntLittle(u16),
|
|
|
|
.characteristics = try in.readIntLittle(u32),
|
2018-07-21 11:30:11 -07:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getSection(self: *Coff, comptime name: []const u8) ?*Section {
|
|
|
|
for (self.sections.toSlice()) |*sec| {
|
|
|
|
if (mem.eql(u8, sec.header.name[0..name.len], name)) {
|
|
|
|
return sec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
const CoffHeader = struct {
|
2018-07-21 11:30:11 -07:00
|
|
|
machine: u16,
|
|
|
|
number_of_sections: u16,
|
|
|
|
timedate_stamp: u32,
|
|
|
|
pointer_to_symbol_table: u32,
|
|
|
|
number_of_symbols: u32,
|
|
|
|
size_of_optional_header: u16,
|
2018-09-12 11:26:21 -07:00
|
|
|
characteristics: u16,
|
2018-07-21 11:30:11 -07:00
|
|
|
};
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
const OptionalHeader = struct {
|
|
|
|
const DataDirectory = struct {
|
2018-07-21 11:30:11 -07:00
|
|
|
virtual_address: u32,
|
2018-09-12 11:26:21 -07:00
|
|
|
size: u32,
|
2018-07-21 11:30:11 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
magic: u16,
|
|
|
|
data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory,
|
|
|
|
};
|
|
|
|
|
2019-07-28 10:03:36 -07:00
|
|
|
const DebugDirectoryEntry = packed struct {
|
|
|
|
characteristiccs: u32,
|
|
|
|
time_date_stamp: u32,
|
|
|
|
major_version: u16,
|
|
|
|
minor_version: u16,
|
|
|
|
@"type": u32,
|
|
|
|
size_of_data: u32,
|
|
|
|
address_of_raw_data: u32,
|
|
|
|
pointer_to_raw_data: u32,
|
|
|
|
};
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
pub const Section = struct {
|
2018-07-21 11:30:11 -07:00
|
|
|
header: SectionHeader,
|
|
|
|
};
|
|
|
|
|
2018-11-13 05:08:37 -08:00
|
|
|
const SectionHeader = struct {
|
|
|
|
const Misc = union {
|
2018-07-21 11:30:11 -07:00
|
|
|
physical_address: u32,
|
2018-09-12 11:26:21 -07:00
|
|
|
virtual_size: u32,
|
2018-07-21 11:30:11 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
name: [8]u8,
|
|
|
|
misc: Misc,
|
|
|
|
virtual_address: u32,
|
|
|
|
size_of_raw_data: u32,
|
|
|
|
pointer_to_raw_data: u32,
|
|
|
|
pointer_to_relocations: u32,
|
|
|
|
pointer_to_line_numbers: u32,
|
|
|
|
number_of_relocations: u16,
|
|
|
|
number_of_line_numbers: u16,
|
|
|
|
characteristics: u32,
|
2018-08-29 16:00:24 -07:00
|
|
|
};
|