Skip to content

Commit

Permalink
Very much WIP base implementation for ziglang#721.
Browse files Browse the repository at this point in the history
Currently does:
- read COFF executable file
- locate and load corresponding .pdb file
- expose .pdb content as streams (PDB format)
  • Loading branch information
Sahnvour committed Jul 21, 2018
1 parent f47655e commit 2ec9a11
Show file tree
Hide file tree
Showing 7 changed files with 563 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Expand Up @@ -427,6 +427,7 @@ set(ZIG_STD_FILES
"c/index.zig"
"c/linux.zig"
"c/windows.zig"
"coff.zig"
"crypto/blake2.zig"
"crypto/hmac.zig"
"crypto/index.zig"
Expand Down Expand Up @@ -544,6 +545,7 @@ set(ZIG_STD_FILES
"os/windows/index.zig"
"os/windows/util.zig"
"os/zen.zig"
"pdb.zig"
"rand/index.zig"
"rand/ziggurat.zig"
"segmented_list.zig"
Expand Down
238 changes: 238 additions & 0 deletions std/coff.zig
@@ -0,0 +1,238 @@
const builtin = @import("builtin");
const std = @import("index.zig");
const io = std.io;
const mem = std.mem;
const os = std.os;

const ArrayList = std.ArrayList;

// CoffHeader.machine values
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx
const IMAGE_FILE_MACHINE_I386 = 0x014c;
const IMAGE_FILE_MACHINE_IA64 = 0x0200;
const IMAGE_FILE_MACHINE_AMD64 = 0x8664;

// 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;
const DEBUG_DIRECTORY = 6;

pub const CoffError = error {
InvalidPEMagic,
InvalidPEHeader,
InvalidMachine,
MissingCoffSection,
};

pub const Coff = struct {
in_file: os.File,
allocator: *mem.Allocator,

coff_header: CoffHeader,
pe_header: OptionalHeader,
sections: ArrayList(Section),

guid: [16]u8,
age: u32,

pub fn loadHeader(self: *Coff) !void {
const pe_pointer_offset = 0x3C;

var file_stream = io.FileInStream.init(&self.in_file);
const in = &file_stream.stream;

var magic: [2]u8 = undefined;
try in.readNoEof(magic[0..]);
if (!mem.eql(u8, magic, "MZ"))
return error.InvalidPEMagic;

// Seek to PE File Header (coff header)
try self.in_file.seekTo(pe_pointer_offset);
const pe_magic_offset = try in.readIntLe(u32);
try self.in_file.seekTo(pe_magic_offset);

var pe_header_magic: [4]u8 = undefined;
try in.readNoEof(pe_header_magic[0..]);
if (!mem.eql(u8, pe_header_magic, []u8{'P', 'E', 0, 0}))
return error.InvalidPEHeader;

self.coff_header = CoffHeader {
.machine = try in.readIntLe(u16),
.number_of_sections = try in.readIntLe(u16),
.timedate_stamp = try in.readIntLe(u32),
.pointer_to_symbol_table = try in.readIntLe(u32),
.number_of_symbols = try in.readIntLe(u32),
.size_of_optional_header = try in.readIntLe(u16),
.characteristics = try in.readIntLe(u16),
};

switch (self.coff_header.machine) {
IMAGE_FILE_MACHINE_I386,
IMAGE_FILE_MACHINE_AMD64,
IMAGE_FILE_MACHINE_IA64
=> {},
else => return error.InvalidMachine,
}

try self.loadOptionalHeader(&file_stream);
}

fn loadOptionalHeader(self: *Coff, file_stream: *io.FileInStream) !void {
const in = &file_stream.stream;
self.pe_header.magic = try in.readIntLe(u16);
std.debug.warn("reading pe optional\n");
// 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);
}
else if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 12 * @sizeOf(u32) + 5 * @sizeOf(u64);
}
else
return error.InvalidPEMagic;

std.debug.warn("skipping {}\n", skip_size);
try self.in_file.seekForward(skip_size);

const number_of_rva_and_sizes = try in.readIntLe(u32);
//std.debug.warn("indicating {} data dirs\n", number_of_rva_and_sizes);
if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
return error.InvalidPEHeader;

for (self.pe_header.data_directory) |*data_dir| {
data_dir.* = OptionalHeader.DataDirectory {
.virtual_address = try in.readIntLe(u32),
.size = try in.readIntLe(u32),
};
//std.debug.warn("data_dir @ {x}, size {}\n", data_dir.virtual_address, data_dir.size);
}
std.debug.warn("loaded data directories\n");
}

pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
try self.loadSections();
const header = (self.getSection(".rdata") orelse return error.MissingCoffSection).header;

// The linker puts a chunk that contains the .pdb path right after the
// debug_directory.
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;
std.debug.warn("file offset {x}\n", file_offset);
try self.in_file.seekTo(file_offset + debug_dir.size);

var file_stream = io.FileInStream.init(&self.in_file);
const in = &file_stream.stream;

var cv_signature: [4]u8 = undefined; // CodeView signature
try in.readNoEof(cv_signature[0..]);
// 'RSDS' indicates PDB70 format, used by lld.
if (!mem.eql(u8, cv_signature, "RSDS"))
return error.InvalidPEMagic;
std.debug.warn("cv_signature {}\n", cv_signature);
try in.readNoEof(self.guid[0..]);
self.age = try in.readIntLe(u32);

// Finally read the null-terminated string.
var byte = try in.readByte();
var i: usize = 0;
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;

return i;
}

