From 3d07f057b1ccbc20258114fde762969bcfddf1d3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 5 Jan 2021 21:58:33 +0100 Subject: [PATCH 01/16] macho: prealloc DATA_CONST and DATA segments --- src/link/MachO.zig | 77 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dbc982f5e2..8252d6925d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -52,6 +52,8 @@ load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, pagezero_segment_cmd_index: ?u16 = null, /// __TEXT segment text_segment_cmd_index: ?u16 = null, +/// __DATA_CONST segment +data_const_segment_cmd_index: ?u16 = null, /// __DATA segment data_segment_cmd_index: ?u16 = null, /// __LINKEDIT segment @@ -1519,6 +1521,64 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } + if (self.data_const_segment_cmd_index == null) { + self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; + const address_and_offset = self.nextSegmentAddressAndOffset(); + + const ideal_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); + + log.debug("found __DATA_CONST segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size }); + + try self.load_commands.append(self.base.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA_CONST"), + .vmaddr = address_and_offset.address, + .vmsize = needed_size, + .fileoff = address_and_offset.offset, + .filesize = needed_size, + .maxprot = maxprot, + .initprot = initprot, + .nsects = 0, + .flags = 0, + }), + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } + if (self.data_segment_cmd_index == null) { + self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; + const initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; + const address_and_offset = self.nextSegmentAddressAndOffset(); + + const ideal_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); + + log.debug("found __DATA segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size }); + + try self.load_commands.append(self.base.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA"), + .vmaddr = address_and_offset.address, + .vmsize = needed_size, + .fileoff = address_and_offset.offset, + .filesize = needed_size, + .maxprot = maxprot, + .initprot = initprot, + .nsects = 0, + .flags = 0, + }), + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -1907,16 +1967,13 @@ const NextSegmentAddressAndOffset = struct { }; fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset { - const prev_segment_idx = blk: { - if (self.data_segment_cmd_index) |idx| { - break :blk idx; - } else if (self.text_segment_cmd_index) |idx| { - break :blk idx; - } else { - unreachable; // unhandled LC_SEGMENT_64 load command before __TEXT + var prev_segment_idx: ?usize = null; // We use optional here for safety. + for (self.load_commands.items) |cmd, i| { + if (cmd == .Segment) { + prev_segment_idx = i; } - }; - const prev_segment = self.load_commands.items[prev_segment_idx].Segment; + } + const prev_segment = self.load_commands.items[prev_segment_idx.?].Segment; const address = prev_segment.inner.vmaddr + prev_segment.inner.vmsize; const offset = prev_segment.inner.fileoff + prev_segment.inner.filesize; return .{ @@ -2464,6 +2521,8 @@ fn parseFromFile(self: *MachO, file: fs.File) !void { } } else if (parseAndCmpName(&x.inner.segname, "__DATA")) { self.data_segment_cmd_index = i; + } else if (parseAndCmpName(&x.inner.segname, "__DATA_CONST")) { + self.data_const_segment_cmd_index = i; } }, macho.LC_DYLD_INFO_ONLY => { From 51a13f8ea6f1596a3163ac656e0ddb1156d1399a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 5 Jan 2021 23:17:53 +0100 Subject: [PATCH 02/16] macho: add missing data sections --- src/link/MachO.zig | 172 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8252d6925d..61be154a13 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -89,6 +89,16 @@ code_signature_cmd_index: ?u16 = null, text_section_index: ?u16 = null, /// Index into __TEXT,__ziggot section. got_section_index: ?u16 = null, +/// Index into __TEXT,__stubs section. +stubs_section_index: ?u16 = null, +/// Index into __TEXT,__stub_helper section. +stub_helper_section_index: ?u16 = null, +/// Index into __DATA_CONST,__got section. +data_got_section_index: ?u16 = null, +/// Index into __DATA,__la_symbol_ptr section. +la_symbol_ptr_section_index: ?u16 = null, +/// Index into __DATA,__data section. +data_section_index: ?u16 = null, /// The absolute address of the entry point. entry_addr: ?u64 = null, @@ -1437,7 +1447,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const program_code_size_hint = self.base.options.program_code_size_hint; const offset_table_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint; - const ideal_size = self.header_pad + program_code_size_hint + offset_table_size_hint; + const ideal_size = self.header_pad + program_code_size_hint + 3 * offset_table_size_hint; const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size }); @@ -1494,9 +1504,13 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.got_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const text_section = &text_segment.sections.items[self.text_section_index.?]; self.got_section_index = @intCast(u16, text_segment.sections.items.len); + const alignment: u2 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); @@ -1510,7 +1524,73 @@ pub fn populateMissingMetadata(self: *MachO) !void { .addr = text_segment.inner.vmaddr + off, .size = needed_size, .offset = @intCast(u32, off), - .@"align" = 3, // 2^@sizeOf(u64) + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } + if (self.stubs_section_index == null) { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stubs_section_index = @intCast(u16, text_segment.sections.items.len); + + const alignment: u2 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); + assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. + + log.debug("found __stubs section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + + try text_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__stubs"), + .segname = makeStaticString("__TEXT"), + .addr = text_segment.inner.vmaddr + off, + .size = 0, // This will be populated later in tandem with .reserved2 field. + .offset = @intCast(u32, off), + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } + if (self.stub_helper_section_index == null) { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stub_helper_section_index = @intCast(u16, text_segment.sections.items.len); + + const alignment: u2 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); + assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. + + log.debug("found __stubs section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + + try text_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__stub_helper"), + .segname = makeStaticString("__TEXT"), + .addr = text_segment.inner.vmaddr + off, + .size = needed_size, + .offset = @intCast(u32, off), + .@"align" = alignment, .reloff = 0, .nreloc = 0, .flags = flags, @@ -1550,13 +1630,41 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } + if (self.data_got_section_index == null) { + const dc_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + self.data_got_section_index = @intCast(u16, dc_segment.sections.items.len); + + const flags = macho.S_NON_LAZY_SYMBOL_POINTERS; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = dc_segment.findFreeSpace(needed_size, @alignOf(u64), null); + assert(off + needed_size <= dc_segment.inner.fileoff + dc_segment.inner.filesize); // TODO Must expand __DATA_CONST segment. + + log.debug("found __got section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + + try dc_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA_CONST"), + .addr = dc_segment.inner.vmaddr + off - dc_segment.inner.fileoff, + .size = needed_size, + .offset = @intCast(u32, off), + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } if (self.data_segment_cmd_index == null) { self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE; const initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE; const address_and_offset = self.nextSegmentAddressAndOffset(); - const ideal_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const ideal_size = 2 * @sizeOf(u64) * self.base.options.symbol_count_hint; const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, self.page_size); log.debug("found __DATA segment free space 0x{x} to 0x{x}", .{ address_and_offset.offset, address_and_offset.offset + needed_size }); @@ -1579,6 +1687,62 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } + if (self.la_symbol_ptr_section_index == null) { + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.la_symbol_ptr_section_index = @intCast(u16, data_segment.sections.items.len); + + const flags = macho.S_LAZY_SYMBOL_POINTERS; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null); + assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment. + + log.debug("found __la_symbol_ptr section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + + try data_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__la_symbol_ptr"), + .segname = makeStaticString("__DATA"), + .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff, + .size = needed_size, + .offset = @intCast(u32, off), + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } + if (self.data_section_index == null) { + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.data_section_index = @intCast(u16, data_segment.sections.items.len); + + const flags = macho.S_REGULAR; + const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; + const off = data_segment.findFreeSpace(needed_size, @alignOf(u64), null); + assert(off + needed_size <= data_segment.inner.fileoff + data_segment.inner.filesize); // TODO Must expand __DATA segment. + + log.debug("found __data section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + + try data_segment.addSection(self.base.allocator, .{ + .sectname = makeStaticString("__data"), + .segname = makeStaticString("__DATA"), + .addr = data_segment.inner.vmaddr + off - data_segment.inner.fileoff, + .size = needed_size, + .offset = @intCast(u32, off), + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + self.header_dirty = true; + self.load_commands_dirty = true; + } if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); From f44732c1b0d7516c4a8169f7381cbcf55e1ae460 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 6 Jan 2021 01:13:25 +0100 Subject: [PATCH 03/16] macho: populate stubs and stub_helper --- src/codegen.zig | 22 +++++++++++-- src/link/MachO.zig | 80 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index d83eba2219..7100227db0 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1859,8 +1859,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => unreachable, // unsupported architecture on MachO } - } else if (func_value.castTag(.extern_fn)) |_| { - return self.fail(inst.base.src, "TODO implement calling extern functions", .{}); + } else if (func_value.castTag(.extern_fn)) |func_payload| { + const decl = func_payload.data; + const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); + defer self.bin_file.allocator.free(decl_name); + const name = try macho_file.makeString(decl_name); + const symbol = macho_file.undef_symbols.items.len; + try macho_file.undef_symbols.append(self.bin_file.allocator, .{ + .n_strx = name, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }); + try macho_file.stub_fixups.append(self.bin_file.allocator, .{ + .symbol = symbol, + .start = self.code.items.len, + .len = 4, + }); + // We mark the space and fix it up later. + writeInt(u32, try self.code.addManyAsArray(4), 0); } else { return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 61be154a13..079478ecfd 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -115,6 +115,7 @@ global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, dyld_stub_binder_index: ?u16 = null, +next_stub_helper_off: ?u64 = null, /// Table of symbol names aka the string table. string_table: std.ArrayListUnmanaged(u8) = .{}, @@ -162,6 +163,14 @@ last_text_block: ?*TextBlock = null, /// rather than sitting in the global scope. pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, +stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, + +pub const StubFixup = struct { + symbol: usize, + start: usize, + len: usize, +}; + pub const PieFixup = struct { /// Target address we wanted to address in absolute terms. address: u64, @@ -1223,7 +1232,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } // Perform PIE fixups (if any) - const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const got_section = text_segment.sections.items[self.got_section_index.?]; while (self.pie_fixups.popOrNull()) |fixup| { const target_addr = fixup.address; @@ -1243,6 +1252,45 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } } + // Resolve stubs (if any) + const stubs = &text_segment.sections.items[self.stubs_section_index.?]; + const stub_h = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + while (self.stub_fixups.popOrNull()) |fixup| { + // TODO increment offset for stub writing + const stub_addr = stubs.addr; + const text_addr = symbol.n_value + fixup.start; + const displacement = @intCast(u32, stub_addr - text_addr); + var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + + const end = stub_h.addr + self.next_stub_helper_off.? - stub_h.offset; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, end); + try self.base.file.?.pwriteAll(&buf, la_ptr.offset); + + const displacement2 = la_ptr.addr - stubs.addr; + var ccode: [2 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = @intCast(u19, displacement2 / 4), + }).toU32()); + mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32()); + try self.base.file.?.pwriteAll(&ccode, stubs.offset); + stubs.size = 2 * @sizeOf(u32); + stubs.reserved2 = 2 * @sizeOf(u32); + + const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4); + var cccode: [3 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, cccode[0..4], aarch64.Instruction.ldr(.w16, .{ + .literal = 0x2, + }).toU32()); + mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); + mem.writeIntLittle(u32, cccode[8..12], 0); + try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); + self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); + } + const text_section = text_segment.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; const file_offset = text_section.offset + section_offset; @@ -1555,7 +1603,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .sectname = makeStaticString("__stubs"), .segname = makeStaticString("__TEXT"), .addr = text_segment.inner.vmaddr + off, - .size = 0, // This will be populated later in tandem with .reserved2 field. + .size = needed_size, .offset = @intCast(u32, off), .@"align" = alignment, .reloff = 0, @@ -1582,7 +1630,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. - log.debug("found __stubs section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); + log.debug("found __stub_helper section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); try text_segment.addSection(self.base.allocator, .{ .sectname = makeStaticString("__stub_helper"), @@ -1987,6 +2035,30 @@ pub fn populateMissingMetadata(self: *MachO) !void { .n_value = 0, }); } + if (self.next_stub_helper_off == null) { + const text = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const sh = &text.sections.items[self.stub_helper_section_index.?]; + const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data_data = &data.sections.items[self.data_section_index.?]; + const displacement = data_data.addr - sh.addr; + var code: [4 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, @intCast(i21, displacement)).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( + .x16, + .x17, + aarch64.Register.sp, + aarch64.Instruction.LoadStorePairOffset.pre_index(-16), + ).toU32()); + const dc = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &dc.sections.items[self.data_got_section_index.?]; + const displacement2 = got.addr - sh.addr - 2 * @sizeOf(u32); + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ + .literal = @intCast(u19, displacement2 / 4), + }).toU32()); + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); + self.next_stub_helper_off = sh.offset + 4 * @sizeOf(u32); + try self.base.file.?.pwriteAll(&code, sh.offset); + } } fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { @@ -2101,7 +2173,7 @@ pub fn makeStaticString(comptime bytes: []const u8) [16]u8 { return buf; } -fn makeString(self: *MachO, bytes: []const u8) !u32 { +pub fn makeString(self: *MachO, bytes: []const u8) !u32 { try self.string_table.ensureCapacity(self.base.allocator, self.string_table.items.len + bytes.len + 1); const result = @intCast(u32, self.string_table.items.len); self.string_table.appendSliceAssumeCapacity(bytes); From 5ae82956aac029fb45c09e5da2db8d86b9fa0157 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 6 Jan 2021 17:24:48 +0100 Subject: [PATCH 04/16] macho: write out rebase info --- src/link/MachO.zig | 127 +++++++++++++++++++++++++++++-------- src/link/MachO/imports.zig | 43 +++++++++++++ 2 files changed, 144 insertions(+), 26 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 079478ecfd..23ebff1784 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -123,6 +123,8 @@ string_table: std.ArrayListUnmanaged(u8) = .{}, /// Table of trampolines to the actual symbols in __text section. offset_table: std.ArrayListUnmanaged(u64) = .{}, +/// Table of rebase info entries. +rebase_info_table: RebaseInfoTable = .{}, /// Table of binding info entries. binding_info_table: BindingInfoTable = .{}, /// Table of lazy binding info entries. @@ -133,6 +135,7 @@ error_flags: File.ErrorFlags = File.ErrorFlags{}, offset_table_count_dirty: bool = false, header_dirty: bool = false, load_commands_dirty: bool = false, +rebase_info_dirty: bool = false, binding_info_dirty: bool = false, lazy_binding_info_dirty: bool = false, export_info_dirty: bool = false, @@ -400,6 +403,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { main_cmd.entryoff = addr - text_segment.inner.vmaddr; self.load_commands_dirty = true; } + try self.writeRebaseInfoTable(); try self.writeBindingInfoTable(); try self.writeLazyBindingInfoTable(); try self.writeExportTrie(); @@ -425,6 +429,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { .Lib => return error.TODOImplementWritingLibFiles, } + { + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nindirectsyms = 0; + } + try self.writeLoadCommands(); try self.writeHeader(); @@ -439,6 +448,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { assert(!self.offset_table_count_dirty); assert(!self.header_dirty); assert(!self.load_commands_dirty); + assert(!self.rebase_info_dirty); assert(!self.binding_info_dirty); assert(!self.lazy_binding_info_dirty); assert(!self.export_info_dirty); @@ -1289,6 +1299,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { mem.writeIntLittle(u32, cccode[8..12], 0); try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); + + try self.rebase_info_table.symbols.append(self.base.allocator, .{ + .segment = 3, + .offset = 0, + }); + self.rebase_info_dirty = true; } const text_section = text_segment.sections.items[self.text_section_index.?]; @@ -1821,12 +1837,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.dyld_info_cmd_index == null) { self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); - // TODO Preallocate rebase, binding, and lazy binding info. - const export_size = 2; - const export_off = self.findFreeSpaceLinkedit(export_size, 1); - - log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + export_size }); - try self.load_commands.append(self.base.allocator, .{ .DyldInfoOnly = .{ .cmd = macho.LC_DYLD_INFO_ONLY, @@ -1839,37 +1849,67 @@ pub fn populateMissingMetadata(self: *MachO) !void { .weak_bind_size = 0, .lazy_bind_off = 0, .lazy_bind_size = 0, - .export_off = @intCast(u32, export_off), - .export_size = export_size, + .export_off = 0, + .export_size = 0, }, }); + + const dyld = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + + // Preallocate rebase, binding, lazy binding info, and export info. + const expected_size = 48; // TODO This is totally random. + const rebase_off = self.findFreeSpaceLinkedit(expected_size, 1); + log.debug("found rebase info free space 0x{x} to 0x{x}", .{ rebase_off, rebase_off + expected_size }); + dyld.rebase_off = @intCast(u32, rebase_off); + dyld.rebase_size = expected_size; + + const bind_off = self.findFreeSpaceLinkedit(expected_size, 1); + log.debug("found binding info free space 0x{x} to 0x{x}", .{ bind_off, bind_off + expected_size }); + dyld.bind_off = @intCast(u32, bind_off); + dyld.bind_size = expected_size; + + const lazy_bind_off = self.findFreeSpaceLinkedit(expected_size, 1); + log.debug("found lazy binding info free space 0x{x} to 0x{x}", .{ lazy_bind_off, lazy_bind_off + expected_size }); + dyld.lazy_bind_off = @intCast(u32, lazy_bind_off); + dyld.lazy_bind_size = expected_size; + + const export_off = self.findFreeSpaceLinkedit(expected_size, 1); + log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + expected_size }); + dyld.export_off = @intCast(u32, export_off); + dyld.export_size = expected_size; + self.header_dirty = true; self.load_commands_dirty = true; } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); - const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64); - const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64)); - - log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size }); - - try self.string_table.append(self.base.allocator, 0); // Need a null at position 0. - const strtab_size = self.string_table.items.len; - const strtab_off = self.findFreeSpaceLinkedit(strtab_size, 1); - - log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size }); - try self.load_commands.append(self.base.allocator, .{ .Symtab = .{ .cmd = macho.LC_SYMTAB, .cmdsize = @sizeOf(macho.symtab_command), - .symoff = @intCast(u32, symtab_off), - .nsyms = @intCast(u32, self.base.options.symbol_count_hint), - .stroff = @intCast(u32, strtab_off), - .strsize = @intCast(u32, strtab_size), + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 0, }, }); + + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + + const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64); + const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64)); + log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size }); + symtab.symoff = @intCast(u32, symtab_off); + symtab.nsyms = @intCast(u32, self.base.options.symbol_count_hint); + + try self.string_table.append(self.base.allocator, 0); // Need a null at position 0. + const strtab_size = self.string_table.items.len; + const strtab_off = self.findFreeSpaceLinkedit(strtab_size, 1); + log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size }); + symtab.stroff = @intCast(u32, strtab_off); + symtab.strsize = @intCast(u32, strtab_size); + self.header_dirty = true; self.load_commands_dirty = true; self.string_table_dirty = true; @@ -1877,7 +1917,11 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.dysymtab_cmd_index == null) { self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); - // TODO Preallocate space for indirect symbol table. + // Preallocate space for indirect symbol table. + const indsymtab_size = self.base.options.symbol_count_hint * @sizeOf(u64); // Each entry is just a u64. + const indsymtab_off = self.findFreeSpaceLinkedit(indsymtab_size, @sizeOf(u64)); + + log.debug("found indirect symbol table free space 0x{x} to 0x{x}", .{ indsymtab_off, indsymtab_off + indsymtab_size }); try self.load_commands.append(self.base.allocator, .{ .Dysymtab = .{ @@ -1895,8 +1939,8 @@ pub fn populateMissingMetadata(self: *MachO) !void { .nmodtab = 0, .extrefsymoff = 0, .nextrefsyms = 0, - .indirectsymoff = 0, - .nindirectsyms = 0, + .indirectsymoff = @intCast(u32, indsymtab_off), + .nindirectsyms = @intCast(u32, self.base.options.symbol_count_hint), .extreloff = 0, .nextrel = 0, .locreloff = 0, @@ -2565,6 +2609,37 @@ fn writeExportTrie(self: *MachO) !void { self.export_info_dirty = false; } +fn writeRebaseInfoTable(self: *MachO) !void { + if (!self.rebase_info_dirty) return; + + const tracy = trace(@src()); + defer tracy.end(); + + const size = try self.rebase_info_table.calcSize(); + var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); + defer self.base.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try self.rebase_info_table.write(stream.writer()); + + const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + const allocated_size = self.allocatedSizeLinkedit(dyld_info.rebase_off); + const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); + + if (needed_size > allocated_size) { + dyld_info.rebase_off = 0; + dyld_info.rebase_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1)); + } + + dyld_info.rebase_size = @intCast(u32, needed_size); + log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); + + try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off); + self.load_commands_dirty = true; + self.rebase_info_dirty = false; +} + fn writeBindingInfoTable(self: *MachO) !void { if (!self.binding_info_dirty) return; diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig index c7f8d5c6a5..d039359c97 100644 --- a/src/link/MachO/imports.zig +++ b/src/link/MachO/imports.zig @@ -6,6 +6,49 @@ const mem = std.mem; const assert = std.debug.assert; const Allocator = mem.Allocator; +pub const RebaseInfoTable = struct { + rebase_type: u8 = macho.REBASE_TYPE_POINTER, + symbols: std.ArrayListUnmanaged(Symbol) = .{}, + + pub const Symbol = struct { + segment: u8, + offset: i64, + }; + + pub fn deinit(self: *RebaseInfoTable, allocator: *Allocator) void { + self.symbols.deinit(allocator); + } + + /// Write the rebase info table to byte stream. + pub fn write(self: RebaseInfoTable, writer: anytype) !void { + try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type)); + + for (self.symbols.items) |symbol| { + try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); + try leb.writeILEB128(writer, symbol.offset); + try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); + } + + try writer.writeByte(macho.REBASE_OPCODE_DONE); + } + + /// Calculate size in bytes of this rebase info table. + pub fn calcSize(self: *RebaseInfoTable) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 1; + + for (self.symbols.items) |symbol| { + size += 1; + try leb.writeILEB128(writer, symbol.offset); + size += 1; + } + + size += 1 + stream.bytes_written; + return size; + } +}; + /// Table of binding info entries used to tell the dyld which /// symbols to bind at loading time. pub const BindingInfoTable = struct { From 6d2a6513f963780fe88235218213dee4b1a0a7dd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 6 Jan 2021 17:36:31 +0100 Subject: [PATCH 05/16] macho: write out binding info --- src/link/MachO.zig | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 23ebff1784..77cbd10dc3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1305,6 +1305,18 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .offset = 0, }); self.rebase_info_dirty = true; + + const sym = self.undef_symbols.items[fixup.symbol]; + const name_str = self.getString(sym.n_strx); + var name = try self.base.allocator.alloc(u8, name_str.len); + mem.copy(u8, name, name_str); + try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{ + .segment = 3, + .offset = 0, + .dylib_ordinal = 1, + .name = name, + }); + self.lazy_binding_info_dirty = true; } const text_section = text_segment.sections.items[self.text_section_index.?]; @@ -2078,6 +2090,17 @@ pub fn populateMissingMetadata(self: *MachO) !void { .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, .n_value = 0, }); + + self.binding_info_table.dylib_ordinal = 1; + const nn = self.getString(name); + var n = try self.base.allocator.alloc(u8, nn.len); + mem.copy(u8, n, nn); + try self.binding_info_table.symbols.append(self.base.allocator, .{ + .name = n, + .segment = 2, + .offset = 0, + }); + self.binding_info_dirty = true; } if (self.next_stub_helper_off == null) { const text = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; From bb691ea16b89c29d98cc2d36bf2af029c5dd1844 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 6 Jan 2021 17:45:26 +0100 Subject: [PATCH 06/16] macho: first working draft of externs on macOS --- src/link/MachO.zig | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 77cbd10dc3..eb747beff8 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -408,6 +408,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.writeLazyBindingInfoTable(); try self.writeExportTrie(); try self.writeAllGlobalAndUndefSymbols(); + try self.writeIndirectSymbolTable(); try self.writeStringTable(); try self.updateLinkeditSegmentSizes(); @@ -429,11 +430,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { .Lib => return error.TODOImplementWritingLibFiles, } - { - const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - dysymtab.nindirectsyms = 0; - } - try self.writeLoadCommands(); try self.writeHeader(); @@ -2534,6 +2530,32 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { self.load_commands_dirty = true; } +fn writeIndirectSymbolTable(self: *MachO) !void { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = &text_seg.sections.items[self.stubs_section_index.?]; + const dc_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &dc_seg.sections.items[self.data_got_section_index.?]; + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la = &data_seg.sections.items[self.la_symbol_ptr_section_index.?]; + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nindirectsyms = 0; + for (self.undef_symbols.items) |sym, i| { + const idx = @intCast(u32, dysymtab.iundefsym + i); + var buf: [@sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, &buf, idx); + try self.base.file.?.pwriteAll(&buf, dysymtab.indirectsymoff + i * @sizeOf(u32)); + dysymtab.nindirectsyms += 1; + if (i == self.dyld_stub_binder_index.?) { + got.reserved1 = @intCast(u32, i); + continue; + } + stubs.reserved1 = @intCast(u32, i); + try self.base.file.?.pwriteAll(&buf, dysymtab.indirectsymoff + (i + 1) * @sizeOf(u32)); + la.reserved1 = @intCast(u32, i + 1); + dysymtab.nindirectsyms += 1; + } +} + fn writeCodeSignaturePadding(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); From 2f7cd7119387b517b07e5feb02a13e69651fe9eb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 7 Jan 2021 20:49:07 +0100 Subject: [PATCH 07/16] macho: fully working PoC with main and exit --- src/link/MachO.zig | 66 ++++++++++++++++++++++++++------------ src/link/MachO/imports.zig | 7 ++-- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index eb747beff8..e83fab6294 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1263,9 +1263,10 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const stub_h = &text_segment.sections.items[self.stub_helper_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const la_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; - while (self.stub_fixups.popOrNull()) |fixup| { + for (self.stub_fixups.items) |fixup, idx| { + const i = @intCast(u32, idx); // TODO increment offset for stub writing - const stub_addr = stubs.addr; + const stub_addr = stubs.addr + i * stubs.reserved2; const text_addr = symbol.n_value + fixup.start; const displacement = @intCast(u32, stub_addr - text_addr); var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; @@ -1274,17 +1275,16 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const end = stub_h.addr + self.next_stub_helper_off.? - stub_h.offset; var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); - try self.base.file.?.pwriteAll(&buf, la_ptr.offset); + try self.base.file.?.pwriteAll(&buf, la_ptr.offset + i * @sizeOf(u64)); - const displacement2 = la_ptr.addr - stubs.addr; + const la_ptr_addr = la_ptr.addr + i * @sizeOf(u64); + const displacement2 = la_ptr_addr - stub_addr; var ccode: [2 * @sizeOf(u32)]u8 = undefined; mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{ .literal = @intCast(u19, displacement2 / 4), }).toU32()); mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32()); - try self.base.file.?.pwriteAll(&ccode, stubs.offset); - stubs.size = 2 * @sizeOf(u32); - stubs.reserved2 = 2 * @sizeOf(u32); + try self.base.file.?.pwriteAll(&ccode, stubs.offset + i * stubs.reserved2); const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4); var cccode: [3 * @sizeOf(u32)]u8 = undefined; @@ -1292,13 +1292,13 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .literal = 0x2, }).toU32()); mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); - mem.writeIntLittle(u32, cccode[8..12], 0); + mem.writeIntLittle(u32, cccode[8..12], i * 0xd); try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); try self.rebase_info_table.symbols.append(self.base.allocator, .{ .segment = 3, - .offset = 0, + .offset = i * stubs.reserved2, }); self.rebase_info_dirty = true; @@ -1308,12 +1308,13 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { mem.copy(u8, name, name_str); try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{ .segment = 3, - .offset = 0, + .offset = i * @sizeOf(u64), .dylib_ordinal = 1, .name = name, }); self.lazy_binding_info_dirty = true; } + self.stub_fixups.shrinkRetainingCapacity(0); const text_section = text_segment.sections.items[self.text_section_index.?]; const section_offset = symbol.n_value - text_section.addr; @@ -1634,7 +1635,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .nreloc = 0, .flags = flags, .reserved1 = 0, - .reserved2 = 0, + .reserved2 = 2 * @sizeOf(u32), .reserved3 = 0, }); self.header_dirty = true; @@ -2539,20 +2540,45 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const la = &data_seg.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; dysymtab.nindirectsyms = 0; + + var buf: [@sizeOf(u32)]u8 = undefined; + var off = dysymtab.indirectsymoff; + var idx: u32 = 0; + + stubs.reserved1 = 0; for (self.undef_symbols.items) |sym, i| { - const idx = @intCast(u32, dysymtab.iundefsym + i); - var buf: [@sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, &buf, idx); - try self.base.file.?.pwriteAll(&buf, dysymtab.indirectsymoff + i * @sizeOf(u32)); - dysymtab.nindirectsyms += 1; if (i == self.dyld_stub_binder_index.?) { - got.reserved1 = @intCast(u32, i); continue; } - stubs.reserved1 = @intCast(u32, i); - try self.base.file.?.pwriteAll(&buf, dysymtab.indirectsymoff + (i + 1) * @sizeOf(u32)); - la.reserved1 = @intCast(u32, i + 1); + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + mem.writeIntLittle(u32, &buf, symtab_idx); + try self.base.file.?.pwriteAll(&buf, off); + off += @sizeOf(u32); dysymtab.nindirectsyms += 1; + idx += 1; + } + + got.reserved1 = @intCast(u32, self.undef_symbols.items.len - 1); + if (self.dyld_stub_binder_index) |i| { + const symtab_idx = i + dysymtab.iundefsym; + mem.writeIntLittle(u32, &buf, symtab_idx); + try self.base.file.?.pwriteAll(&buf, off); + off += @sizeOf(u32); + dysymtab.nindirectsyms += 1; + idx += 1; + } + + la.reserved1 = got.reserved1 + 1; + for (self.undef_symbols.items) |sym, i| { + if (i == self.dyld_stub_binder_index.?) { + continue; + } + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + mem.writeIntLittle(u32, &buf, symtab_idx); + try self.base.file.?.pwriteAll(&buf, off); + off += @sizeOf(u32); + dysymtab.nindirectsyms += 1; + idx += 1; } } diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig index d039359c97..6128992af3 100644 --- a/src/link/MachO/imports.zig +++ b/src/link/MachO/imports.zig @@ -21,9 +21,8 @@ pub const RebaseInfoTable = struct { /// Write the rebase info table to byte stream. pub fn write(self: RebaseInfoTable, writer: anytype) !void { - try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type)); - for (self.symbols.items) |symbol| { + try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type)); try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); try leb.writeILEB128(writer, symbol.offset); try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); @@ -36,10 +35,10 @@ pub const RebaseInfoTable = struct { pub fn calcSize(self: *RebaseInfoTable) !u64 { var stream = std.io.countingWriter(std.io.null_writer); var writer = stream.writer(); - var size: u64 = 1; + var size: u64 = 0; for (self.symbols.items) |symbol| { - size += 1; + size += 2; try leb.writeILEB128(writer, symbol.offset); size += 1; } From 44a052a65f5c59b02383178f36fcc231df28b49f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 8 Jan 2021 22:26:18 +0100 Subject: [PATCH 08/16] macho: write out stubs for new externs only --- src/codegen.zig | 30 ++++++++++------ src/link/MachO.zig | 85 ++++++++++++++++++++++++---------------------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 7100227db0..84148fcdd4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1862,18 +1862,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } else if (func_value.castTag(.extern_fn)) |func_payload| { const decl = func_payload.data; const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); - defer self.bin_file.allocator.free(decl_name); - const name = try macho_file.makeString(decl_name); - const symbol = macho_file.undef_symbols.items.len; - try macho_file.undef_symbols.append(self.bin_file.allocator, .{ - .n_strx = name, - .n_type = std.macho.N_UNDF | std.macho.N_EXT, - .n_sect = 0, - .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, - .n_value = 0, - }); + const exists: bool = macho_file.externs.contains(decl_name); + const symbol: u32 = blk: { + if (macho_file.externs.get(decl_name)) |index| { + self.bin_file.allocator.free(decl_name); + break :blk index; + } else { + const extern_index = @intCast(u32, macho_file.undef_symbols.items.len - 1); // TODO + try macho_file.externs.putNoClobber(self.bin_file.allocator, decl_name, extern_index); + const name = try macho_file.makeString(decl_name); + try macho_file.undef_symbols.append(self.bin_file.allocator, .{ + .n_strx = name, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }); + break :blk extern_index; + } + }; try macho_file.stub_fixups.append(self.bin_file.allocator, .{ .symbol = symbol, + .exists = exists, .start = self.code.items.len, .len = 4, }); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e83fab6294..3c876bf59d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -167,9 +167,11 @@ last_text_block: ?*TextBlock = null, pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, +externs: std.StringHashMapUnmanaged(u32) = .{}, pub const StubFixup = struct { - symbol: usize, + symbol: u32, + exists: bool, start: usize, len: usize, }; @@ -1263,56 +1265,57 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const stub_h = &text_segment.sections.items[self.stub_helper_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const la_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; - for (self.stub_fixups.items) |fixup, idx| { - const i = @intCast(u32, idx); + for (self.stub_fixups.items) |fixup| { // TODO increment offset for stub writing - const stub_addr = stubs.addr + i * stubs.reserved2; + const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; const text_addr = symbol.n_value + fixup.start; const displacement = @intCast(u32, stub_addr - text_addr); var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); - const end = stub_h.addr + self.next_stub_helper_off.? - stub_h.offset; - var buf: [@sizeOf(u64)]u8 = undefined; - mem.writeIntLittle(u64, &buf, end); - try self.base.file.?.pwriteAll(&buf, la_ptr.offset + i * @sizeOf(u64)); + if (!fixup.exists) { + const end = stub_h.addr + self.next_stub_helper_off.? - stub_h.offset; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, end); + try self.base.file.?.pwriteAll(&buf, la_ptr.offset + fixup.symbol * @sizeOf(u64)); - const la_ptr_addr = la_ptr.addr + i * @sizeOf(u64); - const displacement2 = la_ptr_addr - stub_addr; - var ccode: [2 * @sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{ - .literal = @intCast(u19, displacement2 / 4), - }).toU32()); - mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32()); - try self.base.file.?.pwriteAll(&ccode, stubs.offset + i * stubs.reserved2); + const la_ptr_addr = la_ptr.addr + fixup.symbol * @sizeOf(u64); + const displacement2 = la_ptr_addr - stub_addr; + var ccode: [2 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = @intCast(u19, displacement2 / 4), + }).toU32()); + mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32()); + try self.base.file.?.pwriteAll(&ccode, stubs.offset + fixup.symbol * stubs.reserved2); - const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4); - var cccode: [3 * @sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, cccode[0..4], aarch64.Instruction.ldr(.w16, .{ - .literal = 0x2, - }).toU32()); - mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); - mem.writeIntLittle(u32, cccode[8..12], i * 0xd); - try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); - self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); + const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4); + var cccode: [3 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, cccode[0..4], aarch64.Instruction.ldr(.w16, .{ + .literal = 0x2, + }).toU32()); + mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); + mem.writeIntLittle(u32, cccode[8..12], fixup.symbol * 0xd); + try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); + self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); - try self.rebase_info_table.symbols.append(self.base.allocator, .{ - .segment = 3, - .offset = i * stubs.reserved2, - }); - self.rebase_info_dirty = true; + try self.rebase_info_table.symbols.append(self.base.allocator, .{ + .segment = 3, + .offset = fixup.symbol * stubs.reserved2, + }); + self.rebase_info_dirty = true; - const sym = self.undef_symbols.items[fixup.symbol]; - const name_str = self.getString(sym.n_strx); - var name = try self.base.allocator.alloc(u8, name_str.len); - mem.copy(u8, name, name_str); - try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{ - .segment = 3, - .offset = i * @sizeOf(u64), - .dylib_ordinal = 1, - .name = name, - }); - self.lazy_binding_info_dirty = true; + const sym = self.undef_symbols.items[fixup.symbol + 1]; + const name_str = self.getString(sym.n_strx); + var name = try self.base.allocator.alloc(u8, name_str.len); + mem.copy(u8, name, name_str); + try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{ + .segment = 3, + .offset = fixup.symbol * @sizeOf(u64), + .dylib_ordinal = 1, + .name = name, + }); + self.lazy_binding_info_dirty = true; + } } self.stub_fixups.shrinkRetainingCapacity(0); From 21c7217e09b48bf3bf9656fb1e8e69b85422b02e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 8 Jan 2021 22:40:09 +0100 Subject: [PATCH 09/16] macho: memorize start of stubs in helper --- src/link/MachO.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3c876bf59d..04941c4b68 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -115,7 +115,7 @@ global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, dyld_stub_binder_index: ?u16 = null, -next_stub_helper_off: ?u64 = null, +stub_helper_stubs_start_off: ?u64 = null, /// Table of symbol names aka the string table. string_table: std.ArrayListUnmanaged(u8) = .{}, @@ -1274,7 +1274,8 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); if (!fixup.exists) { - const end = stub_h.addr + self.next_stub_helper_off.? - stub_h.offset; + const stub_off = self.stub_helper_stubs_start_off.? + fixup.symbol * 3 * @sizeOf(u32); + const end = stub_h.addr + stub_off - stub_h.offset; var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); try self.base.file.?.pwriteAll(&buf, la_ptr.offset + fixup.symbol * @sizeOf(u64)); @@ -1295,8 +1296,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }).toU32()); mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); mem.writeIntLittle(u32, cccode[8..12], fixup.symbol * 0xd); - try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?); - self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32); + try self.base.file.?.pwriteAll(&cccode, stub_off); try self.rebase_info_table.symbols.append(self.base.allocator, .{ .segment = 3, @@ -2102,7 +2102,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { }); self.binding_info_dirty = true; } - if (self.next_stub_helper_off == null) { + if (self.stub_helper_stubs_start_off == null) { const text = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const sh = &text.sections.items[self.stub_helper_section_index.?]; const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; @@ -2123,7 +2123,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .literal = @intCast(u19, displacement2 / 4), }).toU32()); mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); - self.next_stub_helper_off = sh.offset + 4 * @sizeOf(u32); + self.stub_helper_stubs_start_off = sh.offset + 4 * @sizeOf(u32); try self.base.file.?.pwriteAll(&code, sh.offset); } } From b86d0e488b9dfc7d943da86a34998360e24da225 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 9 Jan 2021 19:29:33 +0100 Subject: [PATCH 10/16] macho: refactor writing and managing externs --- src/codegen.zig | 26 +- src/link/MachO.zig | 381 ++++++++++++++++------------- src/link/MachO/imports.zig | 474 +++++++++++-------------------------- 3 files changed, 375 insertions(+), 506 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 84148fcdd4..ad4215191f 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1861,29 +1861,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } else if (func_value.castTag(.extern_fn)) |func_payload| { const decl = func_payload.data; + // We don't free the decl_name immediately unless it already exists. + // If it doesn't, it will get autofreed when we clean up the extern symbol table. const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); - const exists: bool = macho_file.externs.contains(decl_name); + const already_defined = macho_file.extern_lazy_symbols.contains(decl_name); const symbol: u32 = blk: { - if (macho_file.externs.get(decl_name)) |index| { + if (macho_file.extern_lazy_symbols.get(decl_name)) |sym| { self.bin_file.allocator.free(decl_name); - break :blk index; + break :blk sym.index; } else { - const extern_index = @intCast(u32, macho_file.undef_symbols.items.len - 1); // TODO - try macho_file.externs.putNoClobber(self.bin_file.allocator, decl_name, extern_index); - const name = try macho_file.makeString(decl_name); - try macho_file.undef_symbols.append(self.bin_file.allocator, .{ - .n_strx = name, - .n_type = std.macho.N_UNDF | std.macho.N_EXT, - .n_sect = 0, - .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, - .n_value = 0, + const index = @intCast(u32, macho_file.extern_lazy_symbols.items().len); + try macho_file.extern_lazy_symbols.putNoClobber(self.bin_file.allocator, decl_name, .{ + .name = decl_name, + .dylib_ordinal = 1, // TODO this is now hardcoded, since we only support libSystem. + .index = index, }); - break :blk extern_index; + break :blk index; } }; try macho_file.stub_fixups.append(self.bin_file.allocator, .{ .symbol = symbol, - .exists = exists, + .already_defined = already_defined, .start = self.code.items.len, .len = 4, }); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 04941c4b68..7d86c27aa9 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -105,16 +105,17 @@ entry_addr: ?u64 = null, /// Table of all local symbols /// Internally references string table for names (which are optional). local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, -/// Table of all defined global symbols +/// Table of all global symbols global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, -/// Table of all undefined symbols -undef_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +/// Table of all extern nonlazy symbols, indexed by name. +extern_nonlazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +/// Table of all extern lazy symbols, indexed by name. +extern_lazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, -dyld_stub_binder_index: ?u16 = null, stub_helper_stubs_start_off: ?u64 = null, /// Table of symbol names aka the string table. @@ -123,13 +124,6 @@ string_table: std.ArrayListUnmanaged(u8) = .{}, /// Table of trampolines to the actual symbols in __text section. offset_table: std.ArrayListUnmanaged(u64) = .{}, -/// Table of rebase info entries. -rebase_info_table: RebaseInfoTable = .{}, -/// Table of binding info entries. -binding_info_table: BindingInfoTable = .{}, -/// Table of lazy binding info entries. -lazy_binding_info_table: LazyBindingInfoTable = .{}, - error_flags: File.ErrorFlags = File.ErrorFlags{}, offset_table_count_dirty: bool = false, @@ -167,11 +161,10 @@ last_text_block: ?*TextBlock = null, pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, -externs: std.StringHashMapUnmanaged(u32) = .{}, pub const StubFixup = struct { symbol: u32, - exists: bool, + already_defined: bool, start: usize, len: usize, }; @@ -920,42 +913,42 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { return error.NoSymbolTableFound; } - // Parse dyld info - try self.parseBindingInfoTable(); - try self.parseLazyBindingInfoTable(); + // // Parse dyld info + // try self.parseBindingInfoTable(); + // try self.parseLazyBindingInfoTable(); - // Update the dylib ordinals. - self.binding_info_table.dylib_ordinal = next_ordinal; - for (self.lazy_binding_info_table.symbols.items) |*symbol| { - symbol.dylib_ordinal = next_ordinal; - } + // // Update the dylib ordinals. + // self.binding_info_table.dylib_ordinal = next_ordinal; + // for (self.lazy_binding_info_table.symbols.items) |*symbol| { + // symbol.dylib_ordinal = next_ordinal; + // } - // Write updated dyld info. - const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - { - const size = try self.binding_info_table.calcSize(); - assert(dyld_info.bind_size >= size); + // // Write updated dyld info. + // const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + // { + // const size = try self.binding_info_table.calcSize(); + // assert(dyld_info.bind_size >= size); - var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); - defer self.base.allocator.free(buffer); + // var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); + // defer self.base.allocator.free(buffer); - var stream = std.io.fixedBufferStream(buffer); - try self.binding_info_table.write(stream.writer()); + // var stream = std.io.fixedBufferStream(buffer); + // try self.binding_info_table.write(stream.writer()); - try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); - } - { - const size = try self.lazy_binding_info_table.calcSize(); - assert(dyld_info.lazy_bind_size >= size); + // try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); + // } + // { + // const size = try self.lazy_binding_info_table.calcSize(); + // assert(dyld_info.lazy_bind_size >= size); - var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); - defer self.base.allocator.free(buffer); + // var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); + // defer self.base.allocator.free(buffer); - var stream = std.io.fixedBufferStream(buffer); - try self.lazy_binding_info_table.write(stream.writer()); + // var stream = std.io.fixedBufferStream(buffer); + // try self.lazy_binding_info_table.write(stream.writer()); - try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); - } + // try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); + // } // Write updated load commands and the header try self.writeLoadCommands(); @@ -1037,14 +1030,13 @@ pub fn deinit(self: *MachO) void { if (self.d_sym) |*ds| { ds.deinit(self.base.allocator); } - self.binding_info_table.deinit(self.base.allocator); - self.lazy_binding_info_table.deinit(self.base.allocator); self.pie_fixups.deinit(self.base.allocator); 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); self.string_table.deinit(self.base.allocator); - self.undef_symbols.deinit(self.base.allocator); + self.extern_lazy_symbols.deinit(self.base.allocator); + self.extern_nonlazy_symbols.deinit(self.base.allocator); self.global_symbols.deinit(self.base.allocator); self.global_symbol_free_list.deinit(self.base.allocator); self.local_symbols.deinit(self.base.allocator); @@ -1261,59 +1253,28 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } // Resolve stubs (if any) - const stubs = &text_segment.sections.items[self.stubs_section_index.?]; - const stub_h = &text_segment.sections.items[self.stub_helper_section_index.?]; - const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const la_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + const stubs = text_segment.sections.items[self.stubs_section_index.?]; for (self.stub_fixups.items) |fixup| { - // TODO increment offset for stub writing const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; const text_addr = symbol.n_value + fixup.start; const displacement = @intCast(u32, stub_addr - text_addr); var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + switch (self.base.options.target.cpu.arch) { + .x86_64 => return error.TODOImplementStubFixupsForx86_64, + .aarch64 => { + mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); + }, + else => unreachable, + } + if (!fixup.already_defined) { + try self.writeStub(fixup.symbol); + try self.writeStubInStubHelper(fixup.symbol); + try self.writeLazySymbolPointer(fixup.symbol); - if (!fixup.exists) { - const stub_off = self.stub_helper_stubs_start_off.? + fixup.symbol * 3 * @sizeOf(u32); - const end = stub_h.addr + stub_off - stub_h.offset; - var buf: [@sizeOf(u64)]u8 = undefined; - mem.writeIntLittle(u64, &buf, end); - try self.base.file.?.pwriteAll(&buf, la_ptr.offset + fixup.symbol * @sizeOf(u64)); - - const la_ptr_addr = la_ptr.addr + fixup.symbol * @sizeOf(u64); - const displacement2 = la_ptr_addr - stub_addr; - var ccode: [2 * @sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, ccode[0..4], aarch64.Instruction.ldr(.x16, .{ - .literal = @intCast(u19, displacement2 / 4), - }).toU32()); - mem.writeIntLittle(u32, ccode[4..8], aarch64.Instruction.br(.x16).toU32()); - try self.base.file.?.pwriteAll(&ccode, stubs.offset + fixup.symbol * stubs.reserved2); - - const displacement3 = @intCast(i64, stub_h.addr) - @intCast(i64, end + 4); - var cccode: [3 * @sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, cccode[0..4], aarch64.Instruction.ldr(.w16, .{ - .literal = 0x2, - }).toU32()); - mem.writeIntLittle(u32, cccode[4..8], aarch64.Instruction.b(@intCast(i28, displacement3)).toU32()); - mem.writeIntLittle(u32, cccode[8..12], fixup.symbol * 0xd); - try self.base.file.?.pwriteAll(&cccode, stub_off); - - try self.rebase_info_table.symbols.append(self.base.allocator, .{ - .segment = 3, - .offset = fixup.symbol * stubs.reserved2, - }); + const extern_sym = &self.extern_lazy_symbols.items()[fixup.symbol].value; + extern_sym.segment = self.data_segment_cmd_index.?; + extern_sym.offset = fixup.symbol * @sizeOf(u64); self.rebase_info_dirty = true; - - const sym = self.undef_symbols.items[fixup.symbol + 1]; - const name_str = self.getString(sym.n_strx); - var name = try self.base.allocator.alloc(u8, name_str.len); - mem.copy(u8, name, name_str); - try self.lazy_binding_info_table.symbols.append(self.base.allocator, .{ - .segment = 3, - .offset = fixup.symbol * @sizeOf(u64), - .dylib_ordinal = 1, - .name = name, - }); self.lazy_binding_info_dirty = true; } } @@ -2080,51 +2041,51 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (self.dyld_stub_binder_index == null) { - self.dyld_stub_binder_index = @intCast(u16, self.undef_symbols.items.len); - const name = try self.makeString("dyld_stub_binder"); - try self.undef_symbols.append(self.base.allocator, .{ - .n_strx = name, - .n_type = macho.N_UNDF | macho.N_EXT, - .n_sect = 0, - .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, - .n_value = 0, - }); - - self.binding_info_table.dylib_ordinal = 1; - const nn = self.getString(name); - var n = try self.base.allocator.alloc(u8, nn.len); - mem.copy(u8, n, nn); - try self.binding_info_table.symbols.append(self.base.allocator, .{ - .name = n, - .segment = 2, - .offset = 0, + if (!self.extern_nonlazy_symbols.contains("dyld_stub_binder")) { + const index = @intCast(u32, self.extern_nonlazy_symbols.items().len); + const name = try std.fmt.allocPrint(self.base.allocator, "dyld_stub_binder", .{}); + try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{ + .name = name, + .dylib_ordinal = 1, // TODO this is currently hardcoded. + .index = index, + .segment = self.data_const_segment_cmd_index.?, + .offset = index * @sizeOf(u64), }); self.binding_info_dirty = true; } if (self.stub_helper_stubs_start_off == null) { - const text = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const sh = &text.sections.items[self.stub_helper_section_index.?]; - const data = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const data_data = &data.sections.items[self.data_section_index.?]; - const displacement = data_data.addr - sh.addr; - var code: [4 * @sizeOf(u32)]u8 = undefined; - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, @intCast(i21, displacement)).toU32()); - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( - .x16, - .x17, - aarch64.Register.sp, - aarch64.Instruction.LoadStorePairOffset.pre_index(-16), - ).toU32()); - const dc = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &dc.sections.items[self.data_got_section_index.?]; - const displacement2 = got.addr - sh.addr - 2 * @sizeOf(u32); - mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ - .literal = @intCast(u19, displacement2 / 4), - }).toU32()); - mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); - self.stub_helper_stubs_start_off = sh.offset + 4 * @sizeOf(u32); - try self.base.file.?.pwriteAll(&code, sh.offset); + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data = &data_segment.sections.items[self.data_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.data_got_section_index.?]; + switch (self.base.options.target.cpu.arch) { + .x86_64 => return error.TODOImplementStubHelperForX86_64, + .aarch64 => { + var code: [4 * @sizeOf(u32)]u8 = undefined; + { + const displacement = data.addr - stub_helper.addr; + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, @intCast(i21, displacement)).toU32()); + } + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( + .x16, + .x17, + aarch64.Register.sp, + aarch64.Instruction.LoadStorePairOffset.pre_index(-16), + ).toU32()); + { + const displacement = got.addr - stub_helper.addr - 2 * @sizeOf(u32); + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ + .literal = @intCast(u19, displacement / 4), + }).toU32()); + } + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); + self.stub_helper_stubs_start_off = stub_helper.offset + 4 * @sizeOf(u32); + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + }, + else => unreachable, + } } } @@ -2460,11 +2421,73 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { try self.base.file.?.pwriteAll(&code, off); } +fn writeLazySymbolPointer(self: *MachO, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32); + const end = stub_helper.addr + stub_off - stub_helper.offset; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, end); + const off = la_symbol_ptr.offset + index * @sizeOf(u64); + log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + try self.base.file.?.pwriteAll(&buf, off); +} + +fn writeStub(self: *MachO, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = text_segment.sections.items[self.stubs_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_off = stubs.offset + index * stubs.reserved2; + const stub_addr = stubs.addr + index * stubs.reserved2; + const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); + const displacement = la_ptr_addr - stub_addr; + log.debug("writing stub at 0x{x}", .{stub_off}); + switch (self.base.options.target.cpu.arch) { + .x86_64 => return error.TODOImplementWritingStubsForx86_64, + .aarch64 => { + var code: [2 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = @intCast(u19, displacement / 4), + }).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32()); + try self.base.file.?.pwriteAll(&code, stub_off); + }, + else => unreachable, + } +} + +fn writeStubInStubHelper(self: *MachO, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + + const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32); + const end = stub_helper.addr + stub_off - stub_helper.offset; + const displacement = @intCast(i64, stub_helper.addr) - @intCast(i64, end + 4); + switch (self.base.options.target.cpu.arch) { + .x86_64 => return error.TODOImplementWritingStubsInStubHelperForx86_64, + .aarch64 => { + var code: [3 * @sizeOf(u32)]u8 = undefined; + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ + .literal = 0x2, + }).toU32()); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); + mem.writeIntLittle(u32, code[8..12], index * 0xd); // TODO This is the size of lazy binding opcode block. + try self.base.file.?.pwriteAll(&code, stub_off); + }, + else => unreachable, + } +} + fn relocateSymbolTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const nlocals = self.local_symbols.items.len; const nglobals = self.global_symbols.items.len; - const nundefs = self.undef_symbols.items.len; + const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; const nsyms = nlocals + nglobals + nundefs; if (symtab.nsyms < nsyms) { @@ -2509,7 +2532,31 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const nlocals = self.local_symbols.items.len; const nglobals = self.global_symbols.items.len; - const nundefs = self.undef_symbols.items.len; + + const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; + var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); + defer undefs.deinit(); + try undefs.ensureCapacity(nundefs); + for (self.extern_lazy_symbols.items()) |entry| { + const name = try self.makeString(entry.key); + undefs.appendAssumeCapacity(.{ + .n_strx = name, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }); + } + for (self.extern_nonlazy_symbols.items()) |entry| { + const name = try self.makeString(entry.key); + undefs.appendAssumeCapacity(.{ + .n_strx = name, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }); + } const locals_off = symtab.symoff; const locals_size = nlocals * @sizeOf(macho.nlist_64); @@ -2521,8 +2568,8 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const undefs_off = globals_off + globals_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); - log.debug("writing undef symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), undefs_off); + log.debug("writing extern symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); // Update dynamic symbol table. const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; @@ -2546,42 +2593,33 @@ fn writeIndirectSymbolTable(self: *MachO) !void { var buf: [@sizeOf(u32)]u8 = undefined; var off = dysymtab.indirectsymoff; - var idx: u32 = 0; stubs.reserved1 = 0; - for (self.undef_symbols.items) |sym, i| { - if (i == self.dyld_stub_binder_index.?) { - continue; - } - const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + for (self.extern_lazy_symbols.items()) |entry| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); dysymtab.nindirectsyms += 1; - idx += 1; } - got.reserved1 = @intCast(u32, self.undef_symbols.items.len - 1); - if (self.dyld_stub_binder_index) |i| { - const symtab_idx = i + dysymtab.iundefsym; + const base_id = @intCast(u32, self.extern_lazy_symbols.items().len); + got.reserved1 = base_id; + for (self.extern_nonlazy_symbols.items()) |entry| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index + base_id); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); dysymtab.nindirectsyms += 1; - idx += 1; } - la.reserved1 = got.reserved1 + 1; - for (self.undef_symbols.items) |sym, i| { - if (i == self.dyld_stub_binder_index.?) { - continue; - } - const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + la.reserved1 = got.reserved1 + @intCast(u32, self.extern_nonlazy_symbols.items().len); + for (self.extern_lazy_symbols.items()) |entry| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); dysymtab.nindirectsyms += 1; - idx += 1; } } @@ -2689,12 +2727,19 @@ fn writeRebaseInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try self.rebase_info_table.calcSize(); + var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_lazy_symbols.items().len); + defer self.base.allocator.free(symbols); + + for (self.extern_lazy_symbols.items()) |*entry, i| { + symbols[i] = &entry.value; + } + + const size = try rebaseInfoSize(symbols); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try self.rebase_info_table.write(stream.writer()); + try writeRebaseInfo(symbols, stream.writer()); const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -2720,12 +2765,19 @@ fn writeBindingInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try self.binding_info_table.calcSize(); + var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_nonlazy_symbols.items().len); + defer self.base.allocator.free(symbols); + + for (self.extern_nonlazy_symbols.items()) |*entry, i| { + symbols[i] = &entry.value; + } + + const size = try bindInfoSize(symbols); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try self.binding_info_table.write(stream.writer()); + try writeBindInfo(symbols, stream.writer()); const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -2748,12 +2800,19 @@ fn writeBindingInfoTable(self: *MachO) !void { fn writeLazyBindingInfoTable(self: *MachO) !void { if (!self.lazy_binding_info_dirty) return; - const size = try self.lazy_binding_info_table.calcSize(); + var symbols = try self.base.allocator.alloc(*const ExternSymbol, self.extern_lazy_symbols.items().len); + defer self.base.allocator.free(symbols); + + for (self.extern_lazy_symbols.items()) |*entry, i| { + symbols[i] = &entry.value; + } + + const size = try lazyBindInfoSize(symbols); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try self.lazy_binding_info_table.write(stream.writer()); + try writeLazyBindInfo(symbols, stream.writer()); const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; @@ -3001,7 +3060,7 @@ fn parseBindingInfoTable(self: *MachO) !void { assert(nread == buffer.len); var stream = std.io.fixedBufferStream(buffer); - try self.binding_info_table.read(stream.reader(), self.base.allocator); + // try self.binding_info_table.read(stream.reader(), self.base.allocator); } fn parseLazyBindingInfoTable(self: *MachO) !void { @@ -3012,5 +3071,5 @@ fn parseLazyBindingInfoTable(self: *MachO) !void { assert(nread == buffer.len); var stream = std.io.fixedBufferStream(buffer); - try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator); + // try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator); } diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig index 6128992af3..8e0f72e1de 100644 --- a/src/link/MachO/imports.zig +++ b/src/link/MachO/imports.zig @@ -6,365 +6,177 @@ const mem = std.mem; const assert = std.debug.assert; const Allocator = mem.Allocator; -pub const RebaseInfoTable = struct { - rebase_type: u8 = macho.REBASE_TYPE_POINTER, - symbols: std.ArrayListUnmanaged(Symbol) = .{}, +pub const ExternSymbol = struct { + /// Symbol name. + /// We own the memory, therefore we'll need to free it by calling `deinit`. + /// In self-hosted, we don't expect it to be null ever. + /// However, this is for backwards compatibility with LLD when + /// we'll be patching things up post mortem. + name: ?[]u8 = null, - pub const Symbol = struct { - segment: u8, - offset: i64, - }; + /// Id of the dynamic library where the specified entries can be found. + /// Id of 0 means self. + /// TODO this should really be an id into the table of all defined + /// dylibs. + dylib_ordinal: i64 = 0, - pub fn deinit(self: *RebaseInfoTable, allocator: *Allocator) void { - self.symbols.deinit(allocator); - } + segment: u16 = 0, + offset: u32 = 0, + addend: ?i32 = null, + index: u32, - /// Write the rebase info table to byte stream. - pub fn write(self: RebaseInfoTable, writer: anytype) !void { - for (self.symbols.items) |symbol| { - try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type)); - try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); + pub fn deinit(self: *ExternSymbol, allocator: *Allocator) void { + if (self.name) |*name| { + allocator.free(name); } - - try writer.writeByte(macho.REBASE_OPCODE_DONE); - } - - /// Calculate size in bytes of this rebase info table. - pub fn calcSize(self: *RebaseInfoTable) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (self.symbols.items) |symbol| { - size += 2; - try leb.writeILEB128(writer, symbol.offset); - size += 1; - } - - size += 1 + stream.bytes_written; - return size; } }; -/// Table of binding info entries used to tell the dyld which -/// symbols to bind at loading time. -pub const BindingInfoTable = struct { - /// Id of the dynamic library where the specified entries can be found. - dylib_ordinal: i64 = 0, +pub fn rebaseInfoSize(symbols: []*const ExternSymbol) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; - /// Binding type; defaults to pointer type. - binding_type: u8 = macho.BIND_TYPE_POINTER, - - symbols: std.ArrayListUnmanaged(Symbol) = .{}, - - pub const Symbol = struct { - /// Symbol name. - name: ?[]u8 = null, - - /// Id of the segment where to bind this symbol to. - segment: u8, - - /// Offset of this symbol wrt to the segment id encoded in `segment`. - offset: i64, - - /// Addend value (if any). - addend: ?i64 = null, - }; - - pub fn deinit(self: *BindingInfoTable, allocator: *Allocator) void { - for (self.symbols.items) |*symbol| { - if (symbol.name) |name| { - allocator.free(name); - } - } - self.symbols.deinit(allocator); + for (symbols) |symbol| { + size += 2; + try leb.writeILEB128(writer, symbol.offset); + size += 1; } - /// Parse the binding info table from byte stream. - pub fn read(self: *BindingInfoTable, reader: anytype, allocator: *Allocator) !void { - var symbol: Symbol = .{ - .segment = 0, - .offset = 0, - }; + size += 1 + stream.bytes_written; + return size; +} - var dylib_ordinal_set = false; - var done = false; - while (true) { - const inst = reader.readByte() catch |err| switch (err) { - error.EndOfStream => break, - else => return err, - }; - const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; - const opcode: u8 = inst & macho.BIND_OPCODE_MASK; - - switch (opcode) { - macho.BIND_OPCODE_DO_BIND => { - try self.symbols.append(allocator, symbol); - symbol = .{ - .segment = 0, - .offset = 0, - }; - }, - macho.BIND_OPCODE_DONE => { - done = true; - break; - }, - macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { - var name = std.ArrayList(u8).init(allocator); - var next = try reader.readByte(); - while (next != @as(u8, 0)) { - try name.append(next); - next = try reader.readByte(); - } - symbol.name = name.toOwnedSlice(); - }, - macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { - symbol.segment = imm; - symbol.offset = try leb.readILEB128(i64, reader); - }, - macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { - assert(!dylib_ordinal_set); - self.dylib_ordinal = imm; - }, - macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { - assert(!dylib_ordinal_set); - self.dylib_ordinal = try leb.readILEB128(i64, reader); - }, - macho.BIND_OPCODE_SET_TYPE_IMM => { - self.binding_type = imm; - }, - macho.BIND_OPCODE_SET_ADDEND_SLEB => { - symbol.addend = try leb.readILEB128(i64, reader); - }, - else => { - std.log.warn("unhandled BIND_OPCODE_: 0x{x}", .{opcode}); - }, - } - } - assert(done); +pub fn writeRebaseInfo(symbols: []*const ExternSymbol, writer: anytype) !void { + for (symbols) |symbol| { + try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); + try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); + try leb.writeILEB128(writer, symbol.offset); + try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); } + try writer.writeByte(macho.REBASE_OPCODE_DONE); +} - /// Write the binding info table to byte stream. - pub fn write(self: BindingInfoTable, writer: anytype) !void { - if (self.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, self.dylib_ordinal)); - } else if (self.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, self.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, self.dylib_ordinal))); +pub fn bindInfoSize(symbols: []*const ExternSymbol) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (symbols) |symbol| { + size += 1; + if (symbol.dylib_ordinal > 15) { + try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); } - try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, self.binding_type)); + size += 1; - for (self.symbols.items) |symbol| { - if (symbol.name) |name| { - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(name); - try writer.writeByte(0); - } - - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); - try leb.writeILEB128(writer, addend); - } - - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - } - - try writer.writeByte(macho.BIND_OPCODE_DONE); - } - - /// Calculate size in bytes of this binding info table. - pub fn calcSize(self: *BindingInfoTable) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 1; - - if (self.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, self.dylib_ordinal)); + if (symbol.name) |name| { + size += 1; + size += name.len; + size += 1; } size += 1; + try leb.writeILEB128(writer, symbol.offset); - for (self.symbols.items) |symbol| { - if (symbol.name) |name| { - size += 1; - size += name.len; - size += 1; - } - + if (symbol.addend) |addend| { size += 1; - try leb.writeILEB128(writer, symbol.offset); + try leb.writeILEB128(writer, addend); + } - if (symbol.addend) |addend| { - size += 1; - try leb.writeILEB128(writer, addend); - } + size += 2; + } + size += stream.bytes_written; + return size; +} + +pub fn writeBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void { + for (symbols) |symbol| { + if (symbol.dylib_ordinal > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); + } else if (symbol.dylib_ordinal > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); + } + try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); + + if (symbol.name) |name| { + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(name); + try writer.writeByte(0); + } + + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); + try leb.writeILEB128(writer, symbol.offset); + + if (symbol.addend) |addend| { + try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); + try leb.writeILEB128(writer, addend); + } + + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + try writer.writeByte(macho.BIND_OPCODE_DONE); + } +} + +pub fn lazyBindInfoSize(symbols: []*const ExternSymbol) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (symbols) |symbol| { + size += 1; + try leb.writeILEB128(writer, symbol.offset); + + if (symbol.addend) |addend| { + size += 1; + try leb.writeILEB128(writer, addend); + } + + size += 1; + if (symbol.dylib_ordinal > 15) { + try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); + } + if (symbol.name) |name| { + size += 1; + size += name.len; size += 1; } - - size += 1 + stream.bytes_written; - return size; - } -}; - -/// Table of lazy binding info entries used to tell the dyld which -/// symbols to lazily bind at first load of a dylib. -pub const LazyBindingInfoTable = struct { - symbols: std.ArrayListUnmanaged(Symbol) = .{}, - - pub const Symbol = struct { - /// Symbol name. - name: ?[]u8 = null, - - /// Offset of this symbol wrt to the segment id encoded in `segment`. - offset: i64, - - /// Id of the dylib where this symbol is expected to reside. - /// Positive ordinals point at dylibs imported with LC_LOAD_DYLIB, - /// 0 means this binary, -1 the main executable, and -2 flat lookup. - dylib_ordinal: i64, - - /// Id of the segment where to bind this symbol to. - segment: u8, - - /// Addend value (if any). - addend: ?i64 = null, - }; - - pub fn deinit(self: *LazyBindingInfoTable, allocator: *Allocator) void { - for (self.symbols.items) |*symbol| { - if (symbol.name) |name| { - allocator.free(name); - } - } - self.symbols.deinit(allocator); + size += 2; } - /// Parse the binding info table from byte stream. - pub fn read(self: *LazyBindingInfoTable, reader: anytype, allocator: *Allocator) !void { - var symbol: Symbol = .{ - .offset = 0, - .segment = 0, - .dylib_ordinal = 0, - }; + size += stream.bytes_written; + return size; +} - var done = false; - while (true) { - const inst = reader.readByte() catch |err| switch (err) { - error.EndOfStream => break, - else => return err, - }; - const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; - const opcode: u8 = inst & macho.BIND_OPCODE_MASK; +pub fn writeLazyBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void { + for (symbols) |symbol| { + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); + try leb.writeILEB128(writer, symbol.offset); - switch (opcode) { - macho.BIND_OPCODE_DO_BIND => { - try self.symbols.append(allocator, symbol); - }, - macho.BIND_OPCODE_DONE => { - done = true; - symbol = .{ - .offset = 0, - .segment = 0, - .dylib_ordinal = 0, - }; - }, - macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { - var name = std.ArrayList(u8).init(allocator); - var next = try reader.readByte(); - while (next != @as(u8, 0)) { - try name.append(next); - next = try reader.readByte(); - } - symbol.name = name.toOwnedSlice(); - }, - macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { - symbol.segment = imm; - symbol.offset = try leb.readILEB128(i64, reader); - }, - macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { - symbol.dylib_ordinal = imm; - }, - macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { - symbol.dylib_ordinal = try leb.readILEB128(i64, reader); - }, - macho.BIND_OPCODE_SET_ADDEND_SLEB => { - symbol.addend = try leb.readILEB128(i64, reader); - }, - else => { - std.log.warn("unhandled BIND_OPCODE_: 0x{x}", .{opcode}); - }, - } - } - assert(done); - } - - /// Write the binding info table to byte stream. - pub fn write(self: LazyBindingInfoTable, writer: anytype) !void { - for (self.symbols.items) |symbol| { - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); - try leb.writeILEB128(writer, addend); - } - - if (symbol.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } else if (symbol.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } - - if (symbol.name) |name| { - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(name); - try writer.writeByte(0); - } - - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - try writer.writeByte(macho.BIND_OPCODE_DONE); - } - } - - /// Calculate size in bytes of this binding info table. - pub fn calcSize(self: *LazyBindingInfoTable) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (self.symbols.items) |symbol| { - size += 1; - try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - size += 1; - try leb.writeILEB128(writer, addend); - } - - size += 1; - if (symbol.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } - if (symbol.name) |name| { - size += 1; - size += name.len; - size += 1; - } - size += 2; + if (symbol.addend) |addend| { + try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); + try leb.writeILEB128(writer, addend); } - size += stream.bytes_written; - return size; + if (symbol.dylib_ordinal > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); + } else if (symbol.dylib_ordinal > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); + } + + if (symbol.name) |name| { + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(name); + try writer.writeByte(0); + } + + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + try writer.writeByte(macho.BIND_OPCODE_DONE); } -}; +} From 7d40aaad2b514703995458909b315f222543c4cd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 9 Jan 2021 20:38:55 +0100 Subject: [PATCH 11/16] macho: document more code + add test case --- src/codegen.zig | 23 ++++++-------- src/link/MachO.zig | 64 ++++++++++++++++++++++++-------------- src/link/MachO/imports.zig | 31 +++--------------- test/stage2/aarch64.zig | 17 ++++++++++ 4 files changed, 72 insertions(+), 63 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index ad4215191f..05898c77c8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1865,19 +1865,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // If it doesn't, it will get autofreed when we clean up the extern symbol table. const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name}); const already_defined = macho_file.extern_lazy_symbols.contains(decl_name); - const symbol: u32 = blk: { - if (macho_file.extern_lazy_symbols.get(decl_name)) |sym| { - self.bin_file.allocator.free(decl_name); - break :blk sym.index; - } else { - const index = @intCast(u32, macho_file.extern_lazy_symbols.items().len); - try macho_file.extern_lazy_symbols.putNoClobber(self.bin_file.allocator, decl_name, .{ - .name = decl_name, - .dylib_ordinal = 1, // TODO this is now hardcoded, since we only support libSystem. - .index = index, - }); - break :blk index; - } + const symbol: u32 = if (macho_file.extern_lazy_symbols.getIndex(decl_name)) |index| blk: { + self.bin_file.allocator.free(decl_name); + break :blk @intCast(u32, index); + } else blk: { + const index = @intCast(u32, macho_file.extern_lazy_symbols.items().len); + try macho_file.extern_lazy_symbols.putNoClobber(self.bin_file.allocator, decl_name, .{ + .name = decl_name, + .dylib_ordinal = 1, // TODO this is now hardcoded, since we only support libSystem. + }); + break :blk index; }; try macho_file.stub_fixups.append(self.bin_file.allocator, .{ .symbol = symbol, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 7d86c27aa9..c3808911eb 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -159,16 +159,13 @@ last_text_block: ?*TextBlock = null, /// prior to calling `generateSymbol`, and then immediately deallocated /// rather than sitting in the global scope. pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, - +/// A list of all stub (extern decls) fixups required for this run of the linker. +/// Warning, this is currently NOT thread-safe. See the TODO below. +/// TODO Move this list inside `updateDecl` where it should be allocated +/// prior to calling `generateSymbol`, and then immediately deallocated +/// rather than sitting in the global scope. stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, -pub const StubFixup = struct { - symbol: u32, - already_defined: bool, - start: usize, - len: usize, -}; - pub const PieFixup = struct { /// Target address we wanted to address in absolute terms. address: u64, @@ -179,6 +176,19 @@ pub const PieFixup = struct { len: usize, }; +pub const StubFixup = struct { + /// Id of extern (lazy) symbol. + symbol: u32, + /// Signals whether the symbol has already been declared before. If so, + /// then there is no need to rewrite the stub entry and related. + already_defined: bool, + /// Where in the byte stream we should perform the fixup. + start: usize, + /// The length of the byte stream. For x86_64, this will be + /// variable. For aarch64, it will be fixed at 4 bytes. + len: usize, +}; + /// `alloc_num / alloc_den` is the factor of padding when allocating. pub const alloc_num = 4; pub const alloc_den = 3; @@ -1030,13 +1040,20 @@ pub fn deinit(self: *MachO) void { if (self.d_sym) |*ds| { ds.deinit(self.base.allocator); } + for (self.extern_lazy_symbols.items()) |*entry| { + entry.value.deinit(self.base.allocator); + } + self.extern_lazy_symbols.deinit(self.base.allocator); + for (self.extern_nonlazy_symbols.items()) |*entry| { + entry.value.deinit(self.base.allocator); + } + self.extern_nonlazy_symbols.deinit(self.base.allocator); self.pie_fixups.deinit(self.base.allocator); + self.stub_fixups.deinit(self.base.allocator); 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); self.string_table.deinit(self.base.allocator); - self.extern_lazy_symbols.deinit(self.base.allocator); - self.extern_nonlazy_symbols.deinit(self.base.allocator); self.global_symbols.deinit(self.base.allocator); self.global_symbol_free_list.deinit(self.base.allocator); self.local_symbols.deinit(self.base.allocator); @@ -2047,7 +2064,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{ .name = name, .dylib_ordinal = 1, // TODO this is currently hardcoded. - .index = index, .segment = self.data_const_segment_cmd_index.?, .offset = index * @sizeOf(u64), }); @@ -2582,12 +2598,12 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { } fn writeIndirectSymbolTable(self: *MachO) !void { - const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const stubs = &text_seg.sections.items[self.stubs_section_index.?]; - const dc_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &dc_seg.sections.items[self.data_got_section_index.?]; - const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const la = &data_seg.sections.items[self.la_symbol_ptr_section_index.?]; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = &text_segment.sections.items[self.stubs_section_index.?]; + const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_seg.sections.items[self.data_got_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; dysymtab.nindirectsyms = 0; @@ -2595,8 +2611,8 @@ fn writeIndirectSymbolTable(self: *MachO) !void { var off = dysymtab.indirectsymoff; stubs.reserved1 = 0; - for (self.extern_lazy_symbols.items()) |entry| { - const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index); + for (self.extern_lazy_symbols.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); @@ -2605,17 +2621,17 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const base_id = @intCast(u32, self.extern_lazy_symbols.items().len); got.reserved1 = base_id; - for (self.extern_nonlazy_symbols.items()) |entry| { - const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index + base_id); + for (self.extern_nonlazy_symbols.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); dysymtab.nindirectsyms += 1; } - la.reserved1 = got.reserved1 + @intCast(u32, self.extern_nonlazy_symbols.items().len); - for (self.extern_lazy_symbols.items()) |entry| { - const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.value.index); + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, self.extern_nonlazy_symbols.items().len); + for (self.extern_lazy_symbols.items()) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); mem.writeIntLittle(u32, &buf, symtab_idx); try self.base.file.?.pwriteAll(&buf, off); off += @sizeOf(u32); diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig index 8e0f72e1de..c5f6211f1a 100644 --- a/src/link/MachO/imports.zig +++ b/src/link/MachO/imports.zig @@ -20,13 +20,15 @@ pub const ExternSymbol = struct { /// dylibs. dylib_ordinal: i64 = 0, + /// Id of the segment where this symbol is defined (will have its address + /// resolved). segment: u16 = 0, + + /// Offset relative to the start address of the `segment`. offset: u32 = 0, - addend: ?i32 = null, - index: u32, pub fn deinit(self: *ExternSymbol, allocator: *Allocator) void { - if (self.name) |*name| { + if (self.name) |name| { allocator.free(name); } } @@ -77,12 +79,6 @@ pub fn bindInfoSize(symbols: []*const ExternSymbol) !u64 { size += 1; try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - size += 1; - try leb.writeILEB128(writer, addend); - } - size += 2; } @@ -110,12 +106,6 @@ pub fn writeBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void { try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); - try leb.writeILEB128(writer, addend); - } - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); try writer.writeByte(macho.BIND_OPCODE_DONE); } @@ -129,12 +119,6 @@ pub fn lazyBindInfoSize(symbols: []*const ExternSymbol) !u64 { for (symbols) |symbol| { size += 1; try leb.writeILEB128(writer, symbol.offset); - - if (symbol.addend) |addend| { - size += 1; - try leb.writeILEB128(writer, addend); - } - size += 1; if (symbol.dylib_ordinal > 15) { try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); @@ -156,11 +140,6 @@ pub fn writeLazyBindInfo(symbols: []*const ExternSymbol, writer: anytype) !void try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); try leb.writeILEB128(writer, symbol.offset); - if (symbol.addend) |addend| { - try writer.writeByte(macho.BIND_OPCODE_SET_ADDEND_SLEB); - try leb.writeILEB128(writer, addend); - } - if (symbol.dylib_ordinal > 15) { try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig index 7d05c60bb8..1dde30e969 100644 --- a/test/stage2/aarch64.zig +++ b/test/stage2/aarch64.zig @@ -199,4 +199,21 @@ pub fn addCases(ctx: *TestContext) !void { "", ); } + + { + var case = ctx.exe("hello world linked to libc", macos_aarch64); + + // TODO rewrite this test once we handle more int conversions and return args. + case.addCompareOutput( + \\extern "c" fn write(usize, usize, usize) void; + \\extern "c" fn exit(usize) noreturn; + \\ + \\export fn _start() noreturn { + \\ write(1, @ptrToInt("Hello, World!\n"), 14); + \\ exit(0); + \\} + , + "Hello, World!\n", + ); + } } From 1b91a9f4c840741e77e5c7ce826c5f80c9f9ee80 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 9 Jan 2021 23:04:00 +0100 Subject: [PATCH 12/16] macho: bring back lld cc hot-fix --- src/link/MachO.zig | 92 ++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c3808911eb..3839158f70 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -923,42 +923,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { return error.NoSymbolTableFound; } - // // Parse dyld info - // try self.parseBindingInfoTable(); - // try self.parseLazyBindingInfoTable(); - - // // Update the dylib ordinals. - // self.binding_info_table.dylib_ordinal = next_ordinal; - // for (self.lazy_binding_info_table.symbols.items) |*symbol| { - // symbol.dylib_ordinal = next_ordinal; - // } - - // // Write updated dyld info. - // const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - // { - // const size = try self.binding_info_table.calcSize(); - // assert(dyld_info.bind_size >= size); - - // var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); - // defer self.base.allocator.free(buffer); - - // var stream = std.io.fixedBufferStream(buffer); - // try self.binding_info_table.write(stream.writer()); - - // try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); - // } - // { - // const size = try self.lazy_binding_info_table.calcSize(); - // assert(dyld_info.lazy_bind_size >= size); - - // var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); - // defer self.base.allocator.free(buffer); - - // var stream = std.io.fixedBufferStream(buffer); - // try self.lazy_binding_info_table.write(stream.writer()); - - // try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); - // } + // Patch dyld info + try self.fixupBindInfo(next_ordinal); + try self.fixupLazyBindInfo(next_ordinal); // Write updated load commands and the header try self.writeLoadCommands(); @@ -3068,24 +3035,61 @@ fn parseStringTable(self: *MachO) !void { self.string_table.appendSliceAssumeCapacity(buffer); } -fn parseBindingInfoTable(self: *MachO) !void { +fn fixupBindInfo(self: *MachO, dylib_ordinal: u32) !void { const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; var buffer = try self.base.allocator.alloc(u8, dyld_info.bind_size); defer self.base.allocator.free(buffer); const nread = try self.base.file.?.preadAll(buffer, dyld_info.bind_off); assert(nread == buffer.len); - - var stream = std.io.fixedBufferStream(buffer); - // try self.binding_info_table.read(stream.reader(), self.base.allocator); + try self.fixupInfoCommon(buffer, dylib_ordinal); + try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); } -fn parseLazyBindingInfoTable(self: *MachO) !void { +fn fixupLazyBindInfo(self: *MachO, dylib_ordinal: u32) !void { const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; var buffer = try self.base.allocator.alloc(u8, dyld_info.lazy_bind_size); defer self.base.allocator.free(buffer); const nread = try self.base.file.?.preadAll(buffer, dyld_info.lazy_bind_off); assert(nread == buffer.len); - - var stream = std.io.fixedBufferStream(buffer); - // try self.lazy_binding_info_table.read(stream.reader(), self.base.allocator); + try self.fixupInfoCommon(buffer, dylib_ordinal); + try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); +} + +fn fixupInfoCommon(self: *MachO, buffer: []u8, dylib_ordinal: u32) !void { + var stream = std.io.fixedBufferStream(buffer); + var reader = stream.reader(); + + while (true) { + const inst = reader.readByte() catch |err| switch (err) { + error.EndOfStream => break, + else => return err, + }; + const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; + const opcode: u8 = inst & macho.BIND_OPCODE_MASK; + + switch (opcode) { + macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { + var next = try reader.readByte(); + while (next != @as(u8, 0)) { + next = try reader.readByte(); + } + }, + macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { + _ = try std.leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { + // Perform the fixup. + try stream.seekBy(-1); + var writer = stream.writer(); + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, dylib_ordinal)); + }, + macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { + _ = try std.leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_ADDEND_SLEB => { + _ = try std.leb.readILEB128(i64, reader); + }, + else => {}, + } + } } From f0d7ec6f33634edb0ddb3ba5d5b306e9f2de5418 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 10 Jan 2021 00:21:34 +0100 Subject: [PATCH 13/16] macho: add x86_64 support --- src/codegen.zig | 22 +++++++++-- src/link/MachO.zig | 91 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 05898c77c8..bfb1540e40 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1876,14 +1876,30 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }); break :blk index; }; + const start = self.code.items.len; + const len: usize = blk: { + switch (arch) { + .x86_64 => { + // callq + try self.code.ensureCapacity(self.code.items.len + 5); + self.code.appendSliceAssumeCapacity(&[5]u8{ 0xe8, 0x0, 0x0, 0x0, 0x0 }); + break :blk 5; + }, + .aarch64 => { + // bl + writeInt(u32, try self.code.addManyAsArray(4), 0); + break :blk 4; + }, + else => unreachable, // unsupported architecture on MachO + } + }; try macho_file.stub_fixups.append(self.bin_file.allocator, .{ .symbol = symbol, .already_defined = already_defined, - .start = self.code.items.len, - .len = 4, + .start = start, + .len = len, }); // We mark the space and fix it up later. - writeInt(u32, try self.code.addManyAsArray(4), 0); } else { return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3839158f70..fed3ee7836 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1241,14 +1241,18 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { for (self.stub_fixups.items) |fixup| { const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; const text_addr = symbol.n_value + fixup.start; - const displacement = @intCast(u32, stub_addr - text_addr); - var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; switch (self.base.options.target.cpu.arch) { - .x86_64 => return error.TODOImplementStubFixupsForx86_64, + .x86_64 => { + const displacement = @intCast(u32, stub_addr - text_addr - fixup.len); + var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; + mem.writeIntSliceLittle(u32, placeholder, displacement); + }, .aarch64 => { + const displacement = @intCast(u32, stub_addr - text_addr); + var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(@intCast(i28, displacement)).toU32()); }, - else => unreachable, + else => unreachable, // unsupported target architecture } if (!fixup.already_defined) { try self.writeStub(fixup.symbol); @@ -1565,6 +1569,11 @@ pub fn populateMissingMetadata(self: *MachO) !void { .aarch64 => 2, else => unreachable, // unhandled architecture type }; + const stub_size: u4 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 6, + .aarch64 => 2 * @sizeOf(u32), + else => unreachable, // unhandled architecture type + }; const flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); @@ -1583,7 +1592,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { .nreloc = 0, .flags = flags, .reserved1 = 0, - .reserved2 = 2 * @sizeOf(u32), + .reserved2 = stub_size, .reserved3 = 0, }); self.header_dirty = true; @@ -2044,7 +2053,30 @@ pub fn populateMissingMetadata(self: *MachO) !void { const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; const got = &data_const_segment.sections.items[self.data_got_section_index.?]; switch (self.base.options.target.cpu.arch) { - .x86_64 => return error.TODOImplementStubHelperForX86_64, + .x86_64 => { + const code_size = 15; + var code: [code_size]u8 = undefined; + // lea %r11, [rip + disp] + code[0] = 0x4c; + code[1] = 0x8d; + code[2] = 0x1d; + { + const displacement = @intCast(u32, data.addr - stub_helper.addr - 7); + mem.writeIntLittle(u32, code[3..7], displacement); + } + // push %r11 + code[7] = 0x41; + code[8] = 0x53; + // jmp [rip + disp] + code[9] = 0xff; + code[10] = 0x25; + { + const displacement = @intCast(u32, got.addr - stub_helper.addr - code_size); + mem.writeIntLittle(u32, code[11..], displacement); + } + self.stub_helper_stubs_start_off = stub_helper.offset + code_size; + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + }, .aarch64 => { var code: [4 * @sizeOf(u32)]u8 = undefined; { @@ -2410,7 +2442,12 @@ fn writeLazySymbolPointer(self: *MachO, index: u32) !void { const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; - const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32); + const stub_size: u4 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; const end = stub_helper.addr + stub_off - stub_helper.offset; var buf: [@sizeOf(u64)]u8 = undefined; mem.writeIntLittle(u64, &buf, end); @@ -2428,42 +2465,62 @@ fn writeStub(self: *MachO, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); - const displacement = la_ptr_addr - stub_addr; log.debug("writing stub at 0x{x}", .{stub_off}); + var code = try self.base.allocator.alloc(u8, stubs.reserved2); + defer self.base.allocator.free(code); switch (self.base.options.target.cpu.arch) { - .x86_64 => return error.TODOImplementWritingStubsForx86_64, + .x86_64 => { + const displacement = @intCast(u32, la_ptr_addr - stub_addr - stubs.reserved2); + // jmp + code[0] = 0xff; + code[1] = 0x25; + mem.writeIntLittle(u32, code[2..][0..4], displacement); + }, .aarch64 => { - var code: [2 * @sizeOf(u32)]u8 = undefined; + const displacement = la_ptr_addr - stub_addr; mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ .literal = @intCast(u19, displacement / 4), }).toU32()); mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32()); - try self.base.file.?.pwriteAll(&code, stub_off); }, else => unreachable, } + try self.base.file.?.pwriteAll(code, stub_off); } fn writeStubInStubHelper(self: *MachO, index: u32) !void { const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; - const stub_off = self.stub_helper_stubs_start_off.? + index * 3 * @sizeOf(u32); - const end = stub_helper.addr + stub_off - stub_helper.offset; - const displacement = @intCast(i64, stub_helper.addr) - @intCast(i64, end + 4); + const stub_size: u4 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + var code = try self.base.allocator.alloc(u8, stub_size); + defer self.base.allocator.free(code); switch (self.base.options.target.cpu.arch) { - .x86_64 => return error.TODOImplementWritingStubsInStubHelperForx86_64, + .x86_64 => { + const displacement = @intCast(i32, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size); + // pushq + code[0] = 0x68; + mem.writeIntLittle(u32, code[1..][0..4], index * 0xd); // TODO + // jmpq + code[5] = 0xe9; + mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); + }, .aarch64 => { - var code: [3 * @sizeOf(u32)]u8 = undefined; + const displacement = @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4; mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ .literal = 0x2, }).toU32()); mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); mem.writeIntLittle(u32, code[8..12], index * 0xd); // TODO This is the size of lazy binding opcode block. - try self.base.file.?.pwriteAll(&code, stub_off); }, else => unreachable, } + try self.base.file.?.pwriteAll(code, stub_off); } fn relocateSymbolTable(self: *MachO) !void { From 2ea0901dd639a98f182b71f7da7fde988fdcd91b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 10 Jan 2021 21:06:56 +0100 Subject: [PATCH 14/16] macho: properly populate offset into lazy bind info --- src/link/MachO.zig | 75 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index fed3ee7836..27c395da95 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2505,7 +2505,7 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { const displacement = @intCast(i32, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size); // pushq code[0] = 0x68; - mem.writeIntLittle(u32, code[1..][0..4], index * 0xd); // TODO + mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. // jmpq code[5] = 0xe9; mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); @@ -2516,7 +2516,7 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { .literal = 0x2, }).toU32()); mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(@intCast(i28, displacement)).toU32()); - mem.writeIntLittle(u32, code[8..12], index * 0xd); // TODO This is the size of lazy binding opcode block. + mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. }, else => unreachable, } @@ -2630,6 +2630,7 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; dysymtab.nindirectsyms = 0; + // TODO check if we have allocated enough size. var buf: [@sizeOf(u32)]u8 = undefined; var off = dysymtab.indirectsymoff; @@ -2868,10 +2869,78 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); + try self.populateLazyBindOffsetsInStubHelper(buffer); self.load_commands_dirty = true; self.lazy_binding_info_dirty = false; } +fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { + if (self.extern_lazy_symbols.items().len == 0) return; + + var stream = std.io.fixedBufferStream(buffer); + var reader = stream.reader(); + var offsets = std.ArrayList(u32).init(self.base.allocator); + try offsets.append(0); + defer offsets.deinit(); + var valid_block = false; + + while (true) { + const inst = reader.readByte() catch |err| switch (err) { + error.EndOfStream => break, + else => return err, + }; + const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; + const opcode: u8 = inst & macho.BIND_OPCODE_MASK; + + switch (opcode) { + macho.BIND_OPCODE_DO_BIND => { + valid_block = true; + }, + macho.BIND_OPCODE_DONE => { + if (valid_block) { + const offset = try stream.getPos(); + try offsets.append(@intCast(u32, offset)); + } + valid_block = false; + }, + macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { + var next = try reader.readByte(); + while (next != @as(u8, 0)) { + next = try reader.readByte(); + } + }, + macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { + _ = try std.leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { + _ = try std.leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_ADDEND_SLEB => { + _ = try std.leb.readILEB128(i64, reader); + }, + else => {}, + } + } + assert(self.extern_lazy_symbols.items().len <= offsets.items.len); + + const stub_size: u4 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const off: u4 = switch (self.base.options.target.cpu.arch) { + .x86_64 => 1, + .aarch64 => 2 * @sizeOf(u32), + else => unreachable, + }; + var buf: [@sizeOf(u32)]u8 = undefined; + for (self.extern_lazy_symbols.items()) |_, i| { + const placeholder_off = self.stub_helper_stubs_start_off.? + i * stub_size + off; + mem.writeIntLittle(u32, &buf, offsets.items[i]); + try self.base.file.?.pwriteAll(&buf, placeholder_off); + } +} + fn writeStringTable(self: *MachO) !void { if (!self.string_table_dirty) return; @@ -2979,8 +3048,6 @@ fn writeHeader(self: *MachO) !void { } /// Parse MachO contents from existing binary file. -/// TODO This method is incomplete and currently parses only the header -/// plus the load commands. fn parseFromFile(self: *MachO, file: fs.File) !void { self.base.file = file; var reader = file.reader(); From 4ffa8952ccaef106ef4dfcdb4369960816337ba3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 10 Jan 2021 21:10:26 +0100 Subject: [PATCH 15/16] macho: add x86_64 tests --- test/stage2/aarch64.zig | 3 ++- test/stage2/test.zig | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig index 1dde30e969..fa3ded5368 100644 --- a/test/stage2/aarch64.zig +++ b/test/stage2/aarch64.zig @@ -209,7 +209,8 @@ pub fn addCases(ctx: *TestContext) !void { \\extern "c" fn exit(usize) noreturn; \\ \\export fn _start() noreturn { - \\ write(1, @ptrToInt("Hello, World!\n"), 14); + \\ write(1, @ptrToInt("Hello,"), 6); + \\ write(1, @ptrToInt(" World!\n,"), 8); \\ exit(0); \\} , diff --git a/test/stage2/test.zig b/test/stage2/test.zig index cd1d6c259d..a6ccade3ea 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1495,4 +1495,22 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":8:10: error: evaluation exceeded 1000 backwards branches"}); } + + { + var case = ctx.exe("hello world linked to libc", macosx_x64); + + // TODO rewrite this test once we handle more int conversions and return args. + case.addCompareOutput( + \\extern "c" fn write(usize, usize, usize) void; + \\extern "c" fn exit(usize) noreturn; + \\ + \\export fn _start() noreturn { + \\ write(1, @ptrToInt("Hello,"), 6); + \\ write(1, @ptrToInt(" World!\n,"), 8); + \\ exit(0); + \\} + , + "Hello, World!\n", + ); + } } From 212814932578f5916bff2dd04d501e1d30be740c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 10 Jan 2021 21:33:25 +0100 Subject: [PATCH 16/16] macho: update DebugSymbols to include DATA_CONST seg --- src/link/MachO/DebugSymbols.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 11f87d5495..81a016ce42 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -39,6 +39,8 @@ load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, pagezero_segment_cmd_index: ?u16 = null, /// __TEXT segment text_segment_cmd_index: ?u16 = null, +/// __DATA_CONST segment +data_const_segment_cmd_index: ?u16 = null, /// __DATA segment data_segment_cmd_index: ?u16 = null, /// __LINKEDIT segment @@ -171,6 +173,15 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void self.header_dirty = true; self.load_commands_dirty = true; } + if (self.data_const_segment_cmd_index == null) outer: { + if (self.base.data_const_segment_cmd_index == null) break :outer; // __DATA_CONST is optional + self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + const base_cmd = self.base.load_commands.items[self.base.data_const_segment_cmd_index.?].Segment; + const cmd = try self.copySegmentCommand(allocator, base_cmd); + try self.load_commands.append(allocator, .{ .Segment = cmd }); + self.header_dirty = true; + self.load_commands_dirty = true; + } if (self.data_segment_cmd_index == null) outer: { if (self.base.data_segment_cmd_index == null) break :outer; // __DATA is optional self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);