diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 36ddfc4e2a..e302571671 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -50,6 +50,7 @@ text_section_index: ?u16 = null, got_section_index: ?u16 = null, rdata_section_index: ?u16 = null, data_section_index: ?u16 = null, +reloc_section_index: ?u16 = null, locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, @@ -98,11 +99,16 @@ atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, /// with `Decl` `main`, and lives as long as that `Decl`. unnamed_const_atoms: UnnamedConstTable = .{}, -/// A table of relocations indexed by the owning them `TextBlock`. -/// Note that once we refactor `TextBlock`'s lifetime and ownership rules, +/// A table of relocations indexed by the owning them `Atom`. +/// Note that once we refactor `Atom`'s lifetime and ownership rules, /// this will be a table indexed by index into the list of Atoms. relocs: RelocTable = .{}, +/// A table of base relocations indexed by the owning them `Atom`. +/// Note that once we refactor `Atom`'s lifetime and ownership rules, +/// this will be a table indexed by index into the list of Atoms. +base_relocs: BaseRelocationTable = .{}, + pub const Reloc = struct { @"type": enum { got, @@ -117,6 +123,7 @@ pub const Reloc = struct { }; const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc)); +const BaseRelocationTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(u32)); const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom)); const default_file_alignment: u16 = 0x200; @@ -150,7 +157,17 @@ const Section = struct { free_list: std.ArrayListUnmanaged(*Atom) = .{}, }; -pub const PtrWidth = enum { p32, p64 }; +pub const PtrWidth = enum { + p32, + p64, + + fn abiSize(pw: PtrWidth) u4 { + return switch (pw) { + .p32 => 4, + .p64 => 8, + }; + } +}; pub const SrcFn = void; pub const Export = struct { @@ -274,6 +291,14 @@ pub fn deinit(self: *Coff) void { } self.relocs.deinit(gpa); } + + { + var it = self.base_relocs.valueIterator(); + while (it.next()) |relocs| { + relocs.deinit(gpa); + } + self.base_relocs.deinit(gpa); + } } fn populateMissingMetadata(self: *Coff) !void { @@ -307,7 +332,7 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.got_section_index == null) { self.got_section_index = @intCast(u16, self.sections.slice().len); - const file_size = @intCast(u32, self.base.options.symbol_count_hint); + const file_size = @intCast(u32, self.base.options.symbol_count_hint) * self.ptr_width.abiSize(); const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ @@ -378,6 +403,31 @@ fn populateMissingMetadata(self: *Coff) !void { try self.sections.append(gpa, .{ .header = header }); } + if (self.reloc_section_index == null) { + self.reloc_section_index = @intCast(u16, self.sections.slice().len); + const file_size = @intCast(u32, self.base.options.symbol_count_hint) * @sizeOf(coff.BaseRelocation); + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .reloc free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_PURGEABLE = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".reloc"); + try self.sections.append(gpa, .{ .header = header }); + } + if (self.strtab_offset == null) { try self.strtab.buffer.append(gpa, 0); self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1); @@ -605,6 +655,14 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { .prev_vaddr = sym.value, }); + const target_sym = self.getSymbol(target); + switch (target_sym.section_number) { + .UNDEFINED => @panic("TODO generate a binding for undefined GOT target"), + .ABSOLUTE => {}, + .DEBUG => unreachable, // not possible + else => try atom.addBaseRelocation(self, 0), + } + return atom; } @@ -1179,6 +1237,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod try self.resolveRelocs(atom.*); } } + try self.writeBaseRelocations(); if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; @@ -1216,6 +1275,83 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +/// TODO: note if we need to rewrite base relocations by dirtying any of the entries in the global table +/// TODO: note that .ABSOLUTE is used as padding within each block; we could use this fact to do +/// incremental updates and writes into the table instead of doing it all at once +fn writeBaseRelocations(self: *Coff) !void { + const gpa = self.base.allocator; + + var pages = std.AutoHashMap(u32, std.ArrayList(coff.BaseRelocation)).init(gpa); + defer { + var it = pages.valueIterator(); + while (it.next()) |inner| { + inner.deinit(); + } + pages.deinit(); + } + + var it = self.base_relocs.iterator(); + while (it.next()) |entry| { + const atom = entry.key_ptr.*; + const offsets = entry.value_ptr.*; + + for (offsets.items) |offset| { + const sym = atom.getSymbol(self); + const rva = sym.value + offset; + const page = mem.alignBackwardGeneric(u32, rva, self.page_size); + const gop = try pages.getOrPut(page); + if (!gop.found_existing) { + gop.value_ptr.* = std.ArrayList(coff.BaseRelocation).init(gpa); + } + try gop.value_ptr.append(.{ + .offset = @intCast(u12, rva - page), + .@"type" = .DIR64, + }); + } + } + + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + + var pages_it = pages.iterator(); + while (pages_it.next()) |entry| { + // Pad to required 4byte alignment + if (!mem.isAlignedGeneric( + usize, + entry.value_ptr.items.len * @sizeOf(coff.BaseRelocation), + @sizeOf(u32), + )) { + try entry.value_ptr.append(.{ + .offset = 0, + .@"type" = .ABSOLUTE, + }); + } + + const block_size = @intCast( + u32, + entry.value_ptr.items.len * @sizeOf(coff.BaseRelocation) + @sizeOf(coff.BaseRelocationDirectoryEntry), + ); + try buffer.ensureUnusedCapacity(block_size); + buffer.appendSliceAssumeCapacity(mem.asBytes(&coff.BaseRelocationDirectoryEntry{ + .page_rva = entry.key_ptr.*, + .block_size = block_size, + })); + buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(entry.value_ptr.items)); + } + + const header = &self.sections.items(.header)[self.reloc_section_index.?]; + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size = @intCast(u32, buffer.items.len); + assert(needed_size < sect_capacity); // TODO expand .reloc section + + try self.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data); + + self.data_directories[@enumToInt(coff.DirectoryEntry.BASERELOC)] = .{ + .virtual_address = header.virtual_address, + .size = needed_size, + }; +} + fn writeStrtab(self: *Coff) !void { const allocated_size = self.allocatedSize(self.strtab_offset.?); const needed_size = @intCast(u32, self.strtab.len()); @@ -1277,8 +1413,8 @@ fn writeHeader(self: *Coff) !void { writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; const dll_flags: coff.DllFlags = .{ - .HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie), - .DYNAMIC_BASE = 0, + .HIGH_ENTROPY_VA = 1, // TODO do we want to permit non-PIE builds at all? + .DYNAMIC_BASE = 1, .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index 6c085a8f58..a7608d9a34 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -2,6 +2,7 @@ const Atom = @This(); const std = @import("std"); const coff = std.coff; +const log = std.log.scoped(.link); const Allocator = std.mem.Allocator; @@ -100,11 +101,20 @@ pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { pub fn addRelocation(self: *Atom, coff_file: *Coff, reloc: Reloc) !void { const gpa = coff_file.base.allocator; - // TODO causes a segfault on Windows - // log.debug("adding reloc of type {s} to target %{d}", .{ @tagName(reloc.@"type"), reloc.target.sym_index }); + log.debug(" (adding reloc of type {s} to target %{d})", .{ @tagName(reloc.@"type"), reloc.target.sym_index }); const gop = try coff_file.relocs.getOrPut(gpa, self); if (!gop.found_existing) { gop.value_ptr.* = .{}; } try gop.value_ptr.append(gpa, reloc); } + +pub fn addBaseRelocation(self: *Atom, coff_file: *Coff, offset: u32) !void { + const gpa = coff_file.base.allocator; + log.debug(" (adding base relocation at offset 0x{x} in %{d})", .{ offset, self.sym_index }); + const gop = try coff_file.base_relocs.getOrPut(gpa, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, offset); +}