Start working on PE/COFF linking.

This commit is contained in:
Alexandros Naskos 2020-08-27 13:28:54 +03:00
parent 88724b2a89
commit fac9a4e286
4 changed files with 256 additions and 2 deletions

View File

@ -1820,6 +1820,9 @@ fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void {
try self.markOutdatedDecl(decl);
decl.contents_hash = contents_hash;
} else switch (self.bin_file.tag) {
.coff => {
// TODO Implement for COFF
},
.elf => if (decl.fn_link.elf.len != 0) {
// TODO Look into detecting when this would be unnecessary by storing enough state
// in `Decl` to notice that the line number did not change.
@ -2078,12 +2081,14 @@ fn allocateNewDecl(
.deletion_flag = false,
.contents_hash = contents_hash,
.link = switch (self.bin_file.tag) {
.coff => .{ .coff = {} }, // @TODO
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
.c => .{ .c = {} },
.wasm => .{ .wasm = {} },
},
.fn_link = switch (self.bin_file.tag) {
.coff => .{ .coff = {} }, // @TODO
.elf => .{ .elf = link.File.Elf.SrcFn.empty },
.macho => .{ .macho = link.File.MachO.SrcFn.empty },
.c => .{ .c = {} },

View File

@ -34,6 +34,7 @@ pub const File = struct {
pub const LinkBlock = union {
elf: Elf.TextBlock,
coff: void, // @TODO
macho: MachO.TextBlock,
c: void,
wasm: void,
@ -41,6 +42,7 @@ pub const File = struct {
pub const LinkFn = union {
elf: Elf.SrcFn,
coff: void, // @TODO
macho: MachO.SrcFn,
c: void,
wasm: ?Wasm.FnData,
@ -66,7 +68,7 @@ pub const File = struct {
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
switch (options.object_format) {
.unknown => unreachable,
.coff => return error.TODOImplementCoff,
.coff => return Coff.openPath(allocator, dir, sub_path, options),
.elf => return Elf.openPath(allocator, dir, sub_path, options),
.macho => return MachO.openPath(allocator, dir, sub_path, options),
.wasm => return Wasm.openPath(allocator, dir, sub_path, options),
@ -85,7 +87,7 @@ pub const File = struct {
pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
switch (base.tag) {
.elf, .macho => {
.coff, .elf, .macho => {
if (base.file != null) return;
base.file = try dir.createFile(sub_path, .{
.truncate = false,
@ -112,6 +114,7 @@ pub const File = struct {
/// after allocateDeclIndexes for any given Decl.
pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
@ -121,6 +124,7 @@ pub const File = struct {
pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
.c, .wasm => {},
@ -131,6 +135,7 @@ pub const File = struct {
/// any given Decl.
pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
.c, .wasm => {},
@ -140,6 +145,7 @@ pub const File = struct {
pub fn deinit(base: *File) void {
if (base.file) |f| f.close();
switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).deinit(),
.elf => @fieldParentPtr(Elf, "base", base).deinit(),
.macho => @fieldParentPtr(MachO, "base", base).deinit(),
.c => @fieldParentPtr(C, "base", base).deinit(),
@ -149,6 +155,11 @@ pub const File = struct {
pub fn destroy(base: *File) void {
switch (base.tag) {
.coff => {
const parent = @fieldParentPtr(Coff, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
.elf => {
const parent = @fieldParentPtr(Elf, "base", base);
parent.deinit();
@ -177,6 +188,7 @@ pub const File = struct {
defer tracy.end();
try switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).flush(module),
.elf => @fieldParentPtr(Elf, "base", base).flush(module),
.macho => @fieldParentPtr(MachO, "base", base).flush(module),
.c => @fieldParentPtr(C, "base", base).flush(module),
@ -186,6 +198,7 @@ pub const File = struct {
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
.macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
.c => unreachable,
@ -195,6 +208,7 @@ pub const File = struct {
pub fn errorFlags(base: *File) ErrorFlags {
return switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).error_flags,
.elf => @fieldParentPtr(Elf, "base", base).error_flags,
.macho => @fieldParentPtr(MachO, "base", base).error_flags,
.c => return .{ .no_entry_point_found = false },
@ -211,6 +225,7 @@ pub const File = struct {
exports: []const *Module.Export,
) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
.c => return {},
@ -220,6 +235,7 @@ pub const File = struct {
pub fn getDeclVAddr(base: *File, decl: *const Module.Decl) u64 {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl),
.elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl),
.macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl),
.c => unreachable,
@ -228,6 +244,7 @@ pub const File = struct {
}
pub const Tag = enum {
coff,
elf,
macho,
c,
@ -239,6 +256,7 @@ pub const File = struct {
};
pub const C = @import("link/C.zig");
pub const Coff = @import("link/Coff.zig");
pub const Elf = @import("link/Elf.zig");
pub const MachO = @import("link/MachO.zig");
pub const Wasm = @import("link/Wasm.zig");

View File

@ -0,0 +1,231 @@
const Coff = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const fs = std.fs;
const Module = @import("../Module.zig");
const codegen = @import("../codegen/wasm.zig");
const link = @import("../link.zig");
pub const base_tag: link.File.Tag = .coff;
const msdos_stub = @embedFile("msdos-stub.bin");
const coff_file_header_offset = msdos_stub.len + 4;
const optional_header_offset = coff_file_header_offset + 20;
base: link.File,
ptr_width: enum { p32, p64 },
error_flags: link.File.ErrorFlags = .{},
coff_file_header_dirty: bool = false,
optional_header_dirty: bool = false,
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
assert(options.object_format == .coff);
const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) });
errdefer file.close();
var coff_file = try allocator.create(Coff);
errdefer allocator.destroy(coff_file);
coff_file.* = openFile(allocator, file, options) catch |err| switch (err) {
error.IncrFailed => try createFile(allocator, file, options),
else => |e| return e,
};
return &coff_file.base;
}
/// Returns error.IncrFailed if incremental update could not be performed.
fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Coff {
switch (options.output_mode) {
.Exe => {},
.Obj => return error.IncrFailed, // @TODO DO OBJ FILES
.Lib => return error.IncrFailed,
}
var self: Coff = .{
.base = .{
.file = file,
.tag = .coff,
.options = options,
.allocator = allocator,
},
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedELFArchitecture,
},
};
errdefer self.deinit();
// TODO implement reading the PE/COFF file
return error.IncrFailed;
}
/// Truncates the existing file contents and overwrites the contents.
/// Returns an error if `file` is not already open with +read +write +seek abilities.
fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Coff {
switch (options.output_mode) {
.Exe => {},
.Obj => return error.TODOImplementWritingObjFiles, // @TODO DO OBJ FILES
.Lib => return error.TODOImplementWritingLibFiles,
}
var self: Coff = .{
.base = .{
.tag = .coff,
.options = options,
.allocator = allocator,
.file = file,
},
.ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
32 => .p32,
64 => .p64,
else => return error.UnsupportedCOFFArchitecture,
},
.coff_file_header_dirty = true,
.optional_header_dirty = true,
};
errdefer self.deinit();
var output = self.base.file.?.writer();
// MS-DOS stub + PE magic
try output.writeAll(msdos_stub ++ "PE\x00\x00");
const machine_type: u16 = switch (self.base.options.target.cpu.arch) {
.x86_64 => 0x8664,
.i386 => 0x014c,
.riscv32 => 0x5032,
.riscv64 => 0x5064,
else => return error.UnsupportedCOFFArchitecture,
};
// Start of COFF file header
try output.writeIntLittle(u16, machine_type);
try output.writeIntLittle(u16, switch (self.ptr_width) {
.p32 => @as(u16, 98),
.p64 => 114,
});
try output.writeAll("\x00" ** 14);
// Characteristics - IMAGE_FILE_RELOCS_STRIPPED | IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED
var characteristics: u16 = 0x0001 | 0x000 | 0x02002; // @TODO Remove debug info stripped flag when necessary
switch (self.ptr_width) {
// IMAGE_FILE_32BIT_MACHINE
.p32 => characteristics |= 0x0100,
// IMAGE_FILE_LARGE_ADDRESS_AWARE
.p64 => characteristics |= 0x0020,
}
try output.writeIntLittle(u16, characteristics);
try output.writeIntLittle(u16, switch (self.ptr_width) {
.p32 => @as(u16, 0x10b),
.p64 => 0x20b,
});
// Start of optional header
// TODO Linker version, use 0.0 for now.
try output.writeAll("\x00" ** 2);
// Zero out every field until "BaseOfCode"
// @TODO Actually write entry point address, base of code address
try output.writeAll("\x00" ** 20);
switch (self.ptr_width) {
.p32 => {
// Zero out base of data
try output.writeAll("\x00" ** 4);
// Write image base
try output.writeIntLittle(u32, 0x40000000);
},
.p64 => {
// Write image base
try output.writeIntLittle(u64, 0x40000000);
},
}
// Section alignment - default to 256
try output.writeIntLittle(u32, 256);
// File alignment - default to 512
try output.writeIntLittle(u32, 512);
// TODO - Minimum required windows version - use 6.0 (aka vista for now)
try output.writeIntLittle(u16, 0x6);
try output.writeIntLittle(u16, 0x0);
// TODO - Image version - use 0.0 for now
try output.writeIntLittle(u32, 0x0);
// Subsystem version
try output.writeIntLittle(u16, 0x6);
try output.writeIntLittle(u16, 0x0);
// Reserved zeroes
try output.writeIntLittle(u32, 0x0);
// Size of image - initialize to zero
try output.writeIntLittle(u32, 0x0);
// @TODO Size of headers - calculate this.
try output.writeIntLittle(u32, 0x0);
// Checksum
try output.writeIntLittle(u32, 0x0);
// Subsystem
try output.writeIntLittle(u16, 0x3);
// @TODO Dll characteristics, just using a value from a LLVM produced executable for now.
try output.writeIntLittle(u16, 0x8160);
switch (self.ptr_width) {
.p32 => {
// Stack reserve
try output.writeIntLittle(u32, 0x1000000);
// Stack commit
try output.writeIntLittle(u32, 0x1000);
// Heap reserve
try output.writeIntLittle(u32, 0x100000);
// Heap commit
try output.writeIntLittle(u32, 0x100);
},
.p64 => {
// Stack reserve
try output.writeIntLittle(u64, 0x1000000);
// Stack commit
try output.writeIntLittle(u64, 0x1000);
// Heap reserve
try output.writeIntLittle(u64, 0x100000);
// Heap commit
try output.writeIntLittle(u64, 0x100);
},
}
// Reserved loader flags
try output.writeIntLittle(u32, 0x0);
// Number of RVA + sizes
try output.writeIntLittle(u32, 0x0);
return self;
}
pub fn flush(self: *Coff, module: *Module) !void {
// @TODO Implement this
}
pub fn freeDecl(self: *Coff, decl: *Module.Decl) void {
// @TODO Implement this
}
pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
// @TODO Implement this
}
pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void {
// @TODO Implement this
}
pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void {
// @TODO Implement this
}
pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, exports: []const *Module.Export) !void {
// @TODO Implement this
}
pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 {
// @TODO Implement this
return 0;
}
pub fn deinit(self: *Coff) void {
// @TODO
}

Binary file not shown.