pub fn loadSections(self: *Coff) !void {
if (self.sections.len != 0)
return;

self.sections = ArrayList(Section).init(self.allocator);

var file_stream = io.FileInStream.init(&self.in_file);
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..]);
try self.sections.append(Section {
.header = SectionHeader {
.name = name,
.misc = SectionHeader.Misc { .physical_address = try in.readIntLe(u32) },
.virtual_address = try in.readIntLe(u32),
.size_of_raw_data = try in.readIntLe(u32),
.pointer_to_raw_data = try in.readIntLe(u32),
.pointer_to_relocations = try in.readIntLe(u32),
.pointer_to_line_numbers = try in.readIntLe(u32),
.number_of_relocations = try in.readIntLe(u16),
.number_of_line_numbers = try in.readIntLe(u16),
.characteristics = try in.readIntLe(u32),
},
});
}
std.debug.warn("loaded {} sections\n", self.coff_header.number_of_sections);
}

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;
}

};

const CoffHeader = struct {
machine: u16,
number_of_sections: u16,
timedate_stamp: u32,
pointer_to_symbol_table: u32,
number_of_symbols: u32,
size_of_optional_header: u16,
characteristics: u16
};

const OptionalHeader = struct {
const DataDirectory = struct {
virtual_address: u32,
size: u32
};

magic: u16,
data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory,
};

const Section = struct {
header: SectionHeader,
};

const SectionHeader = struct {
const Misc = union {
physical_address: u32,
virtual_size: u32
};

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,
};
47 changes: 45 additions & 2 deletions std/debug/index.zig
Expand Up @@ -6,6 +6,9 @@ const os = std.os;
const elf = std.elf;
const DW = std.dwarf;
const macho = std.macho;
const coff = std.coff;
const pdb = std.pdb;
const windows = os.windows;
const ArrayList = std.ArrayList;
const builtin = @import("builtin");

Expand Down Expand Up @@ -197,7 +200,13 @@ fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address: us
const ptr_hex = "0x{x}";

