diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e7603e2bde..8d528ca647 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -285,8 +285,7 @@ pub fn createEmpty( }; try self.d_sym.?.initMetadata(self); } else { - try self.reportUnexpectedError("TODO: implement generating and emitting __DWARF in .o file", .{}); - return error.Unexpected; + @panic("TODO: implement generating and emitting __DWARF in .o file"); }, .code_view => unreachable, } @@ -597,7 +596,6 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node try self.allocateSections(); self.allocateSegments(); - self.allocateAtoms(); self.allocateSyntheticSymbols(); try self.allocateLinkeditSegment(); @@ -615,7 +613,8 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node if (!atom.flags.alive) continue; const sect = &self.sections.items(.header)[atom.out_n_sect]; if (sect.isZerofill()) continue; - if (mem.indexOf(u8, sect.segName(), "ZIG") == null) continue; // Non-Zig sections are handled separately + if (!self.isZigSection(atom.out_n_sect)) continue; // Non-Zig sections are handled separately + if (atom.getRelocs(self).len == 0) continue; // TODO: we will resolve and write ZigObject's TLS data twice: // once here, and once in writeAtoms const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; @@ -636,7 +635,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node return error.FlushFailure; }, }; - const file_offset = sect.offset + atom.value - sect.addr; + const file_offset = sect.offset + atom.value; atom.resolveRelocs(self, code) catch |err| switch (err) { error.ResolveFailed => has_resolve_error = true, else => |e| { @@ -2025,7 +2024,7 @@ pub fn sortSections(self: *MachO) !void { for (zo.symtab.items(.nlist)) |*sym| { if (sym.sect()) { - sym.n_sect = backlinks[sym.n_sect]; + sym.n_sect = backlinks[sym.n_sect - 1] + 1; } } @@ -2232,11 +2231,11 @@ fn initSegments(self: *MachO) !void { log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size}); log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size}); } - _ = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size }); + self.pagezero_seg_index = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size }); } // __TEXT segment is non-optional - _ = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") }); + self.text_seg_index = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") }); // Next, create segments required by sections for (slice.items(.header)) |header| { @@ -2248,15 +2247,57 @@ fn initSegments(self: *MachO) !void { } // Add __LINKEDIT - _ = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") }); + self.linkedit_seg_index = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") }); // Sort segments - const sortFn = struct { - fn sortFn(ctx: void, lhs: macho.segment_command_64, rhs: macho.segment_command_64) bool { - return segmentLessThan(ctx, lhs.segName(), rhs.segName()); + const Entry = struct { + index: u8, + + pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool { + return segmentLessThan( + {}, + macho_file.segments.items[lhs.index].segName(), + macho_file.segments.items[rhs.index].segName(), + ); } - }.sortFn; - mem.sort(macho.segment_command_64, self.segments.items, {}, sortFn); + }; + + var entries = try std.ArrayList(Entry).initCapacity(gpa, self.segments.items.len); + defer entries.deinit(); + for (0..self.segments.items.len) |index| { + entries.appendAssumeCapacity(.{ .index = @intCast(index) }); + } + + mem.sort(Entry, entries.items, self, Entry.lessThan); + + const backlinks = try gpa.alloc(u8, entries.items.len); + defer gpa.free(backlinks); + for (entries.items, 0..) |entry, i| { + backlinks[entry.index] = @intCast(i); + } + + const segments = try self.segments.toOwnedSlice(gpa); + defer gpa.free(segments); + + try self.segments.ensureTotalCapacityPrecise(gpa, segments.len); + for (entries.items) |sorted| { + self.segments.appendAssumeCapacity(segments[sorted.index]); + } + + for (&[_]*?u8{ + &self.pagezero_seg_index, + &self.text_seg_index, + &self.linkedit_seg_index, + &self.zig_text_seg_index, + &self.zig_got_seg_index, + &self.zig_const_seg_index, + &self.zig_data_seg_index, + &self.zig_bss_seg_index, + }) |maybe_index| { + if (maybe_index.*) |*index| { + index.* = backlinks[index.*]; + } + } // Attach sections to segments for (slice.items(.header), slice.items(.segment_id)) |header, *seg_id| { @@ -2277,15 +2318,6 @@ fn initSegments(self: *MachO) !void { segment.nsects += 1; seg_id.* = segment_id; } - - self.pagezero_seg_index = self.getSegmentByName("__PAGEZERO"); - self.text_seg_index = self.getSegmentByName("__TEXT").?; - self.linkedit_seg_index = self.getSegmentByName("__LINKEDIT").?; - self.zig_text_seg_index = self.getSegmentByName("__TEXT_ZIG"); - self.zig_got_seg_index = self.getSegmentByName("__GOT_ZIG"); - self.zig_const_seg_index = self.getSegmentByName("__CONST_ZIG"); - self.zig_data_seg_index = self.getSegmentByName("__DATA_ZIG"); - self.zig_bss_seg_index = self.getSegmentByName("__BSS_ZIG"); } fn allocateSections(self: *MachO) !void { @@ -2300,8 +2332,8 @@ fn allocateSections(self: *MachO) !void { const page_size = self.getPageSize(); const slice = self.sections.slice(); - const last_index = for (slice.items(.header), 0..) |header, i| { - if (mem.indexOf(u8, header.segName(), "ZIG")) |_| break i; + const last_index = for (0..slice.items(.header).len) |i| { + if (self.isZigSection(@intCast(i))) break i; } else slice.items(.header).len; for (slice.items(.header)[0..last_index], slice.items(.segment_id)[0..last_index]) |*header, curr_seg_id| { @@ -2354,8 +2386,8 @@ fn allocateSections(self: *MachO) !void { /// We allocate segments in a separate step to also consider segments that have no sections. fn allocateSegments(self: *MachO) void { const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0; - const last_index = for (self.segments.items, 0..) |seg, i| { - if (mem.indexOf(u8, seg.segName(), "ZIG")) |_| break i; + const last_index = for (0..self.segments.items.len) |i| { + if (self.isZigSegment(@intCast(i))) break i; } else self.segments.items.len; var vmaddr: u64 = if (self.pagezero_seg_index) |index| @@ -2392,23 +2424,6 @@ fn allocateSegments(self: *MachO) void { } } -pub fn allocateAtoms(self: *MachO) void { - const slice = self.sections.slice(); - for (slice.items(.header), slice.items(.atoms)) |header, atoms| { - if (atoms.items.len == 0) continue; - for (atoms.items) |atom_index| { - const atom = self.getAtom(atom_index).?; - assert(atom.flags.alive); - atom.value += header.addr; - } - } - - for (self.thunks.items) |*thunk| { - const header = self.sections.items(.header)[thunk.out_n_sect]; - thunk.value += header.addr; - } -} - fn allocateSyntheticSymbols(self: *MachO) void { const text_seg = self.getTextSegment(); @@ -2603,7 +2618,7 @@ fn writeAtoms(self: *MachO) !void { for (atoms.items) |atom_index| { const atom = self.getAtom(atom_index).?; assert(atom.flags.alive); - const off = math.cast(usize, atom.value - header.addr) orelse return error.Overflow; + const off = math.cast(usize, atom.value) orelse return error.Overflow; const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; try atom.getData(self, buffer[off..][0..atom_size]); atom.resolveRelocs(self, buffer[off..][0..atom_size]) catch |err| switch (err) { @@ -2617,7 +2632,7 @@ fn writeAtoms(self: *MachO) !void { for (self.thunks.items) |thunk| { const header = slice.items(.header)[thunk.out_n_sect]; - const offset = thunk.value - header.addr + header.offset; + const offset = thunk.value + header.offset; const buffer = try gpa.alloc(u8, thunk.size()); defer gpa.free(buffer); var stream = std.io.fixedBufferStream(buffer); @@ -2825,7 +2840,7 @@ pub fn writeDataInCode(self: *MachO, base_address: u64, off: u32) !u32 { if (atom.flags.alive) for (in_dices[start_dice..next_dice]) |dice| { dices.appendAssumeCapacity(.{ - .offset = @intCast(atom.value + dice.offset - start_off - base_address), + .offset = @intCast(atom.getAddress(self) + dice.offset - start_off - base_address), .length = dice.length, .kind = dice.kind, }); @@ -3276,6 +3291,34 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { return null; } +fn detectAllocCollisionVirtual(self: *MachO, start: u64, size: u64) ?u64 { + // Conservatively commit one page size as reserved space for the headers as we + // expect it to grow and everything else be moved in flush anyhow. + const header_size = self.getPageSize(); + if (start < header_size) + return header_size; + + const end = start + padToIdeal(size); + + for (self.sections.items(.header)) |header| { + const increased_size = padToIdeal(header.size); + const test_end = header.addr + increased_size; + if (end > header.addr and start < test_end) { + return test_end; + } + } + + for (self.segments.items) |seg| { + const increased_size = padToIdeal(seg.vmsize); + const test_end = seg.vmaddr +| increased_size; + if (end > seg.vmaddr and start < test_end) { + return test_end; + } + } + + return null; +} + fn allocatedSize(self: *MachO, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); @@ -3290,7 +3333,7 @@ fn allocatedSize(self: *MachO, start: u64) u64 { return min_pos - start; } -fn allocatedVirtualSize(self: *MachO, start: u64) u64 { +fn allocatedSizeVirtual(self: *MachO, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); for (self.segments.items) |seg| { @@ -3300,7 +3343,7 @@ fn allocatedVirtualSize(self: *MachO, start: u64) u64 { return min_pos - start; } -fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { +pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { var start: u64 = 0; while (self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForward(u64, item_end, min_alignment); @@ -3308,18 +3351,30 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { return start; } +pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) u64 { + var start: u64 = 0; + while (self.detectAllocCollisionVirtual(start, object_size)) |item_end| { + start = mem.alignForward(u64, item_end, min_alignment); + } + return start; +} + +pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { + const file = self.base.file.?; + const amt = try file.copyRangeAll(old_offset, file, new_offset, size); + if (amt != size) return error.InputOutput; +} + /// Like File.copyRangeAll but also ensures the source region is zeroed out after copy. /// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader. fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { const gpa = self.base.comp.gpa; - const file = self.base.file.?; - const amt = try file.copyRangeAll(old_offset, file, new_offset, size); - if (amt != size) return error.InputOutput; + try self.copyRangeAll(old_offset, new_offset, size); const size_u = math.cast(usize, size) orelse return error.Overflow; const zeroes = try gpa.alloc(u8, size_u); defer gpa.free(zeroes); @memset(zeroes, 0); - try file.pwriteAll(zeroes, old_offset); + try self.base.file.?.pwriteAll(zeroes, old_offset); } const InitMetadataOptions = struct { @@ -3391,8 +3446,6 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { .prot = macho.PROT.READ | macho.PROT.WRITE, }); } - } else { - @panic("TODO initMetadata when relocatable"); } const appendSect = struct { @@ -3406,6 +3459,19 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { } }.appendSect; + const allocSect = struct { + fn allocSect(macho_file: *MachO, sect_id: u8, size: u64) !void { + const sect = &macho_file.sections.items(.header)[sect_id]; + const alignment = try math.powi(u32, 2, sect.@"align"); + if (!sect.isZerofill()) { + sect.offset = math.cast(u32, macho_file.findFreeSpace(size, alignment)) orelse + return error.Overflow; + } + sect.addr = macho_file.findFreeSpaceVirtual(size, alignment); + sect.size = size; + } + }.allocSect; + { self.zig_text_sect_index = try self.addSection("__TEXT_ZIG", "__text_zig", .{ .alignment = switch (self.getTarget().cpu.arch) { @@ -3415,7 +3481,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { }, .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }); - appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?); + if (self.base.isRelocatable()) { + try allocSect(self, self.zig_text_sect_index.?, options.program_code_size_hint); + } else { + appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?); + } } if (!self.base.isRelocatable()) { @@ -3427,33 +3497,52 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { { self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{}); - appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?); + if (self.base.isRelocatable()) { + try allocSect(self, self.zig_const_sect_index.?, 1024); + } else { + appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?); + } } { self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{}); - appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?); + if (self.base.isRelocatable()) { + try allocSect(self, self.zig_data_sect_index.?, 1024); + } else { + appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?); + } } { self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{ .flags = macho.S_ZEROFILL, }); - appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?); + if (self.base.isRelocatable()) { + try allocSect(self, self.zig_bss_sect_index.?, 1024); + } else { + appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?); + } } } pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { + if (self.base.isRelocatable()) { + try self.growSectionRelocatable(sect_index, needed_size); + } else { + try self.growSectionNonRelocatable(sect_index, needed_size); + } +} + +fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { const sect = &self.sections.items(.header)[sect_index]; - const seg_id = self.sections.items(.segment_id)[sect_index]; - const seg = &self.segments.items[seg_id]; if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) { const existing_size = sect.size; sect.size = 0; // Must move the entire section. - const new_offset = self.findFreeSpace(needed_size, self.getPageSize()); + const alignment = self.getPageSize(); + const new_offset = self.findFreeSpace(needed_size, alignment); log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{ sect.segName(), @@ -3465,15 +3554,19 @@ pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size); sect.offset = @intCast(new_offset); - seg.fileoff = new_offset; } sect.size = needed_size; + + const seg_id = self.sections.items(.segment_id)[sect_index]; + const seg = &self.segments.items[seg_id]; + seg.fileoff = sect.offset; + if (!sect.isZerofill()) { seg.filesize = needed_size; } - const mem_capacity = self.allocatedVirtualSize(seg.vmaddr); + const mem_capacity = self.allocatedSizeVirtual(seg.vmaddr); if (needed_size > mem_capacity) { var err = try self.addErrorWithNotes(2); try err.addMsg(self, "fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{ @@ -3487,6 +3580,36 @@ pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { seg.vmsize = needed_size; } +fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { + const sect = &self.sections.items(.header)[sect_index]; + + if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) { + const existing_size = sect.size; + sect.size = 0; + + // Must move the entire section. + const alignment = try math.powi(u32, 2, sect.@"align"); + const new_offset = self.findFreeSpace(needed_size, alignment); + const new_addr = self.findFreeSpaceVirtual(needed_size, alignment); + + log.debug("new '{s},{s}' file offset 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ + sect.segName(), + sect.sectName(), + new_offset, + new_offset + existing_size, + new_addr, + new_addr + existing_size, + }); + + try self.copyRangeAll(sect.offset, new_offset, existing_size); + + sect.offset = @intCast(new_offset); + sect.addr = new_addr; + } + + sect.size = needed_size; +} + pub fn getTarget(self: MachO) std.Target { return self.base.comp.root_mod.resolved_target.result; } @@ -3532,6 +3655,36 @@ inline fn requiresThunks(self: MachO) bool { return self.getTarget().cpu.arch == .aarch64; } +pub fn isZigSegment(self: MachO, seg_id: u8) bool { + inline for (&[_]?u8{ + self.zig_text_seg_index, + self.zig_got_seg_index, + self.zig_const_seg_index, + self.zig_data_seg_index, + self.zig_bss_seg_index, + }) |maybe_index| { + if (maybe_index) |index| { + if (index == seg_id) return true; + } + } + return false; +} + +pub fn isZigSection(self: MachO, sect_id: u8) bool { + inline for (&[_]?u8{ + self.zig_text_sect_index, + self.zig_got_sect_index, + self.zig_const_sect_index, + self.zig_data_sect_index, + self.zig_bss_sect_index, + }) |maybe_index| { + if (maybe_index) |index| { + if (index == sect_id) return true; + } + } + return false; +} + pub fn addSegment(self: *MachO, name: []const u8, opts: struct { vmaddr: u64 = 0, vmsize: u64 = 0, @@ -4033,10 +4186,13 @@ fn formatSections( _ = unused_fmt_string; const slice = self.sections.slice(); for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| { - try writer.print("sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x})\n", .{ - i, seg_id, header.segName(), header.sectName(), header.offset, header.addr, - header.@"align", header.size, - }); + try writer.print( + "sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x}) : relocs({x};{d})\n", + .{ + i, seg_id, header.segName(), header.sectName(), header.addr, header.offset, + header.@"align", header.size, header.reloff, header.nreloc, + }, + ); } } diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 14c2526108..309a246969 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -1,4 +1,4 @@ -/// Address allocated for this Atom. +/// Address offset allocated for this Atom wrt to its section start address. value: u64 = 0, /// Name of this Atom. @@ -84,6 +84,11 @@ pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 { return self.getInputSection(macho_file).addr + self.off; } +pub fn getAddress(self: Atom, macho_file: *MachO) u64 { + const header = macho_file.sections.items(.header)[self.out_n_sect]; + return header.addr + self.value; +} + pub fn getPriority(self: Atom, macho_file: *MachO) u64 { const file = self.getFile(macho_file); return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect)); @@ -114,9 +119,12 @@ pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { const segname, const sectname, const flags = blk: { + const segname = sect.segName(); + const sectname = sect.sectName(); + if (sect.isCode()) break :blk .{ "__TEXT", - sect.sectName(), + sectname, macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }; @@ -127,34 +135,29 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { => break :blk .{ "__TEXT", "__const", macho.S_REGULAR }, macho.S_CSTRING_LITERALS => { - if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{ - sect.segName(), sect.sectName(), macho.S_REGULAR, + if (mem.startsWith(u8, sectname, "__objc")) break :blk .{ + segname, sectname, macho.S_REGULAR, }; break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS }; }, macho.S_MOD_INIT_FUNC_POINTERS, macho.S_MOD_TERM_FUNC_POINTERS, - => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags }, - macho.S_LITERAL_POINTERS, + => break :blk .{ "__DATA_CONST", sectname, sect.flags }, + macho.S_ZEROFILL, macho.S_GB_ZEROFILL, macho.S_THREAD_LOCAL_VARIABLES, macho.S_THREAD_LOCAL_VARIABLE_POINTERS, macho.S_THREAD_LOCAL_REGULAR, macho.S_THREAD_LOCAL_ZEROFILL, - => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, + => break :blk .{ "__DATA", sectname, sect.flags }, - macho.S_COALESCED => break :blk .{ - sect.segName(), - sect.sectName(), - macho.S_REGULAR, - }, + // TODO: do we need this check here? + macho.S_COALESCED => break :blk .{ segname, sectname, macho.S_REGULAR }, macho.S_REGULAR => { - const segname = sect.segName(); - const sectname = sect.sectName(); if (mem.eql(u8, segname, "__DATA")) { if (mem.eql(u8, sectname, "__const") or mem.eql(u8, sectname, "__cfstring") or @@ -168,7 +171,7 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { break :blk .{ segname, sectname, sect.flags }; }, - else => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, + else => break :blk .{ segname, sectname, sect.flags }, } }; const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection( @@ -189,14 +192,17 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { /// File offset relocation happens transparently, so it is not included in /// this calculation. pub fn capacity(self: Atom, macho_file: *MachO) u64 { - const next_value = if (macho_file.getAtom(self.next_index)) |next| next.value else std.math.maxInt(u32); - return next_value - self.value; + const next_addr = if (macho_file.getAtom(self.next_index)) |next| + next.getAddress(macho_file) + else + std.math.maxInt(u32); + return next_addr - self.getAddress(macho_file); } pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { // No need to keep a free list node for the last block. const next = macho_file.getAtom(self.next_index) orelse return false; - const cap = next.value - self.value; + const cap = next.getAddress(macho_file) - self.getAddress(macho_file); const ideal_cap = MachO.padToIdeal(self.size); if (cap <= ideal_cap) return false; const surplus = cap - ideal_cap; @@ -263,15 +269,15 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void { atom_placement = last.atom_index; break :blk new_start_vaddr; } else { - break :blk sect.addr; + break :blk 0; } }; log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{ self.atom_index, self.getName(macho_file), - self.value, - self.value + self.size, + self.getAddress(macho_file), + self.getAddress(macho_file) + self.size, }); const expand_section = if (atom_placement) |placement_index| @@ -279,7 +285,7 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void { else true; if (expand_section) { - const needed_size = (self.value + self.size) - sect.addr; + const needed_size = self.value + self.size; try macho_file.growSection(self.out_n_sect, needed_size); last_atom_index.* = self.atom_index; @@ -544,7 +550,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { const name = self.getName(macho_file); const relocs = self.getRelocs(macho_file); - relocs_log.debug("{x}: {s}", .{ self.value, name }); + relocs_log.debug("{x}: {s}", .{ self.getAddress(macho_file), name }); var has_error = false; var stream = std.io.fixedBufferStream(buffer); @@ -569,7 +575,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { try macho_file.reportParseError2( file.getIndex(), "{s}: 0x{x}: 0x{x}: failed to relax relocation: type {s}, target {s}", - .{ name, self.value, rel.offset, @tagName(rel.type), target }, + .{ name, self.getAddress(macho_file), rel.offset, @tagName(rel.type), target }, ); has_error = true; }, @@ -604,7 +610,7 @@ fn resolveRelocInner( const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow; const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect]; const seg = macho_file.segments.items[seg_id]; - const P = @as(i64, @intCast(self.value)) + @as(i64, @intCast(rel_offset)); + const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset)); const A = rel.addend + rel.getRelocAddend(cpu_arch); const S: i64 = @intCast(rel.getTargetAddress(macho_file)); const G: i64 = @intCast(rel.getGotTargetAddress(macho_file)); @@ -690,7 +696,7 @@ fn resolveRelocInner( .aarch64 => { const disp: i28 = math.cast(i28, S + A - P) orelse blk: { const thunk = self.getThunk(macho_file); - const S_: i64 = @intCast(thunk.getAddress(rel.target)); + const S_: i64 = @intCast(thunk.getTargetAddress(rel.target, macho_file)); break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow; }; var inst = aarch64.Instruction{ @@ -919,7 +925,7 @@ const x86_64 = struct { var err = try macho_file.addErrorWithNotes(2); try err.addMsg(macho_file, "{s}: 0x{x}: 0x{x}: failed to relax relocation of type {s}", .{ self.getName(macho_file), - self.value, + self.getAddress(macho_file), rel.offset, @tagName(rel.type), }); @@ -990,12 +996,11 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra const cpu_arch = macho_file.getTarget().cpu.arch; const relocs = self.getRelocs(macho_file); - const sect = macho_file.sections.items(.header)[self.out_n_sect]; var stream = std.io.fixedBufferStream(code); for (relocs) |rel| { const rel_offset = rel.offset - self.off; - const r_address: i32 = math.cast(i32, self.value + rel_offset - sect.addr) orelse return error.Overflow; + const r_address: i32 = math.cast(i32, self.value + rel_offset) orelse return error.Overflow; const r_symbolnum = r_symbolnum: { const r_symbolnum: u32 = switch (rel.tag) { .local => rel.getTargetAtom(macho_file).out_n_sect + 1, @@ -1062,7 +1067,7 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra .x86_64 => { if (rel.meta.pcrel) { if (rel.tag == .local) { - addend -= @as(i64, @intCast(self.value + rel_offset)); + addend -= @as(i64, @intCast(self.getAddress(macho_file) + rel_offset)); } else { addend += 4; } @@ -1143,10 +1148,10 @@ fn format2( _ = unused_fmt_string; const atom = ctx.atom; const macho_file = ctx.macho_file; - try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{ - atom.atom_index, atom.getName(macho_file), atom.value, - atom.out_n_sect, atom.alignment, atom.size, - atom.thunk_index, + try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d}) : thunk({d})", .{ + atom.atom_index, atom.getName(macho_file), atom.getAddress(macho_file), + atom.out_n_sect, atom.alignment, atom.size, + atom.getRelocs(macho_file).len, atom.thunk_index, }); if (!atom.flags.alive) try writer.writeAll(" : [*]"); if (atom.unwind_records.len > 0) { diff --git a/src/link/MachO/Relocation.zig b/src/link/MachO/Relocation.zig index eff628071f..e350684eac 100644 --- a/src/link/MachO/Relocation.zig +++ b/src/link/MachO/Relocation.zig @@ -22,7 +22,7 @@ pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom { pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 { return switch (rel.tag) { - .local => rel.getTargetAtom(macho_file).value, + .local => rel.getTargetAtom(macho_file).getAddress(macho_file), .@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file), }; } diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig index a61e6f9579..c85918457b 100644 --- a/src/link/MachO/Symbol.zig +++ b/src/link/MachO/Symbol.zig @@ -118,7 +118,7 @@ pub fn getAddress(symbol: Symbol, opts: struct { return symbol.getObjcStubsAddress(macho_file); } } - if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value; + if (symbol.getAtom(macho_file)) |atom| return atom.getAddress(macho_file) + symbol.value; return symbol.value; } @@ -145,7 +145,7 @@ pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { const extra = symbol.getExtra(macho_file).?; const atom = macho_file.getAtom(extra.objc_selrefs).?; assert(atom.flags.alive); - return atom.value; + return atom.getAddress(macho_file); } pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { diff --git a/src/link/MachO/UnwindInfo.zig b/src/link/MachO/UnwindInfo.zig index 8f62cc2f88..e16e03b09a 100644 --- a/src/link/MachO/UnwindInfo.zig +++ b/src/link/MachO/UnwindInfo.zig @@ -490,12 +490,12 @@ pub const Record = struct { pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 { const atom = rec.getAtom(macho_file); - return atom.value + rec.atom_offset; + return atom.getAddress(macho_file) + rec.atom_offset; } pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 { const lsda = rec.getLsdaAtom(macho_file) orelse return 0; - return lsda.value + rec.lsda_offset; + return lsda.getAddress(macho_file) + rec.lsda_offset; } pub fn format( diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 0634cb68f7..df2cac41db 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -154,7 +154,7 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8 @memset(buffer, 0); }, else => { - const file_offset = sect.offset + atom.value - sect.addr; + const file_offset = sect.offset + atom.value; const amt = try macho_file.base.file.?.preadAll(buffer, file_offset); if (amt != buffer.len) return error.InputOutput; }, @@ -196,8 +196,10 @@ pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) void { const atom = macho_file.getAtom(atom_index).?; break :blk nlist.n_value - atom.getInputAddress(macho_file); } else nlist.n_value; + const out_n_sect = if (nlist.sect()) macho_file.getAtom(atom_index).?.out_n_sect else 0; symbol.value = value; symbol.atom = atom_index; + symbol.out_n_sect = out_n_sect; symbol.nlist_idx = nlist_idx; symbol.file = self.index; symbol.flags.weak = nlist.weakDef(); @@ -715,7 +717,7 @@ fn updateDeclCode( } else if (code.len < old_size) { atom.shrink(macho_file); } else if (macho_file.getAtom(atom.next_index) == null) { - const needed_size = atom.value + code.len - sect.addr; + const needed_size = atom.value + code.len; sect.size = needed_size; } } else { @@ -733,7 +735,7 @@ fn updateDeclCode( } if (!sect.isZerofill()) { - const file_offset = sect.offset + atom.value - sect.addr; + const file_offset = sect.offset + atom.value; try macho_file.base.file.?.pwriteAll(code, file_offset); } } @@ -1036,7 +1038,7 @@ fn lowerConst( nlist.n_value = 0; const sect = macho_file.sections.items(.header)[output_section_index]; - const file_offset = sect.offset + atom.value - sect.addr; + const file_offset = sect.offset + atom.value; try macho_file.base.file.?.pwriteAll(code, file_offset); return .{ .ok = sym_index }; @@ -1213,7 +1215,7 @@ fn updateLazySymbol( } const sect = macho_file.sections.items(.header)[output_section_index]; - const file_offset = sect.offset + atom.value - sect.addr; + const file_offset = sect.offset + atom.value; try macho_file.base.file.?.pwriteAll(code, file_offset); } diff --git a/src/link/MachO/eh_frame.zig b/src/link/MachO/eh_frame.zig index 24b3d751a4..8e71960e69 100644 --- a/src/link/MachO/eh_frame.zig +++ b/src/link/MachO/eh_frame.zig @@ -416,7 +416,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { { const offset = fde.out_offset + 8; const saddr = sect.addr + offset; - const taddr = fde.getAtom(macho_file).value; + const taddr = fde.getAtom(macho_file).getAddress(macho_file); std.mem.writeInt( i64, buffer[offset..][0..8], @@ -428,7 +428,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { if (fde.getLsdaAtom(macho_file)) |atom| { const offset = fde.out_offset + fde.lsda_ptr_offset; const saddr = sect.addr + offset; - const taddr = atom.value + fde.lsda_offset; + const taddr = atom.getAddress(macho_file) + fde.lsda_offset; switch (fde.getCie(macho_file).lsda_size.?) { .p32 => std.mem.writeInt( i32, @@ -501,7 +501,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho. { const offset = fde.out_offset + 8; const saddr = sect.addr + offset; - const taddr = fde.getAtom(macho_file).value; + const taddr = fde.getAtom(macho_file).getAddress(macho_file); std.mem.writeInt( i64, code[offset..][0..8], @@ -513,7 +513,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho. if (fde.getLsdaAtom(macho_file)) |atom| { const offset = fde.out_offset + fde.lsda_ptr_offset; const saddr = sect.addr + offset; - const taddr = atom.value + fde.lsda_offset; + const taddr = atom.getAddress(macho_file) + fde.lsda_offset; switch (fde.getCie(macho_file).lsda_size.?) { .p32 => std.mem.writeInt( i32, diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 0b3df180a5..a69423b333 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -12,7 +12,7 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u if (module_obj_path) |path| try positionals.append(.{ .path = path }); - if (positionals.items.len == 1) { + if (macho_file.getZigObject() == null and positionals.items.len == 1) { // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all // debug info segments/sections (this is apparently by design by Apple), we copy // the *only* input file over. @@ -46,50 +46,23 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u try macho_file.addUndefinedGlobals(); try macho_file.resolveSymbols(); - markExports(macho_file); - claimUnresolved(macho_file); + try markExports(macho_file); + try claimUnresolved(macho_file); try initOutputSections(macho_file); try macho_file.sortSections(); try macho_file.addAtomsToSections(); try calcSectionSizes(macho_file); - { - // For relocatable, we only ever need a single segment so create it now. - const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC; - try macho_file.segments.append(gpa, .{ - .cmdsize = @sizeOf(macho.segment_command_64), - .segname = MachO.makeStaticString(""), - .maxprot = prot, - .initprot = prot, - }); - const seg = &macho_file.segments.items[0]; - seg.nsects = @intCast(macho_file.sections.items(.header).len); - seg.cmdsize += seg.nsects * @sizeOf(macho.section_64); - } + try createSegment(macho_file); + try allocateSections(macho_file); + allocateSegment(macho_file); - var off = try allocateSections(macho_file); - - { - // Allocate the single segment. - assert(macho_file.segments.items.len == 1); - const seg = &macho_file.segments.items[0]; - var vmaddr: u64 = 0; - var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); - seg.vmaddr = vmaddr; - seg.fileoff = fileoff; - - for (macho_file.sections.items(.header)) |header| { - vmaddr = header.addr + header.size; - if (!header.isZerofill()) { - fileoff = header.offset + header.size; - } - } - - seg.vmsize = vmaddr - seg.vmaddr; - seg.filesize = fileoff - seg.fileoff; - } - - macho_file.allocateAtoms(); + var off = off: { + const seg = macho_file.segments.items[0]; + const off = math.cast(u32, seg.fileoff + seg.filesize) orelse return error.Overflow; + break :off mem.alignForward(u32, off, @alignOf(macho.relocation_info)); + }; + off = allocateSectionsRelocs(macho_file, off); state_log.debug("{}", .{macho_file.dumpState()}); @@ -109,8 +82,13 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u try writeHeader(macho_file, ncmds, sizeofcmds); } -fn markExports(macho_file: *MachO) void { - for (macho_file.objects.items) |index| { +fn markExports(macho_file: *MachO) error{OutOfMemory}!void { + var objects = try std.ArrayList(File.Index).initCapacity(macho_file.base.comp.gpa, macho_file.objects.items.len + 1); + defer objects.deinit(); + if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); + objects.appendSliceAssumeCapacity(macho_file.objects.items); + + for (objects.items) |index| { for (macho_file.getFile(index).?.getSymbols()) |sym_index| { const sym = macho_file.getSymbol(sym_index); const file = sym.getFile(macho_file) orelse continue; @@ -122,13 +100,22 @@ fn markExports(macho_file: *MachO) void { } } -fn claimUnresolved(macho_file: *MachO) void { - for (macho_file.objects.items) |index| { - const object = macho_file.getFile(index).?.object; +fn claimUnresolved(macho_file: *MachO) error{OutOfMemory}!void { + var objects = try std.ArrayList(File.Index).initCapacity(macho_file.base.comp.gpa, macho_file.objects.items.len + 1); + defer objects.deinit(); + if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); + objects.appendSliceAssumeCapacity(macho_file.objects.items); - for (object.symbols.items, 0..) |sym_index, i| { + for (objects.items) |index| { + const file = macho_file.getFile(index).?; + + for (file.getSymbols(), 0..) |sym_index, i| { const nlist_idx = @as(Symbol.Index, @intCast(i)); - const nlist = object.symtab.items(.nlist)[nlist_idx]; + const nlist = switch (file) { + .object => |x| x.symtab.items(.nlist)[nlist_idx], + .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], + else => unreachable, + }; if (!nlist.ext()) continue; if (!nlist.undf()) continue; @@ -203,6 +190,16 @@ fn calcSectionSizes(macho_file: *MachO) !void { sect.@"align" = 3; sect.nreloc = eh_frame.calcNumRelocs(macho_file); } + + if (macho_file.getZigObject()) |zo| { + for (zo.atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const header = &macho_file.sections.items(.header)[atom.out_n_sect]; + if (!macho_file.isZigSection(atom.out_n_sect)) continue; + header.nreloc += atom.calcNumRelocs(macho_file); + } + } } fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void { @@ -231,30 +228,66 @@ fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void { sect.@"align" = 3; } -fn allocateSections(macho_file: *MachO) !u32 { - var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); - var vmaddr: u64 = 0; +fn allocateSections(macho_file: *MachO) !void { const slice = macho_file.sections.slice(); - for (slice.items(.header)) |*header| { - const alignment = try math.powi(u32, 2, header.@"align"); - vmaddr = mem.alignForward(u64, vmaddr, alignment); - header.addr = vmaddr; - vmaddr += header.size; + const last_index = for (0..slice.items(.header).len) |i| { + if (macho_file.isZigSection(@intCast(i))) break i; + } else slice.items(.header).len; + for (slice.items(.header)[0..last_index]) |*header| { + const alignment = try math.powi(u32, 2, header.@"align"); if (!header.isZerofill()) { - fileoff = mem.alignForward(u32, fileoff, alignment); - header.offset = fileoff; - fileoff += @intCast(header.size); + header.offset = math.cast(u32, macho_file.findFreeSpace(header.size, alignment)) orelse + return error.Overflow; + } + header.addr = macho_file.findFreeSpaceVirtual(header.size, alignment); + } +} + +fn createSegment(macho_file: *MachO) !void { + const gpa = macho_file.base.comp.gpa; + + // For relocatable, we only ever need a single segment so create it now. + const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC; + try macho_file.segments.append(gpa, .{ + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = MachO.makeStaticString(""), + .maxprot = prot, + .initprot = prot, + }); + const seg = &macho_file.segments.items[0]; + seg.nsects = @intCast(macho_file.sections.items(.header).len); + seg.cmdsize += seg.nsects * @sizeOf(macho.section_64); +} + +fn allocateSegment(macho_file: *MachO) void { + // Allocate the single segment. + const seg = &macho_file.segments.items[0]; + var vmaddr: u64 = 0; + var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64); + seg.vmaddr = vmaddr; + seg.fileoff = fileoff; + + for (macho_file.sections.items(.header)) |header| { + vmaddr = @max(vmaddr, header.addr + header.size); + if (!header.isZerofill()) { + fileoff = @max(fileoff, header.offset + header.size); } } + seg.vmsize = vmaddr - seg.vmaddr; + seg.filesize = fileoff - seg.fileoff; +} + +fn allocateSectionsRelocs(macho_file: *MachO, off: u32) u32 { + var fileoff = off; + const slice = macho_file.sections.slice(); for (slice.items(.header)) |*header| { if (header.nreloc == 0) continue; header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info)); fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info); } - return fileoff; } @@ -272,9 +305,10 @@ fn writeAtoms(macho_file: *MachO) !void { const cpu_arch = macho_file.getTarget().cpu.arch; const slice = macho_file.sections.slice(); - for (slice.items(.header), slice.items(.atoms)) |header, atoms| { + for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { if (atoms.items.len == 0) continue; if (header.isZerofill()) continue; + if (macho_file.isZigSection(@intCast(i))) continue; const size = math.cast(usize, header.size) orelse return error.Overflow; const code = try gpa.alloc(u8, size); @@ -288,9 +322,9 @@ fn writeAtoms(macho_file: *MachO) !void { for (atoms.items) |atom_index| { const atom = macho_file.getAtom(atom_index).?; assert(atom.flags.alive); - const off = math.cast(usize, atom.value - header.addr) orelse return error.Overflow; + const off = math.cast(usize, atom.value) orelse return error.Overflow; const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; - try atom.getFile(macho_file).object.getAtomData(atom.*, code[off..][0..atom_size]); + try atom.getData(macho_file, code[off..][0..atom_size]); try atom.writeRelocs(macho_file, code[off..][0..atom_size], &relocs); } @@ -302,6 +336,63 @@ fn writeAtoms(macho_file: *MachO) !void { try macho_file.base.file.?.pwriteAll(code, header.offset); try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); } + + if (macho_file.getZigObject()) |zo| { + // TODO: this is ugly; perhaps we should aggregrate before? + var relocs = std.AutoArrayHashMap(u8, std.ArrayList(macho.relocation_info)).init(gpa); + defer { + for (relocs.values()) |*list| { + list.deinit(); + } + relocs.deinit(); + } + + for (macho_file.sections.items(.header), 0..) |header, n_sect| { + if (header.isZerofill()) continue; + if (!macho_file.isZigSection(@intCast(n_sect))) continue; + const gop = try relocs.getOrPut(@intCast(n_sect)); + if (gop.found_existing) continue; + gop.value_ptr.* = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); + } + + for (zo.atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const header = macho_file.sections.items(.header)[atom.out_n_sect]; + if (header.isZerofill()) continue; + if (!macho_file.isZigSection(atom.out_n_sect)) continue; + if (atom.getRelocs(macho_file).len == 0) continue; + const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; + const code = try gpa.alloc(u8, atom_size); + defer gpa.free(code); + atom.getData(macho_file, code) catch |err| switch (err) { + error.InputOutput => { + try macho_file.reportUnexpectedError("fetching code for '{s}' failed", .{ + atom.getName(macho_file), + }); + return error.FlushFailure; + }, + else => |e| { + try macho_file.reportUnexpectedError("unexpected error while fetching code for '{s}': {s}", .{ + atom.getName(macho_file), + @errorName(e), + }); + return error.FlushFailure; + }, + }; + const file_offset = header.offset + atom.value; + const rels = relocs.getPtr(atom.out_n_sect).?; + try atom.writeRelocs(macho_file, code, rels); + try macho_file.base.file.?.pwriteAll(code, file_offset); + } + + for (relocs.keys(), relocs.values()) |sect_id, rels| { + const header = macho_file.sections.items(.header)[sect_id]; + assert(rels.items.len == header.nreloc); + mem.sort(macho.relocation_info, rels.items, {}, sortReloc); + try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(rels.items), header.reloff); + } + } } fn writeCompactUnwind(macho_file: *MachO) !void { @@ -492,6 +583,7 @@ const assert = std.debug.assert; const eh_frame = @import("eh_frame.zig"); const link = @import("../../link.zig"); const load_commands = @import("load_commands.zig"); +const log = std.log.scoped(.link); const macho = std.macho; const math = std.math; const mem = std.mem; @@ -501,5 +593,6 @@ const trace = @import("../../tracy.zig").trace; const Atom = @import("Atom.zig"); const Compilation = @import("../../Compilation.zig"); +const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Symbol = @import("Symbol.zig"); diff --git a/src/link/MachO/thunks.zig b/src/link/MachO/thunks.zig index 2e9602f8d8..cd9ea20c86 100644 --- a/src/link/MachO/thunks.zig +++ b/src/link/MachO/thunks.zig @@ -66,7 +66,7 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { if (atom.out_n_sect != target.out_n_sect) return false; const target_atom = target.getAtom(macho_file).?; if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; - const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off)); + const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off)); const taddr: i64 = @intCast(rel.getTargetAddress(macho_file)); _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; return true; @@ -85,14 +85,19 @@ pub const Thunk = struct { return thunk.symbols.keys().len * trampoline_size; } - pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 { - return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size; + pub fn getAddress(thunk: Thunk, macho_file: *MachO) u64 { + const header = macho_file.sections.items(.header)[thunk.out_n_sect]; + return header.addr + thunk.value; + } + + pub fn getTargetAddress(thunk: Thunk, sym_index: Symbol.Index, macho_file: *MachO) u64 { + return thunk.getAddress(macho_file) + thunk.symbols.getIndex(sym_index).? * trampoline_size; } pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { for (thunk.symbols.keys(), 0..) |sym_index, i| { const sym = macho_file.getSymbol(sym_index); - const saddr = thunk.value + i * trampoline_size; + const saddr = thunk.getAddress(macho_file) + i * trampoline_size; const taddr = sym.getAddress(.{}, macho_file); const pages = try Relocation.calcNumberOfPages(saddr, taddr); try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); diff --git a/test/link/link.zig b/test/link/link.zig index f227831971..2678b2cf84 100644 --- a/test/link/link.zig +++ b/test/link/link.zig @@ -27,14 +27,23 @@ pub const Options = struct { optimize: std.builtin.OptimizeMode = .Debug, use_llvm: bool = true, use_lld: bool = false, + strip: ?bool = null, }; pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step { const target = opts.target.result.zigTriple(b.allocator) catch @panic("OOM"); const optimize = @tagName(opts.optimize); const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm"; - const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}", .{ - prefix, target, optimize, use_llvm, + const use_lld = if (opts.use_lld) "lld" else "no-lld"; + if (opts.strip) |strip| { + const s = if (strip) "strip" else "no-strip"; + const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}-{s}", .{ + prefix, target, optimize, use_llvm, use_lld, s, + }) catch @panic("OOM"); + return b.step(name, ""); + } + const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}", .{ + prefix, target, optimize, use_llvm, use_lld, }) catch @panic("OOM"); return b.step(name, ""); } @@ -87,7 +96,7 @@ fn addCompileStep( break :rsf b.addWriteFiles().add("a.zig", bytes); }, .pic = overlay.pic, - .strip = overlay.strip, + .strip = if (base.strip) |s| s else overlay.strip, }, .use_llvm = base.use_llvm, .use_lld = base.use_lld, diff --git a/test/link/macho.zig b/test/link/macho.zig index 1feb509af3..7bc83b2276 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -15,6 +15,11 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step { .os_tag = .macos, }); + // Exercise linker with self-hosted backend (no LLVM) + macho_step.dependOn(testHelloZig(b, .{ .use_llvm = false, .target = x86_64_target })); + macho_step.dependOn(testRelocatableZig(b, .{ .use_llvm = false, .strip = true, .target = x86_64_target })); + + // Exercise linker with LLVM backend macho_step.dependOn(testDeadStrip(b, .{ .target = default_target })); macho_step.dependOn(testEmptyObject(b, .{ .target = default_target })); macho_step.dependOn(testEmptyZig(b, .{ .target = default_target })); @@ -1234,7 +1239,14 @@ fn testRelocatableZig(b: *Build, opts: Options) *Step { const run = addRunArtifact(exe); run.addCheck(.{ .expect_stderr_match = b.dupe("incrFoo=1") }); run.addCheck(.{ .expect_stderr_match = b.dupe("decrFoo=0") }); - run.addCheck(.{ .expect_stderr_match = b.dupe("panic: Oh no!") }); + if (opts.use_llvm) { + // TODO: enable this once self-hosted can print panics and stack traces + run.addCheck(.{ .expect_stderr_match = b.dupe("panic: Oh no!") }); + } + if (builtin.os.tag == .macos) { + const signal: u32 = if (opts.use_llvm) std.os.darwin.SIG.ABRT else std.os.darwin.SIG.TRAP; + run.addCheck(.{ .expect_term = .{ .Signal = signal } }); + } test_step.dependOn(&run.step); return test_step; @@ -2307,6 +2319,7 @@ fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step { return link.addTestStep(b, "macho-" ++ prefix, opts); } +const builtin = @import("builtin"); const addAsmSourceBytes = link.addAsmSourceBytes; const addCSourceBytes = link.addCSourceBytes; const addRunArtifact = link.addRunArtifact;