From 57a81bb5596f7487d728cf9a91013347e368912b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 29 Sep 2020 08:40:00 +0200 Subject: [PATCH] Re-draft required elements for minimal MachO binary --- src/link/MachO.zig | 135 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 961b64a840..6eb4e523bd 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -27,6 +27,10 @@ const LoadCommand = union(enum) { LinkeditData: macho.linkedit_data_command, Symtab: macho.symtab_command, Dysymtab: macho.dysymtab_command, + DyldInfo: macho.dyld_info_command, + Dylinker: macho.dylinker_command, + Dylib: macho.dylib_command, + EntryPoint: macho.entry_point_command, pub fn cmdsize(self: LoadCommand) u32 { return switch (self) { @@ -34,6 +38,10 @@ const LoadCommand = union(enum) { .LinkeditData => |x| x.cmdsize, .Symtab => |x| x.cmdsize, .Dysymtab => |x| x.cmdsize, + .DyldInfo => |x| x.cmdsize, + .Dylinker => |x| x.cmdsize, + .Dylib => |x| x.cmdsize, + .EntryPoint => |x| x.cmdsize, }; } @@ -43,6 +51,10 @@ const LoadCommand = union(enum) { .LinkeditData => |cmd| writeGeneric(cmd, file, offset), .Symtab => |cmd| writeGeneric(cmd, file, offset), .Dysymtab => |cmd| writeGeneric(cmd, file, offset), + .DyldInfo => |cmd| writeGeneric(cmd, file, offset), + .Dylinker => |cmd| writeGeneric(cmd, file, offset), + .Dylib => |cmd| writeGeneric(cmd, file, offset), + .EntryPoint => |cmd| writeGeneric(cmd, file, offset), }; } @@ -56,24 +68,42 @@ base: File, /// Table of all load commands load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, +/// __PAGEZERO segment +pagezero_segment_cmd_index: ?u16 = null, +/// __TEXT segment +text_segment_cmd_index: ?u16 = null, +/// __DATA segment +data_segment_cmd_index: ?u16 = null, +/// __LINKEDIT segment +linkedit_segment_cmd_index: ?u16 = null, segment_cmd_index: ?u16 = null, +/// Dyld info +dyld_info_cmd_index: ?u16 = null, +/// Symbol table symtab_cmd_index: ?u16 = null, +/// Dynamic symbol table dysymtab_cmd_index: ?u16 = null, +/// Path to dyld linker +dylinker_cmd_index: ?u16 = null, +/// Path to libSystem +libsystem_cmd_index: ?u16 = null, +/// Data-in-code section of __LINKEDIT segment data_in_code_cmd_index: ?u16 = null, +/// Address to entry point function +function_starts_cmd_index: ?u16 = null, +/// Main/entry point +/// Specifies offset wrt __TEXT segment start address to the main entry point +/// of the binary. +main_cmd_index: ?u16 = null, /// Table of all sections sections: std.ArrayListUnmanaged(macho.section_64) = .{}, -/// __TEXT segment sections +/// __TEXT,__text section text_section_index: ?u16 = null, -cstring_section_index: ?u16 = null, -const_text_section_index: ?u16 = null, -stubs_section_index: ?u16 = null, -stub_helper_section_index: ?u16 = null, -/// __DATA segment sections +/// __DATA,__got section got_section_index: ?u16 = null, -const_data_section_index: ?u16 = null, entry_addr: ?u64 = null, @@ -734,11 +764,13 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .n_desc = 0, .n_value = addr, }; + self.offset_table.items[decl.link.macho.offset_table_index.?] = addr; // 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) orelse &[0]*Module.Export{}; try self.updateDeclExports(module, decl, decl_exports); try self.writeSymbol(decl.link.macho.symbol_table_index.?); + try self.writeOffsetTableEntry(decl.link.macho.offset_table_index.?); const text_section = self.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; @@ -778,6 +810,25 @@ pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { } pub fn populateMissingMetadata(self: *MachO) !void { + if (self.pagezero_segment_cmd_index == null) { + self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .Segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__PAGEZERO"), + .vmaddr = 0, + .vmsize = 0x1000, // size always set to 4GB + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }, + }); + self.cmd_table_dirty = true; + } if (self.segment_cmd_index == null) { self.segment_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ @@ -818,7 +869,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { segment.nsects += 1; const file_size = self.base.options.program_code_size_hint; - const off = @intCast(u32, self.findFreeSpace(file_size, 1)); + const off = @intCast(u32, self.findFreeSpace(file_size, 64)); const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); @@ -829,7 +880,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .addr = 0, .size = file_size, .offset = off, - .@"align" = 0x1000, + .@"align" = 12, .reloff = 0, .nreloc = 0, .flags = flags, @@ -843,6 +894,43 @@ pub fn populateMissingMetadata(self: *MachO) !void { segment.fileoff = off; log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]}); + log.debug("update segment {}\n", .{segment}); + } + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.items.len); + const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; + const text_sect = &self.sections.items[self.text_section_index.?]; + segment.cmdsize += @sizeOf(macho.section_64); + segment.nsects += 1; + + const p_align = @sizeOf(u64); + const file_size = p_align * self.base.options.symbol_count_hint; + const off = @intCast(u32, self.findFreeSpace(file_size, p_align)); + + log.debug("found __got section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + + const padding_size = off - text_sect.offset - text_sect.size; + + try self.sections.append(self.base.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA"), + .addr = text_sect.addr + text_sect.size + padding_size, + .size = file_size, + .offset = off, + .@"align" = 3, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + + segment.vmsize += file_size + padding_size; + segment.filesize += file_size + padding_size; + + log.debug("initial got section {}\n", .{self.sections.items[self.got_section_index.?]}); + log.debug("update segment {}\n", .{segment}); } { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; @@ -875,8 +963,9 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const addr = blk: { if (self.last_text_block) |last| { const last_symbol = self.symbol_table.items[last.symbol_table_index.?]; - const end_addr = last_symbol.n_value + last.size; - const new_start_addr = mem.alignForwardGeneric(u64, end_addr, alignment); + const ideal_capacity = last.size * alloc_num / alloc_den; + const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity; + const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment); block_placement = last; break :blk new_start_addr; } else { @@ -893,12 +982,6 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, assert(needed_size <= text_capacity); // TODO handle growth self.last_text_block = text_block; - text_section.size = needed_size; - segment.vmsize = needed_size; - segment.filesize = needed_size; - if (alignment < text_section.@"align") { - text_section.@"align" = @intCast(u32, alignment); - } } text_block.size = new_block_size; @@ -961,11 +1044,8 @@ fn addPadding(self: *MachO, size: u64, file_offset: u64) !void { fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { const hdr_size: u64 = @sizeOf(macho.mach_header_64); - if (start < hdr_size) - return hdr_size; - + if (start < hdr_size) return hdr_size; const end = start + satMul(size, alloc_num) / alloc_den; - { const off = @sizeOf(macho.mach_header_64); var tight_size: u64 = 0; @@ -978,7 +1058,6 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { return test_end; } } - for (self.sections.items) |section| { const increased_size = satMul(section.size, alloc_num) / alloc_den; const test_end = section.offset + increased_size; @@ -986,7 +1065,6 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { return test_end; } } - if (self.symtab_cmd_index) |symtab_index| { const symtab = self.load_commands.items[symtab_index].Symtab; { @@ -1005,7 +1083,6 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { } } } - return null; } @@ -1048,6 +1125,16 @@ fn writeSymbol(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); } +fn writeOffsetTableEntry(self: *MachO, index: usize) !void { + const sect = &self.sections.items[self.got_section_index.?]; + const endian = self.base.options.target.cpu.arch.endian(); + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + const off = sect.offset + @sizeOf(u64) * index; + log.debug("writing offset table entry 0x{x} at 0x{x}\n", .{ self.offset_table.items[index], off }); + try self.base.file.?.pwriteAll(&buf, off); +} + /// Writes Mach-O file header. /// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping /// variables.