From 3c10221030c47a68c17b4e037abd8afcdfd08486 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 21 Aug 2022 09:56:55 +0200 Subject: coff: move header writing logic into flush --- src/link/Coff.zig | 501 +++++++++++++++++++++++++++--------------------------- 1 file changed, 253 insertions(+), 248 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e4f0227aec..d7e6ebe162 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -144,39 +144,10 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // TODO Write object specific relocations, COFF symbol table, then enable object file output. - switch (options.output_mode) { - .Exe => {}, - .Obj => return error.TODOImplementWritingObjFiles, - .Lib => return error.TODOImplementWritingLibFiles, - } - - var coff_file_header_offset: u32 = 0; - if (options.output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header + const coff_file_header_offset: u32 = if (options.output_mode == .Exe) msdos_stub.len + 4 else 0; + const default_offset_table_size = file_alignment; const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - + const default_size_of_code = 0; const optional_header_size = switch (options.output_mode) { .Exe => data_directory_count * 8 + switch (self.ptr_width) { .p32 => @as(u16, 96), @@ -184,218 +155,15 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }, else => 0, }; - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment); + self.section_data_offset = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, file_alignment); + const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, section_alignment); self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; self.offset_table_size = default_offset_table_size; self.section_table_offset = section_table_offset; self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; self.text_section_size = default_size_of_code; - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var characteristics: std.coff.CoffHeaderFlags = .{ - .DEBUG_STRIPPED = 1, // TODO remove debug info stripped flag when necessary - .RELOCS_STRIPPED = 1, - }; - if (options.output_mode == .Exe) { - characteristics.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => characteristics.@"32BIT_MACHINE" = 1, - .p64 => characteristics.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, characteristics)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (options.output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (options.output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); - return self; } @@ -728,6 +496,12 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! if (decl.val.tag() == .extern_fn) { return; // TODO Should we do more when front-end analyzed extern decl? } + if (decl.val.castTag(.variable)) |payload| { + const variable = payload.data; + if (variable.is_extern) { + return; // TODO Should we do more when front-end analyzed extern decl? + } + } // TODO COFF/PE debug information // TODO Implement exports @@ -735,9 +509,10 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); + const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ .ty = decl.ty, - .val = decl.val, + .val = decl_val, }, &code_buffer, .none, .{ .parent_atom_index = 0, }); @@ -862,15 +637,14 @@ pub fn updateDeclExports( continue; } } - if (mem.eql(u8, exp.options.name, "_start")) { + if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; } else { try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than 'wWinMainCRTStartup'", .{}), ); - continue; } } } @@ -884,14 +658,13 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } return; } - if (build_options.have_llvm and self.base.options.use_lld) { + const use_lld = build_options.have_llvm and self.base.options.use_lld; + if (use_lld) { return self.linkWithLLD(comp, prog_node); - } else { - switch (self.base.options.effectiveOutputMode()) { - .Exe, .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } - return self.flushModule(comp, prog_node); + } + switch (self.base.options.output_mode) { + .Exe, .Obj => return self.flushModule(comp, prog_node), + .Lib => return error.TODOImplementWritingLibFiles, } } @@ -909,6 +682,238 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + const output_mode = self.base.options.output_mode; + log.debug("in flushModule with {}", .{output_mode}); + + var coff_file_header_offset: u32 = 0; + if (output_mode == .Exe) { + // Write the MS-DOS stub and the PE signature + try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); + coff_file_header_offset = msdos_stub.len + 4; + } + + // COFF file header + const data_directory_count = 0; + var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; + var index: usize = 0; + + const machine = self.base.options.target.cpu.arch.toCoffMachine(); + if (machine == .Unknown) { + return error.UnsupportedCOFFArchitecture; + } + mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); + index += 2; + + // Number of sections (we only use .got, .text) + mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); + index += 2; + // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + + const optional_header_size = switch (output_mode) { + .Exe => data_directory_count * 8 + switch (self.ptr_width) { + .p32 => @as(u16, 96), + .p64 => 112, + }, + else => 0, + }; + + const default_offset_table_size = file_alignment; + const default_size_of_code = 0; + + // Size of file when loaded in memory + const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); + + mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); + index += 2; + + // Characteristics + var characteristics: u16 = std.coff.IMAGE_FILE_DEBUG_STRIPPED | std.coff.IMAGE_FILE_RELOCS_STRIPPED; // TODO Remove debug info stripped flag when necessary + if (output_mode == .Exe) { + characteristics |= std.coff.IMAGE_FILE_EXECUTABLE_IMAGE; + } + switch (self.ptr_width) { + .p32 => characteristics |= std.coff.IMAGE_FILE_32BIT_MACHINE, + .p64 => characteristics |= std.coff.IMAGE_FILE_LARGE_ADDRESS_AWARE, + } + mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); + index += 2; + + assert(index == 20); + try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); + + if (output_mode == .Exe) { + self.optional_header_offset = coff_file_header_offset + 20; + // Optional header + index = 0; + mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { + .p32 => @as(u16, 0x10b), + .p64 => 0x20b, + }); + index += 2; + + // Linker version (u8 + u8) + mem.set(u8, hdr_data[index..][0..2], 0); + index += 2; + + // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) + mem.set(u8, hdr_data[index..][0..20], 0); + index += 20; + + if (self.ptr_width == .p32) { + // Base of data relative to the image base (UNUSED) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + + // Image base address + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); + index += 4; + } else { + // Image base address + mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); + index += 8; + } + + // Section alignment + mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); + index += 4; + // File alignment + mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); + index += 4; + // Required OS version, 6.0 is vista + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + index += 2; + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Image version + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + // Required subsystem version, same as OS version + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + index += 2; + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Reserved zeroes (u32) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + index += 4; + // CheckSum (u32) + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + // Subsystem, TODO: Let users specify the subsystem, always CUI for now + mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); + index += 2; + // DLL characteristics + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); + index += 2; + + switch (self.ptr_width) { + .p32 => { + // Size of stack reserve + commit + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + index += 4; + // Size of heap reserve + commit + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); + index += 4; + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + index += 4; + }, + .p64 => { + // Size of stack reserve + commit + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); + index += 8; + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + index += 8; + // Size of heap reserve + commit + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); + index += 8; + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + index += 8; + }, + } + + // Reserved zeroes + mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + + // Number of data directories + mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); + index += 4; + // Initialize data directories to zero + mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); + index += data_directory_count * 8; + + assert(index == optional_header_size); + } + + // Write section table. + // First, the .got section + hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; + index += 8; + if (output_mode == .Exe) { + // Virtual size (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + index += 4; + // Virtual address (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); + index += 4; + } else { + mem.set(u8, hdr_data[index..][0..8], 0); + index += 8; + } + // Size of raw data (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + index += 4; + // File pointer to the start of the section + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + index += 4; + // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + // Section flags + mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); + index += 4; + // Then, the .text section + hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; + index += 8; + if (output_mode == .Exe) { + // Virtual size (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + index += 4; + // Virtual address (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); + index += 4; + } else { + mem.set(u8, hdr_data[index..][0..8], 0); + index += 8; + } + // Size of raw data (u32) + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + index += 4; + // File pointer to the start of the section + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); + index += 4; + // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) + mem.set(u8, hdr_data[index..][0..12], 0); + index += 12; + // Section flags + mem.writeIntLittle( + u32, + hdr_data[index..][0..4], + std.coff.IMAGE_SCN_CNT_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, + ); + index += 4; + + assert(index == optional_header_size + section_table_size); + try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); + try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); + if (self.text_section_size_dirty) { // Write the new raw size in the .text header var buf: [4]u8 = undefined; -- cgit v1.2.3 From 580bfe01c89c4e95cfb341b73514b8ec769ce635 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 10:28:33 +0200 Subject: coff: fix after rebase --- src/link/Coff.zig | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d7e6ebe162..adfb843e04 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -729,15 +729,19 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod index += 2; // Characteristics - var characteristics: u16 = std.coff.IMAGE_FILE_DEBUG_STRIPPED | std.coff.IMAGE_FILE_RELOCS_STRIPPED; // TODO Remove debug info stripped flag when necessary + var flags: std.coff.CoffHeaderFlags = .{ + // TODO Remove debug info stripped flag when necessary + .DEBUG_STRIPPED = 1, + .RELOCS_STRIPPED = 1, + }; if (output_mode == .Exe) { - characteristics |= std.coff.IMAGE_FILE_EXECUTABLE_IMAGE; + flags.EXECUTABLE_IMAGE = 1; } switch (self.ptr_width) { - .p32 => characteristics |= std.coff.IMAGE_FILE_32BIT_MACHINE, - .p64 => characteristics |= std.coff.IMAGE_FILE_LARGE_ADDRESS_AWARE, + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, } - mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); + mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, flags)); index += 2; assert(index == 20); @@ -877,7 +881,10 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); + mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + })); index += 4; // Then, the .text section hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; @@ -903,11 +910,12 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - mem.writeIntLittle( - u32, - hdr_data[index..][0..4], - std.coff.IMAGE_SCN_CNT_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, - ); + mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + })); index += 4; assert(index == optional_header_size + section_table_size); -- cgit v1.2.3 From 90b3599c6846b29a6162a670783d6c2763683f36 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 14:11:14 +0200 Subject: coff: reorganize the linker --- CMakeLists.txt | 2 + lib/std/coff.zig | 9 + src/Module.zig | 8 +- src/Sema.zig | 2 +- src/arch/aarch64/CodeGen.zig | 14 +- src/arch/arm/CodeGen.zig | 14 +- src/arch/riscv64/CodeGen.zig | 14 +- src/arch/x86_64/CodeGen.zig | 14 +- src/link.zig | 6 +- src/link/Coff.zig | 1599 +++++++++------------------------------ src/link/Coff/Atom.zig | 85 +++ src/link/Coff/lld.zig | 602 +++++++++++++++ src/link/MachO.zig | 3 +- src/link/MachO/DebugSymbols.zig | 1 - 14 files changed, 1116 insertions(+), 1257 deletions(-) create mode 100644 src/link/Coff/Atom.zig create mode 100644 src/link/Coff/lld.zig (limited to 'src/link') diff --git a/CMakeLists.txt b/CMakeLists.txt index 835f3c4d62..372338bb7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -753,6 +753,8 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link.zig" "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" diff --git a/lib/std/coff.zig b/lib/std/coff.zig index f9de318e7a..1640d44fb5 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -303,6 +303,15 @@ pub const SectionHeader = extern struct { return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable; } + pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void { + assert(new_alignment > 0 and new_alignment <= 8192); + self.flags.ALIGN = std.math.log2(new_alignment); + } + + pub fn isCode(self: SectionHeader) bool { + return self.flags.CNT_CODE == 0b1; + } + pub fn isComdat(self: SectionHeader) bool { return self.flags.LNK_COMDAT == 0b1; } diff --git a/src/Module.zig b/src/Module.zig index a92849e127..706ab5ff17 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5259,9 +5259,9 @@ pub fn clearDecl( // TODO instead of a union, put this memory trailing Decl objects, // and allow it to be variably sized. decl.link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, @@ -5680,9 +5680,9 @@ pub fn allocateNewDecl( .zir_decl_index = 0, .src_scope = src_scope, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, diff --git a/src/Sema.zig b/src/Sema.zig index f884684d73..a4e2106afb 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5076,7 +5076,7 @@ pub fn analyzeExport( }, .src = src, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = {} }, + .coff => .{ .coff = .{} }, .elf => .{ .elf = .{} }, .macho => .{ .macho = .{} }, .plan9 => .{ .plan9 = null }, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index d256f9a558..98f778ef35 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3475,10 +3475,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -5110,8 +5111,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index e8f0507614..0f796c530d 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3709,10 +3709,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk @intCast(u32, got_sym.value); + } else unreachable; try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { @@ -5549,8 +5550,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 06adcff6d4..2bb68086d8 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1755,10 +1755,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -2592,8 +2593,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 106d2feec0..d7263523c2 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3971,10 +3971,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - @intCast(u32, coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes) - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -6847,8 +6848,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/link.zig b/src/link.zig index 60c6b24640..421188bd47 100644 --- a/src/link.zig +++ b/src/link.zig @@ -245,8 +245,8 @@ pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, - coff: Coff.TextBlock, - macho: MachO.TextBlock, + coff: Coff.Atom, + macho: MachO.Atom, plan9: Plan9.DeclBlock, c: void, wasm: Wasm.DeclBlock, @@ -267,7 +267,7 @@ pub const File = struct { pub const Export = union { elf: Elf.Export, - coff: void, + coff: Coff.Export, macho: MachO.Export, plan9: Plan9.Export, c: void, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index adfb843e04..67172abcd4 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,39 +1,30 @@ const Coff = @This(); const std = @import("std"); +const build_options = @import("build_options"); const builtin = @import("builtin"); -const log = std.log.scoped(.link); -const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const fs = std.fs; -const allocPrint = std.fmt.allocPrint; +const coff = std.coff; +const log = std.log.scoped(.link); +const math = std.math; const mem = std.mem; -const lldMain = @import("../main.zig").lldMain; -const trace = @import("../tracy.zig").trace; -const Module = @import("../Module.zig"); -const Compilation = @import("../Compilation.zig"); +const Allocator = std.mem.Allocator; + const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); -const build_options = @import("build_options"); -const Cache = @import("../Cache.zig"); -const mingw = @import("../mingw.zig"); +const lld = @import("Coff/lld.zig"); +const trace = @import("../tracy.zig").trace; + const Air = @import("../Air.zig"); +pub const Atom = @import("Coff/Atom.zig"); +const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; +const Module = @import("../Module.zig"); +const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); -const allocation_padding = 4 / 3; -const minimum_text_block_size = 64 * allocation_padding; - -const section_alignment = 4096; -const file_alignment = 512; -const default_image_base = 0x400_000; -const section_table_size = 2 * 40; -comptime { - assert(mem.isAligned(default_image_base, section_alignment)); -} - pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); @@ -42,91 +33,94 @@ const msdos_stub = @embedFile("msdos-stub.bin"); llvm_object: ?*LlvmObject = null, base: link.File, -ptr_width: PtrWidth, error_flags: link.File.ErrorFlags = .{}, -text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, -last_text_block: ?*TextBlock = null, - -/// Section table file pointer. -section_table_offset: u32 = 0, -/// Section data file pointer. -section_data_offset: u32 = 0, -/// Optional header file pointer. -optional_header_offset: u32 = 0, - -/// Absolute virtual address of the offset table when the executable is loaded in memory. -offset_table_virtual_address: u32 = 0, -/// Current size of the offset table on disk, must be a multiple of `file_alignment` -offset_table_size: u32 = 0, -/// Contains absolute virtual addresses -offset_table: std.ArrayListUnmanaged(u64) = .{}, -/// Free list of offset table indices -offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, - -/// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u32 = null, +ptr_width: PtrWidth, -/// Absolute virtual address of the text section when the executable is loaded in memory. -text_section_virtual_address: u32 = 0, -/// Current size of the `.text` section on disk, must be a multiple of `file_alignment` -text_section_size: u32 = 0, +sections: std.MultiArrayList(Section) = .{}, -offset_table_size_dirty: bool = false, -text_section_size_dirty: bool = false, -/// This flag is set when the virtual size of the whole image file when loaded in memory has changed -/// and needs to be updated in the optional header. -size_of_image_dirty: bool = false, +text_section_index: ?u16 = null, +got_section_index: ?u16 = null, -pub const PtrWidth = enum { p32, p64 }; +locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, +globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, -pub const TextBlock = struct { - /// Offset of the code relative to the start of the text section - text_offset: u32, - /// Used size of the text block - size: u32, - /// This field is undefined for symbols with size = 0. - offset_table_index: u32, - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `TextBlock`. - prev: ?*TextBlock, - next: ?*TextBlock, - - pub const empty = TextBlock{ - .text_offset = 0, - .size = 0, - .offset_table_index = undefined, - .prev = null, - .next = null, - }; +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, - /// Returns how much room there is to grow in virtual address space. - fn capacity(self: TextBlock) u64 { - if (self.next) |next| { - return next.text_offset - self.text_offset; - } - // This is the last block, the capacity is only limited by the address space. - return std.math.maxInt(u32) - self.text_offset; - } +strtab: StringTable(.strtab) = .{}, - fn freeListEligible(self: TextBlock) bool { - // No need to keep a free list node for the last block. - const next = self.next orelse return false; - const cap = next.text_offset - self.text_offset; - const ideal_cap = self.size * allocation_padding; - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= minimum_text_block_size; - } +got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, +got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, - /// Absolute virtual address of the text block when the file is loaded in memory. - fn getVAddr(self: TextBlock, coff: Coff) u32 { - return coff.text_section_virtual_address + self.text_offset; - } +/// Virtual address of the entry point procedure relative to image base. +entry_addr: ?u64 = null, + +/// Table of Decls that are currently alive. +/// We store them here so that we can properly dispose of any allocated +/// memory within the atom in the incremental linker. +/// TODO consolidate this. +decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{}, + +/// List of atoms that are either synthetic or map directly to the Zig source program. +managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, + +/// Table of atoms indexed by the symbol index. +atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, + +const page_size: u16 = 0x1000; + +const Section = struct { + header: coff.SectionHeader, + + last_atom: ?*Atom = null, + + /// A list of atoms that have surplus capacity. This list can have false + /// positives, as functions grow and shrink over time, only sometimes being added + /// or removed from the freelist. + /// + /// An atom has surplus capacity when its overcapacity value is greater than + /// padToIdeal(minimum_atom_size). That is, when it has so + /// much extra capacity, that we could fit a small new symbol in it, itself with + /// ideal_capacity or more. + /// + /// Ideal capacity is defined by size + (size / ideal_factor). + /// + /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that + /// overcapacity can be negative. A simple way to have negative overcapacity is to + /// allocate a fresh atom, which will have ideal capacity, and then grow it + /// by 1 byte. It will then have -1 overcapacity. + free_list: std.ArrayListUnmanaged(*Atom) = .{}, }; +pub const PtrWidth = enum { p32, p64 }; pub const SrcFn = void; +pub const Export = struct { + sym_index: ?u32 = null, +}; + +pub const SymbolWithLoc = struct { + // Index into the respective symbol table. + sym_index: u32, + + // null means it's a synthetic global or Zig source. + file: ?u32 = null, +}; + +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; + +/// In order for a slice of bytes to be considered eligible to keep metadata pointing at +/// it as a possible place to put new symbols, it must have enough room for this many bytes +/// (plus extra for reserved capacity). +const minimum_text_block_size = 64; +pub const min_text_capacity = padToIdeal(minimum_text_block_size); + +/// We commit 0x1000 = 4096 bytes of space to the headers. +/// This should be plenty for any potential future extensions. +const default_headerpad_size: u32 = 0x1000; + pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -144,25 +138,18 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - const coff_file_header_offset: u32 = if (options.output_mode == .Exe) msdos_stub.len + 4 else 0; - const default_offset_table_size = file_alignment; - const data_directory_count = 0; - const default_size_of_code = 0; - const optional_header_size = switch (options.output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - self.section_data_offset = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, section_alignment); - self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; - self.offset_table_size = default_offset_table_size; - self.section_table_offset = section_table_offset; - self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; - self.text_section_size = default_size_of_code; + // Index 0 is always a null symbol. + try self.locals.append(allocator, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); + try self.strtab.buffer.append(allocator, 0); + + try self.populateMissingMetadata(); return self; } @@ -193,245 +180,259 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { return self; } -pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { - if (self.llvm_object) |_| return; +pub fn deinit(self: *Coff) void { + const gpa = self.base.allocator; - try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + } - const decl = self.base.options.module.?.declPtr(decl_index); - if (self.offset_table_free_list.popOrNull()) |i| { - decl.link.coff.offset_table_index = i; - } else { - decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len); - _ = self.offset_table.addOneAssumeCapacity(); + for (self.sections.items(.free_list)) |*free_list| { + free_list.deinit(gpa); + } + self.sections.deinit(gpa); - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - if (self.offset_table.items.len > self.offset_table_size / entry_size) { - self.offset_table_size_dirty = true; - } + for (self.managed_atoms.items) |atom| { + gpa.destroy(atom); } + self.managed_atoms.deinit(gpa); - self.offset_table.items[decl.link.coff.offset_table_index] = 0; + self.locals.deinit(gpa); + self.globals.deinit(gpa); + self.locals_free_list.deinit(gpa); + self.strtab.deinit(gpa); + self.got_entries.deinit(gpa); + self.got_entries_free_list.deinit(gpa); + self.decls.deinit(gpa); + self.atom_by_index_table.deinit(gpa); } -fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const new_block_min_capacity = new_block_size * allocation_padding; +fn populateMissingMetadata(self: *Coff) !void { + _ = self; + @panic("TODO populateMissingMetadata"); +} + +pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { + if (self.llvm_object) |_| return; + const decl = self.base.options.module.?.declPtr(decl_index); + if (decl.link.coff.sym_index != 0) return; + decl.link.coff.sym_index = try self.allocateSymbol(); + const gpa = self.base.allocator; + try self.atom_by_index_table.putNoClobber(gpa, decl.link.coff.sym_index, &decl.link.coff); + try self.decls.putNoClobber(gpa, decl_index, null); +} + +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const tracy = trace(@src()); + defer tracy.end(); - // We use these to indicate our intention to update metadata, placing the new block, + const header = &self.sections.items(.header)[sect_id]; + const free_list = &self.sections.items(.free_list)[sect_id]; + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size; + + // We use these to indicate our intention to update metadata, placing the new atom, // and possibly removing a free list node. // It would be simpler to do it inside the for loop below, but that would cause a // problem if an error was returned later in the function. So this action // is actually carried out at the end of the function, when errors are no longer possible. - var block_placement: ?*TextBlock = null; + var atom_placement: ?*Atom = null; var free_list_removal: ?usize = null; - const vaddr = blk: { + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + var vaddr = blk: { var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - const free_block = self.text_block_free_list.items[i]; - - const next_block_text_offset = free_block.text_offset + free_block.capacity(); - const new_block_text_offset = mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address; - if (new_block_text_offset < next_block_text_offset and next_block_text_offset - new_block_text_offset >= new_block_min_capacity) { - block_placement = free_block; - - const remaining_capacity = next_block_text_offset - new_block_text_offset - new_block_min_capacity; - if (remaining_capacity < minimum_text_block_size) { - free_list_removal = i; - } - - break :blk new_block_text_offset + self.text_section_virtual_address; - } else { - if (!free_block.freeListEligible()) { - _ = self.text_block_free_list.swapRemove(i); + while (i < free_list.items.len) { + const big_atom = free_list.items[i]; + // We now have a pointer to a live atom that has too much capacity. + // Is it enough that we could fit this new atom? + const sym = big_atom.getSymbol(self); + const capacity = big_atom.capacity(self); + const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; + const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.n_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the atom that it points to has grown to take up + // more of the extra capacity. + if (!big_atom.freeListEligible(self)) { + _ = free_list.swapRemove(i); } else { i += 1; } continue; } - } else if (self.last_text_block) |last| { - const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); - block_placement = last; - break :blk new_block_vaddr; + // At this point we know that we will place the new atom here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + const keep_free_list_node = remaining_capacity >= min_text_capacity; + + // Set up the metadata to be updated, after errors are no longer possible. + atom_placement = big_atom; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (maybe_last_atom.*) |last| { + const last_symbol = last.getSymbol(self); + const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; + const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + atom_placement = last; + break :blk new_start_vaddr; } else { - break :blk self.text_section_virtual_address; + break :blk mem.alignForwardGeneric(u64, header.addr, alignment); } }; - const expand_text_section = block_placement == null or block_placement.?.next == null; - if (expand_text_section) { - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment)); - if (needed_size > self.text_section_size) { - const current_text_section_virtual_size = mem.alignForwardGeneric(u32, self.text_section_size, section_alignment); - const new_text_section_virtual_size = mem.alignForwardGeneric(u32, needed_size, section_alignment); - if (current_text_section_virtual_size != new_text_section_virtual_size) { - self.size_of_image_dirty = true; - // Write new virtual size - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); - } + const expand_section = atom_placement == null or atom_placement.?.next == null; + if (expand_section) { + @panic("TODO expand section in allocateAtom"); + } - self.text_section_size = needed_size; - self.text_section_size_dirty = true; - } - self.last_text_block = text_block; + if (header.getAlignment() < alignment) { + header.setAlignment(alignment); } - text_block.text_offset = @intCast(u32, vaddr - self.text_section_virtual_address); - text_block.size = @intCast(u32, new_block_size); - - // This function can also reallocate a text block. - // In this case we need to "unplug" it from its previous location before - // plugging it in to its new location. - if (text_block.prev) |prev| { - prev.next = text_block.next; + atom.size = new_atom_size; + atom.alignment = alignment; + + if (atom.prev) |prev| { + prev.next = atom.next; } - if (text_block.next) |next| { - next.prev = text_block.prev; + if (atom.next) |next| { + next.prev = atom.prev; } - if (block_placement) |big_block| { - text_block.prev = big_block; - text_block.next = big_block.next; - big_block.next = text_block; + if (atom_placement) |big_atom| { + atom.prev = big_atom; + atom.next = big_atom.next; + big_atom.next = atom; } else { - text_block.prev = null; - text_block.next = null; + atom.prev = null; + atom.next = null; } if (free_list_removal) |i| { - _ = self.text_block_free_list.swapRemove(i); + _ = free_list.swapRemove(i); } + return vaddr; } -fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const block_vaddr = text_block.getVAddr(self.*); - const align_ok = mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr; - const need_realloc = !align_ok or new_block_size > text_block.capacity(); - if (!need_realloc) return @as(u64, block_vaddr); - return self.allocateTextBlock(text_block, new_block_size, alignment); +fn allocateSymbol(self: *Coff) !u32 { + const gpa = self.base.allocator; + try self.locals.ensureUnusedCapacity(gpa, 1); + + const index = blk: { + if (self.locals_free_list.popOrNull()) |index| { + log.debug(" (reusing symbol index {d})", .{index}); + break :blk index; + } else { + log.debug(" (allocating symbol index {d})", .{self.locals.items.len}); + const index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); + break :blk index; + } + }; + + self.locals.items[index] = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + + return index; } -fn shrinkTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64) void { - text_block.size = @intCast(u32, new_block_size); - if (text_block.capacity() - text_block.size >= minimum_text_block_size) { - self.text_block_free_list.append(self.base.allocator, text_block) catch {}; +pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { + const gpa = self.base.allocator; + try self.got_entries.ensureUnusedCapacity(gpa, 1); + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + self.got_entries.keys()[index] = target; + return index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + try self.got_entries.putAssumeCapacityNoClobber(target, 0); + return index; } } -fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const sym = atom.getSymbol(self); + const align_ok = mem.alignBackwardGeneric(u64, sym.value, alignment) == sym.value; + const need_realloc = !align_ok or new_atom_size > atom.capacity(self); + if (!need_realloc) return sym.value; + return self.allocateAtom(atom, new_atom_size, alignment, sect_id); +} + +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void { + _ = self; + _ = atom; + _ = new_block_size; + _ = sect_id; + // TODO check the new capacity, and if it crosses the size threshold into a big enough + // capacity, insert a free list node for it. +} + +fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { + log.debug("freeAtom {*}", .{atom}); + + const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { var i: usize = 0; - // TODO turn text_block_free_list into a hash map - while (i < self.text_block_free_list.items.len) { - if (self.text_block_free_list.items[i] == text_block) { - _ = self.text_block_free_list.swapRemove(i); + // TODO turn free_list into a hash map + while (i < free_list.items.len) { + if (free_list.items[i] == atom) { + _ = free_list.swapRemove(i); continue; } - if (self.text_block_free_list.items[i] == text_block.prev) { + if (free_list.items[i] == atom.prev) { already_have_free_list_node = true; } i += 1; } } - if (self.last_text_block == text_block) { - self.last_text_block = text_block.prev; - } - if (text_block.prev) |prev| { - prev.next = text_block.next; - if (!already_have_free_list_node and prev.freeListEligible()) { - // The free list is heuristics, it doesn't have to be perfect, so we can - // ignore the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch {}; + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + if (maybe_last_atom.*) |last_atom| { + if (last_atom == atom) { + if (atom.prev) |prev| { + // TODO shrink the section size here + maybe_last_atom.* = prev; + } else { + maybe_last_atom.* = null; + } } } - if (text_block.next) |next| { - next.prev = text_block.prev; - } -} + if (atom.prev) |prev| { + prev.next = atom.next; -fn writeOffsetTableEntry(self: *Coff, index: usize) !void { - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - const endian = self.base.options.target.cpu.arch.endian(); - - const offset_table_start = self.section_data_offset; - if (self.offset_table_size_dirty) { - const current_raw_size = self.offset_table_size; - const new_raw_size = self.offset_table_size * 2; - log.debug("growing offset table from raw size {} to {}\n", .{ current_raw_size, new_raw_size }); - - // Move the text section to a new place in the executable - const current_text_section_start = self.section_data_offset + current_raw_size; - const new_text_section_start = self.section_data_offset + new_raw_size; - - const amt = try self.base.file.?.copyRangeAll(current_text_section_start, self.base.file.?, new_text_section_start, self.text_section_size); - if (amt != self.text_section_size) return error.InputOutput; - - // Write the new raw size in the .got header - var buf: [8]u8 = undefined; - mem.writeIntLittle(u32, buf[0..4], new_raw_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 16); - // Write the new .text section file offset in the .text section header - mem.writeIntLittle(u32, buf[0..4], new_text_section_start); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 20); - - const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); - const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment); - // If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section - // and the virtual size of the `.got` section - - if (new_virtual_size != current_virtual_size) { - log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size }); - self.size_of_image_dirty = true; - const va_offset = new_virtual_size - current_virtual_size; - - // Write .got virtual size - mem.writeIntLittle(u32, buf[0..4], new_virtual_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 8); - - // Write .text new virtual address - self.text_section_virtual_address = self.text_section_virtual_address + va_offset; - mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); - - // Fix the VAs in the offset table - for (self.offset_table.items) |*va, idx| { - if (va.* != 0) { - va.* += va_offset; - - switch (entry_size) { - 4 => { - mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian); - try self.base.file.?.pwriteAll(buf[0..4], offset_table_start + idx * entry_size); - }, - 8 => { - mem.writeInt(u64, &buf, va.*, endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size); - }, - else => unreachable, - } - } - } + if (!already_have_free_list_node and prev.freeListEligible(self)) { + // The free list is heuristics, it doesn't have to be perfect, so we can + // ignore the OOM here. + free_list.append(self.base.allocator, prev) catch {}; } - self.offset_table_size = new_raw_size; - self.offset_table_size_dirty = false; + } else { + atom.prev = null; } - // Write the new entry - switch (entry_size) { - 4 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - 8 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - else => unreachable, + + if (atom.next) |next| { + next.prev = atom.prev; + } else { + atom.next = null; } } @@ -470,15 +471,19 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - return self.finishUpdateDecl(module, func.owner_decl, code); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code has sym {}", .{sym}); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl_index, decl_exports); } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 { _ = self; _ = tv; _ = decl_index; - log.debug("TODO lowerUnnamedConst for Coff", .{}); - return error.AnalysisFail; + @panic("TODO lowerUnnamedConst"); } pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void { @@ -503,9 +508,6 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! } } - // TODO COFF/PE debug information - // TODO Implement exports - var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); @@ -526,49 +528,21 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - return self.finishUpdateDecl(module, decl_index, code); -} - -fn finishUpdateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index, code: []const u8) !void { - const decl = module.declPtr(decl_index); - const required_alignment = decl.ty.abiAlignment(self.base.options.target); - const curr_size = decl.link.coff.size; - if (curr_size != 0) { - const capacity = decl.link.coff.capacity(); - const need_realloc = code.len > capacity or - !mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); - if (need_realloc) { - const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset; - const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); - if (vaddr != curr_vaddr) { - log.debug(" (writing new offset table entry)\n", .{}); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - } else if (code.len < curr_size) { - self.shrinkTextBlock(&decl.link.coff, code.len); - } - } else { - const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{ - mem.sliceTo(decl.name, 0), - vaddr, - std.fmt.fmtIntSizeDec(code.len), - }); - errdefer self.freeTextBlock(&decl.link.coff); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - - // Write the code into the file - try self.base.file.?.pwriteAll(code, self.section_data_offset + self.offset_table_size + decl.link.coff.text_offset); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code for {}", .{sym}); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl_index, decl_exports); } +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !*coff.Symbol { + _ = self; + _ = decl_index; + _ = code; + @panic("TODO updateDeclCode"); +} + pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); @@ -577,9 +551,31 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); + log.debug("freeDecl {*}", .{decl}); + + const kv = self.decls.fetchRemove(decl_index); + if (kv.?.value) |index| { + self.freeAtom(&decl.link.coff, index); + } + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. - self.freeTextBlock(&decl.link.coff); - self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {}; + const gpa = self.base.allocator; + const sym_index = decl.link.coff.sym_index; + if (sym_index != 0) { + self.locals_free_list.append(gpa, sym_index) catch {}; + + // Try freeing GOT atom if this decl had one + const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + if (self.got_entries.getIndex(got_target)) |got_index| { + self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {}; + self.got_entries.values()[got_index] = 0; + log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index }); + } + + self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0); + _ = self.atom_by_index_table.remove(sym_index); + decl.link.coff.sym_index = 0; + } } pub fn updateDeclExports( @@ -625,28 +621,7 @@ pub fn updateDeclExports( if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - const decl = module.declPtr(decl_index); - for (exports) |exp| { - if (exp.options.section) |section_name| { - if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), - ); - continue; - } - } - if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; - } else { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than 'wWinMainCRTStartup'", .{}), - ); - } - } + @panic("TODO updateDeclExports"); } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -660,7 +635,7 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } const use_lld = build_options.have_llvm and self.base.options.use_lld; if (use_lld) { - return self.linkWithLLD(comp, prog_node); + return lld.linkWithLLD(self, comp, prog_node); } switch (self.base.options.output_mode) { .Exe, .Obj => return self.flushModule(comp, prog_node), @@ -682,888 +657,68 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - const output_mode = self.base.options.output_mode; - log.debug("in flushModule with {}", .{output_mode}); - - var coff_file_header_offset: u32 = 0; - if (output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header - const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - - const optional_header_size = switch (output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var flags: std.coff.CoffHeaderFlags = .{ - // TODO Remove debug info stripped flag when necessary - .DEBUG_STRIPPED = 1, - .RELOCS_STRIPPED = 1, - }; - if (output_mode == .Exe) { - flags.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => flags.@"32BIT_MACHINE" = 1, - .p64 => flags.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, flags)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); - - if (self.text_section_size_dirty) { - // Write the new raw size in the .text header - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.text_section_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16); - try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size); - self.text_section_size_dirty = false; - } - - if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment); - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_size_of_image); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); - self.size_of_image_dirty = false; - } - if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; - - if (self.base.options.output_mode == .Exe) { - // Write AddressOfEntryPoint - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.entry_addr.?); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); - } } } -fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { - const tracy = trace(@src()); - defer tracy.end(); - - var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; - if (use_stage1) { - const obj_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = self.base.options.root_name, - .target = self.base.options.target, - .output_mode = .Obj, - }); - switch (self.base.options.cache_mode) { - .incremental => break :blk try module.zig_cache_artifact_directory.join( - arena, - &[_][]const u8{obj_basename}, - ), - .whole => break :blk try fs.path.join(arena, &.{ - fs.path.dirname(full_out_path).?, obj_basename, - }), - } - } - - try self.flushModule(comp, prog_node); - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); - } else { - break :blk self.base.intermediary_basename.?; - } - } else null; - - var sub_prog_node = prog_node.start("LLD Link", 0); - sub_prog_node.activate(); - sub_prog_node.context.refresh(); - defer sub_prog_node.end(); - - const is_lib = self.base.options.output_mode == .Lib; - const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; - const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; - const target = self.base.options.target; - - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!self.base.options.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!self.base.options.disable_lld_caching) { - man = comp.cache_parent.obtain(); - self.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 7); - - for (self.base.options.objects) |obj| { - _ = try man.addFile(obj.path, null); - man.hash.add(obj.must_link); - } - for (comp.c_object_table.keys()) |key| { - _ = try man.addFile(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(self.base.options.entry); - man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.lib_dirs); - man.hash.add(self.base.options.skip_linker_dependencies); - if (self.base.options.link_libc) { - man.hash.add(self.base.options.libc_installation != null); - if (self.base.options.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); - man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); - man.hash.addOptional(self.base.options.subsystem); - man.hash.add(self.base.options.is_test); - man.hash.add(self.base.options.tsaware); - man.hash.add(self.base.options.nxcompat); - man.hash.add(self.base.options.dynamicbase); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.addOptional(self.base.options.major_subsystem_version); - man.hash.addOptional(self.base.options.minor_subsystem_version); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (self.base.options.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (self.base.options.objects.len != 0) - break :blk self.base.options.objects[0].path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk p; - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - // This can happen when using --enable-cache and using the stage1 backend. In this case - // we can skip the file copy. - if (!mem.eql(u8, the_object_path, full_out_path)) { - try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); - } - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (!self.base.options.strip) { - try argv.append("-DEBUG"); - } - if (self.base.options.lto) { - switch (self.base.options.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } - } - if (self.base.options.output_mode == .Exe) { - const stack_size = self.base.options.stack_size_override orelse 16777216; - try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); - } - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); - } - - if (target.cpu.arch == .i386) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch.isARM()) { - if (target.cpu.arch.ptrBitWidth() == 32) { - try argv.append("-MACHINE:ARM"); - } else { - try argv.append("-MACHINE:ARM64"); - } - } - - for (self.base.options.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } - - if (is_dyn_lib) { - try argv.append("-DLL"); - } - - if (self.base.options.entry) |entry| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); - } - - if (self.base.options.tsaware) { - try argv.append("-tsaware"); - } - if (self.base.options.nxcompat) { - try argv.append("-nxcompat"); - } - if (self.base.options.dynamicbase) { - try argv.append("-dynamicbase"); - } - - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - - if (self.base.options.implib_emit) |emit| { - const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); - - if (target.abi == .msvc) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } - - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); - } - - try argv.ensureUnusedCapacity(self.base.options.objects.len); - for (self.base.options.objects) |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); - } else { - argv.appendAssumeCapacity(obj.path); - } - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (self.base.options.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (module.stage1_flags.have_c_main or self.base.options.is_test or - module.stage1_flags.have_winmain_crt_startup or - module.stage1_flags.have_wwinmain_crt_startup) - { - break :blk .Console; - } - if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; - - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = ss: { - if (self.base.options.major_subsystem_version) |major| { - if (self.base.options.minor_subsystem_version) |minor| { - break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); - } else { - break :ss try allocPrint(arena, ",{d}", .{major}); - } - } - break :ss ""; - }; - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; - - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-ALIGN:32", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - try argv.append("-lldmingw"); - - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); - } - - try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); - - for (mingw.always_link_libs) |name| { - if (!self.base.options.system_libs.contains(name)) { - const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); - try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); - } - } - } else { - const lib_str = switch (self.base.options.link_mode) { - .Dynamic => "", - .Static => "lib", - }; - const d_str = switch (self.base.options.optimize_mode) { - .Debug => "d", - else => "", - }; - switch (self.base.options.link_mode) { - .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), - .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), - } - - try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); - try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib) { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } - - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); - } - - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } - - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { - if (!self.base.options.link_libc) { - if (comp.libc_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MinGW doesn't provide libssp symbols - if (target.abi.isGnu()) { - if (comp.libssp_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - - try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); - for (self.base.options.system_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(crt_file.full_object_path); - continue; - } - if (try self.findLib(arena, lib_basename)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try self.findLib(arena, fallback_name)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } - - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } - - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.ChildProcess.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } - - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return error.LLDReportedFailure; - } - } - } - } - - if (!self.base.options.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); - } -} - -fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 { - for (self.base.options.lib_dirs) |lib_dir| { - const full_path = try fs.path.join(arena, &.{ lib_dir, name }); - fs.cwd().access(full_path, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return full_path; - } - return null; -} - pub fn getDeclVAddr( self: *Coff, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo, ) !u64 { + _ = self; + _ = decl_index; _ = reloc_info; - const mod = self.base.options.module.?; - const decl = mod.declPtr(decl_index); - assert(self.llvm_object == null); - return self.text_section_virtual_address + decl.link.coff.text_offset; + @panic("TODO getDeclVAddr"); } pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { _ = self; _ = module; _ = decl; - // TODO Implement this + log.debug("TODO implement updateDeclLineNumber", .{}); } -pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); - } +pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + // TODO https://github.com/ziglang/zig/issues/1284 + return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch + math.maxInt(@TypeOf(actual_size)); +} + +/// Returns pointer-to-symbol described by `sym_with_loc` descriptor. +pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} + +/// Returns symbol described by `sym_with_loc` descriptor. +pub fn getSymbol(self: *Coff, sym_loc: SymbolWithLoc) coff.Symbol { + return self.getSymbolPtr(sym_loc).*; +} + +/// Returns name of the symbol described by `sym_with_loc` descriptor. +pub fn getSymbolName(self: *Coff, sym_loc: SymbolWithLoc) []const u8 { + assert(sym_loc.file == null); // TODO linking object files + const sym = self.locals.items[sym_loc.sym_index]; + const offset = sym.getNameOffset() orelse return sym.getName().?; + return self.strtab.get(offset).?; +} + +/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor. +/// Returns null on failure. +pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + assert(sym_loc.file == null); // TODO linking with object files + return self.atom_by_index_table.get(sym_loc.sym_index); +} - self.text_block_free_list.deinit(self.base.allocator); - self.offset_table.deinit(self.base.allocator); - self.offset_table_free_list.deinit(self.base.allocator); +/// Returns GOT atom that references `sym_with_loc` if one exists. +/// Returns null otherwise. +pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + const got_index = self.got_entries.get(sym_loc) orelse return null; + return self.atom_by_index_table.get(got_index); } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig new file mode 100644 index 0000000000..b61e77f53e --- /dev/null +++ b/src/link/Coff/Atom.zig @@ -0,0 +1,85 @@ +const Atom = @This(); + +const std = @import("std"); +const coff = std.coff; + +const Allocator = std.mem.Allocator; + +const Coff = @import("../Coff.zig"); +const SymbolWithLoc = Coff.SymbolWithLoc; + +/// Each decl always gets a local symbol with the fully qualified name. +/// The vaddr and size are found here directly. +/// The file offset is found by computing the vaddr offset from the section vaddr +/// the symbol references, and adding that to the file offset of the section. +/// If this field is 0, it means the codegen size = 0 and there is no symbol or +/// offset table entry. +sym_index: u32, + +/// null means symbol defined by Zig source. +file: ?u32, + +/// Used size of the atom +size: u64, + +/// Alignment of the atom +alignment: u32, + +/// Points to the previous and next neighbors, based on the `text_offset`. +/// This can be used to find, for example, the capacity of this `Atom`. +prev: ?*Atom, +next: ?*Atom, + +pub const empty = Atom{ + .sym_index = 0, + .file = null, + .size = 0, + .alignment = 0, + .prev = null, + .next = null, +}; + +pub fn deinit(self: *Atom, gpa: Allocator) void { + _ = self; + _ = gpa; +} + +pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { + return self.getSymbolPtr(coff_file).*; +} + +pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { + return coff_file.getSymbolPtr(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { + return .{ .sym_index = self.sym_index, .file = self.file }; +} + +/// Returns how much room there is to grow in virtual address space. +pub fn capacity(self: Atom, coff_file: *Coff) u64 { + const self_sym = self.getSymbol(coff_file); + if (self.next) |next| { + const next_sym = next.getSymbol(coff_file); + return next_sym.value - self_sym.value; + } else { + // We are the last atom. + // The capacity is limited only by virtual address space. + return std.math.maxInt(u64) - self_sym.value; + } +} + +pub fn freeListEligible(self: Atom, coff_file: *Coff) bool { + // No need to keep a free list node for the last atom. + const next = self.next orelse return false; + const self_sym = self.getSymbol(coff_file); + const next_sym = next.getSymbol(coff_file); + const cap = next_sym.value - self_sym.value; + const ideal_cap = Coff.padToIdeal(self.size); + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= Coff.min_text_capacity; +} diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig new file mode 100644 index 0000000000..feae0134e9 --- /dev/null +++ b/src/link/Coff/lld.zig @@ -0,0 +1,602 @@ +const std = @import("std"); +const build_options = @import("build_options"); +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const mingw = @import("../../mingw.zig"); +const link = @import("../../link.zig"); +const lldMain = @import("../../main.zig").lldMain; +const trace = @import("../../tracy.zig").trace; + +const Allocator = mem.Allocator; + +const Cache = @import("../../Cache.zig"); +const Coff = @import("../Coff.zig"); +const Compilation = @import("../../Compilation.zig"); + +pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { + const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; + if (use_stage1) { + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = self.base.options.root_name, + .target = self.base.options.target, + .output_mode = .Obj, + }); + switch (self.base.options.cache_mode) { + .incremental => break :blk try module.zig_cache_artifact_directory.join( + arena, + &[_][]const u8{obj_basename}, + ), + .whole => break :blk try fs.path.join(arena, &.{ + fs.path.dirname(full_out_path).?, obj_basename, + }), + } + } + + try self.flushModule(comp, prog_node); + + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); + } else { + break :blk self.base.intermediary_basename.?; + } + } else null; + + var sub_prog_node = prog_node.start("LLD Link", 0); + sub_prog_node.activate(); + sub_prog_node.context.refresh(); + defer sub_prog_node.end(); + + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; + const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; + const target = self.base.options.target; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!self.base.options.disable_lld_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!self.base.options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + self.base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 7); + + for (self.base.options.objects) |obj| { + _ = try man.addFile(obj.path, null); + man.hash.add(obj.must_link); + } + for (comp.c_object_table.keys()) |key| { + _ = try man.addFile(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(self.base.options.entry); + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.image_base_override); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.add(self.base.options.skip_linker_dependencies); + if (self.base.options.link_libc) { + man.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); + man.hash.addOptional(self.base.options.subsystem); + man.hash.add(self.base.options.is_test); + man.hash.add(self.base.options.tsaware); + man.hash.add(self.base.options.nxcompat); + man.hash.add(self.base.options.dynamicbase); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.addOptional(self.base.options.major_subsystem_version); + man.hash.addOptional(self.base.options.minor_subsystem_version); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (self.base.options.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0].path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (!self.base.options.strip) { + try argv.append("-DEBUG"); + } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (self.base.options.output_mode == .Exe) { + const stack_size = self.base.options.stack_size_override orelse 16777216; + try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); + } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + } + + if (target.cpu.arch == .i386) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch.isARM()) { + if (target.cpu.arch.ptrBitWidth() == 32) { + try argv.append("-MACHINE:ARM"); + } else { + try argv.append("-MACHINE:ARM64"); + } + } + + for (self.base.options.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (self.base.options.entry) |entry| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); + } + + if (self.base.options.tsaware) { + try argv.append("-tsaware"); + } + if (self.base.options.nxcompat) { + try argv.append("-nxcompat"); + } + if (self.base.options.dynamicbase) { + try argv.append("-dynamicbase"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); + } + + try argv.ensureUnusedCapacity(self.base.options.objects.len); + for (self.base.options.objects) |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); + } else { + argv.appendAssumeCapacity(obj.path); + } + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (self.base.options.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (module.stage1_flags.have_c_main or self.base.options.is_test or + module.stage1_flags.have_winmain_crt_startup or + module.stage1_flags.have_wwinmain_crt_startup) + { + break :blk .Console; + } + if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = ss: { + if (self.base.options.major_subsystem_version) |major| { + if (self.base.options.minor_subsystem_version) |minor| { + break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); + } else { + break :ss try allocPrint(arena, ",{d}", .{major}); + } + } + break :ss ""; + }; + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-ALIGN:32", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + try argv.append("-lldmingw"); + + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); + } + + try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); + + for (mingw.always_link_libs) |name| { + if (!self.base.options.system_libs.contains(name)) { + const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); + try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); + } + } + } else { + const lib_str = switch (self.base.options.link_mode) { + .Dynamic => "", + .Static => "lib", + }; + const d_str = switch (self.base.options.optimize_mode) { + .Debug => "d", + else => "", + }; + switch (self.base.options.link_mode) { + .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), + .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), + } + + try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); + try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib) { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libunwind dep + if (self.base.options.link_libunwind) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + + if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { + if (!self.base.options.link_libc) { + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MinGW doesn't provide libssp symbols + if (target.abi.isGnu()) { + if (comp.libssp_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + + try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); + for (self.base.options.system_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(crt_file.full_object_path); + continue; + } + if (try findLib(arena, lib_basename, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } + + if (std.process.can_spawn) { + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + var child = std.ChildProcess.init(argv.items, arena); + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + std.process.exit(code); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } + } + } else { + const exit_code = try lldMain(arena, argv.items, false); + if (exit_code != 0) { + if (comp.clang_passthrough_mode) { + std.process.exit(exit_code); + } else { + return error.LLDReportedFailure; + } + } + } + } + + if (!self.base.options.disable_lld_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + self.base.lock = man.toOwnedLock(); + } +} + +fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 { + for (lib_dirs) |lib_dir| { + const full_path = try fs.path.join(arena, &.{ lib_dir, name }); + fs.cwd().access(full_path, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return full_path; + } + return null; +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 764e4e71b2..af25441066 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -26,7 +26,7 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -const Atom = @import("MachO/Atom.zig"); +pub const Atom = @import("MachO/Atom.zig"); const Cache = @import("../Cache.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); @@ -44,7 +44,6 @@ const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const Value = @import("../value.zig").Value; -pub const TextBlock = Atom; pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); pub const base_tag: File.Tag = File.Tag.macho; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index c2aa562db5..a7dc6391c2 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -18,7 +18,6 @@ const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); const Module = @import("../../Module.zig"); const StringTable = @import("../strtab.zig").StringTable; -const TextBlock = MachO.TextBlock; const Type = @import("../../type.zig").Type; base: *MachO, -- cgit v1.2.3 From ed481e3837218659ab1c18d9d4b836cf656307b9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 26 Aug 2022 18:02:12 +0200 Subject: coff: write headers to file --- lib/std/coff.zig | 15 ++++ src/link/Coff.zig | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 244 insertions(+), 9 deletions(-) (limited to 'src/link') diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 1640d44fb5..08f8e7e8a3 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -787,6 +787,21 @@ pub const MachineType = enum(u16) { /// MIPS little-endian WCE v2 WCEMIPSV2 = 0x169, + pub fn fromTargetCpuArch(arch: std.Target.Cpu.Arch) MachineType { + return switch (arch) { + .arm => .ARM, + .powerpc => .POWERPC, + .riscv32 => .RISCV32, + .thumb => .Thumb, + .i386 => .I386, + .aarch64 => .ARM64, + .riscv64 => .RISCV64, + .x86_64 => .X64, + // there's cases we don't (yet) handle + else => unreachable, + }; + } + pub fn toTargetCpuArch(machine_type: MachineType) ?std.Target.Cpu.Arch { return switch (machine_type) { .ARM => .arm, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 67172abcd4..a270843fdd 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -49,11 +49,13 @@ locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, +symtab_offset: ?u32 = null, + got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u64 = null, +entry_addr: ?u32 = null, /// Table of Decls that are currently alive. /// We store them here so that we can properly dispose of any allocated @@ -117,10 +119,6 @@ const ideal_factor = 3; const minimum_text_block_size = 64; pub const min_text_capacity = padToIdeal(minimum_text_block_size); -/// We commit 0x1000 = 4096 bytes of space to the headers. -/// This should be plenty for any potential future extensions. -const default_headerpad_size: u32 = 0x1000; - pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -208,8 +206,11 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { - _ = self; - @panic("TODO populateMissingMetadata"); + log.debug("{x}", .{msdos_stub.len}); + + if (self.text_section_index == null) {} + if (self.got_section_index == null) {} + if (self.symtab_offset == null) {} } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -540,7 +541,8 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) _ = self; _ = decl_index; _ = code; - @panic("TODO updateDeclCode"); + log.debug("TODO updateDeclCode", .{}); + return &self.locals.items[0]; } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -621,7 +623,7 @@ pub fn updateDeclExports( if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - @panic("TODO updateDeclExports"); + log.debug("TODO updateDeclExports", .{}); } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -657,6 +659,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeHeader(); + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; @@ -664,6 +668,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; } + self.error_flags.no_entry_point_found = false; } pub fn getDeclVAddr( @@ -684,12 +689,227 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeHeader(self: *Coff) !void { + const gpa = self.base.allocator; + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + const writer = buffer.writer(); + + try buffer.ensureUnusedCapacity(msdos_stub.len + 8); + writer.writeAll(msdos_stub) catch unreachable; + writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes + writer.writeAll("PE\x00\x00") catch unreachable; + buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); + + var num_data_directories: u32 = 1; + var size_of_optional_header = switch (self.ptr_width) { + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), + } + num_data_directories * @sizeOf(coff.ImageDataDirectory); + + var flags = coff.CoffHeaderFlags{ + .EXECUTABLE_IMAGE = 1, + .DEBUG_STRIPPED = 1, // TODO + }; + switch (self.ptr_width) { + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, + } + if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic) { + flags.DLL = 1; + } + + var coff_header = coff.CoffHeader{ + .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), + .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section + .time_date_stamp = 0, // TODO + .pointer_to_symbol_table = self.symtab_offset orelse 0, + .number_of_symbols = if (self.symtab_offset) |_| self.getTotalNumberOfSymbols() else 0, + .size_of_optional_header = @intCast(u16, size_of_optional_header), + .flags = flags, + }; + + try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); + writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; + + const subsystem: coff.Subsystem = .WINDOWS_CUI; + switch (self.ptr_width) { + .p32 => { + try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); + var opt_header = coff.OptionalHeaderPE32{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = 0, + .size_of_initialized_data = 0, + .size_of_uninitialized_data = 0, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = 0, + .base_of_data = 0, + .image_base = 0, + .section_alignment = 0, + .file_alignment = 0, + .major_operating_system_version = 0, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 0, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = 0, + .size_of_headers = 0, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = .{}, + .size_of_stack_reserve = 0, + .size_of_stack_commit = 0, + .size_of_heap_reserve = 0, + .size_of_heap_commit = 0, + .loader_flags = 0, + .number_of_rva_and_sizes = num_data_directories, + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + .p64 => { + try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE64)); + var opt_header = coff.OptionalHeaderPE64{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = 0, + .size_of_initialized_data = 0, + .size_of_uninitialized_data = 0, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = 0, + .image_base = 0, + .section_alignment = 0, + .file_alignment = 0, + .major_operating_system_version = 0, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 0, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = 0, + .size_of_headers = 0, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = .{}, + .size_of_stack_reserve = 0, + .size_of_stack_commit = 0, + .size_of_heap_reserve = 0, + .size_of_heap_commit = 0, + .loader_flags = 0, + .number_of_rva_and_sizes = num_data_directories, + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + } + + try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); + writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ + .virtual_address = 0, + .size = 0, + })) catch unreachable; + + try self.base.file.?.pwriteAll(buffer.items, 0); +} + +fn getTotalNumberOfSymbols(self: Coff) u32 { + _ = self; + // TODO + return 0; +} + pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch math.maxInt(@TypeOf(actual_size)); } +// fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { +// const small_ptr = self.ptr_width == .p32; +// const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); +// if (start < default_header_size) +// return ehdr_size; + +// const end = start + padToIdeal(size); + +// if (self.shdr_table_offset) |off| { +// const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); +// const tight_size = self.sections.items.len * shdr_size; +// const increased_size = padToIdeal(tight_size); +// const test_end = off + increased_size; +// if (end > off and start < test_end) { +// return test_end; +// } +// } + +// if (self.phdr_table_offset) |off| { +// const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); +// const tight_size = self.sections.items.len * phdr_size; +// const increased_size = padToIdeal(tight_size); +// const test_end = off + increased_size; +// if (end > off and start < test_end) { +// return test_end; +// } +// } + +// for (self.sections.items) |section| { +// const increased_size = padToIdeal(section.sh_size); +// const test_end = section.sh_offset + increased_size; +// if (end > section.sh_offset and start < test_end) { +// return test_end; +// } +// } +// for (self.program_headers.items) |program_header| { +// const increased_size = padToIdeal(program_header.p_filesz); +// const test_end = program_header.p_offset + increased_size; +// if (end > program_header.p_offset and start < test_end) { +// return test_end; +// } +// } +// return null; +// } + +// pub fn allocatedSize(self: *Coff, start: u64) u64 { +// if (start == 0) +// return 0; +// var min_pos: u64 = std.math.maxInt(u64); +// if (self.shdr_table_offset) |off| { +// if (off > start and off < min_pos) min_pos = off; +// } +// if (self.phdr_table_offset) |off| { +// if (off > start and off < min_pos) min_pos = off; +// } +// for (self.sections.items) |section| { +// if (section.sh_offset <= start) continue; +// if (section.sh_offset < min_pos) min_pos = section.sh_offset; +// } +// for (self.program_headers.items) |program_header| { +// if (program_header.p_offset <= start) continue; +// if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; +// } +// return min_pos - start; +// } + +// pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { +// var start: u64 = 0; +// while (self.detectAllocCollision(start, object_size)) |item_end| { +// start = mem.alignForwardGeneric(u64, item_end, min_alignment); +// } +// return start; +// } + +fn getMaxOptionalHeaderSize(self: Coff) usize { + const hdr_size: usize = switch (self.ptr_width) { + .p32 => @sizeOf(coff.OptionalHeaderPE32), + .p64 => @sizeOf(coff.OptionalHeaderPE64), + }; + return hdr_size + @sizeOf(coff.ImageDataDirectory) * 16; +} + /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { assert(sym_loc.file == null); // TODO linking object files -- cgit v1.2.3 From 93127a615be716efa0a74ca08e0a17fda2f4074f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Aug 2022 15:05:43 +0200 Subject: coff: set some defaults for PE headers --- src/Compilation.zig | 1 - src/link/Coff.zig | 71 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 23 deletions(-) (limited to 'src/link') diff --git a/src/Compilation.zig b/src/Compilation.zig index 32d06e5907..8e4b322230 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1127,7 +1127,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { link_eh_frame_hdr or options.link_emit_relocs or options.output_mode == .Lib or - options.image_base_override != null or options.linker_script != null or options.version_script != null or options.emit_implib != null or build_id) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a270843fdd..61868221d7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -69,7 +69,10 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, -const page_size: u16 = 0x1000; +const default_section_alignment: u16 = 0x1000; +const default_file_alignment: u16 = 0x200; +const default_image_base_dll: u64 = 0x10000000; +const default_image_base_exe: u64 = 0x10000; const Section = struct { header: coff.SectionHeader, @@ -701,7 +704,7 @@ fn writeHeader(self: *Coff) !void { writer.writeAll("PE\x00\x00") catch unreachable; buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); - var num_data_directories: u32 = 1; + var num_data_directories: u32 = 0; var size_of_optional_header = switch (self.ptr_width) { .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), @@ -732,7 +735,25 @@ fn writeHeader(self: *Coff) !void { try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; + const dll_flags: coff.DllFlags = .{ + .HIGH_ENTROPY_VA = 0, // TODO handle ASLR + .DYNAMIC_BASE = 0, // TODO handle ASLR + .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app + .NX_COMPAT = 1, // We are compatible with Data Execution Prevention + }; const subsystem: coff.Subsystem = .WINDOWS_CUI; + const size_of_headers: u32 = @intCast( + u32, + msdos_stub.len + 8 + size_of_optional_header + self.sections.slice().len * @sizeOf(coff.SectionHeader), + ); + const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); + const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); + const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { + .Exe => default_image_base_exe, + .Lib => default_image_base_dll, + else => unreachable, + }; + switch (self.ptr_width) { .p32 => { try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); @@ -746,21 +767,21 @@ fn writeHeader(self: *Coff) !void { .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, .base_of_data = 0, - .image_base = 0, - .section_alignment = 0, - .file_alignment = 0, - .major_operating_system_version = 0, + .image_base = @intCast(u32, image_base), + .section_alignment = default_section_alignment, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, .minor_operating_system_version = 0, .major_image_version = 0, .minor_image_version = 0, - .major_subsystem_version = 0, + .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = 0, - .size_of_headers = 0, + .size_of_image = size_of_image_aligned, + .size_of_headers = size_of_headers_aligned, .checksum = 0, .subsystem = subsystem, - .dll_flags = .{}, + .dll_flags = dll_flags, .size_of_stack_reserve = 0, .size_of_stack_commit = 0, .size_of_heap_reserve = 0, @@ -781,21 +802,21 @@ fn writeHeader(self: *Coff) !void { .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, - .image_base = 0, - .section_alignment = 0, - .file_alignment = 0, - .major_operating_system_version = 0, + .image_base = image_base, + .section_alignment = default_section_alignment, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, .minor_operating_system_version = 0, .major_image_version = 0, .minor_image_version = 0, - .major_subsystem_version = 0, + .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = 0, - .size_of_headers = 0, + .size_of_image = size_of_image_aligned, + .size_of_headers = size_of_headers_aligned, .checksum = 0, .subsystem = subsystem, - .dll_flags = .{}, + .dll_flags = dll_flags, .size_of_stack_reserve = 0, .size_of_stack_commit = 0, .size_of_heap_reserve = 0, @@ -808,11 +829,17 @@ fn writeHeader(self: *Coff) !void { } try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); - writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ - .virtual_address = 0, - .size = 0, - })) catch unreachable; + { + var i: usize = 0; + while (i < num_data_directories) : (i += 1) { + writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ + .virtual_address = 0, + .size = 0, + })) catch unreachable; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } -- cgit v1.2.3 From 9fc6933418be00df54db79aa8eb3e2c759a2c038 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 27 Aug 2022 18:10:29 +0200 Subject: coff: write data directory and section headers to file --- src/link/Coff.zig | 141 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 68 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 61868221d7..2fda648263 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,6 +38,7 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, sections: std.MultiArrayList(Section) = .{}, +data_directories: std.ArrayListUnmanaged(coff.ImageDataDirectory) = .{}, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -49,8 +50,6 @@ locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, -symtab_offset: ?u32 = null, - got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, @@ -73,6 +72,7 @@ const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; +const default_header_size: u64 = default_section_alignment; const Section = struct { header: coff.SectionHeader, @@ -192,6 +192,7 @@ pub fn deinit(self: *Coff) void { free_list.deinit(gpa); } self.sections.deinit(gpa); + self.data_directories.deinit(gpa); for (self.managed_atoms.items) |atom| { gpa.destroy(atom); @@ -209,11 +210,8 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { - log.debug("{x}", .{msdos_stub.len}); - if (self.text_section_index == null) {} if (self.got_section_index == null) {} - if (self.symtab_offset == null) {} } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -662,6 +660,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeDataDirectoriesHeaders(); + try self.writeSectionHeaders(); try self.writeHeader(); if (self.entry_addr == null and self.base.options.output_mode == .Exe) { @@ -692,24 +692,28 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeSectionHeaders(self: *Coff) !void { + const offset = self.getSectionHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); +} + +fn writeDataDirectoriesHeaders(self: *Coff) !void { + const offset = self.getDataDirectoryHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.data_directories.items), offset); +} + fn writeHeader(self: *Coff) !void { const gpa = self.base.allocator; var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); const writer = buffer.writer(); - try buffer.ensureUnusedCapacity(msdos_stub.len + 8); + try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); writer.writeAll(msdos_stub) catch unreachable; writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes writer.writeAll("PE\x00\x00") catch unreachable; buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); - var num_data_directories: u32 = 0; - var size_of_optional_header = switch (self.ptr_width) { - .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), - .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), - } + num_data_directories * @sizeOf(coff.ImageDataDirectory); - var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, .DEBUG_STRIPPED = 1, // TODO @@ -722,17 +726,20 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } + const size_of_optional_header = @intCast( + u16, + self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(), + ); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section .time_date_stamp = 0, // TODO - .pointer_to_symbol_table = self.symtab_offset orelse 0, - .number_of_symbols = if (self.symtab_offset) |_| self.getTotalNumberOfSymbols() else 0, - .size_of_optional_header = @intCast(u16, size_of_optional_header), + .pointer_to_symbol_table = 0, + .number_of_symbols = 0, + .size_of_optional_header = size_of_optional_header, .flags = flags, }; - try buffer.ensureUnusedCapacity(@sizeOf(coff.CoffHeader)); writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; const dll_flags: coff.DllFlags = .{ @@ -742,10 +749,7 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = @intCast( - u32, - msdos_stub.len + 8 + size_of_optional_header + self.sections.slice().len * @sizeOf(coff.SectionHeader), - ); + const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { @@ -756,7 +760,6 @@ fn writeHeader(self: *Coff) !void { switch (self.ptr_width) { .p32 => { - try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE32)); var opt_header = coff.OptionalHeaderPE32{ .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, @@ -787,12 +790,11 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = num_data_directories, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, .p64 => { - try buffer.ensureUnusedCapacity(@sizeOf(coff.OptionalHeaderPE64)); var opt_header = coff.OptionalHeaderPE64{ .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, @@ -822,33 +824,16 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = num_data_directories, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, } - try buffer.ensureUnusedCapacity(num_data_directories * @sizeOf(coff.ImageDataDirectory)); - { - var i: usize = 0; - while (i < num_data_directories) : (i += 1) { - writer.writeAll(mem.asBytes(&coff.ImageDataDirectory{ - .virtual_address = 0, - .size = 0, - })) catch unreachable; - } - } - try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } -fn getTotalNumberOfSymbols(self: Coff) u32 { - _ = self; - // TODO - return 0; -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch @@ -856,14 +841,12 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { } // fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { -// const small_ptr = self.ptr_width == .p32; -// const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); // if (start < default_header_size) -// return ehdr_size; +// return default_header_size; // const end = start + padToIdeal(size); -// if (self.shdr_table_offset) |off| { +// if (self.symtab_offset) |off| { // const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); // const tight_size = self.sections.items.len * shdr_size; // const increased_size = padToIdeal(tight_size); @@ -900,26 +883,26 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // return null; // } -// pub fn allocatedSize(self: *Coff, start: u64) u64 { -// if (start == 0) -// return 0; -// var min_pos: u64 = std.math.maxInt(u64); -// if (self.shdr_table_offset) |off| { -// if (off > start and off < min_pos) min_pos = off; -// } -// if (self.phdr_table_offset) |off| { -// if (off > start and off < min_pos) min_pos = off; -// } -// for (self.sections.items) |section| { -// if (section.sh_offset <= start) continue; -// if (section.sh_offset < min_pos) min_pos = section.sh_offset; -// } -// for (self.program_headers.items) |program_header| { -// if (program_header.p_offset <= start) continue; -// if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; -// } -// return min_pos - start; -// } +// // pub fn allocatedSize(self: *Coff, start: u64) u64 { +// // if (start == 0) +// // return 0; +// // var min_pos: u64 = std.math.maxInt(u64); +// // if (self.shdr_table_offset) |off| { +// // if (off > start and off < min_pos) min_pos = off; +// // } +// // if (self.phdr_table_offset) |off| { +// // if (off > start and off < min_pos) min_pos = off; +// // } +// // for (self.sections.items) |section| { +// // if (section.sh_offset <= start) continue; +// // if (section.sh_offset < min_pos) min_pos = section.sh_offset; +// // } +// // for (self.program_headers.items) |program_header| { +// // if (program_header.p_offset <= start) continue; +// // if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; +// // } +// // return min_pos - start; +// // } // pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { // var start: u64 = 0; @@ -929,12 +912,34 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // return start; // } -fn getMaxOptionalHeaderSize(self: Coff) usize { - const hdr_size: usize = switch (self.ptr_width) { +inline fn getSizeOfHeaders(self: Coff) usize { + const msdos_hdr_size = msdos_stub.len + 8; + return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(); +} + +inline fn getOptionalHeaderSize(self: Coff) usize { + return switch (self.ptr_width) { .p32 => @sizeOf(coff.OptionalHeaderPE32), .p64 => @sizeOf(coff.OptionalHeaderPE64), }; - return hdr_size + @sizeOf(coff.ImageDataDirectory) * 16; +} + +inline fn getDataDirectoryHeadersSize(self: Coff) usize { + return self.data_directories.items.len * @sizeOf(coff.ImageDataDirectory); +} + +inline fn getSectionHeadersSize(self: Coff) usize { + return self.sections.slice().len * @sizeOf(coff.SectionHeader); +} + +inline fn getDataDirectoryHeadersOffset(self: Coff) usize { + const msdos_hdr_size = msdos_stub.len + 8; + return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize(); +} + +inline fn getSectionHeadersOffset(self: Coff) usize { + return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. -- cgit v1.2.3 From da00e6dd596685e8f0ad9650145309c00485da6f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 08:31:13 +0200 Subject: coff: always write all data directory headers to file Maximum number is always 16, and this also unbreaks `dumpbin.exe` run on a simple section-less PE image created with our linker. --- src/link/Coff.zig | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 2fda648263..517ee6a3c0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -38,7 +38,7 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, sections: std.MultiArrayList(Section) = .{}, -data_directories: std.ArrayListUnmanaged(coff.ImageDataDirectory) = .{}, +data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -72,7 +72,6 @@ const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; -const default_header_size: u64 = default_section_alignment; const Section = struct { header: coff.SectionHeader, @@ -171,6 +170,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -192,7 +192,6 @@ pub fn deinit(self: *Coff) void { free_list.deinit(gpa); } self.sections.deinit(gpa); - self.data_directories.deinit(gpa); for (self.managed_atoms.items) |atom| { gpa.destroy(atom); @@ -699,7 +698,7 @@ fn writeSectionHeaders(self: *Coff) !void { fn writeDataDirectoriesHeaders(self: *Coff) !void { const offset = self.getDataDirectoryHeadersOffset(); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.data_directories.items), offset); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(&self.data_directories), offset); } fn writeHeader(self: *Coff) !void { @@ -790,7 +789,7 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, @@ -824,7 +823,7 @@ fn writeHeader(self: *Coff) !void { .size_of_heap_reserve = 0, .size_of_heap_commit = 0, .loader_flags = 0, - .number_of_rva_and_sizes = @intCast(u32, self.data_directories.items.len), + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; }, @@ -841,32 +840,13 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { } // fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { -// if (start < default_header_size) -// return default_header_size; +// const headers_size = self.getSizeOfHeaders(); +// if (start < headers_size) +// return headers_size; // const end = start + padToIdeal(size); -// if (self.symtab_offset) |off| { -// const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); -// const tight_size = self.sections.items.len * shdr_size; -// const increased_size = padToIdeal(tight_size); -// const test_end = off + increased_size; -// if (end > off and start < test_end) { -// return test_end; -// } -// } - -// if (self.phdr_table_offset) |off| { -// const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); -// const tight_size = self.sections.items.len * phdr_size; -// const increased_size = padToIdeal(tight_size); -// const test_end = off + increased_size; -// if (end > off and start < test_end) { -// return test_end; -// } -// } - -// for (self.sections.items) |section| { +// for (self.sections.items(.header)) |header| { // const increased_size = padToIdeal(section.sh_size); // const test_end = section.sh_offset + increased_size; // if (end > section.sh_offset and start < test_end) { @@ -926,7 +906,7 @@ inline fn getOptionalHeaderSize(self: Coff) usize { } inline fn getDataDirectoryHeadersSize(self: Coff) usize { - return self.data_directories.items.len * @sizeOf(coff.ImageDataDirectory); + return self.data_directories.len * @sizeOf(coff.ImageDataDirectory); } inline fn getSectionHeadersSize(self: Coff) usize { -- cgit v1.2.3 From 3aa99f45b8d9b22a2d2d09457f19ca3ef20764f6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 09:14:18 +0200 Subject: coff: initial implementation of incremental file allocs --- src/link/Coff.zig | 88 +++++++++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 52 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 517ee6a3c0..a2542bdbac 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -839,58 +839,42 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { math.maxInt(@TypeOf(actual_size)); } -// fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { -// const headers_size = self.getSizeOfHeaders(); -// if (start < headers_size) -// return headers_size; - -// const end = start + padToIdeal(size); - -// for (self.sections.items(.header)) |header| { -// const increased_size = padToIdeal(section.sh_size); -// const test_end = section.sh_offset + increased_size; -// if (end > section.sh_offset and start < test_end) { -// return test_end; -// } -// } -// for (self.program_headers.items) |program_header| { -// const increased_size = padToIdeal(program_header.p_filesz); -// const test_end = program_header.p_offset + increased_size; -// if (end > program_header.p_offset and start < test_end) { -// return test_end; -// } -// } -// return null; -// } - -// // pub fn allocatedSize(self: *Coff, start: u64) u64 { -// // if (start == 0) -// // return 0; -// // var min_pos: u64 = std.math.maxInt(u64); -// // if (self.shdr_table_offset) |off| { -// // if (off > start and off < min_pos) min_pos = off; -// // } -// // if (self.phdr_table_offset) |off| { -// // if (off > start and off < min_pos) min_pos = off; -// // } -// // for (self.sections.items) |section| { -// // if (section.sh_offset <= start) continue; -// // if (section.sh_offset < min_pos) min_pos = section.sh_offset; -// // } -// // for (self.program_headers.items) |program_header| { -// // if (program_header.p_offset <= start) continue; -// // if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; -// // } -// // return min_pos - start; -// // } - -// pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { -// var start: u64 = 0; -// while (self.detectAllocCollision(start, object_size)) |item_end| { -// start = mem.alignForwardGeneric(u64, item_end, min_alignment); -// } -// return start; -// } +fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { + const headers_size = self.getSizeOfHeaders(); + if (start < headers_size) + return headers_size; + + const end = start + padToIdeal(size); + + for (self.sections.items(.header)) |header| { + const increased_size = padToIdeal(header.size_of_raw_data); + const test_end = header.pointer_to_raw_data + increased_size; + if (end > header.pointer_to_raw_data and start < test_end) { + return test_end; + } + } + + return null; +} + +pub fn allocatedSize(self: *Coff, start: u64) u64 { + if (start == 0) + return 0; + var min_pos: u64 = std.math.maxInt(u64); + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data <= start) continue; + if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; + } + return min_pos - start; +} + +pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { + var start: u64 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; +} inline fn getSizeOfHeaders(self: Coff) usize { const msdos_hdr_size = msdos_stub.len + 8; -- cgit v1.2.3 From ff0abad2a9b9701287818684bb60d638a97172f2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 09:38:58 +0200 Subject: coff: allow for strtab in final PE image I believe this is going to be vital for section headers having names that require the use of a string table. --- src/link/Coff.zig | 42 ++++++++++++++++++++++++++++++------------ src/link/strtab.zig | 4 ++++ 2 files changed, 34 insertions(+), 12 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a2542bdbac..7a68852782 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -49,6 +49,7 @@ globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, locals_free_list: std.ArrayListUnmanaged(u32) = .{}, strtab: StringTable(.strtab) = .{}, +strtab_offset: ?u32 = null, got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, @@ -138,17 +139,6 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // Index 0 is always a null symbol. - try self.locals.append(allocator, .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = @intToEnum(coff.SectionNumber, 0), - .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }); - try self.strtab.buffer.append(allocator, 0); - try self.populateMissingMetadata(); return self; @@ -209,8 +199,25 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { + const gpa = self.base.allocator; + if (self.text_section_index == null) {} + if (self.got_section_index == null) {} + + if (self.strtab_offset == null) { + try self.strtab.buffer.append(gpa, 0); + } + + // Index 0 is always a null symbol. + try self.locals.append(gpa, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -733,7 +740,7 @@ fn writeHeader(self: *Coff) !void { .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section .time_date_stamp = 0, // TODO - .pointer_to_symbol_table = 0, + .pointer_to_symbol_table = self.strtab_offset orelse 0, .number_of_symbols = 0, .size_of_optional_header = size_of_optional_header, .flags = flags, @@ -846,6 +853,14 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { const end = start + padToIdeal(size); + if (self.strtab_offset) |off| { + const increased_size = padToIdeal(self.strtab.len()); + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } + for (self.sections.items(.header)) |header| { const increased_size = padToIdeal(header.size_of_raw_data); const test_end = header.pointer_to_raw_data + increased_size; @@ -861,6 +876,9 @@ pub fn allocatedSize(self: *Coff, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); + if (self.strtab_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } for (self.sections.items(.header)) |header| { if (header.pointer_to_raw_data <= start) continue; if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; diff --git a/src/link/strtab.zig b/src/link/strtab.zig index ae9b00027e..8e314f189f 100644 --- a/src/link/strtab.zig +++ b/src/link/strtab.zig @@ -109,5 +109,9 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type { pub fn getAssumeExists(self: Self, off: u32) []const u8 { return self.get(off) orelse unreachable; } + + pub fn len(self: Self) usize { + return self.buffer.items.len; + } }; } -- cgit v1.2.3 From f36029a3851756d6e98eb486c89dc741db79752e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 10:15:27 +0200 Subject: coff: add helpers for setting section/symbol names --- src/link/Coff.zig | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 7a68852782..0d591e504f 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -5,6 +5,7 @@ const build_options = @import("build_options"); const builtin = @import("builtin"); const assert = std.debug.assert; const coff = std.coff; +const fmt = std.fmt; const log = std.log.scoped(.link); const math = std.math; const mem = std.mem; @@ -36,6 +37,7 @@ base: link.File, error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, +page_size: u32, sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, @@ -69,7 +71,6 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, -const default_section_alignment: u16 = 0x1000; const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; @@ -150,6 +151,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { 33...64 => .p64, else => return error.UnsupportedCOFFArchitecture, }; + const page_size: u32 = switch (options.target.cpu.arch) { + else => 0x1000, + }; const self = try gpa.create(Coff); errdefer gpa.destroy(self); self.* = .{ @@ -160,6 +164,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .page_size = page_size, .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), }; @@ -199,9 +204,15 @@ pub fn deinit(self: *Coff) void { } fn populateMissingMetadata(self: *Coff) !void { + assert(self.llvm_object == null); const gpa = self.base.allocator; - if (self.text_section_index == null) {} + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.slice().len); + const file_size = self.base.options.program_code_size_hint; + const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers + log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + } if (self.got_section_index == null) {} @@ -756,7 +767,7 @@ fn writeHeader(self: *Coff) !void { }; const subsystem: coff.Subsystem = .WINDOWS_CUI; const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); - const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_section_alignment); + const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { .Exe => default_image_base_exe, @@ -777,7 +788,7 @@ fn writeHeader(self: *Coff) !void { .base_of_code = 0, .base_of_data = 0, .image_base = @intCast(u32, image_base), - .section_alignment = default_section_alignment, + .section_alignment = self.page_size, .file_alignment = default_file_alignment, .major_operating_system_version = 6, .minor_operating_system_version = 0, @@ -811,7 +822,7 @@ fn writeHeader(self: *Coff) !void { .address_of_entry_point = self.entry_addr orelse 0, .base_of_code = 0, .image_base = image_base, - .section_alignment = default_section_alignment, + .section_alignment = self.page_size, .file_alignment = default_file_alignment, .major_operating_system_version = 6, .minor_operating_system_version = 0, @@ -956,3 +967,23 @@ pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { const got_index = self.got_entries.get(sym_loc) orelse return null; return self.atom_by_index_table.get(got_index); } + +fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &header.name, name); + return; + } + const offset = try self.strtab.insert(self.base.allocator, name); + const name_offset = try fmt.bufPrint(&header.name, "/{d}", .{offset}); + mem.set(u8, header.name[name_offset.len..], 0); +} + +fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &symbol.name, name); + return; + } + const offset = try self.strtab.insert(self.base.allocator, name); + mem.copy(u8, symbol.name[0..4], 0); + _ = try fmt.bufPrint(symbol.name[4..], "{d}", .{offset}); +} -- cgit v1.2.3 From 2a994ba4a713f0bd469265ea3c55e335048d4c91 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 14:25:58 +0200 Subject: coff: populate missing section metadata --- src/link/Coff.zig | 139 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 13 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0d591e504f..74a0a5720d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -43,7 +43,9 @@ sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, -got_section_index: ?u16 = null, +rdata_section_index: ?u16 = null, +pdata_section_index: ?u16 = null, +data_section_index: ?u16 = null, locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, @@ -212,12 +214,103 @@ fn populateMissingMetadata(self: *Coff) !void { const file_size = self.base.options.program_code_size_hint; const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".text"); + try self.sections.append(gpa, .{ .header = header }); } - if (self.got_section_index == null) {} + if (self.pdata_section_index == null) { + self.pdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size = self.base.options.symbol_count_hint; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".pdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.rdata_section_index == null) { + self.rdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".rdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.data_section_index == null) { + self.data_section_index = @intCast(u16, self.sections.slice().len); + const file_size = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = @intCast(u32, file_size), + .virtual_address = @intCast(u32, off), + .size_of_raw_data = @intCast(u32, file_size), + .pointer_to_raw_data = @intCast(u32, off), + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + }, + }; + try self.setSectionName(&header, ".data"); + try self.sections.append(gpa, .{ .header = header }); + } if (self.strtab_offset == null) { try self.strtab.buffer.append(gpa, 0); + self.strtab_offset = @intCast(u32, self.findFreeSpace(self.strtab.len(), 1)); + log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); } // Index 0 is always a null symbol. @@ -677,6 +770,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + try self.writeStrtab(); try self.writeDataDirectoriesHeaders(); try self.writeSectionHeaders(); try self.writeHeader(); @@ -709,6 +803,19 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v log.debug("TODO implement updateDeclLineNumber", .{}); } +fn writeStrtab(self: *Coff) !void { + const allocated_size = self.allocatedSize(self.strtab_offset.?); + const needed_size = self.strtab.len(); + + if (needed_size > allocated_size) { + self.strtab_offset = null; + self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } + + log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size }); + try self.base.file.?.pwriteAll(self.strtab.buffer.items, self.strtab_offset.?); +} + fn writeSectionHeaders(self: *Coff) !void { const offset = self.getSectionHeadersOffset(); try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); @@ -729,7 +836,7 @@ fn writeHeader(self: *Coff) !void { writer.writeAll(msdos_stub) catch unreachable; writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes writer.writeAll("PE\x00\x00") catch unreachable; - buffer.items[0x3c] = @intCast(u8, msdos_stub.len + 4); + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len + 4); var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, @@ -743,10 +850,7 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } - const size_of_optional_header = @intCast( - u16, - self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(), - ); + const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section @@ -774,6 +878,13 @@ fn writeHeader(self: *Coff) !void { .Lib => default_image_base_dll, else => unreachable, }; + const text_section = self.sections.get(self.text_section_index.?).header; + + var size_of_initialized_data: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.flags.CNT_INITIALIZED_DATA == 0) continue; + size_of_initialized_data += header.virtual_size; + } switch (self.ptr_width) { .p32 => { @@ -781,11 +892,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = 0, - .size_of_initialized_data = 0, + .size_of_code = text_section.virtual_size, + .size_of_initialized_data = size_of_initialized_data, .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = 0, + .base_of_code = text_section.virtual_address, .base_of_data = 0, .image_base = @intCast(u32, image_base), .section_alignment = self.page_size, @@ -816,11 +927,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = 0, - .size_of_initialized_data = 0, + .size_of_code = text_section.virtual_size, + .size_of_initialized_data = size_of_initialized_data, .size_of_uninitialized_data = 0, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = 0, + .base_of_code = text_section.virtual_address, .image_base = image_base, .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -971,6 +1082,7 @@ pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { if (name.len <= 8) { mem.copy(u8, &header.name, name); + mem.set(u8, header.name[name.len..], 0); return; } const offset = try self.strtab.insert(self.base.allocator, name); @@ -981,6 +1093,7 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { if (name.len <= 8) { mem.copy(u8, &symbol.name, name); + mem.set(u8, symbol.name[name.len..], 0); return; } const offset = try self.strtab.insert(self.base.allocator, name); -- cgit v1.2.3 From e5b8a1ac27367402c703a25774bc228499cfeb37 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 28 Aug 2022 16:10:02 +0200 Subject: coff: allocate and write atoms to file --- src/link/Coff.zig | 443 +++++++++++++++++++++++++++++++++++++------------ src/link/Coff/Atom.zig | 16 +- 2 files changed, 347 insertions(+), 112 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 74a0a5720d..a92c751ca7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -23,6 +23,7 @@ const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const Module = @import("../Module.zig"); +const Object = @import("Coff/Object.zig"); const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); @@ -39,6 +40,8 @@ error_flags: link.File.ErrorFlags = .{}, ptr_width: PtrWidth, page_size: u32, +objects: std.ArrayListUnmanaged(Object) = .{}, + sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, @@ -185,6 +188,11 @@ pub fn deinit(self: *Coff) void { if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); } + for (self.objects.items) |*object| { + object.deinit(gpa); + } + self.objects.deinit(gpa); + for (self.sections.items(.free_list)) |*free_list| { free_list.deinit(gpa); } @@ -211,15 +219,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.text_section_index == null) { self.text_section_index = @intCast(u16, self.sections.slice().len); - const file_size = self.base.options.program_code_size_hint; + const file_size = @intCast(u32, self.base.options.program_code_size_hint); const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .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, @@ -236,15 +244,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.pdata_section_index == null) { self.pdata_section_index = @intCast(u16, self.sections.slice().len); - const file_size = self.base.options.symbol_count_hint; + const file_size = @intCast(u32, self.base.options.symbol_count_hint); const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .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, @@ -260,15 +268,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.rdata_section_index == null) { self.rdata_section_index = @intCast(u16, self.sections.slice().len); - const file_size = 1024; + const file_size: u32 = 1024; const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .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, @@ -284,15 +292,15 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.data_section_index == null) { self.data_section_index = @intCast(u16, self.sections.slice().len); - const file_size = 1024; + const file_size: u32 = 1024; const off = self.findFreeSpace(file_size, self.page_size); log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, - .virtual_size = @intCast(u32, file_size), - .virtual_address = @intCast(u32, off), - .size_of_raw_data = @intCast(u32, file_size), - .pointer_to_raw_data = @intCast(u32, off), + .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, @@ -309,7 +317,7 @@ fn populateMissingMetadata(self: *Coff) !void { if (self.strtab_offset == null) { try self.strtab.buffer.append(gpa, 0); - self.strtab_offset = @intCast(u32, self.findFreeSpace(self.strtab.len(), 1)); + self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1); log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); } @@ -334,7 +342,7 @@ pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { try self.decls.putNoClobber(gpa, decl_index, null); } -fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { const tracy = trace(@src()); defer tracy.end(); @@ -362,10 +370,10 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, se const sym = big_atom.getSymbol(self); const capacity = big_atom.capacity(self); const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; - const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity; - const capacity_end_vaddr = sym.n_value + capacity; + const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.value + capacity; const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; - const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + const new_start_vaddr = mem.alignBackwardGeneric(u32, new_start_vaddr_unaligned, alignment); if (new_start_vaddr < ideal_capacity_end_vaddr) { // Additional bookkeeping here to notice if this free list node // should be deleted because the atom that it points to has grown to take up @@ -392,23 +400,30 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, se } else if (maybe_last_atom.*) |last| { const last_symbol = last.getSymbol(self); const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; - const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity; - const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u32, ideal_capacity_end_vaddr, alignment); atom_placement = last; break :blk new_start_vaddr; } else { - break :blk mem.alignForwardGeneric(u64, header.addr, alignment); + break :blk mem.alignForwardGeneric(u32, header.virtual_address, alignment); } }; const expand_section = atom_placement == null or atom_placement.?.next == null; if (expand_section) { - @panic("TODO expand section in allocateAtom"); + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address; + if (needed_size > sect_capacity) { + @panic("TODO move section"); + } + maybe_last_atom.* = atom; + header.virtual_size = needed_size; + header.size_of_raw_data = needed_size; } - if (header.getAlignment() < alignment) { - header.setAlignment(alignment); - } + // if (header.getAlignment().? < alignment) { + // header.setAlignment(alignment); + // } atom.size = new_atom_size; atom.alignment = alignment; @@ -465,30 +480,39 @@ fn allocateSymbol(self: *Coff) !u32 { pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { const gpa = self.base.allocator; try self.got_entries.ensureUnusedCapacity(gpa, 1); - if (self.got_entries_free_list.popOrNull()) |index| { - log.debug(" (reusing GOT entry index {d})", .{index}); - if (self.got_entries.getIndex(target)) |existing| { - assert(existing == index); + const index: u32 = blk: { + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + break :blk index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + self.got_entries.putAssumeCapacityNoClobber(target, 0); + break :blk index; } - self.got_entries.keys()[index] = target; - return index; - } else { - log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); - const index = @intCast(u32, self.got_entries.keys().len); - try self.got_entries.putAssumeCapacityNoClobber(target, 0); - return index; - } + }; + self.got_entries.keys()[index] = target; + return index; +} + +fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { + _ = self; + _ = target; + @panic("TODO createGotAtom"); } -fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { const sym = atom.getSymbol(self); - const align_ok = mem.alignBackwardGeneric(u64, sym.value, alignment) == sym.value; + const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value; const need_realloc = !align_ok or new_atom_size > atom.capacity(self); if (!need_realloc) return sym.value; return self.allocateAtom(atom, new_atom_size, alignment, sect_id); } -fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void { +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32, sect_id: u16) void { _ = self; _ = atom; _ = new_block_size; @@ -497,6 +521,22 @@ fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void // capacity, insert a free list node for it. } +fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { + const section = self.sections.get(sect_id); + const sym = atom.getSymbol(self); + const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; + try self.resolveRelocs(atom, code); + log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); + try self.base.file.?.pwriteAll(code, file_offset); +} + +fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) !void { + _ = self; + _ = atom; + _ = code; + log.debug("TODO resolveRelocs", .{}); +} + fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { log.debug("freeAtom {*}", .{atom}); @@ -583,8 +623,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - const sym = try self.updateDeclCode(decl_index, code); - log.debug("updated decl code has sym {}", .{sym}); + try self.updateDeclCode(decl_index, code); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -640,20 +679,104 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - const sym = try self.updateDeclCode(decl_index, code); - log.debug("updated decl code for {}", .{sym}); + try self.updateDeclCode(decl_index, code); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl_index, decl_exports); } -fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !*coff.Symbol { - _ = self; - _ = decl_index; - _ = code; - log.debug("TODO updateDeclCode", .{}); - return &self.locals.items[0]; +fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { + const ty = decl.ty; + const zig_ty = ty.zigTypeTag(); + const val = decl.val; + const index: u16 = blk: { + if (val.isUndefDeep()) { + // TODO in release-fast and release-small, we should put undef in .bss + break :blk self.data_section_index.?; + } + + switch (zig_ty) { + .Fn => break :blk self.text_section_index.?, + else => { + if (val.castTag(.variable)) |_| { + break :blk self.data_section_index.?; + } + break :blk self.rdata_section_index.?; + }, + } + }; + return index; +} + +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !void { + const gpa = self.base.allocator; + const mod = self.base.options.module.?; + const decl = mod.declPtr(decl_index); + + const decl_name = try decl.getFullyQualifiedName(mod); + defer gpa.free(decl_name); + + log.debug("updateDeclCode {s}{*}", .{ decl_name, decl }); + const required_alignment = decl.getAlignment(self.base.options.target); + + const decl_ptr = self.decls.getPtr(decl_index).?; + if (decl_ptr.* == null) { + decl_ptr.* = self.getDeclOutputSection(decl); + } + const sect_index = decl_ptr.*.?; + + const code_len = @intCast(u32, code.len); + const atom = &decl.link.coff; + assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() + if (atom.size != 0) { + const sym = atom.getSymbolPtr(self); + const capacity = atom.capacity(self); + const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment); + + if (need_realloc) { + const vaddr = try self.growAtom(atom, code_len, required_alignment, sect_index); + log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); + log.debug(" (required alignment 0x{x}", .{required_alignment}); + + if (vaddr != sym.value) { + sym.value = vaddr; + log.debug(" (updating GOT entry)", .{}); + var buffer: [@sizeOf(u64)]u8 = undefined; + const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; + try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + } + } else if (code_len < atom.size) { + self.shrinkAtom(atom, code_len, sect_index); + } + atom.size = code_len; + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + sym.storage_class = .NULL; + } else { + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + const vaddr = try self.allocateAtom(atom, code_len, required_alignment, sect_index); + errdefer self.freeAtom(atom, sect_index); + + log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); + + atom.size = code_len; + sym.value = vaddr; + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + sym.storage_class = .NULL; + + const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; + _ = try self.allocateGotEntry(got_target); + const got_atom = try self.createGotAtom(got_target); + + var buffer: [@sizeOf(u64)]u8 = undefined; + try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + } + + try self.writeAtom(atom, code, sect_index); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -701,40 +824,142 @@ pub fn updateDeclExports( @panic("Attempted to compile for object format that was disabled by build configuration"); } - // Even in the case of LLVM, we need to notice certain exported symbols in order to - // detect the default subsystem. + if (build_options.have_llvm) { + // Even in the case of LLVM, we need to notice certain exported symbols in order to + // detect the default subsystem. + for (exports) |exp| { + const exported_decl = module.declPtr(exp.exported_decl); + if (exported_decl.getFunction() == null) continue; + const winapi_cc = switch (self.base.options.target.cpu.arch) { + .i386 => std.builtin.CallingConvention.Stdcall, + else => std.builtin.CallingConvention.C, + }; + const decl_cc = exported_decl.ty.fnCallingConvention(); + if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and + self.base.options.link_libc) + { + module.stage1_flags.have_c_main = true; + } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { + if (mem.eql(u8, exp.options.name, "WinMain")) { + module.stage1_flags.have_winmain = true; + } else if (mem.eql(u8, exp.options.name, "wWinMain")) { + module.stage1_flags.have_wwinmain = true; + } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { + module.stage1_flags.have_winmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { + module.stage1_flags.have_wwinmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { + module.stage1_flags.have_dllmain_crt_startup = true; + } + } + } + + if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); + } + + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + + const decl = module.declPtr(decl_index); + const atom = &decl.link.coff; + if (atom.sym_index == 0) return; + const decl_sym = atom.getSymbol(self); + for (exports) |exp| { - const exported_decl = module.declPtr(exp.exported_decl); - if (exported_decl.getFunction() == null) continue; - const winapi_cc = switch (self.base.options.target.cpu.arch) { - .i386 => std.builtin.CallingConvention.Stdcall, - else => std.builtin.CallingConvention.C, - }; - const decl_cc = exported_decl.ty.fnCallingConvention(); - if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and - self.base.options.link_libc) - { - module.stage1_flags.have_c_main = true; - } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { - if (mem.eql(u8, exp.options.name, "WinMain")) { - module.stage1_flags.have_winmain = true; - } else if (mem.eql(u8, exp.options.name, "wWinMain")) { - module.stage1_flags.have_wwinmain = true; - } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { - module.stage1_flags.have_winmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - module.stage1_flags.have_wwinmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { - module.stage1_flags.have_dllmain_crt_startup = true; + log.debug("adding new export '{s}'", .{exp.options.name}); + + if (exp.options.section) |section_name| { + if (!mem.eql(u8, section_name, ".text")) { + try module.failed_exports.putNoClobber( + module.gpa, + exp, + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: ExportOptions.section", + .{}, + ), + ); + continue; } } + + if (exp.options.linkage == .LinkOnce) { + try module.failed_exports.putNoClobber( + module.gpa, + exp, + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: GlobalLinkage.LinkOnce", + .{}, + ), + ); + continue; + } + + const sym_index = exp.link.macho.sym_index orelse blk: { + const sym_index = try self.allocateSymbol(); + exp.link.coff.sym_index = sym_index; + break :blk sym_index; + }; + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + try self.setSymbolName(sym, exp.options.name); + sym.value = decl_sym.value; + sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + + switch (exp.options.linkage) { + .Strong => { + sym.storage_class = .EXTERNAL; + }, + .Internal => @panic("TODO Internal"), + .Weak => @panic("TODO WeakExternal"), + else => unreachable, + } + + self.resolveGlobalSymbol(sym_loc) catch |err| switch (err) { + error.MultipleSymbolDefinitions => { + const global = self.globals.get(exp.options.name).?; + if (sym_loc.sym_index != global.sym_index and global.file != null) { + _ = try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + \\LinkError: symbol '{s}' defined multiple times + \\ first definition in '{s}' + , + .{ exp.options.name, self.objects.items[global.file.?].name }, + )); + } + }, + else => |e| return e, + }; } +} - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); +fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { + const gpa = self.base.allocator; + const sym = self.getSymbol(current); + _ = sym; + const sym_name = self.getSymbolName(current); + + const name = try gpa.dupe(u8, sym_name); + const global_index = @intCast(u32, self.globals.values().len); + _ = global_index; + const gop = try self.globals.getOrPut(gpa, name); + defer if (gop.found_existing) gpa.free(name); + + if (!gop.found_existing) { + gop.value_ptr.* = current; + // TODO undef + tentative + return; } - log.debug("TODO updateDeclExports", .{}); + log.debug("TODO finish resolveGlobalSymbols implementation", .{}); + return error.MultipleSymbolDefinitions; } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -805,7 +1030,7 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v fn writeStrtab(self: *Coff) !void { const allocated_size = self.allocatedSize(self.strtab_offset.?); - const needed_size = self.strtab.len(); + const needed_size = @intCast(u32, self.strtab.len()); if (needed_size > allocated_size) { self.strtab_offset = null; @@ -870,7 +1095,7 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = @intCast(u32, self.getSizeOfHeaders()); + const size_of_headers: u32 = self.getSizeOfHeaders(); const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { @@ -968,7 +1193,7 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { math.maxInt(@TypeOf(actual_size)); } -fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { +fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { const headers_size = self.getSizeOfHeaders(); if (start < headers_size) return headers_size; @@ -976,7 +1201,7 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { const end = start + padToIdeal(size); if (self.strtab_offset) |off| { - const increased_size = padToIdeal(self.strtab.len()); + const increased_size = padToIdeal(@intCast(u32, self.strtab.len())); const test_end = off + increased_size; if (end > off and start < test_end) { return test_end; @@ -994,10 +1219,10 @@ fn detectAllocCollision(self: *Coff, start: u64, size: u64) ?u64 { return null; } -pub fn allocatedSize(self: *Coff, start: u64) u64 { +pub fn allocatedSize(self: *Coff, start: u32) u32 { if (start == 0) return 0; - var min_pos: u64 = std.math.maxInt(u64); + var min_pos: u32 = std.math.maxInt(u32); if (self.strtab_offset) |off| { if (off > start and off < min_pos) min_pos = off; } @@ -1008,41 +1233,41 @@ pub fn allocatedSize(self: *Coff, start: u64) u64 { return min_pos - start; } -pub fn findFreeSpace(self: *Coff, object_size: u64, min_alignment: u32) u64 { - var start: u64 = 0; +pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { + var start: u32 = 0; while (self.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForwardGeneric(u64, item_end, min_alignment); + start = mem.alignForwardGeneric(u32, item_end, min_alignment); } return start; } -inline fn getSizeOfHeaders(self: Coff) usize { +inline fn getSizeOfHeaders(self: Coff) u32 { const msdos_hdr_size = msdos_stub.len + 8; - return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + - self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize(); + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); } -inline fn getOptionalHeaderSize(self: Coff) usize { +inline fn getOptionalHeaderSize(self: Coff) u32 { return switch (self.ptr_width) { - .p32 => @sizeOf(coff.OptionalHeaderPE32), - .p64 => @sizeOf(coff.OptionalHeaderPE64), + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), }; } -inline fn getDataDirectoryHeadersSize(self: Coff) usize { - return self.data_directories.len * @sizeOf(coff.ImageDataDirectory); +inline fn getDataDirectoryHeadersSize(self: Coff) u32 { + return @intCast(u32, self.data_directories.len * @sizeOf(coff.ImageDataDirectory)); } -inline fn getSectionHeadersSize(self: Coff) usize { - return self.sections.slice().len * @sizeOf(coff.SectionHeader); +inline fn getSectionHeadersSize(self: Coff) u32 { + return @intCast(u32, self.sections.slice().len * @sizeOf(coff.SectionHeader)); } -inline fn getDataDirectoryHeadersOffset(self: Coff) usize { +inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { const msdos_hdr_size = msdos_stub.len + 8; - return msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize(); + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); } -inline fn getSectionHeadersOffset(self: Coff) usize { +inline fn getSectionHeadersOffset(self: Coff) u32 { return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } @@ -1086,7 +1311,7 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v return; } const offset = try self.strtab.insert(self.base.allocator, name); - const name_offset = try fmt.bufPrint(&header.name, "/{d}", .{offset}); + const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; mem.set(u8, header.name[name_offset.len..], 0); } @@ -1097,6 +1322,6 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { return; } const offset = try self.strtab.insert(self.base.allocator, name); - mem.copy(u8, symbol.name[0..4], 0); - _ = try fmt.bufPrint(symbol.name[4..], "{d}", .{offset}); + mem.set(u8, symbol.name[0..4], 0); + _ = fmt.bufPrint(symbol.name[4..], "{d}", .{offset}) catch unreachable; } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index b61e77f53e..d8420c8850 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -20,7 +20,7 @@ sym_index: u32, file: ?u32, /// Used size of the atom -size: u64, +size: u32, /// Alignment of the atom alignment: u32, @@ -44,10 +44,12 @@ pub fn deinit(self: *Atom, gpa: Allocator) void { _ = gpa; } +/// Returns symbol referencing this atom. pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { return self.getSymbolPtr(coff_file).*; } +/// Returns pointer-to-symbol referencing this atom. pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { return coff_file.getSymbolPtr(.{ .sym_index = self.sym_index, @@ -59,8 +61,16 @@ pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { return .{ .sym_index = self.sym_index, .file = self.file }; } +/// Returns the name of this atom. +pub fn getName(self: Atom, coff_file: *Coff) []const u8 { + return coff_file.getSymbolName(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + /// Returns how much room there is to grow in virtual address space. -pub fn capacity(self: Atom, coff_file: *Coff) u64 { +pub fn capacity(self: Atom, coff_file: *Coff) u32 { const self_sym = self.getSymbol(coff_file); if (self.next) |next| { const next_sym = next.getSymbol(coff_file); @@ -68,7 +78,7 @@ pub fn capacity(self: Atom, coff_file: *Coff) u64 { } else { // We are the last atom. // The capacity is limited only by virtual address space. - return std.math.maxInt(u64) - self_sym.value; + return std.math.maxInt(u32) - self_sym.value; } } -- cgit v1.2.3 From 30baba899cd20c6bc2224f3713f58ba40bbd8709 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 08:50:39 +0200 Subject: coff: add missing bits required for minimal PE example --- lib/std/start.zig | 2 +- src/Module.zig | 3 + src/link/Coff.zig | 315 +++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 243 insertions(+), 77 deletions(-) (limited to 'src/link') diff --git a/lib/std/start.zig b/lib/std/start.zig index e7056a69d0..9f70cce1ea 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -37,7 +37,7 @@ comptime { @export(main2, .{ .name = "main" }); } } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "wWinMainCRTStartup")) { + if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { diff --git a/src/Module.zig b/src/Module.zig index 706ab5ff17..66c36b939b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5391,6 +5391,9 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) void { if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| { wasm.deleteExport(exp.link.wasm); } + if (mod.comp.bin_file.cast(link.File.Coff)) |coff| { + coff.deleteExport(exp.link.coff); + } if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| { failed_kv.value.destroy(mod.gpa); } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a92c751ca7..ba66676706 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -46,8 +46,8 @@ sections: std.MultiArrayList(Section) = .{}, data_directories: [16]coff.ImageDataDirectory, text_section_index: ?u16 = null, +got_section_index: ?u16 = null, rdata_section_index: ?u16 = null, -pdata_section_index: ?u16 = null, data_section_index: ?u16 = null, locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, @@ -76,9 +76,49 @@ managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, /// Table of atoms indexed by the symbol index. atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, +/// Table of unnamed constants associated with a parent `Decl`. +/// We store them here so that we can free the constants whenever the `Decl` +/// needs updating or is freed. +/// +/// For example, +/// +/// ```zig +/// const Foo = struct{ +/// a: u8, +/// }; +/// +/// pub fn main() void { +/// var foo = Foo{ .a = 1 }; +/// _ = foo; +/// } +/// ``` +/// +/// value assigned to label `foo` is an unnamed constant belonging/associated +/// 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, +/// this will be a table indexed by index into the list of Atoms. +relocs: RelocTable = .{}, + +const Reloc = struct { + target: SymbolWithLoc, + offset: u32, + addend: u32, + prev_vaddr: u32, +}; + +const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc)); +const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom)); + const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; const default_image_base_exe: u64 = 0x10000; +const default_size_of_stack_reserve: u32 = 0x1000000; +const default_size_of_stack_commit: u32 = 0x1000; +const default_size_of_heap_reserve: u32 = 0x100000; +const default_size_of_heap_commit: u32 = 0x1000; const Section = struct { header: coff.SectionHeader, @@ -211,6 +251,22 @@ pub fn deinit(self: *Coff) void { self.got_entries_free_list.deinit(gpa); self.decls.deinit(gpa); self.atom_by_index_table.deinit(gpa); + + { + var it = self.unnamed_const_atoms.valueIterator(); + while (it.next()) |atoms| { + atoms.deinit(gpa); + } + self.unnamed_const_atoms.deinit(gpa); + } + + { + var it = self.relocs.valueIterator(); + while (it.next()) |relocs| { + relocs.deinit(gpa); + } + self.relocs.deinit(gpa); + } } fn populateMissingMetadata(self: *Coff) !void { @@ -242,11 +298,11 @@ fn populateMissingMetadata(self: *Coff) !void { try self.sections.append(gpa, .{ .header = header }); } - if (self.pdata_section_index == null) { - self.pdata_section_index = @intCast(u16, self.sections.slice().len); + 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 off = self.findFreeSpace(file_size, self.page_size); - log.debug("found .pdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size }); var header = coff.SectionHeader{ .name = undefined, .virtual_size = file_size, @@ -262,7 +318,7 @@ fn populateMissingMetadata(self: *Coff) !void { .MEM_READ = 1, }, }; - try self.setSectionName(&header, ".pdata"); + try self.setSectionName(&header, ".got"); try self.sections.append(gpa, .{ .header = header }); } @@ -330,6 +386,20 @@ fn populateMissingMetadata(self: *Coff) !void { .storage_class = .NULL, .number_of_aux_symbols = 0, }); + + { + // We need to find out what the max file offset is according to section headers. + // Otherwise, we may end up with an COFF binary with file size not matching the final section's + // offset + it's filesize. + // TODO I don't like this here one bit + var max_file_offset: u64 = 0; + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { + max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + } } pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { @@ -418,7 +488,7 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, se } maybe_last_atom.* = atom; header.virtual_size = needed_size; - header.size_of_raw_data = needed_size; + header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } // if (header.getAlignment().? < alignment) { @@ -499,9 +569,35 @@ pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { } fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { - _ = self; - _ = target; - @panic("TODO createGotAtom"); + const gpa = self.base.allocator; + const atom = try gpa.create(Atom); + errdefer gpa.destroy(atom); + atom.* = Atom.empty; + atom.sym_index = try self.allocateSymbol(); + atom.size = @sizeOf(u64); + atom.alignment = @alignOf(u64); + + try self.managed_atoms.append(gpa, atom); + try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + + const sym = atom.getSymbolPtr(self); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment, self.got_section_index.?); + sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + + log.debug("allocated {s} atom at 0x{x}", .{ atom.getName(self), sym.value }); + + const gop_relocs = try self.relocs.getOrPut(gpa, atom); + if (!gop_relocs.found_existing) { + gop_relocs.value_ptr.* = .{}; + } + try gop_relocs.value_ptr.append(gpa, .{ + .target = target, + .offset = 0, + .addend = 0, + .prev_vaddr = sym.value, + }); + + return atom; } fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { @@ -525,16 +621,46 @@ fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { const section = self.sections.get(sect_id); const sym = atom.getSymbol(self); const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; - try self.resolveRelocs(atom, code); + const resolved = try self.resolveRelocs(atom, code); + defer self.base.allocator.free(resolved); log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); - try self.base.file.?.pwriteAll(code, file_offset); + try self.base.file.?.pwriteAll(resolved, file_offset); } -fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) !void { - _ = self; - _ = atom; - _ = code; - log.debug("TODO resolveRelocs", .{}); +fn writeGotAtom(self: *Coff, atom: *Atom) !void { + switch (self.ptr_width) { + .p32 => { + var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); + try self.writeAtom(atom, &buffer, self.got_section_index.?); + }, + .p64 => { + var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); + try self.writeAtom(atom, &buffer, self.got_section_index.?); + }, + } +} + +fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) ![]const u8 { + const gpa = self.base.allocator; + const resolved = try gpa.dupe(u8, code); + const relocs = self.relocs.get(atom) orelse return resolved; + + for (relocs.items) |*reloc| { + const target_sym = self.getSymbol(reloc.target); + const target_vaddr = target_sym.value + reloc.addend; + if (target_vaddr == reloc.prev_vaddr) continue; + + log.debug(" ({x}: [() => 0x{x} ({s}))", .{ reloc.offset, target_vaddr, self.getSymbolName(reloc.target) }); + + switch (self.ptr_width) { + .p32 => mem.writeIntLittle(u32, resolved[reloc.offset..][0..4], @intCast(u32, target_vaddr)), + .p64 => mem.writeIntLittle(u64, resolved[reloc.offset..][0..8], target_vaddr), + } + + reloc.prev_vaddr = target_vaddr; + } + + return resolved; } fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { @@ -623,7 +749,7 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - try self.updateDeclCode(decl_index, code); + try self.updateDeclCode(decl_index, code, .FUNCTION); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -679,7 +805,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - try self.updateDeclCode(decl_index, code); + try self.updateDeclCode(decl_index, code, .NULL); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; @@ -709,7 +835,7 @@ fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { return index; } -fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !void { +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, complex_type: coff.ComplexType) !void { const gpa = self.base.allocator; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -742,9 +868,8 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) if (vaddr != sym.value) { sym.value = vaddr; log.debug(" (updating GOT entry)", .{}); - var buffer: [@sizeOf(u64)]u8 = undefined; const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; - try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + try self.writeGotAtom(got_atom); } } else if (code_len < atom.size) { self.shrinkAtom(atom, code_len, sect_index); @@ -752,8 +877,7 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) atom.size = code_len; try self.setSymbolName(sym, decl_name); sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; - sym.storage_class = .NULL; + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; } else { const sym = atom.getSymbolPtr(self); try self.setSymbolName(sym, decl_name); @@ -765,15 +889,12 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) atom.size = code_len; sym.value = vaddr; sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; - sym.storage_class = .NULL; + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; _ = try self.allocateGotEntry(got_target); const got_atom = try self.createGotAtom(got_target); - - var buffer: [@sizeOf(u64)]u8 = undefined; - try self.writeAtom(got_atom, &buffer, self.pdata_section_index.?); + try self.writeGotAtom(got_atom); } try self.writeAtom(atom, code, sect_index); @@ -900,7 +1021,7 @@ pub fn updateDeclExports( continue; } - const sym_index = exp.link.macho.sym_index orelse blk: { + const sym_index = exp.link.coff.sym_index orelse blk: { const sym_index = try self.allocateSymbol(); exp.link.coff.sym_index = sym_index; break :blk sym_index; @@ -921,22 +1042,36 @@ pub fn updateDeclExports( else => unreachable, } - self.resolveGlobalSymbol(sym_loc) catch |err| switch (err) { - error.MultipleSymbolDefinitions => { - const global = self.globals.get(exp.options.name).?; - if (sym_loc.sym_index != global.sym_index and global.file != null) { - _ = try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create( - gpa, - decl.srcLoc(), - \\LinkError: symbol '{s}' defined multiple times - \\ first definition in '{s}' - , - .{ exp.options.name, self.objects.items[global.file.?].name }, - )); - } - }, - else => |e| return e, - }; + try self.resolveGlobalSymbol(sym_loc); + } +} + +pub fn deleteExport(self: *Coff, exp: Export) void { + if (self.llvm_object) |_| return; + const sym_index = exp.sym_index orelse return; + + const gpa = self.base.allocator; + + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + const sym_name = self.getSymbolName(sym_loc); + log.debug("deleting export '{s}'", .{sym_name}); + assert(sym.storage_class == .EXTERNAL); + sym.* = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + self.locals_free_list.append(gpa, sym_index) catch {}; + + if (self.globals.get(sym_name)) |global| blk: { + if (global.sym_index != sym_index) break :blk; + if (global.file != null) break :blk; + const kv = self.globals.fetchSwapRemove(sym_name); + gpa.free(kv.?.key); } } @@ -959,7 +1094,6 @@ fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { } log.debug("TODO finish resolveGlobalSymbols implementation", .{}); - return error.MultipleSymbolDefinitions; } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -995,10 +1129,13 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + if (self.getEntryPoint()) |entry_sym_loc| { + self.entry_addr = self.getSymbol(entry_sym_loc).value; + } + try self.writeStrtab(); try self.writeDataDirectoriesHeaders(); try self.writeSectionHeaders(); - try self.writeHeader(); if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); @@ -1006,8 +1143,8 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; + try self.writeHeader(); } - self.error_flags.no_entry_point_found = false; } pub fn getDeclVAddr( @@ -1075,11 +1212,12 @@ fn writeHeader(self: *Coff) !void { flags.DLL = 1; } + const timestamp = std.time.timestamp(); const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); var coff_header = coff.CoffHeader{ .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section - .time_date_stamp = 0, // TODO + .time_date_stamp = @truncate(u32, @bitCast(u64, timestamp)), .pointer_to_symbol_table = self.strtab_offset orelse 0, .number_of_symbols = 0, .size_of_optional_header = size_of_optional_header, @@ -1095,20 +1233,30 @@ fn writeHeader(self: *Coff) !void { .NX_COMPAT = 1, // We are compatible with Data Execution Prevention }; const subsystem: coff.Subsystem = .WINDOWS_CUI; - const size_of_headers: u32 = self.getSizeOfHeaders(); - const size_of_image_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, self.page_size); - const size_of_headers_aligned: u32 = mem.alignForwardGeneric(u32, size_of_headers, default_file_alignment); + const size_of_image: u32 = self.getSizeOfImage(); + const size_of_headers: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), default_file_alignment); const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { .Exe => default_image_base_exe, .Lib => default_image_base_dll, else => unreachable, }; - const text_section = self.sections.get(self.text_section_index.?).header; + const base_of_code = self.sections.get(self.text_section_index.?).header.virtual_address; + const base_of_data = self.sections.get(self.data_section_index.?).header.virtual_address; + + var size_of_code: u32 = 0; var size_of_initialized_data: u32 = 0; + var size_of_uninitialized_data: u32 = 0; for (self.sections.items(.header)) |header| { - if (header.flags.CNT_INITIALIZED_DATA == 0) continue; - size_of_initialized_data += header.virtual_size; + if (header.flags.CNT_CODE == 1) { + size_of_code += header.size_of_raw_data; + } + if (header.flags.CNT_INITIALIZED_DATA == 1) { + size_of_initialized_data += header.size_of_raw_data; + } + if (header.flags.CNT_UNINITIALIZED_DATA == 1) { + size_of_uninitialized_data += header.size_of_raw_data; + } } switch (self.ptr_width) { @@ -1117,12 +1265,12 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = text_section.virtual_size, + .size_of_code = size_of_code, .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = 0, + .size_of_uninitialized_data = size_of_uninitialized_data, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = text_section.virtual_address, - .base_of_data = 0, + .base_of_code = base_of_code, + .base_of_data = base_of_data, .image_base = @intCast(u32, image_base), .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -1133,15 +1281,15 @@ fn writeHeader(self: *Coff) !void { .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = size_of_image_aligned, - .size_of_headers = size_of_headers_aligned, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, .checksum = 0, .subsystem = subsystem, .dll_flags = dll_flags, - .size_of_stack_reserve = 0, - .size_of_stack_commit = 0, - .size_of_heap_reserve = 0, - .size_of_heap_commit = 0, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; @@ -1152,11 +1300,11 @@ fn writeHeader(self: *Coff) !void { .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, .major_linker_version = 0, .minor_linker_version = 0, - .size_of_code = text_section.virtual_size, + .size_of_code = size_of_code, .size_of_initialized_data = size_of_initialized_data, - .size_of_uninitialized_data = 0, + .size_of_uninitialized_data = size_of_uninitialized_data, .address_of_entry_point = self.entry_addr orelse 0, - .base_of_code = text_section.virtual_address, + .base_of_code = base_of_code, .image_base = image_base, .section_alignment = self.page_size, .file_alignment = default_file_alignment, @@ -1167,15 +1315,15 @@ fn writeHeader(self: *Coff) !void { .major_subsystem_version = 6, .minor_subsystem_version = 0, .win32_version_value = 0, - .size_of_image = size_of_image_aligned, - .size_of_headers = size_of_headers_aligned, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, .checksum = 0, .subsystem = subsystem, .dll_flags = dll_flags, - .size_of_stack_reserve = 0, - .size_of_stack_commit = 0, - .size_of_heap_reserve = 0, - .size_of_heap_commit = 0, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, .loader_flags = 0, .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), }; @@ -1183,7 +1331,6 @@ fn writeHeader(self: *Coff) !void { }, } - try self.base.file.?.pwriteAll(&[_]u8{0}, size_of_headers_aligned); try self.base.file.?.pwriteAll(buffer.items, 0); } @@ -1271,6 +1418,22 @@ inline fn getSectionHeadersOffset(self: Coff) u32 { return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); } +inline fn getSizeOfImage(self: Coff) u32 { + var max_image_size: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.virtual_address + header.virtual_size > max_image_size) { + max_image_size = header.virtual_address + header.virtual_size; + } + } + return mem.alignForwardGeneric(u32, @maximum(max_image_size, self.getSizeOfHeaders()), self.page_size); +} + +/// Returns symbol location corresponding to the set entrypoint (if any). +pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { + const entry_name = self.base.options.entry orelse "mainCRTStartup"; // TODO this is incomplete + return self.globals.get(entry_name); +} + /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { assert(sym_loc.file == null); // TODO linking object files @@ -1323,5 +1486,5 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { } const offset = try self.strtab.insert(self.base.allocator, name); mem.set(u8, symbol.name[0..4], 0); - _ = fmt.bufPrint(symbol.name[4..], "{d}", .{offset}) catch unreachable; + mem.writeIntLittle(u32, symbol.name[4..8], offset); } -- cgit v1.2.3 From b4e3b87a526378c33d4158ccc622a5183eadfb3f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 13:21:32 +0200 Subject: coff: ...and lift-off! --- src/link/Coff.zig | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) (limited to 'src/link') diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ba66676706..bbe5e89a3d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -30,6 +30,7 @@ const TypedValue = @import("../TypedValue.zig"); pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); +const N_DATA_DIRS: u5 = 16; /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. llvm_object: ?*LlvmObject = null, @@ -43,7 +44,7 @@ page_size: u32, objects: std.ArrayListUnmanaged(Object) = .{}, sections: std.MultiArrayList(Section) = .{}, -data_directories: [16]coff.ImageDataDirectory, +data_directories: [N_DATA_DIRS]coff.ImageDataDirectory, text_section_index: ?u16 = null, got_section_index: ?u16 = null, @@ -114,7 +115,7 @@ const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayL const default_file_alignment: u16 = 0x200; const default_image_base_dll: u64 = 0x10000000; -const default_image_base_exe: u64 = 0x10000; +const default_image_base_exe: u64 = 0x400000; const default_size_of_stack_reserve: u32 = 0x1000000; const default_size_of_stack_commit: u32 = 0x1000; const default_size_of_heap_reserve: u32 = 0x100000; @@ -210,7 +211,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { }, .ptr_width = ptr_width, .page_size = page_size, - .data_directories = comptime mem.zeroes([16]coff.ImageDataDirectory), + .data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -487,8 +488,8 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, se @panic("TODO move section"); } maybe_last_atom.* = atom; - header.virtual_size = needed_size; - header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); + // header.virtual_size = needed_size; + // header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } // if (header.getAlignment().? < alignment) { @@ -1196,10 +1197,9 @@ fn writeHeader(self: *Coff) !void { try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); writer.writeAll(msdos_stub) catch unreachable; - writer.writeByteNTimes(0, 4) catch unreachable; // align to 8 bytes - writer.writeAll("PE\x00\x00") catch unreachable; - mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len + 4); + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len); + writer.writeAll("PE\x00\x00") catch unreachable; var flags = coff.CoffHeaderFlags{ .EXECUTABLE_IMAGE = 1, .DEBUG_STRIPPED = 1, // TODO @@ -1345,10 +1345,10 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { if (start < headers_size) return headers_size; - const end = start + padToIdeal(size); + const end = start + size; if (self.strtab_offset) |off| { - const increased_size = padToIdeal(@intCast(u32, self.strtab.len())); + const increased_size = @intCast(u32, self.strtab.len()); const test_end = off + increased_size; if (end > off and start < test_end) { return test_end; @@ -1356,7 +1356,7 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { } for (self.sections.items(.header)) |header| { - const increased_size = padToIdeal(header.size_of_raw_data); + const increased_size = header.size_of_raw_data; const test_end = header.pointer_to_raw_data + increased_size; if (end > header.pointer_to_raw_data and start < test_end) { return test_end; @@ -1389,7 +1389,7 @@ pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { } inline fn getSizeOfHeaders(self: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 8; + const msdos_hdr_size = msdos_stub.len + 4; return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); } @@ -1410,7 +1410,7 @@ inline fn getSectionHeadersSize(self: Coff) u32 { } inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { - const msdos_hdr_size = msdos_stub.len + 8; + const msdos_hdr_size = msdos_stub.len + 4; return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); } @@ -1419,13 +1419,11 @@ inline fn getSectionHeadersOffset(self: Coff) u32 { } inline fn getSizeOfImage(self: Coff) u32 { - var max_image_size: u32 = 0; + var image_size: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), self.page_size); for (self.sections.items(.header)) |header| { - if (header.virtual_address + header.virtual_size > max_image_size) { - max_image_size = header.virtual_address + header.virtual_size; - } + image_size += mem.alignForwardGeneric(u32, header.virtual_size, self.page_size); } - return mem.alignForwardGeneric(u32, @maximum(max_image_size, self.getSizeOfHeaders()), self.page_size); + return image_size; } /// Returns symbol location corresponding to the set entrypoint (if any). -- cgit v1.2.3 From db1a3bb0e70338dc5261adf148284c1408d3df87 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 14:12:39 +0200 Subject: coff: fallback to _start as default entry point for now This is not technically correct, but given that we are not yet able to link against the CRT, it's a good default until then. Add basic logging of generated symbol table in the linker. --- lib/std/start.zig | 4 --- src/link/Coff.zig | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 9 deletions(-) (limited to 'src/link') diff --git a/lib/std/start.zig b/lib/std/start.zig index 9f70cce1ea..49094ab02d 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -36,10 +36,6 @@ comptime { if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { @export(main2, .{ .name = "main" }); } - } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { - @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); - } } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { @export(wasiMain2, .{ .name = "_start" }); } else { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bbe5e89a3d..a90d323482 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1130,6 +1130,10 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); + if (build_options.enable_logging) { + self.logSymtab(); + } + if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; } @@ -1428,7 +1432,7 @@ inline fn getSizeOfImage(self: Coff) u32 { /// Returns symbol location corresponding to the set entrypoint (if any). pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { - const entry_name = self.base.options.entry orelse "mainCRTStartup"; // TODO this is incomplete + const entry_name = self.base.options.entry orelse "_start"; // TODO this is incomplete return self.globals.get(entry_name); } @@ -1439,14 +1443,15 @@ pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { } /// Returns symbol described by `sym_with_loc` descriptor. -pub fn getSymbol(self: *Coff, sym_loc: SymbolWithLoc) coff.Symbol { - return self.getSymbolPtr(sym_loc).*; +pub fn getSymbol(self: *const Coff, sym_loc: SymbolWithLoc) *const coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; } /// Returns name of the symbol described by `sym_with_loc` descriptor. -pub fn getSymbolName(self: *Coff, sym_loc: SymbolWithLoc) []const u8 { +pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 { assert(sym_loc.file == null); // TODO linking object files - const sym = self.locals.items[sym_loc.sym_index]; + const sym = self.getSymbol(sym_loc); const offset = sym.getNameOffset() orelse return sym.getName().?; return self.strtab.get(offset).?; } @@ -1486,3 +1491,81 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { mem.set(u8, symbol.name[0..4], 0); mem.writeIntLittle(u32, symbol.name[4..8], offset); } + +fn logSymAttributes(sym: *const coff.Symbol, buf: *[4]u8) []const u8 { + mem.set(u8, buf[0..4], '_'); + switch (sym.section_number) { + .UNDEFINED => { + buf[3] = 'u'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + .ABSOLUTE => unreachable, // handle ABSOLUTE + .DEBUG => unreachable, + else => { + buf[0] = 's'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + } + return buf[0..]; +} + +fn logSymtab(self: *Coff) void { + var buf: [4]u8 = undefined; + + log.debug("symtab:", .{}); + log.debug(" object(null)", .{}); + for (self.locals.items) |*sym, sym_id| { + const where = if (sym.section_number == .UNDEFINED) "ord" else "sect"; + const def_index: u16 = switch (sym.section_number) { + .UNDEFINED => 0, // TODO + .ABSOLUTE => unreachable, // TODO + .DEBUG => unreachable, // TODO + else => @enumToInt(sym.section_number), + }; + log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ + sym_id, + self.getSymbolName(.{ .sym_index = @intCast(u32, sym_id), .file = null }), + sym.value, + where, + def_index, + logSymAttributes(sym, &buf), + }); + } + + log.debug("globals table:", .{}); + for (self.globals.keys()) |name, id| { + const value = self.globals.values()[id]; + log.debug(" {s} => %{d} in object({?d})", .{ name, value.sym_index, value.file }); + } + + log.debug("GOT entries:", .{}); + for (self.got_entries.keys()) |target, i| { + const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null }); + const target_sym = self.getSymbol(target); + if (target_sym.section_number == .UNDEFINED) { + log.debug(" {d}@{x} => import('{s}')", .{ + i, + got_sym.value, + self.getSymbolName(target), + }); + } else { + log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{ + i, + got_sym.value, + target.sym_index, + target.file, + logSymAttributes(target_sym, &buf), + }); + } + } +} -- cgit v1.2.3 From f0d4ce4494f910508a127aad83cfbc557ac4aef9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 18:15:08 +0200 Subject: coff: add basic handling of GOT PC relative indirection --- src/arch/x86_64/CodeGen.zig | 62 ++++++++++++++++------ src/arch/x86_64/Emit.zig | 21 +++++++- src/link/Coff.zig | 125 +++++++++++++++++++++++++++++--------------- src/link/Coff/Atom.zig | 13 +++-- 4 files changed, 157 insertions(+), 64 deletions(-) (limited to 'src/link') diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d7263523c2..60ac26ecc6 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2664,6 +2664,10 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue }; const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); + const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag) + fn_owner_decl.link.macho.sym_index + else + fn_owner_decl.link.coff.sym_index; _ = try self.addInst(.{ .tag = .lea_pie, .ops = Mir.Inst.Ops.encode(.{ @@ -2672,7 +2676,7 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue }), .data = .{ .relocation = .{ - .atom_index = fn_owner_decl.link.macho.sym_index, + .atom_index = atom_index, .sym_index = sym_index, }, }, @@ -3961,21 +3965,17 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. const mod = self.bin_file.options.module.?; - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -3999,14 +3999,47 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. .data = undefined, }); } - } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { + } else if (self.bin_file.cast(link.File.Coff)) |_| { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const fn_owner_decl = mod.declPtr(func.owner_decl); - try self.genSetReg(Type.initTag(.usize), .rax, .{ - .got_load = fn_owner_decl.link.macho.sym_index, + const sym_index = fn_owner_decl.link.coff.sym_index; + try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); + // callq *%rax + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, }); + } else if (func_value.castTag(.extern_fn)) |_| { + return self.fail("TODO implement calling extern functions", .{}); + } else { + return self.fail("TODO implement calling bitcasted functions", .{}); + } + } else { + assert(ty.zigTypeTag() == .Pointer); + const mcv = try self.resolveInst(callee); + try self.genSetReg(Type.initTag(.usize), .rax, mcv); + _ = try self.addInst(.{ + .tag = .call, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = .rax, + .flags = 0b01, + }), + .data = undefined, + }); + } + } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { + if (self.air.value(callee)) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; + const fn_owner_decl = mod.declPtr(func.owner_decl); + const sym_index = fn_owner_decl.link.macho.sym_index; + try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); // callq *%rax _ = try self.addInst(.{ .tag = .call, @@ -6847,10 +6880,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + assert(decl.link.coff.sym_index != 0); + return MCValue{ .got_load = decl.link.coff.sym_index }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 8b61c6077c..9cf75ab7e3 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -994,6 +994,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { ); const end_offset = emit.code.items.len; + const gpa = emit.bin_file.allocator; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { const reloc_type = switch (ops.flags) { @@ -1003,7 +1004,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?; log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index }); - try atom.relocs.append(emit.bin_file.allocator, .{ + try atom.relocs.append(gpa, .{ .offset = @intCast(u32, end_offset - 4), .target = .{ .sym_index = relocation.sym_index, .file = null }, .addend = 0, @@ -1012,6 +1013,24 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .length = 2, .@"type" = reloc_type, }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?; + log.debug("adding reloc to local @{d}", .{relocation.sym_index}); + const gop = try coff_file.relocs.getOrPut(gpa, atom); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, .{ + .@"type" = switch (ops.flags) { + 0b00 => .got_pcrel, + 0b01 => .direct, + else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + }, + .target = .{ .sym_index = relocation.sym_index, .file = null }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .prev_vaddr = atom.getSymbol(coff_file).value, + }); } else { return emit.fail( "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a90d323482..0c3fbfd6a0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -104,6 +104,10 @@ unnamed_const_atoms: UnnamedConstTable = .{}, relocs: RelocTable = .{}, const Reloc = struct { + @"type": enum { + got_pcrel, + direct, + }, target: SymbolWithLoc, offset: u32, addend: u32, @@ -413,10 +417,11 @@ pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { try self.decls.putNoClobber(gpa, decl_index, null); } -fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { const tracy = trace(@src()); defer tracy.end(); + const sect_id = @enumToInt(atom.getSymbol(self).section_number) - 1; const header = &self.sections.items(.header)[sect_id]; const free_list = &self.sections.items(.free_list)[sect_id]; const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; @@ -580,18 +585,20 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { try self.managed_atoms.append(gpa, atom); try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + self.got_entries.getPtr(target).?.* = atom.sym_index; const sym = atom.getSymbolPtr(self); - sym.value = try self.allocateAtom(atom, atom.size, atom.alignment, self.got_section_index.?); sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment); - log.debug("allocated {s} atom at 0x{x}", .{ atom.getName(self), sym.value }); + log.debug("allocated GOT atom at 0x{x}", .{sym.value}); const gop_relocs = try self.relocs.getOrPut(gpa, atom); if (!gop_relocs.found_existing) { gop_relocs.value_ptr.* = .{}; } try gop_relocs.value_ptr.append(gpa, .{ + .@"type" = .direct, .target = target, .offset = 0, .addend = 0, @@ -601,72 +608,98 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { return atom; } -fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32, sect_id: u16) !u32 { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { const sym = atom.getSymbol(self); const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value; const need_realloc = !align_ok or new_atom_size > atom.capacity(self); if (!need_realloc) return sym.value; - return self.allocateAtom(atom, new_atom_size, alignment, sect_id); + return self.allocateAtom(atom, new_atom_size, alignment); } -fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32, sect_id: u16) void { +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32) void { _ = self; _ = atom; _ = new_block_size; - _ = sect_id; // TODO check the new capacity, and if it crosses the size threshold into a big enough // capacity, insert a free list node for it. } -fn writeAtom(self: *Coff, atom: *Atom, code: []const u8, sect_id: u16) !void { - const section = self.sections.get(sect_id); +fn writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void { const sym = atom.getSymbol(self); + const section = self.sections.get(@enumToInt(sym.section_number) - 1); const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; - const resolved = try self.resolveRelocs(atom, code); - defer self.base.allocator.free(resolved); log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); - try self.base.file.?.pwriteAll(resolved, file_offset); + try self.base.file.?.pwriteAll(code, file_offset); + try self.resolveRelocs(atom); } fn writeGotAtom(self: *Coff, atom: *Atom) !void { switch (self.ptr_width) { .p32 => { var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); - try self.writeAtom(atom, &buffer, self.got_section_index.?); + try self.writeAtom(atom, &buffer); }, .p64 => { var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); - try self.writeAtom(atom, &buffer, self.got_section_index.?); + try self.writeAtom(atom, &buffer); }, } } -fn resolveRelocs(self: *Coff, atom: *Atom, code: []const u8) ![]const u8 { - const gpa = self.base.allocator; - const resolved = try gpa.dupe(u8, code); - const relocs = self.relocs.get(atom) orelse return resolved; +fn resolveRelocs(self: *Coff, atom: *Atom) !void { + const relocs = self.relocs.get(atom) orelse return; + const source_sym = atom.getSymbol(self); + const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header; + const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address; + + log.debug("relocating '{s}'", .{atom.getName(self)}); for (relocs.items) |*reloc| { - const target_sym = self.getSymbol(reloc.target); - const target_vaddr = target_sym.value + reloc.addend; - if (target_vaddr == reloc.prev_vaddr) continue; + const target_vaddr = switch (reloc.@"type") { + .got_pcrel => blk: { + const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; + break :blk got_atom.getSymbol(self).value; + }, + .direct => self.getSymbol(reloc.target).value, + }; + const target_vaddr_with_addend = target_vaddr + reloc.addend; - log.debug(" ({x}: [() => 0x{x} ({s}))", .{ reloc.offset, target_vaddr, self.getSymbolName(reloc.target) }); + if (target_vaddr_with_addend == reloc.prev_vaddr) continue; - switch (self.ptr_width) { - .p32 => mem.writeIntLittle(u32, resolved[reloc.offset..][0..4], @intCast(u32, target_vaddr)), - .p64 => mem.writeIntLittle(u64, resolved[reloc.offset..][0..8], target_vaddr), + log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ + reloc.offset, + target_vaddr_with_addend, + self.getSymbolName(reloc.target), + @tagName(reloc.@"type"), + }); + + switch (reloc.@"type") { + .got_pcrel => { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + }, + .direct => switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => try self.base.file.?.pwriteAll( + mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + }, } - reloc.prev_vaddr = target_vaddr; + reloc.prev_vaddr = target_vaddr_with_addend; } - - return resolved; } -fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { +fn freeAtom(self: *Coff, atom: *Atom) void { log.debug("freeAtom {*}", .{atom}); + const sym = atom.getSymbol(self); + const sect_id = @enumToInt(sym.section_number) - 1; const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { @@ -858,11 +891,14 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() if (atom.size != 0) { const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + const capacity = atom.capacity(self); const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment); - if (need_realloc) { - const vaddr = try self.growAtom(atom, code_len, required_alignment, sect_index); + const vaddr = try self.growAtom(atom, code_len, required_alignment); log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); log.debug(" (required alignment 0x{x}", .{required_alignment}); @@ -873,24 +909,20 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, try self.writeGotAtom(got_atom); } } else if (code_len < atom.size) { - self.shrinkAtom(atom, code_len, sect_index); + self.shrinkAtom(atom, code_len); } atom.size = code_len; - try self.setSymbolName(sym, decl_name); - sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; } else { const sym = atom.getSymbolPtr(self); try self.setSymbolName(sym, decl_name); - const vaddr = try self.allocateAtom(atom, code_len, required_alignment, sect_index); - errdefer self.freeAtom(atom, sect_index); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + const vaddr = try self.allocateAtom(atom, code_len, required_alignment); + errdefer self.freeAtom(atom); log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); - atom.size = code_len; sym.value = vaddr; - sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); - sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; _ = try self.allocateGotEntry(got_target); @@ -898,7 +930,7 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, try self.writeGotAtom(got_atom); } - try self.writeAtom(atom, code, sect_index); + try self.writeAtom(atom, code); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -912,8 +944,8 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { log.debug("freeDecl {*}", .{decl}); const kv = self.decls.fetchRemove(decl_index); - if (kv.?.value) |index| { - self.freeAtom(&decl.link.coff, index); + if (kv.?.value) |_| { + self.freeAtom(&decl.link.coff); } // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. @@ -1134,6 +1166,13 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod self.logSymtab(); } + { + var it = self.relocs.keyIterator(); + while (it.next()) |atom| { + try self.resolveRelocs(atom.*); + } + } + if (self.getEntryPoint()) |entry_sym_loc| { self.entry_addr = self.getSymbol(entry_sym_loc).value; } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig index d8420c8850..2e59187d2b 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -45,8 +45,11 @@ pub fn deinit(self: *Atom, gpa: Allocator) void { } /// Returns symbol referencing this atom. -pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { - return self.getSymbolPtr(coff_file).*; +pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol { + return coff_file.getSymbol(.{ + .sym_index = self.sym_index, + .file = self.file, + }); } /// Returns pointer-to-symbol referencing this atom. @@ -62,7 +65,7 @@ pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { } /// Returns the name of this atom. -pub fn getName(self: Atom, coff_file: *Coff) []const u8 { +pub fn getName(self: Atom, coff_file: *const Coff) []const u8 { return coff_file.getSymbolName(.{ .sym_index = self.sym_index, .file = self.file, @@ -70,7 +73,7 @@ pub fn getName(self: Atom, coff_file: *Coff) []const u8 { } /// Returns how much room there is to grow in virtual address space. -pub fn capacity(self: Atom, coff_file: *Coff) u32 { +pub fn capacity(self: Atom, coff_file: *const Coff) u32 { const self_sym = self.getSymbol(coff_file); if (self.next) |next| { const next_sym = next.getSymbol(coff_file); @@ -82,7 +85,7 @@ pub fn capacity(self: Atom, coff_file: *Coff) u32 { } } -pub fn freeListEligible(self: Atom, coff_file: *Coff) bool { +pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { // No need to keep a free list node for the last atom. const next = self.next orelse return false; const self_sym = self.getSymbol(coff_file); -- cgit v1.2.3 From ebdb2867363256a265646f2a1b66d3762a51b7ba Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Aug 2022 18:57:02 +0200 Subject: coff: commit missing Object.zig placeholder --- src/link/Coff/Object.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/link/Coff/Object.zig (limited to 'src/link') diff --git a/src/link/Coff/Object.zig b/src/link/Coff/Object.zig new file mode 100644 index 0000000000..63bbd07463 --- /dev/null +++ b/src/link/Coff/Object.zig @@ -0,0 +1,12 @@ +const Object = @This(); + +const std = @import("std"); +const mem = std.mem; + +const Allocator = mem.Allocator; + +name: []const u8, + +pub fn deinit(self: *Object, gpa: Allocator) void { + gpa.free(self.name); +} -- cgit v1.2.3 From 601f2147e0344621d238e5209e0d36c69be3cc03 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Aug 2022 00:35:14 +0200 Subject: coff: cleanup relocations; remove COFF support from other backends Given that COFF will want to support PIC from ground-up, there is no point in leaving outdated code for COFF in other backends such as arm or aarch64. Instead, when we are ready to look into those, we can start figuring out what to add and where. --- src/arch/aarch64/CodeGen.zig | 18 +++++++---------- src/arch/arm/CodeGen.zig | 14 ++++--------- src/arch/riscv64/CodeGen.zig | 18 +++++++---------- src/arch/x86_64/CodeGen.zig | 20 +++++++++---------- src/arch/x86_64/Emit.zig | 35 ++++++++++++++++----------------- src/arch/x86_64/Mir.zig | 13 ++++++------ src/link/Coff.zig | 47 +++++++++++++++++++++++++------------------- src/link/Coff/Atom.zig | 12 +++++++++++ 8 files changed, 89 insertions(+), 88 deletions(-) (limited to 'src/link') diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 98f778ef35..884fd68d55 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3466,20 +3466,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // on linking. const mod = self.bin_file.options.module.?; if (self.air.value(callee)) |func_value| { - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -3547,6 +3543,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling bitcasted functions", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else unreachable; } else { assert(ty.zigTypeTag() == .Pointer); @@ -5110,10 +5108,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 0f796c530d..cefcf3b114 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3698,7 +3698,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. switch (self.bin_file.tag) { - .elf, .coff => { + .elf => { if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; @@ -3709,12 +3709,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk @intCast(u32, got_sym.value); } else unreachable; - try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { return self.fail("TODO implement calling extern functions", .{}); @@ -3752,6 +3747,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } }, .macho => unreachable, // unsupported architecture for MachO + .coff => return self.fail("TODO implement call in COFF for {}", .{self.target.cpu.arch}), .plan9 => return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch}), else => unreachable, } @@ -5549,10 +5545,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 2bb68086d8..cd1d0e4050 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1718,7 +1718,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. // Due to incremental compilation, how function calls are generated depends // on linking. - if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) { + if (self.bin_file.cast(link.File.Elf)) |elf_file| { for (info.args) |mc_arg, arg_i| { const arg = args[arg_i]; const arg_ty = self.air.typeOf(arg); @@ -1752,14 +1752,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const ptr_bytes: u64 = @divExact(ptr_bits, 8); const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(func.owner_decl); - const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { + const got_addr = blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - break :blk got_sym.value; - } else unreachable; + }; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -1778,6 +1774,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. } else { return self.fail("TODO implement calling runtime known function pointer", .{}); } + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch}); } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Plan9)) |_| { @@ -2592,10 +2590,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // TODO I'm hacking my way through here by repurposing .memory for storing // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; - const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); - return MCValue{ .memory = got_sym.value }; + } else if (self.bin_file.cast(link.File.Coff)) |_| { + return self.fail("TODO codegen COFF const Decl pointer", .{}); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 60ac26ecc6..e5d47e589a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2657,19 +2657,19 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue .direct_load, => |sym_index| { const abi_size = @intCast(u32, ptr_ty.abiSize(self.target.*)); - const flags: u2 = switch (ptr) { - .got_load => 0b00, - .direct_load => 0b01, - else => unreachable, - }; const mod = self.bin_file.options.module.?; const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl); const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag) fn_owner_decl.link.macho.sym_index else fn_owner_decl.link.coff.sym_index; + const flags: u2 = switch (ptr) { + .got_load => 0b00, + .direct_load => 0b01, + else => unreachable, + }; _ = try self.addInst(.{ - .tag = .lea_pie, + .tag = .lea_pic, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size), .flags = flags, @@ -4004,9 +4004,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. if (func_value.castTag(.function)) |func_payload| { const func = func_payload.data; const fn_owner_decl = mod.declPtr(func.owner_decl); - const sym_index = fn_owner_decl.link.coff.sym_index; - try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index }); - // callq *%rax + try self.genSetReg(Type.initTag(.usize), .rax, .{ + .got_load = fn_owner_decl.link.coff.sym_index, + }); _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ @@ -6876,8 +6876,6 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |_| { - // Because MachO is PIE-always-on, we defer memory address resolution until - // the linker has enough info to perform relocations. assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |_| { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 9cf75ab7e3..12f3e9118f 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -137,7 +137,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .fld => try emit.mirFld(inst), .lea => try emit.mirLea(inst), - .lea_pie => try emit.mirLeaPie(inst), + .lea_pic => try emit.mirLeaPic(inst), .shl => try emit.mirShift(.shl, inst), .sal => try emit.mirShift(.sal, inst), @@ -338,7 +338,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { .base = ops.reg1, }), emit.code); }, - 0b11 => return emit.fail("TODO unused JMP/CALL variant 0b11", .{}), + 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), } } @@ -784,7 +784,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // FD return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); }, - else => return emit.fail("TODO unused variant: movabs 0b{b}", .{ops.flags}), + else => return emit.fail("TODO unused movabs variant", .{}), } } @@ -978,12 +978,17 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea_pie); + assert(tag == .lea_pic); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const relocation = emit.mir.instructions.items(.data)[inst].relocation; + switch (ops.flags) { + 0b00, 0b01 => {}, + else => return emit.fail("TODO unused LEA PIC variants 0b10 and 0b11", .{}), + } + // lea reg1, [rip + reloc] // RM try lowerToRmEnc( @@ -1000,7 +1005,7 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const reloc_type = switch (ops.flags) { 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), - else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + else => unreachable, }; const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?; log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index }); @@ -1015,27 +1020,21 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }); } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?; - log.debug("adding reloc to local @{d}", .{relocation.sym_index}); - const gop = try coff_file.relocs.getOrPut(gpa, atom); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - try gop.value_ptr.append(gpa, .{ + try atom.addRelocation(coff_file, .{ .@"type" = switch (ops.flags) { - 0b00 => .got_pcrel, + 0b00 => .got, 0b01 => .direct, - else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), + else => unreachable, }, .target = .{ .sym_index = relocation.sym_index, .file = null }, .offset = @intCast(u32, end_offset - 4), .addend = 0, + .pcrel = true, + .length = 2, .prev_vaddr = atom.getSymbol(coff_file).value, }); } else { - return emit.fail( - "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", - .{}, - ); + return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index f67b48a271..71aecc5e85 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -178,11 +178,11 @@ pub const Inst = struct { lea, /// ops flags: form: - /// 0b00 reg1, [rip + reloc] // via GOT emits X86_64_RELOC_GOT relocation - /// 0b01 reg1, [rip + reloc] // direct load emits X86_64_RELOC_SIGNED relocation + /// 0b00 reg1, [rip + reloc] // via GOT PIC + /// 0b01 reg1, [rip + reloc] // direct load PIC /// Notes: /// * `Data` contains `relocation` - lea_pie, + lea_pic, /// ops flags: form: /// 0b00 reg1, 1 @@ -242,15 +242,14 @@ pub const Inst = struct { imul_complex, /// ops flags: form: - /// 0bX0 reg1, imm64 - /// 0bX1 rax, moffs64 + /// 0b00 reg1, imm64 + /// 0b01 rax, moffs64 /// Notes: /// * If reg1 is 64-bit, the immediate is 64-bit and stored /// within extra data `Imm64`. - /// * For 0bX1, reg1 (or reg2) need to be + /// * For 0b01, reg1 (or reg2) need to be /// a version of rax. If reg1 == .none, then reg2 == .rax, /// or vice versa. - /// TODO handle scaling movabs, /// ops flags: form: diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0c3fbfd6a0..36ddfc4e2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -103,14 +103,16 @@ unnamed_const_atoms: UnnamedConstTable = .{}, /// this will be a table indexed by index into the list of Atoms. relocs: RelocTable = .{}, -const Reloc = struct { +pub const Reloc = struct { @"type": enum { - got_pcrel, + got, direct, }, target: SymbolWithLoc, offset: u32, addend: u32, + pcrel: bool, + length: u2, prev_vaddr: u32, }; @@ -593,15 +595,13 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { log.debug("allocated GOT atom at 0x{x}", .{sym.value}); - const gop_relocs = try self.relocs.getOrPut(gpa, atom); - if (!gop_relocs.found_existing) { - gop_relocs.value_ptr.* = .{}; - } - try gop_relocs.value_ptr.append(gpa, .{ + try atom.addRelocation(self, .{ .@"type" = .direct, .target = target, .offset = 0, .addend = 0, + .pcrel = false, + .length = 3, .prev_vaddr = sym.value, }); @@ -656,7 +656,7 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void { for (relocs.items) |*reloc| { const target_vaddr = switch (reloc.@"type") { - .got_pcrel => blk: { + .got => blk: { const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; break :blk got_atom.getSymbol(self).value; }, @@ -673,21 +673,28 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void { @tagName(reloc.@"type"), }); - switch (reloc.@"type") { - .got_pcrel => { - const source_vaddr = source_sym.value + reloc.offset; - const disp = target_vaddr_with_addend - source_vaddr - 4; - try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); - }, - .direct => switch (self.ptr_width) { - .p32 => try self.base.file.?.pwriteAll( - mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + if (reloc.pcrel) { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + return; + } + + switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => switch (reloc.length) { + 2 => try self.base.file.?.pwriteAll( + mem.asBytes(&@truncate(u32, target_vaddr_with_addend + default_image_base_exe)), file_offset + reloc.offset, ), - .p64 => try self.base.file.?.pwriteAll( + 3 => try self.base.file.?.pwriteAll( mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), file_offset + reloc.offset, ), + else => unreachable, }, } @@ -1270,8 +1277,8 @@ fn writeHeader(self: *Coff) !void { writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; const dll_flags: coff.DllFlags = .{ - .HIGH_ENTROPY_VA = 0, // TODO handle ASLR - .DYNAMIC_BASE = 0, // TODO handle ASLR + .HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie), + .DYNAMIC_BASE = 0, .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 2e59187d2b..6c085a8f58 100644 --- a/src/link/Coff/Atom.zig +++ b/src/link/Coff/Atom.zig @@ -6,6 +6,7 @@ const coff = std.coff; const Allocator = std.mem.Allocator; const Coff = @import("../Coff.zig"); +const Reloc = Coff.Reloc; const SymbolWithLoc = Coff.SymbolWithLoc; /// Each decl always gets a local symbol with the fully qualified name. @@ -96,3 +97,14 @@ pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool { const surplus = cap - ideal_cap; return surplus >= Coff.min_text_capacity; } + +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 }); + const gop = try coff_file.relocs.getOrPut(gpa, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, reloc); +} -- cgit v1.2.3