aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2024-01-24 20:05:03 +0100
committerGitHub <noreply@github.com>2024-01-24 20:05:03 +0100
commitdcaf43674e35372e1d28ab12c4c4ff9af9f3d646 (patch)
treea4bb41d3e608d9a5f93d0c4521bf083a3d925e25 /src
parent92211135f1424aaca0de131cfe3646248730b1ca (diff)
parent0fd0b765fa84a40446663928db1d3f9a63b7a98d (diff)
downloadzig-dcaf43674e35372e1d28ab12c4c4ff9af9f3d646.tar.gz
zig-dcaf43674e35372e1d28ab12c4c4ff9af9f3d646.zip
Merge pull request #18576 from ziglang/new-macho
macho: upstream a complete rewrite of the MachO linker
Diffstat (limited to 'src')
-rw-r--r--src/Compilation.zig1
-rw-r--r--src/arch/aarch64/CodeGen.zig80
-rw-r--r--src/arch/aarch64/Emit.zig80
-rw-r--r--src/arch/x86_64/CodeGen.zig128
-rw-r--r--src/arch/x86_64/Emit.zig79
-rw-r--r--src/arch/x86_64/Lower.zig177
-rw-r--r--src/codegen.zig27
-rw-r--r--src/link.zig1
-rw-r--r--src/link/MachO.zig8103
-rw-r--r--src/link/MachO/Archive.zig225
-rw-r--r--src/link/MachO/Atom.zig2065
-rw-r--r--src/link/MachO/CodeSignature.zig367
-rw-r--r--src/link/MachO/DwarfInfo.zig735
-rw-r--r--src/link/MachO/Dylib.zig952
-rw-r--r--src/link/MachO/InternalObject.zig249
-rw-r--r--src/link/MachO/Object.zig2885
-rw-r--r--src/link/MachO/Relocation.zig322
-rw-r--r--src/link/MachO/Symbol.zig417
-rw-r--r--src/link/MachO/UnwindInfo.zig1028
-rw-r--r--src/link/MachO/ZigObject.zig1471
-rw-r--r--src/link/MachO/dead_strip.zig576
-rw-r--r--src/link/MachO/dyld_info/Rebase.zig26
-rw-r--r--src/link/MachO/dyld_info/Trie.zig (renamed from src/link/MachO/Trie.zig)511
-rw-r--r--src/link/MachO/dyld_info/bind.zig966
-rw-r--r--src/link/MachO/eh_frame.zig1025
-rw-r--r--src/link/MachO/fat.zig43
-rw-r--r--src/link/MachO/file.zig120
-rw-r--r--src/link/MachO/hasher.zig11
-rw-r--r--src/link/MachO/load_commands.zig486
-rw-r--r--src/link/MachO/relocatable.zig506
-rw-r--r--src/link/MachO/stubs.zig169
-rw-r--r--src/link/MachO/synthetic.zig793
-rw-r--r--src/link/MachO/thunks.zig471
-rw-r--r--src/link/MachO/uuid.zig8
-rw-r--r--src/link/MachO/zld.zig1230
-rw-r--r--src/main.zig5
36 files changed, 13478 insertions, 12860 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig
index 58f56517c3..2e1a5a6e4f 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -1542,6 +1542,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
.frameworks = options.frameworks,
.lib_dirs = options.lib_dirs,
+ .framework_dirs = options.framework_dirs,
.rpath_list = options.rpath_list,
.symbol_wrap_set = options.symbol_wrap_set,
.allow_shlib_undefined = options.linker_allow_shlib_undefined,
diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig
index ee5e58ae05..88f211cc29 100644
--- a/src/arch/aarch64/CodeGen.zig
+++ b/src/arch/aarch64/CodeGen.zig
@@ -4013,10 +4013,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
.import => unreachable,
};
const atom_index = switch (self.bin_file.tag) {
- .macho => blk: {
- const macho_file = self.bin_file.cast(link.File.MachO).?;
- const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
- break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ .macho => {
+ // const macho_file = self.bin_file.cast(link.File.MachO).?;
+ // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
+ // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ @panic("TODO store");
},
.coff => blk: {
const coff_file = self.bin_file.cast(link.File.Coff).?;
@@ -4321,14 +4322,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
const got_addr = @as(u32, @intCast(sym.zigGotAddress(elf_file)));
try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr });
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
- const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
- try self.genSetReg(Type.u64, .x30, .{
- .linker_load = .{
- .type = .got,
- .sym_index = sym_index,
- },
- });
+ _ = macho_file;
+ @panic("TODO airCall");
+ // const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
+ // const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
+ // try self.genSetReg(Type.u64, .x30, .{
+ // .linker_load = .{
+ // .type = .got,
+ // .sym_index = sym_index,
+ // },
+ // });
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
const atom = try coff_file.getOrCreateAtomForDecl(func.owner_decl);
const sym_index = coff_file.getAtom(atom).getSymbolIndex().?;
@@ -4352,18 +4355,20 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name);
const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name);
if (self.bin_file.cast(link.File.MachO)) |macho_file| {
- const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name);
- const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
- const atom_index = macho_file.getAtom(atom).getSymbolIndex().?;
- _ = try self.addInst(.{
- .tag = .call_extern,
- .data = .{
- .relocation = .{
- .atom_index = atom_index,
- .sym_index = sym_index,
- },
- },
- });
+ _ = macho_file;
+ @panic("TODO airCall");
+ // const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name);
+ // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
+ // const atom_index = macho_file.getAtom(atom).getSymbolIndex().?;
+ // _ = try self.addInst(.{
+ // .tag = .call_extern,
+ // .data = .{
+ // .relocation = .{
+ // .atom_index = atom_index,
+ // .sym_index = sym_index,
+ // },
+ // },
+ // });
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name);
try self.genSetReg(Type.u64, .x30, .{
@@ -5532,10 +5537,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
.import => unreachable,
};
const atom_index = switch (self.bin_file.tag) {
- .macho => blk: {
- const macho_file = self.bin_file.cast(link.File.MachO).?;
- const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
- break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ .macho => {
+ // const macho_file = self.bin_file.cast(link.File.MachO).?;
+ // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
+ // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ @panic("TODO genSetStack");
},
.coff => blk: {
const coff_file = self.bin_file.cast(link.File.Coff).?;
@@ -5653,10 +5659,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
.import => .load_memory_import,
};
const atom_index = switch (self.bin_file.tag) {
- .macho => blk: {
- const macho_file = self.bin_file.cast(link.File.MachO).?;
- const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
- break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ .macho => {
+ @panic("TODO genSetReg");
+ // const macho_file = self.bin_file.cast(link.File.MachO).?;
+ // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
+ // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
},
.coff => blk: {
const coff_file = self.bin_file.cast(link.File.Coff).?;
@@ -5850,10 +5857,11 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I
.import => unreachable,
};
const atom_index = switch (self.bin_file.tag) {
- .macho => blk: {
- const macho_file = self.bin_file.cast(link.File.MachO).?;
- const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
- break :blk macho_file.getAtom(atom).getSymbolIndex().?;
+ .macho => {
+ @panic("TODO genSetStackArgument");
+ // const macho_file = self.bin_file.cast(link.File.MachO).?;
+ // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
+ // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
},
.coff => blk: {
const coff_file = self.bin_file.cast(link.File.Coff).?;
diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig
index 96eb5b8b30..d14c0c8aad 100644
--- a/src/arch/aarch64/Emit.zig
+++ b/src/arch/aarch64/Emit.zig
@@ -677,6 +677,7 @@ fn mirDebugEpilogueBegin(emit: *Emit) !void {
fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
assert(emit.mir.instructions.items(.tag)[inst] == .call_extern);
const relocation = emit.mir.instructions.items(.data)[inst].relocation;
+ _ = relocation;
const offset = blk: {
const offset = @as(u32, @intCast(emit.code.items.len));
@@ -684,19 +685,22 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
try emit.writeInstruction(Instruction.bl(0));
break :blk offset;
};
+ _ = offset;
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
- // Add relocation to the decl.
- const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?;
- const target = macho_file.getGlobalByIndex(relocation.sym_index);
- try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
- .type = .branch,
- .target = target,
- .offset = offset,
- .addend = 0,
- .pcrel = true,
- .length = 2,
- });
+ _ = macho_file;
+ @panic("TODO mirCallExtern");
+ // // Add relocation to the decl.
+ // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?;
+ // const target = macho_file.getGlobalByIndex(relocation.sym_index);
+ // try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
+ // .type = .branch,
+ // .target = target,
+ // .offset = offset,
+ // .addend = 0,
+ // .pcrel = true,
+ // .length = 2,
+ // });
} else if (emit.bin_file.cast(link.File.Coff)) |_| {
unreachable; // Calling imports is handled via `.load_memory_import`
} else {
@@ -900,32 +904,34 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
}
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
- const Atom = link.File.MachO.Atom;
- const Relocation = Atom.Relocation;
- const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?;
- try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{
- .target = .{ .sym_index = data.sym_index },
- .offset = offset,
- .addend = 0,
- .pcrel = true,
- .length = 2,
- .type = switch (tag) {
- .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page,
- .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page,
- else => unreachable,
- },
- }, .{
- .target = .{ .sym_index = data.sym_index },
- .offset = offset + 4,
- .addend = 0,
- .pcrel = false,
- .length = 2,
- .type = switch (tag) {
- .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff,
- .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff,
- else => unreachable,
- },
- } });
+ _ = macho_file;
+ @panic("TODO mirLoadMemoryPie");
+ // const Atom = link.File.MachO.Atom;
+ // const Relocation = Atom.Relocation;
+ // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?;
+ // try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{
+ // .target = .{ .sym_index = data.sym_index },
+ // .offset = offset,
+ // .addend = 0,
+ // .pcrel = true,
+ // .length = 2,
+ // .type = switch (tag) {
+ // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page,
+ // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page,
+ // else => unreachable,
+ // },
+ // }, .{
+ // .target = .{ .sym_index = data.sym_index },
+ // .offset = offset + 4,
+ // .addend = 0,
+ // .pcrel = false,
+ // .length = 2,
+ // .type = switch (tag) {
+ // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff,
+ // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff,
+ // else => unreachable,
+ // },
+ // } });
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index, .file = null }).?;
const target = switch (tag) {
diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig
index 55e241cbd4..f427e284e6 100644
--- a/src/arch/x86_64/CodeGen.zig
+++ b/src/arch/x86_64/CodeGen.zig
@@ -139,8 +139,7 @@ const Owner = union(enum) {
if (ctx.bin_file.cast(link.File.Elf)) |elf_file| {
return elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index);
} else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom = try macho_file.getOrCreateAtomForDecl(decl_index);
- return macho_file.getAtom(atom).getSymbolIndex().?;
+ return macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index);
} else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| {
const atom = try coff_file.getOrCreateAtomForDecl(decl_index);
return coff_file.getAtom(atom).getSymbolIndex().?;
@@ -153,9 +152,8 @@ const Owner = union(enum) {
return elf_file.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(elf_file, lazy_sym) catch |err|
ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
} else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
- return ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
- return macho_file.getAtom(atom).getSymbolIndex().?;
+ return macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, lazy_sym) catch |err|
+ ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
} else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| {
const atom = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
return ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
@@ -10951,9 +10949,9 @@ fn genCall(self: *Self, info: union(enum) {
try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
try self.asmRegister(.{ ._, .call }, .rax);
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
- const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
- try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
+ const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, func.owner_decl);
+ const sym = macho_file.getSymbol(sym_index);
+ try self.genSetReg(.rax, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } });
try self.asmRegister(.{ ._, .call }, .rax);
} else if (self.bin_file.cast(link.File.Plan9)) |p9| {
const atom_index = try p9.seeDecl(func.owner_decl);
@@ -13509,24 +13507,27 @@ fn genSetReg(self: *Self, dst_reg: Register, ty: Type, src_mcv: MCValue) InnerEr
},
.lea_symbol => |sym_index| {
const atom_index = try self.owner.getSymbolIndex(self);
- if (self.bin_file.cast(link.File.Elf)) |_| {
- try self.asmRegisterMemory(
- .{ ._, .lea },
- dst_reg.to64(),
- .{
- .base = .{ .reloc = .{
- .atom_index = atom_index,
- .sym_index = sym_index.sym,
- } },
- .mod = .{ .rm = .{
- .size = .qword,
- .disp = sym_index.off,
- } },
- },
- );
- } else return self.fail("TODO emit symbol sequence on {s}", .{
- @tagName(self.bin_file.tag),
- });
+ switch (self.bin_file.tag) {
+ .elf, .macho => {
+ try self.asmRegisterMemory(
+ .{ ._, .lea },
+ dst_reg.to64(),
+ .{
+ .base = .{ .reloc = .{
+ .atom_index = atom_index,
+ .sym_index = sym_index.sym,
+ } },
+ .mod = .{ .rm = .{
+ .size = .qword,
+ .disp = sym_index.off,
+ } },
+ },
+ );
+ },
+ else => return self.fail("TODO emit symbol sequence on {s}", .{
+ @tagName(self.bin_file.tag),
+ }),
+ }
},
.lea_direct, .lea_got => |sym_index| {
const atom_index = try self.owner.getSymbolIndex(self);
@@ -13550,30 +13551,7 @@ fn genSetReg(self: *Self, dst_reg: Register, ty: Type, src_mcv: MCValue) InnerEr
} },
});
},
- .lea_tlv => |sym_index| {
- const atom_index = try self.owner.getSymbolIndex(self);
- if (self.bin_file.cast(link.File.MachO)) |_| {
- _ = try self.addInst(.{
- .tag = .lea,
- .ops = .tlv_reloc,
- .data = .{ .rx = .{
- .r1 = .rdi,
- .payload = try self.addExtra(bits.Symbol{
- .atom_index = atom_index,
- .sym_index = sym_index,
- }),
- } },
- });
- // TODO: spill registers before calling
- try self.asmMemory(.{ ._, .call }, .{
- .base = .{ .reg = .rdi },
- .mod = .{ .rm = .{ .size = .qword } },
- });
- try self.genSetReg(dst_reg.to64(), Type.usize, .{ .register = .rax });
- } else return self.fail("TODO emit ptr to TLV sequence on {s}", .{
- @tagName(self.bin_file.tag),
- });
- },
+ .lea_tlv => unreachable, // TODO: remove this
.air_ref => |src_ref| try self.genSetReg(dst_reg, ty, try self.resolveInst(src_ref)),
}
}
@@ -13810,13 +13788,12 @@ fn genExternSymbolRef(
else => unreachable,
}
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
- const global_index = try macho_file.getGlobalSymbol(callee, lib);
_ = try self.addInst(.{
.tag = .call,
.ops = .extern_fn_reloc,
.data = .{ .reloc = .{
.atom_index = atom_index,
- .sym_index = link.File.MachO.global_symbol_bit | global_index,
+ .sym_index = try macho_file.getGlobalSymbol(callee, lib),
} },
});
} else return self.fail("TODO implement calling extern functions", .{});
@@ -13906,12 +13883,12 @@ fn genLazySymbolRef(
else => unreachable,
}
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
+ const sym_index = macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, lazy_sym) catch |err|
return self.fail("{s} creating lazy symbol", .{@errorName(err)});
- const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
+ const sym = macho_file.getSymbol(sym_index);
switch (tag) {
- .lea, .call => try self.genSetReg(reg, Type.usize, .{ .lea_got = sym_index }),
- .mov => try self.genSetReg(reg, Type.usize, .{ .load_got = sym_index }),
+ .lea, .call => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }),
+ .mov => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }),
else => unreachable,
}
switch (tag) {
@@ -16074,24 +16051,27 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
if (!gop.found_existing) gop.value_ptr.* = InstTracking.init(init: {
const const_mcv = try self.genTypedValue(.{ .ty = ty, .val = Value.fromInterned(ip_index) });
switch (const_mcv) {
- .lea_tlv => |tlv_sym| if (self.bin_file.cast(link.File.Elf)) |_| {
- if (self.mod.pic) {
- try self.spillRegisters(&.{ .rdi, .rax });
- } else {
- try self.spillRegisters(&.{.rax});
- }
- const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{
- .size = 8,
- .alignment = .@"8",
- }));
- try self.genSetMem(
- .{ .frame = frame_index },
- 0,
- Type.usize,
- .{ .lea_symbol = .{ .sym = tlv_sym } },
- );
- break :init .{ .load_frame = .{ .index = frame_index } };
- } else break :init const_mcv,
+ .lea_tlv => |tlv_sym| switch (self.bin_file.tag) {
+ .elf, .macho => {
+ if (self.mod.pic) {
+ try self.spillRegisters(&.{ .rdi, .rax });
+ } else {
+ try self.spillRegisters(&.{.rax});
+ }
+ const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{
+ .size = 8,
+ .alignment = .@"8",
+ }));
+ try self.genSetMem(
+ .{ .frame = frame_index },
+ 0,
+ Type.usize,
+ .{ .lea_symbol = .{ .sym = tlv_sym } },
+ );
+ break :init .{ .load_frame = .{ .index = frame_index } };
+ },
+ else => break :init const_mcv,
+ },
else => break :init const_mcv,
}
});
diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig
index 2c976bd00d..35d6935d77 100644
--- a/src/arch/x86_64/Emit.zig
+++ b/src/arch/x86_64/Emit.zig
@@ -50,19 +50,20 @@ pub fn emitMir(emit: *Emit) Error!void {
});
} else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
- const atom_index =
- macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
- const target = if (link.File.MachO.global_symbol_bit & symbol.sym_index != 0)
- macho_file.getGlobalByIndex(link.File.MachO.global_symbol_mask & symbol.sym_index)
- else
- link.File.MachO.SymbolWithLoc{ .sym_index = symbol.sym_index };
- try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
- .type = .branch,
- .target = target,
+ const atom = macho_file.getSymbol(symbol.atom_index).getAtom(macho_file).?;
+ const sym_index = macho_file.getZigObject().?.symbols.items[symbol.sym_index];
+ try atom.addReloc(macho_file, .{
+ .tag = .@"extern",
.offset = end_offset - 4,
+ .target = sym_index,
.addend = 0,
- .pcrel = true,
- .length = 2,
+ .type = .branch,
+ .meta = .{
+ .pcrel = true,
+ .has_subtractor = false,
+ .length = 2,
+ .symbolnum = 0,
+ },
});
} else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| {
// Add relocation to the decl.
@@ -149,33 +150,47 @@ pub fn emitMir(emit: *Emit) Error!void {
});
}
}
+ } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
+ const is_obj_or_static_lib = switch (emit.lower.output_mode) {
+ .Exe => false,
+ .Obj => true,
+ .Lib => emit.lower.link_mode == .Static,
+ };
+ const atom = macho_file.getSymbol(data.atom_index).getAtom(macho_file).?;
+ const sym_index = macho_file.getZigObject().?.symbols.items[data.sym_index];
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.flags.needs_zig_got and !is_obj_or_static_lib) {
+ _ = try sym.getOrCreateZigGotEntry(sym_index, macho_file);
+ }
+ const @"type": link.File.MachO.Relocation.Type = if (sym.flags.needs_zig_got and !is_obj_or_static_lib)
+ .zig_got_load
+ else if (sym.flags.needs_got)
+ .got_load
+ else if (sym.flags.tlv)
+ .tlv
+ else
+ .signed;
+ try atom.addReloc(macho_file, .{
+ .tag = .@"extern",
+ .offset = @intCast(end_offset - 4),
+ .target = sym_index,
+ .addend = 0,
+ .type = @"type",
+ .meta = .{
+ .pcrel = true,
+ .has_subtractor = false,
+ .length = 2,
+ .symbolnum = 0,
+ },
+ });
} else unreachable,
.linker_got,
.linker_direct,
.linker_import,
- .linker_tlv,
=> |symbol| if (emit.lower.bin_file.cast(link.File.Elf)) |_| {
unreachable;
- } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
- const atom_index =
- macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
- const target = if (link.File.MachO.global_symbol_bit & symbol.sym_index != 0)
- macho_file.getGlobalByIndex(link.File.MachO.global_symbol_mask & symbol.sym_index)
- else
- link.File.MachO.SymbolWithLoc{ .sym_index = symbol.sym_index };
- try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
- .type = switch (lowered_relocs[0].target) {
- .linker_got => .got,
- .linker_direct => .signed,
- .linker_tlv => .tlv,
- else => unreachable,
- },
- .target = target,
- .offset = @intCast(end_offset - 4),
- .addend = 0,
- .pcrel = true,
- .length = 2,
- });
+ } else if (emit.lower.bin_file.cast(link.File.MachO)) |_| {
+ unreachable;
} else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| {
const atom_index = coff_file.getAtomIndexForSymbol(.{
.sym_index = symbol.atom_index,
diff --git a/src/arch/x86_64/Lower.zig b/src/arch/x86_64/Lower.zig
index cc5ae7712b..4e9c37e5aa 100644
--- a/src/arch/x86_64/Lower.zig
+++ b/src/arch/x86_64/Lower.zig
@@ -14,7 +14,7 @@ result_relocs_len: u8 = undefined,
result_insts: [
std.mem.max(usize, &.{
1, // non-pseudo instructions
- 3, // TLS local dynamic (LD) sequence in PIC mode
+ 3, // (ELF only) TLS local dynamic (LD) sequence in PIC mode
2, // cmovcc: cmovcc \ cmovcc
3, // setcc: setcc \ setcc \ logicop
2, // jcc: jcc \ jcc
@@ -32,7 +32,7 @@ result_relocs: [
2, // jcc: jcc \ jcc
2, // test \ jcc \ probe \ sub \ jmp
1, // probe \ sub \ jcc
- 3, // TLS local dynamic (LD) sequence in PIC mode
+ 3, // (ELF only) TLS local dynamic (LD) sequence in PIC mode
})
]Reloc = undefined,
@@ -62,7 +62,6 @@ pub const Reloc = struct {
linker_got: bits.Symbol,
linker_direct: bits.Symbol,
linker_import: bits.Symbol,
- linker_tlv: bits.Symbol,
};
};
@@ -326,18 +325,6 @@ fn reloc(lower: *Lower, target: Reloc.Target) Immediate {
return Immediate.s(0);
}
-fn needsZigGot(sym: bits.Symbol, ctx: *link.File) bool {
- const elf_file = ctx.cast(link.File.Elf).?;
- const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
- return elf_file.symbol(sym_index).flags.needs_zig_got;
-}
-
-fn isTls(sym: bits.Symbol, ctx: *link.File) bool {
- const elf_file = ctx.cast(link.File.Elf).?;
- const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
- return elf_file.symbol(sym_index).flags.is_tls;
-}
-
fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void {
const is_obj_or_static_lib = switch (lower.output_mode) {
.Exe => false,
@@ -359,80 +346,115 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand)
assert(mem_op.sib.disp == 0);
assert(mem_op.sib.scale_index.scale == 0);
- if (isTls(sym, lower.bin_file)) {
- // TODO handle extern TLS vars, i.e., emit GD model
- if (lower.pic) {
- // Here, we currently assume local dynamic TLS vars, and so
- // we emit LD model.
- _ = lower.reloc(.{ .linker_tlsld = sym });
- lower.result_insts[lower.result_insts_len] =
- try Instruction.new(.none, .lea, &[_]Operand{
- .{ .reg = .rdi },
- .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
- });
- lower.result_insts_len += 1;
- if (lower.bin_file.cast(link.File.Elf)) |elf_file| {
+ if (lower.bin_file.cast(link.File.Elf)) |elf_file| {
+ const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
+ const elf_sym = elf_file.symbol(sym_index);
+
+ if (elf_sym.flags.is_tls) {
+ // TODO handle extern TLS vars, i.e., emit GD model
+ if (lower.pic) {
+ // Here, we currently assume local dynamic TLS vars, and so
+ // we emit LD model.
+ _ = lower.reloc(.{ .linker_tlsld = sym });
+ lower.result_insts[lower.result_insts_len] =
+ try Instruction.new(.none, .lea, &[_]Operand{
+ .{ .reg = .rdi },
+ .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
+ });
+ lower.result_insts_len += 1;
_ = lower.reloc(.{ .linker_extern_fn = .{
.atom_index = sym.atom_index,
.sym_index = try elf_file.getGlobalSymbol("__tls_get_addr", null),
} });
+ lower.result_insts[lower.result_insts_len] =
+ try Instruction.new(.none, .call, &[_]Operand{
+ .{ .imm = Immediate.s(0) },
+ });
+ lower.result_insts_len += 1;
+ _ = lower.reloc(.{ .linker_dtpoff = sym });
+ emit_mnemonic = .lea;
+ break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
+ .base = .{ .reg = .rax },
+ .disp = std.math.minInt(i32),
+ }) };
+ } else {
+ // Since we are linking statically, we emit LE model directly.
+ lower.result_insts[lower.result_insts_len] =
+ try Instruction.new(.none, .mov, &[_]Operand{
+ .{ .reg = .rax },
+ .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) },
+ });
+ lower.result_insts_len += 1;
+ _ = lower.reloc(.{ .linker_reloc = sym });
+ emit_mnemonic = .lea;
+ break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
+ .base = .{ .reg = .rax },
+ .disp = std.math.minInt(i32),
+ }) };
}
+ }
+
+ _ = lower.reloc(.{ .linker_reloc = sym });
+ break :op if (lower.pic) switch (mnemonic) {
+ .lea => {
+ break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
+ },
+ .mov => {
+ if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea;
+ break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
+ },
+ else => unreachable,
+ } else switch (mnemonic) {
+ .call => break :op if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) .{
+ .imm = Immediate.s(0),
+ } else .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
+ .base = .{ .reg = .ds },
+ }) },
+ .lea => {
+ emit_mnemonic = .mov;
+ break :op .{ .imm = Immediate.s(0) };
+ },
+ .mov => {
+ if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea;
+ break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
+ .base = .{ .reg = .ds },
+ }) };
+ },
+ else => unreachable,
+ };
+ } else if (lower.bin_file.cast(link.File.MachO)) |macho_file| {
+ const sym_index = macho_file.getZigObject().?.symbols.items[sym.sym_index];
+ const macho_sym = macho_file.getSymbol(sym_index);
+
+ if (macho_sym.flags.tlv) {
+ _ = lower.reloc(.{ .linker_reloc = sym });
lower.result_insts[lower.result_insts_len] =
- try Instruction.new(.none, .call, &[_]Operand{
- .{ .imm = Immediate.s(0) },
+ try Instruction.new(.none, .mov, &[_]Operand{
+ .{ .reg = .rdi },
+ .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
});
lower.result_insts_len += 1;
- _ = lower.reloc(.{ .linker_dtpoff = sym });
- emit_mnemonic = .lea;
- break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
- .base = .{ .reg = .rax },
- .disp = std.math.minInt(i32),
- }) };
- } else {
- // Since we are linking statically, we emit LE model directly.
lower.result_insts[lower.result_insts_len] =
- try Instruction.new(.none, .mov, &[_]Operand{
- .{ .reg = .rax },
- .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) },
+ try Instruction.new(.none, .call, &[_]Operand{
+ .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .rdi } }) },
});
lower.result_insts_len += 1;
- _ = lower.reloc(.{ .linker_reloc = sym });
- emit_mnemonic = .lea;
- break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
- .base = .{ .reg = .rax },
- .disp = std.math.minInt(i32),
- }) };
+ emit_mnemonic = .mov;
+ break :op .{ .reg = .rax };
}
- }
- _ = lower.reloc(.{ .linker_reloc = sym });
- break :op if (lower.pic) switch (mnemonic) {
- .lea => {
- break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
- },
- .mov => {
- if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) emit_mnemonic = .lea;
- break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
- },
- else => unreachable,
- } else switch (mnemonic) {
- .call => break :op if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) .{
- .imm = Immediate.s(0),
- } else .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
- .base = .{ .reg = .ds },
- }) },
- .lea => {
- emit_mnemonic = .mov;
- break :op .{ .imm = Immediate.s(0) };
- },
- .mov => {
- if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) emit_mnemonic = .lea;
- break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
- .base = .{ .reg = .ds },
- }) };
- },
- else => unreachable,
- };
+ _ = lower.reloc(.{ .linker_reloc = sym });
+ break :op switch (mnemonic) {
+ .lea => {
+ break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
+ },
+ .mov => {
+ if (is_obj_or_static_lib and macho_sym.flags.needs_zig_got) emit_mnemonic = .lea;
+ break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
+ },
+ else => unreachable,
+ };
+ }
},
},
};
@@ -584,14 +606,13 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
.extern_fn_reloc => &.{
.{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc }) },
},
- .got_reloc, .direct_reloc, .import_reloc, .tlv_reloc => ops: {
+ .got_reloc, .direct_reloc, .import_reloc => ops: {
const reg = inst.data.rx.r1;
const extra = lower.mir.extraData(bits.Symbol, inst.data.rx.payload).data;
_ = lower.reloc(switch (inst.ops) {
.got_reloc => .{ .linker_got = extra },
.direct_reloc => .{ .linker_direct = extra },
.import_reloc => .{ .linker_import = extra },
- .tlv_reloc => .{ .linker_tlv = extra },
else => unreachable,
});
break :ops &.{
diff --git a/src/codegen.zig b/src/codegen.zig
index 1ac8626a79..e9509c4efd 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -985,19 +985,21 @@ fn genDeclRef(
return GenResult.mcv(.{ .load_symbol = sym.esym_index });
} else if (lf.cast(link.File.MachO)) |macho_file| {
if (is_extern) {
- // TODO make this part of getGlobalSymbol
const name = zcu.intern_pool.stringToSlice(decl.name);
- const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
- defer gpa.free(sym_name);
- const global_index = try macho_file.addUndefined(sym_name, .{ .add_got = true });
- return GenResult.mcv(.{ .load_got = link.File.MachO.global_symbol_bit | global_index });
+ const lib_name = if (decl.getOwnedVariable(zcu)) |ov|
+ zcu.intern_pool.stringToSliceUnwrap(ov.lib_name)
+ else
+ null;
+ const sym_index = try macho_file.getGlobalSymbol(name, lib_name);
+ macho_file.getSymbol(macho_file.getZigObject().?.symbols.items[sym_index]).flags.needs_got = true;
+ return GenResult.mcv(.{ .load_symbol = sym_index });
}
- const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index);
- const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
+ const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index);
+ const sym = macho_file.getSymbol(sym_index);
if (is_threadlocal) {
- return GenResult.mcv(.{ .load_tlv = sym_index });
+ return GenResult.mcv(.{ .load_tlv = sym.nlist_idx });
}
- return GenResult.mcv(.{ .load_got = sym_index });
+ return GenResult.mcv(.{ .load_symbol = sym.nlist_idx });
} else if (lf.cast(link.File.Coff)) |coff_file| {
if (is_extern) {
const name = zcu.intern_pool.stringToSlice(decl.name);
@@ -1041,7 +1043,12 @@ fn genUnnamedConst(
const local = elf_file.symbol(local_sym_index);
return GenResult.mcv(.{ .load_symbol = local.esym_index });
},
- .macho, .coff => {
+ .macho => {
+ const macho_file = lf.cast(link.File.MachO).?;
+ const local = macho_file.getSymbol(local_sym_index);
+ return GenResult.mcv(.{ .load_symbol = local.nlist_idx });
+ },
+ .coff => {
return GenResult.mcv(.{ .load_direct = local_sym_index });
},
.plan9 => {
diff --git a/src/link.zig b/src/link.zig
index 8bd481b399..528ba10d1b 100644
--- a/src/link.zig
+++ b/src/link.zig
@@ -133,6 +133,7 @@ pub const File = struct {
// TODO: remove this. libraries are resolved by the frontend.
lib_dirs: []const []const u8,
+ framework_dirs: []const []const u8,
rpath_list: []const []const u8,
/// (Zig compiler development) Enable dumping of linker's state as JSON.
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index cb26aa0ca3..c9f655fd19 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -1,5 +1,4 @@
-base: File,
-entry_name: ?[]const u8,
+base: link.File,
/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
llvm_object: ?*LlvmObject = null,
@@ -7,7 +6,28 @@ llvm_object: ?*LlvmObject = null,
/// Debug symbols bundle (or dSym).
d_sym: ?DebugSymbols = null,
-mode: Mode,
+/// A list of all input files.
+/// Index of each input file also encodes the priority or precedence of one input file
+/// over another.
+files: std.MultiArrayList(File.Entry) = .{},
+zig_object: ?File.Index = null,
+internal_object: ?File.Index = null,
+objects: std.ArrayListUnmanaged(File.Index) = .{},
+dylibs: std.ArrayListUnmanaged(File.Index) = .{},
+
+segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
+sections: std.MultiArrayList(Section) = .{},
+
+symbols: std.ArrayListUnmanaged(Symbol) = .{},
+symbols_extra: std.ArrayListUnmanaged(u32) = .{},
+symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{},
+globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{},
+/// This table will be populated after `scanRelocs` has run.
+/// Key is symbol index.
+undefs: std.AutoHashMapUnmanaged(Symbol.Index, std.ArrayListUnmanaged(Atom.Index)) = .{},
+/// Global symbols we need to resolve for the link to succeed.
+undefined_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+boundary_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
dyld_info_cmd: macho.dyld_info_command = .{},
symtab_cmd: macho.symtab_command = .{},
@@ -17,133 +37,90 @@ data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE },
uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
-objects: std.ArrayListUnmanaged(Object) = .{},
-archives: std.ArrayListUnmanaged(Archive) = .{},
-dylibs: std.ArrayListUnmanaged(Dylib) = .{},
-dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
-referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
-
-segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
-sections: std.MultiArrayList(Section) = .{},
-
-pagezero_segment_cmd_index: ?u8 = null,
-header_segment_cmd_index: ?u8 = null,
-text_segment_cmd_index: ?u8 = null,
-data_const_segment_cmd_index: ?u8 = null,
-data_segment_cmd_index: ?u8 = null,
-linkedit_segment_cmd_index: ?u8 = null,
-
-text_section_index: ?u8 = null,
-data_const_section_index: ?u8 = null,
-data_section_index: ?u8 = null,
-bss_section_index: ?u8 = null,
-thread_vars_section_index: ?u8 = null,
-thread_data_section_index: ?u8 = null,
-thread_bss_section_index: ?u8 = null,
-eh_frame_section_index: ?u8 = null,
-unwind_info_section_index: ?u8 = null,
-stubs_section_index: ?u8 = null,
-stub_helper_section_index: ?u8 = null,
-got_section_index: ?u8 = null,
-la_symbol_ptr_section_index: ?u8 = null,
-tlv_ptr_section_index: ?u8 = null,
-
-locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
-globals: std.ArrayListUnmanaged(SymbolWithLoc) = .{},
-resolver: std.StringHashMapUnmanaged(u32) = .{},
-unresolved: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
-
-locals_free_list: std.ArrayListUnmanaged(u32) = .{},
-globals_free_list: std.ArrayListUnmanaged(u32) = .{},
-
-dyld_stub_binder_index: ?u32 = null,
-dyld_private_atom_index: ?Atom.Index = null,
-
-strtab: StringTable = .{},
-
-got_table: TableSection(SymbolWithLoc) = .{},
-stub_table: TableSection(SymbolWithLoc) = .{},
-tlv_ptr_table: TableSection(SymbolWithLoc) = .{},
-
-thunk_table: std.AutoHashMapUnmanaged(Atom.Index, thunks.Thunk.Index) = .{},
-thunks: std.ArrayListUnmanaged(thunks.Thunk) = .{},
-
-segment_table_dirty: bool = false,
-got_table_count_dirty: bool = false,
-got_table_contents_dirty: bool = false,
-stub_table_count_dirty: bool = false,
-stub_table_contents_dirty: bool = false,
-stub_helper_preamble_allocated: bool = false,
+pagezero_seg_index: ?u8 = null,
+text_seg_index: ?u8 = null,
+linkedit_seg_index: ?u8 = null,
+text_sect_index: ?u8 = null,
+data_sect_index: ?u8 = null,
+got_sect_index: ?u8 = null,
+stubs_sect_index: ?u8 = null,
+stubs_helper_sect_index: ?u8 = null,
+la_symbol_ptr_sect_index: ?u8 = null,
+tlv_ptr_sect_index: ?u8 = null,
+eh_frame_sect_index: ?u8 = null,
+unwind_info_sect_index: ?u8 = null,
+objc_stubs_sect_index: ?u8 = null,
+
+mh_execute_header_index: ?Symbol.Index = null,
+mh_dylib_header_index: ?Symbol.Index = null,
+dyld_private_index: ?Symbol.Index = null,
+dyld_stub_binder_index: ?Symbol.Index = null,
+dso_handle_index: ?Symbol.Index = null,
+objc_msg_send_index: ?Symbol.Index = null,
+entry_index: ?Symbol.Index = null,
/// List of atoms that are either synthetic or map directly to the Zig source program.
atoms: std.ArrayListUnmanaged(Atom) = .{},
-
-/// Table of atoms indexed by the symbol index.
-atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{},
-
-/// Table of unnamed constants associated with a parent `Decl`.
-/// We store them here so that we can free the constants whenever the `Decl`
-/// needs updating or is freed.
-///
-/// For example,
-///
-/// ```zig
-/// const Foo = struct{
-/// a: u8,
-/// };
-///
-/// pub fn main() void {
-/// var foo = Foo{ .a = 1 };
-/// _ = foo;
-/// }
-/// ```
-///
-/// value assigned to label `foo` is an unnamed constant belonging/associated
-/// with `Decl` `main`, and lives as long as that `Decl`.
-unnamed_const_atoms: UnnamedConstTable = .{},
-anon_decls: AnonDeclTable = .{},
-
-/// A table of relocations indexed by the owning them `Atom`.
-/// Note that once we refactor `Atom`'s lifetime and ownership rules,
-/// this will be a table indexed by index into the list of Atoms.
-relocs: RelocationTable = .{},
-/// TODO I do not have time to make this right but this will go once
-/// MachO linker is rewritten more-or-less to feature the same resolution
-/// mechanism as the ELF linker.
-actions: ActionTable = .{},
-
-/// A table of rebases indexed by the owning them `Atom`.
-/// Note that once we refactor `Atom`'s lifetime and ownership rules,
-/// this will be a table indexed by index into the list of Atoms.
-rebases: RebaseTable = .{},
-
-/// A table of bindings indexed by the owning them `Atom`.
-/// Note that once we refactor `Atom`'s lifetime and ownership rules,
-/// this will be a table indexed by index into the list of Atoms.
-bindings: BindingTable = .{},
-
-/// Table of tracked LazySymbols.
-lazy_syms: LazySymbolTable = .{},
-
-/// Table of tracked Decls.
-decls: DeclTable = .{},
-
-/// Table of threadlocal variables descriptors.
-/// They are emitted in the `__thread_vars` section.
-tlv_table: TlvSymbolTable = .{},
-
-/// Hot-code swapping state.
-hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
-
+thunks: std.ArrayListUnmanaged(Thunk) = .{},
+unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record) = .{},
+
+/// String interning table
+strings: StringTable = .{},
+
+/// Output synthetic sections
+symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+strtab: std.ArrayListUnmanaged(u8) = .{},
+indsymtab: Indsymtab = .{},
+got: GotSection = .{},
+zig_got: ZigGotSection = .{},
+stubs: StubsSection = .{},
+stubs_helper: StubsHelperSection = .{},
+objc_stubs: ObjcStubsSection = .{},
+la_symbol_ptr: LaSymbolPtrSection = .{},
+tlv_ptr: TlvPtrSection = .{},
+rebase: RebaseSection = .{},
+bind: BindSection = .{},
+weak_bind: WeakBindSection = .{},
+lazy_bind: LazyBindSection = .{},
+export_trie: ExportTrieSection = .{},
+unwind_info: UnwindInfo = .{},
+
+/// Tracked loadable segments during incremental linking.
+zig_text_seg_index: ?u8 = null,
+zig_got_seg_index: ?u8 = null,
+zig_const_seg_index: ?u8 = null,
+zig_data_seg_index: ?u8 = null,
+zig_bss_seg_index: ?u8 = null,
+
+/// Tracked section headers with incremental updates to Zig object.
+zig_text_sect_index: ?u8 = null,
+zig_got_sect_index: ?u8 = null,
+zig_const_sect_index: ?u8 = null,
+zig_data_sect_index: ?u8 = null,
+zig_bss_sect_index: ?u8 = null,
+
+has_tlv: bool = false,
+binds_to_weak: bool = false,
+weak_defines: bool = false,
+
+/// Options
+/// SDK layout
sdk_layout: ?SdkLayout,
/// Size of the __PAGEZERO segment.
-pagezero_vmsize: u64,
+pagezero_size: ?u64,
/// Minimum space for future expansion of the load commands.
-headerpad_size: u32,
+headerpad_size: ?u32,
/// Set enough space as if all paths were MATPATHLEN.
headerpad_max_install_names: bool,
/// Remove dylibs that are unreachable by the entry point or exported symbols.
dead_strip_dylibs: bool,
+/// Treatment of undefined symbols
+undefined_treatment: UndefinedTreatment,
+/// Resolved list of library search directories
+lib_dirs: []const []const u8,
+/// Resolved list of framework search directories
+framework_dirs: []const []const u8,
+/// List of input frameworks
frameworks: []const Framework,
/// Install name for the dylib.
/// TODO: unify with soname
@@ -151,6 +128,18 @@ install_name: ?[]const u8,
/// Path to entitlements file.
entitlements: ?[]const u8,
compatibility_version: ?std.SemanticVersion,
+/// Entry name
+entry_name: ?[]const u8,
+platform: Platform,
+sdk_version: ?std.SemanticVersion,
+/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
+no_implicit_dylibs: bool = false,
+/// Whether the linker should parse and always force load objects containing ObjC in archives.
+// TODO: in Zig we currently take -ObjC as always on
+force_load_objc: bool = true,
+
+/// Hot-code swapping state.
+hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
/// When adding a new field, remember to update `hashAddFrameworks`.
pub const Framework = struct {
@@ -167,14 +156,6 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
}
}
-/// The filesystem layout of darwin SDK elements.
-pub const SdkLayout = enum {
- /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }.
- sdk,
- /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }.
- vendored,
-};
-
pub fn createEmpty(
arena: Allocator,
comp: *Compilation,
@@ -183,27 +164,22 @@ pub fn createEmpty(
) !*MachO {
const target = comp.root_mod.resolved_target.result;
assert(target.ofmt == .macho);
- const use_llvm = comp.config.use_llvm;
+
const gpa = comp.gpa;
+ const use_llvm = comp.config.use_llvm;
+ const opt_zcu = comp.module;
const optimize_mode = comp.root_mod.optimize_mode;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
- // TODO: get rid of zld mode
- const mode: Mode = if (use_llvm or !comp.config.have_zcu or comp.cache_use == .whole)
- .zld
- else
- .incremental;
-
- // If using "zld mode" to link, this code should produce an object file so that it
- // can be passed to "zld mode". TODO: get rid of "zld mode".
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
- const zcu_object_sub_path = if (mode != .zld and !use_llvm)
+ const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
+ const allow_shlib_undefined = options.allow_shlib_undefined orelse false;
const self = try arena.create(MachO);
self.* = .{
@@ -215,15 +191,14 @@ pub fn createEmpty(
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 16777216,
- .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
+ .allow_shlib_undefined = allow_shlib_undefined,
.file = null,
.disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
.rpath_list = options.rpath_list,
},
- .mode = mode,
- .pagezero_vmsize = options.pagezero_size orelse default_pagezero_vmsize,
- .headerpad_size = options.headerpad_size orelse default_headerpad_size,
+ .pagezero_size = options.pagezero_size,
+ .headerpad_size = options.headerpad_size,
.headerpad_max_install_names = options.headerpad_max_install_names,
.dead_strip_dylibs = options.dead_strip_dylibs,
.sdk_layout = options.darwin_sdk_layout,
@@ -237,68 +212,77 @@ pub fn createEmpty(
.enabled => default_entry_symbol_name,
.named => |name| name,
},
+ .platform = Platform.fromTarget(target),
+ .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
+ .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
+ .lib_dirs = options.lib_dirs,
+ .framework_dirs = options.framework_dirs,
};
if (use_llvm and comp.config.have_zcu) {
self.llvm_object = try LlvmObject.create(arena, comp);
}
errdefer self.base.destroy();
- log.debug("selected linker mode '{s}'", .{@tagName(self.mode)});
-
- if (mode == .zld) {
- // TODO: get rid of zld mode
- return self;
- }
-
- const file = try emit.directory.handle.createFile(emit.sub_path, .{
+ self.base.file = try emit.directory.handle.createFile(emit.sub_path, .{
.truncate = true,
.read = true,
.mode = link.File.determineMode(false, output_mode, link_mode),
});
- self.base.file = file;
-
- if (comp.config.debug_format != .strip and comp.module != null) {
- // Create dSYM bundle.
- log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
- const d_sym_path = try std.fmt.allocPrint(
- arena,
- "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
- .{emit.sub_path},
- );
+ // Append null file
+ try self.files.append(gpa, .null);
+ // Atom at index 0 is reserved as null atom
+ try self.atoms.append(gpa, .{});
+ // Append empty string to string tables
+ try self.strings.buffer.append(gpa, 0);
+ try self.strtab.append(gpa, 0);
+ // Append null symbols
+ try self.symbols.append(gpa, .{});
+ try self.symbols_extra.append(gpa, 0);
+
+ if (opt_zcu) |zcu| {
+ if (!use_llvm) {
+ const index: File.Index = @intCast(try self.files.addOne(gpa));
+ self.files.set(index, .{ .zig_object = .{
+ .index = index,
+ .path = try std.fmt.allocPrint(arena, "{s}.o", .{std.fs.path.stem(
+ zcu.main_mod.root_src_path,
+ )}),
+ } });
+ self.zig_object = index;
+ try self.getZigObject().?.init(self);
+ try self.initMetadata(.{
+ .symbol_count_hint = options.symbol_count_hint,
+ .program_code_size_hint = options.program_code_size_hint,
+ });
- var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{});
- defer d_sym_bundle.close();
+ // TODO init dwarf
- const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
- .truncate = false,
- .read = true,
- });
+ // if (comp.config.debug_format != .strip) {
+ // // Create dSYM bundle.
+ // log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
- self.d_sym = .{
- .allocator = gpa,
- .dwarf = link.File.Dwarf.init(&self.base, .dwarf32),
- .file = d_sym_file,
- };
- }
+ // const d_sym_path = try std.fmt.allocPrint(
+ // arena,
+ // "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
+ // .{emit.sub_path},
+ // );
- // Index 0 is always a null symbol.
- try self.locals.append(gpa, .{
- .n_strx = 0,
- .n_type = 0,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- });
- try self.strtab.buffer.append(gpa, 0);
+ // var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{});
+ // defer d_sym_bundle.close();
- try self.populateMissingMetadata(.{
- .symbol_count_hint = options.symbol_count_hint,
- .program_code_size_hint = options.program_code_size_hint,
- });
+ // const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
+ // .truncate = false,
+ // .read = true,
+ // });
- if (self.d_sym) |*d_sym| {
- try d_sym.populateMissingMetadata(self);
+ // self.d_sym = .{
+ // .allocator = gpa,
+ // .dwarf = link.File.Dwarf.init(&self.base, .dwarf32),
+ // .file = d_sym_file,
+ // };
+ // }
+ }
}
return self;
@@ -315,27 +299,75 @@ pub fn open(
return createEmpty(arena, comp, emit, options);
}
-pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const output_mode = comp.config.output_mode;
+pub fn deinit(self: *MachO) void {
+ const gpa = self.base.comp.gpa;
- if (output_mode == .Lib and comp.config.link_mode == .Static) {
- if (build_options.have_llvm) {
- return self.base.linkAsArchive(arena, prog_node);
- } else {
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try gpa.dupe(u8, "TODO: non-LLVM archiver for MachO object files"),
- });
- return error.FlushFailure;
+ if (self.llvm_object) |llvm_object| llvm_object.deinit();
+
+ if (self.d_sym) |*d_sym| {
+ d_sym.deinit();
+ }
+
+ for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) {
+ .null => {},
+ .zig_object => data.zig_object.deinit(gpa),
+ .internal => data.internal.deinit(gpa),
+ .object => data.object.deinit(gpa),
+ .dylib => data.dylib.deinit(gpa),
+ };
+ self.files.deinit(gpa);
+ self.objects.deinit(gpa);
+ self.dylibs.deinit(gpa);
+
+ self.segments.deinit(gpa);
+ for (self.sections.items(.atoms)) |*list| {
+ list.deinit(gpa);
+ }
+ self.sections.deinit(gpa);
+
+ self.symbols.deinit(gpa);
+ self.symbols_extra.deinit(gpa);
+ self.symbols_free_list.deinit(gpa);
+ self.globals.deinit(gpa);
+ {
+ var it = self.undefs.iterator();
+ while (it.next()) |entry| {
+ entry.value_ptr.deinit(gpa);
}
+ self.undefs.deinit(gpa);
+ }
+ self.undefined_symbols.deinit(gpa);
+ self.boundary_symbols.deinit(gpa);
+
+ self.strings.deinit(gpa);
+ self.symtab.deinit(gpa);
+ self.strtab.deinit(gpa);
+ self.got.deinit(gpa);
+ self.zig_got.deinit(gpa);
+ self.stubs.deinit(gpa);
+ self.objc_stubs.deinit(gpa);
+ self.tlv_ptr.deinit(gpa);
+ self.rebase.deinit(gpa);
+ self.bind.deinit(gpa);
+ self.weak_bind.deinit(gpa);
+ self.lazy_bind.deinit(gpa);
+ self.export_trie.deinit(gpa);
+ self.unwind_info.deinit(gpa);
+
+ self.atoms.deinit(gpa);
+ for (self.thunks.items) |*thunk| {
+ thunk.deinit(gpa);
}
+ self.thunks.deinit(gpa);
+ self.unwind_records.deinit(gpa);
+}
- switch (self.mode) {
- .zld => return zld.linkWithZld(self, arena, prog_node),
- .incremental => return self.flushModule(arena, prog_node),
+pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
+ // TODO: I think this is just a temp and can be removed once we can emit static archives
+ if (self.base.isStaticLib() and build_options.have_llvm) {
+ return self.base.linkAsArchive(arena, prog_node);
}
+ try self.flushModule(arena, prog_node);
}
pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
@@ -347,278 +379,499 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
if (self.llvm_object) |llvm_object| {
try self.base.emitLlvmObject(arena, llvm_object, prog_node);
- return;
+ // TODO: I think this is just a temp and can be removed once we can emit static archives
+ if (self.base.isStaticLib() and build_options.have_llvm) return;
}
var sub_prog_node = prog_node.start("MachO Flush", 0);
sub_prog_node.activate();
defer sub_prog_node.end();
- const output_mode = comp.config.output_mode;
- const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
const target = comp.root_mod.resolved_target.result;
+ _ = target;
+ const directory = self.base.emit.directory;
+ const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
+ const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
+ if (fs.path.dirname(full_out_path)) |dirname| {
+ break :blk try fs.path.join(arena, &.{ dirname, path });
+ } else {
+ break :blk path;
+ }
+ } else null;
- if (self.lazy_syms.getPtr(.none)) |metadata| {
- // Most lazy symbols can be updated on first use, but
- // anyerror needs to wait for everything to be flushed.
- if (metadata.text_state != .unused) self.updateLazySymbolAtom(
- File.LazySymbol.initDecl(.code, null, module),
- metadata.text_atom,
- self.text_section_index.?,
- ) catch |err| return switch (err) {
- error.CodegenFail => error.FlushFailure,
- else => |e| e,
- };
- if (metadata.data_const_state != .unused) self.updateLazySymbolAtom(
- File.LazySymbol.initDecl(.const_data, null, module),
- metadata.data_const_atom,
- self.data_const_section_index.?,
- ) catch |err| return switch (err) {
- error.CodegenFail => error.FlushFailure,
- else => |e| e,
+ // --verbose-link
+ if (comp.verbose_link) try self.dumpArgv(comp);
+
+ if (self.getZigObject()) |zo| try zo.flushModule(self);
+ if (self.base.isStaticLib()) return self.flushStaticLib(comp, module_obj_path);
+ if (self.base.isObject()) return relocatable.flush(self, comp, module_obj_path);
+
+ var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
+ defer positionals.deinit();
+
+ try positionals.ensureUnusedCapacity(comp.objects.len);
+ positionals.appendSliceAssumeCapacity(comp.objects);
+
+ // This is a set of object files emitted by clang in a single `build-exe` invocation.
+ // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
+ // in this set.
+ try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);
+ for (comp.c_object_table.keys()) |key| {
+ positionals.appendAssumeCapacity(.{ .path = key.status.success.object_path });
+ }
+
+ if (module_obj_path) |path| try positionals.append(.{ .path = path });
+
+ for (positionals.items) |obj| {
+ self.parsePositional(obj.path, obj.must_link) catch |err| switch (err) {
+ error.MalformedObject,
+ error.MalformedArchive,
+ error.MalformedDylib,
+ error.InvalidCpuArch,
+ error.InvalidTarget,
+ => continue, // already reported
+ error.UnknownFileType => try self.reportParseError(obj.path, "unknown file type for an object file", .{}),
+ else => |e| try self.reportParseError(
+ obj.path,
+ "unexpected error: parsing input file failed with error {s}",
+ .{@errorName(e)},
+ ),
};
}
- for (self.lazy_syms.values()) |*metadata| {
- if (metadata.text_state != .unused) metadata.text_state = .flushed;
- if (metadata.data_const_state != .unused) metadata.data_const_state = .flushed;
- }
-
- if (self.d_sym) |*d_sym| {
- try d_sym.dwarf.flushModule(module);
- }
- var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
- try self.resolveLibSystem(arena, comp, &libs);
+ var system_libs = std.ArrayList(SystemLib).init(gpa);
+ defer system_libs.deinit();
- self.base.releaseLock();
-
- for (self.dylibs.items) |*dylib| {
- dylib.deinit(gpa);
+ // libs
+ try system_libs.ensureUnusedCapacity(comp.system_libs.values().len);
+ for (comp.system_libs.values()) |info| {
+ system_libs.appendAssumeCapacity(.{
+ .needed = info.needed,
+ .weak = info.weak,
+ .path = info.path.?,
+ });
}
- self.dylibs.clearRetainingCapacity();
- self.dylibs_map.clearRetainingCapacity();
- self.referenced_dylibs.clearRetainingCapacity();
- var dependent_libs = std.fifo.LinearFifo(DylibReExportInfo, .Dynamic).init(arena);
+ // frameworks
+ try system_libs.ensureUnusedCapacity(self.frameworks.len);
+ for (self.frameworks) |info| {
+ system_libs.appendAssumeCapacity(.{
+ .needed = info.needed,
+ .weak = info.weak,
+ .path = info.path,
+ });
+ }
- for (libs.keys(), libs.values()) |path, lib| {
- const in_file = try std.fs.cwd().openFile(path, .{});
- defer in_file.close();
+ // libc++ dep
+ if (comp.config.link_libcpp) {
+ try system_libs.ensureUnusedCapacity(2);
+ system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
+ system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
+ }
- var parse_ctx = ParseErrorCtx.init(gpa);
- defer parse_ctx.deinit();
+ // libc/libSystem dep
+ self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) {
+ error.MissingLibSystem => {}, // already reported
+ else => |e| return e, // TODO: convert into an error
+ };
- self.parseLibrary(
- in_file,
- path,
- lib,
- false,
- false,
- null,
- &dependent_libs,
- &parse_ctx,
- ) catch |err| try self.handleAndReportParseError(path, err, &parse_ctx);
+ for (system_libs.items) |lib| {
+ self.parseLibrary(lib, false) catch |err| switch (err) {
+ error.MalformedArchive,
+ error.MalformedDylib,
+ error.InvalidCpuArch,
+ => continue, // already reported
+ error.UnknownFileType => try self.reportParseError(lib.path, "unknown file type for a library", .{}),
+ else => |e| try self.reportParseError(
+ lib.path,
+ "unexpected error: parsing library failed with error {s}",
+ .{@errorName(e)},
+ ),
+ };
}
- try self.parseDependentLibs(&dependent_libs);
+ // Finally, link against compiler_rt.
+ const compiler_rt_path: ?[]const u8 = blk: {
+ if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
+ if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
+ if (compiler_rt_path) |path| {
+ self.parsePositional(path, false) catch |err| switch (err) {
+ error.MalformedObject,
+ error.MalformedArchive,
+ error.InvalidCpuArch,
+ error.InvalidTarget,
+ => {}, // already reported
+ error.UnknownFileType => try self.reportParseError(path, "unknown file type for a library", .{}),
+ else => |e| try self.reportParseError(
+ path,
+ "unexpected error: parsing input file failed with error {s}",
+ .{@errorName(e)},
+ ),
+ };
+ }
- try self.resolveSymbols();
+ if (comp.link_errors.items.len > 0) return error.FlushFailure;
- if (self.getEntryPoint() == null) {
- comp.link_error_flags.no_entry_point_found = true;
- }
- if (self.unresolved.count() > 0) {
- try self.reportUndefined();
- return error.FlushFailure;
+ for (self.dylibs.items) |index| {
+ self.getFile(index).?.dylib.umbrella = index;
}
- {
- var it = self.actions.iterator();
- while (it.next()) |entry| {
- const global_index = entry.key_ptr.*;
- const global = self.globals.items[global_index];
- const flags = entry.value_ptr.*;
- if (flags.add_got) try self.addGotEntry(global);
- if (flags.add_stub) try self.addStubEntry(global);
- }
+ if (self.dylibs.items.len > 0) {
+ self.parseDependentDylibs() catch |err| {
+ switch (err) {
+ error.MissingLibraryDependencies => {},
+ else => |e| try self.reportUnexpectedError(
+ "unexpected error while parsing dependent libraries: {s}",
+ .{@errorName(e)},
+ ),
+ }
+ return error.FlushFailure;
+ };
}
- try self.createDyldPrivateAtom();
- try self.writeStubHelperPreamble();
-
- if (output_mode == .Exe and self.getEntryPoint() != null) {
- const global = self.getEntryPoint().?;
- if (self.getSymbol(global).undf()) {
- // We do one additional check here in case the entry point was found in one of the dylibs.
- // (I actually have no idea what this would imply but it is a possible outcome and so we
- // support it.)
- try self.addStubEntry(global);
- }
+ for (self.dylibs.items) |index| {
+ const dylib = self.getFile(index).?.dylib;
+ if (!dylib.explicit and !dylib.hoisted) continue;
+ try dylib.initSymbols(self);
}
- try self.allocateSpecialSymbols();
+ {
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .internal = .{ .index = index } });
+ self.internal_object = index;
+ }
- for (self.relocs.keys()) |atom_index| {
- const relocs = self.relocs.get(atom_index).?;
- const needs_update = for (relocs.items) |reloc| {
- if (reloc.dirty) break true;
- } else false;
+ try self.addUndefinedGlobals();
+ try self.resolveSymbols();
+ try self.resolveSyntheticSymbols();
- if (!needs_update) continue;
+ try self.convertTentativeDefinitions();
+ try self.createObjcSections();
+ try self.claimUnresolved();
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbol(self);
- const section = self.sections.get(sym.n_sect - 1).header;
- const file_offset = section.offset + sym.n_value - section.addr;
+ if (self.base.gc_sections) {
+ try dead_strip.gcAtoms(self);
+ }
- var code = std.ArrayList(u8).init(gpa);
- defer code.deinit();
- try code.resize(math.cast(usize, atom.size) orelse return error.Overflow);
+ self.checkDuplicates() catch |err| switch (err) {
+ error.HasDuplicates => return error.FlushFailure,
+ else => |e| {
+ try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
+ return e;
+ },
+ };
- const amt = try self.base.file.?.preadAll(code.items, file_offset);
- if (amt != code.items.len) return error.InputOutput;
+ try self.markImportsAndExports();
+ self.deadStripDylibs();
- try self.writeAtom(atom_index, code.items);
+ for (self.dylibs.items, 1..) |index, ord| {
+ const dylib = self.getFile(index).?.dylib;
+ dylib.ordinal = @intCast(ord);
}
- // Update GOT if it got moved in memory.
- if (self.got_table_contents_dirty) {
- for (self.got_table.entries.items, 0..) |entry, i| {
- if (!self.got_table.lookup.contains(entry)) continue;
- // TODO: write all in one go rather than incrementally.
- try self.writeOffsetTableEntry(i);
- }
- self.got_table_contents_dirty = false;
- }
+ self.scanRelocs() catch |err| switch (err) {
+ error.HasUndefinedSymbols => return error.FlushFailure,
+ else => |e| {
+ try self.reportUnexpectedError("unexpected error while scanning relocations", .{});
+ return e;
+ },
+ };
- // Update stubs if we moved any section in memory.
- // TODO: we probably don't need to update all sections if only one got moved.
- if (self.stub_table_contents_dirty) {
- for (self.stub_table.entries.items, 0..) |entry, i| {
- if (!self.stub_table.lookup.contains(entry)) continue;
- // TODO: write all in one go rather than incrementally.
- try self.writeStubTableEntry(i);
+ try self.initOutputSections();
+ try self.initSyntheticSections();
+ try self.sortSections();
+ try self.addAtomsToSections();
+ try self.calcSectionSizes();
+ try self.generateUnwindInfo();
+ try self.initSegments();
+
+ try self.allocateSections();
+ self.allocateSegments();
+ self.allocateAtoms();
+ self.allocateSyntheticSymbols();
+ try self.allocateLinkeditSegment();
+
+ state_log.debug("{}", .{self.dumpState()});
+
+ try self.initDyldInfoSections();
+
+ // Beyond this point, everything has been allocated a virtual address and we can resolve
+ // the relocations, and commit objects to file.
+ if (self.getZigObject()) |zo| {
+ var has_resolve_error = false;
+
+ for (zo.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ const sect = &self.sections.items(.header)[atom.out_n_sect];
+ if (sect.isZerofill()) continue;
+ if (mem.indexOf(u8, sect.segName(), "ZIG") == null) continue; // Non-Zig sections are handled separately
+ // TODO: we will resolve and write ZigObject's TLS data twice:
+ // once here, and once in writeAtoms
+ const code = zo.getAtomDataAlloc(self, gpa, atom.*) catch |err| switch (err) {
+ error.InputOutput => {
+ try self.reportUnexpectedError("fetching code for '{s}' failed", .{
+ atom.getName(self),
+ });
+ return error.FlushFailure;
+ },
+ else => |e| {
+ try self.reportUnexpectedError("unexpected error while fetching code for '{s}': {s}", .{
+ atom.getName(self),
+ @errorName(e),
+ });
+ return error.FlushFailure;
+ },
+ };
+ defer gpa.free(code);
+ const file_offset = sect.offset + atom.value - sect.addr;
+ atom.resolveRelocs(self, code) catch |err| switch (err) {
+ error.ResolveFailed => has_resolve_error = true,
+ else => |e| {
+ try self.reportUnexpectedError("unexpected error while resolving relocations", .{});
+ return e;
+ },
+ };
+ try self.base.file.?.pwriteAll(code, file_offset);
}
- self.stub_table_contents_dirty = false;
- }
- if (build_options.enable_logging) {
- self.logSymtab();
- self.logSections();
- self.logAtoms();
+ if (has_resolve_error) return error.FlushFailure;
}
- try self.writeLinkeditSegmentData();
-
- var codesig: ?CodeSignature = if (self.requiresCodeSignature()) blk: {
+ self.writeAtoms() catch |err| switch (err) {
+ error.ResolveFailed => return error.FlushFailure,
+ else => |e| {
+ try self.reportUnexpectedError("unexpected error while resolving relocations", .{});
+ return e;
+ },
+ };
+ try self.writeUnwindInfo();
+ try self.finalizeDyldInfoSections();
+ try self.writeSyntheticSections();
+
+ 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(self.getTextSegment().vmaddr, off);
+ try self.calcSymtabSize();
+ 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.
// We need to do this at this stage so that we have the load commands with proper values
// written out to the file.
// 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(getPageSize(target.cpu.arch));
- codesig.code_directory.ident = self.base.emit.sub_path;
- if (self.entitlements) |path| {
- try codesig.addEntitlements(gpa, path);
- }
+ var codesig = CodeSignature.init(self.getPageSize());
+ codesig.code_directory.ident = fs.path.basename(full_out_path);
+ if (self.entitlements) |path| try codesig.addEntitlements(gpa, path);
try self.writeCodeSignaturePadding(&codesig);
break :blk codesig;
} else null;
defer if (codesig) |*csig| csig.deinit(gpa);
- // Write load commands
- var lc_buffer = std.ArrayList(u8).init(arena);
- const lc_writer = lc_buffer.writer();
-
- try self.writeSegmentHeaders(lc_writer);
- try lc_writer.writeStruct(self.dyld_info_cmd);
- try lc_writer.writeStruct(self.symtab_cmd);
- try lc_writer.writeStruct(self.dysymtab_cmd);
- try load_commands.writeDylinkerLC(lc_writer);
-
- switch (output_mode) {
- .Exe => blk: {
- const seg_id = self.header_segment_cmd_index.?;
- const seg = self.segments.items[seg_id];
- const global = self.getEntryPoint() orelse break :blk;
- const sym = self.getSymbol(global);
-
- const addr: u64 = if (sym.undf())
- // In this case, the symbol has been resolved in one of dylibs and so we point
- // to the stub as its vmaddr value.
- self.getStubsEntryAddress(global).?
- else
- sym.n_value;
+ self.getLinkeditSegment().vmsize = mem.alignForward(
+ u64,
+ self.getLinkeditSegment().filesize,
+ self.getPageSize(),
+ );
- try lc_writer.writeStruct(macho.entry_point_command{
- .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
- .stacksize = self.base.stack_size,
- });
- },
- .Lib => if (comp.config.link_mode == .Dynamic) {
- try load_commands.writeDylibIdLC(self, lc_writer);
- },
- else => {},
+ const ncmds, const sizeofcmds, const uuid_cmd_offset = try self.writeLoadCommands();
+ try self.writeHeader(ncmds, sizeofcmds);
+ try self.writeUuid(uuid_cmd_offset, self.requiresCodeSig());
+
+ if (codesig) |*csig| {
+ try self.writeCodeSignature(csig); // code signing always comes last
+ const emit = self.base.emit;
+ try invalidateKernelCache(emit.directory.handle, emit.sub_path);
}
+}
- try load_commands.writeRpathLCs(self, lc_writer);
- try lc_writer.writeStruct(macho.source_version_command{
- .version = 0,
- });
- {
- const platform = Platform.fromTarget(target);
- const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(self);
- if (platform.isBuildVersionCompatible()) {
- try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
- } else if (platform.isVersionMinCompatible()) {
- try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
+/// --verbose-link output
+fn dumpArgv(self: *MachO, comp: *Compilation) !void {
+ const gpa = self.base.comp.gpa;
+ var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+ defer arena_allocator.deinit();
+ const arena = arena_allocator.allocator();
+
+ const directory = self.base.emit.directory;
+ const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
+ const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
+ if (fs.path.dirname(full_out_path)) |dirname| {
+ break :blk try fs.path.join(arena, &.{ dirname, path });
+ } else {
+ break :blk path;
}
- }
+ } else null;
- const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
- try lc_writer.writeStruct(self.uuid_cmd);
+ var argv = std.ArrayList([]const u8).init(arena);
- try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer);
+ try argv.append("zig");
- if (codesig != null) {
- try lc_writer.writeStruct(self.codesig_cmd);
+ if (self.base.isStaticLib()) {
+ try argv.append("ar");
+ } else {
+ try argv.append("ld");
}
- const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
- try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
- try self.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
- try self.writeUuid(comp, uuid_cmd_offset, codesig != null);
-
- if (codesig) |*csig| {
- try self.writeCodeSignature(comp, csig); // code signing always comes last
- const emit = self.base.emit;
- try invalidateKernelCache(emit.directory.handle, emit.sub_path);
+ if (self.base.isObject()) {
+ try argv.append("-r");
}
- if (self.d_sym) |*d_sym| {
- // Flush debug symbols bundle.
- try d_sym.flushModule(self);
- }
-}
+ try argv.append("-o");
+ try argv.append(full_out_path);
-/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
-/// Any change to the binary will effectively invalidate the kernel's cache
-/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
-/// linking we're modifying a binary in-place, this will end up with the kernel
-/// killing it on every subsequent run. To circumvent it, we will copy the file
-/// into a new inode, remove the original file, and rename the copy to match
-/// the original file. This is super messy, but there doesn't seem any other
-/// way to please the XNU.
-pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void {
- if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
- try dir.copyFile(sub_path, dir, sub_path, .{});
+ if (self.base.isRelocatable()) {
+ for (comp.objects) |obj| {
+ try argv.append(obj.path);
+ }
+
+ for (comp.c_object_table.keys()) |key| {
+ try argv.append(key.status.success.object_path);
+ }
+
+ if (module_obj_path) |p| {
+ try argv.append(p);
+ }
+ } else {
+ if (!self.base.isStatic()) {
+ try argv.append("-dynamic");
+ }
+
+ if (self.base.isDynLib()) {
+ try argv.append("-dylib");
+
+ if (self.install_name) |install_name| {
+ try argv.append("-install_name");
+ try argv.append(install_name);
+ }
+ }
+
+ try argv.append("-platform_version");
+ try argv.append(@tagName(self.platform.os_tag));
+ try argv.append(try std.fmt.allocPrint(arena, "{}", .{self.platform.version}));
+
+ if (self.sdk_version) |ver| {
+ try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
+ } else {
+ try argv.append(try std.fmt.allocPrint(arena, "{}", .{self.platform.version}));
+ }
+
+ if (comp.sysroot) |syslibroot| {
+ try argv.append("-syslibroot");
+ try argv.append(syslibroot);
+ }
+
+ for (self.base.rpath_list) |rpath| {
+ try argv.append("-rpath");
+ try argv.append(rpath);
+ }
+
+ if (self.pagezero_size) |size| {
+ try argv.append("-pagezero_size");
+ try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
+ }
+
+ if (self.headerpad_size) |size| {
+ try argv.append("-headerpad_size");
+ try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
+ }
+
+ if (self.headerpad_max_install_names) {
+ try argv.append("-headerpad_max_install_names");
+ }
+
+ if (self.base.gc_sections) {
+ try argv.append("-dead_strip");
+ }
+
+ if (self.dead_strip_dylibs) {
+ try argv.append("-dead_strip_dylibs");
+ }
+
+ if (self.entry_name) |entry_name| {
+ try argv.appendSlice(&.{ "-e", entry_name });
+ }
+
+ for (comp.objects) |obj| {
+ // TODO: verify this
+ if (obj.must_link) {
+ try argv.append("-force_load");
+ }
+ try argv.append(obj.path);
+ }
+
+ for (comp.c_object_table.keys()) |key| {
+ try argv.append(key.status.success.object_path);
+ }
+
+ if (module_obj_path) |p| {
+ try argv.append(p);
+ }
+
+ if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
+ if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
+
+ if (comp.config.link_libcpp) {
+ try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
+ try argv.append(comp.libcxx_static_lib.?.full_object_path);
+ }
+
+ try argv.append("-o");
+ try argv.append(full_out_path);
+
+ try argv.append("-lSystem");
+
+ for (comp.system_libs.keys()) |l_name| {
+ const info = comp.system_libs.get(l_name).?;
+ const arg = if (info.needed)
+ try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
+ else if (info.weak)
+ try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
+ else
+ try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
+ try argv.append(arg);
+ }
+
+ for (self.frameworks) |framework| {
+ const name = std.fs.path.stem(framework.path);
+ const arg = if (framework.needed)
+ try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
+ else if (framework.weak)
+ try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
+ else
+ try std.fmt.allocPrint(arena, "-framework {s}", .{name});
+ try argv.append(arg);
+ }
+
+ if (self.base.isDynLib() and self.base.allow_shlib_undefined) {
+ try argv.append("-undefined");
+ try argv.append("dynamic_lookup");
+ }
}
+
+ Compilation.dump_argv(argv.items);
}
-inline fn conformUuid(out: *[Md5.digest_length]u8) void {
- // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
- out[6] = (out[6] & 0x0F) | (3 << 4);
- out[8] = (out[8] & 0x3F) | 0x80;
+fn flushStaticLib(self: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
+ _ = comp;
+ _ = module_obj_path;
+
+ var err = try self.addErrorWithNotes(0);
+ try err.addMsg(self, "TODO implement flushStaticLib", .{});
+
+ return error.FlushFailure;
}
pub fn resolveLibSystem(
@@ -643,13 +896,12 @@ pub fn resolveLibSystem(
};
try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
- return;
+ return error.MissingLibSystem;
}
const libsystem_path = try arena.dupe(u8, test_path.items);
- try out_libs.put(libsystem_path, .{
+ try out_libs.append(.{
.needed = true,
- .weak = false,
.path = libsystem_path,
});
}
@@ -700,3288 +952,1819 @@ fn accessLibPath(
}
const ParseError = error{
- UnknownFileType,
+ MalformedObject,
+ MalformedArchive,
+ MalformedDylib,
+ MalformedTbd,
+ NotLibStub,
+ InvalidCpuArch,
InvalidTarget,
InvalidTargetFatLibrary,
- DylibAlreadyExists,
IncompatibleDylibVersion,
OutOfMemory,
Overflow,
InputOutput,
- MalformedArchive,
- NotLibStub,
EndOfStream,
FileSystem,
NotSupported,
+ Unhandled,
+ UnknownFileType,
} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError || tapi.TapiError;
-pub fn parsePositional(
- self: *MachO,
- file: std.fs.File,
- path: []const u8,
- must_link: bool,
- dependent_libs: anytype,
- ctx: *ParseErrorCtx,
-) ParseError!void {
+pub fn parsePositional(self: *MachO, path: []const u8, must_link: bool) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
+ if (try Object.isObject(path)) {
+ try self.parseObject(path);
+ } else {
+ try self.parseLibrary(.{ .path = path }, must_link);
+ }
+}
- if (Object.isObject(file)) {
- try self.parseObject(file, path, ctx);
+fn parseLibrary(self: *MachO, lib: SystemLib, must_link: bool) ParseError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ if (try fat.isFatLibrary(lib.path)) {
+ const fat_arch = try self.parseFatLibrary(lib.path);
+ if (try Archive.isArchive(lib.path, fat_arch)) {
+ try self.parseArchive(lib, must_link, fat_arch);
+ } else if (try Dylib.isDylib(lib.path, fat_arch)) {
+ _ = try self.parseDylib(lib, true, fat_arch);
+ } else return error.UnknownFileType;
+ } else if (try Archive.isArchive(lib.path, null)) {
+ try self.parseArchive(lib, must_link, null);
+ } else if (try Dylib.isDylib(lib.path, null)) {
+ _ = try self.parseDylib(lib, true, null);
} else {
- try self.parseLibrary(file, path, .{
- .path = null,
- .needed = false,
- .weak = false,
- }, must_link, false, null, dependent_libs, ctx);
+ _ = self.parseTbd(lib, true) catch |err| switch (err) {
+ error.MalformedTbd => return error.UnknownFileType,
+ else => |e| return e,
+ };
}
}
-fn parseObject(
- self: *MachO,
- file: std.fs.File,
- path: []const u8,
- ctx: *ParseErrorCtx,
-) ParseError!void {
+fn parseObject(self: *MachO, path: []const u8) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
const mtime: u64 = mtime: {
const stat = file.stat() catch break :mtime 0;
break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));
};
- const file_stat = try file.stat();
- const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow;
- const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null);
-
- var object = Object{
- .name = try gpa.dupe(u8, path),
+ const data = try file.readToEndAlloc(gpa, std.math.maxInt(u32));
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .object = .{
+ .path = try gpa.dupe(u8, path),
.mtime = mtime,
- .contents = contents,
- };
- errdefer object.deinit(gpa);
- try object.parse(gpa);
+ .data = data,
+ .index = index,
+ } });
+ try self.objects.append(gpa, index);
- const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) {
- macho.CPU_TYPE_ARM64 => .aarch64,
- macho.CPU_TYPE_X86_64 => .x86_64,
- else => unreachable,
- };
- const detected_platform = object.getPlatform();
- const this_cpu_arch = target.cpu.arch;
- const this_platform = Platform.fromTarget(target);
+ const object = self.getFile(index).?.object;
+ try object.parse(self);
+}
- if (this_cpu_arch != detected_cpu_arch or
- (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
- {
- const platform = detected_platform orelse this_platform;
- try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch));
- return error.InvalidTarget;
+fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch {
+ var buffer: [2]fat.Arch = undefined;
+ const fat_archs = try fat.parseArchs(path, &buffer);
+ const cpu_arch = self.getTarget().cpu.arch;
+ for (fat_archs) |arch| {
+ if (arch.tag == cpu_arch) return arch;
}
-
- try self.objects.append(gpa, object);
+ try self.reportParseError(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});
+ return error.InvalidCpuArch;
}
-pub fn parseLibrary(
- self: *MachO,
- file: std.fs.File,
- path: []const u8,
- lib: link.SystemLib,
- must_link: bool,
- is_dependent: bool,
- reexport_info: ?DylibReExportInfo,
- dependent_libs: anytype,
- ctx: *ParseErrorCtx,
-) ParseError!void {
+fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Arch) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
- const target = self.base.comp.root_mod.resolved_target.result;
-
- if (fat.isFatLibrary(file)) {
- const offset = try self.parseFatLibrary(file, target.cpu.arch, ctx);
- try file.seekTo(offset);
-
- if (Archive.isArchive(file, offset)) {
- try self.parseArchive(path, offset, must_link, ctx);
- } else if (Dylib.isDylib(file, offset)) {
- try self.parseDylib(file, path, offset, dependent_libs, .{
- .needed = lib.needed,
- .weak = lib.weak,
- .dependent = is_dependent,
- .reexport_info = reexport_info,
- }, ctx);
- } else return error.UnknownFileType;
- } else if (Archive.isArchive(file, 0)) {
- try self.parseArchive(path, 0, must_link, ctx);
- } else if (Dylib.isDylib(file, 0)) {
- try self.parseDylib(file, path, 0, dependent_libs, .{
- .needed = lib.needed,
- .weak = lib.weak,
- .dependent = is_dependent,
- .reexport_info = reexport_info,
- }, ctx);
- } else {
- self.parseLibStub(file, path, dependent_libs, .{
- .needed = lib.needed,
- .weak = lib.weak,
- .dependent = is_dependent,
- .reexport_info = reexport_info,
- }, ctx) catch |err| switch (err) {
- error.NotLibStub, error.UnexpectedToken => return error.UnknownFileType,
+ const gpa = self.base.comp.gpa;
+
+ const file = try std.fs.cwd().openFile(lib.path, .{});
+ defer file.close();
+
+ const data = if (fat_arch) |arch| blk: {
+ try file.seekTo(arch.offset);
+ const data = try gpa.alloc(u8, arch.size);
+ const nread = try file.readAll(data);
+ if (nread != arch.size) return error.InputOutput;
+ break :blk data;
+ } else try file.readToEndAlloc(gpa, std.math.maxInt(u32));
+
+ var archive = Archive{ .path = try gpa.dupe(u8, lib.path), .data = data };
+ defer archive.deinit(gpa);
+ try archive.parse(self);
+
+ var has_parse_error = false;
+ for (archive.objects.items) |extracted| {
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .object = extracted });
+ const object = &self.files.items(.data)[index].object;
+ object.index = index;
+ object.alive = must_link or lib.needed; // TODO: or self.options.all_load;
+ object.hidden = lib.hidden;
+ object.parse(self) catch |err| switch (err) {
+ error.MalformedObject,
+ error.InvalidCpuArch,
+ error.InvalidTarget,
+ => has_parse_error = true,
else => |e| return e,
};
+ try self.objects.append(gpa, index);
+
+ // Finally, we do a post-parse check for -ObjC to see if we need to force load this member
+ // anyhow.
+ object.alive = object.alive or (self.force_load_objc and object.hasObjc());
}
+ if (has_parse_error) return error.MalformedArchive;
}
-pub fn parseFatLibrary(
- self: *MachO,
- file: std.fs.File,
- cpu_arch: std.Target.Cpu.Arch,
- ctx: *ParseErrorCtx,
-) ParseError!u64 {
+fn parseDylib(self: *MachO, lib: SystemLib, explicit: bool, fat_arch: ?fat.Arch) ParseError!File.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
const gpa = self.base.comp.gpa;
- const fat_archs = try fat.parseArchs(gpa, file);
- defer gpa.free(fat_archs);
+ const file = try std.fs.cwd().openFile(lib.path, .{});
+ defer file.close();
+
+ const data = if (fat_arch) |arch| blk: {
+ try file.seekTo(arch.offset);
+ const data = try gpa.alloc(u8, arch.size);
+ const nread = try file.readAll(data);
+ if (nread != arch.size) return error.InputOutput;
+ break :blk data;
+ } else try file.readToEndAlloc(gpa, std.math.maxInt(u32));
+
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .dylib = .{
+ .path = try gpa.dupe(u8, lib.path),
+ .data = data,
+ .index = index,
+ .needed = lib.needed,
+ .weak = lib.weak,
+ .reexport = lib.reexport,
+ .explicit = explicit,
+ } });
+ const dylib = &self.files.items(.data)[index].dylib;
+ try dylib.parse(self);
+
+ try self.dylibs.append(gpa, index);
- const offset = for (fat_archs) |arch| {
- if (arch.tag == cpu_arch) break arch.offset;
- } else {
- try ctx.detected_targets.ensureUnusedCapacity(fat_archs.len);
- for (fat_archs) |arch| {
- ctx.detected_targets.appendAssumeCapacity(try ctx.arena().dupe(u8, @tagName(arch.tag)));
- }
- return error.InvalidTargetFatLibrary;
- };
- return offset;
+ return index;
}
-fn parseArchive(
- self: *MachO,
- path: []const u8,
- fat_offset: u64,
- must_link: bool,
- ctx: *ParseErrorCtx,
-) ParseError!void {
- const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
-
- // We take ownership of the file so that we can store it for the duration of symbol resolution.
- // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF?
- const file = try std.fs.cwd().openFile(path, .{});
- try file.seekTo(fat_offset);
-
- var archive = Archive{
- .file = file,
- .fat_offset = fat_offset,
- .name = try gpa.dupe(u8, path),
- };
- errdefer archive.deinit(gpa);
+fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
- try archive.parse(gpa, file.reader());
+ const gpa = self.base.comp.gpa;
+ const file = try std.fs.cwd().openFile(lib.path, .{});
+ defer file.close();
- // Verify arch and platform
- if (archive.toc.values().len > 0) {
- const offsets = archive.toc.values()[0].items;
- assert(offsets.len > 0);
- const off = offsets[0];
- var object = try archive.parseObject(gpa, off); // TODO we are doing all this work to pull the header only!
- defer object.deinit(gpa);
+ var lib_stub = LibStub.loadFromFile(gpa, file) catch return error.MalformedTbd; // TODO actually handle different errors
+ defer lib_stub.deinit();
- const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) {
- macho.CPU_TYPE_ARM64 => .aarch64,
- macho.CPU_TYPE_X86_64 => .x86_64,
- else => unreachable,
- };
- const detected_platform = object.getPlatform();
- const this_cpu_arch = target.cpu.arch;
- const this_platform = Platform.fromTarget(target);
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .dylib = .{
+ .path = try gpa.dupe(u8, lib.path),
+ .data = &[0]u8{},
+ .index = index,
+ .needed = lib.needed,
+ .weak = lib.weak,
+ .reexport = lib.reexport,
+ .explicit = explicit,
+ } });
+ const dylib = &self.files.items(.data)[index].dylib;
+ try dylib.parseTbd(self.getTarget().cpu.arch, self.platform, lib_stub, self);
+ try self.dylibs.append(gpa, index);
- if (this_cpu_arch != detected_cpu_arch or
- (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
- {
- const platform = detected_platform orelse this_platform;
- try ctx.detected_targets.append(try platform.allocPrintTarget(gpa, detected_cpu_arch));
- return error.InvalidTarget;
- }
- }
+ return index;
+}
- if (must_link) {
- // Get all offsets from the ToC
- var offsets = std.AutoArrayHashMap(u32, void).init(gpa);
- defer offsets.deinit();
- for (archive.toc.values()) |offs| {
- for (offs.items) |off| {
- _ = try offsets.getOrPut(off);
+/// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final
+/// image unless overriden by -no_implicit_dylibs.
+fn isHoisted(self: *MachO, install_name: []const u8) bool {
+ if (self.no_implicit_dylibs) return true;
+ if (std.fs.path.dirname(install_name)) |dirname| {
+ if (mem.startsWith(u8, dirname, "/usr/lib")) return true;
+ if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| {
+ const basename = std.fs.path.basename(install_name);
+ if (mem.indexOfScalar(u8, path, '.')) |index| {
+ if (mem.eql(u8, basename, path[0..index])) return true;
}
}
- for (offsets.keys()) |off| {
- const object = try archive.parseObject(gpa, off);
- try self.objects.append(gpa, object);
- }
- } else {
- try self.archives.append(gpa, archive);
}
+ return false;
}
-pub const DylibReExportInfo = struct {
- id: Dylib.Id,
- parent: u16,
-};
-
-const DylibOpts = struct {
- reexport_info: ?DylibReExportInfo = null,
- dependent: bool = false,
- needed: bool = false,
- weak: bool = false,
-};
-
-fn parseDylib(
- self: *MachO,
- file: std.fs.File,
- path: []const u8,
- offset: u64,
- dependent_libs: anytype,
- dylib_options: DylibOpts,
- ctx: *ParseErrorCtx,
-) ParseError!void {
- const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
- const file_stat = try file.stat();
- const file_size = math.cast(usize, file_stat.size - offset) orelse return error.Overflow;
-
- const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null);
- defer gpa.free(contents);
-
- var dylib = Dylib{ .path = try gpa.dupe(u8, path), .weak = dylib_options.weak };
- errdefer dylib.deinit(gpa);
-
- try dylib.parseFromBinary(
- gpa,
- @intCast(self.dylibs.items.len), // TODO defer it till later
- dependent_libs,
- path,
- contents,
- );
-
- const detected_cpu_arch: std.Target.Cpu.Arch = switch (dylib.header.?.cputype) {
- macho.CPU_TYPE_ARM64 => .aarch64,
- macho.CPU_TYPE_X86_64 => .x86_64,
- else => unreachable,
+fn accessPath(path: []const u8) !bool {
+ std.fs.cwd().access(path, .{}) catch |err| switch (err) {
+ error.FileNotFound => return false,
+ else => |e| return e,
};
- const detected_platform = dylib.getPlatform(contents);
- const this_cpu_arch = target.cpu.arch;
- const this_platform = Platform.fromTarget(target);
-
- if (this_cpu_arch != detected_cpu_arch or
- (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
- {
- const platform = detected_platform orelse this_platform;
- try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch));
- return error.InvalidTarget;
- }
-
- try self.addDylib(dylib, dylib_options, ctx);
+ return true;
}
-fn parseLibStub(
- self: *MachO,
- file: std.fs.File,
- path: []const u8,
- dependent_libs: anytype,
- dylib_options: DylibOpts,
- ctx: *ParseErrorCtx,
-) ParseError!void {
- const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
-
- var lib_stub = try LibStub.loadFromFile(gpa, file);
- defer lib_stub.deinit();
-
- if (lib_stub.inner.len == 0) return error.NotLibStub;
-
- // Verify target
- {
- var matcher = try Dylib.TargetMatcher.init(gpa, target);
- defer matcher.deinit();
-
- const first_tbd = lib_stub.inner[0];
- const targets = try first_tbd.targets(gpa);
- defer {
- for (targets) |t| gpa.free(t);
- gpa.free(targets);
- }
- if (!matcher.matchesTarget(targets)) {
- try ctx.detected_targets.ensureUnusedCapacity(targets.len);
- for (targets) |t| {
- ctx.detected_targets.appendAssumeCapacity(try ctx.arena().dupe(u8, t));
- }
- return error.InvalidTarget;
+fn resolveLib(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
+ const path = try std.fmt.allocPrint(arena, "lib{s}", .{name});
+ for (search_dirs) |dir| {
+ for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
+ const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
+ const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
+ if (try accessPath(full_path)) return full_path;
}
}
-
- var dylib = Dylib{ .path = try gpa.dupe(u8, path), .weak = dylib_options.weak };
- errdefer dylib.deinit(gpa);
-
- try dylib.parseFromStub(
- gpa,
- target,
- lib_stub,
- @intCast(self.dylibs.items.len), // TODO defer it till later
- dependent_libs,
- path,
- );
-
- try self.addDylib(dylib, dylib_options, ctx);
+ return null;
}
-fn addDylib(self: *MachO, dylib: Dylib, dylib_options: DylibOpts, ctx: *ParseErrorCtx) ParseError!void {
- if (dylib_options.reexport_info) |reexport_info| {
- if (dylib.id.?.current_version < reexport_info.id.compatibility_version) {
- ctx.detected_dylib_id = .{
- .parent = reexport_info.parent,
- .required_version = reexport_info.id.compatibility_version,
- .found_version = dylib.id.?.current_version,
- };
- return error.IncompatibleDylibVersion;
+fn resolveFramework(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
+ const prefix = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
+ const path = try std.fs.path.join(arena, &[_][]const u8{ prefix, name });
+ for (search_dirs) |dir| {
+ for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
+ const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
+ const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
+ if (try accessPath(full_path)) return full_path;
}
}
-
- const gpa = self.base.comp.gpa;
- const gop = try self.dylibs_map.getOrPut(gpa, dylib.id.?.name);
- if (gop.found_existing) return error.DylibAlreadyExists;
-
- gop.value_ptr.* = @as(u16, @intCast(self.dylibs.items.len));
- try self.dylibs.append(gpa, dylib);
-
- const should_link_dylib_even_if_unreachable = blk: {
- if (self.dead_strip_dylibs and !dylib_options.needed) break :blk false;
- break :blk !(dylib_options.dependent or self.referenced_dylibs.contains(gop.value_ptr.*));
- };
-
- if (should_link_dylib_even_if_unreachable) {
- try self.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {});
- }
+ return null;
}
-pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {
+fn parseDependentDylibs(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
- // At this point, we can now parse dependents of dylibs preserving the inclusion order of:
- // 1) anything on the linker line is parsed first
- // 2) afterwards, we parse dependents of the included dylibs
- // TODO this should not be performed if the user specifies `-flat_namespace` flag.
- // See ld64 manpages.
- const comp = self.base.comp;
- const gpa = comp.gpa;
-
- while (dependent_libs.readItem()) |dep_id| {
- defer dep_id.id.deinit(gpa);
-
- if (self.dylibs_map.contains(dep_id.id.name)) continue;
+ const gpa = self.base.comp.gpa;
+ const lib_dirs = self.lib_dirs;
+ const framework_dirs = self.framework_dirs;
+
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times
+ // in which case we should track that and return File.Index immediately instead re-parsing paths.
+
+ var has_errors = false;
+ var index: usize = 0;
+ while (index < self.dylibs.items.len) : (index += 1) {
+ const dylib_index = self.dylibs.items[index];
+
+ var dependents = std.ArrayList(File.Index).init(gpa);
+ defer dependents.deinit();
+ try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len);
+
+ const is_weak = self.getFile(dylib_index).?.dylib.weak;
+ for (self.getFile(dylib_index).?.dylib.dependents.items) |id| {
+ // We will search for the dependent dylibs in the following order:
+ // 1. Basename is in search lib directories or framework directories
+ // 2. If name is an absolute path, search as-is optionally prepending a syslibroot
+ // if specified.
+ // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with
+ // dependees list of rpaths, and search there.
+ // 4. Finally, just search the provided relative path directly in CWD.
+ const full_path = full_path: {
+ fail: {
+ const stem = std.fs.path.stem(id.name);
+ const framework_name = try std.fmt.allocPrint(gpa, "{s}.framework" ++ std.fs.path.sep_str ++ "{s}", .{
+ stem,
+ stem,
+ });
+ defer gpa.free(framework_name);
+
+ if (mem.endsWith(u8, id.name, framework_name)) {
+ // Framework
+ const full_path = (try resolveFramework(arena.allocator(), framework_dirs, stem)) orelse break :fail;
+ break :full_path full_path;
+ }
+
+ // Library
+ const lib_name = eatPrefix(stem, "lib") orelse stem;
+ const full_path = (try resolveLib(arena.allocator(), lib_dirs, lib_name)) orelse break :fail;
+ break :full_path full_path;
+ }
- const parent = &self.dylibs.items[dep_id.parent];
- const weak = parent.weak;
- const dirname = fs.path.dirname(dep_id.id.name) orelse "";
- const stem = fs.path.stem(dep_id.id.name);
+ if (std.fs.path.isAbsolute(id.name)) {
+ const path = if (self.base.comp.sysroot) |root|
+ try std.fs.path.join(arena.allocator(), &.{ root, id.name })
+ else
+ id.name;
+ for (&[_][]const u8{ "", ".tbd", ".dylib" }) |ext| {
+ const full_path = try std.fmt.allocPrint(arena.allocator(), "{s}{s}", .{ path, ext });
+ if (try accessPath(full_path)) break :full_path full_path;
+ }
+ }
- var arena_allocator = std.heap.ArenaAllocator.init(gpa);
- defer arena_allocator.deinit();
- const arena = arena_allocator.allocator();
+ if (eatPrefix(id.name, "@rpath/")) |path| {
+ const dylib = self.getFile(dylib_index).?.dylib;
+ for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| {
+ const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath;
+ const rel_path = try std.fs.path.join(arena.allocator(), &.{ prefix, path });
+ var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const full_path = std.fs.realpath(rel_path, &buffer) catch continue;
+ break :full_path full_path;
+ }
+ } else if (eatPrefix(id.name, "@loader_path/")) |_| {
+ try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
+ return error.Unhandled;
+ } else if (eatPrefix(id.name, "@executable_path/")) |_| {
+ try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
+ return error.Unhandled;
+ }
- var test_path = std.ArrayList(u8).init(arena);
- var checked_paths = std.ArrayList([]const u8).init(arena);
+ var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const full_path = std.fs.realpath(id.name, &buffer) catch {
+ dependents.appendAssumeCapacity(0);
+ continue;
+ };
+ break :full_path full_path;
+ };
+ const lib = SystemLib{
+ .path = full_path,
+ .weak = is_weak,
+ };
+ const file_index = file_index: {
+ if (try fat.isFatLibrary(lib.path)) {
+ const fat_arch = try self.parseFatLibrary(lib.path);
+ if (try Dylib.isDylib(lib.path, fat_arch)) {
+ break :file_index try self.parseDylib(lib, false, fat_arch);
+ } else break :file_index @as(File.Index, 0);
+ } else if (try Dylib.isDylib(lib.path, null)) {
+ break :file_index try self.parseDylib(lib, false, null);
+ } else {
+ const file_index = self.parseTbd(lib, false) catch |err| switch (err) {
+ error.MalformedTbd => @as(File.Index, 0),
+ else => |e| return e,
+ };
+ break :file_index file_index;
+ }
+ };
+ dependents.appendAssumeCapacity(file_index);
+ }
- success: {
- if (comp.sysroot) |root| {
- const dir = try fs.path.join(arena, &[_][]const u8{ root, dirname });
- if (try accessLibPath(gpa, &test_path, &checked_paths, dir, stem)) break :success;
+ const dylib = self.getFile(dylib_index).?.dylib;
+ for (dylib.dependents.items, dependents.items) |id, file_index| {
+ if (self.getFile(file_index)) |file| {
+ const dep_dylib = file.dylib;
+ dep_dylib.hoisted = self.isHoisted(id.name);
+ if (self.getFile(dep_dylib.umbrella) == null) {
+ dep_dylib.umbrella = dylib.umbrella;
+ }
+ if (!dep_dylib.hoisted) {
+ const umbrella = dep_dylib.getUmbrella(self);
+ for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| {
+ try umbrella.addExport(gpa, dep_dylib.getString(off), flags);
+ }
+ try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len);
+ for (dep_dylib.rpaths.keys()) |rpath| {
+ umbrella.rpaths.putAssumeCapacity(rpath, {});
+ }
+ }
+ } else {
+ try self.reportDependencyError(
+ dylib.getUmbrella(self).index,
+ id.name,
+ "unable to resolve dependency",
+ .{},
+ );
+ has_errors = true;
}
-
- if (try accessLibPath(gpa, &test_path, &checked_paths, dirname, stem)) break :success;
-
- try self.reportMissingLibraryError(
- checked_paths.items,
- "missing dynamic library dependency: '{s}'",
- .{dep_id.id.name},
- );
- continue;
}
-
- const full_path = test_path.items;
- const file = try std.fs.cwd().openFile(full_path, .{});
- defer file.close();
-
- log.debug("parsing dependency {s} at fully resolved path {s}", .{ dep_id.id.name, full_path });
-
- var parse_ctx = ParseErrorCtx.init(gpa);
- defer parse_ctx.deinit();
-
- self.parseLibrary(file, full_path, .{
- .path = null,
- .needed = false,
- .weak = weak,
- }, false, true, dep_id, dependent_libs, &parse_ctx) catch |err|
- try self.handleAndReportParseError(full_path, err, &parse_ctx);
-
- // TODO I think that it would be nice to rewrite this error to include metadata for failed dependency
- // in addition to parsing error
}
-}
-pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []u8) !void {
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbol(self);
- const section = self.sections.get(sym.n_sect - 1);
- const file_offset = section.header.offset + sym.n_value - section.header.addr;
- log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset });
+ if (has_errors) return error.MissingLibraryDependencies;
+}
- // Gather relocs which can be resolved.
+pub fn addUndefinedGlobals(self: *MachO) !void {
const gpa = self.base.comp.gpa;
- var relocs = std.ArrayList(*Relocation).init(gpa);
- defer relocs.deinit();
-
- if (self.relocs.getPtr(atom_index)) |rels| {
- try relocs.ensureTotalCapacityPrecise(rels.items.len);
- for (rels.items) |*reloc| {
- if (reloc.isResolvable(self) and reloc.dirty) {
- relocs.appendAssumeCapacity(reloc);
- }
- }
- }
- Atom.resolveRelocations(self, atom_index, relocs.items, code);
-
- if (is_hot_update_compatible) {
- if (self.hot_state.mach_task) |task| {
- self.writeToMemory(task, section.segment_index, sym.n_value, code) catch |err| {
- log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)});
- };
- }
+ try self.undefined_symbols.ensureUnusedCapacity(gpa, self.base.comp.force_undefined_symbols.keys().len);
+ for (self.base.comp.force_undefined_symbols.keys()) |name| {
+ const off = try self.strings.insert(gpa, name);
+ const gop = try self.getOrCreateGlobal(off);
+ self.undefined_symbols.appendAssumeCapacity(gop.index);
}
- try self.base.file.?.pwriteAll(code, file_offset);
-
- // Now we can mark the relocs as resolved.
- while (relocs.popOrNull()) |reloc| {
- reloc.dirty = false;
+ if (!self.base.isDynLib() and self.entry_name != null) {
+ const off = try self.strings.insert(gpa, self.entry_name.?);
+ const gop = try self.getOrCreateGlobal(off);
+ self.entry_index = gop.index;
}
-}
-
-fn writeToMemory(self: *MachO, task: std.os.darwin.MachTask, segment_index: u8, addr: u64, code: []const u8) !void {
- const segment = self.segments.items[segment_index];
- const target = self.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const nwritten = if (!segment.isWriteable())
- try task.writeMemProtected(addr, code, cpu_arch)
- else
- try task.writeMem(addr, code, cpu_arch);
- if (nwritten != code.len) return error.InputOutput;
-}
-
-fn writeOffsetTableEntry(self: *MachO, index: usize) !void {
- const sect_id = self.got_section_index.?;
- if (self.got_table_count_dirty) {
- const needed_size = self.got_table.entries.items.len * @sizeOf(u64);
- try self.growSection(sect_id, needed_size);
- self.got_table_count_dirty = false;
+ {
+ const off = try self.strings.insert(gpa, "dyld_stub_binder");
+ const gop = try self.getOrCreateGlobal(off);
+ self.dyld_stub_binder_index = gop.index;
}
- const header = &self.sections.items(.header)[sect_id];
- const segment_index = self.sections.items(.segment_index)[sect_id];
- const entry = self.got_table.entries.items[index];
- const entry_value = self.getSymbol(entry).n_value;
- const entry_offset = index * @sizeOf(u64);
- const file_offset = header.offset + entry_offset;
- const vmaddr = header.addr + entry_offset;
-
- log.debug("writing GOT entry {d}: @{x} => {x}", .{ index, vmaddr, entry_value });
-
- var buf: [@sizeOf(u64)]u8 = undefined;
- mem.writeInt(u64, &buf, entry_value, .little);
- try self.base.file.?.pwriteAll(&buf, file_offset);
-
- if (is_hot_update_compatible) {
- if (self.hot_state.mach_task) |task| {
- self.writeToMemory(task, segment_index, vmaddr, &buf) catch |err| {
- log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)});
- };
- }
+ {
+ const off = try self.strings.insert(gpa, "_objc_msgSend");
+ const gop = try self.getOrCreateGlobal(off);
+ self.objc_msg_send_index = gop.index;
}
}
-fn writeStubHelperPreamble(self: *MachO) !void {
- if (self.stub_helper_preamble_allocated) return;
-
- const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const size = stubs.stubHelperPreambleSize(cpu_arch);
-
- var buf = try std.ArrayList(u8).initCapacity(gpa, size);
- defer buf.deinit();
-
- const dyld_private_addr = self.getAtom(self.dyld_private_atom_index.?).getSymbol(self).n_value;
- const dyld_stub_binder_got_addr = blk: {
- const index = self.got_table.lookup.get(self.getGlobalByIndex(self.dyld_stub_binder_index.?)).?;
- const header = self.sections.items(.header)[self.got_section_index.?];
- break :blk header.addr + @sizeOf(u64) * index;
- };
- const header = self.sections.items(.header)[self.stub_helper_section_index.?];
-
- try stubs.writeStubHelperPreambleCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = header.addr,
- .dyld_private_addr = dyld_private_addr,
- .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
- }, buf.writer());
- try self.base.file.?.pwriteAll(buf.items, header.offset);
-
- self.stub_helper_preamble_allocated = true;
-}
-
-fn writeStubTableEntry(self: *MachO, index: usize) !void {
- const target = self.base.comp.root_mod.resolved_target.result;
- const stubs_sect_id = self.stubs_section_index.?;
- const stub_helper_sect_id = self.stub_helper_section_index.?;
- const laptr_sect_id = self.la_symbol_ptr_section_index.?;
-
- const cpu_arch = target.cpu.arch;
- const stub_entry_size = stubs.stubSize(cpu_arch);
- const stub_helper_entry_size = stubs.stubHelperSize(cpu_arch);
- const stub_helper_preamble_size = stubs.stubHelperPreambleSize(cpu_arch);
-
- if (self.stub_table_count_dirty) {
- // We grow all 3 sections one by one.
- {
- const needed_size = stub_entry_size * self.stub_table.entries.items.len;
- try self.growSection(stubs_sect_id, needed_size);
- }
- {
- const needed_size = stub_helper_preamble_size + stub_helper_entry_size * self.stub_table.entries.items.len;
- try self.growSection(stub_helper_sect_id, needed_size);
- }
- {
- const needed_size = @sizeOf(u64) * self.stub_table.entries.items.len;
- try self.growSection(laptr_sect_id, needed_size);
- }
- self.stub_table_count_dirty = false;
- }
-
- const gpa = self.base.comp.gpa;
+/// When resolving symbols, we approach the problem similarly to `mold`.
+/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
+/// 2. Resolve symbols across all shared objects.
+/// 3. Mark live objects (see `MachO.markLive`)
+/// 4. Reset state of all resolved globals since we will redo this bit on the pruned set.
+/// 5. Remove references to dead objects/shared objects
+/// 6. Re-run symbol resolution on pruned objects and shared objects sets.
+pub fn resolveSymbols(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- const stubs_header = self.sections.items(.header)[stubs_sect_id];
- const stub_helper_header = self.sections.items(.header)[stub_helper_sect_id];
- const laptr_header = self.sections.items(.header)[laptr_sect_id];
+ // Resolve symbols in the ZigObject. For now, we assume that it's always live.
+ if (self.getZigObject()) |zo| zo.asFile().resolveSymbols(self);
+ // Resolve symbols on the set of all objects and shared objects (even if some are unneeded).
+ for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self);
+ for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self);
- const entry = self.stub_table.entries.items[index];
- const stub_addr: u64 = stubs_header.addr + stub_entry_size * index;
- const stub_helper_addr: u64 = stub_helper_header.addr + stub_helper_preamble_size + stub_helper_entry_size * index;
- const laptr_addr: u64 = laptr_header.addr + @sizeOf(u64) * index;
+ // Mark live objects.
+ self.markLive();
- log.debug("writing stub entry {d}: @{x} => '{s}'", .{ index, stub_addr, self.getSymbolName(entry) });
+ // Reset state of all globals after marking live objects.
+ if (self.getZigObject()) |zo| zo.asFile().resetGlobals(self);
+ for (self.objects.items) |index| self.getFile(index).?.resetGlobals(self);
+ for (self.dylibs.items) |index| self.getFile(index).?.resetGlobals(self);
- {
- var buf = try std.ArrayList(u8).initCapacity(gpa, stub_entry_size);
- defer buf.deinit();
- try stubs.writeStubCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = stub_addr,
- .target_addr = laptr_addr,
- }, buf.writer());
- const off = stubs_header.offset + stub_entry_size * index;
- try self.base.file.?.pwriteAll(buf.items, off);
+ // Prune dead objects.
+ var i: usize = 0;
+ while (i < self.objects.items.len) {
+ const index = self.objects.items[i];
+ if (!self.getFile(index).?.object.alive) {
+ _ = self.objects.orderedRemove(i);
+ } else i += 1;
}
- {
- var buf = try std.ArrayList(u8).initCapacity(gpa, stub_helper_entry_size);
- defer buf.deinit();
- try stubs.writeStubHelperCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = stub_helper_addr,
- .target_addr = stub_helper_header.addr,
- }, buf.writer());
- const off = stub_helper_header.offset + stub_helper_preamble_size + stub_helper_entry_size * index;
- try self.base.file.?.pwriteAll(buf.items, off);
- }
+ // Re-resolve the symbols.
+ if (self.getZigObject()) |zo| zo.resolveSymbols(self);
+ for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self);
+ for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self);
+}
- {
- var buf: [@sizeOf(u64)]u8 = undefined;
- mem.writeInt(u64, &buf, stub_helper_addr, .little);
- const off = laptr_header.offset + @sizeOf(u64) * index;
- try self.base.file.?.pwriteAll(&buf, off);
- }
+fn markLive(self: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
- // TODO: generating new stub entry will require pulling the address of the symbol from the
- // target dylib when updating directly in memory.
- if (is_hot_update_compatible) {
- if (self.hot_state.mach_task) |_| {
- @panic("TODO: update a stub entry in memory");
+ for (self.undefined_symbols.items) |index| {
+ if (self.getSymbol(index).getFile(self)) |file| {
+ if (file == .object) file.object.alive = true;
}
}
-}
-
-fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void {
- log.debug("marking relocs dirty by target: {}", .{target});
- // TODO: reverse-lookup might come in handy here
- for (self.relocs.values()) |*relocs| {
- for (relocs.items) |*reloc| {
- if (!reloc.target.eql(target)) continue;
- reloc.dirty = true;
+ if (self.entry_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self)) |file| {
+ if (file == .object) file.object.alive = true;
}
}
+ if (self.getZigObject()) |zo| zo.markLive(self);
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ if (object.alive) object.markLive(self);
+ }
}
-fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void {
- log.debug("marking relocs dirty by address: {x}", .{addr});
+fn resolveSyntheticSymbols(self: *MachO) !void {
+ const internal = self.getInternalObject() orelse return;
- const got_moved = blk: {
- const sect_id = self.got_section_index orelse break :blk false;
- break :blk self.sections.items(.header)[sect_id].addr > addr;
- };
- const stubs_moved = blk: {
- const sect_id = self.stubs_section_index orelse break :blk false;
- break :blk self.sections.items(.header)[sect_id].addr > addr;
- };
-
- for (self.relocs.values()) |*relocs| {
- for (relocs.items) |*reloc| {
- if (reloc.isGotIndirection()) {
- reloc.dirty = reloc.dirty or got_moved;
- } else if (reloc.isStubTrampoline(self)) {
- reloc.dirty = reloc.dirty or stubs_moved;
- } else {
- const target_addr = reloc.getTargetBaseAddress(self) orelse continue;
- if (target_addr > addr) reloc.dirty = true;
- }
- }
+ if (!self.base.isDynLib()) {
+ self.mh_execute_header_index = try internal.addSymbol("__mh_execute_header", self);
+ const sym = self.getSymbol(self.mh_execute_header_index.?);
+ sym.flags.@"export" = true;
+ sym.flags.dyn_ref = true;
+ sym.visibility = .global;
+ } else {
+ self.mh_dylib_header_index = try internal.addSymbol("__mh_dylib_header", self);
}
- // TODO: dirty only really affected GOT cells
- for (self.got_table.entries.items) |entry| {
- const target_addr = self.getSymbol(entry).n_value;
- if (target_addr > addr) {
- self.got_table_contents_dirty = true;
- break;
- }
- }
+ self.dso_handle_index = try internal.addSymbol("___dso_handle", self);
+ self.dyld_private_index = try internal.addSymbol("dyld_private", self);
{
- const stubs_addr = self.getSegment(self.stubs_section_index.?).vmaddr;
- const stub_helper_addr = self.getSegment(self.stub_helper_section_index.?).vmaddr;
- const laptr_addr = self.getSegment(self.la_symbol_ptr_section_index.?).vmaddr;
- if (stubs_addr > addr or stub_helper_addr > addr or laptr_addr > addr)
- self.stub_table_contents_dirty = true;
- }
-}
-
-pub fn allocateSpecialSymbols(self: *MachO) !void {
- for (&[_][]const u8{
- "___dso_handle",
- "__mh_execute_header",
- }) |name| {
- const global = self.getGlobal(name) orelse continue;
- if (global.getFile() != null) continue;
- const sym = self.getSymbolPtr(global);
- const seg = self.getSegment(self.text_section_index.?);
- sym.n_sect = self.text_section_index.? + 1;
- sym.n_value = seg.vmaddr;
-
- log.debug("allocating {s}(@0x{x},sect({d})) at the start of {s}", .{
- name,
- sym.n_value,
- sym.n_sect,
- seg.segName(),
- });
- }
-
- for (self.globals.items) |global| {
- const sym = self.getSymbolPtr(global);
- if (sym.n_desc != N_BOUNDARY) continue;
- if (self.getSectionBoundarySymbol(global)) |bsym| {
- const sect_id = self.getSectionByName(bsym.segname, bsym.sectname) orelse {
- try self.reportUnresolvedBoundarySymbol(self.getSymbolName(global), "section not found: {s},{s}", .{
- bsym.segname, bsym.sectname,
- });
- continue;
- };
- const sect = self.sections.items(.header)[sect_id];
- sym.n_sect = sect_id + 1;
- sym.n_value = switch (bsym.kind) {
- .start => sect.addr,
- .stop => sect.addr + sect.size,
- };
-
- log.debug("allocating {s} at @0x{x} sect({d})", .{
- self.getSymbolName(global),
- sym.n_value,
- sym.n_sect,
- });
-
- continue;
+ const gpa = self.base.comp.gpa;
+ var boundary_symbols = std.AutoHashMap(Symbol.Index, void).init(gpa);
+ defer boundary_symbols.deinit();
+
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ for (object.symbols.items, 0..) |sym_index, i| {
+ const nlist = object.symtab.items(.nlist)[i];
+ const name = self.getSymbol(sym_index).getName(self);
+ if (!nlist.undf() or !nlist.ext()) continue;
+ if (mem.startsWith(u8, name, "segment$start$") or
+ mem.startsWith(u8, name, "segment$stop$") or
+ mem.startsWith(u8, name, "section$start$") or
+ mem.startsWith(u8, name, "section$stop$"))
+ {
+ _ = try boundary_symbols.put(sym_index, {});
+ }
+ }
}
- if (self.getSegmentBoundarySymbol(global)) |bsym| {
- const seg_id = self.getSegmentByName(bsym.segname) orelse {
- try self.reportUnresolvedBoundarySymbol(self.getSymbolName(global), "segment not found: {s}", .{
- bsym.segname,
- });
- continue;
- };
- const seg = self.segments.items[seg_id];
- sym.n_value = switch (bsym.kind) {
- .start => seg.vmaddr,
- .stop => seg.vmaddr + seg.vmsize,
- };
-
- log.debug("allocating {s} at @0x{x} ", .{ self.getSymbolName(global), sym.n_value });
+ try self.boundary_symbols.ensureTotalCapacityPrecise(gpa, boundary_symbols.count());
- continue;
+ var it = boundary_symbols.iterator();
+ while (it.next()) |entry| {
+ _ = try internal.addSymbol(self.getSymbol(entry.key_ptr.*).getName(self), self);
+ self.boundary_symbols.appendAssumeCapacity(entry.key_ptr.*);
}
}
}
-const CreateAtomOpts = struct {
- size: u64 = 0,
- alignment: Alignment = .@"1",
-};
-
-pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Index {
- const gpa = self.base.comp.gpa;
- const index = @as(Atom.Index, @intCast(self.atoms.items.len));
- const atom = try self.atoms.addOne(gpa);
- atom.* = .{};
- atom.sym_index = sym_index;
- atom.size = opts.size;
- atom.alignment = opts.alignment;
- log.debug("creating ATOM(%{d}) at index {d}", .{ sym_index, index });
- return index;
-}
-
-pub fn createTentativeDefAtoms(self: *MachO) !void {
- const gpa = self.base.comp.gpa;
-
- for (self.globals.items) |global| {
- const sym = self.getSymbolPtr(global);
- if (!sym.tentative()) continue;
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
-
- log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{
- global.sym_index, self.getSymbolName(global), global.file,
- });
-
- // Convert any tentative definition into a regular symbol and allocate
- // text blocks for each tentative definition.
- const size = sym.n_value;
- const alignment = (sym.n_desc >> 8) & 0x0f;
-
- if (self.bss_section_index == null) {
- self.bss_section_index = try self.initSection("__DATA", "__bss", .{
- .flags = macho.S_ZEROFILL,
- });
- }
-
- sym.* = .{
- .n_strx = sym.n_strx,
- .n_type = macho.N_SECT | macho.N_EXT,
- .n_sect = self.bss_section_index.? + 1,
- .n_desc = 0,
- .n_value = 0,
- };
-
- const atom_index = try self.createAtom(global.sym_index, .{
- .size = size,
- .alignment = @enumFromInt(alignment),
- });
- const atom = self.getAtomPtr(atom_index);
- atom.file = global.file;
-
- self.addAtomToSection(atom_index);
-
- assert(global.getFile() != null);
- const object = &self.objects.items[global.getFile().?];
- try object.atoms.append(gpa, atom_index);
- object.atom_by_index_table[global.sym_index] = atom_index;
+fn convertTentativeDefinitions(self: *MachO) !void {
+ for (self.objects.items) |index| {
+ try self.getFile(index).?.object.convertTentativeDefinitions(self);
}
}
-pub fn createDyldPrivateAtom(self: *MachO) !void {
- if (self.dyld_private_atom_index != null) return;
-
- const sym_index = try self.allocateSymbol();
- const atom_index = try self.createAtom(sym_index, .{
- .size = @sizeOf(u64),
- .alignment = .@"8",
- });
+fn createObjcSections(self: *MachO) !void {
const gpa = self.base.comp.gpa;
- try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
-
- if (self.data_section_index == null) {
- self.data_section_index = try self.initSection("__DATA", "__data", .{});
+ var objc_msgsend_syms = std.AutoArrayHashMap(Symbol.Index, void).init(gpa);
+ defer objc_msgsend_syms.deinit();
+
+ for (self.objects.items) |index| {
+ const object = self.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 = self.getSymbol(sym_index);
+ if (sym.getFile(self) != null) continue;
+ if (mem.startsWith(u8, sym.getName(self), "_objc_msgSend$")) {
+ _ = try objc_msgsend_syms.put(sym_index, {});
+ }
+ }
}
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbolPtr(self);
- sym.n_type = macho.N_SECT;
- sym.n_sect = self.data_section_index.? + 1;
- self.dyld_private_atom_index = atom_index;
-
- switch (self.mode) {
- .zld => self.addAtomToSection(atom_index),
- .incremental => {
- sym.n_value = try self.allocateAtom(atom_index, atom.size, .@"8");
- log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value});
- var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
- try self.writeAtom(atom_index, &buffer);
- },
+ for (objc_msgsend_syms.keys()) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ sym.value = 0;
+ sym.atom = 0;
+ sym.nlist_idx = 0;
+ sym.file = self.internal_object.?;
+ sym.flags = .{};
+ sym.visibility = .hidden;
+ const object = self.getInternalObject().?;
+ const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?;
+ const selrefs_index = try object.addObjcMsgsendSections(name, self);
+ try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self);
+ try object.symbols.append(gpa, sym_index);
}
}
-fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index {
+fn claimUnresolved(self: *MachO) error{OutOfMemory}!void {
const gpa = self.base.comp.gpa;
- const size = 3 * @sizeOf(u64);
- const required_alignment: Alignment = .@"1";
- const sym_index = try self.allocateSymbol();
- const atom_index = try self.createAtom(sym_index, .{});
- try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
- self.getAtomPtr(atom_index).size = size;
-
- const sym = self.getAtom(atom_index).getSymbolPtr(self);
- sym.n_type = macho.N_SECT;
- sym.n_sect = self.thread_vars_section_index.? + 1;
- sym.n_strx = try self.strtab.insert(gpa, sym_name);
- sym.n_value = try self.allocateAtom(atom_index, size, required_alignment);
-
- log.debug("allocated threadlocal descriptor atom '{s}' at 0x{x}", .{ sym_name, sym.n_value });
-
- try Atom.addRelocation(self, atom_index, .{
- .type = .tlv_initializer,
- .target = target,
- .offset = 0x10,
- .addend = 0,
- .pcrel = false,
- .length = 3,
- });
- var code: [size]u8 = undefined;
- @memset(&code, 0);
- try self.writeAtom(atom_index, &code);
+ var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
+ defer objects.deinit();
+ if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
+ objects.appendSliceAssumeCapacity(self.objects.items);
- return atom_index;
-}
+ for (objects.items) |index| {
+ const file = self.getFile(index).?;
-pub fn createMhExecuteHeaderSymbol(self: *MachO) !void {
- const output_mode = self.base.comp.config.output_mode;
- if (output_mode != .Exe) return;
+ for (file.getSymbols(), 0..) |sym_index, i| {
+ const nlist_idx = @as(Symbol.Index, @intCast(i));
+ const nlist = switch (file) {
+ .object => |x| x.symtab.items(.nlist)[nlist_idx],
+ .zig_object => |x| x.symtab.items(.nlist)[nlist_idx],
+ else => unreachable,
+ };
+ if (!nlist.ext()) continue;
+ if (!nlist.undf()) continue;
- const gpa = self.base.comp.gpa;
- const sym_index = try self.allocateSymbol();
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
- const sym = self.getSymbolPtr(sym_loc);
- sym.* = .{
- .n_strx = try self.strtab.insert(gpa, "__mh_execute_header"),
- .n_type = macho.N_SECT | macho.N_EXT,
- .n_sect = 0,
- .n_desc = macho.REFERENCED_DYNAMICALLY,
- .n_value = 0,
- };
+ const sym = self.getSymbol(sym_index);
+ if (sym.getFile(self) != null) continue;
- const gop = try self.getOrPutGlobalPtr("__mh_execute_header");
- if (gop.found_existing) {
- const global = gop.value_ptr.*;
- if (global.getFile()) |file| {
- const global_object = &self.objects.items[file];
- global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?;
+ const is_import = switch (self.undefined_treatment) {
+ .@"error" => false,
+ .warn, .suppress => nlist.weakRef(),
+ .dynamic_lookup => true,
+ };
+ if (is_import) {
+ sym.value = 0;
+ sym.atom = 0;
+ sym.nlist_idx = 0;
+ sym.file = self.internal_object.?;
+ sym.flags.weak = false;
+ sym.flags.weak_ref = nlist.weakRef();
+ sym.flags.import = is_import;
+ sym.visibility = .global;
+ try self.getInternalObject().?.symbols.append(self.base.comp.gpa, sym_index);
+ }
}
}
- gop.value_ptr.* = sym_loc;
}
-pub fn createDsoHandleSymbol(self: *MachO) !void {
- const global = self.getGlobalPtr("___dso_handle") orelse return;
- if (!self.getSymbol(global.*).undf()) return;
-
+fn checkDuplicates(self: *MachO) !void {
const gpa = self.base.comp.gpa;
- const sym_index = try self.allocateSymbol();
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
- const sym = self.getSymbolPtr(sym_loc);
- sym.* = .{
- .n_strx = try self.strtab.insert(gpa, "___dso_handle"),
- .n_type = macho.N_SECT | macho.N_EXT,
- .n_sect = 0,
- .n_desc = macho.N_WEAK_DEF,
- .n_value = 0,
- };
- const global_index = self.getGlobalIndex("___dso_handle").?;
- if (global.getFile()) |file| {
- const global_object = &self.objects.items[file];
- global_object.globals_lookup[global.sym_index] = global_index;
- }
- global.* = sym_loc;
- _ = self.unresolved.swapRemove(self.getGlobalIndex("___dso_handle").?);
-}
-pub fn resolveSymbols(self: *MachO) !void {
- const comp = self.base.comp;
- const output_mode = comp.config.output_mode;
- // We add the specified entrypoint as the first unresolved symbols so that
- // we search for it in libraries should there be no object files specified
- // on the linker line.
- if (output_mode == .Exe) {
- if (self.entry_name) |entry_name| {
- _ = try self.addUndefined(entry_name, .{});
+ var dupes = std.AutoArrayHashMap(Symbol.Index, std.ArrayListUnmanaged(File.Index)).init(gpa);
+ defer {
+ for (dupes.values()) |*list| {
+ list.deinit(gpa);
}
+ dupes.deinit();
}
- // Force resolution of any symbols requested by the user.
- for (comp.force_undefined_symbols.keys()) |sym_name| {
- _ = try self.addUndefined(sym_name, .{});
+ if (self.getZigObject()) |zo| {
+ try zo.checkDuplicates(&dupes, self);
}
- for (self.objects.items, 0..) |_, object_id| {
- try self.resolveSymbolsInObject(@as(u32, @intCast(object_id)));
+ for (self.objects.items) |index| {
+ try self.getFile(index).?.object.checkDuplicates(&dupes, self);
}
- try self.resolveSymbolsInArchives();
-
- // Finally, force resolution of dyld_stub_binder if there are imports
- // requested.
- if (self.unresolved.count() > 0 and self.dyld_stub_binder_index == null) {
- self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .{ .add_got = true });
- }
- if (comp.config.any_non_single_threaded and self.mode == .incremental) {
- _ = try self.addUndefined("__tlv_bootstrap", .{});
- }
-
- try self.resolveSymbolsInDylibs();
-
- try self.createMhExecuteHeaderSymbol();
- try self.createDsoHandleSymbol();
- try self.resolveSymbolsAtLoading();
-
- // Final stop, check if unresolved contain any of the special magic boundary symbols
- // * section$start$
- // * section$stop$
- // * segment$start$
- // * segment$stop$
- try self.resolveBoundarySymbols();
+ try self.reportDuplicates(dupes);
}
-fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
+fn markImportsAndExports(self: *MachO) error{OutOfMemory}!void {
const gpa = self.base.comp.gpa;
- const sym = self.getSymbol(current);
- const sym_name = self.getSymbolName(current);
-
- const gop = try self.getOrPutGlobalPtr(sym_name);
- if (!gop.found_existing) {
- gop.value_ptr.* = current;
- if (sym.undf() and !sym.tentative()) {
- try self.unresolved.putNoClobber(gpa, self.getGlobalIndex(sym_name).?, {});
- }
- return;
- }
- const global_index = self.getGlobalIndex(sym_name).?;
- const global = gop.value_ptr.*;
- const global_sym = self.getSymbol(global);
-
- // Cases to consider: sym vs global_sym
- // 1. strong(sym) and strong(global_sym) => error
- // 2. strong(sym) and weak(global_sym) => sym
- // 3. strong(sym) and tentative(global_sym) => sym
- // 4. strong(sym) and undf(global_sym) => sym
- // 5. weak(sym) and strong(global_sym) => global_sym
- // 6. weak(sym) and tentative(global_sym) => sym
- // 7. weak(sym) and undf(global_sym) => sym
- // 8. tentative(sym) and strong(global_sym) => global_sym
- // 9. tentative(sym) and weak(global_sym) => global_sym
- // 10. tentative(sym) and tentative(global_sym) => pick larger
- // 11. tentative(sym) and undf(global_sym) => sym
- // 12. undf(sym) and * => global_sym
- //
- // Reduces to:
- // 1. strong(sym) and strong(global_sym) => error
- // 2. * and strong(global_sym) => global_sym
- // 3. weak(sym) and weak(global_sym) => global_sym
- // 4. tentative(sym) and tentative(global_sym) => pick larger
- // 5. undf(sym) and * => global_sym
- // 6. else => sym
-
- const sym_is_strong = sym.sect() and !(sym.weakDef() or sym.pext());
- const global_is_strong = global_sym.sect() and !(global_sym.weakDef() or global_sym.pext());
- const sym_is_weak = sym.sect() and (sym.weakDef() or sym.pext());
- const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext());
-
- if (sym_is_strong and global_is_strong) {
- // TODO redo this logic with corresponding logic in updateExports to avoid this
- // ugly check.
- if (self.mode == .zld) {
- try self.reportSymbolCollision(global, current);
+ var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
+ defer objects.deinit();
+ if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
+ objects.appendSliceAssumeCapacity(self.objects.items);
+
+ for (objects.items) |index| {
+ for (self.getFile(index).?.getSymbols()) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ const file = sym.getFile(self) orelse continue;
+ if (sym.visibility != .global) continue;
+ if (file == .dylib and !sym.flags.abs) {
+ sym.flags.import = true;
+ continue;
+ }
+ if (file.getIndex() == index) {
+ sym.flags.@"export" = true;
+ }
}
- return error.MultipleSymbolDefinitions;
}
- if (current.getFile()) |file| {
- const object = &self.objects.items[file];
- object.globals_lookup[current.sym_index] = global_index;
- }
-
- if (global_is_strong) return;
- if (sym_is_weak and global_is_weak) return;
- if (sym.tentative() and global_sym.tentative()) {
- if (global_sym.n_value >= sym.n_value) return;
- }
- if (sym.undf() and !sym.tentative()) return;
-
- if (global.getFile()) |file| {
- const global_object = &self.objects.items[file];
- global_object.globals_lookup[global.sym_index] = global_index;
- }
- _ = self.unresolved.swapRemove(global_index);
-
- gop.value_ptr.* = current;
-}
-
-fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void {
- const object = &self.objects.items[object_id];
- const in_symtab = object.in_symtab orelse return;
-
- log.debug("resolving symbols in '{s}'", .{object.name});
-
- var sym_index: u32 = 0;
- while (sym_index < in_symtab.len) : (sym_index += 1) {
- const sym = &object.symtab[sym_index];
- const sym_name = object.getSymbolName(sym_index);
- const sym_with_loc = SymbolWithLoc{
- .sym_index = sym_index,
- .file = object_id + 1,
- };
-
- if (sym.stab() or sym.indr() or sym.abs()) {
- try self.reportUnhandledSymbolType(sym_with_loc);
- continue;
+ for (self.undefined_symbols.items) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self)) |file| {
+ if (sym.visibility != .global) continue;
+ if (file == .dylib and !sym.flags.abs) sym.flags.import = true;
}
-
- if (sym.sect() and !sym.ext()) {
- log.debug("symbol '{s}' local to object {s}; skipping...", .{
- sym_name,
- object.name,
- });
- continue;
- }
-
- self.resolveGlobalSymbol(.{
- .sym_index = sym_index,
- .file = object_id + 1,
- }) catch |err| switch (err) {
- error.MultipleSymbolDefinitions => return error.FlushFailure,
- else => |e| return e,
- };
}
-}
-
-fn resolveSymbolsInArchives(self: *MachO) !void {
- if (self.archives.items.len == 0) return;
-
- const gpa = self.base.comp.gpa;
- var next_sym: usize = 0;
- loop: while (next_sym < self.unresolved.count()) {
- const global = self.globals.items[self.unresolved.keys()[next_sym]];
- const sym_name = self.getSymbolName(global);
-
- for (self.archives.items) |archive| {
- // Check if the entry exists in a static archive.
- const offsets = archive.toc.get(sym_name) orelse {
- // No hit.
- continue;
- };
- assert(offsets.items.len > 0);
-
- const object_id = @as(u16, @intCast(self.objects.items.len));
- const object = try archive.parseObject(gpa, offsets.items[0]);
- try self.objects.append(gpa, object);
- try self.resolveSymbolsInObject(object_id);
- continue :loop;
+ for (&[_]?Symbol.Index{
+ self.entry_index,
+ self.dyld_stub_binder_index,
+ self.objc_msg_send_index,
+ }) |index| {
+ if (index) |idx| {
+ const sym = self.getSymbol(idx);
+ if (sym.getFile(self)) |file| {
+ if (file == .dylib) sym.flags.import = true;
+ }
}
-
- next_sym += 1;
}
}
-fn resolveSymbolsInDylibs(self: *MachO) !void {
- if (self.dylibs.items.len == 0) return;
-
- const gpa = self.base.comp.gpa;
- var next_sym: usize = 0;
- loop: while (next_sym < self.unresolved.count()) {
- const global_index = self.unresolved.keys()[next_sym];
- const global = self.globals.items[global_index];
- const sym = self.getSymbolPtr(global);
- const sym_name = self.getSymbolName(global);
-
- for (self.dylibs.items, 0..) |dylib, id| {
- if (!dylib.symbols.contains(sym_name)) continue;
-
- const dylib_id = @as(u16, @intCast(id));
- if (!self.referenced_dylibs.contains(dylib_id)) {
- try self.referenced_dylibs.putNoClobber(gpa, dylib_id, {});
- }
-
- const ordinal = self.referenced_dylibs.getIndex(dylib_id) orelse unreachable;
- sym.n_type |= macho.N_EXT;
- sym.n_desc = @as(u16, @intCast(ordinal + 1)) * macho.N_SYMBOL_RESOLVER;
-
- if (dylib.weak) {
- sym.n_desc |= macho.N_WEAK_REF;
+fn deadStripDylibs(self: *MachO) void {
+ for (&[_]?Symbol.Index{
+ self.entry_index,
+ self.dyld_stub_binder_index,
+ self.objc_msg_send_index,
+ }) |index| {
+ if (index) |idx| {
+ const sym = self.getSymbol(idx);
+ if (sym.getFile(self)) |file| {
+ if (file == .dylib) file.dylib.referenced = true;
}
-
- _ = self.unresolved.swapRemove(global_index);
-
- continue :loop;
}
-
- next_sym += 1;
}
-}
-
-fn resolveSymbolsAtLoading(self: *MachO) !void {
- const output_mode = self.base.comp.config.output_mode;
- const is_lib = output_mode == .Lib;
- const is_dyn_lib = self.base.comp.config.link_mode == .Dynamic and is_lib;
- const allow_undef = is_dyn_lib and self.base.allow_shlib_undefined;
-
- var next_sym: usize = 0;
- while (next_sym < self.unresolved.count()) {
- const global_index = self.unresolved.keys()[next_sym];
- const global = self.globals.items[global_index];
- const sym = self.getSymbolPtr(global);
-
- if (sym.discarded()) {
- sym.* = .{
- .n_strx = 0,
- .n_type = macho.N_UNDF,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- };
- _ = self.unresolved.swapRemove(global_index);
- continue;
- } else if (allow_undef) {
- const n_desc = @as(
- u16,
- @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP * @as(i16, @intCast(macho.N_SYMBOL_RESOLVER))),
- );
- sym.n_type = macho.N_EXT;
- sym.n_desc = n_desc;
- _ = self.unresolved.swapRemove(global_index);
- continue;
- }
- next_sym += 1;
+ for (self.dylibs.items) |index| {
+ self.getFile(index).?.dylib.markReferenced(self);
}
-}
-fn resolveBoundarySymbols(self: *MachO) !void {
- const gpa = self.base.comp.gpa;
- var next_sym: usize = 0;
- while (next_sym < self.unresolved.count()) {
- const global_index = self.unresolved.keys()[next_sym];
- const global = &self.globals.items[global_index];
-
- if (self.getSectionBoundarySymbol(global.*) != null or self.getSegmentBoundarySymbol(global.*) != null) {
- const sym_index = try self.allocateSymbol();
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
- const sym = self.getSymbolPtr(sym_loc);
- sym.* = .{
- .n_strx = try self.strtab.insert(gpa, self.getSymbolName(global.*)),
- .n_type = macho.N_SECT | macho.N_EXT,
- .n_sect = 0,
- .n_desc = N_BOUNDARY,
- .n_value = 0,
- };
- if (global.getFile()) |file| {
- const global_object = &self.objects.items[file];
- global_object.globals_lookup[global.sym_index] = global_index;
- }
- global.* = sym_loc;
- _ = self.unresolved.swapRemove(global_index);
- continue;
- }
-
- next_sym += 1;
+ var i: usize = 0;
+ while (i < self.dylibs.items.len) {
+ const index = self.dylibs.items[i];
+ if (!self.getFile(index).?.dylib.isAlive(self)) {
+ _ = self.dylibs.orderedRemove(i);
+ } else i += 1;
}
}
-pub fn deinit(self: *MachO) void {
- const gpa = self.base.comp.gpa;
-
- if (self.llvm_object) |llvm_object| llvm_object.deinit();
-
- if (self.d_sym) |*d_sym| {
- d_sym.deinit();
- }
+fn scanRelocs(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- self.got_table.deinit(gpa);
- self.stub_table.deinit(gpa);
- self.tlv_ptr_table.deinit(gpa);
- self.thunk_table.deinit(gpa);
+ if (self.getZigObject()) |zo| try zo.scanRelocs(self);
- for (self.thunks.items) |*thunk| {
- thunk.deinit(gpa);
+ for (self.objects.items) |index| {
+ try self.getFile(index).?.object.scanRelocs(self);
}
- self.thunks.deinit(gpa);
- self.strtab.deinit(gpa);
- self.locals.deinit(gpa);
- self.globals.deinit(gpa);
- self.locals_free_list.deinit(gpa);
- self.globals_free_list.deinit(gpa);
- self.unresolved.deinit(gpa);
+ try self.reportUndefs();
- {
- var it = self.resolver.keyIterator();
- while (it.next()) |key_ptr| {
- gpa.free(key_ptr.*);
+ if (self.entry_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) != null) {
+ if (sym.flags.import) sym.flags.stubs = true;
}
- self.resolver.deinit(gpa);
}
- for (self.objects.items) |*object| {
- object.deinit(gpa);
- }
- self.objects.deinit(gpa);
- for (self.archives.items) |*archive| {
- archive.deinit(gpa);
+ if (self.dyld_stub_binder_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) != null) sym.flags.needs_got = true;
}
- self.archives.deinit(gpa);
- for (self.dylibs.items) |*dylib| {
- dylib.deinit(gpa);
- }
- self.dylibs.deinit(gpa);
- self.dylibs_map.deinit(gpa);
- self.referenced_dylibs.deinit(gpa);
- self.segments.deinit(gpa);
-
- for (self.sections.items(.free_list)) |*list| {
- list.deinit(gpa);
- }
- self.sections.deinit(gpa);
-
- self.atoms.deinit(gpa);
-
- for (self.decls.values()) |*m| {
- m.exports.deinit(gpa);
+ if (self.objc_msg_send_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) != null)
+ sym.flags.needs_got = true; // TODO is it always needed, or only if we are synthesising fast stubs?
}
- self.decls.deinit(gpa);
- self.lazy_syms.deinit(gpa);
- self.tlv_table.deinit(gpa);
-
- for (self.unnamed_const_atoms.values()) |*atoms| {
- atoms.deinit(gpa);
- }
- self.unnamed_const_atoms.deinit(gpa);
-
- {
- var it = self.anon_decls.iterator();
- while (it.next()) |entry| {
- entry.value_ptr.exports.deinit(gpa);
+ for (self.symbols.items, 0..) |*symbol, i| {
+ const index = @as(Symbol.Index, @intCast(i));
+ if (symbol.flags.needs_got) {
+ log.debug("'{s}' needs GOT", .{symbol.getName(self)});
+ try self.got.addSymbol(index, self);
+ }
+ if (symbol.flags.stubs) {
+ log.debug("'{s}' needs STUBS", .{symbol.getName(self)});
+ try self.stubs.addSymbol(index, self);
+ }
+ if (symbol.flags.tlv_ptr) {
+ log.debug("'{s}' needs TLV pointer", .{symbol.getName(self)});
+ try self.tlv_ptr.addSymbol(index, self);
+ }
+ if (symbol.flags.objc_stubs) {
+ log.debug("'{s}' needs OBJC STUBS", .{symbol.getName(self)});
+ try self.objc_stubs.addSymbol(index, self);
}
- self.anon_decls.deinit(gpa);
}
+}
- self.atom_by_index_table.deinit(gpa);
-
- for (self.relocs.values()) |*relocs| {
- relocs.deinit(gpa);
- }
- self.relocs.deinit(gpa);
- self.actions.deinit(gpa);
+fn reportUndefs(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- for (self.rebases.values()) |*rebases| {
- rebases.deinit(gpa);
+ switch (self.undefined_treatment) {
+ .dynamic_lookup, .suppress => return,
+ .@"error", .warn => {},
}
- self.rebases.deinit(gpa);
- for (self.bindings.values()) |*bindings| {
- bindings.deinit(gpa);
- }
- self.bindings.deinit(gpa);
-}
+ const max_notes = 4;
-fn freeAtom(self: *MachO, atom_index: Atom.Index) void {
- const gpa = self.base.comp.gpa;
- log.debug("freeAtom {d}", .{atom_index});
+ var has_undefs = false;
+ var it = self.undefs.iterator();
+ while (it.next()) |entry| {
+ const undef_sym = self.getSymbol(entry.key_ptr.*);
+ const notes = entry.value_ptr.*;
+ const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
- // Remove any relocs and base relocs associated with this Atom
- Atom.freeRelocations(self, atom_index);
+ var err = try self.addErrorWithNotes(nnotes);
+ try err.addMsg(self, "undefined symbol: {s}", .{undef_sym.getName(self)});
+ has_undefs = true;
- const atom = self.getAtom(atom_index);
- const sect_id = atom.getSymbol(self).n_sect - 1;
- const free_list = &self.sections.items(.free_list)[sect_id];
- var already_have_free_list_node = false;
- {
- var i: usize = 0;
- // TODO turn free_list into a hash map
- while (i < free_list.items.len) {
- if (free_list.items[i] == atom_index) {
- _ = free_list.swapRemove(i);
- continue;
- }
- if (free_list.items[i] == atom.prev_index) {
- already_have_free_list_node = true;
- }
- i += 1;
+ var inote: usize = 0;
+ while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
+ const atom = self.getAtom(notes.items[inote]).?;
+ const file = atom.getFile(self);
+ try err.addNote(self, "referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) });
}
- }
- const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sect_id];
- if (maybe_last_atom_index.*) |last_atom_index| {
- if (last_atom_index == atom_index) {
- if (atom.prev_index) |prev_index| {
- // TODO shrink the section size here
- maybe_last_atom_index.* = prev_index;
- } else {
- maybe_last_atom_index.* = null;
- }
+ if (notes.items.len > max_notes) {
+ const remaining = notes.items.len - max_notes;
+ try err.addNote(self, "referenced {d} more times", .{remaining});
}
}
- if (atom.prev_index) |prev_index| {
- const prev = self.getAtomPtr(prev_index);
- prev.next_index = atom.next_index;
+ for (self.undefined_symbols.items) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) != null) continue; // If undefined in an object file, will be reported above
+ has_undefs = true;
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
+ try err.addNote(self, "-u command line option", .{});
+ }
- if (!already_have_free_list_node and prev.*.freeListEligible(self)) {
- // The free list is heuristics, it doesn't have to be perfect, so we can ignore
- // the OOM here.
- free_list.append(gpa, prev_index) catch {};
+ if (self.entry_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) == null) {
+ has_undefs = true;
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
+ try err.addNote(self, "implicit entry/start for main executable", .{});
}
- } else {
- self.getAtomPtr(atom_index).prev_index = null;
}
- if (atom.next_index) |next_index| {
- self.getAtomPtr(next_index).prev_index = atom.prev_index;
- } else {
- self.getAtomPtr(atom_index).next_index = null;
+ if (self.dyld_stub_binder_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) == null and self.stubs_sect_index != null) {
+ has_undefs = true;
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
+ try err.addNote(self, "implicit -u command line option", .{});
+ }
}
- // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
- const sym_index = atom.getSymbolIndex().?;
-
- self.locals_free_list.append(gpa, sym_index) catch {};
-
- // Try freeing GOT atom if this decl had one
- self.got_table.freeEntry(gpa, .{ .sym_index = sym_index });
-
- if (self.d_sym) |*d_sym| {
- d_sym.swapRemoveRelocs(sym_index);
+ if (self.objc_msg_send_index) |index| {
+ const sym = self.getSymbol(index);
+ if (sym.getFile(self) == null and self.objc_stubs_sect_index != null) {
+ has_undefs = true;
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
+ try err.addNote(self, "implicit -u command line option", .{});
+ }
}
- self.locals.items[sym_index].n_type = 0;
- _ = self.atom_by_index_table.remove(sym_index);
- log.debug(" adding local symbol index {d} to free list", .{sym_index});
- self.getAtomPtr(atom_index).sym_index = 0;
-}
-
-fn shrinkAtom(self: *MachO, atom_index: Atom.Index, new_block_size: u64) void {
- _ = self;
- _ = atom_index;
- _ = new_block_size;
- // TODO check the new capacity, and if it crosses the size threshold into a big enough
- // capacity, insert a free list node for it.
-}
-
-fn growAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: Alignment) !u64 {
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbol(self);
- const align_ok = alignment.check(sym.n_value);
- const need_realloc = !align_ok or new_atom_size > atom.capacity(self);
- if (!need_realloc) return sym.n_value;
- return self.allocateAtom(atom_index, new_atom_size, alignment);
+ if (has_undefs) return error.HasUndefinedSymbols;
}
-pub fn allocateSymbol(self: *MachO) !u32 {
- const gpa = self.base.comp.gpa;
- try self.locals.ensureUnusedCapacity(gpa, 1);
-
- const index = blk: {
- if (self.locals_free_list.popOrNull()) |index| {
- log.debug(" (reusing symbol index {d})", .{index});
- break :blk index;
- } else {
- log.debug(" (allocating symbol index {d})", .{self.locals.items.len});
- const index = @as(u32, @intCast(self.locals.items.len));
- _ = self.locals.addOneAssumeCapacity();
- break :blk index;
+fn initOutputSections(self: *MachO) !void {
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ for (object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);
}
- };
-
- self.locals.items[index] = .{
- .n_strx = 0,
- .n_type = 0,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- };
-
- return index;
-}
-
-fn allocateGlobal(self: *MachO) !u32 {
- const gpa = self.base.comp.gpa;
- try self.globals.ensureUnusedCapacity(gpa, 1);
-
- const index = blk: {
- if (self.globals_free_list.popOrNull()) |index| {
- log.debug(" (reusing global index {d})", .{index});
- break :blk index;
- } else {
- log.debug(" (allocating symbol index {d})", .{self.globals.items.len});
- const index = @as(u32, @intCast(self.globals.items.len));
- _ = self.globals.addOneAssumeCapacity();
- break :blk index;
+ }
+ if (self.getInternalObject()) |object| {
+ for (object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);
}
- };
-
- self.globals.items[index] = .{ .sym_index = 0 };
-
- return index;
+ }
+ if (self.text_sect_index == null) {
+ self.text_sect_index = try self.addSection("__TEXT", "__text", .{
+ .alignment = switch (self.getTarget().cpu.arch) {
+ .x86_64 => 0,
+ .aarch64 => 2,
+ else => unreachable,
+ },
+ .flags = macho.S_REGULAR |
+ macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+ });
+ }
+ if (self.data_sect_index == null) {
+ self.data_sect_index = try self.addSection("__DATA", "__data", .{});
+ }
}
-pub fn addGotEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
- if (self.got_table.lookup.contains(reloc_target)) return;
- const gpa = self.base.comp.gpa;
- const got_index = try self.got_table.allocateEntry(gpa, reloc_target);
- if (self.got_section_index == null) {
- self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{
+fn initSyntheticSections(self: *MachO) !void {
+ const cpu_arch = self.getTarget().cpu.arch;
+
+ if (self.got.symbols.items.len > 0) {
+ self.got_sect_index = try self.addSection("__DATA_CONST", "__got", .{
.flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
+ .reserved1 = @intCast(self.stubs.symbols.items.len),
});
}
- if (self.mode == .incremental) {
- try self.writeOffsetTableEntry(got_index);
- self.got_table_count_dirty = true;
- self.markRelocsDirtyByTarget(reloc_target);
- }
-}
-pub fn addStubEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
- if (self.stub_table.lookup.contains(reloc_target)) return;
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const cpu_arch = comp.root_mod.resolved_target.result.cpu.arch;
- const stub_index = try self.stub_table.allocateEntry(gpa, reloc_target);
- if (self.stubs_section_index == null) {
- self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{
+ if (self.stubs.symbols.items.len > 0) {
+ self.stubs_sect_index = try self.addSection("__TEXT", "__stubs", .{
.flags = macho.S_SYMBOL_STUBS |
- macho.S_ATTR_PURE_INSTRUCTIONS |
- macho.S_ATTR_SOME_INSTRUCTIONS,
- .reserved2 = stubs.stubSize(cpu_arch),
+ macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+ .reserved1 = 0,
+ .reserved2 = switch (cpu_arch) {
+ .x86_64 => 6,
+ .aarch64 => 3 * @sizeOf(u32),
+ else => 0,
+ },
});
- self.stub_helper_section_index = try self.initSection("__TEXT", "__stub_helper", .{
- .flags = macho.S_REGULAR |
- macho.S_ATTR_PURE_INSTRUCTIONS |
- macho.S_ATTR_SOME_INSTRUCTIONS,
+ self.stubs_helper_sect_index = try self.addSection("__TEXT", "__stub_helper", .{
+ .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
});
- self.la_symbol_ptr_section_index = try self.initSection("__DATA", "__la_symbol_ptr", .{
+ self.la_symbol_ptr_sect_index = try self.addSection("__DATA", "__la_symbol_ptr", .{
.flags = macho.S_LAZY_SYMBOL_POINTERS,
+ .reserved1 = @intCast(self.stubs.symbols.items.len + self.got.symbols.items.len),
});
}
- if (self.mode == .incremental) {
- try self.writeStubTableEntry(stub_index);
- self.stub_table_count_dirty = true;
- self.markRelocsDirtyByTarget(reloc_target);
+
+ if (self.objc_stubs.symbols.items.len > 0) {
+ self.objc_stubs_sect_index = try self.addSection("__TEXT", "__objc_stubs", .{
+ .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+ });
}
-}
-pub fn addTlvPtrEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
- if (self.tlv_ptr_table.lookup.contains(reloc_target)) return;
- const gpa = self.base.comp.gpa;
- _ = try self.tlv_ptr_table.allocateEntry(gpa, reloc_target);
- if (self.tlv_ptr_section_index == null) {
- self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{
+ if (self.tlv_ptr.symbols.items.len > 0) {
+ self.tlv_ptr_sect_index = try self.addSection("__DATA", "__thread_ptrs", .{
.flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
});
}
-}
-pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
- if (build_options.skip_non_native and builtin.object_format != .macho) {
- @panic("Attempted to compile for object format that was disabled by build configuration");
+ const needs_unwind_info = for (self.objects.items) |index| {
+ 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", .{});
}
- if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func_index, air, liveness);
- const tracy = trace(@src());
- defer tracy.end();
-
- const func = mod.funcInfo(func_index);
- const decl_index = func.owner_decl;
- const decl = mod.declPtr(decl_index);
-
- const atom_index = try self.getOrCreateAtomForDecl(decl_index);
- self.freeUnnamedConsts(decl_index);
- Atom.freeRelocations(self, atom_index);
- const gpa = self.base.comp.gpa;
- var code_buffer = std.ArrayList(u8).init(gpa);
- defer code_buffer.deinit();
-
- var decl_state = if (self.d_sym) |*d_sym|
- try d_sym.dwarf.initDeclState(mod, decl_index)
- else
- null;
- defer if (decl_state) |*ds| ds.deinit();
-
- const res = if (decl_state) |*ds|
- try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .{
- .dwarf = ds,
- })
- else
- try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .none);
-
- const code = switch (res) {
- .ok => code_buffer.items,
- .fail => |em| {
- decl.analysis = .codegen_failure;
- try mod.failed_decls.put(mod.gpa, decl_index, em);
- return;
- },
- };
+ const needs_eh_frame = for (self.objects.items) |index| {
+ if (self.getFile(index).?.object.hasEhFrameRecords()) break true;
+ } else false;
+ if (needs_eh_frame) {
+ assert(needs_unwind_info);
+ self.eh_frame_sect_index = try self.addSection("__TEXT", "__eh_frame", .{});
+ }
- const addr = try self.updateDeclCode(decl_index, code);
+ for (self.boundary_symbols.items) |sym_index| {
+ const gpa = self.base.comp.gpa;
+ const sym = self.getSymbol(sym_index);
+ const name = sym.getName(self);
- if (decl_state) |*ds| {
- try self.d_sym.?.dwarf.commitDeclState(
- mod,
- decl_index,
- addr,
- self.getAtom(atom_index).size,
- ds,
- );
+ if (eatPrefix(name, "segment$start$")) |segname| {
+ if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
+ const prot = getSegmentProt(segname);
+ _ = try self.segments.append(gpa, .{
+ .cmdsize = @sizeOf(macho.segment_command_64),
+ .segname = makeStaticString(segname),
+ .initprot = prot,
+ .maxprot = prot,
+ });
+ }
+ } else if (eatPrefix(name, "segment$stop$")) |segname| {
+ if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
+ const prot = getSegmentProt(segname);
+ _ = try self.segments.append(gpa, .{
+ .cmdsize = @sizeOf(macho.segment_command_64),
+ .segname = makeStaticString(segname),
+ .initprot = prot,
+ .maxprot = prot,
+ });
+ }
+ } else if (eatPrefix(name, "section$start$")) |actual_name| {
+ const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
+ const segname = actual_name[0..sep]; // TODO check segname is valid
+ const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
+ if (self.getSectionByName(segname, sectname) == null) {
+ _ = try self.addSection(segname, sectname, .{});
+ }
+ } else if (eatPrefix(name, "section$stop$")) |actual_name| {
+ const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
+ const segname = actual_name[0..sep]; // TODO check segname is valid
+ const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
+ if (self.getSectionByName(segname, sectname) == null) {
+ _ = try self.addSection(segname, sectname, .{});
+ }
+ } else unreachable;
}
-
- // Since we updated the vaddr and the size, each corresponding export symbol also
- // needs to be updated.
- try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
}
-pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
- const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index);
- if (!gop.found_existing) {
- gop.value_ptr.* = .{};
- }
- const unnamed_consts = gop.value_ptr;
- const decl = mod.declPtr(decl_index);
- const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
- const index = unnamed_consts.items.len;
- const name = try std.fmt.allocPrint(gpa, "___unnamed_{s}_{d}", .{ decl_name, index });
- defer gpa.free(name);
- const atom_index = switch (try self.lowerConst(name, typed_value, typed_value.ty.abiAlignment(mod), self.data_const_section_index.?, decl.srcLoc(mod))) {
- .ok => |atom_index| atom_index,
- .fail => |em| {
- decl.analysis = .codegen_failure;
- try mod.failed_decls.put(mod.gpa, decl_index, em);
- log.debug("{s}", .{em.msg});
- return error.CodegenFail;
- },
- };
- try unnamed_consts.append(gpa, atom_index);
- const atom = self.getAtomPtr(atom_index);
- return atom.getSymbolIndex().?;
+fn getSegmentProt(segname: []const u8) macho.vm_prot_t {
+ if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE;
+ if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC;
+ if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ;
+ return macho.PROT.READ | macho.PROT.WRITE;
}
-const LowerConstResult = union(enum) {
- ok: Atom.Index,
- fail: *Module.ErrorMsg,
-};
-
-fn lowerConst(
- self: *MachO,
- name: []const u8,
- tv: TypedValue,
- required_alignment: InternPool.Alignment,
- sect_id: u8,
- src_loc: Module.SrcLoc,
-) !LowerConstResult {
- const gpa = self.base.comp.gpa;
-
- var code_buffer = std.ArrayList(u8).init(gpa);
- defer code_buffer.deinit();
-
- log.debug("allocating symbol indexes for {s}", .{name});
-
- const sym_index = try self.allocateSymbol();
- const atom_index = try self.createAtom(sym_index, .{});
- try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
-
- const res = try codegen.generateSymbol(&self.base, src_loc, tv, &code_buffer, .none, .{
- .parent_atom_index = self.getAtom(atom_index).getSymbolIndex().?,
- });
- const code = switch (res) {
- .ok => code_buffer.items,
- .fail => |em| return .{ .fail = em },
- };
-
- const atom = self.getAtomPtr(atom_index);
- atom.size = code.len;
- // TODO: work out logic for disambiguating functions from function pointers
- // const sect_id = self.getDeclOutputSection(decl_index);
- const symbol = atom.getSymbolPtr(self);
- const name_str_index = try self.strtab.insert(gpa, name);
- symbol.n_strx = name_str_index;
- symbol.n_type = macho.N_SECT;
- symbol.n_sect = sect_id + 1;
- symbol.n_value = try self.allocateAtom(atom_index, code.len, required_alignment);
- errdefer self.freeAtom(atom_index);
-
- log.debug("allocated atom for {s} at 0x{x}", .{ name, symbol.n_value });
- log.debug(" (required alignment 0x{x})", .{required_alignment});
-
- try self.writeAtom(atom_index, code);
- self.markRelocsDirtyByTarget(atom.getSymbolWithLoc());
-
- return .{ .ok = atom_index };
+fn getSegmentRank(segname: []const u8) u8 {
+ if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0;
+ if (mem.eql(u8, segname, "__LINKEDIT")) return 0xf;
+ if (mem.indexOf(u8, segname, "ZIG")) |_| return 0xe;
+ 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;
}
-pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void {
- if (build_options.skip_non_native and builtin.object_format != .macho) {
- @panic("Attempted to compile for object format that was disabled by build configuration");
- }
- if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index);
- const tracy = trace(@src());
- defer tracy.end();
-
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const decl = mod.declPtr(decl_index);
-
- if (decl.val.getExternFunc(mod)) |_| {
- return;
+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;
+}
- if (decl.isExtern(mod)) {
- // TODO make this part of getGlobalSymbol
- const name = mod.intern_pool.stringToSlice(decl.name);
- const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
- defer gpa.free(sym_name);
- _ = try self.addUndefined(sym_name, .{ .add_got = true });
- return;
+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,
- const is_threadlocal = if (decl.val.getVariable(mod)) |variable|
- variable.is_threadlocal and comp.config.any_non_single_threaded
- else
- false;
- if (is_threadlocal) return self.updateThreadlocalVariable(mod, decl_index);
-
- const atom_index = try self.getOrCreateAtomForDecl(decl_index);
- const sym_index = self.getAtom(atom_index).getSymbolIndex().?;
- Atom.freeRelocations(self, atom_index);
-
- var code_buffer = std.ArrayList(u8).init(gpa);
- defer code_buffer.deinit();
-
- var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym|
- try d_sym.dwarf.initDeclState(mod, decl_index)
- else
- null;
- defer if (decl_state) |*ds| ds.deinit();
-
- const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
- const res = if (decl_state) |*ds|
- try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
- .ty = decl.ty,
- .val = decl_val,
- }, &code_buffer, .{
- .dwarf = ds,
- }, .{
- .parent_atom_index = sym_index,
- })
- else
- try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
- .ty = decl.ty,
- .val = decl_val,
- }, &code_buffer, .none, .{
- .parent_atom_index = sym_index,
- });
+ 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,
- const code = switch (res) {
- .ok => code_buffer.items,
- .fail => |em| {
- decl.analysis = .codegen_failure;
- try mod.failed_decls.put(mod.gpa, decl_index, em);
- return;
+ 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;
},
- };
- const addr = try self.updateDeclCode(decl_index, code);
-
- if (decl_state) |*ds| {
- try self.d_sym.?.dwarf.commitDeclState(
- mod,
- decl_index,
- addr,
- self.getAtom(atom_index).size,
- ds,
- );
}
-
- // Since we updated the vaddr and the size, each corresponding export symbol also
- // needs to be updated.
- try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
}
-fn updateLazySymbolAtom(
- self: *MachO,
- sym: File.LazySymbol,
- atom_index: Atom.Index,
- section_index: u8,
-) !void {
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
-
- var required_alignment: Alignment = .none;
- var code_buffer = std.ArrayList(u8).init(gpa);
- defer code_buffer.deinit();
-
- const name_str_index = blk: {
- const name = try std.fmt.allocPrint(gpa, "___lazy_{s}_{}", .{
- @tagName(sym.kind),
- sym.ty.fmt(mod),
- });
- defer gpa.free(name);
- break :blk try self.strtab.insert(gpa, name);
- };
- const name = self.strtab.get(name_str_index).?;
-
- const atom = self.getAtomPtr(atom_index);
- const local_sym_index = atom.getSymbolIndex().?;
-
- const src = if (sym.ty.getOwnerDeclOrNull(mod)) |owner_decl|
- mod.declPtr(owner_decl).srcLoc(mod)
- else
- Module.SrcLoc{
- .file_scope = undefined,
- .parent_decl_node = undefined,
- .lazy = .unneeded,
- };
- const res = try codegen.generateLazySymbol(
- &self.base,
- src,
- sym,
- &required_alignment,
- &code_buffer,
- .none,
- .{ .parent_atom_index = local_sym_index },
- );
- const code = switch (res) {
- .ok => code_buffer.items,
- .fail => |em| {
- log.debug("{s}", .{em.msg});
- return error.CodegenFail;
- },
- };
-
- const symbol = atom.getSymbolPtr(self);
- symbol.n_strx = name_str_index;
- symbol.n_type = macho.N_SECT;
- symbol.n_sect = section_index + 1;
- symbol.n_desc = 0;
-
- const vaddr = try self.allocateAtom(atom_index, code.len, required_alignment);
- errdefer self.freeAtom(atom_index);
-
- log.debug("allocated atom for {s} at 0x{x}", .{ name, vaddr });
- log.debug(" (required alignment 0x{x})", .{required_alignment});
-
- atom.size = code.len;
- symbol.n_value = vaddr;
-
- try self.addGotEntry(.{ .sym_index = local_sym_index });
- try self.writeAtom(atom_index, code);
-}
-
-pub fn getOrCreateAtomForLazySymbol(self: *MachO, sym: File.LazySymbol) !Atom.Index {
- const mod = self.base.comp.module.?;
- const gpa = self.base.comp.gpa;
- const gop = try self.lazy_syms.getOrPut(gpa, sym.getDecl(mod));
- errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
- if (!gop.found_existing) gop.value_ptr.* = .{};
- const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) {
- .code => .{ .atom = &gop.value_ptr.text_atom, .state = &gop.value_ptr.text_state },
- .const_data => .{
- .atom = &gop.value_ptr.data_const_atom,
- .state = &gop.value_ptr.data_const_state,
- },
- };
- switch (metadata.state.*) {
- .unused => {
- const sym_index = try self.allocateSymbol();
- metadata.atom.* = try self.createAtom(sym_index, .{});
- try self.atom_by_index_table.putNoClobber(gpa, sym_index, metadata.atom.*);
- },
- .pending_flush => return metadata.atom.*,
- .flushed => {},
- }
- metadata.state.* = .pending_flush;
- const atom = metadata.atom.*;
- // anyerror needs to be deferred until flushModule
- if (sym.getDecl(mod) != .none) try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
- .code => self.text_section_index.?,
- .const_data => self.data_const_section_index.?,
- });
- return atom;
+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 lhs_rank < rhs_rank;
+ }
+ return segmentLessThan(ctx, lhs.segName(), rhs.segName());
}
-fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
- const mod = self.base.comp.module.?;
- // Lowering a TLV on macOS involves two stages:
- // 1. first we lower the initializer into appopriate section (__thread_data or __thread_bss)
- // 2. next, we create a corresponding threadlocal variable descriptor in __thread_vars
-
- // 1. Lower the initializer value.
- const init_atom_index = try self.getOrCreateAtomForDecl(decl_index);
- const init_atom = self.getAtomPtr(init_atom_index);
- const init_sym_index = init_atom.getSymbolIndex().?;
- Atom.freeRelocations(self, init_atom_index);
+pub fn sortSections(self: *MachO) !void {
+ const Entry = struct {
+ index: u8,
- const gpa = self.base.comp.gpa;
-
- var code_buffer = std.ArrayList(u8).init(gpa);
- defer code_buffer.deinit();
-
- var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym|
- try d_sym.dwarf.initDeclState(module, decl_index)
- else
- null;
- defer if (decl_state) |*ds| ds.deinit();
-
- const decl = module.declPtr(decl_index);
- const decl_metadata = self.decls.get(decl_index).?;
- const decl_val = Value.fromInterned(decl.val.getVariable(mod).?.init);
- const res = if (decl_state) |*ds|
- try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
- .ty = decl.ty,
- .val = decl_val,
- }, &code_buffer, .{
- .dwarf = ds,
- }, .{
- .parent_atom_index = init_sym_index,
- })
- else
- try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
- .ty = decl.ty,
- .val = decl_val,
- }, &code_buffer, .none, .{
- .parent_atom_index = init_sym_index,
- });
-
- const code = switch (res) {
- .ok => code_buffer.items,
- .fail => |em| {
- decl.analysis = .codegen_failure;
- try module.failed_decls.put(module.gpa, decl_index, em);
- return;
- },
+ pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool {
+ return sectionLessThan(
+ {},
+ macho_file.sections.items(.header)[lhs.index],
+ macho_file.sections.items(.header)[rhs.index],
+ );
+ }
};
- const required_alignment = decl.getAlignment(mod);
-
- const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(module));
-
- const init_sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{decl_name});
- defer gpa.free(init_sym_name);
-
- const sect_id = decl_metadata.section;
- const init_sym = init_atom.getSymbolPtr(self);
- init_sym.n_strx = try self.strtab.insert(gpa, init_sym_name);
- init_sym.n_type = macho.N_SECT;
- init_sym.n_sect = sect_id + 1;
- init_sym.n_desc = 0;
- init_atom.size = code.len;
-
- init_sym.n_value = try self.allocateAtom(init_atom_index, code.len, required_alignment);
- errdefer self.freeAtom(init_atom_index);
+ const gpa = self.base.comp.gpa;
- log.debug("allocated atom for {s} at 0x{x}", .{ init_sym_name, init_sym.n_value });
- log.debug(" (required alignment 0x{x})", .{required_alignment});
+ var entries = try std.ArrayList(Entry).initCapacity(gpa, self.sections.slice().len);
+ defer entries.deinit();
+ for (0..self.sections.slice().len) |index| {
+ entries.appendAssumeCapacity(.{ .index = @intCast(index) });
+ }
- try self.writeAtom(init_atom_index, code);
+ mem.sort(Entry, entries.items, self, Entry.lessThan);
- if (decl_state) |*ds| {
- try self.d_sym.?.dwarf.commitDeclState(
- module,
- decl_index,
- init_sym.n_value,
- self.getAtom(init_atom_index).size,
- ds,
- );
+ const backlinks = try gpa.alloc(u8, entries.items.len);
+ defer gpa.free(backlinks);
+ for (entries.items, 0..) |entry, i| {
+ backlinks[entry.index] = @intCast(i);
}
- try self.updateExports(module, .{ .decl_index = decl_index }, module.getDeclExports(decl_index));
-
- // 2. Create a TLV descriptor.
- const init_atom_sym_loc = init_atom.getSymbolWithLoc();
- const gop = try self.tlv_table.getOrPut(gpa, init_atom_sym_loc);
- assert(!gop.found_existing);
- gop.value_ptr.* = try self.createThreadLocalDescriptorAtom(decl_name, init_atom_sym_loc);
- self.markRelocsDirtyByTarget(init_atom_sym_loc);
-}
+ var slice = self.sections.toOwnedSlice();
+ defer slice.deinit(gpa);
-pub fn getOrCreateAtomForDecl(self: *MachO, decl_index: InternPool.DeclIndex) !Atom.Index {
- const gpa = self.base.comp.gpa;
- const gop = try self.decls.getOrPut(gpa, decl_index);
- if (!gop.found_existing) {
- const sym_index = try self.allocateSymbol();
- const atom_index = try self.createAtom(sym_index, .{});
- try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
- gop.value_ptr.* = .{
- .atom = atom_index,
- .section = self.getDeclOutputSection(decl_index),
- .exports = .{},
- };
+ try self.sections.ensureTotalCapacity(gpa, slice.len);
+ for (entries.items) |sorted| {
+ self.sections.appendAssumeCapacity(slice.get(sorted.index));
}
- return gop.value_ptr.atom;
-}
-
-fn getDeclOutputSection(self: *MachO, decl_index: InternPool.DeclIndex) u8 {
- const decl = self.base.comp.module.?.declPtr(decl_index);
- const ty = decl.ty;
- const val = decl.val;
- const mod = self.base.comp.module.?;
- const zig_ty = ty.zigTypeTag(mod);
- const any_non_single_threaded = self.base.comp.config.any_non_single_threaded;
- const optimize_mode = self.base.comp.root_mod.optimize_mode;
- const sect_id: u8 = blk: {
- // TODO finish and audit this function
- if (val.isUndefDeep(mod)) {
- if (optimize_mode == .ReleaseFast or optimize_mode == .ReleaseSmall) {
- @panic("TODO __DATA,__bss");
- } else {
- break :blk self.data_section_index.?;
- }
+
+ if (self.getZigObject()) |zo| {
+ for (zo.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ atom.out_n_sect = backlinks[atom.out_n_sect];
}
- if (val.getVariable(mod)) |variable| {
- if (variable.is_threadlocal and any_non_single_threaded) {
- break :blk self.thread_data_section_index.?;
+ for (zo.symtab.items(.nlist)) |*sym| {
+ if (sym.sect()) {
+ sym.n_sect = backlinks[sym.n_sect];
}
- break :blk self.data_section_index.?;
}
- switch (zig_ty) {
- // TODO: what if this is a function pointer?
- .Fn => break :blk self.text_section_index.?,
- else => {
- if (val.getVariable(mod)) |_| {
- break :blk self.data_section_index.?;
- }
- break :blk self.data_const_section_index.?;
- },
+ for (zo.symbols.items) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ const atom = sym.getAtom(self) orelse continue;
+ if (!atom.flags.alive) continue;
+ if (sym.getFile(self).?.getIndex() != zo.index) continue;
+ sym.out_n_sect = backlinks[sym.out_n_sect];
}
- };
- return sect_id;
-}
+ }
-fn updateDeclCode(self: *MachO, decl_index: InternPool.DeclIndex, code: []u8) !u64 {
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
- const decl = mod.declPtr(decl_index);
-
- const required_alignment = decl.getAlignment(mod);
-
- const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
-
- const decl_metadata = self.decls.get(decl_index).?;
- const atom_index = decl_metadata.atom;
- const atom = self.getAtom(atom_index);
- const sym_index = atom.getSymbolIndex().?;
- const sect_id = decl_metadata.section;
- const header = &self.sections.items(.header)[sect_id];
- const segment = self.getSegment(sect_id);
- const code_len = code.len;
-
- if (atom.size != 0) {
- const sym = atom.getSymbolPtr(self);
- sym.n_strx = try self.strtab.insert(gpa, decl_name);
- sym.n_type = macho.N_SECT;
- sym.n_sect = sect_id + 1;
- sym.n_desc = 0;
-
- const capacity = atom.capacity(self);
- const need_realloc = code_len > capacity or !required_alignment.check(sym.n_value);
-
- if (need_realloc) {
- const vaddr = try self.growAtom(atom_index, code_len, required_alignment);
- log.debug("growing {s} and moving from 0x{x} to 0x{x}", .{ decl_name, sym.n_value, vaddr });
- log.debug(" (required alignment 0x{x})", .{required_alignment});
-
- if (vaddr != sym.n_value) {
- sym.n_value = vaddr;
- log.debug(" (updating GOT entry)", .{});
- const got_atom_index = self.got_table.lookup.get(.{ .sym_index = sym_index }).?;
- try self.writeOffsetTableEntry(got_atom_index);
- self.markRelocsDirtyByTarget(.{ .sym_index = sym_index });
- }
- } else if (code_len < atom.size) {
- self.shrinkAtom(atom_index, code_len);
- } else if (atom.next_index == null) {
- const needed_size = (sym.n_value + code_len) - segment.vmaddr;
- header.size = needed_size;
+ for (self.objects.items) |index| {
+ for (self.getFile(index).?.object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ atom.out_n_sect = backlinks[atom.out_n_sect];
}
- self.getAtomPtr(atom_index).size = code_len;
- } else {
- const sym = atom.getSymbolPtr(self);
- sym.n_strx = try self.strtab.insert(gpa, decl_name);
- sym.n_type = macho.N_SECT;
- sym.n_sect = sect_id + 1;
- sym.n_desc = 0;
-
- const vaddr = try self.allocateAtom(atom_index, code_len, required_alignment);
- errdefer self.freeAtom(atom_index);
-
- log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr });
- log.debug(" (required alignment 0x{x})", .{required_alignment});
-
- self.getAtomPtr(atom_index).size = code_len;
- sym.n_value = vaddr;
-
- try self.addGotEntry(.{ .sym_index = sym_index });
}
- try self.writeAtom(atom_index, code);
-
- return atom.getSymbol(self).n_value;
-}
-
-pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
- if (self.d_sym) |*d_sym| {
- try d_sym.dwarf.updateDeclLineNumber(module, decl_index);
+ if (self.getInternalObject()) |object| {
+ for (object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ atom.out_n_sect = backlinks[atom.out_n_sect];
+ }
}
-}
-pub fn updateExports(
- self: *MachO,
- mod: *Module,
- exported: Module.Exported,
- exports: []const *Module.Export,
-) File.UpdateExportsError!void {
- if (build_options.skip_non_native and builtin.object_format != .macho) {
- @panic("Attempted to compile for object format that was disabled by build configuration");
+ for (&[_]*?u8{
+ &self.data_sect_index,
+ &self.got_sect_index,
+ &self.zig_got_sect_index,
+ &self.stubs_sect_index,
+ &self.stubs_helper_sect_index,
+ &self.la_symbol_ptr_sect_index,
+ &self.tlv_ptr_sect_index,
+ &self.eh_frame_sect_index,
+ &self.unwind_info_sect_index,
+ &self.objc_stubs_sect_index,
+ }) |maybe_index| {
+ if (maybe_index.*) |*index| {
+ index.* = backlinks[index.*];
+ }
}
- if (self.llvm_object) |llvm_object|
- return llvm_object.updateExports(mod, exported, exports);
+}
+pub fn addAtomsToSections(self: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
- const gpa = self.base.comp.gpa;
-
- const metadata = switch (exported) {
- .decl_index => |decl_index| blk: {
- _ = try self.getOrCreateAtomForDecl(decl_index);
- break :blk self.decls.getPtr(decl_index).?;
- },
- .value => |value| self.anon_decls.getPtr(value) orelse blk: {
- const first_exp = exports[0];
- const res = try self.lowerAnonDecl(value, .none, first_exp.getSrcLoc(mod));
- switch (res) {
- .ok => {},
- .fail => |em| {
- // TODO maybe it's enough to return an error here and let Module.processExportsInner
- // handle the error?
- try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
- mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em);
- return;
- },
- }
- break :blk self.anon_decls.getPtr(value).?;
- },
- };
- const atom_index = metadata.atom;
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbol(self);
-
- for (exports) |exp| {
- const exp_name = try std.fmt.allocPrint(gpa, "_{}", .{
- exp.opts.name.fmt(&mod.intern_pool),
- });
- defer gpa.free(exp_name);
-
- log.debug("adding new export '{s}'", .{exp_name});
-
- if (exp.opts.section.unwrap()) |section_name| {
- if (!mod.intern_pool.stringEqlSlice(section_name, "__text")) {
- try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
- gpa,
- exp.getSrcLoc(mod),
- "Unimplemented: ExportOptions.section",
- .{},
- ));
- continue;
- }
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ for (object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
+ try atoms.append(self.base.comp.gpa, atom_index);
}
-
- if (exp.opts.linkage == .LinkOnce) {
- try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
- gpa,
- exp.getSrcLoc(mod),
- "Unimplemented: GlobalLinkage.LinkOnce",
- .{},
- ));
- continue;
+ for (object.symbols.items) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ const atom = sym.getAtom(self) orelse continue;
+ if (!atom.flags.alive) continue;
+ if (sym.getFile(self).?.getIndex() != index) continue;
+ sym.out_n_sect = atom.out_n_sect;
}
-
- const global_sym_index = metadata.getExport(self, exp_name) orelse blk: {
- const global_sym_index = if (self.getGlobalIndex(exp_name)) |global_index| ind: {
- const global = self.globals.items[global_index];
- // TODO this is just plain wrong as it all should happen in a single `resolveSymbols`
- // pass. This will go away once we abstact away Zig's incremental compilation into
- // its own module.
- if (global.getFile() == null and self.getSymbol(global).undf()) {
- _ = self.unresolved.swapRemove(global_index);
- break :ind global.sym_index;
- }
- break :ind try self.allocateSymbol();
- } else try self.allocateSymbol();
- try metadata.exports.append(gpa, global_sym_index);
- break :blk global_sym_index;
- };
- const global_sym_loc = SymbolWithLoc{ .sym_index = global_sym_index };
- const global_sym = self.getSymbolPtr(global_sym_loc);
- global_sym.* = .{
- .n_strx = try self.strtab.insert(gpa, exp_name),
- .n_type = macho.N_SECT | macho.N_EXT,
- .n_sect = metadata.section + 1,
- .n_desc = 0,
- .n_value = sym.n_value,
- };
-
- switch (exp.opts.linkage) {
- .Internal => {
- // Symbol should be hidden, or in MachO lingo, private extern.
- // We should also mark the symbol as Weak: n_desc == N_WEAK_DEF.
- global_sym.n_type |= macho.N_PEXT;
- global_sym.n_desc |= macho.N_WEAK_DEF;
- },
- .Strong => {},
- .Weak => {
- // Weak linkage is specified as part of n_desc field.
- // Symbol's n_type is like for a symbol with strong linkage.
- global_sym.n_desc |= macho.N_WEAK_DEF;
- },
- else => unreachable,
+ }
+ if (self.getInternalObject()) |object| {
+ for (object.atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
+ try atoms.append(self.base.comp.gpa, atom_index);
+ }
+ for (object.symbols.items) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ const atom = sym.getAtom(self) orelse continue;
+ if (!atom.flags.alive) continue;
+ if (sym.getFile(self).?.getIndex() != object.index) continue;
+ sym.out_n_sect = atom.out_n_sect;
}
-
- self.resolveGlobalSymbol(global_sym_loc) catch |err| switch (err) {
- error.MultipleSymbolDefinitions => {
- // TODO: this needs rethinking
- const global = self.getGlobal(exp_name).?;
- if (global_sym_loc.sym_index != global.sym_index and global.getFile() != null) {
- _ = try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create(
- gpa,
- exp.getSrcLoc(mod),
- \\LinkError: symbol '{s}' defined multiple times
- ,
- .{exp_name},
- ));
- }
- },
- else => |e| return e,
- };
}
}
-pub fn deleteDeclExport(
- self: *MachO,
- decl_index: InternPool.DeclIndex,
- name: InternPool.NullTerminatedString,
-) Allocator.Error!void {
- if (self.llvm_object) |_| return;
- const metadata = self.decls.getPtr(decl_index) orelse return;
+fn calcSectionSizes(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
- const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{mod.intern_pool.stringToSlice(name)});
- defer gpa.free(exp_name);
- const sym_index = metadata.getExportPtr(self, exp_name) orelse return;
-
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index.* };
- const sym = self.getSymbolPtr(sym_loc);
- log.debug("deleting export '{s}'", .{exp_name});
- assert(sym.sect() and sym.ext());
- sym.* = .{
- .n_strx = 0,
- .n_type = 0,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- };
- self.locals_free_list.append(gpa, sym_index.*) catch {};
+ const cpu_arch = self.getTarget().cpu.arch;
- if (self.resolver.fetchRemove(exp_name)) |entry| {
- defer gpa.free(entry.key);
- self.globals_free_list.append(gpa, entry.value) catch {};
- self.globals.items[entry.value] = .{ .sym_index = 0 };
+ if (self.data_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size += @sizeOf(u64);
+ header.@"align" = 3;
}
- sym_index.* = 0;
-}
-
-fn freeUnnamedConsts(self: *MachO, decl_index: InternPool.DeclIndex) void {
- const gpa = self.base.comp.gpa;
- const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return;
- for (unnamed_consts.items) |atom| {
- self.freeAtom(atom);
+ const slice = self.sections.slice();
+ for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
+ if (atoms.items.len == 0) continue;
+ if (self.requiresThunks() and header.isCode()) continue;
+
+ for (atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index).?;
+ const atom_alignment = atom.alignment.toByteUnits(1);
+ 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.toLog2Units());
+ }
}
- unnamed_consts.clearAndFree(gpa);
-}
-pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void {
- if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
- const decl = mod.declPtr(decl_index);
+ if (self.requiresThunks()) {
+ for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
+ if (!header.isCode()) continue;
+ if (atoms.items.len == 0) continue;
- log.debug("freeDecl {*}", .{decl});
+ // Create jump/branch range extenders if needed.
+ try thunks.createThunks(@intCast(i), self);
+ }
+ }
- if (self.decls.fetchSwapRemove(decl_index)) |const_kv| {
- var kv = const_kv;
- self.freeAtom(kv.value.atom);
- self.freeUnnamedConsts(decl_index);
- kv.value.exports.deinit(gpa);
+ if (self.got_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.got.size();
+ header.@"align" = 3;
}
- if (self.d_sym) |*d_sym| {
- d_sym.dwarf.freeDecl(decl_index);
+ if (self.stubs_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.stubs.size(self);
+ header.@"align" = switch (cpu_arch) {
+ .x86_64 => 1,
+ .aarch64 => 2,
+ else => 0,
+ };
}
-}
-pub fn getDeclVAddr(self: *MachO, decl_index: InternPool.DeclIndex, reloc_info: File.RelocInfo) !u64 {
- assert(self.llvm_object == null);
+ if (self.stubs_helper_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.stubs_helper.size(self);
+ header.@"align" = 2;
+ }
- const this_atom_index = try self.getOrCreateAtomForDecl(decl_index);
- const sym_index = self.getAtom(this_atom_index).getSymbolIndex().?;
- const atom_index = self.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent_atom_index }).?;
- try Atom.addRelocation(self, atom_index, .{
- .type = .unsigned,
- .target = .{ .sym_index = sym_index },
- .offset = @as(u32, @intCast(reloc_info.offset)),
- .addend = reloc_info.addend,
- .pcrel = false,
- .length = 3,
- });
- try Atom.addRebase(self, atom_index, @as(u32, @intCast(reloc_info.offset)));
+ if (self.la_symbol_ptr_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.la_symbol_ptr.size(self);
+ header.@"align" = 3;
+ }
- return 0;
-}
+ if (self.tlv_ptr_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.tlv_ptr.size();
+ header.@"align" = 3;
+ }
-pub fn lowerAnonDecl(
- self: *MachO,
- decl_val: InternPool.Index,
- explicit_alignment: InternPool.Alignment,
- src_loc: Module.SrcLoc,
-) !codegen.Result {
- const gpa = self.base.comp.gpa;
- const mod = self.base.comp.module.?;
- const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
- const decl_alignment = switch (explicit_alignment) {
- .none => ty.abiAlignment(mod),
- else => explicit_alignment,
- };
- if (self.anon_decls.get(decl_val)) |metadata| {
- const existing_addr = self.getAtom(metadata.atom).getSymbol(self).n_value;
- if (decl_alignment.check(existing_addr))
- return .ok;
- }
-
- const val = Value.fromInterned(decl_val);
- const tv = TypedValue{ .ty = ty, .val = val };
- var name_buf: [32]u8 = undefined;
- const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
- @intFromEnum(decl_val),
- }) catch unreachable;
- const res = self.lowerConst(
- name,
- tv,
- decl_alignment,
- self.data_const_section_index.?,
- src_loc,
- ) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- else => |e| return .{ .fail = try Module.ErrorMsg.create(
- gpa,
- src_loc,
- "unable to lower constant value: {s}",
- .{@errorName(e)},
- ) },
- };
- const atom_index = switch (res) {
- .ok => |atom_index| atom_index,
- .fail => |em| return .{ .fail = em },
- };
- try self.anon_decls.put(gpa, decl_val, .{
- .atom = atom_index,
- .section = self.data_const_section_index.?,
- });
- return .ok;
+ if (self.objc_stubs_sect_index) |idx| {
+ const header = &self.sections.items(.header)[idx];
+ header.size = self.objc_stubs.size(self);
+ header.@"align" = switch (cpu_arch) {
+ .x86_64 => 0,
+ .aarch64 => 2,
+ else => 0,
+ };
+ }
}
-pub fn getAnonDeclVAddr(self: *MachO, decl_val: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
- assert(self.llvm_object == null);
-
- const this_atom_index = self.anon_decls.get(decl_val).?.atom;
- const sym_index = self.getAtom(this_atom_index).getSymbolIndex().?;
- const atom_index = self.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent_atom_index }).?;
- try Atom.addRelocation(self, atom_index, .{
- .type = .unsigned,
- .target = .{ .sym_index = sym_index },
- .offset = @as(u32, @intCast(reloc_info.offset)),
- .addend = reloc_info.addend,
- .pcrel = false,
- .length = 3,
- });
- try Atom.addRebase(self, atom_index, @as(u32, @intCast(reloc_info.offset)));
+fn generateUnwindInfo(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- return 0;
+ if (self.eh_frame_sect_index) |index| {
+ const sect = &self.sections.items(.header)[index];
+ sect.size = try eh_frame.calcSize(self);
+ sect.@"align" = 3;
+ }
+ if (self.unwind_info_sect_index) |index| {
+ const sect = &self.sections.items(.header)[index];
+ self.unwind_info.generate(self) catch |err| switch (err) {
+ error.TooManyPersonalities => return self.reportUnexpectedError(
+ "too many personalities in unwind info",
+ .{},
+ ),
+ else => |e| return e,
+ };
+ sect.size = self.unwind_info.calcSize();
+ sect.@"align" = 2;
+ }
}
-const PopulateMissingMetadataOptions = struct {
- symbol_count_hint: u64,
- program_code_size_hint: u64,
-};
-
-fn populateMissingMetadata(self: *MachO, options: PopulateMissingMetadataOptions) !void {
- assert(self.mode == .incremental);
-
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const target = comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const pagezero_vmsize = self.calcPagezeroSize();
+fn initSegments(self: *MachO) !void {
+ const gpa = self.base.comp.gpa;
+ const slice = self.sections.slice();
- if (self.pagezero_segment_cmd_index == null) {
- if (pagezero_vmsize > 0) {
- self.pagezero_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
- try self.segments.append(gpa, .{
- .segname = makeStaticString("__PAGEZERO"),
- .vmsize = pagezero_vmsize,
- .cmdsize = @sizeOf(macho.segment_command_64),
- });
+ // Add __PAGEZERO if required
+ const pagezero_size = self.pagezero_size orelse default_pagezero_size;
+ const aligned_pagezero_size = mem.alignBackward(u64, pagezero_size, self.getPageSize());
+ if (!self.base.isDynLib() and aligned_pagezero_size > 0) {
+ if (aligned_pagezero_size != pagezero_size) {
+ // TODO convert into a warning
+ log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size});
+ log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size});
}
+ _ = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size });
}
- if (self.header_segment_cmd_index == null) {
- // The first __TEXT segment is immovable and covers MachO header and load commands.
- self.header_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
- const ideal_size = self.headerpad_size;
- const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), getPageSize(cpu_arch));
-
- log.debug("found __TEXT segment (header-only) free space 0x{x} to 0x{x}", .{ 0, needed_size });
+ // __TEXT segment is non-optional
+ _ = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") });
- try self.segments.append(gpa, .{
- .segname = makeStaticString("__TEXT"),
- .vmaddr = pagezero_vmsize,
- .vmsize = needed_size,
- .filesize = needed_size,
- .maxprot = macho.PROT.READ | macho.PROT.EXEC,
- .initprot = macho.PROT.READ | macho.PROT.EXEC,
- .cmdsize = @sizeOf(macho.segment_command_64),
- });
- self.segment_table_dirty = true;
+ // Next, create segments required by sections
+ for (slice.items(.header)) |header| {
+ const segname = header.segName();
+ if (self.getSegmentByName(segname) == null) {
+ const flags: u32 = if (mem.startsWith(u8, segname, "__DATA_CONST")) macho.SG_READ_ONLY else 0;
+ _ = try self.addSegment(segname, .{ .prot = getSegmentProt(segname), .flags = flags });
+ }
}
- if (self.text_section_index == null) {
- // Sadly, segments need unique string identfiers for some reason.
- self.text_section_index = try self.allocateSection("__TEXT1", "__text", .{
- .size = options.program_code_size_hint,
- .alignment = switch (cpu_arch) {
- .x86_64 => 1,
- .aarch64 => @sizeOf(u32),
- else => unreachable, // unhandled architecture type
- },
- .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
- .prot = macho.PROT.READ | macho.PROT.EXEC,
- });
- self.segment_table_dirty = true;
- }
+ // Add __LINKEDIT
+ _ = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") });
- if (self.stubs_section_index == null) {
- const stub_size = stubs.stubSize(cpu_arch);
- self.stubs_section_index = try self.allocateSection("__TEXT2", "__stubs", .{
- .size = stub_size,
- .alignment = stubs.stubAlignment(cpu_arch),
- .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
- .reserved2 = stub_size,
- .prot = macho.PROT.READ | macho.PROT.EXEC,
- });
- self.segment_table_dirty = true;
- }
+ // Sort segments
+ const sortFn = struct {
+ fn sortFn(ctx: void, lhs: macho.segment_command_64, rhs: macho.segment_command_64) bool {
+ return segmentLessThan(ctx, lhs.segName(), rhs.segName());
+ }
+ }.sortFn;
+ mem.sort(macho.segment_command_64, self.segments.items, {}, sortFn);
+
+ // Attach sections to segments
+ for (slice.items(.header), slice.items(.segment_id)) |header, *seg_id| {
+ const segname = header.segName();
+ const segment_id = self.getSegmentByName(segname) orelse blk: {
+ const segment_id = @as(u8, @intCast(self.segments.items.len));
+ const protection = getSegmentProt(segname);
+ try self.segments.append(gpa, .{
+ .cmdsize = @sizeOf(macho.segment_command_64),
+ .segname = makeStaticString(segname),
+ .maxprot = protection,
+ .initprot = protection,
+ });
+ break :blk segment_id;
+ };
+ const segment = &self.segments.items[segment_id];
+ segment.cmdsize += @sizeOf(macho.section_64);
+ segment.nsects += 1;
+ seg_id.* = segment_id;
+ }
+
+ self.pagezero_seg_index = self.getSegmentByName("__PAGEZERO");
+ self.text_seg_index = self.getSegmentByName("__TEXT").?;
+ self.linkedit_seg_index = self.getSegmentByName("__LINKEDIT").?;
+ self.zig_text_seg_index = self.getSegmentByName("__TEXT_ZIG");
+ self.zig_got_seg_index = self.getSegmentByName("__GOT_ZIG");
+ self.zig_const_seg_index = self.getSegmentByName("__CONST_ZIG");
+ self.zig_data_seg_index = self.getSegmentByName("__DATA_ZIG");
+ self.zig_bss_seg_index = self.getSegmentByName("__BSS_ZIG");
+}
+
+fn allocateSections(self: *MachO) !void {
+ const headerpad = load_commands.calcMinHeaderPadSize(self);
+ var vmaddr: u64 = if (self.pagezero_seg_index) |index|
+ self.segments.items[index].vmaddr + self.segments.items[index].vmsize
+ else
+ 0;
+ vmaddr += headerpad;
+ var fileoff = headerpad;
+ var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0;
- if (self.stub_helper_section_index == null) {
- self.stub_helper_section_index = try self.allocateSection("__TEXT3", "__stub_helper", .{
- .size = @sizeOf(u32),
- .alignment = stubs.stubAlignment(cpu_arch),
- .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
- .prot = macho.PROT.READ | macho.PROT.EXEC,
- });
- self.segment_table_dirty = true;
- }
+ const page_size = self.getPageSize();
+ const slice = self.sections.slice();
+ const last_index = for (slice.items(.header), 0..) |header, i| {
+ if (mem.indexOf(u8, header.segName(), "ZIG")) |_| break i;
+ } else slice.items(.header).len;
+
+ for (slice.items(.header)[0..last_index], slice.items(.segment_id)[0..last_index]) |*header, curr_seg_id| {
+ if (prev_seg_id != curr_seg_id) {
+ vmaddr = mem.alignForward(u64, vmaddr, page_size);
+ fileoff = mem.alignForward(u32, fileoff, page_size);
+ }
- if (self.got_section_index == null) {
- self.got_section_index = try self.allocateSection("__DATA_CONST", "__got", .{
- .size = @sizeOf(u64) * options.symbol_count_hint,
- .alignment = @alignOf(u64),
- .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
- }
+ const alignment = try math.powi(u32, 2, header.@"align");
- if (self.data_const_section_index == null) {
- self.data_const_section_index = try self.allocateSection("__DATA_CONST1", "__const", .{
- .size = @sizeOf(u64),
- .alignment = @alignOf(u64),
- .flags = macho.S_REGULAR,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
- }
+ vmaddr = mem.alignForward(u64, vmaddr, alignment);
+ header.addr = vmaddr;
+ vmaddr += header.size;
- if (self.la_symbol_ptr_section_index == null) {
- self.la_symbol_ptr_section_index = try self.allocateSection("__DATA", "__la_symbol_ptr", .{
- .size = @sizeOf(u64),
- .alignment = @alignOf(u64),
- .flags = macho.S_LAZY_SYMBOL_POINTERS,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
- }
+ if (!header.isZerofill()) {
+ fileoff = mem.alignForward(u32, fileoff, alignment);
+ header.offset = fileoff;
+ fileoff += @intCast(header.size);
+ }
- if (self.data_section_index == null) {
- self.data_section_index = try self.allocateSection("__DATA1", "__data", .{
- .size = @sizeOf(u64),
- .alignment = @alignOf(u64),
- .flags = macho.S_REGULAR,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
+ prev_seg_id = curr_seg_id;
}
- if (comp.config.any_non_single_threaded) {
- if (self.thread_vars_section_index == null) {
- self.thread_vars_section_index = try self.allocateSection("__DATA2", "__thread_vars", .{
- .size = @sizeOf(u64) * 3,
- .alignment = @sizeOf(u64),
- .flags = macho.S_THREAD_LOCAL_VARIABLES,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
- }
+ fileoff = mem.alignForward(u32, fileoff, page_size);
+ for (slice.items(.header)[last_index..], slice.items(.segment_id)[last_index..]) |*header, seg_id| {
+ if (header.isZerofill()) continue;
+ if (header.offset < fileoff) {
+ const existing_size = header.size;
+ header.size = 0;
- if (self.thread_data_section_index == null) {
- self.thread_data_section_index = try self.allocateSection("__DATA3", "__thread_data", .{
- .size = @sizeOf(u64),
- .alignment = @alignOf(u64),
- .flags = macho.S_THREAD_LOCAL_REGULAR,
- .prot = macho.PROT.READ | macho.PROT.WRITE,
- });
- self.segment_table_dirty = true;
- }
- }
+ // Must move the entire section.
+ const new_offset = self.findFreeSpace(existing_size, page_size);
- if (self.linkedit_segment_cmd_index == null) {
- self.linkedit_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
+ log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{
+ header.segName(),
+ header.sectName(),
+ new_offset,
+ new_offset + existing_size,
+ });
- try self.segments.append(gpa, .{
- .segname = makeStaticString("__LINKEDIT"),
- .maxprot = macho.PROT.READ,
- .initprot = macho.PROT.READ,
- .cmdsize = @sizeOf(macho.segment_command_64),
- });
- }
-}
+ try self.copyRangeAllZeroOut(header.offset, new_offset, existing_size);
-fn calcPagezeroSize(self: *MachO) u64 {
- const output_mode = self.base.comp.config.output_mode;
- const target = self.base.comp.root_mod.resolved_target.result;
- const page_size = getPageSize(target.cpu.arch);
- const aligned_pagezero_vmsize = mem.alignBackward(u64, self.pagezero_vmsize, page_size);
- if (output_mode == .Lib) return 0;
- if (aligned_pagezero_vmsize == 0) return 0;
- if (aligned_pagezero_vmsize != self.pagezero_vmsize) {
- log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{self.pagezero_vmsize});
- log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize});
+ header.offset = @intCast(new_offset);
+ header.size = existing_size;
+ self.segments.items[seg_id].fileoff = new_offset;
+ }
}
- return aligned_pagezero_vmsize;
}
-const InitSectionOpts = struct {
- flags: u32 = macho.S_REGULAR,
- reserved1: u32 = 0,
- reserved2: u32 = 0,
-};
+/// We allocate segments in a separate step to also consider segments that have no sections.
+fn allocateSegments(self: *MachO) void {
+ const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0;
+ const last_index = for (self.segments.items, 0..) |seg, i| {
+ if (mem.indexOf(u8, seg.segName(), "ZIG")) |_| break i;
+ } else self.segments.items.len;
-pub fn initSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: InitSectionOpts) !u8 {
- log.debug("creating section '{s},{s}'", .{ segname, sectname });
- const index = @as(u8, @intCast(self.sections.slice().len));
- const gpa = self.base.comp.gpa;
- try self.sections.append(gpa, .{
- .segment_index = undefined, // Segments will be created automatically later down the pipeline
- .header = .{
- .sectname = makeStaticString(sectname),
- .segname = makeStaticString(segname),
- .flags = opts.flags,
- .reserved1 = opts.reserved1,
- .reserved2 = opts.reserved2,
- },
- });
- return index;
-}
+ var vmaddr: u64 = if (self.pagezero_seg_index) |index|
+ self.segments.items[index].vmaddr + self.segments.items[index].vmsize
+ else
+ 0;
+ var fileoff: u64 = 0;
-fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: struct {
- size: u64 = 0,
- alignment: u32 = 0,
- prot: macho.vm_prot_t = macho.PROT.NONE,
- flags: u32 = macho.S_REGULAR,
- reserved2: u32 = 0,
-}) !u8 {
- const gpa = self.base.comp.gpa;
- const target = self.base.comp.root_mod.resolved_target.result;
- const page_size = getPageSize(target.cpu.arch);
- // In incremental context, we create one section per segment pairing. This way,
- // we can move the segment in raw file as we please.
- const segment_id = @as(u8, @intCast(self.segments.items.len));
- const vmaddr = blk: {
- const prev_segment = self.segments.items[segment_id - 1];
- break :blk mem.alignForward(u64, prev_segment.vmaddr + prev_segment.vmsize, page_size);
- };
- // We commit more memory than needed upfront so that we don't have to reallocate too soon.
- const vmsize = mem.alignForward(u64, opts.size, page_size);
- const off = self.findFreeSpace(opts.size, page_size);
-
- log.debug("found {s},{s} free space 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
- segname,
- sectname,
- off,
- off + opts.size,
- vmaddr,
- vmaddr + vmsize,
- });
+ const page_size = self.getPageSize();
+ const slice = self.sections.slice();
- const seg = try self.segments.addOne(gpa);
- seg.* = .{
- .segname = makeStaticString(segname),
- .vmaddr = vmaddr,
- .vmsize = vmsize,
- .fileoff = off,
- .filesize = vmsize,
- .maxprot = opts.prot,
- .initprot = opts.prot,
- .nsects = 1,
- .cmdsize = @sizeOf(macho.segment_command_64) + @sizeOf(macho.section_64),
- };
+ var next_sect_id: u8 = 0;
+ for (self.segments.items[first_index..last_index], first_index..last_index) |*seg, seg_id| {
+ seg.vmaddr = vmaddr;
+ seg.fileoff = fileoff;
- const sect_id = try self.initSection(segname, sectname, .{
- .flags = opts.flags,
- .reserved2 = opts.reserved2,
- });
- const section = &self.sections.items(.header)[sect_id];
- section.addr = mem.alignForward(u64, vmaddr, opts.alignment);
- section.offset = mem.alignForward(u32, @as(u32, @intCast(off)), opts.alignment);
- section.size = opts.size;
- section.@"align" = math.log2(opts.alignment);
- self.sections.items(.segment_index)[sect_id] = segment_id;
- assert(!section.isZerofill()); // TODO zerofill sections
-
- return sect_id;
-}
-
-fn growSection(self: *MachO, sect_id: u8, needed_size: u64) !void {
- const header = &self.sections.items(.header)[sect_id];
- const segment_index = self.sections.items(.segment_index)[sect_id];
- const segment = &self.segments.items[segment_index];
- const maybe_last_atom_index = self.sections.items(.last_atom_index)[sect_id];
- const sect_capacity = self.allocatedSize(header.offset);
- const target = self.base.comp.root_mod.resolved_target.result;
- const page_size = getPageSize(target.cpu.arch);
-
- if (needed_size > sect_capacity) {
- const new_offset = self.findFreeSpace(needed_size, page_size);
- const current_size = if (maybe_last_atom_index) |last_atom_index| blk: {
- const last_atom = self.getAtom(last_atom_index);
- const sym = last_atom.getSymbol(self);
- break :blk (sym.n_value + last_atom.size) - segment.vmaddr;
- } else header.size;
-
- log.debug("moving {s},{s} from 0x{x} to 0x{x}", .{
- header.segName(),
- header.sectName(),
- header.offset,
- new_offset,
- });
+ while (next_sect_id < slice.items(.header).len) : (next_sect_id += 1) {
+ const header = slice.items(.header)[next_sect_id];
+ const sid = slice.items(.segment_id)[next_sect_id];
- const amt = try self.base.file.?.copyRangeAll(
- header.offset,
- self.base.file.?,
- new_offset,
- current_size,
- );
- if (amt != current_size) return error.InputOutput;
- header.offset = @as(u32, @intCast(new_offset));
- segment.fileoff = new_offset;
- }
-
- const sect_vm_capacity = self.allocatedVirtualSize(segment.vmaddr);
- if (needed_size > sect_vm_capacity) {
- self.markRelocsDirtyByAddress(segment.vmaddr + segment.vmsize);
- try self.growSectionVirtualMemory(sect_id, needed_size);
- }
-
- header.size = needed_size;
- segment.filesize = mem.alignForward(u64, needed_size, page_size);
- segment.vmsize = mem.alignForward(u64, needed_size, page_size);
-}
-
-fn growSectionVirtualMemory(self: *MachO, sect_id: u8, needed_size: u64) !void {
- const target = self.base.comp.root_mod.resolved_target.result;
- const page_size = getPageSize(target.cpu.arch);
- const header = &self.sections.items(.header)[sect_id];
- const segment = self.getSegmentPtr(sect_id);
- const increased_size = padToIdeal(needed_size);
- const old_aligned_end = segment.vmaddr + segment.vmsize;
- const new_aligned_end = segment.vmaddr + mem.alignForward(u64, increased_size, page_size);
- const diff = new_aligned_end - old_aligned_end;
- log.debug("shifting every segment after {s},{s} in virtual memory by {x}", .{
- header.segName(),
- header.sectName(),
- diff,
- });
+ if (seg_id != sid) break;
- // TODO: enforce order by increasing VM addresses in self.sections container.
- for (self.sections.items(.header)[sect_id + 1 ..], 0..) |*next_header, next_sect_id| {
- const index = @as(u8, @intCast(sect_id + 1 + next_sect_id));
- const next_segment = self.getSegmentPtr(index);
- next_header.addr += diff;
- next_segment.vmaddr += diff;
-
- const maybe_last_atom_index = &self.sections.items(.last_atom_index)[index];
- if (maybe_last_atom_index.*) |last_atom_index| {
- var atom_index = last_atom_index;
- while (true) {
- const atom = self.getAtom(atom_index);
- const sym = atom.getSymbolPtr(self);
- sym.n_value += diff;
-
- if (atom.prev_index) |prev_index| {
- atom_index = prev_index;
- } else break;
+ vmaddr = header.addr + header.size;
+ if (!header.isZerofill()) {
+ fileoff = header.offset + header.size;
}
}
- }
-}
-pub fn addAtomToSection(self: *MachO, atom_index: Atom.Index) void {
- assert(self.mode == .zld);
- const atom = self.getAtomPtr(atom_index);
- const sym = self.getSymbol(atom.getSymbolWithLoc());
- var section = self.sections.get(sym.n_sect - 1);
- if (section.header.size > 0) {
- const last_atom = self.getAtomPtr(section.last_atom_index.?);
- last_atom.next_index = atom_index;
- atom.prev_index = section.last_atom_index;
- } else {
- section.first_atom_index = atom_index;
+ seg.vmsize = vmaddr - seg.vmaddr;
+ seg.filesize = fileoff - seg.fileoff;
+
+ vmaddr = mem.alignForward(u64, vmaddr, page_size);
+ fileoff = mem.alignForward(u64, fileoff, page_size);
}
- section.last_atom_index = atom_index;
- section.header.size += atom.size;
- self.sections.set(sym.n_sect - 1, section);
}
-fn allocateAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: Alignment) !u64 {
- const tracy = trace(@src());
- defer tracy.end();
-
- assert(self.mode == .incremental);
-
- const atom = self.getAtom(atom_index);
- const sect_id = atom.getSymbol(self).n_sect - 1;
- const segment = self.getSegmentPtr(sect_id);
- const header = &self.sections.items(.header)[sect_id];
- const free_list = &self.sections.items(.free_list)[sect_id];
- const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sect_id];
- const requires_padding = blk: {
- if (!header.isCode()) break :blk false;
- if (header.isSymbolStubs()) break :blk false;
- if (mem.eql(u8, "__stub_helper", header.sectName())) break :blk false;
- break :blk true;
- };
- const new_atom_ideal_capacity = if (requires_padding) padToIdeal(new_atom_size) else new_atom_size;
-
- // We use these to indicate our intention to update metadata, placing the new atom,
- // and possibly removing a free list node.
- // It would be simpler to do it inside the for loop below, but that would cause a
- // problem if an error was returned later in the function. So this action
- // is actually carried out at the end of the function, when errors are no longer possible.
- var atom_placement: ?Atom.Index = null;
- var free_list_removal: ?usize = null;
-
- // First we look for an appropriately sized free list node.
- // The list is unordered. We'll just take the first thing that works.
- const vaddr = blk: {
- var i: usize = 0;
- while (i < free_list.items.len) {
- const big_atom_index = free_list.items[i];
- const big_atom = self.getAtom(big_atom_index);
- // We now have a pointer to a live atom that has too much capacity.
- // Is it enough that we could fit this new atom?
- const sym = big_atom.getSymbol(self);
- const capacity = big_atom.capacity(self);
- const ideal_capacity = if (requires_padding) padToIdeal(capacity) else capacity;
- const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity;
- const capacity_end_vaddr = sym.n_value + capacity;
- const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
- const new_start_vaddr = alignment.backward(new_start_vaddr_unaligned);
- if (new_start_vaddr < ideal_capacity_end_vaddr) {
- // Additional bookkeeping here to notice if this free list node
- // should be deleted because the atom that it points to has grown to take up
- // more of the extra capacity.
- if (!big_atom.freeListEligible(self)) {
- _ = free_list.swapRemove(i);
- } else {
- i += 1;
- }
- continue;
- }
- // At this point we know that we will place the new atom here. But the
- // remaining question is whether there is still yet enough capacity left
- // over for there to still be a free list node.
- const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
- const keep_free_list_node = remaining_capacity >= min_text_capacity;
-
- // Set up the metadata to be updated, after errors are no longer possible.
- atom_placement = big_atom_index;
- if (!keep_free_list_node) {
- free_list_removal = i;
- }
- break :blk new_start_vaddr;
- } else if (maybe_last_atom_index.*) |last_index| {
- const last = self.getAtom(last_index);
- const last_symbol = last.getSymbol(self);
- const ideal_capacity = if (requires_padding) padToIdeal(last.size) else last.size;
- const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity;
- const new_start_vaddr = alignment.forward(ideal_capacity_end_vaddr);
- atom_placement = last_index;
- break :blk new_start_vaddr;
- } else {
- break :blk alignment.forward(segment.vmaddr);
+pub fn allocateAtoms(self: *MachO) void {
+ const slice = self.sections.slice();
+ for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
+ if (atoms.items.len == 0) continue;
+ for (atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index).?;
+ assert(atom.flags.alive);
+ atom.value += header.addr;
}
- };
+ }
- const expand_section = if (atom_placement) |placement_index|
- self.getAtom(placement_index).next_index == null
- else
- true;
- if (expand_section) {
- const needed_size = (vaddr + new_atom_size) - segment.vmaddr;
- try self.growSection(sect_id, needed_size);
- maybe_last_atom_index.* = atom_index;
- self.segment_table_dirty = true;
+ for (self.thunks.items) |*thunk| {
+ const header = self.sections.items(.header)[thunk.out_n_sect];
+ thunk.value += header.addr;
}
+}
- assert(alignment != .none);
- header.@"align" = @min(header.@"align", @intFromEnum(alignment));
- self.getAtomPtr(atom_index).size = new_atom_size;
+fn allocateSyntheticSymbols(self: *MachO) void {
+ const text_seg = self.getTextSegment();
- if (atom.prev_index) |prev_index| {
- const prev = self.getAtomPtr(prev_index);
- prev.next_index = atom.next_index;
- }
- if (atom.next_index) |next_index| {
- const next = self.getAtomPtr(next_index);
- next.prev_index = atom.prev_index;
+ if (self.mh_execute_header_index) |index| {
+ const global = self.getSymbol(index);
+ global.value = text_seg.vmaddr;
}
- if (atom_placement) |big_atom_index| {
- const big_atom = self.getAtomPtr(big_atom_index);
- const atom_ptr = self.getAtomPtr(atom_index);
- atom_ptr.prev_index = big_atom_index;
- atom_ptr.next_index = big_atom.next_index;
- big_atom.next_index = atom_index;
- } else {
- const atom_ptr = self.getAtomPtr(atom_index);
- atom_ptr.prev_index = null;
- atom_ptr.next_index = null;
- }
- if (free_list_removal) |i| {
- _ = free_list.swapRemove(i);
+ if (self.data_sect_index) |idx| {
+ const sect = self.sections.items(.header)[idx];
+ for (&[_]?Symbol.Index{
+ self.dso_handle_index,
+ self.mh_dylib_header_index,
+ self.dyld_private_index,
+ }) |maybe_index| {
+ if (maybe_index) |index| {
+ const global = self.getSymbol(index);
+ global.value = sect.addr;
+ global.out_n_sect = idx;
+ }
+ }
}
- return vaddr;
-}
+ for (self.boundary_symbols.items) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ const name = sym.getName(self);
-pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
- _ = lib_name;
- const gpa = self.base.comp.gpa;
- const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
- defer gpa.free(sym_name);
- return self.addUndefined(sym_name, .{ .add_stub = true });
-}
+ sym.flags.@"export" = false;
+ sym.value = text_seg.vmaddr;
-pub fn writeSegmentHeaders(self: *MachO, writer: anytype) !void {
- for (self.segments.items, 0..) |seg, i| {
- const indexes = self.getSectionIndexes(@intCast(i));
- var out_seg = seg;
- out_seg.cmdsize = @sizeOf(macho.segment_command_64);
- out_seg.nsects = 0;
-
- // Update section headers count; any section with size of 0 is excluded
- // since it doesn't have any data in the final binary file.
- for (self.sections.items(.header)[indexes.start..indexes.end]) |header| {
- if (header.size == 0) continue;
- out_seg.cmdsize += @sizeOf(macho.section_64);
- out_seg.nsects += 1;
- }
+ if (mem.startsWith(u8, name, "segment$start$")) {
+ const segname = name["segment$start$".len..];
+ if (self.getSegmentByName(segname)) |seg_id| {
+ const seg = self.segments.items[seg_id];
+ sym.value = seg.vmaddr;
+ }
+ } else if (mem.startsWith(u8, name, "segment$stop$")) {
+ const segname = name["segment$stop$".len..];
+ if (self.getSegmentByName(segname)) |seg_id| {
+ const seg = self.segments.items[seg_id];
+ sym.value = seg.vmaddr + seg.vmsize;
+ }
+ } else if (mem.startsWith(u8, name, "section$start$")) {
+ const actual_name = name["section$start$".len..];
+ const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
+ const segname = actual_name[0..sep];
+ const sectname = actual_name[sep + 1 ..];
+ if (self.getSectionByName(segname, sectname)) |sect_id| {
+ const sect = self.sections.items(.header)[sect_id];
+ sym.value = sect.addr;
+ sym.out_n_sect = sect_id;
+ }
+ } else if (mem.startsWith(u8, name, "section$stop$")) {
+ const actual_name = name["section$stop$".len..];
+ const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
+ const segname = actual_name[0..sep];
+ const sectname = actual_name[sep + 1 ..];
+ if (self.getSectionByName(segname, sectname)) |sect_id| {
+ const sect = self.sections.items(.header)[sect_id];
+ sym.value = sect.addr + sect.size;
+ sym.out_n_sect = sect_id;
+ }
+ } else unreachable;
+ }
- if (out_seg.nsects == 0 and
- (mem.eql(u8, out_seg.segName(), "__DATA_CONST") or
- mem.eql(u8, out_seg.segName(), "__DATA"))) continue;
+ if (self.objc_stubs.symbols.items.len > 0) {
+ const addr = self.sections.items(.header)[self.objc_stubs_sect_index.?].addr;
- try writer.writeStruct(out_seg);
- for (self.sections.items(.header)[indexes.start..indexes.end]) |header| {
- if (header.size == 0) continue;
- try writer.writeStruct(header);
+ for (self.objc_stubs.symbols.items, 0..) |sym_index, idx| {
+ const sym = self.getSymbol(sym_index);
+ sym.value = addr + idx * ObjcStubsSection.entrySize(self.getTarget().cpu.arch);
+ sym.out_n_sect = self.objc_stubs_sect_index.?;
}
}
}
-pub fn writeLinkeditSegmentData(self: *MachO) !void {
- const target = self.base.comp.root_mod.resolved_target.result;
- const page_size = getPageSize(target.cpu.arch);
- const seg = self.getLinkeditSegmentPtr();
- seg.filesize = 0;
- seg.vmsize = 0;
-
- for (self.segments.items, 0..) |segment, id| {
- if (self.linkedit_segment_cmd_index.? == @as(u8, @intCast(id))) continue;
- if (seg.vmaddr < segment.vmaddr + segment.vmsize) {
- seg.vmaddr = mem.alignForward(u64, segment.vmaddr + segment.vmsize, page_size);
- }
- if (seg.fileoff < segment.fileoff + segment.filesize) {
- seg.fileoff = mem.alignForward(u64, segment.fileoff + segment.filesize, page_size);
- }
- }
+fn allocateLinkeditSegment(self: *MachO) !void {
+ var fileoff: u64 = 0;
+ var vmaddr: u64 = 0;
- try self.writeDyldInfoData();
- // TODO handle this better
- if (self.mode == .zld) {
- try self.writeFunctionStarts();
- try self.writeDataInCode();
+ for (self.segments.items) |seg| {
+ if (fileoff < seg.fileoff + seg.filesize) fileoff = seg.fileoff + seg.filesize;
+ if (vmaddr < seg.vmaddr + seg.vmsize) vmaddr = seg.vmaddr + seg.vmsize;
}
- try self.writeSymtabs();
- seg.vmsize = mem.alignForward(u64, seg.filesize, page_size);
+ const page_size = self.getPageSize();
+ const seg = self.getLinkeditSegment();
+ seg.vmaddr = mem.alignForward(u64, vmaddr, page_size);
+ seg.fileoff = mem.alignForward(u64, fileoff, page_size);
}
-fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, table: anytype) !void {
- const gpa = self.base.comp.gpa;
- const header = self.sections.items(.header)[sect_id];
- const segment_index = self.sections.items(.segment_index)[sect_id];
- const segment = self.segments.items[segment_index];
- const base_offset = header.addr - segment.vmaddr;
- const is_got = if (self.got_section_index) |index| index == sect_id else false;
-
- try rebase.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
-
- for (table.entries.items, 0..) |entry, i| {
- if (!table.lookup.contains(entry)) continue;
- const sym = self.getSymbol(entry);
- if (is_got and sym.undf()) continue;
- const offset = i * @sizeOf(u64);
- log.debug(" | rebase at {x}", .{base_offset + offset});
- rebase.entries.appendAssumeCapacity(.{
- .offset = base_offset + offset,
- .segment_id = segment_index,
- });
- }
-}
+fn initDyldInfoSections(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
-fn collectRebaseData(self: *MachO, rebase: *Rebase) !void {
const gpa = self.base.comp.gpa;
- const slice = self.sections.slice();
-
- for (self.rebases.keys(), 0..) |atom_index, i| {
- const atom = self.getAtom(atom_index);
- log.debug(" ATOM(%{?d}, '{s}')", .{ atom.getSymbolIndex(), atom.getName(self) });
-
- const sym = atom.getSymbol(self);
- const segment_index = slice.items(.segment_index)[sym.n_sect - 1];
- const seg = self.getSegment(sym.n_sect - 1);
-
- const base_offset = sym.n_value - seg.vmaddr;
- const rebases = self.rebases.values()[i];
- try rebase.entries.ensureUnusedCapacity(gpa, rebases.items.len);
-
- for (rebases.items) |offset| {
- log.debug(" | rebase at {x}", .{base_offset + offset});
-
- rebase.entries.appendAssumeCapacity(.{
- .offset = base_offset + offset,
- .segment_id = segment_index,
- });
- }
- }
-
- // Unpack GOT entries
- if (self.got_section_index) |sect_id| {
- try self.collectRebaseDataFromTableSection(sect_id, rebase, self.got_table);
+ if (self.zig_got_sect_index != null) try self.zig_got.addDyldRelocs(self);
+ if (self.got_sect_index != null) try self.got.addDyldRelocs(self);
+ if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self);
+ if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self);
+ try self.initExportTrie();
+
+ var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
+ defer objects.deinit();
+ if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
+ objects.appendSliceAssumeCapacity(self.objects.items);
+
+ var nrebases: usize = 0;
+ var nbinds: usize = 0;
+ var nweak_binds: usize = 0;
+ for (objects.items) |index| {
+ const ctx = switch (self.getFile(index).?) {
+ .zig_object => |x| x.dynamic_relocs,
+ .object => |x| x.dynamic_relocs,
+ else => unreachable,
+ };
+ nrebases += ctx.rebase_relocs;
+ nbinds += ctx.bind_relocs;
+ nweak_binds += ctx.weak_bind_relocs;
}
+ try self.rebase.entries.ensureUnusedCapacity(gpa, nrebases);
+ try self.bind.entries.ensureUnusedCapacity(gpa, nbinds);
+ try self.weak_bind.entries.ensureUnusedCapacity(gpa, nweak_binds);
+}
- // Next, unpack __la_symbol_ptr entries
- if (self.la_symbol_ptr_section_index) |sect_id| {
- try self.collectRebaseDataFromTableSection(sect_id, rebase, self.stub_table);
- }
+fn initExportTrie(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- // Finally, unpack the rest.
- const target = self.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- for (self.objects.items) |*object| {
- for (object.atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const sym = self.getSymbol(atom.getSymbolWithLoc());
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
-
- const sect_id = sym.n_sect - 1;
- const section = self.sections.items(.header)[sect_id];
- const segment_id = self.sections.items(.segment_index)[sect_id];
- const segment = self.segments.items[segment_id];
- if (segment.maxprot & macho.PROT.WRITE == 0) continue;
- switch (section.type()) {
- macho.S_LITERAL_POINTERS,
- macho.S_REGULAR,
- macho.S_MOD_INIT_FUNC_POINTERS,
- macho.S_MOD_TERM_FUNC_POINTERS,
- => {},
- else => continue,
+ const gpa = self.base.comp.gpa;
+ try self.export_trie.init(gpa);
+
+ const seg = self.getTextSegment();
+ for (self.objects.items) |index| {
+ for (self.getFile(index).?.getSymbols()) |sym_index| {
+ const sym = self.getSymbol(sym_index);
+ if (!sym.flags.@"export") continue;
+ if (sym.getAtom(self)) |atom| if (!atom.flags.alive) continue;
+ if (sym.getFile(self).?.getIndex() != index) continue;
+ var flags: u64 = if (sym.flags.abs)
+ macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
+ else if (sym.flags.tlv)
+ macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
+ else
+ macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
+ if (sym.flags.weak) {
+ flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
+ self.weak_defines = true;
+ self.binds_to_weak = true;
}
-
- log.debug(" ATOM({d}, %{d}, '{s}')", .{
- atom_index,
- atom.sym_index,
- self.getSymbolName(atom.getSymbolWithLoc()),
+ try self.export_trie.put(gpa, .{
+ .name = sym.getName(self),
+ .vmaddr_offset = sym.getAddress(.{ .stubs = false }, self) - seg.vmaddr,
+ .export_flags = flags,
});
-
- const code = Atom.getAtomCode(self, atom_index);
- const relocs = Atom.getAtomRelocs(self, atom_index);
- const ctx = Atom.getRelocContext(self, atom_index);
-
- for (relocs) |rel| {
- switch (cpu_arch) {
- .aarch64 => {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
- if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
- if (rel.r_length != 3) continue;
- },
- .x86_64 => {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
- if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
- if (rel.r_length != 3) continue;
- },
- else => unreachable,
- }
- const reloc_target = Atom.parseRelocTarget(self, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- });
- const target_sym = self.getSymbol(reloc_target);
- if (target_sym.undf()) continue;
-
- const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr));
- const rel_offset = rel.r_address - ctx.base_offset;
- const offset = @as(u64, @intCast(base_offset + rel_offset));
- log.debug(" | rebase at {x}", .{offset});
-
- try rebase.entries.append(gpa, .{
- .offset = offset,
- .segment_id = segment_id,
- });
- }
}
}
- try rebase.finalize(gpa);
-}
-
-fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void {
- const gpa = self.base.comp.gpa;
- const header = self.sections.items(.header)[sect_id];
- const segment_index = self.sections.items(.segment_index)[sect_id];
- const segment = self.segments.items[segment_index];
- const base_offset = header.addr - segment.vmaddr;
-
- try bind.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
-
- for (table.entries.items, 0..) |entry, i| {
- if (!table.lookup.contains(entry)) continue;
- const bind_sym = self.getSymbol(entry);
- if (!bind_sym.undf()) continue;
- const offset = i * @sizeOf(u64);
- log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
- base_offset + offset,
- self.getSymbolName(entry),
- @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER),
- });
- if (bind_sym.weakRef()) {
- log.debug(" | marking as weak ref ", .{});
- }
- bind.entries.appendAssumeCapacity(.{
- .target = entry,
- .offset = base_offset + offset,
- .segment_id = segment_index,
- .addend = 0,
+ if (self.mh_execute_header_index) |index| {
+ const sym = self.getSymbol(index);
+ try self.export_trie.put(gpa, .{
+ .name = sym.getName(self),
+ .vmaddr_offset = sym.getAddress(.{}, self) - seg.vmaddr,
+ .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
});
}
}
-fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void {
- const gpa = self.base.comp.gpa;
- const slice = self.sections.slice();
-
- for (raw_bindings.keys(), 0..) |atom_index, i| {
- const atom = self.getAtom(atom_index);
- log.debug(" ATOM(%{?d}, '{s}')", .{ atom.getSymbolIndex(), atom.getName(self) });
-
- const sym = atom.getSymbol(self);
- const segment_index = slice.items(.segment_index)[sym.n_sect - 1];
- const seg = self.getSegment(sym.n_sect - 1);
+fn writeAtoms(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- const base_offset = sym.n_value - seg.vmaddr;
+ const gpa = self.base.comp.gpa;
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
- const bindings = raw_bindings.values()[i];
- try bind.entries.ensureUnusedCapacity(gpa, bindings.items.len);
+ const cpu_arch = self.getTarget().cpu.arch;
+ const slice = self.sections.slice();
- for (bindings.items) |binding| {
- const bind_sym = self.getSymbol(binding.target);
- const bind_sym_name = self.getSymbolName(binding.target);
- const dylib_ordinal = @divTrunc(
- @as(i16, @bitCast(bind_sym.n_desc)),
- macho.N_SYMBOL_RESOLVER,
- );
- log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
- binding.offset + base_offset,
- bind_sym_name,
- dylib_ordinal,
- });
- if (bind_sym.weakRef()) {
- log.debug(" | marking as weak ref ", .{});
- }
- bind.entries.appendAssumeCapacity(.{
- .target = binding.target,
- .offset = binding.offset + base_offset,
- .segment_id = segment_index,
- .addend = 0,
- });
+ var has_resolve_error = false;
+ for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
+ if (atoms.items.len == 0) continue;
+ if (header.isZerofill()) continue;
+
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ const buffer = try gpa.alloc(u8, size);
+ defer gpa.free(buffer);
+ const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
+ @memset(buffer, padding_byte);
+
+ for (atoms.items) |atom_index| {
+ const atom = self.getAtom(atom_index).?;
+ assert(atom.flags.alive);
+ const off = math.cast(usize, atom.value - header.addr) orelse return error.Overflow;
+ const data = switch (atom.getFile(self)) {
+ .object => |x| try x.getAtomData(atom.*),
+ .zig_object => |x| try x.getAtomDataAlloc(self, arena.allocator(), atom.*),
+ else => unreachable,
+ };
+ const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
+ @memcpy(buffer[off..][0..atom_size], data);
+ atom.resolveRelocs(self, buffer[off..][0..atom_size]) catch |err| switch (err) {
+ error.ResolveFailed => has_resolve_error = true,
+ else => |e| return e,
+ };
}
- }
- // Unpack GOT pointers
- if (self.got_section_index) |sect_id| {
- try self.collectBindDataFromTableSection(sect_id, bind, self.got_table);
+ try self.base.file.?.pwriteAll(buffer, header.offset);
}
- // Next, unpack TLV pointers section
- if (self.tlv_ptr_section_index) |sect_id| {
- try self.collectBindDataFromTableSection(sect_id, bind, self.tlv_ptr_table);
+ for (self.thunks.items) |thunk| {
+ const header = slice.items(.header)[thunk.out_n_sect];
+ const offset = thunk.value - header.addr + header.offset;
+ const buffer = try gpa.alloc(u8, thunk.size());
+ defer gpa.free(buffer);
+ var stream = std.io.fixedBufferStream(buffer);
+ try thunk.write(self, stream.writer());
+ try self.base.file.?.pwriteAll(buffer, offset);
}
- // Finally, unpack the rest.
- const target = self.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- for (self.objects.items) |*object| {
- for (object.atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const sym = self.getSymbol(atom.getSymbolWithLoc());
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
-
- const sect_id = sym.n_sect - 1;
- const section = self.sections.items(.header)[sect_id];
- const segment_id = self.sections.items(.segment_index)[sect_id];
- const segment = self.segments.items[segment_id];
- if (segment.maxprot & macho.PROT.WRITE == 0) continue;
- switch (section.type()) {
- macho.S_LITERAL_POINTERS,
- macho.S_REGULAR,
- macho.S_MOD_INIT_FUNC_POINTERS,
- macho.S_MOD_TERM_FUNC_POINTERS,
- => {},
- else => continue,
- }
-
- log.debug(" ATOM({d}, %{d}, '{s}')", .{
- atom_index,
- atom.sym_index,
- self.getSymbolName(atom.getSymbolWithLoc()),
- });
+ if (has_resolve_error) return error.ResolveFailed;
+}
- const code = Atom.getAtomCode(self, atom_index);
- const relocs = Atom.getAtomRelocs(self, atom_index);
- const ctx = Atom.getRelocContext(self, atom_index);
+fn writeUnwindInfo(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- for (relocs) |rel| {
- switch (cpu_arch) {
- .aarch64 => {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
- if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
- if (rel.r_length != 3) continue;
- },
- .x86_64 => {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
- if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
- if (rel.r_length != 3) continue;
- },
- else => unreachable,
- }
+ const gpa = self.base.comp.gpa;
- const global = Atom.parseRelocTarget(self, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- });
- const bind_sym_name = self.getSymbolName(global);
- const bind_sym = self.getSymbol(global);
- if (!bind_sym.undf()) continue;
-
- const base_offset = sym.n_value - segment.vmaddr;
- const rel_offset = @as(u32, @intCast(rel.r_address - ctx.base_offset));
- const offset = @as(u64, @intCast(base_offset + rel_offset));
- const addend = mem.readInt(i64, code[rel_offset..][0..8], .little);
-
- const dylib_ordinal = @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER);
- log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
- base_offset,
- bind_sym_name,
- dylib_ordinal,
- });
- log.debug(" | with addend {x}", .{addend});
- if (bind_sym.weakRef()) {
- log.debug(" | marking as weak ref ", .{});
- }
- try bind.entries.append(gpa, .{
- .target = global,
- .offset = offset,
- .segment_id = segment_id,
- .addend = addend,
- });
- }
- }
+ if (self.eh_frame_sect_index) |index| {
+ const header = self.sections.items(.header)[index];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ const buffer = try gpa.alloc(u8, size);
+ defer gpa.free(buffer);
+ eh_frame.write(self, buffer);
+ try self.base.file.?.pwriteAll(buffer, header.offset);
}
- try bind.finalize(gpa, self);
-}
-
-fn collectLazyBindData(self: *MachO, bind: anytype) !void {
- const sect_id = self.la_symbol_ptr_section_index orelse return;
- const gpa = self.base.comp.gpa;
- try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table);
- try bind.finalize(gpa, self);
+ if (self.unwind_info_sect_index) |index| {
+ const header = self.sections.items(.header)[index];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ const buffer = try gpa.alloc(u8, size);
+ defer gpa.free(buffer);
+ try self.unwind_info.write(self, buffer);
+ try self.base.file.?.pwriteAll(buffer, header.offset);
+ }
}
-fn collectExportData(self: *MachO, trie: *Trie) !void {
+fn finalizeDyldInfoSections(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
const gpa = self.base.comp.gpa;
- // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER.
- log.debug("generating export trie", .{});
-
- const exec_segment = self.segments.items[self.header_segment_cmd_index.?];
- const base_address = exec_segment.vmaddr;
-
- for (self.globals.items) |global| {
- const sym = self.getSymbol(global);
-
- if (sym.undf()) continue;
- assert(sym.ext());
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
+ try self.rebase.finalize(gpa);
+ try self.bind.finalize(gpa, self);
+ try self.weak_bind.finalize(gpa, self);
+ try self.lazy_bind.finalize(gpa, self);
+ try self.export_trie.finalize(gpa);
+}
- const sym_name = self.getSymbolName(global);
- log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });
- try trie.put(gpa, .{
- .name = sym_name,
- .vmaddr_offset = sym.n_value - base_address,
- .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
- });
- }
+fn writeSyntheticSections(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- try trie.finalize(gpa);
-}
+ const gpa = self.base.comp.gpa;
-fn writeDyldInfoData(self: *MachO) !void {
+ if (self.got_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.got.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+
+ if (self.stubs_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.stubs.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+
+ if (self.stubs_helper_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.stubs_helper.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+
+ if (self.la_symbol_ptr_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.la_symbol_ptr.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+
+ if (self.tlv_ptr_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.tlv_ptr.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+
+ if (self.objc_stubs_sect_index) |sect_id| {
+ const header = self.sections.items(.header)[sect_id];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
+ defer buffer.deinit();
+ try self.objc_stubs.write(self, buffer.writer());
+ assert(buffer.items.len == header.size);
+ try self.base.file.?.pwriteAll(buffer.items, header.offset);
+ }
+}
+
+fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
const tracy = trace(@src());
defer tracy.end();
const gpa = self.base.comp.gpa;
+ const cmd = &self.dyld_info_cmd;
+ var needed_size: u32 = 0;
- var rebase = Rebase{};
- defer rebase.deinit(gpa);
- try self.collectRebaseData(&rebase);
-
- var bind = Bind{};
- defer bind.deinit(gpa);
- try self.collectBindData(&bind, self.bindings);
-
- var lazy_bind = LazyBind{};
- defer lazy_bind.deinit(gpa);
- try self.collectLazyBindData(&lazy_bind);
-
- var trie: Trie = .{};
- defer trie.deinit(gpa);
- try trie.init(gpa);
- try self.collectExportData(&trie);
-
- const link_seg = self.getLinkeditSegmentPtr();
- assert(mem.isAlignedGeneric(u64, link_seg.fileoff, @alignOf(u64)));
- const rebase_off = link_seg.fileoff;
- const rebase_size = rebase.size();
- const rebase_size_aligned = mem.alignForward(u64, rebase_size, @alignOf(u64));
- log.debug("writing rebase info from 0x{x} to 0x{x}", .{ rebase_off, rebase_off + rebase_size_aligned });
-
- const bind_off = rebase_off + rebase_size_aligned;
- const bind_size = bind.size();
- const bind_size_aligned = mem.alignForward(u64, bind_size, @alignOf(u64));
- log.debug("writing bind info from 0x{x} to 0x{x}", .{ bind_off, bind_off + bind_size_aligned });
-
- const lazy_bind_off = bind_off + bind_size_aligned;
- const lazy_bind_size = lazy_bind.size();
- const lazy_bind_size_aligned = mem.alignForward(u64, lazy_bind_size, @alignOf(u64));
- log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
- lazy_bind_off,
- lazy_bind_off + lazy_bind_size_aligned,
- });
+ cmd.rebase_off = needed_size;
+ cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64));
+ needed_size += cmd.rebase_size;
+
+ cmd.bind_off = needed_size;
+ cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64));
+ needed_size += cmd.bind_size;
- const export_off = lazy_bind_off + lazy_bind_size_aligned;
- const export_size = trie.size;
- const export_size_aligned = mem.alignForward(u64, export_size, @alignOf(u64));
- log.debug("writing export trie from 0x{x} to 0x{x}", .{ export_off, export_off + export_size_aligned });
+ cmd.weak_bind_off = needed_size;
+ cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64));
+ needed_size += cmd.weak_bind_size;
- const needed_size = math.cast(usize, export_off + export_size_aligned - rebase_off) orelse
- return error.Overflow;
- link_seg.filesize = needed_size;
- assert(mem.isAlignedGeneric(u64, link_seg.fileoff + link_seg.filesize, @alignOf(u64)));
+ cmd.lazy_bind_off = needed_size;
+ cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64));
+ needed_size += cmd.lazy_bind_size;
+
+ cmd.export_off = needed_size;
+ cmd.export_size = mem.alignForward(u32, @intCast(self.export_trie.size), @alignOf(u64));
+ needed_size += cmd.export_size;
const buffer = try gpa.alloc(u8, needed_size);
defer gpa.free(buffer);
@@ -3990,689 +2773,374 @@ fn writeDyldInfoData(self: *MachO) !void {
var stream = std.io.fixedBufferStream(buffer);
const writer = stream.writer();
- try rebase.write(writer);
- try stream.seekTo(bind_off - rebase_off);
-
- try bind.write(writer);
- try stream.seekTo(lazy_bind_off - rebase_off);
-
- try lazy_bind.write(writer);
- try stream.seekTo(export_off - rebase_off);
+ try self.rebase.write(writer);
+ try stream.seekTo(cmd.bind_off);
+ try self.bind.write(writer);
+ try stream.seekTo(cmd.weak_bind_off);
+ try self.weak_bind.write(writer);
+ try stream.seekTo(cmd.lazy_bind_off);
+ try self.lazy_bind.write(writer);
+ try stream.seekTo(cmd.export_off);
+ try self.export_trie.write(writer);
- _ = try trie.write(writer);
+ cmd.rebase_off += off;
+ cmd.bind_off += off;
+ cmd.weak_bind_off += off;
+ cmd.lazy_bind_off += off;
+ cmd.export_off += off;
- log.debug("writing dyld info from 0x{x} to 0x{x}", .{
- rebase_off,
- rebase_off + needed_size,
- });
-
- try self.base.file.?.pwriteAll(buffer, rebase_off);
- try self.populateLazyBindOffsetsInStubHelper(lazy_bind);
+ try self.base.file.?.pwriteAll(buffer, off);
- self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off));
- self.dyld_info_cmd.rebase_size = @as(u32, @intCast(rebase_size_aligned));
- self.dyld_info_cmd.bind_off = @as(u32, @intCast(bind_off));
- self.dyld_info_cmd.bind_size = @as(u32, @intCast(bind_size_aligned));
- self.dyld_info_cmd.lazy_bind_off = @as(u32, @intCast(lazy_bind_off));
- self.dyld_info_cmd.lazy_bind_size = @as(u32, @intCast(lazy_bind_size_aligned));
- self.dyld_info_cmd.export_off = @as(u32, @intCast(export_off));
- self.dyld_info_cmd.export_size = @as(u32, @intCast(export_size_aligned));
+ return off + needed_size;
}
-fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: anytype) !void {
- if (lazy_bind.size() == 0) return;
-
- const stub_helper_section_index = self.stub_helper_section_index.?;
- // assert(ctx.stub_helper_preamble_allocated);
-
- const header = self.sections.items(.header)[stub_helper_section_index];
-
- const target = self.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const preamble_size = stubs.stubHelperPreambleSize(cpu_arch);
- const stub_size = stubs.stubHelperSize(cpu_arch);
- const stub_offset = stubs.stubOffsetInStubHelper(cpu_arch);
- const base_offset = header.offset + preamble_size;
-
- for (lazy_bind.offsets.items, 0..) |bind_offset, index| {
- const file_offset = base_offset + index * stub_size + stub_offset;
-
- log.debug("writing lazy bind offset 0x{x} ({s}) in stub helper at 0x{x}", .{
- bind_offset,
- self.getSymbolName(lazy_bind.entries.items[index].target),
- file_offset,
- });
-
- try self.base.file.?.pwriteAll(mem.asBytes(&bind_offset), file_offset);
- }
+fn writeFunctionStarts(self: *MachO, off: u32) !u32 {
+ // TODO actually write it out
+ const cmd = &self.function_starts_cmd;
+ cmd.dataoff = off;
+ return off;
}
-const asc_u64 = std.sort.asc(u64);
+pub fn writeDataInCode(self: *MachO, base_address: u64, off: u32) !u32 {
+ const cmd = &self.data_in_code_cmd;
+ cmd.dataoff = off;
-fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void {
- const sym = self.getSymbol(sym_loc);
- if (sym.n_strx == 0) return;
- if (sym.n_desc == N_DEAD) return;
- if (sym.n_desc == N_BOUNDARY) return;
- if (self.symbolIsTemp(sym_loc)) return;
- try addresses.append(sym.n_value);
-}
-
-fn writeFunctionStarts(self: *MachO) !void {
const gpa = self.base.comp.gpa;
- const seg = self.segments.items[self.header_segment_cmd_index.?];
+ var dices = std.ArrayList(macho.data_in_code_entry).init(gpa);
+ defer dices.deinit();
- // We need to sort by address first
- var addresses = std.ArrayList(u64).init(gpa);
- defer addresses.deinit();
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ const in_dices = object.getDataInCode();
- for (self.objects.items) |object| {
- for (object.exec_atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const sym_loc = atom.getSymbolWithLoc();
- try self.addSymbolToFunctionStarts(sym_loc, &addresses);
+ try dices.ensureUnusedCapacity(in_dices.len);
- var it = Atom.getInnerSymbolsIterator(self, atom_index);
- while (it.next()) |inner_sym_loc| {
- try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses);
- }
+ var next_dice: usize = 0;
+ for (object.atoms.items) |atom_index| {
+ if (next_dice >= in_dices.len) break;
+ const atom = self.getAtom(atom_index) orelse continue;
+ const start_off = atom.getInputAddress(self);
+ const end_off = start_off + atom.size;
+ const start_dice = next_dice;
+
+ if (end_off < in_dices[next_dice].offset) continue;
+
+ while (next_dice < in_dices.len and
+ in_dices[next_dice].offset < end_off) : (next_dice += 1)
+ {}
+
+ if (atom.flags.alive) for (in_dices[start_dice..next_dice]) |dice| {
+ dices.appendAssumeCapacity(.{
+ .offset = @intCast(atom.value + dice.offset - start_off - base_address),
+ .length = dice.length,
+ .kind = dice.kind,
+ });
+ };
}
}
- mem.sort(u64, addresses.items, {}, asc_u64);
+ const needed_size = math.cast(u32, dices.items.len * @sizeOf(macho.data_in_code_entry)) orelse return error.Overflow;
+ cmd.datasize = needed_size;
- var offsets = std.ArrayList(u32).init(gpa);
- defer offsets.deinit();
- try offsets.ensureTotalCapacityPrecise(addresses.items.len);
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(dices.items), cmd.dataoff);
- var last_off: u32 = 0;
- for (addresses.items) |addr| {
- const offset = @as(u32, @intCast(addr - seg.vmaddr));
- const diff = offset - last_off;
+ return off + needed_size;
+}
- if (diff == 0) continue;
+pub fn calcSymtabSize(self: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = self.base.comp.gpa;
- offsets.appendAssumeCapacity(diff);
- last_off = offset;
+ var nlocals: u32 = 0;
+ var nstabs: u32 = 0;
+ var nexports: u32 = 0;
+ var nimports: u32 = 0;
+ var strsize: u32 = 0;
+
+ var files = std.ArrayList(File.Index).init(gpa);
+ defer files.deinit();
+ try files.ensureTotalCapacityPrecise(self.objects.items.len + self.dylibs.items.len + 2);
+ if (self.zig_object) |index| files.appendAssumeCapacity(index);
+ for (self.objects.items) |index| files.appendAssumeCapacity(index);
+ for (self.dylibs.items) |index| files.appendAssumeCapacity(index);
+ if (self.internal_object) |index| files.appendAssumeCapacity(index);
+
+ for (files.items) |index| {
+ const file = self.getFile(index).?;
+ const ctx = switch (file) {
+ inline else => |x| &x.output_symtab_ctx,
+ };
+ ctx.ilocal = nlocals;
+ ctx.istab = nstabs;
+ ctx.iexport = nexports;
+ ctx.iimport = nimports;
+ try file.calcSymtabSize(self);
+ nlocals += ctx.nlocals;
+ nstabs += ctx.nstabs;
+ nexports += ctx.nexports;
+ nimports += ctx.nimports;
+ strsize += ctx.strsize;
+ }
+
+ for (files.items) |index| {
+ const file = self.getFile(index).?;
+ const ctx = switch (file) {
+ inline else => |x| &x.output_symtab_ctx,
+ };
+ ctx.istab += nlocals;
+ ctx.iexport += nlocals + nstabs;
+ ctx.iimport += nlocals + nstabs + nexports;
}
- var buffer = std.ArrayList(u8).init(gpa);
- defer buffer.deinit();
-
- const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64)));
- try buffer.ensureTotalCapacity(max_size);
-
- for (offsets.items) |offset| {
- try std.leb.writeULEB128(buffer.writer(), offset);
+ {
+ const cmd = &self.symtab_cmd;
+ cmd.nsyms = nlocals + nstabs + nexports + nimports;
+ cmd.strsize = strsize + 1;
}
- const link_seg = self.getLinkeditSegmentPtr();
- const offset = link_seg.fileoff + link_seg.filesize;
- assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
- const needed_size = buffer.items.len;
- const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
- const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
- if (padding > 0) {
- try buffer.ensureUnusedCapacity(padding);
- buffer.appendNTimesAssumeCapacity(0, padding);
+ {
+ const cmd = &self.dysymtab_cmd;
+ cmd.ilocalsym = 0;
+ cmd.nlocalsym = nlocals + nstabs;
+ cmd.iextdefsym = nlocals + nstabs;
+ cmd.nextdefsym = nexports;
+ cmd.iundefsym = nlocals + nstabs + nexports;
+ cmd.nundefsym = nimports;
}
- link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff;
-
- log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
+}
- try self.base.file.?.pwriteAll(buffer.items, offset);
+pub fn writeSymtab(self: *MachO, off: u32) !u32 {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = self.base.comp.gpa;
+ const cmd = &self.symtab_cmd;
+ cmd.symoff = off;
- self.function_starts_cmd.dataoff = @as(u32, @intCast(offset));
- self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
-}
+ try self.symtab.resize(gpa, cmd.nsyms);
+ try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1);
-fn filterDataInCode(
- dices: []const macho.data_in_code_entry,
- start_addr: u64,
- end_addr: u64,
-) []const macho.data_in_code_entry {
- const Predicate = struct {
- addr: u64,
+ if (self.getZigObject()) |zo| {
+ zo.writeSymtab(self);
+ }
+ for (self.objects.items) |index| {
+ try self.getFile(index).?.writeSymtab(self);
+ }
+ for (self.dylibs.items) |index| {
+ try self.getFile(index).?.writeSymtab(self);
+ }
+ if (self.getInternalObject()) |internal| {
+ internal.writeSymtab(self);
+ }
- pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool {
- return dice.offset >= self.addr;
- }
- };
+ assert(self.strtab.items.len == cmd.strsize);
- const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr });
- const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start;
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff);
- return dices[start..end];
+ return off + cmd.nsyms * @sizeOf(macho.nlist_64);
}
-pub fn writeDataInCode(self: *MachO) !void {
+fn writeIndsymtab(self: *MachO, off: u32) !u32 {
const gpa = self.base.comp.gpa;
- var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa);
- defer out_dice.deinit();
-
- const text_sect_id = self.text_section_index orelse return;
- const text_sect_header = self.sections.items(.header)[text_sect_id];
-
- for (self.objects.items) |object| {
- if (!object.hasDataInCode()) continue;
- const dice = object.data_in_code.items;
- try out_dice.ensureUnusedCapacity(dice.len);
-
- for (object.exec_atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const sym = self.getSymbol(atom.getSymbolWithLoc());
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) return;
-
- const source_addr = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
- source_sym.n_value
- else blk: {
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const source_sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- break :blk object.getSourceSection(source_sect_id).addr;
- };
- const filtered_dice = filterDataInCode(dice, source_addr, source_addr + atom.size);
- const base = math.cast(u32, sym.n_value - text_sect_header.addr + text_sect_header.offset) orelse
- return error.Overflow;
-
- for (filtered_dice) |single| {
- const offset = math.cast(u32, single.offset - source_addr + base) orelse
- return error.Overflow;
- out_dice.appendAssumeCapacity(.{
- .offset = offset,
- .length = single.length,
- .kind = single.kind,
- });
- }
- }
- }
-
- const seg = self.getLinkeditSegmentPtr();
- const offset = seg.fileoff + seg.filesize;
- assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
- const needed_size = out_dice.items.len * @sizeOf(macho.data_in_code_entry);
- const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
- seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
- const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
- defer gpa.free(buffer);
- {
- const src = mem.sliceAsBytes(out_dice.items);
- @memcpy(buffer[0..src.len], src);
- @memset(buffer[src.len..], 0);
- }
+ const cmd = &self.dysymtab_cmd;
+ cmd.indirectsymoff = off;
+ cmd.nindirectsyms = self.indsymtab.nsyms(self);
- log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
+ const needed_size = cmd.nindirectsyms * @sizeOf(u32);
+ var buffer = try std.ArrayList(u8).initCapacity(gpa, needed_size);
+ defer buffer.deinit();
+ try self.indsymtab.write(self, buffer.writer());
- try self.base.file.?.pwriteAll(buffer, offset);
+ try self.base.file.?.pwriteAll(buffer.items, cmd.indirectsymoff);
+ assert(buffer.items.len == needed_size);
- self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset));
- self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
+ return off + needed_size;
}
-fn writeSymtabs(self: *MachO) !void {
- var ctx = try self.writeSymtab();
- defer ctx.imports_table.deinit();
- try self.writeDysymtab(ctx);
- try self.writeStrtab();
+pub fn writeStrtab(self: *MachO, off: u32) !u32 {
+ const cmd = &self.symtab_cmd;
+ cmd.stroff = off;
+ try self.base.file.?.pwriteAll(self.strtab.items, cmd.stroff);
+ return off + cmd.strsize;
}
-fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void {
- const sym = self.getSymbol(sym_loc);
- if (sym.n_strx == 0) return; // no name, skip
- if (sym.n_desc == N_DEAD) return; // garbage-collected, skip
- if (sym.n_desc == N_BOUNDARY) return; // boundary symbol, skip
- if (sym.ext()) return; // an export lands in its own symtab section, skip
- if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip
+fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
const gpa = self.base.comp.gpa;
- var out_sym = sym;
- out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(sym_loc));
- try locals.append(out_sym);
-}
-
-fn writeSymtab(self: *MachO) !SymtabCtx {
- const comp = self.base.comp;
- const gpa = comp.gpa;
-
- var locals = std.ArrayList(macho.nlist_64).init(gpa);
- defer locals.deinit();
+ const needed_size = load_commands.calcLoadCommandsSize(self, false);
+ const buffer = try gpa.alloc(u8, needed_size);
+ defer gpa.free(buffer);
- for (0..self.locals.items.len) |sym_id| {
- try self.addLocalToSymtab(.{ .sym_index = @intCast(sym_id) }, &locals);
- }
+ var stream = std.io.fixedBufferStream(buffer);
+ var cwriter = std.io.countingWriter(stream.writer());
+ const writer = cwriter.writer();
- for (self.objects.items) |object| {
- for (object.atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const sym_loc = atom.getSymbolWithLoc();
- try self.addLocalToSymtab(sym_loc, &locals);
+ var ncmds: usize = 0;
- var it = Atom.getInnerSymbolsIterator(self, atom_index);
- while (it.next()) |inner_sym_loc| {
- try self.addLocalToSymtab(inner_sym_loc, &locals);
+ // Segment and section load commands
+ {
+ const slice = self.sections.slice();
+ var sect_id: usize = 0;
+ for (self.segments.items) |seg| {
+ try writer.writeStruct(seg);
+ for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| {
+ try writer.writeStruct(header);
}
+ sect_id += seg.nsects;
}
+ ncmds += self.segments.items.len;
+ }
+
+ try writer.writeStruct(self.dyld_info_cmd);
+ ncmds += 1;
+ try writer.writeStruct(self.function_starts_cmd);
+ ncmds += 1;
+ try writer.writeStruct(self.data_in_code_cmd);
+ ncmds += 1;
+ try writer.writeStruct(self.symtab_cmd);
+ ncmds += 1;
+ try writer.writeStruct(self.dysymtab_cmd);
+ ncmds += 1;
+ try load_commands.writeDylinkerLC(writer);
+ ncmds += 1;
+
+ if (self.entry_index) |global_index| {
+ const sym = self.getSymbol(global_index);
+ const seg = self.getTextSegment();
+ const entryoff: u32 = if (sym.getFile(self) == null)
+ 0
+ else
+ @as(u32, @intCast(sym.getAddress(.{ .stubs = true }, self) - seg.vmaddr));
+ try writer.writeStruct(macho.entry_point_command{
+ .entryoff = entryoff,
+ .stacksize = self.base.stack_size,
+ });
+ ncmds += 1;
}
- var exports = std.ArrayList(macho.nlist_64).init(gpa);
- defer exports.deinit();
-
- for (self.globals.items) |global| {
- const sym = self.getSymbol(global);
- if (sym.undf()) continue; // import, skip
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
- var out_sym = sym;
- out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
- try exports.append(out_sym);
+ if (self.base.isDynLib()) {
+ try load_commands.writeDylibIdLC(self, writer);
+ ncmds += 1;
}
- var imports = std.ArrayList(macho.nlist_64).init(gpa);
- defer imports.deinit();
+ try load_commands.writeRpathLCs(self.base.rpath_list, writer);
+ ncmds += self.base.rpath_list.len;
- var imports_table = std.AutoHashMap(SymbolWithLoc, u32).init(gpa);
+ try writer.writeStruct(macho.source_version_command{ .version = 0 });
+ ncmds += 1;
- for (self.globals.items) |global| {
- const sym = self.getSymbol(global);
- if (sym.n_strx == 0) continue; // no name, skip
- if (!sym.undf()) continue; // not an import, skip
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
- const new_index = @as(u32, @intCast(imports.items.len));
- var out_sym = sym;
- out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
- try imports.append(out_sym);
- try imports_table.putNoClobber(global, new_index);
+ if (self.platform.isBuildVersionCompatible()) {
+ try load_commands.writeBuildVersionLC(self.platform, self.sdk_version, writer);
+ ncmds += 1;
+ } else {
+ try load_commands.writeVersionMinLC(self.platform, self.sdk_version, writer);
+ ncmds += 1;
+ }
+
+ const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + cwriter.bytes_written;
+ try writer.writeStruct(self.uuid_cmd);
+ ncmds += 1;
+
+ for (self.dylibs.items) |index| {
+ const dylib = self.getFile(index).?.dylib;
+ assert(dylib.isAlive(self));
+ const dylib_id = dylib.id.?;
+ try load_commands.writeDylibLC(.{
+ .cmd = if (dylib.weak)
+ .LOAD_WEAK_DYLIB
+ else if (dylib.reexport)
+ .REEXPORT_DYLIB
+ else
+ .LOAD_DYLIB,
+ .name = dylib_id.name,
+ .timestamp = dylib_id.timestamp,
+ .current_version = dylib_id.current_version,
+ .compatibility_version = dylib_id.compatibility_version,
+ }, writer);
+ ncmds += 1;
}
- // We generate stabs last in order to ensure that the strtab always has debug info
- // strings trailing
- if (comp.config.debug_format != .strip) {
- for (self.objects.items) |object| {
- assert(self.d_sym == null); // TODO
- try self.generateSymbolStabs(object, &locals);
- }
+ if (self.requiresCodeSig()) {
+ try writer.writeStruct(self.codesig_cmd);
+ ncmds += 1;
}
- const nlocals = @as(u32, @intCast(locals.items.len));
- const nexports = @as(u32, @intCast(exports.items.len));
- const nimports = @as(u32, @intCast(imports.items.len));
- const nsyms = nlocals + nexports + nimports;
+ assert(cwriter.bytes_written == needed_size);
- const seg = self.getLinkeditSegmentPtr();
- const offset = seg.fileoff + seg.filesize;
- assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
- const needed_size = nsyms * @sizeOf(macho.nlist_64);
- seg.filesize = offset + needed_size - seg.fileoff;
- assert(mem.isAlignedGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64)));
+ try self.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
- var buffer = std.ArrayList(u8).init(gpa);
- defer buffer.deinit();
- try buffer.ensureTotalCapacityPrecise(needed_size);
- buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(locals.items));
- buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(exports.items));
- buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(imports.items));
-
- log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
- try self.base.file.?.pwriteAll(buffer.items, offset);
-
- self.symtab_cmd.symoff = @as(u32, @intCast(offset));
- self.symtab_cmd.nsyms = nsyms;
-
- return SymtabCtx{
- .nlocalsym = nlocals,
- .nextdefsym = nexports,
- .nundefsym = nimports,
- .imports_table = imports_table,
- };
+ return .{ ncmds, buffer.len, uuid_cmd_offset };
}
-// TODO this function currently skips generating symbol stabs in case errors are encountered in DWARF data.
-// I think we should actually report those errors to the user and let them decide if they want to strip debug info
-// in that case or not.
-fn generateSymbolStabs(
- self: *MachO,
- object: Object,
- locals: *std.ArrayList(macho.nlist_64),
-) !void {
- log.debug("generating stabs for '{s}'", .{object.name});
-
- const gpa = self.base.comp.gpa;
- var debug_info = object.parseDwarfInfo();
-
- var lookup = DwarfInfo.AbbrevLookupTable.init(gpa);
- defer lookup.deinit();
- try lookup.ensureUnusedCapacity(std.math.maxInt(u8));
-
- // We assume there is only one CU.
- var cu_it = debug_info.getCompileUnitIterator();
- const compile_unit = while (try cu_it.next()) |cu| {
- const offset = math.cast(usize, cu.cuh.debug_abbrev_offset) orelse return error.Overflow;
- try debug_info.genAbbrevLookupByKind(offset, &lookup);
- break cu;
- } else {
- log.debug("no compile unit found in debug info in {s}; skipping", .{object.name});
- return;
- };
-
- var abbrev_it = compile_unit.getAbbrevEntryIterator(debug_info);
- const maybe_cu_entry: ?DwarfInfo.AbbrevEntry = blk: {
- while (abbrev_it.next(lookup) catch break :blk null) |entry| switch (entry.tag) {
- dwarf.TAG.compile_unit => break :blk entry,
- else => continue,
- } else break :blk null;
- };
-
- const cu_entry = maybe_cu_entry orelse {
- log.debug("missing DWARF_TAG_compile_unit tag in {s}; skipping", .{object.name});
- return;
- };
-
- var maybe_tu_name: ?[]const u8 = null;
- var maybe_tu_comp_dir: ?[]const u8 = null;
- var attr_it = cu_entry.getAttributeIterator(debug_info, compile_unit.cuh);
+fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void {
+ var header: macho.mach_header_64 = .{};
+ header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK;
- blk: {
- while (attr_it.next() catch break :blk) |attr| switch (attr.name) {
- dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue,
- dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue,
- else => continue,
- };
- }
+ // TODO: if (self.options.namespace == .two_level) {
+ header.flags |= macho.MH_TWOLEVEL;
+ // }
- if (maybe_tu_name == null or maybe_tu_comp_dir == null) {
- log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name});
- return;
+ switch (self.getTarget().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 => {},
}
- const tu_name = maybe_tu_name.?;
- const tu_comp_dir = maybe_tu_comp_dir.?;
-
- // Open scope
- try locals.ensureUnusedCapacity(3);
- locals.appendAssumeCapacity(.{
- .n_strx = try self.strtab.insert(gpa, tu_comp_dir),
- .n_type = macho.N_SO,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- });
- locals.appendAssumeCapacity(.{
- .n_strx = try self.strtab.insert(gpa, tu_name),
- .n_type = macho.N_SO,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- });
- locals.appendAssumeCapacity(.{
- .n_strx = try self.strtab.insert(gpa, object.name),
- .n_type = macho.N_OSO,
- .n_sect = 0,
- .n_desc = 1,
- .n_value = object.mtime,
- });
-
- var stabs_buf: [4]macho.nlist_64 = undefined;
-
- var name_lookup: ?DwarfInfo.SubprogramLookupByName = if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) blk: {
- var name_lookup = DwarfInfo.SubprogramLookupByName.init(gpa);
- errdefer name_lookup.deinit();
- try name_lookup.ensureUnusedCapacity(@as(u32, @intCast(object.atoms.items.len)));
- debug_info.genSubprogramLookupByName(compile_unit, lookup, &name_lookup) catch |err| switch (err) {
- error.UnhandledDwFormValue => {}, // TODO I don't like the fact we constantly re-iterate and hit this; we should validate once a priori
- else => |e| return e,
- };
- break :blk name_lookup;
- } else null;
- defer if (name_lookup) |*nl| nl.deinit();
-
- for (object.atoms.items) |atom_index| {
- const atom = self.getAtom(atom_index);
- const stabs = try self.generateSymbolStabsForSymbol(
- atom_index,
- atom.getSymbolWithLoc(),
- name_lookup,
- &stabs_buf,
- );
- try locals.appendSlice(stabs);
-
- var it = Atom.getInnerSymbolsIterator(self, atom_index);
- while (it.next()) |sym_loc| {
- const contained_stabs = try self.generateSymbolStabsForSymbol(
- atom_index,
- sym_loc,
- name_lookup,
- &stabs_buf,
- );
- try locals.appendSlice(contained_stabs);
- }
+ if (self.base.isDynLib()) {
+ header.filetype = macho.MH_DYLIB;
+ } else {
+ header.filetype = macho.MH_EXECUTE;
+ header.flags |= macho.MH_PIE;
}
- // Close scope
- try locals.append(.{
- .n_strx = 0,
- .n_type = macho.N_SO,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- });
-}
-
-fn generateSymbolStabsForSymbol(
- self: *MachO,
- atom_index: Atom.Index,
- sym_loc: SymbolWithLoc,
- lookup: ?DwarfInfo.SubprogramLookupByName,
- buf: *[4]macho.nlist_64,
-) ![]const macho.nlist_64 {
- const gpa = self.base.comp.gpa;
- const object = self.objects.items[sym_loc.getFile().?];
- const sym = self.getSymbol(sym_loc);
- const sym_name = self.getSymbolName(sym_loc);
- const header = self.sections.items(.header)[sym.n_sect - 1];
-
- if (sym.n_strx == 0) return buf[0..0];
- if (self.symbolIsTemp(sym_loc)) return buf[0..0];
-
- if (!header.isCode()) {
- // Since we are not dealing with machine code, it's either a global or a static depending
- // on the linkage scope.
- if (sym.sect() and sym.ext()) {
- // Global gets an N_GSYM stab type.
- buf[0] = .{
- .n_strx = try self.strtab.insert(gpa, sym_name),
- .n_type = macho.N_GSYM,
- .n_sect = sym.n_sect,
- .n_desc = 0,
- .n_value = 0,
- };
- } else {
- // Local static gets an N_STSYM stab type.
- buf[0] = .{
- .n_strx = try self.strtab.insert(gpa, sym_name),
- .n_type = macho.N_STSYM,
- .n_sect = sym.n_sect,
- .n_desc = 0,
- .n_value = sym.n_value,
- };
- }
- return buf[0..1];
+ const has_reexports = for (self.dylibs.items) |index| {
+ if (self.getFile(index).?.dylib.reexport) break true;
+ } else false;
+ if (!has_reexports) {
+ header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
}
- const size: u64 = size: {
- if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) {
- break :size self.getAtom(atom_index).size;
- }
-
- // Since we don't have subsections to work with, we need to infer the size of each function
- // the slow way by scanning the debug info for matching symbol names and extracting
- // the symbol's DWARF_AT_low_pc and DWARF_AT_high_pc values.
- const source_sym = object.getSourceSymbol(sym_loc.sym_index) orelse return buf[0..0];
- const subprogram = lookup.?.get(sym_name[1..]) orelse return buf[0..0];
-
- if (subprogram.addr <= source_sym.n_value and source_sym.n_value < subprogram.addr + subprogram.size) {
- break :size subprogram.size;
- } else {
- log.debug("no stab found for {s}", .{sym_name});
- return buf[0..0];
- }
- };
-
- buf[0] = .{
- .n_strx = 0,
- .n_type = macho.N_BNSYM,
- .n_sect = sym.n_sect,
- .n_desc = 0,
- .n_value = sym.n_value,
- };
- buf[1] = .{
- .n_strx = try self.strtab.insert(gpa, sym_name),
- .n_type = macho.N_FUN,
- .n_sect = sym.n_sect,
- .n_desc = 0,
- .n_value = sym.n_value,
- };
- buf[2] = .{
- .n_strx = 0,
- .n_type = macho.N_FUN,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = size,
- };
- buf[3] = .{
- .n_strx = 0,
- .n_type = macho.N_ENSYM,
- .n_sect = sym.n_sect,
- .n_desc = 0,
- .n_value = size,
- };
-
- return buf;
-}
-
-pub fn writeStrtab(self: *MachO) !void {
- const gpa = self.base.comp.gpa;
- const seg = self.getLinkeditSegmentPtr();
- const offset = seg.fileoff + seg.filesize;
- assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
- const needed_size = self.strtab.buffer.items.len;
- const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
- seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
- log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
- const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
- defer gpa.free(buffer);
- @memcpy(buffer[0..self.strtab.buffer.items.len], self.strtab.buffer.items);
- @memset(buffer[self.strtab.buffer.items.len..], 0);
-
- try self.base.file.?.pwriteAll(buffer, offset);
-
- self.symtab_cmd.stroff = @as(u32, @intCast(offset));
- self.symtab_cmd.strsize = @as(u32, @intCast(needed_size_aligned));
-}
-
-const SymtabCtx = struct {
- nlocalsym: u32,
- nextdefsym: u32,
- nundefsym: u32,
- imports_table: std.AutoHashMap(SymbolWithLoc, u32),
-};
-
-pub fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
- const gpa = self.base.comp.gpa;
- const nstubs = @as(u32, @intCast(self.stub_table.lookup.count()));
- const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count()));
- const nindirectsyms = nstubs * 2 + ngot_entries;
- const iextdefsym = ctx.nlocalsym;
- const iundefsym = iextdefsym + ctx.nextdefsym;
-
- const seg = self.getLinkeditSegmentPtr();
- const offset = seg.fileoff + seg.filesize;
- assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
- const needed_size = nindirectsyms * @sizeOf(u32);
- const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
- seg.filesize = offset + needed_size_aligned - seg.fileoff;
-
- log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
-
- var buf = std.ArrayList(u8).init(gpa);
- defer buf.deinit();
- try buf.ensureTotalCapacity(math.cast(usize, needed_size_aligned) orelse return error.Overflow);
- const writer = buf.writer();
-
- if (self.stubs_section_index) |sect_id| {
- const stubs_header = &self.sections.items(.header)[sect_id];
- stubs_header.reserved1 = 0;
- for (self.stub_table.entries.items) |entry| {
- if (!self.stub_table.lookup.contains(entry)) continue;
- const target_sym = self.getSymbol(entry);
- assert(target_sym.undf());
- try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
- }
+ if (self.has_tlv) {
+ header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
}
-
- if (self.got_section_index) |sect_id| {
- const got = &self.sections.items(.header)[sect_id];
- got.reserved1 = nstubs;
- for (self.got_table.entries.items) |entry| {
- if (!self.got_table.lookup.contains(entry)) continue;
- const target_sym = self.getSymbol(entry);
- if (target_sym.undf()) {
- try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
- } else {
- try writer.writeInt(u32, macho.INDIRECT_SYMBOL_LOCAL, .little);
- }
- }
+ if (self.binds_to_weak) {
+ header.flags |= macho.MH_BINDS_TO_WEAK;
}
-
- if (self.la_symbol_ptr_section_index) |sect_id| {
- const la_symbol_ptr = &self.sections.items(.header)[sect_id];
- la_symbol_ptr.reserved1 = nstubs + ngot_entries;
- for (self.stub_table.entries.items) |entry| {
- if (!self.stub_table.lookup.contains(entry)) continue;
- const target_sym = self.getSymbol(entry);
- assert(target_sym.undf());
- try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
- }
+ if (self.weak_defines) {
+ header.flags |= macho.MH_WEAK_DEFINES;
}
- const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
- if (padding > 0) {
- buf.appendNTimesAssumeCapacity(0, padding);
- }
+ header.ncmds = @intCast(ncmds);
+ header.sizeofcmds = @intCast(sizeofcmds);
- assert(buf.items.len == needed_size_aligned);
- try self.base.file.?.pwriteAll(buf.items, offset);
+ log.debug("writing Mach-O header {}", .{header});
- self.dysymtab_cmd.nlocalsym = ctx.nlocalsym;
- self.dysymtab_cmd.iextdefsym = iextdefsym;
- self.dysymtab_cmd.nextdefsym = ctx.nextdefsym;
- self.dysymtab_cmd.iundefsym = iundefsym;
- self.dysymtab_cmd.nundefsym = ctx.nundefsym;
- self.dysymtab_cmd.indirectsymoff = @as(u32, @intCast(offset));
- self.dysymtab_cmd.nindirectsyms = nindirectsyms;
+ try self.base.file.?.pwriteAll(mem.asBytes(&header), 0);
}
-pub fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
+fn writeUuid(self: *MachO, uuid_cmd_offset: u64, has_codesig: bool) !void {
const file_size = if (!has_codesig) blk: {
- const seg = self.getLinkeditSegmentPtr();
+ const seg = self.getLinkeditSegment();
break :blk seg.fileoff + seg.filesize;
} else self.codesig_cmd.dataoff;
- try calcUuid(comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
+ try calcUuid(self.base.comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset);
}
pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
- const target = self.base.comp.root_mod.resolved_target.result;
- const seg = self.getLinkeditSegmentPtr();
+ const seg = self.getLinkeditSegment();
// Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
// https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16);
const needed_size = code_sig.estimateSize(offset);
seg.filesize = offset + needed_size - seg.fileoff;
- seg.vmsize = mem.alignForward(u64, seg.filesize, getPageSize(target.cpu.arch));
+ seg.vmsize = mem.alignForward(u64, seg.filesize, self.getPageSize());
log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
// Pad out the space. We need to do this to calculate valid hashes for everything in the file
// except for code signature data.
@@ -4682,22 +3150,19 @@ pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
self.codesig_cmd.datasize = @as(u32, @intCast(needed_size));
}
-pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void {
- const output_mode = self.base.comp.config.output_mode;
- const seg_id = self.header_segment_cmd_index.?;
- const seg = self.segments.items[seg_id];
+pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
+ const seg = self.getTextSegment();
const offset = self.codesig_cmd.dataoff;
- const gpa = self.base.comp.gpa;
- var buffer = std.ArrayList(u8).init(gpa);
+ var buffer = std.ArrayList(u8).init(self.base.comp.gpa);
defer buffer.deinit();
try buffer.ensureTotalCapacityPrecise(code_sig.size());
- try code_sig.writeAdhocSignature(comp, .{
+ try code_sig.writeAdhocSignature(self, .{
.file = self.base.file.?,
.exec_seg_base = seg.fileoff,
.exec_seg_limit = seg.filesize,
.file_size = offset,
- .output_mode = output_mode,
+ .dylib = self.base.isDynLib(),
}, buffer.writer());
assert(buffer.items.len == code_sig.size());
@@ -4709,51 +3174,79 @@ pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *Cod
try self.base.file.?.pwriteAll(buffer.items, offset);
}
-/// Writes Mach-O file header.
-pub fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
- const output_mode = self.base.comp.config.output_mode;
+pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
+ if (build_options.skip_non_native and builtin.object_format != .macho) {
+ @panic("Attempted to compile for object format that was disabled by build configuration");
+ }
+ if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func_index, air, liveness);
+ return self.getZigObject().?.updateFunc(self, mod, func_index, air, liveness);
+}
- var header: macho.mach_header_64 = .{};
- header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL;
+pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
+ return self.getZigObject().?.lowerUnnamedConst(self, typed_value, decl_index);
+}
- const target = self.base.comp.root_mod.resolved_target.result;
- switch (target.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 => unreachable,
+pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void {
+ if (build_options.skip_non_native and builtin.object_format != .macho) {
+ @panic("Attempted to compile for object format that was disabled by build configuration");
}
+ if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index);
+ return self.getZigObject().?.updateDecl(self, mod, decl_index);
+}
- switch (output_mode) {
- .Exe => {
- header.filetype = macho.MH_EXECUTE;
- },
- .Lib => {
- // By this point, it can only be a dylib.
- header.filetype = macho.MH_DYLIB;
- header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
- },
- else => unreachable,
- }
+pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
+ if (self.llvm_object) |_| return;
+ return self.getZigObject().?.updateDeclLineNumber(module, decl_index);
+}
- if (self.thread_vars_section_index) |sect_id| {
- header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
- if (self.sections.items(.header)[sect_id].size > 0) {
- header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
- }
+pub fn updateExports(
+ self: *MachO,
+ mod: *Module,
+ exported: Module.Exported,
+ exports: []const *Module.Export,
+) link.File.UpdateExportsError!void {
+ if (build_options.skip_non_native and builtin.object_format != .macho) {
+ @panic("Attempted to compile for object format that was disabled by build configuration");
}
+ if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports);
+ return self.getZigObject().?.updateExports(self, mod, exported, exports);
+}
+
+pub fn deleteDeclExport(
+ self: *MachO,
+ decl_index: InternPool.DeclIndex,
+ name: InternPool.NullTerminatedString,
+) Allocator.Error!void {
+ if (self.llvm_object) |_| return;
+ return self.getZigObject().?.deleteDeclExport(self, decl_index, name);
+}
- header.ncmds = ncmds;
- header.sizeofcmds = sizeofcmds;
+pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void {
+ if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
+ return self.getZigObject().?.freeDecl(decl_index);
+}
- log.debug("writing Mach-O header {}", .{header});
+pub fn getDeclVAddr(self: *MachO, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
+ assert(self.llvm_object == null);
+ return self.getZigObject().?.getDeclVAddr(self, decl_index, reloc_info);
+}
- try self.base.file.?.pwriteAll(mem.asBytes(&header), 0);
+pub fn lowerAnonDecl(
+ self: *MachO,
+ decl_val: InternPool.Index,
+ explicit_alignment: InternPool.Alignment,
+ src_loc: Module.SrcLoc,
+) !codegen.Result {
+ return self.getZigObject().?.lowerAnonDecl(self, decl_val, explicit_alignment, src_loc);
+}
+
+pub fn getAnonDeclVAddr(self: *MachO, decl_val: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
+ assert(self.llvm_object == null);
+ return self.getZigObject().?.getAnonDeclVAddr(self, decl_val, reloc_info);
+}
+
+pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
+ return self.getZigObject().?.getGlobalSymbol(self, name, lib_name);
}
pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
@@ -4761,33 +3254,55 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
}
fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 {
- // TODO: header and load commands have to be part of the __TEXT segment
- const header_size = self.segments.items[self.header_segment_cmd_index.?].filesize;
+ // Conservatively commit one page size as reserved space for the headers as we
+ // expect it to grow and everything else be moved in flush anyhow.
+ const header_size = self.getPageSize();
if (start < header_size)
return header_size;
const end = start + padToIdeal(size);
for (self.sections.items(.header)) |header| {
- const tight_size = header.size;
- const increased_size = padToIdeal(tight_size);
+ if (header.isZerofill()) continue;
+ const increased_size = padToIdeal(header.size);
const test_end = header.offset + increased_size;
if (end > header.offset and start < test_end) {
return test_end;
}
}
+ for (self.segments.items) |seg| {
+ const increased_size = padToIdeal(seg.filesize);
+ const test_end = seg.fileoff +| increased_size;
+ if (end > seg.fileoff and start < test_end) {
+ return test_end;
+ }
+ }
+
return null;
}
fn allocatedSize(self: *MachO, start: u64) u64 {
- if (start == 0)
- return 0;
+ if (start == 0) return 0;
var min_pos: u64 = std.math.maxInt(u64);
for (self.sections.items(.header)) |header| {
if (header.offset <= start) continue;
if (header.offset < min_pos) min_pos = header.offset;
}
+ for (self.segments.items) |seg| {
+ if (seg.fileoff <= start) continue;
+ if (seg.fileoff < min_pos) min_pos = seg.fileoff;
+ }
+ return min_pos - start;
+}
+
+fn allocatedVirtualSize(self: *MachO, start: u64) u64 {
+ if (start == 0) return 0;
+ var min_pos: u64 = std.math.maxInt(u64);
+ for (self.segments.items) |seg| {
+ if (seg.vmaddr <= start) continue;
+ if (seg.vmaddr < min_pos) min_pos = seg.vmaddr;
+ }
return min_pos - start;
}
@@ -4799,977 +3314,1058 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 {
return start;
}
-pub fn allocatedVirtualSize(self: *MachO, start: u64) u64 {
- if (start == 0)
- return 0;
- var min_pos: u64 = std.math.maxInt(u64);
- for (self.sections.items(.segment_index)) |seg_id| {
- const segment = self.segments.items[seg_id];
- if (segment.vmaddr <= start) continue;
- if (segment.vmaddr < min_pos) min_pos = segment.vmaddr;
- }
- return min_pos - start;
+/// Like File.copyRangeAll but also ensures the source region is zeroed out after copy.
+/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.
+fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
+ const gpa = self.base.comp.gpa;
+ const file = self.base.file.?;
+ const amt = try file.copyRangeAll(old_offset, file, new_offset, size);
+ if (amt != size) return error.InputOutput;
+ const size_u = math.cast(usize, size) orelse return error.Overflow;
+ const zeroes = try gpa.alloc(u8, size_u);
+ defer gpa.free(zeroes);
+ @memset(zeroes, 0);
+ try file.pwriteAll(zeroes, old_offset);
}
-pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void {
- if (!is_hot_update_compatible) return;
+const InitMetadataOptions = struct {
+ symbol_count_hint: u64,
+ program_code_size_hint: u64,
+};
- const mach_task = try std.os.darwin.machTaskForPid(pid);
- log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task });
- self.hot_state.mach_task = mach_task;
+// TODO: move to ZigObject
+fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
+ if (!self.base.isRelocatable()) {
+ const base_vmaddr = blk: {
+ const pagezero_size = self.pagezero_size orelse default_pagezero_size;
+ break :blk mem.alignBackward(u64, pagezero_size, self.getPageSize());
+ };
- // TODO start exception handler in another thread
+ {
+ const filesize = options.program_code_size_hint;
+ const off = self.findFreeSpace(filesize, self.getPageSize());
+ self.zig_text_seg_index = try self.addSegment("__TEXT_ZIG", .{
+ .fileoff = off,
+ .filesize = filesize,
+ .vmaddr = base_vmaddr + 0x8000000,
+ .vmsize = filesize,
+ .prot = macho.PROT.READ | macho.PROT.EXEC,
+ });
+ }
- // TODO enable ones we register for exceptions
- // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0);
-}
+ {
+ const filesize = options.symbol_count_hint * @sizeOf(u64);
+ const off = self.findFreeSpace(filesize, self.getPageSize());
+ self.zig_got_seg_index = try self.addSegment("__GOT_ZIG", .{
+ .fileoff = off,
+ .filesize = filesize,
+ .vmaddr = base_vmaddr + 0x4000000,
+ .vmsize = filesize,
+ .prot = macho.PROT.READ | macho.PROT.WRITE,
+ });
+ }
-pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void {
- if (!is_hot_update_compatible) return;
+ {
+ const filesize: u64 = 1024;
+ const off = self.findFreeSpace(filesize, self.getPageSize());
+ self.zig_const_seg_index = try self.addSegment("__CONST_ZIG", .{
+ .fileoff = off,
+ .filesize = filesize,
+ .vmaddr = base_vmaddr + 0xc000000,
+ .vmsize = filesize,
+ .prot = macho.PROT.READ | macho.PROT.WRITE,
+ });
+ }
- _ = pid;
+ {
+ const filesize: u64 = 1024;
+ const off = self.findFreeSpace(filesize, self.getPageSize());
+ self.zig_data_seg_index = try self.addSegment("__DATA_ZIG", .{
+ .fileoff = off,
+ .filesize = filesize,
+ .vmaddr = base_vmaddr + 0x10000000,
+ .vmsize = filesize,
+ .prot = macho.PROT.READ | macho.PROT.WRITE,
+ });
+ }
- // TODO stop exception handler
+ {
+ const memsize: u64 = 1024;
+ self.zig_bss_seg_index = try self.addSegment("__BSS_ZIG", .{
+ .vmaddr = base_vmaddr + 0x14000000,
+ .vmsize = memsize,
+ .prot = macho.PROT.READ | macho.PROT.WRITE,
+ });
+ }
+ } else {
+ @panic("TODO initMetadata when relocatable");
+ }
- // TODO see comment in ptraceAttach
- // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0);
+ const appendSect = struct {
+ fn appendSect(macho_file: *MachO, sect_id: u8, seg_id: u8) void {
+ const sect = &macho_file.sections.items(.header)[sect_id];
+ const seg = macho_file.segments.items[seg_id];
+ sect.addr = seg.vmaddr;
+ sect.offset = @intCast(seg.fileoff);
+ sect.size = seg.vmsize;
+ macho_file.sections.items(.segment_id)[sect_id] = seg_id;
+ }
+ }.appendSect;
- self.hot_state.mach_task = null;
-}
+ {
+ self.zig_text_sect_index = try self.addSection("__TEXT_ZIG", "__text_zig", .{
+ .alignment = switch (self.getTarget().cpu.arch) {
+ .aarch64 => 2,
+ .x86_64 => 0,
+ else => unreachable,
+ },
+ .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+ });
+ appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?);
+ }
-pub fn addUndefined(self: *MachO, name: []const u8, flags: RelocFlags) !u32 {
- const gpa = self.base.comp.gpa;
+ if (!self.base.isRelocatable()) {
+ self.zig_got_sect_index = try self.addSection("__GOT_ZIG", "__got_zig", .{
+ .alignment = 3,
+ });
+ appendSect(self, self.zig_got_sect_index.?, self.zig_got_seg_index.?);
+ }
- const gop = try self.getOrPutGlobalPtr(name);
- const global_index = self.getGlobalIndex(name).?;
+ {
+ self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{});
+ appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?);
+ }
- if (gop.found_existing) {
- try self.updateRelocActions(global_index, flags);
- return global_index;
+ {
+ self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{});
+ appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?);
}
- const sym_index = try self.allocateSymbol();
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
- gop.value_ptr.* = sym_loc;
+ {
+ self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{
+ .flags = macho.S_ZEROFILL,
+ });
+ appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?);
+ }
+}
- const sym = self.getSymbolPtr(sym_loc);
- sym.n_strx = try self.strtab.insert(gpa, name);
- sym.n_type = macho.N_EXT | macho.N_UNDF;
+pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void {
+ const sect = &self.sections.items(.header)[sect_index];
+ const seg_id = self.sections.items(.segment_id)[sect_index];
+ const seg = &self.segments.items[seg_id];
- try self.unresolved.putNoClobber(gpa, global_index, {});
- try self.updateRelocActions(global_index, flags);
+ if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) {
+ const existing_size = sect.size;
+ sect.size = 0;
- return global_index;
-}
+ // Must move the entire section.
+ const new_offset = self.findFreeSpace(needed_size, self.getPageSize());
-fn updateRelocActions(self: *MachO, global_index: u32, flags: RelocFlags) !void {
- const gpa = self.base.comp.gpa;
- const act_gop = try self.actions.getOrPut(gpa, global_index);
- if (!act_gop.found_existing) {
- act_gop.value_ptr.* = .{};
- }
- act_gop.value_ptr.add_got = act_gop.value_ptr.add_got or flags.add_got;
- act_gop.value_ptr.add_stub = act_gop.value_ptr.add_stub or flags.add_stub;
-}
+ log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{
+ sect.segName(),
+ sect.sectName(),
+ new_offset,
+ new_offset + existing_size,
+ });
-pub fn makeStaticString(bytes: []const u8) [16]u8 {
- var buf = [_]u8{0} ** 16;
- @memcpy(buf[0..bytes.len], bytes);
- return buf;
-}
+ try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size);
-pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 {
- for (self.segments.items, 0..) |seg, i| {
- if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
- } else return null;
-}
+ sect.offset = @intCast(new_offset);
+ seg.fileoff = new_offset;
+ }
-pub fn getSegment(self: MachO, sect_id: u8) macho.segment_command_64 {
- const index = self.sections.items(.segment_index)[sect_id];
- return self.segments.items[index];
-}
+ sect.size = needed_size;
+ if (!sect.isZerofill()) {
+ seg.filesize = needed_size;
+ }
+
+ const mem_capacity = self.allocatedVirtualSize(seg.vmaddr);
+ if (needed_size > mem_capacity) {
+ var err = try self.addErrorWithNotes(2);
+ try err.addMsg(self, "fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{
+ seg_id,
+ seg.segName(),
+ });
+ try err.addNote(self, "TODO: emit relocations to memory locations in self-hosted backends", .{});
+ try err.addNote(self, "as a workaround, try increasing pre-allocated virtual memory of each segment", .{});
+ }
-pub fn getSegmentPtr(self: *MachO, sect_id: u8) *macho.segment_command_64 {
- const index = self.sections.items(.segment_index)[sect_id];
- return &self.segments.items[index];
+ seg.vmsize = needed_size;
}
-pub fn getLinkeditSegmentPtr(self: *MachO) *macho.segment_command_64 {
- const index = self.linkedit_segment_cmd_index.?;
- return &self.segments.items[index];
+pub fn getTarget(self: MachO) std.Target {
+ return self.base.comp.root_mod.resolved_target.result;
}
-pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 {
- // TODO investigate caching with a hashmap
- for (self.sections.items(.header), 0..) |header, i| {
- if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
- return @as(u8, @intCast(i));
- } else return null;
+/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
+/// Any change to the binary will effectively invalidate the kernel's cache
+/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
+/// linking we're modifying a binary in-place, this will end up with the kernel
+/// killing it on every subsequent run. To circumvent it, we will copy the file
+/// into a new inode, remove the original file, and rename the copy to match
+/// the original file. This is super messy, but there doesn't seem any other
+/// way to please the XNU.
+pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void {
+ if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
+ try dir.copyFile(sub_path, dir, sub_path, .{});
+ }
}
-pub fn getSectionIndexes(self: MachO, segment_index: u8) struct { start: u8, end: u8 } {
- var start: u8 = 0;
- const nsects = for (self.segments.items, 0..) |seg, i| {
- if (i == segment_index) break @as(u8, @intCast(seg.nsects));
- start += @as(u8, @intCast(seg.nsects));
- } else 0;
- return .{ .start = start, .end = start + nsects };
+inline fn conformUuid(out: *[Md5.digest_length]u8) void {
+ // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
+ out[6] = (out[6] & 0x0F) | (3 << 4);
+ out[8] = (out[8] & 0x3F) | 0x80;
}
-pub fn symbolIsTemp(self: *MachO, sym_with_loc: SymbolWithLoc) bool {
- const sym = self.getSymbol(sym_with_loc);
- if (!sym.sect()) return false;
- if (sym.ext()) return false;
- const sym_name = self.getSymbolName(sym_with_loc);
- return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
+pub inline fn getPageSize(self: MachO) u16 {
+ return switch (self.getTarget().cpu.arch) {
+ .aarch64 => 0x4000,
+ .x86_64 => 0x1000,
+ else => unreachable,
+ };
}
-/// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
-pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 {
- if (sym_with_loc.getFile()) |file| {
- const object = &self.objects.items[file];
- return &object.symtab[sym_with_loc.sym_index];
- } else {
- return &self.locals.items[sym_with_loc.sym_index];
- }
+pub fn requiresCodeSig(self: MachO) bool {
+ if (self.entitlements) |_| return true;
+ // if (self.options.adhoc_codesign) |cs| return cs;
+ return switch (self.getTarget().cpu.arch) {
+ .aarch64 => true,
+ else => false,
+ };
}
-/// Returns symbol described by `sym_with_loc` descriptor.
-pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
- if (sym_with_loc.getFile()) |file| {
- const object = &self.objects.items[file];
- return object.symtab[sym_with_loc.sym_index];
- } else {
- return self.locals.items[sym_with_loc.sym_index];
- }
+inline fn requiresThunks(self: MachO) bool {
+ return self.getTarget().cpu.arch == .aarch64;
}
-/// Returns name of the symbol described by `sym_with_loc` descriptor.
-pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 {
- if (sym_with_loc.getFile()) |file| {
- const object = self.objects.items[file];
- return object.getSymbolName(sym_with_loc.sym_index);
- } else {
- const sym = self.locals.items[sym_with_loc.sym_index];
- return self.strtab.get(sym.n_strx).?;
- }
+pub fn addSegment(self: *MachO, name: []const u8, opts: struct {
+ vmaddr: u64 = 0,
+ vmsize: u64 = 0,
+ fileoff: u64 = 0,
+ filesize: u64 = 0,
+ prot: macho.vm_prot_t = macho.PROT.NONE,
+ flags: u32 = 0,
+}) error{OutOfMemory}!u8 {
+ const gpa = self.base.comp.gpa;
+ const index = @as(u8, @intCast(self.segments.items.len));
+ try self.segments.append(gpa, .{
+ .segname = makeStaticString(name),
+ .vmaddr = opts.vmaddr,
+ .vmsize = opts.vmsize,
+ .fileoff = opts.fileoff,
+ .filesize = opts.filesize,
+ .maxprot = opts.prot,
+ .initprot = opts.prot,
+ .nsects = 0,
+ .cmdsize = @sizeOf(macho.segment_command_64),
+ });
+ return index;
}
-const BoundarySymbolKind = enum {
- start,
- stop,
+const AddSectionOpts = struct {
+ alignment: u32 = 0,
+ flags: u32 = macho.S_REGULAR,
+ reserved1: u32 = 0,
+ reserved2: u32 = 0,
};
-const SectionBoundarySymbol = struct {
- kind: BoundarySymbolKind,
+pub fn addSection(
+ self: *MachO,
segname: []const u8,
sectname: []const u8,
-};
-
-pub fn getSectionBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SectionBoundarySymbol {
- const sym_name = self.getSymbolName(sym_with_loc);
- if (mem.startsWith(u8, sym_name, "section$")) {
- const trailing = sym_name["section$".len..];
- const kind: BoundarySymbolKind = kind: {
- if (mem.startsWith(u8, trailing, "start$")) break :kind .start;
- if (mem.startsWith(u8, trailing, "stop$")) break :kind .stop;
- return null;
- };
- const names = trailing[@tagName(kind).len + 1 ..];
- const sep_idx = mem.indexOf(u8, names, "$") orelse return null;
- const segname = names[0..sep_idx];
- const sectname = names[sep_idx + 1 ..];
- return .{ .kind = kind, .segname = segname, .sectname = sectname };
- }
- return null;
+ opts: AddSectionOpts,
+) !u8 {
+ const gpa = self.base.comp.gpa;
+ const index = @as(u8, @intCast(try self.sections.addOne(gpa)));
+ self.sections.set(index, .{
+ .segment_id = 0, // Segments will be created automatically later down the pipeline.
+ .header = .{
+ .sectname = makeStaticString(sectname),
+ .segname = makeStaticString(segname),
+ .@"align" = opts.alignment,
+ .flags = opts.flags,
+ .reserved1 = opts.reserved1,
+ .reserved2 = opts.reserved2,
+ },
+ });
+ return index;
}
-const SegmentBoundarySymbol = struct {
- kind: BoundarySymbolKind,
- segname: []const u8,
-};
+pub fn makeStaticString(bytes: []const u8) [16]u8 {
+ var buf = [_]u8{0} ** 16;
+ @memcpy(buf[0..bytes.len], bytes);
+ return buf;
+}
-pub fn getSegmentBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SegmentBoundarySymbol {
- const sym_name = self.getSymbolName(sym_with_loc);
- if (mem.startsWith(u8, sym_name, "segment$")) {
- const trailing = sym_name["segment$".len..];
- const kind: BoundarySymbolKind = kind: {
- if (mem.startsWith(u8, trailing, "start$")) break :kind .start;
- if (mem.startsWith(u8, trailing, "stop$")) break :kind .stop;
- return null;
- };
- const segname = trailing[@tagName(kind).len + 1 ..];
- return .{ .kind = kind, .segname = segname };
- }
- return null;
+pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 {
+ for (self.segments.items, 0..) |seg, i| {
+ if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
+ } else return null;
}
-/// Returns pointer to the global entry for `name` if one exists.
-pub fn getGlobalPtr(self: *MachO, name: []const u8) ?*SymbolWithLoc {
- const global_index = self.resolver.get(name) orelse return null;
- return &self.globals.items[global_index];
+pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 {
+ for (self.sections.items(.header), 0..) |header, i| {
+ if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
+ return @as(u8, @intCast(i));
+ } else return null;
}
-/// Returns the global entry for `name` if one exists.
-pub fn getGlobal(self: *const MachO, name: []const u8) ?SymbolWithLoc {
- const global_index = self.resolver.get(name) orelse return null;
- return self.globals.items[global_index];
+pub fn getTlsAddress(self: MachO) u64 {
+ for (self.sections.items(.header)) |header| switch (header.type()) {
+ macho.S_THREAD_LOCAL_REGULAR,
+ macho.S_THREAD_LOCAL_ZEROFILL,
+ => return header.addr,
+ else => {},
+ };
+ return 0;
}
-/// Returns the index of the global entry for `name` if one exists.
-pub fn getGlobalIndex(self: *const MachO, name: []const u8) ?u32 {
- return self.resolver.get(name);
+pub inline fn getTextSegment(self: *MachO) *macho.segment_command_64 {
+ return &self.segments.items[self.text_seg_index.?];
}
-/// Returns global entry at `index`.
-pub fn getGlobalByIndex(self: *const MachO, index: u32) SymbolWithLoc {
- assert(index < self.globals.items.len);
- return self.globals.items[index];
+pub inline fn getLinkeditSegment(self: *MachO) *macho.segment_command_64 {
+ return &self.segments.items[self.linkedit_seg_index.?];
}
-const GetOrPutGlobalPtrResult = struct {
- found_existing: bool,
- value_ptr: *SymbolWithLoc,
-};
+pub fn getFile(self: *MachO, index: File.Index) ?File {
+ const tag = self.files.items(.tags)[index];
+ return switch (tag) {
+ .null => null,
+ .zig_object => .{ .zig_object = &self.files.items(.data)[index].zig_object },
+ .internal => .{ .internal = &self.files.items(.data)[index].internal },
+ .object => .{ .object = &self.files.items(.data)[index].object },
+ .dylib => .{ .dylib = &self.files.items(.data)[index].dylib },
+ };
+}
-/// Used only for disambiguating local from global at relocation level.
-/// TODO this must go away.
-pub const global_symbol_bit: u32 = 0x80000000;
-pub const global_symbol_mask: u32 = 0x7fffffff;
+pub fn getZigObject(self: *MachO) ?*ZigObject {
+ const index = self.zig_object orelse return null;
+ return self.getFile(index).?.zig_object;
+}
-/// Return pointer to the global entry for `name` if one exists.
-/// Puts a new global entry for `name` if one doesn't exist, and
-/// returns a pointer to it.
-pub fn getOrPutGlobalPtr(self: *MachO, name: []const u8) !GetOrPutGlobalPtrResult {
- if (self.getGlobalPtr(name)) |ptr| {
- return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr };
- }
- const gpa = self.base.comp.gpa;
- const global_index = try self.allocateGlobal();
- const global_name = try gpa.dupe(u8, name);
- _ = try self.resolver.put(gpa, global_name, global_index);
- const ptr = &self.globals.items[global_index];
- return GetOrPutGlobalPtrResult{ .found_existing = false, .value_ptr = ptr };
+pub fn getInternalObject(self: *MachO) ?*InternalObject {
+ const index = self.internal_object orelse return null;
+ return self.getFile(index).?.internal;
}
-pub fn getAtom(self: *MachO, atom_index: Atom.Index) Atom {
- assert(atom_index < self.atoms.items.len);
- return self.atoms.items[atom_index];
+pub fn addAtom(self: *MachO) error{OutOfMemory}!Atom.Index {
+ const index = @as(Atom.Index, @intCast(self.atoms.items.len));
+ const atom = try self.atoms.addOne(self.base.comp.gpa);
+ atom.* = .{};
+ return index;
}
-pub fn getAtomPtr(self: *MachO, atom_index: Atom.Index) *Atom {
- assert(atom_index < self.atoms.items.len);
- return &self.atoms.items[atom_index];
+pub fn getAtom(self: *MachO, index: Atom.Index) ?*Atom {
+ if (index == 0) return null;
+ assert(index < self.atoms.items.len);
+ return &self.atoms.items[index];
}
-/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor.
-/// Returns null on failure.
-pub fn getAtomIndexForSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) ?Atom.Index {
- assert(sym_with_loc.getFile() == null);
- return self.atom_by_index_table.get(sym_with_loc.sym_index);
+pub fn addSymbol(self: *MachO) !Symbol.Index {
+ const index = @as(Symbol.Index, @intCast(self.symbols.items.len));
+ const symbol = try self.symbols.addOne(self.base.comp.gpa);
+ symbol.* = .{};
+ return index;
}
-pub fn getGotEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
- const index = self.got_table.lookup.get(sym_with_loc) orelse return null;
- const header = self.sections.items(.header)[self.got_section_index.?];
- return header.addr + @sizeOf(u64) * index;
+pub fn getSymbol(self: *MachO, index: Symbol.Index) *Symbol {
+ assert(index < self.symbols.items.len);
+ return &self.symbols.items[index];
}
-pub fn getTlvPtrEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
- const index = self.tlv_ptr_table.lookup.get(sym_with_loc) orelse return null;
- const header = self.sections.items(.header)[self.tlv_ptr_section_index.?];
- return header.addr + @sizeOf(u64) * index;
+pub fn addSymbolExtra(self: *MachO, extra: Symbol.Extra) !u32 {
+ const fields = @typeInfo(Symbol.Extra).Struct.fields;
+ try self.symbols_extra.ensureUnusedCapacity(self.base.comp.gpa, fields.len);
+ return self.addSymbolExtraAssumeCapacity(extra);
}
-pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
- const target = self.base.comp.root_mod.resolved_target.result;
- const index = self.stub_table.lookup.get(sym_with_loc) orelse return null;
- const header = self.sections.items(.header)[self.stubs_section_index.?];
- return header.addr + stubs.stubSize(target.cpu.arch) * index;
+pub fn addSymbolExtraAssumeCapacity(self: *MachO, extra: Symbol.Extra) u32 {
+ const index = @as(u32, @intCast(self.symbols_extra.items.len));
+ const fields = @typeInfo(Symbol.Extra).Struct.fields;
+ inline for (fields) |field| {
+ self.symbols_extra.appendAssumeCapacity(switch (field.type) {
+ u32 => @field(extra, field.name),
+ else => @compileError("bad field type"),
+ });
+ }
+ return index;
}
-/// Returns symbol location corresponding to the set entrypoint if any.
-/// Asserts output mode is executable.
-pub fn getEntryPoint(self: MachO) ?SymbolWithLoc {
- const entry_name = self.entry_name orelse return null;
- const global = self.getGlobal(entry_name) orelse return null;
- return global;
+pub fn getSymbolExtra(self: MachO, index: u32) ?Symbol.Extra {
+ if (index == 0) return null;
+ const fields = @typeInfo(Symbol.Extra).Struct.fields;
+ var i: usize = index;
+ var result: Symbol.Extra = undefined;
+ inline for (fields) |field| {
+ @field(result, field.name) = switch (field.type) {
+ u32 => self.symbols_extra.items[i],
+ else => @compileError("bad field type"),
+ };
+ i += 1;
+ }
+ return result;
}
-pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols {
- if (self.d_sym == null) return null;
- return &self.d_sym.?;
+pub fn setSymbolExtra(self: *MachO, index: u32, extra: Symbol.Extra) void {
+ assert(index > 0);
+ const fields = @typeInfo(Symbol.Extra).Struct.fields;
+ inline for (fields, 0..) |field, i| {
+ self.symbols_extra.items[index + i] = switch (field.type) {
+ u32 => @field(extra, field.name),
+ else => @compileError("bad field type"),
+ };
+ }
}
-pub inline fn getPageSize(cpu_arch: std.Target.Cpu.Arch) u16 {
- return switch (cpu_arch) {
- .aarch64 => 0x4000,
- .x86_64 => 0x1000,
- else => unreachable,
+const GetOrCreateGlobalResult = struct {
+ found_existing: bool,
+ index: Symbol.Index,
+};
+
+pub fn getOrCreateGlobal(self: *MachO, off: u32) !GetOrCreateGlobalResult {
+ const gpa = self.base.comp.gpa;
+ const gop = try self.globals.getOrPut(gpa, off);
+ if (!gop.found_existing) {
+ const index = try self.addSymbol();
+ const global = self.getSymbol(index);
+ global.name = off;
+ gop.value_ptr.* = index;
+ }
+ return .{
+ .found_existing = gop.found_existing,
+ .index = gop.value_ptr.*,
};
}
-pub fn requiresCodeSignature(m: *MachO) bool {
- if (m.entitlements) |_| return true;
- const comp = m.base.comp;
- const target = comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const os_tag = target.os.tag;
- const abi = target.abi;
- if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) return true;
- return false;
+pub fn getGlobalByName(self: *MachO, name: []const u8) ?Symbol.Index {
+ const off = self.strings.getOffset(name) orelse return null;
+ return self.globals.get(off);
}
-pub fn getSegmentPrecedence(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;
- return 0x4;
+pub fn addUnwindRecord(self: *MachO) !UnwindInfo.Record.Index {
+ const index = @as(UnwindInfo.Record.Index, @intCast(self.unwind_records.items.len));
+ const rec = try self.unwind_records.addOne(self.base.comp.gpa);
+ rec.* = .{};
+ return index;
}
-pub fn getSegmentMemoryProtection(segname: []const u8) macho.vm_prot_t {
- if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE;
- if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC;
- if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ;
- return macho.PROT.READ | macho.PROT.WRITE;
+pub fn getUnwindRecord(self: *MachO, index: UnwindInfo.Record.Index) *UnwindInfo.Record {
+ assert(index < self.unwind_records.items.len);
+ return &self.unwind_records.items[index];
}
-pub fn getSectionPrecedence(header: macho.section_64) u8 {
- const segment_precedence: u4 = getSegmentPrecedence(header.segName());
- const section_precedence: 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, "__eh_frame", header.sectName())) break :blk 0xf;
- break :blk 0x3;
- },
- }
- };
- return (@as(u8, @intCast(segment_precedence)) << 4) + section_precedence;
+pub fn addThunk(self: *MachO) !Thunk.Index {
+ const index = @as(Thunk.Index, @intCast(self.thunks.items.len));
+ const thunk = try self.thunks.addOne(self.base.comp.gpa);
+ thunk.* = .{};
+ return index;
}
-pub const ParseErrorCtx = struct {
- arena_allocator: std.heap.ArenaAllocator,
- detected_dylib_id: struct {
- parent: u16,
- required_version: u32,
- found_version: u32,
- },
- detected_targets: std.ArrayList([]const u8),
-
- pub fn init(gpa: Allocator) ParseErrorCtx {
- return .{
- .arena_allocator = std.heap.ArenaAllocator.init(gpa),
- .detected_dylib_id = undefined,
- .detected_targets = std.ArrayList([]const u8).init(gpa),
- };
- }
+pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk {
+ assert(index < self.thunks.items.len);
+ return &self.thunks.items[index];
+}
- pub fn deinit(ctx: *ParseErrorCtx) void {
- ctx.arena_allocator.deinit();
- ctx.detected_targets.deinit();
- }
+pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 {
+ if (mem.startsWith(u8, path, prefix)) return path[prefix.len..];
+ return null;
+}
- pub fn arena(ctx: *ParseErrorCtx) Allocator {
- return ctx.arena_allocator.allocator();
+const ErrorWithNotes = struct {
+ /// Allocated index in comp.link_errors array.
+ index: usize,
+
+ /// Next available note slot.
+ note_slot: usize = 0,
+
+ pub fn addMsg(
+ err: ErrorWithNotes,
+ macho_file: *MachO,
+ comptime format: []const u8,
+ args: anytype,
+ ) error{OutOfMemory}!void {
+ const comp = macho_file.base.comp;
+ const gpa = comp.gpa;
+ const err_msg = &comp.link_errors.items[err.index];
+ err_msg.msg = try std.fmt.allocPrint(gpa, format, args);
+ }
+
+ pub fn addNote(
+ err: *ErrorWithNotes,
+ macho_file: *MachO,
+ comptime format: []const u8,
+ args: anytype,
+ ) error{OutOfMemory}!void {
+ const comp = macho_file.base.comp;
+ const gpa = comp.gpa;
+ const err_msg = &comp.link_errors.items[err.index];
+ assert(err.note_slot < err_msg.notes.len);
+ err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) };
+ err.note_slot += 1;
}
};
-pub fn handleAndReportParseError(
- self: *MachO,
- path: []const u8,
- err: ParseError,
- ctx: *const ParseErrorCtx,
-) error{OutOfMemory}!void {
- const target = self.base.comp.root_mod.resolved_target.result;
- const gpa = self.base.comp.gpa;
- const cpu_arch = target.cpu.arch;
- switch (err) {
- error.DylibAlreadyExists => {},
- error.IncompatibleDylibVersion => {
- const parent = &self.dylibs.items[ctx.detected_dylib_id.parent];
- try self.reportDependencyError(
- if (parent.id) |id| id.name else parent.path,
- path,
- "incompatible dylib version: expected at least '{}', but found '{}'",
- .{
- load_commands.appleVersionToSemanticVersion(ctx.detected_dylib_id.required_version),
- load_commands.appleVersionToSemanticVersion(ctx.detected_dylib_id.found_version),
- },
- );
- },
- error.UnknownFileType => try self.reportParseError(path, "unknown file type", .{}),
- error.InvalidTarget, error.InvalidTargetFatLibrary => {
- var targets_string = std.ArrayList(u8).init(gpa);
- defer targets_string.deinit();
-
- if (ctx.detected_targets.items.len > 1) {
- try targets_string.writer().writeAll("(");
- for (ctx.detected_targets.items) |t| {
- try targets_string.writer().print("{s}, ", .{t});
- }
- try targets_string.resize(targets_string.items.len - 2);
- try targets_string.writer().writeAll(")");
- } else {
- try targets_string.writer().writeAll(ctx.detected_targets.items[0]);
- }
+pub fn addErrorWithNotes(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
+ const comp = self.base.comp;
+ const gpa = comp.gpa;
+ try comp.link_errors.ensureUnusedCapacity(gpa, 1);
+ return self.addErrorWithNotesAssumeCapacity(note_count);
+}
- switch (err) {
- error.InvalidTarget => try self.reportParseError(
- path,
- "invalid target: expected '{}', but found '{s}'",
- .{ Platform.fromTarget(target).fmtTarget(cpu_arch), targets_string.items },
- ),
- error.InvalidTargetFatLibrary => try self.reportParseError(
- path,
- "invalid architecture in universal library: expected '{s}', but found '{s}'",
- .{ @tagName(cpu_arch), targets_string.items },
- ),
- else => unreachable,
- }
- },
- else => |e| try self.reportParseError(path, "{s}: parsing object failed", .{@errorName(e)}),
- }
+fn addErrorWithNotesAssumeCapacity(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
+ const comp = self.base.comp;
+ const gpa = comp.gpa;
+ const index = comp.link_errors.items.len;
+ const err = comp.link_errors.addOneAssumeCapacity();
+ err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) };
+ return .{ .index = index };
}
-fn reportMissingLibraryError(
+pub fn reportParseError(
self: *MachO,
- checked_paths: []const []const u8,
+ path: []const u8,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
- const notes = try gpa.alloc(File.ErrorMsg, checked_paths.len);
- errdefer gpa.free(notes);
- for (checked_paths, notes) |path, *note| {
- note.* = .{ .msg = try std.fmt.allocPrint(gpa, "tried {s}", .{path}) };
- }
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try std.fmt.allocPrint(gpa, format, args),
- .notes = notes,
- });
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, format, args);
+ try err.addNote(self, "while parsing {s}", .{path});
}
-fn reportDependencyError(
+pub fn reportParseError2(
self: *MachO,
- parent: []const u8,
- path: ?[]const u8,
+ file_index: File.Index,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
- var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2);
- defer notes.deinit();
- if (path) |p| {
- notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "while parsing {s}", .{p}) });
- }
- notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "a dependency of {s}", .{parent}) });
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try std.fmt.allocPrint(gpa, format, args),
- .notes = try notes.toOwnedSlice(),
- });
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, format, args);
+ try err.addNote(self, "while parsing {}", .{self.getFile(file_index).?.fmtPath()});
}
-pub fn reportParseError(
+fn reportMissingLibraryError(
self: *MachO,
- path: []const u8,
+ checked_paths: []const []const u8,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
- var notes = try gpa.alloc(File.ErrorMsg, 1);
- errdefer gpa.free(notes);
- notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "while parsing {s}", .{path}) };
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try std.fmt.allocPrint(gpa, format, args),
- .notes = notes,
- });
+ var err = try self.addErrorWithNotes(checked_paths.len);
+ try err.addMsg(self, format, args);
+ for (checked_paths) |path| {
+ try err.addNote(self, "tried {s}", .{path});
+ }
}
-pub fn reportUnresolvedBoundarySymbol(
+fn reportDependencyError(
self: *MachO,
- sym_name: []const u8,
+ parent: File.Index,
+ path: ?[]const u8,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
- var notes = try gpa.alloc(File.ErrorMsg, 1);
- errdefer gpa.free(notes);
- notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "while resolving {s}", .{sym_name}) };
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try std.fmt.allocPrint(gpa, format, args),
- .notes = notes,
- });
+ var err = try self.addErrorWithNotes(2);
+ try err.addMsg(self, format, args);
+ if (path) |p| {
+ try err.addNote(self, "while parsing {s}", .{p});
+ }
+ try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()});
}
-pub fn reportUndefined(self: *MachO) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const count = self.unresolved.count();
- try comp.link_errors.ensureUnusedCapacity(gpa, count);
-
- for (self.unresolved.keys()) |global_index| {
- const global = self.globals.items[global_index];
- const sym_name = self.getSymbolName(global);
+pub fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void {
+ var err = try self.addErrorWithNotes(1);
+ try err.addMsg(self, format, args);
+ try err.addNote(self, "please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{});
+}
- var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 1);
- defer notes.deinit();
+fn reportDuplicates(self: *MachO, dupes: anytype) error{ HasDuplicates, OutOfMemory }!void {
+ const tracy = trace(@src());
+ defer tracy.end();
- if (global.getFile()) |file| {
- const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{
- self.objects.items[file].name,
- });
- notes.appendAssumeCapacity(.{ .msg = note });
- }
+ const max_notes = 3;
- var err_msg = File.ErrorMsg{
- .msg = try std.fmt.allocPrint(gpa, "undefined reference to symbol {s}", .{sym_name}),
- };
- err_msg.notes = try notes.toOwnedSlice();
+ var has_dupes = false;
+ var it = dupes.iterator();
+ while (it.next()) |entry| {
+ const sym = self.getSymbol(entry.key_ptr.*);
+ const notes = entry.value_ptr.*;
+ const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
- comp.link_errors.appendAssumeCapacity(err_msg);
- }
-}
+ var err = try self.addErrorWithNotes(nnotes + 1);
+ try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.getName(self)});
+ try err.addNote(self, "defined by {}", .{sym.getFile(self).?.fmtPath()});
-fn reportSymbolCollision(
- self: *MachO,
- first: SymbolWithLoc,
- other: SymbolWithLoc,
-) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
+ var inote: usize = 0;
+ while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
+ const file = self.getFile(notes.items[inote]).?;
+ try err.addNote(self, "defined by {}", .{file.fmtPath()});
+ }
- var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2);
- defer notes.deinit();
+ if (notes.items.len > max_notes) {
+ const remaining = notes.items.len - max_notes;
+ try err.addNote(self, "defined {d} more times", .{remaining});
+ }
- if (first.getFile()) |file| {
- const note = try std.fmt.allocPrint(gpa, "first definition in {s}", .{
- self.objects.items[file].name,
- });
- notes.appendAssumeCapacity(.{ .msg = note });
- }
- if (other.getFile()) |file| {
- const note = try std.fmt.allocPrint(gpa, "next definition in {s}", .{
- self.objects.items[file].name,
- });
- notes.appendAssumeCapacity(.{ .msg = note });
+ has_dupes = true;
}
- var err_msg = File.ErrorMsg{ .msg = try std.fmt.allocPrint(gpa, "symbol {s} defined multiple times", .{
- self.getSymbolName(first),
- }) };
- err_msg.notes = try notes.toOwnedSlice();
+ if (has_dupes) return error.HasDuplicates;
+}
- comp.link_errors.appendAssumeCapacity(err_msg);
+pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols {
+ if (self.d_sym) |*ds| {
+ return ds;
+ } else return null;
}
-fn reportUnhandledSymbolType(self: *MachO, sym_with_loc: SymbolWithLoc) error{OutOfMemory}!void {
- const comp = self.base.comp;
- const gpa = comp.gpa;
- try comp.link_errors.ensureUnusedCapacity(gpa, 1);
+pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void {
+ if (!is_hot_update_compatible) return;
- const notes = try gpa.alloc(File.ErrorMsg, 1);
- errdefer gpa.free(notes);
+ const mach_task = try std.os.darwin.machTaskForPid(pid);
+ log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task });
+ self.hot_state.mach_task = mach_task;
- const file = sym_with_loc.getFile().?;
- notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "defined in {s}", .{self.objects.items[file].name}) };
+ // TODO start exception handler in another thread
- const sym = self.getSymbol(sym_with_loc);
- const sym_type = if (sym.stab())
- "stab"
- else if (sym.indr())
- "indirect"
- else if (sym.abs())
- "absolute"
- else
- unreachable;
-
- comp.link_errors.appendAssumeCapacity(.{
- .msg = try std.fmt.allocPrint(gpa, "unhandled symbol type: '{s}' has type {s}", .{
- self.getSymbolName(sym_with_loc),
- sym_type,
- }),
- .notes = notes,
- });
+ // TODO enable ones we register for exceptions
+ // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0);
}
-/// Binary search
-pub fn bsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize {
- if (!@hasDecl(@TypeOf(predicate), "predicate"))
- @compileError("Predicate is required to define fn predicate(@This(), T) bool");
+pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void {
+ if (!is_hot_update_compatible) return;
- var min: usize = 0;
- var max: usize = haystack.len;
- while (min < max) {
- const index = (min + max) / 2;
- const curr = haystack[index];
- if (predicate.predicate(curr)) {
- min = index + 1;
- } else {
- max = index;
- }
- }
- return min;
-}
+ _ = pid;
-/// Linear search
-pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize {
- if (!@hasDecl(@TypeOf(predicate), "predicate"))
- @compileError("Predicate is required to define fn predicate(@This(), T) bool");
+ // TODO stop exception handler
- var i: usize = 0;
- while (i < haystack.len) : (i += 1) {
- if (predicate.predicate(haystack[i])) break;
- }
- return i;
+ // TODO see comment in ptraceAttach
+ // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0);
+
+ self.hot_state.mach_task = null;
}
-pub fn logSegments(self: *MachO) void {
- log.debug("segments:", .{});
- for (self.segments.items, 0..) |segment, i| {
- log.debug(" segment({d}): {s} @{x} ({x}), sizeof({x})", .{
- i,
- segment.segName(),
- segment.fileoff,
- segment.vmaddr,
- segment.vmsize,
- });
- }
+pub fn dumpState(self: *MachO) std.fmt.Formatter(fmtDumpState) {
+ return .{ .data = self };
}
-pub fn logSections(self: *MachO) void {
- log.debug("sections:", .{});
- for (self.sections.items(.header), 0..) |header, i| {
- log.debug(" sect({d}): {s},{s} @{x} ({x}), sizeof({x})", .{
- i + 1,
- header.segName(),
- header.sectName(),
- header.offset,
- header.addr,
- header.size,
+fn fmtDumpState(
+ self: *MachO,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ if (self.getZigObject()) |zo| {
+ try writer.print("zig_object({d}) : {s}\n", .{ zo.index, zo.path });
+ try writer.print("{}{}\n", .{
+ zo.fmtAtoms(self),
+ zo.fmtSymtab(self),
});
}
-}
-
-fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 {
- if (sym.sect()) {
- buf[0] = 's';
+ for (self.objects.items) |index| {
+ const object = self.getFile(index).?.object;
+ try writer.print("object({d}) : {} : has_debug({})", .{
+ index,
+ object.fmtPath(),
+ object.hasDebugInfo(),
+ });
+ if (!object.alive) try writer.writeAll(" : ([*])");
+ try writer.writeByte('\n');
+ try writer.print("{}{}{}{}{}\n", .{
+ object.fmtAtoms(self),
+ object.fmtCies(self),
+ object.fmtFdes(self),
+ object.fmtUnwindRecords(self),
+ object.fmtSymtab(self),
+ });
}
- if (sym.ext()) {
- if (sym.weakDef() or sym.pext()) {
- buf[1] = 'w';
- } else {
- buf[1] = 'e';
- }
+ for (self.dylibs.items) |index| {
+ const dylib = self.getFile(index).?.dylib;
+ try writer.print("dylib({d}) : {s} : needed({}) : weak({})", .{
+ index,
+ dylib.path,
+ dylib.needed,
+ dylib.weak,
+ });
+ if (!dylib.isAlive(self)) try writer.writeAll(" : ([*])");
+ try writer.writeByte('\n');
+ try writer.print("{}\n", .{dylib.fmtSymtab(self)});
}
- if (sym.tentative()) {
- buf[2] = 't';
+ if (self.getInternalObject()) |internal| {
+ try writer.print("internal({d}) : internal\n", .{internal.index});
+ try writer.print("{}{}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) });
}
- if (sym.undf()) {
- buf[3] = 'u';
+ try writer.writeAll("thunks\n");
+ for (self.thunks.items, 0..) |thunk, index| {
+ try writer.print("thunk({d}) : {}\n", .{ index, thunk.fmt(self) });
}
- return buf[0..];
+ try writer.print("stubs\n{}\n", .{self.stubs.fmt(self)});
+ try writer.print("objc_stubs\n{}\n", .{self.objc_stubs.fmt(self)});
+ try writer.print("got\n{}\n", .{self.got.fmt(self)});
+ try writer.print("zig_got\n{}\n", .{self.zig_got.fmt(self)});
+ try writer.print("tlv_ptr\n{}\n", .{self.tlv_ptr.fmt(self)});
+ try writer.writeByte('\n');
+ try writer.print("sections\n{}\n", .{self.fmtSections()});
+ try writer.print("segments\n{}\n", .{self.fmtSegments()});
}
-pub fn logSymtab(self: *MachO) void {
- var buf: [4]u8 = undefined;
-
- const scoped_log = std.log.scoped(.symtab);
+fn fmtSections(self: *MachO) std.fmt.Formatter(formatSections) {
+ return .{ .data = self };
+}
- scoped_log.debug("locals:", .{});
- for (self.objects.items, 0..) |object, id| {
- scoped_log.debug(" object({d}): {s}", .{ id, object.name });
- if (object.in_symtab == null) continue;
- for (object.symtab, 0..) |sym, sym_id| {
- @memset(&buf, '_');
- scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{
- sym_id,
- object.getSymbolName(@as(u32, @intCast(sym_id))),
- sym.n_value,
- sym.n_sect,
- logSymAttributes(sym, &buf),
- });
- }
- }
- scoped_log.debug(" object(-1)", .{});
- for (self.locals.items, 0..) |sym, sym_id| {
- if (sym.undf()) continue;
- scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{
- sym_id,
- self.strtab.get(sym.n_strx).?,
- sym.n_value,
- sym.n_sect,
- logSymAttributes(sym, &buf),
+fn formatSections(
+ self: *MachO,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ const slice = self.sections.slice();
+ for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| {
+ try writer.print("sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x})\n", .{
+ i, seg_id, header.segName(), header.sectName(), header.offset, header.addr,
+ header.@"align", header.size,
});
}
+}
- scoped_log.debug("exports:", .{});
- for (self.globals.items, 0..) |global, i| {
- const sym = self.getSymbol(global);
- if (sym.undf()) continue;
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
- scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s} (def in object({?}))", .{
- i,
- self.getSymbolName(global),
- sym.n_value,
- sym.n_sect,
- logSymAttributes(sym, &buf),
- global.file,
- });
- }
+fn fmtSegments(self: *MachO) std.fmt.Formatter(formatSegments) {
+ return .{ .data = self };
+}
- scoped_log.debug("imports:", .{});
- for (self.globals.items, 0..) |global, i| {
- const sym = self.getSymbol(global);
- if (!sym.undf()) continue;
- if (sym.n_desc == N_DEAD) continue;
- if (sym.n_desc == N_BOUNDARY) continue;
- const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER);
- scoped_log.debug(" %{d}: {s} @{x} in ord({d}), {s}", .{
- i,
- self.getSymbolName(global),
- sym.n_value,
- ord,
- logSymAttributes(sym, &buf),
+fn formatSegments(
+ self: *MachO,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ for (self.segments.items, 0..) |seg, i| {
+ try writer.print("seg({d}) : {s} : @{x}-{x} ({x}-{x})\n", .{
+ i, seg.segName(), seg.vmaddr, seg.vmaddr + seg.vmsize,
+ seg.fileoff, seg.fileoff + seg.filesize,
});
}
-
- scoped_log.debug("GOT entries:", .{});
- scoped_log.debug("{}", .{self.got_table});
-
- scoped_log.debug("TLV pointers:", .{});
- scoped_log.debug("{}", .{self.tlv_ptr_table});
-
- scoped_log.debug("stubs entries:", .{});
- scoped_log.debug("{}", .{self.stub_table});
-
- scoped_log.debug("thunks:", .{});
- for (self.thunks.items, 0..) |thunk, i| {
- scoped_log.debug(" thunk({d})", .{i});
- const slice = thunk.targets.slice();
- for (slice.items(.tag), slice.items(.target), 0..) |tag, target, j| {
- const atom_index = @as(u32, @intCast(thunk.getStartAtomIndex() + j));
- const atom = self.getAtom(atom_index);
- const atom_sym = self.getSymbol(atom.getSymbolWithLoc());
- const target_addr = switch (tag) {
- .stub => self.getStubsEntryAddress(target).?,
- .atom => self.getSymbol(target).n_value,
- };
- scoped_log.debug(" {d}@{x} => {s}({s}@{x})", .{
- j,
- atom_sym.n_value,
- @tagName(tag),
- self.getSymbolName(target),
- target_addr,
- });
- }
- }
}
-pub fn logAtoms(self: *MachO) void {
- log.debug("atoms:", .{});
- const slice = self.sections.slice();
- for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
- var atom_index = first_atom_index orelse continue;
- const header = slice.items(.header)[sect_id];
-
- log.debug("{s},{s}", .{ header.segName(), header.sectName() });
-
- while (true) {
- const atom = self.getAtom(atom_index);
- self.logAtom(atom_index, log);
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
- }
+pub fn fmtSectType(tt: u8) std.fmt.Formatter(formatSectType) {
+ return .{ .data = tt };
}
-pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void {
- if (!build_options.enable_logging) return;
-
- const atom = self.getAtom(atom_index);
- const sym = self.getSymbol(atom.getSymbolWithLoc());
- const sym_name = self.getSymbolName(atom.getSymbolWithLoc());
- logger.debug(" ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{
- atom_index,
- atom.sym_index,
- sym_name,
- sym.n_value,
- atom.size,
- atom.alignment,
- atom.getFile(),
- sym.n_sect,
- });
-
- if (atom.getFile() != null) {
- var it = Atom.getInnerSymbolsIterator(self, atom_index);
- while (it.next()) |sym_loc| {
- const inner = self.getSymbol(sym_loc);
- const inner_name = self.getSymbolName(sym_loc);
- const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index);
-
- logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{
- sym_loc.sym_index,
- inner_name,
- inner.n_value,
- offset,
- });
- }
-
- if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
- const alias = self.getSymbol(sym_loc);
- const alias_name = self.getSymbolName(sym_loc);
-
- logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{
- sym_loc.sym_index,
- alias_name,
- alias.n_value,
- 0,
- });
- }
- }
+fn formatSectType(
+ tt: u8,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ const name = switch (tt) {
+ macho.S_REGULAR => "REGULAR",
+ macho.S_ZEROFILL => "ZEROFILL",
+ macho.S_CSTRING_LITERALS => "CSTRING_LITERALS",
+ macho.S_4BYTE_LITERALS => "4BYTE_LITERALS",
+ macho.S_8BYTE_LITERALS => "8BYTE_LITERALS",
+ macho.S_16BYTE_LITERALS => "16BYTE_LITERALS",
+ macho.S_LITERAL_POINTERS => "LITERAL_POINTERS",
+ macho.S_NON_LAZY_SYMBOL_POINTERS => "NON_LAZY_SYMBOL_POINTERS",
+ macho.S_LAZY_SYMBOL_POINTERS => "LAZY_SYMBOL_POINTERS",
+ macho.S_SYMBOL_STUBS => "SYMBOL_STUBS",
+ macho.S_MOD_INIT_FUNC_POINTERS => "MOD_INIT_FUNC_POINTERS",
+ macho.S_MOD_TERM_FUNC_POINTERS => "MOD_TERM_FUNC_POINTERS",
+ macho.S_COALESCED => "COALESCED",
+ macho.S_GB_ZEROFILL => "GB_ZEROFILL",
+ macho.S_INTERPOSING => "INTERPOSING",
+ macho.S_DTRACE_DOF => "DTRACE_DOF",
+ macho.S_THREAD_LOCAL_REGULAR => "THREAD_LOCAL_REGULAR",
+ macho.S_THREAD_LOCAL_ZEROFILL => "THREAD_LOCAL_ZEROFILL",
+ macho.S_THREAD_LOCAL_VARIABLES => "THREAD_LOCAL_VARIABLES",
+ macho.S_THREAD_LOCAL_VARIABLE_POINTERS => "THREAD_LOCAL_VARIABLE_POINTERS",
+ macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => "THREAD_LOCAL_INIT_FUNCTION_POINTERS",
+ macho.S_INIT_FUNC_OFFSETS => "INIT_FUNC_OFFSETS",
+ else => |x| return writer.print("UNKNOWN({x})", .{x}),
+ };
+ try writer.print("{s}", .{name});
}
+const is_hot_update_compatible = switch (builtin.target.os.tag) {
+ .macos => true,
+ else => false,
+};
+
const default_entry_symbol_name = "_main";
-pub const base_tag: File.Tag = File.Tag.macho;
+pub const base_tag: link.File.Tag = link.File.Tag.macho;
pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2)));
-/// Mode of operation of the linker.
-pub const Mode = enum {
- /// Incremental mode will preallocate segments/sections and is compatible with
- /// watch and HCS modes of operation.
- incremental,
- /// Zld mode will link relocatables in a traditional, one-shot
- /// fashion (default for LLVM backend). It acts as a drop-in replacement for
- /// LLD.
- zld,
-};
-
-pub const Section = struct {
+const Section = struct {
header: macho.section_64,
- segment_index: u8,
- first_atom_index: ?Atom.Index = null,
- last_atom_index: ?Atom.Index = null,
-
- /// A list of atoms that have surplus capacity. This list can have false
- /// positives, as functions grow and shrink over time, only sometimes being added
- /// or removed from the freelist.
- ///
- /// An atom has surplus capacity when its overcapacity value is greater than
- /// padToIdeal(minimum_atom_size). That is, when it has so
- /// much extra capacity, that we could fit a small new symbol in it, itself with
- /// ideal_capacity or more.
- ///
- /// Ideal capacity is defined by size + (size / ideal_factor).
- ///
- /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
- /// overcapacity can be negative. A simple way to have negative overcapacity is to
- /// allocate a fresh atom, which will have ideal capacity, and then grow it
- /// by 1 byte. It will then have -1 overcapacity.
+ segment_id: u8,
+ atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
+ last_atom_index: Atom.Index = 0,
};
-const is_hot_update_compatible = switch (builtin.target.os.tag) {
- .macos => true,
- else => false,
+const HotUpdateState = struct {
+ mach_task: ?std.os.darwin.MachTask = null,
};
-const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
+pub const DynamicRelocs = struct {
+ rebase_relocs: u32 = 0,
+ bind_relocs: u32 = 0,
+ weak_bind_relocs: u32 = 0,
+};
-const LazySymbolMetadata = struct {
- const State = enum { unused, pending_flush, flushed };
- text_atom: Atom.Index = undefined,
- data_const_atom: Atom.Index = undefined,
- text_state: State = .unused,
- data_const_state: State = .unused,
+pub const SymtabCtx = struct {
+ ilocal: u32 = 0,
+ istab: u32 = 0,
+ iexport: u32 = 0,
+ iimport: u32 = 0,
+ nlocals: u32 = 0,
+ nstabs: u32 = 0,
+ nexports: u32 = 0,
+ nimports: u32 = 0,
+ strsize: u32 = 0,
};
-const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index);
+pub const null_sym = macho.nlist_64{
+ .n_strx = 0,
+ .n_type = 0,
+ .n_sect = 0,
+ .n_desc = 0,
+ .n_value = 0,
+};
+
+pub const Platform = struct {
+ os_tag: std.Target.Os.Tag,
+ abi: std.Target.Abi,
+ version: std.SemanticVersion,
+
+ /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
+ /// the extracted minimum platform version.
+ pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
+ switch (lc.cmd()) {
+ .BUILD_VERSION => {
+ const cmd = lc.cast(macho.build_version_command).?;
+ return .{
+ .os_tag = switch (cmd.platform) {
+ .MACOS => .macos,
+ .IOS, .IOSSIMULATOR => .ios,
+ .TVOS, .TVOSSIMULATOR => .tvos,
+ .WATCHOS, .WATCHOSSIMULATOR => .watchos,
+ else => @panic("TODO"),
+ },
+ .abi = switch (cmd.platform) {
+ .IOSSIMULATOR,
+ .TVOSSIMULATOR,
+ .WATCHOSSIMULATOR,
+ => .simulator,
+ else => .none,
+ },
+ .version = appleVersionToSemanticVersion(cmd.minos),
+ };
+ },
+ .VERSION_MIN_MACOSX,
+ .VERSION_MIN_IPHONEOS,
+ .VERSION_MIN_TVOS,
+ .VERSION_MIN_WATCHOS,
+ => {
+ const cmd = lc.cast(macho.version_min_command).?;
+ return .{
+ .os_tag = switch (lc.cmd()) {
+ .VERSION_MIN_MACOSX => .macos,
+ .VERSION_MIN_IPHONEOS => .ios,
+ .VERSION_MIN_TVOS => .tvos,
+ .VERSION_MIN_WATCHOS => .watchos,
+ else => unreachable,
+ },
+ .abi = .none,
+ .version = appleVersionToSemanticVersion(cmd.version),
+ };
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn fromTarget(target: std.Target) Platform {
+ return .{
+ .os_tag = target.os.tag,
+ .abi = target.abi,
+ .version = target.os.version_range.semver.min,
+ };
+ }
-const DeclMetadata = struct {
- atom: Atom.Index,
- section: u8,
- /// A list of all exports aliases of this Decl.
- /// TODO do we actually need this at all?
- exports: std.ArrayListUnmanaged(u32) = .{},
+ pub fn toAppleVersion(plat: Platform) u32 {
+ return semanticVersionToAppleVersion(plat.version);
+ }
+
+ pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
+ return switch (plat.os_tag) {
+ .macos => .MACOS,
+ .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
+ .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
+ .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
+ else => unreachable,
+ };
+ }
- fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 {
- for (m.exports.items) |exp| {
- if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp;
+ pub fn isBuildVersionCompatible(plat: Platform) bool {
+ inline for (supported_platforms) |sup_plat| {
+ if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
+ return sup_plat[2] <= plat.toAppleVersion();
+ }
}
- return null;
+ return false;
}
- fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 {
- for (m.exports.items) |*exp| {
- if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp;
+ pub fn isVersionMinCompatible(plat: Platform) bool {
+ inline for (supported_platforms) |sup_plat| {
+ if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
+ return sup_plat[3] <= plat.toAppleVersion();
+ }
}
- return null;
+ return false;
}
-};
-const DeclTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
-const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
-const BindingTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Binding));
-const UnnamedConstTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Atom.Index));
-const RebaseTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32));
-const RelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation));
-const ActionTable = std.AutoHashMapUnmanaged(u32, RelocFlags);
-
-pub const RelocFlags = packed struct {
- add_got: bool = false,
- add_stub: bool = false,
-};
+ pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
+ return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
+ }
-pub const SymbolWithLoc = extern struct {
- // Index into the respective symbol table.
- sym_index: u32,
+ const FmtCtx = struct {
+ platform: Platform,
+ cpu_arch: std.Target.Cpu.Arch,
+ };
- // 0 means it's a synthetic global.
- file: u32 = 0,
+ pub fn formatTarget(
+ ctx: FmtCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
+ if (ctx.platform.abi != .none) {
+ try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
+ }
+ }
- pub fn getFile(self: SymbolWithLoc) ?u32 {
- if (self.file == 0) return null;
- return self.file - 1;
+ /// Caller owns the memory.
+ pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
+ var buffer = std.ArrayList(u8).init(gpa);
+ defer buffer.deinit();
+ try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
+ return buffer.toOwnedSlice();
}
- pub fn eql(self: SymbolWithLoc, other: SymbolWithLoc) bool {
- return self.file == other.file and self.sym_index == other.sym_index;
+ pub fn eqlTarget(plat: Platform, other: Platform) bool {
+ return plat.os_tag == other.os_tag and plat.abi == other.abi;
}
};
-const HotUpdateState = struct {
- mach_task: ?std.os.darwin.MachTask = null,
+const SupportedPlatforms = struct {
+ std.Target.Os.Tag,
+ std.Target.Abi,
+ u32, // Min platform version for which to emit LC_BUILD_VERSION
+ u32, // Min supported platform version
+};
+
+// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
+// zig fmt: off
+const supported_platforms = [_]SupportedPlatforms{
+ .{ .macos, .none, 0xA0E00, 0xA0800 },
+ .{ .ios, .none, 0xC0000, 0x70000 },
+ .{ .tvos, .none, 0xC0000, 0x70000 },
+ .{ .watchos, .none, 0x50000, 0x20000 },
+ .{ .ios, .simulator, 0xD0000, 0x80000 },
+ .{ .tvos, .simulator, 0xD0000, 0x80000 },
+ .{ .watchos, .simulator, 0x60000, 0x20000 },
};
+// zig fmt: on
+
+pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
+ const major = version.major;
+ const minor = version.minor;
+ const patch = version.patch;
+ return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
+}
+
+pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
+ return .{
+ .major = @as(u16, @truncate(version >> 16)),
+ .minor = @as(u8, @truncate(version >> 8)),
+ .patch = @as(u8, @truncate(version)),
+ };
+}
+
+fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion {
+ const gpa = comp.gpa;
+
+ var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+ defer arena_allocator.deinit();
+ const arena = arena_allocator.allocator();
+
+ const sdk_dir = switch (sdk_layout) {
+ .sdk => comp.sysroot.?,
+ .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
+ };
+ if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
+ return parseSdkVersion(ver);
+ } else |_| {
+ // Read from settings should always succeed when vendored.
+ // TODO: convert to fatal linker error
+ if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
+ }
+
+ // infer from pathname
+ const stem = std.fs.path.stem(sdk_dir);
+ const start = for (stem, 0..) |c, i| {
+ if (std.ascii.isDigit(c)) break i;
+ } else stem.len;
+ const end = for (stem[start..], start..) |c, i| {
+ if (std.ascii.isDigit(c) or c == '.') continue;
+ break i;
+ } else stem.len;
+ return parseSdkVersion(stem[start..end]);
+}
+
+// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
+// Use property `MinimalDisplayName` to determine version.
+// The file/property is also available with vendored libc.
+fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
+ const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
+ const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
+ const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
+ if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
+ return error.SdkVersionFailure;
+}
+
+// Versions reported by Apple aren't exactly semantically valid as they usually omit
+// the patch component, so we parse SDK value by hand.
+fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
+ var parsed: std.SemanticVersion = .{
+ .major = 0,
+ .minor = 0,
+ .patch = 0,
+ };
+
+ const parseNext = struct {
+ fn parseNext(it: anytype) ?u16 {
+ const nn = it.next() orelse return null;
+ return std.fmt.parseInt(u16, nn, 10) catch null;
+ }
+ }.parseNext;
+
+ var it = std.mem.splitAny(u8, raw, ".");
+ parsed.major = parseNext(&it) orelse return null;
+ parsed.minor = parseNext(&it) orelse return null;
+ parsed.patch = parseNext(&it) orelse 0;
+ return parsed;
+}
/// When allocating, the ideal_capacity is calculated by
/// actual_capacity + (actual_capacity / ideal_factor)
@@ -5783,13 +4379,37 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size);
/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and
/// start of __TEXT segment.
-pub const default_pagezero_vmsize: u64 = 0x100000000;
+pub const default_pagezero_size: u64 = 0x100000000;
/// We commit 0x1000 = 4096 bytes of space to the header and
/// the table of load commands. This should be plenty for any
/// potential future extensions.
pub const default_headerpad_size: u32 = 0x1000;
+const SystemLib = struct {
+ path: []const u8,
+ needed: bool = false,
+ weak: bool = false,
+ hidden: bool = false,
+ reexport: bool = false,
+ must_link: bool = false,
+};
+
+/// The filesystem layout of darwin SDK elements.
+pub const SdkLayout = enum {
+ /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }.
+ sdk,
+ /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }.
+ vendored,
+};
+
+const UndefinedTreatment = enum {
+ @"error",
+ warn,
+ suppress,
+ dynamic_lookup,
+};
+
const MachO = @This();
const std = @import("std");
@@ -5799,6 +4419,7 @@ const assert = std.debug.assert;
const dwarf = std.dwarf;
const fs = std.fs;
const log = std.log.scoped(.link);
+const state_log = std.log.scoped(.link_state);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
@@ -5808,46 +4429,56 @@ const aarch64 = @import("../arch/aarch64/bits.zig");
const calcUuid = @import("MachO/uuid.zig").calcUuid;
const codegen = @import("../codegen.zig");
const dead_strip = @import("MachO/dead_strip.zig");
+const eh_frame = @import("MachO/eh_frame.zig");
const fat = @import("MachO/fat.zig");
const link = @import("../link.zig");
const llvm_backend = @import("../codegen/llvm.zig");
const load_commands = @import("MachO/load_commands.zig");
-const stubs = @import("MachO/stubs.zig");
+const relocatable = @import("MachO/relocatable.zig");
const tapi = @import("tapi.zig");
const target_util = @import("../target.zig");
const thunks = @import("MachO/thunks.zig");
const trace = @import("../tracy.zig").trace;
-const zld = @import("MachO/zld.zig");
+const synthetic = @import("MachO/synthetic.zig");
const Air = @import("../Air.zig");
+const Alignment = Atom.Alignment;
const Allocator = mem.Allocator;
const Archive = @import("MachO/Archive.zig");
pub const Atom = @import("MachO/Atom.zig");
+const BindSection = synthetic.BindSection;
const Cache = std.Build.Cache;
const CodeSignature = @import("MachO/CodeSignature.zig");
const Compilation = @import("../Compilation.zig");
+pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
const Dwarf = File.Dwarf;
const DwarfInfo = @import("MachO/DwarfInfo.zig");
const Dylib = @import("MachO/Dylib.zig");
-const File = link.File;
+const ExportTrieSection = synthetic.ExportTrieSection;
+const File = @import("MachO/file.zig").File;
+const GotSection = synthetic.GotSection;
+const Indsymtab = synthetic.Indsymtab;
+const InternalObject = @import("MachO/InternalObject.zig");
+const ObjcStubsSection = synthetic.ObjcStubsSection;
const Object = @import("MachO/Object.zig");
+const LazyBindSection = synthetic.LazyBindSection;
+const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
const LibStub = tapi.LibStub;
const Liveness = @import("../Liveness.zig");
const LlvmObject = @import("../codegen/llvm.zig").Object;
const Md5 = std.crypto.hash.Md5;
const Module = @import("../Module.zig");
const InternPool = @import("../InternPool.zig");
-const Platform = load_commands.Platform;
-const Relocation = @import("MachO/Relocation.zig");
+const RebaseSection = synthetic.RebaseSection;
+pub const Relocation = @import("MachO/Relocation.zig");
const StringTable = @import("StringTable.zig");
-const TableSection = @import("table_section.zig").TableSection;
-const Trie = @import("MachO/Trie.zig");
-const Type = @import("../type.zig").Type;
+const StubsSection = synthetic.StubsSection;
+const StubsHelperSection = synthetic.StubsHelperSection;
+const Symbol = @import("MachO/Symbol.zig");
+const Thunk = thunks.Thunk;
+const TlvPtrSection = synthetic.TlvPtrSection;
const TypedValue = @import("../TypedValue.zig");
-const Value = @import("../value.zig").Value;
-const Alignment = Atom.Alignment;
-
-pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
-pub const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, SymbolWithLoc);
-pub const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, SymbolWithLoc);
-pub const Rebase = @import("MachO/dyld_info/Rebase.zig");
+const UnwindInfo = @import("MachO/UnwindInfo.zig");
+const WeakBindSection = synthetic.WeakBindSection;
+const ZigGotSection = synthetic.ZigGotSection;
+const ZigObject = @import("MachO/ZigObject.zig");
diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig
index ba3915f51b..7203d89b94 100644
--- a/src/link/MachO/Archive.zig
+++ b/src/link/MachO/Archive.zig
@@ -1,20 +1,15 @@
-file: fs.File,
-fat_offset: u64,
-name: []const u8,
-header: ar_hdr = undefined,
+path: []const u8,
+data: []const u8,
-/// Parsed table of contents.
-/// Each symbol name points to a list of all definition
-/// sites within the current static archive.
-toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
+objects: std.ArrayListUnmanaged(Object) = .{},
// Archive files start with the ARMAG identifying string. Then follows a
// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
// member indicates, for each member file.
/// String that begins an archive file.
-const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
+pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
/// Size of that string.
-const SARMAG: u4 = 8;
+pub const SARMAG: u4 = 8;
/// String in ar_fmag at the end of each header.
const ARFMAG: *const [2:0]u8 = "`\n";
@@ -41,177 +36,111 @@ const ar_hdr = extern struct {
/// Always contains ARFMAG.
ar_fmag: [2]u8,
- const NameOrLength = union(enum) {
- Name: []const u8,
- Length: u32,
- };
- fn nameOrLength(self: ar_hdr) !NameOrLength {
- const value = getValue(&self.ar_name);
- const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
- const len = value.len;
- if (slash_index == len - 1) {
- // Name stored directly
- return NameOrLength{ .Name = value };
- } else {
- // Name follows the header directly and its length is encoded in
- // the name field.
- const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
- return NameOrLength{ .Length = length };
- }
- }
-
fn date(self: ar_hdr) !u64 {
- const value = getValue(&self.ar_date);
+ const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u64, value, 10);
}
fn size(self: ar_hdr) !u32 {
- const value = getValue(&self.ar_size);
+ const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u32, value, 10);
}
- fn getValue(raw: []const u8) []const u8 {
- return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)});
+ fn name(self: *const ar_hdr) ?[]const u8 {
+ const value = &self.ar_name;
+ if (mem.startsWith(u8, value, "#1/")) return null;
+ const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
+ return value[0..sentinel];
}
-};
-pub fn isArchive(file: fs.File, fat_offset: u64) bool {
- const reader = file.reader();
- const magic = reader.readBytesNoEof(SARMAG) catch return false;
- defer file.seekTo(fat_offset) catch {};
- return mem.eql(u8, &magic, ARMAG);
-}
-
-pub fn deinit(self: *Archive, allocator: Allocator) void {
- self.file.close();
- for (self.toc.keys()) |*key| {
- allocator.free(key.*);
- }
- for (self.toc.values()) |*value| {
- value.deinit(allocator);
+ fn nameLength(self: ar_hdr) !?u32 {
+ const value = &self.ar_name;
+ if (!mem.startsWith(u8, value, "#1/")) return null;
+ const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
+ return try std.fmt.parseInt(u32, trimmed, 10);
}
- self.toc.deinit(allocator);
- allocator.free(self.name);
-}
-
-pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void {
- _ = try reader.readBytesNoEof(SARMAG);
- self.header = try reader.readStruct(ar_hdr);
- const name_or_length = try self.header.nameOrLength();
- const embedded_name = try parseName(allocator, name_or_length, reader);
- log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
- defer allocator.free(embedded_name);
-
- try self.parseTableOfContents(allocator, reader);
-}
+};
-fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 {
- var name: []u8 = undefined;
- switch (name_or_length) {
- .Name => |n| {
- name = try allocator.dupe(u8, n);
- },
- .Length => |len| {
- var n = try allocator.alloc(u8, len);
- defer allocator.free(n);
- try reader.readNoEof(n);
- const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len;
- name = try allocator.dupe(u8, n[0..actual_len]);
- },
+pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool {
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
+ if (fat_arch) |arch| {
+ try file.seekTo(arch.offset);
}
- return name;
+ const magic = file.reader().readBytesNoEof(SARMAG) catch return false;
+ if (!mem.eql(u8, &magic, ARMAG)) return false;
+ return true;
}
-fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {
- const symtab_size = try reader.readInt(u32, .little);
- const symtab = try allocator.alloc(u8, symtab_size);
- defer allocator.free(symtab);
-
- reader.readNoEof(symtab) catch {
- log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
- return error.MalformedArchive;
- };
+pub fn deinit(self: *Archive, allocator: Allocator) void {
+ allocator.free(self.data);
+ allocator.free(self.path);
+ self.objects.deinit(allocator);
+}
- const strtab_size = try reader.readInt(u32, .little);
- const strtab = try allocator.alloc(u8, strtab_size);
- defer allocator.free(strtab);
+pub fn parse(self: *Archive, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
- reader.readNoEof(strtab) catch {
- log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
- return error.MalformedArchive;
- };
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
- var symtab_stream = std.io.fixedBufferStream(symtab);
- var symtab_reader = symtab_stream.reader();
+ var stream = std.io.fixedBufferStream(self.data);
+ const reader = stream.reader();
+ _ = try reader.readBytesNoEof(SARMAG);
while (true) {
- const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) {
- error.EndOfStream => break,
- else => |e| return e,
- };
- const object_offset = try symtab_reader.readInt(u32, .little);
+ if (stream.pos >= self.data.len) break;
+ if (!mem.isAligned(stream.pos, 2)) stream.pos += 1;
- const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0);
- const owned_name = try allocator.dupe(u8, sym_name);
- const res = try self.toc.getOrPut(allocator, owned_name);
- defer if (res.found_existing) allocator.free(owned_name);
+ const hdr = try reader.readStruct(ar_hdr);
- if (!res.found_existing) {
- res.value_ptr.* = .{};
+ if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
+ try macho_file.reportParseError(self.path, "invalid header delimiter: expected '{s}', found '{s}'", .{
+ std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
+ });
+ return error.MalformedArchive;
}
- try res.value_ptr.append(allocator, object_offset);
- }
-}
+ var size = try hdr.size();
+ const name = name: {
+ if (hdr.name()) |n| break :name n;
+ if (try hdr.nameLength()) |len| {
+ size -= len;
+ const buf = try arena.allocator().alloc(u8, len);
+ try reader.readNoEof(buf);
+ const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
+ break :name buf[0..actual_len];
+ }
+ unreachable;
+ };
+ defer {
+ _ = stream.seekBy(size) catch {};
+ }
-pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object {
- const reader = self.file.reader();
- try reader.context.seekTo(self.fat_offset + offset);
-
- const object_header = try reader.readStruct(ar_hdr);
-
- const name_or_length = try object_header.nameOrLength();
- const object_name = try parseName(gpa, name_or_length, reader);
- defer gpa.free(object_name);
-
- log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
-
- const name = name: {
- var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- const path = try std.os.realpath(self.name, &buffer);
- break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
- };
-
- const object_name_len = switch (name_or_length) {
- .Name => 0,
- .Length => |len| len,
- };
- const object_size = (try object_header.size()) - object_name_len;
- const contents = try gpa.allocWithOptions(u8, object_size, @alignOf(u64), null);
- const amt = try reader.readAll(contents);
- if (amt != object_size) {
- return error.InputOutput;
- }
+ if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue;
- var object = Object{
- .name = name,
- .mtime = object_header.date() catch 0,
- .contents = contents,
- };
+ const object = Object{
+ .archive = try gpa.dupe(u8, self.path),
+ .path = try gpa.dupe(u8, name),
+ .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),
+ .index = undefined,
+ .alive = false,
+ .mtime = hdr.date() catch 0,
+ };
- try object.parse(gpa);
+ log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
- return object;
+ try self.objects.append(gpa, object);
+ }
}
-const Archive = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
+const fat = @import("fat.zig");
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
+const std = @import("std");
const Allocator = mem.Allocator;
+const Archive = @This();
+const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig
index d76a6de841..57fb67f505 100644
--- a/src/link/MachO/Atom.zig
+++ b/src/link/MachO/Atom.zig
@@ -1,1271 +1,1166 @@
-/// Each Atom always gets a symbol with the fully qualified name.
-/// The symbol can reside in any object file context structure in `symtab` array
-/// (see `Object`), or if the symbol is a synthetic symbol such as a GOT cell or
-/// a stub trampoline, it can be found in the linkers `locals` arraylist.
-/// If this field is 0 and file is 0, it means the codegen size = 0 and there is no symbol or
-/// offset table entry.
-sym_index: u32 = 0,
-
-/// 0 means an Atom is a synthetic Atom such as a GOT cell defined by the linker.
-/// Otherwise, it is the index into appropriate object file (indexing from 1).
-/// Prefer using `getFile()` helper to get the file index out rather than using
-/// the field directly.
-file: u32 = 0,
-
-/// If this Atom is not a synthetic Atom, i.e., references a subsection in an
-/// Object file, `inner_sym_index` and `inner_nsyms_trailing` tell where and if
-/// this Atom contains any additional symbol references that fall within this Atom's
-/// address range. These could for example be an alias symbol which can be used
-/// internally by the relocation records, or if the Object file couldn't be split
-/// into subsections, this Atom may encompass an entire input section.
-inner_sym_index: u32 = 0,
-inner_nsyms_trailing: u32 = 0,
-
-/// Size and alignment of this atom
-/// Unlike in Elf, we need to store the size of this symbol as part of
-/// the atom since macho.nlist_64 lacks this information.
+/// Address allocated for this Atom.
+value: u64 = 0,
+
+/// Name of this Atom.
+name: u32 = 0,
+
+/// Index into linker's input file table.
+file: File.Index = 0,
+
+/// Size of this atom
size: u64 = 0,
-/// Alignment of this atom as a power of 2.
-/// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned.
+/// Alignment of this atom as a power of two.
alignment: Alignment = .@"1",
-/// Points to the previous and next neighbours
-/// TODO use the same trick as with symbols: reserve index 0 as null atom
-next_index: ?Index = null,
-prev_index: ?Index = null,
+/// Index of the input section.
+n_sect: u32 = 0,
-pub const Alignment = @import("../../InternPool.zig").Alignment;
+/// Index of the output section.
+out_n_sect: u8 = 0,
-pub const Index = u32;
+/// Offset within the parent section pointed to by n_sect.
+/// off + size <= parent section size.
+off: u64 = 0,
-pub const Binding = struct {
- target: SymbolWithLoc,
- offset: u64,
-};
+/// Relocations of this atom.
+relocs: Loc = .{},
-/// Returns `null` if the Atom is a synthetic Atom.
-/// Otherwise, returns an index into an array of Objects.
-pub fn getFile(self: Atom) ?u32 {
- if (self.file == 0) return null;
- return self.file - 1;
-}
+/// Index of this atom in the linker's atoms table.
+atom_index: Index = 0,
-pub fn getSymbolIndex(self: Atom) ?u32 {
- if (self.getFile() == null and self.sym_index == 0) return null;
- return self.sym_index;
+/// Index of the thunk for this atom.
+thunk_index: Thunk.Index = 0,
+
+/// Unwind records associated with this atom.
+unwind_records: Loc = .{},
+
+flags: Flags = .{},
+
+/// Points to the previous and next neighbors, based on the `text_offset`.
+/// This can be used to find, for example, the capacity of this `TextBlock`.
+prev_index: Index = 0,
+next_index: Index = 0,
+
+pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 {
+ return macho_file.strings.getAssumeExists(self.name);
}
-/// Returns symbol referencing this atom.
-pub fn getSymbol(self: Atom, macho_file: *MachO) macho.nlist_64 {
- return self.getSymbolPtr(macho_file).*;
+pub fn getFile(self: Atom, macho_file: *MachO) File {
+ return macho_file.getFile(self.file).?;
}
-/// Returns pointer-to-symbol referencing this atom.
-pub fn getSymbolPtr(self: Atom, macho_file: *MachO) *macho.nlist_64 {
- const sym_index = self.getSymbolIndex().?;
- return macho_file.getSymbolPtr(.{ .sym_index = sym_index, .file = self.file });
+pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
+ return switch (self.getFile(macho_file)) {
+ .zig_object => |x| x.getAtomRelocs(self),
+ .object => |x| x.getAtomRelocs(self),
+ else => unreachable,
+ };
}
-pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
- const sym_index = self.getSymbolIndex().?;
- return .{ .sym_index = sym_index, .file = self.file };
+pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 {
+ return switch (self.getFile(macho_file)) {
+ .zig_object => |x| x.getInputSection(self, macho_file),
+ .object => |x| x.sections.items(.header)[self.n_sect],
+ else => unreachable,
+ };
}
-/// Returns the name of this atom.
-pub fn getName(self: Atom, macho_file: *MachO) []const u8 {
- const sym_index = self.getSymbolIndex().?;
- return macho_file.getSymbolName(.{ .sym_index = sym_index, .file = self.file });
+pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 {
+ return self.getInputSection(macho_file).addr + self.off;
}
-/// Returns how much room there is to grow in virtual address space.
-/// File offset relocation happens transparently, so it is not included in
-/// this calculation.
-pub fn capacity(self: Atom, macho_file: *MachO) u64 {
- const self_sym = self.getSymbol(macho_file);
- if (self.next_index) |next_index| {
- const next = macho_file.getAtom(next_index);
- const next_sym = next.getSymbol(macho_file);
- return next_sym.n_value - self_sym.n_value;
- } else {
- // We are the last atom.
- // The capacity is limited only by virtual address space.
- return macho_file.allocatedVirtualSize(self_sym.n_value);
- }
+pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
+ const file = self.getFile(macho_file);
+ return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect));
}
-pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
- // No need to keep a free list node for the last atom.
- const next_index = self.next_index orelse return false;
- const next = macho_file.getAtom(next_index);
- const self_sym = self.getSymbol(macho_file);
- const next_sym = next.getSymbol(macho_file);
- const cap = next_sym.n_value - self_sym.n_value;
- const ideal_cap = MachO.padToIdeal(self.size);
- if (cap <= ideal_cap) return false;
- const surplus = cap - ideal_cap;
- return surplus >= MachO.min_text_capacity;
+pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
+ return switch (self.getFile(macho_file)) {
+ .dylib => unreachable,
+ .zig_object, .internal => &[0]UnwindInfo.Record.Index{},
+ .object => |x| x.unwind_records.items[self.unwind_records.pos..][0..self.unwind_records.len],
+ };
}
-pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 {
- const segname = sect.segName();
- const sectname = sect.sectName();
- const res: ?u8 = blk: {
- if (mem.eql(u8, "__LLVM", segname)) {
- log.debug("TODO LLVM section: type 0x{x}, name '{s},{s}'", .{
- sect.flags, segname, sectname,
- });
- break :blk null;
- }
+pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
+ for (self.getUnwindRecords(macho_file)) |cu_index| {
+ const cu = macho_file.getUnwindRecord(cu_index);
+ cu.alive = false;
- // We handle unwind info separately.
- if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) {
- break :blk null;
- }
- if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) {
- break :blk null;
+ if (cu.getFdePtr(macho_file)) |fde| {
+ fde.alive = false;
}
+ }
+}
- if (sect.isCode()) {
- if (macho_file.text_section_index == null) {
- macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{
- .flags = macho.S_REGULAR |
- macho.S_ATTR_PURE_INSTRUCTIONS |
- macho.S_ATTR_SOME_INSTRUCTIONS,
- });
- }
- break :blk macho_file.text_section_index.?;
- }
+pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
+ return macho_file.getThunk(self.thunk_index);
+}
- if (sect.isDebug()) {
- break :blk null;
- }
+pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
+ const segname, const sectname, const flags = blk: {
+ if (sect.isCode()) break :blk .{
+ "__TEXT",
+ sect.sectName(),
+ macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
+ };
switch (sect.type()) {
macho.S_4BYTE_LITERALS,
macho.S_8BYTE_LITERALS,
macho.S_16BYTE_LITERALS,
- => {
- break :blk macho_file.getSectionByName("__TEXT", "__const") orelse
- try macho_file.initSection("__TEXT", "__const", .{});
- },
+ => break :blk .{ "__TEXT", "__const", macho.S_REGULAR },
+
macho.S_CSTRING_LITERALS => {
- if (mem.startsWith(u8, sectname, "__objc")) {
- break :blk macho_file.getSectionByName(segname, sectname) orelse
- try macho_file.initSection(segname, sectname, .{});
- }
- break :blk macho_file.getSectionByName("__TEXT", "__cstring") orelse
- try macho_file.initSection("__TEXT", "__cstring", .{
- .flags = macho.S_CSTRING_LITERALS,
- });
+ if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{
+ sect.segName(), sect.sectName(), macho.S_REGULAR,
+ };
+ break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS };
},
+
macho.S_MOD_INIT_FUNC_POINTERS,
macho.S_MOD_TERM_FUNC_POINTERS,
- => {
- break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
- try macho_file.initSection("__DATA_CONST", sectname, .{
- .flags = sect.flags,
- });
- },
+ => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags },
+
macho.S_LITERAL_POINTERS,
macho.S_ZEROFILL,
+ macho.S_GB_ZEROFILL,
macho.S_THREAD_LOCAL_VARIABLES,
macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
macho.S_THREAD_LOCAL_REGULAR,
macho.S_THREAD_LOCAL_ZEROFILL,
- => {
- break :blk macho_file.getSectionByName(segname, sectname) orelse
- try macho_file.initSection(segname, sectname, .{
- .flags = sect.flags,
- });
- },
- macho.S_COALESCED => {
- break :blk macho_file.getSectionByName(segname, sectname) orelse
- try macho_file.initSection(segname, sectname, .{});
+ => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
+
+ macho.S_COALESCED => break :blk .{
+ sect.segName(),
+ sect.sectName(),
+ macho.S_REGULAR,
},
+
macho.S_REGULAR => {
- if (mem.eql(u8, segname, "__TEXT")) {
- if (mem.eql(u8, sectname, "__rodata") or
- mem.eql(u8, sectname, "__typelink") or
- mem.eql(u8, sectname, "__itablink") or
- mem.eql(u8, sectname, "__gosymtab") or
- mem.eql(u8, sectname, "__gopclntab"))
- {
- break :blk macho_file.getSectionByName("__TEXT", sectname) orelse
- try macho_file.initSection("__TEXT", sectname, .{});
- }
- }
+ const segname = sect.segName();
+ const sectname = sect.sectName();
if (mem.eql(u8, segname, "__DATA")) {
if (mem.eql(u8, sectname, "__const") or
mem.eql(u8, sectname, "__cfstring") or
mem.eql(u8, sectname, "__objc_classlist") or
- mem.eql(u8, sectname, "__objc_imageinfo"))
- {
- break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
- try macho_file.initSection("__DATA_CONST", sectname, .{});
- } else if (mem.eql(u8, sectname, "__data")) {
- if (macho_file.data_section_index == null) {
- macho_file.data_section_index = try macho_file.initSection("__DATA", "__data", .{});
- }
- break :blk macho_file.data_section_index.?;
- }
+ mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{
+ "__DATA_CONST",
+ sectname,
+ macho.S_REGULAR,
+ };
}
- break :blk macho_file.getSectionByName(segname, sectname) orelse
- try macho_file.initSection(segname, sectname, .{});
+ break :blk .{ segname, sectname, sect.flags };
},
- else => break :blk null,
- }
- };
- // TODO we can do this directly in the selection logic above.
- // Or is it not worth it?
- if (macho_file.data_const_section_index == null) {
- if (macho_file.getSectionByName("__DATA_CONST", "__const")) |index| {
- macho_file.data_const_section_index = index;
- }
- }
- if (macho_file.thread_vars_section_index == null) {
- if (macho_file.getSectionByName("__DATA", "__thread_vars")) |index| {
- macho_file.thread_vars_section_index = index;
- }
- }
- if (macho_file.thread_data_section_index == null) {
- if (macho_file.getSectionByName("__DATA", "__thread_data")) |index| {
- macho_file.thread_data_section_index = index;
- }
- }
- if (macho_file.thread_bss_section_index == null) {
- if (macho_file.getSectionByName("__DATA", "__thread_bss")) |index| {
- macho_file.thread_bss_section_index = index;
+ else => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
}
+ };
+ const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(
+ segname,
+ sectname,
+ .{ .flags = flags },
+ );
+ if (mem.eql(u8, segname, "__TEXT") and mem.eql(u8, sectname, "__text")) {
+ macho_file.text_sect_index = osec;
}
- if (macho_file.bss_section_index == null) {
- if (macho_file.getSectionByName("__DATA", "__bss")) |index| {
- macho_file.bss_section_index = index;
- }
+ if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) {
+ macho_file.data_sect_index = osec;
}
+ return osec;
+}
- return res;
+/// Returns how much room there is to grow in virtual address space.
+/// File offset relocation happens transparently, so it is not included in
+/// this calculation.
+pub fn capacity(self: Atom, macho_file: *MachO) u64 {
+ const next_value = if (macho_file.getAtom(self.next_index)) |next| next.value else std.math.maxInt(u32);
+ return next_value - self.value;
}
-pub fn addRelocation(macho_file: *MachO, atom_index: Index, reloc: Relocation) !void {
- return addRelocations(macho_file, atom_index, &[_]Relocation{reloc});
+pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
+ // No need to keep a free list node for the last block.
+ const next = macho_file.getAtom(self.next_index) orelse return false;
+ const cap = next.value - self.value;
+ const ideal_cap = MachO.padToIdeal(self.size);
+ if (cap <= ideal_cap) return false;
+ const surplus = cap - ideal_cap;
+ return surplus >= MachO.min_text_capacity;
}
-pub fn addRelocations(macho_file: *MachO, atom_index: Index, relocs: []const Relocation) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const gop = try macho_file.relocs.getOrPut(gpa, atom_index);
- if (!gop.found_existing) {
- gop.value_ptr.* = .{};
- }
- try gop.value_ptr.ensureUnusedCapacity(gpa, relocs.len);
- for (relocs) |reloc| {
- log.debug(" (adding reloc of type {s} to target %{d})", .{
- @tagName(reloc.type),
- reloc.target.sym_index,
- });
- gop.value_ptr.appendAssumeCapacity(reloc);
+pub fn allocate(self: *Atom, macho_file: *MachO) !void {
+ const sect = &macho_file.sections.items(.header)[self.out_n_sect];
+ const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
+ const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
+ const new_atom_ideal_capacity = MachO.padToIdeal(self.size);
+
+ // We use these to indicate our intention to update metadata, placing the new atom,
+ // and possibly removing a free list node.
+ // It would be simpler to do it inside the for loop below, but that would cause a
+ // problem if an error was returned later in the function. So this action
+ // is actually carried out at the end of the function, when errors are no longer possible.
+ var atom_placement: ?Atom.Index = null;
+ var free_list_removal: ?usize = null;
+
+ // First we look for an appropriately sized free list node.
+ // The list is unordered. We'll just take the first thing that works.
+ self.value = blk: {
+ var i: usize = free_list.items.len;
+ while (i < free_list.items.len) {
+ const big_atom_index = free_list.items[i];
+ const big_atom = macho_file.getAtom(big_atom_index).?;
+ // We now have a pointer to a live atom that has too much capacity.
+ // Is it enough that we could fit this new atom?
+ const cap = big_atom.capacity(macho_file);
+ const ideal_capacity = MachO.padToIdeal(cap);
+ const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity;
+ const capacity_end_vaddr = big_atom.value + cap;
+ const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
+ const new_start_vaddr = self.alignment.backward(new_start_vaddr_unaligned);
+ if (new_start_vaddr < ideal_capacity_end_vaddr) {
+ // Additional bookkeeping here to notice if this free list node
+ // should be deleted because the block that it points to has grown to take up
+ // more of the extra capacity.
+ if (!big_atom.freeListEligible(macho_file)) {
+ _ = free_list.swapRemove(i);
+ } else {
+ i += 1;
+ }
+ continue;
+ }
+ // At this point we know that we will place the new block here. But the
+ // remaining question is whether there is still yet enough capacity left
+ // over for there to still be a free list node.
+ const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
+ const keep_free_list_node = remaining_capacity >= MachO.min_text_capacity;
+
+ // Set up the metadata to be updated, after errors are no longer possible.
+ atom_placement = big_atom_index;
+ if (!keep_free_list_node) {
+ free_list_removal = i;
+ }
+ break :blk new_start_vaddr;
+ } else if (macho_file.getAtom(last_atom_index.*)) |last| {
+ const ideal_capacity = MachO.padToIdeal(last.size);
+ const ideal_capacity_end_vaddr = last.value + ideal_capacity;
+ const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
+ // Set up the metadata to be updated, after errors are no longer possible.
+ atom_placement = last.atom_index;
+ break :blk new_start_vaddr;
+ } else {
+ break :blk sect.addr;
+ }
+ };
+
+ log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{
+ self.atom_index,
+ self.getName(macho_file),
+ self.value,
+ self.value + self.size,
+ });
+
+ const expand_section = if (atom_placement) |placement_index|
+ macho_file.getAtom(placement_index).?.next_index == 0
+ else
+ true;
+ if (expand_section) {
+ const needed_size = (self.value + self.size) - sect.addr;
+ try macho_file.growSection(self.out_n_sect, needed_size);
+ last_atom_index.* = self.atom_index;
+
+ // const zig_object = macho_file_file.getZigObject().?;
+ // if (zig_object.dwarf) |_| {
+ // // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
+ // // range of the compilation unit. When we expand the text section, this range changes,
+ // // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
+ // zig_object.debug_info_header_dirty = true;
+ // // This becomes dirty for the same reason. We could potentially make this more
+ // // fine-grained with the addition of support for more compilation units. It is planned to
+ // // model each package as a different compilation unit.
+ // zig_object.debug_aranges_section_dirty = true;
+ // }
}
-}
+ sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units());
-pub fn addRebase(macho_file: *MachO, atom_index: Index, offset: u32) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const atom = macho_file.getAtom(atom_index);
- log.debug(" (adding rebase at offset 0x{x} in %{?d})", .{ offset, atom.getSymbolIndex() });
- const gop = try macho_file.rebases.getOrPut(gpa, atom_index);
- if (!gop.found_existing) {
- gop.value_ptr.* = .{};
+ // This function can also reallocate an atom.
+ // In this case we need to "unplug" it from its previous location before
+ // plugging it in to its new location.
+ if (macho_file.getAtom(self.prev_index)) |prev| {
+ prev.next_index = self.next_index;
+ }
+ if (macho_file.getAtom(self.next_index)) |next| {
+ next.prev_index = self.prev_index;
}
- try gop.value_ptr.append(gpa, offset);
-}
-pub fn addBinding(macho_file: *MachO, atom_index: Index, binding: Binding) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const atom = macho_file.getAtom(atom_index);
- log.debug(" (adding binding to symbol {s} at offset 0x{x} in %{?d})", .{
- macho_file.getSymbolName(binding.target),
- binding.offset,
- atom.getSymbolIndex(),
- });
- const gop = try macho_file.bindings.getOrPut(gpa, atom_index);
- if (!gop.found_existing) {
- gop.value_ptr.* = .{};
+ if (atom_placement) |big_atom_index| {
+ const big_atom = macho_file.getAtom(big_atom_index).?;
+ self.prev_index = big_atom_index;
+ self.next_index = big_atom.next_index;
+ big_atom.next_index = self.atom_index;
+ } else {
+ self.prev_index = 0;
+ self.next_index = 0;
+ }
+ if (free_list_removal) |i| {
+ _ = free_list.swapRemove(i);
}
- try gop.value_ptr.append(gpa, binding);
+
+ self.flags.alive = true;
}
-pub fn resolveRelocations(
- macho_file: *MachO,
- atom_index: Index,
- relocs: []*const Relocation,
- code: []u8,
-) void {
- relocs_log.debug("relocating '{s}'", .{macho_file.getAtom(atom_index).getName(macho_file)});
- for (relocs) |reloc| {
- reloc.resolve(macho_file, atom_index, code);
- }
+pub fn shrink(self: *Atom, macho_file: *MachO) void {
+ _ = self;
+ _ = macho_file;
}
-pub fn freeRelocations(macho_file: *MachO, atom_index: Index) void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- var removed_relocs = macho_file.relocs.fetchOrderedRemove(atom_index);
- if (removed_relocs) |*relocs| relocs.value.deinit(gpa);
- var removed_rebases = macho_file.rebases.fetchOrderedRemove(atom_index);
- if (removed_rebases) |*rebases| rebases.value.deinit(gpa);
- var removed_bindings = macho_file.bindings.fetchOrderedRemove(atom_index);
- if (removed_bindings) |*bindings| bindings.value.deinit(gpa);
+pub fn grow(self: *Atom, macho_file: *MachO) !void {
+ if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file))
+ try self.allocate(macho_file);
}
-const InnerSymIterator = struct {
- sym_index: u32,
- nsyms: u32,
- file: u32,
- pos: u32 = 0,
+pub fn free(self: *Atom, macho_file: *MachO) void {
+ log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.getName(macho_file) });
- pub fn next(it: *@This()) ?SymbolWithLoc {
- if (it.pos == it.nsyms) return null;
- const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file };
- it.pos += 1;
- return res;
+ const comp = macho_file.base.comp;
+ const gpa = comp.gpa;
+ const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
+ const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
+ var already_have_free_list_node = false;
+ {
+ var i: usize = 0;
+ // TODO turn free_list into a hash map
+ while (i < free_list.items.len) {
+ if (free_list.items[i] == self.atom_index) {
+ _ = free_list.swapRemove(i);
+ continue;
+ }
+ if (free_list.items[i] == self.prev_index) {
+ already_have_free_list_node = true;
+ }
+ i += 1;
+ }
}
-};
-
-/// Returns an iterator over potentially contained symbols.
-/// Panics when called on a synthetic Atom.
-pub fn getInnerSymbolsIterator(macho_file: *MachO, atom_index: Index) InnerSymIterator {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null);
- return .{
- .sym_index = atom.inner_sym_index,
- .nsyms = atom.inner_nsyms_trailing,
- .file = atom.file,
- };
-}
-/// Returns a section alias symbol if one is defined.
-/// An alias symbol is used to represent the start of an input section
-/// if there were no symbols defined within that range.
-/// Alias symbols are only used on x86_64.
-pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null);
-
- const object = macho_file.objects.items[atom.getFile().?];
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const ntotal = @as(u32, @intCast(object.symtab.len));
- var sym_index: u32 = nbase;
- while (sym_index < ntotal) : (sym_index += 1) {
- if (object.getAtomIndexForSymbol(sym_index)) |other_atom_index| {
- if (other_atom_index == atom_index) return SymbolWithLoc{
- .sym_index = sym_index,
- .file = atom.file,
- };
+ if (macho_file.getAtom(last_atom_index.*)) |last_atom| {
+ if (last_atom.atom_index == self.atom_index) {
+ if (macho_file.getAtom(self.prev_index)) |_| {
+ // TODO shrink the section size here
+ last_atom_index.* = self.prev_index;
+ } else {
+ last_atom_index.* = 0;
+ }
}
}
- return null;
-}
-/// Given an index into a contained symbol within, calculates an offset wrt
-/// the start of this Atom.
-pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null);
-
- if (atom.sym_index == sym_index) return 0;
-
- const object = macho_file.objects.items[atom.getFile().?];
- const source_sym = object.getSourceSymbol(sym_index).?;
- const base_addr = if (object.getSourceSymbol(atom.sym_index)) |sym|
- sym.n_value
- else blk: {
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- const source_sect = object.getSourceSection(sect_id);
- break :blk source_sect.addr;
- };
- return source_sym.n_value - base_addr;
-}
+ if (macho_file.getAtom(self.prev_index)) |prev| {
+ prev.next_index = self.next_index;
+ if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) {
+ // The free list is heuristics, it doesn't have to be perfect, so we can
+ // ignore the OOM here.
+ free_list.append(gpa, prev.atom_index) catch {};
+ }
+ } else {
+ self.prev_index = 0;
+ }
-pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const arch = target.cpu.arch;
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null); // synthetic atoms do not have relocs
+ if (macho_file.getAtom(self.next_index)) |next| {
+ next.prev_index = self.prev_index;
+ } else {
+ self.next_index = 0;
+ }
- return switch (arch) {
- .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs),
- .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs),
- else => unreachable,
- };
+ // TODO create relocs free list
+ self.freeRelocs(macho_file);
+ // TODO figure out how to free input section mappind in ZigModule
+ // const zig_object = macho_file.zigObjectPtr().?
+ // assert(zig_object.atoms.swapRemove(self.atom_index));
+ self.* = .{};
}
-const RelocContext = struct {
- base_addr: i64 = 0,
- base_offset: i32 = 0,
-};
-
-pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null); // synthetic atoms do not have relocs
+pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const file = self.getFile(macho_file);
+ assert(file == .zig_object);
+ const rels = &file.zig_object.relocs.items[self.relocs.pos];
+ try rels.append(gpa, reloc);
+ self.relocs.len += 1;
+}
- const object = macho_file.objects.items[atom.getFile().?];
- if (object.getSourceSymbol(atom.sym_index)) |source_sym| {
- const source_sect = object.getSourceSection(source_sym.n_sect - 1);
- return .{
- .base_addr = @as(i64, @intCast(source_sect.addr)),
- .base_offset = @as(i32, @intCast(source_sym.n_value - source_sect.addr)),
- };
- }
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- const source_sect = object.getSourceSection(sect_id);
- return .{
- .base_addr = @as(i64, @intCast(source_sect.addr)),
- .base_offset = 0,
- };
+pub fn freeRelocs(self: *Atom, macho_file: *MachO) void {
+ self.getFile(macho_file).zig_object.freeAtomRelocs(self.*);
+ self.relocs.len = 0;
}
-pub fn parseRelocTarget(macho_file: *MachO, ctx: struct {
- object_id: u32,
- rel: macho.relocation_info,
- code: []const u8,
- base_addr: i64 = 0,
- base_offset: i32 = 0,
-}) SymbolWithLoc {
+pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
+ assert(self.flags.alive);
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const object = &macho_file.objects.items[ctx.object_id];
- log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name });
-
- const sym_index = if (ctx.rel.r_extern == 0) sym_index: {
- const sect_id = @as(u8, @intCast(ctx.rel.r_symbolnum - 1));
- const rel_offset = @as(u32, @intCast(ctx.rel.r_address - ctx.base_offset));
-
- const address_in_section = if (ctx.rel.r_pcrel == 0) blk: {
- break :blk if (ctx.rel.r_length == 3)
- mem.readInt(u64, ctx.code[rel_offset..][0..8], .little)
- else
- mem.readInt(u32, ctx.code[rel_offset..][0..4], .little);
- } else blk: {
- assert(target.cpu.arch == .x86_64);
- const correction: u3 = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.rel.r_type))) {
- .X86_64_RELOC_SIGNED => 0,
- .X86_64_RELOC_SIGNED_1 => 1,
- .X86_64_RELOC_SIGNED_2 => 2,
- .X86_64_RELOC_SIGNED_4 => 4,
- else => unreachable,
- };
- const addend = mem.readInt(i32, ctx.code[rel_offset..][0..4], .little);
- const target_address = @as(i64, @intCast(ctx.base_addr)) + ctx.rel.r_address + 4 + correction + addend;
- break :blk @as(u64, @intCast(target_address));
- };
-
- // Find containing atom
- log.debug(" | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id });
- break :sym_index object.getSymbolByAddress(address_in_section, sect_id);
- } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
-
- const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };
- const sym = macho_file.getSymbol(sym_loc);
- const reloc_target = if (sym.sect() and !sym.ext())
- sym_loc
- else if (object.getGlobal(sym_index)) |global_index|
- macho_file.globals.items[global_index]
- else
- sym_loc;
- log.debug(" | target %{d} ('{s}') in object({?d})", .{
- reloc_target.sym_index,
- macho_file.getSymbolName(reloc_target),
- reloc_target.getFile(),
- });
- return reloc_target;
-}
-
-pub fn getRelocTargetAtomIndex(macho_file: *MachO, target: SymbolWithLoc) ?Index {
- if (target.getFile() == null) {
- const target_sym_name = macho_file.getSymbolName(target);
- if (mem.eql(u8, "__mh_execute_header", target_sym_name)) return null;
- if (mem.eql(u8, "___dso_handle", target_sym_name)) return null;
-
- unreachable; // referenced symbol not found
- }
-
- const object = macho_file.objects.items[target.getFile().?];
- return object.getAtomIndexForSymbol(target.sym_index);
-}
+ const dynrel_ctx = switch (self.getFile(macho_file)) {
+ .zig_object => |x| &x.dynamic_relocs,
+ .object => |x| &x.dynamic_relocs,
+ else => unreachable,
+ };
+ const relocs = self.getRelocs(macho_file);
-fn scanAtomRelocsArm64(
- macho_file: *MachO,
- atom_index: Index,
- relocs: []align(1) const macho.relocation_info,
-) !void {
for (relocs) |rel| {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
-
- switch (rel_type) {
- .ARM64_RELOC_ADDEND, .ARM64_RELOC_SUBTRACTOR => continue,
- else => {},
- }
-
- if (rel.r_extern == 0) continue;
+ if (try self.reportUndefSymbol(rel, macho_file)) continue;
+
+ 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) {
+ symbol.flags.stubs = true;
+ if (symbol.flags.weak) {
+ macho_file.binds_to_weak = true;
+ }
+ } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) {
+ symbol.flags.objc_stubs = true;
+ }
+ },
- const atom = macho_file.getAtom(atom_index);
- const object = &macho_file.objects.items[atom.getFile().?];
- const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
- const sym_loc = SymbolWithLoc{
- .sym_index = sym_index,
- .file = atom.file,
- };
+ .got_load,
+ .got_load_page,
+ .got_load_pageoff,
+ => {
+ const symbol = rel.getTargetSymbol(macho_file);
+ if (symbol.flags.import or
+ (symbol.flags.@"export" and symbol.flags.weak) or
+ symbol.flags.interposable or
+ macho_file.getTarget().cpu.arch == .aarch64) // TODO relax on arm64
+ {
+ symbol.flags.needs_got = true;
+ if (symbol.flags.weak) {
+ macho_file.binds_to_weak = true;
+ }
+ }
+ },
- const target = if (object.getGlobal(sym_index)) |global_index|
- macho_file.globals.items[global_index]
- else
- sym_loc;
+ .zig_got_load => {
+ assert(rel.getTargetSymbol(macho_file).flags.has_zig_got);
+ },
- switch (rel_type) {
- .ARM64_RELOC_BRANCH26 => {
- // TODO rewrite relocation
- const sym = macho_file.getSymbol(target);
- if (sym.undf()) try macho_file.addStubEntry(target);
+ .got => {
+ rel.getTargetSymbol(macho_file).flags.needs_got = true;
},
- .ARM64_RELOC_GOT_LOAD_PAGE21,
- .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
- .ARM64_RELOC_POINTER_TO_GOT,
+
+ .tlv,
+ .tlvp_page,
+ .tlvp_pageoff,
=> {
- // TODO rewrite relocation
- try macho_file.addGotEntry(target);
+ const symbol = rel.getTargetSymbol(macho_file);
+ if (!symbol.flags.tlv) {
+ try macho_file.reportParseError2(
+ self.getFile(macho_file).getIndex(),
+ "{s}: illegal thread-local variable reference to regular symbol {s}",
+ .{ self.getName(macho_file), symbol.getName(macho_file) },
+ );
+ }
+ 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;
+ }
+ }
},
- .ARM64_RELOC_TLVP_LOAD_PAGE21,
- .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
- => {
- const sym = macho_file.getSymbol(target);
- if (sym.undf()) try macho_file.addTlvPtrEntry(target);
+
+ .unsigned => {
+ if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
+ if (rel.tag == .@"extern") {
+ const symbol = rel.getTargetSymbol(macho_file);
+ if (symbol.isTlvInit(macho_file)) {
+ macho_file.has_tlv = true;
+ continue;
+ }
+ if (symbol.flags.import) {
+ dynrel_ctx.bind_relocs += 1;
+ if (symbol.flags.weak) {
+ dynrel_ctx.weak_bind_relocs += 1;
+ macho_file.binds_to_weak = true;
+ }
+ continue;
+ }
+ if (symbol.flags.@"export" and symbol.flags.weak) {
+ dynrel_ctx.weak_bind_relocs += 1;
+ macho_file.binds_to_weak = true;
+ } else if (symbol.flags.interposable) {
+ dynrel_ctx.bind_relocs += 1;
+ }
+ }
+ dynrel_ctx.rebase_relocs += 1;
+ }
},
- else => {},
+
+ .signed,
+ .signed1,
+ .signed2,
+ .signed4,
+ .page,
+ .pageoff,
+ .subtractor,
+ => {},
}
}
}
-fn scanAtomRelocsX86(
- macho_file: *MachO,
- atom_index: Index,
- relocs: []align(1) const macho.relocation_info,
-) !void {
- for (relocs) |rel| {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
+fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
+ if (rel.tag == .local) return false;
- switch (rel_type) {
- .X86_64_RELOC_SUBTRACTOR => continue,
- else => {},
+ const sym = rel.getTargetSymbol(macho_file);
+ if (sym.getFile(macho_file) == null) {
+ const gpa = macho_file.base.comp.gpa;
+ const gop = try macho_file.undefs.getOrPut(gpa, rel.target);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
}
+ try gop.value_ptr.append(gpa, self.atom_index);
+ return true;
+ }
- if (rel.r_extern == 0) continue;
+ return false;
+}
- const atom = macho_file.getAtom(atom_index);
- const object = &macho_file.objects.items[atom.getFile().?];
- const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
- const sym_loc = SymbolWithLoc{
- .sym_index = sym_index,
- .file = atom.file,
- };
+pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- const target = if (object.getGlobal(sym_index)) |global_index|
- macho_file.globals.items[global_index]
- else
- sym_loc;
+ assert(!self.getInputSection(macho_file).isZerofill());
+ const file = self.getFile(macho_file);
+ const name = self.getName(macho_file);
+ const relocs = self.getRelocs(macho_file);
- switch (rel_type) {
- .X86_64_RELOC_BRANCH => {
- // TODO rewrite relocation
- const sym = macho_file.getSymbol(target);
- if (sym.undf()) try macho_file.addStubEntry(target);
- },
- .X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => {
- // TODO rewrite relocation
- try macho_file.addGotEntry(target);
- },
- .X86_64_RELOC_TLV => {
- const sym = macho_file.getSymbol(target);
- if (sym.undf()) try macho_file.addTlvPtrEntry(target);
- },
- else => {},
- }
- }
-}
+ relocs_log.debug("{x}: {s}", .{ self.value, name });
-pub fn resolveRelocs(
- macho_file: *MachO,
- atom_index: Index,
- atom_code: []u8,
- atom_relocs: []align(1) const macho.relocation_info,
-) !void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const arch = target.cpu.arch;
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null); // synthetic atoms do not have relocs
-
- relocs_log.debug("resolving relocations in ATOM(%{d}, '{s}')", .{
- atom.sym_index,
- macho_file.getSymbolName(atom.getSymbolWithLoc()),
- });
+ var stream = std.io.fixedBufferStream(buffer);
- const ctx = getRelocContext(macho_file, atom_index);
-
- return switch (arch) {
- .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx),
- .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx),
- else => unreachable,
- };
-}
+ var i: usize = 0;
+ while (i < relocs.len) : (i += 1) {
+ const rel = relocs[i];
+ const rel_offset = rel.offset - self.off;
+ const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
-pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) u64 {
- const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse {
- // If there is no atom for target, we still need to check for special, atom-less
- // symbols such as `___dso_handle`.
- const target_name = macho_file.getSymbolName(target);
- const atomless_sym = macho_file.getSymbol(target);
- log.debug(" | atomless target '{s}'", .{target_name});
- return atomless_sym.n_value;
- };
- const target_atom = macho_file.getAtom(target_atom_index);
- log.debug(" | target ATOM(%{d}, '{s}') in object({?})", .{
- target_atom.sym_index,
- macho_file.getSymbolName(target_atom.getSymbolWithLoc()),
- target_atom.getFile(),
- });
+ if (rel.tag == .@"extern") {
+ if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue;
+ }
- const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
- assert(target_sym.n_desc != MachO.N_DEAD);
-
- // If `target` is contained within the target atom, pull its address value.
- const offset = if (target_atom.getFile() != null) blk: {
- const object = macho_file.objects.items[target_atom.getFile().?];
- break :blk if (object.getSourceSymbol(target.sym_index)) |_|
- Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index)
- else
- 0; // section alias
- } else 0;
- const base_address: u64 = if (is_tlv) base_address: {
- // For TLV relocations, the value specified as a relocation is the displacement from the
- // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first
- // defined TLV template init section in the following order:
- // * wrt to __thread_data if defined, then
- // * wrt to __thread_bss
- // TODO remember to check what the mechanism was prior to HAS_TLV_INITIALIZERS in earlier versions of macOS
- const sect_id: u16 = sect_id: {
- if (macho_file.thread_data_section_index) |i| {
- break :sect_id i;
- } else if (macho_file.thread_bss_section_index) |i| {
- break :sect_id i;
- } else break :base_address 0;
+ try stream.seekTo(rel_offset);
+ self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| {
+ switch (err) {
+ error.RelaxFail => {
+ try macho_file.reportParseError2(
+ file.getIndex(),
+ "{s}: 0x{x}: failed to relax relocation: in {s}",
+ .{ name, rel.offset, @tagName(rel.type) },
+ );
+ return error.ResolveFailed;
+ },
+ else => |e| return e,
+ }
};
- break :base_address macho_file.sections.items(.header)[sect_id].addr;
- } else 0;
- return target_sym.n_value + offset - base_address;
+ }
}
-fn resolveRelocsArm64(
- macho_file: *MachO,
- atom_index: Index,
- atom_code: []u8,
- atom_relocs: []align(1) const macho.relocation_info,
- context: RelocContext,
-) !void {
- const atom = macho_file.getAtom(atom_index);
- const object = macho_file.objects.items[atom.getFile().?];
-
- var addend: ?i64 = null;
- var subtractor: ?SymbolWithLoc = null;
+const ResolveError = error{
+ RelaxFail,
+ NoSpaceLeft,
+ DivisionByZero,
+ UnexpectedRemainder,
+ Overflow,
+};
- for (atom_relocs) |rel| {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
+fn resolveRelocInner(
+ self: Atom,
+ rel: Relocation,
+ subtractor: ?Relocation,
+ code: []u8,
+ macho_file: *MachO,
+ writer: anytype,
+) ResolveError!void {
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
+ const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
+ const seg = macho_file.segments.items[seg_id];
+ const P = @as(i64, @intCast(self.value)) + @as(i64, @intCast(rel_offset));
+ const A = rel.addend + rel.getRelocAddend(cpu_arch);
+ const S: i64 = @intCast(rel.getTargetAddress(macho_file));
+ const G: i64 = @intCast(rel.getGotTargetAddress(macho_file));
+ const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
+ const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0;
+ // Address of the __got_zig table entry if any.
+ const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file)));
+
+ switch (rel.tag) {
+ .local => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] atom({d})", .{
+ P,
+ rel_offset,
+ @tagName(rel.type),
+ S + A - SUB,
+ rel.getTargetAtom(macho_file).atom_index,
+ }),
+ .@"extern" => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] G({x}) ZG({x}) ({s})", .{
+ P,
+ rel_offset,
+ @tagName(rel.type),
+ S + A - SUB,
+ G + A,
+ ZIG_GOT + A,
+ rel.getTargetSymbol(macho_file).getName(macho_file),
+ }),
+ }
- switch (rel_type) {
- .ARM64_RELOC_ADDEND => {
- assert(addend == null);
+ switch (rel.type) {
+ .subtractor => {},
+
+ .unsigned => {
+ assert(!rel.meta.pcrel);
+ if (rel.meta.length == 3) {
+ if (rel.tag == .@"extern") {
+ const sym = rel.getTargetSymbol(macho_file);
+ if (sym.isTlvInit(macho_file)) {
+ try writer.writeInt(u64, @intCast(S - TLS), .little);
+ return;
+ }
+ const entry = bind.Entry{
+ .target = rel.target,
+ .offset = @as(u64, @intCast(P)) - seg.vmaddr,
+ .segment_id = seg_id,
+ .addend = A,
+ };
+ if (sym.flags.import) {
+ macho_file.bind.entries.appendAssumeCapacity(entry);
+ if (sym.flags.weak) {
+ macho_file.weak_bind.entries.appendAssumeCapacity(entry);
+ }
+ return;
+ }
+ 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(.{
+ .offset = @as(u64, @intCast(P)) - seg.vmaddr,
+ .segment_id = seg_id,
+ });
+ try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
+ } else if (rel.meta.length == 2) {
+ try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
+ } else unreachable;
+ },
- relocs_log.debug(" RELA({s}) @ {x} => {x}", .{ @tagName(rel_type), rel.r_address, rel.r_symbolnum });
+ .got => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ try writer.writeInt(i32, @intCast(G + A - P), .little);
+ },
- addend = rel.r_symbolnum;
- continue;
- },
- .ARM64_RELOC_SUBTRACTOR => {
- assert(subtractor == null);
-
- relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{
- @tagName(rel_type),
- rel.r_address,
- rel.r_symbolnum,
- atom.getFile(),
- });
+ .branch => {
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ assert(rel.tag == .@"extern");
+
+ switch (cpu_arch) {
+ .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little),
+ .aarch64 => {
+ const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
+ const thunk = self.getThunk(macho_file);
+ const S_: i64 = @intCast(thunk.getAddress(rel.target));
+ break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
+ };
+ var inst = aarch64.Instruction{
+ .unconditional_branch_immediate = mem.bytesToValue(std.meta.TagPayload(
+ aarch64.Instruction,
+ aarch64.Instruction.unconditional_branch_immediate,
+ ), code[rel_offset..][0..4]),
+ };
+ inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(disp >> 2))));
+ try writer.writeInt(u32, inst.toU32(), .little);
+ },
+ else => unreachable,
+ }
+ },
- subtractor = parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = atom_code,
- .base_addr = context.base_addr,
- .base_offset = context.base_offset,
- });
- continue;
- },
- else => {},
- }
+ .got_load => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ if (rel.getTargetSymbol(macho_file).flags.has_got) {
+ try writer.writeInt(i32, @intCast(G + A - P), .little);
+ } else {
+ try x86_64.relaxGotLoad(code[rel_offset - 3 ..]);
+ try writer.writeInt(i32, @intCast(S + A - P), .little);
+ }
+ },
- const target = parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = atom_code,
- .base_addr = context.base_addr,
- .base_offset = context.base_offset,
- });
- const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset));
-
- relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{
- @tagName(rel_type),
- rel.r_address,
- target.sym_index,
- macho_file.getSymbolName(target),
- target.getFile(),
- });
-
- const source_addr = blk: {
- const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- break :blk source_sym.n_value + rel_offset;
- };
- const target_addr = blk: {
- if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
- if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
- break :blk macho_file.getTlvPtrEntryAddress(target).?;
- if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
- break :blk macho_file.getStubsEntryAddress(target).?;
- const is_tlv = is_tlv: {
- const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
- break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
- };
- break :blk getRelocTargetAddress(macho_file, target, is_tlv);
- };
+ .zig_got_load => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ switch (cpu_arch) {
+ .x86_64 => try writer.writeInt(i32, @intCast(ZIG_GOT + A - P), .little),
+ .aarch64 => @panic("TODO resolve __got_zig indirection reloc"),
+ else => unreachable,
+ }
+ },
- relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});
+ .tlv => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ const sym = rel.getTargetSymbol(macho_file);
+ if (sym.flags.tlv_ptr) {
+ const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
+ try writer.writeInt(i32, @intCast(S_ + A - P), .little);
+ } else {
+ try x86_64.relaxTlv(code[rel_offset - 3 ..]);
+ try writer.writeInt(i32, @intCast(S + A - P), .little);
+ }
+ },
- switch (rel_type) {
- .ARM64_RELOC_BRANCH26 => {
- relocs_log.debug(" source {s} (object({?})), target {s}", .{
- macho_file.getSymbolName(atom.getSymbolWithLoc()),
- atom.getFile(),
- macho_file.getSymbolName(target),
- });
+ .signed, .signed1, .signed2, .signed4 => {
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ try writer.writeInt(i32, @intCast(S + A - P), .little);
+ },
- const displacement = if (Relocation.calcPcRelativeDisplacementArm64(
- source_addr,
- target_addr,
- )) |disp| blk: {
- relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});
- break :blk disp;
- } else |_| blk: {
- const thunk_index = macho_file.thunk_table.get(atom_index).?;
- const thunk = macho_file.thunks.items[thunk_index];
- const thunk_sym_loc = if (macho_file.getSymbol(target).undf())
- thunk.getTrampoline(macho_file, .stub, target).?
- else
- thunk.getTrampoline(macho_file, .atom, target).?;
- const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value;
- relocs_log.debug(" | target_addr = 0x{x} (thunk)", .{thunk_addr});
- break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr);
+ .page,
+ .got_load_page,
+ .tlvp_page,
+ => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(rel.meta.pcrel);
+ const sym = rel.getTargetSymbol(macho_file);
+ const source = math.cast(u64, P) orelse return error.Overflow;
+ const target = target: {
+ const target = switch (rel.type) {
+ .page => S + A,
+ .got_load_page => G + A,
+ .tlvp_page => if (sym.flags.tlv_ptr) blk: {
+ const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
+ break :blk S_ + A;
+ } else S + A,
+ else => unreachable,
};
+ break :target math.cast(u64, target) orelse return error.Overflow;
+ };
+ const pages = @as(u21, @bitCast(try Relocation.calcNumberOfPages(source, target)));
+ var inst = aarch64.Instruction{
+ .pc_relative_address = mem.bytesToValue(std.meta.TagPayload(
+ aarch64.Instruction,
+ aarch64.Instruction.pc_relative_address,
+ ), code[rel_offset..][0..4]),
+ };
+ inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
+ inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
+ try writer.writeInt(u32, inst.toU32(), .little);
+ },
- const code = atom_code[rel_offset..][0..4];
+ .pageoff => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(!rel.meta.pcrel);
+ const target = math.cast(u64, S + A) orelse return error.Overflow;
+ const inst_code = code[rel_offset..][0..4];
+ if (Relocation.isArithmeticOp(inst_code)) {
+ const off = try Relocation.calcPageOffset(target, .arithmetic);
var inst = aarch64.Instruction{
- .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
+ .add_subtract_immediate = mem.bytesToValue(std.meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.unconditional_branch_immediate,
- ), code),
+ aarch64.Instruction.add_subtract_immediate,
+ ), inst_code),
};
- inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
- mem.writeInt(u32, code, inst.toU32(), .little);
- },
-
- .ARM64_RELOC_PAGE21,
- .ARM64_RELOC_GOT_LOAD_PAGE21,
- .ARM64_RELOC_TLVP_LOAD_PAGE21,
- => {
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
-
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
-
- const pages = @as(u21, @bitCast(Relocation.calcNumberOfPages(source_addr, adjusted_target_addr)));
- const code = atom_code[rel_offset..][0..4];
+ inst.add_subtract_immediate.imm12 = off;
+ try writer.writeInt(u32, inst.toU32(), .little);
+ } else {
var inst = aarch64.Instruction{
- .pc_relative_address = mem.bytesToValue(meta.TagPayload(
+ .load_store_register = mem.bytesToValue(std.meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.pc_relative_address,
- ), code),
+ aarch64.Instruction.load_store_register,
+ ), inst_code),
};
- inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
- inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
- mem.writeInt(u32, code, inst.toU32(), .little);
- addend = null;
- },
+ const off = try Relocation.calcPageOffset(target, switch (inst.load_store_register.size) {
+ 0 => if (inst.load_store_register.v == 1)
+ Relocation.PageOffsetInstKind.load_store_128
+ else
+ Relocation.PageOffsetInstKind.load_store_8,
+ 1 => .load_store_16,
+ 2 => .load_store_32,
+ 3 => .load_store_64,
+ });
+ inst.load_store_register.offset = off;
+ try writer.writeInt(u32, inst.toU32(), .little);
+ }
+ },
- .ARM64_RELOC_PAGEOFF12 => {
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
+ .got_load_pageoff => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(!rel.meta.pcrel);
+ const target = math.cast(u64, G + A) orelse return error.Overflow;
+ const off = try Relocation.calcPageOffset(target, .load_store_64);
+ var inst: aarch64.Instruction = .{
+ .load_store_register = mem.bytesToValue(std.meta.TagPayload(
+ aarch64.Instruction,
+ aarch64.Instruction.load_store_register,
+ ), code[rel_offset..][0..4]),
+ };
+ inst.load_store_register.offset = off;
+ try writer.writeInt(u32, inst.toU32(), .little);
+ },
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
+ .tlvp_pageoff => {
+ assert(rel.tag == .@"extern");
+ assert(rel.meta.length == 2);
+ assert(!rel.meta.pcrel);
+
+ const sym = rel.getTargetSymbol(macho_file);
+ const target = target: {
+ const target = if (sym.flags.tlv_ptr) blk: {
+ const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
+ break :blk S_ + A;
+ } else S + A;
+ break :target math.cast(u64, target) orelse return error.Overflow;
+ };
- const code = atom_code[rel_offset..][0..4];
- if (Relocation.isArithmeticOp(code)) {
- const off = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic);
- var inst = aarch64.Instruction{
- .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.add_subtract_immediate,
- ), code),
+ const RegInfo = struct {
+ rd: u5,
+ rn: u5,
+ size: u2,
+ };
+
+ const inst_code = code[rel_offset..][0..4];
+ const reg_info: RegInfo = blk: {
+ if (Relocation.isArithmeticOp(inst_code)) {
+ const inst = mem.bytesToValue(std.meta.TagPayload(
+ aarch64.Instruction,
+ aarch64.Instruction.add_subtract_immediate,
+ ), inst_code);
+ break :blk .{
+ .rd = inst.rd,
+ .rn = inst.rn,
+ .size = inst.sf,
};
- inst.add_subtract_immediate.imm12 = off;
- mem.writeInt(u32, code, inst.toU32(), .little);
} else {
- var inst = aarch64.Instruction{
- .load_store_register = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.load_store_register,
- ), code),
- };
- const off = try Relocation.calcPageOffset(adjusted_target_addr, switch (inst.load_store_register.size) {
- 0 => if (inst.load_store_register.v == 1)
- Relocation.PageOffsetInstKind.load_store_128
- else
- Relocation.PageOffsetInstKind.load_store_8,
- 1 => .load_store_16,
- 2 => .load_store_32,
- 3 => .load_store_64,
- });
- inst.load_store_register.offset = off;
- mem.writeInt(u32, code, inst.toU32(), .little);
- }
- addend = null;
- },
-
- .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
- const code = atom_code[rel_offset..][0..4];
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
-
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
-
- const off = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64);
- var inst: aarch64.Instruction = .{
- .load_store_register = mem.bytesToValue(meta.TagPayload(
+ const inst = mem.bytesToValue(std.meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
- ), code),
- };
- inst.load_store_register.offset = off;
- mem.writeInt(u32, code, inst.toU32(), .little);
- addend = null;
- },
-
- .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
- const code = atom_code[rel_offset..][0..4];
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
-
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
+ ), inst_code);
+ break :blk .{
+ .rd = inst.rt,
+ .rn = inst.rn,
+ .size = inst.size,
+ };
+ }
+ };
- const RegInfo = struct {
- rd: u5,
- rn: u5,
- size: u2,
- };
- const reg_info: RegInfo = blk: {
- if (Relocation.isArithmeticOp(code)) {
- const inst = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.add_subtract_immediate,
- ), code);
- break :blk .{
- .rd = inst.rd,
- .rn = inst.rn,
- .size = inst.sf,
- };
- } else {
- const inst = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.load_store_register,
- ), code);
- break :blk .{
- .rd = inst.rt,
- .rn = inst.rn,
- .size = inst.size,
- };
- }
- };
+ var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{
+ .load_store_register = .{
+ .rt = reg_info.rd,
+ .rn = reg_info.rn,
+ .offset = try Relocation.calcPageOffset(target, .load_store_64),
+ .opc = 0b01,
+ .op1 = 0b01,
+ .v = 0,
+ .size = reg_info.size,
+ },
+ } else aarch64.Instruction{
+ .add_subtract_immediate = .{
+ .rd = reg_info.rd,
+ .rn = reg_info.rn,
+ .imm12 = try Relocation.calcPageOffset(target, .arithmetic),
+ .sh = 0,
+ .s = 0,
+ .op = 0,
+ .sf = @as(u1, @truncate(reg_info.size)),
+ },
+ };
+ try writer.writeInt(u32, inst.toU32(), .little);
+ },
+ }
+}
- var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{
- .load_store_register = .{
- .rt = reg_info.rd,
- .rn = reg_info.rn,
- .offset = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64),
- .opc = 0b01,
- .op1 = 0b01,
- .v = 0,
- .size = reg_info.size,
- },
- } else aarch64.Instruction{
- .add_subtract_immediate = .{
- .rd = reg_info.rd,
- .rn = reg_info.rn,
- .imm12 = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic),
- .sh = 0,
- .s = 0,
- .op = 0,
- .sf = @as(u1, @truncate(reg_info.size)),
- },
- };
- mem.writeInt(u32, code, inst.toU32(), .little);
- addend = null;
+const x86_64 = struct {
+ fn relaxGotLoad(code: []u8) error{RelaxFail}!void {
+ const old_inst = disassemble(code) orelse return error.RelaxFail;
+ switch (old_inst.encoding.mnemonic) {
+ .mov => {
+ const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
+ relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
+ encode(&.{inst}, code) catch return error.RelaxFail;
},
+ else => return error.RelaxFail,
+ }
+ }
- .ARM64_RELOC_POINTER_TO_GOT => {
- relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});
- const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
- return error.Overflow;
- mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @bitCast(result)), .little);
+ fn relaxTlv(code: []u8) error{RelaxFail}!void {
+ const old_inst = disassemble(code) orelse return error.RelaxFail;
+ switch (old_inst.encoding.mnemonic) {
+ .mov => {
+ const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
+ relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
+ encode(&.{inst}, code) catch return error.RelaxFail;
},
+ else => return error.RelaxFail,
+ }
+ }
- .ARM64_RELOC_UNSIGNED => {
- var ptr_addend = if (rel.r_length == 3)
- mem.readInt(i64, atom_code[rel_offset..][0..8], .little)
- else
- mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
+ fn disassemble(code: []const u8) ?Instruction {
+ var disas = Disassembler.init(code);
+ const inst = disas.next() catch return null;
+ return inst;
+ }
- if (rel.r_extern == 0) {
- const base_addr = if (target.sym_index >= object.source_address_lookup.len)
- @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
- else
- object.source_address_lookup[target.sym_index];
- ptr_addend -= base_addr;
- }
+ fn encode(insts: []const Instruction, code: []u8) !void {
+ var stream = std.io.fixedBufferStream(code);
+ const writer = stream.writer();
+ for (insts) |inst| {
+ try inst.encode(writer, .{});
+ }
+ }
- const result = blk: {
- if (subtractor) |sub| {
- const sym = macho_file.getSymbol(sub);
- break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend;
- } else {
- break :blk @as(i64, @intCast(target_addr)) + ptr_addend;
- }
- };
- relocs_log.debug(" | target_addr = 0x{x}", .{result});
+ const bits = @import("../../arch/x86_64/bits.zig");
+ const encoder = @import("../../arch/x86_64/encoder.zig");
+ const Disassembler = @import("../../arch/x86_64/Disassembler.zig");
+ const Immediate = bits.Immediate;
+ const Instruction = encoder.Instruction;
+};
- if (rel.r_length == 3) {
- mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);
- } else {
- mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little);
+pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
+ const relocs = self.getRelocs(macho_file);
+ switch (macho_file.getTarget().cpu.arch) {
+ .aarch64 => {
+ var nreloc: u32 = 0;
+ for (relocs) |rel| {
+ nreloc += 1;
+ switch (rel.type) {
+ .page, .pageoff => if (rel.addend > 0) {
+ nreloc += 1;
+ },
+ else => {},
}
-
- subtractor = null;
- },
-
- .ARM64_RELOC_ADDEND => unreachable,
- .ARM64_RELOC_SUBTRACTOR => unreachable,
- }
+ }
+ return nreloc;
+ },
+ .x86_64 => return @intCast(relocs.len),
+ else => unreachable,
}
}
-fn resolveRelocsX86(
- macho_file: *MachO,
- atom_index: Index,
- atom_code: []u8,
- atom_relocs: []align(1) const macho.relocation_info,
- context: RelocContext,
-) !void {
- const atom = macho_file.getAtom(atom_index);
- const object = macho_file.objects.items[atom.getFile().?];
-
- var subtractor: ?SymbolWithLoc = null;
-
- for (atom_relocs) |rel| {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
-
- switch (rel_type) {
- .X86_64_RELOC_SUBTRACTOR => {
- assert(subtractor == null);
+pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{
- @tagName(rel_type),
- rel.r_address,
- rel.r_symbolnum,
- atom.getFile(),
- });
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const relocs = self.getRelocs(macho_file);
+ const sect = macho_file.sections.items(.header)[self.out_n_sect];
+ var stream = std.io.fixedBufferStream(code);
- subtractor = parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = atom_code,
- .base_addr = context.base_addr,
- .base_offset = context.base_offset,
- });
- continue;
- },
- else => {},
- }
-
- const target = parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = atom_code,
- .base_addr = context.base_addr,
- .base_offset = context.base_offset,
- });
- const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset));
-
- relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{
- @tagName(rel_type),
- rel.r_address,
- target.sym_index,
- macho_file.getSymbolName(target),
- target.getFile(),
- });
-
- const source_addr = blk: {
- const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- break :blk source_sym.n_value + rel_offset;
- };
- const target_addr = blk: {
- if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
- if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
- break :blk macho_file.getStubsEntryAddress(target).?;
- if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
- break :blk macho_file.getTlvPtrEntryAddress(target).?;
- const is_tlv = is_tlv: {
- const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
- break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
+ 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 :blk getRelocTargetAddress(macho_file, target, is_tlv);
+ 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;
+ }
- relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});
-
- switch (rel_type) {
- .X86_64_RELOC_BRANCH => {
- const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
- const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
- mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
- },
-
- .X86_64_RELOC_GOT,
- .X86_64_RELOC_GOT_LOAD,
- => {
- const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
- const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
- mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
- },
-
- .X86_64_RELOC_TLV => {
- const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
- const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
-
- if (macho_file.tlv_ptr_table.lookup.get(target) == null) {
- // We need to rewrite the opcode from movq to leaq.
- atom_code[rel_offset - 2] = 0x8d;
+ 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),
+ });
}
- mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
- },
-
- .X86_64_RELOC_SIGNED,
- .X86_64_RELOC_SIGNED_1,
- .X86_64_RELOC_SIGNED_2,
- .X86_64_RELOC_SIGNED_4,
- => {
- const correction: u3 = switch (rel_type) {
- .X86_64_RELOC_SIGNED => 0,
- .X86_64_RELOC_SIGNED_1 => 1,
- .X86_64_RELOC_SIGNED_2 => 2,
- .X86_64_RELOC_SIGNED_4 => 4,
- else => unreachable,
+ 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,
+
+ .zig_got_load,
+ .signed,
+ .signed1,
+ .signed2,
+ .signed4,
+ .got_load,
+ .tlv,
+ => unreachable,
};
- var addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little) + correction;
-
- if (rel.r_extern == 0) {
- const base_addr = if (target.sym_index >= object.source_address_lookup.len)
- @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
- else
- object.source_address_lookup[target.sym_index];
- addend += @as(i32, @intCast(@as(i64, @intCast(context.base_addr)) + rel.r_address + 4 -
- @as(i64, @intCast(base_addr))));
- }
-
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
-
- relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
-
- const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, correction);
- mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
+ 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_RELOC_UNSIGNED => {
- var addend = if (rel.r_length == 3)
- mem.readInt(i64, atom_code[rel_offset..][0..8], .little)
- else
- mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
-
- if (rel.r_extern == 0) {
- const base_addr = if (target.sym_index >= object.source_address_lookup.len)
- @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
- else
- object.source_address_lookup[target.sym_index];
- addend -= base_addr;
- }
-
- const result = blk: {
- if (subtractor) |sub| {
- const sym = macho_file.getSymbol(sub);
- break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + addend;
+ .x86_64 => {
+ if (rel.meta.pcrel) {
+ if (rel.tag == .local) {
+ addend -= @as(i64, @intCast(self.value + rel_offset));
} else {
- break :blk @as(i64, @intCast(target_addr)) + addend;
+ addend += 4;
}
- };
- relocs_log.debug(" | target_addr = 0x{x}", .{result});
-
- if (rel.r_length == 3) {
- mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);
- } else {
- mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little);
+ }
+ switch (rel.meta.length) {
+ 0, 1 => unreachable,
+ 2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
+ 3 => try stream.writer().writeInt(i64, addend, .little),
}
- subtractor = null;
+ 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,
+
+ .zig_got_load,
+ .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),
+ });
},
-
- .X86_64_RELOC_SUBTRACTOR => unreachable,
+ else => unreachable,
}
}
}
-pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code.
- const object = macho_file.objects.items[atom.getFile().?];
- const source_sym = object.getSourceSymbol(atom.sym_index) orelse {
- // If there was no matching symbol present in the source symtab, this means
- // we are dealing with either an entire section, or part of it, but also
- // starting at the beginning.
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- const source_sect = object.getSourceSection(sect_id);
- assert(!source_sect.isZerofill());
- const code = object.getSectionContents(source_sect);
- const code_len = @as(usize, @intCast(atom.size));
- return code[0..code_len];
- };
- const source_sect = object.getSourceSection(source_sym.n_sect - 1);
- assert(!source_sect.isZerofill());
- const code = object.getSectionContents(source_sect);
- const offset = @as(usize, @intCast(source_sym.n_value - source_sect.addr));
- const code_len = @as(usize, @intCast(atom.size));
- return code[offset..][0..code_len];
+pub fn format(
+ atom: Atom,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = atom;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format Atom directly");
}
-pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info {
- const atom = macho_file.getAtom(atom_index);
- assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs.
- const object = macho_file.objects.items[atom.getFile().?];
- const cache = object.relocs_lookup[atom.sym_index];
-
- const source_sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
- break :blk source_sym.n_sect - 1;
- } else blk: {
- // If there was no matching symbol present in the source symtab, this means
- // we are dealing with either an entire section, or part of it, but also
- // starting at the beginning.
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- break :blk sect_id;
- };
- const source_sect = object.getSourceSection(source_sect_id);
- assert(!source_sect.isZerofill());
- const relocs = object.getRelocs(source_sect_id);
- return relocs[cache.start..][0..cache.len];
+pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .atom = atom,
+ .macho_file = macho_file,
+ } };
}
-pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- switch (target.cpu.arch) {
- .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
- .ARM64_RELOC_GOT_LOAD_PAGE21,
- .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
- .ARM64_RELOC_POINTER_TO_GOT,
- => return true,
- else => return false,
- },
- .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
- .X86_64_RELOC_GOT,
- .X86_64_RELOC_GOT_LOAD,
- => return true,
- else => return false,
- },
- else => unreachable,
- }
-}
+const FormatContext = struct {
+ atom: Atom,
+ macho_file: *MachO,
+};
-pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- switch (target.cpu.arch) {
- .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
- .ARM64_RELOC_TLVP_LOAD_PAGE21,
- .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
- => return true,
- else => return false,
- },
- .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
- .X86_64_RELOC_TLV => return true,
- else => return false,
- },
- else => unreachable,
+fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ const atom = ctx.atom;
+ const macho_file = ctx.macho_file;
+ try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{
+ atom.atom_index, atom.getName(macho_file), atom.value,
+ atom.out_n_sect, atom.alignment, atom.size,
+ atom.thunk_index,
+ });
+ if (!atom.flags.alive) try writer.writeAll(" : [*]");
+ if (atom.unwind_records.len > 0) {
+ try writer.writeAll(" : unwind{ ");
+ for (atom.getUnwindRecords(macho_file), atom.unwind_records.pos..) |index, i| {
+ const rec = macho_file.getUnwindRecord(index);
+ try writer.print("{d}", .{index});
+ if (!rec.alive) try writer.writeAll("([*])");
+ if (i < atom.unwind_records.pos + atom.unwind_records.len - 1) try writer.writeAll(", ");
+ }
+ try writer.writeAll(" }");
}
}
-pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- switch (target.cpu.arch) {
- .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
- .ARM64_RELOC_BRANCH26 => return true,
- else => return false,
- },
- .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
- .X86_64_RELOC_BRANCH => return true,
- else => return false,
- },
- else => unreachable,
- }
-}
+pub const Index = u32;
-const Atom = @This();
+pub const Flags = packed struct {
+ /// Specifies whether this atom is alive or has been garbage collected.
+ alive: bool = true,
+
+ /// Specifies if the atom has been visited during garbage collection.
+ visited: bool = false,
+};
+
+pub const Loc = struct {
+ pos: u32 = 0,
+ len: u32 = 0,
+};
+
+pub const Alignment = @import("../../InternPool.zig").Alignment;
-const std = @import("std");
-const build_options = @import("build_options");
const aarch64 = @import("../../arch/aarch64/bits.zig");
const assert = std.debug.assert;
-const log = std.log.scoped(.link);
-const relocs_log = std.log.scoped(.link_relocs);
+const bind = @import("dyld_info/bind.zig");
const macho = std.macho;
const math = std.math;
const mem = std.mem;
-const meta = std.meta;
+const log = std.log.scoped(.link);
+const relocs_log = std.log.scoped(.link_relocs);
+const std = @import("std");
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
-const Arch = std.Target.Cpu.Arch;
+const Atom = @This();
+const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
-pub const Relocation = @import("Relocation.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
+const Object = @import("Object.zig");
+const Relocation = @import("Relocation.zig");
+const Symbol = @import("Symbol.zig");
+const Thunk = @import("thunks.zig").Thunk;
+const UnwindInfo = @import("UnwindInfo.zig");
diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig
index 0f49ee6a64..045bad712b 100644
--- a/src/link/MachO/CodeSignature.zig
+++ b/src/link/MachO/CodeSignature.zig
@@ -1,175 +1,16 @@
-page_size: u16,
-code_directory: CodeDirectory,
-requirements: ?Requirements = null,
-entitlements: ?Entitlements = null,
-signature: ?Signature = null,
-
-pub fn init(page_size: u16) CodeSignature {
- return .{
- .page_size = page_size,
- .code_directory = CodeDirectory.init(page_size),
- };
-}
-
-pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
- self.code_directory.deinit(allocator);
- if (self.requirements) |*req| {
- req.deinit(allocator);
- }
- if (self.entitlements) |*ents| {
- ents.deinit(allocator);
- }
- if (self.signature) |*sig| {
- sig.deinit(allocator);
- }
-}
-
-pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
- const file = try fs.cwd().openFile(path, .{});
- defer file.close();
- const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
- self.entitlements = .{ .inner = inner };
-}
-
-pub const WriteOpts = struct {
- file: fs.File,
- exec_seg_base: u64,
- exec_seg_limit: u64,
- file_size: u32,
- output_mode: std.builtin.OutputMode,
-};
-
-pub fn writeAdhocSignature(
- self: *CodeSignature,
- comp: *const Compilation,
- opts: WriteOpts,
- writer: anytype,
-) !void {
- const gpa = comp.gpa;
-
- var header: macho.SuperBlob = .{
- .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
- .length = @sizeOf(macho.SuperBlob),
- .count = 0,
- };
-
- var blobs = std.ArrayList(Blob).init(gpa);
- defer blobs.deinit();
-
- self.code_directory.inner.execSegBase = opts.exec_seg_base;
- self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
- self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
- self.code_directory.inner.codeLimit = opts.file_size;
-
- const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
-
- try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
- self.code_directory.code_slots.items.len = total_pages;
- self.code_directory.inner.nCodeSlots = total_pages;
-
- // Calculate hash for each page (in file) and write it to the buffer
- var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
- try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
- .chunk_size = self.page_size,
- .max_file_size = opts.file_size,
- });
-
- try blobs.append(.{ .code_directory = &self.code_directory });
- header.length += @sizeOf(macho.BlobIndex);
- header.count += 1;
-
- var hash: [hash_size]u8 = undefined;
-
- if (self.requirements) |*req| {
- var buf = std.ArrayList(u8).init(gpa);
- defer buf.deinit();
- try req.write(buf.writer());
- Sha256.hash(buf.items, &hash, .{});
- self.code_directory.addSpecialHash(req.slotType(), hash);
-
- try blobs.append(.{ .requirements = req });
- header.count += 1;
- header.length += @sizeOf(macho.BlobIndex) + req.size();
- }
-
- if (self.entitlements) |*ents| {
- var buf = std.ArrayList(u8).init(gpa);
- defer buf.deinit();
- try ents.write(buf.writer());
- Sha256.hash(buf.items, &hash, .{});
- self.code_directory.addSpecialHash(ents.slotType(), hash);
-
- try blobs.append(.{ .entitlements = ents });
- header.count += 1;
- header.length += @sizeOf(macho.BlobIndex) + ents.size();
- }
-
- if (self.signature) |*sig| {
- try blobs.append(.{ .signature = sig });
- header.count += 1;
- header.length += @sizeOf(macho.BlobIndex) + sig.size();
- }
-
- self.code_directory.inner.hashOffset =
- @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
- self.code_directory.inner.length = self.code_directory.size();
- header.length += self.code_directory.size();
-
- try writer.writeInt(u32, header.magic, .big);
- try writer.writeInt(u32, header.length, .big);
- try writer.writeInt(u32, header.count, .big);
-
- var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
- for (blobs.items) |blob| {
- try writer.writeInt(u32, blob.slotType(), .big);
- try writer.writeInt(u32, offset, .big);
- offset += blob.size();
- }
-
- for (blobs.items) |blob| {
- try blob.write(writer);
- }
-}
-
-pub fn size(self: CodeSignature) u32 {
- var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
- if (self.requirements) |req| {
- ssize += @sizeOf(macho.BlobIndex) + req.size();
- }
- if (self.entitlements) |ent| {
- ssize += @sizeOf(macho.BlobIndex) + ent.size();
- }
- if (self.signature) |sig| {
- ssize += @sizeOf(macho.BlobIndex) + sig.size();
- }
- return ssize;
-}
-
-pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
- var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
- // Approx code slots
- const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
- ssize += total_pages * hash_size;
- var n_special_slots: u32 = 0;
- if (self.requirements) |req| {
- ssize += @sizeOf(macho.BlobIndex) + req.size();
- n_special_slots = @max(n_special_slots, req.slotType());
- }
- if (self.entitlements) |ent| {
- ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
- n_special_slots = @max(n_special_slots, ent.slotType());
- }
- if (self.signature) |sig| {
- ssize += @sizeOf(macho.BlobIndex) + sig.size();
- }
- ssize += n_special_slots * hash_size;
- return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
-}
+const CodeSignature = @This();
-pub fn clear(self: *CodeSignature, allocator: Allocator) void {
- self.code_directory.deinit(allocator);
- self.code_directory = CodeDirectory.init(self.page_size);
-}
+const std = @import("std");
+const assert = std.debug.assert;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+const testing = std.testing;
+const Allocator = mem.Allocator;
+const Hasher = @import("hasher.zig").ParallelHasher;
+const MachO = @import("../MachO.zig");
+const Sha256 = std.crypto.hash.sha2.Sha256;
const hash_size = Sha256.digest_length;
@@ -257,7 +98,7 @@ const CodeDirectory = struct {
fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
assert(index > 0);
self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);
- self.special_slots[index - 1] = hash;
+ @memcpy(&self.special_slots[index - 1], &hash);
}
fn slotType(self: CodeDirectory) u32 {
@@ -376,17 +217,175 @@ const Signature = struct {
}
};
-const CodeSignature = @This();
+page_size: u16,
+code_directory: CodeDirectory,
+requirements: ?Requirements = null,
+entitlements: ?Entitlements = null,
+signature: ?Signature = null,
-const std = @import("std");
-const assert = std.debug.assert;
-const fs = std.fs;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const mem = std.mem;
-const testing = std.testing;
+pub fn init(page_size: u16) CodeSignature {
+ return .{
+ .page_size = page_size,
+ .code_directory = CodeDirectory.init(page_size),
+ };
+}
-const Allocator = mem.Allocator;
-const Compilation = @import("../../Compilation.zig");
-const Hasher = @import("hasher.zig").ParallelHasher;
-const Sha256 = std.crypto.hash.sha2.Sha256;
+pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
+ self.code_directory.deinit(allocator);
+ if (self.requirements) |*req| {
+ req.deinit(allocator);
+ }
+ if (self.entitlements) |*ents| {
+ ents.deinit(allocator);
+ }
+ if (self.signature) |*sig| {
+ sig.deinit(allocator);
+ }
+}
+
+pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
+ const file = try fs.cwd().openFile(path, .{});
+ defer file.close();
+ const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
+ self.entitlements = .{ .inner = inner };
+}
+
+pub const WriteOpts = struct {
+ file: fs.File,
+ exec_seg_base: u64,
+ exec_seg_limit: u64,
+ file_size: u32,
+ dylib: bool,
+};
+
+pub fn writeAdhocSignature(
+ self: *CodeSignature,
+ macho_file: *MachO,
+ opts: WriteOpts,
+ writer: anytype,
+) !void {
+ const allocator = macho_file.base.comp.gpa;
+
+ var header: macho.SuperBlob = .{
+ .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
+ .length = @sizeOf(macho.SuperBlob),
+ .count = 0,
+ };
+
+ var blobs = std.ArrayList(Blob).init(allocator);
+ defer blobs.deinit();
+
+ self.code_directory.inner.execSegBase = opts.exec_seg_base;
+ self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
+ self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0;
+ self.code_directory.inner.codeLimit = opts.file_size;
+
+ const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
+
+ try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
+ self.code_directory.code_slots.items.len = total_pages;
+ self.code_directory.inner.nCodeSlots = total_pages;
+
+ // Calculate hash for each page (in file) and write it to the buffer
+ var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.comp.thread_pool };
+ try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
+ .chunk_size = self.page_size,
+ .max_file_size = opts.file_size,
+ });
+
+ try blobs.append(.{ .code_directory = &self.code_directory });
+ header.length += @sizeOf(macho.BlobIndex);
+ header.count += 1;
+
+ var hash: [hash_size]u8 = undefined;
+
+ if (self.requirements) |*req| {
+ var buf = std.ArrayList(u8).init(allocator);
+ defer buf.deinit();
+ try req.write(buf.writer());
+ Sha256.hash(buf.items, &hash, .{});
+ self.code_directory.addSpecialHash(req.slotType(), hash);
+
+ try blobs.append(.{ .requirements = req });
+ header.count += 1;
+ header.length += @sizeOf(macho.BlobIndex) + req.size();
+ }
+
+ if (self.entitlements) |*ents| {
+ var buf = std.ArrayList(u8).init(allocator);
+ defer buf.deinit();
+ try ents.write(buf.writer());
+ Sha256.hash(buf.items, &hash, .{});
+ self.code_directory.addSpecialHash(ents.slotType(), hash);
+
+ try blobs.append(.{ .entitlements = ents });
+ header.count += 1;
+ header.length += @sizeOf(macho.BlobIndex) + ents.size();
+ }
+
+ if (self.signature) |*sig| {
+ try blobs.append(.{ .signature = sig });
+ header.count += 1;
+ header.length += @sizeOf(macho.BlobIndex) + sig.size();
+ }
+
+ self.code_directory.inner.hashOffset =
+ @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
+ self.code_directory.inner.length = self.code_directory.size();
+ header.length += self.code_directory.size();
+
+ try writer.writeInt(u32, header.magic, .big);
+ try writer.writeInt(u32, header.length, .big);
+ try writer.writeInt(u32, header.count, .big);
+
+ var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
+ for (blobs.items) |blob| {
+ try writer.writeInt(u32, blob.slotType(), .big);
+ try writer.writeInt(u32, offset, .big);
+ offset += blob.size();
+ }
+
+ for (blobs.items) |blob| {
+ try blob.write(writer);
+ }
+}
+
+pub fn size(self: CodeSignature) u32 {
+ var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
+ if (self.requirements) |req| {
+ ssize += @sizeOf(macho.BlobIndex) + req.size();
+ }
+ if (self.entitlements) |ent| {
+ ssize += @sizeOf(macho.BlobIndex) + ent.size();
+ }
+ if (self.signature) |sig| {
+ ssize += @sizeOf(macho.BlobIndex) + sig.size();
+ }
+ return ssize;
+}
+
+pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
+ var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
+ // Approx code slots
+ const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
+ ssize += total_pages * hash_size;
+ var n_special_slots: u32 = 0;
+ if (self.requirements) |req| {
+ ssize += @sizeOf(macho.BlobIndex) + req.size();
+ n_special_slots = @max(n_special_slots, req.slotType());
+ }
+ if (self.entitlements) |ent| {
+ ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
+ n_special_slots = @max(n_special_slots, ent.slotType());
+ }
+ if (self.signature) |sig| {
+ ssize += @sizeOf(macho.BlobIndex) + sig.size();
+ }
+ ssize += n_special_slots * hash_size;
+ return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
+}
+
+pub fn clear(self: *CodeSignature, allocator: Allocator) void {
+ self.code_directory.deinit(allocator);
+ self.code_directory = CodeDirectory.init(self.page_size);
+}
diff --git a/src/link/MachO/DwarfInfo.zig b/src/link/MachO/DwarfInfo.zig
index a1e0ae458a..036738225d 100644
--- a/src/link/MachO/DwarfInfo.zig
+++ b/src/link/MachO/DwarfInfo.zig
@@ -2,377 +2,175 @@ debug_info: []const u8,
debug_abbrev: []const u8,
debug_str: []const u8,
-pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator {
- return .{ .ctx = self };
+/// Abbreviation table indexed by offset in the .debug_abbrev bytestream
+abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{},
+/// List of compile units as they appear in the .debug_info bytestream
+compile_units: std.ArrayListUnmanaged(CompileUnit) = .{},
+
+pub fn init(dw: *DwarfInfo, allocator: Allocator) !void {
+ try dw.parseAbbrevTables(allocator);
+ try dw.parseCompileUnits(allocator);
}
-const CompileUnitIterator = struct {
- ctx: DwarfInfo,
- pos: usize = 0,
-
- pub fn next(self: *CompileUnitIterator) !?CompileUnit {
- if (self.pos >= self.ctx.debug_info.len) return null;
-
- var stream = std.io.fixedBufferStream(self.ctx.debug_info[self.pos..]);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
-
- const cuh = try CompileUnit.Header.read(reader);
- const total_length = cuh.length + @as(u64, if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32));
- const offset = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
-
- const cu = CompileUnit{
- .cuh = cuh,
- .debug_info_off = self.pos + offset,
- };
-
- self.pos += (math.cast(usize, total_length) orelse return error.Overflow);
-
- return cu;
+pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void {
+ dw.abbrev_tables.deinit(allocator);
+ for (dw.compile_units.items) |*cu| {
+ cu.deinit(allocator);
}
-};
-
-pub fn genSubprogramLookupByName(
- self: DwarfInfo,
- compile_unit: CompileUnit,
- abbrev_lookup: AbbrevLookupTable,
- lookup: *SubprogramLookupByName,
-) !void {
- var abbrev_it = compile_unit.getAbbrevEntryIterator(self);
- while (try abbrev_it.next(abbrev_lookup)) |entry| switch (entry.tag) {
- dwarf.TAG.subprogram => {
- var attr_it = entry.getAttributeIterator(self, compile_unit.cuh);
-
- var name: ?[]const u8 = null;
- var low_pc: ?u64 = null;
- var high_pc: ?u64 = null;
-
- while (try attr_it.next()) |attr| switch (attr.name) {
- dwarf.AT.name => if (attr.getString(self, compile_unit.cuh)) |str| {
- name = str;
- },
- dwarf.AT.low_pc => {
- if (attr.getAddr(self, compile_unit.cuh)) |addr| {
- low_pc = addr;
- }
- if (try attr.getConstant(self)) |constant| {
- low_pc = @as(u64, @intCast(constant));
- }
- },
- dwarf.AT.high_pc => {
- if (attr.getAddr(self, compile_unit.cuh)) |addr| {
- high_pc = addr;
- }
- if (try attr.getConstant(self)) |constant| {
- high_pc = @as(u64, @intCast(constant));
- }
- },
- else => {},
- };
-
- if (name == null or low_pc == null or high_pc == null) continue;
+ dw.compile_units.deinit(allocator);
+}
- try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? });
- },
- else => {},
- };
+fn getString(dw: DwarfInfo, off: usize) [:0]const u8 {
+ assert(off < dw.debug_str.len);
+ return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.debug_str.ptr + off)), 0);
}
-pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void {
- const data = self.debug_abbrev[off..];
- var stream = std.io.fixedBufferStream(data);
+fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const debug_abbrev = dw.debug_abbrev;
+ var stream = std.io.fixedBufferStream(debug_abbrev);
var creader = std.io.countingReader(stream.reader());
const reader = creader.reader();
while (true) {
- const kind = try leb.readULEB128(u64, reader);
+ if (creader.bytes_read >= debug_abbrev.len) break;
- if (kind == 0) break;
-
- const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
- _ = try leb.readULEB128(u64, reader); // TAG
- _ = try reader.readByte(); // CHILDREN
+ try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1);
+ const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read));
+ assert(!table_gop.found_existing);
+ const table = table_gop.value_ptr;
+ table.* = .{};
while (true) {
- const name = try leb.readULEB128(u64, reader);
- const form = try leb.readULEB128(u64, reader);
-
- if (name == 0 and form == 0) break;
- }
-
- const next_pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
-
- try lookup.putNoClobber(kind, .{
- .pos = pos,
- .len = next_pos - pos - 2,
- });
- }
-}
+ const code = try leb.readULEB128(Code, reader);
+ if (code == 0) break;
+
+ try table.decls.ensureUnusedCapacity(allocator, 1);
+ const decl_gop = table.decls.getOrPutAssumeCapacity(code);
+ assert(!decl_gop.found_existing);
+ const decl = decl_gop.value_ptr;
+ decl.* = .{
+ .code = code,
+ .tag = undefined,
+ .children = false,
+ };
+ decl.tag = try leb.readULEB128(Tag, reader);
+ decl.children = (try reader.readByte()) > 0;
-pub const CompileUnit = struct {
- cuh: Header,
- debug_info_off: usize,
-
- pub const Header = struct {
- is_64bit: bool,
- length: u64,
- version: u16,
- debug_abbrev_offset: u64,
- address_size: u8,
-
- fn read(reader: anytype) !Header {
- var length: u64 = try reader.readInt(u32, .little);
-
- const is_64bit = length == 0xffffffff;
- if (is_64bit) {
- length = try reader.readInt(u64, .little);
+ while (true) {
+ const at = try leb.readULEB128(At, reader);
+ const form = try leb.readULEB128(Form, reader);
+ if (at == 0 and form == 0) break;
+
+ try decl.attrs.ensureUnusedCapacity(allocator, 1);
+ const attr_gop = decl.attrs.getOrPutAssumeCapacity(at);
+ assert(!attr_gop.found_existing);
+ const attr = attr_gop.value_ptr;
+ attr.* = .{
+ .at = at,
+ .form = form,
+ };
}
-
- const version = try reader.readInt(u16, .little);
- const debug_abbrev_offset = if (is_64bit)
- try reader.readInt(u64, .little)
- else
- try reader.readInt(u32, .little);
- const address_size = try reader.readInt(u8, .little);
-
- return Header{
- .is_64bit = is_64bit,
- .length = length,
- .version = version,
- .debug_abbrev_offset = debug_abbrev_offset,
- .address_size = address_size,
- };
}
- };
-
- inline fn getDebugInfo(self: CompileUnit, ctx: DwarfInfo) []const u8 {
- return ctx.debug_info[self.debug_info_off..][0..self.cuh.length];
- }
-
- pub fn getAbbrevEntryIterator(self: CompileUnit, ctx: DwarfInfo) AbbrevEntryIterator {
- return .{ .cu = self, .ctx = ctx };
}
-};
-
-const AbbrevEntryIterator = struct {
- cu: CompileUnit,
- ctx: DwarfInfo,
- pos: usize = 0,
-
- pub fn next(self: *AbbrevEntryIterator, lookup: AbbrevLookupTable) !?AbbrevEntry {
- if (self.pos + self.cu.debug_info_off >= self.ctx.debug_info.len) return null;
-
- const debug_info = self.ctx.debug_info[self.pos + self.cu.debug_info_off ..];
- var stream = std.io.fixedBufferStream(debug_info);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+}
- const kind = try leb.readULEB128(u64, reader);
- self.pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
+fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- if (kind == 0) {
- return AbbrevEntry.null();
- }
+ const debug_info = dw.debug_info;
+ var stream = std.io.fixedBufferStream(debug_info);
+ var creader = std.io.countingReader(stream.reader());
+ const reader = creader.reader();
- const abbrev_pos = lookup.get(kind) orelse return null;
- const len = try findAbbrevEntrySize(
- self.ctx,
- abbrev_pos.pos,
- abbrev_pos.len,
- self.pos + self.cu.debug_info_off,
- self.cu.cuh,
- );
- const entry = try getAbbrevEntry(
- self.ctx,
- abbrev_pos.pos,
- abbrev_pos.len,
- self.pos + self.cu.debug_info_off,
- len,
- );
-
- self.pos += len;
-
- return entry;
- }
-};
+ while (true) {
+ if (creader.bytes_read == debug_info.len) break;
-pub const AbbrevEntry = struct {
- tag: u64,
- children: u8,
- debug_abbrev_off: usize,
- debug_abbrev_len: usize,
- debug_info_off: usize,
- debug_info_len: usize,
-
- fn @"null"() AbbrevEntry {
- return .{
- .tag = 0,
- .children = dwarf.CHILDREN.no,
- .debug_abbrev_off = 0,
- .debug_abbrev_len = 0,
- .debug_info_off = 0,
- .debug_info_len = 0,
+ const cu = try dw.compile_units.addOne(allocator);
+ cu.* = .{
+ .header = undefined,
+ .pos = creader.bytes_read,
};
- }
-
- pub fn hasChildren(self: AbbrevEntry) bool {
- return self.children == dwarf.CHILDREN.yes;
- }
-
- inline fn getDebugInfo(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
- return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
- }
-
- inline fn getDebugAbbrev(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
- return ctx.debug_abbrev[self.debug_abbrev_off..][0..self.debug_abbrev_len];
- }
-
- pub fn getAttributeIterator(self: AbbrevEntry, ctx: DwarfInfo, cuh: CompileUnit.Header) AttributeIterator {
- return .{ .entry = self, .ctx = ctx, .cuh = cuh };
- }
-};
-
-pub const Attribute = struct {
- name: u64,
- form: u64,
- debug_info_off: usize,
- debug_info_len: usize,
- inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 {
- return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
- }
-
- pub fn getString(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?[]const u8 {
- const debug_info = self.getDebugInfo(ctx);
-
- switch (self.form) {
- dwarf.FORM.string => {
- return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_info.ptr)), 0);
- },
- dwarf.FORM.strp => {
- const off = if (cuh.is_64bit)
- mem.readInt(u64, debug_info[0..8], .little)
- else
- mem.readInt(u32, debug_info[0..4], .little);
- return ctx.getString(off);
- },
- else => return null,
+ var length: u64 = try reader.readInt(u32, .little);
+ const is_64bit = length == 0xffffffff;
+ if (is_64bit) {
+ length = try reader.readInt(u64, .little);
}
+ cu.header.format = if (is_64bit) .dwarf64 else .dwarf32;
+ cu.header.length = length;
+ cu.header.version = try reader.readInt(u16, .little);
+ cu.header.debug_abbrev_offset = try readOffset(cu.header.format, reader);
+ cu.header.address_size = try reader.readInt(u8, .little);
+
+ const table = dw.abbrev_tables.get(cu.header.debug_abbrev_offset).?;
+ try dw.parseDie(allocator, cu, table, null, &creader);
}
+}
- pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 {
- const debug_info = self.getDebugInfo(ctx);
- var stream = std.io.fixedBufferStream(debug_info);
- const reader = stream.reader();
-
- return switch (self.form) {
- dwarf.FORM.data1 => debug_info[0],
- dwarf.FORM.data2 => mem.readInt(u16, debug_info[0..2], .little),
- dwarf.FORM.data4 => mem.readInt(u32, debug_info[0..4], .little),
- dwarf.FORM.data8 => mem.readInt(u64, debug_info[0..8], .little),
- dwarf.FORM.udata => try leb.readULEB128(u64, reader),
- dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
- else => null,
- };
- }
-
- pub fn getAddr(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?u64 {
- if (self.form != dwarf.FORM.addr) return null;
- const debug_info = self.getDebugInfo(ctx);
- return switch (cuh.address_size) {
- 1 => debug_info[0],
- 2 => mem.readInt(u16, debug_info[0..2], .little),
- 4 => mem.readInt(u32, debug_info[0..4], .little),
- 8 => mem.readInt(u64, debug_info[0..8], .little),
- else => unreachable,
- };
- }
-};
-
-const AttributeIterator = struct {
- entry: AbbrevEntry,
- ctx: DwarfInfo,
- cuh: CompileUnit.Header,
- debug_abbrev_pos: usize = 0,
- debug_info_pos: usize = 0,
+fn parseDie(
+ dw: *DwarfInfo,
+ allocator: Allocator,
+ cu: *CompileUnit,
+ table: AbbrevTable,
+ parent: ?u32,
+ creader: anytype,
+) anyerror!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ while (creader.bytes_read < cu.nextCompileUnitOffset()) {
+ const die = try cu.addDie(allocator);
+ cu.diePtr(die).* = .{ .code = undefined };
+ if (parent) |p| {
+ try cu.diePtr(p).children.append(allocator, die);
+ } else {
+ try cu.children.append(allocator, die);
+ }
- pub fn next(self: *AttributeIterator) !?Attribute {
- const debug_abbrev = self.entry.getDebugAbbrev(self.ctx);
- if (self.debug_abbrev_pos >= debug_abbrev.len) return null;
+ const code = try leb.readULEB128(Code, creader.reader());
+ cu.diePtr(die).code = code;
- var stream = std.io.fixedBufferStream(debug_abbrev[self.debug_abbrev_pos..]);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+ if (code == 0) {
+ if (parent == null) continue;
+ return; // Close scope
+ }
- const name = try leb.readULEB128(u64, reader);
- const form = try leb.readULEB128(u64, reader);
-
- self.debug_abbrev_pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
-
- const len = try findFormSize(
- self.ctx,
- form,
- self.debug_info_pos + self.entry.debug_info_off,
- self.cuh,
- );
- const attr = Attribute{
- .name = name,
- .form = form,
- .debug_info_off = self.debug_info_pos + self.entry.debug_info_off,
- .debug_info_len = len,
- };
+ const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors
+ const data = dw.debug_info;
+ try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len);
- self.debug_info_pos += len;
+ for (decl.attrs.values()) |attr| {
+ const start = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
+ try advanceByFormSize(cu, attr.form, creader);
+ const end = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
+ cu.diePtr(die).values.appendAssumeCapacity(data[start..end]);
+ }
- return attr;
+ if (decl.children) {
+ // Open scope
+ try dw.parseDie(allocator, cu, table, die, creader);
+ }
}
-};
-
-fn getAbbrevEntry(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, di_len: usize) !AbbrevEntry {
- const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
- var stream = std.io.fixedBufferStream(debug_abbrev);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
-
- const tag = try leb.readULEB128(u64, reader);
- const children = switch (tag) {
- std.dwarf.TAG.const_type,
- std.dwarf.TAG.packed_type,
- std.dwarf.TAG.pointer_type,
- std.dwarf.TAG.reference_type,
- std.dwarf.TAG.restrict_type,
- std.dwarf.TAG.rvalue_reference_type,
- std.dwarf.TAG.shared_type,
- std.dwarf.TAG.volatile_type,
- => if (creader.bytes_read == da_len) std.dwarf.CHILDREN.no else try reader.readByte(),
- else => try reader.readByte(),
- };
-
- const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
-
- return AbbrevEntry{
- .tag = tag,
- .children = children,
- .debug_abbrev_off = pos + da_off,
- .debug_abbrev_len = da_len - pos,
- .debug_info_off = di_off,
- .debug_info_len = di_len,
- };
}
-fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize {
- const debug_info = self.debug_info[di_off..];
- var stream = std.io.fixedBufferStream(debug_info);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const reader = creader.reader();
switch (form) {
dwarf.FORM.strp,
dwarf.FORM.sec_offset,
dwarf.FORM.ref_addr,
- => return if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32),
+ => {
+ _ = try readOffset(cu.header.format, reader);
+ },
- dwarf.FORM.addr => return cuh.address_size,
+ dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}),
dwarf.FORM.block1,
dwarf.FORM.block2,
@@ -390,115 +188,284 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head
while (i < len) : (i += 1) {
_ = try reader.readByte();
}
- return math.cast(usize, creader.bytes_read) orelse error.Overflow;
},
dwarf.FORM.exprloc => {
- const expr_len = try leb.readULEB128(u64, reader);
+ const len = try leb.readULEB128(u64, reader);
var i: u64 = 0;
- while (i < expr_len) : (i += 1) {
+ while (i < len) : (i += 1) {
_ = try reader.readByte();
}
- return math.cast(usize, creader.bytes_read) orelse error.Overflow;
},
- dwarf.FORM.flag_present => return 0,
+ dwarf.FORM.flag_present => {},
dwarf.FORM.data1,
dwarf.FORM.ref1,
dwarf.FORM.flag,
- => return @sizeOf(u8),
+ => try reader.skipBytes(1, .{}),
dwarf.FORM.data2,
dwarf.FORM.ref2,
- => return @sizeOf(u16),
+ => try reader.skipBytes(2, .{}),
dwarf.FORM.data4,
dwarf.FORM.ref4,
- => return @sizeOf(u32),
+ => try reader.skipBytes(4, .{}),
dwarf.FORM.data8,
dwarf.FORM.ref8,
dwarf.FORM.ref_sig8,
- => return @sizeOf(u64),
+ => try reader.skipBytes(8, .{}),
dwarf.FORM.udata,
dwarf.FORM.ref_udata,
=> {
_ = try leb.readULEB128(u64, reader);
- return math.cast(usize, creader.bytes_read) orelse error.Overflow;
},
dwarf.FORM.sdata => {
_ = try leb.readILEB128(i64, reader);
- return math.cast(usize, creader.bytes_read) orelse error.Overflow;
},
dwarf.FORM.string => {
- var count: usize = 0;
while (true) {
const byte = try reader.readByte();
- count += 1;
if (byte == 0x0) break;
}
- return count;
},
else => {
- // TODO figure out how to handle this
- log.debug("unhandled DW_FORM_* value with identifier {x}", .{form});
+ // TODO better errors
+ log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
return error.UnhandledDwFormValue;
},
}
}
-fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize {
- const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
- var stream = std.io.fixedBufferStream(debug_abbrev);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+fn readOffset(format: Format, reader: anytype) !u64 {
+ return switch (format) {
+ .dwarf32 => try reader.readInt(u32, .little),
+ .dwarf64 => try reader.readInt(u64, .little),
+ };
+}
- const tag = try leb.readULEB128(u64, reader);
- switch (tag) {
- std.dwarf.TAG.const_type,
- std.dwarf.TAG.packed_type,
- std.dwarf.TAG.pointer_type,
- std.dwarf.TAG.reference_type,
- std.dwarf.TAG.restrict_type,
- std.dwarf.TAG.rvalue_reference_type,
- std.dwarf.TAG.shared_type,
- std.dwarf.TAG.volatile_type,
- => if (creader.bytes_read != da_len) {
- _ = try reader.readByte();
- },
- else => _ = try reader.readByte(),
+pub const AbbrevTable = struct {
+ /// Table of abbreviation declarations indexed by their assigned code value
+ decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{},
+
+ pub fn deinit(table: *AbbrevTable, gpa: Allocator) void {
+ for (table.decls.values()) |*decl| {
+ decl.deinit(gpa);
+ }
+ table.decls.deinit(gpa);
}
+};
+
+pub const Decl = struct {
+ code: Code,
+ tag: Tag,
+ children: bool,
+
+ /// Table of attributes indexed by their AT value
+ attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{},
- var len: usize = 0;
- while (creader.bytes_read < debug_abbrev.len) {
- _ = try leb.readULEB128(u64, reader);
- const form = try leb.readULEB128(u64, reader);
- const form_len = try self.findFormSize(form, di_off + len, cuh);
- len += form_len;
+ pub fn deinit(decl: *Decl, gpa: Allocator) void {
+ decl.attrs.deinit(gpa);
}
+};
- return len;
-}
+pub const Attr = struct {
+ at: At,
+ form: Form,
+};
-fn getString(self: DwarfInfo, off: u64) []const u8 {
- assert(off < self.debug_str.len);
- return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0);
-}
+pub const At = u64;
+pub const Code = u64;
+pub const Form = u64;
+pub const Tag = u64;
+
+pub const CompileUnitHeader = struct {
+ format: Format,
+ length: u64,
+ version: u16,
+ debug_abbrev_offset: u64,
+ address_size: u8,
+};
-const DwarfInfo = @This();
+pub const CompileUnit = struct {
+ header: CompileUnitHeader,
+ pos: u64,
+ dies: std.ArrayListUnmanaged(Die) = .{},
+ children: std.ArrayListUnmanaged(Die.Index) = .{},
+
+ pub fn deinit(cu: *CompileUnit, gpa: Allocator) void {
+ for (cu.dies.items) |*die| {
+ die.deinit(gpa);
+ }
+ cu.dies.deinit(gpa);
+ cu.children.deinit(gpa);
+ }
+
+ pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index {
+ const index = @as(Die.Index, @intCast(cu.dies.items.len));
+ _ = try cu.dies.addOne(gpa);
+ return index;
+ }
+
+ pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die {
+ return &cu.dies.items[index];
+ }
+
+ pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
+ assert(cu.dies.items.len > 0);
+ const die = cu.dies.items[0];
+ const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null;
+ return res.getString(cu.header.format, ctx);
+ }
+
+ pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
+ assert(cu.dies.items.len > 0);
+ const die = cu.dies.items[0];
+ const res = die.find(dwarf.AT.name, cu, ctx) orelse return null;
+ return res.getString(cu.header.format, ctx);
+ }
+
+ pub fn nextCompileUnitOffset(cu: CompileUnit) u64 {
+ return cu.pos + switch (cu.header.format) {
+ .dwarf32 => @as(u64, 4),
+ .dwarf64 => 12,
+ } + cu.header.length;
+ }
+};
+
+pub const Die = struct {
+ code: Code,
+ values: std.ArrayListUnmanaged([]const u8) = .{},
+ children: std.ArrayListUnmanaged(Die.Index) = .{},
+
+ pub fn deinit(die: *Die, gpa: Allocator) void {
+ die.values.deinit(gpa);
+ die.children.deinit(gpa);
+ }
+
+ pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue {
+ const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null;
+ const decl = table.decls.get(die.code).?;
+ const index = decl.attrs.getIndex(at) orelse return null;
+ const attr = decl.attrs.values()[index];
+ const value = die.values.items[index];
+ return .{ .attr = attr, .bytes = value };
+ }
+
+ pub const Index = u32;
+};
+
+pub const DieValue = struct {
+ attr: Attr,
+ bytes: []const u8,
+
+ pub fn getFlag(value: DieValue) ?bool {
+ return switch (value.attr.form) {
+ dwarf.FORM.flag => value.bytes[0] == 1,
+ dwarf.FORM.flag_present => true,
+ else => null,
+ };
+ }
+
+ pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
+ switch (value.attr.form) {
+ dwarf.FORM.string => {
+ return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0);
+ },
+ dwarf.FORM.strp => {
+ const off = switch (format) {
+ .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
+ .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
+ };
+ const off_u = std.math.cast(usize, off) orelse return error.Overflow;
+ return ctx.getString(off_u);
+ },
+ else => return null,
+ }
+ }
+
+ pub fn getSecOffset(value: DieValue, format: Format) ?u64 {
+ return switch (value.attr.form) {
+ dwarf.FORM.sec_offset => switch (format) {
+ .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
+ .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
+ },
+ else => null,
+ };
+ }
+
+ pub fn getConstant(value: DieValue) !?i128 {
+ var stream = std.io.fixedBufferStream(value.bytes);
+ const reader = stream.reader();
+ return switch (value.attr.form) {
+ dwarf.FORM.data1 => value.bytes[0],
+ dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little),
+ dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little),
+ dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little),
+ dwarf.FORM.udata => try leb.readULEB128(u64, reader),
+ dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
+ else => null,
+ };
+ }
+
+ pub fn getReference(value: DieValue, format: Format) !?u64 {
+ var stream = std.io.fixedBufferStream(value.bytes);
+ const reader = stream.reader();
+ return switch (value.attr.form) {
+ dwarf.FORM.ref1 => value.bytes[0],
+ dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little),
+ dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little),
+ dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little),
+ dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader),
+ dwarf.FORM.ref_addr => switch (format) {
+ .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
+ .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
+ },
+ else => null,
+ };
+ }
+
+ pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 {
+ return switch (value.attr.form) {
+ dwarf.FORM.addr => switch (header.address_size) {
+ 1 => value.bytes[0],
+ 2 => mem.readInt(u16, value.bytes[0..2], .little),
+ 4 => mem.readInt(u32, value.bytes[0..4], .little),
+ 8 => mem.readInt(u64, value.bytes[0..8], .little),
+ else => null,
+ },
+ else => null,
+ };
+ }
+
+ pub fn getExprloc(value: DieValue) !?[]const u8 {
+ if (value.attr.form != dwarf.FORM.exprloc) return null;
+ var stream = std.io.fixedBufferStream(value.bytes);
+ var creader = std.io.countingReader(stream.reader());
+ const reader = creader.reader();
+ const expr_len = try leb.readULEB128(u64, reader);
+ return value.bytes[creader.bytes_read..][0..expr_len];
+ }
+};
+
+pub const Format = enum {
+ dwarf32,
+ dwarf64,
+};
-const std = @import("std");
const assert = std.debug.assert;
const dwarf = std.dwarf;
const leb = std.leb;
-const log = std.log.scoped(.macho);
-const math = std.math;
+const log = std.log.scoped(.link);
const mem = std.mem;
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
-pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
-pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });
+const DwarfInfo = @This();
+const MachO = @import("../MachO.zig");
diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig
index 65d503b1ae..363ec2e3f9 100644
--- a/src/link/MachO/Dylib.zig
+++ b/src/link/MachO/Dylib.zig
@@ -1,340 +1,252 @@
path: []const u8,
-id: ?Id = null,
-weak: bool = false,
-/// Header is only set if Dylib is parsed directly from a binary and not a stub file.
-header: ?macho.mach_header_64 = null,
-
-/// Parsed symbol table represented as hash map of symbols'
-/// names. We can and should defer creating *Symbols until
-/// a symbol is referenced by an object file.
-///
-/// The value for each parsed symbol represents whether the
-/// symbol is defined as a weak symbol or strong.
-/// TODO when the referenced symbol is weak, ld64 marks it as
-/// N_REF_TO_WEAK but need to investigate if there's more to it
-/// such as weak binding entry or simply weak. For now, we generate
-/// standard bind or lazy bind.
-symbols: std.StringArrayHashMapUnmanaged(bool) = .{},
-
-pub const Id = struct {
- name: []const u8,
- timestamp: u32,
- current_version: u32,
- compatibility_version: u32,
-
- pub fn default(allocator: Allocator, name: []const u8) !Id {
- return Id{
- .name = try allocator.dupe(u8, name),
- .timestamp = 2,
- .current_version = 0x10000,
- .compatibility_version = 0x10000,
- };
- }
+data: []const u8,
+index: File.Index,
- pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
- return Id{
- .name = try allocator.dupe(u8, name),
- .timestamp = lc.dylib.timestamp,
- .current_version = lc.dylib.current_version,
- .compatibility_version = lc.dylib.compatibility_version,
- };
- }
-
- pub fn deinit(id: Id, allocator: Allocator) void {
- allocator.free(id.name);
- }
-
- pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
-
- pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
- id.current_version = try parseVersion(version);
- }
-
- pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
- id.compatibility_version = try parseVersion(version);
- }
-
- fn parseVersion(version: anytype) ParseError!u32 {
- const string = blk: {
- switch (version) {
- .int => |int| {
- var out: u32 = 0;
- const major = math.cast(u16, int) orelse return error.Overflow;
- out += @as(u32, @intCast(major)) << 16;
- return out;
- },
- .float => |float| {
- var buf: [256]u8 = undefined;
- break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
- },
- .string => |string| {
- break :blk string;
- },
- }
- };
-
- var out: u32 = 0;
- var values: [3][]const u8 = undefined;
-
- var split = mem.splitScalar(u8, string, '.');
- var count: u4 = 0;
- while (split.next()) |value| {
- if (count > 2) {
- log.debug("malformed version field: {s}", .{string});
- return 0x10000;
- }
- values[count] = value;
- count += 1;
- }
-
- if (count > 2) {
- out += try fmt.parseInt(u8, values[2], 10);
- }
- if (count > 1) {
- out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;
- }
- out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
-
- return out;
+header: ?macho.mach_header_64 = null,
+exports: std.MultiArrayList(Export) = .{},
+strtab: std.ArrayListUnmanaged(u8) = .{},
+id: ?Id = null,
+ordinal: u16 = 0,
+
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+dependents: std.ArrayListUnmanaged(Id) = .{},
+rpaths: std.StringArrayHashMapUnmanaged(void) = .{},
+umbrella: File.Index = 0,
+platform: ?MachO.Platform = null,
+
+needed: bool,
+weak: bool,
+reexport: bool,
+explicit: bool,
+hoisted: bool = true,
+referenced: bool = false,
+
+output_symtab_ctx: MachO.SymtabCtx = .{},
+
+pub fn isDylib(path: []const u8, fat_arch: ?fat.Arch) !bool {
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
+ if (fat_arch) |arch| {
+ try file.seekTo(arch.offset);
}
-};
-
-pub fn isDylib(file: std.fs.File, fat_offset: u64) bool {
- const reader = file.reader();
- const hdr = reader.readStruct(macho.mach_header_64) catch return false;
- defer file.seekTo(fat_offset) catch {};
- return hdr.filetype == macho.MH_DYLIB;
+ const header = file.reader().readStruct(macho.mach_header_64) catch return false;
+ return header.filetype == macho.MH_DYLIB;
}
pub fn deinit(self: *Dylib, allocator: Allocator) void {
+ allocator.free(self.data);
allocator.free(self.path);
- for (self.symbols.keys()) |key| {
- allocator.free(key);
- }
+ self.exports.deinit(allocator);
+ self.strtab.deinit(allocator);
+ if (self.id) |*id| id.deinit(allocator);
self.symbols.deinit(allocator);
- if (self.id) |*id| {
+ for (self.dependents.items) |*id| {
id.deinit(allocator);
}
+ self.dependents.deinit(allocator);
+ self.rpaths.deinit(allocator);
}
-pub fn parseFromBinary(
- self: *Dylib,
- allocator: Allocator,
- dylib_id: u16,
- dependent_libs: anytype,
- name: []const u8,
- data: []align(@alignOf(u64)) const u8,
-) !void {
- var stream = std.io.fixedBufferStream(data);
+pub fn parse(self: *Dylib, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ var stream = std.io.fixedBufferStream(self.data);
const reader = stream.reader();
- log.debug("parsing shared library '{s}'", .{name});
+ log.debug("parsing dylib from binary", .{});
self.header = try reader.readStruct(macho.mach_header_64);
- const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
- var it = LoadCommandIterator{
- .ncmds = self.header.?.ncmds,
- .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
+ const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
+ macho.CPU_TYPE_ARM64 => .aarch64,
+ macho.CPU_TYPE_X86_64 => .x86_64,
+ else => |x| {
+ try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
+ return error.InvalidCpuArch;
+ },
};
- while (it.next()) |cmd| {
- switch (cmd.cmd()) {
- .SYMTAB => {
- const symtab_cmd = cmd.cast(macho.symtab_command).?;
- const symtab = @as(
- [*]const macho.nlist_64,
- // Alignment is guaranteed as a dylib is a final linked image and has to have sections
- // properly aligned in order to be correctly loaded by the loader.
- @ptrCast(@alignCast(&data[symtab_cmd.symoff])),
- )[0..symtab_cmd.nsyms];
- const strtab = data[symtab_cmd.stroff..][0..symtab_cmd.strsize];
-
- for (symtab) |sym| {
- const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
- if (!add_to_symtab) continue;
-
- const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + sym.n_strx)), 0);
- try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
- }
- },
- .ID_DYLIB => {
- self.id = try Id.fromLoadCommand(
- allocator,
- cmd.cast(macho.dylib_command).?,
- cmd.getDylibPathName(),
- );
- },
- .REEXPORT_DYLIB => {
- if (should_lookup_reexports) {
- // Parse install_name to dependent dylib.
- const id = try Id.fromLoadCommand(
- allocator,
- cmd.cast(macho.dylib_command).?,
- cmd.getDylibPathName(),
- );
- try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id });
- }
- },
- else => {},
- }
+ if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
+ try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
+ return error.InvalidCpuArch;
}
-}
-/// Returns Platform composed from the first encountered build version type load command:
-/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
-pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform {
+ const lc_id = self.getLoadCommand(.ID_DYLIB) orelse {
+ try macho_file.reportParseError2(self.index, "missing LC_ID_DYLIB load command", .{});
+ return error.MalformedDylib;
+ };
+ self.id = try Id.fromLoadCommand(gpa, lc_id.cast(macho.dylib_command).?, lc_id.getDylibPathName());
+
var it = LoadCommandIterator{
.ncmds = self.header.?.ncmds,
- .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
+ .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
};
- while (it.next()) |cmd| {
- switch (cmd.cmd()) {
- .BUILD_VERSION,
- .VERSION_MIN_MACOSX,
- .VERSION_MIN_IPHONEOS,
- .VERSION_MIN_TVOS,
- .VERSION_MIN_WATCHOS,
- => return Platform.fromLoadCommand(cmd),
- else => {},
- }
- } else return null;
-}
-
-fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
- const expanded = &[_][]const u8{
- try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
- try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
+ while (it.next()) |cmd| switch (cmd.cmd()) {
+ .REEXPORT_DYLIB => if (self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) {
+ const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName());
+ try self.dependents.append(gpa, id);
+ },
+ .DYLD_INFO_ONLY => {
+ const dyld_cmd = cmd.cast(macho.dyld_info_command).?;
+ const data = self.data[dyld_cmd.export_off..][0..dyld_cmd.export_size];
+ try self.parseTrie(data, macho_file);
+ },
+ .DYLD_EXPORTS_TRIE => {
+ const ld_cmd = cmd.cast(macho.linkedit_data_command).?;
+ const data = self.data[ld_cmd.dataoff..][0..ld_cmd.datasize];
+ try self.parseTrie(data, macho_file);
+ },
+ .RPATH => {
+ const path = cmd.getRpathPathName();
+ try self.rpaths.put(gpa, path, {});
+ },
+ else => {},
};
- for (expanded) |sym| {
- if (self.symbols.contains(sym)) continue;
- try self.symbols.putNoClobber(allocator, sym, false);
- }
-}
+ self.initPlatform();
-fn addObjCIVarSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
- const expanded = try std.fmt.allocPrint(allocator, "_OBJC_IVAR_$_{s}", .{sym_name});
- if (self.symbols.contains(expanded)) return;
- try self.symbols.putNoClobber(allocator, expanded, false);
-}
-
-fn addObjCEhTypeSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
- const expanded = try std.fmt.allocPrint(allocator, "_OBJC_EHTYPE_$_{s}", .{sym_name});
- if (self.symbols.contains(expanded)) return;
- try self.symbols.putNoClobber(allocator, expanded, false);
+ if (self.platform) |platform| {
+ if (!macho_file.platform.eqlTarget(platform)) {
+ try macho_file.reportParseError2(self.index, "invalid platform: {}", .{
+ platform.fmtTarget(macho_file.getTarget().cpu.arch),
+ });
+ return error.InvalidTarget;
+ }
+ // TODO: this can cause the CI to fail so I'm commenting this check out so that
+ // I can work out the rest of the changes first
+ // if (macho_file.platform.version.order(platform.version) == .lt) {
+ // try macho_file.reportParseError2(self.index, "object file built for newer platform: {}: {} < {}", .{
+ // macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch),
+ // macho_file.platform.version,
+ // platform.version,
+ // });
+ // return error.InvalidTarget;
+ // }
+ }
}
-fn addSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
- if (self.symbols.contains(sym_name)) return;
- try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
-}
+const TrieIterator = struct {
+ data: []const u8,
+ pos: usize = 0,
-fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
- if (self.symbols.contains(sym_name)) return;
- try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true);
-}
+ fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) {
+ return std.io.fixedBufferStream(it.data[it.pos..]);
+ }
-pub const TargetMatcher = struct {
- allocator: Allocator,
- cpu_arch: std.Target.Cpu.Arch,
- os_tag: std.Target.Os.Tag,
- abi: std.Target.Abi,
- target_strings: std.ArrayListUnmanaged([]const u8) = .{},
+ fn readULEB128(it: *TrieIterator) !u64 {
+ var stream = it.getStream();
+ var creader = std.io.countingReader(stream.reader());
+ const reader = creader.reader();
+ const value = try std.leb.readULEB128(u64, reader);
+ it.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow;
+ return value;
+ }
- pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher {
- var self = TargetMatcher{
- .allocator = allocator,
- .cpu_arch = target.cpu.arch,
- .os_tag = target.os.tag,
- .abi = target.abi,
- };
- const apple_string = try toAppleTargetTriple(allocator, self.cpu_arch, self.os_tag, self.abi);
- try self.target_strings.append(allocator, apple_string);
+ fn readString(it: *TrieIterator) ![:0]const u8 {
+ var stream = it.getStream();
+ const reader = stream.reader();
- if (self.abi == .simulator) {
- // For Apple simulator targets, linking gets tricky as we need to link against the simulator
- // hosts dylibs too.
- const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none);
- try self.target_strings.append(allocator, host_target);
+ var count: usize = 0;
+ while (true) : (count += 1) {
+ const byte = try reader.readByte();
+ if (byte == 0) break;
}
- return self;
+ const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0];
+ it.pos += count + 1;
+ return str;
}
- pub fn deinit(self: *TargetMatcher) void {
- for (self.target_strings.items) |t| {
- self.allocator.free(t);
- }
- self.target_strings.deinit(self.allocator);
+ fn readByte(it: *TrieIterator) !u8 {
+ var stream = it.getStream();
+ const value = try stream.reader().readByte();
+ it.pos += 1;
+ return value;
}
+};
- inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 {
- return switch (cpu_arch) {
- .aarch64 => "arm64",
- .x86_64 => "x86_64",
- else => unreachable,
- };
- }
+pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void {
+ try self.exports.append(allocator, .{
+ .name = try self.insertString(allocator, name),
+ .flags = flags,
+ });
+}
- inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 {
- return switch (abi) {
- .none => null,
- .simulator => "simulator",
- .macabi => "maccatalyst",
- else => unreachable,
+fn parseTrieNode(
+ self: *Dylib,
+ it: *TrieIterator,
+ allocator: Allocator,
+ arena: Allocator,
+ prefix: []const u8,
+) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const size = try it.readULEB128();
+ if (size > 0) {
+ const flags = try it.readULEB128();
+ const kind = flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK;
+ const out_flags = Export.Flags{
+ .abs = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE,
+ .tlv = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL,
+ .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
};
- }
-
- pub fn toAppleTargetTriple(
- allocator: Allocator,
- cpu_arch: std.Target.Cpu.Arch,
- os_tag: std.Target.Os.Tag,
- abi: std.Target.Abi,
- ) ![]const u8 {
- const cpu_arch_s = fmtCpuArch(cpu_arch);
- const os_tag_s = @tagName(os_tag);
- if (fmtAbi(abi)) |abi_s| {
- return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch_s, os_tag_s, abi_s });
+ if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) {
+ _ = try it.readULEB128(); // dylib ordinal
+ const name = try it.readString();
+ try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags);
+ } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) {
+ _ = try it.readULEB128(); // stub offset
+ _ = try it.readULEB128(); // resolver offset
+ try self.addExport(allocator, prefix, out_flags);
+ } else {
+ _ = try it.readULEB128(); // VM offset
+ try self.addExport(allocator, prefix, out_flags);
}
- return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s });
}
- fn hasValue(stack: []const []const u8, needle: []const u8) bool {
- for (stack) |v| {
- if (mem.eql(u8, v, needle)) return true;
- }
- return false;
- }
+ const nedges = try it.readByte();
- pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
- for (self.target_strings.items) |t| {
- if (hasValue(targets, t)) return true;
- }
- return false;
+ for (0..nedges) |_| {
+ const label = try it.readString();
+ const off = try it.readULEB128();
+ const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
+ const curr = it.pos;
+ it.pos = math.cast(usize, off) orelse return error.Overflow;
+ try self.parseTrieNode(it, allocator, arena, prefix_label);
+ it.pos = curr;
}
+}
- fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
- return hasValue(archs, fmtCpuArch(self.cpu_arch));
- }
-};
+fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ var it: TrieIterator = .{ .data = data };
+ try self.parseTrieNode(&it, gpa, arena.allocator(), "");
+}
-pub fn parseFromStub(
+pub fn parseTbd(
self: *Dylib,
- allocator: Allocator,
- target: std.Target,
+ cpu_arch: std.Target.Cpu.Arch,
+ platform: MachO.Platform,
lib_stub: LibStub,
- dylib_id: u16,
- dependent_libs: anytype,
- name: []const u8,
+ macho_file: *MachO,
) !void {
- if (lib_stub.inner.len == 0) return error.NotLibStub;
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
- log.debug("parsing shared library from stub '{s}'", .{name});
+ log.debug("parsing dylib from stub", .{});
const umbrella_lib = lib_stub.inner[0];
{
- var id = try Id.default(allocator, umbrella_lib.installName());
+ var id = try Id.default(gpa, umbrella_lib.installName());
if (umbrella_lib.currentVersion()) |version| {
try id.parseCurrentVersion(version);
}
@@ -344,21 +256,18 @@ pub fn parseFromStub(
self.id = id;
}
- var umbrella_libs = std.StringHashMap(void).init(allocator);
+ var umbrella_libs = std.StringHashMap(void).init(gpa);
defer umbrella_libs.deinit();
log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
- var matcher = try TargetMatcher.init(allocator, target);
+ self.platform = platform;
+
+ var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.toApplePlatform());
defer matcher.deinit();
for (lib_stub.inner, 0..) |elem, stub_index| {
- const targets = try elem.targets(allocator);
- defer {
- for (targets) |t| allocator.free(t);
- allocator.free(targets);
- }
- if (!matcher.matchesTarget(targets)) continue;
+ if (!(try matcher.matchesTargetTbd(elem))) continue;
if (stub_index > 0) {
// TODO I thought that we could switch on presence of `parent-umbrella` map;
@@ -375,43 +284,42 @@ pub fn parseFromStub(
if (exp.symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{});
}
}
if (exp.weak_symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addWeakSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{ .weak = true });
}
}
if (exp.objc_classes) |objc_classes| {
for (objc_classes) |class_name| {
- try self.addObjCClassSymbol(allocator, class_name);
+ try self.addObjCClass(gpa, class_name);
}
}
if (exp.objc_ivars) |objc_ivars| {
for (objc_ivars) |ivar| {
- try self.addObjCIVarSymbol(allocator, ivar);
+ try self.addObjCIVar(gpa, ivar);
}
}
if (exp.objc_eh_types) |objc_eh_types| {
for (objc_eh_types) |eht| {
- try self.addObjCEhTypeSymbol(allocator, eht);
+ try self.addObjCEhType(gpa, eht);
}
}
- // TODO track which libs were already parsed in different steps
if (exp.re_exports) |re_exports| {
for (re_exports) |lib| {
if (umbrella_libs.contains(lib)) continue;
log.debug(" (found re-export '{s}')", .{lib});
- const dep_id = try Id.default(allocator, lib);
- try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });
+ const dep_id = try Id.default(gpa, lib);
+ try self.dependents.append(gpa, dep_id);
}
}
}
@@ -424,31 +332,31 @@ pub fn parseFromStub(
if (exp.symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{});
}
}
if (exp.weak_symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addWeakSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{ .weak = true });
}
}
if (exp.objc_classes) |classes| {
for (classes) |sym_name| {
- try self.addObjCClassSymbol(allocator, sym_name);
+ try self.addObjCClass(gpa, sym_name);
}
}
if (exp.objc_ivars) |objc_ivars| {
for (objc_ivars) |ivar| {
- try self.addObjCIVarSymbol(allocator, ivar);
+ try self.addObjCIVar(gpa, ivar);
}
}
if (exp.objc_eh_types) |objc_eh_types| {
for (objc_eh_types) |eht| {
- try self.addObjCEhTypeSymbol(allocator, eht);
+ try self.addObjCEhType(gpa, eht);
}
}
}
@@ -460,31 +368,31 @@ pub fn parseFromStub(
if (reexp.symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{});
}
}
if (reexp.weak_symbols) |symbols| {
for (symbols) |sym_name| {
- try self.addWeakSymbol(allocator, sym_name);
+ try self.addExport(gpa, sym_name, .{ .weak = true });
}
}
if (reexp.objc_classes) |classes| {
for (classes) |sym_name| {
- try self.addObjCClassSymbol(allocator, sym_name);
+ try self.addObjCClass(gpa, sym_name);
}
}
if (reexp.objc_ivars) |objc_ivars| {
for (objc_ivars) |ivar| {
- try self.addObjCIVarSymbol(allocator, ivar);
+ try self.addObjCIVar(gpa, ivar);
}
}
if (reexp.objc_eh_types) |objc_eh_types| {
for (objc_eh_types) |eht| {
- try self.addObjCEhTypeSymbol(allocator, eht);
+ try self.addObjCEhType(gpa, eht);
}
}
}
@@ -492,19 +400,19 @@ pub fn parseFromStub(
if (stub.objc_classes) |classes| {
for (classes) |sym_name| {
- try self.addObjCClassSymbol(allocator, sym_name);
+ try self.addObjCClass(gpa, sym_name);
}
}
if (stub.objc_ivars) |objc_ivars| {
for (objc_ivars) |ivar| {
- try self.addObjCIVarSymbol(allocator, ivar);
+ try self.addObjCIVar(gpa, ivar);
}
}
if (stub.objc_eh_types) |objc_eh_types| {
for (objc_eh_types) |eht| {
- try self.addObjCEhTypeSymbol(allocator, eht);
+ try self.addObjCEhType(gpa, eht);
}
}
},
@@ -514,10 +422,9 @@ pub fn parseFromStub(
// For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
// re-exports directly in the stub file.
for (lib_stub.inner) |elem| {
- if (elem == .v3) break;
+ if (elem == .v3) continue;
const stub = elem.v4;
- // TODO track which libs were already parsed in different steps
if (stub.reexported_libraries) |reexports| {
for (reexports) |reexp| {
if (!matcher.matchesTarget(reexp.targets)) continue;
@@ -527,30 +434,437 @@ pub fn parseFromStub(
log.debug(" (found re-export '{s}')", .{lib});
- const dep_id = try Id.default(allocator, lib);
- try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });
+ const dep_id = try Id.default(gpa, lib);
+ try self.dependents.append(gpa, dep_id);
}
}
}
}
}
-const Dylib = @This();
+fn addObjCClass(self: *Dylib, allocator: Allocator, name: []const u8) !void {
+ try self.addObjCExport(allocator, "_OBJC_CLASS_", name);
+ try self.addObjCExport(allocator, "_OBJC_METACLASS_", name);
+}
+
+fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void {
+ try self.addObjCExport(allocator, "_OBJC_IVAR_", name);
+}
+
+fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void {
+ try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name);
+}
+
+fn addObjCExport(
+ self: *Dylib,
+ allocator: Allocator,
+ comptime prefix: []const u8,
+ name: []const u8,
+) !void {
+ const full_name = try std.fmt.allocPrint(allocator, prefix ++ "$_{s}", .{name});
+ defer allocator.free(full_name);
+ try self.addExport(allocator, full_name, .{});
+}
+
+pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+
+ try self.symbols.ensureTotalCapacityPrecise(gpa, self.exports.items(.name).len);
+
+ for (self.exports.items(.name)) |noff| {
+ const name = self.getString(noff);
+ const off = try macho_file.strings.insert(gpa, name);
+ const gop = try macho_file.getOrCreateGlobal(off);
+ self.symbols.addOneAssumeCapacity().* = gop.index;
+ }
+}
+
+fn initPlatform(self: *Dylib) void {
+ var it = LoadCommandIterator{
+ .ncmds = self.header.?.ncmds,
+ .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
+ };
+ self.platform = while (it.next()) |cmd| {
+ switch (cmd.cmd()) {
+ .BUILD_VERSION,
+ .VERSION_MIN_MACOSX,
+ .VERSION_MIN_IPHONEOS,
+ .VERSION_MIN_TVOS,
+ .VERSION_MIN_WATCHOS,
+ => break MachO.Platform.fromLoadCommand(cmd),
+ else => {},
+ }
+ } else null;
+}
+
+pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ if (!self.explicit and !self.hoisted) return;
+
+ for (self.symbols.items, self.exports.items(.flags)) |index, flags| {
+ const global = macho_file.getSymbol(index);
+ if (self.asFile().getSymbolRank(.{
+ .weak = flags.weak,
+ }) < global.getSymbolRank(macho_file)) {
+ global.value = 0;
+ global.atom = 0;
+ global.nlist_idx = 0;
+ global.file = self.index;
+ global.flags.weak = flags.weak;
+ global.flags.weak_ref = false;
+ global.flags.tlv = flags.tlv;
+ global.flags.dyn_ref = false;
+ global.flags.tentative = false;
+ global.visibility = .global;
+ }
+ }
+}
+
+pub fn resetGlobals(self: *Dylib, macho_file: *MachO) void {
+ for (self.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ const name = sym.name;
+ sym.* = .{};
+ sym.name = name;
+ }
+}
+
+pub fn isAlive(self: Dylib, macho_file: *MachO) bool {
+ if (!macho_file.dead_strip_dylibs) return self.explicit or self.referenced or self.needed;
+ return self.referenced or self.needed;
+}
+
+pub fn markReferenced(self: *Dylib, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items) |global_index| {
+ const global = macho_file.getSymbol(global_index);
+ const file_ptr = global.getFile(macho_file) orelse continue;
+ if (file_ptr.getIndex() != self.index) continue;
+ if (global.isLocal()) continue;
+ self.referenced = true;
+ break;
+ }
+}
+
+pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items) |global_index| {
+ const global = macho_file.getSymbol(global_index);
+ const file_ptr = global.getFile(macho_file) orelse continue;
+ if (file_ptr.getIndex() != self.index) continue;
+ if (global.isLocal()) continue;
+ assert(global.flags.import);
+ global.flags.output_symtab = true;
+ try global.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
+ self.output_symtab_ctx.nimports += 1;
+ self.output_symtab_ctx.strsize += @as(u32, @intCast(global.getName(macho_file).len + 1));
+ }
+}
+
+pub fn writeSymtab(self: Dylib, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items) |global_index| {
+ const global = macho_file.getSymbol(global_index);
+ const file = global.getFile(macho_file) orelse continue;
+ if (file.getIndex() != self.index) continue;
+ const idx = global.getOutputSymtabIndex(macho_file) orelse continue;
+ const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
+ macho_file.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
+ macho_file.strtab.appendAssumeCapacity(0);
+ const out_sym = &macho_file.symtab.items[idx];
+ out_sym.n_strx = n_strx;
+ global.setOutputSym(macho_file, out_sym);
+ }
+}
+
+pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib {
+ return macho_file.getFile(self.umbrella).?.dylib;
+}
+
+fn getLoadCommand(self: Dylib, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
+ var it = LoadCommandIterator{
+ .ncmds = self.header.?.ncmds,
+ .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
+ };
+ while (it.next()) |cmd| {
+ if (cmd.cmd() == lc) return cmd;
+ } else return null;
+}
+
+fn insertString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 {
+ const off = @as(u32, @intCast(self.strtab.items.len));
+ try self.strtab.writer(allocator).print("{s}\x00", .{name});
+ return off;
+}
+
+pub inline fn getString(self: Dylib, off: u32) [:0]const u8 {
+ assert(off < self.strtab.items.len);
+ return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
+}
+
+pub fn asFile(self: *Dylib) File {
+ return .{ .dylib = self };
+}
+
+pub fn format(
+ self: *Dylib,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = self;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format dylib directly");
+}
+
+pub fn fmtSymtab(self: *Dylib, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
+ return .{ .data = .{
+ .dylib = self,
+ .macho_file = macho_file,
+ } };
+}
+
+const FormatContext = struct {
+ dylib: *Dylib,
+ macho_file: *MachO,
+};
+
+fn formatSymtab(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const dylib = ctx.dylib;
+ try writer.writeAll(" globals\n");
+ for (dylib.symbols.items) |index| {
+ const global = ctx.macho_file.getSymbol(index);
+ try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
+ }
+}
+
+pub const TargetMatcher = struct {
+ allocator: Allocator,
+ cpu_arch: std.Target.Cpu.Arch,
+ platform: macho.PLATFORM,
+ target_strings: std.ArrayListUnmanaged([]const u8) = .{},
+
+ pub fn init(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) !TargetMatcher {
+ var self = TargetMatcher{
+ .allocator = allocator,
+ .cpu_arch = cpu_arch,
+ .platform = platform,
+ };
+ const apple_string = try targetToAppleString(allocator, cpu_arch, platform);
+ try self.target_strings.append(allocator, apple_string);
+
+ switch (platform) {
+ .IOSSIMULATOR, .TVOSSIMULATOR, .WATCHOSSIMULATOR => {
+ // For Apple simulator targets, linking gets tricky as we need to link against the simulator
+ // hosts dylibs too.
+ const host_target = try targetToAppleString(allocator, cpu_arch, .MACOS);
+ try self.target_strings.append(allocator, host_target);
+ },
+ else => {},
+ }
+
+ return self;
+ }
+
+ pub fn deinit(self: *TargetMatcher) void {
+ for (self.target_strings.items) |t| {
+ self.allocator.free(t);
+ }
+ self.target_strings.deinit(self.allocator);
+ }
+
+ inline fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 {
+ return switch (cpu_arch) {
+ .aarch64 => "arm64",
+ .x86_64 => "x86_64",
+ else => unreachable,
+ };
+ }
+
+ pub fn targetToAppleString(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) ![]const u8 {
+ const arch = cpuArchToAppleString(cpu_arch);
+ const plat = switch (platform) {
+ .MACOS => "macos",
+ .IOS => "ios",
+ .TVOS => "tvos",
+ .WATCHOS => "watchos",
+ .IOSSIMULATOR => "ios-simulator",
+ .TVOSSIMULATOR => "tvos-simulator",
+ .WATCHOSSIMULATOR => "watchos-simulator",
+ .BRIDGEOS => "bridgeos",
+ .MACCATALYST => "maccatalyst",
+ .DRIVERKIT => "driverkit",
+ else => unreachable,
+ };
+ return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, plat });
+ }
+
+ fn hasValue(stack: []const []const u8, needle: []const u8) bool {
+ for (stack) |v| {
+ if (mem.eql(u8, v, needle)) return true;
+ }
+ return false;
+ }
+
+ fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
+ return hasValue(archs, cpuArchToAppleString(self.cpu_arch));
+ }
+
+ fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
+ for (self.target_strings.items) |t| {
+ if (hasValue(targets, t)) return true;
+ }
+ return false;
+ }
+
+ pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
+ var arena = std.heap.ArenaAllocator.init(self.allocator);
+ defer arena.deinit();
+
+ const targets = switch (tbd) {
+ .v3 => |v3| blk: {
+ var targets = std.ArrayList([]const u8).init(arena.allocator());
+ for (v3.archs) |arch| {
+ const target = try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform });
+ try targets.append(target);
+ }
+ break :blk targets.items;
+ },
+ .v4 => |v4| v4.targets,
+ };
+
+ return self.matchesTarget(targets);
+ }
+};
+
+pub const Id = struct {
+ name: []const u8,
+ timestamp: u32,
+ current_version: u32,
+ compatibility_version: u32,
+
+ pub fn default(allocator: Allocator, name: []const u8) !Id {
+ return Id{
+ .name = try allocator.dupe(u8, name),
+ .timestamp = 2,
+ .current_version = 0x10000,
+ .compatibility_version = 0x10000,
+ };
+ }
+
+ pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
+ return Id{
+ .name = try allocator.dupe(u8, name),
+ .timestamp = lc.dylib.timestamp,
+ .current_version = lc.dylib.current_version,
+ .compatibility_version = lc.dylib.compatibility_version,
+ };
+ }
+
+ pub fn deinit(id: Id, allocator: Allocator) void {
+ allocator.free(id.name);
+ }
+
+ pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
+
+ pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
+ id.current_version = try parseVersion(version);
+ }
+
+ pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
+ id.compatibility_version = try parseVersion(version);
+ }
+
+ fn parseVersion(version: anytype) ParseError!u32 {
+ const string = blk: {
+ switch (version) {
+ .int => |int| {
+ var out: u32 = 0;
+ const major = math.cast(u16, int) orelse return error.Overflow;
+ out += @as(u32, @intCast(major)) << 16;
+ return out;
+ },
+ .float => |float| {
+ var buf: [256]u8 = undefined;
+ break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
+ },
+ .string => |string| {
+ break :blk string;
+ },
+ }
+ };
+
+ var out: u32 = 0;
+ var values: [3][]const u8 = undefined;
+
+ var split = mem.split(u8, string, ".");
+ var count: u4 = 0;
+ while (split.next()) |value| {
+ if (count > 2) {
+ log.debug("malformed version field: {s}", .{string});
+ return 0x10000;
+ }
+ values[count] = value;
+ count += 1;
+ }
+
+ if (count > 2) {
+ out += try fmt.parseInt(u8, values[2], 10);
+ }
+ if (count > 1) {
+ out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;
+ }
+ out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
+
+ return out;
+ }
+};
+
+const Export = struct {
+ name: u32,
+ flags: Flags,
+
+ const Flags = packed struct {
+ abs: bool = false,
+ weak: bool = false,
+ tlv: bool = false,
+ };
+};
-const std = @import("std");
const assert = std.debug.assert;
+const fat = @import("fat.zig");
const fs = std.fs;
const fmt = std.fmt;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
-const fat = @import("fat.zig");
const tapi = @import("../tapi.zig");
+const trace = @import("../../tracy.zig").trace;
+const std = @import("std");
const Allocator = mem.Allocator;
+const Dylib = @This();
+const File = @import("file.zig").File;
const LibStub = tapi.LibStub;
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
-const Platform = @import("load_commands.zig").Platform;
+const Symbol = @import("Symbol.zig");
const Tbd = tapi.Tbd;
diff --git a/src/link/MachO/InternalObject.zig b/src/link/MachO/InternalObject.zig
new file mode 100644
index 0000000000..88663c2e37
--- /dev/null
+++ b/src/link/MachO/InternalObject.zig
@@ -0,0 +1,249 @@
+index: File.Index,
+
+sections: std.MultiArrayList(Section) = .{},
+atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+objc_methnames: std.ArrayListUnmanaged(u8) = .{},
+objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
+
+output_symtab_ctx: MachO.SymtabCtx = .{},
+
+pub fn deinit(self: *InternalObject, allocator: Allocator) void {
+ for (self.sections.items(.relocs)) |*relocs| {
+ relocs.deinit(allocator);
+ }
+ self.sections.deinit(allocator);
+ self.atoms.deinit(allocator);
+ self.symbols.deinit(allocator);
+ self.objc_methnames.deinit(allocator);
+}
+
+pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index {
+ const gpa = macho_file.base.comp.gpa;
+ try self.symbols.ensureUnusedCapacity(gpa, 1);
+ const off = try macho_file.strings.insert(gpa, name);
+ const gop = try macho_file.getOrCreateGlobal(off);
+ self.symbols.addOneAssumeCapacity().* = gop.index;
+ const sym = macho_file.getSymbol(gop.index);
+ sym.* = .{ .name = off, .file = self.index };
+ return gop.index;
+}
+
+/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
+pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 {
+ const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);
+ return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file);
+}
+
+fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const atom_index = try macho_file.addAtom();
+ try self.atoms.append(gpa, atom_index);
+
+ const name = try std.fmt.allocPrintZ(gpa, "__TEXT$__objc_methname${s}", .{methname});
+ defer gpa.free(name);
+ const atom = macho_file.getAtom(atom_index).?;
+ atom.atom_index = atom_index;
+ atom.name = try macho_file.strings.insert(gpa, name);
+ atom.file = self.index;
+ atom.size = methname.len + 1;
+ atom.alignment = .@"1";
+
+ const n_sect = try self.addSection(gpa, "__TEXT", "__objc_methname");
+ const sect = &self.sections.items(.header)[n_sect];
+ sect.flags = macho.S_CSTRING_LITERALS;
+ sect.size = atom.size;
+ sect.@"align" = 0;
+ atom.n_sect = n_sect;
+ self.sections.items(.extra)[n_sect].is_objc_methname = true;
+
+ sect.offset = @intCast(self.objc_methnames.items.len);
+ try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1);
+ self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable;
+
+ return atom_index;
+}
+
+fn addObjcSelrefsSection(
+ self: *InternalObject,
+ methname: []const u8,
+ methname_atom_index: Atom.Index,
+ macho_file: *MachO,
+) !Atom.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const atom_index = try macho_file.addAtom();
+ try self.atoms.append(gpa, atom_index);
+
+ const name = try std.fmt.allocPrintZ(gpa, "__DATA$__objc_selrefs${s}", .{methname});
+ defer gpa.free(name);
+ const atom = macho_file.getAtom(atom_index).?;
+ atom.atom_index = atom_index;
+ atom.name = try macho_file.strings.insert(gpa, name);
+ atom.file = self.index;
+ atom.size = @sizeOf(u64);
+ atom.alignment = .@"8";
+
+ const n_sect = try self.addSection(gpa, "__DATA", "__objc_selrefs");
+ const sect = &self.sections.items(.header)[n_sect];
+ sect.flags = macho.S_LITERAL_POINTERS | macho.S_ATTR_NO_DEAD_STRIP;
+ sect.offset = 0;
+ sect.size = atom.size;
+ sect.@"align" = 3;
+ atom.n_sect = n_sect;
+ self.sections.items(.extra)[n_sect].is_objc_selref = true;
+
+ const relocs = &self.sections.items(.relocs)[n_sect];
+ try relocs.ensureUnusedCapacity(gpa, 1);
+ relocs.appendAssumeCapacity(.{
+ .tag = .local,
+ .offset = 0,
+ .target = methname_atom_index,
+ .addend = 0,
+ .type = .unsigned,
+ .meta = .{
+ .pcrel = false,
+ .length = 3,
+ .symbolnum = 0, // Only used when synthesising unwind records so can be anything
+ .has_subtractor = false,
+ },
+ });
+ atom.relocs = .{ .pos = 0, .len = 1 };
+
+ return atom_index;
+}
+
+pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
+ for (self.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
+ sym.flags.output_symtab = true;
+ if (sym.isLocal()) {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
+ self.output_symtab_ctx.nlocals += 1;
+ } else if (sym.flags.@"export") {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
+ self.output_symtab_ctx.nexports += 1;
+ } else {
+ assert(sym.flags.import);
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
+ self.output_symtab_ctx.nimports += 1;
+ }
+ self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
+ }
+}
+
+pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void {
+ for (self.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
+ const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
+ const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
+ macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+ macho_file.strtab.appendAssumeCapacity(0);
+ const out_sym = &macho_file.symtab.items[idx];
+ out_sym.n_strx = n_strx;
+ sym.setOutputSym(macho_file, out_sym);
+ }
+}
+
+fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
+ const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
+ self.sections.set(n_sect, .{
+ .header = .{
+ .sectname = MachO.makeStaticString(sectname),
+ .segname = MachO.makeStaticString(segname),
+ },
+ });
+ return n_sect;
+}
+
+pub fn getSectionData(self: *const InternalObject, index: u32) []const u8 {
+ const slice = self.sections.slice();
+ assert(index < slice.items(.header).len);
+ const sect = slice.items(.header)[index];
+ const extra = slice.items(.extra)[index];
+ if (extra.is_objc_methname) {
+ return self.objc_methnames.items[sect.offset..][0..sect.size];
+ } else if (extra.is_objc_selref) {
+ return &self.objc_selrefs;
+ } else @panic("ref to non-existent section");
+}
+
+pub fn asFile(self: *InternalObject) File {
+ return .{ .internal = self };
+}
+
+const FormatContext = struct {
+ self: *InternalObject,
+ macho_file: *MachO,
+};
+
+pub fn fmtAtoms(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
+ return .{ .data = .{
+ .self = self,
+ .macho_file = macho_file,
+ } };
+}
+
+fn formatAtoms(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.writeAll(" atoms\n");
+ for (ctx.self.atoms.items) |atom_index| {
+ const atom = ctx.macho_file.getAtom(atom_index).?;
+ try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
+ }
+}
+
+pub fn fmtSymtab(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
+ return .{ .data = .{
+ .self = self,
+ .macho_file = macho_file,
+ } };
+}
+
+fn formatSymtab(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.writeAll(" symbols\n");
+ for (ctx.self.symbols.items) |index| {
+ const global = ctx.macho_file.getSymbol(index);
+ try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
+ }
+}
+
+const Section = struct {
+ header: macho.section_64,
+ relocs: std.ArrayListUnmanaged(Relocation) = .{},
+ extra: Extra = .{},
+
+ const Extra = packed struct {
+ is_objc_methname: bool = false,
+ is_objc_selref: bool = false,
+ };
+};
+
+const assert = std.debug.assert;
+const macho = std.macho;
+const mem = std.mem;
+const std = @import("std");
+
+const Allocator = std.mem.Allocator;
+const Atom = @import("Atom.zig");
+const File = @import("file.zig").File;
+const InternalObject = @This();
+const MachO = @import("../MachO.zig");
+const Object = @import("Object.zig");
+const Relocation = @import("Relocation.zig");
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig
index ad069b845e..9aecf0a78e 100644
--- a/src/link/MachO/Object.zig
+++ b/src/link/MachO/Object.zig
@@ -1,1130 +1,2183 @@
-//! Represents an input relocatable Object file.
-//! Each Object is fully loaded into memory for easier
-//! access into different data within.
-
-name: []const u8,
+archive: ?[]const u8 = null,
+path: []const u8,
mtime: u64,
-contents: []align(@alignOf(u64)) const u8,
-
-header: macho.mach_header_64 = undefined,
-
-/// Symtab and strtab might not exist for empty object files so we use an optional
-/// to signal this.
-in_symtab: ?[]align(1) const macho.nlist_64 = null,
-in_strtab: ?[]const u8 = null,
-
-/// Output symtab is sorted so that we can easily reference symbols following each
-/// other in address space.
-/// The length of the symtab is at least of the input symtab length however there
-/// can be trailing section symbols.
-symtab: []macho.nlist_64 = undefined,
-/// Can be undefined as set together with in_symtab.
-source_symtab_lookup: []u32 = undefined,
-/// Can be undefined as set together with in_symtab.
-reverse_symtab_lookup: []u32 = undefined,
-/// Can be undefined as set together with in_symtab.
-source_address_lookup: []i64 = undefined,
-/// Can be undefined as set together with in_symtab.
-source_section_index_lookup: []Entry = undefined,
-/// Can be undefined as set together with in_symtab.
-strtab_lookup: []u32 = undefined,
-/// Can be undefined as set together with in_symtab.
-atom_by_index_table: []?Atom.Index = undefined,
-/// Can be undefined as set together with in_symtab.
-globals_lookup: []i64 = undefined,
-/// Can be undefined as set together with in_symtab.
-relocs_lookup: []Entry = undefined,
-
-/// All relocations sorted and flatened, sorted by address descending
-/// per section.
-relocations: std.ArrayListUnmanaged(macho.relocation_info) = .{},
-/// Beginning index to the relocations array for each input section
-/// defined within this Object file.
-section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{},
-
-/// Data-in-code records sorted by address.
-data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
+data: []const u8,
+index: File.Index,
+
+header: ?macho.mach_header_64 = null,
+sections: std.MultiArrayList(Section) = .{},
+symtab: std.MultiArrayList(Nlist) = .{},
+strtab: []const u8 = &[0]u8{},
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
-exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
-eh_frame_sect_id: ?u8 = null,
-eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
-eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
+platform: ?MachO.Platform = null,
+dwarf_info: ?DwarfInfo = null,
+stab_files: std.ArrayListUnmanaged(StabFile) = .{},
-unwind_info_sect_id: ?u8 = null,
-unwind_relocs_lookup: []Record = undefined,
-unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
+eh_frame_sect_index: ?u8 = null,
+compact_unwind_sect_index: ?u8 = null,
+cies: std.ArrayListUnmanaged(Cie) = .{},
+fdes: std.ArrayListUnmanaged(Fde) = .{},
+eh_frame_data: std.ArrayListUnmanaged(u8) = .{},
+unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{},
-const Entry = struct {
- start: u32 = 0,
- len: u32 = 0,
-};
+alive: bool = true,
+hidden: bool = false,
-const Record = struct {
- dead: bool,
- reloc: Entry,
-};
+dynamic_relocs: MachO.DynamicRelocs = .{},
+output_symtab_ctx: MachO.SymtabCtx = .{},
-pub fn isObject(file: std.fs.File) bool {
- const reader = file.reader();
- const hdr = reader.readStruct(macho.mach_header_64) catch return false;
- defer file.seekTo(0) catch {};
- return hdr.filetype == macho.MH_OBJECT;
+pub fn isObject(path: []const u8) !bool {
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
+ const header = file.reader().readStruct(macho.mach_header_64) catch return false;
+ return header.filetype == macho.MH_OBJECT;
}
-pub fn deinit(self: *Object, gpa: Allocator) void {
- self.atoms.deinit(gpa);
- self.exec_atoms.deinit(gpa);
- gpa.free(self.name);
- gpa.free(self.contents);
- if (self.in_symtab) |_| {
- gpa.free(self.source_symtab_lookup);
- gpa.free(self.reverse_symtab_lookup);
- gpa.free(self.source_address_lookup);
- gpa.free(self.source_section_index_lookup);
- gpa.free(self.strtab_lookup);
- gpa.free(self.symtab);
- gpa.free(self.atom_by_index_table);
- gpa.free(self.globals_lookup);
- gpa.free(self.relocs_lookup);
+pub fn deinit(self: *Object, allocator: Allocator) void {
+ for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
+ relocs.deinit(allocator);
+ sub.deinit(allocator);
}
- self.eh_frame_relocs_lookup.deinit(gpa);
- self.eh_frame_records_lookup.deinit(gpa);
- if (self.hasUnwindRecords()) {
- gpa.free(self.unwind_relocs_lookup);
+ self.sections.deinit(allocator);
+ self.symtab.deinit(allocator);
+ self.symbols.deinit(allocator);
+ self.atoms.deinit(allocator);
+ self.cies.deinit(allocator);
+ self.fdes.deinit(allocator);
+ 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.unwind_records_lookup.deinit(gpa);
- self.relocations.deinit(gpa);
- self.section_relocs_lookup.deinit(gpa);
- self.data_in_code.deinit(gpa);
+ self.stab_files.deinit(allocator);
+ allocator.free(self.data);
}
-pub fn parse(self: *Object, allocator: Allocator) !void {
- var stream = std.io.fixedBufferStream(self.contents);
+pub fn parse(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ var stream = std.io.fixedBufferStream(self.data);
const reader = stream.reader();
self.header = try reader.readStruct(macho.mach_header_64);
- var it = LoadCommandIterator{
- .ncmds = self.header.ncmds,
- .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
+ const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
+ macho.CPU_TYPE_ARM64 => .aarch64,
+ macho.CPU_TYPE_X86_64 => .x86_64,
+ else => |x| {
+ try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
+ return error.InvalidCpuArch;
+ },
};
- const nsects = self.getSourceSections().len;
-
- // Prepopulate relocations per section lookup table.
- try self.section_relocs_lookup.resize(allocator, nsects);
- @memset(self.section_relocs_lookup.items, 0);
-
- // Parse symtab.
- const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
- .SYMTAB => break cmd.cast(macho.symtab_command).?,
- else => {},
- } else return;
-
- self.in_symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.contents.ptr + symtab.symoff))[0..symtab.nsyms];
- self.in_strtab = self.contents[symtab.stroff..][0..symtab.strsize];
-
- self.symtab = try allocator.alloc(macho.nlist_64, self.in_symtab.?.len + nsects);
- self.source_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
- self.reverse_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
- self.strtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
- self.globals_lookup = try allocator.alloc(i64, self.in_symtab.?.len);
- self.atom_by_index_table = try allocator.alloc(?Atom.Index, self.in_symtab.?.len + nsects);
- self.relocs_lookup = try allocator.alloc(Entry, self.in_symtab.?.len + nsects);
- // This is wasteful but we need to be able to lookup source symbol address after stripping and
- // allocating of sections.
- self.source_address_lookup = try allocator.alloc(i64, self.in_symtab.?.len);
- self.source_section_index_lookup = try allocator.alloc(Entry, nsects);
-
- for (self.symtab) |*sym| {
- sym.* = .{
- .n_value = 0,
- .n_sect = 0,
- .n_desc = 0,
- .n_strx = 0,
- .n_type = 0,
- };
- }
-
- @memset(self.globals_lookup, -1);
- @memset(self.atom_by_index_table, null);
- @memset(self.source_section_index_lookup, .{});
- @memset(self.relocs_lookup, .{});
-
- // You would expect that the symbol table is at least pre-sorted based on symbol's type:
- // local < extern defined < undefined. Unfortunately, this is not guaranteed! For instance,
- // the GO compiler does not necessarily respect that therefore we sort immediately by type
- // and address within.
- var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(allocator, self.in_symtab.?.len);
- defer sorted_all_syms.deinit();
-
- for (self.in_symtab.?, 0..) |_, index| {
- sorted_all_syms.appendAssumeCapacity(.{ .index = @as(u32, @intCast(index)) });
+ if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
+ try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
+ return error.InvalidCpuArch;
}
- // We sort by type: defined < undefined, and
- // afterwards by address in each group. Normally, dysymtab should
- // be enough to guarantee the sort, but turns out not every compiler
- // is kind enough to specify the symbols in the correct order.
- mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan);
-
- var prev_sect_id: u8 = 0;
- var section_index_lookup: ?Entry = null;
- for (sorted_all_syms.items, 0..) |sym_id, i| {
- const sym = sym_id.getSymbol(self);
-
- if (section_index_lookup) |*lookup| {
- if (sym.n_sect != prev_sect_id or sym.undf()) {
- self.source_section_index_lookup[prev_sect_id - 1] = lookup.*;
- section_index_lookup = null;
- } else {
- lookup.len += 1;
+ if (self.getLoadCommand(.SEGMENT_64)) |lc| {
+ const sections = lc.getSections();
+ try self.sections.ensureUnusedCapacity(gpa, sections.len);
+ for (sections) |sect| {
+ const index = try self.sections.addOne(gpa);
+ self.sections.set(index, .{ .header = sect });
+
+ if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
+ self.eh_frame_sect_index = @intCast(index);
+ } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) {
+ self.compact_unwind_sect_index = @intCast(index);
}
}
- if (sym.sect() and section_index_lookup == null) {
- section_index_lookup = .{ .start = @as(u32, @intCast(i)), .len = 1 };
+ }
+ if (self.getLoadCommand(.SYMTAB)) |lc| {
+ const cmd = lc.cast(macho.symtab_command).?;
+ self.strtab = self.data[cmd.stroff..][0..cmd.strsize];
+
+ const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.data.ptr + cmd.symoff))[0..cmd.nsyms];
+ try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
+ for (symtab) |nlist| {
+ self.symtab.appendAssumeCapacity(.{
+ .nlist = nlist,
+ .atom = 0,
+ .size = 0,
+ });
}
+ }
- prev_sect_id = sym.n_sect;
+ const NlistIdx = struct {
+ nlist: macho.nlist_64,
+ idx: usize,
- self.symtab[i] = sym;
- self.source_symtab_lookup[i] = sym_id.index;
- self.reverse_symtab_lookup[sym_id.index] = @as(u32, @intCast(i));
- self.source_address_lookup[i] = if (sym.undf()) -1 else @as(i64, @intCast(sym.n_value));
+ fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
+ if (!nl.ext()) {
+ const name = ctx.getString(nl.n_strx);
+ if (name.len == 0) return 5;
+ if (name[0] == 'l' or name[0] == 'L') return 4;
+ return 3;
+ }
+ return if (nl.weakDef()) 2 else 1;
+ }
- const sym_name_len = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.in_strtab.?.ptr + sym.n_strx)), 0).len + 1;
- self.strtab_lookup[i] = @as(u32, @intCast(sym_name_len));
- }
+ fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {
+ if (lhs.nlist.n_sect == rhs.nlist.n_sect) {
+ if (lhs.nlist.n_value == rhs.nlist.n_value) {
+ return rank(ctx, lhs.nlist) < rank(ctx, rhs.nlist);
+ }
+ return lhs.nlist.n_value < rhs.nlist.n_value;
+ }
+ return lhs.nlist.n_sect < rhs.nlist.n_sect;
+ }
+ };
- // If there were no undefined symbols, make sure we populate the
- // source section index lookup for the last scanned section.
- if (section_index_lookup) |lookup| {
- self.source_section_index_lookup[prev_sect_id - 1] = lookup;
+ var nlists = try std.ArrayList(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
+ defer nlists.deinit();
+ for (self.symtab.items(.nlist), 0..) |nlist, i| {
+ if (nlist.stab() or !nlist.sect()) continue;
+ nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
}
+ mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
- // Parse __TEXT,__eh_frame header if one exists
- self.eh_frame_sect_id = self.getSourceSectionIndexByName("__TEXT", "__eh_frame");
-
- // Parse __LD,__compact_unwind header if one exists
- self.unwind_info_sect_id = self.getSourceSectionIndexByName("__LD", "__compact_unwind");
- if (self.hasUnwindRecords()) {
- self.unwind_relocs_lookup = try allocator.alloc(Record, self.getUnwindRecords().len);
- @memset(self.unwind_relocs_lookup, .{ .dead = true, .reloc = .{} });
+ if (self.hasSubsections()) {
+ try self.initSubsections(nlists.items, macho_file);
+ } else {
+ try self.initSections(nlists.items, macho_file);
}
-}
-const SymbolAtIndex = struct {
- index: u32,
+ try self.initLiteralSections(macho_file);
+ try self.linkNlistToAtom(macho_file);
- const Context = *const Object;
+ try self.sortAtoms(macho_file);
+ try self.initSymbols(macho_file);
+ try self.initSymbolStabs(nlists.items, macho_file);
+ try self.initRelocs(macho_file);
- fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 {
- return ctx.in_symtab.?[self.index];
+ // Parse DWARF __TEXT,__eh_frame section
+ if (self.eh_frame_sect_index) |index| {
+ try self.initEhFrameRecords(index, macho_file);
}
- fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 {
- const off = self.getSymbol(ctx).n_strx;
- return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.in_strtab.?.ptr + off)), 0);
+ // Parse Apple's __LD,__compact_unwind section
+ if (self.compact_unwind_sect_index) |index| {
+ try self.initUnwindRecords(index, macho_file);
}
- fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 {
- const sym = self.getSymbol(ctx);
- if (!sym.ext()) {
- const sym_name = self.getSymbolName(ctx);
- if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3;
- return 2;
- }
- if (sym.weakDef() or sym.pext()) return 1;
- return 0;
+ if (self.hasUnwindRecords() or self.hasEhFrameRecords()) {
+ try self.parseUnwindRecords(macho_file);
}
- /// Performs lexicographic-like check.
- /// * lhs and rhs defined
- /// * if lhs == rhs
- /// * if lhs.n_sect == rhs.n_sect
- /// * ext < weak < local < temp
- /// * lhs.n_sect < rhs.n_sect
- /// * lhs < rhs
- /// * !rhs is undefined
- fn lessThan(ctx: Context, lhs_index: SymbolAtIndex, rhs_index: SymbolAtIndex) bool {
- const lhs = lhs_index.getSymbol(ctx);
- const rhs = rhs_index.getSymbol(ctx);
- if (lhs.sect() and rhs.sect()) {
- if (lhs.n_value == rhs.n_value) {
- if (lhs.n_sect == rhs.n_sect) {
- const lhs_senior = lhs_index.getSymbolSeniority(ctx);
- const rhs_senior = rhs_index.getSymbolSeniority(ctx);
- if (lhs_senior == rhs_senior) {
- return lessThanByNStrx(ctx, lhs_index, rhs_index);
- } else return lhs_senior < rhs_senior;
- } else return lhs.n_sect < rhs.n_sect;
- } else return lhs.n_value < rhs.n_value;
- } else if (lhs.undf() and rhs.undf()) {
- return lessThanByNStrx(ctx, lhs_index, rhs_index);
- } else return rhs.undf();
- }
+ self.initPlatform();
- fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool {
- return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx;
+ if (self.platform) |platform| {
+ if (!macho_file.platform.eqlTarget(platform)) {
+ try macho_file.reportParseError2(self.index, "invalid platform: {}", .{
+ platform.fmtTarget(macho_file.getTarget().cpu.arch),
+ });
+ return error.InvalidTarget;
+ }
+ // TODO: this causes the CI to fail so I'm commenting this check out so that
+ // I can work out the rest of the changes first
+ // if (macho_file.platform.version.order(platform.version) == .lt) {
+ // try macho_file.reportParseError2(self.index, "object file built for newer platform: {}: {} < {}", .{
+ // macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch),
+ // macho_file.platform.version,
+ // platform.version,
+ // });
+ // return error.InvalidTarget;
+ // }
}
-};
-fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct {
- index: u32,
- len: u32,
-} {
- const FirstMatch = struct {
- n_sect: u8,
+ try self.initDwarfInfo(macho_file);
- pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
- return symbol.n_sect == pred.n_sect;
+ 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;
}
- };
- const FirstNonMatch = struct {
- n_sect: u8,
+ }
+}
- pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
- return symbol.n_sect != pred.n_sect;
- }
+inline fn isLiteral(sect: macho.section_64) bool {
+ return switch (sect.type()) {
+ macho.S_CSTRING_LITERALS,
+ macho.S_4BYTE_LITERALS,
+ macho.S_8BYTE_LITERALS,
+ macho.S_16BYTE_LITERALS,
+ macho.S_LITERAL_POINTERS,
+ => true,
+ else => false,
};
+}
- const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{
- .n_sect = n_sect,
- });
- const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{
- .n_sect = n_sect,
- });
+fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const slice = self.sections.slice();
+ for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
+ if (isLiteral(sect)) continue;
+
+ const nlist_start = for (nlists, 0..) |nlist, i| {
+ if (nlist.nlist.n_sect - 1 == n_sect) break i;
+ } else nlists.len;
+ const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
+ if (nlist.nlist.n_sect - 1 != n_sect) break i;
+ } else nlists.len;
+
+ if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) {
+ const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
+ defer gpa.free(name);
+ const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr;
+ const atom_index = try self.addAtom(.{
+ .name = name,
+ .n_sect = @intCast(n_sect),
+ .off = 0,
+ .size = size,
+ .alignment = sect.@"align",
+ }, macho_file);
+ try subsections.append(gpa, .{
+ .atom = atom_index,
+ .off = 0,
+ });
+ }
- return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };
+ var idx: usize = nlist_start;
+ while (idx < nlist_end) {
+ const alias_start = idx;
+ const nlist = nlists[alias_start];
+
+ while (idx < nlist_end and
+ nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
+ {}
+
+ const size = if (idx < nlist_end)
+ nlists[idx].nlist.n_value - nlist.nlist.n_value
+ else
+ sect.addr + sect.size - nlist.nlist.n_value;
+ const alignment = if (nlist.nlist.n_value > 0)
+ @min(@ctz(nlist.nlist.n_value), sect.@"align")
+ else
+ sect.@"align";
+ const atom_index = try self.addAtom(.{
+ .name = self.getString(nlist.nlist.n_strx),
+ .n_sect = @intCast(n_sect),
+ .off = nlist.nlist.n_value - sect.addr,
+ .size = size,
+ .alignment = alignment,
+ }, macho_file);
+ try subsections.append(gpa, .{
+ .atom = atom_index,
+ .off = nlist.nlist.n_value - sect.addr,
+ });
+
+ for (alias_start..idx) |i| {
+ self.symtab.items(.size)[nlists[i].idx] = size;
+ }
+ }
+ }
}
-fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr: u64) struct {
- index: u32,
- len: u32,
-} {
- const Predicate = struct {
- addr: u64,
-
- pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
- return symbol.n_value >= pred.addr;
+fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const slice = self.sections.slice();
+
+ try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
+
+ for (slice.items(.header), 0..) |sect, n_sect| {
+ if (isLiteral(sect)) continue;
+
+ const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
+ defer gpa.free(name);
+
+ const atom_index = try self.addAtom(.{
+ .name = name,
+ .n_sect = @intCast(n_sect),
+ .off = 0,
+ .size = sect.size,
+ .alignment = sect.@"align",
+ }, macho_file);
+ try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
+
+ const nlist_start = for (nlists, 0..) |nlist, i| {
+ if (nlist.nlist.n_sect - 1 == n_sect) break i;
+ } else nlists.len;
+ const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
+ if (nlist.nlist.n_sect - 1 != n_sect) break i;
+ } else nlists.len;
+
+ var idx: usize = nlist_start;
+ while (idx < nlist_end) {
+ const nlist = nlists[idx];
+
+ while (idx < nlist_end and
+ nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
+ {}
+
+ const size = if (idx < nlist_end)
+ nlists[idx].nlist.n_value - nlist.nlist.n_value
+ else
+ sect.addr + sect.size - nlist.nlist.n_value;
+
+ for (nlist_start..idx) |i| {
+ self.symtab.items(.size)[nlists[i].idx] = size;
+ }
}
- };
+ }
+}
- const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{
- .addr = start_addr,
- });
- const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{
- .addr = end_addr,
- });
+const AddAtomArgs = struct {
+ name: [:0]const u8,
+ n_sect: u8,
+ off: u64,
+ size: u64,
+ alignment: u32,
+};
- return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };
+fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const atom_index = try macho_file.addAtom();
+ const atom = macho_file.getAtom(atom_index).?;
+ atom.file = self.index;
+ atom.atom_index = atom_index;
+ atom.name = try macho_file.strings.insert(gpa, args.name);
+ atom.n_sect = args.n_sect;
+ atom.size = args.size;
+ atom.alignment = Atom.Alignment.fromLog2Units(args.alignment);
+ atom.off = args.off;
+ try self.atoms.append(gpa, atom_index);
+ return atom_index;
}
-const SortedSection = struct {
- header: macho.section_64,
- id: u8,
-};
+fn initLiteralSections(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ // TODO here we should split into equal-sized records, hash the contents, and then
+ // deduplicate - ICF.
+ // For now, we simply cover each literal section with one large atom.
+ const gpa = macho_file.base.comp.gpa;
+ const slice = self.sections.slice();
+
+ try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
+
+ for (slice.items(.header), 0..) |sect, n_sect| {
+ if (!isLiteral(sect)) continue;
+
+ const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
+ defer gpa.free(name);
+
+ const atom_index = try self.addAtom(.{
+ .name = name,
+ .n_sect = @intCast(n_sect),
+ .off = 0,
+ .size = sect.size,
+ .alignment = sect.@"align",
+ }, macho_file);
+ try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
+ }
+}
-fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) bool {
- _ = ctx;
- if (lhs.header.addr == rhs.header.addr) {
- return lhs.id < rhs.id;
+pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const slice = self.sections.slice();
+ for (slice.items(.header), slice.items(.subsections), 0..) |sect, subs, n_sect| {
+ if (subs.items.len == 0) continue;
+ if (sect.addr == addr) return subs.items[0].atom;
+ if (sect.addr < addr and addr < sect.addr + sect.size) {
+ return self.findAtomInSection(addr, @intCast(n_sect));
+ }
}
- return lhs.header.addr < rhs.header.addr;
+ return null;
}
-pub const SplitIntoAtomsError = error{
- OutOfMemory,
- EndOfStream,
- MissingEhFrameSection,
- BadDwarfCfi,
-};
+fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const slice = self.sections.slice();
+ const sect = slice.items(.header)[n_sect];
+ const subsections = slice.items(.subsections)[n_sect];
+
+ var min: usize = 0;
+ var max: usize = subsections.items.len;
+ while (min < max) {
+ const idx = (min + max) / 2;
+ const sub = subsections.items[idx];
+ const sub_addr = sect.addr + sub.off;
+ const sub_size = if (idx + 1 < subsections.items.len)
+ subsections.items[idx + 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 (sub_addr < addr) {
+ min = idx + 1;
+ } else {
+ max = idx;
+ }
+ }
-pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name });
+ 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;
+ }
- try self.splitRegularSections(macho_file, object_id);
- try self.parseEhFrameSection(macho_file, object_id);
- try self.parseUnwindInfo(macho_file, object_id);
- try self.parseDataInCode(gpa);
+ return null;
}
-/// Splits input regular sections into Atoms.
-/// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section
-/// into subsections where each subsection then represents an Atom.
-pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
-
- const sections = self.getSourceSections();
- for (sections, 0..) |sect, id| {
- if (sect.isDebug()) continue;
- const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse {
- log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() });
- continue;
- };
- if (sect.size == 0) continue;
-
- const sect_id = @as(u8, @intCast(id));
- const sym = self.getSectionAliasSymbolPtr(sect_id);
- sym.* = .{
- .n_strx = 0,
- .n_type = macho.N_SECT,
- .n_sect = out_sect_id + 1,
- .n_desc = 0,
- .n_value = sect.addr,
- };
+fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| {
+ if (!nlist.stab() and nlist.sect()) {
+ if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| {
+ atom.* = atom_index;
+ } else {
+ try macho_file.reportParseError2(self.index, "symbol {s} not attached to any (sub)section", .{
+ self.getString(nlist.n_strx),
+ });
+ return error.MalformedObject;
+ }
+ }
}
+}
- if (self.in_symtab == null) {
- for (sections, 0..) |sect, id| {
- if (sect.isDebug()) continue;
- const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
- if (sect.size == 0) continue;
-
- const sect_id: u8 = @intCast(id);
- const sym_index = self.getSectionAliasSymbolIndex(sect_id);
- const atom_index = try self.createAtomFromSubsection(
- macho_file,
- object_id,
- sym_index,
- sym_index,
- 1,
- sect.size,
- Alignment.fromLog2Units(sect.@"align"),
- out_sect_id,
- );
- macho_file.addAtomToSection(atom_index);
+fn initSymbols(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const slice = self.symtab.slice();
+
+ try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len);
+
+ for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| {
+ if (nlist.ext()) {
+ const name = self.getString(nlist.n_strx);
+ const off = try macho_file.strings.insert(gpa, name);
+ const gop = try macho_file.getOrCreateGlobal(off);
+ self.symbols.addOneAssumeCapacity().* = gop.index;
+ continue;
}
- return;
- }
- // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
- // have to infer the start of undef section in the symtab ourselves.
- const iundefsym = blk: {
- const dysymtab = self.getDysymtab() orelse {
- var iundefsym: usize = self.in_symtab.?.len;
- while (iundefsym > 0) : (iundefsym -= 1) {
- const sym = self.symtab[iundefsym - 1];
- if (sym.sect()) break;
- }
- break :blk iundefsym;
+ const index = try macho_file.addSymbol();
+ self.symbols.appendAssumeCapacity(index);
+ const symbol = macho_file.getSymbol(index);
+ const name = self.getString(nlist.n_strx);
+ symbol.* = .{
+ .value = nlist.n_value,
+ .name = try macho_file.strings.insert(gpa, name),
+ .nlist_idx = @intCast(i),
+ .atom = 0,
+ .file = self.index,
};
- break :blk dysymtab.iundefsym;
- };
-
- // We only care about defined symbols, so filter every other out.
- const symtab = try gpa.dupe(macho.nlist_64, self.symtab[0..iundefsym]);
- defer gpa.free(symtab);
- const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
+ if (macho_file.getAtom(atom_index)) |atom| {
+ assert(!nlist.abs());
+ symbol.value -= atom.getInputAddress(macho_file);
+ symbol.atom = atom_index;
+ }
- // Sort section headers by address.
- var sorted_sections = try gpa.alloc(SortedSection, sections.len);
- defer gpa.free(sorted_sections);
+ symbol.flags.abs = nlist.abs();
+ symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
- for (sections, 0..) |sect, id| {
- sorted_sections[id] = .{ .header = sect, .id = @as(u8, @intCast(id)) };
+ if (nlist.sect() and
+ self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
+ {
+ symbol.flags.tlv = true;
+ }
}
+}
- mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress);
-
- var sect_sym_index: u32 = 0;
- for (sorted_sections) |section| {
- const sect = section.header;
- if (sect.isDebug()) continue;
-
- const sect_id = section.id;
- log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });
-
- // Get output segment/section in the final artifact.
- const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
-
- log.debug(" output sect({d}, '{s},{s}')", .{
- out_sect_id + 1,
- macho_file.sections.items(.header)[out_sect_id].segName(),
- macho_file.sections.items(.header)[out_sect_id].sectName(),
- });
-
- try self.parseRelocs(gpa, section.id);
-
- const cpu_arch = target.cpu.arch;
- const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1);
- const sect_start_index = sect_sym_index + sect_loc.index;
-
- sect_sym_index += sect_loc.len;
-
- if (sect.size == 0) continue;
- if (subsections_via_symbols and sect_loc.len > 0) {
- // If the first nlist does not match the start of the section,
- // then we need to encapsulate the memory range [section start, first symbol)
- // as a temporary symbol and insert the matching Atom.
- const first_sym = symtab[sect_start_index];
- if (first_sym.n_value > sect.addr) {
- const sym_index = self.getSectionAliasSymbolIndex(sect_id);
- const atom_size = first_sym.n_value - sect.addr;
- const atom_index = try self.createAtomFromSubsection(
- macho_file,
- object_id,
- sym_index,
- sym_index,
- 1,
- atom_size,
- Alignment.fromLog2Units(sect.@"align"),
- out_sect_id,
- );
- if (!sect.isZerofill()) {
- try self.cacheRelocs(macho_file, atom_index);
- }
- macho_file.addAtomToSection(atom_index);
- }
+fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- var next_sym_index = sect_start_index;
- while (next_sym_index < sect_start_index + sect_loc.len) {
- const next_sym = symtab[next_sym_index];
- const addr = next_sym.n_value;
- const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1);
- assert(atom_loc.len > 0);
- const atom_sym_index = atom_loc.index + next_sym_index;
- const nsyms_trailing = atom_loc.len;
- next_sym_index += atom_loc.len;
-
- const atom_size = if (next_sym_index < sect_start_index + sect_loc.len)
- symtab[next_sym_index].n_value - addr
- else
- sect.addr + sect.size - addr;
+ const SymbolLookup = struct {
+ ctx: *const Object,
+ entries: @TypeOf(nlists),
- const atom_align = Alignment.fromLog2Units(if (addr > 0)
- @min(@ctz(addr), sect.@"align")
- else
- sect.@"align");
-
- const atom_index = try self.createAtomFromSubsection(
- macho_file,
- object_id,
- atom_sym_index,
- atom_sym_index,
- nsyms_trailing,
- atom_size,
- atom_align,
- out_sect_id,
- );
-
- // TODO rework this at the relocation level
- if (cpu_arch == .x86_64 and addr == sect.addr) {
- // In x86_64 relocs, it can so happen that the compiler refers to the same
- // atom by both the actual assigned symbol and the start of the section. In this
- // case, we need to link the two together so add an alias.
- const alias_index = self.getSectionAliasSymbolIndex(sect_id);
- self.atom_by_index_table[alias_index] = atom_index;
- }
- if (!sect.isZerofill()) {
- try self.cacheRelocs(macho_file, atom_index);
- }
- macho_file.addAtomToSection(atom_index);
+ 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];
}
- } else {
- const alias_index = self.getSectionAliasSymbolIndex(sect_id);
- const atom_index = try self.createAtomFromSubsection(
- macho_file,
- object_id,
- alias_index,
- sect_start_index,
- sect_loc.len,
- sect.size,
- Alignment.fromLog2Units(sect.@"align"),
- out_sect_id,
- );
- if (!sect.isZerofill()) {
- try self.cacheRelocs(macho_file, atom_index);
+ 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.comp.gpa;
+ 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) {
+ try macho_file.reportParseError2(self.index, "unexpected symbol stab type 0x{x} as the first entry", .{
+ open.n_type,
+ });
+ return error.MalformedObject;
+ }
+
+ 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 => {
+ try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{
+ nlist.n_type,
+ });
+ return error.MalformedObject;
+ },
}
- macho_file.addAtomToSection(atom_index);
+ try sf.stabs.append(gpa, stab);
}
+
+ try self.stab_files.append(gpa, sf);
}
}
-fn createAtomFromSubsection(
- self: *Object,
- macho_file: *MachO,
- object_id: u32,
- sym_index: u32,
- inner_sym_index: u32,
- inner_nsyms_trailing: u32,
- size: u64,
- alignment: Alignment,
- out_sect_id: u8,
-) !Atom.Index {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const atom_index = try macho_file.createAtom(sym_index, .{
- .size = size,
- .alignment = alignment,
- });
- const atom = macho_file.getAtomPtr(atom_index);
- atom.inner_sym_index = inner_sym_index;
- atom.inner_nsyms_trailing = inner_nsyms_trailing;
- atom.file = object_id + 1;
- self.symtab[sym_index].n_sect = out_sect_id + 1;
-
- log.debug("creating ATOM(%{d}, '{s}') in sect({d}, '{s},{s}') in object({d})", .{
- sym_index,
- self.getSymbolName(sym_index),
- out_sect_id + 1,
- macho_file.sections.items(.header)[out_sect_id].segName(),
- macho_file.sections.items(.header)[out_sect_id].sectName(),
- object_id,
- });
+fn sortAtoms(self: *Object, macho_file: *MachO) !void {
+ const lessThanAtom = struct {
+ fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool {
+ return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx);
+ }
+ }.lessThanAtom;
+ mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom);
+}
- try self.atoms.append(gpa, atom_index);
- self.atom_by_index_table[sym_index] = atom_index;
+fn initRelocs(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const slice = self.sections.slice();
+
+ 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;
+
+ switch (cpu_arch) {
+ .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
+ .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
+ else => unreachable,
+ }
- var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (it.next()) |sym_loc| {
- const inner = macho_file.getSymbolPtr(sym_loc);
- inner.n_sect = out_sect_id + 1;
- self.atom_by_index_table[sym_loc.sym_index] = atom_index;
+ mem.sort(Relocation, out.items, {}, Relocation.lessThan);
}
- const out_sect = macho_file.sections.items(.header)[out_sect_id];
- if (out_sect.isCode() and
- mem.eql(u8, "__TEXT", out_sect.segName()) and
- mem.eql(u8, "__text", out_sect.sectName()))
- {
- // TODO currently assuming a single section for executable machine code
- try self.exec_atoms.append(gpa, atom_index);
- }
+ for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| {
+ if (sect.isZerofill()) continue;
- return atom_index;
-}
+ var next_reloc: u32 = 0;
+ for (subsections.items) |subsection| {
+ const atom = macho_file.getAtom(subsection.atom).?;
+ if (!atom.flags.alive) continue;
+ if (next_reloc >= relocs.items.len) break;
+ const end_addr = atom.off + atom.size;
+ atom.relocs.pos = next_reloc;
-fn filterRelocs(
- relocs: []align(1) const macho.relocation_info,
- start_addr: u64,
- end_addr: u64,
-) Entry {
- const Predicate = struct {
- addr: u64,
+ while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {}
- pub fn predicate(self: @This(), rel: macho.relocation_info) bool {
- return rel.r_address >= self.addr;
+ atom.relocs.len = next_reloc - atom.relocs.pos;
}
- };
- const LPredicate = struct {
- addr: u64,
+ }
+}
- pub fn predicate(self: @This(), rel: macho.relocation_info) bool {
- return rel.r_address < self.addr;
+fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const nlists = self.symtab.items(.nlist);
+ const slice = self.sections.slice();
+ const sect = slice.items(.header)[sect_id];
+ const relocs = slice.items(.relocs)[sect_id];
+
+ const data = try self.getSectionData(sect_id);
+ try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len);
+ self.eh_frame_data.appendSliceAssumeCapacity(data);
+
+ // Check for non-personality relocs in FDEs and apply them
+ for (relocs.items, 0..) |rel, i| {
+ switch (rel.type) {
+ .unsigned => {
+ 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),
+ };
+ const A = rel.addend;
+ const SUB: i64 = blk: {
+ const sub_rel = relocs.items[i - 1];
+ break :blk switch (sub_rel.tag) {
+ .local => sub_rel.meta.symbolnum,
+ .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value),
+ };
+ };
+ 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 => {},
}
- };
-
- const start = MachO.bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr });
- const len = MachO.lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr });
+ }
- return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) };
-}
+ var it = eh_frame.Iterator{ .data = self.eh_frame_data.items };
+ while (try it.next()) |rec| {
+ switch (rec.tag) {
+ .cie => try self.cies.append(gpa, .{
+ .offset = rec.offset,
+ .size = rec.size,
+ .file = self.index,
+ }),
+ .fde => try self.fdes.append(gpa, .{
+ .offset = rec.offset,
+ .size = rec.size,
+ .cie = undefined,
+ .file = self.index,
+ }),
+ }
+ }
-/// Parse all relocs for the input section, and sort in descending order.
-/// Previously, I have wrongly assumed the compilers output relocations for each
-/// section in a sorted manner which is simply not true.
-fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {
- const section = self.getSourceSection(sect_id);
- const start = @as(u32, @intCast(self.relocations.items.len));
- if (self.getSourceRelocs(section)) |relocs| {
- try self.relocations.ensureUnusedCapacity(gpa, relocs.len);
- self.relocations.appendUnalignedSliceAssumeCapacity(relocs);
- mem.sort(macho.relocation_info, self.relocations.items[start..], {}, relocGreaterThan);
+ for (self.cies.items) |*cie| {
+ try cie.parse(macho_file);
}
- self.section_relocs_lookup.items[sect_id] = start;
-}
-fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void {
- const atom = macho_file.getAtom(atom_index);
-
- const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
- break :blk source_sym.n_sect - 1;
- } else blk: {
- // If there was no matching symbol present in the source symtab, this means
- // we are dealing with either an entire section, or part of it, but also
- // starting at the beginning.
- const nbase = @as(u32, @intCast(self.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- break :blk sect_id;
- };
- const source_sect = self.getSourceSection(source_sect_id);
- assert(!source_sect.isZerofill());
- const relocs = self.getRelocs(source_sect_id);
-
- self.relocs_lookup[atom.sym_index] = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
- const offset = source_sym.n_value - source_sect.addr;
- break :blk filterRelocs(relocs, offset, offset + atom.size);
- } else filterRelocs(relocs, 0, atom.size);
-}
+ for (self.fdes.items) |*fde| {
+ try fde.parse(macho_file);
+ }
-fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
- _ = ctx;
- return lhs.r_address > rhs.r_address;
+ const sortFn = struct {
+ fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool {
+ return lhs.getAtom(ctx).getInputAddress(ctx) < rhs.getAtom(ctx).getInputAddress(ctx);
+ }
+ }.sortFn;
+
+ mem.sort(Fde, self.fdes.items, macho_file, sortFn);
+
+ // Parse and attach personality pointers to CIEs if any
+ for (relocs.items) |rel| {
+ switch (rel.type) {
+ .got => {
+ assert(rel.meta.length == 2 and rel.tag == .@"extern");
+ const cie = for (self.cies.items) |*cie| {
+ if (cie.offset <= rel.offset and rel.offset < cie.offset + cie.getSize()) break cie;
+ } else {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ sect.segName(), sect.sectName(), rel.offset,
+ });
+ return error.MalformedObject;
+ };
+ cie.personality = .{ .index = @intCast(rel.target), .offset = rel.offset - cie.offset };
+ },
+ else => {},
+ }
+ }
}
-fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void {
- const sect_id = self.eh_frame_sect_id orelse return;
- const sect = self.getSourceSection(sect_id);
+fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
- log.debug("parsing __TEXT,__eh_frame section", .{});
+ const SymbolLookup = struct {
+ ctx: *const Object,
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
+ 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;
+ }
+ };
- if (macho_file.eh_frame_section_index == null) {
- macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{});
+ const gpa = macho_file.base.comp.gpa;
+ const data = try 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);
+
+ const header = self.sections.items(.header)[sect_id];
+ const relocs = self.sections.items(.relocs)[sect_id].items;
+ var reloc_idx: usize = 0;
+ for (recs, self.unwind_records.items, 0..) |rec, *out_index, rec_idx| {
+ const rec_start = rec_idx * @sizeOf(macho.compact_unwind_entry);
+ const rec_end = rec_start + @sizeOf(macho.compact_unwind_entry);
+ const reloc_start = reloc_idx;
+ while (reloc_idx < relocs.len and
+ relocs[reloc_idx].offset < rec_end) : (reloc_idx += 1)
+ {}
+
+ out_index.* = try macho_file.addUnwindRecord();
+ const out = macho_file.getUnwindRecord(out_index.*);
+ out.length = rec.rangeLength;
+ out.enc = .{ .enc = rec.compactUnwindEncoding };
+ out.file = self.index;
+
+ for (relocs[reloc_start..reloc_idx]) |rel| {
+ if (rel.type != .unsigned or rel.meta.length != 3) {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ header.segName(), header.sectName(), rel.offset,
+ });
+ return error.MalformedObject;
+ }
+ assert(rel.type == .unsigned and rel.meta.length == 3); // TODO error
+ const offset = rel.offset - rec_start;
+ switch (offset) {
+ 0 => switch (rel.tag) { // target symbol
+ .@"extern" => {
+ out.atom = self.symtab.items(.atom)[rel.meta.symbolnum];
+ out.atom_offset = @intCast(rec.rangeStart);
+ },
+ .local => if (self.findAtom(rec.rangeStart)) |atom_index| {
+ out.atom = atom_index;
+ const atom = out.getAtom(macho_file);
+ out.atom_offset = @intCast(rec.rangeStart - atom.getInputAddress(macho_file));
+ } else {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ header.segName(), header.sectName(), rel.offset,
+ });
+ return error.MalformedObject;
+ },
+ },
+ 16 => switch (rel.tag) { // personality function
+ .@"extern" => {
+ out.personality = rel.target;
+ },
+ .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| {
+ out.personality = sym_index;
+ } else {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ header.segName(), header.sectName(), rel.offset,
+ });
+ return error.MalformedObject;
+ },
+ },
+ 24 => switch (rel.tag) { // lsda
+ .@"extern" => {
+ out.lsda = self.symtab.items(.atom)[rel.meta.symbolnum];
+ out.lsda_offset = @intCast(rec.lsda);
+ },
+ .local => if (self.findAtom(rec.lsda)) |atom_index| {
+ out.lsda = atom_index;
+ const atom = out.getLsdaAtom(macho_file).?;
+ out.lsda_offset = @intCast(rec.lsda - atom.getInputAddress(macho_file));
+ } else {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ header.segName(), header.sectName(), rel.offset,
+ });
+ return error.MalformedObject;
+ },
+ },
+ else => {},
+ }
+ }
}
+}
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- try self.parseRelocs(gpa, sect_id);
- const relocs = self.getRelocs(sect_id);
-
- var it = self.getEhFrameRecordsIterator();
- var record_count: u32 = 0;
- while (try it.next()) |_| {
- record_count += 1;
+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
+ // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record
+ // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie
+ // 4. if an atom doesn't have either, synthesise a null unwind info record
+
+ const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null };
+
+ const gpa = macho_file.base.comp.gpa;
+ 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() and sect.size > 0) {
+ try superposition.ensureUnusedCapacity(1);
+ const gop = superposition.getOrPutAssumeCapacity(nlist.n_value);
+ if (gop.found_existing) {
+ assert(gop.value_ptr.atom == atom and gop.value_ptr.size == size);
+ }
+ gop.value_ptr.* = .{ .atom = atom, .size = size };
+ }
}
- try self.eh_frame_relocs_lookup.ensureTotalCapacity(gpa, record_count);
- try self.eh_frame_records_lookup.ensureUnusedCapacity(gpa, record_count);
+ for (self.unwind_records.items) |rec_index| {
+ const rec = macho_file.getUnwindRecord(rec_index);
+ const atom = rec.getAtom(macho_file);
+ const addr = atom.getInputAddress(macho_file) + rec.atom_offset;
+ superposition.getPtr(addr).?.cu = rec_index;
+ }
- it.reset();
+ for (self.fdes.items, 0..) |fde, fde_index| {
+ const atom = fde.getAtom(macho_file);
+ const addr = atom.getInputAddress(macho_file) + fde.atom_offset;
+ superposition.getPtr(addr).?.fde = @intCast(fde_index);
+ }
- while (try it.next()) |record| {
- const offset = it.pos - record.getSize();
- const rel_pos: Entry = switch (cpu_arch) {
- .aarch64 => filterRelocs(relocs, offset, offset + record.getSize()),
- .x86_64 => .{},
- else => unreachable,
- };
- self.eh_frame_relocs_lookup.putAssumeCapacityNoClobber(offset, .{
- .dead = false,
- .reloc = rel_pos,
- });
-
- if (record.tag == .fde) {
- const reloc_target = blk: {
- switch (cpu_arch) {
- .aarch64 => {
- assert(rel_pos.len > 0); // TODO convert to an error as the FDE eh frame is malformed
- // Find function symbol that this record describes
- const rel = for (relocs[rel_pos.start..][0..rel_pos.len]) |rel| {
- if (rel.r_address - @as(i32, @intCast(offset)) == 8 and
- @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED)
- break rel;
- } else unreachable;
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = it.data[offset..],
- .base_offset = @as(i32, @intCast(offset)),
- });
- break :blk reloc_target;
- },
- .x86_64 => {
- const target_address = record.getTargetSymbolAddress(.{
- .base_addr = sect.addr,
- .base_offset = offset,
- });
- const target_sym_index = self.getSymbolByAddress(target_address, null);
- const reloc_target = if (self.getGlobal(target_sym_index)) |global_index|
- macho_file.globals.items[global_index]
- else
- SymbolWithLoc{ .sym_index = target_sym_index, .file = object_id + 1 };
- break :blk reloc_target;
- },
- else => unreachable,
+ for (superposition.keys(), superposition.values()) |addr, meta| {
+ if (meta.fde) |fde_index| {
+ const fde = &self.fdes.items[fde_index];
+
+ if (meta.cu) |rec_index| {
+ const rec = macho_file.getUnwindRecord(rec_index);
+ if (!rec.enc.isDwarf(macho_file)) {
+ // Mark FDE dead
+ fde.alive = false;
+ } else {
+ // Tie FDE to unwind record
+ rec.fde = fde_index;
}
- };
- if (reloc_target.getFile() != object_id) {
- log.debug("FDE at offset {x} marked DEAD", .{offset});
- self.eh_frame_relocs_lookup.getPtr(offset).?.dead = true;
} else {
- // You would think that we are done but turns out that the compilers may use
- // whichever symbol alias they want for a target symbol. This in particular
- // very problematic when using Zig's @export feature to re-export symbols under
- // additional names. For that reason, we need to ensure we record aliases here
- // too so that we can tie them with their matching unwind records and vice versa.
- const aliases = self.getSymbolAliases(reloc_target.sym_index);
- var i: u32 = 0;
- while (i < aliases.len) : (i += 1) {
- const actual_target = SymbolWithLoc{
- .sym_index = i + aliases.start,
- .file = reloc_target.file,
- };
- log.debug("FDE at offset {x} tracks {s}", .{
- offset,
- macho_file.getSymbolName(actual_target),
- });
- try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset);
+ // Synthesise new unwind info record
+ 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(meta.size);
+ rec.atom = fde.atom;
+ rec.atom_offset = fde.atom_offset;
+ rec.fde = fde_index;
+ rec.file = fde.file;
+ switch (macho_file.getTarget().cpu.arch) {
+ .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF),
+ .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF),
+ else => unreachable,
}
}
+ } else if (meta.cu == null and meta.fde == null) {
+ // Create a null record
+ const rec_index = try macho_file.addUnwindRecord();
+ const rec = macho_file.getUnwindRecord(rec_index);
+ const atom = macho_file.getAtom(meta.atom).?;
+ try self.unwind_records.append(gpa, rec_index);
+ rec.length = @intCast(meta.size);
+ rec.atom = meta.atom;
+ rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file));
+ rec.file = self.index;
}
}
-}
-fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const sect_id = self.unwind_info_sect_id orelse {
- // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,
- // we will try fully synthesising unwind info records to somewhat match Apple ld's
- // approach. However, we will only synthesise DWARF records and nothing more. For this reason,
- // we still create the output `__TEXT,__unwind_info` section.
- if (self.hasEhFrameRecords()) {
- if (macho_file.unwind_info_section_index == null) {
- macho_file.unwind_info_section_index = try macho_file.initSection(
- "__TEXT",
- "__unwind_info",
- .{},
- );
- }
+ 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;
}
- return;
- };
+ }.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 };
+ }
+}
- log.debug("parsing unwind info in {s}", .{self.name});
+fn initPlatform(self: *Object) void {
+ var it = LoadCommandIterator{
+ .ncmds = self.header.?.ncmds,
+ .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
+ };
+ self.platform = while (it.next()) |cmd| {
+ switch (cmd.cmd()) {
+ .BUILD_VERSION,
+ .VERSION_MIN_MACOSX,
+ .VERSION_MIN_IPHONEOS,
+ .VERSION_MIN_TVOS,
+ .VERSION_MIN_WATCHOS,
+ => break MachO.Platform.fromLoadCommand(cmd),
+ else => {},
+ }
+ } else null;
+}
- if (macho_file.unwind_info_section_index == null) {
- macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{});
+/// Currently, we only check if a compile unit for this input object file exists
+/// 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, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+
+ var debug_info_index: ?usize = null;
+ var debug_abbrev_index: ?usize = null;
+ var debug_str_index: ?usize = null;
+
+ for (self.sections.items(.header), 0..) |sect, index| {
+ if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
+ if (mem.eql(u8, sect.sectName(), "__debug_info")) debug_info_index = index;
+ if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) debug_abbrev_index = index;
+ if (mem.eql(u8, sect.sectName(), "__debug_str")) debug_str_index = index;
}
- const unwind_records = self.getUnwindRecords();
+ if (debug_info_index == null or debug_abbrev_index == null) return;
- try self.unwind_records_lookup.ensureUnusedCapacity(gpa, @as(u32, @intCast(unwind_records.len)));
+ var dwarf_info = DwarfInfo{
+ .debug_info = try self.getSectionData(@intCast(debug_info_index.?)),
+ .debug_abbrev = try self.getSectionData(@intCast(debug_abbrev_index.?)),
+ .debug_str = if (debug_str_index) |index| try self.getSectionData(@intCast(index)) else "",
+ };
+ dwarf_info.init(gpa) catch {
+ try macho_file.reportParseError2(self.index, "invalid __DWARF info found", .{});
+ return error.MalformedObject;
+ };
+ self.dwarf_info = dwarf_info;
+}
- const needs_eh_frame = for (unwind_records) |record| {
- if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;
- } else false;
+pub fn resolveSymbols(self: *Object, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
- if (needs_eh_frame and !self.hasEhFrameRecords()) return error.MissingEhFrameSection;
+ for (self.symbols.items, 0..) |index, i| {
+ const nlist_idx = @as(Symbol.Index, @intCast(i));
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ const atom_index = self.symtab.items(.atom)[nlist_idx];
- try self.parseRelocs(gpa, sect_id);
- const relocs = self.getRelocs(sect_id);
+ if (!nlist.ext()) continue;
+ if (nlist.undf() and !nlist.tentative()) continue;
+ if (nlist.sect()) {
+ const atom = macho_file.getAtom(atom_index).?;
+ if (!atom.flags.alive) continue;
+ }
- for (unwind_records, 0..) |record, record_id| {
- const offset = record_id * @sizeOf(macho.compact_unwind_entry);
- const rel_pos = filterRelocs(
- relocs,
- offset,
- offset + @sizeOf(macho.compact_unwind_entry),
- );
- assert(rel_pos.len > 0); // TODO convert to an error as the unwind info is malformed
- self.unwind_relocs_lookup[record_id] = .{
- .dead = false,
- .reloc = rel_pos,
- };
+ const symbol = macho_file.getSymbol(index);
+ if (self.asFile().getSymbolRank(.{
+ .archive = !self.alive,
+ .weak = nlist.weakDef(),
+ .tentative = nlist.tentative(),
+ }) < symbol.getSymbolRank(macho_file)) {
+ const value = if (nlist.sect()) blk: {
+ const atom = macho_file.getAtom(atom_index).?;
+ break :blk nlist.n_value - atom.getInputAddress(macho_file);
+ } else nlist.n_value;
+ symbol.value = value;
+ symbol.atom = atom_index;
+ symbol.nlist_idx = nlist_idx;
+ symbol.file = self.index;
+ symbol.flags.weak = nlist.weakDef();
+ symbol.flags.abs = nlist.abs();
+ symbol.flags.tentative = nlist.tentative();
+ symbol.flags.weak_ref = false;
+ symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
+ symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
+ // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
+ symbol.flags.interposable = false;
+
+ if (nlist.sect() and
+ self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
+ {
+ symbol.flags.tlv = true;
+ }
+ }
- // Find function symbol that this record describes
- const rel = relocs[rel_pos.start..][rel_pos.len - 1];
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(offset)),
- });
- if (reloc_target.getFile() != object_id) {
- log.debug("unwind record {d} marked DEAD", .{record_id});
- self.unwind_relocs_lookup[record_id].dead = true;
- } else {
- // You would think that we are done but turns out that the compilers may use
- // whichever symbol alias they want for a target symbol. This in particular
- // very problematic when using Zig's @export feature to re-export symbols under
- // additional names. For that reason, we need to ensure we record aliases here
- // too so that we can tie them with their matching unwind records and vice versa.
- const aliases = self.getSymbolAliases(reloc_target.sym_index);
- var i: u32 = 0;
- while (i < aliases.len) : (i += 1) {
- const actual_target = SymbolWithLoc{
- .sym_index = i + aliases.start,
- .file = reloc_target.file,
- };
- log.debug("unwind record {d} tracks {s}", .{
- record_id,
- macho_file.getSymbolName(actual_target),
- });
- try self.unwind_records_lookup.putNoClobber(gpa, actual_target, @intCast(record_id));
+ // Regardless of who the winner is, we still merge symbol visibility here.
+ if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
+ if (symbol.visibility != .global) {
+ symbol.visibility = .hidden;
}
+ } else {
+ symbol.visibility = .global;
}
}
}
-pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 {
- const symtab = self.in_symtab.?;
- if (index >= symtab.len) return null;
- const mapped_index = self.source_symtab_lookup[index];
- return symtab[mapped_index];
+pub fn resetGlobals(self: *Object, macho_file: *MachO) void {
+ for (self.symbols.items, 0..) |sym_index, nlist_idx| {
+ if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
+ const sym = macho_file.getSymbol(sym_index);
+ const name = sym.name;
+ sym.* = .{};
+ sym.name = name;
+ }
}
-pub fn getSourceSection(self: Object, index: u8) macho.section_64 {
- const sections = self.getSourceSections();
- assert(index < sections.len);
- return sections[index];
+pub fn markLive(self: *Object, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items, 0..) |index, nlist_idx| {
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ if (!nlist.ext()) continue;
+
+ const sym = macho_file.getSymbol(index);
+ const file = sym.getFile(macho_file) orelse continue;
+ const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
+ if (should_keep and file == .object and !file.object.alive) {
+ file.object.alive = true;
+ file.object.markLive(macho_file);
+ }
+ }
}
-pub fn getSourceSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 {
- const index = self.getSourceSectionIndexByName(segname, sectname) orelse return null;
- const sections = self.getSourceSections();
- return sections[index];
+pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void {
+ for (self.symbols.items, 0..) |index, nlist_idx| {
+ const sym = macho_file.getSymbol(index);
+ if (sym.visibility != .global) continue;
+ const file = sym.getFile(macho_file) orelse continue;
+ if (file.getIndex() == self.index) continue;
+
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
+ const gop = try dupes.getOrPut(index);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
+ }
+ try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
+ }
+ }
}
-pub fn getSourceSectionIndexByName(self: Object, segname: []const u8, sectname: []const u8) ?u8 {
- const sections = self.getSourceSections();
- for (sections, 0..) |sect, i| {
- if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName()))
- return @as(u8, @intCast(i));
- } else return null;
+pub fn scanRelocs(self: Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.atoms.items) |atom_index| {
+ const atom = macho_file.getAtom(atom_index).?;
+ if (!atom.flags.alive) continue;
+ const sect = atom.getInputSection(macho_file);
+ if (sect.isZerofill()) continue;
+ try atom.scanRelocs(macho_file);
+ }
+
+ for (self.unwind_records.items) |rec_index| {
+ const rec = macho_file.getUnwindRecord(rec_index);
+ if (!rec.alive) continue;
+ if (rec.getFde(macho_file)) |fde| {
+ if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| {
+ sym.flags.needs_got = true;
+ }
+ } else if (rec.getPersonality(macho_file)) |sym| {
+ sym.flags.needs_got = true;
+ }
+ }
}
-pub fn getSourceSections(self: Object) []align(1) const macho.section_64 {
- var it = LoadCommandIterator{
- .ncmds = self.header.ncmds,
- .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
- };
- while (it.next()) |cmd| switch (cmd.cmd()) {
- .SEGMENT_64 => {
- return cmd.getSections();
+pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+
+ for (self.symbols.items, 0..) |index, i| {
+ const sym = macho_file.getSymbol(index);
+ if (!sym.flags.tentative) continue;
+ const sym_file = sym.getFile(macho_file).?;
+ if (sym_file.getIndex() != self.index) continue;
+
+ const nlist_idx = @as(Symbol.Index, @intCast(i));
+ const nlist = &self.symtab.items(.nlist)[nlist_idx];
+ const nlist_atom = &self.symtab.items(.atom)[nlist_idx];
+
+ const atom_index = try macho_file.addAtom();
+ try self.atoms.append(gpa, atom_index);
+
+ const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)});
+ defer gpa.free(name);
+ const atom = macho_file.getAtom(atom_index).?;
+ atom.atom_index = atom_index;
+ atom.name = try macho_file.strings.insert(gpa, name);
+ atom.file = self.index;
+ atom.size = nlist.n_value;
+ atom.alignment = Atom.Alignment.fromLog2Units((nlist.n_desc >> 8) & 0x0f);
+
+ const n_sect = try self.addSection(gpa, "__DATA", "__common");
+ const sect = &self.sections.items(.header)[n_sect];
+ sect.flags = macho.S_ZEROFILL;
+ sect.size = atom.size;
+ sect.@"align" = atom.alignment.toLog2Units();
+ atom.n_sect = n_sect;
+
+ sym.value = 0;
+ sym.atom = atom_index;
+ sym.flags.weak = false;
+ sym.flags.weak_ref = false;
+ sym.flags.tentative = false;
+ sym.visibility = .global;
+
+ nlist.n_value = 0;
+ nlist.n_type = macho.N_EXT | macho.N_SECT;
+ nlist.n_sect = 0;
+ nlist.n_desc = 0;
+ nlist_atom.* = atom_index;
+ }
+}
+
+fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
+ const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
+ self.sections.set(n_sect, .{
+ .header = .{
+ .sectname = MachO.makeStaticString(sectname),
+ .segname = MachO.makeStaticString(segname),
},
- else => {},
- } else unreachable;
+ });
+ return n_sect;
}
-pub fn parseDataInCode(self: *Object, gpa: Allocator) !void {
- var it = LoadCommandIterator{
- .ncmds = self.header.ncmds,
- .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
- };
- const cmd = while (it.next()) |cmd| {
- switch (cmd.cmd()) {
- .DATA_IN_CODE => break cmd.cast(macho.linkedit_data_command).?,
- else => {},
+pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ 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.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
+ if (sym.isSymbolStab(macho_file)) continue;
+ const name = sym.getName(macho_file);
+ // 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.base.isObject()) continue;
+ sym.flags.output_symtab = true;
+ if (sym.isLocal()) {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
+ self.output_symtab_ctx.nlocals += 1;
+ } else if (sym.flags.@"export") {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
+ self.output_symtab_ctx.nexports += 1;
+ } else {
+ assert(sym.flags.import);
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
+ self.output_symtab_ctx.nimports += 1;
}
- } else return;
- const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
- const dice = @as([*]align(1) const macho.data_in_code_entry, @ptrCast(self.contents.ptr + cmd.dataoff))[0..ndice];
- try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len);
- self.data_in_code.appendUnalignedSliceAssumeCapacity(dice);
- mem.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan);
-}
+ self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
+ }
-fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool {
- _ = ctx;
- return lhs.offset < rhs.offset;
+ if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
+ try self.calcStabsSize(macho_file);
}
-fn getDysymtab(self: Object) ?macho.dysymtab_command {
- var it = LoadCommandIterator{
- .ncmds = self.header.ncmds,
- .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
- };
- while (it.next()) |cmd| {
- switch (cmd.cmd()) {
- .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?,
- else => {},
+pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
+ if (self.dwarf_info) |dw| {
+ // TODO handle multiple CUs
+ const cu = dw.compile_units.items[0];
+ const comp_dir = try cu.getCompileDir(dw) orelse return;
+ const tu_name = try 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));
}
- } else return null;
+
+ 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.base.isObject()) {
+ 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;
+ }
+ }
+ }
}
-pub fn parseDwarfInfo(self: Object) DwarfInfo {
- var di = DwarfInfo{
- .debug_info = &[0]u8{},
- .debug_abbrev = &[0]u8{},
- .debug_str = &[0]u8{},
- };
- for (self.getSourceSections()) |sect| {
- if (!sect.isDebug()) continue;
- const sectname = sect.sectName();
- if (mem.eql(u8, sectname, "__debug_info")) {
- di.debug_info = self.getSectionContents(sect);
- } else if (mem.eql(u8, sectname, "__debug_abbrev")) {
- di.debug_abbrev = self.getSectionContents(sect);
- } else if (mem.eql(u8, sectname, "__debug_str")) {
- di.debug_str = self.getSectionContents(sect);
+pub fn writeSymtab(self: Object, macho_file: *MachO) error{Overflow}!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ 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;
+ const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
+ const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
+ macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+ macho_file.strtab.appendAssumeCapacity(0);
+ const out_sym = &macho_file.symtab.items[idx];
+ out_sym.n_strx = n_strx;
+ sym.setOutputSym(macho_file, out_sym);
+ }
+
+ if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
+ try self.writeStabs(macho_file);
+}
+
+pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void {
+ const writeFuncStab = struct {
+ inline fn writeFuncStab(
+ n_strx: u32,
+ n_sect: u8,
+ n_value: u64,
+ size: u64,
+ index: u32,
+ ctx: *MachO,
+ ) void {
+ ctx.symtab.items[index] = .{
+ .n_strx = 0,
+ .n_type = macho.N_BNSYM,
+ .n_sect = n_sect,
+ .n_desc = 0,
+ .n_value = n_value,
+ };
+ ctx.symtab.items[index + 1] = .{
+ .n_strx = n_strx,
+ .n_type = macho.N_FUN,
+ .n_sect = n_sect,
+ .n_desc = 0,
+ .n_value = n_value,
+ };
+ ctx.symtab.items[index + 2] = .{
+ .n_strx = 0,
+ .n_type = macho.N_FUN,
+ .n_sect = 0,
+ .n_desc = 0,
+ .n_value = size,
+ };
+ ctx.symtab.items[index + 3] = .{
+ .n_strx = 0,
+ .n_type = macho.N_ENSYM,
+ .n_sect = n_sect,
+ .n_desc = 0,
+ .n_value = size,
+ };
+ }
+ }.writeFuncStab;
+
+ var index = self.output_symtab_ctx.istab;
+
+ if (self.dwarf_info) |dw| {
+ // TODO handle multiple CUs
+ const cu = dw.compile_units.items[0];
+ const comp_dir = try cu.getCompileDir(dw) orelse return;
+ const tu_name = try 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);
+ 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(')');
+ 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.base.isObject()) {
+ 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;
+ }
+ }
+
+ // 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,
+ };
+ } 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 = 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(sf.getTuName(self));
+ 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));
+ 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;
}
}
- return di;
}
-/// Returns Platform composed from the first encountered build version type load command:
-/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
-pub fn getPlatform(self: Object) ?Platform {
+fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
var it = LoadCommandIterator{
- .ncmds = self.header.ncmds,
- .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
+ .ncmds = self.header.?.ncmds,
+ .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
};
while (it.next()) |cmd| {
- switch (cmd.cmd()) {
- .BUILD_VERSION,
- .VERSION_MIN_MACOSX,
- .VERSION_MIN_IPHONEOS,
- .VERSION_MIN_TVOS,
- .VERSION_MIN_WATCHOS,
- => return Platform.fromLoadCommand(cmd),
- else => {},
- }
+ if (cmd.cmd() == lc) return cmd;
} else return null;
}
-pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
- const size = @as(usize, @intCast(sect.size));
- return self.contents[sect.offset..][0..size];
+pub fn getSectionData(self: *const Object, index: u32) error{Overflow}![]const u8 {
+ const slice = self.sections.slice();
+ assert(index < slice.items(.header).len);
+ const sect = slice.items(.header)[index];
+ const off = math.cast(usize, sect.offset) orelse return error.Overflow;
+ const size = math.cast(usize, sect.size) orelse return error.Overflow;
+ return self.data[off..][0..size];
+}
+
+pub fn getAtomData(self: *const Object, atom: Atom) error{Overflow}![]const u8 {
+ const data = try self.getSectionData(atom.n_sect);
+ const off = math.cast(usize, atom.off) orelse return error.Overflow;
+ const size = math.cast(usize, atom.size) orelse return error.Overflow;
+ return data[off..][0..size];
}
-pub fn getSectionAliasSymbolIndex(self: Object, sect_id: u8) u32 {
- const start = @as(u32, @intCast(self.in_symtab.?.len));
- return start + sect_id;
+pub fn getAtomRelocs(self: *const Object, atom: Atom) []const Relocation {
+ const relocs = self.sections.items(.relocs)[atom.n_sect];
+ return relocs.items[atom.relocs.pos..][0..atom.relocs.len];
}
-pub fn getSectionAliasSymbol(self: *Object, sect_id: u8) macho.nlist_64 {
- return self.symtab[self.getSectionAliasSymbolIndex(sect_id)];
+fn getString(self: Object, off: u32) [:0]const u8 {
+ assert(off < self.strtab.len);
+ return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
}
-pub fn getSectionAliasSymbolPtr(self: *Object, sect_id: u8) *macho.nlist_64 {
- return &self.symtab[self.getSectionAliasSymbolIndex(sect_id)];
+pub fn hasUnwindRecords(self: Object) bool {
+ return self.unwind_records.items.len > 0;
}
-fn getSourceRelocs(self: Object, sect: macho.section_64) ?[]align(1) const macho.relocation_info {
- if (sect.nreloc == 0) return null;
- return @as([*]align(1) const macho.relocation_info, @ptrCast(self.contents.ptr + sect.reloff))[0..sect.nreloc];
+pub fn hasEhFrameRecords(self: Object) bool {
+ return self.cies.items.len > 0;
}
-pub fn getRelocs(self: Object, sect_id: u8) []const macho.relocation_info {
- const sect = self.getSourceSection(sect_id);
- const start = self.section_relocs_lookup.items[sect_id];
- const len = sect.nreloc;
- return self.relocations.items[start..][0..len];
+/// TODO handle multiple CUs
+pub fn hasDebugInfo(self: Object) bool {
+ if (self.dwarf_info) |dw| {
+ return dw.compile_units.items.len > 0;
+ }
+ return self.hasSymbolStabs();
}
-pub fn getSymbolName(self: Object, index: u32) []const u8 {
- const strtab = self.in_strtab.?;
- const sym = self.symtab[index];
+fn hasSymbolStabs(self: Object) bool {
+ return self.stab_files.items.len > 0;
+}
- if (self.getSourceSymbol(index) == null) {
- assert(sym.n_strx == 0);
- return "";
+pub fn hasObjc(self: Object) bool {
+ for (self.symtab.items(.nlist)) |nlist| {
+ const name = self.getString(nlist.n_strx);
+ if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true;
+ }
+ for (self.sections.items(.header)) |sect| {
+ if (mem.eql(u8, sect.segName(), "__DATA") and mem.eql(u8, sect.sectName(), "__objc_catlist")) return true;
+ if (mem.eql(u8, sect.segName(), "__TEXT") and mem.eql(u8, sect.sectName(), "__swift")) return true;
}
+ return false;
+}
- const start = sym.n_strx;
- const len = self.strtab_lookup[index];
+pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry {
+ const lc = self.getLoadCommand(.DATA_IN_CODE) orelse return &[0]macho.data_in_code_entry{};
+ const cmd = lc.cast(macho.linkedit_data_command).?;
+ const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
+ const dice = @as(
+ [*]align(1) const macho.data_in_code_entry,
+ @ptrCast(self.data.ptr + cmd.dataoff),
+ )[0..ndice];
+ return dice;
+}
- return strtab[start..][0 .. len - 1 :0];
+pub inline fn hasSubsections(self: Object) bool {
+ return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
}
-fn getSymbolAliases(self: Object, index: u32) Entry {
- const addr = self.source_address_lookup[index];
- var start = index;
- while (start > 0 and
- self.source_address_lookup[start - 1] == addr) : (start -= 1)
- {}
- const end: u32 = for (self.source_address_lookup[start..], start..) |saddr, i| {
- if (saddr != addr) break @as(u32, @intCast(i));
- } else @as(u32, @intCast(self.source_address_lookup.len));
- return .{ .start = start, .len = end - start };
+pub fn asFile(self: *Object) File {
+ return .{ .object = self };
}
-pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
- // Find containing atom
- const Predicate = struct {
- addr: i64,
+pub fn format(
+ self: *Object,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = self;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format objects directly");
+}
- pub fn predicate(pred: @This(), other: i64) bool {
- return if (other == -1) true else other > pred.addr;
- }
- };
+const FormatContext = struct {
+ object: *Object,
+ macho_file: *MachO,
+};
- if (sect_hint) |sect_id| {
- if (self.source_section_index_lookup[sect_id].len > 0) {
- const lookup = self.source_section_index_lookup[sect_id];
- const target_sym_index = MachO.lsearch(
- i64,
- self.source_address_lookup[lookup.start..][0..lookup.len],
- Predicate{ .addr = @as(i64, @intCast(addr)) },
- );
- if (target_sym_index > 0) {
- // Hone in on the most senior alias of the target symbol.
- // See SymbolAtIndex.lessThan for more context.
- const aliases = self.getSymbolAliases(@intCast(lookup.start + target_sym_index - 1));
- return aliases.start;
- }
- }
- return self.getSectionAliasSymbolIndex(sect_id);
+pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
+ return .{ .data = .{
+ .object = self,
+ .macho_file = macho_file,
+ } };
+}
+
+fn formatAtoms(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const object = ctx.object;
+ try writer.writeAll(" atoms\n");
+ for (object.atoms.items) |atom_index| {
+ const atom = ctx.macho_file.getAtom(atom_index).?;
+ try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
}
+}
- const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{
- .addr = @as(i64, @intCast(addr)),
- });
- assert(target_sym_index > 0);
- return @as(u32, @intCast(target_sym_index - 1));
+pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatCies) {
+ return .{ .data = .{
+ .object = self,
+ .macho_file = macho_file,
+ } };
}
-pub fn getGlobal(self: Object, sym_index: u32) ?u32 {
- if (self.globals_lookup[sym_index] == -1) return null;
- return @as(u32, @intCast(self.globals_lookup[sym_index]));
+fn formatCies(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const object = ctx.object;
+ try writer.writeAll(" cies\n");
+ for (object.cies.items, 0..) |cie, i| {
+ try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.macho_file) });
+ }
}
-pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index {
- return self.atom_by_index_table[sym_index];
+pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatFdes) {
+ return .{ .data = .{
+ .object = self,
+ .macho_file = macho_file,
+ } };
}
-pub fn hasUnwindRecords(self: Object) bool {
- return self.unwind_info_sect_id != null;
+fn formatFdes(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const object = ctx.object;
+ try writer.writeAll(" fdes\n");
+ for (object.fdes.items, 0..) |fde, i| {
+ try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.macho_file) });
+ }
}
-pub fn getUnwindRecords(self: Object) []align(1) const macho.compact_unwind_entry {
- const sect_id = self.unwind_info_sect_id orelse return &[0]macho.compact_unwind_entry{};
- const sect = self.getSourceSection(sect_id);
- const data = self.getSectionContents(sect);
- const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
- return @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries];
+pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatUnwindRecords) {
+ return .{ .data = .{
+ .object = self,
+ .macho_file = macho_file,
+ } };
}
-pub fn hasEhFrameRecords(self: Object) bool {
- return self.eh_frame_sect_id != null;
+fn formatUnwindRecords(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const object = ctx.object;
+ const macho_file = ctx.macho_file;
+ try writer.writeAll(" unwind records\n");
+ for (object.unwind_records.items) |rec| {
+ try writer.print(" rec({d}) : {}\n", .{ rec, macho_file.getUnwindRecord(rec).fmt(macho_file) });
+ }
}
-pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator {
- const sect_id = self.eh_frame_sect_id orelse return .{ .data = &[0]u8{} };
- const sect = self.getSourceSection(sect_id);
- const data = self.getSectionContents(sect);
- return .{ .data = data };
+pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
+ return .{ .data = .{
+ .object = self,
+ .macho_file = macho_file,
+ } };
}
-pub fn hasDataInCode(self: Object) bool {
- return self.data_in_code.items.len > 0;
+fn formatSymtab(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const object = ctx.object;
+ try writer.writeAll(" symbols\n");
+ for (object.symbols.items) |index| {
+ const sym = ctx.macho_file.getSymbol(index);
+ try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)});
+ }
}
-const Object = @This();
+pub fn fmtPath(self: Object) std.fmt.Formatter(formatPath) {
+ return .{ .data = self };
+}
+
+fn formatPath(
+ object: Object,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ if (object.archive) |path| {
+ try writer.writeAll(path);
+ try writer.writeByte('(');
+ try writer.writeAll(object.path);
+ try writer.writeByte(')');
+ } else try writer.writeAll(object.path);
+}
+
+const Section = struct {
+ header: macho.section_64,
+ subsections: std.ArrayListUnmanaged(Subsection) = .{},
+ relocs: std.ArrayListUnmanaged(Relocation) = .{},
+};
+
+const Subsection = struct {
+ atom: Atom.Index,
+ off: u64,
+};
+
+pub const Nlist = struct {
+ nlist: macho.nlist_64,
+ size: u64,
+ 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,
+ n_sect: u8,
+ sect: macho.section_64,
+ out: *std.ArrayListUnmanaged(Relocation),
+ macho_file: *MachO,
+ ) !void {
+ const gpa = macho_file.base.comp.gpa;
+
+ const relocs = @as(
+ [*]align(1) const macho.relocation_info,
+ @ptrCast(self.data.ptr + sect.reloff),
+ )[0..sect.nreloc];
+ const code = try self.getSectionData(@intCast(n_sect));
+
+ try out.ensureTotalCapacityPrecise(gpa, relocs.len);
+
+ var i: usize = 0;
+ while (i < relocs.len) : (i += 1) {
+ const rel = relocs[i];
+ const rel_type: macho.reloc_type_x86_64 = @enumFromInt(rel.r_type);
+ const rel_offset = @as(u32, @intCast(rel.r_address));
+
+ var addend = switch (rel.r_length) {
+ 0 => code[rel_offset],
+ 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
+ 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
+ 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
+ };
+ addend += switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
+ .X86_64_RELOC_SIGNED_1 => 1,
+ .X86_64_RELOC_SIGNED_2 => 2,
+ .X86_64_RELOC_SIGNED_4 => 4,
+ else => 0,
+ };
+
+ 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
+ else
+ addend;
+ const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ sect.segName(), sect.sectName(), rel.r_address,
+ });
+ return error.MalformedObject;
+ };
+ addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
+ break :blk target;
+ } else self.symbols.items[rel.r_symbolnum];
+
+ const has_subtractor = if (i > 0 and
+ @as(macho.reloc_type_x86_64, @enumFromInt(relocs[i - 1].r_type)) == .X86_64_RELOC_SUBTRACTOR)
+ blk: {
+ if (rel_type != .X86_64_RELOC_UNSIGNED) {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: X86_64_RELOC_SUBTRACTOR followed by {s}", .{
+ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
+ });
+ return error.MalformedObject;
+ }
+ break :blk true;
+ } else false;
+
+ const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
+ switch (err) {
+ error.Pcrel => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: PC-relative {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ error.NonPcrel => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ error.InvalidLength => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
+ ),
+ error.NonExtern => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: non-extern target in {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ }
+ return error.MalformedObject;
+ };
+
+ out.appendAssumeCapacity(.{
+ .tag = if (rel.r_extern == 1) .@"extern" else .local,
+ .offset = @as(u32, @intCast(rel.r_address)),
+ .target = target,
+ .addend = addend,
+ .type = @"type",
+ .meta = .{
+ .pcrel = rel.r_pcrel == 1,
+ .has_subtractor = has_subtractor,
+ .length = rel.r_length,
+ .symbolnum = rel.r_symbolnum,
+ },
+ });
+ }
+ }
+
+ fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_x86_64) !Relocation.Type {
+ switch (rel_type) {
+ .X86_64_RELOC_UNSIGNED => {
+ if (rel.r_pcrel == 1) return error.Pcrel;
+ if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
+ return .unsigned;
+ },
+
+ .X86_64_RELOC_SUBTRACTOR => {
+ if (rel.r_pcrel == 1) return error.Pcrel;
+ return .subtractor;
+ },
+
+ .X86_64_RELOC_BRANCH,
+ .X86_64_RELOC_GOT_LOAD,
+ .X86_64_RELOC_GOT,
+ .X86_64_RELOC_TLV,
+ => {
+ if (rel.r_pcrel == 0) return error.NonPcrel;
+ if (rel.r_length != 2) return error.InvalidLength;
+ if (rel.r_extern == 0) return error.NonExtern;
+ return switch (rel_type) {
+ .X86_64_RELOC_BRANCH => .branch,
+ .X86_64_RELOC_GOT_LOAD => .got_load,
+ .X86_64_RELOC_GOT => .got,
+ .X86_64_RELOC_TLV => .tlv,
+ else => unreachable,
+ };
+ },
+
+ .X86_64_RELOC_SIGNED,
+ .X86_64_RELOC_SIGNED_1,
+ .X86_64_RELOC_SIGNED_2,
+ .X86_64_RELOC_SIGNED_4,
+ => {
+ if (rel.r_pcrel == 0) return error.NonPcrel;
+ if (rel.r_length != 2) return error.InvalidLength;
+ return switch (rel_type) {
+ .X86_64_RELOC_SIGNED => .signed,
+ .X86_64_RELOC_SIGNED_1 => .signed1,
+ .X86_64_RELOC_SIGNED_2 => .signed2,
+ .X86_64_RELOC_SIGNED_4 => .signed4,
+ else => unreachable,
+ };
+ },
+ }
+ }
+};
+
+const aarch64 = struct {
+ fn parseRelocs(
+ self: *const Object,
+ n_sect: u8,
+ sect: macho.section_64,
+ out: *std.ArrayListUnmanaged(Relocation),
+ macho_file: *MachO,
+ ) !void {
+ const gpa = macho_file.base.comp.gpa;
+
+ const relocs = @as(
+ [*]align(1) const macho.relocation_info,
+ @ptrCast(self.data.ptr + sect.reloff),
+ )[0..sect.nreloc];
+ const code = try self.getSectionData(@intCast(n_sect));
+
+ try out.ensureTotalCapacityPrecise(gpa, relocs.len);
+
+ var i: usize = 0;
+ while (i < relocs.len) : (i += 1) {
+ var rel = relocs[i];
+ const rel_offset = @as(u32, @intCast(rel.r_address));
+
+ var addend: i64 = 0;
+
+ switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
+ .ARM64_RELOC_ADDEND => {
+ addend = rel.r_symbolnum;
+ i += 1;
+ if (i >= relocs.len) {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: unterminated ARM64_RELOC_ADDEND", .{
+ sect.segName(), sect.sectName(), rel_offset,
+ });
+ return error.MalformedObject;
+ }
+ rel = relocs[i];
+ switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
+ .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
+ else => |x| {
+ try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: ARM64_RELOC_ADDEND followed by {s}",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(x) },
+ );
+ return error.MalformedObject;
+ },
+ }
+ },
+ .ARM64_RELOC_UNSIGNED => {
+ addend = switch (rel.r_length) {
+ 0 => code[rel_offset],
+ 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
+ 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
+ 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
+ };
+ },
+ else => {},
+ }
+
+ const rel_type: macho.reloc_type_arm64 = @enumFromInt(rel.r_type);
+
+ 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
+ else
+ addend;
+ const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
+ sect.segName(), sect.sectName(), rel.r_address,
+ });
+ return error.MalformedObject;
+ };
+ addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
+ break :blk target;
+ } else self.symbols.items[rel.r_symbolnum];
+
+ const has_subtractor = if (i > 0 and
+ @as(macho.reloc_type_arm64, @enumFromInt(relocs[i - 1].r_type)) == .ARM64_RELOC_SUBTRACTOR)
+ blk: {
+ if (rel_type != .ARM64_RELOC_UNSIGNED) {
+ try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: ARM64_RELOC_SUBTRACTOR followed by {s}", .{
+ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
+ });
+ return error.MalformedObject;
+ }
+ break :blk true;
+ } else false;
+
+ const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
+ switch (err) {
+ error.Pcrel => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: PC-relative {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ error.NonPcrel => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ error.InvalidLength => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
+ ),
+ error.NonExtern => try macho_file.reportParseError2(
+ self.index,
+ "{s},{s}: 0x{x}: non-extern target in {s} relocation",
+ .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
+ ),
+ }
+ return error.MalformedObject;
+ };
+
+ out.appendAssumeCapacity(.{
+ .tag = if (rel.r_extern == 1) .@"extern" else .local,
+ .offset = @as(u32, @intCast(rel.r_address)),
+ .target = target,
+ .addend = addend,
+ .type = @"type",
+ .meta = .{
+ .pcrel = rel.r_pcrel == 1,
+ .has_subtractor = has_subtractor,
+ .length = rel.r_length,
+ .symbolnum = rel.r_symbolnum,
+ },
+ });
+ }
+ }
+
+ fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_arm64) !Relocation.Type {
+ switch (rel_type) {
+ .ARM64_RELOC_UNSIGNED => {
+ if (rel.r_pcrel == 1) return error.Pcrel;
+ if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
+ return .unsigned;
+ },
+
+ .ARM64_RELOC_SUBTRACTOR => {
+ if (rel.r_pcrel == 1) return error.Pcrel;
+ return .subtractor;
+ },
+
+ .ARM64_RELOC_BRANCH26,
+ .ARM64_RELOC_PAGE21,
+ .ARM64_RELOC_GOT_LOAD_PAGE21,
+ .ARM64_RELOC_TLVP_LOAD_PAGE21,
+ .ARM64_RELOC_POINTER_TO_GOT,
+ => {
+ if (rel.r_pcrel == 0) return error.NonPcrel;
+ if (rel.r_length != 2) return error.InvalidLength;
+ if (rel.r_extern == 0) return error.NonExtern;
+ return switch (rel_type) {
+ .ARM64_RELOC_BRANCH26 => .branch,
+ .ARM64_RELOC_PAGE21 => .page,
+ .ARM64_RELOC_GOT_LOAD_PAGE21 => .got_load_page,
+ .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp_page,
+ .ARM64_RELOC_POINTER_TO_GOT => .got,
+ else => unreachable,
+ };
+ },
+
+ .ARM64_RELOC_PAGEOFF12,
+ .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
+ .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
+ => {
+ if (rel.r_pcrel == 1) return error.Pcrel;
+ if (rel.r_length != 2) return error.InvalidLength;
+ if (rel.r_extern == 0) return error.NonExtern;
+ return switch (rel_type) {
+ .ARM64_RELOC_PAGEOFF12 => .pageoff,
+ .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got_load_pageoff,
+ .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp_pageoff,
+ else => unreachable,
+ };
+ },
+
+ .ARM64_RELOC_ADDEND => unreachable, // We make it part of the addend field
+ }
+ }
+};
-const std = @import("std");
-const build_options = @import("build_options");
const assert = std.debug.assert;
-const dwarf = std.dwarf;
const eh_frame = @import("eh_frame.zig");
-const fs = std.fs;
-const io = std.io;
const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
-const sort = std.sort;
const trace = @import("../../tracy.zig").trace;
+const std = @import("std");
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
+const Cie = eh_frame.Cie;
const DwarfInfo = @import("DwarfInfo.zig");
+const Fde = eh_frame.Fde;
+const File = @import("file.zig").File;
const LoadCommandIterator = macho.LoadCommandIterator;
const MachO = @import("../MachO.zig");
-const Platform = @import("load_commands.zig").Platform;
-const SymbolWithLoc = MachO.SymbolWithLoc;
+const Object = @This();
+const Relocation = @import("Relocation.zig");
+const Symbol = @import("Symbol.zig");
const UnwindInfo = @import("UnwindInfo.zig");
-const Alignment = Atom.Alignment;
diff --git a/src/link/MachO/Relocation.zig b/src/link/MachO/Relocation.zig
index 85c19c7608..eff628071f 100644
--- a/src/link/MachO/Relocation.zig
+++ b/src/link/MachO/Relocation.zig
@@ -1,235 +1,69 @@
-//! Relocation used by the self-hosted backends to instruct the linker where and how to
-//! fixup the values when flushing the contents to file and/or memory.
-
-type: Type,
-target: SymbolWithLoc,
+tag: enum { @"extern", local },
offset: u32,
+target: u32,
addend: i64,
-pcrel: bool,
-length: u2,
-dirty: bool = true,
-
-pub const Type = enum {
- // x86, x86_64
- /// RIP-relative displacement to a GOT pointer
- got,
- /// RIP-relative displacement
- signed,
- /// RIP-relative displacement to a TLV thunk
- tlv,
-
- // aarch64
- /// PC-relative distance to target page in GOT section
- got_page,
- /// Offset to a GOT pointer relative to the start of a page in GOT section
- got_pageoff,
- /// PC-relative distance to target page in a section
- page,
- /// Offset to a pointer relative to the start of a page in a section
- pageoff,
-
- // common
- /// PC/RIP-relative displacement B/BL/CALL
- branch,
- /// Absolute pointer value
- unsigned,
- /// Relative offset to TLV initializer
- tlv_initializer,
-};
-
-/// Returns true if and only if the reloc can be resolved.
-pub fn isResolvable(self: Relocation, macho_file: *MachO) bool {
- _ = self.getTargetBaseAddress(macho_file) orelse return false;
- return true;
+type: Type,
+meta: packed struct {
+ pcrel: bool,
+ has_subtractor: bool,
+ length: u2,
+ symbolnum: u24,
+},
+
+pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol {
+ assert(rel.tag == .@"extern");
+ return macho_file.getSymbol(rel.target);
}
-pub fn isGotIndirection(self: Relocation) bool {
- return switch (self.type) {
- .got, .got_page, .got_pageoff => true,
- else => false,
- };
+pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom {
+ assert(rel.tag == .local);
+ return macho_file.getAtom(rel.target).?;
}
-pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool {
- return switch (self.type) {
- .branch => macho_file.getSymbol(self.target).undf(),
- else => false,
+pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
+ return switch (rel.tag) {
+ .local => rel.getTargetAtom(macho_file).value,
+ .@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file),
};
}
-pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- if (self.isStubTrampoline(macho_file)) {
- const index = macho_file.stub_table.lookup.get(self.target) orelse return null;
- const header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
- return header.addr +
- index * @import("stubs.zig").stubSize(target.cpu.arch);
- }
- switch (self.type) {
- .got, .got_page, .got_pageoff => {
- const got_index = macho_file.got_table.lookup.get(self.target) orelse return null;
- const header = macho_file.sections.items(.header)[macho_file.got_section_index.?];
- return header.addr + got_index * @sizeOf(u64);
- },
- .tlv => {
- const atom_index = macho_file.tlv_table.get(self.target) orelse return null;
- const atom = macho_file.getAtom(atom_index);
- return atom.getSymbol(macho_file).n_value;
- },
- else => {
- const target_atom_index = macho_file.getAtomIndexForSymbol(self.target) orelse return null;
- const target_atom = macho_file.getAtom(target_atom_index);
- return target_atom.getSymbol(macho_file).n_value;
- },
- }
-}
-
-pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const arch = target.cpu.arch;
- const atom = macho_file.getAtom(atom_index);
- const source_sym = atom.getSymbol(macho_file);
- const source_addr = source_sym.n_value + self.offset;
-
- const target_base_addr = self.getTargetBaseAddress(macho_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
- const target_addr: i64 = switch (self.type) {
- .tlv_initializer => blk: {
- assert(self.addend == 0); // Addend here makes no sense.
- const header = macho_file.sections.items(.header)[macho_file.thread_data_section_index.?];
- break :blk @as(i64, @intCast(target_base_addr - header.addr));
- },
- else => @as(i64, @intCast(target_base_addr)) + self.addend,
+pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
+ return switch (rel.tag) {
+ .local => 0,
+ .@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file),
};
-
- relocs_log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
- source_addr,
- target_addr,
- macho_file.getSymbolName(self.target),
- @tagName(self.type),
- });
-
- switch (arch) {
- .aarch64 => self.resolveAarch64(source_addr, target_addr, code),
- .x86_64 => self.resolveX8664(source_addr, target_addr, code),
- else => unreachable,
- }
-}
-
-fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {
- var buffer = code[self.offset..];
- switch (self.type) {
- .branch => {
- const displacement = math.cast(
- i28,
- @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)),
- ) orelse unreachable; // TODO codegen should never allow for jump larger than i28 displacement
- var inst = aarch64.Instruction{
- .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.unconditional_branch_immediate,
- ), buffer[0..4]),
- };
- inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
- mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
- },
- .page, .got_page => {
- const source_page = @as(i32, @intCast(source_addr >> 12));
- const target_page = @as(i32, @intCast(target_addr >> 12));
- const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page))));
- var inst = aarch64.Instruction{
- .pc_relative_address = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.pc_relative_address,
- ), buffer[0..4]),
- };
- inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
- inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
- mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
- },
- .pageoff, .got_pageoff => {
- const narrowed = @as(u12, @truncate(@as(u64, @intCast(target_addr))));
- if (isArithmeticOp(buffer[0..4])) {
- var inst = aarch64.Instruction{
- .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.add_subtract_immediate,
- ), buffer[0..4]),
- };
- inst.add_subtract_immediate.imm12 = narrowed;
- mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
- } else {
- var inst = aarch64.Instruction{
- .load_store_register = mem.bytesToValue(meta.TagPayload(
- aarch64.Instruction,
- aarch64.Instruction.load_store_register,
- ), buffer[0..4]),
- };
- const offset: u12 = blk: {
- if (inst.load_store_register.size == 0) {
- if (inst.load_store_register.v == 1) {
- // 128-bit SIMD is scaled by 16.
- break :blk @divExact(narrowed, 16);
- }
- // Otherwise, 8-bit SIMD or ldrb.
- break :blk narrowed;
- } else {
- const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable;
- break :blk @divExact(narrowed, denom);
- }
- };
- inst.load_store_register.offset = offset;
- mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
- }
- },
- .tlv_initializer, .unsigned => switch (self.length) {
- 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little),
- 3 => mem.writeInt(u64, buffer[0..8], @as(u64, @bitCast(target_addr)), .little),
- else => unreachable,
- },
- .got, .signed, .tlv => unreachable, // Invalid target architecture.
- }
-}
-
-fn resolveX8664(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {
- switch (self.type) {
- .branch, .got, .tlv, .signed => {
- const displacement = @as(i32, @intCast(@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 4));
- mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @bitCast(displacement)), .little);
- },
- .tlv_initializer, .unsigned => {
- switch (self.length) {
- 2 => {
- mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little);
- },
- 3 => {
- mem.writeInt(u64, code[self.offset..][0..8], @as(u64, @bitCast(target_addr)), .little);
- },
- else => unreachable,
- }
- },
- .got_page, .got_pageoff, .page, .pageoff => unreachable, // Invalid target architecture.
- }
}
-pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
- const group_decode = @as(u5, @truncate(inst[3]));
- return ((group_decode >> 2) == 4);
+pub fn getZigGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
+ return switch (rel.tag) {
+ .local => 0,
+ .@"extern" => rel.getTargetSymbol(macho_file).getZigGotAddress(macho_file),
+ };
}
-pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 {
- const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction));
- return math.cast(i32, disp) orelse error.Overflow;
+pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 {
+ const addend: i64 = switch (rel.type) {
+ .signed => 0,
+ .signed1 => -1,
+ .signed2 => -2,
+ .signed4 => -4,
+ else => 0,
+ };
+ return switch (cpu_arch) {
+ .x86_64 => if (rel.meta.pcrel) addend - 4 else addend,
+ else => addend,
+ };
}
-pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 {
- const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
- return math.cast(i28, disp) orelse error.Overflow;
+pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool {
+ _ = ctx;
+ return lhs.offset < rhs.offset;
}
-pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 {
- const source_page = @as(i32, @intCast(source_addr >> 12));
- const target_page = @as(i32, @intCast(target_addr >> 12));
- const pages = @as(i21, @intCast(target_page - source_page));
+pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 {
+ const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow;
+ const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow;
+ const pages = math.cast(i21, tpage - spage) orelse return error.Overflow;
return pages;
}
@@ -242,8 +76,8 @@ pub const PageOffsetInstKind = enum {
load_store_128,
};
-pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
- const narrowed = @as(u12, @truncate(target_addr));
+pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 {
+ const narrowed = @as(u12, @truncate(taddr));
return switch (kind) {
.arithmetic, .load_store_8 => narrowed,
.load_store_16 => try math.divExact(u12, narrowed, 2),
@@ -253,17 +87,59 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
};
}
-const Relocation = @This();
+pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
+ const group_decode = @as(u5, @truncate(inst[3]));
+ return ((group_decode >> 2) == 4);
+}
+
+pub const Type = enum {
+ // x86_64
+ /// RIP-relative displacement (X86_64_RELOC_SIGNED)
+ signed,
+ /// RIP-relative displacement (X86_64_RELOC_SIGNED_1)
+ signed1,
+ /// RIP-relative displacement (X86_64_RELOC_SIGNED_2)
+ signed2,
+ /// RIP-relative displacement (X86_64_RELOC_SIGNED_4)
+ signed4,
+ /// RIP-relative GOT load (X86_64_RELOC_GOT_LOAD)
+ got_load,
+ /// RIP-relative TLV load (X86_64_RELOC_TLV)
+ tlv,
+ /// Zig-specific __got_zig indirection
+ zig_got_load,
+
+ // arm64
+ /// PC-relative load (distance to page, ARM64_RELOC_PAGE21)
+ page,
+ /// Non-PC-relative offset to symbol (ARM64_RELOC_PAGEOFF12)
+ pageoff,
+ /// PC-relative GOT load (distance to page, ARM64_RELOC_GOT_LOAD_PAGE21)
+ got_load_page,
+ /// Non-PC-relative offset to GOT slot (ARM64_RELOC_GOT_LOAD_PAGEOFF12)
+ got_load_pageoff,
+ /// PC-relative TLV load (distance to page, ARM64_RELOC_TLVP_LOAD_PAGE21)
+ tlvp_page,
+ /// Non-PC-relative offset to TLV slot (ARM64_RELOC_TLVP_LOAD_PAGEOFF12)
+ tlvp_pageoff,
+
+ // common
+ /// PC-relative call/bl/b (X86_64_RELOC_BRANCH or ARM64_RELOC_BRANCH26)
+ branch,
+ /// PC-relative displacement to GOT pointer (X86_64_RELOC_GOT or ARM64_RELOC_POINTER_TO_GOT)
+ got,
+ /// Absolute subtractor value (X86_64_RELOC_SUBTRACTOR or ARM64_RELOC_SUBTRACTOR)
+ subtractor,
+ /// Absolute relocation (X86_64_RELOC_UNSIGNED or ARM64_RELOC_UNSIGNED)
+ unsigned,
+};
-const std = @import("std");
-const aarch64 = @import("../../arch/aarch64/bits.zig");
const assert = std.debug.assert;
-const relocs_log = std.log.scoped(.link_relocs);
const macho = std.macho;
const math = std.math;
-const mem = std.mem;
-const meta = std.meta;
+const std = @import("std");
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
+const Relocation = @This();
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig
new file mode 100644
index 0000000000..e8a8a561b7
--- /dev/null
+++ b/src/link/MachO/Symbol.zig
@@ -0,0 +1,417 @@
+//! Represents a defined symbol.
+
+/// Allocated address value of this symbol.
+value: u64 = 0,
+
+/// Offset into the linker's intern table.
+name: u32 = 0,
+
+/// File where this symbol is defined.
+file: File.Index = 0,
+
+/// Atom containing this symbol if any.
+/// Index of 0 means there is no associated atom with this symbol.
+/// Use `getAtom` to get the pointer to the atom.
+atom: Atom.Index = 0,
+
+/// Assigned output section index for this atom.
+out_n_sect: u16 = 0,
+
+/// Index of the source nlist this symbol references.
+/// Use `getNlist` to pull the nlist from the relevant file.
+nlist_idx: Index = 0,
+
+/// Misc flags for the symbol packaged as packed struct for compression.
+flags: Flags = .{},
+
+visibility: Visibility = .local,
+
+extra: u32 = 0,
+
+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;
+}
+
+pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
+ const file = symbol.getFile(macho_file).?;
+ const is_dylib_weak = switch (file) {
+ .dylib => |x| x.weak,
+ else => false,
+ };
+ return is_dylib_weak or symbol.flags.weak_ref;
+}
+
+pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
+ return macho_file.strings.getAssumeExists(symbol.name);
+}
+
+pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
+ return macho_file.getAtom(symbol.atom);
+}
+
+pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
+ return macho_file.getFile(symbol.file);
+}
+
+/// Asserts file is an object.
+pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
+ const file = symbol.getFile(macho_file).?;
+ return switch (file) {
+ .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
+ else => unreachable,
+ };
+}
+
+pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 {
+ const file = symbol.getFile(macho_file).?;
+ assert(file == .object);
+ return file.object.symtab.items(.size)[symbol.nlist_idx];
+}
+
+pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 {
+ assert(symbol.flags.import);
+ const file = symbol.getFile(macho_file) orelse return null;
+ return switch (file) {
+ .dylib => |x| x.ordinal,
+ else => null,
+ };
+}
+
+pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 {
+ const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32);
+ const in_archive = switch (file) {
+ .object => |x| !x.alive,
+ else => false,
+ };
+ return file.getSymbolRank(.{
+ .archive = in_archive,
+ .weak = symbol.flags.weak,
+ .tentative = symbol.flags.tentative,
+ });
+}
+
+pub fn getAddress(symbol: Symbol, opts: struct {
+ stubs: bool = true,
+}, macho_file: *MachO) u64 {
+ if (opts.stubs) {
+ if (symbol.flags.stubs) {
+ return symbol.getStubsAddress(macho_file);
+ } else if (symbol.flags.objc_stubs) {
+ return symbol.getObjcStubsAddress(macho_file);
+ }
+ }
+ if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value;
+ return symbol.value;
+}
+
+pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.has_got) return 0;
+ const extra = symbol.getExtra(macho_file).?;
+ return macho_file.got.getAddress(extra.got, macho_file);
+}
+
+pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.stubs) return 0;
+ const extra = symbol.getExtra(macho_file).?;
+ return macho_file.stubs.getAddress(extra.stubs, macho_file);
+}
+
+pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.objc_stubs) return 0;
+ const extra = symbol.getExtra(macho_file).?;
+ return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
+}
+
+pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.objc_stubs) return 0;
+ const extra = symbol.getExtra(macho_file).?;
+ const atom = macho_file.getAtom(extra.objc_selrefs).?;
+ assert(atom.flags.alive);
+ return atom.value;
+}
+
+pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.tlv_ptr) return 0;
+ const extra = symbol.getExtra(macho_file).?;
+ return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
+}
+
+const GetOrCreateZigGotEntryResult = struct {
+ found_existing: bool,
+ index: ZigGotSection.Index,
+};
+
+pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
+ assert(!macho_file.base.isRelocatable());
+ assert(symbol.flags.needs_zig_got);
+ if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got };
+ const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
+ return .{ .found_existing = false, .index = index };
+}
+
+pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
+ if (!symbol.flags.has_zig_got) return 0;
+ const extras = symbol.getExtra(macho_file).?;
+ return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
+}
+
+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,
+ };
+ var idx = symbol.getExtra(macho_file).?.symtab;
+ if (symbol.isLocal()) {
+ idx += symtab_ctx.ilocal;
+ } else if (symbol.flags.@"export") {
+ idx += symtab_ctx.iexport;
+ } else {
+ assert(symbol.flags.import);
+ idx += symtab_ctx.iimport;
+ }
+ return idx;
+}
+
+const AddExtraOpts = struct {
+ got: ?u32 = null,
+ zig_got: ?u32 = null,
+ stubs: ?u32 = null,
+ objc_stubs: ?u32 = null,
+ objc_selrefs: ?u32 = null,
+ tlv_ptr: ?u32 = null,
+ symtab: ?u32 = null,
+};
+
+pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
+ if (symbol.getExtra(macho_file) == null) {
+ symbol.extra = try macho_file.addSymbolExtra(.{});
+ }
+ var extra = symbol.getExtra(macho_file).?;
+ inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
+ if (@field(opts, field.name)) |x| {
+ @field(extra, field.name) = x;
+ }
+ }
+ symbol.setExtra(extra, macho_file);
+}
+
+pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
+ return macho_file.getSymbolExtra(symbol.extra);
+}
+
+pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
+ macho_file.setSymbolExtra(symbol.extra, extra);
+}
+
+pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
+ if (symbol.isLocal()) {
+ 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(.{ .stubs = false }, macho_file);
+
+ switch (symbol.visibility) {
+ .hidden => out.n_type |= macho.N_PEXT,
+ else => {},
+ }
+ } else if (symbol.flags.@"export") {
+ assert(symbol.visibility == .global);
+ 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(.{ .stubs = false }, macho_file);
+ out.n_desc = 0;
+
+ if (symbol.flags.weak) {
+ out.n_desc |= macho.N_WEAK_DEF;
+ }
+ if (symbol.flags.dyn_ref) {
+ out.n_desc |= macho.REFERENCED_DYNAMICALLY;
+ }
+ } else {
+ assert(symbol.visibility == .global);
+ out.n_type = macho.N_EXT;
+ out.n_sect = 0;
+ out.n_value = 0;
+ out.n_desc = 0;
+
+ // TODO:
+ // const ord: u16 = if (macho_file.options.namespace == .flat)
+ // @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP))
+ // else if (symbol.getDylibOrdinal(macho_file)) |ord|
+ // ord
+ // else
+ // macho.BIND_SPECIAL_DYLIB_SELF;
+ const ord: u16 = if (symbol.getDylibOrdinal(macho_file)) |ord|
+ ord
+ else
+ macho.BIND_SPECIAL_DYLIB_SELF;
+ out.n_desc = macho.N_SYMBOL_RESOLVER * ord;
+
+ if (symbol.flags.weak) {
+ out.n_desc |= macho.N_WEAK_DEF;
+ }
+
+ if (symbol.weakRef(macho_file)) {
+ out.n_desc |= macho.N_WEAK_REF;
+ }
+ }
+}
+
+pub fn format(
+ symbol: Symbol,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = symbol;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format symbols directly");
+}
+
+const FormatContext = struct {
+ symbol: Symbol,
+ macho_file: *MachO,
+};
+
+pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .symbol = symbol,
+ .macho_file = macho_file,
+ } };
+}
+
+fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ const symbol = ctx.symbol;
+ try writer.print("%{d} : {s} : @{x}", .{
+ symbol.nlist_idx,
+ symbol.getName(ctx.macho_file),
+ symbol.getAddress(.{}, ctx.macho_file),
+ });
+ if (symbol.getFile(ctx.macho_file)) |file| {
+ if (symbol.out_n_sect != 0) {
+ try writer.print(" : sect({d})", .{symbol.out_n_sect});
+ }
+ if (symbol.getAtom(ctx.macho_file)) |atom| {
+ try writer.print(" : atom({d})", .{atom.atom_index});
+ }
+ var buf: [2]u8 = .{'_'} ** 2;
+ if (symbol.flags.@"export") buf[0] = 'E';
+ 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) {
+ .zig_object => |x| try writer.print(" : zig_object({d})", .{x.index}),
+ .internal => |x| try writer.print(" : internal({d})", .{x.index}),
+ .object => |x| try writer.print(" : object({d})", .{x.index}),
+ .dylib => |x| try writer.print(" : dylib({d})", .{x.index}),
+ }
+ } else try writer.writeAll(" : unresolved");
+}
+
+pub const Flags = packed struct {
+ /// Whether the symbol is imported at runtime.
+ import: bool = false,
+
+ /// Whether the symbol is exported at runtime.
+ @"export": bool = false,
+
+ /// Whether this symbol is weak.
+ weak: bool = false,
+
+ /// Whether this symbol is weakly referenced.
+ weak_ref: bool = false,
+
+ /// Whether this symbol is dynamically referenced.
+ dyn_ref: bool = false,
+
+ /// Whether this symbol was marked as N_NO_DEAD_STRIP.
+ no_dead_strip: bool = false,
+
+ /// Whether this symbol can be interposed at runtime.
+ interposable: bool = false,
+
+ /// Whether this symbol is absolute.
+ abs: bool = false,
+
+ /// Whether this symbol is a tentative definition.
+ tentative: bool = false,
+
+ /// Whether this symbol is a thread-local variable.
+ tlv: bool = false,
+
+ /// Whether the symbol makes into the output symtab or not.
+ output_symtab: bool = false,
+
+ /// Whether the symbol contains __got indirection.
+ needs_got: bool = false,
+ has_got: bool = false,
+
+ /// Whether the symbol contains __got_zig indirection.
+ needs_zig_got: bool = false,
+ has_zig_got: bool = false,
+
+ /// Whether the symbols contains __stubs indirection.
+ stubs: bool = false,
+
+ /// Whether the symbol has a TLV pointer.
+ tlv_ptr: bool = false,
+
+ /// Whether the symbol contains __objc_stubs indirection.
+ objc_stubs: bool = false,
+};
+
+pub const Visibility = enum {
+ global,
+ hidden,
+ local,
+};
+
+pub const Extra = struct {
+ got: u32 = 0,
+ zig_got: u32 = 0,
+ stubs: u32 = 0,
+ objc_stubs: u32 = 0,
+ objc_selrefs: u32 = 0,
+ tlv_ptr: u32 = 0,
+ symtab: u32 = 0,
+};
+
+pub const Index = u32;
+
+const assert = std.debug.assert;
+const macho = std.macho;
+const std = @import("std");
+
+const Atom = @import("Atom.zig");
+const File = @import("file.zig").File;
+const MachO = @import("../MachO.zig");
+const Nlist = Object.Nlist;
+const Object = @import("Object.zig");
+const Symbol = @This();
+const ZigGotSection = @import("synthetic.zig").ZigGotSection;
diff --git a/src/link/MachO/UnwindInfo.zig b/src/link/MachO/UnwindInfo.zig
index 7223b5555f..8f62cc2f88 100644
--- a/src/link/MachO/UnwindInfo.zig
+++ b/src/link/MachO/UnwindInfo.zig
@@ -1,376 +1,132 @@
-gpa: Allocator,
-
/// List of all unwind records gathered from all objects and sorted
-/// by source function address.
-records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{},
-records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{},
+/// by allocated relative function address within the section.
+records: std.ArrayListUnmanaged(Record.Index) = .{},
/// List of all personalities referenced by either unwind info entries
/// or __eh_frame entries.
-personalities: [max_personalities]SymbolWithLoc = undefined,
+personalities: [max_personalities]Symbol.Index = undefined,
personalities_count: u2 = 0,
/// List of common encodings sorted in descending order with the most common first.
-common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined,
+common_encodings: [max_common_encodings]Encoding = undefined,
common_encodings_count: u7 = 0,
/// List of record indexes containing an LSDA pointer.
-lsdas: std.ArrayListUnmanaged(RecordIndex) = .{},
-lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{},
+lsdas: std.ArrayListUnmanaged(u32) = .{},
+lsdas_lookup: std.ArrayListUnmanaged(u32) = .{},
/// List of second level pages.
pages: std.ArrayListUnmanaged(Page) = .{},
-/// Upper bound (exclusive) of all the record ranges
-end_boundary: u64 = 0,
-
-const RecordIndex = u32;
-
-const max_personalities = 3;
-const max_common_encodings = 127;
-const max_compact_encodings = 256;
-
-const second_level_page_bytes = 0x1000;
-const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
-
-const max_regular_second_level_entries =
- (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
- @sizeOf(macho.unwind_info_regular_second_level_entry);
-
-const max_compressed_second_level_entries =
- (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
- @sizeOf(u32);
-
-const compressed_entry_func_offset_mask = ~@as(u24, 0);
-
-const Page = struct {
- kind: enum { regular, compressed },
- start: RecordIndex,
- count: u16,
- page_encodings: [max_compact_encodings]RecordIndex = undefined,
- page_encodings_count: u9 = 0,
-
- fn appendPageEncoding(page: *Page, record_id: RecordIndex) void {
- assert(page.page_encodings_count <= max_compact_encodings);
- page.page_encodings[page.page_encodings_count] = record_id;
- page.page_encodings_count += 1;
- }
-
- fn getPageEncoding(
- page: *const Page,
- info: *const UnwindInfo,
- enc: macho.compact_unwind_encoding_t,
- ) ?u8 {
- comptime var index: u9 = 0;
- inline while (index < max_compact_encodings) : (index += 1) {
- if (index >= page.page_encodings_count) return null;
- const record_id = page.page_encodings[index];
- const record = info.records.items[record_id];
- if (record.compactUnwindEncoding == enc) {
- return @as(u8, @intCast(index));
- }
- }
- return null;
- }
-
- fn format(
- page: *const Page,
- comptime unused_format_string: []const u8,
- options: std.fmt.FormatOptions,
- writer: anytype,
- ) !void {
- _ = page;
- _ = unused_format_string;
- _ = options;
- _ = writer;
- @compileError("do not format Page directly; use page.fmtDebug()");
- }
-
- const DumpCtx = struct {
- page: *const Page,
- info: *const UnwindInfo,
- };
-
- fn dump(
- ctx: DumpCtx,
- comptime unused_format_string: []const u8,
- options: std.fmt.FormatOptions,
- writer: anytype,
- ) @TypeOf(writer).Error!void {
- _ = options;
- comptime assert(unused_format_string.len == 0);
- try writer.writeAll("Page:\n");
- try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
- try writer.print(" entries: {d} - {d}\n", .{
- ctx.page.start,
- ctx.page.start + ctx.page.count,
- });
- try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
- for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |record_id, i| {
- const record = ctx.info.records.items[record_id];
- const enc = record.compactUnwindEncoding;
- try writer.print(" {d}: 0x{x:0>8}\n", .{ ctx.info.common_encodings_count + i, enc });
- }
- }
-
- fn fmtDebug(page: *const Page, info: *const UnwindInfo) std.fmt.Formatter(dump) {
- return .{ .data = .{
- .page = page,
- .info = info,
- } };
- }
-
- fn write(page: *const Page, info: *const UnwindInfo, writer: anytype) !void {
- switch (page.kind) {
- .regular => {
- try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
- .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
- .entryCount = page.count,
- });
-
- for (info.records.items[page.start..][0..page.count]) |record| {
- try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
- .functionOffset = @as(u32, @intCast(record.rangeStart)),
- .encoding = record.compactUnwindEncoding,
- });
- }
- },
- .compressed => {
- const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
- @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
- try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
- .entryPageOffset = entry_offset,
- .entryCount = page.count,
- .encodingsPageOffset = @sizeOf(
- macho.unwind_info_compressed_second_level_page_header,
- ),
- .encodingsCount = page.page_encodings_count,
- });
-
- for (page.page_encodings[0..page.page_encodings_count]) |record_id| {
- const enc = info.records.items[record_id].compactUnwindEncoding;
- try writer.writeInt(u32, enc, .little);
- }
-
- assert(page.count > 0);
- const first_entry = info.records.items[page.start];
- for (info.records.items[page.start..][0..page.count]) |record| {
- const enc_index = blk: {
- if (info.getCommonEncoding(record.compactUnwindEncoding)) |id| {
- break :blk id;
- }
- const ncommon = info.common_encodings_count;
- break :blk ncommon + page.getPageEncoding(info, record.compactUnwindEncoding).?;
- };
- const compressed = macho.UnwindInfoCompressedEntry{
- .funcOffset = @as(u24, @intCast(record.rangeStart - first_entry.rangeStart)),
- .encodingIndex = @as(u8, @intCast(enc_index)),
- };
- try writer.writeStruct(compressed);
- }
- },
- }
- }
-};
+pub fn deinit(info: *UnwindInfo, allocator: Allocator) void {
+ info.records.deinit(allocator);
+ info.pages.deinit(allocator);
+ info.lsdas.deinit(allocator);
+ info.lsdas_lookup.deinit(allocator);
+}
-pub fn deinit(info: *UnwindInfo) void {
- info.records.deinit(info.gpa);
- info.records_lookup.deinit(info.gpa);
- info.pages.deinit(info.gpa);
- info.lsdas.deinit(info.gpa);
- info.lsdas_lookup.deinit(info.gpa);
+fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool {
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const lhs = macho_file.getUnwindRecord(lhs_index);
+ const rhs = macho_file.getUnwindRecord(rhs_index);
+ if (cpu_arch == .x86_64) {
+ if (lhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND) or
+ rhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND)) return false;
+ }
+ const lhs_per = lhs.personality orelse 0;
+ const rhs_per = rhs.personality orelse 0;
+ return lhs.enc.eql(rhs.enc) and
+ lhs_per == rhs_per and
+ lhs.fde == rhs.fde and
+ lhs.getLsdaAtom(macho_file) == null and rhs.getLsdaAtom(macho_file) == null;
}
-pub fn scanRelocs(macho_file: *MachO) !void {
- if (macho_file.unwind_info_section_index == null) return;
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- for (macho_file.objects.items, 0..) |*object, object_id| {
- const unwind_records = object.getUnwindRecords();
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (inner_syms_it.next()) |sym| {
- const record_id = object.unwind_records_lookup.get(sym) orelse continue;
- if (object.unwind_relocs_lookup[record_id].dead) continue;
- const record = unwind_records[record_id];
- if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
- if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
- // Personality function; add GOT pointer.
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = @as(u32, @intCast(object_id)),
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
- });
- try macho_file.addGotEntry(reloc_target);
- }
- }
+pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+
+ log.debug("generating unwind info", .{});
+
+ // Collect all unwind records
+ for (macho_file.sections.items(.atoms)) |atoms| {
+ for (atoms.items) |atom_index| {
+ const atom = macho_file.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ const recs = atom.getUnwindRecords(macho_file);
+ try info.records.ensureUnusedCapacity(gpa, recs.len);
+ for (recs) |rec| {
+ if (!macho_file.getUnwindRecord(rec).alive) continue;
+ info.records.appendAssumeCapacity(rec);
}
}
}
-}
-
-pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
- if (macho_file.unwind_info_section_index == null) return;
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
-
- var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
- defer records.deinit();
-
- var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa);
- defer sym_indexes.deinit();
-
- // TODO handle dead stripping
- for (macho_file.objects.items, 0..) |*object, object_id| {
- log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id });
- const unwind_records = object.getUnwindRecords();
-
- // Contents of unwind records does not have to cover all symbol in executable section
- // so we need insert them ourselves.
- try records.ensureUnusedCapacity(object.exec_atoms.items.len);
- try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
-
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- var prev_symbol: ?SymbolWithLoc = null;
- while (inner_syms_it.next()) |symbol| {
- var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
- if (object.unwind_relocs_lookup[record_id].dead) continue;
- var record = unwind_records[record_id];
-
- if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
- info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
- } else {
- if (getPersonalityFunctionReloc(
- macho_file,
- @as(u32, @intCast(object_id)),
- record_id,
- )) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = @as(u32, @intCast(object_id)),
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
- });
- const personality_index = info.getPersonalityFunction(reloc_target) orelse inner: {
- const personality_index = info.personalities_count;
- info.personalities[personality_index] = reloc_target;
- info.personalities_count += 1;
- break :inner personality_index;
- };
-
- record.personalityFunction = personality_index + 1;
- UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
- }
-
- if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = @as(u32, @intCast(object_id)),
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
- });
- record.lsda = @as(u64, @bitCast(reloc_target));
- }
- }
- break :blk record;
- } else blk: {
- const sym = macho_file.getSymbol(symbol);
- if (sym.n_desc == MachO.N_DEAD) continue;
- if (prev_symbol) |prev_sym| {
- const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
- const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
- if (prev_addr == curr_addr) continue;
- }
-
- if (!object.hasUnwindRecords()) {
- if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
- if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
- var record = nullRecord();
- info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
- switch (cpu_arch) {
- .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
- .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
- else => unreachable,
- }
- break :blk record;
- }
- }
-
- break :blk nullRecord();
- };
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbol(symbol);
- assert(sym.n_desc != MachO.N_DEAD);
- const size = if (inner_syms_it.next()) |next_sym| blk: {
- // All this trouble to account for symbol aliases.
- // TODO I think that remodelling the linker so that a Symbol references an Atom
- // is the way to go, kinda like we do for ELF. We might also want to perhaps tag
- // symbol aliases somehow so that they are excluded from everything except relocation
- // resolution.
- defer inner_syms_it.pos -= 1;
- const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
- const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
- if (next_addr > curr_addr) break :blk next_addr - curr_addr;
- break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
- } else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
- record.rangeStart = sym.n_value;
- record.rangeLength = @as(u32, @intCast(size));
-
- try records.append(record);
- try sym_indexes.append(symbol);
-
- prev_symbol = symbol;
+ // Encode records
+ for (info.records.items) |index| {
+ const rec = macho_file.getUnwindRecord(index);
+ if (rec.getFde(macho_file)) |fde| {
+ rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset));
+ if (fde.getLsdaAtom(macho_file)) |lsda| {
+ rec.lsda = lsda.atom_index;
+ rec.lsda_offset = fde.lsda_offset;
+ rec.enc.setHasLsda(true);
}
+ const cie = fde.getCie(macho_file);
+ if (cie.getPersonality(macho_file)) |_| {
+ const personality_index = try info.getOrPutPersonalityFunction(cie.personality.?.index); // TODO handle error
+ rec.enc.setPersonalityIndex(personality_index + 1);
+ }
+ } else if (rec.getPersonality(macho_file)) |_| {
+ const personality_index = try info.getOrPutPersonalityFunction(rec.personality.?); // TODO handle error
+ rec.enc.setPersonalityIndex(personality_index + 1);
}
}
- // Record the ending boundary before folding.
- assert(records.items.len > 0);
- info.end_boundary = blk: {
- const last_record = records.items[records.items.len - 1];
- break :blk last_record.rangeStart + last_record.rangeLength;
- };
+ // Sort by assigned relative address within each output section
+ const sortFn = struct {
+ fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: 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);
+ if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx);
+ return lhsa.out_n_sect < rhsa.out_n_sect;
+ }
+ }.sortFn;
+ mem.sort(Record.Index, info.records.items, macho_file, sortFn);
- // Fold records
- try info.records.ensureTotalCapacity(info.gpa, records.items.len);
- try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len)));
-
- var maybe_prev: ?macho.compact_unwind_entry = null;
- for (records.items, 0..) |record, i| {
- const record_id = blk: {
- if (maybe_prev) |prev| {
- const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
- if (is_dwarf or
- (prev.compactUnwindEncoding != record.compactUnwindEncoding) or
- (prev.personalityFunction != record.personalityFunction) or
- record.lsda > 0)
- {
- const record_id = @as(RecordIndex, @intCast(info.records.items.len));
- info.records.appendAssumeCapacity(record);
- maybe_prev = record;
- break :blk record_id;
- } else {
- break :blk @as(RecordIndex, @intCast(info.records.items.len - 1));
- }
+ // Fold the records
+ // Any adjacent two records that share encoding can be folded into one.
+ {
+ var i: usize = 0;
+ var j: usize = 1;
+ while (j < info.records.items.len) : (j += 1) {
+ if (canFold(macho_file, info.records.items[i], info.records.items[j])) {
+ const rec = macho_file.getUnwindRecord(info.records.items[i]);
+ rec.length += macho_file.getUnwindRecord(info.records.items[j]).length + 1;
} else {
- const record_id = @as(RecordIndex, @intCast(info.records.items.len));
- info.records.appendAssumeCapacity(record);
- maybe_prev = record;
- break :blk record_id;
+ i += 1;
+ info.records.items[i] = info.records.items[j];
}
- };
- info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id);
+ }
+ info.records.shrinkAndFree(gpa, i + 1);
+ }
+
+ for (info.records.items) |rec_index| {
+ const rec = macho_file.getUnwindRecord(rec_index);
+ const atom = rec.getAtom(macho_file);
+ log.debug("@{x}-{x} : {s} : rec({d}) : {}", .{
+ rec.getAtomAddress(macho_file),
+ rec.getAtomAddress(macho_file) + rec.length,
+ atom.getName(macho_file),
+ rec_index,
+ rec.enc,
+ });
}
// Calculate common encodings
{
const CommonEncWithCount = struct {
- enc: macho.compact_unwind_encoding_t,
+ enc: Encoding,
count: u32,
fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool {
@@ -380,39 +136,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
};
const Context = struct {
- pub fn hash(ctx: @This(), key: macho.compact_unwind_encoding_t) u32 {
+ pub fn hash(ctx: @This(), key: Encoding) u32 {
_ = ctx;
- return key;
+ return key.enc;
}
pub fn eql(
ctx: @This(),
- key1: macho.compact_unwind_encoding_t,
- key2: macho.compact_unwind_encoding_t,
+ key1: Encoding,
+ key2: Encoding,
b_index: usize,
) bool {
_ = ctx;
_ = b_index;
- return key1 == key2;
+ return key1.eql(key2);
}
};
var common_encodings_counts = std.ArrayHashMap(
- macho.compact_unwind_encoding_t,
+ Encoding,
CommonEncWithCount,
Context,
false,
- ).init(info.gpa);
+ ).init(gpa);
defer common_encodings_counts.deinit();
- for (info.records.items) |record| {
- assert(!isNull(record));
- if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue;
- const enc = record.compactUnwindEncoding;
- const gop = try common_encodings_counts.getOrPut(enc);
+ for (info.records.items) |rec_index| {
+ const rec = macho_file.getUnwindRecord(rec_index);
+ if (rec.enc.isDwarf(macho_file)) continue;
+ const gop = try common_encodings_counts.getOrPut(rec.enc);
if (!gop.found_existing) {
gop.value_ptr.* = .{
- .enc = enc,
+ .enc = rec.enc,
.count = 0,
};
}
@@ -427,7 +182,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
if (i >= max_common_encodings) break;
if (slice[i].count < 2) continue;
info.appendCommonEncoding(slice[i].enc);
- log.debug("adding common encoding: {d} => 0x{x:0>8}", .{ i, slice[i].enc });
+ log.debug("adding common encoding: {d} => {}", .{ i, slice[i].enc });
}
}
@@ -435,8 +190,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
{
var i: u32 = 0;
while (i < info.records.items.len) {
- const range_start_max: u64 =
- info.records.items[i].rangeStart + compressed_entry_func_offset_mask;
+ const rec = macho_file.getUnwindRecord(info.records.items[i]);
+ const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask;
var encoding_count: u9 = info.common_encodings_count;
var space_left: u32 = second_level_page_words -
@sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32);
@@ -447,19 +202,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
};
while (space_left >= 1 and i < info.records.items.len) {
- const record = info.records.items[i];
- const enc = record.compactUnwindEncoding;
- const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
+ const next = macho_file.getUnwindRecord(info.records.items[i]);
+ const is_dwarf = next.enc.isDwarf(macho_file);
- if (record.rangeStart >= range_start_max) {
+ if (next.getAtomAddress(macho_file) >= range_start_max) {
break;
- } else if (info.getCommonEncoding(enc) != null or
- page.getPageEncoding(info, enc) != null and !is_dwarf)
+ } else if (info.getCommonEncoding(next.enc) != null or
+ page.getPageEncoding(next.enc) != null and !is_dwarf)
{
i += 1;
space_left -= 1;
} else if (space_left >= 2 and encoding_count < max_compact_encodings) {
- page.appendPageEncoding(i);
+ page.appendPageEncoding(next.enc);
i += 1;
space_left -= 2;
encoding_count += 1;
@@ -481,63 +235,26 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
page.kind = .compressed;
}
- log.debug("{}", .{page.fmtDebug(info)});
+ log.debug("{}", .{page.fmt(info.*)});
- try info.pages.append(info.gpa, page);
+ try info.pages.append(gpa, page);
}
}
- // Save indices of records requiring LSDA relocation
- try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(info.records.items.len)));
- for (info.records.items, 0..) |rec, i| {
- info.lsdas_lookup.putAssumeCapacityNoClobber(@as(RecordIndex, @intCast(i)), @as(u32, @intCast(info.lsdas.items.len)));
- if (rec.lsda == 0) continue;
- try info.lsdas.append(info.gpa, @as(RecordIndex, @intCast(i)));
- }
-}
-
-fn collectPersonalityFromDwarf(
- info: *UnwindInfo,
- macho_file: *MachO,
- object_id: u32,
- sym_loc: SymbolWithLoc,
- record: *macho.compact_unwind_entry,
-) void {
- const object = &macho_file.objects.items[object_id];
- var it = object.getEhFrameRecordsIterator();
- const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
- it.seekTo(fde_offset);
- const fde = (it.next() catch return).?; // We don't care about the error since we already handled it
- const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
- const cie_offset = fde_offset + 4 - cie_ptr;
- it.seekTo(cie_offset);
- const cie = (it.next() catch return).?; // We don't care about the error since we already handled it
-
- if (cie.getPersonalityPointerReloc(
- macho_file,
- @as(u32, @intCast(object_id)),
- cie_offset,
- )) |target| {
- const personality_index = info.getPersonalityFunction(target) orelse inner: {
- const personality_index = info.personalities_count;
- info.personalities[personality_index] = target;
- info.personalities_count += 1;
- break :inner personality_index;
- };
-
- record.personalityFunction = personality_index + 1;
- UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
+ // Save records having an LSDA pointer
+ log.debug("LSDA pointers:", .{});
+ try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len);
+ for (info.records.items, 0..) |index, i| {
+ const rec = macho_file.getUnwindRecord(index);
+ info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len));
+ if (rec.getLsdaAtom(macho_file)) |lsda| {
+ log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index });
+ try info.lsdas.append(gpa, @intCast(i));
+ }
}
}
-pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void {
- const sect_id = macho_file.unwind_info_section_index orelse return;
- const sect = &macho_file.sections.items(.header)[sect_id];
- sect.@"align" = 2;
- sect.size = info.calcRequiredSize();
-}
-
-fn calcRequiredSize(info: UnwindInfo) usize {
+pub fn calcSize(info: UnwindInfo) usize {
var total_size: usize = 0;
total_size += @sizeOf(macho.unwind_info_section_header);
total_size +=
@@ -549,59 +266,12 @@ fn calcRequiredSize(info: UnwindInfo) usize {
return total_size;
}
-pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
- const sect_id = macho_file.unwind_info_section_index orelse return;
- const sect = &macho_file.sections.items(.header)[sect_id];
- const seg_id = macho_file.sections.items(.segment_index)[sect_id];
- const seg = macho_file.segments.items[seg_id];
-
- const text_sect_id = macho_file.text_section_index.?;
- const text_sect = macho_file.sections.items(.header)[text_sect_id];
-
- var personalities: [max_personalities]u32 = undefined;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
-
- log.debug("Personalities:", .{});
- for (info.personalities[0..info.personalities_count], 0..) |reloc_target, i| {
- const addr = macho_file.getGotEntryAddress(reloc_target).?;
- personalities[i] = @as(u32, @intCast(addr - seg.vmaddr));
- log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], macho_file.getSymbolName(reloc_target) });
- }
-
- for (info.records.items) |*rec| {
- // Finalize missing address values
- rec.rangeStart += text_sect.addr - seg.vmaddr;
- if (rec.personalityFunction > 0) {
- const index = math.cast(usize, rec.personalityFunction - 1) orelse return error.Overflow;
- rec.personalityFunction = personalities[index];
- }
-
- if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) {
- const lsda_target = @as(SymbolWithLoc, @bitCast(rec.lsda));
- if (lsda_target.getFile()) |_| {
- const sym = macho_file.getSymbol(lsda_target);
- rec.lsda = sym.n_value - seg.vmaddr;
- }
- }
- }
-
- for (info.records.items, 0..) |record, i| {
- log.debug("Unwind record at offset 0x{x}", .{i * @sizeOf(macho.compact_unwind_entry)});
- log.debug(" start: 0x{x}", .{record.rangeStart});
- log.debug(" length: 0x{x}", .{record.rangeLength});
- log.debug(" compact encoding: 0x{x:0>8}", .{record.compactUnwindEncoding});
- log.debug(" personality: 0x{x}", .{record.personalityFunction});
- log.debug(" LSDA: 0x{x}", .{record.lsda});
- }
+pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
+ const seg = macho_file.getTextSegment();
+ const header = macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?];
- var buffer = std.ArrayList(u8).init(info.gpa);
- defer buffer.deinit();
-
- const size = info.calcRequiredSize();
- try buffer.ensureTotalCapacityPrecise(size);
-
- var cwriter = std.io.countingWriter(buffer.writer());
+ var stream = std.io.fixedBufferStream(buffer);
+ var cwriter = std.io.countingWriter(stream.writer());
const writer = cwriter.writer();
const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header);
@@ -621,203 +291,403 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
});
try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count]));
- try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count]));
- const pages_base_offset = @as(u32, @intCast(size - (info.pages.items.len * second_level_page_bytes)));
+ for (info.personalities[0..info.personalities_count]) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little);
+ }
+
+ const pages_base_offset = @as(u32, @intCast(header.size - (info.pages.items.len * second_level_page_bytes)));
const lsda_base_offset = @as(u32, @intCast(pages_base_offset -
(info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));
for (info.pages.items, 0..) |page, i| {
assert(page.count > 0);
- const first_entry = info.records.items[page.start];
+ const rec = macho_file.getUnwindRecord(info.records.items[page.start]);
try writer.writeStruct(macho.unwind_info_section_header_index_entry{
- .functionOffset = @as(u32, @intCast(first_entry.rangeStart)),
+ .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
.secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)),
.lsdaIndexArraySectionOffset = lsda_base_offset +
- info.lsdas_lookup.get(page.start).? * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
+ info.lsdas_lookup.items[page.start] * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
});
}
- // Relocate end boundary address
- const end_boundary = @as(u32, @intCast(info.end_boundary + text_sect.addr - seg.vmaddr));
+ const last_rec = macho_file.getUnwindRecord(info.records.items[info.records.items.len - 1]);
+ const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr));
try writer.writeStruct(macho.unwind_info_section_header_index_entry{
- .functionOffset = end_boundary,
+ .functionOffset = sentinel_address,
.secondLevelPagesSectionOffset = 0,
.lsdaIndexArraySectionOffset = lsda_base_offset +
@as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
});
- for (info.lsdas.items) |record_id| {
- const record = info.records.items[record_id];
+ for (info.lsdas.items) |index| {
+ const rec = macho_file.getUnwindRecord(info.records.items[index]);
try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{
- .functionOffset = @as(u32, @intCast(record.rangeStart)),
- .lsdaOffset = @as(u32, @intCast(record.lsda)),
+ .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
+ .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)),
});
}
for (info.pages.items) |page| {
const start = cwriter.bytes_written;
- try page.write(info, writer);
+ try page.write(info, macho_file, writer);
const nwritten = cwriter.bytes_written - start;
if (nwritten < second_level_page_bytes) {
- const offset = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow;
- try writer.writeByteNTimes(0, offset);
+ const padding = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow;
+ try writer.writeByteNTimes(0, padding);
}
}
- const padding = buffer.items.len - cwriter.bytes_written;
+ const padding = buffer.len - cwriter.bytes_written;
if (padding > 0) {
- const offset = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow;
- @memset(buffer.items[offset..], 0);
- }
-
- try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
-}
-
-fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info {
- const object = &macho_file.objects.items[object_id];
- assert(object.hasUnwindRecords());
- const rel_pos = object.unwind_relocs_lookup[record_id].reloc;
- const relocs = object.getRelocs(object.unwind_info_sect_id.?);
- return relocs[rel_pos.start..][0..rel_pos.len];
-}
-
-fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool {
- const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
- const rel_offset = rel.r_address - base_offset;
- return rel_offset == 16;
-}
-
-pub fn getPersonalityFunctionReloc(
- macho_file: *MachO,
- object_id: u32,
- record_id: usize,
-) ?macho.relocation_info {
- const relocs = getRelocs(macho_file, object_id, record_id);
- for (relocs) |rel| {
- if (isPersonalityFunction(record_id, rel)) return rel;
+ const off = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow;
+ @memset(buffer[off..], 0);
}
- return null;
}
-fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 {
+fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 {
comptime var index: u2 = 0;
inline while (index < max_personalities) : (index += 1) {
- if (index >= info.personalities_count) return null;
- if (info.personalities[index].eql(global_index)) {
+ if (info.personalities[index] == sym_index) {
+ return index;
+ } else if (index == info.personalities_count) {
+ info.personalities[index] = sym_index;
+ info.personalities_count += 1;
return index;
}
}
- return null;
-}
-
-fn isLsda(record_id: usize, rel: macho.relocation_info) bool {
- const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
- const rel_offset = rel.r_address - base_offset;
- return rel_offset == 24;
+ return error.TooManyPersonalities;
}
-pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info {
- const relocs = getRelocs(macho_file, object_id, record_id);
- for (relocs) |rel| {
- if (isLsda(record_id, rel)) return rel;
- }
- return null;
-}
-
-pub fn isNull(rec: macho.compact_unwind_entry) bool {
- return rec.rangeStart == 0 and
- rec.rangeLength == 0 and
- rec.compactUnwindEncoding == 0 and
- rec.lsda == 0 and
- rec.personalityFunction == 0;
-}
-
-inline fn nullRecord() macho.compact_unwind_entry {
- return .{
- .rangeStart = 0,
- .rangeLength = 0,
- .compactUnwindEncoding = 0,
- .personalityFunction = 0,
- .lsda = 0,
- };
-}
-
-fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void {
+fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void {
assert(info.common_encodings_count <= max_common_encodings);
info.common_encodings[info.common_encodings_count] = enc;
info.common_encodings_count += 1;
}
-fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 {
+fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 {
comptime var index: u7 = 0;
inline while (index < max_common_encodings) : (index += 1) {
if (index >= info.common_encodings_count) return null;
- if (info.common_encodings[index] == enc) {
+ if (info.common_encodings[index].eql(enc)) {
return index;
}
}
return null;
}
-pub const UnwindEncoding = struct {
- pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 {
+pub const Encoding = extern struct {
+ enc: macho.compact_unwind_encoding_t,
+
+ pub fn getMode(enc: Encoding) u4 {
comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
- return @as(u4, @truncate((enc & macho.UNWIND_ARM64_MODE_MASK) >> 24));
+ const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
+ return @as(u4, @truncate((enc.enc & macho.UNWIND_ARM64_MODE_MASK) >> shift));
}
- pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool {
- const mode = getMode(enc);
- return switch (cpu_arch) {
+ pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool {
+ const mode = enc.getMode();
+ return switch (macho_file.getTarget().cpu.arch) {
.aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF,
.x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF,
else => unreachable,
};
}
- pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void {
- enc.* |= @as(u32, @intCast(@intFromEnum(mode))) << 24;
+ pub fn setMode(enc: *Encoding, mode: anytype) void {
+ comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
+ const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
+ enc.enc |= @as(u32, @intCast(@intFromEnum(mode))) << shift;
}
- pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool {
- const has_lsda = @as(u1, @truncate((enc & macho.UNWIND_HAS_LSDA) >> 31));
+ pub fn hasLsda(enc: Encoding) bool {
+ const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
+ const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> shift));
return has_lsda == 1;
}
- pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void {
- const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31;
- enc.* |= mask;
+ pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void {
+ const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
+ const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << shift;
+ enc.enc |= mask;
}
- pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 {
- const index = @as(u2, @truncate((enc & macho.UNWIND_PERSONALITY_MASK) >> 28));
+ pub fn getPersonalityIndex(enc: Encoding) u2 {
+ const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
+ const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> shift));
return index;
}
- pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void {
- const mask = @as(u32, @intCast(index)) << 28;
- enc.* |= mask;
+ pub fn setPersonalityIndex(enc: *Encoding, index: u2) void {
+ const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
+ const mask = @as(u32, @intCast(index)) << shift;
+ enc.enc |= mask;
}
- pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 {
- assert(isDwarf(enc, cpu_arch));
- const offset = @as(u24, @truncate(enc));
+ pub fn getDwarfSectionOffset(enc: Encoding) u24 {
+ const offset = @as(u24, @truncate(enc.enc));
return offset;
}
- pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void {
- assert(isDwarf(enc.*, cpu_arch));
- enc.* |= offset;
+ pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void {
+ enc.enc |= offset;
+ }
+
+ pub fn eql(enc: Encoding, other: Encoding) bool {
+ return enc.enc == other.enc;
+ }
+
+ pub fn format(
+ enc: Encoding,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.print("0x{x:0>8}", .{enc.enc});
}
};
-const UnwindInfo = @This();
+pub const Record = struct {
+ length: u32 = 0,
+ enc: Encoding = .{ .enc = 0 },
+ atom: Atom.Index = 0,
+ atom_offset: u32 = 0,
+ lsda: Atom.Index = 0,
+ lsda_offset: u32 = 0,
+ personality: ?Symbol.Index = null, // TODO make this zero-is-null
+ fde: Fde.Index = 0, // TODO actually make FDE at 0 an invalid FDE
+ file: File.Index = 0,
+ alive: bool = true,
+
+ pub fn getObject(rec: Record, macho_file: *MachO) *Object {
+ return macho_file.getFile(rec.file).?.object;
+ }
+
+ pub fn getAtom(rec: Record, macho_file: *MachO) *Atom {
+ return macho_file.getAtom(rec.atom).?;
+ }
+
+ pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom {
+ return macho_file.getAtom(rec.lsda);
+ }
+
+ pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol {
+ const personality = rec.personality orelse return null;
+ return macho_file.getSymbol(personality);
+ }
+
+ pub fn getFde(rec: Record, macho_file: *MachO) ?Fde {
+ if (!rec.enc.isDwarf(macho_file)) return null;
+ return rec.getObject(macho_file).fdes.items[rec.fde];
+ }
+
+ pub fn getFdePtr(rec: Record, macho_file: *MachO) ?*Fde {
+ if (!rec.enc.isDwarf(macho_file)) return null;
+ return &rec.getObject(macho_file).fdes.items[rec.fde];
+ }
+
+ pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 {
+ const atom = rec.getAtom(macho_file);
+ return atom.value + rec.atom_offset;
+ }
+
+ pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 {
+ const lsda = rec.getLsdaAtom(macho_file) orelse return 0;
+ return lsda.value + rec.lsda_offset;
+ }
+
+ pub fn format(
+ rec: Record,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = rec;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format UnwindInfo.Records directly");
+ }
+
+ pub fn fmt(rec: Record, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .rec = rec,
+ .macho_file = macho_file,
+ } };
+ }
+
+ const FormatContext = struct {
+ rec: Record,
+ macho_file: *MachO,
+ };
+
+ fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const rec = ctx.rec;
+ const macho_file = ctx.macho_file;
+ try writer.print("{x} : len({x})", .{
+ rec.enc.enc, rec.length,
+ });
+ if (rec.enc.isDwarf(macho_file)) try writer.print(" : fde({d})", .{rec.fde});
+ try writer.print(" : {s}", .{rec.getAtom(macho_file).getName(macho_file)});
+ if (!rec.alive) try writer.writeAll(" : [*]");
+ }
+
+ pub const Index = u32;
+};
+
+const max_personalities = 3;
+const max_common_encodings = 127;
+const max_compact_encodings = 256;
+
+const second_level_page_bytes = 0x1000;
+const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
+
+const max_regular_second_level_entries =
+ (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
+ @sizeOf(macho.unwind_info_regular_second_level_entry);
+
+const max_compressed_second_level_entries =
+ (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
+ @sizeOf(u32);
+
+const compressed_entry_func_offset_mask = ~@as(u24, 0);
+
+const Page = struct {
+ kind: enum { regular, compressed },
+ start: u32,
+ count: u16,
+ page_encodings: [max_compact_encodings]Encoding = undefined,
+ page_encodings_count: u9 = 0,
+
+ fn appendPageEncoding(page: *Page, enc: Encoding) void {
+ assert(page.page_encodings_count <= max_compact_encodings);
+ page.page_encodings[page.page_encodings_count] = enc;
+ page.page_encodings_count += 1;
+ }
+
+ fn getPageEncoding(page: Page, enc: Encoding) ?u8 {
+ comptime var index: u9 = 0;
+ inline while (index < max_compact_encodings) : (index += 1) {
+ if (index >= page.page_encodings_count) return null;
+ if (page.page_encodings[index].eql(enc)) {
+ return @as(u8, @intCast(index));
+ }
+ }
+ return null;
+ }
+
+ fn format(
+ page: *const Page,
+ comptime unused_format_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = page;
+ _ = unused_format_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format Page directly; use page.fmt()");
+ }
+
+ const FormatPageContext = struct {
+ page: Page,
+ info: UnwindInfo,
+ };
+
+ fn format2(
+ ctx: FormatPageContext,
+ comptime unused_format_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) @TypeOf(writer).Error!void {
+ _ = options;
+ _ = unused_format_string;
+ try writer.writeAll("Page:\n");
+ try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
+ try writer.print(" entries: {d} - {d}\n", .{
+ ctx.page.start,
+ ctx.page.start + ctx.page.count,
+ });
+ try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
+ for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |enc, i| {
+ try writer.print(" {d}: {}\n", .{ ctx.info.common_encodings_count + i, enc });
+ }
+ }
+
+ fn fmt(page: Page, info: UnwindInfo) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .page = page,
+ .info = info,
+ } };
+ }
+
+ fn write(page: Page, info: UnwindInfo, macho_file: *MachO, writer: anytype) !void {
+ const seg = macho_file.getTextSegment();
+
+ switch (page.kind) {
+ .regular => {
+ try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
+ .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
+ .entryCount = page.count,
+ });
+
+ for (info.records.items[page.start..][0..page.count]) |index| {
+ const rec = macho_file.getUnwindRecord(index);
+ try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
+ .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
+ .encoding = rec.enc.enc,
+ });
+ }
+ },
+ .compressed => {
+ const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
+ @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
+ try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
+ .entryPageOffset = entry_offset,
+ .entryCount = page.count,
+ .encodingsPageOffset = @sizeOf(macho.unwind_info_compressed_second_level_page_header),
+ .encodingsCount = page.page_encodings_count,
+ });
+
+ for (page.page_encodings[0..page.page_encodings_count]) |enc| {
+ try writer.writeInt(u32, enc.enc, .little);
+ }
+
+ assert(page.count > 0);
+ const first_rec = macho_file.getUnwindRecord(info.records.items[page.start]);
+ for (info.records.items[page.start..][0..page.count]) |index| {
+ const rec = macho_file.getUnwindRecord(index);
+ const enc_index = blk: {
+ if (info.getCommonEncoding(rec.enc)) |id| break :blk id;
+ const ncommon = info.common_encodings_count;
+ break :blk ncommon + page.getPageEncoding(rec.enc).?;
+ };
+ const compressed = macho.UnwindInfoCompressedEntry{
+ .funcOffset = @as(u24, @intCast(rec.getAtomAddress(macho_file) - first_rec.getAtomAddress(macho_file))),
+ .encodingIndex = @as(u8, @intCast(enc_index)),
+ };
+ try writer.writeStruct(compressed);
+ }
+ },
+ }
+ }
+};
const std = @import("std");
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const fs = std.fs;
const leb = std.leb;
-const log = std.log.scoped(.unwind_info);
+const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
@@ -825,7 +695,9 @@ const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
-const EhFrameRecord = eh_frame.EhFrameRecord;
+const Fde = eh_frame.Fde;
+const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
+const Symbol = @import("Symbol.zig");
+const UnwindInfo = @This();
diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig
new file mode 100644
index 0000000000..6f55a077b5
--- /dev/null
+++ b/src/link/MachO/ZigObject.zig
@@ -0,0 +1,1471 @@
+/// Externally owned memory.
+path: []const u8,
+index: File.Index,
+
+symtab: std.MultiArrayList(Nlist) = .{},
+
+symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
+globals_lookup: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{},
+
+/// Table of tracked LazySymbols.
+lazy_syms: LazySymbolTable = .{},
+
+/// Table of tracked Decls.
+decls: DeclTable = .{},
+
+/// Table of unnamed constants associated with a parent `Decl`.
+/// We store them here so that we can free the constants whenever the `Decl`
+/// needs updating or is freed.
+///
+/// For example,
+///
+/// ```zig
+/// const Foo = struct{
+/// a: u8,
+/// };
+///
+/// pub fn main() void {
+/// var foo = Foo{ .a = 1 };
+/// _ = foo;
+/// }
+/// ```
+///
+/// value assigned to label `foo` is an unnamed constant belonging/associated
+/// with `Decl` `main`, and lives as long as that `Decl`.
+unnamed_consts: UnnamedConstTable = .{},
+
+/// Table of tracked AnonDecls.
+anon_decls: AnonDeclTable = .{},
+
+/// TLV initializers indexed by Atom.Index.
+tlv_initializers: TlvInitializerTable = .{},
+
+/// A table of relocations.
+relocs: RelocationTable = .{},
+
+dynamic_relocs: MachO.DynamicRelocs = .{},
+output_symtab_ctx: MachO.SymtabCtx = .{},
+
+pub fn init(self: *ZigObject, macho_file: *MachO) !void {
+ const comp = macho_file.base.comp;
+ const gpa = comp.gpa;
+
+ try self.atoms.append(gpa, 0); // null input section
+}
+
+pub fn deinit(self: *ZigObject, allocator: Allocator) void {
+ self.symtab.deinit(allocator);
+ self.symbols.deinit(allocator);
+ self.atoms.deinit(allocator);
+ self.globals_lookup.deinit(allocator);
+
+ {
+ var it = self.decls.iterator();
+ while (it.next()) |entry| {
+ entry.value_ptr.exports.deinit(allocator);
+ }
+ self.decls.deinit(allocator);
+ }
+
+ self.lazy_syms.deinit(allocator);
+
+ {
+ var it = self.unnamed_consts.valueIterator();
+ while (it.next()) |syms| {
+ syms.deinit(allocator);
+ }
+ self.unnamed_consts.deinit(allocator);
+ }
+
+ {
+ var it = self.anon_decls.iterator();
+ while (it.next()) |entry| {
+ entry.value_ptr.exports.deinit(allocator);
+ }
+ self.anon_decls.deinit(allocator);
+ }
+
+ for (self.relocs.items) |*list| {
+ list.deinit(allocator);
+ }
+ self.relocs.deinit(allocator);
+
+ for (self.tlv_initializers.values()) |*tlv_init| {
+ tlv_init.deinit(allocator);
+ }
+ self.tlv_initializers.deinit(allocator);
+}
+
+fn addNlist(self: *ZigObject, allocator: Allocator) !Symbol.Index {
+ try self.symtab.ensureUnusedCapacity(allocator, 1);
+ const index = @as(Symbol.Index, @intCast(self.symtab.addOneAssumeCapacity()));
+ self.symtab.set(index, .{
+ .nlist = MachO.null_sym,
+ .size = 0,
+ .atom = 0,
+ });
+ return index;
+}
+
+pub fn addAtom(self: *ZigObject, macho_file: *MachO) !Symbol.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const atom_index = try macho_file.addAtom();
+ const symbol_index = try macho_file.addSymbol();
+ const nlist_index = try self.addNlist(gpa);
+
+ try self.atoms.append(gpa, atom_index);
+ try self.symbols.append(gpa, symbol_index);
+
+ const atom = macho_file.getAtom(atom_index).?;
+ atom.file = self.index;
+ atom.atom_index = atom_index;
+
+ const symbol = macho_file.getSymbol(symbol_index);
+ symbol.file = self.index;
+ symbol.atom = atom_index;
+
+ self.symtab.items(.atom)[nlist_index] = atom_index;
+ symbol.nlist_idx = nlist_index;
+
+ const relocs_index = @as(u32, @intCast(self.relocs.items.len));
+ const relocs = try self.relocs.addOne(gpa);
+ relocs.* = .{};
+ atom.relocs = .{ .pos = relocs_index, .len = 0 };
+
+ return symbol_index;
+}
+
+/// Caller owns the memory.
+pub fn getAtomDataAlloc(
+ self: ZigObject,
+ macho_file: *MachO,
+ allocator: Allocator,
+ atom: Atom,
+) ![]u8 {
+ assert(atom.file == self.index);
+ const sect = macho_file.sections.items(.header)[atom.out_n_sect];
+ assert(!sect.isZerofill());
+
+ switch (sect.type()) {
+ macho.S_THREAD_LOCAL_REGULAR => {
+ const tlv = self.tlv_initializers.get(atom.atom_index).?;
+ const data = try allocator.dupe(u8, tlv.data);
+ return data;
+ },
+ macho.S_THREAD_LOCAL_VARIABLES => {
+ const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
+ const data = try allocator.alloc(u8, size);
+ @memset(data, 0);
+ return data;
+ },
+ else => {
+ const file_offset = sect.offset + atom.value - sect.addr;
+ const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
+ const data = try allocator.alloc(u8, size);
+ errdefer allocator.free(data);
+ const amt = try macho_file.base.file.?.preadAll(data, file_offset);
+ if (amt != data.len) return error.InputOutput;
+ return data;
+ },
+ }
+}
+
+pub fn getAtomRelocs(self: *ZigObject, atom: Atom) []const Relocation {
+ const relocs = self.relocs.items[atom.relocs.pos];
+ return relocs.items[0..atom.relocs.len];
+}
+
+pub fn freeAtomRelocs(self: *ZigObject, atom: Atom) void {
+ self.relocs.items[atom.relocs.pos].clearRetainingCapacity();
+}
+
+pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items, 0..) |index, i| {
+ const nlist_idx = @as(Symbol.Index, @intCast(i));
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ const atom_index = self.symtab.items(.atom)[nlist_idx];
+
+ if (!nlist.ext()) continue;
+ if (nlist.undf() and !nlist.tentative()) continue;
+ if (nlist.sect()) {
+ const atom = macho_file.getAtom(atom_index).?;
+ if (!atom.flags.alive) continue;
+ }
+
+ const symbol = macho_file.getSymbol(index);
+ if (self.asFile().getSymbolRank(.{
+ .archive = false,
+ .weak = nlist.weakDef(),
+ .tentative = nlist.tentative(),
+ }) < symbol.getSymbolRank(macho_file)) {
+ const value = if (nlist.sect()) blk: {
+ const atom = macho_file.getAtom(atom_index).?;
+ break :blk nlist.n_value - atom.getInputAddress(macho_file);
+ } else nlist.n_value;
+ symbol.value = value;
+ symbol.atom = atom_index;
+ symbol.nlist_idx = nlist_idx;
+ symbol.file = self.index;
+ symbol.flags.weak = nlist.weakDef();
+ symbol.flags.abs = nlist.abs();
+ symbol.flags.tentative = nlist.tentative();
+ symbol.flags.weak_ref = false;
+ symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
+ symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
+ // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
+ symbol.flags.interposable = false;
+
+ if (nlist.sect() and
+ macho_file.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
+ {
+ symbol.flags.tlv = true;
+ }
+ }
+
+ // Regardless of who the winner is, we still merge symbol visibility here.
+ if (nlist.pext() or (nlist.weakDef() and nlist.weakRef())) {
+ if (symbol.visibility != .global) {
+ symbol.visibility = .hidden;
+ }
+ } else {
+ symbol.visibility = .global;
+ }
+ }
+}
+
+pub fn resetGlobals(self: *ZigObject, macho_file: *MachO) void {
+ for (self.symbols.items, 0..) |sym_index, nlist_idx| {
+ if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
+ const sym = macho_file.getSymbol(sym_index);
+ const name = sym.name;
+ sym.* = .{};
+ sym.name = name;
+ }
+}
+
+pub fn markLive(self: *ZigObject, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (self.symbols.items, 0..) |index, nlist_idx| {
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ if (!nlist.ext()) continue;
+
+ const sym = macho_file.getSymbol(index);
+ const file = sym.getFile(macho_file) orelse continue;
+ const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
+ if (should_keep and file == .object and !file.object.alive) {
+ file.object.alive = true;
+ file.object.markLive(macho_file);
+ }
+ }
+}
+
+pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !void {
+ for (self.symbols.items, 0..) |index, nlist_idx| {
+ const sym = macho_file.getSymbol(index);
+ if (sym.visibility != .global) continue;
+ const file = sym.getFile(macho_file) orelse continue;
+ if (file.getIndex() == self.index) continue;
+
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+ if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
+ const gop = try dupes.getOrPut(index);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
+ }
+ try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
+ }
+ }
+}
+
+pub fn scanRelocs(self: *ZigObject, macho_file: *MachO) !void {
+ for (self.atoms.items) |atom_index| {
+ const atom = macho_file.getAtom(atom_index) orelse continue;
+ if (!atom.flags.alive) continue;
+ const sect = atom.getInputSection(macho_file);
+ if (sect.isZerofill()) continue;
+ try atom.scanRelocs(macho_file);
+ }
+}
+
+pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ 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.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
+ sym.flags.output_symtab = true;
+ if (sym.isLocal()) {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
+ self.output_symtab_ctx.nlocals += 1;
+ } else if (sym.flags.@"export") {
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
+ self.output_symtab_ctx.nexports += 1;
+ } else {
+ assert(sym.flags.import);
+ try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
+ self.output_symtab_ctx.nimports += 1;
+ }
+ self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
+ }
+}
+
+pub fn writeSymtab(self: ZigObject, macho_file: *MachO) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ 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;
+ const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
+ const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
+ macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+ macho_file.strtab.appendAssumeCapacity(0);
+ const out_sym = &macho_file.symtab.items[idx];
+ out_sym.n_strx = n_strx;
+ sym.setOutputSym(macho_file, out_sym);
+ }
+}
+
+pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.section_64 {
+ _ = self;
+ var sect = macho_file.sections.items(.header)[atom.out_n_sect];
+ sect.addr = 0;
+ sect.offset = 0;
+ sect.size = atom.size;
+ sect.@"align" = atom.alignment.toLog2Units();
+ return sect;
+}
+
+pub fn flushModule(self: *ZigObject, macho_file: *MachO) !void {
+ // Handle any lazy symbols that were emitted by incremental compilation.
+ if (self.lazy_syms.getPtr(.none)) |metadata| {
+ const zcu = macho_file.base.comp.module.?;
+
+ // Most lazy symbols can be updated on first use, but
+ // anyerror needs to wait for everything to be flushed.
+ if (metadata.text_state != .unused) self.updateLazySymbol(
+ macho_file,
+ link.File.LazySymbol.initDecl(.code, null, zcu),
+ metadata.text_symbol_index,
+ ) catch |err| return switch (err) {
+ error.CodegenFail => error.FlushFailure,
+ else => |e| e,
+ };
+ if (metadata.const_state != .unused) self.updateLazySymbol(
+ macho_file,
+ link.File.LazySymbol.initDecl(.const_data, null, zcu),
+ metadata.const_symbol_index,
+ ) catch |err| return switch (err) {
+ error.CodegenFail => error.FlushFailure,
+ else => |e| e,
+ };
+ }
+ for (self.lazy_syms.values()) |*metadata| {
+ if (metadata.text_state != .unused) metadata.text_state = .flushed;
+ if (metadata.const_state != .unused) metadata.const_state = .flushed;
+ }
+}
+
+pub fn getDeclVAddr(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_index: InternPool.DeclIndex,
+ reloc_info: link.File.RelocInfo,
+) !u64 {
+ const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
+ const sym = macho_file.getSymbol(sym_index);
+ const vaddr = sym.getAddress(.{}, macho_file);
+ const parent_atom = macho_file.getSymbol(reloc_info.parent_atom_index).getAtom(macho_file).?;
+ try parent_atom.addReloc(macho_file, .{
+ .tag = .@"extern",
+ .offset = @intCast(reloc_info.offset),
+ .target = sym_index,
+ .addend = reloc_info.addend,
+ .type = .unsigned,
+ .meta = .{
+ .pcrel = false,
+ .has_subtractor = false,
+ .length = 3,
+ .symbolnum = 0,
+ },
+ });
+ return vaddr;
+}
+
+pub fn getAnonDeclVAddr(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_val: InternPool.Index,
+ reloc_info: link.File.RelocInfo,
+) !u64 {
+ const sym_index = self.anon_decls.get(decl_val).?.symbol_index;
+ const sym = macho_file.getSymbol(sym_index);
+ const vaddr = sym.getAddress(.{}, macho_file);
+ const parent_atom = macho_file.getSymbol(reloc_info.parent_atom_index).getAtom(macho_file).?;
+ try parent_atom.addReloc(macho_file, .{
+ .tag = .@"extern",
+ .offset = @intCast(reloc_info.offset),
+ .target = sym_index,
+ .addend = reloc_info.addend,
+ .type = .unsigned,
+ .meta = .{
+ .pcrel = false,
+ .has_subtractor = false,
+ .length = 3,
+ .symbolnum = 0,
+ },
+ });
+ return vaddr;
+}
+
+pub fn lowerAnonDecl(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_val: InternPool.Index,
+ explicit_alignment: Atom.Alignment,
+ src_loc: Module.SrcLoc,
+) !codegen.Result {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+ const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
+ const decl_alignment = switch (explicit_alignment) {
+ .none => ty.abiAlignment(mod),
+ else => explicit_alignment,
+ };
+ if (self.anon_decls.get(decl_val)) |metadata| {
+ const existing_alignment = macho_file.getSymbol(metadata.symbol_index).getAtom(macho_file).?.alignment;
+ if (decl_alignment.order(existing_alignment).compare(.lte))
+ return .ok;
+ }
+
+ const val = Value.fromInterned(decl_val);
+ const tv = TypedValue{ .ty = ty, .val = val };
+ var name_buf: [32]u8 = undefined;
+ const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
+ @intFromEnum(decl_val),
+ }) catch unreachable;
+ const res = self.lowerConst(
+ macho_file,
+ name,
+ tv,
+ decl_alignment,
+ macho_file.zig_const_sect_index.?,
+ src_loc,
+ ) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => |e| return .{ .fail = try Module.ErrorMsg.create(
+ gpa,
+ src_loc,
+ "unable to lower constant value: {s}",
+ .{@errorName(e)},
+ ) },
+ };
+ const sym_index = switch (res) {
+ .ok => |sym_index| sym_index,
+ .fail => |em| return .{ .fail = em },
+ };
+ try self.anon_decls.put(gpa, decl_val, .{ .symbol_index = sym_index });
+ return .ok;
+}
+
+fn freeUnnamedConsts(self: *ZigObject, macho_file: *MachO, decl_index: InternPool.DeclIndex) void {
+ const gpa = macho_file.base.comp.gpa;
+ const unnamed_consts = self.unnamed_consts.getPtr(decl_index) orelse return;
+ for (unnamed_consts.items) |sym_index| {
+ self.freeDeclMetadata(macho_file, sym_index);
+ }
+ unnamed_consts.clearAndFree(gpa);
+}
+
+fn freeDeclMetadata(self: *ZigObject, macho_file: *MachO, sym_index: Symbol.Index) void {
+ _ = self;
+ const gpa = macho_file.base.comp.gpa;
+ const sym = macho_file.getSymbol(sym_index);
+ sym.getAtom(macho_file).?.free(macho_file);
+ log.debug("adding %{d} to local symbols free list", .{sym_index});
+ macho_file.symbols_free_list.append(gpa, sym_index) catch {};
+ macho_file.symbols.items[sym_index] = .{};
+ // TODO free GOT entry here
+}
+
+pub fn freeDecl(self: *ZigObject, macho_file: *MachO, decl_index: InternPool.DeclIndex) void {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+ const decl = mod.declPtr(decl_index);
+
+ log.debug("freeDecl {*}", .{decl});
+
+ if (self.decls.fetchRemove(decl_index)) |const_kv| {
+ var kv = const_kv;
+ const sym_index = kv.value.symbol_index;
+ self.freeDeclMetadata(macho_file, sym_index);
+ self.freeUnnamedConsts(macho_file, decl_index);
+ kv.value.exports.deinit(gpa);
+ }
+
+ // TODO free decl in dSYM
+}
+
+pub fn updateFunc(
+ self: *ZigObject,
+ macho_file: *MachO,
+ mod: *Module,
+ func_index: InternPool.Index,
+ air: Air,
+ liveness: Liveness,
+) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ const func = mod.funcInfo(func_index);
+ const decl_index = func.owner_decl;
+ const decl = mod.declPtr(decl_index);
+
+ const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
+ self.freeUnnamedConsts(macho_file, decl_index);
+ macho_file.getSymbol(sym_index).getAtom(macho_file).?.freeRelocs(macho_file);
+
+ var code_buffer = std.ArrayList(u8).init(gpa);
+ defer code_buffer.deinit();
+
+ var decl_state: ?Dwarf.DeclState = null; // TODO: Dwarf
+ defer if (decl_state) |*ds| ds.deinit();
+
+ const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
+ const res = try codegen.generateFunction(
+ &macho_file.base,
+ decl.srcLoc(mod),
+ func_index,
+ air,
+ liveness,
+ &code_buffer,
+ dio,
+ );
+
+ const code = switch (res) {
+ .ok => code_buffer.items,
+ .fail => |em| {
+ decl.analysis = .codegen_failure;
+ try mod.failed_decls.put(mod.gpa, decl_index, em);
+ return;
+ },
+ };
+
+ const sect_index = try self.getDeclOutputSection(macho_file, decl, code);
+ try self.updateDeclCode(macho_file, decl_index, sym_index, sect_index, code);
+
+ // if (decl_state) |*ds| {
+ // const sym = elf_file.symbol(sym_index);
+ // try self.dwarf.?.commitDeclState(
+ // mod,
+ // decl_index,
+ // sym.value,
+ // sym.atom(elf_file).?.size,
+ // ds,
+ // );
+ // }
+
+ // Since we updated the vaddr and the size, each corresponding export
+ // symbol also needs to be updated.
+ return self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
+}
+
+pub fn updateDecl(
+ self: *ZigObject,
+ macho_file: *MachO,
+ mod: *Module,
+ decl_index: InternPool.DeclIndex,
+) link.File.UpdateDeclError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const decl = mod.declPtr(decl_index);
+
+ if (decl.val.getExternFunc(mod)) |_| {
+ return;
+ }
+
+ if (decl.isExtern(mod)) {
+ // Extern variable gets a __got entry only
+ const variable = decl.getOwnedVariable(mod).?;
+ const name = mod.intern_pool.stringToSlice(decl.name);
+ const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name);
+ const index = try self.getGlobalSymbol(macho_file, name, lib_name);
+ const actual_index = self.symbols.items[index];
+ macho_file.getSymbol(actual_index).flags.needs_got = true;
+ return;
+ }
+
+ const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
+ macho_file.getSymbol(sym_index).getAtom(macho_file).?.freeRelocs(macho_file);
+
+ const gpa = macho_file.base.comp.gpa;
+ var code_buffer = std.ArrayList(u8).init(gpa);
+ defer code_buffer.deinit();
+
+ var decl_state: ?Dwarf.DeclState = null; // TODO: Dwarf
+ defer if (decl_state) |*ds| ds.deinit();
+
+ const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
+ const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
+ const res =
+ try codegen.generateSymbol(&macho_file.base, decl.srcLoc(mod), .{
+ .ty = decl.ty,
+ .val = decl_val,
+ }, &code_buffer, dio, .{
+ .parent_atom_index = sym_index,
+ });
+
+ const code = switch (res) {
+ .ok => code_buffer.items,
+ .fail => |em| {
+ decl.analysis = .codegen_failure;
+ try mod.failed_decls.put(mod.gpa, decl_index, em);
+ return;
+ },
+ };
+ const sect_index = try self.getDeclOutputSection(macho_file, decl, code);
+ const is_threadlocal = switch (macho_file.sections.items(.header)[sect_index].type()) {
+ macho.S_THREAD_LOCAL_ZEROFILL, macho.S_THREAD_LOCAL_REGULAR => true,
+ else => false,
+ };
+ if (is_threadlocal) {
+ try self.updateTlv(macho_file, decl_index, sym_index, sect_index, code);
+ } else {
+ try self.updateDeclCode(macho_file, decl_index, sym_index, sect_index, code);
+ }
+
+ // if (decl_state) |*ds| {
+ // try self.d_sym.?.dwarf.commitDeclState(
+ // mod,
+ // decl_index,
+ // addr,
+ // self.getAtom(atom_index).size,
+ // ds,
+ // );
+ // }
+
+ // Since we updated the vaddr and the size, each corresponding export symbol also
+ // needs to be updated.
+ try self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
+}
+
+fn updateDeclCode(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_index: InternPool.DeclIndex,
+ sym_index: Symbol.Index,
+ sect_index: u8,
+ code: []const u8,
+) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+ const decl = mod.declPtr(decl_index);
+ const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+
+ log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
+
+ const required_alignment = decl.getAlignment(mod);
+
+ const sect = &macho_file.sections.items(.header)[sect_index];
+ const sym = macho_file.getSymbol(sym_index);
+ const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
+ const atom = sym.getAtom(macho_file).?;
+
+ sym.out_n_sect = sect_index;
+ atom.out_n_sect = sect_index;
+
+ sym.name = try macho_file.strings.insert(gpa, decl_name);
+ atom.flags.alive = true;
+ atom.name = sym.name;
+ nlist.n_strx = sym.name;
+ nlist.n_type = macho.N_SECT;
+ nlist.n_sect = sect_index + 1;
+ self.symtab.items(.size)[sym.nlist_idx] = code.len;
+
+ const old_size = atom.size;
+ const old_vaddr = atom.value;
+ atom.alignment = required_alignment;
+ atom.size = code.len;
+
+ if (old_size > 0) {
+ const capacity = atom.capacity(macho_file);
+ const need_realloc = code.len > capacity or !required_alignment.check(atom.value);
+
+ if (need_realloc) {
+ try atom.grow(macho_file);
+ log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, old_vaddr, atom.value });
+ if (old_vaddr != atom.value) {
+ sym.value = 0;
+ nlist.n_value = 0;
+
+ if (!macho_file.base.isRelocatable()) {
+ log.debug(" (updating offset table entry)", .{});
+ assert(sym.flags.has_zig_got);
+ const extra = sym.getExtra(macho_file).?;
+ try macho_file.zig_got.writeOne(macho_file, extra.zig_got);
+ }
+ }
+ } else if (code.len < old_size) {
+ atom.shrink(macho_file);
+ } else if (macho_file.getAtom(atom.next_index) == null) {
+ const needed_size = atom.value + code.len - sect.addr;
+ sect.size = needed_size;
+ }
+ } else {
+ try atom.allocate(macho_file);
+ errdefer self.freeDeclMetadata(macho_file, sym_index);
+
+ sym.value = 0;
+ sym.flags.needs_zig_got = true;
+ nlist.n_value = 0;
+
+ if (!macho_file.base.isRelocatable()) {
+ const gop = try sym.getOrCreateZigGotEntry(sym_index, macho_file);
+ try macho_file.zig_got.writeOne(macho_file, gop.index);
+ }
+ }
+
+ if (!sect.isZerofill()) {
+ const file_offset = sect.offset + atom.value - sect.addr;
+ try macho_file.base.file.?.pwriteAll(code, file_offset);
+ }
+}
+
+/// Lowering a TLV on macOS involves two stages:
+/// 1. first we lower the initializer into appopriate section (__thread_data or __thread_bss)
+/// 2. next, we create a corresponding threadlocal variable descriptor in __thread_vars
+fn updateTlv(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_index: InternPool.DeclIndex,
+ sym_index: Symbol.Index,
+ sect_index: u8,
+ code: []const u8,
+) !void {
+ const mod = macho_file.base.comp.module.?;
+ const decl = mod.declPtr(decl_index);
+ const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+
+ log.debug("updateTlv {s} ({*})", .{ decl_name, decl });
+
+ const required_alignment = decl.getAlignment(mod);
+
+ // 1. Lower TLV initializer
+ const init_sym_index = try self.createTlvInitializer(
+ macho_file,
+ decl_name,
+ required_alignment,
+ sect_index,
+ code,
+ );
+
+ // 2. Create TLV descriptor
+ try self.createTlvDescriptor(macho_file, sym_index, init_sym_index, decl_name);
+}
+
+fn createTlvInitializer(
+ self: *ZigObject,
+ macho_file: *MachO,
+ name: []const u8,
+ alignment: Atom.Alignment,
+ sect_index: u8,
+ code: []const u8,
+) !Symbol.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{name});
+ defer gpa.free(sym_name);
+
+ const sym_index = try self.addAtom(macho_file);
+ const sym = macho_file.getSymbol(sym_index);
+ const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
+ const atom = sym.getAtom(macho_file).?;
+
+ sym.out_n_sect = sect_index;
+ atom.out_n_sect = sect_index;
+
+ sym.value = 0;
+ sym.name = try macho_file.strings.insert(gpa, sym_name);
+ atom.flags.alive = true;
+ atom.name = sym.name;
+ nlist.n_strx = sym.name;
+ nlist.n_sect = sect_index + 1;
+ nlist.n_type = macho.N_SECT;
+ nlist.n_value = 0;
+ self.symtab.items(.size)[sym.nlist_idx] = code.len;
+
+ atom.alignment = alignment;
+ atom.size = code.len;
+
+ const slice = macho_file.sections.slice();
+ const header = slice.items(.header)[sect_index];
+ const atoms = &slice.items(.atoms)[sect_index];
+
+ const gop = try self.tlv_initializers.getOrPut(gpa, atom.atom_index);
+ assert(!gop.found_existing); // TODO incremental updates
+ gop.value_ptr.* = .{ .symbol_index = sym_index };
+
+ // We only store the data for the TLV if it's non-zerofill.
+ if (!header.isZerofill()) {
+ gop.value_ptr.data = try gpa.dupe(u8, code);
+ }
+
+ try atoms.append(gpa, atom.atom_index);
+
+ return sym_index;
+}
+
+fn createTlvDescriptor(
+ self: *ZigObject,
+ macho_file: *MachO,
+ sym_index: Symbol.Index,
+ init_sym_index: Symbol.Index,
+ name: []const u8,
+) !void {
+ const gpa = macho_file.base.comp.gpa;
+
+ const sym = macho_file.getSymbol(sym_index);
+ const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
+ const atom = sym.getAtom(macho_file).?;
+ const alignment = Atom.Alignment.fromNonzeroByteUnits(@alignOf(u64));
+ const size: u64 = @sizeOf(u64) * 3;
+
+ const sect_index = macho_file.getSectionByName("__DATA", "__thread_vars") orelse
+ try macho_file.addSection("__DATA", "__thread_vars", .{
+ .flags = macho.S_THREAD_LOCAL_VARIABLES,
+ });
+ sym.out_n_sect = sect_index;
+ atom.out_n_sect = sect_index;
+
+ sym.value = 0;
+ sym.name = try macho_file.strings.insert(gpa, name);
+ atom.flags.alive = true;
+ atom.name = sym.name;
+ nlist.n_strx = sym.name;
+ nlist.n_sect = sect_index + 1;
+ nlist.n_type = macho.N_SECT;
+ nlist.n_value = 0;
+ self.symtab.items(.size)[sym.nlist_idx] = size;
+
+ atom.alignment = alignment;
+ atom.size = size;
+
+ const tlv_bootstrap_index = blk: {
+ const index = try self.getGlobalSymbol(macho_file, "_tlv_bootstrap", null);
+ break :blk self.symbols.items[index];
+ };
+ try atom.addReloc(macho_file, .{
+ .tag = .@"extern",
+ .offset = 0,
+ .target = tlv_bootstrap_index,
+ .addend = 0,
+ .type = .unsigned,
+ .meta = .{
+ .pcrel = false,
+ .has_subtractor = false,
+ .length = 3,
+ .symbolnum = 0,
+ },
+ });
+ try atom.addReloc(macho_file, .{
+ .tag = .@"extern",
+ .offset = 16,
+ .target = init_sym_index,
+ .addend = 0,
+ .type = .unsigned,
+ .meta = .{
+ .pcrel = false,
+ .has_subtractor = false,
+ .length = 3,
+ .symbolnum = 0,
+ },
+ });
+
+ try macho_file.sections.items(.atoms)[sect_index].append(gpa, atom.atom_index);
+}
+
+fn getDeclOutputSection(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl: *const Module.Decl,
+ code: []const u8,
+) error{OutOfMemory}!u8 {
+ _ = self;
+ const mod = macho_file.base.comp.module.?;
+ const any_non_single_threaded = macho_file.base.comp.config.any_non_single_threaded;
+ const sect_id: u8 = switch (decl.ty.zigTypeTag(mod)) {
+ .Fn => macho_file.zig_text_sect_index.?,
+ else => blk: {
+ if (decl.getOwnedVariable(mod)) |variable| {
+ if (variable.is_threadlocal and any_non_single_threaded) {
+ const is_all_zeroes = for (code) |byte| {
+ if (byte != 0) break false;
+ } else true;
+ if (is_all_zeroes) break :blk macho_file.getSectionByName("__DATA", "__thread_bss") orelse try macho_file.addSection(
+ "__DATA",
+ "__thread_bss",
+ .{ .flags = macho.S_THREAD_LOCAL_ZEROFILL },
+ );
+ break :blk macho_file.getSectionByName("__DATA", "__thread_data") orelse try macho_file.addSection(
+ "__DATA",
+ "__thread_data",
+ .{ .flags = macho.S_THREAD_LOCAL_REGULAR },
+ );
+ }
+
+ if (variable.is_const) break :blk macho_file.zig_const_sect_index.?;
+ if (Value.fromInterned(variable.init).isUndefDeep(mod)) {
+ // TODO: get the optimize_mode from the Module that owns the decl instead
+ // of using the root module here.
+ break :blk switch (macho_file.base.comp.root_mod.optimize_mode) {
+ .Debug, .ReleaseSafe => macho_file.zig_data_sect_index.?,
+ .ReleaseFast, .ReleaseSmall => macho_file.zig_bss_sect_index.?,
+ };
+ }
+
+ // TODO I blatantly copied the logic from the Wasm linker, but is there a less
+ // intrusive check for all zeroes than this?
+ const is_all_zeroes = for (code) |byte| {
+ if (byte != 0) break false;
+ } else true;
+ if (is_all_zeroes) break :blk macho_file.zig_bss_sect_index.?;
+ break :blk macho_file.zig_data_sect_index.?;
+ }
+ break :blk macho_file.zig_const_sect_index.?;
+ },
+ };
+ return sect_id;
+}
+
+pub fn lowerUnnamedConst(
+ self: *ZigObject,
+ macho_file: *MachO,
+ typed_value: TypedValue,
+ decl_index: InternPool.DeclIndex,
+) !u32 {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+ const gop = try self.unnamed_consts.getOrPut(gpa, decl_index);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
+ }
+ const unnamed_consts = gop.value_ptr;
+ const decl = mod.declPtr(decl_index);
+ const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+ const index = unnamed_consts.items.len;
+ const name = try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
+ defer gpa.free(name);
+ const sym_index = switch (try self.lowerConst(
+ macho_file,
+ name,
+ typed_value,
+ typed_value.ty.abiAlignment(mod),
+ macho_file.zig_const_sect_index.?,
+ decl.srcLoc(mod),
+ )) {
+ .ok => |sym_index| sym_index,
+ .fail => |em| {
+ decl.analysis = .codegen_failure;
+ try mod.failed_decls.put(mod.gpa, decl_index, em);
+ log.err("{s}", .{em.msg});
+ return error.CodegenFail;
+ },
+ };
+ const sym = macho_file.getSymbol(sym_index);
+ try unnamed_consts.append(gpa, sym.atom);
+ return sym_index;
+}
+
+const LowerConstResult = union(enum) {
+ ok: Symbol.Index,
+ fail: *Module.ErrorMsg,
+};
+
+fn lowerConst(
+ self: *ZigObject,
+ macho_file: *MachO,
+ name: []const u8,
+ tv: TypedValue,
+ required_alignment: Atom.Alignment,
+ output_section_index: u8,
+ src_loc: Module.SrcLoc,
+) !LowerConstResult {
+ const gpa = macho_file.base.comp.gpa;
+
+ var code_buffer = std.ArrayList(u8).init(gpa);
+ defer code_buffer.deinit();
+
+ const sym_index = try self.addAtom(macho_file);
+
+ const res = try codegen.generateSymbol(&macho_file.base, src_loc, tv, &code_buffer, .{
+ .none = {},
+ }, .{
+ .parent_atom_index = sym_index,
+ });
+ const code = switch (res) {
+ .ok => code_buffer.items,
+ .fail => |em| return .{ .fail = em },
+ };
+
+ const sym = macho_file.getSymbol(sym_index);
+ const name_str_index = try macho_file.strings.insert(gpa, name);
+ sym.name = name_str_index;
+ sym.out_n_sect = output_section_index;
+
+ const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
+ nlist.n_strx = name_str_index;
+ nlist.n_type = macho.N_SECT;
+ nlist.n_sect = output_section_index + 1;
+ self.symtab.items(.size)[sym.nlist_idx] = code.len;
+
+ const atom = sym.getAtom(macho_file).?;
+ atom.flags.alive = true;
+ atom.name = name_str_index;
+ atom.alignment = required_alignment;
+ atom.size = code.len;
+ atom.out_n_sect = output_section_index;
+
+ try atom.allocate(macho_file);
+ // TODO rename and re-audit this method
+ errdefer self.freeDeclMetadata(macho_file, sym_index);
+
+ sym.value = 0;
+ nlist.n_value = 0;
+
+ const sect = macho_file.sections.items(.header)[output_section_index];
+ const file_offset = sect.offset + atom.value - sect.addr;
+ try macho_file.base.file.?.pwriteAll(code, file_offset);
+
+ return .{ .ok = sym_index };
+}
+
+pub fn updateExports(
+ self: *ZigObject,
+ macho_file: *MachO,
+ mod: *Module,
+ exported: Module.Exported,
+ exports: []const *Module.Export,
+) link.File.UpdateExportsError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ const metadata = switch (exported) {
+ .decl_index => |decl_index| blk: {
+ _ = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
+ break :blk self.decls.getPtr(decl_index).?;
+ },
+ .value => |value| self.anon_decls.getPtr(value) orelse blk: {
+ const first_exp = exports[0];
+ const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.getSrcLoc(mod));
+ switch (res) {
+ .ok => {},
+ .fail => |em| {
+ // TODO maybe it's enough to return an error here and let Module.processExportsInner
+ // handle the error?
+ try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
+ mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em);
+ return;
+ },
+ }
+ break :blk self.anon_decls.getPtr(value).?;
+ },
+ };
+ const sym_index = metadata.symbol_index;
+ const nlist_idx = macho_file.getSymbol(sym_index).nlist_idx;
+ const nlist = self.symtab.items(.nlist)[nlist_idx];
+
+ for (exports) |exp| {
+ if (exp.opts.section.unwrap()) |section_name| {
+ if (!mod.intern_pool.stringEqlSlice(section_name, "__text")) {
+ try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
+ mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create(
+ gpa,
+ exp.getSrcLoc(mod),
+ "Unimplemented: ExportOptions.section",
+ .{},
+ ));
+ continue;
+ }
+ }
+ if (exp.opts.linkage == .LinkOnce) {
+ try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
+ gpa,
+ exp.getSrcLoc(mod),
+ "Unimplemented: GlobalLinkage.LinkOnce",
+ .{},
+ ));
+ continue;
+ }
+
+ const exp_name = mod.intern_pool.stringToSlice(exp.opts.name);
+ const global_nlist_index = if (metadata.@"export"(self, macho_file, exp_name)) |exp_index|
+ exp_index.*
+ else blk: {
+ const global_nlist_index = try self.getGlobalSymbol(macho_file, exp_name, null);
+ try metadata.exports.append(gpa, global_nlist_index);
+ break :blk global_nlist_index;
+ };
+ const global_nlist = &self.symtab.items(.nlist)[global_nlist_index];
+ global_nlist.n_value = nlist.n_value;
+ global_nlist.n_sect = nlist.n_sect;
+ global_nlist.n_type = macho.N_EXT | macho.N_SECT;
+ self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];
+ self.symtab.items(.atom)[global_nlist_index] = self.symtab.items(.atom)[nlist_idx];
+
+ switch (exp.opts.linkage) {
+ .Internal => {
+ // Symbol should be hidden, or in MachO lingo, private extern.
+ global_nlist.n_type |= macho.N_PEXT;
+ },
+ .Strong => {},
+ .Weak => {
+ // Weak linkage is specified as part of n_desc field.
+ // Symbol's n_type is like for a symbol with strong linkage.
+ global_nlist.n_desc |= macho.N_WEAK_DEF;
+ },
+ else => unreachable,
+ }
+ }
+}
+
+fn updateLazySymbol(
+ self: *ZigObject,
+ macho_file: *MachO,
+ lazy_sym: link.File.LazySymbol,
+ symbol_index: Symbol.Index,
+) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+
+ var required_alignment: Atom.Alignment = .none;
+ var code_buffer = std.ArrayList(u8).init(gpa);
+ defer code_buffer.deinit();
+
+ const name_str_index = blk: {
+ const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{}", .{
+ @tagName(lazy_sym.kind),
+ lazy_sym.ty.fmt(mod),
+ });
+ defer gpa.free(name);
+ break :blk try macho_file.strings.insert(gpa, name);
+ };
+
+ const src = if (lazy_sym.ty.getOwnerDeclOrNull(mod)) |owner_decl|
+ mod.declPtr(owner_decl).srcLoc(mod)
+ else
+ Module.SrcLoc{
+ .file_scope = undefined,
+ .parent_decl_node = undefined,
+ .lazy = .unneeded,
+ };
+ const res = try codegen.generateLazySymbol(
+ &macho_file.base,
+ src,
+ lazy_sym,
+ &required_alignment,
+ &code_buffer,
+ .none,
+ .{ .parent_atom_index = symbol_index },
+ );
+ const code = switch (res) {
+ .ok => code_buffer.items,
+ .fail => |em| {
+ log.err("{s}", .{em.msg});
+ return error.CodegenFail;
+ },
+ };
+
+ const output_section_index = switch (lazy_sym.kind) {
+ .code => macho_file.zig_text_sect_index.?,
+ .const_data => macho_file.zig_const_sect_index.?,
+ };
+ const sym = macho_file.getSymbol(symbol_index);
+ sym.name = name_str_index;
+ sym.out_n_sect = output_section_index;
+
+ const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
+ nlist.n_strx = name_str_index;
+ nlist.n_type = macho.N_SECT;
+ nlist.n_sect = output_section_index + 1;
+ self.symtab.items(.size)[sym.nlist_idx] = code.len;
+
+ const atom = sym.getAtom(macho_file).?;
+ atom.flags.alive = true;
+ atom.name = name_str_index;
+ atom.alignment = required_alignment;
+ atom.size = code.len;
+ atom.out_n_sect = output_section_index;
+
+ try atom.allocate(macho_file);
+ errdefer self.freeDeclMetadata(macho_file, symbol_index);
+
+ sym.value = 0;
+ sym.flags.needs_zig_got = true;
+ nlist.n_value = 0;
+
+ if (!macho_file.base.isRelocatable()) {
+ const gop = try sym.getOrCreateZigGotEntry(symbol_index, macho_file);
+ try macho_file.zig_got.writeOne(macho_file, gop.index);
+ }
+
+ const sect = macho_file.sections.items(.header)[output_section_index];
+ const file_offset = sect.offset + atom.value - sect.addr;
+ try macho_file.base.file.?.pwriteAll(code, file_offset);
+}
+
+/// Must be called only after a successful call to `updateDecl`.
+pub fn updateDeclLineNumber(
+ self: *ZigObject,
+ mod: *Module,
+ decl_index: InternPool.DeclIndex,
+) !void {
+ _ = self;
+ _ = mod;
+ _ = decl_index;
+ // TODO: Dwarf
+}
+
+pub fn deleteDeclExport(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_index: InternPool.DeclIndex,
+ name: InternPool.NullTerminatedString,
+) void {
+ const metadata = self.decls.getPtr(decl_index) orelse return;
+
+ const mod = macho_file.base.comp.module.?;
+ const exp_name = mod.intern_pool.stringToSlice(name);
+ const nlist_index = metadata.@"export"(self, macho_file, exp_name) orelse return;
+
+ log.debug("deleting export '{s}'", .{exp_name});
+
+ const nlist = &self.symtab.items(.nlist)[nlist_index.*];
+ self.symtab.items(.size)[nlist_index.*] = 0;
+ _ = self.globals_lookup.remove(nlist.n_strx);
+ const sym_index = macho_file.globals.get(nlist.n_strx).?;
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.file == self.index) {
+ _ = macho_file.globals.swapRemove(nlist.n_strx);
+ sym.* = .{};
+ }
+ nlist.* = MachO.null_sym;
+}
+
+pub fn getGlobalSymbol(self: *ZigObject, macho_file: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
+ _ = lib_name;
+ const gpa = macho_file.base.comp.gpa;
+ const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
+ defer gpa.free(sym_name);
+ const off = try macho_file.strings.insert(gpa, sym_name);
+ const lookup_gop = try self.globals_lookup.getOrPut(gpa, off);
+ if (!lookup_gop.found_existing) {
+ const nlist_index = try self.addNlist(gpa);
+ const nlist = &self.symtab.items(.nlist)[nlist_index];
+ nlist.n_strx = off;
+ nlist.n_type = macho.N_EXT;
+ lookup_gop.value_ptr.* = nlist_index;
+ const gop = try macho_file.getOrCreateGlobal(off);
+ try self.symbols.append(gpa, gop.index);
+ }
+ return lookup_gop.value_ptr.*;
+}
+
+pub fn getOrCreateMetadataForDecl(
+ self: *ZigObject,
+ macho_file: *MachO,
+ decl_index: InternPool.DeclIndex,
+) !Symbol.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const gop = try self.decls.getOrPut(gpa, decl_index);
+ if (!gop.found_existing) {
+ const any_non_single_threaded = macho_file.base.comp.config.any_non_single_threaded;
+ const sym_index = try self.addAtom(macho_file);
+ const mod = macho_file.base.comp.module.?;
+ const decl = mod.declPtr(decl_index);
+ const sym = macho_file.getSymbol(sym_index);
+ if (decl.getOwnedVariable(mod)) |variable| {
+ if (variable.is_threadlocal and any_non_single_threaded) {
+ sym.flags.tlv = true;
+ }
+ }
+ if (!sym.flags.tlv) {
+ sym.flags.needs_zig_got = true;
+ }
+ gop.value_ptr.* = .{ .symbol_index = sym_index };
+ }
+ return gop.value_ptr.symbol_index;
+}
+
+pub fn getOrCreateMetadataForLazySymbol(
+ self: *ZigObject,
+ macho_file: *MachO,
+ lazy_sym: link.File.LazySymbol,
+) !Symbol.Index {
+ const gpa = macho_file.base.comp.gpa;
+ const mod = macho_file.base.comp.module.?;
+ const gop = try self.lazy_syms.getOrPut(gpa, lazy_sym.getDecl(mod));
+ errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
+ if (!gop.found_existing) gop.value_ptr.* = .{};
+ const metadata: struct {
+ symbol_index: *Symbol.Index,
+ state: *LazySymbolMetadata.State,
+ } = switch (lazy_sym.kind) {
+ .code => .{
+ .symbol_index = &gop.value_ptr.text_symbol_index,
+ .state = &gop.value_ptr.text_state,
+ },
+ .const_data => .{
+ .symbol_index = &gop.value_ptr.const_symbol_index,
+ .state = &gop.value_ptr.const_state,
+ },
+ };
+ switch (metadata.state.*) {
+ .unused => {
+ const symbol_index = try self.addAtom(macho_file);
+ const sym = macho_file.getSymbol(symbol_index);
+ sym.flags.needs_zig_got = true;
+ metadata.symbol_index.* = symbol_index;
+ },
+ .pending_flush => return metadata.symbol_index.*,
+ .flushed => {},
+ }
+ metadata.state.* = .pending_flush;
+ const symbol_index = metadata.symbol_index.*;
+ // anyerror needs to be deferred until flushModule
+ if (lazy_sym.getDecl(mod) != .none) try self.updateLazySymbol(macho_file, lazy_sym, symbol_index);
+ return symbol_index;
+}
+
+pub fn asFile(self: *ZigObject) File {
+ return .{ .zig_object = self };
+}
+
+pub fn fmtSymtab(self: *ZigObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
+ return .{ .data = .{
+ .self = self,
+ .macho_file = macho_file,
+ } };
+}
+
+const FormatContext = struct {
+ self: *ZigObject,
+ macho_file: *MachO,
+};
+
+fn formatSymtab(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.writeAll(" symbols\n");
+ for (ctx.self.symbols.items) |index| {
+ const sym = ctx.macho_file.getSymbol(index);
+ try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)});
+ }
+}
+
+pub fn fmtAtoms(self: *ZigObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
+ return .{ .data = .{
+ .self = self,
+ .macho_file = macho_file,
+ } };
+}
+
+fn formatAtoms(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.writeAll(" atoms\n");
+ for (ctx.self.atoms.items) |atom_index| {
+ const atom = ctx.macho_file.getAtom(atom_index) orelse continue;
+ try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
+ }
+}
+
+const DeclMetadata = struct {
+ symbol_index: Symbol.Index,
+ /// A list of all exports aliases of this Decl.
+ exports: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+ fn @"export"(m: DeclMetadata, zig_object: *ZigObject, macho_file: *MachO, name: []const u8) ?*u32 {
+ for (m.exports.items) |*exp| {
+ const nlist = zig_object.symtab.items(.nlist)[exp.*];
+ const exp_name = macho_file.strings.getAssumeExists(nlist.n_strx);
+ if (mem.eql(u8, name, exp_name)) return exp;
+ }
+ return null;
+ }
+};
+
+const LazySymbolMetadata = struct {
+ const State = enum { unused, pending_flush, flushed };
+ text_symbol_index: Symbol.Index = undefined,
+ const_symbol_index: Symbol.Index = undefined,
+ text_state: State = .unused,
+ const_state: State = .unused,
+};
+
+const TlvInitializer = struct {
+ symbol_index: Symbol.Index,
+ data: []const u8 = &[0]u8{},
+
+ fn deinit(tlv_init: *TlvInitializer, allocator: Allocator) void {
+ allocator.free(tlv_init.data);
+ }
+};
+
+const DeclTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
+const UnnamedConstTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Symbol.Index));
+const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
+const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
+const RelocationTable = std.ArrayListUnmanaged(std.ArrayListUnmanaged(Relocation));
+const TlvInitializerTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlvInitializer);
+
+const assert = std.debug.assert;
+const builtin = @import("builtin");
+const codegen = @import("../../codegen.zig");
+const link = @import("../../link.zig");
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+const trace = @import("../../tracy.zig").trace;
+const std = @import("std");
+
+const Air = @import("../../Air.zig");
+const Allocator = std.mem.Allocator;
+const Archive = @import("Archive.zig");
+const Atom = @import("Atom.zig");
+const Dwarf = @import("../Dwarf.zig");
+const File = @import("file.zig").File;
+const InternPool = @import("../../InternPool.zig");
+const Liveness = @import("../../Liveness.zig");
+const MachO = @import("../MachO.zig");
+const Nlist = Object.Nlist;
+const Module = @import("../../Module.zig");
+const Object = @import("Object.zig");
+const Relocation = @import("Relocation.zig");
+const Symbol = @import("Symbol.zig");
+const StringTable = @import("../StringTable.zig");
+const Type = @import("../../type.zig").Type;
+const Value = @import("../../value.zig").Value;
+const TypedValue = @import("../../TypedValue.zig");
+const ZigObject = @This();
diff --git a/src/link/MachO/dead_strip.zig b/src/link/MachO/dead_strip.zig
index fe3740e826..e91682ca58 100644
--- a/src/link/MachO/dead_strip.zig
+++ b/src/link/MachO/dead_strip.zig
@@ -1,495 +1,209 @@
-//! An algorithm for dead stripping of unreferenced Atoms.
-
pub fn gcAtoms(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- var arena = std.heap.ArenaAllocator.init(gpa);
- defer arena.deinit();
-
- var roots = AtomTable.init(arena.allocator());
- try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len)));
+ const gpa = macho_file.base.comp.gpa;
- var alive = AtomTable.init(arena.allocator());
- try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len)));
+ var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
+ defer objects.deinit();
+ for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index);
+ if (macho_file.internal_object) |index| objects.appendAssumeCapacity(index);
- try collectRoots(macho_file, &roots);
- mark(macho_file, roots, &alive);
- prune(macho_file, alive);
-}
+ var roots = std.ArrayList(*Atom).init(gpa);
+ defer roots.deinit();
-fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
- const sym = macho_file.getSymbol(sym_loc);
- assert(!sym.undf());
- const object = &macho_file.objects.items[file];
- const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error
- log.debug("root(ATOM({d}, %{d}, {d}))", .{
- atom_index,
- macho_file.getAtom(atom_index).sym_index,
- file,
- });
- _ = try roots.getOrPut(atom_index);
+ try collectRoots(&roots, objects.items, macho_file);
+ mark(roots.items, objects.items, macho_file);
+ prune(objects.items, macho_file);
}
-fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void {
- log.debug("collecting roots", .{});
-
- const comp = macho_file.base.comp;
-
- switch (comp.config.output_mode) {
- .Exe => {
- // Add entrypoint as GC root
- if (macho_file.getEntryPoint()) |global| {
- if (global.getFile()) |file| {
- try addRoot(macho_file, roots, file, global);
- } else {
- assert(macho_file.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
- }
- }
- },
- else => |other| {
- assert(other == .Lib);
- // Add exports as GC roots
- for (macho_file.globals.items) |global| {
- const sym = macho_file.getSymbol(global);
- if (sym.undf()) continue;
- if (sym.n_desc == MachO.N_BOUNDARY) continue;
+fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
+ for (objects) |index| {
+ const object = macho_file.getFile(index).?;
+ for (object.getSymbols()) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ const file = sym.getFile(macho_file) orelse continue;
+ if (file.getIndex() != index) continue;
+ if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global))
+ try markSymbol(sym, roots, macho_file);
+ }
- if (global.getFile()) |file| {
- try addRoot(macho_file, roots, file, global);
- }
+ for (object.getAtoms()) |atom_index| {
+ const atom = macho_file.getAtom(atom_index).?;
+ const isec = atom.getInputSection(macho_file);
+ switch (isec.type()) {
+ macho.S_MOD_INIT_FUNC_POINTERS,
+ macho.S_MOD_TERM_FUNC_POINTERS,
+ => if (markAtom(atom)) try roots.append(atom),
+
+ else => if (isec.isDontDeadStrip() and markAtom(atom)) {
+ try roots.append(atom);
+ },
}
- },
- }
-
- // Add all symbols force-defined by the user.
- for (comp.force_undefined_symbols.keys()) |sym_name| {
- const global_index = macho_file.resolver.get(sym_name).?;
- const global = macho_file.globals.items[global_index];
- const sym = macho_file.getSymbol(global);
- assert(!sym.undf());
- try addRoot(macho_file, roots, global.getFile().?, global);
+ }
}
- for (macho_file.objects.items) |object| {
- const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
-
- for (object.atoms.items) |atom_index| {
- const is_gc_root = blk: {
- // Modelled after ld64 which treats each object file compiled without MH_SUBSECTIONS_VIA_SYMBOLS
- // as a root.
- if (!has_subsections) break :blk true;
-
- const atom = macho_file.getAtom(atom_index);
- const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
- source_sym.n_sect - 1
- else sect_id: {
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- break :sect_id sect_id;
- };
- const source_sect = object.getSourceSection(sect_id);
- if (source_sect.isDontDeadStrip()) break :blk true;
- switch (source_sect.type()) {
- macho.S_MOD_INIT_FUNC_POINTERS,
- macho.S_MOD_TERM_FUNC_POINTERS,
- => break :blk true,
- else => break :blk false,
- }
- };
-
- if (is_gc_root) {
- _ = try roots.getOrPut(atom_index);
-
- log.debug("root(ATOM({d}, %{d}, {?d}))", .{
- atom_index,
- macho_file.getAtom(atom_index).sym_index,
- macho_file.getAtom(atom_index).getFile(),
- });
- }
+ for (macho_file.objects.items) |index| {
+ for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| {
+ const cu = macho_file.getUnwindRecord(cu_index);
+ if (!cu.alive) continue;
+ if (cu.getFde(macho_file)) |fde| {
+ if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
+ } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
}
}
-}
-
-fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void {
- if (alive.contains(atom_index)) return;
-
- const atom = macho_file.getAtom(atom_index);
- const sym_loc = atom.getSymbolWithLoc();
- log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
-
- alive.putAssumeCapacityNoClobber(atom_index, {});
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
-
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const header = macho_file.sections.items(.header)[sym.n_sect - 1];
- if (header.isZerofill()) return;
-
- const code = Atom.getAtomCode(macho_file, atom_index);
- const relocs = Atom.getAtomRelocs(macho_file, atom_index);
- const ctx = Atom.getRelocContext(macho_file, atom_index);
-
- for (relocs) |rel| {
- const reloc_target = switch (cpu_arch) {
- .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
- .ARM64_RELOC_ADDEND => continue,
- else => Atom.parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- }),
- },
- .x86_64 => Atom.parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- }),
- else => unreachable,
- };
- const target_sym = macho_file.getSymbol(reloc_target);
-
- if (target_sym.undf()) continue;
- if (reloc_target.getFile() == null) {
- const target_sym_name = macho_file.getSymbolName(reloc_target);
- if (mem.eql(u8, "__mh_execute_header", target_sym_name)) continue;
- if (mem.eql(u8, "___dso_handle", target_sym_name)) continue;
+ for (macho_file.undefined_symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ try markSymbol(sym, roots, macho_file);
+ }
- unreachable; // referenced symbol not found
+ for (&[_]?Symbol.Index{
+ macho_file.entry_index,
+ macho_file.dyld_stub_binder_index,
+ macho_file.objc_msg_send_index,
+ }) |index| {
+ if (index) |idx| {
+ const sym = macho_file.getSymbol(idx);
+ try markSymbol(sym, roots, macho_file);
}
-
- const object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index).?;
- log.debug(" following ATOM({d}, %{d}, {?d})", .{
- target_atom_index,
- macho_file.getAtom(target_atom_index).sym_index,
- macho_file.getAtom(target_atom_index).getFile(),
- });
-
- markLive(macho_file, target_atom_index, alive);
}
}
-fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool {
- const atom = macho_file.getAtom(atom_index);
- const sym_loc = atom.getSymbolWithLoc();
-
- log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
-
- const sym = macho_file.getSymbol(sym_loc);
- const header = macho_file.sections.items(.header)[sym.n_sect - 1];
- assert(!header.isZerofill());
-
- const code = Atom.getAtomCode(macho_file, atom_index);
- const relocs = Atom.getAtomRelocs(macho_file, atom_index);
- const ctx = Atom.getRelocContext(macho_file, atom_index);
-
- for (relocs) |rel| {
- const reloc_target = switch (cpu_arch) {
- .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
- .ARM64_RELOC_ADDEND => continue,
- else => Atom.parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- }),
- },
- .x86_64 => Atom.parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- }),
- else => unreachable,
- };
-
- const object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index) orelse {
- log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(reloc_target)});
- continue;
- };
- if (alive.contains(target_atom_index)) {
- log.debug(" refers live ATOM({d}, %{d}, {?d})", .{
- target_atom_index,
- macho_file.getAtom(target_atom_index).sym_index,
- macho_file.getAtom(target_atom_index).getFile(),
- });
- return true;
- }
- }
+fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void {
+ const atom = sym.getAtom(macho_file) orelse return;
+ if (markAtom(atom)) try roots.append(atom);
+}
- return false;
+fn markAtom(atom: *Atom) bool {
+ const already_visited = atom.flags.visited;
+ atom.flags.visited = true;
+ return atom.flags.alive and !already_visited;
}
-fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void {
- var it = roots.keyIterator();
- while (it.next()) |root| {
- markLive(macho_file, root.*, alive);
+fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void {
+ for (roots) |root| {
+ markLive(root, macho_file);
}
var loop: bool = true;
while (loop) {
loop = false;
- for (macho_file.objects.items) |object| {
- for (object.atoms.items) |atom_index| {
- if (alive.contains(atom_index)) continue;
-
- const atom = macho_file.getAtom(atom_index);
- const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
- source_sym.n_sect - 1
- else blk: {
- const nbase = @as(u32, @intCast(object.in_symtab.?.len));
- const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
- break :blk sect_id;
- };
- const source_sect = object.getSourceSection(sect_id);
-
- if (source_sect.isDontDeadStripIfReferencesLive()) {
- if (refersLive(macho_file, atom_index, alive.*)) {
- markLive(macho_file, atom_index, alive);
- loop = true;
- }
+ for (objects) |index| {
+ for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
+ const atom = macho_file.getAtom(atom_index).?;
+ const isec = atom.getInputSection(macho_file);
+ if (isec.isDontDeadStripIfReferencesLive() and
+ !(mem.eql(u8, isec.sectName(), "__eh_frame") or
+ mem.eql(u8, isec.sectName(), "__compact_unwind") or
+ isec.attrs() & macho.S_ATTR_DEBUG != 0) and
+ !atom.flags.alive and refersLive(atom, macho_file))
+ {
+ markLive(atom, macho_file);
+ loop = true;
}
}
}
}
-
- for (macho_file.objects.items, 0..) |_, object_id| {
- // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,
- // marking all references as live.
- markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive);
- }
}
-fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void {
- const object = &macho_file.objects.items[object_id];
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
-
- const unwind_records = object.getUnwindRecords();
+fn markLive(atom: *Atom, macho_file: *MachO) void {
+ assert(atom.flags.visited);
+ atom.flags.alive = true;
+ track_live_log.debug("{}marking live atom({d},{s})", .{
+ track_live_level,
+ atom.atom_index,
+ atom.getName(macho_file),
+ });
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
+ if (build_options.enable_logging)
+ track_live_level.incr();
- if (!object.hasUnwindRecords()) {
- if (alive.contains(atom_index)) {
- // Mark references live and continue.
- markEhFrameRecords(macho_file, object_id, atom_index, alive);
- } else {
- while (inner_syms_it.next()) |sym| {
- if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
- // Mark dead and continue.
- object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
- }
- }
- }
- continue;
+ for (atom.getRelocs(macho_file)) |rel| {
+ const target_atom = switch (rel.tag) {
+ .local => rel.getTargetAtom(macho_file),
+ .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
+ };
+ if (target_atom) |ta| {
+ if (markAtom(ta)) markLive(ta, macho_file);
}
+ }
- while (inner_syms_it.next()) |sym| {
- const record_id = object.unwind_records_lookup.get(sym) orelse continue;
- if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
- if (!alive.contains(atom_index)) {
- // Mark the record dead and continue.
- object.unwind_relocs_lookup[record_id].dead = true;
- if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
- object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
- }
- continue;
- }
+ for (atom.getUnwindRecords(macho_file)) |cu_index| {
+ const cu = macho_file.getUnwindRecord(cu_index);
+ const cu_atom = cu.getAtom(macho_file);
+ if (markAtom(cu_atom)) markLive(cu_atom, macho_file);
- const record = unwind_records[record_id];
- if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
- markEhFrameRecords(macho_file, object_id, atom_index, alive);
- } else {
- if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
- });
- const target_sym = macho_file.getSymbol(reloc_target);
- if (!target_sym.undf()) {
- const target_object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
- markLive(macho_file, target_atom_index, alive);
- }
- }
+ if (cu.getLsdaAtom(macho_file)) |lsda| {
+ if (markAtom(lsda)) markLive(lsda, macho_file);
+ }
+ if (cu.getFde(macho_file)) |fde| {
+ const fde_atom = fde.getAtom(macho_file);
+ if (markAtom(fde_atom)) markLive(fde_atom, macho_file);
- if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = mem.asBytes(&record),
- .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
- });
- const target_object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
- markLive(macho_file, target_atom_index, alive);
- }
+ if (fde.getLsdaAtom(macho_file)) |lsda| {
+ if (markAtom(lsda)) markLive(lsda, macho_file);
}
}
}
}
-fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const object = &macho_file.objects.items[object_id];
- var it = object.getEhFrameRecordsIterator();
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
-
- while (inner_syms_it.next()) |sym| {
- const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
- it.seekTo(fde_offset);
- const fde = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
-
- const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
- const cie_offset = fde_offset + 4 - cie_ptr;
- it.seekTo(cie_offset);
- const cie = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
-
- switch (cpu_arch) {
- .aarch64 => {
- // Mark FDE references which should include any referenced LSDA record
- const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset);
- for (relocs) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = fde.data,
- .base_offset = @as(i32, @intCast(fde_offset)) + 4,
- });
- const target_sym = macho_file.getSymbol(reloc_target);
- if (!target_sym.undf()) blk: {
- const target_object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index) orelse
- break :blk;
- markLive(macho_file, target_atom_index, alive);
- }
- }
- },
- .x86_64 => {
- const sect = object.getSourceSection(object.eh_frame_sect_id.?);
- const lsda_ptr = fde.getLsdaPointer(cie, .{
- .base_addr = sect.addr,
- .base_offset = fde_offset,
- }) catch continue; // We don't care about the error at this point since it was already handled
- if (lsda_ptr) |lsda_address| {
- // Mark LSDA record as live
- const sym_index = object.getSymbolByAddress(lsda_address, null);
- const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
- markLive(macho_file, target_atom_index, alive);
- }
- },
- else => unreachable,
+fn refersLive(atom: *Atom, macho_file: *MachO) bool {
+ for (atom.getRelocs(macho_file)) |rel| {
+ const target_atom = switch (rel.tag) {
+ .local => rel.getTargetAtom(macho_file),
+ .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
+ };
+ if (target_atom) |ta| {
+ if (ta.flags.alive) return true;
}
+ }
+ return false;
+}
- // Mark CIE references which should include any referenced personalities
- // that are defined locally.
- if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| {
- const target_sym = macho_file.getSymbol(reloc_target);
- if (!target_sym.undf()) {
- const target_object = macho_file.objects.items[reloc_target.getFile().?];
- const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
- markLive(macho_file, target_atom_index, alive);
+fn prune(objects: []const File.Index, macho_file: *MachO) void {
+ for (objects) |index| {
+ for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
+ const atom = macho_file.getAtom(atom_index).?;
+ if (atom.flags.alive and !atom.flags.visited) {
+ atom.flags.alive = false;
+ atom.markUnwindRecordsDead(macho_file);
}
}
}
}
-fn prune(macho_file: *MachO, alive: AtomTable) void {
- log.debug("pruning dead atoms", .{});
- for (macho_file.objects.items) |*object| {
- var i: usize = 0;
- while (i < object.atoms.items.len) {
- const atom_index = object.atoms.items[i];
- if (alive.contains(atom_index)) {
- i += 1;
- continue;
- }
-
- const atom = macho_file.getAtom(atom_index);
- const sym_loc = atom.getSymbolWithLoc();
-
- log.debug("prune(ATOM({d}, %{d}, {?d}))", .{
- atom_index,
- sym_loc.sym_index,
- sym_loc.getFile(),
- });
- log.debug(" {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name });
-
- const sym = macho_file.getSymbolPtr(sym_loc);
- const sect_id = sym.n_sect - 1;
- var section = macho_file.sections.get(sect_id);
- section.header.size -= atom.size;
-
- if (atom.prev_index) |prev_index| {
- const prev = macho_file.getAtomPtr(prev_index);
- prev.next_index = atom.next_index;
- } else {
- if (atom.next_index) |next_index| {
- section.first_atom_index = next_index;
- }
- }
- if (atom.next_index) |next_index| {
- const next = macho_file.getAtomPtr(next_index);
- next.prev_index = atom.prev_index;
- } else {
- if (atom.prev_index) |prev_index| {
- section.last_atom_index = prev_index;
- } else {
- assert(section.header.size == 0);
- section.first_atom_index = null;
- section.last_atom_index = null;
- }
- }
-
- macho_file.sections.set(sect_id, section);
- _ = object.atoms.swapRemove(i);
-
- sym.n_desc = MachO.N_DEAD;
+const Level = struct {
+ value: usize = 0,
- var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (inner_sym_it.next()) |inner| {
- const inner_sym = macho_file.getSymbolPtr(inner);
- inner_sym.n_desc = MachO.N_DEAD;
- }
+ fn incr(self: *@This()) void {
+ self.value += 1;
+ }
- if (Atom.getSectionAlias(macho_file, atom_index)) |alias| {
- const alias_sym = macho_file.getSymbolPtr(alias);
- alias_sym.n_desc = MachO.N_DEAD;
- }
- }
+ pub fn format(
+ self: *const @This(),
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ try writer.writeByteNTimes(' ', self.value);
}
-}
+};
+
+var track_live_level: Level = .{};
-const std = @import("std");
const assert = std.debug.assert;
-const eh_frame = @import("eh_frame.zig");
+const build_options = @import("build_options");
const log = std.log.scoped(.dead_strip);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
+const trace = @import("../../tracy.zig").trace;
+const track_live_log = std.log.scoped(.dead_strip_track_live);
+const std = @import("std");
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
+const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const UnwindInfo = @import("UnwindInfo.zig");
-
-const AtomTable = std.AutoHashMap(Atom.Index, void);
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/dyld_info/Rebase.zig b/src/link/MachO/dyld_info/Rebase.zig
index 512e23eddb..c0cda1584a 100644
--- a/src/link/MachO/dyld_info/Rebase.zig
+++ b/src/link/MachO/dyld_info/Rebase.zig
@@ -1,7 +1,18 @@
+const Rebase = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const leb = std.leb;
+const log = std.log.scoped(.link_dyld_info);
+const macho = std.macho;
+const testing = std.testing;
+
+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,
@@ -28,6 +39,8 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
const writer = rebase.buffer.writer(gpa);
+ log.debug("rebase opcodes", .{});
+
std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan);
try setTypePointer(writer);
@@ -561,14 +574,3 @@ test "rebase - composite" {
macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items);
}
-
-const Rebase = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const leb = std.leb;
-const log = std.log.scoped(.dyld_info);
-const macho = std.macho;
-const testing = std.testing;
-
-const Allocator = std.mem.Allocator;
diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/dyld_info/Trie.zig
index 98add0315c..edef57569a 100644
--- a/src/link/MachO/Trie.zig
+++ b/src/link/MachO/dyld_info/Trie.zig
@@ -28,6 +28,248 @@
//! After the optional exported symbol information is a byte of how many edges (0-255) that
//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
+const Trie = @This();
+
+const std = @import("std");
+const mem = std.mem;
+const leb = std.leb;
+const log = std.log.scoped(.macho);
+const macho = std.macho;
+const testing = std.testing;
+const assert = std.debug.assert;
+const Allocator = mem.Allocator;
+
+pub const Node = struct {
+ base: *Trie,
+
+ /// Terminal info associated with this node.
+ /// If this node is not a terminal node, info is null.
+ terminal_info: ?struct {
+ /// Export flags associated with this exported symbol.
+ export_flags: u64,
+ /// VM address offset wrt to the section this symbol is defined against.
+ vmaddr_offset: u64,
+ } = null,
+
+ /// Offset of this node in the trie output byte stream.
+ trie_offset: ?u64 = null,
+
+ /// List of all edges originating from this node.
+ edges: std.ArrayListUnmanaged(Edge) = .{},
+
+ node_dirty: bool = true,
+
+ /// Edge connecting to nodes in the trie.
+ pub const Edge = struct {
+ from: *Node,
+ to: *Node,
+ label: []u8,
+
+ fn deinit(self: *Edge, allocator: Allocator) void {
+ self.to.deinit(allocator);
+ allocator.destroy(self.to);
+ allocator.free(self.label);
+ self.from = undefined;
+ self.to = undefined;
+ self.label = undefined;
+ }
+ };
+
+ fn deinit(self: *Node, allocator: Allocator) void {
+ for (self.edges.items) |*edge| {
+ edge.deinit(allocator);
+ }
+ self.edges.deinit(allocator);
+ }
+
+ /// Inserts a new node starting from `self`.
+ fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
+ // Check for match with edges from this node.
+ for (self.edges.items) |*edge| {
+ const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
+ if (match == 0) continue;
+ if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
+
+ // Found a match, need to splice up nodes.
+ // From: A -> B
+ // To: A -> C -> B
+ const mid = try allocator.create(Node);
+ mid.* = .{ .base = self.base };
+ const to_label = try allocator.dupe(u8, edge.label[match..]);
+ allocator.free(edge.label);
+ const to_node = edge.to;
+ edge.to = mid;
+ edge.label = try allocator.dupe(u8, label[0..match]);
+ self.base.node_count += 1;
+
+ try mid.edges.append(allocator, .{
+ .from = mid,
+ .to = to_node,
+ .label = to_label,
+ });
+
+ return if (match == label.len) mid else mid.put(allocator, label[match..]);
+ }
+
+ // Add a new node.
+ const node = try allocator.create(Node);
+ node.* = .{ .base = self.base };
+ self.base.node_count += 1;
+
+ try self.edges.append(allocator, .{
+ .from = self,
+ .to = node,
+ .label = try allocator.dupe(u8, label),
+ });
+
+ return node;
+ }
+
+ /// Recursively parses the node from the input byte stream.
+ fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
+ self.node_dirty = true;
+ const trie_offset = try reader.context.getPos();
+ self.trie_offset = trie_offset;
+
+ var nread: usize = 0;
+
+ const node_size = try leb.readULEB128(u64, reader);
+ if (node_size > 0) {
+ const export_flags = try leb.readULEB128(u64, reader);
+ // TODO Parse special flags.
+ assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
+ export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
+
+ const vmaddr_offset = try leb.readULEB128(u64, reader);
+
+ self.terminal_info = .{
+ .export_flags = export_flags,
+ .vmaddr_offset = vmaddr_offset,
+ };
+ }
+
+ const nedges = try reader.readByte();
+ self.base.node_count += nedges;
+
+ nread += (try reader.context.getPos()) - trie_offset;
+
+ var i: usize = 0;
+ while (i < nedges) : (i += 1) {
+ const edge_start_pos = try reader.context.getPos();
+
+ const label = blk: {
+ var label_buf = std.ArrayList(u8).init(allocator);
+ while (true) {
+ const next = try reader.readByte();
+ if (next == @as(u8, 0))
+ break;
+ try label_buf.append(next);
+ }
+ break :blk try label_buf.toOwnedSlice();
+ };
+
+ const seek_to = try leb.readULEB128(u64, reader);
+ const return_pos = try reader.context.getPos();
+
+ nread += return_pos - edge_start_pos;
+ try reader.context.seekTo(seek_to);
+
+ const node = try allocator.create(Node);
+ node.* = .{ .base = self.base };
+
+ nread += try node.read(allocator, reader);
+ try self.edges.append(allocator, .{
+ .from = self,
+ .to = node,
+ .label = label,
+ });
+ try reader.context.seekTo(return_pos);
+ }
+
+ return nread;
+ }
+
+ /// Writes this node to a byte stream.
+ /// The children of this node *are* not written to the byte stream
+ /// recursively. To write all nodes to a byte stream in sequence,
+ /// iterate over `Trie.ordered_nodes` and call this method on each node.
+ /// This is one of the requirements of the MachO.
+ /// Panics if `finalize` was not called before calling this method.
+ fn write(self: Node, writer: anytype) !void {
+ assert(!self.node_dirty);
+ if (self.terminal_info) |info| {
+ // Terminal node info: encode export flags and vmaddr offset of this symbol.
+ var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
+ var info_stream = std.io.fixedBufferStream(&info_buf);
+ // TODO Implement for special flags.
+ assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
+ info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
+ try leb.writeULEB128(info_stream.writer(), info.export_flags);
+ try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
+
+ // Encode the size of the terminal node info.
+ var size_buf: [@sizeOf(u64)]u8 = undefined;
+ var size_stream = std.io.fixedBufferStream(&size_buf);
+ try leb.writeULEB128(size_stream.writer(), info_stream.pos);
+
+ // Now, write them to the output stream.
+ try writer.writeAll(size_buf[0..size_stream.pos]);
+ try writer.writeAll(info_buf[0..info_stream.pos]);
+ } else {
+ // Non-terminal node is delimited by 0 byte.
+ try writer.writeByte(0);
+ }
+ // Write number of edges (max legal number of edges is 256).
+ try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
+
+ for (self.edges.items) |edge| {
+ // Write edge label and offset to next node in trie.
+ try writer.writeAll(edge.label);
+ try writer.writeByte(0);
+ try leb.writeULEB128(writer, edge.to.trie_offset.?);
+ }
+ }
+
+ const FinalizeResult = struct {
+ /// Current size of this node in bytes.
+ node_size: u64,
+
+ /// True if the trie offset of this node in the output byte stream
+ /// would need updating; false otherwise.
+ updated: bool,
+ };
+
+ /// Updates offset of this node in the output byte stream.
+ fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
+ var stream = std.io.countingWriter(std.io.null_writer);
+ const writer = stream.writer();
+
+ var node_size: u64 = 0;
+ if (self.terminal_info) |info| {
+ try leb.writeULEB128(writer, info.export_flags);
+ try leb.writeULEB128(writer, info.vmaddr_offset);
+ try leb.writeULEB128(writer, stream.bytes_written);
+ } else {
+ node_size += 1; // 0x0 for non-terminal nodes
+ }
+ node_size += 1; // 1 byte for edge count
+
+ for (self.edges.items) |edge| {
+ const next_node_offset = edge.to.trie_offset orelse 0;
+ node_size += edge.label.len + 1;
+ try leb.writeULEB128(writer, next_node_offset);
+ }
+
+ const trie_offset = self.trie_offset orelse 0;
+ const updated = offset_in_trie != trie_offset;
+ self.trie_offset = offset_in_trie;
+ self.node_dirty = false;
+ node_size += stream.bytes_written;
+
+ return FinalizeResult{ .node_size = node_size, .updated = updated };
+ }
+};
+
/// The root node of the trie.
root: ?*Node = null,
@@ -124,13 +366,11 @@ pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize
/// Write the trie to a byte stream.
/// Panics if the trie was not finalized using `finalize` before calling this method.
-pub fn write(self: Trie, writer: anytype) !u64 {
+pub fn write(self: Trie, writer: anytype) !void {
assert(!self.trie_dirty);
- var counting_writer = std.io.countingWriter(writer);
for (self.ordered_nodes.items) |node| {
- try node.write(counting_writer.writer());
+ try node.write(writer);
}
- return counting_writer.bytes_written;
}
pub fn init(self: *Trie, allocator: Allocator) !void {
@@ -155,15 +395,15 @@ test "Trie node count" {
defer trie.deinit(gpa);
try trie.init(gpa);
- try testing.expectEqual(trie.node_count, 0);
- try testing.expect(trie.root == null);
+ try testing.expectEqual(@as(usize, 1), trie.node_count);
+ try testing.expect(trie.root != null);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
- try testing.expectEqual(trie.node_count, 2);
+ try testing.expectEqual(@as(usize, 2), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
@@ -171,14 +411,14 @@ test "Trie node count" {
.vmaddr_offset = 0,
.export_flags = 0,
});
- try testing.expectEqual(trie.node_count, 2);
+ try testing.expectEqual(@as(usize, 2), trie.node_count);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
- try testing.expectEqual(trie.node_count, 4);
+ try testing.expectEqual(@as(usize, 4), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
@@ -186,13 +426,13 @@ test "Trie node count" {
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
- try testing.expectEqual(trie.node_count, 4);
+ try testing.expectEqual(@as(usize, 4), trie.node_count);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
- try testing.expectEqual(trie.node_count, 4);
+ try testing.expectEqual(@as(usize, 4), trie.node_count);
}
test "Trie basic" {
@@ -279,7 +519,7 @@ test "write Trie to a byte stream" {
});
try trie.finalize(gpa);
- try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes.
+ try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{
0x0, 0x1, // node root
@@ -308,7 +548,7 @@ test "write Trie to a byte stream" {
}
test "parse Trie from byte stream" {
- var gpa = testing.allocator;
+ const gpa = testing.allocator;
const in_buffer = [_]u8{
0x0, 0x1, // node root
@@ -339,7 +579,7 @@ test "parse Trie from byte stream" {
}
test "ordering bug" {
- var gpa = testing.allocator;
+ const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
@@ -354,6 +594,7 @@ test "ordering bug" {
.vmaddr_offset = 0x8008,
.export_flags = 0,
});
+
try trie.finalize(gpa);
const exp_buffer = [_]u8{
@@ -369,245 +610,3 @@ test "ordering bug" {
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
-
-pub const Node = struct {
- base: *Trie,
-
- /// Terminal info associated with this node.
- /// If this node is not a terminal node, info is null.
- terminal_info: ?struct {
- /// Export flags associated with this exported symbol.
- export_flags: u64,
- /// VM address offset wrt to the section this symbol is defined against.
- vmaddr_offset: u64,
- } = null,
-
- /// Offset of this node in the trie output byte stream.
- trie_offset: ?u64 = null,
-
- /// List of all edges originating from this node.
- edges: std.ArrayListUnmanaged(Edge) = .{},
-
- node_dirty: bool = true,
-
- /// Edge connecting to nodes in the trie.
- pub const Edge = struct {
- from: *Node,
- to: *Node,
- label: []u8,
-
- fn deinit(self: *Edge, allocator: Allocator) void {
- self.to.deinit(allocator);
- allocator.destroy(self.to);
- allocator.free(self.label);
- self.from = undefined;
- self.to = undefined;
- self.label = undefined;
- }
- };
-
- fn deinit(self: *Node, allocator: Allocator) void {
- for (self.edges.items) |*edge| {
- edge.deinit(allocator);
- }
- self.edges.deinit(allocator);
- }
-
- /// Inserts a new node starting from `self`.
- fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
- // Check for match with edges from this node.
- for (self.edges.items) |*edge| {
- const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
- if (match == 0) continue;
- if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
-
- // Found a match, need to splice up nodes.
- // From: A -> B
- // To: A -> C -> B
- const mid = try allocator.create(Node);
- mid.* = .{ .base = self.base };
- const to_label = try allocator.dupe(u8, edge.label[match..]);
- allocator.free(edge.label);
- const to_node = edge.to;
- edge.to = mid;
- edge.label = try allocator.dupe(u8, label[0..match]);
- self.base.node_count += 1;
-
- try mid.edges.append(allocator, .{
- .from = mid,
- .to = to_node,
- .label = to_label,
- });
-
- return if (match == label.len) mid else mid.put(allocator, label[match..]);
- }
-
- // Add a new node.
- const node = try allocator.create(Node);
- node.* = .{ .base = self.base };
- self.base.node_count += 1;
-
- try self.edges.append(allocator, .{
- .from = self,
- .to = node,
- .label = try allocator.dupe(u8, label),
- });
-
- return node;
- }
-
- /// Recursively parses the node from the input byte stream.
- fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
- self.node_dirty = true;
- const trie_offset = try reader.context.getPos();
- self.trie_offset = trie_offset;
-
- var nread: usize = 0;
-
- const node_size = try leb.readULEB128(u64, reader);
- if (node_size > 0) {
- const export_flags = try leb.readULEB128(u64, reader);
- // TODO Parse special flags.
- assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
- export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
-
- const vmaddr_offset = try leb.readULEB128(u64, reader);
-
- self.terminal_info = .{
- .export_flags = export_flags,
- .vmaddr_offset = vmaddr_offset,
- };
- }
-
- const nedges = try reader.readByte();
- self.base.node_count += nedges;
-
- nread += (try reader.context.getPos()) - trie_offset;
-
- var i: usize = 0;
- while (i < nedges) : (i += 1) {
- const edge_start_pos = try reader.context.getPos();
-
- const label = blk: {
- var label_buf = std.ArrayList(u8).init(allocator);
- while (true) {
- const next = try reader.readByte();
- if (next == @as(u8, 0))
- break;
- try label_buf.append(next);
- }
- break :blk try label_buf.toOwnedSlice();
- };
-
- const seek_to = try leb.readULEB128(u64, reader);
- const return_pos = try reader.context.getPos();
-
- nread += return_pos - edge_start_pos;
- try reader.context.seekTo(seek_to);
-
- const node = try allocator.create(Node);
- node.* = .{ .base = self.base };
-
- nread += try node.read(allocator, reader);
- try self.edges.append(allocator, .{
- .from = self,
- .to = node,
- .label = label,
- });
- try reader.context.seekTo(return_pos);
- }
-
- return nread;
- }
-
- /// Writes this node to a byte stream.
- /// The children of this node *are* not written to the byte stream
- /// recursively. To write all nodes to a byte stream in sequence,
- /// iterate over `Trie.ordered_nodes` and call this method on each node.
- /// This is one of the requirements of the MachO.
- /// Panics if `finalize` was not called before calling this method.
- fn write(self: Node, writer: anytype) !void {
- assert(!self.node_dirty);
- if (self.terminal_info) |info| {
- // Terminal node info: encode export flags and vmaddr offset of this symbol.
- var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
- var info_stream = std.io.fixedBufferStream(&info_buf);
- // TODO Implement for special flags.
- assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
- info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
- try leb.writeULEB128(info_stream.writer(), info.export_flags);
- try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
-
- // Encode the size of the terminal node info.
- var size_buf: [@sizeOf(u64)]u8 = undefined;
- var size_stream = std.io.fixedBufferStream(&size_buf);
- try leb.writeULEB128(size_stream.writer(), info_stream.pos);
-
- // Now, write them to the output stream.
- try writer.writeAll(size_buf[0..size_stream.pos]);
- try writer.writeAll(info_buf[0..info_stream.pos]);
- } else {
- // Non-terminal node is delimited by 0 byte.
- try writer.writeByte(0);
- }
- // Write number of edges (max legal number of edges is 256).
- try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
-
- for (self.edges.items) |edge| {
- // Write edge label and offset to next node in trie.
- try writer.writeAll(edge.label);
- try writer.writeByte(0);
- try leb.writeULEB128(writer, edge.to.trie_offset.?);
- }
- }
-
- const FinalizeResult = struct {
- /// Current size of this node in bytes.
- node_size: u64,
-
- /// True if the trie offset of this node in the output byte stream
- /// would need updating; false otherwise.
- updated: bool,
- };
-
- /// Updates offset of this node in the output byte stream.
- fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
- var stream = std.io.countingWriter(std.io.null_writer);
- const writer = stream.writer();
-
- var node_size: u64 = 0;
- if (self.terminal_info) |info| {
- try leb.writeULEB128(writer, info.export_flags);
- try leb.writeULEB128(writer, info.vmaddr_offset);
- try leb.writeULEB128(writer, stream.bytes_written);
- } else {
- node_size += 1; // 0x0 for non-terminal nodes
- }
- node_size += 1; // 1 byte for edge count
-
- for (self.edges.items) |edge| {
- const next_node_offset = edge.to.trie_offset orelse 0;
- node_size += edge.label.len + 1;
- try leb.writeULEB128(writer, next_node_offset);
- }
-
- const trie_offset = self.trie_offset orelse 0;
- const updated = offset_in_trie != trie_offset;
- self.trie_offset = offset_in_trie;
- self.node_dirty = false;
- node_size += stream.bytes_written;
-
- return FinalizeResult{ .node_size = node_size, .updated = updated };
- }
-};
-
-const Trie = @This();
-
-const std = @import("std");
-const mem = std.mem;
-const leb = std.leb;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const testing = std.testing;
-const assert = std.debug.assert;
-const Allocator = mem.Allocator;
diff --git a/src/link/MachO/dyld_info/bind.zig b/src/link/MachO/dyld_info/bind.zig
index ca4e73a283..7c0d2ab692 100644
--- a/src/link/MachO/dyld_info/bind.zig
+++ b/src/link/MachO/dyld_info/bind.zig
@@ -1,231 +1,397 @@
-pub fn Bind(comptime Ctx: type, comptime Target: type) type {
- return struct {
- entries: std.ArrayListUnmanaged(Entry) = .{},
- buffer: std.ArrayListUnmanaged(u8) = .{},
-
- const Self = @This();
-
- const Entry = struct {
- target: Target,
- offset: u64,
- segment_id: u8,
- addend: i64,
-
- pub fn lessThan(ctx: Ctx, entry: Entry, other: Entry) bool {
- if (entry.segment_id == other.segment_id) {
- if (entry.target.eql(other.target)) {
- return entry.offset < other.offset;
- }
- const entry_name = ctx.getSymbolName(entry.target);
- const other_name = ctx.getSymbolName(other.target);
- return std.mem.lessThan(u8, entry_name, other_name);
- }
- return entry.segment_id < other.segment_id;
- }
- };
+const std = @import("std");
+const assert = std.debug.assert;
+const leb = std.leb;
+const log = std.log.scoped(.link_dyld_info);
+const macho = std.macho;
+const testing = std.testing;
- pub fn deinit(self: *Self, gpa: Allocator) void {
- self.entries.deinit(gpa);
- self.buffer.deinit(gpa);
+const Allocator = std.mem.Allocator;
+const MachO = @import("../../MachO.zig");
+const Symbol = @import("../Symbol.zig");
+
+pub const Entry = struct {
+ target: Symbol.Index,
+ offset: u64,
+ segment_id: u8,
+ addend: i64,
+
+ pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool {
+ if (entry.segment_id == other.segment_id) {
+ if (entry.target == other.target) {
+ return entry.offset < other.offset;
+ }
+ const entry_name = ctx.getSymbol(entry.target).getName(ctx);
+ const other_name = ctx.getSymbol(other.target).getName(ctx);
+ return std.mem.lessThan(u8, entry_name, other_name);
}
+ return entry.segment_id < other.segment_id;
+ }
+};
- pub fn size(self: Self) u64 {
- return @as(u64, @intCast(self.buffer.items.len));
- }
+pub const Bind = struct {
+ entries: std.ArrayListUnmanaged(Entry) = .{},
+ buffer: std.ArrayListUnmanaged(u8) = .{},
- pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
- if (self.entries.items.len == 0) return;
+ const Self = @This();
- const writer = self.buffer.writer(gpa);
+ pub fn deinit(self: *Self, gpa: Allocator) void {
+ self.entries.deinit(gpa);
+ self.buffer.deinit(gpa);
+ }
- std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
+ pub fn size(self: Self) u64 {
+ return @as(u64, @intCast(self.buffer.items.len));
+ }
- var start: usize = 0;
- var seg_id: ?u8 = null;
- for (self.entries.items, 0..) |entry, i| {
- if (seg_id != null and seg_id.? == entry.segment_id) continue;
- try finalizeSegment(self.entries.items[start..i], ctx, writer);
- seg_id = entry.segment_id;
- start = i;
- }
+ pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
+ if (self.entries.items.len == 0) return;
- try finalizeSegment(self.entries.items[start..], ctx, writer);
- try done(writer);
+ const writer = self.buffer.writer(gpa);
+
+ log.debug("bind opcodes", .{});
+
+ std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
+
+ var start: usize = 0;
+ var seg_id: ?u8 = null;
+ for (self.entries.items, 0..) |entry, i| {
+ if (seg_id != null and seg_id.? == entry.segment_id) continue;
+ try finalizeSegment(self.entries.items[start..i], ctx, writer);
+ seg_id = entry.segment_id;
+ start = i;
}
- fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void {
- if (entries.len == 0) return;
-
- const seg_id = entries[0].segment_id;
- try setSegmentOffset(seg_id, 0, writer);
-
- var offset: u64 = 0;
- var addend: i64 = 0;
- var count: usize = 0;
- var skip: u64 = 0;
- var target: ?Target = null;
-
- var state: enum {
- start,
- bind_single,
- bind_times_skip,
- } = .start;
-
- var i: usize = 0;
- while (i < entries.len) : (i += 1) {
- const current = entries[i];
- if (target == null or !target.?.eql(current.target)) {
- switch (state) {
- .start => {},
- .bind_single => try doBind(writer),
- .bind_times_skip => try doBindTimesSkip(count, skip, writer),
- }
- state = .start;
- target = current.target;
+ try finalizeSegment(self.entries.items[start..], ctx, writer);
+ try done(writer);
+ }
- const sym = ctx.getSymbol(current.target);
- const name = ctx.getSymbolName(current.target);
- const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
- const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
+ fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
+ if (entries.len == 0) return;
- try setSymbol(name, flags, writer);
- try setTypePointer(writer);
- try setDylibOrdinal(ordinal, writer);
+ const seg_id = entries[0].segment_id;
+ try setSegmentOffset(seg_id, 0, writer);
- if (current.addend != addend) {
- addend = current.addend;
- try setAddend(addend, writer);
- }
- }
+ var offset: u64 = 0;
+ var addend: i64 = 0;
+ var count: usize = 0;
+ var skip: u64 = 0;
+ var target: ?Symbol.Index = null;
+
+ var state: enum {
+ start,
+ bind_single,
+ bind_times_skip,
+ } = .start;
- log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
- log.debug(" => {x}", .{current.offset});
+ var i: usize = 0;
+ while (i < entries.len) : (i += 1) {
+ const current = entries[i];
+ if (target == null or target.? != current.target) {
switch (state) {
- .start => {
- if (current.offset < offset) {
- try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
- offset = offset - (offset - current.offset);
- } else if (current.offset > offset) {
- const delta = current.offset - offset;
- try addAddr(delta, writer);
- offset += delta;
- }
- state = .bind_single;
- offset += @sizeOf(u64);
- count = 1;
- },
- .bind_single => {
- if (current.offset == offset) {
- try doBind(writer);
- state = .start;
- } else if (current.offset > offset) {
- const delta = current.offset - offset;
- state = .bind_times_skip;
- skip = @as(u64, @intCast(delta));
- offset += skip;
- } else unreachable;
- i -= 1;
- },
- .bind_times_skip => {
- if (current.offset < offset) {
- count -= 1;
- if (count == 1) {
- try doBindAddAddr(skip, writer);
- } else {
- try doBindTimesSkip(count, skip, writer);
- }
- state = .start;
- offset = offset - (@sizeOf(u64) + skip);
- i -= 2;
- } else if (current.offset == offset) {
- count += 1;
- offset += @sizeOf(u64) + skip;
- } else {
- try doBindTimesSkip(count, skip, writer);
- state = .start;
- i -= 1;
- }
- },
+ .start => {},
+ .bind_single => try doBind(writer),
+ .bind_times_skip => try doBindTimesSkip(count, skip, writer),
+ }
+ state = .start;
+ target = current.target;
+
+ const sym = ctx.getSymbol(current.target);
+ const name = sym.getName(ctx);
+ const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
+ const ordinal: i16 = ord: {
+ if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ if (sym.flags.import) {
+ // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
+ }
+ if (ctx.undefined_treatment == .dynamic_lookup)
+ break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ break :ord macho.BIND_SPECIAL_DYLIB_SELF;
+ };
+
+ try setSymbol(name, flags, writer);
+ try setTypePointer(writer);
+ try setDylibOrdinal(ordinal, writer);
+
+ if (current.addend != addend) {
+ addend = current.addend;
+ try setAddend(addend, writer);
}
}
+ log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
+ log.debug(" => {x}", .{current.offset});
switch (state) {
- .start => unreachable,
- .bind_single => try doBind(writer),
- .bind_times_skip => try doBindTimesSkip(count, skip, writer),
+ .start => {
+ if (current.offset < offset) {
+ try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
+ offset = offset - (offset - current.offset);
+ } else if (current.offset > offset) {
+ const delta = current.offset - offset;
+ try addAddr(delta, writer);
+ offset += delta;
+ }
+ state = .bind_single;
+ offset += @sizeOf(u64);
+ count = 1;
+ },
+ .bind_single => {
+ if (current.offset == offset) {
+ try doBind(writer);
+ state = .start;
+ } else if (current.offset > offset) {
+ const delta = current.offset - offset;
+ state = .bind_times_skip;
+ skip = @as(u64, @intCast(delta));
+ offset += skip;
+ } else unreachable;
+ i -= 1;
+ },
+ .bind_times_skip => {
+ if (current.offset < offset) {
+ count -= 1;
+ if (count == 1) {
+ try doBindAddAddr(skip, writer);
+ } else {
+ try doBindTimesSkip(count, skip, writer);
+ }
+ state = .start;
+ offset = offset - (@sizeOf(u64) + skip);
+ i -= 2;
+ } else if (current.offset == offset) {
+ count += 1;
+ offset += @sizeOf(u64) + skip;
+ } else {
+ try doBindTimesSkip(count, skip, writer);
+ state = .start;
+ i -= 1;
+ }
+ },
}
}
- pub fn write(self: Self, writer: anytype) !void {
- if (self.size() == 0) return;
- try writer.writeAll(self.buffer.items);
+ switch (state) {
+ .start => unreachable,
+ .bind_single => try doBind(writer),
+ .bind_times_skip => try doBindTimesSkip(count, skip, writer),
}
- };
-}
+ }
-pub fn LazyBind(comptime Ctx: type, comptime Target: type) type {
- return struct {
- entries: std.ArrayListUnmanaged(Entry) = .{},
- buffer: std.ArrayListUnmanaged(u8) = .{},
- offsets: std.ArrayListUnmanaged(u32) = .{},
-
- const Self = @This();
-
- const Entry = struct {
- target: Target,
- offset: u64,
- segment_id: u8,
- addend: i64,
- };
-
- pub fn deinit(self: *Self, gpa: Allocator) void {
- self.entries.deinit(gpa);
- self.buffer.deinit(gpa);
- self.offsets.deinit(gpa);
- }
+ pub fn write(self: Self, writer: anytype) !void {
+ if (self.size() == 0) return;
+ try writer.writeAll(self.buffer.items);
+ }
+};
+
+pub const WeakBind = struct {
+ entries: std.ArrayListUnmanaged(Entry) = .{},
+ buffer: std.ArrayListUnmanaged(u8) = .{},
+
+ const Self = @This();
+
+ pub fn deinit(self: *Self, gpa: Allocator) void {
+ self.entries.deinit(gpa);
+ self.buffer.deinit(gpa);
+ }
+
+ pub fn size(self: Self) u64 {
+ return @as(u64, @intCast(self.buffer.items.len));
+ }
+
+ pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
+ if (self.entries.items.len == 0) return;
+
+ const writer = self.buffer.writer(gpa);
+
+ log.debug("weak bind opcodes", .{});
+
+ std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
- pub fn size(self: Self) u64 {
- return @as(u64, @intCast(self.buffer.items.len));
+ var start: usize = 0;
+ var seg_id: ?u8 = null;
+ for (self.entries.items, 0..) |entry, i| {
+ if (seg_id != null and seg_id.? == entry.segment_id) continue;
+ try finalizeSegment(self.entries.items[start..i], ctx, writer);
+ seg_id = entry.segment_id;
+ start = i;
}
- pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
- if (self.entries.items.len == 0) return;
+ try finalizeSegment(self.entries.items[start..], ctx, writer);
+ try done(writer);
+ }
+
+ fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
+ if (entries.len == 0) return;
- try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
+ const seg_id = entries[0].segment_id;
+ try setSegmentOffset(seg_id, 0, writer);
- var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
- const writer = cwriter.writer();
+ var offset: u64 = 0;
+ var addend: i64 = 0;
+ var count: usize = 0;
+ var skip: u64 = 0;
+ var target: ?Symbol.Index = null;
- var addend: i64 = 0;
+ var state: enum {
+ start,
+ bind_single,
+ bind_times_skip,
+ } = .start;
- for (self.entries.items) |entry| {
- self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));
+ var i: usize = 0;
+ while (i < entries.len) : (i += 1) {
+ const current = entries[i];
+ if (target == null or target.? != current.target) {
+ switch (state) {
+ .start => {},
+ .bind_single => try doBind(writer),
+ .bind_times_skip => try doBindTimesSkip(count, skip, writer),
+ }
+ state = .start;
+ target = current.target;
- const sym = ctx.getSymbol(entry.target);
- const name = ctx.getSymbolName(entry.target);
- const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
- const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
+ const sym = ctx.getSymbol(current.target);
+ const name = sym.getName(ctx);
+ const flags: u8 = 0; // TODO NON_WEAK_DEFINITION
- try setSegmentOffset(entry.segment_id, entry.offset, writer);
try setSymbol(name, flags, writer);
- try setDylibOrdinal(ordinal, writer);
+ try setTypePointer(writer);
- if (entry.addend != addend) {
- try setAddend(entry.addend, writer);
- addend = entry.addend;
+ if (current.addend != addend) {
+ addend = current.addend;
+ try setAddend(addend, writer);
}
+ }
- try doBind(writer);
- try done(writer);
+ log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
+ log.debug(" => {x}", .{current.offset});
+ switch (state) {
+ .start => {
+ if (current.offset < offset) {
+ try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
+ offset = offset - (offset - current.offset);
+ } else if (current.offset > offset) {
+ const delta = current.offset - offset;
+ try addAddr(delta, writer);
+ offset += delta;
+ }
+ state = .bind_single;
+ offset += @sizeOf(u64);
+ count = 1;
+ },
+ .bind_single => {
+ if (current.offset == offset) {
+ try doBind(writer);
+ state = .start;
+ } else if (current.offset > offset) {
+ const delta = current.offset - offset;
+ state = .bind_times_skip;
+ skip = @as(u64, @intCast(delta));
+ offset += skip;
+ } else unreachable;
+ i -= 1;
+ },
+ .bind_times_skip => {
+ if (current.offset < offset) {
+ count -= 1;
+ if (count == 1) {
+ try doBindAddAddr(skip, writer);
+ } else {
+ try doBindTimesSkip(count, skip, writer);
+ }
+ state = .start;
+ offset = offset - (@sizeOf(u64) + skip);
+ i -= 2;
+ } else if (current.offset == offset) {
+ count += 1;
+ offset += @sizeOf(u64) + skip;
+ } else {
+ try doBindTimesSkip(count, skip, writer);
+ state = .start;
+ i -= 1;
+ }
+ },
}
}
- pub fn write(self: Self, writer: anytype) !void {
- if (self.size() == 0) return;
- try writer.writeAll(self.buffer.items);
+ switch (state) {
+ .start => unreachable,
+ .bind_single => try doBind(writer),
+ .bind_times_skip => try doBindTimesSkip(count, skip, writer),
}
- };
-}
+ }
+
+ pub fn write(self: Self, writer: anytype) !void {
+ if (self.size() == 0) return;
+ try writer.writeAll(self.buffer.items);
+ }
+};
+
+pub const LazyBind = struct {
+ entries: std.ArrayListUnmanaged(Entry) = .{},
+ buffer: std.ArrayListUnmanaged(u8) = .{},
+ offsets: std.ArrayListUnmanaged(u32) = .{},
+
+ const Self = @This();
+
+ pub fn deinit(self: *Self, gpa: Allocator) void {
+ self.entries.deinit(gpa);
+ self.buffer.deinit(gpa);
+ self.offsets.deinit(gpa);
+ }
+
+ pub fn size(self: Self) u64 {
+ return @as(u64, @intCast(self.buffer.items.len));
+ }
+
+ pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
+ if (self.entries.items.len == 0) return;
+
+ try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
+
+ var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
+ const writer = cwriter.writer();
+
+ log.debug("lazy bind opcodes", .{});
+
+ var addend: i64 = 0;
+
+ for (self.entries.items) |entry| {
+ self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));
+
+ const sym = ctx.getSymbol(entry.target);
+ const name = sym.getName(ctx);
+ const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
+ const ordinal: i16 = ord: {
+ if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ if (sym.flags.import) {
+ // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
+ }
+ if (ctx.undefined_treatment == .dynamic_lookup)
+ break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+ break :ord macho.BIND_SPECIAL_DYLIB_SELF;
+ };
+
+ try setSegmentOffset(entry.segment_id, entry.offset, writer);
+ try setSymbol(name, flags, writer);
+ try setDylibOrdinal(ordinal, writer);
+
+ if (entry.addend != addend) {
+ try setAddend(entry.addend, writer);
+ addend = entry.addend;
+ }
+
+ try doBind(writer);
+ try done(writer);
+ }
+ }
+
+ pub fn write(self: Self, writer: anytype) !void {
+ if (self.size() == 0) return;
+ try writer.writeAll(self.buffer.items);
+ }
+};
fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
@@ -312,429 +478,3 @@ fn done(writer: anytype) !void {
log.debug(">>> done", .{});
try writer.writeByte(macho.BIND_OPCODE_DONE);
}
-
-const TestContext = struct {
- symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
- strtab: std.ArrayListUnmanaged(u8) = .{},
-
- const Target = struct {
- index: u32,
-
- fn eql(this: Target, other: Target) bool {
- return this.index == other.index;
- }
- };
-
- fn deinit(ctx: *TestContext, gpa: Allocator) void {
- ctx.symbols.deinit(gpa);
- ctx.strtab.deinit(gpa);
- }
-
- fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void {
- const n_strx = try ctx.addString(gpa, name);
- var n_desc = @as(u16, @bitCast(ordinal * macho.N_SYMBOL_RESOLVER));
- n_desc |= flags;
- try ctx.symbols.append(gpa, .{
- .n_value = 0,
- .n_strx = n_strx,
- .n_desc = n_desc,
- .n_type = macho.N_EXT,
- .n_sect = 0,
- });
- }
-
- fn addString(ctx: *TestContext, gpa: Allocator, name: []const u8) !u32 {
- const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
- try ctx.strtab.appendSlice(gpa, name);
- try ctx.strtab.append(gpa, 0);
- return n_strx;
- }
-
- fn getSymbol(ctx: TestContext, target: Target) macho.nlist_64 {
- return ctx.symbols.items[target.index];
- }
-
- fn getSymbolName(ctx: TestContext, target: Target) []const u8 {
- const sym = ctx.getSymbol(target);
- assert(sym.n_strx < ctx.strtab.items.len);
- return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + sym.n_strx)), 0);
- }
-};
-
-fn generateTestContext() !TestContext {
- const gpa = testing.allocator;
- var ctx = TestContext{};
- try ctx.addSymbol(gpa, "_import_1", 1, 0);
- try ctx.addSymbol(gpa, "_import_2", 1, 0);
- try ctx.addSymbol(gpa, "_import_3", 1, 0);
- try ctx.addSymbol(gpa, "_import_4", 2, 0);
- try ctx.addSymbol(gpa, "_import_5_weak", 2, macho.N_WEAK_REF);
- try ctx.addSymbol(gpa, "_import_6", 2, 0);
- return ctx;
-}
-
-test "bind - no entries" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = Bind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.finalize(gpa, test_context);
- try testing.expectEqual(@as(u64, 0), bind.size());
-}
-
-test "bind - single entry" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = Bind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.entries.append(gpa, .{
- .offset = 0x10,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.finalize(gpa, test_context);
- try testing.expectEqualSlices(u8, &[_]u8{
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
- 0x0,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x31,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0x10,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DONE,
- }, bind.buffer.items);
-}
-
-test "bind - multiple occurrences within the same segment" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = Bind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.entries.append(gpa, .{
- .offset = 0x10,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x18,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x20,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x28,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
-
- try bind.finalize(gpa, test_context);
- try testing.expectEqualSlices(u8, &[_]u8{
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
- 0x0,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x31,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0x10,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DONE,
- }, bind.buffer.items);
-}
-
-test "bind - multiple occurrences with skip and addend" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = Bind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.entries.append(gpa, .{
- .offset = 0x0,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x10,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x20,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x30,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0x10,
- });
-
- try bind.finalize(gpa, test_context);
- try testing.expectEqualSlices(u8, &[_]u8{
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
- 0x0,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x31,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_SET_ADDEND_SLEB,
- 0x10,
- macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
- 0x4,
- 0x8,
- macho.BIND_OPCODE_DONE,
- }, bind.buffer.items);
-}
-
-test "bind - complex" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = Bind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.entries.append(gpa, .{
- .offset = 0x58,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x100,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 1 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x110,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 1 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x130,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 1 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x140,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 1 },
- .addend = 0x10,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x148,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 2 },
- .addend = 0,
- });
-
- try bind.finalize(gpa, test_context);
- try testing.expectEqualSlices(u8, &[_]u8{
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
- 0x0,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x31,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0x58,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x32,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_SET_ADDEND_SLEB,
- 0x10,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0xa0,
- 0x1,
- macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
- 0x2,
- 0x8,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0x10,
- macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
- 0x2,
- 0x8,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x33,
- 0x0,
- macho.BIND_OPCODE_SET_TYPE_IMM | 1,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_SET_ADDEND_SLEB,
- 0x0,
- macho.BIND_OPCODE_ADD_ADDR_ULEB,
- 0xf8,
- 0xff,
- 0xff,
- 0xff,
- 0xff,
- 0xff,
- 0xff,
- 0xff,
- 0xff,
- 0x1,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DONE,
- }, bind.buffer.items);
-}
-
-test "lazy bind" {
- const gpa = testing.allocator;
-
- var test_context = try generateTestContext();
- defer test_context.deinit(gpa);
-
- var bind = LazyBind(TestContext, TestContext.Target){};
- defer bind.deinit(gpa);
-
- try bind.entries.append(gpa, .{
- .offset = 0x10,
- .segment_id = 1,
- .target = TestContext.Target{ .index = 0 },
- .addend = 0,
- });
- try bind.entries.append(gpa, .{
- .offset = 0x20,
- .segment_id = 2,
- .target = TestContext.Target{ .index = 1 },
- .addend = 0x10,
- });
-
- try bind.finalize(gpa, test_context);
- try testing.expectEqualSlices(u8, &[_]u8{
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
- 0x10,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x31,
- 0x0,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DONE,
- macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
- 0x20,
- macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
- 0x5f,
- 0x69,
- 0x6d,
- 0x70,
- 0x6f,
- 0x72,
- 0x74,
- 0x5f,
- 0x32,
- 0x0,
- macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
- macho.BIND_OPCODE_SET_ADDEND_SLEB,
- 0x10,
- macho.BIND_OPCODE_DO_BIND,
- macho.BIND_OPCODE_DONE,
- }, bind.buffer.items);
-}
-
-const std = @import("std");
-const assert = std.debug.assert;
-const leb = std.leb;
-const log = std.log.scoped(.dyld_info);
-const macho = std.macho;
-const testing = std.testing;
-
-const Allocator = std.mem.Allocator;
diff --git a/src/link/MachO/eh_frame.zig b/src/link/MachO/eh_frame.zig
index 4b51d09683..24b3d751a4 100644
--- a/src/link/MachO/eh_frame.zig
+++ b/src/link/MachO/eh_frame.zig
@@ -1,628 +1,537 @@
-pub fn scanRelocs(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- for (macho_file.objects.items, 0..) |*object, object_id| {
- var cies = std.AutoHashMap(u32, void).init(gpa);
- defer cies.deinit();
-
- var it = object.getEhFrameRecordsIterator();
-
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (inner_syms_it.next()) |sym| {
- const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
- if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
- it.seekTo(fde_offset);
- const fde = (it.next() catch continue).?; // We don't care about this error since we already handled it
-
- const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset);
- const cie_offset = fde_offset + 4 - cie_ptr;
-
- if (!cies.contains(cie_offset)) {
- try cies.putNoClobber(cie_offset, {});
- it.seekTo(cie_offset);
- const cie = (it.next() catch continue).?; // We don't care about this error since we already handled it
- try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset);
+pub const Cie = struct {
+ /// Includes 4byte size cell.
+ offset: u32,
+ out_offset: u32 = 0,
+ size: u32,
+ lsda_size: ?enum { p32, p64 } = null,
+ personality: ?Personality = null,
+ file: File.Index = 0,
+ alive: bool = false,
+
+ pub fn parse(cie: *Cie, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const data = cie.getData(macho_file);
+ const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0);
+
+ if (aug[0] != 'z') return; // TODO should we error out?
+
+ var stream = std.io.fixedBufferStream(data[9 + aug.len + 1 ..]);
+ var creader = std.io.countingReader(stream.reader());
+ const reader = creader.reader();
+
+ _ = try leb.readULEB128(u64, reader); // code alignment factor
+ _ = try leb.readULEB128(u64, reader); // data alignment factor
+ _ = try leb.readULEB128(u64, reader); // return address register
+ _ = try leb.readULEB128(u64, reader); // augmentation data length
+
+ for (aug[1..]) |ch| switch (ch) {
+ 'R' => {
+ const enc = try reader.readByte();
+ if (enc & 0xf != EH_PE.absptr or enc & EH_PE.pcrel == 0) {
+ @panic("unexpected pointer encoding"); // TODO error
}
- }
- }
- }
-}
-
-pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) error{OutOfMemory}!void {
- const sect_id = macho_file.eh_frame_section_index orelse return;
- const sect = &macho_file.sections.items(.header)[sect_id];
- sect.@"align" = 3;
- sect.size = 0;
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- var size: u32 = 0;
-
- for (macho_file.objects.items, 0..) |*object, object_id| {
- var cies = std.AutoHashMap(u32, u32).init(gpa);
- defer cies.deinit();
-
- var eh_it = object.getEhFrameRecordsIterator();
-
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (inner_syms_it.next()) |sym| {
- const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
- if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
-
- const record_id = unwind_info.records_lookup.get(sym) orelse continue;
- const record = unwind_info.records.items[record_id];
-
- // TODO skip this check if no __compact_unwind is present
- const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
- if (!is_dwarf) continue;
-
- eh_it.seekTo(fde_record_offset);
- const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
-
- const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
- const cie_offset = fde_record_offset + 4 - cie_ptr;
-
- const gop = try cies.getOrPut(cie_offset);
- if (!gop.found_existing) {
- eh_it.seekTo(cie_offset);
- const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
- gop.value_ptr.* = size;
- size += source_cie_record.getSize();
+ },
+ 'P' => {
+ const enc = try reader.readByte();
+ if (enc != EH_PE.pcrel | EH_PE.indirect | EH_PE.sdata4) {
+ @panic("unexpected personality pointer encoding"); // TODO error
}
+ _ = try reader.readInt(u32, .little); // personality pointer
+ },
+ 'L' => {
+ const enc = try reader.readByte();
+ switch (enc & 0xf) {
+ EH_PE.sdata4 => cie.lsda_size = .p32,
+ EH_PE.absptr => cie.lsda_size = .p64,
+ else => unreachable, // TODO error
+ }
+ },
+ else => @panic("unexpected augmentation string"), // TODO error
+ };
+ }
- size += source_fde_record.getSize();
- }
- }
+ pub inline fn getSize(cie: Cie) u32 {
+ return cie.size + 4;
+ }
- sect.size = size;
+ pub fn getObject(cie: Cie, macho_file: *MachO) *Object {
+ const file = macho_file.getFile(cie.file).?;
+ return file.object;
}
-}
-pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void {
- const sect_id = macho_file.eh_frame_section_index orelse return;
- const sect = macho_file.sections.items(.header)[sect_id];
- const seg_id = macho_file.sections.items(.segment_index)[sect_id];
- const seg = macho_file.segments.items[seg_id];
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- var eh_records = std.AutoArrayHashMap(u32, EhFrameRecord(true)).init(gpa);
- defer {
- for (eh_records.values()) |*rec| {
- rec.deinit(gpa);
- }
- eh_records.deinit();
+ pub fn getData(cie: Cie, macho_file: *MachO) []const u8 {
+ const object = cie.getObject(macho_file);
+ return object.eh_frame_data.items[cie.offset..][0..cie.getSize()];
}
- var eh_frame_offset: u32 = 0;
+ pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
+ const personality = cie.personality orelse return null;
+ return macho_file.getSymbol(personality.index);
+ }
- for (macho_file.objects.items, 0..) |*object, object_id| {
- try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len)));
+ pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool {
+ if (!std.mem.eql(u8, cie.getData(macho_file), other.getData(macho_file))) return false;
+ if (cie.personality != null and other.personality != null) {
+ if (cie.personality.?.index != other.personality.?.index) return false;
+ }
+ if (cie.personality != null or other.personality != null) return false;
+ return true;
+ }
- var cies = std.AutoHashMap(u32, u32).init(gpa);
- defer cies.deinit();
+ pub fn format(
+ cie: Cie,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = cie;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format CIEs directly");
+ }
- var eh_it = object.getEhFrameRecordsIterator();
+ pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .cie = cie,
+ .macho_file = macho_file,
+ } };
+ }
- for (object.exec_atoms.items) |atom_index| {
- var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (inner_syms_it.next()) |reloc_target| {
- const fde_record_offset = object.eh_frame_records_lookup.get(reloc_target) orelse continue;
- if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
+ const FormatContext = struct {
+ cie: Cie,
+ macho_file: *MachO,
+ };
- const record_id = unwind_info.records_lookup.get(reloc_target) orelse continue;
- const record = &unwind_info.records.items[record_id];
+ fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const cie = ctx.cie;
+ try writer.print("@{x} : size({x})", .{
+ cie.offset,
+ cie.getSize(),
+ });
+ if (!cie.alive) try writer.writeAll(" : [*]");
+ }
- // TODO skip this check if no __compact_unwind is present
- const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
- if (!is_dwarf) continue;
+ pub const Index = u32;
- eh_it.seekTo(fde_record_offset);
- const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
+ pub const Personality = struct {
+ index: Symbol.Index = 0,
+ offset: u32 = 0,
+ };
+};
- const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
- const cie_offset = fde_record_offset + 4 - cie_ptr;
+pub const Fde = struct {
+ /// Includes 4byte size cell.
+ offset: u32,
+ out_offset: u32 = 0,
+ size: u32,
+ cie: Cie.Index,
+ atom: Atom.Index = 0,
+ atom_offset: u32 = 0,
+ lsda: Atom.Index = 0,
+ lsda_offset: u32 = 0,
+ lsda_ptr_offset: u32 = 0,
+ file: File.Index = 0,
+ alive: bool = true,
+
+ pub fn parse(fde: *Fde, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const data = fde.getData(macho_file);
+ const object = fde.getObject(macho_file);
+ const sect = object.sections.items(.header)[object.eh_frame_sect_index.?];
+
+ // Parse target atom index
+ const pc_begin = std.mem.readInt(i64, data[8..][0..8], .little);
+ const taddr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + 8)) + pc_begin);
+ fde.atom = object.findAtom(taddr) orelse {
+ try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid function reference in FDE", .{
+ sect.segName(), sect.sectName(), fde.offset + 8,
+ });
+ return error.MalformedObject;
+ };
+ const atom = fde.getAtom(macho_file);
+ fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file));
+
+ // Associate with a CIE
+ const cie_ptr = std.mem.readInt(u32, data[4..8], .little);
+ const cie_offset = fde.offset + 4 - cie_ptr;
+ const cie_index = for (object.cies.items, 0..) |cie, cie_index| {
+ if (cie.offset == cie_offset) break @as(Cie.Index, @intCast(cie_index));
+ } else null;
+ if (cie_index) |cie| {
+ fde.cie = cie;
+ } else {
+ try macho_file.reportParseError2(object.index, "no matching CIE found for FDE at offset {x}", .{
+ fde.offset,
+ });
+ return error.MalformedObject;
+ }
- const gop = try cies.getOrPut(cie_offset);
- if (!gop.found_existing) {
- eh_it.seekTo(cie_offset);
- const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
- var cie_record = try source_cie_record.toOwned(gpa);
- try cie_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
- .source_offset = cie_offset,
- .out_offset = eh_frame_offset,
- .sect_addr = sect.addr,
- });
- eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
- gop.value_ptr.* = eh_frame_offset;
- eh_frame_offset += cie_record.getSize();
- }
+ const cie = fde.getCie(macho_file);
- var fde_record = try source_fde_record.toOwned(gpa);
- try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
- .source_offset = fde_record_offset,
- .out_offset = eh_frame_offset,
- .sect_addr = sect.addr,
+ // Parse LSDA atom index if any
+ if (cie.lsda_size) |lsda_size| {
+ var stream = std.io.fixedBufferStream(data[24..]);
+ var creader = std.io.countingReader(stream.reader());
+ const reader = creader.reader();
+ _ = try leb.readULEB128(u64, reader); // augmentation length
+ 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 + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr);
+ fde.lsda = object.findAtom(lsda_addr) orelse {
+ try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid LSDA reference in FDE", .{
+ sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset,
});
- fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
-
- switch (cpu_arch) {
- .aarch64 => {}, // relocs take care of LSDA pointers
- .x86_64 => {
- // We need to relocate target symbol address ourselves.
- const atom_sym = macho_file.getSymbol(reloc_target);
- try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
- .base_addr = sect.addr,
- .base_offset = eh_frame_offset,
- });
-
- // We need to parse LSDA pointer and relocate ourselves.
- const cie_record = eh_records.get(
- eh_frame_offset + 4 - fde_record.getCiePointer(),
- ).?;
- const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
- const source_lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
- .base_addr = eh_frame_sect.addr,
- .base_offset = fde_record_offset,
- }) catch continue; // We already handled this error
- if (source_lsda_ptr) |ptr| {
- const sym_index = object.getSymbolByAddress(ptr, null);
- const sym = object.symtab[sym_index];
- fde_record.setLsdaPointer(cie_record, sym.n_value, .{
- .base_addr = sect.addr,
- .base_offset = eh_frame_offset,
- }) catch continue; // We already handled this error
- }
- },
- else => unreachable,
- }
-
- eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
-
- UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
- &record.compactUnwindEncoding,
- cpu_arch,
- @as(u24, @intCast(eh_frame_offset)),
- );
-
- const cie_record = eh_records.get(
- eh_frame_offset + 4 - fde_record.getCiePointer(),
- ).?;
- const lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
- .base_addr = sect.addr,
- .base_offset = eh_frame_offset,
- }) catch continue; // We already handled this error
- if (lsda_ptr) |ptr| {
- record.lsda = ptr - seg.vmaddr;
- }
-
- eh_frame_offset += fde_record.getSize();
- }
+ return error.MalformedObject;
+ };
+ const lsda_atom = fde.getLsdaAtom(macho_file).?;
+ fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file));
}
}
- var buffer = std.ArrayList(u8).init(gpa);
- defer buffer.deinit();
- const writer = buffer.writer();
+ pub inline fn getSize(fde: Fde) u32 {
+ return fde.size + 4;
+ }
- for (eh_records.values()) |record| {
- try writer.writeInt(u32, record.size, .little);
- try buffer.appendSlice(record.data);
+ pub fn getObject(fde: Fde, macho_file: *MachO) *Object {
+ const file = macho_file.getFile(fde.file).?;
+ return file.object;
}
- try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
-}
-const EhFrameRecordTag = enum { cie, fde };
+ pub fn getData(fde: Fde, macho_file: *MachO) []const u8 {
+ const object = fde.getObject(macho_file);
+ return object.eh_frame_data.items[fde.offset..][0..fde.getSize()];
+ }
-pub fn EhFrameRecord(comptime is_mutable: bool) type {
- return struct {
- tag: EhFrameRecordTag,
- size: u32,
- data: if (is_mutable) []u8 else []const u8,
+ pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie {
+ const object = fde.getObject(macho_file);
+ return &object.cies.items[fde.cie];
+ }
- const Record = @This();
+ pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
+ return macho_file.getAtom(fde.atom).?;
+ }
- pub fn deinit(rec: *Record, gpa: Allocator) void {
- comptime assert(is_mutable);
- gpa.free(rec.data);
- }
+ pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
+ return macho_file.getAtom(fde.lsda);
+ }
- pub fn toOwned(rec: Record, gpa: Allocator) Allocator.Error!EhFrameRecord(true) {
- const data = try gpa.dupe(u8, rec.data);
- return EhFrameRecord(true){
- .tag = rec.tag,
- .size = rec.size,
- .data = data,
- };
- }
+ pub fn format(
+ fde: Fde,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = fde;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format FDEs directly");
+ }
- pub inline fn getSize(rec: Record) u32 {
- return 4 + rec.size;
- }
+ pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .fde = fde,
+ .macho_file = macho_file,
+ } };
+ }
- pub fn scanRelocs(
- rec: Record,
- macho_file: *MachO,
- object_id: u32,
- source_offset: u32,
- ) !void {
- if (rec.getPersonalityPointerReloc(macho_file, object_id, source_offset)) |target| {
- try macho_file.addGotEntry(target);
- }
- }
+ const FormatContext = struct {
+ fde: Fde,
+ macho_file: *MachO,
+ };
- pub fn getTargetSymbolAddress(rec: Record, ctx: struct {
- base_addr: u64,
- base_offset: u64,
- }) u64 {
- assert(rec.tag == .fde);
- const addend = mem.readInt(i64, rec.data[4..][0..8], .little);
- return @as(u64, @intCast(@as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)) + addend));
- }
+ fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ const fde = ctx.fde;
+ const macho_file = ctx.macho_file;
+ try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
+ fde.offset,
+ fde.getSize(),
+ fde.cie,
+ fde.getAtom(macho_file).getName(macho_file),
+ });
+ if (!fde.alive) try writer.writeAll(" : [*]");
+ }
- pub fn setTargetSymbolAddress(rec: *Record, value: u64, ctx: struct {
- base_addr: u64,
- base_offset: u64,
- }) !void {
- assert(rec.tag == .fde);
- const addend = @as(i64, @intCast(value)) - @as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8));
- mem.writeInt(i64, rec.data[4..][0..8], addend, .little);
- }
+ pub const Index = u32;
+};
- pub fn getPersonalityPointerReloc(
- rec: Record,
- macho_file: *MachO,
- object_id: u32,
- source_offset: u32,
- ) ?SymbolWithLoc {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const relocs = getRelocs(macho_file, object_id, source_offset);
- for (relocs) |rel| {
- switch (cpu_arch) {
- .aarch64 => {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
- switch (rel_type) {
- .ARM64_RELOC_SUBTRACTOR,
- .ARM64_RELOC_UNSIGNED,
- => continue,
- .ARM64_RELOC_POINTER_TO_GOT => {},
- else => unreachable,
- }
- },
- .x86_64 => {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
- switch (rel_type) {
- .X86_64_RELOC_GOT => {},
- else => unreachable,
- }
- },
- else => unreachable,
- }
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = rec.data,
- .base_offset = @as(i32, @intCast(source_offset)) + 4,
- });
- return reloc_target;
- }
- return null;
- }
+pub const Iterator = struct {
+ data: []const u8,
+ pos: u32 = 0,
- pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct {
- source_offset: u32,
- out_offset: u32,
- sect_addr: u64,
- }) !void {
- comptime assert(is_mutable);
-
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const relocs = getRelocs(macho_file, object_id, ctx.source_offset);
-
- for (relocs) |rel| {
- const reloc_target = Atom.parseRelocTarget(macho_file, .{
- .object_id = object_id,
- .rel = rel,
- .code = rec.data,
- .base_offset = @as(i32, @intCast(ctx.source_offset)) + 4,
- });
- const rel_offset = @as(u32, @intCast(rel.r_address - @as(i32, @intCast(ctx.source_offset)) - 4));
- const source_addr = ctx.sect_addr + rel_offset + ctx.out_offset + 4;
-
- switch (cpu_arch) {
- .aarch64 => {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
- switch (rel_type) {
- .ARM64_RELOC_SUBTRACTOR => {
- // Address of the __eh_frame in the source object file
- },
- .ARM64_RELOC_POINTER_TO_GOT => {
- const target_addr = macho_file.getGotEntryAddress(reloc_target).?;
- const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
- return error.Overflow;
- mem.writeInt(i32, rec.data[rel_offset..][0..4], result, .little);
- },
- .ARM64_RELOC_UNSIGNED => {
- assert(rel.r_extern == 1);
- const target_addr = Atom.getRelocTargetAddress(macho_file, reloc_target, false);
- const result = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
- mem.writeInt(i64, rec.data[rel_offset..][0..8], @as(i64, @intCast(result)), .little);
- },
- else => unreachable,
- }
- },
- .x86_64 => {
- const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
- switch (rel_type) {
- .X86_64_RELOC_GOT => {
- const target_addr = macho_file.getGotEntryAddress(reloc_target).?;
- const addend = mem.readInt(i32, rec.data[rel_offset..][0..4], .little);
- const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
- const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
- mem.writeInt(i32, rec.data[rel_offset..][0..4], disp, .little);
- },
- else => unreachable,
- }
- },
- else => unreachable,
- }
- }
- }
+ pub const Record = struct {
+ tag: enum { fde, cie },
+ offset: u32,
+ size: u32,
+ };
- pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 {
- assert(rec.tag == .fde);
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const addend = mem.readInt(u32, rec.data[0..4], .little);
- switch (cpu_arch) {
- .aarch64 => {
- const relocs = getRelocs(macho_file, object_id, offset);
- const maybe_rel = for (relocs) |rel| {
- if (rel.r_address - @as(i32, @intCast(offset)) == 4 and
- @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_SUBTRACTOR)
- break rel;
- } else null;
- const rel = maybe_rel orelse return addend;
- const object = &macho_file.objects.items[object_id];
- const target_addr = object.in_symtab.?[rel.r_symbolnum].n_value;
- const sect = object.getSourceSection(object.eh_frame_sect_id.?);
- return @intCast(sect.addr + offset - target_addr + addend);
- },
- .x86_64 => return addend,
- else => unreachable,
- }
- }
+ pub fn next(it: *Iterator) !?Record {
+ if (it.pos >= it.data.len) return null;
- pub fn getCiePointer(rec: Record) u32 {
- assert(rec.tag == .fde);
- return mem.readInt(u32, rec.data[0..4], .little);
- }
+ var stream = std.io.fixedBufferStream(it.data[it.pos..]);
+ const reader = stream.reader();
- pub fn setCiePointer(rec: *Record, ptr: u32) void {
- assert(rec.tag == .fde);
- mem.writeInt(u32, rec.data[0..4], ptr, .little);
- }
+ const size = try reader.readInt(u32, .little);
+ if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS");
- pub fn getAugmentationString(rec: Record) []const u8 {
- assert(rec.tag == .cie);
- return mem.sliceTo(@as([*:0]const u8, @ptrCast(rec.data.ptr + 5)), 0);
- }
+ const id = try reader.readInt(u32, .little);
+ const record = Record{
+ .tag = if (id == 0) .cie else .fde,
+ .offset = it.pos,
+ .size = size,
+ };
+ it.pos += size + 4;
- pub fn getPersonalityPointer(rec: Record, ctx: struct {
- base_addr: u64,
- base_offset: u64,
- }) !?u64 {
- assert(rec.tag == .cie);
- const aug_str = rec.getAugmentationString();
+ return record;
+ }
+};
- var stream = std.io.fixedBufferStream(rec.data[9 + aug_str.len ..]);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+pub fn calcSize(macho_file: *MachO) !u32 {
+ const tracy = trace(@src());
+ defer tracy.end();
- for (aug_str, 0..) |ch, i| switch (ch) {
- 'z' => if (i > 0) {
- return error.BadDwarfCfi;
- } else {
- _ = try leb.readULEB128(u64, reader);
- },
- 'R' => {
- _ = try reader.readByte();
- },
- 'P' => {
- const enc = try reader.readByte();
- const offset = ctx.base_offset + 13 + aug_str.len + creader.bytes_read;
- const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader);
- return ptr;
- },
- 'L' => {
- _ = try reader.readByte();
- },
- 'S', 'B', 'G' => {},
- else => return error.BadDwarfCfi,
- };
+ var offset: u32 = 0;
- return null;
- }
+ var cies = std.ArrayList(Cie).init(macho_file.base.comp.gpa);
+ defer cies.deinit();
+
+ for (macho_file.objects.items) |index| {
+ const object = macho_file.getFile(index).?.object;
- pub fn getLsdaPointer(rec: Record, cie: Record, ctx: struct {
- base_addr: u64,
- base_offset: u64,
- }) !?u64 {
- assert(rec.tag == .fde);
- const enc = (try cie.getLsdaEncoding()) orelse return null;
- var stream = std.io.fixedBufferStream(rec.data[20..]);
- const reader = stream.reader();
- _ = try reader.readByte();
- const offset = ctx.base_offset + 25;
- const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader);
- return ptr;
+ outer: for (object.cies.items) |*cie| {
+ for (cies.items) |other| {
+ if (other.eql(cie.*, macho_file)) {
+ // We already have a CIE record that has the exact same contents, so instead of
+ // duplicating them, we mark this one dead and set its output offset to be
+ // equal to that of the alive record. This way, we won't have to rewrite
+ // Fde.cie_index field when committing the records to file.
+ cie.out_offset = other.out_offset;
+ continue :outer;
+ }
+ }
+ cie.alive = true;
+ cie.out_offset = offset;
+ offset += cie.getSize();
+ try cies.append(cie.*);
}
+ }
- pub fn setLsdaPointer(rec: *Record, cie: Record, value: u64, ctx: struct {
- base_addr: u64,
- base_offset: u64,
- }) !void {
- assert(rec.tag == .fde);
- const enc = (try cie.getLsdaEncoding()) orelse unreachable;
- var stream = std.io.fixedBufferStream(rec.data[21..]);
- const writer = stream.writer();
- const offset = ctx.base_offset + 25;
- try setEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), value, writer);
+ for (macho_file.objects.items) |index| {
+ const object = macho_file.getFile(index).?.object;
+ for (object.fdes.items) |*fde| {
+ if (!fde.alive) continue;
+ fde.out_offset = offset;
+ offset += fde.getSize();
}
+ }
- fn getLsdaEncoding(rec: Record) !?u8 {
- assert(rec.tag == .cie);
- const aug_str = rec.getAugmentationString();
+ return offset;
+}
- const base_offset = 9 + aug_str.len;
- var stream = std.io.fixedBufferStream(rec.data[base_offset..]);
- var creader = std.io.countingReader(stream.reader());
- const reader = creader.reader();
+pub fn calcNumRelocs(macho_file: *MachO) u32 {
+ const tracy = trace(@src());
+ defer tracy.end();
- for (aug_str, 0..) |ch, i| switch (ch) {
- 'z' => if (i > 0) {
- return error.BadDwarfCfi;
- } else {
- _ = try leb.readULEB128(u64, reader);
- },
- 'R' => {
- _ = try reader.readByte();
- },
- 'P' => {
- const enc = try reader.readByte();
- _ = try getEncodedPointer(enc, 0, reader);
- },
- 'L' => {
- const enc = try reader.readByte();
- return enc;
- },
- 'S', 'B', 'G' => {},
- else => return error.BadDwarfCfi,
- };
+ var nreloc: u32 = 0;
- return null;
+ 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
+ }
}
+ }
- fn getEncodedPointer(enc: u8, pcrel_offset: i64, reader: anytype) !?u64 {
- if (enc == EH_PE.omit) return null;
-
- var ptr: i64 = switch (enc & 0x0F) {
- EH_PE.absptr => @as(i64, @bitCast(try reader.readInt(u64, .little))),
- EH_PE.udata2 => @as(i16, @bitCast(try reader.readInt(u16, .little))),
- EH_PE.udata4 => @as(i32, @bitCast(try reader.readInt(u32, .little))),
- EH_PE.udata8 => @as(i64, @bitCast(try reader.readInt(u64, .little))),
- EH_PE.uleb128 => @as(i64, @bitCast(try leb.readULEB128(u64, reader))),
- EH_PE.sdata2 => try reader.readInt(i16, .little),
- EH_PE.sdata4 => try reader.readInt(i32, .little),
- EH_PE.sdata8 => try reader.readInt(i64, .little),
- EH_PE.sleb128 => try leb.readILEB128(i64, reader),
- else => return null,
- };
+ return nreloc;
+}
- switch (enc & 0x70) {
- EH_PE.absptr => {},
- EH_PE.pcrel => ptr += pcrel_offset,
- EH_PE.datarel,
- EH_PE.textrel,
- EH_PE.funcrel,
- EH_PE.aligned,
- => return null,
- else => return null,
- }
+pub fn write(macho_file: *MachO, buffer: []u8) void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
+ const addend: i64 = switch (macho_file.getTarget().cpu.arch) {
+ .x86_64 => 4,
+ else => 0,
+ };
- return @as(u64, @bitCast(ptr));
+ for (macho_file.objects.items) |index| {
+ const object = macho_file.getFile(index).?.object;
+ for (object.cies.items) |cie| {
+ if (!cie.alive) continue;
+
+ @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
+
+ if (cie.getPersonality(macho_file)) |sym| {
+ const offset = cie.out_offset + cie.personality.?.offset;
+ const saddr = sect.addr + offset;
+ const taddr = sym.getGotAddress(macho_file);
+ std.mem.writeInt(
+ i32,
+ buffer[offset..][0..4],
+ @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
+ .little,
+ );
+ }
}
+ }
+
+ for (macho_file.objects.items) |index| {
+ const object = macho_file.getFile(index).?.object;
+ for (object.fdes.items) |fde| {
+ if (!fde.alive) continue;
- fn setEncodedPointer(enc: u8, pcrel_offset: i64, value: u64, writer: anytype) !void {
- if (enc == EH_PE.omit) return;
+ @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
- var actual = @as(i64, @intCast(value));
+ {
+ const offset = fde.out_offset + 4;
+ const value = offset - fde.getCie(macho_file).out_offset;
+ std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);
+ }
- switch (enc & 0x70) {
- EH_PE.absptr => {},
- EH_PE.pcrel => actual -= pcrel_offset,
- EH_PE.datarel,
- EH_PE.textrel,
- EH_PE.funcrel,
- EH_PE.aligned,
- => unreachable,
- else => unreachable,
+ {
+ const offset = fde.out_offset + 8;
+ const saddr = sect.addr + offset;
+ const taddr = fde.getAtom(macho_file).value;
+ std.mem.writeInt(
+ i64,
+ buffer[offset..][0..8],
+ @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
+ .little,
+ );
}
- switch (enc & 0x0F) {
- EH_PE.absptr => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),
- EH_PE.udata2 => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(actual)))), .little),
- EH_PE.udata4 => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(actual)))), .little),
- EH_PE.udata8 => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),
- EH_PE.uleb128 => try leb.writeULEB128(writer, @as(u64, @bitCast(actual))),
- EH_PE.sdata2 => try writer.writeInt(i16, @as(i16, @intCast(actual)), .little),
- EH_PE.sdata4 => try writer.writeInt(i32, @as(i32, @intCast(actual)), .little),
- EH_PE.sdata8 => try writer.writeInt(i64, actual, .little),
- EH_PE.sleb128 => try leb.writeILEB128(writer, actual),
- else => unreachable,
+ 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,
+ buffer[offset..][0..4],
+ @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
+ .little,
+ ),
+ .p64 => std.mem.writeInt(
+ i64,
+ buffer[offset..][0..8],
+ @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
+ .little,
+ ),
+ }
}
}
- };
-}
-
-pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info {
- const object = &macho_file.objects.items[object_id];
- assert(object.hasEhFrameRecords());
- const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse
- return &[0]macho.relocation_info{};
- const all_relocs = object.getRelocs(object.eh_frame_sect_id.?);
- return all_relocs[urel.reloc.start..][0..urel.reloc.len];
+ }
}
-pub const Iterator = struct {
- data: []const u8,
- pos: u32 = 0,
+pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void {
+ const tracy = trace(@src());
+ defer tracy.end();
- pub fn next(it: *Iterator) !?EhFrameRecord(false) {
- if (it.pos >= it.data.len) return null;
-
- var stream = std.io.fixedBufferStream(it.data[it.pos..]);
- const reader = stream.reader();
+ const cpu_arch = macho_file.getTarget().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,
+ };
- const size = try reader.readInt(u32, .little);
- if (size == 0xFFFFFFFF) {
- log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{});
- return error.BadDwarfCfi;
+ 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,
+ },
+ });
+ }
}
+ }
- const id = try reader.readInt(u32, .little);
- const tag: EhFrameRecordTag = if (id == 0) .cie else .fde;
- const offset: u32 = 4;
- const record = EhFrameRecord(false){
- .tag = tag,
- .size = size,
- .data = it.data[it.pos + offset ..][0..size],
- };
+ for (macho_file.objects.items) |index| {
+ const object = macho_file.getFile(index).?.object;
+ for (object.fdes.items) |fde| {
+ if (!fde.alive) continue;
- it.pos += size + offset;
+ @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
- return record;
- }
+ {
+ 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);
+ }
- pub fn reset(it: *Iterator) void {
- it.pos = 0;
- }
+ {
+ 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,
+ );
+ }
- pub fn seekTo(it: *Iterator, pos: u32) void {
- assert(pos >= 0 and pos < it.data.len);
- it.pos = pos;
+ 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;
@@ -643,17 +552,17 @@ pub const EH_PE = struct {
pub const omit = 0xFF;
};
-const std = @import("std");
const assert = std.debug.assert;
+const leb = std.leb;
const macho = std.macho;
const math = std.math;
const mem = std.mem;
-const leb = std.leb;
-const log = std.log.scoped(.eh_frame);
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
-const Allocator = mem.Allocator;
+const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig");
+const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
-const Relocation = @import("Relocation.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const UnwindInfo = @import("UnwindInfo.zig");
+const Object = @import("Object.zig");
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/fat.zig b/src/link/MachO/fat.zig
index fcaca7d99a..5542d70dc0 100644
--- a/src/link/MachO/fat.zig
+++ b/src/link/MachO/fat.zig
@@ -1,24 +1,34 @@
-pub fn isFatLibrary(file: std.fs.File) bool {
- const reader = file.reader();
- const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false;
- defer file.seekTo(0) catch {};
+const std = @import("std");
+const assert = std.debug.assert;
+const builtin = @import("builtin");
+const log = std.log.scoped(.macho);
+const macho = std.macho;
+const mem = std.mem;
+const native_endian = builtin.target.cpu.arch.endian();
+
+const MachO = @import("../MachO.zig");
+
+pub fn isFatLibrary(path: []const u8) !bool {
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
+ const hdr = file.reader().readStructEndian(macho.fat_header, .big) catch return false;
return hdr.magic == macho.FAT_MAGIC;
}
pub const Arch = struct {
tag: std.Target.Cpu.Arch,
- offset: u64,
+ offset: u32,
+ size: u32,
};
-/// Caller owns the memory.
-pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
+pub fn parseArchs(path: []const u8, buffer: *[2]Arch) ![]const Arch {
+ const file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
const reader = file.reader();
const fat_header = try reader.readStructEndian(macho.fat_header, .big);
assert(fat_header.magic == macho.FAT_MAGIC);
- var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch);
- defer archs.deinit();
-
+ var count: usize = 0;
var fat_arch_index: u32 = 0;
while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
const fat_arch = try reader.readStructEndian(macho.fat_arch, .big);
@@ -29,16 +39,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue,
else => continue,
};
-
- archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset });
+ buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size };
+ count += 1;
}
- return archs.toOwnedSlice();
+ return buffer[0..count];
}
-
-const std = @import("std");
-const assert = std.debug.assert;
-const log = std.log.scoped(.archive);
-const macho = std.macho;
-const mem = std.mem;
-const Allocator = mem.Allocator;
diff --git a/src/link/MachO/file.zig b/src/link/MachO/file.zig
new file mode 100644
index 0000000000..67b2b9106e
--- /dev/null
+++ b/src/link/MachO/file.zig
@@ -0,0 +1,120 @@
+pub const File = union(enum) {
+ zig_object: *ZigObject,
+ internal: *InternalObject,
+ object: *Object,
+ dylib: *Dylib,
+
+ pub fn getIndex(file: File) Index {
+ return switch (file) {
+ inline else => |x| x.index,
+ };
+ }
+
+ pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) {
+ return .{ .data = file };
+ }
+
+ fn formatPath(
+ file: File,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = unused_fmt_string;
+ _ = options;
+ switch (file) {
+ .zig_object => |x| try writer.writeAll(x.path),
+ .internal => try writer.writeAll(""),
+ .object => |x| try writer.print("{}", .{x.fmtPath()}),
+ .dylib => |x| try writer.writeAll(x.path),
+ }
+ }
+
+ pub fn resolveSymbols(file: File, macho_file: *MachO) void {
+ switch (file) {
+ .internal => unreachable,
+ inline else => |x| x.resolveSymbols(macho_file),
+ }
+ }
+
+ pub fn resetGlobals(file: File, macho_file: *MachO) void {
+ switch (file) {
+ .internal => unreachable,
+ inline else => |x| x.resetGlobals(macho_file),
+ }
+ }
+
+ /// Encodes symbol rank so that the following ordering applies:
+ /// * strong in object
+ /// * weak in object
+ /// * tentative in object
+ /// * strong in archive/dylib
+ /// * weak in archive/dylib
+ /// * tentative in archive
+ /// * unclaimed
+ pub fn getSymbolRank(file: File, args: struct {
+ archive: bool = false,
+ weak: bool = false,
+ tentative: bool = false,
+ }) u32 {
+ if (file == .object and !args.archive) {
+ const base: u32 = blk: {
+ if (args.tentative) break :blk 3;
+ break :blk if (args.weak) 2 else 1;
+ };
+ return (base << 16) + file.getIndex();
+ }
+ const base: u32 = blk: {
+ if (args.tentative) break :blk 3;
+ break :blk if (args.weak) 2 else 1;
+ };
+ return base + (file.getIndex() << 24);
+ }
+
+ pub fn getSymbols(file: File) []const Symbol.Index {
+ return switch (file) {
+ inline else => |x| x.symbols.items,
+ };
+ }
+
+ pub fn getAtoms(file: File) []const Atom.Index {
+ return switch (file) {
+ .dylib => unreachable,
+ inline else => |x| x.atoms.items,
+ };
+ }
+
+ pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
+ return switch (file) {
+ inline else => |x| x.calcSymtabSize(macho_file),
+ };
+ }
+
+ pub fn writeSymtab(file: File, macho_file: *MachO) !void {
+ return switch (file) {
+ inline else => |x| x.writeSymtab(macho_file),
+ };
+ }
+
+ pub const Index = u32;
+
+ pub const Entry = union(enum) {
+ null: void,
+ zig_object: ZigObject,
+ internal: InternalObject,
+ object: Object,
+ dylib: Dylib,
+ };
+};
+
+const macho = std.macho;
+const std = @import("std");
+
+const Allocator = std.mem.Allocator;
+const Atom = @import("Atom.zig");
+const InternalObject = @import("InternalObject.zig");
+const MachO = @import("../MachO.zig");
+const Object = @import("Object.zig");
+const Dylib = @import("Dylib.zig");
+const Symbol = @import("Symbol.zig");
+const ZigObject = @import("ZigObject.zig");
diff --git a/src/link/MachO/hasher.zig b/src/link/MachO/hasher.zig
index 45847689f3..aff4696c08 100644
--- a/src/link/MachO/hasher.zig
+++ b/src/link/MachO/hasher.zig
@@ -9,6 +9,9 @@ pub fn ParallelHasher(comptime Hasher: type) type {
chunk_size: u64 = 0x4000,
max_file_size: ?u64 = null,
}) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
var wg: WaitGroup = .{};
const file_size = blk: {
@@ -29,7 +32,10 @@ pub fn ParallelHasher(comptime Hasher: type) type {
for (out, results, 0..) |*out_buf, *result, i| {
const fstart = i * chunk_size;
- const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size;
+ const fsize = if (fstart + chunk_size > file_size)
+ file_size - fstart
+ else
+ chunk_size;
wg.start();
try self.thread_pool.spawn(worker, .{
file,
@@ -61,10 +67,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
};
}
-const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const mem = std.mem;
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const ThreadPool = std.Thread.Pool;
diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig
index e155a7a8ed..7d045779fe 100644
--- a/src/link/MachO/load_commands.zig
+++ b/src/link/MachO/load_commands.zig
@@ -1,4 +1,13 @@
-/// Default path to dyld.
+const std = @import("std");
+const assert = std.debug.assert;
+const log = std.log.scoped(.link);
+const macho = std.macho;
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Dylib = @import("Dylib.zig");
+const MachO = @import("../MachO.zig");
+
pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
@@ -7,31 +16,19 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool
return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));
}
-const CalcLCsSizeCtx = struct {
- segments: []const macho.segment_command_64,
- dylibs: []const Dylib,
- referenced_dylibs: []u16,
- wants_function_starts: bool = true,
-};
-
-fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
- const comp = m.base.comp;
- const gpa = comp.gpa;
- var has_text_segment: bool = false;
+pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 {
var sizeofcmds: u64 = 0;
- for (ctx.segments) |seg| {
- sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);
- if (mem.eql(u8, seg.segName(), "__TEXT")) {
- has_text_segment = true;
- }
+
+ // LC_SEGMENT_64
+ sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len;
+ for (macho_file.segments.items) |seg| {
+ sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
}
// LC_DYLD_INFO_ONLY
sizeofcmds += @sizeOf(macho.dyld_info_command);
// LC_FUNCTION_STARTS
- if (has_text_segment and ctx.wants_function_starts) {
- sizeofcmds += @sizeOf(macho.linkedit_data_command);
- }
+ sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_DATA_IN_CODE
sizeofcmds += @sizeOf(macho.linkedit_data_command);
// LC_SYMTAB
@@ -45,15 +42,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
false,
);
// LC_MAIN
- if (comp.config.output_mode == .Exe) {
+ if (!macho_file.base.isDynLib()) {
sizeofcmds += @sizeOf(macho.entry_point_command);
}
// LC_ID_DYLIB
- if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) {
+ if (macho_file.base.isDynLib()) {
sizeofcmds += blk: {
- const emit = m.base.emit;
- const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});
- defer if (m.install_name == null) gpa.free(install_name);
+ const emit = macho_file.base.emit;
+ const install_name = macho_file.install_name orelse emit.sub_path;
break :blk calcInstallNameLen(
@sizeOf(macho.dylib_command),
install_name,
@@ -63,9 +59,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
}
// LC_RPATH
{
- var it = RpathIterator.init(gpa, m.base.rpath_list);
- defer it.deinit();
- while (try it.next()) |rpath| {
+ for (macho_file.base.rpath_list) |rpath| {
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.rpath_command),
rpath,
@@ -75,24 +69,20 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
}
// LC_SOURCE_VERSION
sizeofcmds += @sizeOf(macho.source_version_command);
- // LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing
- {
- const target = comp.root_mod.resolved_target.result;
- const platform = Platform.fromTarget(target);
- if (platform.isBuildVersionCompatible()) {
- // LC_BUILD_VERSION
- sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
- } else if (platform.isVersionMinCompatible()) {
- // LC_VERSION_MIN_
- sizeofcmds += @sizeOf(macho.version_min_command);
- }
+ if (macho_file.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);
}
// LC_UUID
sizeofcmds += @sizeOf(macho.uuid_command);
// LC_LOAD_DYLIB
- for (ctx.referenced_dylibs) |id| {
- const dylib = ctx.dylibs[id];
- const dylib_id = dylib.id orelse unreachable;
+ for (macho_file.dylibs.items) |index| {
+ const dylib = macho_file.getFile(index).?.dylib;
+ assert(dylib.isAlive(macho_file));
+ const dylib_id = dylib.id.?;
sizeofcmds += calcInstallNameLen(
@sizeOf(macho.dylib_command),
dylib_id.name,
@@ -100,19 +90,48 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
);
}
// LC_CODE_SIGNATURE
- if (m.requiresCodeSignature()) {
+ if (macho_file.requiresCodeSig()) {
sizeofcmds += @sizeOf(macho.linkedit_data_command);
}
- return @intCast(sizeofcmds);
+ return @as(u32, @intCast(sizeofcmds));
}
-pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
- var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size;
+pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
+ 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 (macho_file.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 {
+ var padding: u32 = calcLoadCommandsSize(macho_file, false) + (macho_file.headerpad_size orelse 0);
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
- if (m.headerpad_max_install_names) {
- const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true);
+ if (macho_file.headerpad_max_install_names) {
+ const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true);
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
min_headerpad_size + @sizeOf(macho.mach_header_64),
});
@@ -125,34 +144,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
return offset;
}
-pub fn calcNumOfLCs(lc_buffer: []const u8) u32 {
- var ncmds: u32 = 0;
- var pos: usize = 0;
- while (true) {
- if (pos >= lc_buffer.len) break;
- const cmd = @as(*align(1) const macho.load_command, @ptrCast(lc_buffer.ptr + pos)).*;
- ncmds += 1;
- pos += cmd.cmdsize;
- }
- return ncmds;
-}
-
-pub fn writeDylinkerLC(lc_writer: anytype) !void {
+pub fn writeDylinkerLC(writer: anytype) !void {
const name_len = mem.sliceTo(default_dyld_path, 0).len;
const cmdsize = @as(u32, @intCast(mem.alignForward(
u64,
@sizeOf(macho.dylinker_command) + name_len,
@sizeOf(u64),
)));
- try lc_writer.writeStruct(macho.dylinker_command{
+ try writer.writeStruct(macho.dylinker_command{
.cmd = .LOAD_DYLINKER,
.cmdsize = cmdsize,
.name = @sizeOf(macho.dylinker_command),
});
- try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0));
+ try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
if (padding > 0) {
- try lc_writer.writeByteNTimes(0, padding);
+ try writer.writeByteNTimes(0, padding);
}
}
@@ -164,14 +171,14 @@ const WriteDylibLCCtx = struct {
compatibility_version: u32 = 0x10000,
};
-fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
+pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void {
const name_len = ctx.name.len + 1;
const cmdsize = @as(u32, @intCast(mem.alignForward(
u64,
@sizeOf(macho.dylib_command) + name_len,
@sizeOf(u64),
)));
- try lc_writer.writeStruct(macho.dylib_command{
+ try writer.writeStruct(macho.dylib_command{
.cmd = ctx.cmd,
.cmdsize = cmdsize,
.dylib = .{
@@ -181,15 +188,15 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
.compatibility_version = ctx.compatibility_version,
},
});
- try lc_writer.writeAll(ctx.name);
- try lc_writer.writeByte(0);
+ try writer.writeAll(ctx.name);
+ try writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
if (padding > 0) {
- try lc_writer.writeByteNTimes(0, padding);
+ try writer.writeByteNTimes(0, padding);
}
}
-pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void {
+pub fn writeDylibIdLC(macho_file: *MachO, writer: anytype) !void {
const comp = macho_file.base.comp;
const gpa = comp.gpa;
assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic);
@@ -212,62 +219,31 @@ pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void {
.name = install_name,
.current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)),
.compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)),
- }, lc_writer);
+ }, writer);
}
-const RpathIterator = struct {
- buffer: []const []const u8,
- table: std.StringHashMap(void),
- count: usize = 0,
-
- fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
- return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
- }
-
- fn deinit(it: *RpathIterator) void {
- it.table.deinit();
- }
-
- fn next(it: *RpathIterator) !?[]const u8 {
- while (true) {
- if (it.count >= it.buffer.len) return null;
- const rpath = it.buffer[it.count];
- it.count += 1;
- const gop = try it.table.getOrPut(rpath);
- if (gop.found_existing) continue;
- return rpath;
- }
- }
-};
-
-pub fn writeRpathLCs(macho_file: *MachO, lc_writer: anytype) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- var it = RpathIterator.init(gpa, macho_file.base.rpath_list);
- defer it.deinit();
-
- while (try it.next()) |rpath| {
+pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void {
+ for (rpaths) |rpath| {
const rpath_len = rpath.len + 1;
const cmdsize = @as(u32, @intCast(mem.alignForward(
u64,
@sizeOf(macho.rpath_command) + rpath_len,
@sizeOf(u64),
)));
- try lc_writer.writeStruct(macho.rpath_command{
+ try writer.writeStruct(macho.rpath_command{
.cmdsize = cmdsize,
.path = @sizeOf(macho.rpath_command),
});
- try lc_writer.writeAll(rpath);
- try lc_writer.writeByte(0);
+ try writer.writeAll(rpath);
+ try writer.writeByte(0);
const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
if (padding > 0) {
- try lc_writer.writeByteNTimes(0, padding);
+ try writer.writeByteNTimes(0, padding);
}
}
}
-pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
+pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void {
const cmd: macho.LC = switch (platform.os_tag) {
.macos => .VERSION_MIN_MACOSX,
.ios => .VERSION_MIN_IPHONEOS,
@@ -275,298 +251,30 @@ pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion,
.watchos => .VERSION_MIN_WATCHOS,
else => unreachable,
};
- try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
+ try writer.writeAll(mem.asBytes(&macho.version_min_command{
.cmd = cmd,
.version = platform.toAppleVersion(),
- .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
+ .sdk = if (sdk_version) |ver|
+ MachO.semanticVersionToAppleVersion(ver)
+ else
+ platform.toAppleVersion(),
}));
}
-pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
+pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void {
const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
- try lc_writer.writeStruct(macho.build_version_command{
+ try writer.writeStruct(macho.build_version_command{
.cmdsize = cmdsize,
.platform = platform.toApplePlatform(),
.minos = platform.toAppleVersion(),
- .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
+ .sdk = if (sdk_version) |ver|
+ MachO.semanticVersionToAppleVersion(ver)
+ else
+ platform.toAppleVersion(),
.ntools = 1,
});
- try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
+ try writer.writeAll(mem.asBytes(&macho.build_tool_version{
.tool = .ZIG,
.version = 0x0,
}));
}
-
-pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void {
- for (referenced) |index| {
- const dylib = dylibs[index];
- const dylib_id = dylib.id orelse unreachable;
- try writeDylibLC(.{
- .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
- .name = dylib_id.name,
- .timestamp = dylib_id.timestamp,
- .current_version = dylib_id.current_version,
- .compatibility_version = dylib_id.compatibility_version,
- }, lc_writer);
- }
-}
-
-pub const Platform = struct {
- os_tag: std.Target.Os.Tag,
- abi: std.Target.Abi,
- version: std.SemanticVersion,
-
- /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
- /// the extracted minimum platform version.
- pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
- switch (lc.cmd()) {
- .BUILD_VERSION => {
- const cmd = lc.cast(macho.build_version_command).?;
- return .{
- .os_tag = switch (cmd.platform) {
- .MACOS => .macos,
- .IOS, .IOSSIMULATOR => .ios,
- .TVOS, .TVOSSIMULATOR => .tvos,
- .WATCHOS, .WATCHOSSIMULATOR => .watchos,
- else => @panic("TODO"),
- },
- .abi = switch (cmd.platform) {
- .IOSSIMULATOR,
- .TVOSSIMULATOR,
- .WATCHOSSIMULATOR,
- => .simulator,
- else => .none,
- },
- .version = appleVersionToSemanticVersion(cmd.minos),
- };
- },
- .VERSION_MIN_MACOSX,
- .VERSION_MIN_IPHONEOS,
- .VERSION_MIN_TVOS,
- .VERSION_MIN_WATCHOS,
- => {
- const cmd = lc.cast(macho.version_min_command).?;
- return .{
- .os_tag = switch (lc.cmd()) {
- .VERSION_MIN_MACOSX => .macos,
- .VERSION_MIN_IPHONEOS => .ios,
- .VERSION_MIN_TVOS => .tvos,
- .VERSION_MIN_WATCHOS => .watchos,
- else => unreachable,
- },
- .abi = .none,
- .version = appleVersionToSemanticVersion(cmd.version),
- };
- },
- else => unreachable,
- }
- }
-
- pub fn fromTarget(target: std.Target) Platform {
- return .{
- .os_tag = target.os.tag,
- .abi = target.abi,
- .version = target.os.version_range.semver.min,
- };
- }
-
- pub fn toAppleVersion(plat: Platform) u32 {
- return semanticVersionToAppleVersion(plat.version);
- }
-
- pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
- return switch (plat.os_tag) {
- .macos => .MACOS,
- .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
- .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
- .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
- else => unreachable,
- };
- }
-
- pub fn isBuildVersionCompatible(plat: Platform) bool {
- inline for (supported_platforms) |sup_plat| {
- if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
- return sup_plat[2] <= plat.toAppleVersion();
- }
- }
- return false;
- }
-
- pub fn isVersionMinCompatible(plat: Platform) bool {
- inline for (supported_platforms) |sup_plat| {
- if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
- return sup_plat[3] <= plat.toAppleVersion();
- }
- }
- return false;
- }
-
- pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
- return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
- }
-
- const FmtCtx = struct {
- platform: Platform,
- cpu_arch: std.Target.Cpu.Arch,
- };
-
- pub fn formatTarget(
- ctx: FmtCtx,
- comptime unused_fmt_string: []const u8,
- options: std.fmt.FormatOptions,
- writer: anytype,
- ) !void {
- _ = unused_fmt_string;
- _ = options;
- try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
- if (ctx.platform.abi != .none) {
- try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
- }
- }
-
- /// Caller owns the memory.
- pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
- var buffer = std.ArrayList(u8).init(gpa);
- defer buffer.deinit();
- try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
- return buffer.toOwnedSlice();
- }
-
- pub fn eqlTarget(plat: Platform, other: Platform) bool {
- return plat.os_tag == other.os_tag and plat.abi == other.abi;
- }
-};
-
-const SupportedPlatforms = struct {
- std.Target.Os.Tag,
- std.Target.Abi,
- u32, // Min platform version for which to emit LC_BUILD_VERSION
- u32, // Min supported platform version
-};
-
-// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
-// zig fmt: off
-const supported_platforms = [_]SupportedPlatforms{
- .{ .macos, .none, 0xA0E00, 0xA0800 },
- .{ .ios, .none, 0xC0000, 0x70000 },
- .{ .tvos, .none, 0xC0000, 0x70000 },
- .{ .watchos, .none, 0x50000, 0x20000 },
- .{ .ios, .simulator, 0xD0000, 0x80000 },
- .{ .tvos, .simulator, 0xD0000, 0x80000 },
- .{ .watchos, .simulator, 0x60000, 0x20000 },
-};
-// zig fmt: on
-
-inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
- const major = version.major;
- const minor = version.minor;
- const patch = version.patch;
- return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
-}
-
-pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
- return .{
- .major = @as(u16, @truncate(version >> 16)),
- .minor = @as(u8, @truncate(version >> 8)),
- .patch = @as(u8, @truncate(version)),
- };
-}
-
-pub fn inferSdkVersion(macho_file: *MachO) ?std.SemanticVersion {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- var arena_allocator = std.heap.ArenaAllocator.init(gpa);
- defer arena_allocator.deinit();
- const arena = arena_allocator.allocator();
-
- const sdk_layout = macho_file.sdk_layout orelse return null;
- const sdk_dir = switch (sdk_layout) {
- .sdk => comp.sysroot.?,
- .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
- };
- if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
- return parseSdkVersion(ver);
- } else |_| {
- // Read from settings should always succeed when vendored.
- if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
- }
-
- // infer from pathname
- const stem = std.fs.path.stem(sdk_dir);
- const start = for (stem, 0..) |c, i| {
- if (std.ascii.isDigit(c)) break i;
- } else stem.len;
- const end = for (stem[start..], start..) |c, i| {
- if (std.ascii.isDigit(c) or c == '.') continue;
- break i;
- } else stem.len;
- return parseSdkVersion(stem[start..end]);
-}
-
-// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
-// Use property `MinimalDisplayName` to determine version.
-// The file/property is also available with vendored libc.
-fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
- const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
- const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
- const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
- if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
- return error.SdkVersionFailure;
-}
-
-// Versions reported by Apple aren't exactly semantically valid as they usually omit
-// the patch component, so we parse SDK value by hand.
-fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
- var parsed: std.SemanticVersion = .{
- .major = 0,
- .minor = 0,
- .patch = 0,
- };
-
- const parseNext = struct {
- fn parseNext(it: anytype) ?u16 {
- const nn = it.next() orelse return null;
- return std.fmt.parseInt(u16, nn, 10) catch null;
- }
- }.parseNext;
-
- var it = std.mem.splitAny(u8, raw, ".");
- parsed.major = parseNext(&it) orelse return null;
- parsed.minor = parseNext(&it) orelse return null;
- parsed.patch = parseNext(&it) orelse 0;
- return parsed;
-}
-
-const expect = std.testing.expect;
-const expectEqual = std.testing.expectEqual;
-
-fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void {
- const maybe_ver = parseSdkVersion(raw);
- try expect(maybe_ver != null);
- const ver = maybe_ver.?;
- try expectEqual(exp.major, ver.major);
- try expectEqual(exp.minor, ver.minor);
- try expectEqual(exp.patch, ver.patch);
-}
-
-test "parseSdkVersion" {
- try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
- try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
- try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
-
- try expect(parseSdkVersion("11") == null);
-}
-
-const std = @import("std");
-const assert = std.debug.assert;
-const link = @import("../../link.zig");
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const mem = std.mem;
-
-const Allocator = mem.Allocator;
-const Dylib = @import("Dylib.zig");
-const MachO = @import("../MachO.zig");
-const Compilation = @import("../../Compilation.zig");
diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig
new file mode 100644
index 0000000000..1bcbe1f3ab
--- /dev/null
+++ b/src/link/MachO/relocatable.zig
@@ -0,0 +1,506 @@
+pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
+ const gpa = macho_file.base.comp.gpa;
+
+ var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
+ defer positionals.deinit();
+ try positionals.ensureUnusedCapacity(comp.objects.len);
+ positionals.appendSliceAssumeCapacity(comp.objects);
+
+ for (comp.c_object_table.keys()) |key| {
+ try positionals.append(.{ .path = key.status.success.object_path });
+ }
+
+ if (module_obj_path) |path| try positionals.append(.{ .path = path });
+
+ if (positionals.items.len == 1) {
+ // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
+ // debug info segments/sections (this is apparently by design by Apple), we copy
+ // the *only* input file over.
+ // TODO: in the future, when we implement `dsymutil` alternative directly in the Zig
+ // compiler, investigate if we can get rid of this `if` prong here.
+ const path = positionals.items[0].path;
+ const in_file = try std.fs.cwd().openFile(path, .{});
+ const stat = try in_file.stat();
+ const amt = try in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size);
+ if (amt != stat.size) return error.InputOutput; // TODO: report an actual user error
+ return;
+ }
+
+ for (positionals.items) |obj| {
+ macho_file.parsePositional(obj.path, obj.must_link) catch |err| switch (err) {
+ error.MalformedObject,
+ error.MalformedArchive,
+ error.InvalidCpuArch,
+ error.InvalidTarget,
+ => continue, // already reported
+ error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}),
+ else => |e| try macho_file.reportParseError(
+ obj.path,
+ "unexpected error: parsing input file failed with error {s}",
+ .{@errorName(e)},
+ ),
+ };
+ }
+
+ if (comp.link_errors.items.len > 0) return error.FlushFailure;
+
+ try macho_file.addUndefinedGlobals();
+ try macho_file.resolveSymbols();
+ 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(gpa, .{
+ .cmdsize = @sizeOf(macho.segment_command_64),
+ .segname = MachO.makeStaticString(""),
+ .maxprot = prot,
+ .initprot = prot,
+ });
+ const seg = &macho_file.segments.items[0];
+ seg.nsects = @intCast(macho_file.sections.items(.header).len);
+ seg.cmdsize += seg.nsects * @sizeOf(macho.section_64);
+ }
+
+ 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 macho_file.writeDataInCode(0, 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.hasUnwindRecords()) 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.hasEhFrameRecords()) 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 tracy = trace(@src());
+ defer tracy.end();
+
+ 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 = atom.alignment.toByteUnits(1);
+ 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.toLog2Units());
+ 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;
+}
+
+// We need to sort relocations in descending order to be compatible with Apple's linker.
+fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
+ _ = ctx;
+ return lhs.r_address > rhs.r_address;
+}
+
+fn writeAtoms(macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ const cpu_arch = macho_file.getTarget().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 size = math.cast(usize, header.size) orelse return error.Overflow;
+ const code = try gpa.alloc(u8, 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 = math.cast(usize, atom.value - header.addr) orelse return error.Overflow;
+ const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
+ const atom_data = try atom.getFile(macho_file).object.getAtomData(atom.*);
+ @memcpy(code[off..][0..atom_size], atom_data);
+ try atom.writeRelocs(macho_file, code[off..][0..atom_size], &relocs);
+ }
+
+ assert(relocs.items.len == header.nreloc);
+
+ mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
+
+ // 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.comp.gpa;
+ const header = macho_file.sections.items(.header)[sect_index];
+
+ const nrecs = math.cast(usize, @divExact(header.size, @sizeOf(macho.compact_unwind_entry))) orelse return error.Overflow;
+ 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.getTarget().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.getTarget().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.getTarget().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);
+
+ mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
+
+ // 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.comp.gpa;
+ const header = macho_file.sections.items(.header)[sect_index];
+ const size = math.cast(usize, header.size) orelse return error.Overflow;
+
+ const code = try gpa.alloc(u8, 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);
+
+ mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
+
+ // 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 writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {
+ const gpa = macho_file.base.comp.gpa;
+ 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.platform.isBuildVersionCompatible()) {
+ try load_commands.writeBuildVersionLC(macho_file.platform, macho_file.sdk_version, writer);
+ ncmds += 1;
+ } else {
+ try load_commands.writeVersionMinLC(macho_file.platform, macho_file.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.getTarget().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 => {},
+ }
+
+ 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 link = @import("../../link.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(.link_state);
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
+
+const Atom = @import("Atom.zig");
+const Compilation = @import("../../Compilation.zig");
+const MachO = @import("../MachO.zig");
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/stubs.zig b/src/link/MachO/stubs.zig
deleted file mode 100644
index 925aeaa61f..0000000000
--- a/src/link/MachO/stubs.zig
+++ /dev/null
@@ -1,169 +0,0 @@
-pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
- return switch (cpu_arch) {
- .x86_64 => 15,
- .aarch64 => 6 * @sizeOf(u32),
- else => unreachable, // unhandled architecture type
- };
-}
-
-pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 {
- return switch (cpu_arch) {
- .x86_64 => 10,
- .aarch64 => 3 * @sizeOf(u32),
- else => unreachable, // unhandled architecture type
- };
-}
-
-pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 {
- return switch (cpu_arch) {
- .x86_64 => 6,
- .aarch64 => 3 * @sizeOf(u32),
- else => unreachable, // unhandled architecture type
- };
-}
-
-pub inline fn stubAlignment(cpu_arch: std.Target.Cpu.Arch) u8 {
- return switch (cpu_arch) {
- .x86_64 => 1,
- .aarch64 => 4,
- else => unreachable, // unhandled architecture type
- };
-}
-
-pub inline fn stubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u8 {
- return switch (cpu_arch) {
- .x86_64 => 1,
- .aarch64 => 2 * @sizeOf(u32),
- else => unreachable,
- };
-}
-
-pub fn writeStubHelperPreambleCode(args: struct {
- cpu_arch: std.Target.Cpu.Arch,
- source_addr: u64,
- dyld_private_addr: u64,
- dyld_stub_binder_got_addr: u64,
-}, writer: anytype) !void {
- switch (args.cpu_arch) {
- .x86_64 => {
- try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
- {
- const disp = try Relocation.calcPcRelativeDisplacementX86(
- args.source_addr + 3,
- args.dyld_private_addr,
- 0,
- );
- try writer.writeInt(i32, disp, .little);
- }
- try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
- {
- const disp = try Relocation.calcPcRelativeDisplacementX86(
- args.source_addr + 11,
- args.dyld_stub_binder_got_addr,
- 0,
- );
- try writer.writeInt(i32, disp, .little);
- }
- },
- .aarch64 => {
- {
- const pages = Relocation.calcNumberOfPages(args.source_addr, args.dyld_private_addr);
- try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
- }
- {
- const off = try Relocation.calcPageOffset(args.dyld_private_addr, .arithmetic);
- try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
- }
- try writer.writeInt(u32, aarch64.Instruction.stp(
- .x16,
- .x17,
- aarch64.Register.sp,
- aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
- ).toU32(), .little);
- {
- const pages = Relocation.calcNumberOfPages(args.source_addr + 12, args.dyld_stub_binder_got_addr);
- try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
- }
- {
- const off = try Relocation.calcPageOffset(args.dyld_stub_binder_got_addr, .load_store_64);
- try writer.writeInt(u32, aarch64.Instruction.ldr(
- .x16,
- .x16,
- aarch64.Instruction.LoadStoreOffset.imm(off),
- ).toU32(), .little);
- }
- try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
- },
- else => unreachable,
- }
-}
-
-pub fn writeStubHelperCode(args: struct {
- cpu_arch: std.Target.Cpu.Arch,
- source_addr: u64,
- target_addr: u64,
-}, writer: anytype) !void {
- switch (args.cpu_arch) {
- .x86_64 => {
- try writer.writeAll(&.{ 0x68, 0x0, 0x0, 0x0, 0x0, 0xe9 });
- {
- const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 6, args.target_addr, 0);
- try writer.writeInt(i32, disp, .little);
- }
- },
- .aarch64 => {
- const stub_size: u4 = 3 * @sizeOf(u32);
- const literal = blk: {
- const div_res = try std.math.divExact(u64, stub_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 = try Relocation.calcPcRelativeDisplacementArm64(args.source_addr + 4, args.target_addr);
- try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
- }
- try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
- },
- else => unreachable,
- }
-}
-
-pub fn writeStubCode(args: struct {
- cpu_arch: std.Target.Cpu.Arch,
- source_addr: u64,
- target_addr: u64,
-}, writer: anytype) !void {
- switch (args.cpu_arch) {
- .x86_64 => {
- try writer.writeAll(&.{ 0xff, 0x25 });
- {
- const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 2, args.target_addr, 0);
- try writer.writeInt(i32, disp, .little);
- }
- },
- .aarch64 => {
- {
- const pages = Relocation.calcNumberOfPages(args.source_addr, args.target_addr);
- try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
- }
- {
- const off = try Relocation.calcPageOffset(args.target_addr, .load_store_64);
- try writer.writeInt(u32, aarch64.Instruction.ldr(
- .x16,
- .x16,
- aarch64.Instruction.LoadStoreOffset.imm(off),
- ).toU32(), .little);
- }
- try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
- },
- else => unreachable,
- }
-}
-
-const std = @import("std");
-const aarch64 = @import("../../arch/aarch64/bits.zig");
-
-const Relocation = @import("Relocation.zig");
diff --git a/src/link/MachO/synthetic.zig b/src/link/MachO/synthetic.zig
new file mode 100644
index 0000000000..774fbe0d27
--- /dev/null
+++ b/src/link/MachO/synthetic.zig
@@ -0,0 +1,793 @@
+pub const ZigGotSection = struct {
+ entries: std.ArrayListUnmanaged(Symbol.Index) = .{},
+ dirty: bool = false,
+
+ pub const Index = u32;
+
+ pub fn deinit(zig_got: *ZigGotSection, allocator: Allocator) void {
+ zig_got.entries.deinit(allocator);
+ }
+
+ fn allocateEntry(zig_got: *ZigGotSection, allocator: Allocator) !Index {
+ try zig_got.entries.ensureUnusedCapacity(allocator, 1);
+ // TODO add free list
+ const index = @as(Index, @intCast(zig_got.entries.items.len));
+ _ = zig_got.entries.addOneAssumeCapacity();
+ zig_got.dirty = true;
+ return index;
+ }
+
+ pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index {
+ const comp = macho_file.base.comp;
+ const gpa = comp.gpa;
+ const index = try zig_got.allocateEntry(gpa);
+ const entry = &zig_got.entries.items[index];
+ entry.* = sym_index;
+ const symbol = macho_file.getSymbol(sym_index);
+ assert(symbol.flags.needs_zig_got);
+ symbol.flags.has_zig_got = true;
+ try symbol.addExtra(.{ .zig_got = index }, macho_file);
+ return index;
+ }
+
+ pub fn entryOffset(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
+ _ = zig_got;
+ const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?];
+ return sect.offset + @sizeOf(u64) * index;
+ }
+
+ pub fn entryAddress(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
+ _ = zig_got;
+ const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?];
+ return sect.addr + @sizeOf(u64) * index;
+ }
+
+ pub fn size(zig_got: ZigGotSection, macho_file: *MachO) usize {
+ _ = macho_file;
+ return @sizeOf(u64) * zig_got.entries.items.len;
+ }
+
+ pub fn writeOne(zig_got: *ZigGotSection, macho_file: *MachO, index: Index) !void {
+ if (zig_got.dirty) {
+ const needed_size = zig_got.size(macho_file);
+ try macho_file.growSection(macho_file.zig_got_sect_index.?, needed_size);
+ zig_got.dirty = false;
+ }
+ const off = zig_got.entryOffset(index, macho_file);
+ const entry = zig_got.entries.items[index];
+ const value = macho_file.getSymbol(entry).getAddress(.{ .stubs = false }, macho_file);
+
+ var buf: [8]u8 = undefined;
+ std.mem.writeInt(u64, &buf, value, .little);
+ try macho_file.base.file.?.pwriteAll(&buf, off);
+ }
+
+ pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void {
+ for (zig_got.entries.items) |entry| {
+ const symbol = macho_file.getSymbol(entry);
+ const value = symbol.address(.{ .stubs = false }, macho_file);
+ try writer.writeInt(u64, value, .little);
+ }
+ }
+
+ pub fn addDyldRelocs(zig_got: ZigGotSection, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const seg_id = macho_file.sections.items(.segment_id)[macho_file.zig_got_sect_index.?];
+ const seg = macho_file.segments.items[seg_id];
+
+ for (0..zig_got.entries.items.len) |idx| {
+ const addr = zig_got.entryAddress(@intCast(idx), macho_file);
+ try macho_file.rebase.entries.append(gpa, .{
+ .offset = addr - seg.vmaddr,
+ .segment_id = seg_id,
+ });
+ }
+ }
+
+ const FormatCtx = struct {
+ zig_got: ZigGotSection,
+ macho_file: *MachO,
+ };
+
+ pub fn fmt(zig_got: ZigGotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{ .zig_got = zig_got, .macho_file = macho_file } };
+ }
+
+ pub fn format2(
+ ctx: FormatCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ try writer.writeAll("__zig_got\n");
+ for (ctx.zig_got.entries.items, 0..) |entry, index| {
+ const symbol = ctx.macho_file.getSymbol(entry);
+ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+ index,
+ ctx.zig_got.entryAddress(@intCast(index), ctx.macho_file),
+ entry,
+ symbol.getAddress(.{}, ctx.macho_file),
+ symbol.getName(ctx.macho_file),
+ });
+ }
+ }
+};
+
+pub const GotSection = struct {
+ symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+ pub const Index = u32;
+
+ pub fn deinit(got: *GotSection, allocator: Allocator) void {
+ got.symbols.deinit(allocator);
+ }
+
+ pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const index = @as(Index, @intCast(got.symbols.items.len));
+ const entry = try got.symbols.addOne(gpa);
+ entry.* = sym_index;
+ const symbol = macho_file.getSymbol(sym_index);
+ symbol.flags.has_got = true;
+ try symbol.addExtra(.{ .got = index }, macho_file);
+ }
+
+ pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 {
+ assert(index < got.symbols.items.len);
+ const header = macho_file.sections.items(.header)[macho_file.got_sect_index.?];
+ return header.addr + index * @sizeOf(u64);
+ }
+
+ pub fn size(got: GotSection) usize {
+ return got.symbols.items.len * @sizeOf(u64);
+ }
+
+ pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
+ const seg = macho_file.segments.items[seg_id];
+
+ for (got.symbols.items, 0..) |sym_index, idx| {
+ const sym = macho_file.getSymbol(sym_index);
+ const addr = got.getAddress(@intCast(idx), macho_file);
+ const entry = bind.Entry{
+ .target = sym_index,
+ .offset = addr - seg.vmaddr,
+ .segment_id = seg_id,
+ .addend = 0,
+ };
+ if (sym.flags.import) {
+ try macho_file.bind.entries.append(gpa, entry);
+ if (sym.flags.weak) {
+ try macho_file.weak_bind.entries.append(gpa, entry);
+ }
+ } else {
+ try macho_file.rebase.entries.append(gpa, .{
+ .offset = addr - seg.vmaddr,
+ .segment_id = seg_id,
+ });
+ if (sym.flags.weak) {
+ try macho_file.weak_bind.entries.append(gpa, entry);
+ } else if (sym.flags.interposable) {
+ try macho_file.bind.entries.append(gpa, entry);
+ }
+ }
+ }
+ }
+
+ pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ for (got.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file);
+ try writer.writeInt(u64, value, .little);
+ }
+ }
+
+ const FormatCtx = struct {
+ got: GotSection,
+ macho_file: *MachO,
+ };
+
+ pub fn fmt(got: GotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{ .got = got, .macho_file = macho_file } };
+ }
+
+ pub fn format2(
+ ctx: FormatCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ for (ctx.got.symbols.items, 0..) |entry, i| {
+ const symbol = ctx.macho_file.getSymbol(entry);
+ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+ i,
+ symbol.getGotAddress(ctx.macho_file),
+ entry,
+ symbol.getAddress(.{}, ctx.macho_file),
+ symbol.getName(ctx.macho_file),
+ });
+ }
+ }
+};
+
+pub const StubsSection = struct {
+ symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+ pub const Index = u32;
+
+ pub fn deinit(stubs: *StubsSection, allocator: Allocator) void {
+ stubs.symbols.deinit(allocator);
+ }
+
+ pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const index = @as(Index, @intCast(stubs.symbols.items.len));
+ const entry = try stubs.symbols.addOne(gpa);
+ entry.* = sym_index;
+ const symbol = macho_file.getSymbol(sym_index);
+ try symbol.addExtra(.{ .stubs = index }, macho_file);
+ }
+
+ pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 {
+ assert(index < stubs.symbols.items.len);
+ const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
+ return header.addr + index * header.reserved2;
+ }
+
+ pub fn size(stubs: StubsSection, macho_file: *MachO) usize {
+ const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
+ return stubs.symbols.items.len * header.reserved2;
+ }
+
+ pub fn write(stubs: StubsSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
+
+ for (stubs.symbols.items, 0..) |sym_index, idx| {
+ const sym = macho_file.getSymbol(sym_index);
+ const source = sym.getAddress(.{ .stubs = true }, macho_file);
+ const target = laptr_sect.addr + idx * @sizeOf(u64);
+ switch (cpu_arch) {
+ .x86_64 => {
+ try writer.writeAll(&.{ 0xff, 0x25 });
+ try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
+ },
+ .aarch64 => {
+ // TODO relax if possible
+ const pages = try Relocation.calcNumberOfPages(source, target);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(target, .load_store_64);
+ try writer.writeInt(
+ u32,
+ aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
+ .little,
+ );
+ try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
+ },
+ else => unreachable,
+ }
+ }
+ }
+
+ const FormatCtx = struct {
+ stubs: StubsSection,
+ macho_file: *MachO,
+ };
+
+ pub fn fmt(stubs: StubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{ .stubs = stubs, .macho_file = macho_file } };
+ }
+
+ pub fn format2(
+ ctx: FormatCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ for (ctx.stubs.symbols.items, 0..) |entry, i| {
+ const symbol = ctx.macho_file.getSymbol(entry);
+ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+ i,
+ symbol.getStubsAddress(ctx.macho_file),
+ entry,
+ symbol.getAddress(.{}, ctx.macho_file),
+ symbol.getName(ctx.macho_file),
+ });
+ }
+ }
+};
+
+pub const StubsHelperSection = struct {
+ pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize {
+ return switch (cpu_arch) {
+ .x86_64 => 16,
+ .aarch64 => 6 * @sizeOf(u32),
+ else => 0,
+ };
+ }
+
+ pub inline fn entrySize(cpu_arch: std.Target.Cpu.Arch) usize {
+ return switch (cpu_arch) {
+ .x86_64 => 10,
+ .aarch64 => 3 * @sizeOf(u32),
+ else => 0,
+ };
+ }
+
+ pub fn size(stubs_helper: StubsHelperSection, macho_file: *MachO) usize {
+ const tracy = trace(@src());
+ defer tracy.end();
+ _ = stubs_helper;
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ var s: usize = preambleSize(cpu_arch);
+ for (macho_file.stubs.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.flags.weak) continue;
+ s += entrySize(cpu_arch);
+ }
+ return s;
+ }
+
+ pub fn write(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ try stubs_helper.writePreamble(macho_file, writer);
+
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
+ const preamble_size = preambleSize(cpu_arch);
+ const entry_size = entrySize(cpu_arch);
+
+ var idx: usize = 0;
+ for (macho_file.stubs.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ 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;
+ }
+ }
+
+ fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
+ _ = stubs_helper;
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
+ const dyld_private_addr = target: {
+ const sym = macho_file.getSymbol(macho_file.dyld_private_index.?);
+ break :target sym.getAddress(.{}, macho_file);
+ };
+ const dyld_stub_binder_addr = target: {
+ const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?);
+ break :target sym.getGotAddress(macho_file);
+ };
+ switch (cpu_arch) {
+ .x86_64 => {
+ try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
+ 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 => {
+ {
+ // TODO relax if possible
+ const pages = try Relocation.calcNumberOfPages(sect.addr, dyld_private_addr);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(dyld_private_addr, .arithmetic);
+ try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
+ }
+ try writer.writeInt(u32, aarch64.Instruction.stp(
+ .x16,
+ .x17,
+ aarch64.Register.sp,
+ aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
+ ).toU32(), .little);
+ {
+ // TODO relax if possible
+ const pages = try Relocation.calcNumberOfPages(sect.addr + 12, dyld_stub_binder_addr);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(dyld_stub_binder_addr, .load_store_64);
+ try writer.writeInt(u32, aarch64.Instruction.ldr(
+ .x16,
+ .x16,
+ aarch64.Instruction.LoadStoreOffset.imm(off),
+ ).toU32(), .little);
+ }
+ try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
+ },
+ else => unreachable,
+ }
+ }
+};
+
+pub const LaSymbolPtrSection = struct {
+ pub fn size(laptr: LaSymbolPtrSection, macho_file: *MachO) usize {
+ _ = laptr;
+ return macho_file.stubs.symbols.items.len * @sizeOf(u64);
+ }
+
+ pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ _ = laptr;
+ const gpa = macho_file.base.comp.gpa;
+
+ const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
+ const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
+ const seg = macho_file.segments.items[seg_id];
+
+ 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 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,
+ .addend = 0,
+ };
+ if (sym.flags.import) {
+ if (sym.flags.weak) {
+ 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, bind_entry);
+ try macho_file.rebase.entries.append(gpa, rebase_entry);
+ }
+ } else {
+ if (sym.flags.weak) {
+ try macho_file.rebase.entries.append(gpa, rebase_entry);
+ try macho_file.weak_bind.entries.append(gpa, bind_entry);
+ } else if (sym.flags.interposable) {
+ try macho_file.lazy_bind.entries.append(gpa, bind_entry);
+ try macho_file.rebase.entries.append(gpa, rebase_entry);
+ }
+ }
+ }
+ }
+
+ pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ _ = laptr;
+ const cpu_arch = macho_file.getTarget().cpu.arch;
+ const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
+ var stub_helper_idx: u32 = 0;
+ for (macho_file.stubs.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ 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;
+ try writer.writeInt(u64, @intCast(value), .little);
+ }
+ }
+ }
+};
+
+pub const TlvPtrSection = struct {
+ symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+ pub const Index = u32;
+
+ pub fn deinit(tlv: *TlvPtrSection, allocator: Allocator) void {
+ tlv.symbols.deinit(allocator);
+ }
+
+ pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const index = @as(Index, @intCast(tlv.symbols.items.len));
+ const entry = try tlv.symbols.addOne(gpa);
+ entry.* = sym_index;
+ const symbol = macho_file.getSymbol(sym_index);
+ try symbol.addExtra(.{ .tlv_ptr = index }, macho_file);
+ }
+
+ pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 {
+ assert(index < tlv.symbols.items.len);
+ const header = macho_file.sections.items(.header)[macho_file.tlv_ptr_sect_index.?];
+ return header.addr + index * @sizeOf(u64) * 3;
+ }
+
+ pub fn size(tlv: TlvPtrSection) usize {
+ return tlv.symbols.items.len * @sizeOf(u64);
+ }
+
+ pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ const gpa = macho_file.base.comp.gpa;
+ const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
+ const seg = macho_file.segments.items[seg_id];
+
+ for (tlv.symbols.items, 0..) |sym_index, idx| {
+ const sym = macho_file.getSymbol(sym_index);
+ const addr = tlv.getAddress(@intCast(idx), macho_file);
+ const entry = bind.Entry{
+ .target = sym_index,
+ .offset = addr - seg.vmaddr,
+ .segment_id = seg_id,
+ .addend = 0,
+ };
+ if (sym.flags.import) {
+ try macho_file.bind.entries.append(gpa, entry);
+ if (sym.flags.weak) {
+ try macho_file.weak_bind.entries.append(gpa, entry);
+ }
+ } else {
+ try macho_file.rebase.entries.append(gpa, .{
+ .offset = addr - seg.vmaddr,
+ .segment_id = seg_id,
+ });
+ if (sym.flags.weak) {
+ try macho_file.weak_bind.entries.append(gpa, entry);
+ } else if (sym.flags.interposable) {
+ try macho_file.bind.entries.append(gpa, entry);
+ }
+ }
+ }
+ }
+
+ pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (tlv.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ if (sym.flags.import) {
+ try writer.writeInt(u64, 0, .little);
+ } else {
+ try writer.writeInt(u64, sym.getAddress(.{}, macho_file), .little);
+ }
+ }
+ }
+
+ const FormatCtx = struct {
+ tlv: TlvPtrSection,
+ macho_file: *MachO,
+ };
+
+ pub fn fmt(tlv: TlvPtrSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{ .tlv = tlv, .macho_file = macho_file } };
+ }
+
+ pub fn format2(
+ ctx: FormatCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ for (ctx.tlv.symbols.items, 0..) |entry, i| {
+ const symbol = ctx.macho_file.getSymbol(entry);
+ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+ i,
+ symbol.getTlvPtrAddress(ctx.macho_file),
+ entry,
+ symbol.getAddress(.{}, ctx.macho_file),
+ symbol.getName(ctx.macho_file),
+ });
+ }
+ }
+};
+
+pub const ObjcStubsSection = struct {
+ symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
+
+ pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void {
+ objc.symbols.deinit(allocator);
+ }
+
+ pub fn entrySize(cpu_arch: std.Target.Cpu.Arch) u8 {
+ return switch (cpu_arch) {
+ .x86_64 => 13,
+ .aarch64 => 8 * @sizeOf(u32),
+ else => unreachable,
+ };
+ }
+
+ pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
+ const gpa = macho_file.base.comp.gpa;
+ const index = @as(Index, @intCast(objc.symbols.items.len));
+ const entry = try objc.symbols.addOne(gpa);
+ entry.* = sym_index;
+ const symbol = macho_file.getSymbol(sym_index);
+ try symbol.addExtra(.{ .objc_stubs = index }, macho_file);
+ }
+
+ pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 {
+ assert(index < objc.symbols.items.len);
+ const header = macho_file.sections.items(.header)[macho_file.objc_stubs_sect_index.?];
+ return header.addr + index * entrySize(macho_file.getTarget().cpu.arch);
+ }
+
+ pub fn size(objc: ObjcStubsSection, macho_file: *MachO) usize {
+ return objc.symbols.items.len * entrySize(macho_file.getTarget().cpu.arch);
+ }
+
+ pub fn write(objc: ObjcStubsSection, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ for (objc.symbols.items, 0..) |sym_index, idx| {
+ const sym = macho_file.getSymbol(sym_index);
+ const addr = objc.getAddress(@intCast(idx), macho_file);
+ switch (macho_file.getTarget().cpu.arch) {
+ .x86_64 => {
+ try writer.writeAll(&.{ 0x48, 0x8b, 0x35 });
+ {
+ const target = sym.getObjcSelrefsAddress(macho_file);
+ const source = addr;
+ try writer.writeInt(i32, @intCast(target - source - 3 - 4), .little);
+ }
+ try writer.writeAll(&.{ 0xff, 0x25 });
+ {
+ const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
+ const target = target_sym.getGotAddress(macho_file);
+ const source = addr + 7;
+ try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
+ }
+ },
+ .aarch64 => {
+ {
+ const target = sym.getObjcSelrefsAddress(macho_file);
+ const source = addr;
+ const pages = try Relocation.calcNumberOfPages(source, target);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x1, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(target, .load_store_64);
+ try writer.writeInt(
+ u32,
+ aarch64.Instruction.ldr(.x1, .x1, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
+ .little,
+ );
+ }
+ {
+ const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
+ const target = target_sym.getGotAddress(macho_file);
+ const source = addr + 2 * @sizeOf(u32);
+ const pages = try Relocation.calcNumberOfPages(source, target);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(target, .load_store_64);
+ try writer.writeInt(
+ u32,
+ aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
+ .little,
+ );
+ }
+ try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
+ try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
+ try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
+ try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
+ },
+ else => unreachable,
+ }
+ }
+ }
+
+ const FormatCtx = struct {
+ objc: ObjcStubsSection,
+ macho_file: *MachO,
+ };
+
+ pub fn fmt(objc: ObjcStubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{ .objc = objc, .macho_file = macho_file } };
+ }
+
+ pub fn format2(
+ ctx: FormatCtx,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ for (ctx.objc.symbols.items, 0..) |entry, i| {
+ const symbol = ctx.macho_file.getSymbol(entry);
+ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
+ i,
+ symbol.getObjcStubsAddress(ctx.macho_file),
+ entry,
+ symbol.getAddress(.{}, ctx.macho_file),
+ symbol.getName(ctx.macho_file),
+ });
+ }
+ }
+
+ pub const Index = u32;
+};
+
+pub const Indsymtab = struct {
+ pub inline fn nsyms(ind: Indsymtab, macho_file: *MachO) u32 {
+ _ = ind;
+ return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len);
+ }
+
+ pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ _ = ind;
+
+ for (macho_file.stubs.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
+ }
+
+ for (macho_file.got.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
+ }
+
+ for (macho_file.stubs.symbols.items) |sym_index| {
+ const sym = macho_file.getSymbol(sym_index);
+ try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
+ }
+ }
+};
+
+pub const RebaseSection = Rebase;
+pub const BindSection = bind.Bind;
+pub const WeakBindSection = bind.WeakBind;
+pub const LazyBindSection = bind.LazyBind;
+pub const ExportTrieSection = Trie;
+
+const aarch64 = @import("../../arch/aarch64/bits.zig");
+const assert = std.debug.assert;
+const bind = @import("dyld_info/bind.zig");
+const math = std.math;
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
+
+const Allocator = std.mem.Allocator;
+const MachO = @import("../MachO.zig");
+const Rebase = @import("dyld_info/Rebase.zig");
+const Relocation = @import("Relocation.zig");
+const Symbol = @import("Symbol.zig");
+const Trie = @import("dyld_info/Trie.zig");
diff --git a/src/link/MachO/thunks.zig b/src/link/MachO/thunks.zig
index f080de7f80..2e9602f8d8 100644
--- a/src/link/MachO/thunks.zig
+++ b/src/link/MachO/thunks.zig
@@ -1,374 +1,175 @@
-//! An algorithm for allocating output machine code section (aka `__TEXT,__text`),
-//! and insertion of range extending thunks. As such, this algorithm is only run
-//! for a target that requires range extenders such as arm64.
-//!
-//! The algorithm works pessimistically and assumes that any reference to an Atom in
-//! another output section is out of range.
-
-/// Branch instruction has 26 bits immediate but 4 byte aligned.
-const jump_bits = @bitSizeOf(i28);
-
-const max_distance = (1 << (jump_bits - 1));
-
-/// A branch will need an extender if its target is larger than
-/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
-/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
-/// and assume margin to be 5MiB.
-const max_allowed_distance = max_distance - 0x500_000;
-
-pub const Thunk = struct {
- start_index: Atom.Index,
- len: u32,
-
- targets: std.MultiArrayList(Target) = .{},
- lookup: std.AutoHashMapUnmanaged(Target, u32) = .{},
-
- pub const Tag = enum {
- stub,
- atom,
- };
-
- pub const Target = struct {
- tag: Tag,
- target: SymbolWithLoc,
- };
-
- pub const Index = u32;
-
- pub fn deinit(self: *Thunk, gpa: Allocator) void {
- self.targets.deinit(gpa);
- self.lookup.deinit(gpa);
- }
-
- pub fn getStartAtomIndex(self: Thunk) Atom.Index {
- assert(self.len != 0);
- return self.start_index;
- }
-
- pub fn getEndAtomIndex(self: Thunk) Atom.Index {
- assert(self.len != 0);
- return self.start_index + self.len - 1;
- }
-
- pub fn getSize(self: Thunk) u64 {
- return 12 * self.len;
+pub fn createThunks(sect_id: u8, macho_file: *MachO) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = macho_file.base.comp.gpa;
+ const slice = macho_file.sections.slice();
+ const header = &slice.items(.header)[sect_id];
+ const atoms = slice.items(.atoms)[sect_id].items;
+ assert(atoms.len > 0);
+
+ for (atoms) |atom_index| {
+ macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1));
}
- pub fn getAlignment() u32 {
- return @alignOf(u32);
- }
-
- pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc {
- const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null;
- return macho_file.getAtom(atom_index).getSymbolWithLoc();
- }
-};
-
-pub fn createThunks(macho_file: *MachO, sect_id: u8) !void {
- const header = &macho_file.sections.items(.header)[sect_id];
- if (header.size == 0) return;
-
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?;
-
- header.size = 0;
- header.@"align" = 0;
-
- var atom_count: u32 = 0;
-
- {
- var atom_index = first_atom_index;
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
- sym.n_value = 0;
- atom_count += 1;
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
- }
-
- var allocated = std.AutoHashMap(Atom.Index, void).init(gpa);
- defer allocated.deinit();
- try allocated.ensureTotalCapacity(atom_count);
-
- var group_start = first_atom_index;
- var group_end = first_atom_index;
- var offset: u64 = 0;
-
- while (true) {
- const group_start_atom = macho_file.getAtom(group_start);
- log.debug("GROUP START at {d}", .{group_start});
-
- while (true) {
- const atom = macho_file.getAtom(group_end);
- offset = atom.alignment.forward(offset);
-
- const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
- sym.n_value = offset;
- offset += atom.size;
-
- macho_file.logAtom(group_end, log);
-
- header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
-
- allocated.putAssumeCapacityNoClobber(group_end, {});
-
- const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc());
- if (offset - group_start_sym.n_value >= max_allowed_distance) break;
-
- if (atom.next_index) |next_index| {
- group_end = next_index;
- } else break;
+ var i: usize = 0;
+ while (i < atoms.len) {
+ const start = i;
+ const start_atom = macho_file.getAtom(atoms[start]).?;
+ assert(start_atom.flags.alive);
+ start_atom.value = try advance(header, start_atom.size, start_atom.alignment);
+ i += 1;
+
+ while (i < atoms.len and
+ header.size - start_atom.value < max_allowed_distance) : (i += 1)
+ {
+ const atom_index = atoms[i];
+ const atom = macho_file.getAtom(atom_index).?;
+ assert(atom.flags.alive);
+ atom.value = try advance(header, atom.size, atom.alignment);
}
- log.debug("GROUP END at {d}", .{group_end});
-
- // Insert thunk at group_end
- const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len));
- try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
- // Scan relocs in the group and create trampolines for any unreachable callsite.
- var atom_index = group_start;
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- try scanRelocs(
- macho_file,
- atom_index,
- allocated,
- thunk_index,
- group_end,
- );
-
- if (atom_index == group_end) break;
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
+ // Insert a thunk at the group end
+ const thunk_index = try macho_file.addThunk();
+ const thunk = macho_file.getThunk(thunk_index);
+ thunk.out_n_sect = sect_id;
+
+ // Scan relocs in the group and create trampolines for any unreachable callsite
+ for (atoms[start..i]) |atom_index| {
+ const atom = macho_file.getAtom(atom_index).?;
+ log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) });
+ for (atom.getRelocs(macho_file)) |rel| {
+ if (rel.type != .branch) continue;
+ if (isReachable(atom, rel, macho_file)) continue;
+ try thunk.symbols.put(gpa, rel.target, {});
+ }
+ atom.thunk_index = thunk_index;
}
- offset = mem.alignForward(u64, offset, Thunk.getAlignment());
- allocateThunk(macho_file, thunk_index, offset, header);
- offset += macho_file.thunks.items[thunk_index].getSize();
+ thunk.value = try advance(header, thunk.size(), .@"4");
- const thunk = macho_file.thunks.items[thunk_index];
- if (thunk.len == 0) {
- const group_end_atom = macho_file.getAtom(group_end);
- if (group_end_atom.next_index) |next_index| {
- group_start = next_index;
- group_end = next_index;
- } else break;
- } else {
- const thunk_end_atom_index = thunk.getEndAtomIndex();
- const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index);
- if (thunk_end_atom.next_index) |next_index| {
- group_start = next_index;
- group_end = next_index;
- } else break;
- }
+ log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) });
}
-
- header.size = @as(u32, @intCast(offset));
}
-fn allocateThunk(
- macho_file: *MachO,
- thunk_index: Thunk.Index,
- base_offset: u64,
- header: *macho.section_64,
-) void {
- const thunk = macho_file.thunks.items[thunk_index];
- if (thunk.len == 0) return;
-
- const first_atom_index = thunk.getStartAtomIndex();
- const end_atom_index = thunk.getEndAtomIndex();
-
- var atom_index = first_atom_index;
- var offset = base_offset;
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- offset = mem.alignForward(u64, offset, Thunk.getAlignment());
-
- const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
- sym.n_value = offset;
- offset += atom.size;
-
- macho_file.logAtom(atom_index, log);
-
- header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
-
- if (end_atom_index == atom_index) break;
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
+fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) !u64 {
+ const offset = alignment.forward(sect.size);
+ const padding = offset - sect.size;
+ sect.size += padding + size;
+ sect.@"align" = @max(sect.@"align", alignment.toLog2Units());
+ return offset;
}
-fn scanRelocs(
- macho_file: *MachO,
- atom_index: Atom.Index,
- allocated: std.AutoHashMap(Atom.Index, void),
- thunk_index: Thunk.Index,
- group_end: Atom.Index,
-) !void {
- const atom = macho_file.getAtom(atom_index);
- const object = macho_file.objects.items[atom.getFile().?];
-
- const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
- const source_sect = object.getSourceSection(source_sym.n_sect - 1);
- break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr));
- } else 0;
-
- const code = Atom.getAtomCode(macho_file, atom_index);
- const relocs = Atom.getAtomRelocs(macho_file, atom_index);
- const ctx = Atom.getRelocContext(macho_file, atom_index);
+fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
+ const target = rel.getTargetSymbol(macho_file);
+ if (target.flags.stubs or target.flags.objc_stubs) return false;
+ if (atom.out_n_sect != target.out_n_sect) return false;
+ const target_atom = target.getAtom(macho_file).?;
+ if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
+ const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off));
+ const taddr: i64 = @intCast(rel.getTargetAddress(macho_file));
+ _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
+ return true;
+}
- for (relocs) |rel| {
- if (!relocNeedsThunk(rel)) continue;
+pub const Thunk = struct {
+ value: u64 = 0,
+ out_n_sect: u8 = 0,
+ symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{},
- const target = Atom.parseRelocTarget(macho_file, .{
- .object_id = atom.getFile().?,
- .rel = rel,
- .code = code,
- .base_offset = ctx.base_offset,
- .base_addr = ctx.base_addr,
- });
- if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue;
+ pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
+ thunk.symbols.deinit(allocator);
+ }
- log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{
- rel.r_address - base_offset,
- macho_file.getSymbolName(atom.getSymbolWithLoc()),
- macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
- macho_file.getSymbolName(target),
- macho_file.getSymbol(target).n_value,
- });
+ pub fn size(thunk: Thunk) usize {
+ return thunk.symbols.keys().len * trampoline_size;
+ }
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target_sym = macho_file.getSymbol(target);
- const thunk = &macho_file.thunks.items[thunk_index];
+ pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 {
+ return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size;
+ }
- const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom;
- const thunk_target: Thunk.Target = .{ .tag = tag, .target = target };
- const gop = try thunk.lookup.getOrPut(gpa, thunk_target);
- if (!gop.found_existing) {
- gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);
- try thunk.targets.append(gpa, thunk_target);
+ pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void {
+ for (thunk.symbols.keys(), 0..) |sym_index, i| {
+ const sym = macho_file.getSymbol(sym_index);
+ const saddr = thunk.value + i * trampoline_size;
+ const taddr = sym.getAddress(.{}, macho_file);
+ const pages = try Relocation.calcNumberOfPages(saddr, taddr);
+ try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
+ const off = try Relocation.calcPageOffset(taddr, .arithmetic);
+ try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
+ try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
}
-
- try macho_file.thunk_table.put(gpa, atom_index, thunk_index);
}
-}
-
-fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {
- const thunk_atom_index = try createThunkAtom(macho_file);
- const thunk_atom = macho_file.getAtomPtr(thunk_atom_index);
- const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();
- const end_atom = macho_file.getAtomPtr(end_atom_index);
-
- if (end_atom.next_index) |first_after_index| {
- const first_after_atom = macho_file.getAtomPtr(first_after_index);
- first_after_atom.prev_index = thunk_atom_index;
- thunk_atom.next_index = first_after_index;
+ pub fn format(
+ thunk: Thunk,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = thunk;
+ _ = unused_fmt_string;
+ _ = options;
+ _ = writer;
+ @compileError("do not format Thunk directly");
}
- end_atom.next_index = thunk_atom_index;
- thunk_atom.prev_index = end_atom_index;
-
- if (thunk.len == 0) {
- thunk.start_index = thunk_atom_index;
+ pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) {
+ return .{ .data = .{
+ .thunk = thunk,
+ .macho_file = macho_file,
+ } };
}
- thunk.len += 1;
-
- return thunk_atom_index;
-}
-
-inline fn relocNeedsThunk(rel: macho.relocation_info) bool {
- const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
- return rel_type == .ARM64_RELOC_BRANCH26;
-}
-
-fn isReachable(
- macho_file: *MachO,
- atom_index: Atom.Index,
- rel: macho.relocation_info,
- base_offset: i32,
- target: SymbolWithLoc,
- allocated: std.AutoHashMap(Atom.Index, void),
-) bool {
- if (macho_file.stub_table.lookup.contains(target)) return false;
-
- const source_atom = macho_file.getAtom(atom_index);
- const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
-
- const target_object = macho_file.objects.items[target.getFile().?];
- const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
- const target_atom = macho_file.getAtom(target_atom_index);
- const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
-
- if (source_sym.n_sect != target_sym.n_sect) return false;
+ const FormatContext = struct {
+ thunk: Thunk,
+ macho_file: *MachO,
+ };
- if (!allocated.contains(target_atom_index)) return false;
+ fn format2(
+ ctx: FormatContext,
+ comptime unused_fmt_string: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = options;
+ _ = unused_fmt_string;
+ const thunk = ctx.thunk;
+ const macho_file = ctx.macho_file;
+ try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() });
+ for (thunk.symbols.keys()) |index| {
+ const sym = macho_file.getSymbol(index);
+ try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value });
+ }
+ }
- const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset));
- const target_addr = if (Atom.relocRequiresGot(macho_file, rel))
- macho_file.getGotEntryAddress(target).?
- else
- Atom.getRelocTargetAddress(macho_file, target, false);
- _ = Relocation.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch
- return false;
+ const trampoline_size = 3 * @sizeOf(u32);
- return true;
-}
+ pub const Index = u32;
+};
-fn createThunkAtom(macho_file: *MachO) !Atom.Index {
- const sym_index = try macho_file.allocateSymbol();
- const atom_index = try macho_file.createAtom(sym_index, .{
- .size = @sizeOf(u32) * 3,
- .alignment = .@"4",
- });
- const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index });
- sym.n_type = macho.N_SECT;
- sym.n_sect = macho_file.text_section_index.? + 1;
- return atom_index;
-}
+/// Branch instruction has 26 bits immediate but is 4 byte aligned.
+const jump_bits = @bitSizeOf(i28);
+const max_distance = (1 << (jump_bits - 1));
-pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void {
- const slice = thunk.targets.slice();
- for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| {
- const atom = macho_file.getAtom(@intCast(atom_index));
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const source_addr = sym.n_value;
- const tag = slice.items(.tag)[target_index];
- const target = slice.items(.target)[target_index];
- const target_addr = switch (tag) {
- .stub => macho_file.getStubsEntryAddress(target).?,
- .atom => macho_file.getSymbol(target).n_value,
- };
- const pages = Relocation.calcNumberOfPages(source_addr, target_addr);
- try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
- const off = try Relocation.calcPageOffset(target_addr, .arithmetic);
- try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
- try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
- }
-}
+/// A branch will need an extender if its target is larger than
+/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
+/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
+/// and assume margin to be 5MiB.
+const max_allowed_distance = max_distance - 0x500_000;
-const std = @import("std");
+const aarch64 = @import("../../arch/aarch64/bits.zig");
const assert = std.debug.assert;
-const log = std.log.scoped(.thunks);
+const log = std.log.scoped(.link);
const macho = std.macho;
const math = std.math;
const mem = std.mem;
-
-const aarch64 = @import("../../arch/aarch64/bits.zig");
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const MachO = @import("../MachO.zig");
const Relocation = @import("Relocation.zig");
-const SymbolWithLoc = MachO.SymbolWithLoc;
+const Symbol = @import("Symbol.zig");
diff --git a/src/link/MachO/uuid.zig b/src/link/MachO/uuid.zig
index 6d60397149..565ae80b22 100644
--- a/src/link/MachO/uuid.zig
+++ b/src/link/MachO/uuid.zig
@@ -5,6 +5,9 @@
/// TODO LLD also hashes the output filename to disambiguate between same builds with different
/// output files. Should we also do that?
pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
const chunk_size: usize = 1024 * 1024;
const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;
const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks;
@@ -35,11 +38,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
out[8] = (out[8] & 0x3F) | 0x80;
}
-const std = @import("std");
const fs = std.fs;
const mem = std.mem;
+const std = @import("std");
+const trace = @import("../../tracy.zig").trace;
-const Allocator = mem.Allocator;
const Compilation = @import("../../Compilation.zig");
const Md5 = std.crypto.hash.Md5;
const Hasher = @import("hasher.zig").ParallelHasher;
+const ThreadPool = std.Thread.Pool;
diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig
deleted file mode 100644
index 57681dd935..0000000000
--- a/src/link/MachO/zld.zig
+++ /dev/null
@@ -1,1230 +0,0 @@
-pub fn linkWithZld(
- macho_file: *MachO,
- arena: Allocator,
- prog_node: *std.Progress.Node,
-) link.File.FlushError!void {
- const tracy = trace(@src());
- defer tracy.end();
-
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = comp.root_mod.resolved_target.result;
- const emit = macho_file.base.emit;
-
- const directory = emit.directory; // Just an alias to make it shorter to type.
- const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path});
- const opt_zcu = comp.module;
-
- // If there is no Zig code to compile, then we should skip flushing the output file because it
- // will not be part of the linker line anyway.
- const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
- try macho_file.flushModule(arena, prog_node);
-
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :blk try fs.path.join(arena, &.{ dirname, macho_file.base.zcu_object_sub_path.? });
- } else {
- break :blk macho_file.base.zcu_object_sub_path.?;
- }
- } else null;
-
- var sub_prog_node = prog_node.start("MachO Flush", 0);
- sub_prog_node.activate();
- sub_prog_node.context.refresh();
- defer sub_prog_node.end();
-
- const output_mode = comp.config.output_mode;
- const link_mode = comp.config.link_mode;
- const cpu_arch = target.cpu.arch;
- const is_lib = output_mode == .Lib;
- const is_dyn_lib = link_mode == .Dynamic and is_lib;
- const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
- const stack_size = macho_file.base.stack_size;
-
- const id_symlink_basename = "zld.id";
-
- var man: Cache.Manifest = undefined;
- defer if (!macho_file.base.disable_lld_caching) man.deinit();
-
- var digest: [Cache.hex_digest_len]u8 = undefined;
-
- const objects = comp.objects;
-
- if (!macho_file.base.disable_lld_caching) {
- man = comp.cache_parent.obtain();
-
- // We are about to obtain this lock, so here we give other processes a chance first.
- macho_file.base.releaseLock();
-
- comptime assert(Compilation.link_hash_implementation_version == 11);
-
- for (objects) |obj| {
- _ = try man.addFile(obj.path, null);
- man.hash.add(obj.must_link);
- }
- for (comp.c_object_table.keys()) |key| {
- _ = try man.addFile(key.status.success.object_path, null);
- }
- try man.addOptionalFile(module_obj_path);
- // We can skip hashing libc and libc++ components that we are in charge of building from Zig
- // installation sources because they are always a product of the compiler version + target information.
- man.hash.add(stack_size);
- man.hash.add(macho_file.pagezero_vmsize);
- man.hash.add(macho_file.headerpad_size);
- man.hash.add(macho_file.headerpad_max_install_names);
- man.hash.add(macho_file.base.gc_sections);
- man.hash.add(macho_file.dead_strip_dylibs);
- man.hash.add(comp.root_mod.strip);
- try MachO.hashAddFrameworks(&man, macho_file.frameworks);
- man.hash.addListOfBytes(macho_file.base.rpath_list);
- if (is_dyn_lib) {
- man.hash.addOptionalBytes(macho_file.install_name);
- man.hash.addOptional(comp.version);
- }
- try link.hashAddSystemLibs(&man, comp.system_libs);
- man.hash.addOptionalBytes(comp.sysroot);
- man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
- try man.addOptionalFile(macho_file.entitlements);
-
- // We don't actually care whether it's a cache hit or miss; we just
- // need the digest and the lock.
- _ = try man.hit();
- digest = man.final();
-
- var prev_digest_buf: [digest.len]u8 = undefined;
- const prev_digest: []u8 = Cache.readSmallFile(
- directory.handle,
- id_symlink_basename,
- &prev_digest_buf,
- ) catch |err| blk: {
- log.debug("MachO Zld new_digest={s} error: {s}", .{
- std.fmt.fmtSliceHexLower(&digest),
- @errorName(err),
- });
- // Handle this as a cache miss.
- break :blk prev_digest_buf[0..0];
- };
- if (mem.eql(u8, prev_digest, &digest)) {
- // Hot diggity dog! The output binary is already there.
- log.debug("MachO Zld digest={s} match - skipping invocation", .{
- std.fmt.fmtSliceHexLower(&digest),
- });
- macho_file.base.lock = man.toOwnedLock();
- return;
- }
- log.debug("MachO Zld prev_digest={s} new_digest={s}", .{
- std.fmt.fmtSliceHexLower(prev_digest),
- std.fmt.fmtSliceHexLower(&digest),
- });
-
- // We are about to change the output file to be different, so we invalidate the build hash now.
- directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
- error.FileNotFound => {},
- else => |e| return e,
- };
- }
-
- if (output_mode == .Obj) {
- // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy
- // here. TODO: think carefully about how we can avoid this redundant operation when doing
- // build-obj. See also the corresponding TODO in linkAsArchive.
- const the_object_path = blk: {
- if (objects.len != 0) {
- break :blk objects[0].path;
- }
-
- if (comp.c_object_table.count() != 0)
- break :blk comp.c_object_table.keys()[0].status.success.object_path;
-
- if (module_obj_path) |p|
- break :blk p;
-
- // TODO I think this is unreachable. Audit this situation when solving the above TODO
- // regarding eliding redundant object -> object transformations.
- return error.NoObjectsToLink;
- };
- // This can happen when using --enable-cache and using the stage1 backend. In this case
- // we can skip the file copy.
- if (!mem.eql(u8, the_object_path, full_out_path)) {
- try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
- }
- } else {
- const sub_path = emit.sub_path;
-
- const old_file = macho_file.base.file; // TODO is this needed at all?
- defer macho_file.base.file = old_file;
-
- const file = try directory.handle.createFile(sub_path, .{
- .truncate = true,
- .read = true,
- .mode = link.File.determineMode(false, output_mode, link_mode),
- });
- defer file.close();
- macho_file.base.file = file;
-
- // Index 0 is always a null symbol.
- try macho_file.locals.append(gpa, .{
- .n_strx = 0,
- .n_type = 0,
- .n_sect = 0,
- .n_desc = 0,
- .n_value = 0,
- });
- try macho_file.strtab.buffer.append(gpa, 0);
-
- // Positional arguments to the linker such as object files and static archives.
- var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
- try positionals.ensureUnusedCapacity(objects.len);
- positionals.appendSliceAssumeCapacity(objects);
-
- for (comp.c_object_table.keys()) |key| {
- try positionals.append(.{ .path = key.status.success.object_path });
- }
-
- if (module_obj_path) |p| {
- try positionals.append(.{ .path = p });
- }
-
- if (comp.compiler_rt_lib) |lib| try positionals.append(.{ .path = lib.full_object_path });
- if (comp.compiler_rt_obj) |obj| try positionals.append(.{ .path = obj.full_object_path });
-
- // libc++ dep
- if (comp.config.link_libcpp) {
- try positionals.ensureUnusedCapacity(2);
- positionals.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
- positionals.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
- }
-
- var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
-
- {
- const vals = comp.system_libs.values();
- try libs.ensureUnusedCapacity(vals.len);
- for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
- }
-
- {
- try libs.ensureUnusedCapacity(macho_file.frameworks.len);
- for (macho_file.frameworks) |v| libs.putAssumeCapacity(v.path, .{
- .needed = v.needed,
- .weak = v.weak,
- .path = v.path,
- });
- }
-
- try macho_file.resolveLibSystem(arena, comp, &libs);
-
- if (comp.verbose_link) {
- var argv = std.ArrayList([]const u8).init(arena);
-
- try argv.append("zig");
- try argv.append("ld");
-
- if (is_exe_or_dyn_lib) {
- try argv.append("-dynamic");
- }
-
- if (is_dyn_lib) {
- try argv.append("-dylib");
-
- if (macho_file.install_name) |install_name| {
- try argv.append("-install_name");
- try argv.append(install_name);
- }
- }
-
- {
- const platform = Platform.fromTarget(target);
- try argv.append("-platform_version");
- try argv.append(@tagName(platform.os_tag));
- try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
-
- const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
- if (sdk_version) |ver| {
- try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
- } else {
- try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
- }
- }
-
- if (comp.sysroot) |syslibroot| {
- try argv.append("-syslibroot");
- try argv.append(syslibroot);
- }
-
- for (macho_file.base.rpath_list) |rpath| {
- try argv.append("-rpath");
- try argv.append(rpath);
- }
-
- try argv.appendSlice(&.{
- "-pagezero_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.pagezero_vmsize}),
- "-headerpad_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.headerpad_size}),
- });
-
- if (macho_file.headerpad_max_install_names) {
- try argv.append("-headerpad_max_install_names");
- }
-
- if (macho_file.base.gc_sections) {
- try argv.append("-dead_strip");
- }
-
- if (macho_file.dead_strip_dylibs) {
- try argv.append("-dead_strip_dylibs");
- }
-
- if (macho_file.entry_name) |entry_name| {
- try argv.appendSlice(&.{ "-e", entry_name });
- }
-
- for (objects) |obj| {
- if (obj.must_link) {
- try argv.append("-force_load");
- }
- try argv.append(obj.path);
- }
-
- for (comp.c_object_table.keys()) |key| {
- try argv.append(key.status.success.object_path);
- }
-
- if (module_obj_path) |p| {
- try argv.append(p);
- }
-
- if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
- if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
-
- if (comp.config.link_libcpp) {
- try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
- try argv.append(comp.libcxx_static_lib.?.full_object_path);
- }
-
- try argv.append("-o");
- try argv.append(full_out_path);
-
- try argv.append("-lSystem");
-
- for (comp.system_libs.keys()) |l_name| {
- const info = comp.system_libs.get(l_name).?;
- const arg = if (info.needed)
- try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
- else if (info.weak)
- try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
- else
- try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
- try argv.append(arg);
- }
-
- for (macho_file.frameworks) |framework| {
- const name = std.fs.path.stem(framework.path);
- const arg = if (framework.needed)
- try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
- else if (framework.weak)
- try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
- else
- try std.fmt.allocPrint(arena, "-framework {s}", .{name});
- try argv.append(arg);
- }
-
- if (is_dyn_lib and macho_file.base.allow_shlib_undefined) {
- try argv.append("-undefined");
- try argv.append("dynamic_lookup");
- }
-
- Compilation.dump_argv(argv.items);
- }
-
- var dependent_libs = std.fifo.LinearFifo(MachO.DylibReExportInfo, .Dynamic).init(arena);
-
- for (positionals.items) |obj| {
- const in_file = try std.fs.cwd().openFile(obj.path, .{});
- defer in_file.close();
-
- var parse_ctx = MachO.ParseErrorCtx.init(gpa);
- defer parse_ctx.deinit();
-
- macho_file.parsePositional(
- in_file,
- obj.path,
- obj.must_link,
- &dependent_libs,
- &parse_ctx,
- ) catch |err| try macho_file.handleAndReportParseError(obj.path, err, &parse_ctx);
- }
-
- for (libs.keys(), libs.values()) |path, lib| {
- const in_file = try std.fs.cwd().openFile(path, .{});
- defer in_file.close();
-
- var parse_ctx = MachO.ParseErrorCtx.init(gpa);
- defer parse_ctx.deinit();
-
- macho_file.parseLibrary(
- in_file,
- path,
- lib,
- false,
- false,
- null,
- &dependent_libs,
- &parse_ctx,
- ) catch |err| try macho_file.handleAndReportParseError(path, err, &parse_ctx);
- }
-
- try macho_file.parseDependentLibs(&dependent_libs);
-
- try macho_file.resolveSymbols();
- if (macho_file.unresolved.count() > 0) {
- try macho_file.reportUndefined();
- return error.FlushFailure;
- }
-
- for (macho_file.objects.items, 0..) |*object, object_id| {
- object.splitIntoAtoms(macho_file, @as(u32, @intCast(object_id))) catch |err| switch (err) {
- error.MissingEhFrameSection => try macho_file.reportParseError(
- object.name,
- "missing section: '__TEXT,__eh_frame' is required but could not be found",
- .{},
- ),
- error.BadDwarfCfi => try macho_file.reportParseError(
- object.name,
- "invalid DWARF: failed to parse '__TEXT,__eh_frame' section",
- .{},
- ),
- else => |e| return e,
- };
- }
-
- if (macho_file.base.gc_sections) {
- try dead_strip.gcAtoms(macho_file);
- }
-
- try macho_file.createDyldPrivateAtom();
- try macho_file.createTentativeDefAtoms();
-
- if (comp.config.output_mode == .Exe) {
- const global = macho_file.getEntryPoint().?;
- if (macho_file.getSymbol(global).undf()) {
- // We do one additional check here in case the entry point was found in one of the dylibs.
- // (I actually have no idea what this would imply but it is a possible outcome and so we
- // support it.)
- try macho_file.addStubEntry(global);
- }
- }
-
- for (macho_file.objects.items) |object| {
- for (object.atoms.items) |atom_index| {
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const header = macho_file.sections.items(.header)[sym.n_sect - 1];
- if (header.isZerofill()) continue;
-
- const relocs = Atom.getAtomRelocs(macho_file, atom_index);
- try Atom.scanAtomRelocs(macho_file, atom_index, relocs);
- }
- }
-
- try eh_frame.scanRelocs(macho_file);
- try UnwindInfo.scanRelocs(macho_file);
-
- if (macho_file.dyld_stub_binder_index) |index|
- try macho_file.addGotEntry(macho_file.globals.items[index]);
-
- try calcSectionSizes(macho_file);
-
- var unwind_info = UnwindInfo{ .gpa = gpa };
- defer unwind_info.deinit();
- try unwind_info.collect(macho_file);
-
- try eh_frame.calcSectionSize(macho_file, &unwind_info);
- unwind_info.calcSectionSize(macho_file);
-
- try pruneAndSortSections(macho_file);
- try createSegments(macho_file);
- try allocateSegments(macho_file);
-
- try macho_file.allocateSpecialSymbols();
-
- if (build_options.enable_logging) {
- macho_file.logSymtab();
- macho_file.logSegments();
- macho_file.logSections();
- macho_file.logAtoms();
- }
-
- try writeAtoms(macho_file);
- if (target.cpu.arch == .aarch64) try writeThunks(macho_file);
- try writeDyldPrivateAtom(macho_file);
-
- if (macho_file.stubs_section_index) |_| {
- try writeStubs(macho_file);
- try writeStubHelpers(macho_file);
- try writeLaSymbolPtrs(macho_file);
- }
- if (macho_file.got_section_index) |sect_id|
- try writePointerEntries(macho_file, sect_id, &macho_file.got_table);
- if (macho_file.tlv_ptr_section_index) |sect_id|
- try writePointerEntries(macho_file, sect_id, &macho_file.tlv_ptr_table);
-
- try eh_frame.write(macho_file, &unwind_info);
- try unwind_info.write(macho_file);
- try macho_file.writeLinkeditSegmentData();
-
- // If the last section of __DATA segment is zerofill section, we need to ensure
- // that the free space between the end of the last non-zerofill section of __DATA
- // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will
- // copy-paste this space into memory for quicker zerofill operation.
- if (macho_file.data_segment_cmd_index) |data_seg_id| blk: {
- var physical_zerofill_start: ?u64 = null;
- const section_indexes = macho_file.getSectionIndexes(data_seg_id);
- for (macho_file.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
- if (header.isZerofill() and header.size > 0) break;
- physical_zerofill_start = header.offset + header.size;
- } else break :blk;
- const start = physical_zerofill_start orelse break :blk;
- const linkedit = macho_file.getLinkeditSegmentPtr();
- const size = math.cast(usize, linkedit.fileoff - start) orelse return error.Overflow;
- if (size > 0) {
- log.debug("zeroing out zerofill area of length {x} at {x}", .{ size, start });
- const padding = try gpa.alloc(u8, size);
- defer gpa.free(padding);
- @memset(padding, 0);
- try macho_file.base.file.?.pwriteAll(padding, start);
- }
- }
-
- // Write code signature padding if required
- var codesig: ?CodeSignature = if (macho_file.requiresCodeSignature()) blk: {
- // Preallocate space for the code signature.
- // We need to do this at this stage so that we have the load commands with proper values
- // written out to the file.
- // 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(MachO.getPageSize(cpu_arch));
- codesig.code_directory.ident = fs.path.basename(full_out_path);
- if (macho_file.entitlements) |path| {
- try codesig.addEntitlements(gpa, path);
- }
- try macho_file.writeCodeSignaturePadding(&codesig);
- break :blk codesig;
- } else null;
- defer if (codesig) |*csig| csig.deinit(gpa);
-
- // Write load commands
- var lc_buffer = std.ArrayList(u8).init(arena);
- const lc_writer = lc_buffer.writer();
-
- try macho_file.writeSegmentHeaders(lc_writer);
- try lc_writer.writeStruct(macho_file.dyld_info_cmd);
- try lc_writer.writeStruct(macho_file.function_starts_cmd);
- try lc_writer.writeStruct(macho_file.data_in_code_cmd);
- try lc_writer.writeStruct(macho_file.symtab_cmd);
- try lc_writer.writeStruct(macho_file.dysymtab_cmd);
- try load_commands.writeDylinkerLC(lc_writer);
-
- switch (output_mode) {
- .Exe => blk: {
- const seg_id = macho_file.header_segment_cmd_index.?;
- const seg = macho_file.segments.items[seg_id];
- const global = macho_file.getEntryPoint() orelse break :blk;
- const sym = macho_file.getSymbol(global);
-
- const addr: u64 = if (sym.undf())
- // In this case, the symbol has been resolved in one of dylibs and so we point
- // to the stub as its vmaddr value.
- macho_file.getStubsEntryAddress(global).?
- else
- sym.n_value;
-
- try lc_writer.writeStruct(macho.entry_point_command{
- .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
- .stacksize = macho_file.base.stack_size,
- });
- },
- .Lib => if (link_mode == .Dynamic) {
- try load_commands.writeDylibIdLC(macho_file, lc_writer);
- },
- else => {},
- }
-
- try load_commands.writeRpathLCs(macho_file, lc_writer);
- try lc_writer.writeStruct(macho.source_version_command{
- .version = 0,
- });
- {
- const platform = Platform.fromTarget(target);
- const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
- if (platform.isBuildVersionCompatible()) {
- try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
- } else {
- try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
- }
- }
-
- const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
- try lc_writer.writeStruct(macho_file.uuid_cmd);
-
- try load_commands.writeLoadDylibLCs(
- macho_file.dylibs.items,
- macho_file.referenced_dylibs.keys(),
- lc_writer,
- );
-
- if (codesig != null) {
- try lc_writer.writeStruct(macho_file.codesig_cmd);
- }
-
- const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
- try macho_file.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
- try macho_file.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
- try macho_file.writeUuid(comp, uuid_cmd_offset, codesig != null);
-
- if (codesig) |*csig| {
- try macho_file.writeCodeSignature(comp, csig); // code signing always comes last
- try MachO.invalidateKernelCache(directory.handle, macho_file.base.emit.sub_path);
- }
- }
-
- if (!macho_file.base.disable_lld_caching) {
- // Update the file with the digest. If it fails we can continue; it only
- // means that the next invocation will have an unnecessary cache miss.
- Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
- log.debug("failed to save linking hash digest file: {s}", .{@errorName(err)});
- };
- // Again failure here only means an unnecessary cache miss.
- if (man.have_exclusive_lock) {
- man.writeManifest() catch |err| {
- log.debug("failed to write cache manifest when linking: {s}", .{@errorName(err)});
- };
- }
- // We hang on to this lock so that the output file path can be used without
- // other processes clobbering it.
- macho_file.base.lock = man.toOwnedLock();
- }
-}
-
-fn createSegments(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const page_size = MachO.getPageSize(target.cpu.arch);
- const aligned_pagezero_vmsize = mem.alignBackward(u64, macho_file.pagezero_vmsize, page_size);
- if (macho_file.base.comp.config.output_mode != .Lib and aligned_pagezero_vmsize > 0) {
- if (aligned_pagezero_vmsize != macho_file.pagezero_vmsize) {
- log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{macho_file.pagezero_vmsize});
- log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize});
- }
- macho_file.pagezero_segment_cmd_index = @intCast(macho_file.segments.items.len);
- try macho_file.segments.append(gpa, .{
- .cmdsize = @sizeOf(macho.segment_command_64),
- .segname = MachO.makeStaticString("__PAGEZERO"),
- .vmsize = aligned_pagezero_vmsize,
- });
- }
-
- // __TEXT segment is non-optional
- {
- const protection = MachO.getSegmentMemoryProtection("__TEXT");
- macho_file.text_segment_cmd_index = @intCast(macho_file.segments.items.len);
- macho_file.header_segment_cmd_index = macho_file.text_segment_cmd_index.?;
- try macho_file.segments.append(gpa, .{
- .cmdsize = @sizeOf(macho.segment_command_64),
- .segname = MachO.makeStaticString("__TEXT"),
- .maxprot = protection,
- .initprot = protection,
- });
- }
-
- for (macho_file.sections.items(.header), 0..) |header, sect_id| {
- if (header.size == 0) continue; // empty section
-
- const segname = header.segName();
- const segment_id = macho_file.getSegmentByName(segname) orelse blk: {
- log.debug("creating segment '{s}'", .{segname});
- const segment_id = @as(u8, @intCast(macho_file.segments.items.len));
- const protection = MachO.getSegmentMemoryProtection(segname);
- try macho_file.segments.append(gpa, .{
- .cmdsize = @sizeOf(macho.segment_command_64),
- .segname = MachO.makeStaticString(segname),
- .maxprot = protection,
- .initprot = protection,
- });
- break :blk segment_id;
- };
- const segment = &macho_file.segments.items[segment_id];
- segment.cmdsize += @sizeOf(macho.section_64);
- segment.nsects += 1;
- macho_file.sections.items(.segment_index)[sect_id] = segment_id;
- }
-
- if (macho_file.getSegmentByName("__DATA_CONST")) |index| {
- macho_file.data_const_segment_cmd_index = index;
- }
-
- if (macho_file.getSegmentByName("__DATA")) |index| {
- macho_file.data_segment_cmd_index = index;
- }
-
- // __LINKEDIT always comes last
- {
- const protection = MachO.getSegmentMemoryProtection("__LINKEDIT");
- macho_file.linkedit_segment_cmd_index = @intCast(macho_file.segments.items.len);
- try macho_file.segments.append(gpa, .{
- .cmdsize = @sizeOf(macho.segment_command_64),
- .segname = MachO.makeStaticString("__LINKEDIT"),
- .maxprot = protection,
- .initprot = protection,
- });
- }
-}
-
-fn writeAtoms(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const slice = macho_file.sections.slice();
-
- for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
- const header = slice.items(.header)[sect_id];
- if (header.isZerofill()) continue;
-
- var atom_index = first_atom_index orelse continue;
-
- var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow);
- defer gpa.free(buffer);
- @memset(buffer, 0); // TODO with NOPs
-
- log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() });
-
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- if (atom.getFile()) |file| {
- const this_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const padding_size: usize = if (atom.next_index) |next_index| blk: {
- const next_sym = macho_file.getSymbol(macho_file.getAtom(next_index).getSymbolWithLoc());
- const size = next_sym.n_value - (this_sym.n_value + atom.size);
- break :blk math.cast(usize, size) orelse return error.Overflow;
- } else 0;
-
- log.debug(" (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{
- atom.sym_index,
- macho_file.getSymbolName(atom.getSymbolWithLoc()),
- file,
- });
- if (padding_size > 0) {
- log.debug(" (with padding {x})", .{padding_size});
- }
-
- const offset = math.cast(usize, this_sym.n_value - header.addr) orelse
- return error.Overflow;
- log.debug(" (at offset 0x{x})", .{offset});
-
- const code = Atom.getAtomCode(macho_file, atom_index);
- const relocs = Atom.getAtomRelocs(macho_file, atom_index);
- const size = math.cast(usize, atom.size) orelse return error.Overflow;
- @memcpy(buffer[offset .. offset + size], code);
- try Atom.resolveRelocs(
- macho_file,
- atom_index,
- buffer[offset..][0..size],
- relocs,
- );
- }
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
-
- log.debug(" (writing at file offset 0x{x})", .{header.offset});
- try macho_file.base.file.?.pwriteAll(buffer, header.offset);
- }
-}
-
-fn writeDyldPrivateAtom(macho_file: *MachO) !void {
- const atom_index = macho_file.dyld_private_atom_index orelse return;
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- const sect_id = macho_file.data_section_index.?;
- const header = macho_file.sections.items(.header)[sect_id];
- const offset = sym.n_value - header.addr + header.offset;
- log.debug("writing __dyld_private at offset 0x{x}", .{offset});
- const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
- try macho_file.base.file.?.pwriteAll(&buffer, offset);
-}
-
-fn writeThunks(macho_file: *MachO) !void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- assert(target.cpu.arch == .aarch64);
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- const sect_id = macho_file.text_section_index orelse return;
- const header = macho_file.sections.items(.header)[sect_id];
-
- for (macho_file.thunks.items, 0..) |*thunk, i| {
- if (thunk.getSize() == 0) continue;
- const thunk_size = math.cast(usize, thunk.getSize()) orelse return error.Overflow;
- var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk_size);
- defer buffer.deinit();
- try thunks.writeThunkCode(macho_file, thunk, buffer.writer());
- const thunk_atom = macho_file.getAtom(thunk.getStartAtomIndex());
- const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc());
- const offset = thunk_sym.n_value - header.addr + header.offset;
- log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset });
- try macho_file.base.file.?.pwriteAll(buffer.items, offset);
- }
-}
-
-fn writePointerEntries(macho_file: *MachO, sect_id: u8, table: anytype) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const header = macho_file.sections.items(.header)[sect_id];
- const capacity = math.cast(usize, header.size) orelse return error.Overflow;
- var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
- defer buffer.deinit();
- for (table.entries.items) |entry| {
- const sym = macho_file.getSymbol(entry);
- buffer.writer().writeInt(u64, sym.n_value, .little) catch unreachable;
- }
- log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset});
- try macho_file.base.file.?.pwriteAll(buffer.items, header.offset);
-}
-
-fn writeStubs(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const stubs_header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
- const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
-
- const capacity = math.cast(usize, stubs_header.size) orelse return error.Overflow;
- var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
- defer buffer.deinit();
-
- for (0..macho_file.stub_table.count()) |index| {
- try stubs.writeStubCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index,
- .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64),
- }, buffer.writer());
- }
-
- log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset});
- try macho_file.base.file.?.pwriteAll(buffer.items, stubs_header.offset);
-}
-
-fn writeStubHelpers(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
-
- const capacity = math.cast(usize, stub_helper_header.size) orelse return error.Overflow;
- var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
- defer buffer.deinit();
-
- {
- const dyld_private_addr = blk: {
- const atom = macho_file.getAtom(macho_file.dyld_private_atom_index.?);
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
- break :blk sym.n_value;
- };
- const dyld_stub_binder_got_addr = blk: {
- const sym_loc = macho_file.globals.items[macho_file.dyld_stub_binder_index.?];
- break :blk macho_file.getGotEntryAddress(sym_loc).?;
- };
- try stubs.writeStubHelperPreambleCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = stub_helper_header.addr,
- .dyld_private_addr = dyld_private_addr,
- .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
- }, buffer.writer());
- }
-
- for (0..macho_file.stub_table.count()) |index| {
- const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
- stubs.stubHelperSize(cpu_arch) * index;
- try stubs.writeStubHelperCode(.{
- .cpu_arch = cpu_arch,
- .source_addr = source_addr,
- .target_addr = stub_helper_header.addr,
- }, buffer.writer());
- }
-
- log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{
- stub_helper_header.offset,
- });
- try macho_file.base.file.?.pwriteAll(buffer.items, stub_helper_header.offset);
-}
-
-fn writeLaSymbolPtrs(macho_file: *MachO) !void {
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const cpu_arch = target.cpu.arch;
- const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
- const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
-
- const capacity = math.cast(usize, la_symbol_ptr_header.size) orelse return error.Overflow;
- var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
- defer buffer.deinit();
-
- for (0..macho_file.stub_table.count()) |index| {
- const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
- stubs.stubHelperSize(cpu_arch) * index;
- buffer.writer().writeInt(u64, target_addr, .little) catch unreachable;
- }
-
- log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{
- la_symbol_ptr_header.offset,
- });
- try macho_file.base.file.?.pwriteAll(buffer.items, la_symbol_ptr_header.offset);
-}
-
-fn pruneAndSortSections(macho_file: *MachO) !void {
- const Entry = struct {
- index: u8,
-
- pub fn lessThan(ctx: *MachO, lhs: @This(), rhs: @This()) bool {
- const lhs_header = ctx.sections.items(.header)[lhs.index];
- const rhs_header = ctx.sections.items(.header)[rhs.index];
- return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header);
- }
- };
-
- const comp = macho_file.base.comp;
- const gpa = comp.gpa;
-
- var entries = try std.ArrayList(Entry).initCapacity(gpa, macho_file.sections.slice().len);
- defer entries.deinit();
-
- for (0..macho_file.sections.slice().len) |index| {
- const section = macho_file.sections.get(index);
- if (section.header.size == 0) {
- log.debug("pruning section {s},{s} {?d}", .{
- section.header.segName(),
- section.header.sectName(),
- section.first_atom_index,
- });
- for (&[_]*?u8{
- &macho_file.text_section_index,
- &macho_file.data_const_section_index,
- &macho_file.data_section_index,
- &macho_file.bss_section_index,
- &macho_file.thread_vars_section_index,
- &macho_file.thread_data_section_index,
- &macho_file.thread_bss_section_index,
- &macho_file.eh_frame_section_index,
- &macho_file.unwind_info_section_index,
- &macho_file.got_section_index,
- &macho_file.tlv_ptr_section_index,
- &macho_file.stubs_section_index,
- &macho_file.stub_helper_section_index,
- &macho_file.la_symbol_ptr_section_index,
- }) |maybe_index| {
- if (maybe_index.* != null and maybe_index.*.? == index) {
- maybe_index.* = null;
- }
- }
- continue;
- }
- entries.appendAssumeCapacity(.{ .index = @intCast(index) });
- }
-
- mem.sort(Entry, entries.items, macho_file, Entry.lessThan);
-
- var slice = macho_file.sections.toOwnedSlice();
- defer slice.deinit(gpa);
-
- const backlinks = try gpa.alloc(u8, slice.len);
- defer gpa.free(backlinks);
- for (entries.items, 0..) |entry, i| {
- backlinks[entry.index] = @as(u8, @intCast(i));
- }
-
- try macho_file.sections.ensureTotalCapacity(gpa, entries.items.len);
- for (entries.items) |entry| {
- macho_file.sections.appendAssumeCapacity(slice.get(entry.index));
- }
-
- for (&[_]*?u8{
- &macho_file.text_section_index,
- &macho_file.data_const_section_index,
- &macho_file.data_section_index,
- &macho_file.bss_section_index,
- &macho_file.thread_vars_section_index,
- &macho_file.thread_data_section_index,
- &macho_file.thread_bss_section_index,
- &macho_file.eh_frame_section_index,
- &macho_file.unwind_info_section_index,
- &macho_file.got_section_index,
- &macho_file.tlv_ptr_section_index,
- &macho_file.stubs_section_index,
- &macho_file.stub_helper_section_index,
- &macho_file.la_symbol_ptr_section_index,
- }) |maybe_index| {
- if (maybe_index.*) |*index| {
- index.* = backlinks[index.*];
- }
- }
-}
-
-fn calcSectionSizes(macho_file: *MachO) !void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const slice = macho_file.sections.slice();
- for (slice.items(.header), 0..) |*header, sect_id| {
- if (header.size == 0) continue;
- if (macho_file.text_section_index) |txt| {
- if (txt == sect_id and target.cpu.arch == .aarch64) continue;
- }
-
- var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue;
-
- header.size = 0;
- header.@"align" = 0;
-
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- const atom_offset = atom.alignment.forward(header.size);
- const padding = atom_offset - header.size;
-
- const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
- sym.n_value = atom_offset;
-
- header.size += padding + atom.size;
- header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
-
- atom_index = atom.next_index orelse break;
- }
- }
-
- if (macho_file.text_section_index != null and target.cpu.arch == .aarch64) {
- // Create jump/branch range extenders if needed.
- try thunks.createThunks(macho_file, macho_file.text_section_index.?);
- }
-
- // Update offsets of all symbols contained within each Atom.
- // We need to do this since our unwind info synthesiser relies on
- // traversing the symbols when synthesising unwind info and DWARF CFI records.
- for (slice.items(.first_atom_index)) |first_atom_index| {
- var atom_index = first_atom_index orelse continue;
-
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
-
- if (atom.getFile() != null) {
- // Update each symbol contained within the atom
- var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (it.next()) |sym_loc| {
- const inner_sym = macho_file.getSymbolPtr(sym_loc);
- inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
- macho_file,
- atom_index,
- sym_loc.sym_index,
- );
- }
-
- // If there is a section alias, update it now too
- if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
- const alias = macho_file.getSymbolPtr(sym_loc);
- alias.n_value = sym.n_value;
- }
- }
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
- }
-
- if (macho_file.got_section_index) |sect_id| {
- const header = &macho_file.sections.items(.header)[sect_id];
- header.size = macho_file.got_table.count() * @sizeOf(u64);
- header.@"align" = 3;
- }
-
- if (macho_file.tlv_ptr_section_index) |sect_id| {
- const header = &macho_file.sections.items(.header)[sect_id];
- header.size = macho_file.tlv_ptr_table.count() * @sizeOf(u64);
- header.@"align" = 3;
- }
-
- const cpu_arch = target.cpu.arch;
-
- if (macho_file.stubs_section_index) |sect_id| {
- const header = &macho_file.sections.items(.header)[sect_id];
- header.size = macho_file.stub_table.count() * stubs.stubSize(cpu_arch);
- header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
- }
-
- if (macho_file.stub_helper_section_index) |sect_id| {
- const header = &macho_file.sections.items(.header)[sect_id];
- header.size = macho_file.stub_table.count() * stubs.stubHelperSize(cpu_arch) +
- stubs.stubHelperPreambleSize(cpu_arch);
- header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
- }
-
- if (macho_file.la_symbol_ptr_section_index) |sect_id| {
- const header = &macho_file.sections.items(.header)[sect_id];
- header.size = macho_file.stub_table.count() * @sizeOf(u64);
- header.@"align" = 3;
- }
-}
-
-fn allocateSegments(macho_file: *MachO) !void {
- for (macho_file.segments.items, 0..) |*segment, segment_index| {
- const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
- const base_size = if (is_text_segment)
- try load_commands.calcMinHeaderPad(macho_file, .{
- .segments = macho_file.segments.items,
- .dylibs = macho_file.dylibs.items,
- .referenced_dylibs = macho_file.referenced_dylibs.keys(),
- })
- else
- 0;
- try allocateSegment(macho_file, @as(u8, @intCast(segment_index)), base_size);
- }
-}
-
-fn getSegmentAllocBase(macho_file: *MachO, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } {
- if (segment_index > 0) {
- const prev_segment = macho_file.segments.items[segment_index - 1];
- return .{
- .vmaddr = prev_segment.vmaddr + prev_segment.vmsize,
- .fileoff = prev_segment.fileoff + prev_segment.filesize,
- };
- }
- return .{ .vmaddr = 0, .fileoff = 0 };
-}
-
-fn allocateSegment(macho_file: *MachO, segment_index: u8, init_size: u64) !void {
- const target = macho_file.base.comp.root_mod.resolved_target.result;
- const segment = &macho_file.segments.items[segment_index];
-
- if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation
-
- const base = getSegmentAllocBase(macho_file, segment_index);
- segment.vmaddr = base.vmaddr;
- segment.fileoff = base.fileoff;
- segment.filesize = init_size;
- segment.vmsize = init_size;
-
- // Allocate the sections according to their alignment at the beginning of the segment.
- const indexes = macho_file.getSectionIndexes(segment_index);
- var start = init_size;
-
- const slice = macho_file.sections.slice();
- for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| {
- const alignment = try math.powi(u32, 2, header.@"align");
- const start_aligned = mem.alignForward(u64, start, alignment);
- const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1));
-
- header.offset = if (header.isZerofill())
- 0
- else
- @as(u32, @intCast(segment.fileoff + start_aligned));
- header.addr = segment.vmaddr + start_aligned;
-
- if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| {
- var atom_index = first_atom_index;
-
- log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{
- n_sect,
- header.segName(),
- header.sectName(),
- });
-
- while (true) {
- const atom = macho_file.getAtom(atom_index);
- const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
- sym.n_value += header.addr;
- sym.n_sect = n_sect;
-
- log.debug(" ATOM(%{d}, '{s}') @{x}", .{
- atom.sym_index,
- macho_file.getSymbolName(atom.getSymbolWithLoc()),
- sym.n_value,
- });
-
- if (atom.getFile() != null) {
- // Update each symbol contained within the atom
- var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
- while (it.next()) |sym_loc| {
- const inner_sym = macho_file.getSymbolPtr(sym_loc);
- inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
- macho_file,
- atom_index,
- sym_loc.sym_index,
- );
- inner_sym.n_sect = n_sect;
- }
-
- // If there is a section alias, update it now too
- if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
- const alias = macho_file.getSymbolPtr(sym_loc);
- alias.n_value = sym.n_value;
- alias.n_sect = n_sect;
- }
- }
-
- if (atom.next_index) |next_index| {
- atom_index = next_index;
- } else break;
- }
- }
-
- start = start_aligned + header.size;
-
- if (!header.isZerofill()) {
- segment.filesize = start;
- }
- segment.vmsize = start;
- }
-
- const page_size = MachO.getPageSize(target.cpu.arch);
- segment.filesize = mem.alignForward(u64, segment.filesize, page_size);
- segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size);
-}
-
-const std = @import("std");
-const build_options = @import("build_options");
-const assert = std.debug.assert;
-const dwarf = std.dwarf;
-const fs = std.fs;
-const log = std.log.scoped(.link);
-const macho = std.macho;
-const math = std.math;
-const mem = std.mem;
-
-const aarch64 = @import("../../arch/aarch64/bits.zig");
-const calcUuid = @import("uuid.zig").calcUuid;
-const dead_strip = @import("dead_strip.zig");
-const eh_frame = @import("eh_frame.zig");
-const fat = @import("fat.zig");
-const link = @import("../../link.zig");
-const load_commands = @import("load_commands.zig");
-const stubs = @import("stubs.zig");
-const thunks = @import("thunks.zig");
-const trace = @import("../../tracy.zig").trace;
-
-const Allocator = mem.Allocator;
-const Archive = @import("Archive.zig");
-const Atom = @import("Atom.zig");
-const Cache = std.Build.Cache;
-const CodeSignature = @import("CodeSignature.zig");
-const Compilation = @import("../../Compilation.zig");
-const Dylib = @import("Dylib.zig");
-const MachO = @import("../MachO.zig");
-const Md5 = std.crypto.hash.Md5;
-const LibStub = @import("../tapi.zig").LibStub;
-const Object = @import("Object.zig");
-const Platform = load_commands.Platform;
-const Section = MachO.Section;
-const SymbolWithLoc = MachO.SymbolWithLoc;
-const TableSection = @import("../table_section.zig").TableSection;
-const Trie = @import("Trie.zig");
-const UnwindInfo = @import("UnwindInfo.zig");
diff --git a/src/main.zig b/src/main.zig
index fd650384f9..c8b0d95520 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -2823,9 +2823,7 @@ fn buildOutputType(
}
// After this point, resolved_frameworks is used instead of frameworks.
- if (create_module.resolved_options.output_mode == .Obj and
- (target.ofmt == .coff or target.ofmt == .macho))
- {
+ if (create_module.resolved_options.output_mode == .Obj and target.ofmt == .coff) {
const total_obj_count = create_module.c_source_files.items.len +
@intFromBool(root_src_file != null) +
create_module.rc_source_files.items.len +
@@ -3220,6 +3218,7 @@ fn buildOutputType(
.clang_passthrough_mode = clang_passthrough_mode,
.clang_preprocessor_mode = clang_preprocessor_mode,
.version = optional_version,
+ .compatibility_version = compatibility_version,
.libc_installation = if (create_module.libc_installation) |*lci| lci else null,
.verbose_cc = verbose_cc,
.verbose_link = verbose_link,