switch (builtin.os) {
builtin.Os.windows => return error.UnsupportedDebugInfo,
builtin.Os.windows => {
const base_address = @ptrToInt(windows.GetModuleHandleA(null)); // returned HMODULE points to our executable file in memory
const relative_address = address - base_address;
std.debug.warn("{x} - {x} => {x}\n", address, base_address, relative_address);
try debug_info.pdb.getSourceLine(relative_address);
return error.UnsupportedDebugInfo;
},
builtin.Os.macosx => {
// TODO(bnoordhuis) It's theoretically possible to obtain the
// compilation unit from the symbtab but it's not that useful
Expand Down Expand Up @@ -288,7 +297,38 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !*ElfStackTrace {
return st;
},
builtin.ObjectFormat.coff => {
return error.TodoSupportCoffDebugInfo;
var coff_file: coff.Coff = undefined;
coff_file.in_file = try os.openSelfExe();
coff_file.allocator = allocator;
defer coff_file.in_file.close();

try coff_file.loadHeader();

var path: [windows.MAX_PATH]u8 = undefined;
const len = try coff_file.getPdbPath(path[0..]);
std.debug.warn("pdb path {}\n", path[0..len]);

const st = try allocator.create(ElfStackTrace);
errdefer allocator.destroy(st);
st.* = ElfStackTrace {
.pdb = undefined,
};

try st.pdb.openFile(allocator, path[0..len]);

var pdb_stream = st.pdb.getStream(pdb.StreamType.Pdb) orelse return error.CorruptedFile;
std.debug.warn("pdb real filepos {}\n", pdb_stream.getFilePos());
const version = try pdb_stream.stream.readIntLe(u32);
const signature = try pdb_stream.stream.readIntLe(u32);
const age = try pdb_stream.stream.readIntLe(u32);
var guid: [16]u8 = undefined;
try pdb_stream.stream.readNoEof(guid[0..]);
if (!mem.eql(u8, coff_file.guid, guid) or coff_file.age != age)
return error.CorruptedFile;
std.debug.warn("v {} s {} a {}\n", version, signature, age);
// We validated the executable and pdb match.

return st;
},
builtin.ObjectFormat.wasm => {
return error.TodoSupportCOFFDebugInfo;
Expand Down Expand Up @@ -339,6 +379,9 @@ pub const ElfStackTrace = switch (builtin.os) {
self.symbol_table.deinit();
}
},
builtin.Os.windows => struct {
pdb: pdb.Pdb,
},
else => struct {
self_exe_file: os.File,
elf: elf.Elf,
Expand Down
4 changes: 4 additions & 0 deletions std/index.zig
Expand Up @@ -13,6 +13,7 @@ pub const atomic = @import("atomic/index.zig");
pub const base64 = @import("base64.zig");
pub const build = @import("build.zig");
pub const c = @import("c/index.zig");
pub const coff = @import("coff.zig");
pub const crypto = @import("crypto/index.zig");
pub const cstr = @import("cstr.zig");
pub const debug = @import("debug/index.zig");
Expand All @@ -30,6 +31,7 @@ pub const math = @import("math/index.zig");
pub const mem = @import("mem.zig");
pub const net = @import("net.zig");
pub const os = @import("os/index.zig");
pub const pdb = @import("pdb.zig");
pub const rand = @import("rand/index.zig");
pub const sort = @import("sort.zig");
pub const unicode = @import("unicode.zig");
Expand All @@ -49,6 +51,7 @@ test "std" {
_ = @import("base64.zig");
_ = @import("build.zig");
_ = @import("c/index.zig");
_ = @import("coff.zig");
_ = @import("crypto/index.zig");
_ = @import("cstr.zig");
_ = @import("debug/index.zig");
Expand All @@ -67,6 +70,7 @@ test "std" {
_ = @import("heap.zig");
_ = @import("os/index.zig");
_ = @import("rand/index.zig");
_ = @import("pdb.zig");
_ = @import("sort.zig");
_ = @import("unicode.zig");
_ = @import("zig/index.zig");
Expand Down
8 changes: 7 additions & 1 deletion std/os/index.zig
Expand Up @@ -1896,13 +1896,19 @@ pub fn openSelfExe() !os.File {
const self_exe_path = try selfExePath(&fixed_allocator.allocator);
return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
},
Os.windows => {
var fixed_buffer_mem: [windows.MAX_PATH * 2]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
const self_exe_path = try selfExePath(&fixed_allocator.allocator);
return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
},
else => @compileError("Unsupported OS"),
}
}

test "openSelfExe" {
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
Os.linux, Os.macosx, Os.ios, Os.windows => (try openSelfExe()).close(),
else => return, // Unsupported OS.
}
}
Expand Down
2 changes: 2 additions & 0 deletions std/os/windows/index.zig
Expand Up @@ -105,6 +105,8 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
dwFlags: DWORD,
) DWORD;

pub extern "kernel32" stdcallcc fn GetModuleHandleA(lpModuleName: ?LPCSTR) HMODULE;

pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;

pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void;
Expand Down

0 comments on commit 2ec9a11

Please sign in to comment.