From 9ae2c2e755286dc7e2c65128cdde1a9f64c1f9fa Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 13:36:22 +0100 Subject: [PATCH 01/14] macho: rebase __la_symbol_ptr entries --- src/MachO/dyld_info/Rebase.zig | 2 +- src/MachO/synthetic.zig | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/MachO/dyld_info/Rebase.zig b/src/MachO/dyld_info/Rebase.zig index ffad0362..4b6f5688 100644 --- a/src/MachO/dyld_info/Rebase.zig +++ b/src/MachO/dyld_info/Rebase.zig @@ -12,7 +12,7 @@ const Allocator = std.mem.Allocator; entries: std.ArrayListUnmanaged(Entry) = .{}, buffer: std.ArrayListUnmanaged(u8) = .{}, -const Entry = struct { +pub const Entry = struct { offset: u64, segment_id: u8, diff --git a/src/MachO/synthetic.zig b/src/MachO/synthetic.zig index d75e1f08..e7104e2e 100644 --- a/src/MachO/synthetic.zig +++ b/src/MachO/synthetic.zig @@ -340,7 +340,11 @@ pub const LaSymbolPtrSection = struct { for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| { const sym = macho_file.getSymbol(sym_index); const addr = sect.addr + idx * @sizeOf(u64); - const entry = bind.Entry{ + const rebase_entry = Rebase.Entry{ + .offset = addr - seg.vmaddr, + .segment_id = seg_id, + }; + const bind_entry = bind.Entry{ .target = sym_index, .offset = addr - seg.vmaddr, .segment_id = seg_id, @@ -348,20 +352,19 @@ pub const LaSymbolPtrSection = struct { }; if (sym.flags.import) { if (sym.flags.weak) { - try macho_file.bind.entries.append(gpa, entry); - try macho_file.weak_bind.entries.append(gpa, entry); + try macho_file.bind.entries.append(gpa, bind_entry); + try macho_file.weak_bind.entries.append(gpa, bind_entry); } else { - try macho_file.lazy_bind.entries.append(gpa, entry); + try macho_file.lazy_bind.entries.append(gpa, bind_entry); + try macho_file.rebase.entries.append(gpa, rebase_entry); } } else { if (sym.flags.weak) { - try macho_file.rebase.entries.append(gpa, .{ - .offset = addr - seg.vmaddr, - .segment_id = seg_id, - }); - try macho_file.weak_bind.entries.append(gpa, entry); + try macho_file.weak_bind.entries.append(gpa, bind_entry); + try macho_file.rebase.entries.append(gpa, rebase_entry); } else if (sym.flags.interposable) { - try macho_file.lazy_bind.entries.append(gpa, entry); + try macho_file.lazy_bind.entries.append(gpa, bind_entry); + try macho_file.rebase.entries.append(gpa, rebase_entry); } } } From b61e99fff057e18b483721d9467c946ddebd205e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 13:47:53 +0100 Subject: [PATCH 02/14] macho: set filename as ident in code signature --- src/MachO.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MachO.zig b/src/MachO.zig index 3c28f366..794511cb 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -415,7 +415,7 @@ pub fn flush(self: *MachO) !void { // The most important here is to have the correct vm and filesize of the __LINKEDIT segment // where the code signature goes into. var codesig = CodeSignature.init(self.getPageSize()); - codesig.code_directory.ident = self.options.emit.sub_path; + codesig.code_directory.ident = std.fs.path.basename(self.options.emit.sub_path); if (self.options.entitlements) |path| try codesig.addEntitlements(gpa, path); try self.writeCodeSignaturePadding(&codesig); break :blk codesig; From 57ca3b1b0f33cb177c92f1b9159b03456386a6a3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 13:53:12 +0100 Subject: [PATCH 03/14] macho: generate unwind records even if only __eh_frame available --- src/MachO.zig | 4 +-- src/MachO/Object.zig | 72 ++++++++++++++++++++++----------------- src/MachO/relocatable.zig | 4 +-- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/MachO.zig b/src/MachO.zig index 794511cb..c8978c4a 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -1470,14 +1470,14 @@ fn initSyntheticSections(self: *MachO) !void { } const needs_unwind_info = for (self.objects.items) |index| { - if (self.getFile(index).?.object.compact_unwind_sect_index != null) break true; + if (self.getFile(index).?.object.hasUnwindRecords()) 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.eh_frame_sect_index != null) break true; + if (self.getFile(index).?.object.hasEhFrameRecords()) break true; } else false; if (needs_eh_frame) { assert(needs_unwind_info); diff --git a/src/MachO/Object.zig b/src/MachO/Object.zig index c6fc3d17..7e0ba9ad 100644 --- a/src/MachO/Object.zig +++ b/src/MachO/Object.zig @@ -147,6 +147,10 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { try self.initUnwindRecords(index, macho_file); } + if (self.hasUnwindRecords() or self.hasEhFrameRecords()) { + try self.parseUnwindRecords(macho_file); + } + self.initPlatform(); try self.initDwarfInfo(macho_file); @@ -781,36 +785,9 @@ 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 { +fn parseUnwindRecords(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 @@ -829,7 +806,7 @@ fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void { if (nlist.stab()) continue; if (!nlist.sect()) continue; const sect = self.sections.items(.header)[nlist.n_sect - 1]; - if (sect.isCode()) { + if (sect.isCode() and sect.size > 0) { try superposition.ensureUnusedCapacity(1); const gop = superposition.getOrPutAssumeCapacity(nlist.n_value); if (gop.found_existing) { @@ -867,12 +844,10 @@ fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void { } } else { // Synthesise new unwind info record - const fde_data = fde.getData(macho_file); - const atom_size = mem.readInt(u64, fde_data[16..][0..8], .little); const rec_index = try macho_file.addUnwindRecord(); const rec = macho_file.getUnwindRecord(rec_index); try self.unwind_records.append(gpa, rec_index); - rec.length = @intCast(atom_size); + rec.length = @intCast(meta.size); rec.atom = fde.atom; rec.atom_offset = fde.atom_offset; rec.fde = fde_index; @@ -895,6 +870,31 @@ fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void { 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 { @@ -1535,6 +1535,14 @@ pub inline fn hasSubsections(self: Object) bool { return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; } +pub fn hasUnwindRecords(self: Object) bool { + return self.unwind_records.items.len > 0; +} + +pub fn hasEhFrameRecords(self: Object) bool { + return self.cies.items.len > 0; +} + pub fn asFile(self: *Object) File { return .{ .object = self }; } diff --git a/src/MachO/relocatable.zig b/src/MachO/relocatable.zig index 3d2d5b97..0a5256d6 100644 --- a/src/MachO/relocatable.zig +++ b/src/MachO/relocatable.zig @@ -110,7 +110,7 @@ fn initOutputSections(macho_file: *MachO) !void { } const needs_unwind_info = for (macho_file.objects.items) |index| { - if (macho_file.getFile(index).?.object.compact_unwind_sect_index != null) break true; + if (macho_file.getFile(index).?.object.hasUnwindRecords()) break true; } else false; if (needs_unwind_info) { macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{ @@ -119,7 +119,7 @@ fn initOutputSections(macho_file: *MachO) !void { } const needs_eh_frame = for (macho_file.objects.items) |index| { - if (macho_file.getFile(index).?.object.eh_frame_sect_index != null) break true; + if (macho_file.getFile(index).?.object.hasEhFrameRecords()) break true; } else false; if (needs_eh_frame) { assert(needs_unwind_info); From 68b56dc9d86dad516feb536dfbbe51a6f86000c8 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 13:59:32 +0100 Subject: [PATCH 04/14] macho: tweak section and segment sorting logic --- src/MachO.zig | 88 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/MachO.zig b/src/MachO.zig index c8978c4a..0473cba8 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -1536,42 +1536,59 @@ fn getSegmentProt(segname: []const u8) macho.vm_prot_t { fn getSegmentRank(segname: []const u8) u4 { if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0; - if (mem.eql(u8, segname, "__TEXT")) return 0x1; - if (mem.eql(u8, segname, "__DATA_CONST")) return 0x2; - if (mem.eql(u8, segname, "__DATA")) return 0x3; if (mem.eql(u8, segname, "__LINKEDIT")) return 0x5; + if (mem.startsWith(u8, segname, "__TEXT")) return 0x1; + if (mem.startsWith(u8, segname, "__DATA_CONST")) return 0x2; + if (mem.startsWith(u8, segname, "__DATA")) return 0x3; return 0x4; } -fn getSectionRank(self: *MachO, sect_index: u8) u8 { - const header = self.sections.items(.header)[sect_index]; - const segment_rank = getSegmentRank(header.segName()); - const section_rank: u4 = blk: { - if (header.isCode()) { - if (mem.eql(u8, "__text", header.sectName())) break :blk 0x0; - if (header.type() == macho.S_SYMBOL_STUBS) break :blk 0x1; - break :blk 0x2; - } - switch (header.type()) { - macho.S_NON_LAZY_SYMBOL_POINTERS, - macho.S_LAZY_SYMBOL_POINTERS, - => break :blk 0x0, - - macho.S_MOD_INIT_FUNC_POINTERS => break :blk 0x1, - macho.S_MOD_TERM_FUNC_POINTERS => break :blk 0x2, - macho.S_ZEROFILL => break :blk 0xf, - macho.S_THREAD_LOCAL_REGULAR => break :blk 0xd, - macho.S_THREAD_LOCAL_ZEROFILL => break :blk 0xe, - - 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; - }, +fn segmentLessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { + _ = ctx; + const lhs_rank = getSegmentRank(lhs); + const rhs_rank = getSegmentRank(rhs); + if (lhs_rank == rhs_rank) { + return mem.order(u8, lhs, rhs) == .lt; + } + return lhs_rank < rhs_rank; +} + +fn getSectionRank(section: macho.section_64) u8 { + if (section.isCode()) { + if (mem.eql(u8, "__text", section.sectName())) return 0x0; + if (section.type() == macho.S_SYMBOL_STUBS) return 0x1; + return 0x2; + } + switch (section.type()) { + macho.S_NON_LAZY_SYMBOL_POINTERS, + macho.S_LAZY_SYMBOL_POINTERS, + => return 0x0, + + macho.S_MOD_INIT_FUNC_POINTERS => return 0x1, + macho.S_MOD_TERM_FUNC_POINTERS => return 0x2, + macho.S_ZEROFILL => return 0xf, + macho.S_THREAD_LOCAL_REGULAR => return 0xd, + macho.S_THREAD_LOCAL_ZEROFILL => return 0xe, + + else => { + if (mem.eql(u8, "__unwind_info", section.sectName())) return 0xe; + if (mem.eql(u8, "__compact_unwind", section.sectName())) return 0xe; + if (mem.eql(u8, "__eh_frame", section.sectName())) return 0xf; + return 0x3; + }, + } +} + +fn sectionLessThan(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool { + if (mem.eql(u8, lhs.segName(), rhs.segName())) { + const lhs_rank = getSectionRank(lhs); + const rhs_rank = getSectionRank(rhs); + if (lhs_rank == rhs_rank) { + return mem.order(u8, lhs.sectName(), rhs.sectName()) == .lt; } - }; - return (@as(u8, @intCast(segment_rank)) << 4) + section_rank; + return lhs_rank < rhs_rank; + } + return segmentLessThan(ctx, lhs.segName(), rhs.segName()); } pub fn sortSections(self: *MachO) !void { @@ -1579,7 +1596,11 @@ pub fn sortSections(self: *MachO) !void { index: u8, pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool { - return macho_file.getSectionRank(lhs.index) < macho_file.getSectionRank(rhs.index); + return sectionLessThan( + {}, + macho_file.sections.items(.header)[lhs.index], + macho_file.sections.items(.header)[rhs.index], + ); } }; @@ -1839,8 +1860,7 @@ fn initSegments(self: *MachO) !void { const sortFn = struct { fn sortFn(ctx: void, lhs: macho.segment_command_64, rhs: macho.segment_command_64) bool { - _ = ctx; - return getSegmentRank(lhs.segName()) < getSegmentRank(rhs.segName()); + return segmentLessThan(ctx, lhs.segName(), rhs.segName()); } }.sortFn; From 5ab7f4be79f46de0f44d4833d710d52a2151015d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 18:30:25 +0100 Subject: [PATCH 05/14] macho: match __stubs and __stub_helpers alignment with ld64 --- src/MachO.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/MachO.zig b/src/MachO.zig index 0473cba8..06904f29 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -1762,7 +1762,7 @@ fn calcSectionSizes(self: *MachO) !void { const header = &self.sections.items(.header)[idx]; header.size = self.stubs.size(self); header.@"align" = switch (cpu_arch) { - .x86_64 => 0, + .x86_64 => 1, .aarch64 => 2, else => 0, }; @@ -1771,11 +1771,7 @@ fn calcSectionSizes(self: *MachO) !void { if (self.stubs_helper_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.stubs_helper.size(self); - header.@"align" = switch (cpu_arch) { - .x86_64 => 0, - .aarch64 => 2, - else => 0, - }; + header.@"align" = 2; } if (self.la_symbol_ptr_sect_index) |idx| { From c0b35529c0b00fc6bfbaa2800c620e7ad251bf67 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 21:39:32 +0100 Subject: [PATCH 06/14] macho: do not emit stubs address in symtab --- src/MachO/Symbol.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MachO/Symbol.zig b/src/MachO/Symbol.zig index 35e53534..7f9c500d 100644 --- a/src/MachO/Symbol.zig +++ b/src/MachO/Symbol.zig @@ -203,7 +203,7 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); out.n_desc = 0; - out.n_value = symbol.getAddress(.{}, macho_file); + out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); switch (symbol.visibility) { .hidden => out.n_type |= macho.N_PEXT, @@ -214,7 +214,7 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo out.n_type = macho.N_EXT; out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); - out.n_value = symbol.getAddress(.{}, macho_file); + out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); out.n_desc = 0; if (symbol.flags.weak) { From f39557e940ba145d51438fda47ea876e658bfdfd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 21:41:25 +0100 Subject: [PATCH 07/14] macho: align __stub_helper preamble to 4bytes --- src/MachO/synthetic.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MachO/synthetic.zig b/src/MachO/synthetic.zig index e7104e2e..84c5172d 100644 --- a/src/MachO/synthetic.zig +++ b/src/MachO/synthetic.zig @@ -195,7 +195,7 @@ pub const StubsSection = struct { pub const StubsHelperSection = struct { pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize { return switch (cpu_arch) { - .x86_64 => 15, + .x86_64 => 16, .aarch64 => 6 * @sizeOf(u32), else => 0, }; @@ -288,6 +288,7 @@ pub const StubsHelperSection = struct { try writer.writeInt(i32, @intCast(dyld_private_addr - sect.addr - 3 - 4), .little); try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 }); try writer.writeInt(i32, @intCast(dyld_stub_binder_addr - sect.addr - 11 - 4), .little); + try writer.writeByte(0x90); }, .aarch64 => { { From 924478c54614e5878454750773acfe945d4cf86e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jan 2024 22:03:54 +0100 Subject: [PATCH 08/14] macho: write correct target addresses for lazy imports --- src/MachO/synthetic.zig | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/MachO/synthetic.zig b/src/MachO/synthetic.zig index 84c5172d..8e115fae 100644 --- a/src/MachO/synthetic.zig +++ b/src/MachO/synthetic.zig @@ -377,15 +377,19 @@ pub const LaSymbolPtrSection = struct { _ = laptr; const cpu_arch = macho_file.options.cpu_arch.?; const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; - for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| { + var stub_helper_idx: u32 = 0; + for (macho_file.stubs.symbols.items) |sym_index| { const sym = macho_file.getSymbol(sym_index); const value: u64 = if (sym.flags.@"export") sym.getAddress(.{ .stubs = false }, macho_file) else if (sym.flags.weak) @as(u64, 0) - else - sect.addr + StubsHelperSection.preambleSize(cpu_arch) + - StubsHelperSection.entrySize(cpu_arch) * idx; + else value: { + const value = sect.addr + StubsHelperSection.preambleSize(cpu_arch) + + StubsHelperSection.entrySize(cpu_arch) * stub_helper_idx; + stub_helper_idx += 1; + break :value value; + }; try writer.writeInt(u64, @intCast(value), .little); } } From 07e3755eaaaa10c4671262ec8ef6b499b004b06e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 00:08:43 +0100 Subject: [PATCH 09/14] ci: enable more macos runners --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b30d94b..9ec0518a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,13 +5,13 @@ on: [push, pull_request] jobs: default_tools: name: Test default system tools - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # https://github.com/kubkon/zld/issues/44 # os: [macos, ubuntu, windows] - os: [macos, ubuntu] + os: [ macos-11, macos-12, macos-latest, ubuntu-latest ] steps: - if: matrix.os == 'windows' From ccc8011bc231d9ca5ceda9e08ea128a110b25a09 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 00:17:56 +0100 Subject: [PATCH 10/14] test/macho: conditionally enable objc msgsend selector stubs tests --- build.zig | 2 ++ test/macho.zig | 6 ++++++ test/test.zig | 3 +++ 3 files changed, 11 insertions(+) diff --git a/build.zig b/build.zig index ef383bc4..48dc67f0 100644 --- a/build.zig +++ b/build.zig @@ -72,6 +72,7 @@ pub fn build(b: *std.Build) void { const has_static = b.option(bool, "has-static", "Whether the system compiler supports '-static' flag") orelse false; const has_zig = b.option(bool, "has-zig", "Whether the Zig compiler is in path") orelse false; const is_musl = b.option(bool, "musl", "Whether the tests are linked against musl libc") orelse false; + const has_objc_msgsend_stubs = b.option(bool, "has-objc-msgsend-stubs", "Whether the system compiler supports '-fobjc-msgsend-selector-stubs' flag") orelse false; const unit_tests = b.addTest(.{ .root_source_file = .{ .path = "src/Zld.zig" }, @@ -93,6 +94,7 @@ pub fn build(b: *std.Build) void { .has_static = has_static, .has_zig = has_zig, .is_musl = is_musl, + .has_objc_msgsend_stubs = has_objc_msgsend_stubs, })); } diff --git a/test/macho.zig b/test/macho.zig index 32f7c222..36a509fe 100644 --- a/test/macho.zig +++ b/test/macho.zig @@ -6,6 +6,7 @@ pub fn addMachOTests(b: *Build, options: common.Options) *Step { var opts = Options{ .zld = options.zld, .has_zig = options.has_zig, + .has_objc_msgsend_stubs = options.has_objc_msgsend_stubs, .macos_sdk = undefined, .ios_sdk = null, .cc_override = options.cc_override, @@ -1828,6 +1829,8 @@ fn testObjc(b: *Build, opts: Options) *Step { fn testObjcStubs(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-objc-stubs", ""); + if (!opts.has_objc_msgsend_stubs) return skipTestStep(test_step); + const exe = cc(b, "a.out", opts); exe.addObjCSource( \\@import Foundation; @@ -1873,6 +1876,8 @@ fn testObjcStubs(b: *Build, opts: Options) *Step { fn testObjcStubs2(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-objc-stubs-2", ""); + if (!opts.has_objc_msgsend_stubs) return skipTestStep(test_step); + const all_h = saveBytesToFile(b, "all.h", \\#import \\ @@ -3404,6 +3409,7 @@ fn testWeakRef(b: *Build, opts: Options) *Step { const Options = struct { zld: LazyPath, has_zig: bool, + has_objc_msgsend_stubs: bool, macos_sdk: []const u8, ios_sdk: ?[]const u8, cc_override: ?[]const u8, diff --git a/test/test.zig b/test/test.zig index ed0de02d..4fa1e4a2 100644 --- a/test/test.zig +++ b/test/test.zig @@ -3,6 +3,7 @@ pub fn addTests(b: *Build, comp: *Compile, build_opts: struct { has_static: bool, has_zig: bool, is_musl: bool, + has_objc_msgsend_stubs: bool, }) *Step { const test_step = b.step("test-system-tools", "Run all system tools tests"); test_step.dependOn(&comp.step); @@ -24,6 +25,7 @@ pub fn addTests(b: *Build, comp: *Compile, build_opts: struct { .system_compiler = system_compiler, .has_static = build_opts.has_static, .has_zig = build_opts.has_zig, + .has_objc_msgsend_stubs = build_opts.has_objc_msgsend_stubs, .is_musl = build_opts.is_musl, .cc_override = cc_override, }; @@ -44,6 +46,7 @@ pub const Options = struct { system_compiler: SystemCompiler, has_static: bool = false, has_zig: bool = false, + has_objc_msgsend_stubs: bool = false, is_musl: bool = false, cc_override: ?[]const u8 = null, }; From af82d3529441eb4acac4cc775272da5b651c4d43 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 00:23:03 +0100 Subject: [PATCH 11/14] ci: skip objc msgsend stubs tests on macos 11 --- .github/workflows/main.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9ec0518a..91066b8e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: matrix: # https://github.com/kubkon/zld/issues/44 # os: [macos, ubuntu, windows] - os: [ macos-11, macos-12, macos-latest, ubuntu-latest ] + os: [ macos-12, macos-latest, ubuntu-latest ] steps: - if: matrix.os == 'windows' @@ -22,6 +22,17 @@ jobs: version: master - run: zig version - run: zig fmt --check src + - run: zig build test -Dhas-static -Dhas-zig -Dhas-objc-msgsend-stubs + + macos_11: + name: Test macos 11.0 + runs-on: macos-11 + steps: + - uses: actions/checkout@v3 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: master + - run: zig version - run: zig build test -Dhas-static -Dhas-zig gcc_musl: From 75af59433e66db5a68fd69c4f121291a56956c39 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 11:38:00 +0100 Subject: [PATCH 12/14] macho: clean up logic for deciding if needs __stub_helper --- src/MachO/Atom.zig | 29 ++++++++-------- src/MachO/synthetic.zig | 74 +++++++++++++++++++---------------------- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/src/MachO/Atom.zig b/src/MachO/Atom.zig index d734faa4..c2dc5aaa 100644 --- a/src/MachO/Atom.zig +++ b/src/MachO/Atom.zig @@ -183,7 +183,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { switch (rel.type) { .branch => { const symbol = rel.getTargetSymbol(macho_file); - if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) { + if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { symbol.flags.stubs = true; if (symbol.flags.weak) { macho_file.binds_to_weak = true; @@ -199,7 +199,8 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { => { const symbol = rel.getTargetSymbol(macho_file); if (symbol.flags.import or - (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable)) or + (symbol.flags.@"export" and symbol.flags.weak) or + symbol.flags.interposable or macho_file.options.cpu_arch.? == .aarch64) // TODO relax on arm64 { symbol.flags.got = true; @@ -224,7 +225,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { .{ object.fmtPath(), self.getName(macho_file), symbol.getName(macho_file) }, ); } - if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) { + if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { symbol.flags.tlv_ptr = true; if (symbol.flags.weak) { macho_file.binds_to_weak = true; @@ -248,13 +249,11 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { } continue; } - if (symbol.flags.@"export") { - if (symbol.flags.weak) { - object.num_weak_bind_relocs += 1; - macho_file.binds_to_weak = true; - } else if (symbol.flags.interposable) { - object.num_bind_relocs += 1; - } + if (symbol.flags.@"export" and symbol.flags.weak) { + object.num_weak_bind_relocs += 1; + macho_file.binds_to_weak = true; + } else if (symbol.flags.interposable) { + object.num_bind_relocs += 1; } } object.num_rebase_relocs += 1; @@ -391,12 +390,10 @@ fn resolveRelocInner( } return; } - if (sym.flags.@"export") { - if (sym.flags.weak) { - macho_file.weak_bind.entries.appendAssumeCapacity(entry); - } else if (sym.flags.interposable) { - macho_file.bind.entries.appendAssumeCapacity(entry); - } + if (sym.flags.@"export" and sym.flags.weak) { + macho_file.weak_bind.entries.appendAssumeCapacity(entry); + } else if (sym.flags.interposable) { + macho_file.bind.entries.appendAssumeCapacity(entry); } } macho_file.rebase.entries.appendAssumeCapacity(.{ diff --git a/src/MachO/synthetic.zig b/src/MachO/synthetic.zig index 8e115fae..1be36f77 100644 --- a/src/MachO/synthetic.zig +++ b/src/MachO/synthetic.zig @@ -217,9 +217,8 @@ pub const StubsHelperSection = struct { var s: usize = preambleSize(cpu_arch); for (macho_file.stubs.symbols.items) |sym_index| { const sym = macho_file.getSymbol(sym_index); - if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) { - s += entrySize(cpu_arch); - } + if (sym.flags.weak) continue; + s += entrySize(cpu_arch); } return s; } @@ -238,35 +237,34 @@ pub const StubsHelperSection = struct { var idx: usize = 0; for (macho_file.stubs.symbols.items) |sym_index| { const sym = macho_file.getSymbol(sym_index); - if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) { - const offset = macho_file.lazy_bind.offsets.items[idx]; - const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx); - const target: i64 = @intCast(sect.addr); - switch (cpu_arch) { - .x86_64 => { - try writer.writeByte(0x68); - try writer.writeInt(u32, offset, .little); - try writer.writeByte(0xe9); - try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little); - }, - .aarch64 => { - const literal = blk: { - const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4); - break :blk std.math.cast(u18, div_res) orelse return error.Overflow; - }; - try writer.writeInt(u32, aarch64.Instruction.ldrLiteral( - .w16, - literal, - ).toU32(), .little); - const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse - return error.Overflow; - try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little); - try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 }); - }, - else => unreachable, - } - idx += 1; + if (sym.flags.weak) continue; + const offset = macho_file.lazy_bind.offsets.items[idx]; + const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx); + const target: i64 = @intCast(sect.addr); + switch (cpu_arch) { + .x86_64 => { + try writer.writeByte(0x68); + try writer.writeInt(u32, offset, .little); + try writer.writeByte(0xe9); + try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little); + }, + .aarch64 => { + const literal = blk: { + const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4); + break :blk std.math.cast(u18, div_res) orelse return error.Overflow; + }; + try writer.writeInt(u32, aarch64.Instruction.ldrLiteral( + .w16, + literal, + ).toU32(), .little); + const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse + return error.Overflow; + try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little); + try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 }); + }, + else => unreachable, } + idx += 1; } } @@ -380,17 +378,15 @@ pub const LaSymbolPtrSection = struct { var stub_helper_idx: u32 = 0; for (macho_file.stubs.symbols.items) |sym_index| { const sym = macho_file.getSymbol(sym_index); - const value: u64 = if (sym.flags.@"export") - sym.getAddress(.{ .stubs = false }, macho_file) - else if (sym.flags.weak) - @as(u64, 0) - else value: { + if (sym.flags.weak) { + const value = sym.getAddress(.{ .stubs = false }, macho_file); + try writer.writeInt(u64, @intCast(value), .little); + } else { const value = sect.addr + StubsHelperSection.preambleSize(cpu_arch) + StubsHelperSection.entrySize(cpu_arch) * stub_helper_idx; stub_helper_idx += 1; - break :value value; - }; - try writer.writeInt(u64, @intCast(value), .little); + try writer.writeInt(u64, @intCast(value), .little); + } } } }; From 898572533f789661a3a9067dacfafab2588934e3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 11:50:52 +0100 Subject: [PATCH 13/14] macho: set __DATA_CONST* segments flags to SG_READ_ONLY --- src/MachO.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MachO.zig b/src/MachO.zig index 06904f29..61e6d6c7 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -1806,11 +1806,13 @@ fn initSegments(self: *MachO) !void { const segname = header.segName(); if (self.getSegmentByName(segname) == null) { const prot = getSegmentProt(segname); + const flags: u32 = if (mem.startsWith(u8, segname, "__DATA_CONST")) 0x10 else 0; // TODO usee macho.SG_READ_ONLY once upstreamed try self.segments.append(gpa, .{ .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString(segname), .maxprot = prot, .initprot = prot, + .flags = flags, }); } } From b97a0bbadf3f161e55a5238503403c783e420e8d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jan 2024 12:00:28 +0100 Subject: [PATCH 14/14] test/macho: adapt testFlatNamespaceWeak to differences between macOS versions --- test/macho.zig | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/macho.zig b/test/macho.zig index 36a509fe..84ea5b77 100644 --- a/test/macho.zig +++ b/test/macho.zig @@ -1059,12 +1059,24 @@ fn testFlatNamespaceWeak(b: *Build, opts: Options) *Step { test_step.dependOn(&check.step); const run = exe.run(); - run.expectStdOutEqual( - \\main=2 - \\liba=2 - \\libb=2 - \\ - ); + + // TODO: this is quite a huge difference between macOS versions. + // I wonder what changed in dyld's behaviour. + if (builtin.target.os.version_range.semver.isAtLeast(.{ .major = 12, .minor = 0, .patch = 0 }) orelse false) { + run.expectStdOutEqual( + \\main=2 + \\liba=2 + \\libb=2 + \\ + ); + } else { + run.expectStdOutEqual( + \\main=2 + \\liba=1 + \\libb=2 + \\ + ); + } test_step.dependOn(run.step()); }