diff --git a/build.zig b/build.zig index 1ba1c8a3..e3a9567e 100644 --- a/build.zig +++ b/build.zig @@ -5,7 +5,7 @@ const tests = @import("test/test.zig"); const Allocator = std.mem.Allocator; -pub fn build(b: *std.Build.Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const mode = b.standardOptimizeOption(.{}); @@ -27,12 +27,12 @@ pub fn build(b: *std.Build.Builder) void { .target = target, .optimize = mode, }); - exe.addModule("yaml", yaml.module("yaml")); - exe.addModule("dis_x86_64", dis_x86_64.module("dis_x86_64")); + exe.root_module.addImport("yaml", yaml.module("yaml")); + exe.root_module.addImport("dis_x86_64", dis_x86_64.module("dis_x86_64")); exe.linkLibC(); const exe_opts = b.addOptions(); - exe.addOptions("build_options", exe_opts); + exe.root_module.addOptions("build_options", exe_opts); exe_opts.addOption(bool, "enable_logging", enable_logging); exe_opts.addOption(bool, "enable_tracy", enable_tracy != null); @@ -43,17 +43,17 @@ pub fn build(b: *std.Build.Builder) void { ) catch unreachable; // On mingw, we need to opt into windows 7+ to get some features required by tracy. - const tracy_c_flags: []const []const u8 = if (target.isWindows() and target.getAbi() == .gnu) + const tracy_c_flags: []const []const u8 = if (target.result.os.tag == .windows and target.result.abi == .gnu) &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined", "-D_WIN32_WINNT=0x601" } else &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }; exe.addIncludePath(.{ .cwd_relative = tracy_path }); exe.addCSourceFile(.{ .file = .{ .cwd_relative = client_cpp }, .flags = tracy_c_flags }); - exe.linkSystemLibraryName("c++"); - exe.strip = false; + exe.root_module.linkSystemLibrary("c++", .{ .use_pkg_config = .no }); + exe.root_module.strip = false; - if (target.isWindows()) { + if (target.result.os.tag == .windows) { exe.linkSystemLibrary("dbghelp"); exe.linkSystemLibrary("ws2_32"); } @@ -78,11 +78,11 @@ pub fn build(b: *std.Build.Builder) void { .optimize = mode, }); const unit_tests_opts = b.addOptions(); - unit_tests.addOptions("build_options", unit_tests_opts); + unit_tests.root_module.addOptions("build_options", unit_tests_opts); unit_tests_opts.addOption(bool, "enable_logging", enable_logging); unit_tests_opts.addOption(bool, "enable_tracy", enable_tracy != null); - unit_tests.addModule("yaml", yaml.module("yaml")); - unit_tests.addModule("dis_x86_64", dis_x86_64.module("dis_x86_64")); + unit_tests.root_module.addImport("yaml", yaml.module("yaml")); + unit_tests.root_module.addImport("dis_x86_64", dis_x86_64.module("dis_x86_64")); unit_tests.linkLibC(); const test_step = b.step("test", "Run tests"); @@ -96,8 +96,8 @@ pub fn build(b: *std.Build.Builder) void { } fn addSymlinks( - builder: *std.Build.Builder, - install: *std.Build.InstallArtifactStep, + builder: *std.Build, + install: *std.Build.Step.InstallArtifact, names: []const []const u8, ) *CreateSymlinksStep { const step = CreateSymlinksStep.create(builder, install, names); @@ -109,13 +109,13 @@ const CreateSymlinksStep = struct { pub const base_id = .custom; step: std.Build.Step, - builder: *std.Build.Builder, - install: *std.Build.InstallArtifactStep, + builder: *std.Build, + install: *std.Build.Step.InstallArtifact, targets: []const []const u8, pub fn create( - builder: *std.Build.Builder, - install: *std.Build.InstallArtifactStep, + builder: *std.Build, + install: *std.Build.Step.InstallArtifact, targets: []const []const u8, ) *CreateSymlinksStep { const self = builder.allocator.create(CreateSymlinksStep) catch unreachable; @@ -135,7 +135,7 @@ const CreateSymlinksStep = struct { fn make(step: *std.Build.Step, prog_node: *std.Progress.Node) anyerror!void { const self = @fieldParentPtr(CreateSymlinksStep, "step", step); - const install_path = self.install.artifact.getOutputSource().getPath(self.builder); + const install_path = self.install.artifact.getEmittedBin().getPath(self.builder); const rel_source = fs.path.basename(install_path); var node = prog_node.start("creating symlinks", self.targets.len); diff --git a/build.zig.zon b/build.zig.zon index 622d1bb8..1e22afbc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,12 +4,12 @@ .dependencies = .{ .@"zig-yaml" = .{ - .url = "https://github.com/kubkon/zig-yaml/archive/1ed4925bed911b73a189526a6ad82bd8c5c2079a.tar.gz", - .hash = "1220f56d186377820d7ad62ee03987acdd53bc24da83e8f6dff571bc7343f789f69a", + .url = "https://github.com/kubkon/zig-yaml/archive/953bf8e9a10386eb3756d3fc722df634d0d634a9.tar.gz", + .hash = "1220b174728272a3e4b38c27a37bd76e9749fad1668c24538cd8110353e87306360b", }, .@"zig-dis-x86_64" = .{ - .url = "https://github.com/kubkon/zig-dis-x86_64/archive/a9155631990aa6d56fa06fddef304cabb94a0681.tar.gz", - .hash = "1220a4d63ba372f6b5a0fc262f863572dc119727b469f6ccf527ad91790e353bb0f0", + .url = "https://github.com/kubkon/zig-dis-x86_64/archive/752655a8feca210880abb8f1be5acad8e7f4961a.tar.gz", + .hash = "1220591498890a10351d6cadc52cf07a594baeabd7eef7fe3e7a7f43960a3398edea", }, }, diff --git a/src/MachO.zig b/src/MachO.zig index ecf599bf..21336adf 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -82,7 +82,7 @@ pub fn openPath(allocator: Allocator, options: Options, thread_pool: *ThreadPool const file = try options.emit.directory.createFile(options.emit.sub_path, .{ .truncate = true, .read = true, - .mode = if (builtin.os.tag == .windows) 0 else 0o777, + .mode = if (builtin.os.tag == .windows) 0 else if (options.relocatable) 0o666 else 0o777, }); errdefer file.close(); @@ -348,6 +348,9 @@ pub fn flush(self: *MachO) !void { try self.addUndefinedGlobals(); try self.resolveSymbols(); + + if (self.options.relocatable) return relocatable.flush(self); + try self.resolveSyntheticSymbols(); try self.convertTentativeDefinitions(); @@ -388,13 +391,22 @@ pub fn flush(self: *MachO) !void { try self.writeUnwindInfo(); try self.finalizeDyldInfoSections(); try self.writeSyntheticSections(); - try self.writeDyldInfoSections(); - try self.writeFunctionStarts(); - try self.writeDataInCode(); + + var off = math.cast(u32, self.getLinkeditSegment().fileoff) orelse return error.Overflow; + off = try self.writeDyldInfoSections(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeFunctionStarts(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeDataInCode(off); try self.calcSymtabSize(); - try self.writeSymtab(); - try self.writeIndsymtab(); - try self.writeStrtab(); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeSymtab(off); + off = mem.alignForward(u32, off, @alignOf(u32)); + off = try self.writeIndsymtab(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeStrtab(off); + + self.getLinkeditSegment().filesize = off - self.getLinkeditSegment().fileoff; var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: { // Preallocate space for the code signature. @@ -582,14 +594,12 @@ fn validateCpuArch(self: *MachO, index: File.Index) void { macho.CPU_TYPE_X86_64 => .x86_64, else => unreachable, }; - if (self.options.cpu_arch) |self_cpu_arch| { - if (self_cpu_arch != cpu_arch) { - return self.base.fatal("{}: invalid architecture '{s}', expected '{s}'", .{ - file.fmtPath(), - @tagName(cpu_arch), - @tagName(self_cpu_arch), - }); - } + if (self.options.cpu_arch.? != cpu_arch) { + return self.base.fatal("{}: invalid architecture '{s}', expected '{s}'", .{ + file.fmtPath(), + @tagName(cpu_arch), + @tagName(self.options.cpu_arch.?), + }); } } @@ -740,11 +750,13 @@ fn parseArchive(self: *MachO, arena: Allocator, obj: LinkObject) !bool { object.parse(self) catch |err| switch (err) { error.ParseFailed => { has_parse_error = true; - continue; + // TODO see below + // continue; }, else => |e| return e, }; try self.objects.append(gpa, index); + // TODO this should come before reporting any parse errors self.validateCpuArch(index); self.validatePlatform(index); @@ -1458,14 +1470,14 @@ fn initSyntheticSections(self: *MachO) !void { } const needs_unwind_info = for (self.objects.items) |index| { - if (self.getFile(index).?.object.has_unwind) break true; + if (self.getFile(index).?.object.compact_unwind_sect_index != null) break true; } else false; if (needs_unwind_info) { self.unwind_info_sect_index = try self.addSection("__TEXT", "__unwind_info", .{}); } const needs_eh_frame = for (self.objects.items) |index| { - if (self.getFile(index).?.object.has_eh_frame) break true; + if (self.getFile(index).?.object.eh_frame_sect_index != null) break true; } else false; if (needs_eh_frame) { assert(needs_unwind_info); @@ -1553,6 +1565,7 @@ fn getSectionRank(self: *MachO, sect_index: u8) u8 { else => { if (mem.eql(u8, "__unwind_info", header.sectName())) break :blk 0xe; + if (mem.eql(u8, "__compact_unwind", header.sectName())) break :blk 0xe; if (mem.eql(u8, "__eh_frame", header.sectName())) break :blk 0xf; break :blk 0x3; }, @@ -1561,7 +1574,7 @@ fn getSectionRank(self: *MachO, sect_index: u8) u8 { return (@as(u8, @intCast(segment_rank)) << 4) + section_rank; } -fn sortSections(self: *MachO) !void { +pub fn sortSections(self: *MachO) !void { const Entry = struct { index: u8, @@ -1626,7 +1639,7 @@ fn sortSections(self: *MachO) !void { } } -fn addAtomsToSections(self: *MachO) !void { +pub fn addAtomsToSections(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1931,7 +1944,7 @@ fn allocateSegments(self: *MachO) void { } } -fn allocateAtoms(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; @@ -2232,21 +2245,7 @@ fn writeSyntheticSections(self: *MachO) !void { } } -fn getNextLinkeditOffset(self: *MachO, alignment: u64) !u64 { - const seg = self.getLinkeditSegment(); - const off = seg.fileoff + seg.filesize; - const aligned = mem.alignForward(u64, off, alignment); - const padding = aligned - off; - - if (padding > 0) { - try self.base.file.pwriteAll(&[1]u8{0}, aligned); - seg.filesize += padding; - } - - return aligned; -} - -fn writeDyldInfoSections(self: *MachO) !void { +fn writeDyldInfoSections(self: *MachO, off: u32) !u32 { const tracy = trace(@src()); defer tracy.end(); @@ -2291,28 +2290,27 @@ fn writeDyldInfoSections(self: *MachO) !void { try stream.seekTo(cmd.export_off); try self.export_trie.write(writer); - const off = try self.getNextLinkeditOffset(@alignOf(u64)); - cmd.rebase_off += @intCast(off); - cmd.bind_off += @intCast(off); - cmd.weak_bind_off += @intCast(off); - cmd.lazy_bind_off += @intCast(off); - cmd.export_off += @intCast(off); + cmd.rebase_off += off; + cmd.bind_off += off; + cmd.weak_bind_off += off; + cmd.lazy_bind_off += off; + cmd.export_off += off; try self.base.file.pwriteAll(buffer, off); - self.getLinkeditSegment().filesize += needed_size; + return off + needed_size; } -fn writeFunctionStarts(self: *MachO) !void { - const off = try self.getNextLinkeditOffset(@alignOf(u64)); +fn writeFunctionStarts(self: *MachO, off: u32) !u32 { + // TODO actually write it out const cmd = &self.function_starts_cmd; - cmd.dataoff = @intCast(off); + cmd.dataoff = off; + return off; } -fn writeDataInCode(self: *MachO) !void { +fn writeDataInCode(self: *MachO, off: u32) !u32 { const cmd = &self.data_in_code_cmd; - const off = try self.getNextLinkeditOffset(@alignOf(u64)); - cmd.dataoff = @intCast(off); + cmd.dataoff = off; const base = self.getTextSegment().vmaddr; @@ -2350,15 +2348,15 @@ fn writeDataInCode(self: *MachO) !void { } } - const needed_size = dices.items.len * @sizeOf(macho.data_in_code_entry); - cmd.datasize = @intCast(needed_size); + const needed_size = math.cast(u32, dices.items.len * @sizeOf(macho.data_in_code_entry)) orelse return error.Overflow; + cmd.datasize = needed_size; try self.base.file.pwriteAll(mem.sliceAsBytes(dices.items), cmd.dataoff); - self.getLinkeditSegment().filesize += needed_size; + return off + needed_size; } -fn calcSymtabSize(self: *MachO) !void { +pub fn calcSymtabSize(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.allocator; @@ -2420,13 +2418,12 @@ fn calcSymtabSize(self: *MachO) !void { } } -fn writeSymtab(self: *MachO) !void { +pub fn writeSymtab(self: *MachO, off: u32) !u32 { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.allocator; const cmd = &self.symtab_cmd; - const off = try self.getNextLinkeditOffset(@alignOf(u64)); - cmd.symoff = @intCast(off); + cmd.symoff = off; try self.symtab.resize(gpa, cmd.nsyms); try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1); @@ -2445,14 +2442,13 @@ fn writeSymtab(self: *MachO) !void { try self.base.file.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff); - self.getLinkeditSegment().filesize += cmd.nsyms * @sizeOf(macho.nlist_64); + return off + cmd.nsyms * @sizeOf(macho.nlist_64); } -fn writeIndsymtab(self: *MachO) !void { +fn writeIndsymtab(self: *MachO, off: u32) !u32 { const gpa = self.base.allocator; const cmd = &self.dysymtab_cmd; - const off = try self.getNextLinkeditOffset(@alignOf(u32)); - cmd.indirectsymoff = @intCast(off); + cmd.indirectsymoff = off; cmd.nindirectsyms = self.indsymtab.nsyms(self); const needed_size = cmd.nindirectsyms * @sizeOf(u32); @@ -2463,15 +2459,14 @@ fn writeIndsymtab(self: *MachO) !void { try self.base.file.pwriteAll(buffer.items, cmd.indirectsymoff); assert(buffer.items.len == needed_size); - self.getLinkeditSegment().filesize += needed_size; + return off + needed_size; } -fn writeStrtab(self: *MachO) !void { +pub fn writeStrtab(self: *MachO, off: u32) !u32 { const cmd = &self.symtab_cmd; - const off = try self.getNextLinkeditOffset(@alignOf(u64)); - cmd.stroff = @intCast(off); + cmd.stroff = off; try self.base.file.pwriteAll(self.strtab.items, cmd.stroff); - self.getLinkeditSegment().filesize += cmd.strsize; + return off + cmd.strsize; } fn writeLoadCommands(self: *MachO) !struct { usize, usize, usize } { @@ -2736,7 +2731,7 @@ pub fn addSection( const gpa = self.base.allocator; const index = @as(u8, @intCast(try self.sections.addOne(gpa))); self.sections.set(index, .{ - .segment_id = undefined, // Segments will be created automatically later down the pipeline. + .segment_id = 0, // Segments will be created automatically later down the pipeline. .header = .{ .sectname = makeStaticString(sectname), .segname = makeStaticString(segname), @@ -3156,11 +3151,12 @@ const macho = std.macho; const math = std.math; const mem = std.mem; const meta = std.meta; -const thunks = @import("MachO/thunks.zig"); -const trace = @import("tracy.zig").trace; +const relocatable = @import("MachO/relocatable.zig"); const synthetic = @import("MachO/synthetic.zig"); const state_log = std.log.scoped(.state); const std = @import("std"); +const thunks = @import("MachO/thunks.zig"); +const trace = @import("tracy.zig").trace; const Allocator = mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; diff --git a/src/MachO/Atom.zig b/src/MachO/Atom.zig index 473e20f4..d734faa4 100644 --- a/src/MachO/Atom.zig +++ b/src/MachO/Atom.zig @@ -153,7 +153,7 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { macho.S_REGULAR, }; } - break :blk .{ segname, sectname, macho.S_REGULAR }; + break :blk .{ segname, sectname, sect.flags }; }, else => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, @@ -669,6 +669,148 @@ fn encode(insts: []const Instruction, code: []u8) !void { } } +pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 { + switch (macho_file.options.cpu_arch.?) { + .aarch64 => { + var nreloc: u32 = 0; + for (self.getRelocs(macho_file)) |rel| { + nreloc += 1; + switch (rel.type) { + .page, .pageoff => if (rel.addend > 0) { + nreloc += 1; + }, + else => {}, + } + } + return nreloc; + }, + .x86_64 => return @intCast(self.getRelocs(macho_file).len), + else => unreachable, + } +} + +pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const cpu_arch = macho_file.options.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_symbolnum = r_symbolnum: { + const r_symbolnum: u32 = switch (rel.tag) { + .local => rel.getTargetAtom(macho_file).out_n_sect + 1, + .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?, + }; + break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow; + }; + const r_extern = rel.tag == .@"extern"; + var addend = rel.addend + rel.getRelocAddend(cpu_arch); + if (rel.tag == .local) { + const target: i64 = @intCast(rel.getTargetAddress(macho_file)); + addend += target; + } + + try stream.seekTo(rel_offset); + + switch (cpu_arch) { + .aarch64 => { + if (rel.type == .unsigned) switch (rel.meta.length) { + 0, 1 => unreachable, + 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), + 3 => try stream.writer().writeInt(i64, addend, .little), + } else if (addend > 0) { + buffer.appendAssumeCapacity(.{ + .r_address = r_address, + .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow), + .r_pcrel = 0, + .r_length = 2, + .r_extern = 0, + .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), + }); + } + + const r_type: macho.reloc_type_arm64 = switch (rel.type) { + .page => .ARM64_RELOC_PAGE21, + .pageoff => .ARM64_RELOC_PAGEOFF12, + .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21, + .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12, + .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21, + .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, + .branch => .ARM64_RELOC_BRANCH26, + .got => .ARM64_RELOC_POINTER_TO_GOT, + .subtractor => .ARM64_RELOC_SUBTRACTOR, + .unsigned => .ARM64_RELOC_UNSIGNED, + + .signed, + .signed1, + .signed2, + .signed4, + .got_load, + .tlv, + => unreachable, + }; + buffer.appendAssumeCapacity(.{ + .r_address = r_address, + .r_symbolnum = r_symbolnum, + .r_pcrel = @intFromBool(rel.meta.pcrel), + .r_extern = @intFromBool(r_extern), + .r_length = rel.meta.length, + .r_type = @intFromEnum(r_type), + }); + }, + .x86_64 => { + if (rel.meta.pcrel) { + if (rel.tag == .local) { + addend -= @as(i64, @intCast(self.value + rel_offset)); + } else { + addend += 4; + } + } + switch (rel.meta.length) { + 0, 1 => unreachable, + 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), + 3 => try stream.writer().writeInt(i64, addend, .little), + } + + const r_type: macho.reloc_type_x86_64 = switch (rel.type) { + .signed => .X86_64_RELOC_SIGNED, + .signed1 => .X86_64_RELOC_SIGNED_1, + .signed2 => .X86_64_RELOC_SIGNED_2, + .signed4 => .X86_64_RELOC_SIGNED_4, + .got_load => .X86_64_RELOC_GOT_LOAD, + .tlv => .X86_64_RELOC_TLV, + .branch => .X86_64_RELOC_BRANCH, + .got => .X86_64_RELOC_GOT, + .subtractor => .X86_64_RELOC_SUBTRACTOR, + .unsigned => .X86_64_RELOC_UNSIGNED, + + .page, + .pageoff, + .got_load_page, + .got_load_pageoff, + .tlvp_page, + .tlvp_pageoff, + => unreachable, + }; + buffer.appendAssumeCapacity(.{ + .r_address = r_address, + .r_symbolnum = r_symbolnum, + .r_pcrel = @intFromBool(rel.meta.pcrel), + .r_extern = @intFromBool(r_extern), + .r_length = rel.meta.length, + .r_type = @intFromEnum(r_type), + }); + }, + else => unreachable, + } + } +} + pub fn format( atom: Atom, comptime unused_fmt_string: []const u8, diff --git a/src/MachO/Object.zig b/src/MachO/Object.zig index b99e81f4..deb17ba8 100644 --- a/src/MachO/Object.zig +++ b/src/MachO/Object.zig @@ -14,6 +14,7 @@ atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, platform: ?MachO.Options.Platform = null, dwarf_info: ?DwarfInfo = null, +stab_files: std.ArrayListUnmanaged(StabFile) = .{}, eh_frame_sect_index: ?u8 = null, compact_unwind_sect_index: ?u8 = null, @@ -22,8 +23,6 @@ fdes: std.ArrayListUnmanaged(Fde) = .{}, eh_frame_data: std.ArrayListUnmanaged(u8) = .{}, unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{}, -has_unwind: bool = false, -has_eh_frame: bool = false, alive: bool = true, hidden: bool = false, num_rebase_relocs: u32 = 0, @@ -46,6 +45,10 @@ pub fn deinit(self: *Object, allocator: Allocator) void { self.eh_frame_data.deinit(allocator); self.unwind_records.deinit(allocator); if (self.dwarf_info) |*dw| dw.deinit(allocator); + for (self.stab_files.items) |*sf| { + sf.stabs.deinit(allocator); + } + self.stab_files.deinit(allocator); } pub fn parse(self: *Object, macho_file: *MachO) !void { @@ -120,7 +123,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { } mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan); - if (self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) { + if (self.hasSubsections()) { try self.initSubsections(nlists.items, macho_file); } else { try self.initSections(nlists.items, macho_file); @@ -131,6 +134,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { try self.sortAtoms(macho_file); try self.initSymbols(macho_file); + try self.initSymbolStabs(nlists.items, macho_file); try self.initRelocs(macho_file); if (self.eh_frame_sect_index) |index| { @@ -142,7 +146,18 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { } self.initPlatform(); - try self.initDwarfInfo(gpa); + try self.initDwarfInfo(macho_file); + + for (self.atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index).?; + const isec = atom.getInputSection(macho_file); + if (mem.eql(u8, isec.sectName(), "__eh_frame") or + mem.eql(u8, isec.sectName(), "__compact_unwind") or + isec.attrs() & macho.S_ATTR_DEBUG != 0) + { + atom.flags.alive = false; + } + } } inline fn isLiteral(sect: macho.section_64) bool { @@ -163,9 +178,6 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { const gpa = macho_file.base.allocator; const slice = self.sections.slice(); for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| { - if (sect.attrs() & macho.S_ATTR_DEBUG != 0) continue; - if (mem.eql(u8, sect.sectName(), "__eh_frame")) continue; - if (mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; if (isLiteral(sect)) continue; const nlist_start = for (nlists, 0..) |nlist, i| { @@ -237,9 +249,6 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void { try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); for (slice.items(.header), 0..) |sect, n_sect| { - if (sect.attrs() & macho.S_ATTR_DEBUG != 0) continue; - if (mem.eql(u8, sect.sectName(), "__eh_frame")) continue; - if (mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; if (isLiteral(sect)) continue; const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); @@ -316,9 +325,6 @@ fn initLiteralSections(self: *Object, macho_file: *MachO) !void { try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); for (slice.items(.header), 0..) |sect, n_sect| { - if (sect.attrs() & macho.S_ATTR_DEBUG != 0) continue; - if (mem.eql(u8, sect.sectName(), "__eh_frame")) continue; - if (mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; if (!isLiteral(sect)) continue; const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); @@ -374,13 +380,15 @@ fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index { } } - const sub = subsections.items[min]; - const sub_addr = sect.addr + sub.off; - const sub_size = if (min + 1 < subsections.items.len) - subsections.items[min + 1].off - sub.off - else - sect.size - sub.off; - if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom; + if (min < subsections.items.len) { + const sub = subsections.items[min]; + const sub_addr = sect.addr + sub.off; + const sub_size = if (min + 1 < subsections.items.len) + subsections.items[min + 1].off - sub.off + else + sect.size - sub.off; + if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom; + } return null; } @@ -390,10 +398,6 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { defer tracy.end(); for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| { if (!nlist.stab() and nlist.sect()) { - const sect = self.sections.items(.header)[nlist.n_sect - 1]; - if (sect.attrs() & macho.S_ATTR_DEBUG != 0) continue; - if (mem.eql(u8, sect.sectName(), "__eh_frame")) continue; - if (mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| { atom.* = atom_index; } else { @@ -452,6 +456,86 @@ fn initSymbols(self: *Object, macho_file: *MachO) !void { } } +fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const SymbolLookup = struct { + ctx: *const Object, + entries: @TypeOf(nlists), + + fn find(fs: @This(), addr: u64) ?Symbol.Index { + // TODO binary search since we have the list sorted + for (fs.entries) |nlist| { + if (nlist.nlist.n_value == addr) return fs.ctx.symbols.items[nlist.idx]; + } + return null; + } + }; + + const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| { + if (nlist.stab()) break @intCast(i); + } else @intCast(self.symtab.items(.nlist).len); + const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| { + if (!nlist.stab()) break @intCast(i); + } else @intCast(self.symtab.items(.nlist).len); + + if (start == end) return; + + const gpa = macho_file.base.allocator; + const syms = self.symtab.items(.nlist); + const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists }; + + var i: u32 = start; + while (i < end) : (i += 1) { + const open = syms[i]; + if (open.n_type != macho.N_SO) { + macho_file.base.fatal("{}: unexpected symbol stab type 0x{x} as the first entry", .{ + self.fmtPath(), + open.n_type, + }); + return error.ParseFailed; + } + + while (i < end and syms[i].n_type == macho.N_SO and syms[i].n_sect != 0) : (i += 1) {} + + var sf: StabFile = .{ .comp_dir = i }; + // TODO validate + i += 3; + + while (i < end and syms[i].n_type != macho.N_SO) : (i += 1) { + const nlist = syms[i]; + var stab: StabFile.Stab = .{}; + switch (nlist.n_type) { + macho.N_BNSYM => { + stab.tag = .func; + stab.symbol = sym_lookup.find(nlist.n_value); + // TODO validate + i += 3; + }, + macho.N_GSYM => { + stab.tag = .global; + stab.symbol = macho_file.getGlobalByName(self.getString(nlist.n_strx)); + }, + macho.N_STSYM => { + stab.tag = .static; + stab.symbol = sym_lookup.find(nlist.n_value); + }, + else => { + macho_file.base.fatal("{}: unhandled symbol stab type 0x{x}", .{ + self.fmtPath(), + nlist.n_type, + }); + return error.ParseFailed; + }, + } + try sf.stabs.append(gpa, stab); + } + + try self.stab_files.append(gpa, sf); + } +} + fn sortAtoms(self: *Object, macho_file: *MachO) !void { const lessThanAtom = struct { fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool { @@ -469,6 +553,9 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void { for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| { if (sect.nreloc == 0) continue; + // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit + // debug symbol stabs in the relocatable. This made me curious why that is. For now, + // I shall comply, but I wanna compare with dsymutil. if (sect.attrs() & macho.S_ATTR_DEBUG != 0 and !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; @@ -516,7 +603,7 @@ fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { for (relocs.items, 0..) |rel, i| { switch (rel.type) { .unsigned => { - assert(rel.meta.length == 3 and rel.meta.has_subtractor); // TODO error + assert((rel.meta.length == 2 or rel.meta.length == 3) and rel.meta.has_subtractor); // TODO error const S: i64 = switch (rel.tag) { .local => rel.meta.symbolnum, .@"extern" => @intCast(nlists[rel.meta.symbolnum].n_value), @@ -529,7 +616,11 @@ fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value), }; }; - mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little); + switch (rel.meta.length) { + 0, 1 => unreachable, + 2 => mem.writeInt(u32, self.eh_frame_data.items[rel.offset..][0..4], @bitCast(@as(i32, @truncate(S + A - SUB))), .little), + 3 => mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little), + } }, else => {}, } @@ -588,21 +679,27 @@ fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { } } -fn findSymbol(self: Object, addr: u64) ?Symbol.Index { - for (self.symbols.items, 0..) |sym_index, i| { - const nlist = self.symtab.items(.nlist)[i]; - if (nlist.ext() and nlist.n_value == addr) return sym_index; - } - return null; -} - fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); + + const SymbolLookup = struct { + ctx: *const Object, + + fn find(fs: @This(), addr: u64) ?Symbol.Index { + for (fs.ctx.symbols.items, 0..) |sym_index, i| { + const nlist = fs.ctx.symtab.items(.nlist)[i]; + if (nlist.ext() and nlist.n_value == addr) return sym_index; + } + return null; + } + }; + const gpa = macho_file.base.allocator; const data = self.getSectionData(sect_id); const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); const recs = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data.ptr))[0..nrecs]; + const sym_lookup = SymbolLookup{ .ctx = self }; try self.unwind_records.resize(gpa, nrecs); @@ -653,7 +750,7 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { .@"extern" => { out.personality = rel.target; }, - .local => if (self.findSymbol(rec.personalityFunction)) |sym_index| { + .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| { out.personality = sym_index; } else { macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{ @@ -683,6 +780,35 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { } } + if (!macho_file.options.relocatable) try self.synthesiseNullUnwindRecords(macho_file); + + const sortFn = struct { + fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool { + const lhs = ctx.getUnwindRecord(lhs_index); + const rhs = ctx.getUnwindRecord(rhs_index); + const lhsa = lhs.getAtom(ctx); + const rhsa = rhs.getAtom(ctx); + return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset; + } + }.sortFn; + mem.sort(UnwindInfo.Record.Index, self.unwind_records.items, macho_file, sortFn); + + // Associate unwind records to atoms + var next_cu: u32 = 0; + while (next_cu < self.unwind_records.items.len) { + const start = next_cu; + const rec_index = self.unwind_records.items[start]; + const rec = macho_file.getUnwindRecord(rec_index); + while (next_cu < self.unwind_records.items.len and + macho_file.getUnwindRecord(self.unwind_records.items[next_cu]).atom == rec.atom) : (next_cu += 1) + {} + + const atom = rec.getAtom(macho_file); + atom.unwind_records = .{ .pos = start, .len = next_cu - start }; + } +} + +fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void { // Synthesise missing unwind records. // The logic here is as follows: // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead @@ -692,11 +818,13 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null }; + const gpa = macho_file.base.allocator; var superposition = std.AutoArrayHashMap(u64, Superposition).init(gpa); defer superposition.deinit(); const slice = self.symtab.slice(); for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| { + if (nlist.stab()) continue; if (!nlist.sect()) continue; const sect = self.sections.items(.header)[nlist.n_sect - 1]; if (sect.isCode()) { @@ -723,8 +851,6 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { } for (superposition.keys(), superposition.values()) |addr, meta| { - self.has_unwind = true; - if (meta.fde) |fde_index| { const fde = &self.fdes.items[fde_index]; @@ -736,7 +862,6 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { } else { // Tie FDE to unwind record rec.fde = fde_index; - self.has_eh_frame = true; } } else { // Synthesise new unwind info record @@ -755,7 +880,6 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF), else => unreachable, } - self.has_eh_frame = true; } } else if (meta.cu == null and meta.fde == null) { // Create a null record @@ -765,35 +889,10 @@ fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { try self.unwind_records.append(gpa, rec_index); rec.length = @intCast(meta.size); rec.atom = meta.atom; - rec.atom_offset = @intCast(addr - atom.getInputSection(macho_file).addr - atom.off); + rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file)); rec.file = self.index; } } - - const sortFn = struct { - fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool { - const lhs = ctx.getUnwindRecord(lhs_index); - const rhs = ctx.getUnwindRecord(rhs_index); - const lhsa = lhs.getAtom(ctx); - const rhsa = rhs.getAtom(ctx); - return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset; - } - }.sortFn; - mem.sort(UnwindInfo.Record.Index, self.unwind_records.items, macho_file, sortFn); - - // Associate unwind records to atoms - var next_cu: u32 = 0; - while (next_cu < self.unwind_records.items.len) { - const start = next_cu; - const rec_index = self.unwind_records.items[start]; - const rec = macho_file.getUnwindRecord(rec_index); - while (next_cu < self.unwind_records.items.len and - macho_file.getUnwindRecord(self.unwind_records.items[next_cu]).atom == rec.atom) : (next_cu += 1) - {} - - const atom = rec.getAtom(macho_file); - atom.unwind_records = .{ .pos = start, .len = next_cu - start }; - } } fn initPlatform(self: *Object) void { @@ -818,10 +917,12 @@ fn initPlatform(self: *Object) void { /// and record that so that we can emit symbol stabs. /// TODO in the future, we want parse debug info and debug line sections so that /// we can provide nice error locations to the user. -fn initDwarfInfo(self: *Object, allocator: Allocator) !void { +fn initDwarfInfo(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); + const gpa = macho_file.base.allocator; + var debug_info_index: ?usize = null; var debug_abbrev_index: ?usize = null; var debug_str_index: ?usize = null; @@ -840,7 +941,10 @@ fn initDwarfInfo(self: *Object, allocator: Allocator) !void { .debug_abbrev = self.getSectionData(@intCast(debug_abbrev_index.?)), .debug_str = if (debug_str_index) |index| self.getSectionData(@intCast(index)) else "", }; - dwarf_info.init(allocator) catch return; // TODO flag an error + dwarf_info.init(gpa) catch { + macho_file.base.fatal("{}: invalid __DWARF info found", .{self.fmtPath()}); + return error.ParseFailed; + }; self.dwarf_info = dwarf_info; } @@ -1022,9 +1126,11 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { const file = sym.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; - if (sym.getNlist(macho_file).stab()) continue; + if (sym.isSymbolStab(macho_file)) continue; const name = sym.getName(macho_file); - if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue; + // TODO in -r mode, we actually want to merge symbol names and emit only one + // work it out when emitting relocs + if (name.len > 0 and (name[0] == 'L' or name[0] == 'l') and !macho_file.options.relocatable) continue; sym.flags.output_symtab = true; if (sym.isLocal()) { try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); @@ -1044,34 +1150,61 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { } pub fn calcStabsSize(self: *Object, macho_file: *MachO) void { - // TODO handle multiple CUs - const dw = self.dwarf_info.?; - const cu = dw.compile_units.items[0]; - const comp_dir = cu.getCompileDir(dw) orelse return; - const tu_name = cu.getSourceFile(dw) orelse return; - - self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO - self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir - self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name - - if (self.archive) |path| { - self.output_symtab_ctx.strsize += @as(u32, @intCast(path.len + 1 + self.path.len + 1 + 1)); - } else { - self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1)); - } - - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; - if (file.getIndex() != self.index) continue; - if (!sym.flags.output_symtab) continue; - const sect = macho_file.sections.items(.header)[sym.out_n_sect]; - if (sect.isCode()) { - self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM - } else if (sym.visibility == .global) { - self.output_symtab_ctx.nstabs += 1; // N_GSYM + if (self.dwarf_info) |dw| { + // TODO handle multiple CUs + const cu = dw.compile_units.items[0]; + const comp_dir = cu.getCompileDir(dw) orelse return; + const tu_name = cu.getSourceFile(dw) orelse return; + + self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO + self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir + self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name + + if (self.archive) |path| { + self.output_symtab_ctx.strsize += @as(u32, @intCast(path.len + 1 + self.path.len + 1 + 1)); } else { - self.output_symtab_ctx.nstabs += 1; // N_STSYM + self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1)); + } + + for (self.symbols.items) |sym_index| { + const sym = macho_file.getSymbol(sym_index); + const file = sym.getFile(macho_file) orelse continue; + if (file.getIndex() != self.index) continue; + if (!sym.flags.output_symtab) continue; + if (macho_file.options.relocatable) { + const name = sym.getName(macho_file); + if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue; + } + const sect = macho_file.sections.items(.header)[sym.out_n_sect]; + if (sect.isCode()) { + self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM + } else if (sym.visibility == .global) { + self.output_symtab_ctx.nstabs += 1; // N_GSYM + } else { + self.output_symtab_ctx.nstabs += 1; // N_STSYM + } + } + } else { + assert(self.hasSymbolStabs()); + + for (self.stab_files.items) |sf| { + self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO + self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getCompDir(self).len + 1)); // comp_dir + self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getTuName(self).len + 1)); // tu_name + self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getOsoPath(self).len + 1)); // path + + for (sf.stabs.items) |stab| { + const sym = stab.getSymbol(macho_file) orelse continue; + const file = sym.getFile(macho_file).?; + if (file.getIndex() != self.index) continue; + if (!sym.flags.output_symtab) continue; + const nstabs: u32 = switch (stab.tag) { + .func => 4, // N_BNSYM, N_FUN, N_FUN, N_ENSYM + .global => 1, // N_GSYM + .static => 1, // N_STSYM + }; + self.output_symtab_ctx.nstabs += nstabs; + } } } } @@ -1096,7 +1229,7 @@ pub fn writeSymtab(self: Object, macho_file: *MachO) void { if (!macho_file.options.strip and self.hasDebugInfo()) self.writeStabs(macho_file); } -pub fn writeStabs(self: Object, macho_file: *MachO) void { +pub fn writeStabs(self: *const Object, macho_file: *MachO) void { const writeFuncStab = struct { inline fn writeFuncStab( n_strx: u32, @@ -1137,107 +1270,206 @@ pub fn writeStabs(self: Object, macho_file: *MachO) void { } }.writeFuncStab; - // TODO handle multiple CUs - const dw = self.dwarf_info.?; - const cu = dw.compile_units.items[0]; - const comp_dir = cu.getCompileDir(dw) orelse return; - const tu_name = cu.getSourceFile(dw) orelse return; - var index = self.output_symtab_ctx.istab; - // Open scope - // N_SO comp_dir - var n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); - macho_file.strtab.appendSliceAssumeCapacity(comp_dir); - macho_file.strtab.appendAssumeCapacity(0); - macho_file.symtab.items[index] = .{ - .n_strx = n_strx, - .n_type = macho.N_SO, - .n_sect = 0, - .n_desc = 0, - .n_value = 0, - }; - index += 1; - // N_SO tu_name - n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); - macho_file.strtab.appendSliceAssumeCapacity(tu_name); - macho_file.strtab.appendAssumeCapacity(0); - macho_file.symtab.items[index] = .{ - .n_strx = n_strx, - .n_type = macho.N_SO, - .n_sect = 0, - .n_desc = 0, - .n_value = 0, - }; - index += 1; - // N_OSO path - n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); - if (self.archive) |path| { - macho_file.strtab.appendSliceAssumeCapacity(path); - macho_file.strtab.appendAssumeCapacity('('); - macho_file.strtab.appendSliceAssumeCapacity(self.path); - macho_file.strtab.appendAssumeCapacity(')'); + if (self.dwarf_info) |dw| { + // TODO handle multiple CUs + const cu = dw.compile_units.items[0]; + const comp_dir = cu.getCompileDir(dw) orelse return; + const tu_name = cu.getSourceFile(dw) orelse return; + + // Open scope + // N_SO comp_dir + var n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + macho_file.strtab.appendSliceAssumeCapacity(comp_dir); macho_file.strtab.appendAssumeCapacity(0); - } else { - macho_file.strtab.appendSliceAssumeCapacity(self.path); + macho_file.symtab.items[index] = .{ + .n_strx = n_strx, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }; + index += 1; + // N_SO tu_name + n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + macho_file.strtab.appendSliceAssumeCapacity(tu_name); macho_file.strtab.appendAssumeCapacity(0); - } - macho_file.symtab.items[index] = .{ - .n_strx = n_strx, - .n_type = macho.N_OSO, - .n_sect = 0, - .n_desc = 1, - .n_value = self.mtime, - }; - index += 1; + macho_file.symtab.items[index] = .{ + .n_strx = n_strx, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }; + index += 1; + // N_OSO path + n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + if (self.archive) |path| { + macho_file.strtab.appendSliceAssumeCapacity(path); + macho_file.strtab.appendAssumeCapacity('('); + macho_file.strtab.appendSliceAssumeCapacity(self.path); + macho_file.strtab.appendAssumeCapacity(')'); + macho_file.strtab.appendAssumeCapacity(0); + } else { + macho_file.strtab.appendSliceAssumeCapacity(self.path); + macho_file.strtab.appendAssumeCapacity(0); + } + macho_file.symtab.items[index] = .{ + .n_strx = n_strx, + .n_type = macho.N_OSO, + .n_sect = 0, + .n_desc = 1, + .n_value = self.mtime, + }; + index += 1; + + for (self.symbols.items) |sym_index| { + const sym = macho_file.getSymbol(sym_index); + const file = sym.getFile(macho_file) orelse continue; + if (file.getIndex() != self.index) continue; + if (!sym.flags.output_symtab) continue; + if (macho_file.options.relocatable) { + const name = sym.getName(macho_file); + if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue; + } + const sect = macho_file.sections.items(.header)[sym.out_n_sect]; + const sym_n_strx = n_strx: { + const symtab_index = sym.getOutputSymtabIndex(macho_file).?; + const osym = macho_file.symtab.items[symtab_index]; + break :n_strx osym.n_strx; + }; + const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; + const sym_n_value = sym.getAddress(.{}, macho_file); + const sym_size = sym.getSize(macho_file); + if (sect.isCode()) { + writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); + index += 4; + } else if (sym.visibility == .global) { + macho_file.symtab.items[index] = .{ + .n_strx = sym_n_strx, + .n_type = macho.N_GSYM, + .n_sect = sym_n_sect, + .n_desc = 0, + .n_value = 0, + }; + index += 1; + } else { + macho_file.symtab.items[index] = .{ + .n_strx = sym_n_strx, + .n_type = macho.N_STSYM, + .n_sect = sym_n_sect, + .n_desc = 0, + .n_value = sym_n_value, + }; + index += 1; + } + } - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; - if (file.getIndex() != self.index) continue; - if (!sym.flags.output_symtab) continue; - const sect = macho_file.sections.items(.header)[sym.out_n_sect]; - const sym_n_strx = n_strx: { - const symtab_index = sym.getOutputSymtabIndex(macho_file).?; - const osym = macho_file.symtab.items[symtab_index]; - break :n_strx osym.n_strx; + // Close scope + // N_SO + macho_file.symtab.items[index] = .{ + .n_strx = 0, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, }; - const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; - const sym_n_value = sym.getAddress(.{}, macho_file); - const sym_size = sym.getSize(macho_file); - if (sect.isCode()) { - writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); - index += 4; - } else if (sym.visibility == .global) { + } else { + assert(self.hasSymbolStabs()); + + for (self.stab_files.items) |sf| { + // Open scope + // N_SO comp_dir + var n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + macho_file.strtab.appendSliceAssumeCapacity(sf.getCompDir(self)); + macho_file.strtab.appendAssumeCapacity(0); macho_file.symtab.items[index] = .{ - .n_strx = sym_n_strx, - .n_type = macho.N_GSYM, - .n_sect = sym_n_sect, + .n_strx = n_strx, + .n_type = macho.N_SO, + .n_sect = 0, .n_desc = 0, .n_value = 0, }; index += 1; - } else { + // N_SO tu_name + n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + macho_file.strtab.appendSliceAssumeCapacity(sf.getTuName(self)); + macho_file.strtab.appendAssumeCapacity(0); macho_file.symtab.items[index] = .{ - .n_strx = sym_n_strx, - .n_type = macho.N_STSYM, - .n_sect = sym_n_sect, + .n_strx = n_strx, + .n_type = macho.N_SO, + .n_sect = 0, .n_desc = 0, - .n_value = sym_n_value, + .n_value = 0, + }; + index += 1; + // N_OSO path + n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); + macho_file.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self)); + macho_file.strtab.appendAssumeCapacity(0); + macho_file.symtab.items[index] = .{ + .n_strx = n_strx, + .n_type = macho.N_OSO, + .n_sect = 0, + .n_desc = 1, + .n_value = sf.getOsoModTime(self), + }; + index += 1; + + for (sf.stabs.items) |stab| { + const sym = stab.getSymbol(macho_file) orelse continue; + const file = sym.getFile(macho_file).?; + if (file.getIndex() != self.index) continue; + if (!sym.flags.output_symtab) continue; + const sym_n_strx = n_strx: { + const symtab_index = sym.getOutputSymtabIndex(macho_file).?; + const osym = macho_file.symtab.items[symtab_index]; + break :n_strx osym.n_strx; + }; + const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; + const sym_n_value = sym.getAddress(.{}, macho_file); + const sym_size = sym.getSize(macho_file); + switch (stab.tag) { + .func => { + writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); + index += 4; + }, + .global => { + macho_file.symtab.items[index] = .{ + .n_strx = sym_n_strx, + .n_type = macho.N_GSYM, + .n_sect = sym_n_sect, + .n_desc = 0, + .n_value = 0, + }; + index += 1; + }, + .static => { + macho_file.symtab.items[index] = .{ + .n_strx = sym_n_strx, + .n_type = macho.N_STSYM, + .n_sect = sym_n_sect, + .n_desc = 0, + .n_value = sym_n_value, + }; + index += 1; + }, + } + } + + // Close scope + // N_SO + macho_file.symtab.items[index] = .{ + .n_strx = 0, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, }; index += 1; } } - - // Close scope - // N_SO - macho_file.symtab.items[index] = .{ - .n_strx = 0, - .n_type = macho.N_SO, - .n_sect = 0, - .n_desc = 0, - .n_value = 0, - }; } fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand { @@ -1264,8 +1496,14 @@ fn getString(self: Object, off: u32) [:0]const u8 { /// TODO handle multiple CUs pub fn hasDebugInfo(self: Object) bool { - const dw = self.dwarf_info orelse return false; - return dw.compile_units.items.len > 0; + if (self.dwarf_info) |dw| { + return dw.compile_units.items.len > 0; + } + return self.hasSymbolStabs(); +} + +fn hasSymbolStabs(self: Object) bool { + return self.stab_files.items.len > 0; } pub fn hasObjc(self: Object) bool { @@ -1291,6 +1529,10 @@ pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry { return dice; } +pub inline fn hasSubsections(self: Object) bool { + return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; +} + pub fn asFile(self: *Object) File { return .{ .object = self }; } @@ -1463,6 +1705,40 @@ const Nlist = struct { atom: Atom.Index, }; +const StabFile = struct { + comp_dir: u32, + stabs: std.ArrayListUnmanaged(Stab) = .{}, + + fn getCompDir(sf: StabFile, object: *const Object) [:0]const u8 { + const nlist = object.symtab.items(.nlist)[sf.comp_dir]; + return object.getString(nlist.n_strx); + } + + fn getTuName(sf: StabFile, object: *const Object) [:0]const u8 { + const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1]; + return object.getString(nlist.n_strx); + } + + fn getOsoPath(sf: StabFile, object: *const Object) [:0]const u8 { + const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2]; + return object.getString(nlist.n_strx); + } + + fn getOsoModTime(sf: StabFile, object: *const Object) u64 { + const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2]; + return nlist.n_value; + } + + const Stab = struct { + tag: enum { func, global, static } = .func, + symbol: ?Symbol.Index = null, + + fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol { + return if (stab.symbol) |s| macho_file.getSymbol(s) else null; + } + }; +}; + const x86_64 = struct { fn parseRelocs( self: *const Object, @@ -1512,7 +1788,7 @@ const x86_64 = struct { }); return error.ParseFailed; }; - addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputSection(macho_file).addr)); + addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file))); break :blk target; } else self.symbols.items[rel.r_symbolnum]; @@ -1678,7 +1954,7 @@ const aarch64 = struct { const target = if (rel.r_extern == 0) blk: { const nsect = rel.r_symbolnum - 1; const taddr: i64 = if (rel.r_pcrel == 1) - @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4 + @as(i64, @intCast(sect.addr)) + rel.r_address + addend else addend; const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse { @@ -1687,7 +1963,7 @@ const aarch64 = struct { }); return error.ParseFailed; }; - addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputSection(macho_file).addr)); + addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file))); break :blk target; } else self.symbols.items[rel.r_symbolnum]; diff --git a/src/MachO/Options.zig b/src/MachO/Options.zig index 55f72954..48e5b085 100644 --- a/src/MachO/Options.zig +++ b/src/MachO/Options.zig @@ -61,6 +61,7 @@ const usage = \\-platform_version [platform] [min_version] [sdk_version] \\ Sets the platform, oldest supported version of that platform and \\ the SDK it was built against + \\-r Create a relocatable object file \\-reexport-l[name] Link against library and re-export it for the clients \\ -reexport_library [name] \\-rpath [path] Specify runtime path @@ -91,6 +92,7 @@ const cmd = "ld64.zld"; emit: Zld.Emit, dylib: bool = false, +relocatable: bool = false, dynamic: bool = false, cpu_arch: ?std.Target.Cpu.Arch = null, platform: ?Platform = null, @@ -238,6 +240,8 @@ pub fn parse(arena: Allocator, args: []const []const u8, ctx: anytype) !Options try force_undefined_symbols.put(name, {}); } else if (p.flag1("S")) { opts.strip = true; + } else if (p.flag1("r")) { + opts.relocatable = true; } else if (p.flag1("all_load")) { opts.all_load = true; } else if (p.arg1("force_load")) |path| { diff --git a/src/MachO/Symbol.zig b/src/MachO/Symbol.zig index d1d2e638..35e53534 100644 --- a/src/MachO/Symbol.zig +++ b/src/MachO/Symbol.zig @@ -32,6 +32,14 @@ pub fn isLocal(symbol: Symbol) bool { return !(symbol.flags.import or symbol.flags.@"export"); } +pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool { + const file = symbol.getFile(macho_file) orelse return false; + return switch (file) { + .object => symbol.getNlist(macho_file).stab(), + else => false, + }; +} + pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool { const name = symbol.getName(macho_file); return std.mem.indexOf(u8, name, "$tlv$init") != null; @@ -143,6 +151,7 @@ pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 { if (!symbol.flags.output_symtab) return null; + assert(!symbol.isSymbolStab(macho_file)); const file = symbol.getFile(macho_file).?; const symtab_ctx = switch (file) { inline else => |x| x.output_symtab_ctx, @@ -290,6 +299,7 @@ fn format2( if (symbol.flags.import) buf[1] = 'I'; try writer.print(" : {s}", .{&buf}); if (symbol.flags.weak) try writer.writeAll(" : weak"); + if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab"); switch (file) { .internal => |x| try writer.print(" : internal({d})", .{x.index}), .object => |x| try writer.print(" : object({d})", .{x.index}), diff --git a/src/MachO/eh_frame.zig b/src/MachO/eh_frame.zig index 30ec557a..6ca7a5cd 100644 --- a/src/MachO/eh_frame.zig +++ b/src/MachO/eh_frame.zig @@ -139,6 +139,7 @@ pub const Fde = struct { atom_offset: u32 = 0, lsda: Atom.Index = 0, lsda_offset: u32 = 0, + lsda_ptr_offset: u32 = 0, file: File.Index = 0, alive: bool = true, @@ -186,15 +187,15 @@ pub const Fde = struct { var creader = std.io.countingReader(stream.reader()); const reader = creader.reader(); _ = try leb.readULEB128(u64, reader); // augmentation length - const offset = creader.bytes_read; + fde.lsda_ptr_offset = @intCast(creader.bytes_read + 24); const lsda_ptr = switch (lsda_size) { .p32 => try reader.readInt(i32, .little), .p64 => try reader.readInt(i64, .little), }; - const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + 24 + offset + fde.offset)) + lsda_ptr); + const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr); fde.lsda = object.findAtom(lsda_addr) orelse { macho_file.base.fatal("{}: {s},{s}: 0x{x}: invalid LSDA reference in FDE", .{ - object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + offset + 24, + object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset, }); return error.ParseFailed; }; @@ -319,7 +320,6 @@ pub fn calcSize(macho_file: *MachO) !u32 { for (macho_file.objects.items) |index| { const object = macho_file.getFile(index).?.object; - if (!object.has_eh_frame) continue; outer: for (object.cies.items) |*cie| { for (cies.items) |other| { @@ -341,7 +341,6 @@ pub fn calcSize(macho_file: *MachO) !u32 { for (macho_file.objects.items) |index| { const object = macho_file.getFile(index).?.object; - if (!object.has_eh_frame) continue; for (object.fdes.items) |*fde| { if (!fde.alive) continue; fde.out_offset = offset; @@ -352,6 +351,25 @@ pub fn calcSize(macho_file: *MachO) !u32 { return offset; } +pub fn calcNumRelocs(macho_file: *MachO) u32 { + const tracy = trace(@src()); + defer tracy.end(); + + var nreloc: u32 = 0; + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.cies.items) |cie| { + if (!cie.alive) continue; + if (cie.getPersonality(macho_file)) |_| { + nreloc += 1; // personality + } + } + } + + return nreloc; +} + pub fn write(macho_file: *MachO, buffer: []u8) void { const tracy = trace(@src()); defer tracy.end(); @@ -431,6 +449,91 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { } } +pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void { + const tracy = trace(@src()); + defer tracy.end(); + + const cpu_arch = macho_file.options.cpu_arch.?; + const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; + const addend: i64 = switch (cpu_arch) { + .x86_64 => 4, + else => 0, + }; + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.cies.items) |cie| { + if (!cie.alive) continue; + + @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file)); + + if (cie.getPersonality(macho_file)) |sym| { + const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow; + const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; + relocs.appendAssumeCapacity(.{ + .r_address = r_address, + .r_symbolnum = r_symbolnum, + .r_length = 2, + .r_extern = 1, + .r_pcrel = 1, + .r_type = switch (cpu_arch) { + .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT), + .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT), + else => unreachable, + }, + }); + } + } + } + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + + @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file)); + + { + const offset = fde.out_offset + 4; + const value = offset - fde.getCie(macho_file).out_offset; + std.mem.writeInt(u32, code[offset..][0..4], value, .little); + } + + { + const offset = fde.out_offset + 8; + const saddr = sect.addr + offset; + const taddr = fde.getAtom(macho_file).value; + std.mem.writeInt( + i64, + code[offset..][0..8], + @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), + .little, + ); + } + + 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; + switch (fde.getCie(macho_file).lsda_size.?) { + .p32 => std.mem.writeInt( + i32, + code[offset..][0..4], + @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), + .little, + ), + .p64 => std.mem.writeInt( + i64, + code[offset..][0..8], + @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), + .little, + ), + } + } + } + } +} + pub const EH_PE = struct { pub const absptr = 0x00; pub const uleb128 = 0x01; @@ -453,6 +556,8 @@ pub const EH_PE = struct { const assert = std.debug.assert; const leb = std.leb; const macho = std.macho; +const math = std.math; +const mem = std.mem; const std = @import("std"); const trace = @import("../tracy.zig").trace; diff --git a/src/MachO/load_commands.zig b/src/MachO/load_commands.zig index cb39c060..725bd429 100644 --- a/src/MachO/load_commands.zig +++ b/src/MachO/load_commands.zig @@ -101,6 +101,38 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 { return @as(u32, @intCast(sizeofcmds)); } +pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 { + const options = &macho_file.options; + var sizeofcmds: u64 = 0; + + // LC_SEGMENT_64 + { + assert(macho_file.segments.items.len == 1); + sizeofcmds += @sizeOf(macho.segment_command_64); + const seg = macho_file.segments.items[0]; + sizeofcmds += seg.nsects * @sizeOf(macho.section_64); + } + + // LC_DATA_IN_CODE + sizeofcmds += @sizeOf(macho.linkedit_data_command); + // LC_SYMTAB + sizeofcmds += @sizeOf(macho.symtab_command); + // LC_DYSYMTAB + sizeofcmds += @sizeOf(macho.dysymtab_command); + + if (options.platform) |platform| { + if (platform.isBuildVersionCompatible()) { + // LC_BUILD_VERSION + sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); + } else { + // LC_VERSION_MIN_* + sizeofcmds += @sizeOf(macho.version_min_command); + } + } + + return @as(u32, @intCast(sizeofcmds)); +} + pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 { const options = &macho_file.options; var padding: u32 = calcLoadCommandsSize(macho_file, false) + (options.headerpad orelse 0); diff --git a/src/MachO/relocatable.zig b/src/MachO/relocatable.zig new file mode 100644 index 00000000..17caa800 --- /dev/null +++ b/src/MachO/relocatable.zig @@ -0,0 +1,457 @@ +pub fn flush(macho_file: *MachO) !void { + markExports(macho_file); + 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(macho_file.base.allocator, .{ + .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); + } + + 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(); + + state_log.debug("{}", .{macho_file.dumpState()}); + + try macho_file.calcSymtabSize(); + try writeAtoms(macho_file); + try writeCompactUnwind(macho_file); + try writeEhFrame(macho_file); + + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try writeDataInCode(macho_file, off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try macho_file.writeSymtab(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try macho_file.writeStrtab(off); + + const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); + try writeHeader(macho_file, ncmds, sizeofcmds); +} + +fn markExports(macho_file: *MachO) void { + for (macho_file.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; + if (sym.visibility != .global) continue; + if (file.getIndex() == index) { + sym.flags.@"export" = true; + } + } + } +} + +fn claimUnresolved(macho_file: *MachO) void { + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + + for (object.symbols.items, 0..) |sym_index, i| { + const nlist_idx = @as(Symbol.Index, @intCast(i)); + const nlist = object.symtab.items(.nlist)[nlist_idx]; + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + + const sym = macho_file.getSymbol(sym_index); + if (sym.getFile(macho_file) != null) continue; + + sym.value = 0; + sym.atom = 0; + sym.nlist_idx = nlist_idx; + sym.file = index; + sym.flags.weak_ref = nlist.weakRef(); + sym.flags.import = true; + sym.visibility = .global; + } + } +} + +fn initOutputSections(macho_file: *MachO) !void { + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); + } + } + + const needs_unwind_info = for (macho_file.objects.items) |index| { + if (macho_file.getFile(index).?.object.compact_unwind_sect_index != null) break true; + } else false; + if (needs_unwind_info) { + macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{ + .flags = macho.S_ATTR_DEBUG, + }); + } + + const needs_eh_frame = for (macho_file.objects.items) |index| { + if (macho_file.getFile(index).?.object.eh_frame_sect_index != null) break true; + } else false; + if (needs_eh_frame) { + assert(needs_unwind_info); + macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{}); + } +} + +fn calcSectionSizes(macho_file: *MachO) !void { + const slice = macho_file.sections.slice(); + for (slice.items(.header), slice.items(.atoms)) |*header, atoms| { + if (atoms.items.len == 0) continue; + for (atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index).?; + const atom_alignment = try math.powi(u32, 2, atom.alignment); + const offset = mem.alignForward(u64, header.size, atom_alignment); + const padding = offset - header.size; + atom.value = offset; + header.size += padding + atom.size; + header.@"align" = @max(header.@"align", atom.alignment); + header.nreloc += atom.calcNumRelocs(macho_file); + } + } + + if (macho_file.unwind_info_sect_index) |index| { + calcCompactUnwindSize(macho_file, index); + } + + if (macho_file.eh_frame_sect_index) |index| { + const sect = &macho_file.sections.items(.header)[index]; + sect.size = try eh_frame.calcSize(macho_file); + sect.@"align" = 3; + sect.nreloc = eh_frame.calcNumRelocs(macho_file); + } +} + +fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void { + var size: u32 = 0; + var nreloc: u32 = 0; + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.unwind_records.items) |irec| { + const rec = macho_file.getUnwindRecord(irec); + if (!rec.alive) continue; + size += @sizeOf(macho.compact_unwind_entry); + nreloc += 1; + if (rec.getPersonality(macho_file)) |_| { + nreloc += 1; + } + if (rec.getLsdaAtom(macho_file)) |_| { + nreloc += 1; + } + } + } + + const sect = &macho_file.sections.items(.header)[sect_index]; + sect.size = size; + sect.nreloc = nreloc; + 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; + 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; + + if (!header.isZerofill()) { + fileoff = mem.alignForward(u32, fileoff, alignment); + header.offset = fileoff; + fileoff += @intCast(header.size); + } + } + + 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; +} + +fn writeAtoms(macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.allocator; + const cpu_arch = macho_file.options.cpu_arch.?; + const slice = macho_file.sections.slice(); + + for (slice.items(.header), slice.items(.atoms)) |header, atoms| { + if (atoms.items.len == 0) continue; + if (header.isZerofill()) continue; + + const code = try gpa.alloc(u8, header.size); + defer gpa.free(code); + const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; + @memset(code, padding_byte); + + var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); + defer relocs.deinit(); + + for (atoms.items) |atom_index| { + const atom = macho_file.getAtom(atom_index).?; + assert(atom.flags.alive); + const off = atom.value - header.addr; + @memcpy(code[off..][0..atom.size], atom.getCode(macho_file)); + try atom.writeRelocs(macho_file, code[off..][0..atom.size], &relocs); + } + + assert(relocs.items.len == header.nreloc); + + // TODO scattered writes? + try macho_file.base.file.pwriteAll(code, header.offset); + try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); + } +} + +fn writeCompactUnwind(macho_file: *MachO) !void { + const sect_index = macho_file.unwind_info_sect_index orelse return; + const gpa = macho_file.base.allocator; + const header = macho_file.sections.items(.header)[sect_index]; + + const nrecs = @divExact(header.size, @sizeOf(macho.compact_unwind_entry)); + var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs); + defer entries.deinit(); + + var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); + defer relocs.deinit(); + + const addReloc = struct { + fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info { + return .{ + .r_address = offset, + .r_symbolnum = 0, + .r_pcrel = 0, + .r_length = 3, + .r_extern = 0, + .r_type = switch (cpu_arch) { + .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED), + .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED), + else => unreachable, + }, + }; + } + }.addReloc; + + var offset: i32 = 0; + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + for (object.unwind_records.items) |irec| { + const rec = macho_file.getUnwindRecord(irec); + if (!rec.alive) continue; + + var out: macho.compact_unwind_entry = .{ + .rangeStart = 0, + .rangeLength = rec.length, + .compactUnwindEncoding = rec.enc.enc, + .personalityFunction = 0, + .lsda = 0, + }; + + { + // Function address + const atom = rec.getAtom(macho_file); + const addr = rec.getAtomAddress(macho_file); + out.rangeStart = addr; + var reloc = addReloc(offset, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = atom.out_n_sect + 1; + relocs.appendAssumeCapacity(reloc); + } + + // Personality function + if (rec.getPersonality(macho_file)) |sym| { + const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; + var reloc = addReloc(offset + 16, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = r_symbolnum; + reloc.r_extern = 1; + relocs.appendAssumeCapacity(reloc); + } + + // LSDA address + if (rec.getLsdaAtom(macho_file)) |atom| { + const addr = rec.getLsdaAddress(macho_file); + out.lsda = addr; + var reloc = addReloc(offset + 24, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = atom.out_n_sect + 1; + relocs.appendAssumeCapacity(reloc); + } + + entries.appendAssumeCapacity(out); + offset += @sizeOf(macho.compact_unwind_entry); + } + } + + assert(entries.items.len == nrecs); + assert(relocs.items.len == header.nreloc); + + // TODO scattered writes? + try macho_file.base.file.pwriteAll(mem.sliceAsBytes(entries.items), header.offset); + try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); +} + +fn writeEhFrame(macho_file: *MachO) !void { + const sect_index = macho_file.eh_frame_sect_index orelse return; + const gpa = macho_file.base.allocator; + const header = macho_file.sections.items(.header)[sect_index]; + + const code = try gpa.alloc(u8, header.size); + defer gpa.free(code); + + var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); + defer relocs.deinit(); + + try eh_frame.writeRelocs(macho_file, code, &relocs); + assert(relocs.items.len == header.nreloc); + + // TODO scattered writes? + try macho_file.base.file.pwriteAll(code, header.offset); + try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); +} + +fn writeDataInCode(macho_file: *MachO, off: u32) !u32 { + // TODO actually write it out + const cmd = &macho_file.data_in_code_cmd; + cmd.dataoff = off; + return off; +} + +fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } { + const gpa = macho_file.base.allocator; + const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file); + const buffer = try gpa.alloc(u8, needed_size); + defer gpa.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + var cwriter = std.io.countingWriter(stream.writer()); + const writer = cwriter.writer(); + + var ncmds: usize = 0; + + // Segment and section load commands + { + assert(macho_file.segments.items.len == 1); + const seg = macho_file.segments.items[0]; + try writer.writeStruct(seg); + for (macho_file.sections.items(.header)) |header| { + try writer.writeStruct(header); + } + ncmds += 1; + } + + try writer.writeStruct(macho_file.data_in_code_cmd); + ncmds += 1; + try writer.writeStruct(macho_file.symtab_cmd); + ncmds += 1; + try writer.writeStruct(macho_file.dysymtab_cmd); + ncmds += 1; + + if (macho_file.options.platform) |platform| { + if (platform.isBuildVersionCompatible()) { + try load_commands.writeBuildVersionLC(platform, macho_file.options.sdk_version, writer); + ncmds += 1; + } else { + try load_commands.writeVersionMinLC(platform, macho_file.options.sdk_version, writer); + ncmds += 1; + } + } + + assert(cwriter.bytes_written == needed_size); + + try macho_file.base.file.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); + + return .{ ncmds, buffer.len }; +} + +fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { + var header: macho.mach_header_64 = .{}; + header.filetype = macho.MH_OBJECT; + + const subsections_via_symbols = for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + if (object.hasSubsections()) break true; + } else false; + if (subsections_via_symbols) { + header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS; + } + + switch (macho_file.options.cpu_arch.?) { + .aarch64 => { + header.cputype = macho.CPU_TYPE_ARM64; + header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL; + }, + .x86_64 => { + header.cputype = macho.CPU_TYPE_X86_64; + header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL; + }, + else => {}, + } + + if (macho_file.has_tlv) { + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + } + if (macho_file.binds_to_weak) { + header.flags |= macho.MH_BINDS_TO_WEAK; + } + if (macho_file.weak_defines) { + header.flags |= macho.MH_WEAK_DEFINES; + } + + header.ncmds = @intCast(ncmds); + header.sizeofcmds = @intCast(sizeofcmds); + + try macho_file.base.file.pwriteAll(mem.asBytes(&header), 0); +} + +const assert = std.debug.assert; +const eh_frame = @import("eh_frame.zig"); +const load_commands = @import("load_commands.zig"); +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const state_log = std.log.scoped(.state); +const std = @import("std"); +const trace = @import("../tracy.zig").trace; + +const Atom = @import("Atom.zig"); +const MachO = @import("../MachO.zig"); +const Symbol = @import("Symbol.zig"); diff --git a/src/main.zig b/src/main.zig index 46845160..161de5a7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -112,6 +112,7 @@ pub fn main() !void { error.UndefinedSymbols, error.RelocError, error.ResolveFailed, + error.Unimplemented, => { zld.reportWarnings(); zld.reportErrors(); diff --git a/test/elf.zig b/test/elf.zig index 75b2fb0f..b3d9e216 100644 --- a/test/elf.zig +++ b/test/elf.zig @@ -3407,7 +3407,7 @@ fn ar(b: *Build) SysCmd { fn ld(b: *Build, opts: Options) SysCmd { const cmd = Run.create(b, "ld"); - cmd.addFileSourceArg(opts.zld.file); + cmd.addFileArg(opts.zld.file); cmd.addArg("-o"); const out = cmd.addOutputFileArg("a.out"); return .{ .cmd = cmd, .out = out }; diff --git a/test/macho.zig b/test/macho.zig index 6103f6f4..bf247f0f 100644 --- a/test/macho.zig +++ b/test/macho.zig @@ -12,11 +12,11 @@ pub fn addMachOTests(b: *Build, options: common.Options) *Step { }; opts.macos_sdk = std.zig.system.darwin.getSdk(b.allocator, builtin.target) orelse @panic("no macOS SDK found"); opts.ios_sdk = blk: { - const target_info = std.zig.system.NativeTargetInfo.detect(.{ + const target = std.zig.system.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .ios, }) catch break :blk null; - break :blk std.zig.system.darwin.getSdk(b.allocator, target_info.target); + break :blk std.zig.system.darwin.getSdk(b.allocator, target); }; macho_step.dependOn(testAllLoad(b, opts)); @@ -54,6 +54,7 @@ pub fn addMachOTests(b: *Build, options: common.Options) *Step { macho_step.dependOn(testObjcStubs2(b, opts)); macho_step.dependOn(testPagezeroSize(b, opts)); macho_step.dependOn(testReexportsZig(b, opts)); + macho_step.dependOn(testRelocatable(b, opts)); macho_step.dependOn(testSearchStrategy(b, opts)); macho_step.dependOn(testSectionBoundarySymbols(b, opts)); macho_step.dependOn(testSegmentBoundarySymbols(b, opts)); @@ -262,10 +263,6 @@ fn testDeadStrip(b: *Build, opts: Options) *Step { const exe = cc(b, opts); exe.addFileSource(obj_out.file); - const run = exe.run(); - run.expectStdOutEqual("1 2\n"); - test_step.dependOn(run.step()); - const check = exe.check(); check.checkInSymtab(); check.checkContains("live_var1"); @@ -284,6 +281,10 @@ fn testDeadStrip(b: *Build, opts: Options) *Step { check.checkInSymtab(); check.checkContains("dead_fn2"); test_step.dependOn(&check.step); + + const run = exe.run(); + run.expectStdOutEqual("1 2\n"); + test_step.dependOn(run.step()); } { @@ -291,10 +292,6 @@ fn testDeadStrip(b: *Build, opts: Options) *Step { exe.addFileSource(obj_out.file); exe.addArg("-Wl,-dead_strip"); - const run = exe.run(); - run.expectStdOutEqual("1 2\n"); - test_step.dependOn(run.step()); - const check = exe.check(); check.checkInSymtab(); check.checkContains("live_var1"); @@ -313,6 +310,10 @@ fn testDeadStrip(b: *Build, opts: Options) *Step { check.checkInSymtab(); check.checkNotPresent("dead_fn2"); test_step.dependOn(&check.step); + + const run = exe.run(); + run.expectStdOutEqual("1 2\n"); + test_step.dependOn(run.step()); } return test_step; @@ -368,7 +369,6 @@ fn testDylib(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-dylib", ""); const dylib = cc(b, opts); - dylib.addArg("-shared"); dylib.addCSource( \\#include \\char world[] = "world"; @@ -376,6 +376,8 @@ fn testDylib(b: *Build, opts: Options) *Step { \\ return "Hello"; \\} ); + dylib.addArgs(&.{ "-shared", "-Wl,-install_name,@rpath/liba.dylib" }); + const dylib_out = dylib.saveOutputAs("liba.dylib"); const check = dylib.check(); check.checkInHeaders(); @@ -394,7 +396,8 @@ fn testDylib(b: *Build, opts: Options) *Step { \\} ); exe.addArg("-la"); - exe.addPrefixedDirectorySource("-L", dylib.saveOutputAs("liba.dylib").dir); + exe.addPrefixedDirectorySource("-L", dylib_out.dir); + exe.addPrefixedDirectorySource("-Wl,-rpath,", dylib_out.dir); const run = exe.run(); run.expectStdOutEqual("Hello world"); @@ -672,13 +675,14 @@ fn testEntryPointDylib(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-entry-point-dylib", ""); const dylib = cc(b, opts); - dylib.addArgs(&.{ "-shared", "-Wl,-undefined,dynamic_lookup" }); dylib.addCSource( \\extern int my_main(); \\int bootstrap() { \\ return my_main(); \\} ); + dylib.addArgs(&.{ "-shared", "-Wl,-undefined,dynamic_lookup", "-Wl,-install_name,@rpath/liba.dylib" }); + const dylib_out = dylib.saveOutputAs("liba.dylib"); const exe = cc(b, opts); exe.addCSource( @@ -688,8 +692,9 @@ fn testEntryPointDylib(b: *Build, opts: Options) *Step { \\ return 0; \\} ); - exe.addArgs(&.{ "-Wl,-e,_bootstrap", "-Wl,-u,_my_main", "-lbootstrap" }); - exe.addPrefixedDirectorySource("-L", dylib.saveOutputAs("libbootstrap.dylib").dir); + exe.addArgs(&.{ "-Wl,-e,_bootstrap", "-Wl,-u,_my_main", "-la" }); + exe.addPrefixedDirectorySource("-L", dylib_out.dir); + exe.addPrefixedDirectorySource("-Wl,-rpath,", dylib_out.dir); const check = exe.check(); check.checkInHeaders(); @@ -778,12 +783,12 @@ fn testFatDylib(b: *Build, opts: Options) *Step { const dylib_arm64 = cc(b, opts); dylib_arm64.addCSource(a_c); - dylib_arm64.addArgs(&.{ "-shared", "-arch", "arm64" }); + dylib_arm64.addArgs(&.{ "-shared", "-arch", "arm64", "-Wl,-install_name,@rpath/liba.dylib" }); const dylib_arm64_out = dylib_arm64.saveOutputAs("liba.dylib"); const dylib_x64 = cc(b, opts); dylib_x64.addCSource(a_c); - dylib_x64.addArgs(&.{ "-shared", "-arch", "x86_64" }); + dylib_x64.addArgs(&.{ "-shared", "-arch", "x86_64", "-Wl,-install_name,@rpath/liba.dylib" }); const dylib_x64_out = dylib_x64.saveOutputAs("liba.dylib"); const fat_lib = lipo(b); @@ -801,6 +806,7 @@ fn testFatDylib(b: *Build, opts: Options) *Step { \\} ); exe.addFileSource(fat_lib_out.file); + exe.addPrefixedDirectorySource("-Wl,-rpath,", fat_lib_out.dir); const run = exe.run(); run.expectStdOutEqual("42\n"); @@ -1505,13 +1511,15 @@ fn testLinkOrder(b: *Build, opts: Options) *Step { const libc = cc(b, opts); libc.addFileSource(c_o.out); - libc.addArg("-shared"); + libc.addArgs(&.{ "-shared", "-Wl,-install_name,@rpath/libc.dylib" }); + const libc_out = libc.saveOutputAs("libc.dylib"); { const exe = cc(b, opts); - exe.addFileSource(libc.out); + exe.addFileSource(libc_out.file); exe.addFileSource(liba.out); exe.addFileSource(main_o.out); + exe.addPrefixedDirectorySource("-Wl,-rpath,", libc_out.dir); const run = exe.run(); run.expectStdOutEqual("-1 42 42"); @@ -1521,8 +1529,9 @@ fn testLinkOrder(b: *Build, opts: Options) *Step { { const exe = cc(b, opts); exe.addFileSource(liba.out); - exe.addFileSource(libc.out); + exe.addFileSource(libc_out.file); exe.addFileSource(main_o.out); + exe.addPrefixedDirectorySource("-Wl,-rpath,", libc_out.dir); const run = exe.run(); run.expectStdOutEqual("42 0 -2"); @@ -2002,6 +2011,91 @@ fn testReexportsZig(b: *Build, opts: Options) *Step { return test_step; } +fn testRelocatable(b: *Build, opts: Options) *Step { + const test_step = b.step("test-macho-relocatable", ""); + + const a_c = + \\#include + \\int try_me() { + \\ throw std::runtime_error("Oh no!"); + \\} + ; + const b_c = + \\extern int try_me(); + \\int try_again() { + \\ return try_me(); + \\} + ; + const main_c = + \\#include + \\#include + \\extern int try_again(); + \\int main() { + \\ try { + \\ try_again(); + \\ } catch (const std::exception &e) { + \\ std::cout << "exception=" << e.what(); + \\ } + \\ return 0; + \\} + ; + const exp_stdout = "exception=Oh no!"; + + { + const a_o = cc(b, opts); + a_o.addCppSource(a_c); + a_o.addArg("-c"); + + const b_o = cc(b, opts); + b_o.addCppSource(b_c); + b_o.addArg("-c"); + + const c_o = ld(b, opts); + c_o.addFileSource(a_o.out); + c_o.addFileSource(b_o.out); + c_o.addArg("-r"); + + const exe = cc(b, opts); + exe.addCppSource(main_c); + exe.addFileSource(c_o.out); + exe.addArg("-lc++"); + + const run = exe.run(); + run.expectStdOutEqual(exp_stdout); + test_step.dependOn(run.step()); + } + + { + const a_o = cc(b, opts); + a_o.addCppSource(a_c); + a_o.addArg("-c"); + + const b_o = cc(b, opts); + b_o.addCppSource(b_c); + b_o.addArg("-c"); + + const main_o = cc(b, opts); + main_o.addCppSource(main_c); + main_o.addArg("-c"); + + const c_o = ld(b, opts); + c_o.addFileSource(a_o.out); + c_o.addFileSource(b_o.out); + c_o.addFileSource(main_o.out); + c_o.addArg("-r"); + + const exe = cc(b, opts); + exe.addFileSource(c_o.out); + exe.addArg("-lc++"); + + const run = exe.run(); + run.expectStdOutEqual(exp_stdout); + test_step.dependOn(run.step()); + } + + return test_step; +} + fn testSearchStrategy(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-search-strategy", ""); @@ -2431,13 +2525,14 @@ fn testTls(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-tls", ""); const dylib = cc(b, opts); - dylib.addArg("-shared"); dylib.addCSource( \\_Thread_local int a; \\int getA() { \\ return a; \\} ); + dylib.addArgs(&.{ "-shared", "-Wl,-install_name,@rpath/liba.dylib" }); + const dylib_out = dylib.saveOutputAs("liba.dylib"); const exe = cc(b, opts); exe.addCSource( @@ -2454,7 +2549,8 @@ fn testTls(b: *Build, opts: Options) *Step { \\} ); exe.addArg("-la"); - exe.addPrefixedDirectorySource("-L", dylib.saveOutputAs("liba.dylib").dir); + exe.addPrefixedDirectorySource("-L", dylib_out.dir); + exe.addPrefixedDirectorySource("-Wl,-rpath,", dylib_out.dir); const run = exe.run(); run.expectStdOutEqual("2 2 2"); @@ -3239,7 +3335,7 @@ fn lipo(b: *Build) SysCmd { fn ld(b: *Build, opts: Options) SysCmd { const cmd = Run.create(b, "ld"); - cmd.addFileSourceArg(opts.zld.file); + cmd.addFileArg(opts.zld.file); cmd.addArg("-dynamic"); cmd.addArg("-o"); const out = cmd.addOutputFileArg("a.out"); diff --git a/test/test.zig b/test/test.zig index c8bd42e6..a69f3287 100644 --- a/test/test.zig +++ b/test/test.zig @@ -19,7 +19,7 @@ pub fn addTests(b: *Build, comp: *Compile, build_opts: struct { error.OutOfMemory => @panic("OOM"), }; - const zld = FileSourceWithDir.fromFileSource(b, comp.getOutputSource(), "ld"); + const zld = FileSourceWithDir.fromFileSource(b, comp.getEmittedBin(), "ld"); const opts: Options = .{ .zld = zld, @@ -51,10 +51,10 @@ pub const Options = struct { }; /// A system command that tracks the command itself via `cmd` Step.Run and output file -/// via `out` FileSource. +/// via `out` LazyPath. pub const SysCmd = struct { cmd: *Run, - out: FileSource, + out: LazyPath, pub fn addArg(sys_cmd: SysCmd, arg: []const u8) void { sys_cmd.cmd.addArg(arg); @@ -64,19 +64,19 @@ pub const SysCmd = struct { sys_cmd.cmd.addArgs(args); } - pub fn addFileSource(sys_cmd: SysCmd, file: FileSource) void { - sys_cmd.cmd.addFileSourceArg(file); + pub fn addFileSource(sys_cmd: SysCmd, file: LazyPath) void { + sys_cmd.cmd.addFileArg(file); } - pub fn addPrefixedFileSource(sys_cmd: SysCmd, prefix: []const u8, file: FileSource) void { + pub fn addPrefixedFileSource(sys_cmd: SysCmd, prefix: []const u8, file: LazyPath) void { sys_cmd.cmd.addPrefixedFileSourceArg(prefix, file); } - pub fn addDirectorySource(sys_cmd: SysCmd, dir: FileSource) void { + pub fn addDirectorySource(sys_cmd: SysCmd, dir: LazyPath) void { sys_cmd.cmd.addDirectorySourceArg(dir); } - pub fn addPrefixedDirectorySource(sys_cmd: SysCmd, prefix: []const u8, dir: FileSource) void { + pub fn addPrefixedDirectorySource(sys_cmd: SysCmd, prefix: []const u8, dir: LazyPath) void { sys_cmd.cmd.addPrefixedDirectorySourceArg(prefix, dir); } @@ -118,7 +118,7 @@ pub const SysCmd = struct { .zig => "a.zig", .objc => "a.m", }, bytes); - sys_cmd.cmd.addFileSourceArg(file); + sys_cmd.cmd.addFileArg(file); } pub inline fn addEmptyMain(sys_cmd: SysCmd) void { @@ -153,7 +153,7 @@ pub const SysCmd = struct { pub fn run(sys_cmd: SysCmd) RunSysCmd { const b = sys_cmd.cmd.step.owner; const r = Run.create(b, "exec"); - r.addFileSourceArg(sys_cmd.out); + r.addFileArg(sys_cmd.out); r.step.dependOn(&sys_cmd.cmd.step); return .{ .run = r }; } @@ -186,19 +186,19 @@ pub const RunSysCmd = struct { /// This abstraction tie the full path of a file with its immediate directory to make /// the above scenario possible. pub const FileSourceWithDir = struct { - dir: FileSource, - file: FileSource, + dir: LazyPath, + file: LazyPath, - pub fn fromFileSource(b: *Build, in_file: FileSource, basename: []const u8) FileSourceWithDir { + pub fn fromFileSource(b: *Build, in_file: LazyPath, basename: []const u8) FileSourceWithDir { const wf = WriteFile.create(b); - const dir = wf.getDirectorySource(); + const dir = wf.getDirectory(); const file = wf.addCopyFile(in_file, basename); return .{ .dir = dir, .file = file }; } pub fn fromBytes(b: *Build, bytes: []const u8, basename: []const u8) FileSourceWithDir { const wf = WriteFile.create(b); - const dir = wf.getDirectorySource(); + const dir = wf.getDirectory(); const file = wf.add(basename, bytes); return .{ .dir = dir, .file = file }; } @@ -245,7 +245,7 @@ const macho = @import("macho.zig"); const Build = std.Build; const CheckObject = Step.CheckObject; const Compile = Step.Compile; -const FileSource = Build.FileSource; +const LazyPath = Build.LazyPath; const Run = Step.Run; const Step = Build.Step; const WriteFile = Step.WriteFile;