authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-01-24 20:05:03+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-24 20:05:03+01:00
logdcaf43674e35372e1d28ab12c4c4ff9af9f3d646
treea4bb41d3e608d9a5f93d0c4521bf083a3d925e25
parent92211135f1424aaca0de131cfe3646248730b1ca
parent0fd0b765fa84a40446663928db1d3f9a63b7a98d
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #18576 from ziglang/new-macho

macho: upstream a complete rewrite of the MachO linker

117 files changed, 16195 insertions(+), 15404 deletions(-)

CMakeLists.txt+8-3
......@@ -603,20 +603,25 @@ set(ZIG_STAGE2_SOURCES
603603 "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
604604 "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig"
605605 "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
606 "${CMAKE_SOURCE_DIR}/src/link/MachO/InternalObject.zig"
606607 "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
607608 "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig"
608 "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
609 "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
609610 "${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig"
611 "${CMAKE_SOURCE_DIR}/src/link/MachO/ZigObject.zig"
612 "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
610613 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig"
611614 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig"
612 "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"
615 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Trie.zig"
613616 "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig"
614617 "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
618 "${CMAKE_SOURCE_DIR}/src/link/MachO/file.zig"
615619 "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig"
616620 "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"
621 "${CMAKE_SOURCE_DIR}/src/link/MachO/relocatable.zig"
622 "${CMAKE_SOURCE_DIR}/src/link/MachO/synthetic.zig"
617623 "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"
618624 "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig"
619 "${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig"
620625 "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig"
621626 "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"
622627 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
build.zig+1-1
......@@ -623,7 +623,7 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerStepOptions) *std.Build.St
623623 .root_source_file = .{ .path = "src/main.zig" },
624624 .target = options.target,
625625 .optimize = options.optimize,
626 .max_rss = 7_000_000_000,
626 .max_rss = 8_000_000_000,
627627 .strip = options.strip,
628628 .sanitize_thread = options.sanitize_thread,
629629 .single_threaded = options.single_threaded,
lib/std/macho.zig+16
......@@ -1240,6 +1240,22 @@ pub const FAT_MAGIC_64 = 0xcafebabf;
12401240/// NXSwapLong(FAT_MAGIC_64)
12411241pub const FAT_CIGAM_64 = 0xbfbafeca;
12421242
1243/// Segment flags
1244/// The file contents for this segment is for the high part of the VM space, the low part
1245/// is zero filled (for stacks in core files).
1246pub const SG_HIGHVM = 0x1;
1247/// This segment is the VM that is allocated by a fixed VM library, for overlap checking in
1248/// the link editor.
1249pub const SG_FVMLIB = 0x2;
1250/// This segment has nothing that was relocated in it and nothing relocated to it, that is
1251/// it maybe safely replaced without relocation.
1252pub const SG_NORELOC = 0x4;
1253/// This segment is protected. If the segment starts at file offset 0, the
1254/// first page of the segment is not protected. All other pages of the segment are protected.
1255pub const SG_PROTECTED_VERSION_1 = 0x8;
1256/// This segment is made read-only after fixups
1257pub const SG_READ_ONLY = 0x10;
1258
12431259/// The flags field of a section structure is separated into two parts a section
12441260/// type and section attributes. The section types are mutually exclusive (it
12451261/// can only have one type) but the section attributes are not (it may have more
src/Compilation.zig+1
......@@ -1542,6 +1542,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
15421542 .darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
15431543 .frameworks = options.frameworks,
15441544 .lib_dirs = options.lib_dirs,
1545 .framework_dirs = options.framework_dirs,
15451546 .rpath_list = options.rpath_list,
15461547 .symbol_wrap_set = options.symbol_wrap_set,
15471548 .allow_shlib_undefined = options.linker_allow_shlib_undefined,
src/arch/aarch64/CodeGen.zig+44-36
......@@ -4013,10 +4013,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
40134013 .import => unreachable,
40144014 };
40154015 const atom_index = switch (self.bin_file.tag) {
4016 .macho => blk: {
4017 const macho_file = self.bin_file.cast(link.File.MachO).?;
4018 const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
4019 break :blk macho_file.getAtom(atom).getSymbolIndex().?;
4016 .macho => {
4017 // const macho_file = self.bin_file.cast(link.File.MachO).?;
4018 // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
4019 // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
4020 @panic("TODO store");
40204021 },
40214022 .coff => blk: {
40224023 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
43214322 const got_addr = @as(u32, @intCast(sym.zigGotAddress(elf_file)));
43224323 try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr });
43234324 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
4324 const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
4325 const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
4326 try self.genSetReg(Type.u64, .x30, .{
4327 .linker_load = .{
4328 .type = .got,
4329 .sym_index = sym_index,
4330 },
4331 });
4325 _ = macho_file;
4326 @panic("TODO airCall");
4327 // const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
4328 // const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
4329 // try self.genSetReg(Type.u64, .x30, .{
4330 // .linker_load = .{
4331 // .type = .got,
4332 // .sym_index = sym_index,
4333 // },
4334 // });
43324335 } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
43334336 const atom = try coff_file.getOrCreateAtomForDecl(func.owner_decl);
43344337 const sym_index = coff_file.getAtom(atom).getSymbolIndex().?;
......@@ -4352,18 +4355,20 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
43524355 const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name);
43534356 const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name);
43544357 if (self.bin_file.cast(link.File.MachO)) |macho_file| {
4355 const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name);
4356 const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
4357 const atom_index = macho_file.getAtom(atom).getSymbolIndex().?;
4358 _ = try self.addInst(.{
4359 .tag = .call_extern,
4360 .data = .{
4361 .relocation = .{
4362 .atom_index = atom_index,
4363 .sym_index = sym_index,
4364 },
4365 },
4366 });
4358 _ = macho_file;
4359 @panic("TODO airCall");
4360 // const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name);
4361 // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
4362 // const atom_index = macho_file.getAtom(atom).getSymbolIndex().?;
4363 // _ = try self.addInst(.{
4364 // .tag = .call_extern,
4365 // .data = .{
4366 // .relocation = .{
4367 // .atom_index = atom_index,
4368 // .sym_index = sym_index,
4369 // },
4370 // },
4371 // });
43674372 } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
43684373 const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name);
43694374 try self.genSetReg(Type.u64, .x30, .{
......@@ -5532,10 +5537,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
55325537 .import => unreachable,
55335538 };
55345539 const atom_index = switch (self.bin_file.tag) {
5535 .macho => blk: {
5536 const macho_file = self.bin_file.cast(link.File.MachO).?;
5537 const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5538 break :blk macho_file.getAtom(atom).getSymbolIndex().?;
5540 .macho => {
5541 // const macho_file = self.bin_file.cast(link.File.MachO).?;
5542 // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5543 // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
5544 @panic("TODO genSetStack");
55395545 },
55405546 .coff => blk: {
55415547 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
56535659 .import => .load_memory_import,
56545660 };
56555661 const atom_index = switch (self.bin_file.tag) {
5656 .macho => blk: {
5657 const macho_file = self.bin_file.cast(link.File.MachO).?;
5658 const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5659 break :blk macho_file.getAtom(atom).getSymbolIndex().?;
5662 .macho => {
5663 @panic("TODO genSetReg");
5664 // const macho_file = self.bin_file.cast(link.File.MachO).?;
5665 // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5666 // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
56605667 },
56615668 .coff => blk: {
56625669 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
58505857 .import => unreachable,
58515858 };
58525859 const atom_index = switch (self.bin_file.tag) {
5853 .macho => blk: {
5854 const macho_file = self.bin_file.cast(link.File.MachO).?;
5855 const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5856 break :blk macho_file.getAtom(atom).getSymbolIndex().?;
5860 .macho => {
5861 @panic("TODO genSetStackArgument");
5862 // const macho_file = self.bin_file.cast(link.File.MachO).?;
5863 // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl);
5864 // break :blk macho_file.getAtom(atom).getSymbolIndex().?;
58575865 },
58585866 .coff => blk: {
58595867 const coff_file = self.bin_file.cast(link.File.Coff).?;
src/arch/aarch64/Emit.zig+43-37
......@@ -677,6 +677,7 @@ fn mirDebugEpilogueBegin(emit: *Emit) !void {
677677fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
678678 assert(emit.mir.instructions.items(.tag)[inst] == .call_extern);
679679 const relocation = emit.mir.instructions.items(.data)[inst].relocation;
680 _ = relocation;
680681
681682 const offset = blk: {
682683 const offset = @as(u32, @intCast(emit.code.items.len));
......@@ -684,19 +685,22 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
684685 try emit.writeInstruction(Instruction.bl(0));
685686 break :blk offset;
686687 };
688 _ = offset;
687689
688690 if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
689 // Add relocation to the decl.
690 const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?;
691 const target = macho_file.getGlobalByIndex(relocation.sym_index);
692 try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
693 .type = .branch,
694 .target = target,
695 .offset = offset,
696 .addend = 0,
697 .pcrel = true,
698 .length = 2,
699 });
691 _ = macho_file;
692 @panic("TODO mirCallExtern");
693 // // Add relocation to the decl.
694 // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?;
695 // const target = macho_file.getGlobalByIndex(relocation.sym_index);
696 // try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
697 // .type = .branch,
698 // .target = target,
699 // .offset = offset,
700 // .addend = 0,
701 // .pcrel = true,
702 // .length = 2,
703 // });
700704 } else if (emit.bin_file.cast(link.File.Coff)) |_| {
701705 unreachable; // Calling imports is handled via `.load_memory_import`
702706 } else {
......@@ -900,32 +904,34 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
900904 }
901905
902906 if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
903 const Atom = link.File.MachO.Atom;
904 const Relocation = Atom.Relocation;
905 const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?;
906 try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{
907 .target = .{ .sym_index = data.sym_index },
908 .offset = offset,
909 .addend = 0,
910 .pcrel = true,
911 .length = 2,
912 .type = switch (tag) {
913 .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page,
914 .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page,
915 else => unreachable,
916 },
917 }, .{
918 .target = .{ .sym_index = data.sym_index },
919 .offset = offset + 4,
920 .addend = 0,
921 .pcrel = false,
922 .length = 2,
923 .type = switch (tag) {
924 .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff,
925 .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff,
926 else => unreachable,
927 },
928 } });
907 _ = macho_file;
908 @panic("TODO mirLoadMemoryPie");
909 // const Atom = link.File.MachO.Atom;
910 // const Relocation = Atom.Relocation;
911 // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?;
912 // try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{
913 // .target = .{ .sym_index = data.sym_index },
914 // .offset = offset,
915 // .addend = 0,
916 // .pcrel = true,
917 // .length = 2,
918 // .type = switch (tag) {
919 // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page,
920 // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page,
921 // else => unreachable,
922 // },
923 // }, .{
924 // .target = .{ .sym_index = data.sym_index },
925 // .offset = offset + 4,
926 // .addend = 0,
927 // .pcrel = false,
928 // .length = 2,
929 // .type = switch (tag) {
930 // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff,
931 // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff,
932 // else => unreachable,
933 // },
934 // } });
929935 } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
930936 const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index, .file = null }).?;
931937 const target = switch (tag) {
src/arch/x86_64/CodeGen.zig+54-74
......@@ -139,8 +139,7 @@ const Owner = union(enum) {
139139 if (ctx.bin_file.cast(link.File.Elf)) |elf_file| {
140140 return elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index);
141141 } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
142 const atom = try macho_file.getOrCreateAtomForDecl(decl_index);
143 return macho_file.getAtom(atom).getSymbolIndex().?;
142 return macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index);
144143 } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| {
145144 const atom = try coff_file.getOrCreateAtomForDecl(decl_index);
146145 return coff_file.getAtom(atom).getSymbolIndex().?;
......@@ -153,9 +152,8 @@ const Owner = union(enum) {
153152 return elf_file.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(elf_file, lazy_sym) catch |err|
154153 ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
155154 } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
156 const atom = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
157 return ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
158 return macho_file.getAtom(atom).getSymbolIndex().?;
155 return macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, lazy_sym) catch |err|
156 ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
159157 } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| {
160158 const atom = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
161159 return ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
......@@ -10951,9 +10949,9 @@ fn genCall(self: *Self, info: union(enum) {
1095110949 try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
1095210950 try self.asmRegister(.{ ._, .call }, .rax);
1095310951 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
10954 const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
10955 const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
10956 try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index });
10952 const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, func.owner_decl);
10953 const sym = macho_file.getSymbol(sym_index);
10954 try self.genSetReg(.rax, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } });
1095710955 try self.asmRegister(.{ ._, .call }, .rax);
1095810956 } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
1095910957 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
1350913507 },
1351013508 .lea_symbol => |sym_index| {
1351113509 const atom_index = try self.owner.getSymbolIndex(self);
13512 if (self.bin_file.cast(link.File.Elf)) |_| {
13513 try self.asmRegisterMemory(
13514 .{ ._, .lea },
13515 dst_reg.to64(),
13516 .{
13517 .base = .{ .reloc = .{
13518 .atom_index = atom_index,
13519 .sym_index = sym_index.sym,
13520 } },
13521 .mod = .{ .rm = .{
13522 .size = .qword,
13523 .disp = sym_index.off,
13524 } },
13525 },
13526 );
13527 } else return self.fail("TODO emit symbol sequence on {s}", .{
13528 @tagName(self.bin_file.tag),
13529 });
13510 switch (self.bin_file.tag) {
13511 .elf, .macho => {
13512 try self.asmRegisterMemory(
13513 .{ ._, .lea },
13514 dst_reg.to64(),
13515 .{
13516 .base = .{ .reloc = .{
13517 .atom_index = atom_index,
13518 .sym_index = sym_index.sym,
13519 } },
13520 .mod = .{ .rm = .{
13521 .size = .qword,
13522 .disp = sym_index.off,
13523 } },
13524 },
13525 );
13526 },
13527 else => return self.fail("TODO emit symbol sequence on {s}", .{
13528 @tagName(self.bin_file.tag),
13529 }),
13530 }
1353013531 },
1353113532 .lea_direct, .lea_got => |sym_index| {
1353213533 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
1355013551 } },
1355113552 });
1355213553 },
13553 .lea_tlv => |sym_index| {
13554 const atom_index = try self.owner.getSymbolIndex(self);
13555 if (self.bin_file.cast(link.File.MachO)) |_| {
13556 _ = try self.addInst(.{
13557 .tag = .lea,
13558 .ops = .tlv_reloc,
13559 .data = .{ .rx = .{
13560 .r1 = .rdi,
13561 .payload = try self.addExtra(bits.Symbol{
13562 .atom_index = atom_index,
13563 .sym_index = sym_index,
13564 }),
13565 } },
13566 });
13567 // TODO: spill registers before calling
13568 try self.asmMemory(.{ ._, .call }, .{
13569 .base = .{ .reg = .rdi },
13570 .mod = .{ .rm = .{ .size = .qword } },
13571 });
13572 try self.genSetReg(dst_reg.to64(), Type.usize, .{ .register = .rax });
13573 } else return self.fail("TODO emit ptr to TLV sequence on {s}", .{
13574 @tagName(self.bin_file.tag),
13575 });
13576 },
13554 .lea_tlv => unreachable, // TODO: remove this
1357713555 .air_ref => |src_ref| try self.genSetReg(dst_reg, ty, try self.resolveInst(src_ref)),
1357813556 }
1357913557}
......@@ -13810,13 +13788,12 @@ fn genExternSymbolRef(
1381013788 else => unreachable,
1381113789 }
1381213790 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
13813 const global_index = try macho_file.getGlobalSymbol(callee, lib);
1381413791 _ = try self.addInst(.{
1381513792 .tag = .call,
1381613793 .ops = .extern_fn_reloc,
1381713794 .data = .{ .reloc = .{
1381813795 .atom_index = atom_index,
13819 .sym_index = link.File.MachO.global_symbol_bit | global_index,
13796 .sym_index = try macho_file.getGlobalSymbol(callee, lib),
1382013797 } },
1382113798 });
1382213799 } else return self.fail("TODO implement calling extern functions", .{});
......@@ -13906,12 +13883,12 @@ fn genLazySymbolRef(
1390613883 else => unreachable,
1390713884 }
1390813885 } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
13909 const atom_index = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
13886 const sym_index = macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, lazy_sym) catch |err|
1391013887 return self.fail("{s} creating lazy symbol", .{@errorName(err)});
13911 const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
13888 const sym = macho_file.getSymbol(sym_index);
1391213889 switch (tag) {
13913 .lea, .call => try self.genSetReg(reg, Type.usize, .{ .lea_got = sym_index }),
13914 .mov => try self.genSetReg(reg, Type.usize, .{ .load_got = sym_index }),
13890 .lea, .call => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }),
13891 .mov => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }),
1391513892 else => unreachable,
1391613893 }
1391713894 switch (tag) {
......@@ -16074,24 +16051,27 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
1607416051 if (!gop.found_existing) gop.value_ptr.* = InstTracking.init(init: {
1607516052 const const_mcv = try self.genTypedValue(.{ .ty = ty, .val = Value.fromInterned(ip_index) });
1607616053 switch (const_mcv) {
16077 .lea_tlv => |tlv_sym| if (self.bin_file.cast(link.File.Elf)) |_| {
16078 if (self.mod.pic) {
16079 try self.spillRegisters(&.{ .rdi, .rax });
16080 } else {
16081 try self.spillRegisters(&.{.rax});
16082 }
16083 const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{
16084 .size = 8,
16085 .alignment = .@"8",
16086 }));
16087 try self.genSetMem(
16088 .{ .frame = frame_index },
16089 0,
16090 Type.usize,
16091 .{ .lea_symbol = .{ .sym = tlv_sym } },
16092 );
16093 break :init .{ .load_frame = .{ .index = frame_index } };
16094 } else break :init const_mcv,
16054 .lea_tlv => |tlv_sym| switch (self.bin_file.tag) {
16055 .elf, .macho => {
16056 if (self.mod.pic) {
16057 try self.spillRegisters(&.{ .rdi, .rax });
16058 } else {
16059 try self.spillRegisters(&.{.rax});
16060 }
16061 const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{
16062 .size = 8,
16063 .alignment = .@"8",
16064 }));
16065 try self.genSetMem(
16066 .{ .frame = frame_index },
16067 0,
16068 Type.usize,
16069 .{ .lea_symbol = .{ .sym = tlv_sym } },
16070 );
16071 break :init .{ .load_frame = .{ .index = frame_index } };
16072 },
16073 else => break :init const_mcv,
16074 },
1609516075 else => break :init const_mcv,
1609616076 }
1609716077 });
src/arch/x86_64/Emit.zig+47-32
......@@ -50,19 +50,20 @@ pub fn emitMir(emit: *Emit) Error!void {
5050 });
5151 } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
5252 // Add relocation to the decl.
53 const atom_index =
54 macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
55 const target = if (link.File.MachO.global_symbol_bit & symbol.sym_index != 0)
56 macho_file.getGlobalByIndex(link.File.MachO.global_symbol_mask & symbol.sym_index)
57 else
58 link.File.MachO.SymbolWithLoc{ .sym_index = symbol.sym_index };
59 try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
60 .type = .branch,
61 .target = target,
53 const atom = macho_file.getSymbol(symbol.atom_index).getAtom(macho_file).?;
54 const sym_index = macho_file.getZigObject().?.symbols.items[symbol.sym_index];
55 try atom.addReloc(macho_file, .{
56 .tag = .@"extern",
6257 .offset = end_offset - 4,
58 .target = sym_index,
6359 .addend = 0,
64 .pcrel = true,
65 .length = 2,
60 .type = .branch,
61 .meta = .{
62 .pcrel = true,
63 .has_subtractor = false,
64 .length = 2,
65 .symbolnum = 0,
66 },
6667 });
6768 } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| {
6869 // Add relocation to the decl.
......@@ -149,33 +150,47 @@ pub fn emitMir(emit: *Emit) Error!void {
149150 });
150151 }
151152 }
153 } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
154 const is_obj_or_static_lib = switch (emit.lower.output_mode) {
155 .Exe => false,
156 .Obj => true,
157 .Lib => emit.lower.link_mode == .Static,
158 };
159 const atom = macho_file.getSymbol(data.atom_index).getAtom(macho_file).?;
160 const sym_index = macho_file.getZigObject().?.symbols.items[data.sym_index];
161 const sym = macho_file.getSymbol(sym_index);
162 if (sym.flags.needs_zig_got and !is_obj_or_static_lib) {
163 _ = try sym.getOrCreateZigGotEntry(sym_index, macho_file);
164 }
165 const @"type": link.File.MachO.Relocation.Type = if (sym.flags.needs_zig_got and !is_obj_or_static_lib)
166 .zig_got_load
167 else if (sym.flags.needs_got)
168 .got_load
169 else if (sym.flags.tlv)
170 .tlv
171 else
172 .signed;
173 try atom.addReloc(macho_file, .{
174 .tag = .@"extern",
175 .offset = @intCast(end_offset - 4),
176 .target = sym_index,
177 .addend = 0,
178 .type = @"type",
179 .meta = .{
180 .pcrel = true,
181 .has_subtractor = false,
182 .length = 2,
183 .symbolnum = 0,
184 },
185 });
152186 } else unreachable,
153187 .linker_got,
154188 .linker_direct,
155189 .linker_import,
156 .linker_tlv,
157190 => |symbol| if (emit.lower.bin_file.cast(link.File.Elf)) |_| {
158191 unreachable;
159 } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
160 const atom_index =
161 macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
162 const target = if (link.File.MachO.global_symbol_bit & symbol.sym_index != 0)
163 macho_file.getGlobalByIndex(link.File.MachO.global_symbol_mask & symbol.sym_index)
164 else
165 link.File.MachO.SymbolWithLoc{ .sym_index = symbol.sym_index };
166 try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
167 .type = switch (lowered_relocs[0].target) {
168 .linker_got => .got,
169 .linker_direct => .signed,
170 .linker_tlv => .tlv,
171 else => unreachable,
172 },
173 .target = target,
174 .offset = @intCast(end_offset - 4),
175 .addend = 0,
176 .pcrel = true,
177 .length = 2,
178 });
192 } else if (emit.lower.bin_file.cast(link.File.MachO)) |_| {
193 unreachable;
179194 } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| {
180195 const atom_index = coff_file.getAtomIndexForSymbol(.{
181196 .sym_index = symbol.atom_index,
src/arch/x86_64/Lower.zig+99-78
......@@ -14,7 +14,7 @@ result_relocs_len: u8 = undefined,
1414result_insts: [
1515 std.mem.max(usize, &.{
1616 1, // non-pseudo instructions
17 3, // TLS local dynamic (LD) sequence in PIC mode
17 3, // (ELF only) TLS local dynamic (LD) sequence in PIC mode
1818 2, // cmovcc: cmovcc \ cmovcc
1919 3, // setcc: setcc \ setcc \ logicop
2020 2, // jcc: jcc \ jcc
......@@ -32,7 +32,7 @@ result_relocs: [
3232 2, // jcc: jcc \ jcc
3333 2, // test \ jcc \ probe \ sub \ jmp
3434 1, // probe \ sub \ jcc
35 3, // TLS local dynamic (LD) sequence in PIC mode
35 3, // (ELF only) TLS local dynamic (LD) sequence in PIC mode
3636 })
3737]Reloc = undefined,
3838
......@@ -62,7 +62,6 @@ pub const Reloc = struct {
6262 linker_got: bits.Symbol,
6363 linker_direct: bits.Symbol,
6464 linker_import: bits.Symbol,
65 linker_tlv: bits.Symbol,
6665 };
6766};
6867
......@@ -326,18 +325,6 @@ fn reloc(lower: *Lower, target: Reloc.Target) Immediate {
326325 return Immediate.s(0);
327326}
328327
329fn needsZigGot(sym: bits.Symbol, ctx: *link.File) bool {
330 const elf_file = ctx.cast(link.File.Elf).?;
331 const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
332 return elf_file.symbol(sym_index).flags.needs_zig_got;
333}
334
335fn isTls(sym: bits.Symbol, ctx: *link.File) bool {
336 const elf_file = ctx.cast(link.File.Elf).?;
337 const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
338 return elf_file.symbol(sym_index).flags.is_tls;
339}
340
341328fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void {
342329 const is_obj_or_static_lib = switch (lower.output_mode) {
343330 .Exe => false,
......@@ -359,80 +346,115 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand)
359346 assert(mem_op.sib.disp == 0);
360347 assert(mem_op.sib.scale_index.scale == 0);
361348
362 if (isTls(sym, lower.bin_file)) {
363 // TODO handle extern TLS vars, i.e., emit GD model
364 if (lower.pic) {
365 // Here, we currently assume local dynamic TLS vars, and so
366 // we emit LD model.
367 _ = lower.reloc(.{ .linker_tlsld = sym });
368 lower.result_insts[lower.result_insts_len] =
369 try Instruction.new(.none, .lea, &[_]Operand{
370 .{ .reg = .rdi },
371 .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
372 });
373 lower.result_insts_len += 1;
374 if (lower.bin_file.cast(link.File.Elf)) |elf_file| {
349 if (lower.bin_file.cast(link.File.Elf)) |elf_file| {
350 const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index);
351 const elf_sym = elf_file.symbol(sym_index);
352
353 if (elf_sym.flags.is_tls) {
354 // TODO handle extern TLS vars, i.e., emit GD model
355 if (lower.pic) {
356 // Here, we currently assume local dynamic TLS vars, and so
357 // we emit LD model.
358 _ = lower.reloc(.{ .linker_tlsld = sym });
359 lower.result_insts[lower.result_insts_len] =
360 try Instruction.new(.none, .lea, &[_]Operand{
361 .{ .reg = .rdi },
362 .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
363 });
364 lower.result_insts_len += 1;
375365 _ = lower.reloc(.{ .linker_extern_fn = .{
376366 .atom_index = sym.atom_index,
377367 .sym_index = try elf_file.getGlobalSymbol("__tls_get_addr", null),
378368 } });
369 lower.result_insts[lower.result_insts_len] =
370 try Instruction.new(.none, .call, &[_]Operand{
371 .{ .imm = Immediate.s(0) },
372 });
373 lower.result_insts_len += 1;
374 _ = lower.reloc(.{ .linker_dtpoff = sym });
375 emit_mnemonic = .lea;
376 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
377 .base = .{ .reg = .rax },
378 .disp = std.math.minInt(i32),
379 }) };
380 } else {
381 // Since we are linking statically, we emit LE model directly.
382 lower.result_insts[lower.result_insts_len] =
383 try Instruction.new(.none, .mov, &[_]Operand{
384 .{ .reg = .rax },
385 .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) },
386 });
387 lower.result_insts_len += 1;
388 _ = lower.reloc(.{ .linker_reloc = sym });
389 emit_mnemonic = .lea;
390 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
391 .base = .{ .reg = .rax },
392 .disp = std.math.minInt(i32),
393 }) };
379394 }
395 }
396
397 _ = lower.reloc(.{ .linker_reloc = sym });
398 break :op if (lower.pic) switch (mnemonic) {
399 .lea => {
400 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
401 },
402 .mov => {
403 if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea;
404 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
405 },
406 else => unreachable,
407 } else switch (mnemonic) {
408 .call => break :op if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) .{
409 .imm = Immediate.s(0),
410 } else .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
411 .base = .{ .reg = .ds },
412 }) },
413 .lea => {
414 emit_mnemonic = .mov;
415 break :op .{ .imm = Immediate.s(0) };
416 },
417 .mov => {
418 if (is_obj_or_static_lib and elf_sym.flags.needs_zig_got) emit_mnemonic = .lea;
419 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
420 .base = .{ .reg = .ds },
421 }) };
422 },
423 else => unreachable,
424 };
425 } else if (lower.bin_file.cast(link.File.MachO)) |macho_file| {
426 const sym_index = macho_file.getZigObject().?.symbols.items[sym.sym_index];
427 const macho_sym = macho_file.getSymbol(sym_index);
428
429 if (macho_sym.flags.tlv) {
430 _ = lower.reloc(.{ .linker_reloc = sym });
380431 lower.result_insts[lower.result_insts_len] =
381 try Instruction.new(.none, .call, &[_]Operand{
382 .{ .imm = Immediate.s(0) },
432 try Instruction.new(.none, .mov, &[_]Operand{
433 .{ .reg = .rdi },
434 .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) },
383435 });
384436 lower.result_insts_len += 1;
385 _ = lower.reloc(.{ .linker_dtpoff = sym });
386 emit_mnemonic = .lea;
387 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
388 .base = .{ .reg = .rax },
389 .disp = std.math.minInt(i32),
390 }) };
391 } else {
392 // Since we are linking statically, we emit LE model directly.
393437 lower.result_insts[lower.result_insts_len] =
394 try Instruction.new(.none, .mov, &[_]Operand{
395 .{ .reg = .rax },
396 .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) },
438 try Instruction.new(.none, .call, &[_]Operand{
439 .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .rdi } }) },
397440 });
398441 lower.result_insts_len += 1;
399 _ = lower.reloc(.{ .linker_reloc = sym });
400 emit_mnemonic = .lea;
401 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
402 .base = .{ .reg = .rax },
403 .disp = std.math.minInt(i32),
404 }) };
442 emit_mnemonic = .mov;
443 break :op .{ .reg = .rax };
405444 }
406 }
407445
408 _ = lower.reloc(.{ .linker_reloc = sym });
409 break :op if (lower.pic) switch (mnemonic) {
410 .lea => {
411 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
412 },
413 .mov => {
414 if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) emit_mnemonic = .lea;
415 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
416 },
417 else => unreachable,
418 } else switch (mnemonic) {
419 .call => break :op if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) .{
420 .imm = Immediate.s(0),
421 } else .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
422 .base = .{ .reg = .ds },
423 }) },
424 .lea => {
425 emit_mnemonic = .mov;
426 break :op .{ .imm = Immediate.s(0) };
427 },
428 .mov => {
429 if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) emit_mnemonic = .lea;
430 break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{
431 .base = .{ .reg = .ds },
432 }) };
433 },
434 else => unreachable,
435 };
446 _ = lower.reloc(.{ .linker_reloc = sym });
447 break :op switch (mnemonic) {
448 .lea => {
449 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
450 },
451 .mov => {
452 if (is_obj_or_static_lib and macho_sym.flags.needs_zig_got) emit_mnemonic = .lea;
453 break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) };
454 },
455 else => unreachable,
456 };
457 }
436458 },
437459 },
438460 };
......@@ -584,14 +606,13 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
584606 .extern_fn_reloc => &.{
585607 .{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc }) },
586608 },
587 .got_reloc, .direct_reloc, .import_reloc, .tlv_reloc => ops: {
609 .got_reloc, .direct_reloc, .import_reloc => ops: {
588610 const reg = inst.data.rx.r1;
589611 const extra = lower.mir.extraData(bits.Symbol, inst.data.rx.payload).data;
590612 _ = lower.reloc(switch (inst.ops) {
591613 .got_reloc => .{ .linker_got = extra },
592614 .direct_reloc => .{ .linker_direct = extra },
593615 .import_reloc => .{ .linker_import = extra },
594 .tlv_reloc => .{ .linker_tlv = extra },
595616 else => unreachable,
596617 });
597618 break :ops &.{
src/codegen.zig+17-10
......@@ -985,19 +985,21 @@ fn genDeclRef(
985985 return GenResult.mcv(.{ .load_symbol = sym.esym_index });
986986 } else if (lf.cast(link.File.MachO)) |macho_file| {
987987 if (is_extern) {
988 // TODO make this part of getGlobalSymbol
989988 const name = zcu.intern_pool.stringToSlice(decl.name);
990 const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
991 defer gpa.free(sym_name);
992 const global_index = try macho_file.addUndefined(sym_name, .{ .add_got = true });
993 return GenResult.mcv(.{ .load_got = link.File.MachO.global_symbol_bit | global_index });
989 const lib_name = if (decl.getOwnedVariable(zcu)) |ov|
990 zcu.intern_pool.stringToSliceUnwrap(ov.lib_name)
991 else
992 null;
993 const sym_index = try macho_file.getGlobalSymbol(name, lib_name);
994 macho_file.getSymbol(macho_file.getZigObject().?.symbols.items[sym_index]).flags.needs_got = true;
995 return GenResult.mcv(.{ .load_symbol = sym_index });
994996 }
995 const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index);
996 const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
997 const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index);
998 const sym = macho_file.getSymbol(sym_index);
997999 if (is_threadlocal) {
998 return GenResult.mcv(.{ .load_tlv = sym_index });
1000 return GenResult.mcv(.{ .load_tlv = sym.nlist_idx });
9991001 }
1000 return GenResult.mcv(.{ .load_got = sym_index });
1002 return GenResult.mcv(.{ .load_symbol = sym.nlist_idx });
10011003 } else if (lf.cast(link.File.Coff)) |coff_file| {
10021004 if (is_extern) {
10031005 const name = zcu.intern_pool.stringToSlice(decl.name);
......@@ -1041,7 +1043,12 @@ fn genUnnamedConst(
10411043 const local = elf_file.symbol(local_sym_index);
10421044 return GenResult.mcv(.{ .load_symbol = local.esym_index });
10431045 },
1044 .macho, .coff => {
1046 .macho => {
1047 const macho_file = lf.cast(link.File.MachO).?;
1048 const local = macho_file.getSymbol(local_sym_index);
1049 return GenResult.mcv(.{ .load_symbol = local.nlist_idx });
1050 },
1051 .coff => {
10451052 return GenResult.mcv(.{ .load_direct = local_sym_index });
10461053 },
10471054 .plan9 => {
src/link.zig+1
......@@ -133,6 +133,7 @@ pub const File = struct {
133133
134134 // TODO: remove this. libraries are resolved by the frontend.
135135 lib_dirs: []const []const u8,
136 framework_dirs: []const []const u8,
136137 rpath_list: []const []const u8,
137138
138139 /// (Zig compiler development) Enable dumping of linker's state as JSON.
src/link/MachO.zig+3430-4799
......@@ -1,5 +1,4 @@
1base: File,
2entry_name: ?[]const u8,
1base: link.File,
32
43/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
54llvm_object: ?*LlvmObject = null,
......@@ -7,7 +6,28 @@ llvm_object: ?*LlvmObject = null,
76/// Debug symbols bundle (or dSym).
87d_sym: ?DebugSymbols = null,
98
10mode: Mode,
9/// A list of all input files.
10/// Index of each input file also encodes the priority or precedence of one input file
11/// over another.
12files: std.MultiArrayList(File.Entry) = .{},
13zig_object: ?File.Index = null,
14internal_object: ?File.Index = null,
15objects: std.ArrayListUnmanaged(File.Index) = .{},
16dylibs: std.ArrayListUnmanaged(File.Index) = .{},
17
18segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
19sections: std.MultiArrayList(Section) = .{},
20
21symbols: std.ArrayListUnmanaged(Symbol) = .{},
22symbols_extra: std.ArrayListUnmanaged(u32) = .{},
23symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{},
24globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{},
25/// This table will be populated after `scanRelocs` has run.
26/// Key is symbol index.
27undefs: std.AutoHashMapUnmanaged(Symbol.Index, std.ArrayListUnmanaged(Atom.Index)) = .{},
28/// Global symbols we need to resolve for the link to succeed.
29undefined_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
30boundary_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
1131
1232dyld_info_cmd: macho.dyld_info_command = .{},
1333symtab_cmd: macho.symtab_command = .{},
......@@ -17,133 +37,90 @@ data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE },
1737uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
1838codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
1939
20objects: std.ArrayListUnmanaged(Object) = .{},
21archives: std.ArrayListUnmanaged(Archive) = .{},
22dylibs: std.ArrayListUnmanaged(Dylib) = .{},
23dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
24referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
25
26segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
27sections: std.MultiArrayList(Section) = .{},
28
29pagezero_segment_cmd_index: ?u8 = null,
30header_segment_cmd_index: ?u8 = null,
31text_segment_cmd_index: ?u8 = null,
32data_const_segment_cmd_index: ?u8 = null,
33data_segment_cmd_index: ?u8 = null,
34linkedit_segment_cmd_index: ?u8 = null,
35
36text_section_index: ?u8 = null,
37data_const_section_index: ?u8 = null,
38data_section_index: ?u8 = null,
39bss_section_index: ?u8 = null,
40thread_vars_section_index: ?u8 = null,
41thread_data_section_index: ?u8 = null,
42thread_bss_section_index: ?u8 = null,
43eh_frame_section_index: ?u8 = null,
44unwind_info_section_index: ?u8 = null,
45stubs_section_index: ?u8 = null,
46stub_helper_section_index: ?u8 = null,
47got_section_index: ?u8 = null,
48la_symbol_ptr_section_index: ?u8 = null,
49tlv_ptr_section_index: ?u8 = null,
50
51locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
52globals: std.ArrayListUnmanaged(SymbolWithLoc) = .{},
53resolver: std.StringHashMapUnmanaged(u32) = .{},
54unresolved: std.AutoArrayHashMapUnmanaged(u32, void) = .{},
55
56locals_free_list: std.ArrayListUnmanaged(u32) = .{},
57globals_free_list: std.ArrayListUnmanaged(u32) = .{},
58
59dyld_stub_binder_index: ?u32 = null,
60dyld_private_atom_index: ?Atom.Index = null,
61
62strtab: StringTable = .{},
63
64got_table: TableSection(SymbolWithLoc) = .{},
65stub_table: TableSection(SymbolWithLoc) = .{},
66tlv_ptr_table: TableSection(SymbolWithLoc) = .{},
67
68thunk_table: std.AutoHashMapUnmanaged(Atom.Index, thunks.Thunk.Index) = .{},
69thunks: std.ArrayListUnmanaged(thunks.Thunk) = .{},
70
71segment_table_dirty: bool = false,
72got_table_count_dirty: bool = false,
73got_table_contents_dirty: bool = false,
74stub_table_count_dirty: bool = false,
75stub_table_contents_dirty: bool = false,
76stub_helper_preamble_allocated: bool = false,
40pagezero_seg_index: ?u8 = null,
41text_seg_index: ?u8 = null,
42linkedit_seg_index: ?u8 = null,
43text_sect_index: ?u8 = null,
44data_sect_index: ?u8 = null,
45got_sect_index: ?u8 = null,
46stubs_sect_index: ?u8 = null,
47stubs_helper_sect_index: ?u8 = null,
48la_symbol_ptr_sect_index: ?u8 = null,
49tlv_ptr_sect_index: ?u8 = null,
50eh_frame_sect_index: ?u8 = null,
51unwind_info_sect_index: ?u8 = null,
52objc_stubs_sect_index: ?u8 = null,
53
54mh_execute_header_index: ?Symbol.Index = null,
55mh_dylib_header_index: ?Symbol.Index = null,
56dyld_private_index: ?Symbol.Index = null,
57dyld_stub_binder_index: ?Symbol.Index = null,
58dso_handle_index: ?Symbol.Index = null,
59objc_msg_send_index: ?Symbol.Index = null,
60entry_index: ?Symbol.Index = null,
7761
7862/// List of atoms that are either synthetic or map directly to the Zig source program.
7963atoms: std.ArrayListUnmanaged(Atom) = .{},
80
81/// Table of atoms indexed by the symbol index.
82atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{},
83
84/// Table of unnamed constants associated with a parent `Decl`.
85/// We store them here so that we can free the constants whenever the `Decl`
86/// needs updating or is freed.
87///
88/// For example,
89///
90/// ```zig
91/// const Foo = struct{
92/// a: u8,
93/// };
94///
95/// pub fn main() void {
96/// var foo = Foo{ .a = 1 };
97/// _ = foo;
98/// }
99/// ```
100///
101/// value assigned to label `foo` is an unnamed constant belonging/associated
102/// with `Decl` `main`, and lives as long as that `Decl`.
103unnamed_const_atoms: UnnamedConstTable = .{},
104anon_decls: AnonDeclTable = .{},
105
106/// A table of relocations indexed by the owning them `Atom`.
107/// Note that once we refactor `Atom`'s lifetime and ownership rules,
108/// this will be a table indexed by index into the list of Atoms.
109relocs: RelocationTable = .{},
110/// TODO I do not have time to make this right but this will go once
111/// MachO linker is rewritten more-or-less to feature the same resolution
112/// mechanism as the ELF linker.
113actions: ActionTable = .{},
114
115/// A table of rebases indexed by the owning them `Atom`.
116/// Note that once we refactor `Atom`'s lifetime and ownership rules,
117/// this will be a table indexed by index into the list of Atoms.
118rebases: RebaseTable = .{},
119
120/// A table of bindings indexed by the owning them `Atom`.
121/// Note that once we refactor `Atom`'s lifetime and ownership rules,
122/// this will be a table indexed by index into the list of Atoms.
123bindings: BindingTable = .{},
124
125/// Table of tracked LazySymbols.
126lazy_syms: LazySymbolTable = .{},
127
128/// Table of tracked Decls.
129decls: DeclTable = .{},
130
131/// Table of threadlocal variables descriptors.
132/// They are emitted in the `__thread_vars` section.
133tlv_table: TlvSymbolTable = .{},
134
135/// Hot-code swapping state.
136hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
137
64thunks: std.ArrayListUnmanaged(Thunk) = .{},
65unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record) = .{},
66
67/// String interning table
68strings: StringTable = .{},
69
70/// Output synthetic sections
71symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
72strtab: std.ArrayListUnmanaged(u8) = .{},
73indsymtab: Indsymtab = .{},
74got: GotSection = .{},
75zig_got: ZigGotSection = .{},
76stubs: StubsSection = .{},
77stubs_helper: StubsHelperSection = .{},
78objc_stubs: ObjcStubsSection = .{},
79la_symbol_ptr: LaSymbolPtrSection = .{},
80tlv_ptr: TlvPtrSection = .{},
81rebase: RebaseSection = .{},
82bind: BindSection = .{},
83weak_bind: WeakBindSection = .{},
84lazy_bind: LazyBindSection = .{},
85export_trie: ExportTrieSection = .{},
86unwind_info: UnwindInfo = .{},
87
88/// Tracked loadable segments during incremental linking.
89zig_text_seg_index: ?u8 = null,
90zig_got_seg_index: ?u8 = null,
91zig_const_seg_index: ?u8 = null,
92zig_data_seg_index: ?u8 = null,
93zig_bss_seg_index: ?u8 = null,
94
95/// Tracked section headers with incremental updates to Zig object.
96zig_text_sect_index: ?u8 = null,
97zig_got_sect_index: ?u8 = null,
98zig_const_sect_index: ?u8 = null,
99zig_data_sect_index: ?u8 = null,
100zig_bss_sect_index: ?u8 = null,
101
102has_tlv: bool = false,
103binds_to_weak: bool = false,
104weak_defines: bool = false,
105
106/// Options
107/// SDK layout
138108sdk_layout: ?SdkLayout,
139109/// Size of the __PAGEZERO segment.
140pagezero_vmsize: u64,
110pagezero_size: ?u64,
141111/// Minimum space for future expansion of the load commands.
142headerpad_size: u32,
112headerpad_size: ?u32,
143113/// Set enough space as if all paths were MATPATHLEN.
144114headerpad_max_install_names: bool,
145115/// Remove dylibs that are unreachable by the entry point or exported symbols.
146116dead_strip_dylibs: bool,
117/// Treatment of undefined symbols
118undefined_treatment: UndefinedTreatment,
119/// Resolved list of library search directories
120lib_dirs: []const []const u8,
121/// Resolved list of framework search directories
122framework_dirs: []const []const u8,
123/// List of input frameworks
147124frameworks: []const Framework,
148125/// Install name for the dylib.
149126/// TODO: unify with soname
......@@ -151,6 +128,18 @@ install_name: ?[]const u8,
151128/// Path to entitlements file.
152129entitlements: ?[]const u8,
153130compatibility_version: ?std.SemanticVersion,
131/// Entry name
132entry_name: ?[]const u8,
133platform: Platform,
134sdk_version: ?std.SemanticVersion,
135/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
136no_implicit_dylibs: bool = false,
137/// Whether the linker should parse and always force load objects containing ObjC in archives.
138// TODO: in Zig we currently take -ObjC as always on
139force_load_objc: bool = true,
140
141/// Hot-code swapping state.
142hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
154143
155144/// When adding a new field, remember to update `hashAddFrameworks`.
156145pub const Framework = struct {
......@@ -167,14 +156,6 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
167156 }
168157}
169158
170/// The filesystem layout of darwin SDK elements.
171pub const SdkLayout = enum {
172 /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }.
173 sdk,
174 /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }.
175 vendored,
176};
177
178159pub fn createEmpty(
179160 arena: Allocator,
180161 comp: *Compilation,
......@@ -183,27 +164,22 @@ pub fn createEmpty(
183164) !*MachO {
184165 const target = comp.root_mod.resolved_target.result;
185166 assert(target.ofmt == .macho);
186 const use_llvm = comp.config.use_llvm;
167
187168 const gpa = comp.gpa;
169 const use_llvm = comp.config.use_llvm;
170 const opt_zcu = comp.module;
188171 const optimize_mode = comp.root_mod.optimize_mode;
189172 const output_mode = comp.config.output_mode;
190173 const link_mode = comp.config.link_mode;
191174
192 // TODO: get rid of zld mode
193 const mode: Mode = if (use_llvm or !comp.config.have_zcu or comp.cache_use == .whole)
194 .zld
195 else
196 .incremental;
197
198 // If using "zld mode" to link, this code should produce an object file so that it
199 // can be passed to "zld mode". TODO: get rid of "zld mode".
200175 // If using LLVM to generate the object file for the zig compilation unit,
201176 // we need a place to put the object file so that it can be subsequently
202177 // handled.
203 const zcu_object_sub_path = if (mode != .zld and !use_llvm)
178 const zcu_object_sub_path = if (!use_llvm)
204179 null
205180 else
206181 try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
182 const allow_shlib_undefined = options.allow_shlib_undefined orelse false;
207183
208184 const self = try arena.create(MachO);
209185 self.* = .{
......@@ -215,15 +191,14 @@ pub fn createEmpty(
215191 .gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
216192 .print_gc_sections = options.print_gc_sections,
217193 .stack_size = options.stack_size orelse 16777216,
218 .allow_shlib_undefined = options.allow_shlib_undefined orelse false,
194 .allow_shlib_undefined = allow_shlib_undefined,
219195 .file = null,
220196 .disable_lld_caching = options.disable_lld_caching,
221197 .build_id = options.build_id,
222198 .rpath_list = options.rpath_list,
223199 },
224 .mode = mode,
225 .pagezero_vmsize = options.pagezero_size orelse default_pagezero_vmsize,
226 .headerpad_size = options.headerpad_size orelse default_headerpad_size,
200 .pagezero_size = options.pagezero_size,
201 .headerpad_size = options.headerpad_size,
227202 .headerpad_max_install_names = options.headerpad_max_install_names,
228203 .dead_strip_dylibs = options.dead_strip_dylibs,
229204 .sdk_layout = options.darwin_sdk_layout,
......@@ -237,68 +212,77 @@ pub fn createEmpty(
237212 .enabled => default_entry_symbol_name,
238213 .named => |name| name,
239214 },
215 .platform = Platform.fromTarget(target),
216 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
217 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
218 .lib_dirs = options.lib_dirs,
219 .framework_dirs = options.framework_dirs,
240220 };
241221 if (use_llvm and comp.config.have_zcu) {
242222 self.llvm_object = try LlvmObject.create(arena, comp);
243223 }
244224 errdefer self.base.destroy();
245225
246 log.debug("selected linker mode '{s}'", .{@tagName(self.mode)});
247
248 if (mode == .zld) {
249 // TODO: get rid of zld mode
250 return self;
251 }
252
253 const file = try emit.directory.handle.createFile(emit.sub_path, .{
226 self.base.file = try emit.directory.handle.createFile(emit.sub_path, .{
254227 .truncate = true,
255228 .read = true,
256229 .mode = link.File.determineMode(false, output_mode, link_mode),
257230 });
258 self.base.file = file;
259231
260 if (comp.config.debug_format != .strip and comp.module != null) {
261 // Create dSYM bundle.
262 log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
263
264 const d_sym_path = try std.fmt.allocPrint(
265 arena,
266 "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
267 .{emit.sub_path},
268 );
232 // Append null file
233 try self.files.append(gpa, .null);
234 // Atom at index 0 is reserved as null atom
235 try self.atoms.append(gpa, .{});
236 // Append empty string to string tables
237 try self.strings.buffer.append(gpa, 0);
238 try self.strtab.append(gpa, 0);
239 // Append null symbols
240 try self.symbols.append(gpa, .{});
241 try self.symbols_extra.append(gpa, 0);
242
243 if (opt_zcu) |zcu| {
244 if (!use_llvm) {
245 const index: File.Index = @intCast(try self.files.addOne(gpa));
246 self.files.set(index, .{ .zig_object = .{
247 .index = index,
248 .path = try std.fmt.allocPrint(arena, "{s}.o", .{std.fs.path.stem(
249 zcu.main_mod.root_src_path,
250 )}),
251 } });
252 self.zig_object = index;
253 try self.getZigObject().?.init(self);
254 try self.initMetadata(.{
255 .symbol_count_hint = options.symbol_count_hint,
256 .program_code_size_hint = options.program_code_size_hint,
257 });
269258
270 var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{});
271 defer d_sym_bundle.close();
259 // TODO init dwarf
272260
273 const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
274 .truncate = false,
275 .read = true,
276 });
261 // if (comp.config.debug_format != .strip) {
262 // // Create dSYM bundle.
263 // log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
277264
278 self.d_sym = .{
279 .allocator = gpa,
280 .dwarf = link.File.Dwarf.init(&self.base, .dwarf32),
281 .file = d_sym_file,
282 };
283 }
265 // const d_sym_path = try std.fmt.allocPrint(
266 // arena,
267 // "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
268 // .{emit.sub_path},
269 // );
284270
285 // Index 0 is always a null symbol.
286 try self.locals.append(gpa, .{
287 .n_strx = 0,
288 .n_type = 0,
289 .n_sect = 0,
290 .n_desc = 0,
291 .n_value = 0,
292 });
293 try self.strtab.buffer.append(gpa, 0);
271 // var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{});
272 // defer d_sym_bundle.close();
294273
295 try self.populateMissingMetadata(.{
296 .symbol_count_hint = options.symbol_count_hint,
297 .program_code_size_hint = options.program_code_size_hint,
298 });
274 // const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
275 // .truncate = false,
276 // .read = true,
277 // });
299278
300 if (self.d_sym) |*d_sym| {
301 try d_sym.populateMissingMetadata(self);
279 // self.d_sym = .{
280 // .allocator = gpa,
281 // .dwarf = link.File.Dwarf.init(&self.base, .dwarf32),
282 // .file = d_sym_file,
283 // };
284 // }
285 }
302286 }
303287
304288 return self;
......@@ -315,27 +299,75 @@ pub fn open(
315299 return createEmpty(arena, comp, emit, options);
316300}
317301
318pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
319 const comp = self.base.comp;
320 const gpa = comp.gpa;
321 const output_mode = comp.config.output_mode;
302pub fn deinit(self: *MachO) void {
303 const gpa = self.base.comp.gpa;
322304
323 if (output_mode == .Lib and comp.config.link_mode == .Static) {
324 if (build_options.have_llvm) {
325 return self.base.linkAsArchive(arena, prog_node);
326 } else {
327 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
328 comp.link_errors.appendAssumeCapacity(.{
329 .msg = try gpa.dupe(u8, "TODO: non-LLVM archiver for MachO object files"),
330 });
331 return error.FlushFailure;
305 if (self.llvm_object) |llvm_object| llvm_object.deinit();
306
307 if (self.d_sym) |*d_sym| {
308 d_sym.deinit();
309 }
310
311 for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) {
312 .null => {},
313 .zig_object => data.zig_object.deinit(gpa),
314 .internal => data.internal.deinit(gpa),
315 .object => data.object.deinit(gpa),
316 .dylib => data.dylib.deinit(gpa),
317 };
318 self.files.deinit(gpa);
319 self.objects.deinit(gpa);
320 self.dylibs.deinit(gpa);
321
322 self.segments.deinit(gpa);
323 for (self.sections.items(.atoms)) |*list| {
324 list.deinit(gpa);
325 }
326 self.sections.deinit(gpa);
327
328 self.symbols.deinit(gpa);
329 self.symbols_extra.deinit(gpa);
330 self.symbols_free_list.deinit(gpa);
331 self.globals.deinit(gpa);
332 {
333 var it = self.undefs.iterator();
334 while (it.next()) |entry| {
335 entry.value_ptr.deinit(gpa);
332336 }
337 self.undefs.deinit(gpa);
338 }
339 self.undefined_symbols.deinit(gpa);
340 self.boundary_symbols.deinit(gpa);
341
342 self.strings.deinit(gpa);
343 self.symtab.deinit(gpa);
344 self.strtab.deinit(gpa);
345 self.got.deinit(gpa);
346 self.zig_got.deinit(gpa);
347 self.stubs.deinit(gpa);
348 self.objc_stubs.deinit(gpa);
349 self.tlv_ptr.deinit(gpa);
350 self.rebase.deinit(gpa);
351 self.bind.deinit(gpa);
352 self.weak_bind.deinit(gpa);
353 self.lazy_bind.deinit(gpa);
354 self.export_trie.deinit(gpa);
355 self.unwind_info.deinit(gpa);
356
357 self.atoms.deinit(gpa);
358 for (self.thunks.items) |*thunk| {
359 thunk.deinit(gpa);
333360 }
361 self.thunks.deinit(gpa);
362 self.unwind_records.deinit(gpa);
363}
334364
335 switch (self.mode) {
336 .zld => return zld.linkWithZld(self, arena, prog_node),
337 .incremental => return self.flushModule(arena, prog_node),
365pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void {
366 // TODO: I think this is just a temp and can be removed once we can emit static archives
367 if (self.base.isStaticLib() and build_options.have_llvm) {
368 return self.base.linkAsArchive(arena, prog_node);
338369 }
370 try self.flushModule(arena, prog_node);
339371}
340372
341373pub 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
347379
348380 if (self.llvm_object) |llvm_object| {
349381 try self.base.emitLlvmObject(arena, llvm_object, prog_node);
350 return;
382 // TODO: I think this is just a temp and can be removed once we can emit static archives
383 if (self.base.isStaticLib() and build_options.have_llvm) return;
351384 }
352385
353386 var sub_prog_node = prog_node.start("MachO Flush", 0);
354387 sub_prog_node.activate();
355388 defer sub_prog_node.end();
356389
357 const output_mode = comp.config.output_mode;
358 const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
359390 const target = comp.root_mod.resolved_target.result;
391 _ = target;
392 const directory = self.base.emit.directory;
393 const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
394 const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
395 if (fs.path.dirname(full_out_path)) |dirname| {
396 break :blk try fs.path.join(arena, &.{ dirname, path });
397 } else {
398 break :blk path;
399 }
400 } else null;
360401
361 if (self.lazy_syms.getPtr(.none)) |metadata| {
362 // Most lazy symbols can be updated on first use, but
363 // anyerror needs to wait for everything to be flushed.
364 if (metadata.text_state != .unused) self.updateLazySymbolAtom(
365 File.LazySymbol.initDecl(.code, null, module),
366 metadata.text_atom,
367 self.text_section_index.?,
368 ) catch |err| return switch (err) {
369 error.CodegenFail => error.FlushFailure,
370 else => |e| e,
402 // --verbose-link
403 if (comp.verbose_link) try self.dumpArgv(comp);
404
405 if (self.getZigObject()) |zo| try zo.flushModule(self);
406 if (self.base.isStaticLib()) return self.flushStaticLib(comp, module_obj_path);
407 if (self.base.isObject()) return relocatable.flush(self, comp, module_obj_path);
408
409 var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
410 defer positionals.deinit();
411
412 try positionals.ensureUnusedCapacity(comp.objects.len);
413 positionals.appendSliceAssumeCapacity(comp.objects);
414
415 // This is a set of object files emitted by clang in a single `build-exe` invocation.
416 // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
417 // in this set.
418 try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);
419 for (comp.c_object_table.keys()) |key| {
420 positionals.appendAssumeCapacity(.{ .path = key.status.success.object_path });
421 }
422
423 if (module_obj_path) |path| try positionals.append(.{ .path = path });
424
425 for (positionals.items) |obj| {
426 self.parsePositional(obj.path, obj.must_link) catch |err| switch (err) {
427 error.MalformedObject,
428 error.MalformedArchive,
429 error.MalformedDylib,
430 error.InvalidCpuArch,
431 error.InvalidTarget,
432 => continue, // already reported
433 error.UnknownFileType => try self.reportParseError(obj.path, "unknown file type for an object file", .{}),
434 else => |e| try self.reportParseError(
435 obj.path,
436 "unexpected error: parsing input file failed with error {s}",
437 .{@errorName(e)},
438 ),
371439 };
372 if (metadata.data_const_state != .unused) self.updateLazySymbolAtom(
373 File.LazySymbol.initDecl(.const_data, null, module),
374 metadata.data_const_atom,
375 self.data_const_section_index.?,
376 ) catch |err| return switch (err) {
377 error.CodegenFail => error.FlushFailure,
378 else => |e| e,
379 };
380 }
381 for (self.lazy_syms.values()) |*metadata| {
382 if (metadata.text_state != .unused) metadata.text_state = .flushed;
383 if (metadata.data_const_state != .unused) metadata.data_const_state = .flushed;
384 }
385
386 if (self.d_sym) |*d_sym| {
387 try d_sym.dwarf.flushModule(module);
388440 }
389441
390 var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
391 try self.resolveLibSystem(arena, comp, &libs);
392
393 self.base.releaseLock();
442 var system_libs = std.ArrayList(SystemLib).init(gpa);
443 defer system_libs.deinit();
394444
395 for (self.dylibs.items) |*dylib| {
396 dylib.deinit(gpa);
445 // libs
446 try system_libs.ensureUnusedCapacity(comp.system_libs.values().len);
447 for (comp.system_libs.values()) |info| {
448 system_libs.appendAssumeCapacity(.{
449 .needed = info.needed,
450 .weak = info.weak,
451 .path = info.path.?,
452 });
397453 }
398 self.dylibs.clearRetainingCapacity();
399 self.dylibs_map.clearRetainingCapacity();
400 self.referenced_dylibs.clearRetainingCapacity();
401454
402 var dependent_libs = std.fifo.LinearFifo(DylibReExportInfo, .Dynamic).init(arena);
455 // frameworks
456 try system_libs.ensureUnusedCapacity(self.frameworks.len);
457 for (self.frameworks) |info| {
458 system_libs.appendAssumeCapacity(.{
459 .needed = info.needed,
460 .weak = info.weak,
461 .path = info.path,
462 });
463 }
403464
404 for (libs.keys(), libs.values()) |path, lib| {
405 const in_file = try std.fs.cwd().openFile(path, .{});
406 defer in_file.close();
465 // libc++ dep
466 if (comp.config.link_libcpp) {
467 try system_libs.ensureUnusedCapacity(2);
468 system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
469 system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
470 }
407471
408 var parse_ctx = ParseErrorCtx.init(gpa);
409 defer parse_ctx.deinit();
472 // libc/libSystem dep
473 self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) {
474 error.MissingLibSystem => {}, // already reported
475 else => |e| return e, // TODO: convert into an error
476 };
410477
411 self.parseLibrary(
412 in_file,
413 path,
414 lib,
415 false,
416 false,
417 null,
418 &dependent_libs,
419 &parse_ctx,
420 ) catch |err| try self.handleAndReportParseError(path, err, &parse_ctx);
478 for (system_libs.items) |lib| {
479 self.parseLibrary(lib, false) catch |err| switch (err) {
480 error.MalformedArchive,
481 error.MalformedDylib,
482 error.InvalidCpuArch,
483 => continue, // already reported
484 error.UnknownFileType => try self.reportParseError(lib.path, "unknown file type for a library", .{}),
485 else => |e| try self.reportParseError(
486 lib.path,
487 "unexpected error: parsing library failed with error {s}",
488 .{@errorName(e)},
489 ),
490 };
421491 }
422492
423 try self.parseDependentLibs(&dependent_libs);
493 // Finally, link against compiler_rt.
494 const compiler_rt_path: ?[]const u8 = blk: {
495 if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
496 if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
497 break :blk null;
498 };
499 if (compiler_rt_path) |path| {
500 self.parsePositional(path, false) catch |err| switch (err) {
501 error.MalformedObject,
502 error.MalformedArchive,
503 error.InvalidCpuArch,
504 error.InvalidTarget,
505 => {}, // already reported
506 error.UnknownFileType => try self.reportParseError(path, "unknown file type for a library", .{}),
507 else => |e| try self.reportParseError(
508 path,
509 "unexpected error: parsing input file failed with error {s}",
510 .{@errorName(e)},
511 ),
512 };
513 }
424514
425 try self.resolveSymbols();
515 if (comp.link_errors.items.len > 0) return error.FlushFailure;
426516
427 if (self.getEntryPoint() == null) {
428 comp.link_error_flags.no_entry_point_found = true;
429 }
430 if (self.unresolved.count() > 0) {
431 try self.reportUndefined();
432 return error.FlushFailure;
517 for (self.dylibs.items) |index| {
518 self.getFile(index).?.dylib.umbrella = index;
433519 }
434520
435 {
436 var it = self.actions.iterator();
437 while (it.next()) |entry| {
438 const global_index = entry.key_ptr.*;
439 const global = self.globals.items[global_index];
440 const flags = entry.value_ptr.*;
441 if (flags.add_got) try self.addGotEntry(global);
442 if (flags.add_stub) try self.addStubEntry(global);
443 }
521 if (self.dylibs.items.len > 0) {
522 self.parseDependentDylibs() catch |err| {
523 switch (err) {
524 error.MissingLibraryDependencies => {},
525 else => |e| try self.reportUnexpectedError(
526 "unexpected error while parsing dependent libraries: {s}",
527 .{@errorName(e)},
528 ),
529 }
530 return error.FlushFailure;
531 };
444532 }
445533
446 try self.createDyldPrivateAtom();
447 try self.writeStubHelperPreamble();
448
449 if (output_mode == .Exe and self.getEntryPoint() != null) {
450 const global = self.getEntryPoint().?;
451 if (self.getSymbol(global).undf()) {
452 // We do one additional check here in case the entry point was found in one of the dylibs.
453 // (I actually have no idea what this would imply but it is a possible outcome and so we
454 // support it.)
455 try self.addStubEntry(global);
456 }
534 for (self.dylibs.items) |index| {
535 const dylib = self.getFile(index).?.dylib;
536 if (!dylib.explicit and !dylib.hoisted) continue;
537 try dylib.initSymbols(self);
457538 }
458539
459 try self.allocateSpecialSymbols();
540 {
541 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
542 self.files.set(index, .{ .internal = .{ .index = index } });
543 self.internal_object = index;
544 }
460545
461 for (self.relocs.keys()) |atom_index| {
462 const relocs = self.relocs.get(atom_index).?;
463 const needs_update = for (relocs.items) |reloc| {
464 if (reloc.dirty) break true;
465 } else false;
546 try self.addUndefinedGlobals();
547 try self.resolveSymbols();
548 try self.resolveSyntheticSymbols();
466549
467 if (!needs_update) continue;
550 try self.convertTentativeDefinitions();
551 try self.createObjcSections();
552 try self.claimUnresolved();
468553
469 const atom = self.getAtom(atom_index);
470 const sym = atom.getSymbol(self);
471 const section = self.sections.get(sym.n_sect - 1).header;
472 const file_offset = section.offset + sym.n_value - section.addr;
554 if (self.base.gc_sections) {
555 try dead_strip.gcAtoms(self);
556 }
473557
474 var code = std.ArrayList(u8).init(gpa);
475 defer code.deinit();
476 try code.resize(math.cast(usize, atom.size) orelse return error.Overflow);
558 self.checkDuplicates() catch |err| switch (err) {
559 error.HasDuplicates => return error.FlushFailure,
560 else => |e| {
561 try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
562 return e;
563 },
564 };
477565
478 const amt = try self.base.file.?.preadAll(code.items, file_offset);
479 if (amt != code.items.len) return error.InputOutput;
566 try self.markImportsAndExports();
567 self.deadStripDylibs();
480568
481 try self.writeAtom(atom_index, code.items);
569 for (self.dylibs.items, 1..) |index, ord| {
570 const dylib = self.getFile(index).?.dylib;
571 dylib.ordinal = @intCast(ord);
482572 }
483573
484 // Update GOT if it got moved in memory.
485 if (self.got_table_contents_dirty) {
486 for (self.got_table.entries.items, 0..) |entry, i| {
487 if (!self.got_table.lookup.contains(entry)) continue;
488 // TODO: write all in one go rather than incrementally.
489 try self.writeOffsetTableEntry(i);
490 }
491 self.got_table_contents_dirty = false;
492 }
574 self.scanRelocs() catch |err| switch (err) {
575 error.HasUndefinedSymbols => return error.FlushFailure,
576 else => |e| {
577 try self.reportUnexpectedError("unexpected error while scanning relocations", .{});
578 return e;
579 },
580 };
493581
494 // Update stubs if we moved any section in memory.
495 // TODO: we probably don't need to update all sections if only one got moved.
496 if (self.stub_table_contents_dirty) {
497 for (self.stub_table.entries.items, 0..) |entry, i| {
498 if (!self.stub_table.lookup.contains(entry)) continue;
499 // TODO: write all in one go rather than incrementally.
500 try self.writeStubTableEntry(i);
582 try self.initOutputSections();
583 try self.initSyntheticSections();
584 try self.sortSections();
585 try self.addAtomsToSections();
586 try self.calcSectionSizes();
587 try self.generateUnwindInfo();
588 try self.initSegments();
589
590 try self.allocateSections();
591 self.allocateSegments();
592 self.allocateAtoms();
593 self.allocateSyntheticSymbols();
594 try self.allocateLinkeditSegment();
595
596 state_log.debug("{}", .{self.dumpState()});
597
598 try self.initDyldInfoSections();
599
600 // Beyond this point, everything has been allocated a virtual address and we can resolve
601 // the relocations, and commit objects to file.
602 if (self.getZigObject()) |zo| {
603 var has_resolve_error = false;
604
605 for (zo.atoms.items) |atom_index| {
606 const atom = self.getAtom(atom_index) orelse continue;
607 if (!atom.flags.alive) continue;
608 const sect = &self.sections.items(.header)[atom.out_n_sect];
609 if (sect.isZerofill()) continue;
610 if (mem.indexOf(u8, sect.segName(), "ZIG") == null) continue; // Non-Zig sections are handled separately
611 // TODO: we will resolve and write ZigObject's TLS data twice:
612 // once here, and once in writeAtoms
613 const code = zo.getAtomDataAlloc(self, gpa, atom.*) catch |err| switch (err) {
614 error.InputOutput => {
615 try self.reportUnexpectedError("fetching code for '{s}' failed", .{
616 atom.getName(self),
617 });
618 return error.FlushFailure;
619 },
620 else => |e| {
621 try self.reportUnexpectedError("unexpected error while fetching code for '{s}': {s}", .{
622 atom.getName(self),
623 @errorName(e),
624 });
625 return error.FlushFailure;
626 },
627 };
628 defer gpa.free(code);
629 const file_offset = sect.offset + atom.value - sect.addr;
630 atom.resolveRelocs(self, code) catch |err| switch (err) {
631 error.ResolveFailed => has_resolve_error = true,
632 else => |e| {
633 try self.reportUnexpectedError("unexpected error while resolving relocations", .{});
634 return e;
635 },
636 };
637 try self.base.file.?.pwriteAll(code, file_offset);
501638 }
502 self.stub_table_contents_dirty = false;
503 }
504639
505 if (build_options.enable_logging) {
506 self.logSymtab();
507 self.logSections();
508 self.logAtoms();
640 if (has_resolve_error) return error.FlushFailure;
509641 }
510642
511 try self.writeLinkeditSegmentData();
512
513 var codesig: ?CodeSignature = if (self.requiresCodeSignature()) blk: {
643 self.writeAtoms() catch |err| switch (err) {
644 error.ResolveFailed => return error.FlushFailure,
645 else => |e| {
646 try self.reportUnexpectedError("unexpected error while resolving relocations", .{});
647 return e;
648 },
649 };
650 try self.writeUnwindInfo();
651 try self.finalizeDyldInfoSections();
652 try self.writeSyntheticSections();
653
654 var off = math.cast(u32, self.getLinkeditSegment().fileoff) orelse return error.Overflow;
655 off = try self.writeDyldInfoSections(off);
656 off = mem.alignForward(u32, off, @alignOf(u64));
657 off = try self.writeFunctionStarts(off);
658 off = mem.alignForward(u32, off, @alignOf(u64));
659 off = try self.writeDataInCode(self.getTextSegment().vmaddr, off);
660 try self.calcSymtabSize();
661 off = mem.alignForward(u32, off, @alignOf(u64));
662 off = try self.writeSymtab(off);
663 off = mem.alignForward(u32, off, @alignOf(u32));
664 off = try self.writeIndsymtab(off);
665 off = mem.alignForward(u32, off, @alignOf(u64));
666 off = try self.writeStrtab(off);
667
668 self.getLinkeditSegment().filesize = off - self.getLinkeditSegment().fileoff;
669
670 var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: {
514671 // Preallocate space for the code signature.
515672 // We need to do this at this stage so that we have the load commands with proper values
516673 // written out to the file.
517674 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
518675 // where the code signature goes into.
519 var codesig = CodeSignature.init(getPageSize(target.cpu.arch));
520 codesig.code_directory.ident = self.base.emit.sub_path;
521 if (self.entitlements) |path| {
522 try codesig.addEntitlements(gpa, path);
523 }
676 var codesig = CodeSignature.init(self.getPageSize());
677 codesig.code_directory.ident = fs.path.basename(full_out_path);
678 if (self.entitlements) |path| try codesig.addEntitlements(gpa, path);
524679 try self.writeCodeSignaturePadding(&codesig);
525680 break :blk codesig;
526681 } else null;
527682 defer if (codesig) |*csig| csig.deinit(gpa);
528683
529 // Write load commands
530 var lc_buffer = std.ArrayList(u8).init(arena);
531 const lc_writer = lc_buffer.writer();
532
533 try self.writeSegmentHeaders(lc_writer);
534 try lc_writer.writeStruct(self.dyld_info_cmd);
535 try lc_writer.writeStruct(self.symtab_cmd);
536 try lc_writer.writeStruct(self.dysymtab_cmd);
537 try load_commands.writeDylinkerLC(lc_writer);
538
539 switch (output_mode) {
540 .Exe => blk: {
541 const seg_id = self.header_segment_cmd_index.?;
542 const seg = self.segments.items[seg_id];
543 const global = self.getEntryPoint() orelse break :blk;
544 const sym = self.getSymbol(global);
545
546 const addr: u64 = if (sym.undf())
547 // In this case, the symbol has been resolved in one of dylibs and so we point
548 // to the stub as its vmaddr value.
549 self.getStubsEntryAddress(global).?
550 else
551 sym.n_value;
684 self.getLinkeditSegment().vmsize = mem.alignForward(
685 u64,
686 self.getLinkeditSegment().filesize,
687 self.getPageSize(),
688 );
552689
553 try lc_writer.writeStruct(macho.entry_point_command{
554 .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
555 .stacksize = self.base.stack_size,
556 });
557 },
558 .Lib => if (comp.config.link_mode == .Dynamic) {
559 try load_commands.writeDylibIdLC(self, lc_writer);
560 },
561 else => {},
690 const ncmds, const sizeofcmds, const uuid_cmd_offset = try self.writeLoadCommands();
691 try self.writeHeader(ncmds, sizeofcmds);
692 try self.writeUuid(uuid_cmd_offset, self.requiresCodeSig());
693
694 if (codesig) |*csig| {
695 try self.writeCodeSignature(csig); // code signing always comes last
696 const emit = self.base.emit;
697 try invalidateKernelCache(emit.directory.handle, emit.sub_path);
562698 }
699}
563700
564 try load_commands.writeRpathLCs(self, lc_writer);
565 try lc_writer.writeStruct(macho.source_version_command{
566 .version = 0,
567 });
568 {
569 const platform = Platform.fromTarget(target);
570 const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(self);
571 if (platform.isBuildVersionCompatible()) {
572 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
573 } else if (platform.isVersionMinCompatible()) {
574 try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
701/// --verbose-link output
702fn dumpArgv(self: *MachO, comp: *Compilation) !void {
703 const gpa = self.base.comp.gpa;
704 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
705 defer arena_allocator.deinit();
706 const arena = arena_allocator.allocator();
707
708 const directory = self.base.emit.directory;
709 const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
710 const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
711 if (fs.path.dirname(full_out_path)) |dirname| {
712 break :blk try fs.path.join(arena, &.{ dirname, path });
713 } else {
714 break :blk path;
575715 }
576 }
716 } else null;
577717
578 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
579 try lc_writer.writeStruct(self.uuid_cmd);
718 var argv = std.ArrayList([]const u8).init(arena);
580719
581 try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer);
720 try argv.append("zig");
582721
583 if (codesig != null) {
584 try lc_writer.writeStruct(self.codesig_cmd);
722 if (self.base.isStaticLib()) {
723 try argv.append("ar");
724 } else {
725 try argv.append("ld");
585726 }
586727
587 const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
588 try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
589 try self.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
590 try self.writeUuid(comp, uuid_cmd_offset, codesig != null);
591
592 if (codesig) |*csig| {
593 try self.writeCodeSignature(comp, csig); // code signing always comes last
594 const emit = self.base.emit;
595 try invalidateKernelCache(emit.directory.handle, emit.sub_path);
728 if (self.base.isObject()) {
729 try argv.append("-r");
596730 }
597731
598 if (self.d_sym) |*d_sym| {
599 // Flush debug symbols bundle.
600 try d_sym.flushModule(self);
601 }
602}
732 try argv.append("-o");
733 try argv.append(full_out_path);
603734
604/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
605/// Any change to the binary will effectively invalidate the kernel's cache
606/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
607/// linking we're modifying a binary in-place, this will end up with the kernel
608/// killing it on every subsequent run. To circumvent it, we will copy the file
609/// into a new inode, remove the original file, and rename the copy to match
610/// the original file. This is super messy, but there doesn't seem any other
611/// way to please the XNU.
612pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void {
613 if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
614 try dir.copyFile(sub_path, dir, sub_path, .{});
735 if (self.base.isRelocatable()) {
736 for (comp.objects) |obj| {
737 try argv.append(obj.path);
738 }
739
740 for (comp.c_object_table.keys()) |key| {
741 try argv.append(key.status.success.object_path);
742 }
743
744 if (module_obj_path) |p| {
745 try argv.append(p);
746 }
747 } else {
748 if (!self.base.isStatic()) {
749 try argv.append("-dynamic");
750 }
751
752 if (self.base.isDynLib()) {
753 try argv.append("-dylib");
754
755 if (self.install_name) |install_name| {
756 try argv.append("-install_name");
757 try argv.append(install_name);
758 }
759 }
760
761 try argv.append("-platform_version");
762 try argv.append(@tagName(self.platform.os_tag));
763 try argv.append(try std.fmt.allocPrint(arena, "{}", .{self.platform.version}));
764
765 if (self.sdk_version) |ver| {
766 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
767 } else {
768 try argv.append(try std.fmt.allocPrint(arena, "{}", .{self.platform.version}));
769 }
770
771 if (comp.sysroot) |syslibroot| {
772 try argv.append("-syslibroot");
773 try argv.append(syslibroot);
774 }
775
776 for (self.base.rpath_list) |rpath| {
777 try argv.append("-rpath");
778 try argv.append(rpath);
779 }
780
781 if (self.pagezero_size) |size| {
782 try argv.append("-pagezero_size");
783 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
784 }
785
786 if (self.headerpad_size) |size| {
787 try argv.append("-headerpad_size");
788 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
789 }
790
791 if (self.headerpad_max_install_names) {
792 try argv.append("-headerpad_max_install_names");
793 }
794
795 if (self.base.gc_sections) {
796 try argv.append("-dead_strip");
797 }
798
799 if (self.dead_strip_dylibs) {
800 try argv.append("-dead_strip_dylibs");
801 }
802
803 if (self.entry_name) |entry_name| {
804 try argv.appendSlice(&.{ "-e", entry_name });
805 }
806
807 for (comp.objects) |obj| {
808 // TODO: verify this
809 if (obj.must_link) {
810 try argv.append("-force_load");
811 }
812 try argv.append(obj.path);
813 }
814
815 for (comp.c_object_table.keys()) |key| {
816 try argv.append(key.status.success.object_path);
817 }
818
819 if (module_obj_path) |p| {
820 try argv.append(p);
821 }
822
823 if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
824 if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
825
826 if (comp.config.link_libcpp) {
827 try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
828 try argv.append(comp.libcxx_static_lib.?.full_object_path);
829 }
830
831 try argv.append("-o");
832 try argv.append(full_out_path);
833
834 try argv.append("-lSystem");
835
836 for (comp.system_libs.keys()) |l_name| {
837 const info = comp.system_libs.get(l_name).?;
838 const arg = if (info.needed)
839 try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
840 else if (info.weak)
841 try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
842 else
843 try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
844 try argv.append(arg);
845 }
846
847 for (self.frameworks) |framework| {
848 const name = std.fs.path.stem(framework.path);
849 const arg = if (framework.needed)
850 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
851 else if (framework.weak)
852 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
853 else
854 try std.fmt.allocPrint(arena, "-framework {s}", .{name});
855 try argv.append(arg);
856 }
857
858 if (self.base.isDynLib() and self.base.allow_shlib_undefined) {
859 try argv.append("-undefined");
860 try argv.append("dynamic_lookup");
861 }
615862 }
863
864 Compilation.dump_argv(argv.items);
616865}
617866
618inline fn conformUuid(out: *[Md5.digest_length]u8) void {
619 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
620 out[6] = (out[6] & 0x0F) | (3 << 4);
621 out[8] = (out[8] & 0x3F) | 0x80;
867fn flushStaticLib(self: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
868 _ = comp;
869 _ = module_obj_path;
870
871 var err = try self.addErrorWithNotes(0);
872 try err.addMsg(self, "TODO implement flushStaticLib", .{});
873
874 return error.FlushFailure;
622875}
623876
624877pub fn resolveLibSystem(
......@@ -643,13 +896,12 @@ pub fn resolveLibSystem(
643896 };
644897
645898 try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
646 return;
899 return error.MissingLibSystem;
647900 }
648901
649902 const libsystem_path = try arena.dupe(u8, test_path.items);
650 try out_libs.put(libsystem_path, .{
903 try out_libs.append(.{
651904 .needed = true,
652 .weak = false,
653905 .path = libsystem_path,
654906 });
655907}
......@@ -700,3288 +952,1819 @@ fn accessLibPath(
700952}
701953
702954const ParseError = error{
703 UnknownFileType,
955 MalformedObject,
956 MalformedArchive,
957 MalformedDylib,
958 MalformedTbd,
959 NotLibStub,
960 InvalidCpuArch,
704961 InvalidTarget,
705962 InvalidTargetFatLibrary,
706 DylibAlreadyExists,
707963 IncompatibleDylibVersion,
708964 OutOfMemory,
709965 Overflow,
710966 InputOutput,
711 MalformedArchive,
712 NotLibStub,
713967 EndOfStream,
714968 FileSystem,
715969 NotSupported,
970 Unhandled,
971 UnknownFileType,
716972} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError || tapi.TapiError;
717973
718pub fn parsePositional(
719 self: *MachO,
720 file: std.fs.File,
721 path: []const u8,
722 must_link: bool,
723 dependent_libs: anytype,
724 ctx: *ParseErrorCtx,
725) ParseError!void {
974pub fn parsePositional(self: *MachO, path: []const u8, must_link: bool) ParseError!void {
726975 const tracy = trace(@src());
727976 defer tracy.end();
977 if (try Object.isObject(path)) {
978 try self.parseObject(path);
979 } else {
980 try self.parseLibrary(.{ .path = path }, must_link);
981 }
982}
728983
729 if (Object.isObject(file)) {
730 try self.parseObject(file, path, ctx);
984fn parseLibrary(self: *MachO, lib: SystemLib, must_link: bool) ParseError!void {
985 const tracy = trace(@src());
986 defer tracy.end();
987 if (try fat.isFatLibrary(lib.path)) {
988 const fat_arch = try self.parseFatLibrary(lib.path);
989 if (try Archive.isArchive(lib.path, fat_arch)) {
990 try self.parseArchive(lib, must_link, fat_arch);
991 } else if (try Dylib.isDylib(lib.path, fat_arch)) {
992 _ = try self.parseDylib(lib, true, fat_arch);
993 } else return error.UnknownFileType;
994 } else if (try Archive.isArchive(lib.path, null)) {
995 try self.parseArchive(lib, must_link, null);
996 } else if (try Dylib.isDylib(lib.path, null)) {
997 _ = try self.parseDylib(lib, true, null);
731998 } else {
732 try self.parseLibrary(file, path, .{
733 .path = null,
734 .needed = false,
735 .weak = false,
736 }, must_link, false, null, dependent_libs, ctx);
999 _ = self.parseTbd(lib, true) catch |err| switch (err) {
1000 error.MalformedTbd => return error.UnknownFileType,
1001 else => |e| return e,
1002 };
7371003 }
7381004}
7391005
740fn parseObject(
741 self: *MachO,
742 file: std.fs.File,
743 path: []const u8,
744 ctx: *ParseErrorCtx,
745) ParseError!void {
1006fn parseObject(self: *MachO, path: []const u8) ParseError!void {
7461007 const tracy = trace(@src());
7471008 defer tracy.end();
7481009
7491010 const gpa = self.base.comp.gpa;
750 const target = self.base.comp.root_mod.resolved_target.result;
1011 const file = try std.fs.cwd().openFile(path, .{});
1012 defer file.close();
7511013 const mtime: u64 = mtime: {
7521014 const stat = file.stat() catch break :mtime 0;
7531015 break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));
7541016 };
755 const file_stat = try file.stat();
756 const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow;
757 const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null);
758
759 var object = Object{
760 .name = try gpa.dupe(u8, path),
1017 const data = try file.readToEndAlloc(gpa, std.math.maxInt(u32));
1018 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1019 self.files.set(index, .{ .object = .{
1020 .path = try gpa.dupe(u8, path),
7611021 .mtime = mtime,
762 .contents = contents,
763 };
764 errdefer object.deinit(gpa);
765 try object.parse(gpa);
1022 .data = data,
1023 .index = index,
1024 } });
1025 try self.objects.append(gpa, index);
7661026
767 const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) {
768 macho.CPU_TYPE_ARM64 => .aarch64,
769 macho.CPU_TYPE_X86_64 => .x86_64,
770 else => unreachable,
771 };
772 const detected_platform = object.getPlatform();
773 const this_cpu_arch = target.cpu.arch;
774 const this_platform = Platform.fromTarget(target);
1027 const object = self.getFile(index).?.object;
1028 try object.parse(self);
1029}
7751030
776 if (this_cpu_arch != detected_cpu_arch or
777 (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
778 {
779 const platform = detected_platform orelse this_platform;
780 try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch));
781 return error.InvalidTarget;
1031fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch {
1032 var buffer: [2]fat.Arch = undefined;
1033 const fat_archs = try fat.parseArchs(path, &buffer);
1034 const cpu_arch = self.getTarget().cpu.arch;
1035 for (fat_archs) |arch| {
1036 if (arch.tag == cpu_arch) return arch;
7821037 }
783
784 try self.objects.append(gpa, object);
1038 try self.reportParseError(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});
1039 return error.InvalidCpuArch;
7851040}
7861041
787pub fn parseLibrary(
788 self: *MachO,
789 file: std.fs.File,
790 path: []const u8,
791 lib: link.SystemLib,
792 must_link: bool,
793 is_dependent: bool,
794 reexport_info: ?DylibReExportInfo,
795 dependent_libs: anytype,
796 ctx: *ParseErrorCtx,
797) ParseError!void {
1042fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Arch) ParseError!void {
7981043 const tracy = trace(@src());
7991044 defer tracy.end();
8001045
801 const target = self.base.comp.root_mod.resolved_target.result;
802
803 if (fat.isFatLibrary(file)) {
804 const offset = try self.parseFatLibrary(file, target.cpu.arch, ctx);
805 try file.seekTo(offset);
806
807 if (Archive.isArchive(file, offset)) {
808 try self.parseArchive(path, offset, must_link, ctx);
809 } else if (Dylib.isDylib(file, offset)) {
810 try self.parseDylib(file, path, offset, dependent_libs, .{
811 .needed = lib.needed,
812 .weak = lib.weak,
813 .dependent = is_dependent,
814 .reexport_info = reexport_info,
815 }, ctx);
816 } else return error.UnknownFileType;
817 } else if (Archive.isArchive(file, 0)) {
818 try self.parseArchive(path, 0, must_link, ctx);
819 } else if (Dylib.isDylib(file, 0)) {
820 try self.parseDylib(file, path, 0, dependent_libs, .{
821 .needed = lib.needed,
822 .weak = lib.weak,
823 .dependent = is_dependent,
824 .reexport_info = reexport_info,
825 }, ctx);
826 } else {
827 self.parseLibStub(file, path, dependent_libs, .{
828 .needed = lib.needed,
829 .weak = lib.weak,
830 .dependent = is_dependent,
831 .reexport_info = reexport_info,
832 }, ctx) catch |err| switch (err) {
833 error.NotLibStub, error.UnexpectedToken => return error.UnknownFileType,
1046 const gpa = self.base.comp.gpa;
1047
1048 const file = try std.fs.cwd().openFile(lib.path, .{});
1049 defer file.close();
1050
1051 const data = if (fat_arch) |arch| blk: {
1052 try file.seekTo(arch.offset);
1053 const data = try gpa.alloc(u8, arch.size);
1054 const nread = try file.readAll(data);
1055 if (nread != arch.size) return error.InputOutput;
1056 break :blk data;
1057 } else try file.readToEndAlloc(gpa, std.math.maxInt(u32));
1058
1059 var archive = Archive{ .path = try gpa.dupe(u8, lib.path), .data = data };
1060 defer archive.deinit(gpa);
1061 try archive.parse(self);
1062
1063 var has_parse_error = false;
1064 for (archive.objects.items) |extracted| {
1065 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1066 self.files.set(index, .{ .object = extracted });
1067 const object = &self.files.items(.data)[index].object;
1068 object.index = index;
1069 object.alive = must_link or lib.needed; // TODO: or self.options.all_load;
1070 object.hidden = lib.hidden;
1071 object.parse(self) catch |err| switch (err) {
1072 error.MalformedObject,
1073 error.InvalidCpuArch,
1074 error.InvalidTarget,
1075 => has_parse_error = true,
8341076 else => |e| return e,
8351077 };
1078 try self.objects.append(gpa, index);
1079
1080 // Finally, we do a post-parse check for -ObjC to see if we need to force load this member
1081 // anyhow.
1082 object.alive = object.alive or (self.force_load_objc and object.hasObjc());
8361083 }
1084 if (has_parse_error) return error.MalformedArchive;
8371085}
8381086
839pub fn parseFatLibrary(
840 self: *MachO,
841 file: std.fs.File,
842 cpu_arch: std.Target.Cpu.Arch,
843 ctx: *ParseErrorCtx,
844) ParseError!u64 {
1087fn parseDylib(self: *MachO, lib: SystemLib, explicit: bool, fat_arch: ?fat.Arch) ParseError!File.Index {
1088 const tracy = trace(@src());
1089 defer tracy.end();
1090
8451091 const gpa = self.base.comp.gpa;
8461092
847 const fat_archs = try fat.parseArchs(gpa, file);
848 defer gpa.free(fat_archs);
1093 const file = try std.fs.cwd().openFile(lib.path, .{});
1094 defer file.close();
1095
1096 const data = if (fat_arch) |arch| blk: {
1097 try file.seekTo(arch.offset);
1098 const data = try gpa.alloc(u8, arch.size);
1099 const nread = try file.readAll(data);
1100 if (nread != arch.size) return error.InputOutput;
1101 break :blk data;
1102 } else try file.readToEndAlloc(gpa, std.math.maxInt(u32));
1103
1104 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1105 self.files.set(index, .{ .dylib = .{
1106 .path = try gpa.dupe(u8, lib.path),
1107 .data = data,
1108 .index = index,
1109 .needed = lib.needed,
1110 .weak = lib.weak,
1111 .reexport = lib.reexport,
1112 .explicit = explicit,
1113 } });
1114 const dylib = &self.files.items(.data)[index].dylib;
1115 try dylib.parse(self);
1116
1117 try self.dylibs.append(gpa, index);
8491118
850 const offset = for (fat_archs) |arch| {
851 if (arch.tag == cpu_arch) break arch.offset;
852 } else {
853 try ctx.detected_targets.ensureUnusedCapacity(fat_archs.len);
854 for (fat_archs) |arch| {
855 ctx.detected_targets.appendAssumeCapacity(try ctx.arena().dupe(u8, @tagName(arch.tag)));
856 }
857 return error.InvalidTargetFatLibrary;
858 };
859 return offset;
1119 return index;
8601120}
8611121
862fn parseArchive(
863 self: *MachO,
864 path: []const u8,
865 fat_offset: u64,
866 must_link: bool,
867 ctx: *ParseErrorCtx,
868) ParseError!void {
869 const gpa = self.base.comp.gpa;
870 const target = self.base.comp.root_mod.resolved_target.result;
871
872 // We take ownership of the file so that we can store it for the duration of symbol resolution.
873 // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF?
874 const file = try std.fs.cwd().openFile(path, .{});
875 try file.seekTo(fat_offset);
876
877 var archive = Archive{
878 .file = file,
879 .fat_offset = fat_offset,
880 .name = try gpa.dupe(u8, path),
881 };
882 errdefer archive.deinit(gpa);
1122fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index {
1123 const tracy = trace(@src());
1124 defer tracy.end();
8831125
884 try archive.parse(gpa, file.reader());
1126 const gpa = self.base.comp.gpa;
1127 const file = try std.fs.cwd().openFile(lib.path, .{});
1128 defer file.close();
8851129
886 // Verify arch and platform
887 if (archive.toc.values().len > 0) {
888 const offsets = archive.toc.values()[0].items;
889 assert(offsets.len > 0);
890 const off = offsets[0];
891 var object = try archive.parseObject(gpa, off); // TODO we are doing all this work to pull the header only!
892 defer object.deinit(gpa);
1130 var lib_stub = LibStub.loadFromFile(gpa, file) catch return error.MalformedTbd; // TODO actually handle different errors
1131 defer lib_stub.deinit();
8931132
894 const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) {
895 macho.CPU_TYPE_ARM64 => .aarch64,
896 macho.CPU_TYPE_X86_64 => .x86_64,
897 else => unreachable,
898 };
899 const detected_platform = object.getPlatform();
900 const this_cpu_arch = target.cpu.arch;
901 const this_platform = Platform.fromTarget(target);
1133 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1134 self.files.set(index, .{ .dylib = .{
1135 .path = try gpa.dupe(u8, lib.path),
1136 .data = &[0]u8{},
1137 .index = index,
1138 .needed = lib.needed,
1139 .weak = lib.weak,
1140 .reexport = lib.reexport,
1141 .explicit = explicit,
1142 } });
1143 const dylib = &self.files.items(.data)[index].dylib;
1144 try dylib.parseTbd(self.getTarget().cpu.arch, self.platform, lib_stub, self);
1145 try self.dylibs.append(gpa, index);
9021146
903 if (this_cpu_arch != detected_cpu_arch or
904 (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
905 {
906 const platform = detected_platform orelse this_platform;
907 try ctx.detected_targets.append(try platform.allocPrintTarget(gpa, detected_cpu_arch));
908 return error.InvalidTarget;
909 }
910 }
1147 return index;
1148}
9111149
912 if (must_link) {
913 // Get all offsets from the ToC
914 var offsets = std.AutoArrayHashMap(u32, void).init(gpa);
915 defer offsets.deinit();
916 for (archive.toc.values()) |offs| {
917 for (offs.items) |off| {
918 _ = try offsets.getOrPut(off);
1150/// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final
1151/// image unless overriden by -no_implicit_dylibs.
1152fn isHoisted(self: *MachO, install_name: []const u8) bool {
1153 if (self.no_implicit_dylibs) return true;
1154 if (std.fs.path.dirname(install_name)) |dirname| {
1155 if (mem.startsWith(u8, dirname, "/usr/lib")) return true;
1156 if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| {
1157 const basename = std.fs.path.basename(install_name);
1158 if (mem.indexOfScalar(u8, path, '.')) |index| {
1159 if (mem.eql(u8, basename, path[0..index])) return true;
9191160 }
9201161 }
921 for (offsets.keys()) |off| {
922 const object = try archive.parseObject(gpa, off);
923 try self.objects.append(gpa, object);
924 }
925 } else {
926 try self.archives.append(gpa, archive);
9271162 }
1163 return false;
9281164}
9291165
930pub const DylibReExportInfo = struct {
931 id: Dylib.Id,
932 parent: u16,
933};
934
935const DylibOpts = struct {
936 reexport_info: ?DylibReExportInfo = null,
937 dependent: bool = false,
938 needed: bool = false,
939 weak: bool = false,
940};
941
942fn parseDylib(
943 self: *MachO,
944 file: std.fs.File,
945 path: []const u8,
946 offset: u64,
947 dependent_libs: anytype,
948 dylib_options: DylibOpts,
949 ctx: *ParseErrorCtx,
950) ParseError!void {
951 const gpa = self.base.comp.gpa;
952 const target = self.base.comp.root_mod.resolved_target.result;
953 const file_stat = try file.stat();
954 const file_size = math.cast(usize, file_stat.size - offset) orelse return error.Overflow;
955
956 const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null);
957 defer gpa.free(contents);
958
959 var dylib = Dylib{ .path = try gpa.dupe(u8, path), .weak = dylib_options.weak };
960 errdefer dylib.deinit(gpa);
961
962 try dylib.parseFromBinary(
963 gpa,
964 @intCast(self.dylibs.items.len), // TODO defer it till later
965 dependent_libs,
966 path,
967 contents,
968 );
969
970 const detected_cpu_arch: std.Target.Cpu.Arch = switch (dylib.header.?.cputype) {
971 macho.CPU_TYPE_ARM64 => .aarch64,
972 macho.CPU_TYPE_X86_64 => .x86_64,
973 else => unreachable,
1166fn accessPath(path: []const u8) !bool {
1167 std.fs.cwd().access(path, .{}) catch |err| switch (err) {
1168 error.FileNotFound => return false,
1169 else => |e| return e,
9741170 };
975 const detected_platform = dylib.getPlatform(contents);
976 const this_cpu_arch = target.cpu.arch;
977 const this_platform = Platform.fromTarget(target);
1171 return true;
1172}
9781173
979 if (this_cpu_arch != detected_cpu_arch or
980 (detected_platform != null and !detected_platform.?.eqlTarget(this_platform)))
981 {
982 const platform = detected_platform orelse this_platform;
983 try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch));
984 return error.InvalidTarget;
1174fn resolveLib(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
1175 const path = try std.fmt.allocPrint(arena, "lib{s}", .{name});
1176 for (search_dirs) |dir| {
1177 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
1178 const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
1179 const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
1180 if (try accessPath(full_path)) return full_path;
1181 }
9851182 }
1183 return null;
1184}
9861185
987 try self.addDylib(dylib, dylib_options, ctx);
1186fn resolveFramework(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
1187 const prefix = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
1188 const path = try std.fs.path.join(arena, &[_][]const u8{ prefix, name });
1189 for (search_dirs) |dir| {
1190 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
1191 const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
1192 const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
1193 if (try accessPath(full_path)) return full_path;
1194 }
1195 }
1196 return null;
9881197}
9891198
990fn parseLibStub(
991 self: *MachO,
992 file: std.fs.File,
993 path: []const u8,
994 dependent_libs: anytype,
995 dylib_options: DylibOpts,
996 ctx: *ParseErrorCtx,
997) ParseError!void {
1199fn parseDependentDylibs(self: *MachO) !void {
1200 const tracy = trace(@src());
1201 defer tracy.end();
1202
9981203 const gpa = self.base.comp.gpa;
999 const target = self.base.comp.root_mod.resolved_target.result;
1204 const lib_dirs = self.lib_dirs;
1205 const framework_dirs = self.framework_dirs;
1206
1207 var arena = std.heap.ArenaAllocator.init(gpa);
1208 defer arena.deinit();
1209
1210 // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times
1211 // in which case we should track that and return File.Index immediately instead re-parsing paths.
1212
1213 var has_errors = false;
1214 var index: usize = 0;
1215 while (index < self.dylibs.items.len) : (index += 1) {
1216 const dylib_index = self.dylibs.items[index];
1217
1218 var dependents = std.ArrayList(File.Index).init(gpa);
1219 defer dependents.deinit();
1220 try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len);
1221
1222 const is_weak = self.getFile(dylib_index).?.dylib.weak;
1223 for (self.getFile(dylib_index).?.dylib.dependents.items) |id| {
1224 // We will search for the dependent dylibs in the following order:
1225 // 1. Basename is in search lib directories or framework directories
1226 // 2. If name is an absolute path, search as-is optionally prepending a syslibroot
1227 // if specified.
1228 // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with
1229 // dependees list of rpaths, and search there.
1230 // 4. Finally, just search the provided relative path directly in CWD.
1231 const full_path = full_path: {
1232 fail: {
1233 const stem = std.fs.path.stem(id.name);
1234 const framework_name = try std.fmt.allocPrint(gpa, "{s}.framework" ++ std.fs.path.sep_str ++ "{s}", .{
1235 stem,
1236 stem,
1237 });
1238 defer gpa.free(framework_name);
1239
1240 if (mem.endsWith(u8, id.name, framework_name)) {
1241 // Framework
1242 const full_path = (try resolveFramework(arena.allocator(), framework_dirs, stem)) orelse break :fail;
1243 break :full_path full_path;
1244 }
1245
1246 // Library
1247 const lib_name = eatPrefix(stem, "lib") orelse stem;
1248 const full_path = (try resolveLib(arena.allocator(), lib_dirs, lib_name)) orelse break :fail;
1249 break :full_path full_path;
1250 }
10001251
1001 var lib_stub = try LibStub.loadFromFile(gpa, file);
1002 defer lib_stub.deinit();
1252 if (std.fs.path.isAbsolute(id.name)) {
1253 const path = if (self.base.comp.sysroot) |root|
1254 try std.fs.path.join(arena.allocator(), &.{ root, id.name })
1255 else
1256 id.name;
1257 for (&[_][]const u8{ "", ".tbd", ".dylib" }) |ext| {
1258 const full_path = try std.fmt.allocPrint(arena.allocator(), "{s}{s}", .{ path, ext });
1259 if (try accessPath(full_path)) break :full_path full_path;
1260 }
1261 }
10031262
1004 if (lib_stub.inner.len == 0) return error.NotLibStub;
1263 if (eatPrefix(id.name, "@rpath/")) |path| {
1264 const dylib = self.getFile(dylib_index).?.dylib;
1265 for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| {
1266 const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath;
1267 const rel_path = try std.fs.path.join(arena.allocator(), &.{ prefix, path });
1268 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1269 const full_path = std.fs.realpath(rel_path, &buffer) catch continue;
1270 break :full_path full_path;
1271 }
1272 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
1273 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1274 return error.Unhandled;
1275 } else if (eatPrefix(id.name, "@executable_path/")) |_| {
1276 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1277 return error.Unhandled;
1278 }
10051279
1006 // Verify target
1007 {
1008 var matcher = try Dylib.TargetMatcher.init(gpa, target);
1009 defer matcher.deinit();
1010
1011 const first_tbd = lib_stub.inner[0];
1012 const targets = try first_tbd.targets(gpa);
1013 defer {
1014 for (targets) |t| gpa.free(t);
1015 gpa.free(targets);
1280 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1281 const full_path = std.fs.realpath(id.name, &buffer) catch {
1282 dependents.appendAssumeCapacity(0);
1283 continue;
1284 };
1285 break :full_path full_path;
1286 };
1287 const lib = SystemLib{
1288 .path = full_path,
1289 .weak = is_weak,
1290 };
1291 const file_index = file_index: {
1292 if (try fat.isFatLibrary(lib.path)) {
1293 const fat_arch = try self.parseFatLibrary(lib.path);
1294 if (try Dylib.isDylib(lib.path, fat_arch)) {
1295 break :file_index try self.parseDylib(lib, false, fat_arch);
1296 } else break :file_index @as(File.Index, 0);
1297 } else if (try Dylib.isDylib(lib.path, null)) {
1298 break :file_index try self.parseDylib(lib, false, null);
1299 } else {
1300 const file_index = self.parseTbd(lib, false) catch |err| switch (err) {
1301 error.MalformedTbd => @as(File.Index, 0),
1302 else => |e| return e,
1303 };
1304 break :file_index file_index;
1305 }
1306 };
1307 dependents.appendAssumeCapacity(file_index);
10161308 }
1017 if (!matcher.matchesTarget(targets)) {
1018 try ctx.detected_targets.ensureUnusedCapacity(targets.len);
1019 for (targets) |t| {
1020 ctx.detected_targets.appendAssumeCapacity(try ctx.arena().dupe(u8, t));
1309
1310 const dylib = self.getFile(dylib_index).?.dylib;
1311 for (dylib.dependents.items, dependents.items) |id, file_index| {
1312 if (self.getFile(file_index)) |file| {
1313 const dep_dylib = file.dylib;
1314 dep_dylib.hoisted = self.isHoisted(id.name);
1315 if (self.getFile(dep_dylib.umbrella) == null) {
1316 dep_dylib.umbrella = dylib.umbrella;
1317 }
1318 if (!dep_dylib.hoisted) {
1319 const umbrella = dep_dylib.getUmbrella(self);
1320 for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| {
1321 try umbrella.addExport(gpa, dep_dylib.getString(off), flags);
1322 }
1323 try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len);
1324 for (dep_dylib.rpaths.keys()) |rpath| {
1325 umbrella.rpaths.putAssumeCapacity(rpath, {});
1326 }
1327 }
1328 } else {
1329 try self.reportDependencyError(
1330 dylib.getUmbrella(self).index,
1331 id.name,
1332 "unable to resolve dependency",
1333 .{},
1334 );
1335 has_errors = true;
10211336 }
1022 return error.InvalidTarget;
10231337 }
10241338 }
10251339
1026 var dylib = Dylib{ .path = try gpa.dupe(u8, path), .weak = dylib_options.weak };
1027 errdefer dylib.deinit(gpa);
1028
1029 try dylib.parseFromStub(
1030 gpa,
1031 target,
1032 lib_stub,
1033 @intCast(self.dylibs.items.len), // TODO defer it till later
1034 dependent_libs,
1035 path,
1036 );
1037
1038 try self.addDylib(dylib, dylib_options, ctx);
1340 if (has_errors) return error.MissingLibraryDependencies;
10391341}
10401342
1041fn addDylib(self: *MachO, dylib: Dylib, dylib_options: DylibOpts, ctx: *ParseErrorCtx) ParseError!void {
1042 if (dylib_options.reexport_info) |reexport_info| {
1043 if (dylib.id.?.current_version < reexport_info.id.compatibility_version) {
1044 ctx.detected_dylib_id = .{
1045 .parent = reexport_info.parent,
1046 .required_version = reexport_info.id.compatibility_version,
1047 .found_version = dylib.id.?.current_version,
1048 };
1049 return error.IncompatibleDylibVersion;
1050 }
1051 }
1052
1343pub fn addUndefinedGlobals(self: *MachO) !void {
10531344 const gpa = self.base.comp.gpa;
1054 const gop = try self.dylibs_map.getOrPut(gpa, dylib.id.?.name);
1055 if (gop.found_existing) return error.DylibAlreadyExists;
10561345
1057 gop.value_ptr.* = @as(u16, @intCast(self.dylibs.items.len));
1058 try self.dylibs.append(gpa, dylib);
1346 try self.undefined_symbols.ensureUnusedCapacity(gpa, self.base.comp.force_undefined_symbols.keys().len);
1347 for (self.base.comp.force_undefined_symbols.keys()) |name| {
1348 const off = try self.strings.insert(gpa, name);
1349 const gop = try self.getOrCreateGlobal(off);
1350 self.undefined_symbols.appendAssumeCapacity(gop.index);
1351 }
10591352
1060 const should_link_dylib_even_if_unreachable = blk: {
1061 if (self.dead_strip_dylibs and !dylib_options.needed) break :blk false;
1062 break :blk !(dylib_options.dependent or self.referenced_dylibs.contains(gop.value_ptr.*));
1063 };
1353 if (!self.base.isDynLib() and self.entry_name != null) {
1354 const off = try self.strings.insert(gpa, self.entry_name.?);
1355 const gop = try self.getOrCreateGlobal(off);
1356 self.entry_index = gop.index;
1357 }
1358
1359 {
1360 const off = try self.strings.insert(gpa, "dyld_stub_binder");
1361 const gop = try self.getOrCreateGlobal(off);
1362 self.dyld_stub_binder_index = gop.index;
1363 }
10641364
1065 if (should_link_dylib_even_if_unreachable) {
1066 try self.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {});
1365 {
1366 const off = try self.strings.insert(gpa, "_objc_msgSend");
1367 const gop = try self.getOrCreateGlobal(off);
1368 self.objc_msg_send_index = gop.index;
10671369 }
10681370}
10691371
1070pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void {
1372/// When resolving symbols, we approach the problem similarly to `mold`.
1373/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
1374/// 2. Resolve symbols across all shared objects.
1375/// 3. Mark live objects (see `MachO.markLive`)
1376/// 4. Reset state of all resolved globals since we will redo this bit on the pruned set.
1377/// 5. Remove references to dead objects/shared objects
1378/// 6. Re-run symbol resolution on pruned objects and shared objects sets.
1379pub fn resolveSymbols(self: *MachO) !void {
10711380 const tracy = trace(@src());
10721381 defer tracy.end();
10731382
1074 // At this point, we can now parse dependents of dylibs preserving the inclusion order of:
1075 // 1) anything on the linker line is parsed first
1076 // 2) afterwards, we parse dependents of the included dylibs
1077 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
1078 // See ld64 manpages.
1079 const comp = self.base.comp;
1080 const gpa = comp.gpa;
1081
1082 while (dependent_libs.readItem()) |dep_id| {
1083 defer dep_id.id.deinit(gpa);
1084
1085 if (self.dylibs_map.contains(dep_id.id.name)) continue;
1383 // Resolve symbols in the ZigObject. For now, we assume that it's always live.
1384 if (self.getZigObject()) |zo| zo.asFile().resolveSymbols(self);
1385 // Resolve symbols on the set of all objects and shared objects (even if some are unneeded).
1386 for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self);
1387 for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self);
10861388
1087 const parent = &self.dylibs.items[dep_id.parent];
1088 const weak = parent.weak;
1089 const dirname = fs.path.dirname(dep_id.id.name) orelse "";
1090 const stem = fs.path.stem(dep_id.id.name);
1389 // Mark live objects.
1390 self.markLive();
10911391
1092 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1093 defer arena_allocator.deinit();
1094 const arena = arena_allocator.allocator();
1392 // Reset state of all globals after marking live objects.
1393 if (self.getZigObject()) |zo| zo.asFile().resetGlobals(self);
1394 for (self.objects.items) |index| self.getFile(index).?.resetGlobals(self);
1395 for (self.dylibs.items) |index| self.getFile(index).?.resetGlobals(self);
10951396
1096 var test_path = std.ArrayList(u8).init(arena);
1097 var checked_paths = std.ArrayList([]const u8).init(arena);
1397 // Prune dead objects.
1398 var i: usize = 0;
1399 while (i < self.objects.items.len) {
1400 const index = self.objects.items[i];
1401 if (!self.getFile(index).?.object.alive) {
1402 _ = self.objects.orderedRemove(i);
1403 } else i += 1;
1404 }
10981405
1099 success: {
1100 if (comp.sysroot) |root| {
1101 const dir = try fs.path.join(arena, &[_][]const u8{ root, dirname });
1102 if (try accessLibPath(gpa, &test_path, &checked_paths, dir, stem)) break :success;
1103 }
1406 // Re-resolve the symbols.
1407 if (self.getZigObject()) |zo| zo.resolveSymbols(self);
1408 for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self);
1409 for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self);
1410}
11041411
1105 if (try accessLibPath(gpa, &test_path, &checked_paths, dirname, stem)) break :success;
1412fn markLive(self: *MachO) void {
1413 const tracy = trace(@src());
1414 defer tracy.end();
11061415
1107 try self.reportMissingLibraryError(
1108 checked_paths.items,
1109 "missing dynamic library dependency: '{s}'",
1110 .{dep_id.id.name},
1111 );
1112 continue;
1416 for (self.undefined_symbols.items) |index| {
1417 if (self.getSymbol(index).getFile(self)) |file| {
1418 if (file == .object) file.object.alive = true;
11131419 }
1114
1115 const full_path = test_path.items;
1116 const file = try std.fs.cwd().openFile(full_path, .{});
1117 defer file.close();
1118
1119 log.debug("parsing dependency {s} at fully resolved path {s}", .{ dep_id.id.name, full_path });
1120
1121 var parse_ctx = ParseErrorCtx.init(gpa);
1122 defer parse_ctx.deinit();
1123
1124 self.parseLibrary(file, full_path, .{
1125 .path = null,
1126 .needed = false,
1127 .weak = weak,
1128 }, false, true, dep_id, dependent_libs, &parse_ctx) catch |err|
1129 try self.handleAndReportParseError(full_path, err, &parse_ctx);
1130
1131 // TODO I think that it would be nice to rewrite this error to include metadata for failed dependency
1132 // in addition to parsing error
1420 }
1421 if (self.entry_index) |index| {
1422 const sym = self.getSymbol(index);
1423 if (sym.getFile(self)) |file| {
1424 if (file == .object) file.object.alive = true;
1425 }
1426 }
1427 if (self.getZigObject()) |zo| zo.markLive(self);
1428 for (self.objects.items) |index| {
1429 const object = self.getFile(index).?.object;
1430 if (object.alive) object.markLive(self);
11331431 }
11341432}
11351433
1136pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []u8) !void {
1137 const atom = self.getAtom(atom_index);
1138 const sym = atom.getSymbol(self);
1139 const section = self.sections.get(sym.n_sect - 1);
1140 const file_offset = section.header.offset + sym.n_value - section.header.addr;
1141 log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset });
1434fn resolveSyntheticSymbols(self: *MachO) !void {
1435 const internal = self.getInternalObject() orelse return;
11421436
1143 // Gather relocs which can be resolved.
1144 const gpa = self.base.comp.gpa;
1145 var relocs = std.ArrayList(*Relocation).init(gpa);
1146 defer relocs.deinit();
1147
1148 if (self.relocs.getPtr(atom_index)) |rels| {
1149 try relocs.ensureTotalCapacityPrecise(rels.items.len);
1150 for (rels.items) |*reloc| {
1151 if (reloc.isResolvable(self) and reloc.dirty) {
1152 relocs.appendAssumeCapacity(reloc);
1153 }
1154 }
1437 if (!self.base.isDynLib()) {
1438 self.mh_execute_header_index = try internal.addSymbol("__mh_execute_header", self);
1439 const sym = self.getSymbol(self.mh_execute_header_index.?);
1440 sym.flags.@"export" = true;
1441 sym.flags.dyn_ref = true;
1442 sym.visibility = .global;
1443 } else {
1444 self.mh_dylib_header_index = try internal.addSymbol("__mh_dylib_header", self);
11551445 }
11561446
1157 Atom.resolveRelocations(self, atom_index, relocs.items, code);
1447 self.dso_handle_index = try internal.addSymbol("___dso_handle", self);
1448 self.dyld_private_index = try internal.addSymbol("dyld_private", self);
11581449
1159 if (is_hot_update_compatible) {
1160 if (self.hot_state.mach_task) |task| {
1161 self.writeToMemory(task, section.segment_index, sym.n_value, code) catch |err| {
1162 log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)});
1163 };
1450 {
1451 const gpa = self.base.comp.gpa;
1452 var boundary_symbols = std.AutoHashMap(Symbol.Index, void).init(gpa);
1453 defer boundary_symbols.deinit();
1454
1455 for (self.objects.items) |index| {
1456 const object = self.getFile(index).?.object;
1457 for (object.symbols.items, 0..) |sym_index, i| {
1458 const nlist = object.symtab.items(.nlist)[i];
1459 const name = self.getSymbol(sym_index).getName(self);
1460 if (!nlist.undf() or !nlist.ext()) continue;
1461 if (mem.startsWith(u8, name, "segment$start$") or
1462 mem.startsWith(u8, name, "segment$stop$") or
1463 mem.startsWith(u8, name, "section$start$") or
1464 mem.startsWith(u8, name, "section$stop$"))
1465 {
1466 _ = try boundary_symbols.put(sym_index, {});
1467 }
1468 }
11641469 }
1165 }
11661470
1167 try self.base.file.?.pwriteAll(code, file_offset);
1471 try self.boundary_symbols.ensureTotalCapacityPrecise(gpa, boundary_symbols.count());
11681472
1169 // Now we can mark the relocs as resolved.
1170 while (relocs.popOrNull()) |reloc| {
1171 reloc.dirty = false;
1473 var it = boundary_symbols.iterator();
1474 while (it.next()) |entry| {
1475 _ = try internal.addSymbol(self.getSymbol(entry.key_ptr.*).getName(self), self);
1476 self.boundary_symbols.appendAssumeCapacity(entry.key_ptr.*);
1477 }
11721478 }
11731479}
11741480
1175fn writeToMemory(self: *MachO, task: std.os.darwin.MachTask, segment_index: u8, addr: u64, code: []const u8) !void {
1176 const segment = self.segments.items[segment_index];
1177 const target = self.base.comp.root_mod.resolved_target.result;
1178 const cpu_arch = target.cpu.arch;
1179 const nwritten = if (!segment.isWriteable())
1180 try task.writeMemProtected(addr, code, cpu_arch)
1181 else
1182 try task.writeMem(addr, code, cpu_arch);
1183 if (nwritten != code.len) return error.InputOutput;
1481fn convertTentativeDefinitions(self: *MachO) !void {
1482 for (self.objects.items) |index| {
1483 try self.getFile(index).?.object.convertTentativeDefinitions(self);
1484 }
11841485}
11851486
1186fn writeOffsetTableEntry(self: *MachO, index: usize) !void {
1187 const sect_id = self.got_section_index.?;
1188
1189 if (self.got_table_count_dirty) {
1190 const needed_size = self.got_table.entries.items.len * @sizeOf(u64);
1191 try self.growSection(sect_id, needed_size);
1192 self.got_table_count_dirty = false;
1487fn createObjcSections(self: *MachO) !void {
1488 const gpa = self.base.comp.gpa;
1489 var objc_msgsend_syms = std.AutoArrayHashMap(Symbol.Index, void).init(gpa);
1490 defer objc_msgsend_syms.deinit();
1491
1492 for (self.objects.items) |index| {
1493 const object = self.getFile(index).?.object;
1494
1495 for (object.symbols.items, 0..) |sym_index, i| {
1496 const nlist_idx = @as(Symbol.Index, @intCast(i));
1497 const nlist = object.symtab.items(.nlist)[nlist_idx];
1498 if (!nlist.ext()) continue;
1499 if (!nlist.undf()) continue;
1500
1501 const sym = self.getSymbol(sym_index);
1502 if (sym.getFile(self) != null) continue;
1503 if (mem.startsWith(u8, sym.getName(self), "_objc_msgSend$")) {
1504 _ = try objc_msgsend_syms.put(sym_index, {});
1505 }
1506 }
11931507 }
11941508
1195 const header = &self.sections.items(.header)[sect_id];
1196 const segment_index = self.sections.items(.segment_index)[sect_id];
1197 const entry = self.got_table.entries.items[index];
1198 const entry_value = self.getSymbol(entry).n_value;
1199 const entry_offset = index * @sizeOf(u64);
1200 const file_offset = header.offset + entry_offset;
1201 const vmaddr = header.addr + entry_offset;
1202
1203 log.debug("writing GOT entry {d}: @{x} => {x}", .{ index, vmaddr, entry_value });
1204
1205 var buf: [@sizeOf(u64)]u8 = undefined;
1206 mem.writeInt(u64, &buf, entry_value, .little);
1207 try self.base.file.?.pwriteAll(&buf, file_offset);
1208
1209 if (is_hot_update_compatible) {
1210 if (self.hot_state.mach_task) |task| {
1211 self.writeToMemory(task, segment_index, vmaddr, &buf) catch |err| {
1212 log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)});
1213 };
1214 }
1509 for (objc_msgsend_syms.keys()) |sym_index| {
1510 const sym = self.getSymbol(sym_index);
1511 sym.value = 0;
1512 sym.atom = 0;
1513 sym.nlist_idx = 0;
1514 sym.file = self.internal_object.?;
1515 sym.flags = .{};
1516 sym.visibility = .hidden;
1517 const object = self.getInternalObject().?;
1518 const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?;
1519 const selrefs_index = try object.addObjcMsgsendSections(name, self);
1520 try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self);
1521 try object.symbols.append(gpa, sym_index);
12151522 }
12161523}
12171524
1218fn writeStubHelperPreamble(self: *MachO) !void {
1219 if (self.stub_helper_preamble_allocated) return;
1220
1525fn claimUnresolved(self: *MachO) error{OutOfMemory}!void {
12211526 const gpa = self.base.comp.gpa;
1222 const target = self.base.comp.root_mod.resolved_target.result;
1223 const cpu_arch = target.cpu.arch;
1224 const size = stubs.stubHelperPreambleSize(cpu_arch);
1225
1226 var buf = try std.ArrayList(u8).initCapacity(gpa, size);
1227 defer buf.deinit();
1228
1229 const dyld_private_addr = self.getAtom(self.dyld_private_atom_index.?).getSymbol(self).n_value;
1230 const dyld_stub_binder_got_addr = blk: {
1231 const index = self.got_table.lookup.get(self.getGlobalByIndex(self.dyld_stub_binder_index.?)).?;
1232 const header = self.sections.items(.header)[self.got_section_index.?];
1233 break :blk header.addr + @sizeOf(u64) * index;
1234 };
1235 const header = self.sections.items(.header)[self.stub_helper_section_index.?];
12361527
1237 try stubs.writeStubHelperPreambleCode(.{
1238 .cpu_arch = cpu_arch,
1239 .source_addr = header.addr,
1240 .dyld_private_addr = dyld_private_addr,
1241 .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
1242 }, buf.writer());
1243 try self.base.file.?.pwriteAll(buf.items, header.offset);
1528 var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
1529 defer objects.deinit();
1530 if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
1531 objects.appendSliceAssumeCapacity(self.objects.items);
12441532
1245 self.stub_helper_preamble_allocated = true;
1246}
1533 for (objects.items) |index| {
1534 const file = self.getFile(index).?;
12471535
1248fn writeStubTableEntry(self: *MachO, index: usize) !void {
1249 const target = self.base.comp.root_mod.resolved_target.result;
1250 const stubs_sect_id = self.stubs_section_index.?;
1251 const stub_helper_sect_id = self.stub_helper_section_index.?;
1252 const laptr_sect_id = self.la_symbol_ptr_section_index.?;
1536 for (file.getSymbols(), 0..) |sym_index, i| {
1537 const nlist_idx = @as(Symbol.Index, @intCast(i));
1538 const nlist = switch (file) {
1539 .object => |x| x.symtab.items(.nlist)[nlist_idx],
1540 .zig_object => |x| x.symtab.items(.nlist)[nlist_idx],
1541 else => unreachable,
1542 };
1543 if (!nlist.ext()) continue;
1544 if (!nlist.undf()) continue;
12531545
1254 const cpu_arch = target.cpu.arch;
1255 const stub_entry_size = stubs.stubSize(cpu_arch);
1256 const stub_helper_entry_size = stubs.stubHelperSize(cpu_arch);
1257 const stub_helper_preamble_size = stubs.stubHelperPreambleSize(cpu_arch);
1546 const sym = self.getSymbol(sym_index);
1547 if (sym.getFile(self) != null) continue;
12581548
1259 if (self.stub_table_count_dirty) {
1260 // We grow all 3 sections one by one.
1261 {
1262 const needed_size = stub_entry_size * self.stub_table.entries.items.len;
1263 try self.growSection(stubs_sect_id, needed_size);
1264 }
1265 {
1266 const needed_size = stub_helper_preamble_size + stub_helper_entry_size * self.stub_table.entries.items.len;
1267 try self.growSection(stub_helper_sect_id, needed_size);
1268 }
1269 {
1270 const needed_size = @sizeOf(u64) * self.stub_table.entries.items.len;
1271 try self.growSection(laptr_sect_id, needed_size);
1549 const is_import = switch (self.undefined_treatment) {
1550 .@"error" => false,
1551 .warn, .suppress => nlist.weakRef(),
1552 .dynamic_lookup => true,
1553 };
1554 if (is_import) {
1555 sym.value = 0;
1556 sym.atom = 0;
1557 sym.nlist_idx = 0;
1558 sym.file = self.internal_object.?;
1559 sym.flags.weak = false;
1560 sym.flags.weak_ref = nlist.weakRef();
1561 sym.flags.import = is_import;
1562 sym.visibility = .global;
1563 try self.getInternalObject().?.symbols.append(self.base.comp.gpa, sym_index);
1564 }
12721565 }
1273 self.stub_table_count_dirty = false;
12741566 }
1567}
12751568
1569fn checkDuplicates(self: *MachO) !void {
12761570 const gpa = self.base.comp.gpa;
12771571
1278 const stubs_header = self.sections.items(.header)[stubs_sect_id];
1279 const stub_helper_header = self.sections.items(.header)[stub_helper_sect_id];
1280 const laptr_header = self.sections.items(.header)[laptr_sect_id];
1281
1282 const entry = self.stub_table.entries.items[index];
1283 const stub_addr: u64 = stubs_header.addr + stub_entry_size * index;
1284 const stub_helper_addr: u64 = stub_helper_header.addr + stub_helper_preamble_size + stub_helper_entry_size * index;
1285 const laptr_addr: u64 = laptr_header.addr + @sizeOf(u64) * index;
1286
1287 log.debug("writing stub entry {d}: @{x} => '{s}'", .{ index, stub_addr, self.getSymbolName(entry) });
1288
1289 {
1290 var buf = try std.ArrayList(u8).initCapacity(gpa, stub_entry_size);
1291 defer buf.deinit();
1292 try stubs.writeStubCode(.{
1293 .cpu_arch = cpu_arch,
1294 .source_addr = stub_addr,
1295 .target_addr = laptr_addr,
1296 }, buf.writer());
1297 const off = stubs_header.offset + stub_entry_size * index;
1298 try self.base.file.?.pwriteAll(buf.items, off);
1572 var dupes = std.AutoArrayHashMap(Symbol.Index, std.ArrayListUnmanaged(File.Index)).init(gpa);
1573 defer {
1574 for (dupes.values()) |*list| {
1575 list.deinit(gpa);
1576 }
1577 dupes.deinit();
12991578 }
13001579
1301 {
1302 var buf = try std.ArrayList(u8).initCapacity(gpa, stub_helper_entry_size);
1303 defer buf.deinit();
1304 try stubs.writeStubHelperCode(.{
1305 .cpu_arch = cpu_arch,
1306 .source_addr = stub_helper_addr,
1307 .target_addr = stub_helper_header.addr,
1308 }, buf.writer());
1309 const off = stub_helper_header.offset + stub_helper_preamble_size + stub_helper_entry_size * index;
1310 try self.base.file.?.pwriteAll(buf.items, off);
1580 if (self.getZigObject()) |zo| {
1581 try zo.checkDuplicates(&dupes, self);
13111582 }
13121583
1313 {
1314 var buf: [@sizeOf(u64)]u8 = undefined;
1315 mem.writeInt(u64, &buf, stub_helper_addr, .little);
1316 const off = laptr_header.offset + @sizeOf(u64) * index;
1317 try self.base.file.?.pwriteAll(&buf, off);
1584 for (self.objects.items) |index| {
1585 try self.getFile(index).?.object.checkDuplicates(&dupes, self);
13181586 }
13191587
1320 // TODO: generating new stub entry will require pulling the address of the symbol from the
1321 // target dylib when updating directly in memory.
1322 if (is_hot_update_compatible) {
1323 if (self.hot_state.mach_task) |_| {
1324 @panic("TODO: update a stub entry in memory");
1325 }
1326 }
1588 try self.reportDuplicates(dupes);
13271589}
13281590
1329fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void {
1330 log.debug("marking relocs dirty by target: {}", .{target});
1331 // TODO: reverse-lookup might come in handy here
1332 for (self.relocs.values()) |*relocs| {
1333 for (relocs.items) |*reloc| {
1334 if (!reloc.target.eql(target)) continue;
1335 reloc.dirty = true;
1591fn markImportsAndExports(self: *MachO) error{OutOfMemory}!void {
1592 const gpa = self.base.comp.gpa;
1593 var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
1594 defer objects.deinit();
1595 if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
1596 objects.appendSliceAssumeCapacity(self.objects.items);
1597
1598 for (objects.items) |index| {
1599 for (self.getFile(index).?.getSymbols()) |sym_index| {
1600 const sym = self.getSymbol(sym_index);
1601 const file = sym.getFile(self) orelse continue;
1602 if (sym.visibility != .global) continue;
1603 if (file == .dylib and !sym.flags.abs) {
1604 sym.flags.import = true;
1605 continue;
1606 }
1607 if (file.getIndex() == index) {
1608 sym.flags.@"export" = true;
1609 }
13361610 }
13371611 }
1338}
13391612
1340fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void {
1341 log.debug("marking relocs dirty by address: {x}", .{addr});
1342
1343 const got_moved = blk: {
1344 const sect_id = self.got_section_index orelse break :blk false;
1345 break :blk self.sections.items(.header)[sect_id].addr > addr;
1346 };
1347 const stubs_moved = blk: {
1348 const sect_id = self.stubs_section_index orelse break :blk false;
1349 break :blk self.sections.items(.header)[sect_id].addr > addr;
1350 };
1613 for (self.undefined_symbols.items) |index| {
1614 const sym = self.getSymbol(index);
1615 if (sym.getFile(self)) |file| {
1616 if (sym.visibility != .global) continue;
1617 if (file == .dylib and !sym.flags.abs) sym.flags.import = true;
1618 }
1619 }
13511620
1352 for (self.relocs.values()) |*relocs| {
1353 for (relocs.items) |*reloc| {
1354 if (reloc.isGotIndirection()) {
1355 reloc.dirty = reloc.dirty or got_moved;
1356 } else if (reloc.isStubTrampoline(self)) {
1357 reloc.dirty = reloc.dirty or stubs_moved;
1358 } else {
1359 const target_addr = reloc.getTargetBaseAddress(self) orelse continue;
1360 if (target_addr > addr) reloc.dirty = true;
1621 for (&[_]?Symbol.Index{
1622 self.entry_index,
1623 self.dyld_stub_binder_index,
1624 self.objc_msg_send_index,
1625 }) |index| {
1626 if (index) |idx| {
1627 const sym = self.getSymbol(idx);
1628 if (sym.getFile(self)) |file| {
1629 if (file == .dylib) sym.flags.import = true;
13611630 }
13621631 }
13631632 }
1633}
13641634
1365 // TODO: dirty only really affected GOT cells
1366 for (self.got_table.entries.items) |entry| {
1367 const target_addr = self.getSymbol(entry).n_value;
1368 if (target_addr > addr) {
1369 self.got_table_contents_dirty = true;
1370 break;
1635fn deadStripDylibs(self: *MachO) void {
1636 for (&[_]?Symbol.Index{
1637 self.entry_index,
1638 self.dyld_stub_binder_index,
1639 self.objc_msg_send_index,
1640 }) |index| {
1641 if (index) |idx| {
1642 const sym = self.getSymbol(idx);
1643 if (sym.getFile(self)) |file| {
1644 if (file == .dylib) file.dylib.referenced = true;
1645 }
13711646 }
13721647 }
13731648
1374 {
1375 const stubs_addr = self.getSegment(self.stubs_section_index.?).vmaddr;
1376 const stub_helper_addr = self.getSegment(self.stub_helper_section_index.?).vmaddr;
1377 const laptr_addr = self.getSegment(self.la_symbol_ptr_section_index.?).vmaddr;
1378 if (stubs_addr > addr or stub_helper_addr > addr or laptr_addr > addr)
1379 self.stub_table_contents_dirty = true;
1380 }
1381}
1382
1383pub fn allocateSpecialSymbols(self: *MachO) !void {
1384 for (&[_][]const u8{
1385 "___dso_handle",
1386 "__mh_execute_header",
1387 }) |name| {
1388 const global = self.getGlobal(name) orelse continue;
1389 if (global.getFile() != null) continue;
1390 const sym = self.getSymbolPtr(global);
1391 const seg = self.getSegment(self.text_section_index.?);
1392 sym.n_sect = self.text_section_index.? + 1;
1393 sym.n_value = seg.vmaddr;
1394
1395 log.debug("allocating {s}(@0x{x},sect({d})) at the start of {s}", .{
1396 name,
1397 sym.n_value,
1398 sym.n_sect,
1399 seg.segName(),
1400 });
1649 for (self.dylibs.items) |index| {
1650 self.getFile(index).?.dylib.markReferenced(self);
14011651 }
14021652
1403 for (self.globals.items) |global| {
1404 const sym = self.getSymbolPtr(global);
1405 if (sym.n_desc != N_BOUNDARY) continue;
1406 if (self.getSectionBoundarySymbol(global)) |bsym| {
1407 const sect_id = self.getSectionByName(bsym.segname, bsym.sectname) orelse {
1408 try self.reportUnresolvedBoundarySymbol(self.getSymbolName(global), "section not found: {s},{s}", .{
1409 bsym.segname, bsym.sectname,
1410 });
1411 continue;
1412 };
1413 const sect = self.sections.items(.header)[sect_id];
1414 sym.n_sect = sect_id + 1;
1415 sym.n_value = switch (bsym.kind) {
1416 .start => sect.addr,
1417 .stop => sect.addr + sect.size,
1418 };
1419
1420 log.debug("allocating {s} at @0x{x} sect({d})", .{
1421 self.getSymbolName(global),
1422 sym.n_value,
1423 sym.n_sect,
1424 });
1425
1426 continue;
1427 }
1428 if (self.getSegmentBoundarySymbol(global)) |bsym| {
1429 const seg_id = self.getSegmentByName(bsym.segname) orelse {
1430 try self.reportUnresolvedBoundarySymbol(self.getSymbolName(global), "segment not found: {s}", .{
1431 bsym.segname,
1432 });
1433
1434 continue;
1435 };
1436 const seg = self.segments.items[seg_id];
1437 sym.n_value = switch (bsym.kind) {
1438 .start => seg.vmaddr,
1439 .stop => seg.vmaddr + seg.vmsize,
1440 };
1441
1442 log.debug("allocating {s} at @0x{x} ", .{ self.getSymbolName(global), sym.n_value });
1443
1444 continue;
1445 }
1653 var i: usize = 0;
1654 while (i < self.dylibs.items.len) {
1655 const index = self.dylibs.items[i];
1656 if (!self.getFile(index).?.dylib.isAlive(self)) {
1657 _ = self.dylibs.orderedRemove(i);
1658 } else i += 1;
14461659 }
14471660}
14481661
1449const CreateAtomOpts = struct {
1450 size: u64 = 0,
1451 alignment: Alignment = .@"1",
1452};
1453
1454pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Index {
1455 const gpa = self.base.comp.gpa;
1456 const index = @as(Atom.Index, @intCast(self.atoms.items.len));
1457 const atom = try self.atoms.addOne(gpa);
1458 atom.* = .{};
1459 atom.sym_index = sym_index;
1460 atom.size = opts.size;
1461 atom.alignment = opts.alignment;
1462 log.debug("creating ATOM(%{d}) at index {d}", .{ sym_index, index });
1463 return index;
1464}
1465
1466pub fn createTentativeDefAtoms(self: *MachO) !void {
1467 const gpa = self.base.comp.gpa;
1662fn scanRelocs(self: *MachO) !void {
1663 const tracy = trace(@src());
1664 defer tracy.end();
14681665
1469 for (self.globals.items) |global| {
1470 const sym = self.getSymbolPtr(global);
1471 if (!sym.tentative()) continue;
1472 if (sym.n_desc == N_DEAD) continue;
1473 if (sym.n_desc == N_BOUNDARY) continue;
1666 if (self.getZigObject()) |zo| try zo.scanRelocs(self);
14741667
1475 log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{
1476 global.sym_index, self.getSymbolName(global), global.file,
1477 });
1668 for (self.objects.items) |index| {
1669 try self.getFile(index).?.object.scanRelocs(self);
1670 }
14781671
1479 // Convert any tentative definition into a regular symbol and allocate
1480 // text blocks for each tentative definition.
1481 const size = sym.n_value;
1482 const alignment = (sym.n_desc >> 8) & 0x0f;
1672 try self.reportUndefs();
14831673
1484 if (self.bss_section_index == null) {
1485 self.bss_section_index = try self.initSection("__DATA", "__bss", .{
1486 .flags = macho.S_ZEROFILL,
1487 });
1674 if (self.entry_index) |index| {
1675 const sym = self.getSymbol(index);
1676 if (sym.getFile(self) != null) {
1677 if (sym.flags.import) sym.flags.stubs = true;
14881678 }
1489
1490 sym.* = .{
1491 .n_strx = sym.n_strx,
1492 .n_type = macho.N_SECT | macho.N_EXT,
1493 .n_sect = self.bss_section_index.? + 1,
1494 .n_desc = 0,
1495 .n_value = 0,
1496 };
1497
1498 const atom_index = try self.createAtom(global.sym_index, .{
1499 .size = size,
1500 .alignment = @enumFromInt(alignment),
1501 });
1502 const atom = self.getAtomPtr(atom_index);
1503 atom.file = global.file;
1504
1505 self.addAtomToSection(atom_index);
1506
1507 assert(global.getFile() != null);
1508 const object = &self.objects.items[global.getFile().?];
1509 try object.atoms.append(gpa, atom_index);
1510 object.atom_by_index_table[global.sym_index] = atom_index;
15111679 }
1512}
1513
1514pub fn createDyldPrivateAtom(self: *MachO) !void {
1515 if (self.dyld_private_atom_index != null) return;
1516
1517 const sym_index = try self.allocateSymbol();
1518 const atom_index = try self.createAtom(sym_index, .{
1519 .size = @sizeOf(u64),
1520 .alignment = .@"8",
1521 });
1522 const gpa = self.base.comp.gpa;
1523 try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
15241680
1525 if (self.data_section_index == null) {
1526 self.data_section_index = try self.initSection("__DATA", "__data", .{});
1681 if (self.dyld_stub_binder_index) |index| {
1682 const sym = self.getSymbol(index);
1683 if (sym.getFile(self) != null) sym.flags.needs_got = true;
15271684 }
15281685
1529 const atom = self.getAtom(atom_index);
1530 const sym = atom.getSymbolPtr(self);
1531 sym.n_type = macho.N_SECT;
1532 sym.n_sect = self.data_section_index.? + 1;
1533 self.dyld_private_atom_index = atom_index;
1686 if (self.objc_msg_send_index) |index| {
1687 const sym = self.getSymbol(index);
1688 if (sym.getFile(self) != null)
1689 sym.flags.needs_got = true; // TODO is it always needed, or only if we are synthesising fast stubs?
1690 }
15341691
1535 switch (self.mode) {
1536 .zld => self.addAtomToSection(atom_index),
1537 .incremental => {
1538 sym.n_value = try self.allocateAtom(atom_index, atom.size, .@"8");
1539 log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value});
1540 var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
1541 try self.writeAtom(atom_index, &buffer);
1542 },
1692 for (self.symbols.items, 0..) |*symbol, i| {
1693 const index = @as(Symbol.Index, @intCast(i));
1694 if (symbol.flags.needs_got) {
1695 log.debug("'{s}' needs GOT", .{symbol.getName(self)});
1696 try self.got.addSymbol(index, self);
1697 }
1698 if (symbol.flags.stubs) {
1699 log.debug("'{s}' needs STUBS", .{symbol.getName(self)});
1700 try self.stubs.addSymbol(index, self);
1701 }
1702 if (symbol.flags.tlv_ptr) {
1703 log.debug("'{s}' needs TLV pointer", .{symbol.getName(self)});
1704 try self.tlv_ptr.addSymbol(index, self);
1705 }
1706 if (symbol.flags.objc_stubs) {
1707 log.debug("'{s}' needs OBJC STUBS", .{symbol.getName(self)});
1708 try self.objc_stubs.addSymbol(index, self);
1709 }
15431710 }
15441711}
15451712
1546fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index {
1547 const gpa = self.base.comp.gpa;
1548 const size = 3 * @sizeOf(u64);
1549 const required_alignment: Alignment = .@"1";
1550 const sym_index = try self.allocateSymbol();
1551 const atom_index = try self.createAtom(sym_index, .{});
1552 try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
1553 self.getAtomPtr(atom_index).size = size;
1554
1555 const sym = self.getAtom(atom_index).getSymbolPtr(self);
1556 sym.n_type = macho.N_SECT;
1557 sym.n_sect = self.thread_vars_section_index.? + 1;
1558 sym.n_strx = try self.strtab.insert(gpa, sym_name);
1559 sym.n_value = try self.allocateAtom(atom_index, size, required_alignment);
1560
1561 log.debug("allocated threadlocal descriptor atom '{s}' at 0x{x}", .{ sym_name, sym.n_value });
1562
1563 try Atom.addRelocation(self, atom_index, .{
1564 .type = .tlv_initializer,
1565 .target = target,
1566 .offset = 0x10,
1567 .addend = 0,
1568 .pcrel = false,
1569 .length = 3,
1570 });
1713fn reportUndefs(self: *MachO) !void {
1714 const tracy = trace(@src());
1715 defer tracy.end();
15711716
1572 var code: [size]u8 = undefined;
1573 @memset(&code, 0);
1574 try self.writeAtom(atom_index, &code);
1717 switch (self.undefined_treatment) {
1718 .dynamic_lookup, .suppress => return,
1719 .@"error", .warn => {},
1720 }
15751721
1576 return atom_index;
1577}
1722 const max_notes = 4;
15781723
1579pub fn createMhExecuteHeaderSymbol(self: *MachO) !void {
1580 const output_mode = self.base.comp.config.output_mode;
1581 if (output_mode != .Exe) return;
1724 var has_undefs = false;
1725 var it = self.undefs.iterator();
1726 while (it.next()) |entry| {
1727 const undef_sym = self.getSymbol(entry.key_ptr.*);
1728 const notes = entry.value_ptr.*;
1729 const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
15821730
1583 const gpa = self.base.comp.gpa;
1584 const sym_index = try self.allocateSymbol();
1585 const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
1586 const sym = self.getSymbolPtr(sym_loc);
1587 sym.* = .{
1588 .n_strx = try self.strtab.insert(gpa, "__mh_execute_header"),
1589 .n_type = macho.N_SECT | macho.N_EXT,
1590 .n_sect = 0,
1591 .n_desc = macho.REFERENCED_DYNAMICALLY,
1592 .n_value = 0,
1593 };
1731 var err = try self.addErrorWithNotes(nnotes);
1732 try err.addMsg(self, "undefined symbol: {s}", .{undef_sym.getName(self)});
1733 has_undefs = true;
15941734
1595 const gop = try self.getOrPutGlobalPtr("__mh_execute_header");
1596 if (gop.found_existing) {
1597 const global = gop.value_ptr.*;
1598 if (global.getFile()) |file| {
1599 const global_object = &self.objects.items[file];
1600 global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?;
1735 var inote: usize = 0;
1736 while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
1737 const atom = self.getAtom(notes.items[inote]).?;
1738 const file = atom.getFile(self);
1739 try err.addNote(self, "referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) });
16011740 }
1602 }
1603 gop.value_ptr.* = sym_loc;
1604}
1605
1606pub fn createDsoHandleSymbol(self: *MachO) !void {
1607 const global = self.getGlobalPtr("___dso_handle") orelse return;
1608 if (!self.getSymbol(global.*).undf()) return;
1609
1610 const gpa = self.base.comp.gpa;
1611 const sym_index = try self.allocateSymbol();
1612 const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
1613 const sym = self.getSymbolPtr(sym_loc);
1614 sym.* = .{
1615 .n_strx = try self.strtab.insert(gpa, "___dso_handle"),
1616 .n_type = macho.N_SECT | macho.N_EXT,
1617 .n_sect = 0,
1618 .n_desc = macho.N_WEAK_DEF,
1619 .n_value = 0,
1620 };
1621 const global_index = self.getGlobalIndex("___dso_handle").?;
1622 if (global.getFile()) |file| {
1623 const global_object = &self.objects.items[file];
1624 global_object.globals_lookup[global.sym_index] = global_index;
1625 }
1626 global.* = sym_loc;
1627 _ = self.unresolved.swapRemove(self.getGlobalIndex("___dso_handle").?);
1628}
16291741
1630pub fn resolveSymbols(self: *MachO) !void {
1631 const comp = self.base.comp;
1632 const output_mode = comp.config.output_mode;
1633 // We add the specified entrypoint as the first unresolved symbols so that
1634 // we search for it in libraries should there be no object files specified
1635 // on the linker line.
1636 if (output_mode == .Exe) {
1637 if (self.entry_name) |entry_name| {
1638 _ = try self.addUndefined(entry_name, .{});
1742 if (notes.items.len > max_notes) {
1743 const remaining = notes.items.len - max_notes;
1744 try err.addNote(self, "referenced {d} more times", .{remaining});
16391745 }
16401746 }
16411747
1642 // Force resolution of any symbols requested by the user.
1643 for (comp.force_undefined_symbols.keys()) |sym_name| {
1644 _ = try self.addUndefined(sym_name, .{});
1748 for (self.undefined_symbols.items) |index| {
1749 const sym = self.getSymbol(index);
1750 if (sym.getFile(self) != null) continue; // If undefined in an object file, will be reported above
1751 has_undefs = true;
1752 var err = try self.addErrorWithNotes(1);
1753 try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
1754 try err.addNote(self, "-u command line option", .{});
16451755 }
16461756
1647 for (self.objects.items, 0..) |_, object_id| {
1648 try self.resolveSymbolsInObject(@as(u32, @intCast(object_id)));
1757 if (self.entry_index) |index| {
1758 const sym = self.getSymbol(index);
1759 if (sym.getFile(self) == null) {
1760 has_undefs = true;
1761 var err = try self.addErrorWithNotes(1);
1762 try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
1763 try err.addNote(self, "implicit entry/start for main executable", .{});
1764 }
16491765 }
16501766
1651 try self.resolveSymbolsInArchives();
1652
1653 // Finally, force resolution of dyld_stub_binder if there are imports
1654 // requested.
1655 if (self.unresolved.count() > 0 and self.dyld_stub_binder_index == null) {
1656 self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .{ .add_got = true });
1657 }
1658 if (comp.config.any_non_single_threaded and self.mode == .incremental) {
1659 _ = try self.addUndefined("__tlv_bootstrap", .{});
1767 if (self.dyld_stub_binder_index) |index| {
1768 const sym = self.getSymbol(index);
1769 if (sym.getFile(self) == null and self.stubs_sect_index != null) {
1770 has_undefs = true;
1771 var err = try self.addErrorWithNotes(1);
1772 try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
1773 try err.addNote(self, "implicit -u command line option", .{});
1774 }
16601775 }
16611776
1662 try self.resolveSymbolsInDylibs();
1663
1664 try self.createMhExecuteHeaderSymbol();
1665 try self.createDsoHandleSymbol();
1666 try self.resolveSymbolsAtLoading();
1777 if (self.objc_msg_send_index) |index| {
1778 const sym = self.getSymbol(index);
1779 if (sym.getFile(self) == null and self.objc_stubs_sect_index != null) {
1780 has_undefs = true;
1781 var err = try self.addErrorWithNotes(1);
1782 try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)});
1783 try err.addNote(self, "implicit -u command line option", .{});
1784 }
1785 }
16671786
1668 // Final stop, check if unresolved contain any of the special magic boundary symbols
1669 // * section$start$
1670 // * section$stop$
1671 // * segment$start$
1672 // * segment$stop$
1673 try self.resolveBoundarySymbols();
1787 if (has_undefs) return error.HasUndefinedSymbols;
16741788}
16751789
1676fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void {
1677 const gpa = self.base.comp.gpa;
1678 const sym = self.getSymbol(current);
1679 const sym_name = self.getSymbolName(current);
1680
1681 const gop = try self.getOrPutGlobalPtr(sym_name);
1682 if (!gop.found_existing) {
1683 gop.value_ptr.* = current;
1684 if (sym.undf() and !sym.tentative()) {
1685 try self.unresolved.putNoClobber(gpa, self.getGlobalIndex(sym_name).?, {});
1790fn initOutputSections(self: *MachO) !void {
1791 for (self.objects.items) |index| {
1792 const object = self.getFile(index).?.object;
1793 for (object.atoms.items) |atom_index| {
1794 const atom = self.getAtom(atom_index) orelse continue;
1795 if (!atom.flags.alive) continue;
1796 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);
16861797 }
1687 return;
1688 }
1689 const global_index = self.getGlobalIndex(sym_name).?;
1690 const global = gop.value_ptr.*;
1691 const global_sym = self.getSymbol(global);
1692
1693 // Cases to consider: sym vs global_sym
1694 // 1. strong(sym) and strong(global_sym) => error
1695 // 2. strong(sym) and weak(global_sym) => sym
1696 // 3. strong(sym) and tentative(global_sym) => sym
1697 // 4. strong(sym) and undf(global_sym) => sym
1698 // 5. weak(sym) and strong(global_sym) => global_sym
1699 // 6. weak(sym) and tentative(global_sym) => sym
1700 // 7. weak(sym) and undf(global_sym) => sym
1701 // 8. tentative(sym) and strong(global_sym) => global_sym
1702 // 9. tentative(sym) and weak(global_sym) => global_sym
1703 // 10. tentative(sym) and tentative(global_sym) => pick larger
1704 // 11. tentative(sym) and undf(global_sym) => sym
1705 // 12. undf(sym) and * => global_sym
1706 //
1707 // Reduces to:
1708 // 1. strong(sym) and strong(global_sym) => error
1709 // 2. * and strong(global_sym) => global_sym
1710 // 3. weak(sym) and weak(global_sym) => global_sym
1711 // 4. tentative(sym) and tentative(global_sym) => pick larger
1712 // 5. undf(sym) and * => global_sym
1713 // 6. else => sym
1714
1715 const sym_is_strong = sym.sect() and !(sym.weakDef() or sym.pext());
1716 const global_is_strong = global_sym.sect() and !(global_sym.weakDef() or global_sym.pext());
1717 const sym_is_weak = sym.sect() and (sym.weakDef() or sym.pext());
1718 const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext());
1719
1720 if (sym_is_strong and global_is_strong) {
1721 // TODO redo this logic with corresponding logic in updateExports to avoid this
1722 // ugly check.
1723 if (self.mode == .zld) {
1724 try self.reportSymbolCollision(global, current);
1798 }
1799 if (self.getInternalObject()) |object| {
1800 for (object.atoms.items) |atom_index| {
1801 const atom = self.getAtom(atom_index) orelse continue;
1802 if (!atom.flags.alive) continue;
1803 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);
17251804 }
1726 return error.MultipleSymbolDefinitions;
17271805 }
1728
1729 if (current.getFile()) |file| {
1730 const object = &self.objects.items[file];
1731 object.globals_lookup[current.sym_index] = global_index;
1806 if (self.text_sect_index == null) {
1807 self.text_sect_index = try self.addSection("__TEXT", "__text", .{
1808 .alignment = switch (self.getTarget().cpu.arch) {
1809 .x86_64 => 0,
1810 .aarch64 => 2,
1811 else => unreachable,
1812 },
1813 .flags = macho.S_REGULAR |
1814 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1815 });
17321816 }
1733
1734 if (global_is_strong) return;
1735 if (sym_is_weak and global_is_weak) return;
1736 if (sym.tentative() and global_sym.tentative()) {
1737 if (global_sym.n_value >= sym.n_value) return;
1817 if (self.data_sect_index == null) {
1818 self.data_sect_index = try self.addSection("__DATA", "__data", .{});
17381819 }
1739 if (sym.undf() and !sym.tentative()) return;
1820}
17401821
1741 if (global.getFile()) |file| {
1742 const global_object = &self.objects.items[file];
1743 global_object.globals_lookup[global.sym_index] = global_index;
1744 }
1745 _ = self.unresolved.swapRemove(global_index);
1822fn initSyntheticSections(self: *MachO) !void {
1823 const cpu_arch = self.getTarget().cpu.arch;
17461824
1747 gop.value_ptr.* = current;
1748}
1749
1750fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void {
1751 const object = &self.objects.items[object_id];
1752 const in_symtab = object.in_symtab orelse return;
1825 if (self.got.symbols.items.len > 0) {
1826 self.got_sect_index = try self.addSection("__DATA_CONST", "__got", .{
1827 .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
1828 .reserved1 = @intCast(self.stubs.symbols.items.len),
1829 });
1830 }
17531831
1754 log.debug("resolving symbols in '{s}'", .{object.name});
1832 if (self.stubs.symbols.items.len > 0) {
1833 self.stubs_sect_index = try self.addSection("__TEXT", "__stubs", .{
1834 .flags = macho.S_SYMBOL_STUBS |
1835 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1836 .reserved1 = 0,
1837 .reserved2 = switch (cpu_arch) {
1838 .x86_64 => 6,
1839 .aarch64 => 3 * @sizeOf(u32),
1840 else => 0,
1841 },
1842 });
1843 self.stubs_helper_sect_index = try self.addSection("__TEXT", "__stub_helper", .{
1844 .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1845 });
1846 self.la_symbol_ptr_sect_index = try self.addSection("__DATA", "__la_symbol_ptr", .{
1847 .flags = macho.S_LAZY_SYMBOL_POINTERS,
1848 .reserved1 = @intCast(self.stubs.symbols.items.len + self.got.symbols.items.len),
1849 });
1850 }
17551851
1756 var sym_index: u32 = 0;
1757 while (sym_index < in_symtab.len) : (sym_index += 1) {
1758 const sym = &object.symtab[sym_index];
1759 const sym_name = object.getSymbolName(sym_index);
1760 const sym_with_loc = SymbolWithLoc{
1761 .sym_index = sym_index,
1762 .file = object_id + 1,
1763 };
1852 if (self.objc_stubs.symbols.items.len > 0) {
1853 self.objc_stubs_sect_index = try self.addSection("__TEXT", "__objc_stubs", .{
1854 .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1855 });
1856 }
17641857
1765 if (sym.stab() or sym.indr() or sym.abs()) {
1766 try self.reportUnhandledSymbolType(sym_with_loc);
1767 continue;
1768 }
1858 if (self.tlv_ptr.symbols.items.len > 0) {
1859 self.tlv_ptr_sect_index = try self.addSection("__DATA", "__thread_ptrs", .{
1860 .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
1861 });
1862 }
17691863
1770 if (sym.sect() and !sym.ext()) {
1771 log.debug("symbol '{s}' local to object {s}; skipping...", .{
1772 sym_name,
1773 object.name,
1774 });
1775 continue;
1776 }
1864 const needs_unwind_info = for (self.objects.items) |index| {
1865 if (self.getFile(index).?.object.hasUnwindRecords()) break true;
1866 } else false;
1867 if (needs_unwind_info) {
1868 self.unwind_info_sect_index = try self.addSection("__TEXT", "__unwind_info", .{});
1869 }
17771870
1778 self.resolveGlobalSymbol(.{
1779 .sym_index = sym_index,
1780 .file = object_id + 1,
1781 }) catch |err| switch (err) {
1782 error.MultipleSymbolDefinitions => return error.FlushFailure,
1783 else => |e| return e,
1784 };
1871 const needs_eh_frame = for (self.objects.items) |index| {
1872 if (self.getFile(index).?.object.hasEhFrameRecords()) break true;
1873 } else false;
1874 if (needs_eh_frame) {
1875 assert(needs_unwind_info);
1876 self.eh_frame_sect_index = try self.addSection("__TEXT", "__eh_frame", .{});
17851877 }
1786}
17871878
1788fn resolveSymbolsInArchives(self: *MachO) !void {
1789 if (self.archives.items.len == 0) return;
1879 for (self.boundary_symbols.items) |sym_index| {
1880 const gpa = self.base.comp.gpa;
1881 const sym = self.getSymbol(sym_index);
1882 const name = sym.getName(self);
17901883
1791 const gpa = self.base.comp.gpa;
1792 var next_sym: usize = 0;
1793 loop: while (next_sym < self.unresolved.count()) {
1794 const global = self.globals.items[self.unresolved.keys()[next_sym]];
1795 const sym_name = self.getSymbolName(global);
1796
1797 for (self.archives.items) |archive| {
1798 // Check if the entry exists in a static archive.
1799 const offsets = archive.toc.get(sym_name) orelse {
1800 // No hit.
1801 continue;
1802 };
1803 assert(offsets.items.len > 0);
1884 if (eatPrefix(name, "segment$start$")) |segname| {
1885 if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
1886 const prot = getSegmentProt(segname);
1887 _ = try self.segments.append(gpa, .{
1888 .cmdsize = @sizeOf(macho.segment_command_64),
1889 .segname = makeStaticString(segname),
1890 .initprot = prot,
1891 .maxprot = prot,
1892 });
1893 }
1894 } else if (eatPrefix(name, "segment$stop$")) |segname| {
1895 if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
1896 const prot = getSegmentProt(segname);
1897 _ = try self.segments.append(gpa, .{
1898 .cmdsize = @sizeOf(macho.segment_command_64),
1899 .segname = makeStaticString(segname),
1900 .initprot = prot,
1901 .maxprot = prot,
1902 });
1903 }
1904 } else if (eatPrefix(name, "section$start$")) |actual_name| {
1905 const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
1906 const segname = actual_name[0..sep]; // TODO check segname is valid
1907 const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
1908 if (self.getSectionByName(segname, sectname) == null) {
1909 _ = try self.addSection(segname, sectname, .{});
1910 }
1911 } else if (eatPrefix(name, "section$stop$")) |actual_name| {
1912 const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
1913 const segname = actual_name[0..sep]; // TODO check segname is valid
1914 const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
1915 if (self.getSectionByName(segname, sectname) == null) {
1916 _ = try self.addSection(segname, sectname, .{});
1917 }
1918 } else unreachable;
1919 }
1920}
18041921
1805 const object_id = @as(u16, @intCast(self.objects.items.len));
1806 const object = try archive.parseObject(gpa, offsets.items[0]);
1807 try self.objects.append(gpa, object);
1808 try self.resolveSymbolsInObject(object_id);
1922fn getSegmentProt(segname: []const u8) macho.vm_prot_t {
1923 if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE;
1924 if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC;
1925 if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ;
1926 return macho.PROT.READ | macho.PROT.WRITE;
1927}
18091928
1810 continue :loop;
1811 }
1929fn getSegmentRank(segname: []const u8) u8 {
1930 if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0;
1931 if (mem.eql(u8, segname, "__LINKEDIT")) return 0xf;
1932 if (mem.indexOf(u8, segname, "ZIG")) |_| return 0xe;
1933 if (mem.startsWith(u8, segname, "__TEXT")) return 0x1;
1934 if (mem.startsWith(u8, segname, "__DATA_CONST")) return 0x2;
1935 if (mem.startsWith(u8, segname, "__DATA")) return 0x3;
1936 return 0x4;
1937}
18121938
1813 next_sym += 1;
1939fn segmentLessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool {
1940 _ = ctx;
1941 const lhs_rank = getSegmentRank(lhs);
1942 const rhs_rank = getSegmentRank(rhs);
1943 if (lhs_rank == rhs_rank) {
1944 return mem.order(u8, lhs, rhs) == .lt;
18141945 }
1946 return lhs_rank < rhs_rank;
18151947}
18161948
1817fn resolveSymbolsInDylibs(self: *MachO) !void {
1818 if (self.dylibs.items.len == 0) return;
1819
1820 const gpa = self.base.comp.gpa;
1821 var next_sym: usize = 0;
1822 loop: while (next_sym < self.unresolved.count()) {
1823 const global_index = self.unresolved.keys()[next_sym];
1824 const global = self.globals.items[global_index];
1825 const sym = self.getSymbolPtr(global);
1826 const sym_name = self.getSymbolName(global);
1827
1828 for (self.dylibs.items, 0..) |dylib, id| {
1829 if (!dylib.symbols.contains(sym_name)) continue;
1830
1831 const dylib_id = @as(u16, @intCast(id));
1832 if (!self.referenced_dylibs.contains(dylib_id)) {
1833 try self.referenced_dylibs.putNoClobber(gpa, dylib_id, {});
1834 }
1835
1836 const ordinal = self.referenced_dylibs.getIndex(dylib_id) orelse unreachable;
1837 sym.n_type |= macho.N_EXT;
1838 sym.n_desc = @as(u16, @intCast(ordinal + 1)) * macho.N_SYMBOL_RESOLVER;
1949fn getSectionRank(section: macho.section_64) u8 {
1950 if (section.isCode()) {
1951 if (mem.eql(u8, "__text", section.sectName())) return 0x0;
1952 if (section.type() == macho.S_SYMBOL_STUBS) return 0x1;
1953 return 0x2;
1954 }
1955 switch (section.type()) {
1956 macho.S_NON_LAZY_SYMBOL_POINTERS,
1957 macho.S_LAZY_SYMBOL_POINTERS,
1958 => return 0x0,
18391959
1840 if (dylib.weak) {
1841 sym.n_desc |= macho.N_WEAK_REF;
1842 }
1960 macho.S_MOD_INIT_FUNC_POINTERS => return 0x1,
1961 macho.S_MOD_TERM_FUNC_POINTERS => return 0x2,
1962 macho.S_ZEROFILL => return 0xf,
1963 macho.S_THREAD_LOCAL_REGULAR => return 0xd,
1964 macho.S_THREAD_LOCAL_ZEROFILL => return 0xe,
18431965
1844 _ = self.unresolved.swapRemove(global_index);
1966 else => {
1967 if (mem.eql(u8, "__unwind_info", section.sectName())) return 0xe;
1968 if (mem.eql(u8, "__compact_unwind", section.sectName())) return 0xe;
1969 if (mem.eql(u8, "__eh_frame", section.sectName())) return 0xf;
1970 return 0x3;
1971 },
1972 }
1973}
18451974
1846 continue :loop;
1975fn sectionLessThan(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool {
1976 if (mem.eql(u8, lhs.segName(), rhs.segName())) {
1977 const lhs_rank = getSectionRank(lhs);
1978 const rhs_rank = getSectionRank(rhs);
1979 if (lhs_rank == rhs_rank) {
1980 return mem.order(u8, lhs.sectName(), rhs.sectName()) == .lt;
18471981 }
1848
1849 next_sym += 1;
1982 return lhs_rank < rhs_rank;
18501983 }
1984 return segmentLessThan(ctx, lhs.segName(), rhs.segName());
18511985}
18521986
1853fn resolveSymbolsAtLoading(self: *MachO) !void {
1854 const output_mode = self.base.comp.config.output_mode;
1855 const is_lib = output_mode == .Lib;
1856 const is_dyn_lib = self.base.comp.config.link_mode == .Dynamic and is_lib;
1857 const allow_undef = is_dyn_lib and self.base.allow_shlib_undefined;
1858
1859 var next_sym: usize = 0;
1860 while (next_sym < self.unresolved.count()) {
1861 const global_index = self.unresolved.keys()[next_sym];
1862 const global = self.globals.items[global_index];
1863 const sym = self.getSymbolPtr(global);
1987pub fn sortSections(self: *MachO) !void {
1988 const Entry = struct {
1989 index: u8,
18641990
1865 if (sym.discarded()) {
1866 sym.* = .{
1867 .n_strx = 0,
1868 .n_type = macho.N_UNDF,
1869 .n_sect = 0,
1870 .n_desc = 0,
1871 .n_value = 0,
1872 };
1873 _ = self.unresolved.swapRemove(global_index);
1874 continue;
1875 } else if (allow_undef) {
1876 const n_desc = @as(
1877 u16,
1878 @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP * @as(i16, @intCast(macho.N_SYMBOL_RESOLVER))),
1991 pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool {
1992 return sectionLessThan(
1993 {},
1994 macho_file.sections.items(.header)[lhs.index],
1995 macho_file.sections.items(.header)[rhs.index],
18791996 );
1880 sym.n_type = macho.N_EXT;
1881 sym.n_desc = n_desc;
1882 _ = self.unresolved.swapRemove(global_index);
1883 continue;
18841997 }
1998 };
18851999
1886 next_sym += 1;
1887 }
1888}
1889
1890fn resolveBoundarySymbols(self: *MachO) !void {
18912000 const gpa = self.base.comp.gpa;
1892 var next_sym: usize = 0;
1893 while (next_sym < self.unresolved.count()) {
1894 const global_index = self.unresolved.keys()[next_sym];
1895 const global = &self.globals.items[global_index];
1896
1897 if (self.getSectionBoundarySymbol(global.*) != null or self.getSegmentBoundarySymbol(global.*) != null) {
1898 const sym_index = try self.allocateSymbol();
1899 const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
1900 const sym = self.getSymbolPtr(sym_loc);
1901 sym.* = .{
1902 .n_strx = try self.strtab.insert(gpa, self.getSymbolName(global.*)),
1903 .n_type = macho.N_SECT | macho.N_EXT,
1904 .n_sect = 0,
1905 .n_desc = N_BOUNDARY,
1906 .n_value = 0,
1907 };
1908 if (global.getFile()) |file| {
1909 const global_object = &self.objects.items[file];
1910 global_object.globals_lookup[global.sym_index] = global_index;
1911 }
1912 global.* = sym_loc;
1913 _ = self.unresolved.swapRemove(global_index);
1914 continue;
1915 }
19162001
1917 next_sym += 1;
2002 var entries = try std.ArrayList(Entry).initCapacity(gpa, self.sections.slice().len);
2003 defer entries.deinit();
2004 for (0..self.sections.slice().len) |index| {
2005 entries.appendAssumeCapacity(.{ .index = @intCast(index) });
19182006 }
1919}
19202007
1921pub fn deinit(self: *MachO) void {
1922 const gpa = self.base.comp.gpa;
2008 mem.sort(Entry, entries.items, self, Entry.lessThan);
19232009
1924 if (self.llvm_object) |llvm_object| llvm_object.deinit();
2010 const backlinks = try gpa.alloc(u8, entries.items.len);
2011 defer gpa.free(backlinks);
2012 for (entries.items, 0..) |entry, i| {
2013 backlinks[entry.index] = @intCast(i);
2014 }
19252015
1926 if (self.d_sym) |*d_sym| {
1927 d_sym.deinit();
2016 var slice = self.sections.toOwnedSlice();
2017 defer slice.deinit(gpa);
2018
2019 try self.sections.ensureTotalCapacity(gpa, slice.len);
2020 for (entries.items) |sorted| {
2021 self.sections.appendAssumeCapacity(slice.get(sorted.index));
19282022 }
19292023
1930 self.got_table.deinit(gpa);
1931 self.stub_table.deinit(gpa);
1932 self.tlv_ptr_table.deinit(gpa);
1933 self.thunk_table.deinit(gpa);
2024 if (self.getZigObject()) |zo| {
2025 for (zo.atoms.items) |atom_index| {
2026 const atom = self.getAtom(atom_index) orelse continue;
2027 if (!atom.flags.alive) continue;
2028 atom.out_n_sect = backlinks[atom.out_n_sect];
2029 }
19342030
1935 for (self.thunks.items) |*thunk| {
1936 thunk.deinit(gpa);
2031 for (zo.symtab.items(.nlist)) |*sym| {
2032 if (sym.sect()) {
2033 sym.n_sect = backlinks[sym.n_sect];
2034 }
2035 }
2036
2037 for (zo.symbols.items) |sym_index| {
2038 const sym = self.getSymbol(sym_index);
2039 const atom = sym.getAtom(self) orelse continue;
2040 if (!atom.flags.alive) continue;
2041 if (sym.getFile(self).?.getIndex() != zo.index) continue;
2042 sym.out_n_sect = backlinks[sym.out_n_sect];
2043 }
19372044 }
1938 self.thunks.deinit(gpa);
19392045
1940 self.strtab.deinit(gpa);
1941 self.locals.deinit(gpa);
1942 self.globals.deinit(gpa);
1943 self.locals_free_list.deinit(gpa);
1944 self.globals_free_list.deinit(gpa);
1945 self.unresolved.deinit(gpa);
2046 for (self.objects.items) |index| {
2047 for (self.getFile(index).?.object.atoms.items) |atom_index| {
2048 const atom = self.getAtom(atom_index) orelse continue;
2049 if (!atom.flags.alive) continue;
2050 atom.out_n_sect = backlinks[atom.out_n_sect];
2051 }
2052 }
19462053
1947 {
1948 var it = self.resolver.keyIterator();
1949 while (it.next()) |key_ptr| {
1950 gpa.free(key_ptr.*);
2054 if (self.getInternalObject()) |object| {
2055 for (object.atoms.items) |atom_index| {
2056 const atom = self.getAtom(atom_index) orelse continue;
2057 if (!atom.flags.alive) continue;
2058 atom.out_n_sect = backlinks[atom.out_n_sect];
19512059 }
1952 self.resolver.deinit(gpa);
19532060 }
19542061
1955 for (self.objects.items) |*object| {
1956 object.deinit(gpa);
2062 for (&[_]*?u8{
2063 &self.data_sect_index,
2064 &self.got_sect_index,
2065 &self.zig_got_sect_index,
2066 &self.stubs_sect_index,
2067 &self.stubs_helper_sect_index,
2068 &self.la_symbol_ptr_sect_index,
2069 &self.tlv_ptr_sect_index,
2070 &self.eh_frame_sect_index,
2071 &self.unwind_info_sect_index,
2072 &self.objc_stubs_sect_index,
2073 }) |maybe_index| {
2074 if (maybe_index.*) |*index| {
2075 index.* = backlinks[index.*];
2076 }
19572077 }
1958 self.objects.deinit(gpa);
1959 for (self.archives.items) |*archive| {
1960 archive.deinit(gpa);
2078}
2079
2080pub fn addAtomsToSections(self: *MachO) !void {
2081 const tracy = trace(@src());
2082 defer tracy.end();
2083
2084 for (self.objects.items) |index| {
2085 const object = self.getFile(index).?.object;
2086 for (object.atoms.items) |atom_index| {
2087 const atom = self.getAtom(atom_index) orelse continue;
2088 if (!atom.flags.alive) continue;
2089 const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
2090 try atoms.append(self.base.comp.gpa, atom_index);
2091 }
2092 for (object.symbols.items) |sym_index| {
2093 const sym = self.getSymbol(sym_index);
2094 const atom = sym.getAtom(self) orelse continue;
2095 if (!atom.flags.alive) continue;
2096 if (sym.getFile(self).?.getIndex() != index) continue;
2097 sym.out_n_sect = atom.out_n_sect;
2098 }
19612099 }
1962 self.archives.deinit(gpa);
1963 for (self.dylibs.items) |*dylib| {
1964 dylib.deinit(gpa);
2100 if (self.getInternalObject()) |object| {
2101 for (object.atoms.items) |atom_index| {
2102 const atom = self.getAtom(atom_index) orelse continue;
2103 if (!atom.flags.alive) continue;
2104 const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
2105 try atoms.append(self.base.comp.gpa, atom_index);
2106 }
2107 for (object.symbols.items) |sym_index| {
2108 const sym = self.getSymbol(sym_index);
2109 const atom = sym.getAtom(self) orelse continue;
2110 if (!atom.flags.alive) continue;
2111 if (sym.getFile(self).?.getIndex() != object.index) continue;
2112 sym.out_n_sect = atom.out_n_sect;
2113 }
19652114 }
1966 self.dylibs.deinit(gpa);
1967 self.dylibs_map.deinit(gpa);
1968 self.referenced_dylibs.deinit(gpa);
2115}
19692116
1970 self.segments.deinit(gpa);
2117fn calcSectionSizes(self: *MachO) !void {
2118 const tracy = trace(@src());
2119 defer tracy.end();
19712120
1972 for (self.sections.items(.free_list)) |*list| {
1973 list.deinit(gpa);
2121 const cpu_arch = self.getTarget().cpu.arch;
2122
2123 if (self.data_sect_index) |idx| {
2124 const header = &self.sections.items(.header)[idx];
2125 header.size += @sizeOf(u64);
2126 header.@"align" = 3;
19742127 }
1975 self.sections.deinit(gpa);
19762128
1977 self.atoms.deinit(gpa);
2129 const slice = self.sections.slice();
2130 for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
2131 if (atoms.items.len == 0) continue;
2132 if (self.requiresThunks() and header.isCode()) continue;
2133
2134 for (atoms.items) |atom_index| {
2135 const atom = self.getAtom(atom_index).?;
2136 const atom_alignment = atom.alignment.toByteUnits(1);
2137 const offset = mem.alignForward(u64, header.size, atom_alignment);
2138 const padding = offset - header.size;
2139 atom.value = offset;
2140 header.size += padding + atom.size;
2141 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
2142 }
2143 }
2144
2145 if (self.requiresThunks()) {
2146 for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
2147 if (!header.isCode()) continue;
2148 if (atoms.items.len == 0) continue;
19782149
1979 for (self.decls.values()) |*m| {
1980 m.exports.deinit(gpa);
2150 // Create jump/branch range extenders if needed.
2151 try thunks.createThunks(@intCast(i), self);
2152 }
19812153 }
1982 self.decls.deinit(gpa);
19832154
1984 self.lazy_syms.deinit(gpa);
1985 self.tlv_table.deinit(gpa);
2155 if (self.got_sect_index) |idx| {
2156 const header = &self.sections.items(.header)[idx];
2157 header.size = self.got.size();
2158 header.@"align" = 3;
2159 }
19862160
1987 for (self.unnamed_const_atoms.values()) |*atoms| {
1988 atoms.deinit(gpa);
2161 if (self.stubs_sect_index) |idx| {
2162 const header = &self.sections.items(.header)[idx];
2163 header.size = self.stubs.size(self);
2164 header.@"align" = switch (cpu_arch) {
2165 .x86_64 => 1,
2166 .aarch64 => 2,
2167 else => 0,
2168 };
19892169 }
1990 self.unnamed_const_atoms.deinit(gpa);
19912170
1992 {
1993 var it = self.anon_decls.iterator();
1994 while (it.next()) |entry| {
1995 entry.value_ptr.exports.deinit(gpa);
1996 }
1997 self.anon_decls.deinit(gpa);
2171 if (self.stubs_helper_sect_index) |idx| {
2172 const header = &self.sections.items(.header)[idx];
2173 header.size = self.stubs_helper.size(self);
2174 header.@"align" = 2;
19982175 }
19992176
2000 self.atom_by_index_table.deinit(gpa);
2177 if (self.la_symbol_ptr_sect_index) |idx| {
2178 const header = &self.sections.items(.header)[idx];
2179 header.size = self.la_symbol_ptr.size(self);
2180 header.@"align" = 3;
2181 }
20012182
2002 for (self.relocs.values()) |*relocs| {
2003 relocs.deinit(gpa);
2183 if (self.tlv_ptr_sect_index) |idx| {
2184 const header = &self.sections.items(.header)[idx];
2185 header.size = self.tlv_ptr.size();
2186 header.@"align" = 3;
20042187 }
2005 self.relocs.deinit(gpa);
2006 self.actions.deinit(gpa);
20072188
2008 for (self.rebases.values()) |*rebases| {
2009 rebases.deinit(gpa);
2189 if (self.objc_stubs_sect_index) |idx| {
2190 const header = &self.sections.items(.header)[idx];
2191 header.size = self.objc_stubs.size(self);
2192 header.@"align" = switch (cpu_arch) {
2193 .x86_64 => 0,
2194 .aarch64 => 2,
2195 else => 0,
2196 };
20102197 }
2011 self.rebases.deinit(gpa);
2198}
2199
2200fn generateUnwindInfo(self: *MachO) !void {
2201 const tracy = trace(@src());
2202 defer tracy.end();
20122203
2013 for (self.bindings.values()) |*bindings| {
2014 bindings.deinit(gpa);
2204 if (self.eh_frame_sect_index) |index| {
2205 const sect = &self.sections.items(.header)[index];
2206 sect.size = try eh_frame.calcSize(self);
2207 sect.@"align" = 3;
2208 }
2209 if (self.unwind_info_sect_index) |index| {
2210 const sect = &self.sections.items(.header)[index];
2211 self.unwind_info.generate(self) catch |err| switch (err) {
2212 error.TooManyPersonalities => return self.reportUnexpectedError(
2213 "too many personalities in unwind info",
2214 .{},
2215 ),
2216 else => |e| return e,
2217 };
2218 sect.size = self.unwind_info.calcSize();
2219 sect.@"align" = 2;
20152220 }
2016 self.bindings.deinit(gpa);
20172221}
20182222
2019fn freeAtom(self: *MachO, atom_index: Atom.Index) void {
2223fn initSegments(self: *MachO) !void {
20202224 const gpa = self.base.comp.gpa;
2021 log.debug("freeAtom {d}", .{atom_index});
2022
2023 // Remove any relocs and base relocs associated with this Atom
2024 Atom.freeRelocations(self, atom_index);
2225 const slice = self.sections.slice();
20252226
2026 const atom = self.getAtom(atom_index);
2027 const sect_id = atom.getSymbol(self).n_sect - 1;
2028 const free_list = &self.sections.items(.free_list)[sect_id];
2029 var already_have_free_list_node = false;
2030 {
2031 var i: usize = 0;
2032 // TODO turn free_list into a hash map
2033 while (i < free_list.items.len) {
2034 if (free_list.items[i] == atom_index) {
2035 _ = free_list.swapRemove(i);
2036 continue;
2037 }
2038 if (free_list.items[i] == atom.prev_index) {
2039 already_have_free_list_node = true;
2040 }
2041 i += 1;
2227 // Add __PAGEZERO if required
2228 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
2229 const aligned_pagezero_size = mem.alignBackward(u64, pagezero_size, self.getPageSize());
2230 if (!self.base.isDynLib() and aligned_pagezero_size > 0) {
2231 if (aligned_pagezero_size != pagezero_size) {
2232 // TODO convert into a warning
2233 log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size});
2234 log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size});
20422235 }
2236 _ = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size });
20432237 }
20442238
2045 const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sect_id];
2046 if (maybe_last_atom_index.*) |last_atom_index| {
2047 if (last_atom_index == atom_index) {
2048 if (atom.prev_index) |prev_index| {
2049 // TODO shrink the section size here
2050 maybe_last_atom_index.* = prev_index;
2051 } else {
2052 maybe_last_atom_index.* = null;
2053 }
2239 // __TEXT segment is non-optional
2240 _ = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") });
2241
2242 // Next, create segments required by sections
2243 for (slice.items(.header)) |header| {
2244 const segname = header.segName();
2245 if (self.getSegmentByName(segname) == null) {
2246 const flags: u32 = if (mem.startsWith(u8, segname, "__DATA_CONST")) macho.SG_READ_ONLY else 0;
2247 _ = try self.addSegment(segname, .{ .prot = getSegmentProt(segname), .flags = flags });
20542248 }
20552249 }
20562250
2057 if (atom.prev_index) |prev_index| {
2058 const prev = self.getAtomPtr(prev_index);
2059 prev.next_index = atom.next_index;
2251 // Add __LINKEDIT
2252 _ = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") });
20602253
2061 if (!already_have_free_list_node and prev.*.freeListEligible(self)) {
2062 // The free list is heuristics, it doesn't have to be perfect, so we can ignore
2063 // the OOM here.
2064 free_list.append(gpa, prev_index) catch {};
2254 // Sort segments
2255 const sortFn = struct {
2256 fn sortFn(ctx: void, lhs: macho.segment_command_64, rhs: macho.segment_command_64) bool {
2257 return segmentLessThan(ctx, lhs.segName(), rhs.segName());
20652258 }
2066 } else {
2067 self.getAtomPtr(atom_index).prev_index = null;
2068 }
2259 }.sortFn;
2260 mem.sort(macho.segment_command_64, self.segments.items, {}, sortFn);
2261
2262 // Attach sections to segments
2263 for (slice.items(.header), slice.items(.segment_id)) |header, *seg_id| {
2264 const segname = header.segName();
2265 const segment_id = self.getSegmentByName(segname) orelse blk: {
2266 const segment_id = @as(u8, @intCast(self.segments.items.len));
2267 const protection = getSegmentProt(segname);
2268 try self.segments.append(gpa, .{
2269 .cmdsize = @sizeOf(macho.segment_command_64),
2270 .segname = makeStaticString(segname),
2271 .maxprot = protection,
2272 .initprot = protection,
2273 });
2274 break :blk segment_id;
2275 };
2276 const segment = &self.segments.items[segment_id];
2277 segment.cmdsize += @sizeOf(macho.section_64);
2278 segment.nsects += 1;
2279 seg_id.* = segment_id;
2280 }
2281
2282 self.pagezero_seg_index = self.getSegmentByName("__PAGEZERO");
2283 self.text_seg_index = self.getSegmentByName("__TEXT").?;
2284 self.linkedit_seg_index = self.getSegmentByName("__LINKEDIT").?;
2285 self.zig_text_seg_index = self.getSegmentByName("__TEXT_ZIG");
2286 self.zig_got_seg_index = self.getSegmentByName("__GOT_ZIG");
2287 self.zig_const_seg_index = self.getSegmentByName("__CONST_ZIG");
2288 self.zig_data_seg_index = self.getSegmentByName("__DATA_ZIG");
2289 self.zig_bss_seg_index = self.getSegmentByName("__BSS_ZIG");
2290}
2291
2292fn allocateSections(self: *MachO) !void {
2293 const headerpad = load_commands.calcMinHeaderPadSize(self);
2294 var vmaddr: u64 = if (self.pagezero_seg_index) |index|
2295 self.segments.items[index].vmaddr + self.segments.items[index].vmsize
2296 else
2297 0;
2298 vmaddr += headerpad;
2299 var fileoff = headerpad;
2300 var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0;
20692301
2070 if (atom.next_index) |next_index| {
2071 self.getAtomPtr(next_index).prev_index = atom.prev_index;
2072 } else {
2073 self.getAtomPtr(atom_index).next_index = null;
2074 }
2302 const page_size = self.getPageSize();
2303 const slice = self.sections.slice();
2304 const last_index = for (slice.items(.header), 0..) |header, i| {
2305 if (mem.indexOf(u8, header.segName(), "ZIG")) |_| break i;
2306 } else slice.items(.header).len;
2307
2308 for (slice.items(.header)[0..last_index], slice.items(.segment_id)[0..last_index]) |*header, curr_seg_id| {
2309 if (prev_seg_id != curr_seg_id) {
2310 vmaddr = mem.alignForward(u64, vmaddr, page_size);
2311 fileoff = mem.alignForward(u32, fileoff, page_size);
2312 }
20752313
2076 // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
2077 const sym_index = atom.getSymbolIndex().?;
2314 const alignment = try math.powi(u32, 2, header.@"align");
20782315
2079 self.locals_free_list.append(gpa, sym_index) catch {};
2316 vmaddr = mem.alignForward(u64, vmaddr, alignment);
2317 header.addr = vmaddr;
2318 vmaddr += header.size;
20802319
2081 // Try freeing GOT atom if this decl had one
2082 self.got_table.freeEntry(gpa, .{ .sym_index = sym_index });
2320 if (!header.isZerofill()) {
2321 fileoff = mem.alignForward(u32, fileoff, alignment);
2322 header.offset = fileoff;
2323 fileoff += @intCast(header.size);
2324 }
20832325
2084 if (self.d_sym) |*d_sym| {
2085 d_sym.swapRemoveRelocs(sym_index);
2326 prev_seg_id = curr_seg_id;
20862327 }
20872328
2088 self.locals.items[sym_index].n_type = 0;
2089 _ = self.atom_by_index_table.remove(sym_index);
2090 log.debug(" adding local symbol index {d} to free list", .{sym_index});
2091 self.getAtomPtr(atom_index).sym_index = 0;
2092}
2329 fileoff = mem.alignForward(u32, fileoff, page_size);
2330 for (slice.items(.header)[last_index..], slice.items(.segment_id)[last_index..]) |*header, seg_id| {
2331 if (header.isZerofill()) continue;
2332 if (header.offset < fileoff) {
2333 const existing_size = header.size;
2334 header.size = 0;
20932335
2094fn shrinkAtom(self: *MachO, atom_index: Atom.Index, new_block_size: u64) void {
2095 _ = self;
2096 _ = atom_index;
2097 _ = new_block_size;
2098 // TODO check the new capacity, and if it crosses the size threshold into a big enough
2099 // capacity, insert a free list node for it.
2100}
2336 // Must move the entire section.
2337 const new_offset = self.findFreeSpace(existing_size, page_size);
21012338
2102fn growAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: Alignment) !u64 {
2103 const atom = self.getAtom(atom_index);
2104 const sym = atom.getSymbol(self);
2105 const align_ok = alignment.check(sym.n_value);
2106 const need_realloc = !align_ok or new_atom_size > atom.capacity(self);
2107 if (!need_realloc) return sym.n_value;
2108 return self.allocateAtom(atom_index, new_atom_size, alignment);
2109}
2339 log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{
2340 header.segName(),
2341 header.sectName(),
2342 new_offset,
2343 new_offset + existing_size,
2344 });
21102345
2111pub fn allocateSymbol(self: *MachO) !u32 {
2112 const gpa = self.base.comp.gpa;
2113 try self.locals.ensureUnusedCapacity(gpa, 1);
2346 try self.copyRangeAllZeroOut(header.offset, new_offset, existing_size);
21142347
2115 const index = blk: {
2116 if (self.locals_free_list.popOrNull()) |index| {
2117 log.debug(" (reusing symbol index {d})", .{index});
2118 break :blk index;
2119 } else {
2120 log.debug(" (allocating symbol index {d})", .{self.locals.items.len});
2121 const index = @as(u32, @intCast(self.locals.items.len));
2122 _ = self.locals.addOneAssumeCapacity();
2123 break :blk index;
2348 header.offset = @intCast(new_offset);
2349 header.size = existing_size;
2350 self.segments.items[seg_id].fileoff = new_offset;
21242351 }
2125 };
2352 }
2353}
21262354
2127 self.locals.items[index] = .{
2128 .n_strx = 0,
2129 .n_type = 0,
2130 .n_sect = 0,
2131 .n_desc = 0,
2132 .n_value = 0,
2133 };
2355/// We allocate segments in a separate step to also consider segments that have no sections.
2356fn allocateSegments(self: *MachO) void {
2357 const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0;
2358 const last_index = for (self.segments.items, 0..) |seg, i| {
2359 if (mem.indexOf(u8, seg.segName(), "ZIG")) |_| break i;
2360 } else self.segments.items.len;
21342361
2135 return index;
2136}
2362 var vmaddr: u64 = if (self.pagezero_seg_index) |index|
2363 self.segments.items[index].vmaddr + self.segments.items[index].vmsize
2364 else
2365 0;
2366 var fileoff: u64 = 0;
21372367
2138fn allocateGlobal(self: *MachO) !u32 {
2139 const gpa = self.base.comp.gpa;
2140 try self.globals.ensureUnusedCapacity(gpa, 1);
2368 const page_size = self.getPageSize();
2369 const slice = self.sections.slice();
21412370
2142 const index = blk: {
2143 if (self.globals_free_list.popOrNull()) |index| {
2144 log.debug(" (reusing global index {d})", .{index});
2145 break :blk index;
2146 } else {
2147 log.debug(" (allocating symbol index {d})", .{self.globals.items.len});
2148 const index = @as(u32, @intCast(self.globals.items.len));
2149 _ = self.globals.addOneAssumeCapacity();
2150 break :blk index;
2151 }
2152 };
2371 var next_sect_id: u8 = 0;
2372 for (self.segments.items[first_index..last_index], first_index..last_index) |*seg, seg_id| {
2373 seg.vmaddr = vmaddr;
2374 seg.fileoff = fileoff;
21532375
2154 self.globals.items[index] = .{ .sym_index = 0 };
2376 while (next_sect_id < slice.items(.header).len) : (next_sect_id += 1) {
2377 const header = slice.items(.header)[next_sect_id];
2378 const sid = slice.items(.segment_id)[next_sect_id];
21552379
2156 return index;
2157}
2380 if (seg_id != sid) break;
21582381
2159pub fn addGotEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
2160 if (self.got_table.lookup.contains(reloc_target)) return;
2161 const gpa = self.base.comp.gpa;
2162 const got_index = try self.got_table.allocateEntry(gpa, reloc_target);
2163 if (self.got_section_index == null) {
2164 self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{
2165 .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
2166 });
2167 }
2168 if (self.mode == .incremental) {
2169 try self.writeOffsetTableEntry(got_index);
2170 self.got_table_count_dirty = true;
2171 self.markRelocsDirtyByTarget(reloc_target);
2382 vmaddr = header.addr + header.size;
2383 if (!header.isZerofill()) {
2384 fileoff = header.offset + header.size;
2385 }
2386 }
2387
2388 seg.vmsize = vmaddr - seg.vmaddr;
2389 seg.filesize = fileoff - seg.fileoff;
2390
2391 vmaddr = mem.alignForward(u64, vmaddr, page_size);
2392 fileoff = mem.alignForward(u64, fileoff, page_size);
21722393 }
21732394}
21742395
2175pub fn addStubEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
2176 if (self.stub_table.lookup.contains(reloc_target)) return;
2177 const comp = self.base.comp;
2178 const gpa = comp.gpa;
2179 const cpu_arch = comp.root_mod.resolved_target.result.cpu.arch;
2180 const stub_index = try self.stub_table.allocateEntry(gpa, reloc_target);
2181 if (self.stubs_section_index == null) {
2182 self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{
2183 .flags = macho.S_SYMBOL_STUBS |
2184 macho.S_ATTR_PURE_INSTRUCTIONS |
2185 macho.S_ATTR_SOME_INSTRUCTIONS,
2186 .reserved2 = stubs.stubSize(cpu_arch),
2187 });
2188 self.stub_helper_section_index = try self.initSection("__TEXT", "__stub_helper", .{
2189 .flags = macho.S_REGULAR |
2190 macho.S_ATTR_PURE_INSTRUCTIONS |
2191 macho.S_ATTR_SOME_INSTRUCTIONS,
2192 });
2193 self.la_symbol_ptr_section_index = try self.initSection("__DATA", "__la_symbol_ptr", .{
2194 .flags = macho.S_LAZY_SYMBOL_POINTERS,
2195 });
2396pub fn allocateAtoms(self: *MachO) void {
2397 const slice = self.sections.slice();
2398 for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
2399 if (atoms.items.len == 0) continue;
2400 for (atoms.items) |atom_index| {
2401 const atom = self.getAtom(atom_index).?;
2402 assert(atom.flags.alive);
2403 atom.value += header.addr;
2404 }
21962405 }
2197 if (self.mode == .incremental) {
2198 try self.writeStubTableEntry(stub_index);
2199 self.stub_table_count_dirty = true;
2200 self.markRelocsDirtyByTarget(reloc_target);
2406
2407 for (self.thunks.items) |*thunk| {
2408 const header = self.sections.items(.header)[thunk.out_n_sect];
2409 thunk.value += header.addr;
22012410 }
22022411}
22032412
2204pub fn addTlvPtrEntry(self: *MachO, reloc_target: SymbolWithLoc) !void {
2205 if (self.tlv_ptr_table.lookup.contains(reloc_target)) return;
2206 const gpa = self.base.comp.gpa;
2207 _ = try self.tlv_ptr_table.allocateEntry(gpa, reloc_target);
2208 if (self.tlv_ptr_section_index == null) {
2209 self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{
2210 .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
2211 });
2413fn allocateSyntheticSymbols(self: *MachO) void {
2414 const text_seg = self.getTextSegment();
2415
2416 if (self.mh_execute_header_index) |index| {
2417 const global = self.getSymbol(index);
2418 global.value = text_seg.vmaddr;
22122419 }
2213}
22142420
2215pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
2216 if (build_options.skip_non_native and builtin.object_format != .macho) {
2217 @panic("Attempted to compile for object format that was disabled by build configuration");
2421 if (self.data_sect_index) |idx| {
2422 const sect = self.sections.items(.header)[idx];
2423 for (&[_]?Symbol.Index{
2424 self.dso_handle_index,
2425 self.mh_dylib_header_index,
2426 self.dyld_private_index,
2427 }) |maybe_index| {
2428 if (maybe_index) |index| {
2429 const global = self.getSymbol(index);
2430 global.value = sect.addr;
2431 global.out_n_sect = idx;
2432 }
2433 }
22182434 }
2219 if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func_index, air, liveness);
2220 const tracy = trace(@src());
2221 defer tracy.end();
22222435
2223 const func = mod.funcInfo(func_index);
2224 const decl_index = func.owner_decl;
2225 const decl = mod.declPtr(decl_index);
2436 for (self.boundary_symbols.items) |sym_index| {
2437 const sym = self.getSymbol(sym_index);
2438 const name = sym.getName(self);
22262439
2227 const atom_index = try self.getOrCreateAtomForDecl(decl_index);
2228 self.freeUnnamedConsts(decl_index);
2229 Atom.freeRelocations(self, atom_index);
2440 sym.flags.@"export" = false;
2441 sym.value = text_seg.vmaddr;
22302442
2231 const gpa = self.base.comp.gpa;
2232 var code_buffer = std.ArrayList(u8).init(gpa);
2233 defer code_buffer.deinit();
2443 if (mem.startsWith(u8, name, "segment$start$")) {
2444 const segname = name["segment$start$".len..];
2445 if (self.getSegmentByName(segname)) |seg_id| {
2446 const seg = self.segments.items[seg_id];
2447 sym.value = seg.vmaddr;
2448 }
2449 } else if (mem.startsWith(u8, name, "segment$stop$")) {
2450 const segname = name["segment$stop$".len..];
2451 if (self.getSegmentByName(segname)) |seg_id| {
2452 const seg = self.segments.items[seg_id];
2453 sym.value = seg.vmaddr + seg.vmsize;
2454 }
2455 } else if (mem.startsWith(u8, name, "section$start$")) {
2456 const actual_name = name["section$start$".len..];
2457 const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
2458 const segname = actual_name[0..sep];
2459 const sectname = actual_name[sep + 1 ..];
2460 if (self.getSectionByName(segname, sectname)) |sect_id| {
2461 const sect = self.sections.items(.header)[sect_id];
2462 sym.value = sect.addr;
2463 sym.out_n_sect = sect_id;
2464 }
2465 } else if (mem.startsWith(u8, name, "section$stop$")) {
2466 const actual_name = name["section$stop$".len..];
2467 const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic
2468 const segname = actual_name[0..sep];
2469 const sectname = actual_name[sep + 1 ..];
2470 if (self.getSectionByName(segname, sectname)) |sect_id| {
2471 const sect = self.sections.items(.header)[sect_id];
2472 sym.value = sect.addr + sect.size;
2473 sym.out_n_sect = sect_id;
2474 }
2475 } else unreachable;
2476 }
22342477
2235 var decl_state = if (self.d_sym) |*d_sym|
2236 try d_sym.dwarf.initDeclState(mod, decl_index)
2237 else
2238 null;
2239 defer if (decl_state) |*ds| ds.deinit();
2478 if (self.objc_stubs.symbols.items.len > 0) {
2479 const addr = self.sections.items(.header)[self.objc_stubs_sect_index.?].addr;
22402480
2241 const res = if (decl_state) |*ds|
2242 try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .{
2243 .dwarf = ds,
2244 })
2245 else
2246 try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .none);
2247
2248 const code = switch (res) {
2249 .ok => code_buffer.items,
2250 .fail => |em| {
2251 decl.analysis = .codegen_failure;
2252 try mod.failed_decls.put(mod.gpa, decl_index, em);
2253 return;
2254 },
2255 };
2481 for (self.objc_stubs.symbols.items, 0..) |sym_index, idx| {
2482 const sym = self.getSymbol(sym_index);
2483 sym.value = addr + idx * ObjcStubsSection.entrySize(self.getTarget().cpu.arch);
2484 sym.out_n_sect = self.objc_stubs_sect_index.?;
2485 }
2486 }
2487}
22562488
2257 const addr = try self.updateDeclCode(decl_index, code);
2489fn allocateLinkeditSegment(self: *MachO) !void {
2490 var fileoff: u64 = 0;
2491 var vmaddr: u64 = 0;
22582492
2259 if (decl_state) |*ds| {
2260 try self.d_sym.?.dwarf.commitDeclState(
2261 mod,
2262 decl_index,
2263 addr,
2264 self.getAtom(atom_index).size,
2265 ds,
2266 );
2493 for (self.segments.items) |seg| {
2494 if (fileoff < seg.fileoff + seg.filesize) fileoff = seg.fileoff + seg.filesize;
2495 if (vmaddr < seg.vmaddr + seg.vmsize) vmaddr = seg.vmaddr + seg.vmsize;
22672496 }
22682497
2269 // Since we updated the vaddr and the size, each corresponding export symbol also
2270 // needs to be updated.
2271 try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
2498 const page_size = self.getPageSize();
2499 const seg = self.getLinkeditSegment();
2500 seg.vmaddr = mem.alignForward(u64, vmaddr, page_size);
2501 seg.fileoff = mem.alignForward(u64, fileoff, page_size);
22722502}
22732503
2274pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
2504fn initDyldInfoSections(self: *MachO) !void {
2505 const tracy = trace(@src());
2506 defer tracy.end();
2507
22752508 const gpa = self.base.comp.gpa;
2276 const mod = self.base.comp.module.?;
2277 const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index);
2278 if (!gop.found_existing) {
2279 gop.value_ptr.* = .{};
2280 }
2281 const unnamed_consts = gop.value_ptr;
2282 const decl = mod.declPtr(decl_index);
2283 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
2284 const index = unnamed_consts.items.len;
2285 const name = try std.fmt.allocPrint(gpa, "___unnamed_{s}_{d}", .{ decl_name, index });
2286 defer gpa.free(name);
2287 const atom_index = switch (try self.lowerConst(name, typed_value, typed_value.ty.abiAlignment(mod), self.data_const_section_index.?, decl.srcLoc(mod))) {
2288 .ok => |atom_index| atom_index,
2289 .fail => |em| {
2290 decl.analysis = .codegen_failure;
2291 try mod.failed_decls.put(mod.gpa, decl_index, em);
2292 log.debug("{s}", .{em.msg});
2293 return error.CodegenFail;
2294 },
2295 };
2296 try unnamed_consts.append(gpa, atom_index);
2297 const atom = self.getAtomPtr(atom_index);
2298 return atom.getSymbolIndex().?;
2509
2510 if (self.zig_got_sect_index != null) try self.zig_got.addDyldRelocs(self);
2511 if (self.got_sect_index != null) try self.got.addDyldRelocs(self);
2512 if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self);
2513 if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self);
2514 try self.initExportTrie();
2515
2516 var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
2517 defer objects.deinit();
2518 if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
2519 objects.appendSliceAssumeCapacity(self.objects.items);
2520
2521 var nrebases: usize = 0;
2522 var nbinds: usize = 0;
2523 var nweak_binds: usize = 0;
2524 for (objects.items) |index| {
2525 const ctx = switch (self.getFile(index).?) {
2526 .zig_object => |x| x.dynamic_relocs,
2527 .object => |x| x.dynamic_relocs,
2528 else => unreachable,
2529 };
2530 nrebases += ctx.rebase_relocs;
2531 nbinds += ctx.bind_relocs;
2532 nweak_binds += ctx.weak_bind_relocs;
2533 }
2534 try self.rebase.entries.ensureUnusedCapacity(gpa, nrebases);
2535 try self.bind.entries.ensureUnusedCapacity(gpa, nbinds);
2536 try self.weak_bind.entries.ensureUnusedCapacity(gpa, nweak_binds);
22992537}
23002538
2301const LowerConstResult = union(enum) {
2302 ok: Atom.Index,
2303 fail: *Module.ErrorMsg,
2304};
2539fn initExportTrie(self: *MachO) !void {
2540 const tracy = trace(@src());
2541 defer tracy.end();
23052542
2306fn lowerConst(
2307 self: *MachO,
2308 name: []const u8,
2309 tv: TypedValue,
2310 required_alignment: InternPool.Alignment,
2311 sect_id: u8,
2312 src_loc: Module.SrcLoc,
2313) !LowerConstResult {
23142543 const gpa = self.base.comp.gpa;
2544 try self.export_trie.init(gpa);
2545
2546 const seg = self.getTextSegment();
2547 for (self.objects.items) |index| {
2548 for (self.getFile(index).?.getSymbols()) |sym_index| {
2549 const sym = self.getSymbol(sym_index);
2550 if (!sym.flags.@"export") continue;
2551 if (sym.getAtom(self)) |atom| if (!atom.flags.alive) continue;
2552 if (sym.getFile(self).?.getIndex() != index) continue;
2553 var flags: u64 = if (sym.flags.abs)
2554 macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
2555 else if (sym.flags.tlv)
2556 macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
2557 else
2558 macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
2559 if (sym.flags.weak) {
2560 flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
2561 self.weak_defines = true;
2562 self.binds_to_weak = true;
2563 }
2564 try self.export_trie.put(gpa, .{
2565 .name = sym.getName(self),
2566 .vmaddr_offset = sym.getAddress(.{ .stubs = false }, self) - seg.vmaddr,
2567 .export_flags = flags,
2568 });
2569 }
2570 }
23152571
2316 var code_buffer = std.ArrayList(u8).init(gpa);
2317 defer code_buffer.deinit();
2572 if (self.mh_execute_header_index) |index| {
2573 const sym = self.getSymbol(index);
2574 try self.export_trie.put(gpa, .{
2575 .name = sym.getName(self),
2576 .vmaddr_offset = sym.getAddress(.{}, self) - seg.vmaddr,
2577 .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
2578 });
2579 }
2580}
23182581
2319 log.debug("allocating symbol indexes for {s}", .{name});
2582fn writeAtoms(self: *MachO) !void {
2583 const tracy = trace(@src());
2584 defer tracy.end();
23202585
2321 const sym_index = try self.allocateSymbol();
2322 const atom_index = try self.createAtom(sym_index, .{});
2323 try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
2586 const gpa = self.base.comp.gpa;
2587 var arena = std.heap.ArenaAllocator.init(gpa);
2588 defer arena.deinit();
23242589
2325 const res = try codegen.generateSymbol(&self.base, src_loc, tv, &code_buffer, .none, .{
2326 .parent_atom_index = self.getAtom(atom_index).getSymbolIndex().?,
2327 });
2328 const code = switch (res) {
2329 .ok => code_buffer.items,
2330 .fail => |em| return .{ .fail = em },
2331 };
2590 const cpu_arch = self.getTarget().cpu.arch;
2591 const slice = self.sections.slice();
23322592
2333 const atom = self.getAtomPtr(atom_index);
2334 atom.size = code.len;
2335 // TODO: work out logic for disambiguating functions from function pointers
2336 // const sect_id = self.getDeclOutputSection(decl_index);
2337 const symbol = atom.getSymbolPtr(self);
2338 const name_str_index = try self.strtab.insert(gpa, name);
2339 symbol.n_strx = name_str_index;
2340 symbol.n_type = macho.N_SECT;
2341 symbol.n_sect = sect_id + 1;
2342 symbol.n_value = try self.allocateAtom(atom_index, code.len, required_alignment);
2343 errdefer self.freeAtom(atom_index);
2593 var has_resolve_error = false;
2594 for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
2595 if (atoms.items.len == 0) continue;
2596 if (header.isZerofill()) continue;
2597
2598 const size = math.cast(usize, header.size) orelse return error.Overflow;
2599 const buffer = try gpa.alloc(u8, size);
2600 defer gpa.free(buffer);
2601 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
2602 @memset(buffer, padding_byte);
2603
2604 for (atoms.items) |atom_index| {
2605 const atom = self.getAtom(atom_index).?;
2606 assert(atom.flags.alive);
2607 const off = math.cast(usize, atom.value - header.addr) orelse return error.Overflow;
2608 const data = switch (atom.getFile(self)) {
2609 .object => |x| try x.getAtomData(atom.*),
2610 .zig_object => |x| try x.getAtomDataAlloc(self, arena.allocator(), atom.*),
2611 else => unreachable,
2612 };
2613 const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
2614 @memcpy(buffer[off..][0..atom_size], data);
2615 atom.resolveRelocs(self, buffer[off..][0..atom_size]) catch |err| switch (err) {
2616 error.ResolveFailed => has_resolve_error = true,
2617 else => |e| return e,
2618 };
2619 }
23442620
2345 log.debug("allocated atom for {s} at 0x{x}", .{ name, symbol.n_value });
2346 log.debug(" (required alignment 0x{x})", .{required_alignment});
2621 try self.base.file.?.pwriteAll(buffer, header.offset);
2622 }
23472623
2348 try self.writeAtom(atom_index, code);
2349 self.markRelocsDirtyByTarget(atom.getSymbolWithLoc());
2624 for (self.thunks.items) |thunk| {
2625 const header = slice.items(.header)[thunk.out_n_sect];
2626 const offset = thunk.value - header.addr + header.offset;
2627 const buffer = try gpa.alloc(u8, thunk.size());
2628 defer gpa.free(buffer);
2629 var stream = std.io.fixedBufferStream(buffer);
2630 try thunk.write(self, stream.writer());
2631 try self.base.file.?.pwriteAll(buffer, offset);
2632 }
23502633
2351 return .{ .ok = atom_index };
2634 if (has_resolve_error) return error.ResolveFailed;
23522635}
23532636
2354pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void {
2355 if (build_options.skip_non_native and builtin.object_format != .macho) {
2356 @panic("Attempted to compile for object format that was disabled by build configuration");
2357 }
2358 if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index);
2359 const tracy = trace(@src());
2360 defer tracy.end();
2361
2362 const comp = self.base.comp;
2363 const gpa = comp.gpa;
2364 const decl = mod.declPtr(decl_index);
2365
2366 if (decl.val.getExternFunc(mod)) |_| {
2367 return;
2368 }
2369
2370 if (decl.isExtern(mod)) {
2371 // TODO make this part of getGlobalSymbol
2372 const name = mod.intern_pool.stringToSlice(decl.name);
2373 const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
2374 defer gpa.free(sym_name);
2375 _ = try self.addUndefined(sym_name, .{ .add_got = true });
2376 return;
2377 }
2378
2379 const is_threadlocal = if (decl.val.getVariable(mod)) |variable|
2380 variable.is_threadlocal and comp.config.any_non_single_threaded
2381 else
2382 false;
2383 if (is_threadlocal) return self.updateThreadlocalVariable(mod, decl_index);
2384
2385 const atom_index = try self.getOrCreateAtomForDecl(decl_index);
2386 const sym_index = self.getAtom(atom_index).getSymbolIndex().?;
2387 Atom.freeRelocations(self, atom_index);
2388
2389 var code_buffer = std.ArrayList(u8).init(gpa);
2390 defer code_buffer.deinit();
2391
2392 var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym|
2393 try d_sym.dwarf.initDeclState(mod, decl_index)
2394 else
2395 null;
2396 defer if (decl_state) |*ds| ds.deinit();
2397
2398 const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
2399 const res = if (decl_state) |*ds|
2400 try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
2401 .ty = decl.ty,
2402 .val = decl_val,
2403 }, &code_buffer, .{
2404 .dwarf = ds,
2405 }, .{
2406 .parent_atom_index = sym_index,
2407 })
2408 else
2409 try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
2410 .ty = decl.ty,
2411 .val = decl_val,
2412 }, &code_buffer, .none, .{
2413 .parent_atom_index = sym_index,
2414 });
2415
2416 const code = switch (res) {
2417 .ok => code_buffer.items,
2418 .fail => |em| {
2419 decl.analysis = .codegen_failure;
2420 try mod.failed_decls.put(mod.gpa, decl_index, em);
2421 return;
2422 },
2423 };
2424 const addr = try self.updateDeclCode(decl_index, code);
2425
2426 if (decl_state) |*ds| {
2427 try self.d_sym.?.dwarf.commitDeclState(
2428 mod,
2429 decl_index,
2430 addr,
2431 self.getAtom(atom_index).size,
2432 ds,
2433 );
2434 }
2435
2436 // Since we updated the vaddr and the size, each corresponding export symbol also
2437 // needs to be updated.
2438 try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
2439}
2440
2441fn updateLazySymbolAtom(
2442 self: *MachO,
2443 sym: File.LazySymbol,
2444 atom_index: Atom.Index,
2445 section_index: u8,
2446) !void {
2447 const gpa = self.base.comp.gpa;
2448 const mod = self.base.comp.module.?;
2449
2450 var required_alignment: Alignment = .none;
2451 var code_buffer = std.ArrayList(u8).init(gpa);
2452 defer code_buffer.deinit();
2453
2454 const name_str_index = blk: {
2455 const name = try std.fmt.allocPrint(gpa, "___lazy_{s}_{}", .{
2456 @tagName(sym.kind),
2457 sym.ty.fmt(mod),
2458 });
2459 defer gpa.free(name);
2460 break :blk try self.strtab.insert(gpa, name);
2461 };
2462 const name = self.strtab.get(name_str_index).?;
2463
2464 const atom = self.getAtomPtr(atom_index);
2465 const local_sym_index = atom.getSymbolIndex().?;
2466
2467 const src = if (sym.ty.getOwnerDeclOrNull(mod)) |owner_decl|
2468 mod.declPtr(owner_decl).srcLoc(mod)
2469 else
2470 Module.SrcLoc{
2471 .file_scope = undefined,
2472 .parent_decl_node = undefined,
2473 .lazy = .unneeded,
2474 };
2475 const res = try codegen.generateLazySymbol(
2476 &self.base,
2477 src,
2478 sym,
2479 &required_alignment,
2480 &code_buffer,
2481 .none,
2482 .{ .parent_atom_index = local_sym_index },
2483 );
2484 const code = switch (res) {
2485 .ok => code_buffer.items,
2486 .fail => |em| {
2487 log.debug("{s}", .{em.msg});
2488 return error.CodegenFail;
2489 },
2490 };
2491
2492 const symbol = atom.getSymbolPtr(self);
2493 symbol.n_strx = name_str_index;
2494 symbol.n_type = macho.N_SECT;
2495 symbol.n_sect = section_index + 1;
2496 symbol.n_desc = 0;
2497
2498 const vaddr = try self.allocateAtom(atom_index, code.len, required_alignment);
2499 errdefer self.freeAtom(atom_index);
2500
2501 log.debug("allocated atom for {s} at 0x{x}", .{ name, vaddr });
2502 log.debug(" (required alignment 0x{x})", .{required_alignment});
2503
2504 atom.size = code.len;
2505 symbol.n_value = vaddr;
2506
2507 try self.addGotEntry(.{ .sym_index = local_sym_index });
2508 try self.writeAtom(atom_index, code);
2509}
2510
2511pub fn getOrCreateAtomForLazySymbol(self: *MachO, sym: File.LazySymbol) !Atom.Index {
2512 const mod = self.base.comp.module.?;
2513 const gpa = self.base.comp.gpa;
2514 const gop = try self.lazy_syms.getOrPut(gpa, sym.getDecl(mod));
2515 errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
2516 if (!gop.found_existing) gop.value_ptr.* = .{};
2517 const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) {
2518 .code => .{ .atom = &gop.value_ptr.text_atom, .state = &gop.value_ptr.text_state },
2519 .const_data => .{
2520 .atom = &gop.value_ptr.data_const_atom,
2521 .state = &gop.value_ptr.data_const_state,
2522 },
2523 };
2524 switch (metadata.state.*) {
2525 .unused => {
2526 const sym_index = try self.allocateSymbol();
2527 metadata.atom.* = try self.createAtom(sym_index, .{});
2528 try self.atom_by_index_table.putNoClobber(gpa, sym_index, metadata.atom.*);
2529 },
2530 .pending_flush => return metadata.atom.*,
2531 .flushed => {},
2532 }
2533 metadata.state.* = .pending_flush;
2534 const atom = metadata.atom.*;
2535 // anyerror needs to be deferred until flushModule
2536 if (sym.getDecl(mod) != .none) try self.updateLazySymbolAtom(sym, atom, switch (sym.kind) {
2537 .code => self.text_section_index.?,
2538 .const_data => self.data_const_section_index.?,
2539 });
2540 return atom;
2541}
2542
2543fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
2544 const mod = self.base.comp.module.?;
2545 // Lowering a TLV on macOS involves two stages:
2546 // 1. first we lower the initializer into appopriate section (__thread_data or __thread_bss)
2547 // 2. next, we create a corresponding threadlocal variable descriptor in __thread_vars
2548
2549 // 1. Lower the initializer value.
2550 const init_atom_index = try self.getOrCreateAtomForDecl(decl_index);
2551 const init_atom = self.getAtomPtr(init_atom_index);
2552 const init_sym_index = init_atom.getSymbolIndex().?;
2553 Atom.freeRelocations(self, init_atom_index);
2554
2555 const gpa = self.base.comp.gpa;
2556
2557 var code_buffer = std.ArrayList(u8).init(gpa);
2558 defer code_buffer.deinit();
2559
2560 var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym|
2561 try d_sym.dwarf.initDeclState(module, decl_index)
2562 else
2563 null;
2564 defer if (decl_state) |*ds| ds.deinit();
2565
2566 const decl = module.declPtr(decl_index);
2567 const decl_metadata = self.decls.get(decl_index).?;
2568 const decl_val = Value.fromInterned(decl.val.getVariable(mod).?.init);
2569 const res = if (decl_state) |*ds|
2570 try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
2571 .ty = decl.ty,
2572 .val = decl_val,
2573 }, &code_buffer, .{
2574 .dwarf = ds,
2575 }, .{
2576 .parent_atom_index = init_sym_index,
2577 })
2578 else
2579 try codegen.generateSymbol(&self.base, decl.srcLoc(mod), .{
2580 .ty = decl.ty,
2581 .val = decl_val,
2582 }, &code_buffer, .none, .{
2583 .parent_atom_index = init_sym_index,
2584 });
2585
2586 const code = switch (res) {
2587 .ok => code_buffer.items,
2588 .fail => |em| {
2589 decl.analysis = .codegen_failure;
2590 try module.failed_decls.put(module.gpa, decl_index, em);
2591 return;
2592 },
2593 };
2594
2595 const required_alignment = decl.getAlignment(mod);
2596
2597 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(module));
2598
2599 const init_sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{decl_name});
2600 defer gpa.free(init_sym_name);
2601
2602 const sect_id = decl_metadata.section;
2603 const init_sym = init_atom.getSymbolPtr(self);
2604 init_sym.n_strx = try self.strtab.insert(gpa, init_sym_name);
2605 init_sym.n_type = macho.N_SECT;
2606 init_sym.n_sect = sect_id + 1;
2607 init_sym.n_desc = 0;
2608 init_atom.size = code.len;
2609
2610 init_sym.n_value = try self.allocateAtom(init_atom_index, code.len, required_alignment);
2611 errdefer self.freeAtom(init_atom_index);
2612
2613 log.debug("allocated atom for {s} at 0x{x}", .{ init_sym_name, init_sym.n_value });
2614 log.debug(" (required alignment 0x{x})", .{required_alignment});
2615
2616 try self.writeAtom(init_atom_index, code);
2617
2618 if (decl_state) |*ds| {
2619 try self.d_sym.?.dwarf.commitDeclState(
2620 module,
2621 decl_index,
2622 init_sym.n_value,
2623 self.getAtom(init_atom_index).size,
2624 ds,
2625 );
2626 }
2627
2628 try self.updateExports(module, .{ .decl_index = decl_index }, module.getDeclExports(decl_index));
2629
2630 // 2. Create a TLV descriptor.
2631 const init_atom_sym_loc = init_atom.getSymbolWithLoc();
2632 const gop = try self.tlv_table.getOrPut(gpa, init_atom_sym_loc);
2633 assert(!gop.found_existing);
2634 gop.value_ptr.* = try self.createThreadLocalDescriptorAtom(decl_name, init_atom_sym_loc);
2635 self.markRelocsDirtyByTarget(init_atom_sym_loc);
2636}
2637
2638pub fn getOrCreateAtomForDecl(self: *MachO, decl_index: InternPool.DeclIndex) !Atom.Index {
2639 const gpa = self.base.comp.gpa;
2640 const gop = try self.decls.getOrPut(gpa, decl_index);
2641 if (!gop.found_existing) {
2642 const sym_index = try self.allocateSymbol();
2643 const atom_index = try self.createAtom(sym_index, .{});
2644 try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index);
2645 gop.value_ptr.* = .{
2646 .atom = atom_index,
2647 .section = self.getDeclOutputSection(decl_index),
2648 .exports = .{},
2649 };
2650 }
2651 return gop.value_ptr.atom;
2652}
2653
2654fn getDeclOutputSection(self: *MachO, decl_index: InternPool.DeclIndex) u8 {
2655 const decl = self.base.comp.module.?.declPtr(decl_index);
2656 const ty = decl.ty;
2657 const val = decl.val;
2658 const mod = self.base.comp.module.?;
2659 const zig_ty = ty.zigTypeTag(mod);
2660 const any_non_single_threaded = self.base.comp.config.any_non_single_threaded;
2661 const optimize_mode = self.base.comp.root_mod.optimize_mode;
2662 const sect_id: u8 = blk: {
2663 // TODO finish and audit this function
2664 if (val.isUndefDeep(mod)) {
2665 if (optimize_mode == .ReleaseFast or optimize_mode == .ReleaseSmall) {
2666 @panic("TODO __DATA,__bss");
2667 } else {
2668 break :blk self.data_section_index.?;
2669 }
2670 }
2671
2672 if (val.getVariable(mod)) |variable| {
2673 if (variable.is_threadlocal and any_non_single_threaded) {
2674 break :blk self.thread_data_section_index.?;
2675 }
2676 break :blk self.data_section_index.?;
2677 }
2678
2679 switch (zig_ty) {
2680 // TODO: what if this is a function pointer?
2681 .Fn => break :blk self.text_section_index.?,
2682 else => {
2683 if (val.getVariable(mod)) |_| {
2684 break :blk self.data_section_index.?;
2685 }
2686 break :blk self.data_const_section_index.?;
2687 },
2688 }
2689 };
2690 return sect_id;
2691}
2692
2693fn updateDeclCode(self: *MachO, decl_index: InternPool.DeclIndex, code: []u8) !u64 {
2694 const gpa = self.base.comp.gpa;
2695 const mod = self.base.comp.module.?;
2696 const decl = mod.declPtr(decl_index);
2697
2698 const required_alignment = decl.getAlignment(mod);
2699
2700 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
2701
2702 const decl_metadata = self.decls.get(decl_index).?;
2703 const atom_index = decl_metadata.atom;
2704 const atom = self.getAtom(atom_index);
2705 const sym_index = atom.getSymbolIndex().?;
2706 const sect_id = decl_metadata.section;
2707 const header = &self.sections.items(.header)[sect_id];
2708 const segment = self.getSegment(sect_id);
2709 const code_len = code.len;
2710
2711 if (atom.size != 0) {
2712 const sym = atom.getSymbolPtr(self);
2713 sym.n_strx = try self.strtab.insert(gpa, decl_name);
2714 sym.n_type = macho.N_SECT;
2715 sym.n_sect = sect_id + 1;
2716 sym.n_desc = 0;
2717
2718 const capacity = atom.capacity(self);
2719 const need_realloc = code_len > capacity or !required_alignment.check(sym.n_value);
2720
2721 if (need_realloc) {
2722 const vaddr = try self.growAtom(atom_index, code_len, required_alignment);
2723 log.debug("growing {s} and moving from 0x{x} to 0x{x}", .{ decl_name, sym.n_value, vaddr });
2724 log.debug(" (required alignment 0x{x})", .{required_alignment});
2725
2726 if (vaddr != sym.n_value) {
2727 sym.n_value = vaddr;
2728 log.debug(" (updating GOT entry)", .{});
2729 const got_atom_index = self.got_table.lookup.get(.{ .sym_index = sym_index }).?;
2730 try self.writeOffsetTableEntry(got_atom_index);
2731 self.markRelocsDirtyByTarget(.{ .sym_index = sym_index });
2732 }
2733 } else if (code_len < atom.size) {
2734 self.shrinkAtom(atom_index, code_len);
2735 } else if (atom.next_index == null) {
2736 const needed_size = (sym.n_value + code_len) - segment.vmaddr;
2737 header.size = needed_size;
2738 }
2739 self.getAtomPtr(atom_index).size = code_len;
2740 } else {
2741 const sym = atom.getSymbolPtr(self);
2742 sym.n_strx = try self.strtab.insert(gpa, decl_name);
2743 sym.n_type = macho.N_SECT;
2744 sym.n_sect = sect_id + 1;
2745 sym.n_desc = 0;
2746
2747 const vaddr = try self.allocateAtom(atom_index, code_len, required_alignment);
2748 errdefer self.freeAtom(atom_index);
2749
2750 log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr });
2751 log.debug(" (required alignment 0x{x})", .{required_alignment});
2752
2753 self.getAtomPtr(atom_index).size = code_len;
2754 sym.n_value = vaddr;
2755
2756 try self.addGotEntry(.{ .sym_index = sym_index });
2757 }
2758
2759 try self.writeAtom(atom_index, code);
2760
2761 return atom.getSymbol(self).n_value;
2762}
2763
2764pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
2765 if (self.d_sym) |*d_sym| {
2766 try d_sym.dwarf.updateDeclLineNumber(module, decl_index);
2767 }
2768}
2769
2770pub fn updateExports(
2771 self: *MachO,
2772 mod: *Module,
2773 exported: Module.Exported,
2774 exports: []const *Module.Export,
2775) File.UpdateExportsError!void {
2776 if (build_options.skip_non_native and builtin.object_format != .macho) {
2777 @panic("Attempted to compile for object format that was disabled by build configuration");
2778 }
2779 if (self.llvm_object) |llvm_object|
2780 return llvm_object.updateExports(mod, exported, exports);
2781
2782 const tracy = trace(@src());
2783 defer tracy.end();
2784
2785 const gpa = self.base.comp.gpa;
2786
2787 const metadata = switch (exported) {
2788 .decl_index => |decl_index| blk: {
2789 _ = try self.getOrCreateAtomForDecl(decl_index);
2790 break :blk self.decls.getPtr(decl_index).?;
2791 },
2792 .value => |value| self.anon_decls.getPtr(value) orelse blk: {
2793 const first_exp = exports[0];
2794 const res = try self.lowerAnonDecl(value, .none, first_exp.getSrcLoc(mod));
2795 switch (res) {
2796 .ok => {},
2797 .fail => |em| {
2798 // TODO maybe it's enough to return an error here and let Module.processExportsInner
2799 // handle the error?
2800 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
2801 mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em);
2802 return;
2803 },
2804 }
2805 break :blk self.anon_decls.getPtr(value).?;
2806 },
2807 };
2808 const atom_index = metadata.atom;
2809 const atom = self.getAtom(atom_index);
2810 const sym = atom.getSymbol(self);
2811
2812 for (exports) |exp| {
2813 const exp_name = try std.fmt.allocPrint(gpa, "_{}", .{
2814 exp.opts.name.fmt(&mod.intern_pool),
2815 });
2816 defer gpa.free(exp_name);
2817
2818 log.debug("adding new export '{s}'", .{exp_name});
2819
2820 if (exp.opts.section.unwrap()) |section_name| {
2821 if (!mod.intern_pool.stringEqlSlice(section_name, "__text")) {
2822 try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
2823 gpa,
2824 exp.getSrcLoc(mod),
2825 "Unimplemented: ExportOptions.section",
2826 .{},
2827 ));
2828 continue;
2829 }
2830 }
2831
2832 if (exp.opts.linkage == .LinkOnce) {
2833 try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
2834 gpa,
2835 exp.getSrcLoc(mod),
2836 "Unimplemented: GlobalLinkage.LinkOnce",
2837 .{},
2838 ));
2839 continue;
2840 }
2841
2842 const global_sym_index = metadata.getExport(self, exp_name) orelse blk: {
2843 const global_sym_index = if (self.getGlobalIndex(exp_name)) |global_index| ind: {
2844 const global = self.globals.items[global_index];
2845 // TODO this is just plain wrong as it all should happen in a single `resolveSymbols`
2846 // pass. This will go away once we abstact away Zig's incremental compilation into
2847 // its own module.
2848 if (global.getFile() == null and self.getSymbol(global).undf()) {
2849 _ = self.unresolved.swapRemove(global_index);
2850 break :ind global.sym_index;
2851 }
2852 break :ind try self.allocateSymbol();
2853 } else try self.allocateSymbol();
2854 try metadata.exports.append(gpa, global_sym_index);
2855 break :blk global_sym_index;
2856 };
2857 const global_sym_loc = SymbolWithLoc{ .sym_index = global_sym_index };
2858 const global_sym = self.getSymbolPtr(global_sym_loc);
2859 global_sym.* = .{
2860 .n_strx = try self.strtab.insert(gpa, exp_name),
2861 .n_type = macho.N_SECT | macho.N_EXT,
2862 .n_sect = metadata.section + 1,
2863 .n_desc = 0,
2864 .n_value = sym.n_value,
2865 };
2866
2867 switch (exp.opts.linkage) {
2868 .Internal => {
2869 // Symbol should be hidden, or in MachO lingo, private extern.
2870 // We should also mark the symbol as Weak: n_desc == N_WEAK_DEF.
2871 global_sym.n_type |= macho.N_PEXT;
2872 global_sym.n_desc |= macho.N_WEAK_DEF;
2873 },
2874 .Strong => {},
2875 .Weak => {
2876 // Weak linkage is specified as part of n_desc field.
2877 // Symbol's n_type is like for a symbol with strong linkage.
2878 global_sym.n_desc |= macho.N_WEAK_DEF;
2879 },
2880 else => unreachable,
2881 }
2882
2883 self.resolveGlobalSymbol(global_sym_loc) catch |err| switch (err) {
2884 error.MultipleSymbolDefinitions => {
2885 // TODO: this needs rethinking
2886 const global = self.getGlobal(exp_name).?;
2887 if (global_sym_loc.sym_index != global.sym_index and global.getFile() != null) {
2888 _ = try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create(
2889 gpa,
2890 exp.getSrcLoc(mod),
2891 \\LinkError: symbol '{s}' defined multiple times
2892 ,
2893 .{exp_name},
2894 ));
2895 }
2896 },
2897 else => |e| return e,
2898 };
2899 }
2900}
2901
2902pub fn deleteDeclExport(
2903 self: *MachO,
2904 decl_index: InternPool.DeclIndex,
2905 name: InternPool.NullTerminatedString,
2906) Allocator.Error!void {
2907 if (self.llvm_object) |_| return;
2908 const metadata = self.decls.getPtr(decl_index) orelse return;
2909
2910 const gpa = self.base.comp.gpa;
2911 const mod = self.base.comp.module.?;
2912 const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{mod.intern_pool.stringToSlice(name)});
2913 defer gpa.free(exp_name);
2914 const sym_index = metadata.getExportPtr(self, exp_name) orelse return;
2915
2916 const sym_loc = SymbolWithLoc{ .sym_index = sym_index.* };
2917 const sym = self.getSymbolPtr(sym_loc);
2918 log.debug("deleting export '{s}'", .{exp_name});
2919 assert(sym.sect() and sym.ext());
2920 sym.* = .{
2921 .n_strx = 0,
2922 .n_type = 0,
2923 .n_sect = 0,
2924 .n_desc = 0,
2925 .n_value = 0,
2926 };
2927 self.locals_free_list.append(gpa, sym_index.*) catch {};
2928
2929 if (self.resolver.fetchRemove(exp_name)) |entry| {
2930 defer gpa.free(entry.key);
2931 self.globals_free_list.append(gpa, entry.value) catch {};
2932 self.globals.items[entry.value] = .{ .sym_index = 0 };
2933 }
2934
2935 sym_index.* = 0;
2936}
2937
2938fn freeUnnamedConsts(self: *MachO, decl_index: InternPool.DeclIndex) void {
2939 const gpa = self.base.comp.gpa;
2940 const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return;
2941 for (unnamed_consts.items) |atom| {
2942 self.freeAtom(atom);
2943 }
2944 unnamed_consts.clearAndFree(gpa);
2945}
2946
2947pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void {
2948 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
2949 const gpa = self.base.comp.gpa;
2950 const mod = self.base.comp.module.?;
2951 const decl = mod.declPtr(decl_index);
2952
2953 log.debug("freeDecl {*}", .{decl});
2954
2955 if (self.decls.fetchSwapRemove(decl_index)) |const_kv| {
2956 var kv = const_kv;
2957 self.freeAtom(kv.value.atom);
2958 self.freeUnnamedConsts(decl_index);
2959 kv.value.exports.deinit(gpa);
2960 }
2961
2962 if (self.d_sym) |*d_sym| {
2963 d_sym.dwarf.freeDecl(decl_index);
2964 }
2965}
2966
2967pub fn getDeclVAddr(self: *MachO, decl_index: InternPool.DeclIndex, reloc_info: File.RelocInfo) !u64 {
2968 assert(self.llvm_object == null);
2969
2970 const this_atom_index = try self.getOrCreateAtomForDecl(decl_index);
2971 const sym_index = self.getAtom(this_atom_index).getSymbolIndex().?;
2972 const atom_index = self.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent_atom_index }).?;
2973 try Atom.addRelocation(self, atom_index, .{
2974 .type = .unsigned,
2975 .target = .{ .sym_index = sym_index },
2976 .offset = @as(u32, @intCast(reloc_info.offset)),
2977 .addend = reloc_info.addend,
2978 .pcrel = false,
2979 .length = 3,
2980 });
2981 try Atom.addRebase(self, atom_index, @as(u32, @intCast(reloc_info.offset)));
2982
2983 return 0;
2984}
2985
2986pub fn lowerAnonDecl(
2987 self: *MachO,
2988 decl_val: InternPool.Index,
2989 explicit_alignment: InternPool.Alignment,
2990 src_loc: Module.SrcLoc,
2991) !codegen.Result {
2992 const gpa = self.base.comp.gpa;
2993 const mod = self.base.comp.module.?;
2994 const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
2995 const decl_alignment = switch (explicit_alignment) {
2996 .none => ty.abiAlignment(mod),
2997 else => explicit_alignment,
2998 };
2999 if (self.anon_decls.get(decl_val)) |metadata| {
3000 const existing_addr = self.getAtom(metadata.atom).getSymbol(self).n_value;
3001 if (decl_alignment.check(existing_addr))
3002 return .ok;
3003 }
3004
3005 const val = Value.fromInterned(decl_val);
3006 const tv = TypedValue{ .ty = ty, .val = val };
3007 var name_buf: [32]u8 = undefined;
3008 const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
3009 @intFromEnum(decl_val),
3010 }) catch unreachable;
3011 const res = self.lowerConst(
3012 name,
3013 tv,
3014 decl_alignment,
3015 self.data_const_section_index.?,
3016 src_loc,
3017 ) catch |err| switch (err) {
3018 error.OutOfMemory => return error.OutOfMemory,
3019 else => |e| return .{ .fail = try Module.ErrorMsg.create(
3020 gpa,
3021 src_loc,
3022 "unable to lower constant value: {s}",
3023 .{@errorName(e)},
3024 ) },
3025 };
3026 const atom_index = switch (res) {
3027 .ok => |atom_index| atom_index,
3028 .fail => |em| return .{ .fail = em },
3029 };
3030 try self.anon_decls.put(gpa, decl_val, .{
3031 .atom = atom_index,
3032 .section = self.data_const_section_index.?,
3033 });
3034 return .ok;
3035}
3036
3037pub fn getAnonDeclVAddr(self: *MachO, decl_val: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
3038 assert(self.llvm_object == null);
3039
3040 const this_atom_index = self.anon_decls.get(decl_val).?.atom;
3041 const sym_index = self.getAtom(this_atom_index).getSymbolIndex().?;
3042 const atom_index = self.getAtomIndexForSymbol(.{ .sym_index = reloc_info.parent_atom_index }).?;
3043 try Atom.addRelocation(self, atom_index, .{
3044 .type = .unsigned,
3045 .target = .{ .sym_index = sym_index },
3046 .offset = @as(u32, @intCast(reloc_info.offset)),
3047 .addend = reloc_info.addend,
3048 .pcrel = false,
3049 .length = 3,
3050 });
3051 try Atom.addRebase(self, atom_index, @as(u32, @intCast(reloc_info.offset)));
3052
3053 return 0;
3054}
3055
3056const PopulateMissingMetadataOptions = struct {
3057 symbol_count_hint: u64,
3058 program_code_size_hint: u64,
3059};
3060
3061fn populateMissingMetadata(self: *MachO, options: PopulateMissingMetadataOptions) !void {
3062 assert(self.mode == .incremental);
3063
3064 const comp = self.base.comp;
3065 const gpa = comp.gpa;
3066 const target = comp.root_mod.resolved_target.result;
3067 const cpu_arch = target.cpu.arch;
3068 const pagezero_vmsize = self.calcPagezeroSize();
3069
3070 if (self.pagezero_segment_cmd_index == null) {
3071 if (pagezero_vmsize > 0) {
3072 self.pagezero_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
3073 try self.segments.append(gpa, .{
3074 .segname = makeStaticString("__PAGEZERO"),
3075 .vmsize = pagezero_vmsize,
3076 .cmdsize = @sizeOf(macho.segment_command_64),
3077 });
3078 }
3079 }
3080
3081 if (self.header_segment_cmd_index == null) {
3082 // The first __TEXT segment is immovable and covers MachO header and load commands.
3083 self.header_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
3084 const ideal_size = self.headerpad_size;
3085 const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), getPageSize(cpu_arch));
3086
3087 log.debug("found __TEXT segment (header-only) free space 0x{x} to 0x{x}", .{ 0, needed_size });
3088
3089 try self.segments.append(gpa, .{
3090 .segname = makeStaticString("__TEXT"),
3091 .vmaddr = pagezero_vmsize,
3092 .vmsize = needed_size,
3093 .filesize = needed_size,
3094 .maxprot = macho.PROT.READ | macho.PROT.EXEC,
3095 .initprot = macho.PROT.READ | macho.PROT.EXEC,
3096 .cmdsize = @sizeOf(macho.segment_command_64),
3097 });
3098 self.segment_table_dirty = true;
3099 }
3100
3101 if (self.text_section_index == null) {
3102 // Sadly, segments need unique string identfiers for some reason.
3103 self.text_section_index = try self.allocateSection("__TEXT1", "__text", .{
3104 .size = options.program_code_size_hint,
3105 .alignment = switch (cpu_arch) {
3106 .x86_64 => 1,
3107 .aarch64 => @sizeOf(u32),
3108 else => unreachable, // unhandled architecture type
3109 },
3110 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
3111 .prot = macho.PROT.READ | macho.PROT.EXEC,
3112 });
3113 self.segment_table_dirty = true;
3114 }
3115
3116 if (self.stubs_section_index == null) {
3117 const stub_size = stubs.stubSize(cpu_arch);
3118 self.stubs_section_index = try self.allocateSection("__TEXT2", "__stubs", .{
3119 .size = stub_size,
3120 .alignment = stubs.stubAlignment(cpu_arch),
3121 .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
3122 .reserved2 = stub_size,
3123 .prot = macho.PROT.READ | macho.PROT.EXEC,
3124 });
3125 self.segment_table_dirty = true;
3126 }
3127
3128 if (self.stub_helper_section_index == null) {
3129 self.stub_helper_section_index = try self.allocateSection("__TEXT3", "__stub_helper", .{
3130 .size = @sizeOf(u32),
3131 .alignment = stubs.stubAlignment(cpu_arch),
3132 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
3133 .prot = macho.PROT.READ | macho.PROT.EXEC,
3134 });
3135 self.segment_table_dirty = true;
3136 }
3137
3138 if (self.got_section_index == null) {
3139 self.got_section_index = try self.allocateSection("__DATA_CONST", "__got", .{
3140 .size = @sizeOf(u64) * options.symbol_count_hint,
3141 .alignment = @alignOf(u64),
3142 .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
3143 .prot = macho.PROT.READ | macho.PROT.WRITE,
3144 });
3145 self.segment_table_dirty = true;
3146 }
3147
3148 if (self.data_const_section_index == null) {
3149 self.data_const_section_index = try self.allocateSection("__DATA_CONST1", "__const", .{
3150 .size = @sizeOf(u64),
3151 .alignment = @alignOf(u64),
3152 .flags = macho.S_REGULAR,
3153 .prot = macho.PROT.READ | macho.PROT.WRITE,
3154 });
3155 self.segment_table_dirty = true;
3156 }
3157
3158 if (self.la_symbol_ptr_section_index == null) {
3159 self.la_symbol_ptr_section_index = try self.allocateSection("__DATA", "__la_symbol_ptr", .{
3160 .size = @sizeOf(u64),
3161 .alignment = @alignOf(u64),
3162 .flags = macho.S_LAZY_SYMBOL_POINTERS,
3163 .prot = macho.PROT.READ | macho.PROT.WRITE,
3164 });
3165 self.segment_table_dirty = true;
3166 }
3167
3168 if (self.data_section_index == null) {
3169 self.data_section_index = try self.allocateSection("__DATA1", "__data", .{
3170 .size = @sizeOf(u64),
3171 .alignment = @alignOf(u64),
3172 .flags = macho.S_REGULAR,
3173 .prot = macho.PROT.READ | macho.PROT.WRITE,
3174 });
3175 self.segment_table_dirty = true;
3176 }
3177
3178 if (comp.config.any_non_single_threaded) {
3179 if (self.thread_vars_section_index == null) {
3180 self.thread_vars_section_index = try self.allocateSection("__DATA2", "__thread_vars", .{
3181 .size = @sizeOf(u64) * 3,
3182 .alignment = @sizeOf(u64),
3183 .flags = macho.S_THREAD_LOCAL_VARIABLES,
3184 .prot = macho.PROT.READ | macho.PROT.WRITE,
3185 });
3186 self.segment_table_dirty = true;
3187 }
3188
3189 if (self.thread_data_section_index == null) {
3190 self.thread_data_section_index = try self.allocateSection("__DATA3", "__thread_data", .{
3191 .size = @sizeOf(u64),
3192 .alignment = @alignOf(u64),
3193 .flags = macho.S_THREAD_LOCAL_REGULAR,
3194 .prot = macho.PROT.READ | macho.PROT.WRITE,
3195 });
3196 self.segment_table_dirty = true;
3197 }
3198 }
3199
3200 if (self.linkedit_segment_cmd_index == null) {
3201 self.linkedit_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
3202
3203 try self.segments.append(gpa, .{
3204 .segname = makeStaticString("__LINKEDIT"),
3205 .maxprot = macho.PROT.READ,
3206 .initprot = macho.PROT.READ,
3207 .cmdsize = @sizeOf(macho.segment_command_64),
3208 });
3209 }
3210}
3211
3212fn calcPagezeroSize(self: *MachO) u64 {
3213 const output_mode = self.base.comp.config.output_mode;
3214 const target = self.base.comp.root_mod.resolved_target.result;
3215 const page_size = getPageSize(target.cpu.arch);
3216 const aligned_pagezero_vmsize = mem.alignBackward(u64, self.pagezero_vmsize, page_size);
3217 if (output_mode == .Lib) return 0;
3218 if (aligned_pagezero_vmsize == 0) return 0;
3219 if (aligned_pagezero_vmsize != self.pagezero_vmsize) {
3220 log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{self.pagezero_vmsize});
3221 log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize});
3222 }
3223 return aligned_pagezero_vmsize;
3224}
3225
3226const InitSectionOpts = struct {
3227 flags: u32 = macho.S_REGULAR,
3228 reserved1: u32 = 0,
3229 reserved2: u32 = 0,
3230};
3231
3232pub fn initSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: InitSectionOpts) !u8 {
3233 log.debug("creating section '{s},{s}'", .{ segname, sectname });
3234 const index = @as(u8, @intCast(self.sections.slice().len));
3235 const gpa = self.base.comp.gpa;
3236 try self.sections.append(gpa, .{
3237 .segment_index = undefined, // Segments will be created automatically later down the pipeline
3238 .header = .{
3239 .sectname = makeStaticString(sectname),
3240 .segname = makeStaticString(segname),
3241 .flags = opts.flags,
3242 .reserved1 = opts.reserved1,
3243 .reserved2 = opts.reserved2,
3244 },
3245 });
3246 return index;
3247}
3248
3249fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: struct {
3250 size: u64 = 0,
3251 alignment: u32 = 0,
3252 prot: macho.vm_prot_t = macho.PROT.NONE,
3253 flags: u32 = macho.S_REGULAR,
3254 reserved2: u32 = 0,
3255}) !u8 {
3256 const gpa = self.base.comp.gpa;
3257 const target = self.base.comp.root_mod.resolved_target.result;
3258 const page_size = getPageSize(target.cpu.arch);
3259 // In incremental context, we create one section per segment pairing. This way,
3260 // we can move the segment in raw file as we please.
3261 const segment_id = @as(u8, @intCast(self.segments.items.len));
3262 const vmaddr = blk: {
3263 const prev_segment = self.segments.items[segment_id - 1];
3264 break :blk mem.alignForward(u64, prev_segment.vmaddr + prev_segment.vmsize, page_size);
3265 };
3266 // We commit more memory than needed upfront so that we don't have to reallocate too soon.
3267 const vmsize = mem.alignForward(u64, opts.size, page_size);
3268 const off = self.findFreeSpace(opts.size, page_size);
3269
3270 log.debug("found {s},{s} free space 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
3271 segname,
3272 sectname,
3273 off,
3274 off + opts.size,
3275 vmaddr,
3276 vmaddr + vmsize,
3277 });
3278
3279 const seg = try self.segments.addOne(gpa);
3280 seg.* = .{
3281 .segname = makeStaticString(segname),
3282 .vmaddr = vmaddr,
3283 .vmsize = vmsize,
3284 .fileoff = off,
3285 .filesize = vmsize,
3286 .maxprot = opts.prot,
3287 .initprot = opts.prot,
3288 .nsects = 1,
3289 .cmdsize = @sizeOf(macho.segment_command_64) + @sizeOf(macho.section_64),
3290 };
3291
3292 const sect_id = try self.initSection(segname, sectname, .{
3293 .flags = opts.flags,
3294 .reserved2 = opts.reserved2,
3295 });
3296 const section = &self.sections.items(.header)[sect_id];
3297 section.addr = mem.alignForward(u64, vmaddr, opts.alignment);
3298 section.offset = mem.alignForward(u32, @as(u32, @intCast(off)), opts.alignment);
3299 section.size = opts.size;
3300 section.@"align" = math.log2(opts.alignment);
3301 self.sections.items(.segment_index)[sect_id] = segment_id;
3302 assert(!section.isZerofill()); // TODO zerofill sections
3303
3304 return sect_id;
3305}
3306
3307fn growSection(self: *MachO, sect_id: u8, needed_size: u64) !void {
3308 const header = &self.sections.items(.header)[sect_id];
3309 const segment_index = self.sections.items(.segment_index)[sect_id];
3310 const segment = &self.segments.items[segment_index];
3311 const maybe_last_atom_index = self.sections.items(.last_atom_index)[sect_id];
3312 const sect_capacity = self.allocatedSize(header.offset);
3313 const target = self.base.comp.root_mod.resolved_target.result;
3314 const page_size = getPageSize(target.cpu.arch);
3315
3316 if (needed_size > sect_capacity) {
3317 const new_offset = self.findFreeSpace(needed_size, page_size);
3318 const current_size = if (maybe_last_atom_index) |last_atom_index| blk: {
3319 const last_atom = self.getAtom(last_atom_index);
3320 const sym = last_atom.getSymbol(self);
3321 break :blk (sym.n_value + last_atom.size) - segment.vmaddr;
3322 } else header.size;
3323
3324 log.debug("moving {s},{s} from 0x{x} to 0x{x}", .{
3325 header.segName(),
3326 header.sectName(),
3327 header.offset,
3328 new_offset,
3329 });
3330
3331 const amt = try self.base.file.?.copyRangeAll(
3332 header.offset,
3333 self.base.file.?,
3334 new_offset,
3335 current_size,
3336 );
3337 if (amt != current_size) return error.InputOutput;
3338 header.offset = @as(u32, @intCast(new_offset));
3339 segment.fileoff = new_offset;
3340 }
3341
3342 const sect_vm_capacity = self.allocatedVirtualSize(segment.vmaddr);
3343 if (needed_size > sect_vm_capacity) {
3344 self.markRelocsDirtyByAddress(segment.vmaddr + segment.vmsize);
3345 try self.growSectionVirtualMemory(sect_id, needed_size);
3346 }
3347
3348 header.size = needed_size;
3349 segment.filesize = mem.alignForward(u64, needed_size, page_size);
3350 segment.vmsize = mem.alignForward(u64, needed_size, page_size);
3351}
3352
3353fn growSectionVirtualMemory(self: *MachO, sect_id: u8, needed_size: u64) !void {
3354 const target = self.base.comp.root_mod.resolved_target.result;
3355 const page_size = getPageSize(target.cpu.arch);
3356 const header = &self.sections.items(.header)[sect_id];
3357 const segment = self.getSegmentPtr(sect_id);
3358 const increased_size = padToIdeal(needed_size);
3359 const old_aligned_end = segment.vmaddr + segment.vmsize;
3360 const new_aligned_end = segment.vmaddr + mem.alignForward(u64, increased_size, page_size);
3361 const diff = new_aligned_end - old_aligned_end;
3362 log.debug("shifting every segment after {s},{s} in virtual memory by {x}", .{
3363 header.segName(),
3364 header.sectName(),
3365 diff,
3366 });
3367
3368 // TODO: enforce order by increasing VM addresses in self.sections container.
3369 for (self.sections.items(.header)[sect_id + 1 ..], 0..) |*next_header, next_sect_id| {
3370 const index = @as(u8, @intCast(sect_id + 1 + next_sect_id));
3371 const next_segment = self.getSegmentPtr(index);
3372 next_header.addr += diff;
3373 next_segment.vmaddr += diff;
3374
3375 const maybe_last_atom_index = &self.sections.items(.last_atom_index)[index];
3376 if (maybe_last_atom_index.*) |last_atom_index| {
3377 var atom_index = last_atom_index;
3378 while (true) {
3379 const atom = self.getAtom(atom_index);
3380 const sym = atom.getSymbolPtr(self);
3381 sym.n_value += diff;
3382
3383 if (atom.prev_index) |prev_index| {
3384 atom_index = prev_index;
3385 } else break;
3386 }
3387 }
3388 }
3389}
3390
3391pub fn addAtomToSection(self: *MachO, atom_index: Atom.Index) void {
3392 assert(self.mode == .zld);
3393 const atom = self.getAtomPtr(atom_index);
3394 const sym = self.getSymbol(atom.getSymbolWithLoc());
3395 var section = self.sections.get(sym.n_sect - 1);
3396 if (section.header.size > 0) {
3397 const last_atom = self.getAtomPtr(section.last_atom_index.?);
3398 last_atom.next_index = atom_index;
3399 atom.prev_index = section.last_atom_index;
3400 } else {
3401 section.first_atom_index = atom_index;
3402 }
3403 section.last_atom_index = atom_index;
3404 section.header.size += atom.size;
3405 self.sections.set(sym.n_sect - 1, section);
3406}
3407
3408fn allocateAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: Alignment) !u64 {
3409 const tracy = trace(@src());
3410 defer tracy.end();
3411
3412 assert(self.mode == .incremental);
3413
3414 const atom = self.getAtom(atom_index);
3415 const sect_id = atom.getSymbol(self).n_sect - 1;
3416 const segment = self.getSegmentPtr(sect_id);
3417 const header = &self.sections.items(.header)[sect_id];
3418 const free_list = &self.sections.items(.free_list)[sect_id];
3419 const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sect_id];
3420 const requires_padding = blk: {
3421 if (!header.isCode()) break :blk false;
3422 if (header.isSymbolStubs()) break :blk false;
3423 if (mem.eql(u8, "__stub_helper", header.sectName())) break :blk false;
3424 break :blk true;
3425 };
3426 const new_atom_ideal_capacity = if (requires_padding) padToIdeal(new_atom_size) else new_atom_size;
3427
3428 // We use these to indicate our intention to update metadata, placing the new atom,
3429 // and possibly removing a free list node.
3430 // It would be simpler to do it inside the for loop below, but that would cause a
3431 // problem if an error was returned later in the function. So this action
3432 // is actually carried out at the end of the function, when errors are no longer possible.
3433 var atom_placement: ?Atom.Index = null;
3434 var free_list_removal: ?usize = null;
3435
3436 // First we look for an appropriately sized free list node.
3437 // The list is unordered. We'll just take the first thing that works.
3438 const vaddr = blk: {
3439 var i: usize = 0;
3440 while (i < free_list.items.len) {
3441 const big_atom_index = free_list.items[i];
3442 const big_atom = self.getAtom(big_atom_index);
3443 // We now have a pointer to a live atom that has too much capacity.
3444 // Is it enough that we could fit this new atom?
3445 const sym = big_atom.getSymbol(self);
3446 const capacity = big_atom.capacity(self);
3447 const ideal_capacity = if (requires_padding) padToIdeal(capacity) else capacity;
3448 const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity;
3449 const capacity_end_vaddr = sym.n_value + capacity;
3450 const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
3451 const new_start_vaddr = alignment.backward(new_start_vaddr_unaligned);
3452 if (new_start_vaddr < ideal_capacity_end_vaddr) {
3453 // Additional bookkeeping here to notice if this free list node
3454 // should be deleted because the atom that it points to has grown to take up
3455 // more of the extra capacity.
3456 if (!big_atom.freeListEligible(self)) {
3457 _ = free_list.swapRemove(i);
3458 } else {
3459 i += 1;
3460 }
3461 continue;
3462 }
3463 // At this point we know that we will place the new atom here. But the
3464 // remaining question is whether there is still yet enough capacity left
3465 // over for there to still be a free list node.
3466 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
3467 const keep_free_list_node = remaining_capacity >= min_text_capacity;
3468
3469 // Set up the metadata to be updated, after errors are no longer possible.
3470 atom_placement = big_atom_index;
3471 if (!keep_free_list_node) {
3472 free_list_removal = i;
3473 }
3474 break :blk new_start_vaddr;
3475 } else if (maybe_last_atom_index.*) |last_index| {
3476 const last = self.getAtom(last_index);
3477 const last_symbol = last.getSymbol(self);
3478 const ideal_capacity = if (requires_padding) padToIdeal(last.size) else last.size;
3479 const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity;
3480 const new_start_vaddr = alignment.forward(ideal_capacity_end_vaddr);
3481 atom_placement = last_index;
3482 break :blk new_start_vaddr;
3483 } else {
3484 break :blk alignment.forward(segment.vmaddr);
3485 }
3486 };
3487
3488 const expand_section = if (atom_placement) |placement_index|
3489 self.getAtom(placement_index).next_index == null
3490 else
3491 true;
3492 if (expand_section) {
3493 const needed_size = (vaddr + new_atom_size) - segment.vmaddr;
3494 try self.growSection(sect_id, needed_size);
3495 maybe_last_atom_index.* = atom_index;
3496 self.segment_table_dirty = true;
3497 }
3498
3499 assert(alignment != .none);
3500 header.@"align" = @min(header.@"align", @intFromEnum(alignment));
3501 self.getAtomPtr(atom_index).size = new_atom_size;
3502
3503 if (atom.prev_index) |prev_index| {
3504 const prev = self.getAtomPtr(prev_index);
3505 prev.next_index = atom.next_index;
3506 }
3507 if (atom.next_index) |next_index| {
3508 const next = self.getAtomPtr(next_index);
3509 next.prev_index = atom.prev_index;
3510 }
3511
3512 if (atom_placement) |big_atom_index| {
3513 const big_atom = self.getAtomPtr(big_atom_index);
3514 const atom_ptr = self.getAtomPtr(atom_index);
3515 atom_ptr.prev_index = big_atom_index;
3516 atom_ptr.next_index = big_atom.next_index;
3517 big_atom.next_index = atom_index;
3518 } else {
3519 const atom_ptr = self.getAtomPtr(atom_index);
3520 atom_ptr.prev_index = null;
3521 atom_ptr.next_index = null;
3522 }
3523 if (free_list_removal) |i| {
3524 _ = free_list.swapRemove(i);
3525 }
3526
3527 return vaddr;
3528}
3529
3530pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
3531 _ = lib_name;
3532 const gpa = self.base.comp.gpa;
3533 const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
3534 defer gpa.free(sym_name);
3535 return self.addUndefined(sym_name, .{ .add_stub = true });
3536}
3537
3538pub fn writeSegmentHeaders(self: *MachO, writer: anytype) !void {
3539 for (self.segments.items, 0..) |seg, i| {
3540 const indexes = self.getSectionIndexes(@intCast(i));
3541 var out_seg = seg;
3542 out_seg.cmdsize = @sizeOf(macho.segment_command_64);
3543 out_seg.nsects = 0;
3544
3545 // Update section headers count; any section with size of 0 is excluded
3546 // since it doesn't have any data in the final binary file.
3547 for (self.sections.items(.header)[indexes.start..indexes.end]) |header| {
3548 if (header.size == 0) continue;
3549 out_seg.cmdsize += @sizeOf(macho.section_64);
3550 out_seg.nsects += 1;
3551 }
3552
3553 if (out_seg.nsects == 0 and
3554 (mem.eql(u8, out_seg.segName(), "__DATA_CONST") or
3555 mem.eql(u8, out_seg.segName(), "__DATA"))) continue;
3556
3557 try writer.writeStruct(out_seg);
3558 for (self.sections.items(.header)[indexes.start..indexes.end]) |header| {
3559 if (header.size == 0) continue;
3560 try writer.writeStruct(header);
3561 }
3562 }
3563}
3564
3565pub fn writeLinkeditSegmentData(self: *MachO) !void {
3566 const target = self.base.comp.root_mod.resolved_target.result;
3567 const page_size = getPageSize(target.cpu.arch);
3568 const seg = self.getLinkeditSegmentPtr();
3569 seg.filesize = 0;
3570 seg.vmsize = 0;
3571
3572 for (self.segments.items, 0..) |segment, id| {
3573 if (self.linkedit_segment_cmd_index.? == @as(u8, @intCast(id))) continue;
3574 if (seg.vmaddr < segment.vmaddr + segment.vmsize) {
3575 seg.vmaddr = mem.alignForward(u64, segment.vmaddr + segment.vmsize, page_size);
3576 }
3577 if (seg.fileoff < segment.fileoff + segment.filesize) {
3578 seg.fileoff = mem.alignForward(u64, segment.fileoff + segment.filesize, page_size);
3579 }
3580 }
3581
3582 try self.writeDyldInfoData();
3583 // TODO handle this better
3584 if (self.mode == .zld) {
3585 try self.writeFunctionStarts();
3586 try self.writeDataInCode();
3587 }
3588 try self.writeSymtabs();
3589
3590 seg.vmsize = mem.alignForward(u64, seg.filesize, page_size);
3591}
3592
3593fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, table: anytype) !void {
3594 const gpa = self.base.comp.gpa;
3595 const header = self.sections.items(.header)[sect_id];
3596 const segment_index = self.sections.items(.segment_index)[sect_id];
3597 const segment = self.segments.items[segment_index];
3598 const base_offset = header.addr - segment.vmaddr;
3599 const is_got = if (self.got_section_index) |index| index == sect_id else false;
3600
3601 try rebase.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
3602
3603 for (table.entries.items, 0..) |entry, i| {
3604 if (!table.lookup.contains(entry)) continue;
3605 const sym = self.getSymbol(entry);
3606 if (is_got and sym.undf()) continue;
3607 const offset = i * @sizeOf(u64);
3608 log.debug(" | rebase at {x}", .{base_offset + offset});
3609 rebase.entries.appendAssumeCapacity(.{
3610 .offset = base_offset + offset,
3611 .segment_id = segment_index,
3612 });
3613 }
3614}
3615
3616fn collectRebaseData(self: *MachO, rebase: *Rebase) !void {
3617 const gpa = self.base.comp.gpa;
3618 const slice = self.sections.slice();
3619
3620 for (self.rebases.keys(), 0..) |atom_index, i| {
3621 const atom = self.getAtom(atom_index);
3622 log.debug(" ATOM(%{?d}, '{s}')", .{ atom.getSymbolIndex(), atom.getName(self) });
3623
3624 const sym = atom.getSymbol(self);
3625 const segment_index = slice.items(.segment_index)[sym.n_sect - 1];
3626 const seg = self.getSegment(sym.n_sect - 1);
3627
3628 const base_offset = sym.n_value - seg.vmaddr;
3629
3630 const rebases = self.rebases.values()[i];
3631 try rebase.entries.ensureUnusedCapacity(gpa, rebases.items.len);
3632
3633 for (rebases.items) |offset| {
3634 log.debug(" | rebase at {x}", .{base_offset + offset});
3635
3636 rebase.entries.appendAssumeCapacity(.{
3637 .offset = base_offset + offset,
3638 .segment_id = segment_index,
3639 });
3640 }
3641 }
3642
3643 // Unpack GOT entries
3644 if (self.got_section_index) |sect_id| {
3645 try self.collectRebaseDataFromTableSection(sect_id, rebase, self.got_table);
3646 }
3647
3648 // Next, unpack __la_symbol_ptr entries
3649 if (self.la_symbol_ptr_section_index) |sect_id| {
3650 try self.collectRebaseDataFromTableSection(sect_id, rebase, self.stub_table);
3651 }
3652
3653 // Finally, unpack the rest.
3654 const target = self.base.comp.root_mod.resolved_target.result;
3655 const cpu_arch = target.cpu.arch;
3656 for (self.objects.items) |*object| {
3657 for (object.atoms.items) |atom_index| {
3658 const atom = self.getAtom(atom_index);
3659 const sym = self.getSymbol(atom.getSymbolWithLoc());
3660 if (sym.n_desc == N_DEAD) continue;
3661 if (sym.n_desc == N_BOUNDARY) continue;
3662
3663 const sect_id = sym.n_sect - 1;
3664 const section = self.sections.items(.header)[sect_id];
3665 const segment_id = self.sections.items(.segment_index)[sect_id];
3666 const segment = self.segments.items[segment_id];
3667 if (segment.maxprot & macho.PROT.WRITE == 0) continue;
3668 switch (section.type()) {
3669 macho.S_LITERAL_POINTERS,
3670 macho.S_REGULAR,
3671 macho.S_MOD_INIT_FUNC_POINTERS,
3672 macho.S_MOD_TERM_FUNC_POINTERS,
3673 => {},
3674 else => continue,
3675 }
3676
3677 log.debug(" ATOM({d}, %{d}, '{s}')", .{
3678 atom_index,
3679 atom.sym_index,
3680 self.getSymbolName(atom.getSymbolWithLoc()),
3681 });
3682
3683 const code = Atom.getAtomCode(self, atom_index);
3684 const relocs = Atom.getAtomRelocs(self, atom_index);
3685 const ctx = Atom.getRelocContext(self, atom_index);
3686
3687 for (relocs) |rel| {
3688 switch (cpu_arch) {
3689 .aarch64 => {
3690 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
3691 if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
3692 if (rel.r_length != 3) continue;
3693 },
3694 .x86_64 => {
3695 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
3696 if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
3697 if (rel.r_length != 3) continue;
3698 },
3699 else => unreachable,
3700 }
3701 const reloc_target = Atom.parseRelocTarget(self, .{
3702 .object_id = atom.getFile().?,
3703 .rel = rel,
3704 .code = code,
3705 .base_offset = ctx.base_offset,
3706 .base_addr = ctx.base_addr,
3707 });
3708 const target_sym = self.getSymbol(reloc_target);
3709 if (target_sym.undf()) continue;
2637fn writeUnwindInfo(self: *MachO) !void {
2638 const tracy = trace(@src());
2639 defer tracy.end();
37102640
3711 const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr));
3712 const rel_offset = rel.r_address - ctx.base_offset;
3713 const offset = @as(u64, @intCast(base_offset + rel_offset));
3714 log.debug(" | rebase at {x}", .{offset});
2641 const gpa = self.base.comp.gpa;
37152642
3716 try rebase.entries.append(gpa, .{
3717 .offset = offset,
3718 .segment_id = segment_id,
3719 });
3720 }
3721 }
2643 if (self.eh_frame_sect_index) |index| {
2644 const header = self.sections.items(.header)[index];
2645 const size = math.cast(usize, header.size) orelse return error.Overflow;
2646 const buffer = try gpa.alloc(u8, size);
2647 defer gpa.free(buffer);
2648 eh_frame.write(self, buffer);
2649 try self.base.file.?.pwriteAll(buffer, header.offset);
37222650 }
37232651
3724 try rebase.finalize(gpa);
3725}
3726
3727fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void {
3728 const gpa = self.base.comp.gpa;
3729 const header = self.sections.items(.header)[sect_id];
3730 const segment_index = self.sections.items(.segment_index)[sect_id];
3731 const segment = self.segments.items[segment_index];
3732 const base_offset = header.addr - segment.vmaddr;
3733
3734 try bind.entries.ensureUnusedCapacity(gpa, table.entries.items.len);
3735
3736 for (table.entries.items, 0..) |entry, i| {
3737 if (!table.lookup.contains(entry)) continue;
3738 const bind_sym = self.getSymbol(entry);
3739 if (!bind_sym.undf()) continue;
3740 const offset = i * @sizeOf(u64);
3741 log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
3742 base_offset + offset,
3743 self.getSymbolName(entry),
3744 @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER),
3745 });
3746 if (bind_sym.weakRef()) {
3747 log.debug(" | marking as weak ref ", .{});
3748 }
3749 bind.entries.appendAssumeCapacity(.{
3750 .target = entry,
3751 .offset = base_offset + offset,
3752 .segment_id = segment_index,
3753 .addend = 0,
3754 });
2652 if (self.unwind_info_sect_index) |index| {
2653 const header = self.sections.items(.header)[index];
2654 const size = math.cast(usize, header.size) orelse return error.Overflow;
2655 const buffer = try gpa.alloc(u8, size);
2656 defer gpa.free(buffer);
2657 try self.unwind_info.write(self, buffer);
2658 try self.base.file.?.pwriteAll(buffer, header.offset);
37552659 }
37562660}
37572661
3758fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void {
2662fn finalizeDyldInfoSections(self: *MachO) !void {
2663 const tracy = trace(@src());
2664 defer tracy.end();
37592665 const gpa = self.base.comp.gpa;
3760 const slice = self.sections.slice();
3761
3762 for (raw_bindings.keys(), 0..) |atom_index, i| {
3763 const atom = self.getAtom(atom_index);
3764 log.debug(" ATOM(%{?d}, '{s}')", .{ atom.getSymbolIndex(), atom.getName(self) });
3765
3766 const sym = atom.getSymbol(self);
3767 const segment_index = slice.items(.segment_index)[sym.n_sect - 1];
3768 const seg = self.getSegment(sym.n_sect - 1);
3769
3770 const base_offset = sym.n_value - seg.vmaddr;
3771
3772 const bindings = raw_bindings.values()[i];
3773 try bind.entries.ensureUnusedCapacity(gpa, bindings.items.len);
3774
3775 for (bindings.items) |binding| {
3776 const bind_sym = self.getSymbol(binding.target);
3777 const bind_sym_name = self.getSymbolName(binding.target);
3778 const dylib_ordinal = @divTrunc(
3779 @as(i16, @bitCast(bind_sym.n_desc)),
3780 macho.N_SYMBOL_RESOLVER,
3781 );
3782 log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
3783 binding.offset + base_offset,
3784 bind_sym_name,
3785 dylib_ordinal,
3786 });
3787 if (bind_sym.weakRef()) {
3788 log.debug(" | marking as weak ref ", .{});
3789 }
3790 bind.entries.appendAssumeCapacity(.{
3791 .target = binding.target,
3792 .offset = binding.offset + base_offset,
3793 .segment_id = segment_index,
3794 .addend = 0,
3795 });
3796 }
3797 }
3798
3799 // Unpack GOT pointers
3800 if (self.got_section_index) |sect_id| {
3801 try self.collectBindDataFromTableSection(sect_id, bind, self.got_table);
3802 }
3803
3804 // Next, unpack TLV pointers section
3805 if (self.tlv_ptr_section_index) |sect_id| {
3806 try self.collectBindDataFromTableSection(sect_id, bind, self.tlv_ptr_table);
3807 }
3808
3809 // Finally, unpack the rest.
3810 const target = self.base.comp.root_mod.resolved_target.result;
3811 const cpu_arch = target.cpu.arch;
3812 for (self.objects.items) |*object| {
3813 for (object.atoms.items) |atom_index| {
3814 const atom = self.getAtom(atom_index);
3815 const sym = self.getSymbol(atom.getSymbolWithLoc());
3816 if (sym.n_desc == N_DEAD) continue;
3817 if (sym.n_desc == N_BOUNDARY) continue;
3818
3819 const sect_id = sym.n_sect - 1;
3820 const section = self.sections.items(.header)[sect_id];
3821 const segment_id = self.sections.items(.segment_index)[sect_id];
3822 const segment = self.segments.items[segment_id];
3823 if (segment.maxprot & macho.PROT.WRITE == 0) continue;
3824 switch (section.type()) {
3825 macho.S_LITERAL_POINTERS,
3826 macho.S_REGULAR,
3827 macho.S_MOD_INIT_FUNC_POINTERS,
3828 macho.S_MOD_TERM_FUNC_POINTERS,
3829 => {},
3830 else => continue,
3831 }
3832
3833 log.debug(" ATOM({d}, %{d}, '{s}')", .{
3834 atom_index,
3835 atom.sym_index,
3836 self.getSymbolName(atom.getSymbolWithLoc()),
3837 });
3838
3839 const code = Atom.getAtomCode(self, atom_index);
3840 const relocs = Atom.getAtomRelocs(self, atom_index);
3841 const ctx = Atom.getRelocContext(self, atom_index);
38422666
3843 for (relocs) |rel| {
3844 switch (cpu_arch) {
3845 .aarch64 => {
3846 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
3847 if (rel_type != .ARM64_RELOC_UNSIGNED) continue;
3848 if (rel.r_length != 3) continue;
3849 },
3850 .x86_64 => {
3851 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
3852 if (rel_type != .X86_64_RELOC_UNSIGNED) continue;
3853 if (rel.r_length != 3) continue;
3854 },
3855 else => unreachable,
3856 }
3857
3858 const global = Atom.parseRelocTarget(self, .{
3859 .object_id = atom.getFile().?,
3860 .rel = rel,
3861 .code = code,
3862 .base_offset = ctx.base_offset,
3863 .base_addr = ctx.base_addr,
3864 });
3865 const bind_sym_name = self.getSymbolName(global);
3866 const bind_sym = self.getSymbol(global);
3867 if (!bind_sym.undf()) continue;
3868
3869 const base_offset = sym.n_value - segment.vmaddr;
3870 const rel_offset = @as(u32, @intCast(rel.r_address - ctx.base_offset));
3871 const offset = @as(u64, @intCast(base_offset + rel_offset));
3872 const addend = mem.readInt(i64, code[rel_offset..][0..8], .little);
3873
3874 const dylib_ordinal = @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER);
3875 log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{
3876 base_offset,
3877 bind_sym_name,
3878 dylib_ordinal,
3879 });
3880 log.debug(" | with addend {x}", .{addend});
3881 if (bind_sym.weakRef()) {
3882 log.debug(" | marking as weak ref ", .{});
3883 }
3884 try bind.entries.append(gpa, .{
3885 .target = global,
3886 .offset = offset,
3887 .segment_id = segment_id,
3888 .addend = addend,
3889 });
3890 }
3891 }
3892 }
3893
3894 try bind.finalize(gpa, self);
2667 try self.rebase.finalize(gpa);
2668 try self.bind.finalize(gpa, self);
2669 try self.weak_bind.finalize(gpa, self);
2670 try self.lazy_bind.finalize(gpa, self);
2671 try self.export_trie.finalize(gpa);
38952672}
38962673
3897fn collectLazyBindData(self: *MachO, bind: anytype) !void {
3898 const sect_id = self.la_symbol_ptr_section_index orelse return;
3899 const gpa = self.base.comp.gpa;
3900 try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table);
3901 try bind.finalize(gpa, self);
3902}
2674fn writeSyntheticSections(self: *MachO) !void {
2675 const tracy = trace(@src());
2676 defer tracy.end();
39032677
3904fn collectExportData(self: *MachO, trie: *Trie) !void {
39052678 const gpa = self.base.comp.gpa;
39062679
3907 // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER.
3908 log.debug("generating export trie", .{});
3909
3910 const exec_segment = self.segments.items[self.header_segment_cmd_index.?];
3911 const base_address = exec_segment.vmaddr;
3912
3913 for (self.globals.items) |global| {
3914 const sym = self.getSymbol(global);
3915
3916 if (sym.undf()) continue;
3917 assert(sym.ext());
3918 if (sym.n_desc == N_DEAD) continue;
3919 if (sym.n_desc == N_BOUNDARY) continue;
3920
3921 const sym_name = self.getSymbolName(global);
3922 log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value });
3923 try trie.put(gpa, .{
3924 .name = sym_name,
3925 .vmaddr_offset = sym.n_value - base_address,
3926 .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
3927 });
3928 }
3929
3930 try trie.finalize(gpa);
3931}
3932
3933fn writeDyldInfoData(self: *MachO) !void {
2680 if (self.got_sect_index) |sect_id| {
2681 const header = self.sections.items(.header)[sect_id];
2682 const size = math.cast(usize, header.size) orelse return error.Overflow;
2683 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2684 defer buffer.deinit();
2685 try self.got.write(self, buffer.writer());
2686 assert(buffer.items.len == header.size);
2687 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2688 }
2689
2690 if (self.stubs_sect_index) |sect_id| {
2691 const header = self.sections.items(.header)[sect_id];
2692 const size = math.cast(usize, header.size) orelse return error.Overflow;
2693 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2694 defer buffer.deinit();
2695 try self.stubs.write(self, buffer.writer());
2696 assert(buffer.items.len == header.size);
2697 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2698 }
2699
2700 if (self.stubs_helper_sect_index) |sect_id| {
2701 const header = self.sections.items(.header)[sect_id];
2702 const size = math.cast(usize, header.size) orelse return error.Overflow;
2703 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2704 defer buffer.deinit();
2705 try self.stubs_helper.write(self, buffer.writer());
2706 assert(buffer.items.len == header.size);
2707 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2708 }
2709
2710 if (self.la_symbol_ptr_sect_index) |sect_id| {
2711 const header = self.sections.items(.header)[sect_id];
2712 const size = math.cast(usize, header.size) orelse return error.Overflow;
2713 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2714 defer buffer.deinit();
2715 try self.la_symbol_ptr.write(self, buffer.writer());
2716 assert(buffer.items.len == header.size);
2717 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2718 }
2719
2720 if (self.tlv_ptr_sect_index) |sect_id| {
2721 const header = self.sections.items(.header)[sect_id];
2722 const size = math.cast(usize, header.size) orelse return error.Overflow;
2723 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2724 defer buffer.deinit();
2725 try self.tlv_ptr.write(self, buffer.writer());
2726 assert(buffer.items.len == header.size);
2727 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2728 }
2729
2730 if (self.objc_stubs_sect_index) |sect_id| {
2731 const header = self.sections.items(.header)[sect_id];
2732 const size = math.cast(usize, header.size) orelse return error.Overflow;
2733 var buffer = try std.ArrayList(u8).initCapacity(gpa, size);
2734 defer buffer.deinit();
2735 try self.objc_stubs.write(self, buffer.writer());
2736 assert(buffer.items.len == header.size);
2737 try self.base.file.?.pwriteAll(buffer.items, header.offset);
2738 }
2739}
2740
2741fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
39342742 const tracy = trace(@src());
39352743 defer tracy.end();
39362744
39372745 const gpa = self.base.comp.gpa;
2746 const cmd = &self.dyld_info_cmd;
2747 var needed_size: u32 = 0;
39382748
3939 var rebase = Rebase{};
3940 defer rebase.deinit(gpa);
3941 try self.collectRebaseData(&rebase);
3942
3943 var bind = Bind{};
3944 defer bind.deinit(gpa);
3945 try self.collectBindData(&bind, self.bindings);
3946
3947 var lazy_bind = LazyBind{};
3948 defer lazy_bind.deinit(gpa);
3949 try self.collectLazyBindData(&lazy_bind);
3950
3951 var trie: Trie = .{};
3952 defer trie.deinit(gpa);
3953 try trie.init(gpa);
3954 try self.collectExportData(&trie);
3955
3956 const link_seg = self.getLinkeditSegmentPtr();
3957 assert(mem.isAlignedGeneric(u64, link_seg.fileoff, @alignOf(u64)));
3958 const rebase_off = link_seg.fileoff;
3959 const rebase_size = rebase.size();
3960 const rebase_size_aligned = mem.alignForward(u64, rebase_size, @alignOf(u64));
3961 log.debug("writing rebase info from 0x{x} to 0x{x}", .{ rebase_off, rebase_off + rebase_size_aligned });
3962
3963 const bind_off = rebase_off + rebase_size_aligned;
3964 const bind_size = bind.size();
3965 const bind_size_aligned = mem.alignForward(u64, bind_size, @alignOf(u64));
3966 log.debug("writing bind info from 0x{x} to 0x{x}", .{ bind_off, bind_off + bind_size_aligned });
3967
3968 const lazy_bind_off = bind_off + bind_size_aligned;
3969 const lazy_bind_size = lazy_bind.size();
3970 const lazy_bind_size_aligned = mem.alignForward(u64, lazy_bind_size, @alignOf(u64));
3971 log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
3972 lazy_bind_off,
3973 lazy_bind_off + lazy_bind_size_aligned,
3974 });
2749 cmd.rebase_off = needed_size;
2750 cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64));
2751 needed_size += cmd.rebase_size;
2752
2753 cmd.bind_off = needed_size;
2754 cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64));
2755 needed_size += cmd.bind_size;
39752756
3976 const export_off = lazy_bind_off + lazy_bind_size_aligned;
3977 const export_size = trie.size;
3978 const export_size_aligned = mem.alignForward(u64, export_size, @alignOf(u64));
3979 log.debug("writing export trie from 0x{x} to 0x{x}", .{ export_off, export_off + export_size_aligned });
2757 cmd.weak_bind_off = needed_size;
2758 cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64));
2759 needed_size += cmd.weak_bind_size;
39802760
3981 const needed_size = math.cast(usize, export_off + export_size_aligned - rebase_off) orelse
3982 return error.Overflow;
3983 link_seg.filesize = needed_size;
3984 assert(mem.isAlignedGeneric(u64, link_seg.fileoff + link_seg.filesize, @alignOf(u64)));
2761 cmd.lazy_bind_off = needed_size;
2762 cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64));
2763 needed_size += cmd.lazy_bind_size;
2764
2765 cmd.export_off = needed_size;
2766 cmd.export_size = mem.alignForward(u32, @intCast(self.export_trie.size), @alignOf(u64));
2767 needed_size += cmd.export_size;
39852768
39862769 const buffer = try gpa.alloc(u8, needed_size);
39872770 defer gpa.free(buffer);
......@@ -3990,689 +2773,374 @@ fn writeDyldInfoData(self: *MachO) !void {
39902773 var stream = std.io.fixedBufferStream(buffer);
39912774 const writer = stream.writer();
39922775
3993 try rebase.write(writer);
3994 try stream.seekTo(bind_off - rebase_off);
3995
3996 try bind.write(writer);
3997 try stream.seekTo(lazy_bind_off - rebase_off);
3998
3999 try lazy_bind.write(writer);
4000 try stream.seekTo(export_off - rebase_off);
4001
4002 _ = try trie.write(writer);
2776 try self.rebase.write(writer);
2777 try stream.seekTo(cmd.bind_off);
2778 try self.bind.write(writer);
2779 try stream.seekTo(cmd.weak_bind_off);
2780 try self.weak_bind.write(writer);
2781 try stream.seekTo(cmd.lazy_bind_off);
2782 try self.lazy_bind.write(writer);
2783 try stream.seekTo(cmd.export_off);
2784 try self.export_trie.write(writer);
40032785
4004 log.debug("writing dyld info from 0x{x} to 0x{x}", .{
4005 rebase_off,
4006 rebase_off + needed_size,
4007 });
2786 cmd.rebase_off += off;
2787 cmd.bind_off += off;
2788 cmd.weak_bind_off += off;
2789 cmd.lazy_bind_off += off;
2790 cmd.export_off += off;
40082791
4009 try self.base.file.?.pwriteAll(buffer, rebase_off);
4010 try self.populateLazyBindOffsetsInStubHelper(lazy_bind);
2792 try self.base.file.?.pwriteAll(buffer, off);
40112793
4012 self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off));
4013 self.dyld_info_cmd.rebase_size = @as(u32, @intCast(rebase_size_aligned));
4014 self.dyld_info_cmd.bind_off = @as(u32, @intCast(bind_off));
4015 self.dyld_info_cmd.bind_size = @as(u32, @intCast(bind_size_aligned));
4016 self.dyld_info_cmd.lazy_bind_off = @as(u32, @intCast(lazy_bind_off));
4017 self.dyld_info_cmd.lazy_bind_size = @as(u32, @intCast(lazy_bind_size_aligned));
4018 self.dyld_info_cmd.export_off = @as(u32, @intCast(export_off));
4019 self.dyld_info_cmd.export_size = @as(u32, @intCast(export_size_aligned));
2794 return off + needed_size;
40202795}
40212796
4022fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: anytype) !void {
4023 if (lazy_bind.size() == 0) return;
4024
4025 const stub_helper_section_index = self.stub_helper_section_index.?;
4026 // assert(ctx.stub_helper_preamble_allocated);
4027
4028 const header = self.sections.items(.header)[stub_helper_section_index];
4029
4030 const target = self.base.comp.root_mod.resolved_target.result;
4031 const cpu_arch = target.cpu.arch;
4032 const preamble_size = stubs.stubHelperPreambleSize(cpu_arch);
4033 const stub_size = stubs.stubHelperSize(cpu_arch);
4034 const stub_offset = stubs.stubOffsetInStubHelper(cpu_arch);
4035 const base_offset = header.offset + preamble_size;
4036
4037 for (lazy_bind.offsets.items, 0..) |bind_offset, index| {
4038 const file_offset = base_offset + index * stub_size + stub_offset;
4039
4040 log.debug("writing lazy bind offset 0x{x} ({s}) in stub helper at 0x{x}", .{
4041 bind_offset,
4042 self.getSymbolName(lazy_bind.entries.items[index].target),
4043 file_offset,
4044 });
4045
4046 try self.base.file.?.pwriteAll(mem.asBytes(&bind_offset), file_offset);
4047 }
2797fn writeFunctionStarts(self: *MachO, off: u32) !u32 {
2798 // TODO actually write it out
2799 const cmd = &self.function_starts_cmd;
2800 cmd.dataoff = off;
2801 return off;
40482802}
40492803
4050const asc_u64 = std.sort.asc(u64);
4051
4052fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void {
4053 const sym = self.getSymbol(sym_loc);
4054 if (sym.n_strx == 0) return;
4055 if (sym.n_desc == N_DEAD) return;
4056 if (sym.n_desc == N_BOUNDARY) return;
4057 if (self.symbolIsTemp(sym_loc)) return;
4058 try addresses.append(sym.n_value);
4059}
2804pub fn writeDataInCode(self: *MachO, base_address: u64, off: u32) !u32 {
2805 const cmd = &self.data_in_code_cmd;
2806 cmd.dataoff = off;
40602807
4061fn writeFunctionStarts(self: *MachO) !void {
40622808 const gpa = self.base.comp.gpa;
4063 const seg = self.segments.items[self.header_segment_cmd_index.?];
2809 var dices = std.ArrayList(macho.data_in_code_entry).init(gpa);
2810 defer dices.deinit();
40642811
4065 // We need to sort by address first
4066 var addresses = std.ArrayList(u64).init(gpa);
4067 defer addresses.deinit();
2812 for (self.objects.items) |index| {
2813 const object = self.getFile(index).?.object;
2814 const in_dices = object.getDataInCode();
40682815
4069 for (self.objects.items) |object| {
4070 for (object.exec_atoms.items) |atom_index| {
4071 const atom = self.getAtom(atom_index);
4072 const sym_loc = atom.getSymbolWithLoc();
4073 try self.addSymbolToFunctionStarts(sym_loc, &addresses);
2816 try dices.ensureUnusedCapacity(in_dices.len);
40742817
4075 var it = Atom.getInnerSymbolsIterator(self, atom_index);
4076 while (it.next()) |inner_sym_loc| {
4077 try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses);
4078 }
2818 var next_dice: usize = 0;
2819 for (object.atoms.items) |atom_index| {
2820 if (next_dice >= in_dices.len) break;
2821 const atom = self.getAtom(atom_index) orelse continue;
2822 const start_off = atom.getInputAddress(self);
2823 const end_off = start_off + atom.size;
2824 const start_dice = next_dice;
2825
2826 if (end_off < in_dices[next_dice].offset) continue;
2827
2828 while (next_dice < in_dices.len and
2829 in_dices[next_dice].offset < end_off) : (next_dice += 1)
2830 {}
2831
2832 if (atom.flags.alive) for (in_dices[start_dice..next_dice]) |dice| {
2833 dices.appendAssumeCapacity(.{
2834 .offset = @intCast(atom.value + dice.offset - start_off - base_address),
2835 .length = dice.length,
2836 .kind = dice.kind,
2837 });
2838 };
40792839 }
40802840 }
40812841
4082 mem.sort(u64, addresses.items, {}, asc_u64);
2842 const needed_size = math.cast(u32, dices.items.len * @sizeOf(macho.data_in_code_entry)) orelse return error.Overflow;
2843 cmd.datasize = needed_size;
40832844
4084 var offsets = std.ArrayList(u32).init(gpa);
4085 defer offsets.deinit();
4086 try offsets.ensureTotalCapacityPrecise(addresses.items.len);
2845 try self.base.file.?.pwriteAll(mem.sliceAsBytes(dices.items), cmd.dataoff);
40872846
4088 var last_off: u32 = 0;
4089 for (addresses.items) |addr| {
4090 const offset = @as(u32, @intCast(addr - seg.vmaddr));
4091 const diff = offset - last_off;
2847 return off + needed_size;
2848}
40922849
4093 if (diff == 0) continue;
2850pub fn calcSymtabSize(self: *MachO) !void {
2851 const tracy = trace(@src());
2852 defer tracy.end();
2853 const gpa = self.base.comp.gpa;
40942854
4095 offsets.appendAssumeCapacity(diff);
4096 last_off = offset;
2855 var nlocals: u32 = 0;
2856 var nstabs: u32 = 0;
2857 var nexports: u32 = 0;
2858 var nimports: u32 = 0;
2859 var strsize: u32 = 0;
2860
2861 var files = std.ArrayList(File.Index).init(gpa);
2862 defer files.deinit();
2863 try files.ensureTotalCapacityPrecise(self.objects.items.len + self.dylibs.items.len + 2);
2864 if (self.zig_object) |index| files.appendAssumeCapacity(index);
2865 for (self.objects.items) |index| files.appendAssumeCapacity(index);
2866 for (self.dylibs.items) |index| files.appendAssumeCapacity(index);
2867 if (self.internal_object) |index| files.appendAssumeCapacity(index);
2868
2869 for (files.items) |index| {
2870 const file = self.getFile(index).?;
2871 const ctx = switch (file) {
2872 inline else => |x| &x.output_symtab_ctx,
2873 };
2874 ctx.ilocal = nlocals;
2875 ctx.istab = nstabs;
2876 ctx.iexport = nexports;
2877 ctx.iimport = nimports;
2878 try file.calcSymtabSize(self);
2879 nlocals += ctx.nlocals;
2880 nstabs += ctx.nstabs;
2881 nexports += ctx.nexports;
2882 nimports += ctx.nimports;
2883 strsize += ctx.strsize;
2884 }
2885
2886 for (files.items) |index| {
2887 const file = self.getFile(index).?;
2888 const ctx = switch (file) {
2889 inline else => |x| &x.output_symtab_ctx,
2890 };
2891 ctx.istab += nlocals;
2892 ctx.iexport += nlocals + nstabs;
2893 ctx.iimport += nlocals + nstabs + nexports;
40972894 }
40982895
4099 var buffer = std.ArrayList(u8).init(gpa);
4100 defer buffer.deinit();
4101
4102 const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64)));
4103 try buffer.ensureTotalCapacity(max_size);
4104
4105 for (offsets.items) |offset| {
4106 try std.leb.writeULEB128(buffer.writer(), offset);
2896 {
2897 const cmd = &self.symtab_cmd;
2898 cmd.nsyms = nlocals + nstabs + nexports + nimports;
2899 cmd.strsize = strsize + 1;
41072900 }
41082901
4109 const link_seg = self.getLinkeditSegmentPtr();
4110 const offset = link_seg.fileoff + link_seg.filesize;
4111 assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
4112 const needed_size = buffer.items.len;
4113 const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
4114 const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
4115 if (padding > 0) {
4116 try buffer.ensureUnusedCapacity(padding);
4117 buffer.appendNTimesAssumeCapacity(0, padding);
2902 {
2903 const cmd = &self.dysymtab_cmd;
2904 cmd.ilocalsym = 0;
2905 cmd.nlocalsym = nlocals + nstabs;
2906 cmd.iextdefsym = nlocals + nstabs;
2907 cmd.nextdefsym = nexports;
2908 cmd.iundefsym = nlocals + nstabs + nexports;
2909 cmd.nundefsym = nimports;
41182910 }
4119 link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff;
4120
4121 log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
2911}
41222912
4123 try self.base.file.?.pwriteAll(buffer.items, offset);
2913pub fn writeSymtab(self: *MachO, off: u32) !u32 {
2914 const tracy = trace(@src());
2915 defer tracy.end();
2916 const gpa = self.base.comp.gpa;
2917 const cmd = &self.symtab_cmd;
2918 cmd.symoff = off;
41242919
4125 self.function_starts_cmd.dataoff = @as(u32, @intCast(offset));
4126 self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
4127}
2920 try self.symtab.resize(gpa, cmd.nsyms);
2921 try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1);
41282922
4129fn filterDataInCode(
4130 dices: []const macho.data_in_code_entry,
4131 start_addr: u64,
4132 end_addr: u64,
4133) []const macho.data_in_code_entry {
4134 const Predicate = struct {
4135 addr: u64,
2923 if (self.getZigObject()) |zo| {
2924 zo.writeSymtab(self);
2925 }
2926 for (self.objects.items) |index| {
2927 try self.getFile(index).?.writeSymtab(self);
2928 }
2929 for (self.dylibs.items) |index| {
2930 try self.getFile(index).?.writeSymtab(self);
2931 }
2932 if (self.getInternalObject()) |internal| {
2933 internal.writeSymtab(self);
2934 }
41362935
4137 pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool {
4138 return dice.offset >= self.addr;
4139 }
4140 };
2936 assert(self.strtab.items.len == cmd.strsize);
41412937
4142 const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr });
4143 const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start;
2938 try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff);
41442939
4145 return dices[start..end];
2940 return off + cmd.nsyms * @sizeOf(macho.nlist_64);
41462941}
41472942
4148pub fn writeDataInCode(self: *MachO) !void {
2943fn writeIndsymtab(self: *MachO, off: u32) !u32 {
41492944 const gpa = self.base.comp.gpa;
4150 var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa);
4151 defer out_dice.deinit();
4152
4153 const text_sect_id = self.text_section_index orelse return;
4154 const text_sect_header = self.sections.items(.header)[text_sect_id];
4155
4156 for (self.objects.items) |object| {
4157 if (!object.hasDataInCode()) continue;
4158 const dice = object.data_in_code.items;
4159 try out_dice.ensureUnusedCapacity(dice.len);
4160
4161 for (object.exec_atoms.items) |atom_index| {
4162 const atom = self.getAtom(atom_index);
4163 const sym = self.getSymbol(atom.getSymbolWithLoc());
4164 if (sym.n_desc == N_DEAD) continue;
4165 if (sym.n_desc == N_BOUNDARY) return;
4166
4167 const source_addr = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
4168 source_sym.n_value
4169 else blk: {
4170 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
4171 const source_sect_id = @as(u8, @intCast(atom.sym_index - nbase));
4172 break :blk object.getSourceSection(source_sect_id).addr;
4173 };
4174 const filtered_dice = filterDataInCode(dice, source_addr, source_addr + atom.size);
4175 const base = math.cast(u32, sym.n_value - text_sect_header.addr + text_sect_header.offset) orelse
4176 return error.Overflow;
4177
4178 for (filtered_dice) |single| {
4179 const offset = math.cast(u32, single.offset - source_addr + base) orelse
4180 return error.Overflow;
4181 out_dice.appendAssumeCapacity(.{
4182 .offset = offset,
4183 .length = single.length,
4184 .kind = single.kind,
4185 });
4186 }
4187 }
4188 }
4189
4190 const seg = self.getLinkeditSegmentPtr();
4191 const offset = seg.fileoff + seg.filesize;
4192 assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
4193 const needed_size = out_dice.items.len * @sizeOf(macho.data_in_code_entry);
4194 const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
4195 seg.filesize = offset + needed_size_aligned - seg.fileoff;
4196
4197 const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
4198 defer gpa.free(buffer);
4199 {
4200 const src = mem.sliceAsBytes(out_dice.items);
4201 @memcpy(buffer[0..src.len], src);
4202 @memset(buffer[src.len..], 0);
4203 }
2945 const cmd = &self.dysymtab_cmd;
2946 cmd.indirectsymoff = off;
2947 cmd.nindirectsyms = self.indsymtab.nsyms(self);
42042948
4205 log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
2949 const needed_size = cmd.nindirectsyms * @sizeOf(u32);
2950 var buffer = try std.ArrayList(u8).initCapacity(gpa, needed_size);
2951 defer buffer.deinit();
2952 try self.indsymtab.write(self, buffer.writer());
42062953
4207 try self.base.file.?.pwriteAll(buffer, offset);
2954 try self.base.file.?.pwriteAll(buffer.items, cmd.indirectsymoff);
2955 assert(buffer.items.len == needed_size);
42082956
4209 self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset));
4210 self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned));
2957 return off + needed_size;
42112958}
42122959
4213fn writeSymtabs(self: *MachO) !void {
4214 var ctx = try self.writeSymtab();
4215 defer ctx.imports_table.deinit();
4216 try self.writeDysymtab(ctx);
4217 try self.writeStrtab();
2960pub fn writeStrtab(self: *MachO, off: u32) !u32 {
2961 const cmd = &self.symtab_cmd;
2962 cmd.stroff = off;
2963 try self.base.file.?.pwriteAll(self.strtab.items, cmd.stroff);
2964 return off + cmd.strsize;
42182965}
42192966
4220fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void {
4221 const sym = self.getSymbol(sym_loc);
4222 if (sym.n_strx == 0) return; // no name, skip
4223 if (sym.n_desc == N_DEAD) return; // garbage-collected, skip
4224 if (sym.n_desc == N_BOUNDARY) return; // boundary symbol, skip
4225 if (sym.ext()) return; // an export lands in its own symtab section, skip
4226 if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip
2967fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
42272968 const gpa = self.base.comp.gpa;
4228 var out_sym = sym;
4229 out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(sym_loc));
4230 try locals.append(out_sym);
4231}
4232
4233fn writeSymtab(self: *MachO) !SymtabCtx {
4234 const comp = self.base.comp;
4235 const gpa = comp.gpa;
4236
4237 var locals = std.ArrayList(macho.nlist_64).init(gpa);
4238 defer locals.deinit();
2969 const needed_size = load_commands.calcLoadCommandsSize(self, false);
2970 const buffer = try gpa.alloc(u8, needed_size);
2971 defer gpa.free(buffer);
42392972
4240 for (0..self.locals.items.len) |sym_id| {
4241 try self.addLocalToSymtab(.{ .sym_index = @intCast(sym_id) }, &locals);
4242 }
2973 var stream = std.io.fixedBufferStream(buffer);
2974 var cwriter = std.io.countingWriter(stream.writer());
2975 const writer = cwriter.writer();
42432976
4244 for (self.objects.items) |object| {
4245 for (object.atoms.items) |atom_index| {
4246 const atom = self.getAtom(atom_index);
4247 const sym_loc = atom.getSymbolWithLoc();
4248 try self.addLocalToSymtab(sym_loc, &locals);
2977 var ncmds: usize = 0;
42492978
4250 var it = Atom.getInnerSymbolsIterator(self, atom_index);
4251 while (it.next()) |inner_sym_loc| {
4252 try self.addLocalToSymtab(inner_sym_loc, &locals);
2979 // Segment and section load commands
2980 {
2981 const slice = self.sections.slice();
2982 var sect_id: usize = 0;
2983 for (self.segments.items) |seg| {
2984 try writer.writeStruct(seg);
2985 for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| {
2986 try writer.writeStruct(header);
42532987 }
2988 sect_id += seg.nsects;
42542989 }
2990 ncmds += self.segments.items.len;
2991 }
2992
2993 try writer.writeStruct(self.dyld_info_cmd);
2994 ncmds += 1;
2995 try writer.writeStruct(self.function_starts_cmd);
2996 ncmds += 1;
2997 try writer.writeStruct(self.data_in_code_cmd);
2998 ncmds += 1;
2999 try writer.writeStruct(self.symtab_cmd);
3000 ncmds += 1;
3001 try writer.writeStruct(self.dysymtab_cmd);
3002 ncmds += 1;
3003 try load_commands.writeDylinkerLC(writer);
3004 ncmds += 1;
3005
3006 if (self.entry_index) |global_index| {
3007 const sym = self.getSymbol(global_index);
3008 const seg = self.getTextSegment();
3009 const entryoff: u32 = if (sym.getFile(self) == null)
3010 0
3011 else
3012 @as(u32, @intCast(sym.getAddress(.{ .stubs = true }, self) - seg.vmaddr));
3013 try writer.writeStruct(macho.entry_point_command{
3014 .entryoff = entryoff,
3015 .stacksize = self.base.stack_size,
3016 });
3017 ncmds += 1;
42553018 }
42563019
4257 var exports = std.ArrayList(macho.nlist_64).init(gpa);
4258 defer exports.deinit();
4259
4260 for (self.globals.items) |global| {
4261 const sym = self.getSymbol(global);
4262 if (sym.undf()) continue; // import, skip
4263 if (sym.n_desc == N_DEAD) continue;
4264 if (sym.n_desc == N_BOUNDARY) continue;
4265 var out_sym = sym;
4266 out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
4267 try exports.append(out_sym);
3020 if (self.base.isDynLib()) {
3021 try load_commands.writeDylibIdLC(self, writer);
3022 ncmds += 1;
42683023 }
42693024
4270 var imports = std.ArrayList(macho.nlist_64).init(gpa);
4271 defer imports.deinit();
3025 try load_commands.writeRpathLCs(self.base.rpath_list, writer);
3026 ncmds += self.base.rpath_list.len;
42723027
4273 var imports_table = std.AutoHashMap(SymbolWithLoc, u32).init(gpa);
3028 try writer.writeStruct(macho.source_version_command{ .version = 0 });
3029 ncmds += 1;
42743030
4275 for (self.globals.items) |global| {
4276 const sym = self.getSymbol(global);
4277 if (sym.n_strx == 0) continue; // no name, skip
4278 if (!sym.undf()) continue; // not an import, skip
4279 if (sym.n_desc == N_DEAD) continue;
4280 if (sym.n_desc == N_BOUNDARY) continue;
4281 const new_index = @as(u32, @intCast(imports.items.len));
4282 var out_sym = sym;
4283 out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global));
4284 try imports.append(out_sym);
4285 try imports_table.putNoClobber(global, new_index);
3031 if (self.platform.isBuildVersionCompatible()) {
3032 try load_commands.writeBuildVersionLC(self.platform, self.sdk_version, writer);
3033 ncmds += 1;
3034 } else {
3035 try load_commands.writeVersionMinLC(self.platform, self.sdk_version, writer);
3036 ncmds += 1;
3037 }
3038
3039 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + cwriter.bytes_written;
3040 try writer.writeStruct(self.uuid_cmd);
3041 ncmds += 1;
3042
3043 for (self.dylibs.items) |index| {
3044 const dylib = self.getFile(index).?.dylib;
3045 assert(dylib.isAlive(self));
3046 const dylib_id = dylib.id.?;
3047 try load_commands.writeDylibLC(.{
3048 .cmd = if (dylib.weak)
3049 .LOAD_WEAK_DYLIB
3050 else if (dylib.reexport)
3051 .REEXPORT_DYLIB
3052 else
3053 .LOAD_DYLIB,
3054 .name = dylib_id.name,
3055 .timestamp = dylib_id.timestamp,
3056 .current_version = dylib_id.current_version,
3057 .compatibility_version = dylib_id.compatibility_version,
3058 }, writer);
3059 ncmds += 1;
42863060 }
42873061
4288 // We generate stabs last in order to ensure that the strtab always has debug info
4289 // strings trailing
4290 if (comp.config.debug_format != .strip) {
4291 for (self.objects.items) |object| {
4292 assert(self.d_sym == null); // TODO
4293 try self.generateSymbolStabs(object, &locals);
4294 }
3062 if (self.requiresCodeSig()) {
3063 try writer.writeStruct(self.codesig_cmd);
3064 ncmds += 1;
42953065 }
42963066
4297 const nlocals = @as(u32, @intCast(locals.items.len));
4298 const nexports = @as(u32, @intCast(exports.items.len));
4299 const nimports = @as(u32, @intCast(imports.items.len));
4300 const nsyms = nlocals + nexports + nimports;
4301
4302 const seg = self.getLinkeditSegmentPtr();
4303 const offset = seg.fileoff + seg.filesize;
4304 assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
4305 const needed_size = nsyms * @sizeOf(macho.nlist_64);
4306 seg.filesize = offset + needed_size - seg.fileoff;
4307 assert(mem.isAlignedGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64)));
4308
4309 var buffer = std.ArrayList(u8).init(gpa);
4310 defer buffer.deinit();
4311 try buffer.ensureTotalCapacityPrecise(needed_size);
4312 buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(locals.items));
4313 buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(exports.items));
4314 buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(imports.items));
4315
4316 log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
4317 try self.base.file.?.pwriteAll(buffer.items, offset);
3067 assert(cwriter.bytes_written == needed_size);
43183068
4319 self.symtab_cmd.symoff = @as(u32, @intCast(offset));
4320 self.symtab_cmd.nsyms = nsyms;
3069 try self.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
43213070
4322 return SymtabCtx{
4323 .nlocalsym = nlocals,
4324 .nextdefsym = nexports,
4325 .nundefsym = nimports,
4326 .imports_table = imports_table,
4327 };
3071 return .{ ncmds, buffer.len, uuid_cmd_offset };
43283072}
43293073
4330// TODO this function currently skips generating symbol stabs in case errors are encountered in DWARF data.
4331// I think we should actually report those errors to the user and let them decide if they want to strip debug info
4332// in that case or not.
4333fn generateSymbolStabs(
4334 self: *MachO,
4335 object: Object,
4336 locals: *std.ArrayList(macho.nlist_64),
4337) !void {
4338 log.debug("generating stabs for '{s}'", .{object.name});
4339
4340 const gpa = self.base.comp.gpa;
4341 var debug_info = object.parseDwarfInfo();
4342
4343 var lookup = DwarfInfo.AbbrevLookupTable.init(gpa);
4344 defer lookup.deinit();
4345 try lookup.ensureUnusedCapacity(std.math.maxInt(u8));
4346
4347 // We assume there is only one CU.
4348 var cu_it = debug_info.getCompileUnitIterator();
4349 const compile_unit = while (try cu_it.next()) |cu| {
4350 const offset = math.cast(usize, cu.cuh.debug_abbrev_offset) orelse return error.Overflow;
4351 try debug_info.genAbbrevLookupByKind(offset, &lookup);
4352 break cu;
4353 } else {
4354 log.debug("no compile unit found in debug info in {s}; skipping", .{object.name});
4355 return;
4356 };
4357
4358 var abbrev_it = compile_unit.getAbbrevEntryIterator(debug_info);
4359 const maybe_cu_entry: ?DwarfInfo.AbbrevEntry = blk: {
4360 while (abbrev_it.next(lookup) catch break :blk null) |entry| switch (entry.tag) {
4361 dwarf.TAG.compile_unit => break :blk entry,
4362 else => continue,
4363 } else break :blk null;
4364 };
4365
4366 const cu_entry = maybe_cu_entry orelse {
4367 log.debug("missing DWARF_TAG_compile_unit tag in {s}; skipping", .{object.name});
4368 return;
4369 };
4370
4371 var maybe_tu_name: ?[]const u8 = null;
4372 var maybe_tu_comp_dir: ?[]const u8 = null;
4373 var attr_it = cu_entry.getAttributeIterator(debug_info, compile_unit.cuh);
3074fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void {
3075 var header: macho.mach_header_64 = .{};
3076 header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK;
43743077
4375 blk: {
4376 while (attr_it.next() catch break :blk) |attr| switch (attr.name) {
4377 dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue,
4378 dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue,
4379 else => continue,
4380 };
4381 }
3078 // TODO: if (self.options.namespace == .two_level) {
3079 header.flags |= macho.MH_TWOLEVEL;
3080 // }
43823081
4383 if (maybe_tu_name == null or maybe_tu_comp_dir == null) {
4384 log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name});
4385 return;
3082 switch (self.getTarget().cpu.arch) {
3083 .aarch64 => {
3084 header.cputype = macho.CPU_TYPE_ARM64;
3085 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
3086 },
3087 .x86_64 => {
3088 header.cputype = macho.CPU_TYPE_X86_64;
3089 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
3090 },
3091 else => {},
43863092 }
43873093
4388 const tu_name = maybe_tu_name.?;
4389 const tu_comp_dir = maybe_tu_comp_dir.?;
4390
4391 // Open scope
4392 try locals.ensureUnusedCapacity(3);
4393 locals.appendAssumeCapacity(.{
4394 .n_strx = try self.strtab.insert(gpa, tu_comp_dir),
4395 .n_type = macho.N_SO,
4396 .n_sect = 0,
4397 .n_desc = 0,
4398 .n_value = 0,
4399 });
4400 locals.appendAssumeCapacity(.{
4401 .n_strx = try self.strtab.insert(gpa, tu_name),
4402 .n_type = macho.N_SO,
4403 .n_sect = 0,
4404 .n_desc = 0,
4405 .n_value = 0,
4406 });
4407 locals.appendAssumeCapacity(.{
4408 .n_strx = try self.strtab.insert(gpa, object.name),
4409 .n_type = macho.N_OSO,
4410 .n_sect = 0,
4411 .n_desc = 1,
4412 .n_value = object.mtime,
4413 });
4414
4415 var stabs_buf: [4]macho.nlist_64 = undefined;
4416
4417 var name_lookup: ?DwarfInfo.SubprogramLookupByName = if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) blk: {
4418 var name_lookup = DwarfInfo.SubprogramLookupByName.init(gpa);
4419 errdefer name_lookup.deinit();
4420 try name_lookup.ensureUnusedCapacity(@as(u32, @intCast(object.atoms.items.len)));
4421 debug_info.genSubprogramLookupByName(compile_unit, lookup, &name_lookup) catch |err| switch (err) {
4422 error.UnhandledDwFormValue => {}, // TODO I don't like the fact we constantly re-iterate and hit this; we should validate once a priori
4423 else => |e| return e,
4424 };
4425 break :blk name_lookup;
4426 } else null;
4427 defer if (name_lookup) |*nl| nl.deinit();
4428
4429 for (object.atoms.items) |atom_index| {
4430 const atom = self.getAtom(atom_index);
4431 const stabs = try self.generateSymbolStabsForSymbol(
4432 atom_index,
4433 atom.getSymbolWithLoc(),
4434 name_lookup,
4435 &stabs_buf,
4436 );
4437 try locals.appendSlice(stabs);
4438
4439 var it = Atom.getInnerSymbolsIterator(self, atom_index);
4440 while (it.next()) |sym_loc| {
4441 const contained_stabs = try self.generateSymbolStabsForSymbol(
4442 atom_index,
4443 sym_loc,
4444 name_lookup,
4445 &stabs_buf,
4446 );
4447 try locals.appendSlice(contained_stabs);
4448 }
3094 if (self.base.isDynLib()) {
3095 header.filetype = macho.MH_DYLIB;
3096 } else {
3097 header.filetype = macho.MH_EXECUTE;
3098 header.flags |= macho.MH_PIE;
44493099 }
44503100
4451 // Close scope
4452 try locals.append(.{
4453 .n_strx = 0,
4454 .n_type = macho.N_SO,
4455 .n_sect = 0,
4456 .n_desc = 0,
4457 .n_value = 0,
4458 });
4459}
4460
4461fn generateSymbolStabsForSymbol(
4462 self: *MachO,
4463 atom_index: Atom.Index,
4464 sym_loc: SymbolWithLoc,
4465 lookup: ?DwarfInfo.SubprogramLookupByName,
4466 buf: *[4]macho.nlist_64,
4467) ![]const macho.nlist_64 {
4468 const gpa = self.base.comp.gpa;
4469 const object = self.objects.items[sym_loc.getFile().?];
4470 const sym = self.getSymbol(sym_loc);
4471 const sym_name = self.getSymbolName(sym_loc);
4472 const header = self.sections.items(.header)[sym.n_sect - 1];
4473
4474 if (sym.n_strx == 0) return buf[0..0];
4475 if (self.symbolIsTemp(sym_loc)) return buf[0..0];
4476
4477 if (!header.isCode()) {
4478 // Since we are not dealing with machine code, it's either a global or a static depending
4479 // on the linkage scope.
4480 if (sym.sect() and sym.ext()) {
4481 // Global gets an N_GSYM stab type.
4482 buf[0] = .{
4483 .n_strx = try self.strtab.insert(gpa, sym_name),
4484 .n_type = macho.N_GSYM,
4485 .n_sect = sym.n_sect,
4486 .n_desc = 0,
4487 .n_value = 0,
4488 };
4489 } else {
4490 // Local static gets an N_STSYM stab type.
4491 buf[0] = .{
4492 .n_strx = try self.strtab.insert(gpa, sym_name),
4493 .n_type = macho.N_STSYM,
4494 .n_sect = sym.n_sect,
4495 .n_desc = 0,
4496 .n_value = sym.n_value,
4497 };
4498 }
4499 return buf[0..1];
3101 const has_reexports = for (self.dylibs.items) |index| {
3102 if (self.getFile(index).?.dylib.reexport) break true;
3103 } else false;
3104 if (!has_reexports) {
3105 header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
45003106 }
45013107
4502 const size: u64 = size: {
4503 if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) {
4504 break :size self.getAtom(atom_index).size;
4505 }
4506
4507 // Since we don't have subsections to work with, we need to infer the size of each function
4508 // the slow way by scanning the debug info for matching symbol names and extracting
4509 // the symbol's DWARF_AT_low_pc and DWARF_AT_high_pc values.
4510 const source_sym = object.getSourceSymbol(sym_loc.sym_index) orelse return buf[0..0];
4511 const subprogram = lookup.?.get(sym_name[1..]) orelse return buf[0..0];
4512
4513 if (subprogram.addr <= source_sym.n_value and source_sym.n_value < subprogram.addr + subprogram.size) {
4514 break :size subprogram.size;
4515 } else {
4516 log.debug("no stab found for {s}", .{sym_name});
4517 return buf[0..0];
4518 }
4519 };
4520
4521 buf[0] = .{
4522 .n_strx = 0,
4523 .n_type = macho.N_BNSYM,
4524 .n_sect = sym.n_sect,
4525 .n_desc = 0,
4526 .n_value = sym.n_value,
4527 };
4528 buf[1] = .{
4529 .n_strx = try self.strtab.insert(gpa, sym_name),
4530 .n_type = macho.N_FUN,
4531 .n_sect = sym.n_sect,
4532 .n_desc = 0,
4533 .n_value = sym.n_value,
4534 };
4535 buf[2] = .{
4536 .n_strx = 0,
4537 .n_type = macho.N_FUN,
4538 .n_sect = 0,
4539 .n_desc = 0,
4540 .n_value = size,
4541 };
4542 buf[3] = .{
4543 .n_strx = 0,
4544 .n_type = macho.N_ENSYM,
4545 .n_sect = sym.n_sect,
4546 .n_desc = 0,
4547 .n_value = size,
4548 };
4549
4550 return buf;
4551}
4552
4553pub fn writeStrtab(self: *MachO) !void {
4554 const gpa = self.base.comp.gpa;
4555 const seg = self.getLinkeditSegmentPtr();
4556 const offset = seg.fileoff + seg.filesize;
4557 assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
4558 const needed_size = self.strtab.buffer.items.len;
4559 const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
4560 seg.filesize = offset + needed_size_aligned - seg.fileoff;
4561
4562 log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
4563
4564 const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow);
4565 defer gpa.free(buffer);
4566 @memcpy(buffer[0..self.strtab.buffer.items.len], self.strtab.buffer.items);
4567 @memset(buffer[self.strtab.buffer.items.len..], 0);
4568
4569 try self.base.file.?.pwriteAll(buffer, offset);
4570
4571 self.symtab_cmd.stroff = @as(u32, @intCast(offset));
4572 self.symtab_cmd.strsize = @as(u32, @intCast(needed_size_aligned));
4573}
4574
4575const SymtabCtx = struct {
4576 nlocalsym: u32,
4577 nextdefsym: u32,
4578 nundefsym: u32,
4579 imports_table: std.AutoHashMap(SymbolWithLoc, u32),
4580};
4581
4582pub fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void {
4583 const gpa = self.base.comp.gpa;
4584 const nstubs = @as(u32, @intCast(self.stub_table.lookup.count()));
4585 const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count()));
4586 const nindirectsyms = nstubs * 2 + ngot_entries;
4587 const iextdefsym = ctx.nlocalsym;
4588 const iundefsym = iextdefsym + ctx.nextdefsym;
4589
4590 const seg = self.getLinkeditSegmentPtr();
4591 const offset = seg.fileoff + seg.filesize;
4592 assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64)));
4593 const needed_size = nindirectsyms * @sizeOf(u32);
4594 const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64));
4595 seg.filesize = offset + needed_size_aligned - seg.fileoff;
4596
4597 log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned });
4598
4599 var buf = std.ArrayList(u8).init(gpa);
4600 defer buf.deinit();
4601 try buf.ensureTotalCapacity(math.cast(usize, needed_size_aligned) orelse return error.Overflow);
4602 const writer = buf.writer();
4603
4604 if (self.stubs_section_index) |sect_id| {
4605 const stubs_header = &self.sections.items(.header)[sect_id];
4606 stubs_header.reserved1 = 0;
4607 for (self.stub_table.entries.items) |entry| {
4608 if (!self.stub_table.lookup.contains(entry)) continue;
4609 const target_sym = self.getSymbol(entry);
4610 assert(target_sym.undf());
4611 try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
4612 }
3108 if (self.has_tlv) {
3109 header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
46133110 }
4614
4615 if (self.got_section_index) |sect_id| {
4616 const got = &self.sections.items(.header)[sect_id];
4617 got.reserved1 = nstubs;
4618 for (self.got_table.entries.items) |entry| {
4619 if (!self.got_table.lookup.contains(entry)) continue;
4620 const target_sym = self.getSymbol(entry);
4621 if (target_sym.undf()) {
4622 try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
4623 } else {
4624 try writer.writeInt(u32, macho.INDIRECT_SYMBOL_LOCAL, .little);
4625 }
4626 }
3111 if (self.binds_to_weak) {
3112 header.flags |= macho.MH_BINDS_TO_WEAK;
46273113 }
4628
4629 if (self.la_symbol_ptr_section_index) |sect_id| {
4630 const la_symbol_ptr = &self.sections.items(.header)[sect_id];
4631 la_symbol_ptr.reserved1 = nstubs + ngot_entries;
4632 for (self.stub_table.entries.items) |entry| {
4633 if (!self.stub_table.lookup.contains(entry)) continue;
4634 const target_sym = self.getSymbol(entry);
4635 assert(target_sym.undf());
4636 try writer.writeInt(u32, iundefsym + ctx.imports_table.get(entry).?, .little);
4637 }
3114 if (self.weak_defines) {
3115 header.flags |= macho.MH_WEAK_DEFINES;
46383116 }
46393117
4640 const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow;
4641 if (padding > 0) {
4642 buf.appendNTimesAssumeCapacity(0, padding);
4643 }
3118 header.ncmds = @intCast(ncmds);
3119 header.sizeofcmds = @intCast(sizeofcmds);
46443120
4645 assert(buf.items.len == needed_size_aligned);
4646 try self.base.file.?.pwriteAll(buf.items, offset);
3121 log.debug("writing Mach-O header {}", .{header});
46473122
4648 self.dysymtab_cmd.nlocalsym = ctx.nlocalsym;
4649 self.dysymtab_cmd.iextdefsym = iextdefsym;
4650 self.dysymtab_cmd.nextdefsym = ctx.nextdefsym;
4651 self.dysymtab_cmd.iundefsym = iundefsym;
4652 self.dysymtab_cmd.nundefsym = ctx.nundefsym;
4653 self.dysymtab_cmd.indirectsymoff = @as(u32, @intCast(offset));
4654 self.dysymtab_cmd.nindirectsyms = nindirectsyms;
3123 try self.base.file.?.pwriteAll(mem.asBytes(&header), 0);
46553124}
46563125
4657pub fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void {
3126fn writeUuid(self: *MachO, uuid_cmd_offset: u64, has_codesig: bool) !void {
46583127 const file_size = if (!has_codesig) blk: {
4659 const seg = self.getLinkeditSegmentPtr();
3128 const seg = self.getLinkeditSegment();
46603129 break :blk seg.fileoff + seg.filesize;
46613130 } else self.codesig_cmd.dataoff;
4662 try calcUuid(comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
3131 try calcUuid(self.base.comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
46633132 const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
46643133 try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset);
46653134}
46663135
46673136pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
4668 const target = self.base.comp.root_mod.resolved_target.result;
4669 const seg = self.getLinkeditSegmentPtr();
3137 const seg = self.getLinkeditSegment();
46703138 // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
46713139 // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
46723140 const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16);
46733141 const needed_size = code_sig.estimateSize(offset);
46743142 seg.filesize = offset + needed_size - seg.fileoff;
4675 seg.vmsize = mem.alignForward(u64, seg.filesize, getPageSize(target.cpu.arch));
3143 seg.vmsize = mem.alignForward(u64, seg.filesize, self.getPageSize());
46763144 log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
46773145 // Pad out the space. We need to do this to calculate valid hashes for everything in the file
46783146 // except for code signature data.
......@@ -4682,22 +3150,19 @@ pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
46823150 self.codesig_cmd.datasize = @as(u32, @intCast(needed_size));
46833151}
46843152
4685pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void {
4686 const output_mode = self.base.comp.config.output_mode;
4687 const seg_id = self.header_segment_cmd_index.?;
4688 const seg = self.segments.items[seg_id];
3153pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
3154 const seg = self.getTextSegment();
46893155 const offset = self.codesig_cmd.dataoff;
46903156
4691 const gpa = self.base.comp.gpa;
4692 var buffer = std.ArrayList(u8).init(gpa);
3157 var buffer = std.ArrayList(u8).init(self.base.comp.gpa);
46933158 defer buffer.deinit();
46943159 try buffer.ensureTotalCapacityPrecise(code_sig.size());
4695 try code_sig.writeAdhocSignature(comp, .{
3160 try code_sig.writeAdhocSignature(self, .{
46963161 .file = self.base.file.?,
46973162 .exec_seg_base = seg.fileoff,
46983163 .exec_seg_limit = seg.filesize,
46993164 .file_size = offset,
4700 .output_mode = output_mode,
3165 .dylib = self.base.isDynLib(),
47013166 }, buffer.writer());
47023167 assert(buffer.items.len == code_sig.size());
47033168
......@@ -4709,51 +3174,79 @@ pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *Cod
47093174 try self.base.file.?.pwriteAll(buffer.items, offset);
47103175}
47113176
4712/// Writes Mach-O file header.
4713pub fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
4714 const output_mode = self.base.comp.config.output_mode;
3177pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void {
3178 if (build_options.skip_non_native and builtin.object_format != .macho) {
3179 @panic("Attempted to compile for object format that was disabled by build configuration");
3180 }
3181 if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(mod, func_index, air, liveness);
3182 return self.getZigObject().?.updateFunc(self, mod, func_index, air, liveness);
3183}
47153184
4716 var header: macho.mach_header_64 = .{};
4717 header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL;
3185pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 {
3186 return self.getZigObject().?.lowerUnnamedConst(self, typed_value, decl_index);
3187}
47183188
4719 const target = self.base.comp.root_mod.resolved_target.result;
4720 switch (target.cpu.arch) {
4721 .aarch64 => {
4722 header.cputype = macho.CPU_TYPE_ARM64;
4723 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
4724 },
4725 .x86_64 => {
4726 header.cputype = macho.CPU_TYPE_X86_64;
4727 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
4728 },
4729 else => unreachable,
3189pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void {
3190 if (build_options.skip_non_native and builtin.object_format != .macho) {
3191 @panic("Attempted to compile for object format that was disabled by build configuration");
47303192 }
3193 if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index);
3194 return self.getZigObject().?.updateDecl(self, mod, decl_index);
3195}
47313196
4732 switch (output_mode) {
4733 .Exe => {
4734 header.filetype = macho.MH_EXECUTE;
4735 },
4736 .Lib => {
4737 // By this point, it can only be a dylib.
4738 header.filetype = macho.MH_DYLIB;
4739 header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
4740 },
4741 else => unreachable,
4742 }
3197pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void {
3198 if (self.llvm_object) |_| return;
3199 return self.getZigObject().?.updateDeclLineNumber(module, decl_index);
3200}
47433201
4744 if (self.thread_vars_section_index) |sect_id| {
4745 header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
4746 if (self.sections.items(.header)[sect_id].size > 0) {
4747 header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
4748 }
3202pub fn updateExports(
3203 self: *MachO,
3204 mod: *Module,
3205 exported: Module.Exported,
3206 exports: []const *Module.Export,
3207) link.File.UpdateExportsError!void {
3208 if (build_options.skip_non_native and builtin.object_format != .macho) {
3209 @panic("Attempted to compile for object format that was disabled by build configuration");
47493210 }
3211 if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports);
3212 return self.getZigObject().?.updateExports(self, mod, exported, exports);
3213}
47503214
4751 header.ncmds = ncmds;
4752 header.sizeofcmds = sizeofcmds;
3215pub fn deleteDeclExport(
3216 self: *MachO,
3217 decl_index: InternPool.DeclIndex,
3218 name: InternPool.NullTerminatedString,
3219) Allocator.Error!void {
3220 if (self.llvm_object) |_| return;
3221 return self.getZigObject().?.deleteDeclExport(self, decl_index, name);
3222}
47533223
4754 log.debug("writing Mach-O header {}", .{header});
3224pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void {
3225 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
3226 return self.getZigObject().?.freeDecl(decl_index);
3227}
47553228
4756 try self.base.file.?.pwriteAll(mem.asBytes(&header), 0);
3229pub fn getDeclVAddr(self: *MachO, decl_index: InternPool.DeclIndex, reloc_info: link.File.RelocInfo) !u64 {
3230 assert(self.llvm_object == null);
3231 return self.getZigObject().?.getDeclVAddr(self, decl_index, reloc_info);
3232}
3233
3234pub fn lowerAnonDecl(
3235 self: *MachO,
3236 decl_val: InternPool.Index,
3237 explicit_alignment: InternPool.Alignment,
3238 src_loc: Module.SrcLoc,
3239) !codegen.Result {
3240 return self.getZigObject().?.lowerAnonDecl(self, decl_val, explicit_alignment, src_loc);
3241}
3242
3243pub fn getAnonDeclVAddr(self: *MachO, decl_val: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
3244 assert(self.llvm_object == null);
3245 return self.getZigObject().?.getAnonDeclVAddr(self, decl_val, reloc_info);
3246}
3247
3248pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
3249 return self.getZigObject().?.getGlobalSymbol(self, name, lib_name);
47573250}
47583251
47593252pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
......@@ -4761,33 +3254,55 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
47613254}
47623255
47633256fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 {
4764 // TODO: header and load commands have to be part of the __TEXT segment
4765 const header_size = self.segments.items[self.header_segment_cmd_index.?].filesize;
3257 // Conservatively commit one page size as reserved space for the headers as we
3258 // expect it to grow and everything else be moved in flush anyhow.
3259 const header_size = self.getPageSize();
47663260 if (start < header_size)
47673261 return header_size;
47683262
47693263 const end = start + padToIdeal(size);
47703264
47713265 for (self.sections.items(.header)) |header| {
4772 const tight_size = header.size;
4773 const increased_size = padToIdeal(tight_size);
3266 if (header.isZerofill()) continue;
3267 const increased_size = padToIdeal(header.size);
47743268 const test_end = header.offset + increased_size;
47753269 if (end > header.offset and start < test_end) {
47763270 return test_end;
47773271 }
47783272 }
47793273
3274 for (self.segments.items) |seg| {
3275 const increased_size = padToIdeal(seg.filesize);
3276 const test_end = seg.fileoff +| increased_size;
3277 if (end > seg.fileoff and start < test_end) {
3278 return test_end;
3279 }
3280 }
3281
47803282 return null;
47813283}
47823284
47833285fn allocatedSize(self: *MachO, start: u64) u64 {
4784 if (start == 0)
4785 return 0;
3286 if (start == 0) return 0;
47863287 var min_pos: u64 = std.math.maxInt(u64);
47873288 for (self.sections.items(.header)) |header| {
47883289 if (header.offset <= start) continue;
47893290 if (header.offset < min_pos) min_pos = header.offset;
47903291 }
3292 for (self.segments.items) |seg| {
3293 if (seg.fileoff <= start) continue;
3294 if (seg.fileoff < min_pos) min_pos = seg.fileoff;
3295 }
3296 return min_pos - start;
3297}
3298
3299fn allocatedVirtualSize(self: *MachO, start: u64) u64 {
3300 if (start == 0) return 0;
3301 var min_pos: u64 = std.math.maxInt(u64);
3302 for (self.segments.items) |seg| {
3303 if (seg.vmaddr <= start) continue;
3304 if (seg.vmaddr < min_pos) min_pos = seg.vmaddr;
3305 }
47913306 return min_pos - start;
47923307}
47933308
......@@ -4799,452 +3314,534 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 {
47993314 return start;
48003315}
48013316
4802pub fn allocatedVirtualSize(self: *MachO, start: u64) u64 {
4803 if (start == 0)
4804 return 0;
4805 var min_pos: u64 = std.math.maxInt(u64);
4806 for (self.sections.items(.segment_index)) |seg_id| {
4807 const segment = self.segments.items[seg_id];
4808 if (segment.vmaddr <= start) continue;
4809 if (segment.vmaddr < min_pos) min_pos = segment.vmaddr;
4810 }
4811 return min_pos - start;
3317/// Like File.copyRangeAll but also ensures the source region is zeroed out after copy.
3318/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.
3319fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3320 const gpa = self.base.comp.gpa;
3321 const file = self.base.file.?;
3322 const amt = try file.copyRangeAll(old_offset, file, new_offset, size);
3323 if (amt != size) return error.InputOutput;
3324 const size_u = math.cast(usize, size) orelse return error.Overflow;
3325 const zeroes = try gpa.alloc(u8, size_u);
3326 defer gpa.free(zeroes);
3327 @memset(zeroes, 0);
3328 try file.pwriteAll(zeroes, old_offset);
48123329}
48133330
4814pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void {
4815 if (!is_hot_update_compatible) return;
3331const InitMetadataOptions = struct {
3332 symbol_count_hint: u64,
3333 program_code_size_hint: u64,
3334};
48163335
4817 const mach_task = try std.os.darwin.machTaskForPid(pid);
4818 log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task });
4819 self.hot_state.mach_task = mach_task;
3336// TODO: move to ZigObject
3337fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
3338 if (!self.base.isRelocatable()) {
3339 const base_vmaddr = blk: {
3340 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
3341 break :blk mem.alignBackward(u64, pagezero_size, self.getPageSize());
3342 };
48203343
4821 // TODO start exception handler in another thread
3344 {
3345 const filesize = options.program_code_size_hint;
3346 const off = self.findFreeSpace(filesize, self.getPageSize());
3347 self.zig_text_seg_index = try self.addSegment("__TEXT_ZIG", .{
3348 .fileoff = off,
3349 .filesize = filesize,
3350 .vmaddr = base_vmaddr + 0x8000000,
3351 .vmsize = filesize,
3352 .prot = macho.PROT.READ | macho.PROT.EXEC,
3353 });
3354 }
48223355
4823 // TODO enable ones we register for exceptions
4824 // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0);
4825}
3356 {
3357 const filesize = options.symbol_count_hint * @sizeOf(u64);
3358 const off = self.findFreeSpace(filesize, self.getPageSize());
3359 self.zig_got_seg_index = try self.addSegment("__GOT_ZIG", .{
3360 .fileoff = off,
3361 .filesize = filesize,
3362 .vmaddr = base_vmaddr + 0x4000000,
3363 .vmsize = filesize,
3364 .prot = macho.PROT.READ | macho.PROT.WRITE,
3365 });
3366 }
48263367
4827pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void {
4828 if (!is_hot_update_compatible) return;
3368 {
3369 const filesize: u64 = 1024;
3370 const off = self.findFreeSpace(filesize, self.getPageSize());
3371 self.zig_const_seg_index = try self.addSegment("__CONST_ZIG", .{
3372 .fileoff = off,
3373 .filesize = filesize,
3374 .vmaddr = base_vmaddr + 0xc000000,
3375 .vmsize = filesize,
3376 .prot = macho.PROT.READ | macho.PROT.WRITE,
3377 });
3378 }
48293379
4830 _ = pid;
3380 {
3381 const filesize: u64 = 1024;
3382 const off = self.findFreeSpace(filesize, self.getPageSize());
3383 self.zig_data_seg_index = try self.addSegment("__DATA_ZIG", .{
3384 .fileoff = off,
3385 .filesize = filesize,
3386 .vmaddr = base_vmaddr + 0x10000000,
3387 .vmsize = filesize,
3388 .prot = macho.PROT.READ | macho.PROT.WRITE,
3389 });
3390 }
48313391
4832 // TODO stop exception handler
3392 {
3393 const memsize: u64 = 1024;
3394 self.zig_bss_seg_index = try self.addSegment("__BSS_ZIG", .{
3395 .vmaddr = base_vmaddr + 0x14000000,
3396 .vmsize = memsize,
3397 .prot = macho.PROT.READ | macho.PROT.WRITE,
3398 });
3399 }
3400 } else {
3401 @panic("TODO initMetadata when relocatable");
3402 }
48333403
4834 // TODO see comment in ptraceAttach
4835 // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0);
3404 const appendSect = struct {
3405 fn appendSect(macho_file: *MachO, sect_id: u8, seg_id: u8) void {
3406 const sect = &macho_file.sections.items(.header)[sect_id];
3407 const seg = macho_file.segments.items[seg_id];
3408 sect.addr = seg.vmaddr;
3409 sect.offset = @intCast(seg.fileoff);
3410 sect.size = seg.vmsize;
3411 macho_file.sections.items(.segment_id)[sect_id] = seg_id;
3412 }
3413 }.appendSect;
48363414
4837 self.hot_state.mach_task = null;
4838}
3415 {
3416 self.zig_text_sect_index = try self.addSection("__TEXT_ZIG", "__text_zig", .{
3417 .alignment = switch (self.getTarget().cpu.arch) {
3418 .aarch64 => 2,
3419 .x86_64 => 0,
3420 else => unreachable,
3421 },
3422 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
3423 });
3424 appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?);
3425 }
48393426
4840pub fn addUndefined(self: *MachO, name: []const u8, flags: RelocFlags) !u32 {
4841 const gpa = self.base.comp.gpa;
3427 if (!self.base.isRelocatable()) {
3428 self.zig_got_sect_index = try self.addSection("__GOT_ZIG", "__got_zig", .{
3429 .alignment = 3,
3430 });
3431 appendSect(self, self.zig_got_sect_index.?, self.zig_got_seg_index.?);
3432 }
48423433
4843 const gop = try self.getOrPutGlobalPtr(name);
4844 const global_index = self.getGlobalIndex(name).?;
3434 {
3435 self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{});
3436 appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?);
3437 }
48453438
4846 if (gop.found_existing) {
4847 try self.updateRelocActions(global_index, flags);
4848 return global_index;
3439 {
3440 self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{});
3441 appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?);
48493442 }
48503443
4851 const sym_index = try self.allocateSymbol();
4852 const sym_loc = SymbolWithLoc{ .sym_index = sym_index };
4853 gop.value_ptr.* = sym_loc;
3444 {
3445 self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{
3446 .flags = macho.S_ZEROFILL,
3447 });
3448 appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?);
3449 }
3450}
48543451
4855 const sym = self.getSymbolPtr(sym_loc);
4856 sym.n_strx = try self.strtab.insert(gpa, name);
4857 sym.n_type = macho.N_EXT | macho.N_UNDF;
3452pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void {
3453 const sect = &self.sections.items(.header)[sect_index];
3454 const seg_id = self.sections.items(.segment_id)[sect_index];
3455 const seg = &self.segments.items[seg_id];
48583456
4859 try self.unresolved.putNoClobber(gpa, global_index, {});
4860 try self.updateRelocActions(global_index, flags);
3457 if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) {
3458 const existing_size = sect.size;
3459 sect.size = 0;
48613460
4862 return global_index;
4863}
3461 // Must move the entire section.
3462 const new_offset = self.findFreeSpace(needed_size, self.getPageSize());
48643463
4865fn updateRelocActions(self: *MachO, global_index: u32, flags: RelocFlags) !void {
4866 const gpa = self.base.comp.gpa;
4867 const act_gop = try self.actions.getOrPut(gpa, global_index);
4868 if (!act_gop.found_existing) {
4869 act_gop.value_ptr.* = .{};
4870 }
4871 act_gop.value_ptr.add_got = act_gop.value_ptr.add_got or flags.add_got;
4872 act_gop.value_ptr.add_stub = act_gop.value_ptr.add_stub or flags.add_stub;
4873}
3464 log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{
3465 sect.segName(),
3466 sect.sectName(),
3467 new_offset,
3468 new_offset + existing_size,
3469 });
48743470
4875pub fn makeStaticString(bytes: []const u8) [16]u8 {
4876 var buf = [_]u8{0} ** 16;
4877 @memcpy(buf[0..bytes.len], bytes);
4878 return buf;
4879}
3471 try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size);
48803472
4881pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 {
4882 for (self.segments.items, 0..) |seg, i| {
4883 if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
4884 } else return null;
4885}
3473 sect.offset = @intCast(new_offset);
3474 seg.fileoff = new_offset;
3475 }
48863476
4887pub fn getSegment(self: MachO, sect_id: u8) macho.segment_command_64 {
4888 const index = self.sections.items(.segment_index)[sect_id];
4889 return self.segments.items[index];
4890}
3477 sect.size = needed_size;
3478 if (!sect.isZerofill()) {
3479 seg.filesize = needed_size;
3480 }
3481
3482 const mem_capacity = self.allocatedVirtualSize(seg.vmaddr);
3483 if (needed_size > mem_capacity) {
3484 var err = try self.addErrorWithNotes(2);
3485 try err.addMsg(self, "fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{
3486 seg_id,
3487 seg.segName(),
3488 });
3489 try err.addNote(self, "TODO: emit relocations to memory locations in self-hosted backends", .{});
3490 try err.addNote(self, "as a workaround, try increasing pre-allocated virtual memory of each segment", .{});
3491 }
48913492
4892pub fn getSegmentPtr(self: *MachO, sect_id: u8) *macho.segment_command_64 {
4893 const index = self.sections.items(.segment_index)[sect_id];
4894 return &self.segments.items[index];
3493 seg.vmsize = needed_size;
48953494}
48963495
4897pub fn getLinkeditSegmentPtr(self: *MachO) *macho.segment_command_64 {
4898 const index = self.linkedit_segment_cmd_index.?;
4899 return &self.segments.items[index];
3496pub fn getTarget(self: MachO) std.Target {
3497 return self.base.comp.root_mod.resolved_target.result;
49003498}
49013499
4902pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 {
4903 // TODO investigate caching with a hashmap
4904 for (self.sections.items(.header), 0..) |header, i| {
4905 if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
4906 return @as(u8, @intCast(i));
4907 } else return null;
3500/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
3501/// Any change to the binary will effectively invalidate the kernel's cache
3502/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
3503/// linking we're modifying a binary in-place, this will end up with the kernel
3504/// killing it on every subsequent run. To circumvent it, we will copy the file
3505/// into a new inode, remove the original file, and rename the copy to match
3506/// the original file. This is super messy, but there doesn't seem any other
3507/// way to please the XNU.
3508pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void {
3509 if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
3510 try dir.copyFile(sub_path, dir, sub_path, .{});
3511 }
49083512}
49093513
4910pub fn getSectionIndexes(self: MachO, segment_index: u8) struct { start: u8, end: u8 } {
4911 var start: u8 = 0;
4912 const nsects = for (self.segments.items, 0..) |seg, i| {
4913 if (i == segment_index) break @as(u8, @intCast(seg.nsects));
4914 start += @as(u8, @intCast(seg.nsects));
4915 } else 0;
4916 return .{ .start = start, .end = start + nsects };
3514inline fn conformUuid(out: *[Md5.digest_length]u8) void {
3515 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
3516 out[6] = (out[6] & 0x0F) | (3 << 4);
3517 out[8] = (out[8] & 0x3F) | 0x80;
49173518}
49183519
4919pub fn symbolIsTemp(self: *MachO, sym_with_loc: SymbolWithLoc) bool {
4920 const sym = self.getSymbol(sym_with_loc);
4921 if (!sym.sect()) return false;
4922 if (sym.ext()) return false;
4923 const sym_name = self.getSymbolName(sym_with_loc);
4924 return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
3520pub inline fn getPageSize(self: MachO) u16 {
3521 return switch (self.getTarget().cpu.arch) {
3522 .aarch64 => 0x4000,
3523 .x86_64 => 0x1000,
3524 else => unreachable,
3525 };
49253526}
49263527
4927/// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
4928pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 {
4929 if (sym_with_loc.getFile()) |file| {
4930 const object = &self.objects.items[file];
4931 return &object.symtab[sym_with_loc.sym_index];
4932 } else {
4933 return &self.locals.items[sym_with_loc.sym_index];
4934 }
3528pub fn requiresCodeSig(self: MachO) bool {
3529 if (self.entitlements) |_| return true;
3530 // if (self.options.adhoc_codesign) |cs| return cs;
3531 return switch (self.getTarget().cpu.arch) {
3532 .aarch64 => true,
3533 else => false,
3534 };
49353535}
49363536
4937/// Returns symbol described by `sym_with_loc` descriptor.
4938pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 {
4939 if (sym_with_loc.getFile()) |file| {
4940 const object = &self.objects.items[file];
4941 return object.symtab[sym_with_loc.sym_index];
4942 } else {
4943 return self.locals.items[sym_with_loc.sym_index];
4944 }
3537inline fn requiresThunks(self: MachO) bool {
3538 return self.getTarget().cpu.arch == .aarch64;
49453539}
49463540
4947/// Returns name of the symbol described by `sym_with_loc` descriptor.
4948pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 {
4949 if (sym_with_loc.getFile()) |file| {
4950 const object = self.objects.items[file];
4951 return object.getSymbolName(sym_with_loc.sym_index);
4952 } else {
4953 const sym = self.locals.items[sym_with_loc.sym_index];
4954 return self.strtab.get(sym.n_strx).?;
4955 }
3541pub fn addSegment(self: *MachO, name: []const u8, opts: struct {
3542 vmaddr: u64 = 0,
3543 vmsize: u64 = 0,
3544 fileoff: u64 = 0,
3545 filesize: u64 = 0,
3546 prot: macho.vm_prot_t = macho.PROT.NONE,
3547 flags: u32 = 0,
3548}) error{OutOfMemory}!u8 {
3549 const gpa = self.base.comp.gpa;
3550 const index = @as(u8, @intCast(self.segments.items.len));
3551 try self.segments.append(gpa, .{
3552 .segname = makeStaticString(name),
3553 .vmaddr = opts.vmaddr,
3554 .vmsize = opts.vmsize,
3555 .fileoff = opts.fileoff,
3556 .filesize = opts.filesize,
3557 .maxprot = opts.prot,
3558 .initprot = opts.prot,
3559 .nsects = 0,
3560 .cmdsize = @sizeOf(macho.segment_command_64),
3561 });
3562 return index;
49563563}
49573564
4958const BoundarySymbolKind = enum {
4959 start,
4960 stop,
3565const AddSectionOpts = struct {
3566 alignment: u32 = 0,
3567 flags: u32 = macho.S_REGULAR,
3568 reserved1: u32 = 0,
3569 reserved2: u32 = 0,
49613570};
49623571
4963const SectionBoundarySymbol = struct {
4964 kind: BoundarySymbolKind,
3572pub fn addSection(
3573 self: *MachO,
49653574 segname: []const u8,
49663575 sectname: []const u8,
4967};
4968
4969pub fn getSectionBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SectionBoundarySymbol {
4970 const sym_name = self.getSymbolName(sym_with_loc);
4971 if (mem.startsWith(u8, sym_name, "section$")) {
4972 const trailing = sym_name["section$".len..];
4973 const kind: BoundarySymbolKind = kind: {
4974 if (mem.startsWith(u8, trailing, "start$")) break :kind .start;
4975 if (mem.startsWith(u8, trailing, "stop$")) break :kind .stop;
4976 return null;
4977 };
4978 const names = trailing[@tagName(kind).len + 1 ..];
4979 const sep_idx = mem.indexOf(u8, names, "$") orelse return null;
4980 const segname = names[0..sep_idx];
4981 const sectname = names[sep_idx + 1 ..];
4982 return .{ .kind = kind, .segname = segname, .sectname = sectname };
4983 }
4984 return null;
3576 opts: AddSectionOpts,
3577) !u8 {
3578 const gpa = self.base.comp.gpa;
3579 const index = @as(u8, @intCast(try self.sections.addOne(gpa)));
3580 self.sections.set(index, .{
3581 .segment_id = 0, // Segments will be created automatically later down the pipeline.
3582 .header = .{
3583 .sectname = makeStaticString(sectname),
3584 .segname = makeStaticString(segname),
3585 .@"align" = opts.alignment,
3586 .flags = opts.flags,
3587 .reserved1 = opts.reserved1,
3588 .reserved2 = opts.reserved2,
3589 },
3590 });
3591 return index;
49853592}
49863593
4987const SegmentBoundarySymbol = struct {
4988 kind: BoundarySymbolKind,
4989 segname: []const u8,
4990};
3594pub fn makeStaticString(bytes: []const u8) [16]u8 {
3595 var buf = [_]u8{0} ** 16;
3596 @memcpy(buf[0..bytes.len], bytes);
3597 return buf;
3598}
49913599
4992pub fn getSegmentBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SegmentBoundarySymbol {
4993 const sym_name = self.getSymbolName(sym_with_loc);
4994 if (mem.startsWith(u8, sym_name, "segment$")) {
4995 const trailing = sym_name["segment$".len..];
4996 const kind: BoundarySymbolKind = kind: {
4997 if (mem.startsWith(u8, trailing, "start$")) break :kind .start;
4998 if (mem.startsWith(u8, trailing, "stop$")) break :kind .stop;
4999 return null;
5000 };
5001 const segname = trailing[@tagName(kind).len + 1 ..];
5002 return .{ .kind = kind, .segname = segname };
5003 }
5004 return null;
3600pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 {
3601 for (self.segments.items, 0..) |seg, i| {
3602 if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
3603 } else return null;
50053604}
50063605
5007/// Returns pointer to the global entry for `name` if one exists.
5008pub fn getGlobalPtr(self: *MachO, name: []const u8) ?*SymbolWithLoc {
5009 const global_index = self.resolver.get(name) orelse return null;
5010 return &self.globals.items[global_index];
3606pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 {
3607 for (self.sections.items(.header), 0..) |header, i| {
3608 if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
3609 return @as(u8, @intCast(i));
3610 } else return null;
50113611}
50123612
5013/// Returns the global entry for `name` if one exists.
5014pub fn getGlobal(self: *const MachO, name: []const u8) ?SymbolWithLoc {
5015 const global_index = self.resolver.get(name) orelse return null;
5016 return self.globals.items[global_index];
3613pub fn getTlsAddress(self: MachO) u64 {
3614 for (self.sections.items(.header)) |header| switch (header.type()) {
3615 macho.S_THREAD_LOCAL_REGULAR,
3616 macho.S_THREAD_LOCAL_ZEROFILL,
3617 => return header.addr,
3618 else => {},
3619 };
3620 return 0;
50173621}
50183622
5019/// Returns the index of the global entry for `name` if one exists.
5020pub fn getGlobalIndex(self: *const MachO, name: []const u8) ?u32 {
5021 return self.resolver.get(name);
3623pub inline fn getTextSegment(self: *MachO) *macho.segment_command_64 {
3624 return &self.segments.items[self.text_seg_index.?];
50223625}
50233626
5024/// Returns global entry at `index`.
5025pub fn getGlobalByIndex(self: *const MachO, index: u32) SymbolWithLoc {
5026 assert(index < self.globals.items.len);
5027 return self.globals.items[index];
3627pub inline fn getLinkeditSegment(self: *MachO) *macho.segment_command_64 {
3628 return &self.segments.items[self.linkedit_seg_index.?];
50283629}
50293630
5030const GetOrPutGlobalPtrResult = struct {
5031 found_existing: bool,
5032 value_ptr: *SymbolWithLoc,
5033};
3631pub fn getFile(self: *MachO, index: File.Index) ?File {
3632 const tag = self.files.items(.tags)[index];
3633 return switch (tag) {
3634 .null => null,
3635 .zig_object => .{ .zig_object = &self.files.items(.data)[index].zig_object },
3636 .internal => .{ .internal = &self.files.items(.data)[index].internal },
3637 .object => .{ .object = &self.files.items(.data)[index].object },
3638 .dylib => .{ .dylib = &self.files.items(.data)[index].dylib },
3639 };
3640}
50343641
5035/// Used only for disambiguating local from global at relocation level.
5036/// TODO this must go away.
5037pub const global_symbol_bit: u32 = 0x80000000;
5038pub const global_symbol_mask: u32 = 0x7fffffff;
3642pub fn getZigObject(self: *MachO) ?*ZigObject {
3643 const index = self.zig_object orelse return null;
3644 return self.getFile(index).?.zig_object;
3645}
50393646
5040/// Return pointer to the global entry for `name` if one exists.
5041/// Puts a new global entry for `name` if one doesn't exist, and
5042/// returns a pointer to it.
5043pub fn getOrPutGlobalPtr(self: *MachO, name: []const u8) !GetOrPutGlobalPtrResult {
5044 if (self.getGlobalPtr(name)) |ptr| {
5045 return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr };
5046 }
5047 const gpa = self.base.comp.gpa;
5048 const global_index = try self.allocateGlobal();
5049 const global_name = try gpa.dupe(u8, name);
5050 _ = try self.resolver.put(gpa, global_name, global_index);
5051 const ptr = &self.globals.items[global_index];
5052 return GetOrPutGlobalPtrResult{ .found_existing = false, .value_ptr = ptr };
3647pub fn getInternalObject(self: *MachO) ?*InternalObject {
3648 const index = self.internal_object orelse return null;
3649 return self.getFile(index).?.internal;
50533650}
50543651
5055pub fn getAtom(self: *MachO, atom_index: Atom.Index) Atom {
5056 assert(atom_index < self.atoms.items.len);
5057 return self.atoms.items[atom_index];
3652pub fn addAtom(self: *MachO) error{OutOfMemory}!Atom.Index {
3653 const index = @as(Atom.Index, @intCast(self.atoms.items.len));
3654 const atom = try self.atoms.addOne(self.base.comp.gpa);
3655 atom.* = .{};
3656 return index;
50583657}
50593658
5060pub fn getAtomPtr(self: *MachO, atom_index: Atom.Index) *Atom {
5061 assert(atom_index < self.atoms.items.len);
5062 return &self.atoms.items[atom_index];
3659pub fn getAtom(self: *MachO, index: Atom.Index) ?*Atom {
3660 if (index == 0) return null;
3661 assert(index < self.atoms.items.len);
3662 return &self.atoms.items[index];
50633663}
50643664
5065/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor.
5066/// Returns null on failure.
5067pub fn getAtomIndexForSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) ?Atom.Index {
5068 assert(sym_with_loc.getFile() == null);
5069 return self.atom_by_index_table.get(sym_with_loc.sym_index);
3665pub fn addSymbol(self: *MachO) !Symbol.Index {
3666 const index = @as(Symbol.Index, @intCast(self.symbols.items.len));
3667 const symbol = try self.symbols.addOne(self.base.comp.gpa);
3668 symbol.* = .{};
3669 return index;
50703670}
50713671
5072pub fn getGotEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
5073 const index = self.got_table.lookup.get(sym_with_loc) orelse return null;
5074 const header = self.sections.items(.header)[self.got_section_index.?];
5075 return header.addr + @sizeOf(u64) * index;
3672pub fn getSymbol(self: *MachO, index: Symbol.Index) *Symbol {
3673 assert(index < self.symbols.items.len);
3674 return &self.symbols.items[index];
50763675}
50773676
5078pub fn getTlvPtrEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
5079 const index = self.tlv_ptr_table.lookup.get(sym_with_loc) orelse return null;
5080 const header = self.sections.items(.header)[self.tlv_ptr_section_index.?];
5081 return header.addr + @sizeOf(u64) * index;
3677pub fn addSymbolExtra(self: *MachO, extra: Symbol.Extra) !u32 {
3678 const fields = @typeInfo(Symbol.Extra).Struct.fields;
3679 try self.symbols_extra.ensureUnusedCapacity(self.base.comp.gpa, fields.len);
3680 return self.addSymbolExtraAssumeCapacity(extra);
50823681}
50833682
5084pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
5085 const target = self.base.comp.root_mod.resolved_target.result;
5086 const index = self.stub_table.lookup.get(sym_with_loc) orelse return null;
5087 const header = self.sections.items(.header)[self.stubs_section_index.?];
5088 return header.addr + stubs.stubSize(target.cpu.arch) * index;
3683pub fn addSymbolExtraAssumeCapacity(self: *MachO, extra: Symbol.Extra) u32 {
3684 const index = @as(u32, @intCast(self.symbols_extra.items.len));
3685 const fields = @typeInfo(Symbol.Extra).Struct.fields;
3686 inline for (fields) |field| {
3687 self.symbols_extra.appendAssumeCapacity(switch (field.type) {
3688 u32 => @field(extra, field.name),
3689 else => @compileError("bad field type"),
3690 });
3691 }
3692 return index;
50893693}
50903694
5091/// Returns symbol location corresponding to the set entrypoint if any.
5092/// Asserts output mode is executable.
5093pub fn getEntryPoint(self: MachO) ?SymbolWithLoc {
5094 const entry_name = self.entry_name orelse return null;
5095 const global = self.getGlobal(entry_name) orelse return null;
5096 return global;
3695pub fn getSymbolExtra(self: MachO, index: u32) ?Symbol.Extra {
3696 if (index == 0) return null;
3697 const fields = @typeInfo(Symbol.Extra).Struct.fields;
3698 var i: usize = index;
3699 var result: Symbol.Extra = undefined;
3700 inline for (fields) |field| {
3701 @field(result, field.name) = switch (field.type) {
3702 u32 => self.symbols_extra.items[i],
3703 else => @compileError("bad field type"),
3704 };
3705 i += 1;
3706 }
3707 return result;
50973708}
50983709
5099pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols {
5100 if (self.d_sym == null) return null;
5101 return &self.d_sym.?;
3710pub fn setSymbolExtra(self: *MachO, index: u32, extra: Symbol.Extra) void {
3711 assert(index > 0);
3712 const fields = @typeInfo(Symbol.Extra).Struct.fields;
3713 inline for (fields, 0..) |field, i| {
3714 self.symbols_extra.items[index + i] = switch (field.type) {
3715 u32 => @field(extra, field.name),
3716 else => @compileError("bad field type"),
3717 };
3718 }
51023719}
51033720
5104pub inline fn getPageSize(cpu_arch: std.Target.Cpu.Arch) u16 {
5105 return switch (cpu_arch) {
5106 .aarch64 => 0x4000,
5107 .x86_64 => 0x1000,
5108 else => unreachable,
3721const GetOrCreateGlobalResult = struct {
3722 found_existing: bool,
3723 index: Symbol.Index,
3724};
3725
3726pub fn getOrCreateGlobal(self: *MachO, off: u32) !GetOrCreateGlobalResult {
3727 const gpa = self.base.comp.gpa;
3728 const gop = try self.globals.getOrPut(gpa, off);
3729 if (!gop.found_existing) {
3730 const index = try self.addSymbol();
3731 const global = self.getSymbol(index);
3732 global.name = off;
3733 gop.value_ptr.* = index;
3734 }
3735 return .{
3736 .found_existing = gop.found_existing,
3737 .index = gop.value_ptr.*,
51093738 };
51103739}
51113740
5112pub fn requiresCodeSignature(m: *MachO) bool {
5113 if (m.entitlements) |_| return true;
5114 const comp = m.base.comp;
5115 const target = comp.root_mod.resolved_target.result;
5116 const cpu_arch = target.cpu.arch;
5117 const os_tag = target.os.tag;
5118 const abi = target.abi;
5119 if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) return true;
5120 return false;
3741pub fn getGlobalByName(self: *MachO, name: []const u8) ?Symbol.Index {
3742 const off = self.strings.getOffset(name) orelse return null;
3743 return self.globals.get(off);
51213744}
51223745
5123pub fn getSegmentPrecedence(segname: []const u8) u4 {
5124 if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0;
5125 if (mem.eql(u8, segname, "__TEXT")) return 0x1;
5126 if (mem.eql(u8, segname, "__DATA_CONST")) return 0x2;
5127 if (mem.eql(u8, segname, "__DATA")) return 0x3;
5128 if (mem.eql(u8, segname, "__LINKEDIT")) return 0x5;
5129 return 0x4;
3746pub fn addUnwindRecord(self: *MachO) !UnwindInfo.Record.Index {
3747 const index = @as(UnwindInfo.Record.Index, @intCast(self.unwind_records.items.len));
3748 const rec = try self.unwind_records.addOne(self.base.comp.gpa);
3749 rec.* = .{};
3750 return index;
3751}
3752
3753pub fn getUnwindRecord(self: *MachO, index: UnwindInfo.Record.Index) *UnwindInfo.Record {
3754 assert(index < self.unwind_records.items.len);
3755 return &self.unwind_records.items[index];
51303756}
51313757
5132pub fn getSegmentMemoryProtection(segname: []const u8) macho.vm_prot_t {
5133 if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE;
5134 if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC;
5135 if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ;
5136 return macho.PROT.READ | macho.PROT.WRITE;
3758pub fn addThunk(self: *MachO) !Thunk.Index {
3759 const index = @as(Thunk.Index, @intCast(self.thunks.items.len));
3760 const thunk = try self.thunks.addOne(self.base.comp.gpa);
3761 thunk.* = .{};
3762 return index;
51373763}
51383764
5139pub fn getSectionPrecedence(header: macho.section_64) u8 {
5140 const segment_precedence: u4 = getSegmentPrecedence(header.segName());
5141 const section_precedence: u4 = blk: {
5142 if (header.isCode()) {
5143 if (mem.eql(u8, "__text", header.sectName())) break :blk 0x0;
5144 if (header.type() == macho.S_SYMBOL_STUBS) break :blk 0x1;
5145 break :blk 0x2;
5146 }
5147 switch (header.type()) {
5148 macho.S_NON_LAZY_SYMBOL_POINTERS,
5149 macho.S_LAZY_SYMBOL_POINTERS,
5150 => break :blk 0x0,
5151 macho.S_MOD_INIT_FUNC_POINTERS => break :blk 0x1,
5152 macho.S_MOD_TERM_FUNC_POINTERS => break :blk 0x2,
5153 macho.S_ZEROFILL => break :blk 0xf,
5154 macho.S_THREAD_LOCAL_REGULAR => break :blk 0xd,
5155 macho.S_THREAD_LOCAL_ZEROFILL => break :blk 0xe,
5156 else => {
5157 if (mem.eql(u8, "__unwind_info", header.sectName())) break :blk 0xe;
5158 if (mem.eql(u8, "__eh_frame", header.sectName())) break :blk 0xf;
5159 break :blk 0x3;
5160 },
5161 }
5162 };
5163 return (@as(u8, @intCast(segment_precedence)) << 4) + section_precedence;
3765pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk {
3766 assert(index < self.thunks.items.len);
3767 return &self.thunks.items[index];
51643768}
51653769
5166pub const ParseErrorCtx = struct {
5167 arena_allocator: std.heap.ArenaAllocator,
5168 detected_dylib_id: struct {
5169 parent: u16,
5170 required_version: u32,
5171 found_version: u32,
5172 },
5173 detected_targets: std.ArrayList([]const u8),
3770pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 {
3771 if (mem.startsWith(u8, path, prefix)) return path[prefix.len..];
3772 return null;
3773}
51743774
5175 pub fn init(gpa: Allocator) ParseErrorCtx {
5176 return .{
5177 .arena_allocator = std.heap.ArenaAllocator.init(gpa),
5178 .detected_dylib_id = undefined,
5179 .detected_targets = std.ArrayList([]const u8).init(gpa),
5180 };
3775const ErrorWithNotes = struct {
3776 /// Allocated index in comp.link_errors array.
3777 index: usize,
3778
3779 /// Next available note slot.
3780 note_slot: usize = 0,
3781
3782 pub fn addMsg(
3783 err: ErrorWithNotes,
3784 macho_file: *MachO,
3785 comptime format: []const u8,
3786 args: anytype,
3787 ) error{OutOfMemory}!void {
3788 const comp = macho_file.base.comp;
3789 const gpa = comp.gpa;
3790 const err_msg = &comp.link_errors.items[err.index];
3791 err_msg.msg = try std.fmt.allocPrint(gpa, format, args);
3792 }
3793
3794 pub fn addNote(
3795 err: *ErrorWithNotes,
3796 macho_file: *MachO,
3797 comptime format: []const u8,
3798 args: anytype,
3799 ) error{OutOfMemory}!void {
3800 const comp = macho_file.base.comp;
3801 const gpa = comp.gpa;
3802 const err_msg = &comp.link_errors.items[err.index];
3803 assert(err.note_slot < err_msg.notes.len);
3804 err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) };
3805 err.note_slot += 1;
51813806 }
3807};
51823808
5183 pub fn deinit(ctx: *ParseErrorCtx) void {
5184 ctx.arena_allocator.deinit();
5185 ctx.detected_targets.deinit();
5186 }
3809pub fn addErrorWithNotes(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
3810 const comp = self.base.comp;
3811 const gpa = comp.gpa;
3812 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
3813 return self.addErrorWithNotesAssumeCapacity(note_count);
3814}
51873815
5188 pub fn arena(ctx: *ParseErrorCtx) Allocator {
5189 return ctx.arena_allocator.allocator();
5190 }
5191};
3816fn addErrorWithNotesAssumeCapacity(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
3817 const comp = self.base.comp;
3818 const gpa = comp.gpa;
3819 const index = comp.link_errors.items.len;
3820 const err = comp.link_errors.addOneAssumeCapacity();
3821 err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) };
3822 return .{ .index = index };
3823}
51923824
5193pub fn handleAndReportParseError(
3825pub fn reportParseError(
51943826 self: *MachO,
51953827 path: []const u8,
5196 err: ParseError,
5197 ctx: *const ParseErrorCtx,
3828 comptime format: []const u8,
3829 args: anytype,
51983830) error{OutOfMemory}!void {
5199 const target = self.base.comp.root_mod.resolved_target.result;
5200 const gpa = self.base.comp.gpa;
5201 const cpu_arch = target.cpu.arch;
5202 switch (err) {
5203 error.DylibAlreadyExists => {},
5204 error.IncompatibleDylibVersion => {
5205 const parent = &self.dylibs.items[ctx.detected_dylib_id.parent];
5206 try self.reportDependencyError(
5207 if (parent.id) |id| id.name else parent.path,
5208 path,
5209 "incompatible dylib version: expected at least '{}', but found '{}'",
5210 .{
5211 load_commands.appleVersionToSemanticVersion(ctx.detected_dylib_id.required_version),
5212 load_commands.appleVersionToSemanticVersion(ctx.detected_dylib_id.found_version),
5213 },
5214 );
5215 },
5216 error.UnknownFileType => try self.reportParseError(path, "unknown file type", .{}),
5217 error.InvalidTarget, error.InvalidTargetFatLibrary => {
5218 var targets_string = std.ArrayList(u8).init(gpa);
5219 defer targets_string.deinit();
5220
5221 if (ctx.detected_targets.items.len > 1) {
5222 try targets_string.writer().writeAll("(");
5223 for (ctx.detected_targets.items) |t| {
5224 try targets_string.writer().print("{s}, ", .{t});
5225 }
5226 try targets_string.resize(targets_string.items.len - 2);
5227 try targets_string.writer().writeAll(")");
5228 } else {
5229 try targets_string.writer().writeAll(ctx.detected_targets.items[0]);
5230 }
3831 var err = try self.addErrorWithNotes(1);
3832 try err.addMsg(self, format, args);
3833 try err.addNote(self, "while parsing {s}", .{path});
3834}
52313835
5232 switch (err) {
5233 error.InvalidTarget => try self.reportParseError(
5234 path,
5235 "invalid target: expected '{}', but found '{s}'",
5236 .{ Platform.fromTarget(target).fmtTarget(cpu_arch), targets_string.items },
5237 ),
5238 error.InvalidTargetFatLibrary => try self.reportParseError(
5239 path,
5240 "invalid architecture in universal library: expected '{s}', but found '{s}'",
5241 .{ @tagName(cpu_arch), targets_string.items },
5242 ),
5243 else => unreachable,
5244 }
5245 },
5246 else => |e| try self.reportParseError(path, "{s}: parsing object failed", .{@errorName(e)}),
5247 }
3836pub fn reportParseError2(
3837 self: *MachO,
3838 file_index: File.Index,
3839 comptime format: []const u8,
3840 args: anytype,
3841) error{OutOfMemory}!void {
3842 var err = try self.addErrorWithNotes(1);
3843 try err.addMsg(self, format, args);
3844 try err.addNote(self, "while parsing {}", .{self.getFile(file_index).?.fmtPath()});
52483845}
52493846
52503847fn reportMissingLibraryError(
......@@ -5253,523 +3850,522 @@ fn reportMissingLibraryError(
52533850 comptime format: []const u8,
52543851 args: anytype,
52553852) error{OutOfMemory}!void {
5256 const comp = self.base.comp;
5257 const gpa = comp.gpa;
5258 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
5259 const notes = try gpa.alloc(File.ErrorMsg, checked_paths.len);
5260 errdefer gpa.free(notes);
5261 for (checked_paths, notes) |path, *note| {
5262 note.* = .{ .msg = try std.fmt.allocPrint(gpa, "tried {s}", .{path}) };
5263 }
5264 comp.link_errors.appendAssumeCapacity(.{
5265 .msg = try std.fmt.allocPrint(gpa, format, args),
5266 .notes = notes,
5267 });
3853 var err = try self.addErrorWithNotes(checked_paths.len);
3854 try err.addMsg(self, format, args);
3855 for (checked_paths) |path| {
3856 try err.addNote(self, "tried {s}", .{path});
3857 }
52683858}
52693859
52703860fn reportDependencyError(
52713861 self: *MachO,
5272 parent: []const u8,
3862 parent: File.Index,
52733863 path: ?[]const u8,
52743864 comptime format: []const u8,
52753865 args: anytype,
52763866) error{OutOfMemory}!void {
5277 const comp = self.base.comp;
5278 const gpa = comp.gpa;
5279 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
5280 var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2);
5281 defer notes.deinit();
3867 var err = try self.addErrorWithNotes(2);
3868 try err.addMsg(self, format, args);
52823869 if (path) |p| {
5283 notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "while parsing {s}", .{p}) });
3870 try err.addNote(self, "while parsing {s}", .{p});
52843871 }
5285 notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "a dependency of {s}", .{parent}) });
5286 comp.link_errors.appendAssumeCapacity(.{
5287 .msg = try std.fmt.allocPrint(gpa, format, args),
5288 .notes = try notes.toOwnedSlice(),
5289 });
3872 try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()});
52903873}
52913874
5292pub fn reportParseError(
5293 self: *MachO,
5294 path: []const u8,
5295 comptime format: []const u8,
5296 args: anytype,
5297) error{OutOfMemory}!void {
5298 const comp = self.base.comp;
5299 const gpa = comp.gpa;
5300 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
5301 var notes = try gpa.alloc(File.ErrorMsg, 1);
5302 errdefer gpa.free(notes);
5303 notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "while parsing {s}", .{path}) };
5304 comp.link_errors.appendAssumeCapacity(.{
5305 .msg = try std.fmt.allocPrint(gpa, format, args),
5306 .notes = notes,
5307 });
3875pub fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void {
3876 var err = try self.addErrorWithNotes(1);
3877 try err.addMsg(self, format, args);
3878 try err.addNote(self, "please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{});
53083879}
53093880
5310pub fn reportUnresolvedBoundarySymbol(
5311 self: *MachO,
5312 sym_name: []const u8,
5313 comptime format: []const u8,
5314 args: anytype,
5315) error{OutOfMemory}!void {
5316 const comp = self.base.comp;
5317 const gpa = comp.gpa;
5318 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
5319 var notes = try gpa.alloc(File.ErrorMsg, 1);
5320 errdefer gpa.free(notes);
5321 notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "while resolving {s}", .{sym_name}) };
5322 comp.link_errors.appendAssumeCapacity(.{
5323 .msg = try std.fmt.allocPrint(gpa, format, args),
5324 .notes = notes,
5325 });
5326}
3881fn reportDuplicates(self: *MachO, dupes: anytype) error{ HasDuplicates, OutOfMemory }!void {
3882 const tracy = trace(@src());
3883 defer tracy.end();
53273884
5328pub fn reportUndefined(self: *MachO) error{OutOfMemory}!void {
5329 const comp = self.base.comp;
5330 const gpa = comp.gpa;
5331 const count = self.unresolved.count();
5332 try comp.link_errors.ensureUnusedCapacity(gpa, count);
3885 const max_notes = 3;
53333886
5334 for (self.unresolved.keys()) |global_index| {
5335 const global = self.globals.items[global_index];
5336 const sym_name = self.getSymbolName(global);
3887 var has_dupes = false;
3888 var it = dupes.iterator();
3889 while (it.next()) |entry| {
3890 const sym = self.getSymbol(entry.key_ptr.*);
3891 const notes = entry.value_ptr.*;
3892 const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
53373893
5338 var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 1);
5339 defer notes.deinit();
3894 var err = try self.addErrorWithNotes(nnotes + 1);
3895 try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.getName(self)});
3896 try err.addNote(self, "defined by {}", .{sym.getFile(self).?.fmtPath()});
53403897
5341 if (global.getFile()) |file| {
5342 const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{
5343 self.objects.items[file].name,
5344 });
5345 notes.appendAssumeCapacity(.{ .msg = note });
3898 var inote: usize = 0;
3899 while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
3900 const file = self.getFile(notes.items[inote]).?;
3901 try err.addNote(self, "defined by {}", .{file.fmtPath()});
53463902 }
53473903
5348 var err_msg = File.ErrorMsg{
5349 .msg = try std.fmt.allocPrint(gpa, "undefined reference to symbol {s}", .{sym_name}),
5350 };
5351 err_msg.notes = try notes.toOwnedSlice();
3904 if (notes.items.len > max_notes) {
3905 const remaining = notes.items.len - max_notes;
3906 try err.addNote(self, "defined {d} more times", .{remaining});
3907 }
53523908
5353 comp.link_errors.appendAssumeCapacity(err_msg);
3909 has_dupes = true;
53543910 }
3911
3912 if (has_dupes) return error.HasDuplicates;
53553913}
53563914
5357fn reportSymbolCollision(
5358 self: *MachO,
5359 first: SymbolWithLoc,
5360 other: SymbolWithLoc,
5361) error{OutOfMemory}!void {
5362 const comp = self.base.comp;
5363 const gpa = comp.gpa;
5364 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
3915pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols {
3916 if (self.d_sym) |*ds| {
3917 return ds;
3918 } else return null;
3919}
53653920
5366 var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2);
5367 defer notes.deinit();
3921pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void {
3922 if (!is_hot_update_compatible) return;
53683923
5369 if (first.getFile()) |file| {
5370 const note = try std.fmt.allocPrint(gpa, "first definition in {s}", .{
5371 self.objects.items[file].name,
5372 });
5373 notes.appendAssumeCapacity(.{ .msg = note });
5374 }
5375 if (other.getFile()) |file| {
5376 const note = try std.fmt.allocPrint(gpa, "next definition in {s}", .{
5377 self.objects.items[file].name,
5378 });
5379 notes.appendAssumeCapacity(.{ .msg = note });
5380 }
3924 const mach_task = try std.os.darwin.machTaskForPid(pid);
3925 log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task });
3926 self.hot_state.mach_task = mach_task;
53813927
5382 var err_msg = File.ErrorMsg{ .msg = try std.fmt.allocPrint(gpa, "symbol {s} defined multiple times", .{
5383 self.getSymbolName(first),
5384 }) };
5385 err_msg.notes = try notes.toOwnedSlice();
3928 // TODO start exception handler in another thread
53863929
5387 comp.link_errors.appendAssumeCapacity(err_msg);
3930 // TODO enable ones we register for exceptions
3931 // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0);
53883932}
53893933
5390fn reportUnhandledSymbolType(self: *MachO, sym_with_loc: SymbolWithLoc) error{OutOfMemory}!void {
5391 const comp = self.base.comp;
5392 const gpa = comp.gpa;
5393 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
5394
5395 const notes = try gpa.alloc(File.ErrorMsg, 1);
5396 errdefer gpa.free(notes);
3934pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void {
3935 if (!is_hot_update_compatible) return;
53973936
5398 const file = sym_with_loc.getFile().?;
5399 notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "defined in {s}", .{self.objects.items[file].name}) };
3937 _ = pid;
54003938
5401 const sym = self.getSymbol(sym_with_loc);
5402 const sym_type = if (sym.stab())
5403 "stab"
5404 else if (sym.indr())
5405 "indirect"
5406 else if (sym.abs())
5407 "absolute"
5408 else
5409 unreachable;
5410
5411 comp.link_errors.appendAssumeCapacity(.{
5412 .msg = try std.fmt.allocPrint(gpa, "unhandled symbol type: '{s}' has type {s}", .{
5413 self.getSymbolName(sym_with_loc),
5414 sym_type,
5415 }),
5416 .notes = notes,
5417 });
5418}
3939 // TODO stop exception handler
54193940
5420/// Binary search
5421pub fn bsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize {
5422 if (!@hasDecl(@TypeOf(predicate), "predicate"))
5423 @compileError("Predicate is required to define fn predicate(@This(), T) bool");
3941 // TODO see comment in ptraceAttach
3942 // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0);
54243943
5425 var min: usize = 0;
5426 var max: usize = haystack.len;
5427 while (min < max) {
5428 const index = (min + max) / 2;
5429 const curr = haystack[index];
5430 if (predicate.predicate(curr)) {
5431 min = index + 1;
5432 } else {
5433 max = index;
5434 }
5435 }
5436 return min;
3944 self.hot_state.mach_task = null;
54373945}
54383946
5439/// Linear search
5440pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize {
5441 if (!@hasDecl(@TypeOf(predicate), "predicate"))
5442 @compileError("Predicate is required to define fn predicate(@This(), T) bool");
5443
5444 var i: usize = 0;
5445 while (i < haystack.len) : (i += 1) {
5446 if (predicate.predicate(haystack[i])) break;
5447 }
5448 return i;
3947pub fn dumpState(self: *MachO) std.fmt.Formatter(fmtDumpState) {
3948 return .{ .data = self };
54493949}
54503950
5451pub fn logSegments(self: *MachO) void {
5452 log.debug("segments:", .{});
5453 for (self.segments.items, 0..) |segment, i| {
5454 log.debug(" segment({d}): {s} @{x} ({x}), sizeof({x})", .{
5455 i,
5456 segment.segName(),
5457 segment.fileoff,
5458 segment.vmaddr,
5459 segment.vmsize,
3951fn fmtDumpState(
3952 self: *MachO,
3953 comptime unused_fmt_string: []const u8,
3954 options: std.fmt.FormatOptions,
3955 writer: anytype,
3956) !void {
3957 _ = options;
3958 _ = unused_fmt_string;
3959 if (self.getZigObject()) |zo| {
3960 try writer.print("zig_object({d}) : {s}\n", .{ zo.index, zo.path });
3961 try writer.print("{}{}\n", .{
3962 zo.fmtAtoms(self),
3963 zo.fmtSymtab(self),
54603964 });
54613965 }
5462}
5463
5464pub fn logSections(self: *MachO) void {
5465 log.debug("sections:", .{});
5466 for (self.sections.items(.header), 0..) |header, i| {
5467 log.debug(" sect({d}): {s},{s} @{x} ({x}), sizeof({x})", .{
5468 i + 1,
5469 header.segName(),
5470 header.sectName(),
5471 header.offset,
5472 header.addr,
5473 header.size,
3966 for (self.objects.items) |index| {
3967 const object = self.getFile(index).?.object;
3968 try writer.print("object({d}) : {} : has_debug({})", .{
3969 index,
3970 object.fmtPath(),
3971 object.hasDebugInfo(),
3972 });
3973 if (!object.alive) try writer.writeAll(" : ([*])");
3974 try writer.writeByte('\n');
3975 try writer.print("{}{}{}{}{}\n", .{
3976 object.fmtAtoms(self),
3977 object.fmtCies(self),
3978 object.fmtFdes(self),
3979 object.fmtUnwindRecords(self),
3980 object.fmtSymtab(self),
54743981 });
54753982 }
5476}
5477
5478fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 {
5479 if (sym.sect()) {
5480 buf[0] = 's';
5481 }
5482 if (sym.ext()) {
5483 if (sym.weakDef() or sym.pext()) {
5484 buf[1] = 'w';
5485 } else {
5486 buf[1] = 'e';
5487 }
3983 for (self.dylibs.items) |index| {
3984 const dylib = self.getFile(index).?.dylib;
3985 try writer.print("dylib({d}) : {s} : needed({}) : weak({})", .{
3986 index,
3987 dylib.path,
3988 dylib.needed,
3989 dylib.weak,
3990 });
3991 if (!dylib.isAlive(self)) try writer.writeAll(" : ([*])");
3992 try writer.writeByte('\n');
3993 try writer.print("{}\n", .{dylib.fmtSymtab(self)});
54883994 }
5489 if (sym.tentative()) {
5490 buf[2] = 't';
3995 if (self.getInternalObject()) |internal| {
3996 try writer.print("internal({d}) : internal\n", .{internal.index});
3997 try writer.print("{}{}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) });
54913998 }
5492 if (sym.undf()) {
5493 buf[3] = 'u';
3999 try writer.writeAll("thunks\n");
4000 for (self.thunks.items, 0..) |thunk, index| {
4001 try writer.print("thunk({d}) : {}\n", .{ index, thunk.fmt(self) });
54944002 }
5495 return buf[0..];
4003 try writer.print("stubs\n{}\n", .{self.stubs.fmt(self)});
4004 try writer.print("objc_stubs\n{}\n", .{self.objc_stubs.fmt(self)});
4005 try writer.print("got\n{}\n", .{self.got.fmt(self)});
4006 try writer.print("zig_got\n{}\n", .{self.zig_got.fmt(self)});
4007 try writer.print("tlv_ptr\n{}\n", .{self.tlv_ptr.fmt(self)});
4008 try writer.writeByte('\n');
4009 try writer.print("sections\n{}\n", .{self.fmtSections()});
4010 try writer.print("segments\n{}\n", .{self.fmtSegments()});
54964011}
54974012
5498pub fn logSymtab(self: *MachO) void {
5499 var buf: [4]u8 = undefined;
5500
5501 const scoped_log = std.log.scoped(.symtab);
4013fn fmtSections(self: *MachO) std.fmt.Formatter(formatSections) {
4014 return .{ .data = self };
4015}
55024016
5503 scoped_log.debug("locals:", .{});
5504 for (self.objects.items, 0..) |object, id| {
5505 scoped_log.debug(" object({d}): {s}", .{ id, object.name });
5506 if (object.in_symtab == null) continue;
5507 for (object.symtab, 0..) |sym, sym_id| {
5508 @memset(&buf, '_');
5509 scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{
5510 sym_id,
5511 object.getSymbolName(@as(u32, @intCast(sym_id))),
5512 sym.n_value,
5513 sym.n_sect,
5514 logSymAttributes(sym, &buf),
5515 });
5516 }
5517 }
5518 scoped_log.debug(" object(-1)", .{});
5519 for (self.locals.items, 0..) |sym, sym_id| {
5520 if (sym.undf()) continue;
5521 scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{
5522 sym_id,
5523 self.strtab.get(sym.n_strx).?,
5524 sym.n_value,
5525 sym.n_sect,
5526 logSymAttributes(sym, &buf),
4017fn formatSections(
4018 self: *MachO,
4019 comptime unused_fmt_string: []const u8,
4020 options: std.fmt.FormatOptions,
4021 writer: anytype,
4022) !void {
4023 _ = options;
4024 _ = unused_fmt_string;
4025 const slice = self.sections.slice();
4026 for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| {
4027 try writer.print("sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x})\n", .{
4028 i, seg_id, header.segName(), header.sectName(), header.offset, header.addr,
4029 header.@"align", header.size,
55274030 });
55284031 }
4032}
55294033
5530 scoped_log.debug("exports:", .{});
5531 for (self.globals.items, 0..) |global, i| {
5532 const sym = self.getSymbol(global);
5533 if (sym.undf()) continue;
5534 if (sym.n_desc == N_DEAD) continue;
5535 if (sym.n_desc == N_BOUNDARY) continue;
5536 scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s} (def in object({?}))", .{
5537 i,
5538 self.getSymbolName(global),
5539 sym.n_value,
5540 sym.n_sect,
5541 logSymAttributes(sym, &buf),
5542 global.file,
5543 });
5544 }
4034fn fmtSegments(self: *MachO) std.fmt.Formatter(formatSegments) {
4035 return .{ .data = self };
4036}
55454037
5546 scoped_log.debug("imports:", .{});
5547 for (self.globals.items, 0..) |global, i| {
5548 const sym = self.getSymbol(global);
5549 if (!sym.undf()) continue;
5550 if (sym.n_desc == N_DEAD) continue;
5551 if (sym.n_desc == N_BOUNDARY) continue;
5552 const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER);
5553 scoped_log.debug(" %{d}: {s} @{x} in ord({d}), {s}", .{
5554 i,
5555 self.getSymbolName(global),
5556 sym.n_value,
5557 ord,
5558 logSymAttributes(sym, &buf),
4038fn formatSegments(
4039 self: *MachO,
4040 comptime unused_fmt_string: []const u8,
4041 options: std.fmt.FormatOptions,
4042 writer: anytype,
4043) !void {
4044 _ = options;
4045 _ = unused_fmt_string;
4046 for (self.segments.items, 0..) |seg, i| {
4047 try writer.print("seg({d}) : {s} : @{x}-{x} ({x}-{x})\n", .{
4048 i, seg.segName(), seg.vmaddr, seg.vmaddr + seg.vmsize,
4049 seg.fileoff, seg.fileoff + seg.filesize,
55594050 });
55604051 }
5561
5562 scoped_log.debug("GOT entries:", .{});
5563 scoped_log.debug("{}", .{self.got_table});
5564
5565 scoped_log.debug("TLV pointers:", .{});
5566 scoped_log.debug("{}", .{self.tlv_ptr_table});
5567
5568 scoped_log.debug("stubs entries:", .{});
5569 scoped_log.debug("{}", .{self.stub_table});
5570
5571 scoped_log.debug("thunks:", .{});
5572 for (self.thunks.items, 0..) |thunk, i| {
5573 scoped_log.debug(" thunk({d})", .{i});
5574 const slice = thunk.targets.slice();
5575 for (slice.items(.tag), slice.items(.target), 0..) |tag, target, j| {
5576 const atom_index = @as(u32, @intCast(thunk.getStartAtomIndex() + j));
5577 const atom = self.getAtom(atom_index);
5578 const atom_sym = self.getSymbol(atom.getSymbolWithLoc());
5579 const target_addr = switch (tag) {
5580 .stub => self.getStubsEntryAddress(target).?,
5581 .atom => self.getSymbol(target).n_value,
5582 };
5583 scoped_log.debug(" {d}@{x} => {s}({s}@{x})", .{
5584 j,
5585 atom_sym.n_value,
5586 @tagName(tag),
5587 self.getSymbolName(target),
5588 target_addr,
5589 });
5590 }
5591 }
55924052}
55934053
5594pub fn logAtoms(self: *MachO) void {
5595 log.debug("atoms:", .{});
5596 const slice = self.sections.slice();
5597 for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
5598 var atom_index = first_atom_index orelse continue;
5599 const header = slice.items(.header)[sect_id];
5600
5601 log.debug("{s},{s}", .{ header.segName(), header.sectName() });
5602
5603 while (true) {
5604 const atom = self.getAtom(atom_index);
5605 self.logAtom(atom_index, log);
5606
5607 if (atom.next_index) |next_index| {
5608 atom_index = next_index;
5609 } else break;
5610 }
5611 }
4054pub fn fmtSectType(tt: u8) std.fmt.Formatter(formatSectType) {
4055 return .{ .data = tt };
56124056}
56134057
5614pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void {
5615 if (!build_options.enable_logging) return;
5616
5617 const atom = self.getAtom(atom_index);
5618 const sym = self.getSymbol(atom.getSymbolWithLoc());
5619 const sym_name = self.getSymbolName(atom.getSymbolWithLoc());
5620 logger.debug(" ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{
5621 atom_index,
5622 atom.sym_index,
5623 sym_name,
5624 sym.n_value,
5625 atom.size,
5626 atom.alignment,
5627 atom.getFile(),
5628 sym.n_sect,
5629 });
5630
5631 if (atom.getFile() != null) {
5632 var it = Atom.getInnerSymbolsIterator(self, atom_index);
5633 while (it.next()) |sym_loc| {
5634 const inner = self.getSymbol(sym_loc);
5635 const inner_name = self.getSymbolName(sym_loc);
5636 const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index);
5637
5638 logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{
5639 sym_loc.sym_index,
5640 inner_name,
5641 inner.n_value,
5642 offset,
5643 });
5644 }
5645
5646 if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
5647 const alias = self.getSymbol(sym_loc);
5648 const alias_name = self.getSymbolName(sym_loc);
5649
5650 logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{
5651 sym_loc.sym_index,
5652 alias_name,
5653 alias.n_value,
5654 0,
5655 });
5656 }
5657 }
4058fn formatSectType(
4059 tt: u8,
4060 comptime unused_fmt_string: []const u8,
4061 options: std.fmt.FormatOptions,
4062 writer: anytype,
4063) !void {
4064 _ = options;
4065 _ = unused_fmt_string;
4066 const name = switch (tt) {
4067 macho.S_REGULAR => "REGULAR",
4068 macho.S_ZEROFILL => "ZEROFILL",
4069 macho.S_CSTRING_LITERALS => "CSTRING_LITERALS",
4070 macho.S_4BYTE_LITERALS => "4BYTE_LITERALS",
4071 macho.S_8BYTE_LITERALS => "8BYTE_LITERALS",
4072 macho.S_16BYTE_LITERALS => "16BYTE_LITERALS",
4073 macho.S_LITERAL_POINTERS => "LITERAL_POINTERS",
4074 macho.S_NON_LAZY_SYMBOL_POINTERS => "NON_LAZY_SYMBOL_POINTERS",
4075 macho.S_LAZY_SYMBOL_POINTERS => "LAZY_SYMBOL_POINTERS",
4076 macho.S_SYMBOL_STUBS => "SYMBOL_STUBS",
4077 macho.S_MOD_INIT_FUNC_POINTERS => "MOD_INIT_FUNC_POINTERS",
4078 macho.S_MOD_TERM_FUNC_POINTERS => "MOD_TERM_FUNC_POINTERS",
4079 macho.S_COALESCED => "COALESCED",
4080 macho.S_GB_ZEROFILL => "GB_ZEROFILL",
4081 macho.S_INTERPOSING => "INTERPOSING",
4082 macho.S_DTRACE_DOF => "DTRACE_DOF",
4083 macho.S_THREAD_LOCAL_REGULAR => "THREAD_LOCAL_REGULAR",
4084 macho.S_THREAD_LOCAL_ZEROFILL => "THREAD_LOCAL_ZEROFILL",
4085 macho.S_THREAD_LOCAL_VARIABLES => "THREAD_LOCAL_VARIABLES",
4086 macho.S_THREAD_LOCAL_VARIABLE_POINTERS => "THREAD_LOCAL_VARIABLE_POINTERS",
4087 macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => "THREAD_LOCAL_INIT_FUNCTION_POINTERS",
4088 macho.S_INIT_FUNC_OFFSETS => "INIT_FUNC_OFFSETS",
4089 else => |x| return writer.print("UNKNOWN({x})", .{x}),
4090 };
4091 try writer.print("{s}", .{name});
56584092}
56594093
4094const is_hot_update_compatible = switch (builtin.target.os.tag) {
4095 .macos => true,
4096 else => false,
4097};
4098
56604099const default_entry_symbol_name = "_main";
56614100
5662pub const base_tag: File.Tag = File.Tag.macho;
4101pub const base_tag: link.File.Tag = link.File.Tag.macho;
56634102pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
56644103pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2)));
56654104
5666/// Mode of operation of the linker.
5667pub const Mode = enum {
5668 /// Incremental mode will preallocate segments/sections and is compatible with
5669 /// watch and HCS modes of operation.
5670 incremental,
5671 /// Zld mode will link relocatables in a traditional, one-shot
5672 /// fashion (default for LLVM backend). It acts as a drop-in replacement for
5673 /// LLD.
5674 zld,
5675};
5676
5677pub const Section = struct {
4105const Section = struct {
56784106 header: macho.section_64,
5679 segment_index: u8,
5680 first_atom_index: ?Atom.Index = null,
5681 last_atom_index: ?Atom.Index = null,
5682
5683 /// A list of atoms that have surplus capacity. This list can have false
5684 /// positives, as functions grow and shrink over time, only sometimes being added
5685 /// or removed from the freelist.
5686 ///
5687 /// An atom has surplus capacity when its overcapacity value is greater than
5688 /// padToIdeal(minimum_atom_size). That is, when it has so
5689 /// much extra capacity, that we could fit a small new symbol in it, itself with
5690 /// ideal_capacity or more.
5691 ///
5692 /// Ideal capacity is defined by size + (size / ideal_factor).
5693 ///
5694 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
5695 /// overcapacity can be negative. A simple way to have negative overcapacity is to
5696 /// allocate a fresh atom, which will have ideal capacity, and then grow it
5697 /// by 1 byte. It will then have -1 overcapacity.
4107 segment_id: u8,
4108 atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
56984109 free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
4110 last_atom_index: Atom.Index = 0,
56994111};
57004112
5701const is_hot_update_compatible = switch (builtin.target.os.tag) {
5702 .macos => true,
5703 else => false,
4113const HotUpdateState = struct {
4114 mach_task: ?std.os.darwin.MachTask = null,
4115};
4116
4117pub const DynamicRelocs = struct {
4118 rebase_relocs: u32 = 0,
4119 bind_relocs: u32 = 0,
4120 weak_bind_relocs: u32 = 0,
57044121};
57054122
5706const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
4123pub const SymtabCtx = struct {
4124 ilocal: u32 = 0,
4125 istab: u32 = 0,
4126 iexport: u32 = 0,
4127 iimport: u32 = 0,
4128 nlocals: u32 = 0,
4129 nstabs: u32 = 0,
4130 nexports: u32 = 0,
4131 nimports: u32 = 0,
4132 strsize: u32 = 0,
4133};
57074134
5708const LazySymbolMetadata = struct {
5709 const State = enum { unused, pending_flush, flushed };
5710 text_atom: Atom.Index = undefined,
5711 data_const_atom: Atom.Index = undefined,
5712 text_state: State = .unused,
5713 data_const_state: State = .unused,
4135pub const null_sym = macho.nlist_64{
4136 .n_strx = 0,
4137 .n_type = 0,
4138 .n_sect = 0,
4139 .n_desc = 0,
4140 .n_value = 0,
57144141};
57154142
5716const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index);
4143pub const Platform = struct {
4144 os_tag: std.Target.Os.Tag,
4145 abi: std.Target.Abi,
4146 version: std.SemanticVersion,
4147
4148 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
4149 /// the extracted minimum platform version.
4150 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
4151 switch (lc.cmd()) {
4152 .BUILD_VERSION => {
4153 const cmd = lc.cast(macho.build_version_command).?;
4154 return .{
4155 .os_tag = switch (cmd.platform) {
4156 .MACOS => .macos,
4157 .IOS, .IOSSIMULATOR => .ios,
4158 .TVOS, .TVOSSIMULATOR => .tvos,
4159 .WATCHOS, .WATCHOSSIMULATOR => .watchos,
4160 else => @panic("TODO"),
4161 },
4162 .abi = switch (cmd.platform) {
4163 .IOSSIMULATOR,
4164 .TVOSSIMULATOR,
4165 .WATCHOSSIMULATOR,
4166 => .simulator,
4167 else => .none,
4168 },
4169 .version = appleVersionToSemanticVersion(cmd.minos),
4170 };
4171 },
4172 .VERSION_MIN_MACOSX,
4173 .VERSION_MIN_IPHONEOS,
4174 .VERSION_MIN_TVOS,
4175 .VERSION_MIN_WATCHOS,
4176 => {
4177 const cmd = lc.cast(macho.version_min_command).?;
4178 return .{
4179 .os_tag = switch (lc.cmd()) {
4180 .VERSION_MIN_MACOSX => .macos,
4181 .VERSION_MIN_IPHONEOS => .ios,
4182 .VERSION_MIN_TVOS => .tvos,
4183 .VERSION_MIN_WATCHOS => .watchos,
4184 else => unreachable,
4185 },
4186 .abi = .none,
4187 .version = appleVersionToSemanticVersion(cmd.version),
4188 };
4189 },
4190 else => unreachable,
4191 }
4192 }
57174193
5718const DeclMetadata = struct {
5719 atom: Atom.Index,
5720 section: u8,
5721 /// A list of all exports aliases of this Decl.
5722 /// TODO do we actually need this at all?
5723 exports: std.ArrayListUnmanaged(u32) = .{},
4194 pub fn fromTarget(target: std.Target) Platform {
4195 return .{
4196 .os_tag = target.os.tag,
4197 .abi = target.abi,
4198 .version = target.os.version_range.semver.min,
4199 };
4200 }
4201
4202 pub fn toAppleVersion(plat: Platform) u32 {
4203 return semanticVersionToAppleVersion(plat.version);
4204 }
4205
4206 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
4207 return switch (plat.os_tag) {
4208 .macos => .MACOS,
4209 .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
4210 .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
4211 .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
4212 else => unreachable,
4213 };
4214 }
57244215
5725 fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 {
5726 for (m.exports.items) |exp| {
5727 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp;
4216 pub fn isBuildVersionCompatible(plat: Platform) bool {
4217 inline for (supported_platforms) |sup_plat| {
4218 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4219 return sup_plat[2] <= plat.toAppleVersion();
4220 }
57284221 }
5729 return null;
4222 return false;
57304223 }
57314224
5732 fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 {
5733 for (m.exports.items) |*exp| {
5734 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp;
4225 pub fn isVersionMinCompatible(plat: Platform) bool {
4226 inline for (supported_platforms) |sup_plat| {
4227 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4228 return sup_plat[3] <= plat.toAppleVersion();
4229 }
57354230 }
5736 return null;
4231 return false;
57374232 }
5738};
57394233
5740const DeclTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
5741const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
5742const BindingTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Binding));
5743const UnnamedConstTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Atom.Index));
5744const RebaseTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32));
5745const RelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation));
5746const ActionTable = std.AutoHashMapUnmanaged(u32, RelocFlags);
5747
5748pub const RelocFlags = packed struct {
5749 add_got: bool = false,
5750 add_stub: bool = false,
5751};
4234 pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
4235 return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
4236 }
57524237
5753pub const SymbolWithLoc = extern struct {
5754 // Index into the respective symbol table.
5755 sym_index: u32,
4238 const FmtCtx = struct {
4239 platform: Platform,
4240 cpu_arch: std.Target.Cpu.Arch,
4241 };
57564242
5757 // 0 means it's a synthetic global.
5758 file: u32 = 0,
4243 pub fn formatTarget(
4244 ctx: FmtCtx,
4245 comptime unused_fmt_string: []const u8,
4246 options: std.fmt.FormatOptions,
4247 writer: anytype,
4248 ) !void {
4249 _ = unused_fmt_string;
4250 _ = options;
4251 try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
4252 if (ctx.platform.abi != .none) {
4253 try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
4254 }
4255 }
57594256
5760 pub fn getFile(self: SymbolWithLoc) ?u32 {
5761 if (self.file == 0) return null;
5762 return self.file - 1;
4257 /// Caller owns the memory.
4258 pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
4259 var buffer = std.ArrayList(u8).init(gpa);
4260 defer buffer.deinit();
4261 try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
4262 return buffer.toOwnedSlice();
57634263 }
57644264
5765 pub fn eql(self: SymbolWithLoc, other: SymbolWithLoc) bool {
5766 return self.file == other.file and self.sym_index == other.sym_index;
4265 pub fn eqlTarget(plat: Platform, other: Platform) bool {
4266 return plat.os_tag == other.os_tag and plat.abi == other.abi;
57674267 }
57684268};
57694269
5770const HotUpdateState = struct {
5771 mach_task: ?std.os.darwin.MachTask = null,
4270const SupportedPlatforms = struct {
4271 std.Target.Os.Tag,
4272 std.Target.Abi,
4273 u32, // Min platform version for which to emit LC_BUILD_VERSION
4274 u32, // Min supported platform version
4275};
4276
4277// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
4278// zig fmt: off
4279const supported_platforms = [_]SupportedPlatforms{
4280 .{ .macos, .none, 0xA0E00, 0xA0800 },
4281 .{ .ios, .none, 0xC0000, 0x70000 },
4282 .{ .tvos, .none, 0xC0000, 0x70000 },
4283 .{ .watchos, .none, 0x50000, 0x20000 },
4284 .{ .ios, .simulator, 0xD0000, 0x80000 },
4285 .{ .tvos, .simulator, 0xD0000, 0x80000 },
4286 .{ .watchos, .simulator, 0x60000, 0x20000 },
57724287};
4288// zig fmt: on
4289
4290pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
4291 const major = version.major;
4292 const minor = version.minor;
4293 const patch = version.patch;
4294 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
4295}
4296
4297pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
4298 return .{
4299 .major = @as(u16, @truncate(version >> 16)),
4300 .minor = @as(u8, @truncate(version >> 8)),
4301 .patch = @as(u8, @truncate(version)),
4302 };
4303}
4304
4305fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion {
4306 const gpa = comp.gpa;
4307
4308 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
4309 defer arena_allocator.deinit();
4310 const arena = arena_allocator.allocator();
4311
4312 const sdk_dir = switch (sdk_layout) {
4313 .sdk => comp.sysroot.?,
4314 .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
4315 };
4316 if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
4317 return parseSdkVersion(ver);
4318 } else |_| {
4319 // Read from settings should always succeed when vendored.
4320 // TODO: convert to fatal linker error
4321 if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
4322 }
4323
4324 // infer from pathname
4325 const stem = std.fs.path.stem(sdk_dir);
4326 const start = for (stem, 0..) |c, i| {
4327 if (std.ascii.isDigit(c)) break i;
4328 } else stem.len;
4329 const end = for (stem[start..], start..) |c, i| {
4330 if (std.ascii.isDigit(c) or c == '.') continue;
4331 break i;
4332 } else stem.len;
4333 return parseSdkVersion(stem[start..end]);
4334}
4335
4336// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
4337// Use property `MinimalDisplayName` to determine version.
4338// The file/property is also available with vendored libc.
4339fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
4340 const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
4341 const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
4342 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
4343 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
4344 return error.SdkVersionFailure;
4345}
4346
4347// Versions reported by Apple aren't exactly semantically valid as they usually omit
4348// the patch component, so we parse SDK value by hand.
4349fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
4350 var parsed: std.SemanticVersion = .{
4351 .major = 0,
4352 .minor = 0,
4353 .patch = 0,
4354 };
4355
4356 const parseNext = struct {
4357 fn parseNext(it: anytype) ?u16 {
4358 const nn = it.next() orelse return null;
4359 return std.fmt.parseInt(u16, nn, 10) catch null;
4360 }
4361 }.parseNext;
4362
4363 var it = std.mem.splitAny(u8, raw, ".");
4364 parsed.major = parseNext(&it) orelse return null;
4365 parsed.minor = parseNext(&it) orelse return null;
4366 parsed.patch = parseNext(&it) orelse 0;
4367 return parsed;
4368}
57734369
57744370/// When allocating, the ideal_capacity is calculated by
57754371/// actual_capacity + (actual_capacity / ideal_factor)
......@@ -5783,13 +4379,37 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size);
57834379
57844380/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and
57854381/// start of __TEXT segment.
5786pub const default_pagezero_vmsize: u64 = 0x100000000;
4382pub const default_pagezero_size: u64 = 0x100000000;
57874383
57884384/// We commit 0x1000 = 4096 bytes of space to the header and
57894385/// the table of load commands. This should be plenty for any
57904386/// potential future extensions.
57914387pub const default_headerpad_size: u32 = 0x1000;
57924388
4389const SystemLib = struct {
4390 path: []const u8,
4391 needed: bool = false,
4392 weak: bool = false,
4393 hidden: bool = false,
4394 reexport: bool = false,
4395 must_link: bool = false,
4396};
4397
4398/// The filesystem layout of darwin SDK elements.
4399pub const SdkLayout = enum {
4400 /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }.
4401 sdk,
4402 /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, <NONE> }.
4403 vendored,
4404};
4405
4406const UndefinedTreatment = enum {
4407 @"error",
4408 warn,
4409 suppress,
4410 dynamic_lookup,
4411};
4412
57934413const MachO = @This();
57944414
57954415const std = @import("std");
......@@ -5799,6 +4419,7 @@ const assert = std.debug.assert;
57994419const dwarf = std.dwarf;
58004420const fs = std.fs;
58014421const log = std.log.scoped(.link);
4422const state_log = std.log.scoped(.link_state);
58024423const macho = std.macho;
58034424const math = std.math;
58044425const mem = std.mem;
......@@ -5808,46 +4429,56 @@ const aarch64 = @import("../arch/aarch64/bits.zig");
58084429const calcUuid = @import("MachO/uuid.zig").calcUuid;
58094430const codegen = @import("../codegen.zig");
58104431const dead_strip = @import("MachO/dead_strip.zig");
4432const eh_frame = @import("MachO/eh_frame.zig");
58114433const fat = @import("MachO/fat.zig");
58124434const link = @import("../link.zig");
58134435const llvm_backend = @import("../codegen/llvm.zig");
58144436const load_commands = @import("MachO/load_commands.zig");
5815const stubs = @import("MachO/stubs.zig");
4437const relocatable = @import("MachO/relocatable.zig");
58164438const tapi = @import("tapi.zig");
58174439const target_util = @import("../target.zig");
58184440const thunks = @import("MachO/thunks.zig");
58194441const trace = @import("../tracy.zig").trace;
5820const zld = @import("MachO/zld.zig");
4442const synthetic = @import("MachO/synthetic.zig");
58214443
58224444const Air = @import("../Air.zig");
4445const Alignment = Atom.Alignment;
58234446const Allocator = mem.Allocator;
58244447const Archive = @import("MachO/Archive.zig");
58254448pub const Atom = @import("MachO/Atom.zig");
4449const BindSection = synthetic.BindSection;
58264450const Cache = std.Build.Cache;
58274451const CodeSignature = @import("MachO/CodeSignature.zig");
58284452const Compilation = @import("../Compilation.zig");
4453pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
58294454const Dwarf = File.Dwarf;
58304455const DwarfInfo = @import("MachO/DwarfInfo.zig");
58314456const Dylib = @import("MachO/Dylib.zig");
5832const File = link.File;
4457const ExportTrieSection = synthetic.ExportTrieSection;
4458const File = @import("MachO/file.zig").File;
4459const GotSection = synthetic.GotSection;
4460const Indsymtab = synthetic.Indsymtab;
4461const InternalObject = @import("MachO/InternalObject.zig");
4462const ObjcStubsSection = synthetic.ObjcStubsSection;
58334463const Object = @import("MachO/Object.zig");
4464const LazyBindSection = synthetic.LazyBindSection;
4465const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
58344466const LibStub = tapi.LibStub;
58354467const Liveness = @import("../Liveness.zig");
58364468const LlvmObject = @import("../codegen/llvm.zig").Object;
58374469const Md5 = std.crypto.hash.Md5;
58384470const Module = @import("../Module.zig");
58394471const InternPool = @import("../InternPool.zig");
5840const Platform = load_commands.Platform;
5841const Relocation = @import("MachO/Relocation.zig");
4472const RebaseSection = synthetic.RebaseSection;
4473pub const Relocation = @import("MachO/Relocation.zig");
58424474const StringTable = @import("StringTable.zig");
5843const TableSection = @import("table_section.zig").TableSection;
5844const Trie = @import("MachO/Trie.zig");
5845const Type = @import("../type.zig").Type;
4475const StubsSection = synthetic.StubsSection;
4476const StubsHelperSection = synthetic.StubsHelperSection;
4477const Symbol = @import("MachO/Symbol.zig");
4478const Thunk = thunks.Thunk;
4479const TlvPtrSection = synthetic.TlvPtrSection;
58464480const TypedValue = @import("../TypedValue.zig");
5847const Value = @import("../value.zig").Value;
5848const Alignment = Atom.Alignment;
5849
5850pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
5851pub const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, SymbolWithLoc);
5852pub const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, SymbolWithLoc);
5853pub const Rebase = @import("MachO/dyld_info/Rebase.zig");
4481const UnwindInfo = @import("MachO/UnwindInfo.zig");
4482const WeakBindSection = synthetic.WeakBindSection;
4483const ZigGotSection = synthetic.ZigGotSection;
4484const ZigObject = @import("MachO/ZigObject.zig");
src/link/MachO/Archive.zig+77-148
......@@ -1,20 +1,15 @@
1file: fs.File,
2fat_offset: u64,
3name: []const u8,
4header: ar_hdr = undefined,
1path: []const u8,
2data: []const u8,
53
6/// Parsed table of contents.
7/// Each symbol name points to a list of all definition
8/// sites within the current static archive.
9toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
4objects: std.ArrayListUnmanaged(Object) = .{},
105
116// Archive files start with the ARMAG identifying string. Then follows a
127// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
138// member indicates, for each member file.
149/// String that begins an archive file.
15const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
10pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
1611/// Size of that string.
17const SARMAG: u4 = 8;
12pub const SARMAG: u4 = 8;
1813
1914/// String in ar_fmag at the end of each header.
2015const ARFMAG: *const [2:0]u8 = "`\n";
......@@ -41,177 +36,111 @@ const ar_hdr = extern struct {
4136 /// Always contains ARFMAG.
4237 ar_fmag: [2]u8,
4338
44 const NameOrLength = union(enum) {
45 Name: []const u8,
46 Length: u32,
47 };
48 fn nameOrLength(self: ar_hdr) !NameOrLength {
49 const value = getValue(&self.ar_name);
50 const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
51 const len = value.len;
52 if (slash_index == len - 1) {
53 // Name stored directly
54 return NameOrLength{ .Name = value };
55 } else {
56 // Name follows the header directly and its length is encoded in
57 // the name field.
58 const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
59 return NameOrLength{ .Length = length };
60 }
61 }
62
6339 fn date(self: ar_hdr) !u64 {
64 const value = getValue(&self.ar_date);
40 const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
6541 return std.fmt.parseInt(u64, value, 10);
6642 }
6743
6844 fn size(self: ar_hdr) !u32 {
69 const value = getValue(&self.ar_size);
45 const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
7046 return std.fmt.parseInt(u32, value, 10);
7147 }
7248
73 fn getValue(raw: []const u8) []const u8 {
74 return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)});
49 fn name(self: *const ar_hdr) ?[]const u8 {
50 const value = &self.ar_name;
51 if (mem.startsWith(u8, value, "#1/")) return null;
52 const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
53 return value[0..sentinel];
7554 }
76};
7755
78pub fn isArchive(file: fs.File, fat_offset: u64) bool {
79 const reader = file.reader();
80 const magic = reader.readBytesNoEof(SARMAG) catch return false;
81 defer file.seekTo(fat_offset) catch {};
82 return mem.eql(u8, &magic, ARMAG);
83}
84
85pub fn deinit(self: *Archive, allocator: Allocator) void {
86 self.file.close();
87 for (self.toc.keys()) |*key| {
88 allocator.free(key.*);
89 }
90 for (self.toc.values()) |*value| {
91 value.deinit(allocator);
56 fn nameLength(self: ar_hdr) !?u32 {
57 const value = &self.ar_name;
58 if (!mem.startsWith(u8, value, "#1/")) return null;
59 const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
60 return try std.fmt.parseInt(u32, trimmed, 10);
9261 }
93 self.toc.deinit(allocator);
94 allocator.free(self.name);
95}
96
97pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void {
98 _ = try reader.readBytesNoEof(SARMAG);
99 self.header = try reader.readStruct(ar_hdr);
100 const name_or_length = try self.header.nameOrLength();
101 const embedded_name = try parseName(allocator, name_or_length, reader);
102 log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
103 defer allocator.free(embedded_name);
104
105 try self.parseTableOfContents(allocator, reader);
106}
62};
10763
108fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 {
109 var name: []u8 = undefined;
110 switch (name_or_length) {
111 .Name => |n| {
112 name = try allocator.dupe(u8, n);
113 },
114 .Length => |len| {
115 var n = try allocator.alloc(u8, len);
116 defer allocator.free(n);
117 try reader.readNoEof(n);
118 const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len;
119 name = try allocator.dupe(u8, n[0..actual_len]);
120 },
64pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool {
65 const file = try std.fs.cwd().openFile(path, .{});
66 defer file.close();
67 if (fat_arch) |arch| {
68 try file.seekTo(arch.offset);
12169 }
122 return name;
70 const magic = file.reader().readBytesNoEof(SARMAG) catch return false;
71 if (!mem.eql(u8, &magic, ARMAG)) return false;
72 return true;
12373}
12474
125fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {
126 const symtab_size = try reader.readInt(u32, .little);
127 const symtab = try allocator.alloc(u8, symtab_size);
128 defer allocator.free(symtab);
129
130 reader.readNoEof(symtab) catch {
131 log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
132 return error.MalformedArchive;
133 };
75pub fn deinit(self: *Archive, allocator: Allocator) void {
76 allocator.free(self.data);
77 allocator.free(self.path);
78 self.objects.deinit(allocator);
79}
13480
135 const strtab_size = try reader.readInt(u32, .little);
136 const strtab = try allocator.alloc(u8, strtab_size);
137 defer allocator.free(strtab);
81pub fn parse(self: *Archive, macho_file: *MachO) !void {
82 const gpa = macho_file.base.comp.gpa;
13883
139 reader.readNoEof(strtab) catch {
140 log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
141 return error.MalformedArchive;
142 };
84 var arena = std.heap.ArenaAllocator.init(gpa);
85 defer arena.deinit();
14386
144 var symtab_stream = std.io.fixedBufferStream(symtab);
145 var symtab_reader = symtab_stream.reader();
87 var stream = std.io.fixedBufferStream(self.data);
88 const reader = stream.reader();
89 _ = try reader.readBytesNoEof(SARMAG);
14690
14791 while (true) {
148 const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) {
149 error.EndOfStream => break,
150 else => |e| return e,
151 };
152 const object_offset = try symtab_reader.readInt(u32, .little);
92 if (stream.pos >= self.data.len) break;
93 if (!mem.isAligned(stream.pos, 2)) stream.pos += 1;
15394
154 const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0);
155 const owned_name = try allocator.dupe(u8, sym_name);
156 const res = try self.toc.getOrPut(allocator, owned_name);
157 defer if (res.found_existing) allocator.free(owned_name);
95 const hdr = try reader.readStruct(ar_hdr);
15896
159 if (!res.found_existing) {
160 res.value_ptr.* = .{};
97 if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
98 try macho_file.reportParseError(self.path, "invalid header delimiter: expected '{s}', found '{s}'", .{
99 std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
100 });
101 return error.MalformedArchive;
161102 }
162103
163 try res.value_ptr.append(allocator, object_offset);
164 }
165}
104 var size = try hdr.size();
105 const name = name: {
106 if (hdr.name()) |n| break :name n;
107 if (try hdr.nameLength()) |len| {
108 size -= len;
109 const buf = try arena.allocator().alloc(u8, len);
110 try reader.readNoEof(buf);
111 const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
112 break :name buf[0..actual_len];
113 }
114 unreachable;
115 };
116 defer {
117 _ = stream.seekBy(size) catch {};
118 }
166119
167pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object {
168 const reader = self.file.reader();
169 try reader.context.seekTo(self.fat_offset + offset);
170
171 const object_header = try reader.readStruct(ar_hdr);
172
173 const name_or_length = try object_header.nameOrLength();
174 const object_name = try parseName(gpa, name_or_length, reader);
175 defer gpa.free(object_name);
176
177 log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
178
179 const name = name: {
180 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
181 const path = try std.os.realpath(self.name, &buffer);
182 break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
183 };
184
185 const object_name_len = switch (name_or_length) {
186 .Name => 0,
187 .Length => |len| len,
188 };
189 const object_size = (try object_header.size()) - object_name_len;
190 const contents = try gpa.allocWithOptions(u8, object_size, @alignOf(u64), null);
191 const amt = try reader.readAll(contents);
192 if (amt != object_size) {
193 return error.InputOutput;
194 }
120 if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue;
195121
196 var object = Object{
197 .name = name,
198 .mtime = object_header.date() catch 0,
199 .contents = contents,
200 };
122 const object = Object{
123 .archive = try gpa.dupe(u8, self.path),
124 .path = try gpa.dupe(u8, name),
125 .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),
126 .index = undefined,
127 .alive = false,
128 .mtime = hdr.date() catch 0,
129 };
201130
202 try object.parse(gpa);
131 log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
203132
204 return object;
133 try self.objects.append(gpa, object);
134 }
205135}
206136
207const Archive = @This();
208
209const std = @import("std");
210const assert = std.debug.assert;
211const fs = std.fs;
137const fat = @import("fat.zig");
212138const log = std.log.scoped(.link);
213139const macho = std.macho;
214140const mem = std.mem;
141const std = @import("std");
215142
216143const Allocator = mem.Allocator;
144const Archive = @This();
145const MachO = @import("../MachO.zig");
217146const Object = @import("Object.zig");
src/link/MachO/Atom.zig+980-1085
......@@ -1,1271 +1,1166 @@
1/// Each Atom always gets a symbol with the fully qualified name.
2/// The symbol can reside in any object file context structure in `symtab` array
3/// (see `Object`), or if the symbol is a synthetic symbol such as a GOT cell or
4/// a stub trampoline, it can be found in the linkers `locals` arraylist.
5/// If this field is 0 and file is 0, it means the codegen size = 0 and there is no symbol or
6/// offset table entry.
7sym_index: u32 = 0,
8
9/// 0 means an Atom is a synthetic Atom such as a GOT cell defined by the linker.
10/// Otherwise, it is the index into appropriate object file (indexing from 1).
11/// Prefer using `getFile()` helper to get the file index out rather than using
12/// the field directly.
13file: u32 = 0,
14
15/// If this Atom is not a synthetic Atom, i.e., references a subsection in an
16/// Object file, `inner_sym_index` and `inner_nsyms_trailing` tell where and if
17/// this Atom contains any additional symbol references that fall within this Atom's
18/// address range. These could for example be an alias symbol which can be used
19/// internally by the relocation records, or if the Object file couldn't be split
20/// into subsections, this Atom may encompass an entire input section.
21inner_sym_index: u32 = 0,
22inner_nsyms_trailing: u32 = 0,
23
24/// Size and alignment of this atom
25/// Unlike in Elf, we need to store the size of this symbol as part of
26/// the atom since macho.nlist_64 lacks this information.
1/// Address allocated for this Atom.
2value: u64 = 0,
3
4/// Name of this Atom.
5name: u32 = 0,
6
7/// Index into linker's input file table.
8file: File.Index = 0,
9
10/// Size of this atom
2711size: u64 = 0,
2812
29/// Alignment of this atom as a power of 2.
30/// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned.
13/// Alignment of this atom as a power of two.
3114alignment: Alignment = .@"1",
3215
33/// Points to the previous and next neighbours
34/// TODO use the same trick as with symbols: reserve index 0 as null atom
35next_index: ?Index = null,
36prev_index: ?Index = null,
16/// Index of the input section.
17n_sect: u32 = 0,
3718
38pub const Alignment = @import("../../InternPool.zig").Alignment;
19/// Index of the output section.
20out_n_sect: u8 = 0,
3921
40pub const Index = u32;
22/// Offset within the parent section pointed to by n_sect.
23/// off + size <= parent section size.
24off: u64 = 0,
4125
42pub const Binding = struct {
43 target: SymbolWithLoc,
44 offset: u64,
45};
26/// Relocations of this atom.
27relocs: Loc = .{},
4628
47/// Returns `null` if the Atom is a synthetic Atom.
48/// Otherwise, returns an index into an array of Objects.
49pub fn getFile(self: Atom) ?u32 {
50 if (self.file == 0) return null;
51 return self.file - 1;
52}
29/// Index of this atom in the linker's atoms table.
30atom_index: Index = 0,
5331
54pub fn getSymbolIndex(self: Atom) ?u32 {
55 if (self.getFile() == null and self.sym_index == 0) return null;
56 return self.sym_index;
32/// Index of the thunk for this atom.
33thunk_index: Thunk.Index = 0,
34
35/// Unwind records associated with this atom.
36unwind_records: Loc = .{},
37
38flags: Flags = .{},
39
40/// Points to the previous and next neighbors, based on the `text_offset`.
41/// This can be used to find, for example, the capacity of this `TextBlock`.
42prev_index: Index = 0,
43next_index: Index = 0,
44
45pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 {
46 return macho_file.strings.getAssumeExists(self.name);
5747}
5848
59/// Returns symbol referencing this atom.
60pub fn getSymbol(self: Atom, macho_file: *MachO) macho.nlist_64 {
61 return self.getSymbolPtr(macho_file).*;
49pub fn getFile(self: Atom, macho_file: *MachO) File {
50 return macho_file.getFile(self.file).?;
6251}
6352
64/// Returns pointer-to-symbol referencing this atom.
65pub fn getSymbolPtr(self: Atom, macho_file: *MachO) *macho.nlist_64 {
66 const sym_index = self.getSymbolIndex().?;
67 return macho_file.getSymbolPtr(.{ .sym_index = sym_index, .file = self.file });
53pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
54 return switch (self.getFile(macho_file)) {
55 .zig_object => |x| x.getAtomRelocs(self),
56 .object => |x| x.getAtomRelocs(self),
57 else => unreachable,
58 };
6859}
6960
70pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
71 const sym_index = self.getSymbolIndex().?;
72 return .{ .sym_index = sym_index, .file = self.file };
61pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 {
62 return switch (self.getFile(macho_file)) {
63 .zig_object => |x| x.getInputSection(self, macho_file),
64 .object => |x| x.sections.items(.header)[self.n_sect],
65 else => unreachable,
66 };
7367}
7468
75/// Returns the name of this atom.
76pub fn getName(self: Atom, macho_file: *MachO) []const u8 {
77 const sym_index = self.getSymbolIndex().?;
78 return macho_file.getSymbolName(.{ .sym_index = sym_index, .file = self.file });
69pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 {
70 return self.getInputSection(macho_file).addr + self.off;
7971}
8072
81/// Returns how much room there is to grow in virtual address space.
82/// File offset relocation happens transparently, so it is not included in
83/// this calculation.
84pub fn capacity(self: Atom, macho_file: *MachO) u64 {
85 const self_sym = self.getSymbol(macho_file);
86 if (self.next_index) |next_index| {
87 const next = macho_file.getAtom(next_index);
88 const next_sym = next.getSymbol(macho_file);
89 return next_sym.n_value - self_sym.n_value;
90 } else {
91 // We are the last atom.
92 // The capacity is limited only by virtual address space.
93 return macho_file.allocatedVirtualSize(self_sym.n_value);
94 }
73pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
74 const file = self.getFile(macho_file);
75 return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect));
9576}
9677
97pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
98 // No need to keep a free list node for the last atom.
99 const next_index = self.next_index orelse return false;
100 const next = macho_file.getAtom(next_index);
101 const self_sym = self.getSymbol(macho_file);
102 const next_sym = next.getSymbol(macho_file);
103 const cap = next_sym.n_value - self_sym.n_value;
104 const ideal_cap = MachO.padToIdeal(self.size);
105 if (cap <= ideal_cap) return false;
106 const surplus = cap - ideal_cap;
107 return surplus >= MachO.min_text_capacity;
78pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
79 return switch (self.getFile(macho_file)) {
80 .dylib => unreachable,
81 .zig_object, .internal => &[0]UnwindInfo.Record.Index{},
82 .object => |x| x.unwind_records.items[self.unwind_records.pos..][0..self.unwind_records.len],
83 };
10884}
10985
110pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 {
111 const segname = sect.segName();
112 const sectname = sect.sectName();
113 const res: ?u8 = blk: {
114 if (mem.eql(u8, "__LLVM", segname)) {
115 log.debug("TODO LLVM section: type 0x{x}, name '{s},{s}'", .{
116 sect.flags, segname, sectname,
117 });
118 break :blk null;
119 }
86pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
87 for (self.getUnwindRecords(macho_file)) |cu_index| {
88 const cu = macho_file.getUnwindRecord(cu_index);
89 cu.alive = false;
12090
121 // We handle unwind info separately.
122 if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) {
123 break :blk null;
124 }
125 if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) {
126 break :blk null;
91 if (cu.getFdePtr(macho_file)) |fde| {
92 fde.alive = false;
12793 }
94 }
95}
12896
129 if (sect.isCode()) {
130 if (macho_file.text_section_index == null) {
131 macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{
132 .flags = macho.S_REGULAR |
133 macho.S_ATTR_PURE_INSTRUCTIONS |
134 macho.S_ATTR_SOME_INSTRUCTIONS,
135 });
136 }
137 break :blk macho_file.text_section_index.?;
138 }
97pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
98 return macho_file.getThunk(self.thunk_index);
99}
139100
140 if (sect.isDebug()) {
141 break :blk null;
142 }
101pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
102 const segname, const sectname, const flags = blk: {
103 if (sect.isCode()) break :blk .{
104 "__TEXT",
105 sect.sectName(),
106 macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
107 };
143108
144109 switch (sect.type()) {
145110 macho.S_4BYTE_LITERALS,
146111 macho.S_8BYTE_LITERALS,
147112 macho.S_16BYTE_LITERALS,
148 => {
149 break :blk macho_file.getSectionByName("__TEXT", "__const") orelse
150 try macho_file.initSection("__TEXT", "__const", .{});
151 },
113 => break :blk .{ "__TEXT", "__const", macho.S_REGULAR },
114
152115 macho.S_CSTRING_LITERALS => {
153 if (mem.startsWith(u8, sectname, "__objc")) {
154 break :blk macho_file.getSectionByName(segname, sectname) orelse
155 try macho_file.initSection(segname, sectname, .{});
156 }
157 break :blk macho_file.getSectionByName("__TEXT", "__cstring") orelse
158 try macho_file.initSection("__TEXT", "__cstring", .{
159 .flags = macho.S_CSTRING_LITERALS,
160 });
116 if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{
117 sect.segName(), sect.sectName(), macho.S_REGULAR,
118 };
119 break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS };
161120 },
121
162122 macho.S_MOD_INIT_FUNC_POINTERS,
163123 macho.S_MOD_TERM_FUNC_POINTERS,
164 => {
165 break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
166 try macho_file.initSection("__DATA_CONST", sectname, .{
167 .flags = sect.flags,
168 });
169 },
124 => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags },
125
170126 macho.S_LITERAL_POINTERS,
171127 macho.S_ZEROFILL,
128 macho.S_GB_ZEROFILL,
172129 macho.S_THREAD_LOCAL_VARIABLES,
173130 macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
174131 macho.S_THREAD_LOCAL_REGULAR,
175132 macho.S_THREAD_LOCAL_ZEROFILL,
176 => {
177 break :blk macho_file.getSectionByName(segname, sectname) orelse
178 try macho_file.initSection(segname, sectname, .{
179 .flags = sect.flags,
180 });
181 },
182 macho.S_COALESCED => {
183 break :blk macho_file.getSectionByName(segname, sectname) orelse
184 try macho_file.initSection(segname, sectname, .{});
133 => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
134
135 macho.S_COALESCED => break :blk .{
136 sect.segName(),
137 sect.sectName(),
138 macho.S_REGULAR,
185139 },
140
186141 macho.S_REGULAR => {
187 if (mem.eql(u8, segname, "__TEXT")) {
188 if (mem.eql(u8, sectname, "__rodata") or
189 mem.eql(u8, sectname, "__typelink") or
190 mem.eql(u8, sectname, "__itablink") or
191 mem.eql(u8, sectname, "__gosymtab") or
192 mem.eql(u8, sectname, "__gopclntab"))
193 {
194 break :blk macho_file.getSectionByName("__TEXT", sectname) orelse
195 try macho_file.initSection("__TEXT", sectname, .{});
196 }
197 }
142 const segname = sect.segName();
143 const sectname = sect.sectName();
198144 if (mem.eql(u8, segname, "__DATA")) {
199145 if (mem.eql(u8, sectname, "__const") or
200146 mem.eql(u8, sectname, "__cfstring") or
201147 mem.eql(u8, sectname, "__objc_classlist") or
202 mem.eql(u8, sectname, "__objc_imageinfo"))
203 {
204 break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse
205 try macho_file.initSection("__DATA_CONST", sectname, .{});
206 } else if (mem.eql(u8, sectname, "__data")) {
207 if (macho_file.data_section_index == null) {
208 macho_file.data_section_index = try macho_file.initSection("__DATA", "__data", .{});
209 }
210 break :blk macho_file.data_section_index.?;
211 }
148 mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{
149 "__DATA_CONST",
150 sectname,
151 macho.S_REGULAR,
152 };
212153 }
213 break :blk macho_file.getSectionByName(segname, sectname) orelse
214 try macho_file.initSection(segname, sectname, .{});
154 break :blk .{ segname, sectname, sect.flags };
215155 },
216 else => break :blk null,
217 }
218 };
219156
220 // TODO we can do this directly in the selection logic above.
221 // Or is it not worth it?
222 if (macho_file.data_const_section_index == null) {
223 if (macho_file.getSectionByName("__DATA_CONST", "__const")) |index| {
224 macho_file.data_const_section_index = index;
225 }
226 }
227 if (macho_file.thread_vars_section_index == null) {
228 if (macho_file.getSectionByName("__DATA", "__thread_vars")) |index| {
229 macho_file.thread_vars_section_index = index;
230 }
231 }
232 if (macho_file.thread_data_section_index == null) {
233 if (macho_file.getSectionByName("__DATA", "__thread_data")) |index| {
234 macho_file.thread_data_section_index = index;
235 }
236 }
237 if (macho_file.thread_bss_section_index == null) {
238 if (macho_file.getSectionByName("__DATA", "__thread_bss")) |index| {
239 macho_file.thread_bss_section_index = index;
157 else => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
240158 }
159 };
160 const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(
161 segname,
162 sectname,
163 .{ .flags = flags },
164 );
165 if (mem.eql(u8, segname, "__TEXT") and mem.eql(u8, sectname, "__text")) {
166 macho_file.text_sect_index = osec;
241167 }
242 if (macho_file.bss_section_index == null) {
243 if (macho_file.getSectionByName("__DATA", "__bss")) |index| {
244 macho_file.bss_section_index = index;
245 }
168 if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) {
169 macho_file.data_sect_index = osec;
246170 }
171 return osec;
172}
247173
248 return res;
174/// Returns how much room there is to grow in virtual address space.
175/// File offset relocation happens transparently, so it is not included in
176/// this calculation.
177pub fn capacity(self: Atom, macho_file: *MachO) u64 {
178 const next_value = if (macho_file.getAtom(self.next_index)) |next| next.value else std.math.maxInt(u32);
179 return next_value - self.value;
249180}
250181
251pub fn addRelocation(macho_file: *MachO, atom_index: Index, reloc: Relocation) !void {
252 return addRelocations(macho_file, atom_index, &[_]Relocation{reloc});
182pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
183 // No need to keep a free list node for the last block.
184 const next = macho_file.getAtom(self.next_index) orelse return false;
185 const cap = next.value - self.value;
186 const ideal_cap = MachO.padToIdeal(self.size);
187 if (cap <= ideal_cap) return false;
188 const surplus = cap - ideal_cap;
189 return surplus >= MachO.min_text_capacity;
253190}
254191
255pub fn addRelocations(macho_file: *MachO, atom_index: Index, relocs: []const Relocation) !void {
256 const comp = macho_file.base.comp;
257 const gpa = comp.gpa;
258 const gop = try macho_file.relocs.getOrPut(gpa, atom_index);
259 if (!gop.found_existing) {
260 gop.value_ptr.* = .{};
261 }
262 try gop.value_ptr.ensureUnusedCapacity(gpa, relocs.len);
263 for (relocs) |reloc| {
264 log.debug(" (adding reloc of type {s} to target %{d})", .{
265 @tagName(reloc.type),
266 reloc.target.sym_index,
267 });
268 gop.value_ptr.appendAssumeCapacity(reloc);
192pub fn allocate(self: *Atom, macho_file: *MachO) !void {
193 const sect = &macho_file.sections.items(.header)[self.out_n_sect];
194 const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
195 const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
196 const new_atom_ideal_capacity = MachO.padToIdeal(self.size);
197
198 // We use these to indicate our intention to update metadata, placing the new atom,
199 // and possibly removing a free list node.
200 // It would be simpler to do it inside the for loop below, but that would cause a
201 // problem if an error was returned later in the function. So this action
202 // is actually carried out at the end of the function, when errors are no longer possible.
203 var atom_placement: ?Atom.Index = null;
204 var free_list_removal: ?usize = null;
205
206 // First we look for an appropriately sized free list node.
207 // The list is unordered. We'll just take the first thing that works.
208 self.value = blk: {
209 var i: usize = free_list.items.len;
210 while (i < free_list.items.len) {
211 const big_atom_index = free_list.items[i];
212 const big_atom = macho_file.getAtom(big_atom_index).?;
213 // We now have a pointer to a live atom that has too much capacity.
214 // Is it enough that we could fit this new atom?
215 const cap = big_atom.capacity(macho_file);
216 const ideal_capacity = MachO.padToIdeal(cap);
217 const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity;
218 const capacity_end_vaddr = big_atom.value + cap;
219 const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
220 const new_start_vaddr = self.alignment.backward(new_start_vaddr_unaligned);
221 if (new_start_vaddr < ideal_capacity_end_vaddr) {
222 // Additional bookkeeping here to notice if this free list node
223 // should be deleted because the block that it points to has grown to take up
224 // more of the extra capacity.
225 if (!big_atom.freeListEligible(macho_file)) {
226 _ = free_list.swapRemove(i);
227 } else {
228 i += 1;
229 }
230 continue;
231 }
232 // At this point we know that we will place the new block here. But the
233 // remaining question is whether there is still yet enough capacity left
234 // over for there to still be a free list node.
235 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
236 const keep_free_list_node = remaining_capacity >= MachO.min_text_capacity;
237
238 // Set up the metadata to be updated, after errors are no longer possible.
239 atom_placement = big_atom_index;
240 if (!keep_free_list_node) {
241 free_list_removal = i;
242 }
243 break :blk new_start_vaddr;
244 } else if (macho_file.getAtom(last_atom_index.*)) |last| {
245 const ideal_capacity = MachO.padToIdeal(last.size);
246 const ideal_capacity_end_vaddr = last.value + ideal_capacity;
247 const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
248 // Set up the metadata to be updated, after errors are no longer possible.
249 atom_placement = last.atom_index;
250 break :blk new_start_vaddr;
251 } else {
252 break :blk sect.addr;
253 }
254 };
255
256 log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{
257 self.atom_index,
258 self.getName(macho_file),
259 self.value,
260 self.value + self.size,
261 });
262
263 const expand_section = if (atom_placement) |placement_index|
264 macho_file.getAtom(placement_index).?.next_index == 0
265 else
266 true;
267 if (expand_section) {
268 const needed_size = (self.value + self.size) - sect.addr;
269 try macho_file.growSection(self.out_n_sect, needed_size);
270 last_atom_index.* = self.atom_index;
271
272 // const zig_object = macho_file_file.getZigObject().?;
273 // if (zig_object.dwarf) |_| {
274 // // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
275 // // range of the compilation unit. When we expand the text section, this range changes,
276 // // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
277 // zig_object.debug_info_header_dirty = true;
278 // // This becomes dirty for the same reason. We could potentially make this more
279 // // fine-grained with the addition of support for more compilation units. It is planned to
280 // // model each package as a different compilation unit.
281 // zig_object.debug_aranges_section_dirty = true;
282 // }
269283 }
270}
284 sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units());
271285
272pub fn addRebase(macho_file: *MachO, atom_index: Index, offset: u32) !void {
273 const comp = macho_file.base.comp;
274 const gpa = comp.gpa;
275 const atom = macho_file.getAtom(atom_index);
276 log.debug(" (adding rebase at offset 0x{x} in %{?d})", .{ offset, atom.getSymbolIndex() });
277 const gop = try macho_file.rebases.getOrPut(gpa, atom_index);
278 if (!gop.found_existing) {
279 gop.value_ptr.* = .{};
286 // This function can also reallocate an atom.
287 // In this case we need to "unplug" it from its previous location before
288 // plugging it in to its new location.
289 if (macho_file.getAtom(self.prev_index)) |prev| {
290 prev.next_index = self.next_index;
291 }
292 if (macho_file.getAtom(self.next_index)) |next| {
293 next.prev_index = self.prev_index;
280294 }
281 try gop.value_ptr.append(gpa, offset);
282}
283295
284pub fn addBinding(macho_file: *MachO, atom_index: Index, binding: Binding) !void {
285 const comp = macho_file.base.comp;
286 const gpa = comp.gpa;
287 const atom = macho_file.getAtom(atom_index);
288 log.debug(" (adding binding to symbol {s} at offset 0x{x} in %{?d})", .{
289 macho_file.getSymbolName(binding.target),
290 binding.offset,
291 atom.getSymbolIndex(),
292 });
293 const gop = try macho_file.bindings.getOrPut(gpa, atom_index);
294 if (!gop.found_existing) {
295 gop.value_ptr.* = .{};
296 if (atom_placement) |big_atom_index| {
297 const big_atom = macho_file.getAtom(big_atom_index).?;
298 self.prev_index = big_atom_index;
299 self.next_index = big_atom.next_index;
300 big_atom.next_index = self.atom_index;
301 } else {
302 self.prev_index = 0;
303 self.next_index = 0;
304 }
305 if (free_list_removal) |i| {
306 _ = free_list.swapRemove(i);
296307 }
297 try gop.value_ptr.append(gpa, binding);
308
309 self.flags.alive = true;
298310}
299311
300pub fn resolveRelocations(
301 macho_file: *MachO,
302 atom_index: Index,
303 relocs: []*const Relocation,
304 code: []u8,
305) void {
306 relocs_log.debug("relocating '{s}'", .{macho_file.getAtom(atom_index).getName(macho_file)});
307 for (relocs) |reloc| {
308 reloc.resolve(macho_file, atom_index, code);
309 }
312pub fn shrink(self: *Atom, macho_file: *MachO) void {
313 _ = self;
314 _ = macho_file;
310315}
311316
312pub fn freeRelocations(macho_file: *MachO, atom_index: Index) void {
313 const comp = macho_file.base.comp;
314 const gpa = comp.gpa;
315 var removed_relocs = macho_file.relocs.fetchOrderedRemove(atom_index);
316 if (removed_relocs) |*relocs| relocs.value.deinit(gpa);
317 var removed_rebases = macho_file.rebases.fetchOrderedRemove(atom_index);
318 if (removed_rebases) |*rebases| rebases.value.deinit(gpa);
319 var removed_bindings = macho_file.bindings.fetchOrderedRemove(atom_index);
320 if (removed_bindings) |*bindings| bindings.value.deinit(gpa);
317pub fn grow(self: *Atom, macho_file: *MachO) !void {
318 if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file))
319 try self.allocate(macho_file);
321320}
322321
323const InnerSymIterator = struct {
324 sym_index: u32,
325 nsyms: u32,
326 file: u32,
327 pos: u32 = 0,
322pub fn free(self: *Atom, macho_file: *MachO) void {
323 log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.getName(macho_file) });
328324
329 pub fn next(it: *@This()) ?SymbolWithLoc {
330 if (it.pos == it.nsyms) return null;
331 const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file };
332 it.pos += 1;
333 return res;
325 const comp = macho_file.base.comp;
326 const gpa = comp.gpa;
327 const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
328 const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
329 var already_have_free_list_node = false;
330 {
331 var i: usize = 0;
332 // TODO turn free_list into a hash map
333 while (i < free_list.items.len) {
334 if (free_list.items[i] == self.atom_index) {
335 _ = free_list.swapRemove(i);
336 continue;
337 }
338 if (free_list.items[i] == self.prev_index) {
339 already_have_free_list_node = true;
340 }
341 i += 1;
342 }
334343 }
335};
336
337/// Returns an iterator over potentially contained symbols.
338/// Panics when called on a synthetic Atom.
339pub fn getInnerSymbolsIterator(macho_file: *MachO, atom_index: Index) InnerSymIterator {
340 const atom = macho_file.getAtom(atom_index);
341 assert(atom.getFile() != null);
342 return .{
343 .sym_index = atom.inner_sym_index,
344 .nsyms = atom.inner_nsyms_trailing,
345 .file = atom.file,
346 };
347}
348344
349/// Returns a section alias symbol if one is defined.
350/// An alias symbol is used to represent the start of an input section
351/// if there were no symbols defined within that range.
352/// Alias symbols are only used on x86_64.
353pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc {
354 const atom = macho_file.getAtom(atom_index);
355 assert(atom.getFile() != null);
356
357 const object = macho_file.objects.items[atom.getFile().?];
358 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
359 const ntotal = @as(u32, @intCast(object.symtab.len));
360 var sym_index: u32 = nbase;
361 while (sym_index < ntotal) : (sym_index += 1) {
362 if (object.getAtomIndexForSymbol(sym_index)) |other_atom_index| {
363 if (other_atom_index == atom_index) return SymbolWithLoc{
364 .sym_index = sym_index,
365 .file = atom.file,
366 };
345 if (macho_file.getAtom(last_atom_index.*)) |last_atom| {
346 if (last_atom.atom_index == self.atom_index) {
347 if (macho_file.getAtom(self.prev_index)) |_| {
348 // TODO shrink the section size here
349 last_atom_index.* = self.prev_index;
350 } else {
351 last_atom_index.* = 0;
352 }
367353 }
368354 }
369 return null;
370}
371355
372/// Given an index into a contained symbol within, calculates an offset wrt
373/// the start of this Atom.
374pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 {
375 const atom = macho_file.getAtom(atom_index);
376 assert(atom.getFile() != null);
377
378 if (atom.sym_index == sym_index) return 0;
379
380 const object = macho_file.objects.items[atom.getFile().?];
381 const source_sym = object.getSourceSymbol(sym_index).?;
382 const base_addr = if (object.getSourceSymbol(atom.sym_index)) |sym|
383 sym.n_value
384 else blk: {
385 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
386 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
387 const source_sect = object.getSourceSection(sect_id);
388 break :blk source_sect.addr;
389 };
390 return source_sym.n_value - base_addr;
391}
356 if (macho_file.getAtom(self.prev_index)) |prev| {
357 prev.next_index = self.next_index;
358 if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) {
359 // The free list is heuristics, it doesn't have to be perfect, so we can
360 // ignore the OOM here.
361 free_list.append(gpa, prev.atom_index) catch {};
362 }
363 } else {
364 self.prev_index = 0;
365 }
392366
393pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
394 const target = macho_file.base.comp.root_mod.resolved_target.result;
395 const arch = target.cpu.arch;
396 const atom = macho_file.getAtom(atom_index);
397 assert(atom.getFile() != null); // synthetic atoms do not have relocs
367 if (macho_file.getAtom(self.next_index)) |next| {
368 next.prev_index = self.prev_index;
369 } else {
370 self.next_index = 0;
371 }
398372
399 return switch (arch) {
400 .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs),
401 .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs),
402 else => unreachable,
403 };
373 // TODO create relocs free list
374 self.freeRelocs(macho_file);
375 // TODO figure out how to free input section mappind in ZigModule
376 // const zig_object = macho_file.zigObjectPtr().?
377 // assert(zig_object.atoms.swapRemove(self.atom_index));
378 self.* = .{};
404379}
405380
406const RelocContext = struct {
407 base_addr: i64 = 0,
408 base_offset: i32 = 0,
409};
410
411pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext {
412 const atom = macho_file.getAtom(atom_index);
413 assert(atom.getFile() != null); // synthetic atoms do not have relocs
381pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void {
382 const gpa = macho_file.base.comp.gpa;
383 const file = self.getFile(macho_file);
384 assert(file == .zig_object);
385 const rels = &file.zig_object.relocs.items[self.relocs.pos];
386 try rels.append(gpa, reloc);
387 self.relocs.len += 1;
388}
414389
415 const object = macho_file.objects.items[atom.getFile().?];
416 if (object.getSourceSymbol(atom.sym_index)) |source_sym| {
417 const source_sect = object.getSourceSection(source_sym.n_sect - 1);
418 return .{
419 .base_addr = @as(i64, @intCast(source_sect.addr)),
420 .base_offset = @as(i32, @intCast(source_sym.n_value - source_sect.addr)),
421 };
422 }
423 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
424 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
425 const source_sect = object.getSourceSection(sect_id);
426 return .{
427 .base_addr = @as(i64, @intCast(source_sect.addr)),
428 .base_offset = 0,
429 };
390pub fn freeRelocs(self: *Atom, macho_file: *MachO) void {
391 self.getFile(macho_file).zig_object.freeAtomRelocs(self.*);
392 self.relocs.len = 0;
430393}
431394
432pub fn parseRelocTarget(macho_file: *MachO, ctx: struct {
433 object_id: u32,
434 rel: macho.relocation_info,
435 code: []const u8,
436 base_addr: i64 = 0,
437 base_offset: i32 = 0,
438}) SymbolWithLoc {
395pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
439396 const tracy = trace(@src());
440397 defer tracy.end();
398 assert(self.flags.alive);
441399
442 const target = macho_file.base.comp.root_mod.resolved_target.result;
443 const object = &macho_file.objects.items[ctx.object_id];
444 log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name });
445
446 const sym_index = if (ctx.rel.r_extern == 0) sym_index: {
447 const sect_id = @as(u8, @intCast(ctx.rel.r_symbolnum - 1));
448 const rel_offset = @as(u32, @intCast(ctx.rel.r_address - ctx.base_offset));
449
450 const address_in_section = if (ctx.rel.r_pcrel == 0) blk: {
451 break :blk if (ctx.rel.r_length == 3)
452 mem.readInt(u64, ctx.code[rel_offset..][0..8], .little)
453 else
454 mem.readInt(u32, ctx.code[rel_offset..][0..4], .little);
455 } else blk: {
456 assert(target.cpu.arch == .x86_64);
457 const correction: u3 = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.rel.r_type))) {
458 .X86_64_RELOC_SIGNED => 0,
459 .X86_64_RELOC_SIGNED_1 => 1,
460 .X86_64_RELOC_SIGNED_2 => 2,
461 .X86_64_RELOC_SIGNED_4 => 4,
462 else => unreachable,
463 };
464 const addend = mem.readInt(i32, ctx.code[rel_offset..][0..4], .little);
465 const target_address = @as(i64, @intCast(ctx.base_addr)) + ctx.rel.r_address + 4 + correction + addend;
466 break :blk @as(u64, @intCast(target_address));
467 };
468
469 // Find containing atom
470 log.debug(" | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id });
471 break :sym_index object.getSymbolByAddress(address_in_section, sect_id);
472 } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
473
474 const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };
475 const sym = macho_file.getSymbol(sym_loc);
476 const reloc_target = if (sym.sect() and !sym.ext())
477 sym_loc
478 else if (object.getGlobal(sym_index)) |global_index|
479 macho_file.globals.items[global_index]
480 else
481 sym_loc;
482 log.debug(" | target %{d} ('{s}') in object({?d})", .{
483 reloc_target.sym_index,
484 macho_file.getSymbolName(reloc_target),
485 reloc_target.getFile(),
486 });
487 return reloc_target;
488}
489
490pub fn getRelocTargetAtomIndex(macho_file: *MachO, target: SymbolWithLoc) ?Index {
491 if (target.getFile() == null) {
492 const target_sym_name = macho_file.getSymbolName(target);
493 if (mem.eql(u8, "__mh_execute_header", target_sym_name)) return null;
494 if (mem.eql(u8, "___dso_handle", target_sym_name)) return null;
495
496 unreachable; // referenced symbol not found
497 }
498
499 const object = macho_file.objects.items[target.getFile().?];
500 return object.getAtomIndexForSymbol(target.sym_index);
501}
400 const dynrel_ctx = switch (self.getFile(macho_file)) {
401 .zig_object => |x| &x.dynamic_relocs,
402 .object => |x| &x.dynamic_relocs,
403 else => unreachable,
404 };
405 const relocs = self.getRelocs(macho_file);
502406
503fn scanAtomRelocsArm64(
504 macho_file: *MachO,
505 atom_index: Index,
506 relocs: []align(1) const macho.relocation_info,
507) !void {
508407 for (relocs) |rel| {
509 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
510
511 switch (rel_type) {
512 .ARM64_RELOC_ADDEND, .ARM64_RELOC_SUBTRACTOR => continue,
513 else => {},
514 }
515
516 if (rel.r_extern == 0) continue;
408 if (try self.reportUndefSymbol(rel, macho_file)) continue;
409
410 switch (rel.type) {
411 .branch => {
412 const symbol = rel.getTargetSymbol(macho_file);
413 if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
414 symbol.flags.stubs = true;
415 if (symbol.flags.weak) {
416 macho_file.binds_to_weak = true;
417 }
418 } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) {
419 symbol.flags.objc_stubs = true;
420 }
421 },
517422
518 const atom = macho_file.getAtom(atom_index);
519 const object = &macho_file.objects.items[atom.getFile().?];
520 const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
521 const sym_loc = SymbolWithLoc{
522 .sym_index = sym_index,
523 .file = atom.file,
524 };
423 .got_load,
424 .got_load_page,
425 .got_load_pageoff,
426 => {
427 const symbol = rel.getTargetSymbol(macho_file);
428 if (symbol.flags.import or
429 (symbol.flags.@"export" and symbol.flags.weak) or
430 symbol.flags.interposable or
431 macho_file.getTarget().cpu.arch == .aarch64) // TODO relax on arm64
432 {
433 symbol.flags.needs_got = true;
434 if (symbol.flags.weak) {
435 macho_file.binds_to_weak = true;
436 }
437 }
438 },
525439
526 const target = if (object.getGlobal(sym_index)) |global_index|
527 macho_file.globals.items[global_index]
528 else
529 sym_loc;
440 .zig_got_load => {
441 assert(rel.getTargetSymbol(macho_file).flags.has_zig_got);
442 },
530443
531 switch (rel_type) {
532 .ARM64_RELOC_BRANCH26 => {
533 // TODO rewrite relocation
534 const sym = macho_file.getSymbol(target);
535 if (sym.undf()) try macho_file.addStubEntry(target);
444 .got => {
445 rel.getTargetSymbol(macho_file).flags.needs_got = true;
536446 },
537 .ARM64_RELOC_GOT_LOAD_PAGE21,
538 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
539 .ARM64_RELOC_POINTER_TO_GOT,
447
448 .tlv,
449 .tlvp_page,
450 .tlvp_pageoff,
540451 => {
541 // TODO rewrite relocation
542 try macho_file.addGotEntry(target);
452 const symbol = rel.getTargetSymbol(macho_file);
453 if (!symbol.flags.tlv) {
454 try macho_file.reportParseError2(
455 self.getFile(macho_file).getIndex(),
456 "{s}: illegal thread-local variable reference to regular symbol {s}",
457 .{ self.getName(macho_file), symbol.getName(macho_file) },
458 );
459 }
460 if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
461 symbol.flags.tlv_ptr = true;
462 if (symbol.flags.weak) {
463 macho_file.binds_to_weak = true;
464 }
465 }
543466 },
544 .ARM64_RELOC_TLVP_LOAD_PAGE21,
545 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
546 => {
547 const sym = macho_file.getSymbol(target);
548 if (sym.undf()) try macho_file.addTlvPtrEntry(target);
467
468 .unsigned => {
469 if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
470 if (rel.tag == .@"extern") {
471 const symbol = rel.getTargetSymbol(macho_file);
472 if (symbol.isTlvInit(macho_file)) {
473 macho_file.has_tlv = true;
474 continue;
475 }
476 if (symbol.flags.import) {
477 dynrel_ctx.bind_relocs += 1;
478 if (symbol.flags.weak) {
479 dynrel_ctx.weak_bind_relocs += 1;
480 macho_file.binds_to_weak = true;
481 }
482 continue;
483 }
484 if (symbol.flags.@"export" and symbol.flags.weak) {
485 dynrel_ctx.weak_bind_relocs += 1;
486 macho_file.binds_to_weak = true;
487 } else if (symbol.flags.interposable) {
488 dynrel_ctx.bind_relocs += 1;
489 }
490 }
491 dynrel_ctx.rebase_relocs += 1;
492 }
549493 },
550 else => {},
494
495 .signed,
496 .signed1,
497 .signed2,
498 .signed4,
499 .page,
500 .pageoff,
501 .subtractor,
502 => {},
551503 }
552504 }
553505}
554506
555fn scanAtomRelocsX86(
556 macho_file: *MachO,
557 atom_index: Index,
558 relocs: []align(1) const macho.relocation_info,
559) !void {
560 for (relocs) |rel| {
561 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
507fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
508 if (rel.tag == .local) return false;
562509
563 switch (rel_type) {
564 .X86_64_RELOC_SUBTRACTOR => continue,
565 else => {},
510 const sym = rel.getTargetSymbol(macho_file);
511 if (sym.getFile(macho_file) == null) {
512 const gpa = macho_file.base.comp.gpa;
513 const gop = try macho_file.undefs.getOrPut(gpa, rel.target);
514 if (!gop.found_existing) {
515 gop.value_ptr.* = .{};
566516 }
517 try gop.value_ptr.append(gpa, self.atom_index);
518 return true;
519 }
567520
568 if (rel.r_extern == 0) continue;
521 return false;
522}
569523
570 const atom = macho_file.getAtom(atom_index);
571 const object = &macho_file.objects.items[atom.getFile().?];
572 const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];
573 const sym_loc = SymbolWithLoc{
574 .sym_index = sym_index,
575 .file = atom.file,
576 };
524pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
525 const tracy = trace(@src());
526 defer tracy.end();
577527
578 const target = if (object.getGlobal(sym_index)) |global_index|
579 macho_file.globals.items[global_index]
580 else
581 sym_loc;
528 assert(!self.getInputSection(macho_file).isZerofill());
529 const file = self.getFile(macho_file);
530 const name = self.getName(macho_file);
531 const relocs = self.getRelocs(macho_file);
582532
583 switch (rel_type) {
584 .X86_64_RELOC_BRANCH => {
585 // TODO rewrite relocation
586 const sym = macho_file.getSymbol(target);
587 if (sym.undf()) try macho_file.addStubEntry(target);
588 },
589 .X86_64_RELOC_GOT, .X86_64_RELOC_GOT_LOAD => {
590 // TODO rewrite relocation
591 try macho_file.addGotEntry(target);
592 },
593 .X86_64_RELOC_TLV => {
594 const sym = macho_file.getSymbol(target);
595 if (sym.undf()) try macho_file.addTlvPtrEntry(target);
596 },
597 else => {},
598 }
599 }
600}
533 relocs_log.debug("{x}: {s}", .{ self.value, name });
601534
602pub fn resolveRelocs(
603 macho_file: *MachO,
604 atom_index: Index,
605 atom_code: []u8,
606 atom_relocs: []align(1) const macho.relocation_info,
607) !void {
608 const target = macho_file.base.comp.root_mod.resolved_target.result;
609 const arch = target.cpu.arch;
610 const atom = macho_file.getAtom(atom_index);
611 assert(atom.getFile() != null); // synthetic atoms do not have relocs
612
613 relocs_log.debug("resolving relocations in ATOM(%{d}, '{s}')", .{
614 atom.sym_index,
615 macho_file.getSymbolName(atom.getSymbolWithLoc()),
616 });
535 var stream = std.io.fixedBufferStream(buffer);
617536
618 const ctx = getRelocContext(macho_file, atom_index);
619
620 return switch (arch) {
621 .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx),
622 .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx),
623 else => unreachable,
624 };
625}
537 var i: usize = 0;
538 while (i < relocs.len) : (i += 1) {
539 const rel = relocs[i];
540 const rel_offset = rel.offset - self.off;
541 const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
626542
627pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) u64 {
628 const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse {
629 // If there is no atom for target, we still need to check for special, atom-less
630 // symbols such as `___dso_handle`.
631 const target_name = macho_file.getSymbolName(target);
632 const atomless_sym = macho_file.getSymbol(target);
633 log.debug(" | atomless target '{s}'", .{target_name});
634 return atomless_sym.n_value;
635 };
636 const target_atom = macho_file.getAtom(target_atom_index);
637 log.debug(" | target ATOM(%{d}, '{s}') in object({?})", .{
638 target_atom.sym_index,
639 macho_file.getSymbolName(target_atom.getSymbolWithLoc()),
640 target_atom.getFile(),
641 });
543 if (rel.tag == .@"extern") {
544 if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue;
545 }
642546
643 const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
644 assert(target_sym.n_desc != MachO.N_DEAD);
645
646 // If `target` is contained within the target atom, pull its address value.
647 const offset = if (target_atom.getFile() != null) blk: {
648 const object = macho_file.objects.items[target_atom.getFile().?];
649 break :blk if (object.getSourceSymbol(target.sym_index)) |_|
650 Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index)
651 else
652 0; // section alias
653 } else 0;
654 const base_address: u64 = if (is_tlv) base_address: {
655 // For TLV relocations, the value specified as a relocation is the displacement from the
656 // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first
657 // defined TLV template init section in the following order:
658 // * wrt to __thread_data if defined, then
659 // * wrt to __thread_bss
660 // TODO remember to check what the mechanism was prior to HAS_TLV_INITIALIZERS in earlier versions of macOS
661 const sect_id: u16 = sect_id: {
662 if (macho_file.thread_data_section_index) |i| {
663 break :sect_id i;
664 } else if (macho_file.thread_bss_section_index) |i| {
665 break :sect_id i;
666 } else break :base_address 0;
547 try stream.seekTo(rel_offset);
548 self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| {
549 switch (err) {
550 error.RelaxFail => {
551 try macho_file.reportParseError2(
552 file.getIndex(),
553 "{s}: 0x{x}: failed to relax relocation: in {s}",
554 .{ name, rel.offset, @tagName(rel.type) },
555 );
556 return error.ResolveFailed;
557 },
558 else => |e| return e,
559 }
667560 };
668 break :base_address macho_file.sections.items(.header)[sect_id].addr;
669 } else 0;
670 return target_sym.n_value + offset - base_address;
561 }
671562}
672563
673fn resolveRelocsArm64(
674 macho_file: *MachO,
675 atom_index: Index,
676 atom_code: []u8,
677 atom_relocs: []align(1) const macho.relocation_info,
678 context: RelocContext,
679) !void {
680 const atom = macho_file.getAtom(atom_index);
681 const object = macho_file.objects.items[atom.getFile().?];
682
683 var addend: ?i64 = null;
684 var subtractor: ?SymbolWithLoc = null;
564const ResolveError = error{
565 RelaxFail,
566 NoSpaceLeft,
567 DivisionByZero,
568 UnexpectedRemainder,
569 Overflow,
570};
685571
686 for (atom_relocs) |rel| {
687 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
572fn resolveRelocInner(
573 self: Atom,
574 rel: Relocation,
575 subtractor: ?Relocation,
576 code: []u8,
577 macho_file: *MachO,
578 writer: anytype,
579) ResolveError!void {
580 const cpu_arch = macho_file.getTarget().cpu.arch;
581 const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
582 const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
583 const seg = macho_file.segments.items[seg_id];
584 const P = @as(i64, @intCast(self.value)) + @as(i64, @intCast(rel_offset));
585 const A = rel.addend + rel.getRelocAddend(cpu_arch);
586 const S: i64 = @intCast(rel.getTargetAddress(macho_file));
587 const G: i64 = @intCast(rel.getGotTargetAddress(macho_file));
588 const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
589 const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0;
590 // Address of the __got_zig table entry if any.
591 const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file)));
592
593 switch (rel.tag) {
594 .local => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] atom({d})", .{
595 P,
596 rel_offset,
597 @tagName(rel.type),
598 S + A - SUB,
599 rel.getTargetAtom(macho_file).atom_index,
600 }),
601 .@"extern" => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] G({x}) ZG({x}) ({s})", .{
602 P,
603 rel_offset,
604 @tagName(rel.type),
605 S + A - SUB,
606 G + A,
607 ZIG_GOT + A,
608 rel.getTargetSymbol(macho_file).getName(macho_file),
609 }),
610 }
688611
689 switch (rel_type) {
690 .ARM64_RELOC_ADDEND => {
691 assert(addend == null);
612 switch (rel.type) {
613 .subtractor => {},
614
615 .unsigned => {
616 assert(!rel.meta.pcrel);
617 if (rel.meta.length == 3) {
618 if (rel.tag == .@"extern") {
619 const sym = rel.getTargetSymbol(macho_file);
620 if (sym.isTlvInit(macho_file)) {
621 try writer.writeInt(u64, @intCast(S - TLS), .little);
622 return;
623 }
624 const entry = bind.Entry{
625 .target = rel.target,
626 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
627 .segment_id = seg_id,
628 .addend = A,
629 };
630 if (sym.flags.import) {
631 macho_file.bind.entries.appendAssumeCapacity(entry);
632 if (sym.flags.weak) {
633 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
634 }
635 return;
636 }
637 if (sym.flags.@"export" and sym.flags.weak) {
638 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
639 } else if (sym.flags.interposable) {
640 macho_file.bind.entries.appendAssumeCapacity(entry);
641 }
642 }
643 macho_file.rebase.entries.appendAssumeCapacity(.{
644 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
645 .segment_id = seg_id,
646 });
647 try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
648 } else if (rel.meta.length == 2) {
649 try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
650 } else unreachable;
651 },
692652
693 relocs_log.debug(" RELA({s}) @ {x} => {x}", .{ @tagName(rel_type), rel.r_address, rel.r_symbolnum });
653 .got => {
654 assert(rel.tag == .@"extern");
655 assert(rel.meta.length == 2);
656 assert(rel.meta.pcrel);
657 try writer.writeInt(i32, @intCast(G + A - P), .little);
658 },
694659
695 addend = rel.r_symbolnum;
696 continue;
697 },
698 .ARM64_RELOC_SUBTRACTOR => {
699 assert(subtractor == null);
700
701 relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{
702 @tagName(rel_type),
703 rel.r_address,
704 rel.r_symbolnum,
705 atom.getFile(),
706 });
660 .branch => {
661 assert(rel.meta.length == 2);
662 assert(rel.meta.pcrel);
663 assert(rel.tag == .@"extern");
664
665 switch (cpu_arch) {
666 .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little),
667 .aarch64 => {
668 const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
669 const thunk = self.getThunk(macho_file);
670 const S_: i64 = @intCast(thunk.getAddress(rel.target));
671 break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
672 };
673 var inst = aarch64.Instruction{
674 .unconditional_branch_immediate = mem.bytesToValue(std.meta.TagPayload(
675 aarch64.Instruction,
676 aarch64.Instruction.unconditional_branch_immediate,
677 ), code[rel_offset..][0..4]),
678 };
679 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(disp >> 2))));
680 try writer.writeInt(u32, inst.toU32(), .little);
681 },
682 else => unreachable,
683 }
684 },
707685
708 subtractor = parseRelocTarget(macho_file, .{
709 .object_id = atom.getFile().?,
710 .rel = rel,
711 .code = atom_code,
712 .base_addr = context.base_addr,
713 .base_offset = context.base_offset,
714 });
715 continue;
716 },
717 else => {},
718 }
686 .got_load => {
687 assert(rel.tag == .@"extern");
688 assert(rel.meta.length == 2);
689 assert(rel.meta.pcrel);
690 if (rel.getTargetSymbol(macho_file).flags.has_got) {
691 try writer.writeInt(i32, @intCast(G + A - P), .little);
692 } else {
693 try x86_64.relaxGotLoad(code[rel_offset - 3 ..]);
694 try writer.writeInt(i32, @intCast(S + A - P), .little);
695 }
696 },
719697
720 const target = parseRelocTarget(macho_file, .{
721 .object_id = atom.getFile().?,
722 .rel = rel,
723 .code = atom_code,
724 .base_addr = context.base_addr,
725 .base_offset = context.base_offset,
726 });
727 const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset));
728
729 relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{
730 @tagName(rel_type),
731 rel.r_address,
732 target.sym_index,
733 macho_file.getSymbolName(target),
734 target.getFile(),
735 });
736
737 const source_addr = blk: {
738 const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
739 break :blk source_sym.n_value + rel_offset;
740 };
741 const target_addr = blk: {
742 if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
743 if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
744 break :blk macho_file.getTlvPtrEntryAddress(target).?;
745 if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
746 break :blk macho_file.getStubsEntryAddress(target).?;
747 const is_tlv = is_tlv: {
748 const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
749 const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
750 break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
751 };
752 break :blk getRelocTargetAddress(macho_file, target, is_tlv);
753 };
698 .zig_got_load => {
699 assert(rel.tag == .@"extern");
700 assert(rel.meta.length == 2);
701 assert(rel.meta.pcrel);
702 switch (cpu_arch) {
703 .x86_64 => try writer.writeInt(i32, @intCast(ZIG_GOT + A - P), .little),
704 .aarch64 => @panic("TODO resolve __got_zig indirection reloc"),
705 else => unreachable,
706 }
707 },
754708
755 relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});
709 .tlv => {
710 assert(rel.tag == .@"extern");
711 assert(rel.meta.length == 2);
712 assert(rel.meta.pcrel);
713 const sym = rel.getTargetSymbol(macho_file);
714 if (sym.flags.tlv_ptr) {
715 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
716 try writer.writeInt(i32, @intCast(S_ + A - P), .little);
717 } else {
718 try x86_64.relaxTlv(code[rel_offset - 3 ..]);
719 try writer.writeInt(i32, @intCast(S + A - P), .little);
720 }
721 },
756722
757 switch (rel_type) {
758 .ARM64_RELOC_BRANCH26 => {
759 relocs_log.debug(" source {s} (object({?})), target {s}", .{
760 macho_file.getSymbolName(atom.getSymbolWithLoc()),
761 atom.getFile(),
762 macho_file.getSymbolName(target),
763 });
723 .signed, .signed1, .signed2, .signed4 => {
724 assert(rel.meta.length == 2);
725 assert(rel.meta.pcrel);
726 try writer.writeInt(i32, @intCast(S + A - P), .little);
727 },
764728
765 const displacement = if (Relocation.calcPcRelativeDisplacementArm64(
766 source_addr,
767 target_addr,
768 )) |disp| blk: {
769 relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});
770 break :blk disp;
771 } else |_| blk: {
772 const thunk_index = macho_file.thunk_table.get(atom_index).?;
773 const thunk = macho_file.thunks.items[thunk_index];
774 const thunk_sym_loc = if (macho_file.getSymbol(target).undf())
775 thunk.getTrampoline(macho_file, .stub, target).?
776 else
777 thunk.getTrampoline(macho_file, .atom, target).?;
778 const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value;
779 relocs_log.debug(" | target_addr = 0x{x} (thunk)", .{thunk_addr});
780 break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr);
729 .page,
730 .got_load_page,
731 .tlvp_page,
732 => {
733 assert(rel.tag == .@"extern");
734 assert(rel.meta.length == 2);
735 assert(rel.meta.pcrel);
736 const sym = rel.getTargetSymbol(macho_file);
737 const source = math.cast(u64, P) orelse return error.Overflow;
738 const target = target: {
739 const target = switch (rel.type) {
740 .page => S + A,
741 .got_load_page => G + A,
742 .tlvp_page => if (sym.flags.tlv_ptr) blk: {
743 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
744 break :blk S_ + A;
745 } else S + A,
746 else => unreachable,
781747 };
748 break :target math.cast(u64, target) orelse return error.Overflow;
749 };
750 const pages = @as(u21, @bitCast(try Relocation.calcNumberOfPages(source, target)));
751 var inst = aarch64.Instruction{
752 .pc_relative_address = mem.bytesToValue(std.meta.TagPayload(
753 aarch64.Instruction,
754 aarch64.Instruction.pc_relative_address,
755 ), code[rel_offset..][0..4]),
756 };
757 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
758 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
759 try writer.writeInt(u32, inst.toU32(), .little);
760 },
782761
783 const code = atom_code[rel_offset..][0..4];
762 .pageoff => {
763 assert(rel.tag == .@"extern");
764 assert(rel.meta.length == 2);
765 assert(!rel.meta.pcrel);
766 const target = math.cast(u64, S + A) orelse return error.Overflow;
767 const inst_code = code[rel_offset..][0..4];
768 if (Relocation.isArithmeticOp(inst_code)) {
769 const off = try Relocation.calcPageOffset(target, .arithmetic);
784770 var inst = aarch64.Instruction{
785 .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
771 .add_subtract_immediate = mem.bytesToValue(std.meta.TagPayload(
786772 aarch64.Instruction,
787 aarch64.Instruction.unconditional_branch_immediate,
788 ), code),
773 aarch64.Instruction.add_subtract_immediate,
774 ), inst_code),
789775 };
790 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
791 mem.writeInt(u32, code, inst.toU32(), .little);
792 },
793
794 .ARM64_RELOC_PAGE21,
795 .ARM64_RELOC_GOT_LOAD_PAGE21,
796 .ARM64_RELOC_TLVP_LOAD_PAGE21,
797 => {
798 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
799
800 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
801
802 const pages = @as(u21, @bitCast(Relocation.calcNumberOfPages(source_addr, adjusted_target_addr)));
803 const code = atom_code[rel_offset..][0..4];
776 inst.add_subtract_immediate.imm12 = off;
777 try writer.writeInt(u32, inst.toU32(), .little);
778 } else {
804779 var inst = aarch64.Instruction{
805 .pc_relative_address = mem.bytesToValue(meta.TagPayload(
780 .load_store_register = mem.bytesToValue(std.meta.TagPayload(
806781 aarch64.Instruction,
807 aarch64.Instruction.pc_relative_address,
808 ), code),
782 aarch64.Instruction.load_store_register,
783 ), inst_code),
809784 };
810 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
811 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
812 mem.writeInt(u32, code, inst.toU32(), .little);
813 addend = null;
814 },
785 const off = try Relocation.calcPageOffset(target, switch (inst.load_store_register.size) {
786 0 => if (inst.load_store_register.v == 1)
787 Relocation.PageOffsetInstKind.load_store_128
788 else
789 Relocation.PageOffsetInstKind.load_store_8,
790 1 => .load_store_16,
791 2 => .load_store_32,
792 3 => .load_store_64,
793 });
794 inst.load_store_register.offset = off;
795 try writer.writeInt(u32, inst.toU32(), .little);
796 }
797 },
815798
816 .ARM64_RELOC_PAGEOFF12 => {
817 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
799 .got_load_pageoff => {
800 assert(rel.tag == .@"extern");
801 assert(rel.meta.length == 2);
802 assert(!rel.meta.pcrel);
803 const target = math.cast(u64, G + A) orelse return error.Overflow;
804 const off = try Relocation.calcPageOffset(target, .load_store_64);
805 var inst: aarch64.Instruction = .{
806 .load_store_register = mem.bytesToValue(std.meta.TagPayload(
807 aarch64.Instruction,
808 aarch64.Instruction.load_store_register,
809 ), code[rel_offset..][0..4]),
810 };
811 inst.load_store_register.offset = off;
812 try writer.writeInt(u32, inst.toU32(), .little);
813 },
818814
819 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
815 .tlvp_pageoff => {
816 assert(rel.tag == .@"extern");
817 assert(rel.meta.length == 2);
818 assert(!rel.meta.pcrel);
819
820 const sym = rel.getTargetSymbol(macho_file);
821 const target = target: {
822 const target = if (sym.flags.tlv_ptr) blk: {
823 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
824 break :blk S_ + A;
825 } else S + A;
826 break :target math.cast(u64, target) orelse return error.Overflow;
827 };
820828
821 const code = atom_code[rel_offset..][0..4];
822 if (Relocation.isArithmeticOp(code)) {
823 const off = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic);
824 var inst = aarch64.Instruction{
825 .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
826 aarch64.Instruction,
827 aarch64.Instruction.add_subtract_immediate,
828 ), code),
829 const RegInfo = struct {
830 rd: u5,
831 rn: u5,
832 size: u2,
833 };
834
835 const inst_code = code[rel_offset..][0..4];
836 const reg_info: RegInfo = blk: {
837 if (Relocation.isArithmeticOp(inst_code)) {
838 const inst = mem.bytesToValue(std.meta.TagPayload(
839 aarch64.Instruction,
840 aarch64.Instruction.add_subtract_immediate,
841 ), inst_code);
842 break :blk .{
843 .rd = inst.rd,
844 .rn = inst.rn,
845 .size = inst.sf,
829846 };
830 inst.add_subtract_immediate.imm12 = off;
831 mem.writeInt(u32, code, inst.toU32(), .little);
832847 } else {
833 var inst = aarch64.Instruction{
834 .load_store_register = mem.bytesToValue(meta.TagPayload(
835 aarch64.Instruction,
836 aarch64.Instruction.load_store_register,
837 ), code),
838 };
839 const off = try Relocation.calcPageOffset(adjusted_target_addr, switch (inst.load_store_register.size) {
840 0 => if (inst.load_store_register.v == 1)
841 Relocation.PageOffsetInstKind.load_store_128
842 else
843 Relocation.PageOffsetInstKind.load_store_8,
844 1 => .load_store_16,
845 2 => .load_store_32,
846 3 => .load_store_64,
847 });
848 inst.load_store_register.offset = off;
849 mem.writeInt(u32, code, inst.toU32(), .little);
850 }
851 addend = null;
852 },
853
854 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
855 const code = atom_code[rel_offset..][0..4];
856 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
857
858 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
859
860 const off = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64);
861 var inst: aarch64.Instruction = .{
862 .load_store_register = mem.bytesToValue(meta.TagPayload(
848 const inst = mem.bytesToValue(std.meta.TagPayload(
863849 aarch64.Instruction,
864850 aarch64.Instruction.load_store_register,
865 ), code),
866 };
867 inst.load_store_register.offset = off;
868 mem.writeInt(u32, code, inst.toU32(), .little);
869 addend = null;
870 },
871
872 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
873 const code = atom_code[rel_offset..][0..4];
874 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));
875
876 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
851 ), inst_code);
852 break :blk .{
853 .rd = inst.rt,
854 .rn = inst.rn,
855 .size = inst.size,
856 };
857 }
858 };
877859
878 const RegInfo = struct {
879 rd: u5,
880 rn: u5,
881 size: u2,
882 };
883 const reg_info: RegInfo = blk: {
884 if (Relocation.isArithmeticOp(code)) {
885 const inst = mem.bytesToValue(meta.TagPayload(
886 aarch64.Instruction,
887 aarch64.Instruction.add_subtract_immediate,
888 ), code);
889 break :blk .{
890 .rd = inst.rd,
891 .rn = inst.rn,
892 .size = inst.sf,
893 };
894 } else {
895 const inst = mem.bytesToValue(meta.TagPayload(
896 aarch64.Instruction,
897 aarch64.Instruction.load_store_register,
898 ), code);
899 break :blk .{
900 .rd = inst.rt,
901 .rn = inst.rn,
902 .size = inst.size,
903 };
904 }
905 };
860 var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{
861 .load_store_register = .{
862 .rt = reg_info.rd,
863 .rn = reg_info.rn,
864 .offset = try Relocation.calcPageOffset(target, .load_store_64),
865 .opc = 0b01,
866 .op1 = 0b01,
867 .v = 0,
868 .size = reg_info.size,
869 },
870 } else aarch64.Instruction{
871 .add_subtract_immediate = .{
872 .rd = reg_info.rd,
873 .rn = reg_info.rn,
874 .imm12 = try Relocation.calcPageOffset(target, .arithmetic),
875 .sh = 0,
876 .s = 0,
877 .op = 0,
878 .sf = @as(u1, @truncate(reg_info.size)),
879 },
880 };
881 try writer.writeInt(u32, inst.toU32(), .little);
882 },
883 }
884}
906885
907 var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{
908 .load_store_register = .{
909 .rt = reg_info.rd,
910 .rn = reg_info.rn,
911 .offset = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64),
912 .opc = 0b01,
913 .op1 = 0b01,
914 .v = 0,
915 .size = reg_info.size,
916 },
917 } else aarch64.Instruction{
918 .add_subtract_immediate = .{
919 .rd = reg_info.rd,
920 .rn = reg_info.rn,
921 .imm12 = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic),
922 .sh = 0,
923 .s = 0,
924 .op = 0,
925 .sf = @as(u1, @truncate(reg_info.size)),
926 },
927 };
928 mem.writeInt(u32, code, inst.toU32(), .little);
929 addend = null;
886const x86_64 = struct {
887 fn relaxGotLoad(code: []u8) error{RelaxFail}!void {
888 const old_inst = disassemble(code) orelse return error.RelaxFail;
889 switch (old_inst.encoding.mnemonic) {
890 .mov => {
891 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
892 relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
893 encode(&.{inst}, code) catch return error.RelaxFail;
930894 },
895 else => return error.RelaxFail,
896 }
897 }
931898
932 .ARM64_RELOC_POINTER_TO_GOT => {
933 relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});
934 const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
935 return error.Overflow;
936 mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @bitCast(result)), .little);
899 fn relaxTlv(code: []u8) error{RelaxFail}!void {
900 const old_inst = disassemble(code) orelse return error.RelaxFail;
901 switch (old_inst.encoding.mnemonic) {
902 .mov => {
903 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
904 relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
905 encode(&.{inst}, code) catch return error.RelaxFail;
937906 },
907 else => return error.RelaxFail,
908 }
909 }
938910
939 .ARM64_RELOC_UNSIGNED => {
940 var ptr_addend = if (rel.r_length == 3)
941 mem.readInt(i64, atom_code[rel_offset..][0..8], .little)
942 else
943 mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
911 fn disassemble(code: []const u8) ?Instruction {
912 var disas = Disassembler.init(code);
913 const inst = disas.next() catch return null;
914 return inst;
915 }
944916
945 if (rel.r_extern == 0) {
946 const base_addr = if (target.sym_index >= object.source_address_lookup.len)
947 @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
948 else
949 object.source_address_lookup[target.sym_index];
950 ptr_addend -= base_addr;
951 }
917 fn encode(insts: []const Instruction, code: []u8) !void {
918 var stream = std.io.fixedBufferStream(code);
919 const writer = stream.writer();
920 for (insts) |inst| {
921 try inst.encode(writer, .{});
922 }
923 }
952924
953 const result = blk: {
954 if (subtractor) |sub| {
955 const sym = macho_file.getSymbol(sub);
956 break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend;
957 } else {
958 break :blk @as(i64, @intCast(target_addr)) + ptr_addend;
959 }
960 };
961 relocs_log.debug(" | target_addr = 0x{x}", .{result});
925 const bits = @import("../../arch/x86_64/bits.zig");
926 const encoder = @import("../../arch/x86_64/encoder.zig");
927 const Disassembler = @import("../../arch/x86_64/Disassembler.zig");
928 const Immediate = bits.Immediate;
929 const Instruction = encoder.Instruction;
930};
962931
963 if (rel.r_length == 3) {
964 mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);
965 } else {
966 mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little);
932pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
933 const relocs = self.getRelocs(macho_file);
934 switch (macho_file.getTarget().cpu.arch) {
935 .aarch64 => {
936 var nreloc: u32 = 0;
937 for (relocs) |rel| {
938 nreloc += 1;
939 switch (rel.type) {
940 .page, .pageoff => if (rel.addend > 0) {
941 nreloc += 1;
942 },
943 else => {},
967944 }
968
969 subtractor = null;
970 },
971
972 .ARM64_RELOC_ADDEND => unreachable,
973 .ARM64_RELOC_SUBTRACTOR => unreachable,
974 }
945 }
946 return nreloc;
947 },
948 .x86_64 => return @intCast(relocs.len),
949 else => unreachable,
975950 }
976951}
977952
978fn resolveRelocsX86(
979 macho_file: *MachO,
980 atom_index: Index,
981 atom_code: []u8,
982 atom_relocs: []align(1) const macho.relocation_info,
983 context: RelocContext,
984) !void {
985 const atom = macho_file.getAtom(atom_index);
986 const object = macho_file.objects.items[atom.getFile().?];
987
988 var subtractor: ?SymbolWithLoc = null;
989
990 for (atom_relocs) |rel| {
991 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
992
993 switch (rel_type) {
994 .X86_64_RELOC_SUBTRACTOR => {
995 assert(subtractor == null);
953pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void {
954 const tracy = trace(@src());
955 defer tracy.end();
996956
997 relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{
998 @tagName(rel_type),
999 rel.r_address,
1000 rel.r_symbolnum,
1001 atom.getFile(),
1002 });
957 const cpu_arch = macho_file.getTarget().cpu.arch;
958 const relocs = self.getRelocs(macho_file);
959 const sect = macho_file.sections.items(.header)[self.out_n_sect];
960 var stream = std.io.fixedBufferStream(code);
1003961
1004 subtractor = parseRelocTarget(macho_file, .{
1005 .object_id = atom.getFile().?,
1006 .rel = rel,
1007 .code = atom_code,
1008 .base_addr = context.base_addr,
1009 .base_offset = context.base_offset,
1010 });
1011 continue;
1012 },
1013 else => {},
1014 }
1015
1016 const target = parseRelocTarget(macho_file, .{
1017 .object_id = atom.getFile().?,
1018 .rel = rel,
1019 .code = atom_code,
1020 .base_addr = context.base_addr,
1021 .base_offset = context.base_offset,
1022 });
1023 const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset));
1024
1025 relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{
1026 @tagName(rel_type),
1027 rel.r_address,
1028 target.sym_index,
1029 macho_file.getSymbolName(target),
1030 target.getFile(),
1031 });
1032
1033 const source_addr = blk: {
1034 const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
1035 break :blk source_sym.n_value + rel_offset;
1036 };
1037 const target_addr = blk: {
1038 if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;
1039 if (relocIsStub(macho_file, rel) and macho_file.getSymbol(target).undf())
1040 break :blk macho_file.getStubsEntryAddress(target).?;
1041 if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())
1042 break :blk macho_file.getTlvPtrEntryAddress(target).?;
1043 const is_tlv = is_tlv: {
1044 const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
1045 const header = macho_file.sections.items(.header)[source_sym.n_sect - 1];
1046 break :is_tlv header.type() == macho.S_THREAD_LOCAL_VARIABLES;
962 for (relocs) |rel| {
963 const rel_offset = rel.offset - self.off;
964 const r_address: i32 = math.cast(i32, self.value + rel_offset - sect.addr) orelse return error.Overflow;
965 const r_symbolnum = r_symbolnum: {
966 const r_symbolnum: u32 = switch (rel.tag) {
967 .local => rel.getTargetAtom(macho_file).out_n_sect + 1,
968 .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?,
1047969 };
1048 break :blk getRelocTargetAddress(macho_file, target, is_tlv);
970 break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow;
1049971 };
972 const r_extern = rel.tag == .@"extern";
973 var addend = rel.addend + rel.getRelocAddend(cpu_arch);
974 if (rel.tag == .local) {
975 const target: i64 = @intCast(rel.getTargetAddress(macho_file));
976 addend += target;
977 }
1050978
1051 relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});
1052
1053 switch (rel_type) {
1054 .X86_64_RELOC_BRANCH => {
1055 const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
1056 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
1057 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
1058 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
1059 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
1060 },
1061
1062 .X86_64_RELOC_GOT,
1063 .X86_64_RELOC_GOT_LOAD,
1064 => {
1065 const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
1066 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
1067 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
1068 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
1069 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
1070 },
1071
1072 .X86_64_RELOC_TLV => {
1073 const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
1074 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
1075 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
1076 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
1077
1078 if (macho_file.tlv_ptr_table.lookup.get(target) == null) {
1079 // We need to rewrite the opcode from movq to leaq.
1080 atom_code[rel_offset - 2] = 0x8d;
979 try stream.seekTo(rel_offset);
980
981 switch (cpu_arch) {
982 .aarch64 => {
983 if (rel.type == .unsigned) switch (rel.meta.length) {
984 0, 1 => unreachable,
985 2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
986 3 => try stream.writer().writeInt(i64, addend, .little),
987 } else if (addend > 0) {
988 buffer.appendAssumeCapacity(.{
989 .r_address = r_address,
990 .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow),
991 .r_pcrel = 0,
992 .r_length = 2,
993 .r_extern = 0,
994 .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND),
995 });
1081996 }
1082997
1083 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
1084 },
1085
1086 .X86_64_RELOC_SIGNED,
1087 .X86_64_RELOC_SIGNED_1,
1088 .X86_64_RELOC_SIGNED_2,
1089 .X86_64_RELOC_SIGNED_4,
1090 => {
1091 const correction: u3 = switch (rel_type) {
1092 .X86_64_RELOC_SIGNED => 0,
1093 .X86_64_RELOC_SIGNED_1 => 1,
1094 .X86_64_RELOC_SIGNED_2 => 2,
1095 .X86_64_RELOC_SIGNED_4 => 4,
1096 else => unreachable,
998 const r_type: macho.reloc_type_arm64 = switch (rel.type) {
999 .page => .ARM64_RELOC_PAGE21,
1000 .pageoff => .ARM64_RELOC_PAGEOFF12,
1001 .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21,
1002 .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
1003 .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21,
1004 .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
1005 .branch => .ARM64_RELOC_BRANCH26,
1006 .got => .ARM64_RELOC_POINTER_TO_GOT,
1007 .subtractor => .ARM64_RELOC_SUBTRACTOR,
1008 .unsigned => .ARM64_RELOC_UNSIGNED,
1009
1010 .zig_got_load,
1011 .signed,
1012 .signed1,
1013 .signed2,
1014 .signed4,
1015 .got_load,
1016 .tlv,
1017 => unreachable,
10971018 };
1098 var addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little) + correction;
1099
1100 if (rel.r_extern == 0) {
1101 const base_addr = if (target.sym_index >= object.source_address_lookup.len)
1102 @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
1103 else
1104 object.source_address_lookup[target.sym_index];
1105 addend += @as(i32, @intCast(@as(i64, @intCast(context.base_addr)) + rel.r_address + 4 -
1106 @as(i64, @intCast(base_addr))));
1107 }
1108
1109 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
1110
1111 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});
1112
1113 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, correction);
1114 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);
1019 buffer.appendAssumeCapacity(.{
1020 .r_address = r_address,
1021 .r_symbolnum = r_symbolnum,
1022 .r_pcrel = @intFromBool(rel.meta.pcrel),
1023 .r_extern = @intFromBool(r_extern),
1024 .r_length = rel.meta.length,
1025 .r_type = @intFromEnum(r_type),
1026 });
11151027 },
1116
1117 .X86_64_RELOC_UNSIGNED => {
1118 var addend = if (rel.r_length == 3)
1119 mem.readInt(i64, atom_code[rel_offset..][0..8], .little)
1120 else
1121 mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
1122
1123 if (rel.r_extern == 0) {
1124 const base_addr = if (target.sym_index >= object.source_address_lookup.len)
1125 @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
1126 else
1127 object.source_address_lookup[target.sym_index];
1128 addend -= base_addr;
1129 }
1130
1131 const result = blk: {
1132 if (subtractor) |sub| {
1133 const sym = macho_file.getSymbol(sub);
1134 break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + addend;
1028 .x86_64 => {
1029 if (rel.meta.pcrel) {
1030 if (rel.tag == .local) {
1031 addend -= @as(i64, @intCast(self.value + rel_offset));
11351032 } else {
1136 break :blk @as(i64, @intCast(target_addr)) + addend;
1033 addend += 4;
11371034 }
1138 };
1139 relocs_log.debug(" | target_addr = 0x{x}", .{result});
1140
1141 if (rel.r_length == 3) {
1142 mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);
1143 } else {
1144 mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little);
1035 }
1036 switch (rel.meta.length) {
1037 0, 1 => unreachable,
1038 2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
1039 3 => try stream.writer().writeInt(i64, addend, .little),
11451040 }
11461041
1147 subtractor = null;
1042 const r_type: macho.reloc_type_x86_64 = switch (rel.type) {
1043 .signed => .X86_64_RELOC_SIGNED,
1044 .signed1 => .X86_64_RELOC_SIGNED_1,
1045 .signed2 => .X86_64_RELOC_SIGNED_2,
1046 .signed4 => .X86_64_RELOC_SIGNED_4,
1047 .got_load => .X86_64_RELOC_GOT_LOAD,
1048 .tlv => .X86_64_RELOC_TLV,
1049 .branch => .X86_64_RELOC_BRANCH,
1050 .got => .X86_64_RELOC_GOT,
1051 .subtractor => .X86_64_RELOC_SUBTRACTOR,
1052 .unsigned => .X86_64_RELOC_UNSIGNED,
1053
1054 .zig_got_load,
1055 .page,
1056 .pageoff,
1057 .got_load_page,
1058 .got_load_pageoff,
1059 .tlvp_page,
1060 .tlvp_pageoff,
1061 => unreachable,
1062 };
1063 buffer.appendAssumeCapacity(.{
1064 .r_address = r_address,
1065 .r_symbolnum = r_symbolnum,
1066 .r_pcrel = @intFromBool(rel.meta.pcrel),
1067 .r_extern = @intFromBool(r_extern),
1068 .r_length = rel.meta.length,
1069 .r_type = @intFromEnum(r_type),
1070 });
11481071 },
1149
1150 .X86_64_RELOC_SUBTRACTOR => unreachable,
1072 else => unreachable,
11511073 }
11521074 }
11531075}
11541076
1155pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 {
1156 const atom = macho_file.getAtom(atom_index);
1157 assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code.
1158 const object = macho_file.objects.items[atom.getFile().?];
1159 const source_sym = object.getSourceSymbol(atom.sym_index) orelse {
1160 // If there was no matching symbol present in the source symtab, this means
1161 // we are dealing with either an entire section, or part of it, but also
1162 // starting at the beginning.
1163 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
1164 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
1165 const source_sect = object.getSourceSection(sect_id);
1166 assert(!source_sect.isZerofill());
1167 const code = object.getSectionContents(source_sect);
1168 const code_len = @as(usize, @intCast(atom.size));
1169 return code[0..code_len];
1170 };
1171 const source_sect = object.getSourceSection(source_sym.n_sect - 1);
1172 assert(!source_sect.isZerofill());
1173 const code = object.getSectionContents(source_sect);
1174 const offset = @as(usize, @intCast(source_sym.n_value - source_sect.addr));
1175 const code_len = @as(usize, @intCast(atom.size));
1176 return code[offset..][0..code_len];
1077pub fn format(
1078 atom: Atom,
1079 comptime unused_fmt_string: []const u8,
1080 options: std.fmt.FormatOptions,
1081 writer: anytype,
1082) !void {
1083 _ = atom;
1084 _ = unused_fmt_string;
1085 _ = options;
1086 _ = writer;
1087 @compileError("do not format Atom directly");
11771088}
11781089
1179pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info {
1180 const atom = macho_file.getAtom(atom_index);
1181 assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs.
1182 const object = macho_file.objects.items[atom.getFile().?];
1183 const cache = object.relocs_lookup[atom.sym_index];
1184
1185 const source_sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
1186 break :blk source_sym.n_sect - 1;
1187 } else blk: {
1188 // If there was no matching symbol present in the source symtab, this means
1189 // we are dealing with either an entire section, or part of it, but also
1190 // starting at the beginning.
1191 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
1192 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
1193 break :blk sect_id;
1194 };
1195 const source_sect = object.getSourceSection(source_sect_id);
1196 assert(!source_sect.isZerofill());
1197 const relocs = object.getRelocs(source_sect_id);
1198 return relocs[cache.start..][0..cache.len];
1090pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) {
1091 return .{ .data = .{
1092 .atom = atom,
1093 .macho_file = macho_file,
1094 } };
11991095}
12001096
1201pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool {
1202 const target = macho_file.base.comp.root_mod.resolved_target.result;
1203 switch (target.cpu.arch) {
1204 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
1205 .ARM64_RELOC_GOT_LOAD_PAGE21,
1206 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
1207 .ARM64_RELOC_POINTER_TO_GOT,
1208 => return true,
1209 else => return false,
1210 },
1211 .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
1212 .X86_64_RELOC_GOT,
1213 .X86_64_RELOC_GOT_LOAD,
1214 => return true,
1215 else => return false,
1216 },
1217 else => unreachable,
1218 }
1219}
1097const FormatContext = struct {
1098 atom: Atom,
1099 macho_file: *MachO,
1100};
12201101
1221pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool {
1222 const target = macho_file.base.comp.root_mod.resolved_target.result;
1223 switch (target.cpu.arch) {
1224 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
1225 .ARM64_RELOC_TLVP_LOAD_PAGE21,
1226 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
1227 => return true,
1228 else => return false,
1229 },
1230 .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
1231 .X86_64_RELOC_TLV => return true,
1232 else => return false,
1233 },
1234 else => unreachable,
1102fn format2(
1103 ctx: FormatContext,
1104 comptime unused_fmt_string: []const u8,
1105 options: std.fmt.FormatOptions,
1106 writer: anytype,
1107) !void {
1108 _ = options;
1109 _ = unused_fmt_string;
1110 const atom = ctx.atom;
1111 const macho_file = ctx.macho_file;
1112 try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{
1113 atom.atom_index, atom.getName(macho_file), atom.value,
1114 atom.out_n_sect, atom.alignment, atom.size,
1115 atom.thunk_index,
1116 });
1117 if (!atom.flags.alive) try writer.writeAll(" : [*]");
1118 if (atom.unwind_records.len > 0) {
1119 try writer.writeAll(" : unwind{ ");
1120 for (atom.getUnwindRecords(macho_file), atom.unwind_records.pos..) |index, i| {
1121 const rec = macho_file.getUnwindRecord(index);
1122 try writer.print("{d}", .{index});
1123 if (!rec.alive) try writer.writeAll("([*])");
1124 if (i < atom.unwind_records.pos + atom.unwind_records.len - 1) try writer.writeAll(", ");
1125 }
1126 try writer.writeAll(" }");
12351127 }
12361128}
12371129
1238pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool {
1239 const target = macho_file.base.comp.root_mod.resolved_target.result;
1240 switch (target.cpu.arch) {
1241 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
1242 .ARM64_RELOC_BRANCH26 => return true,
1243 else => return false,
1244 },
1245 .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
1246 .X86_64_RELOC_BRANCH => return true,
1247 else => return false,
1248 },
1249 else => unreachable,
1250 }
1251}
1130pub const Index = u32;
12521131
1253const Atom = @This();
1132pub const Flags = packed struct {
1133 /// Specifies whether this atom is alive or has been garbage collected.
1134 alive: bool = true,
1135
1136 /// Specifies if the atom has been visited during garbage collection.
1137 visited: bool = false,
1138};
1139
1140pub const Loc = struct {
1141 pos: u32 = 0,
1142 len: u32 = 0,
1143};
1144
1145pub const Alignment = @import("../../InternPool.zig").Alignment;
12541146
1255const std = @import("std");
1256const build_options = @import("build_options");
12571147const aarch64 = @import("../../arch/aarch64/bits.zig");
12581148const assert = std.debug.assert;
1259const log = std.log.scoped(.link);
1260const relocs_log = std.log.scoped(.link_relocs);
1149const bind = @import("dyld_info/bind.zig");
12611150const macho = std.macho;
12621151const math = std.math;
12631152const mem = std.mem;
1264const meta = std.meta;
1153const log = std.log.scoped(.link);
1154const relocs_log = std.log.scoped(.link_relocs);
1155const std = @import("std");
12651156const trace = @import("../../tracy.zig").trace;
12661157
12671158const Allocator = mem.Allocator;
1268const Arch = std.Target.Cpu.Arch;
1159const Atom = @This();
1160const File = @import("file.zig").File;
12691161const MachO = @import("../MachO.zig");
1270pub const Relocation = @import("Relocation.zig");
1271const SymbolWithLoc = MachO.SymbolWithLoc;
1162const Object = @import("Object.zig");
1163const Relocation = @import("Relocation.zig");
1164const Symbol = @import("Symbol.zig");
1165const Thunk = @import("thunks.zig").Thunk;
1166const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/CodeSignature.zig+183-184
......@@ -1,175 +1,16 @@
1page_size: u16,
2code_directory: CodeDirectory,
3requirements: ?Requirements = null,
4entitlements: ?Entitlements = null,
5signature: ?Signature = null,
6
7pub fn init(page_size: u16) CodeSignature {
8 return .{
9 .page_size = page_size,
10 .code_directory = CodeDirectory.init(page_size),
11 };
12}
13
14pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
15 self.code_directory.deinit(allocator);
16 if (self.requirements) |*req| {
17 req.deinit(allocator);
18 }
19 if (self.entitlements) |*ents| {
20 ents.deinit(allocator);
21 }
22 if (self.signature) |*sig| {
23 sig.deinit(allocator);
24 }
25}
26
27pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
28 const file = try fs.cwd().openFile(path, .{});
29 defer file.close();
30 const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
31 self.entitlements = .{ .inner = inner };
32}
33
34pub const WriteOpts = struct {
35 file: fs.File,
36 exec_seg_base: u64,
37 exec_seg_limit: u64,
38 file_size: u32,
39 output_mode: std.builtin.OutputMode,
40};
41
42pub fn writeAdhocSignature(
43 self: *CodeSignature,
44 comp: *const Compilation,
45 opts: WriteOpts,
46 writer: anytype,
47) !void {
48 const gpa = comp.gpa;
49
50 var header: macho.SuperBlob = .{
51 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
52 .length = @sizeOf(macho.SuperBlob),
53 .count = 0,
54 };
55
56 var blobs = std.ArrayList(Blob).init(gpa);
57 defer blobs.deinit();
58
59 self.code_directory.inner.execSegBase = opts.exec_seg_base;
60 self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
61 self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
62 self.code_directory.inner.codeLimit = opts.file_size;
63
64 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
65
66 try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
67 self.code_directory.code_slots.items.len = total_pages;
68 self.code_directory.inner.nCodeSlots = total_pages;
69
70 // Calculate hash for each page (in file) and write it to the buffer
71 var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
72 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
73 .chunk_size = self.page_size,
74 .max_file_size = opts.file_size,
75 });
76
77 try blobs.append(.{ .code_directory = &self.code_directory });
78 header.length += @sizeOf(macho.BlobIndex);
79 header.count += 1;
80
81 var hash: [hash_size]u8 = undefined;
82
83 if (self.requirements) |*req| {
84 var buf = std.ArrayList(u8).init(gpa);
85 defer buf.deinit();
86 try req.write(buf.writer());
87 Sha256.hash(buf.items, &hash, .{});
88 self.code_directory.addSpecialHash(req.slotType(), hash);
89
90 try blobs.append(.{ .requirements = req });
91 header.count += 1;
92 header.length += @sizeOf(macho.BlobIndex) + req.size();
93 }
94
95 if (self.entitlements) |*ents| {
96 var buf = std.ArrayList(u8).init(gpa);
97 defer buf.deinit();
98 try ents.write(buf.writer());
99 Sha256.hash(buf.items, &hash, .{});
100 self.code_directory.addSpecialHash(ents.slotType(), hash);
101
102 try blobs.append(.{ .entitlements = ents });
103 header.count += 1;
104 header.length += @sizeOf(macho.BlobIndex) + ents.size();
105 }
106
107 if (self.signature) |*sig| {
108 try blobs.append(.{ .signature = sig });
109 header.count += 1;
110 header.length += @sizeOf(macho.BlobIndex) + sig.size();
111 }
112
113 self.code_directory.inner.hashOffset =
114 @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
115 self.code_directory.inner.length = self.code_directory.size();
116 header.length += self.code_directory.size();
117
118 try writer.writeInt(u32, header.magic, .big);
119 try writer.writeInt(u32, header.length, .big);
120 try writer.writeInt(u32, header.count, .big);
121
122 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
123 for (blobs.items) |blob| {
124 try writer.writeInt(u32, blob.slotType(), .big);
125 try writer.writeInt(u32, offset, .big);
126 offset += blob.size();
127 }
128
129 for (blobs.items) |blob| {
130 try blob.write(writer);
131 }
132}
133
134pub fn size(self: CodeSignature) u32 {
135 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
136 if (self.requirements) |req| {
137 ssize += @sizeOf(macho.BlobIndex) + req.size();
138 }
139 if (self.entitlements) |ent| {
140 ssize += @sizeOf(macho.BlobIndex) + ent.size();
141 }
142 if (self.signature) |sig| {
143 ssize += @sizeOf(macho.BlobIndex) + sig.size();
144 }
145 return ssize;
146}
147
148pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
149 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
150 // Approx code slots
151 const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
152 ssize += total_pages * hash_size;
153 var n_special_slots: u32 = 0;
154 if (self.requirements) |req| {
155 ssize += @sizeOf(macho.BlobIndex) + req.size();
156 n_special_slots = @max(n_special_slots, req.slotType());
157 }
158 if (self.entitlements) |ent| {
159 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
160 n_special_slots = @max(n_special_slots, ent.slotType());
161 }
162 if (self.signature) |sig| {
163 ssize += @sizeOf(macho.BlobIndex) + sig.size();
164 }
165 ssize += n_special_slots * hash_size;
166 return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
167}
1const CodeSignature = @This();
1682
169pub fn clear(self: *CodeSignature, allocator: Allocator) void {
170 self.code_directory.deinit(allocator);
171 self.code_directory = CodeDirectory.init(self.page_size);
172}
3const std = @import("std");
4const assert = std.debug.assert;
5const fs = std.fs;
6const log = std.log.scoped(.link);
7const macho = std.macho;
8const mem = std.mem;
9const testing = std.testing;
10const Allocator = mem.Allocator;
11const Hasher = @import("hasher.zig").ParallelHasher;
12const MachO = @import("../MachO.zig");
13const Sha256 = std.crypto.hash.sha2.Sha256;
17314
17415const hash_size = Sha256.digest_length;
17516
......@@ -257,7 +98,7 @@ const CodeDirectory = struct {
25798 fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
25899 assert(index > 0);
259100 self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);
260 self.special_slots[index - 1] = hash;
101 @memcpy(&self.special_slots[index - 1], &hash);
261102 }
262103
263104 fn slotType(self: CodeDirectory) u32 {
......@@ -376,17 +217,175 @@ const Signature = struct {
376217 }
377218};
378219
379const CodeSignature = @This();
220page_size: u16,
221code_directory: CodeDirectory,
222requirements: ?Requirements = null,
223entitlements: ?Entitlements = null,
224signature: ?Signature = null,
380225
381const std = @import("std");
382const assert = std.debug.assert;
383const fs = std.fs;
384const log = std.log.scoped(.link);
385const macho = std.macho;
386const mem = std.mem;
387const testing = std.testing;
226pub fn init(page_size: u16) CodeSignature {
227 return .{
228 .page_size = page_size,
229 .code_directory = CodeDirectory.init(page_size),
230 };
231}
388232
389const Allocator = mem.Allocator;
390const Compilation = @import("../../Compilation.zig");
391const Hasher = @import("hasher.zig").ParallelHasher;
392const Sha256 = std.crypto.hash.sha2.Sha256;
233pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
234 self.code_directory.deinit(allocator);
235 if (self.requirements) |*req| {
236 req.deinit(allocator);
237 }
238 if (self.entitlements) |*ents| {
239 ents.deinit(allocator);
240 }
241 if (self.signature) |*sig| {
242 sig.deinit(allocator);
243 }
244}
245
246pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
247 const file = try fs.cwd().openFile(path, .{});
248 defer file.close();
249 const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
250 self.entitlements = .{ .inner = inner };
251}
252
253pub const WriteOpts = struct {
254 file: fs.File,
255 exec_seg_base: u64,
256 exec_seg_limit: u64,
257 file_size: u32,
258 dylib: bool,
259};
260
261pub fn writeAdhocSignature(
262 self: *CodeSignature,
263 macho_file: *MachO,
264 opts: WriteOpts,
265 writer: anytype,
266) !void {
267 const allocator = macho_file.base.comp.gpa;
268
269 var header: macho.SuperBlob = .{
270 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
271 .length = @sizeOf(macho.SuperBlob),
272 .count = 0,
273 };
274
275 var blobs = std.ArrayList(Blob).init(allocator);
276 defer blobs.deinit();
277
278 self.code_directory.inner.execSegBase = opts.exec_seg_base;
279 self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
280 self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0;
281 self.code_directory.inner.codeLimit = opts.file_size;
282
283 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
284
285 try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
286 self.code_directory.code_slots.items.len = total_pages;
287 self.code_directory.inner.nCodeSlots = total_pages;
288
289 // Calculate hash for each page (in file) and write it to the buffer
290 var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.comp.thread_pool };
291 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
292 .chunk_size = self.page_size,
293 .max_file_size = opts.file_size,
294 });
295
296 try blobs.append(.{ .code_directory = &self.code_directory });
297 header.length += @sizeOf(macho.BlobIndex);
298 header.count += 1;
299
300 var hash: [hash_size]u8 = undefined;
301
302 if (self.requirements) |*req| {
303 var buf = std.ArrayList(u8).init(allocator);
304 defer buf.deinit();
305 try req.write(buf.writer());
306 Sha256.hash(buf.items, &hash, .{});
307 self.code_directory.addSpecialHash(req.slotType(), hash);
308
309 try blobs.append(.{ .requirements = req });
310 header.count += 1;
311 header.length += @sizeOf(macho.BlobIndex) + req.size();
312 }
313
314 if (self.entitlements) |*ents| {
315 var buf = std.ArrayList(u8).init(allocator);
316 defer buf.deinit();
317 try ents.write(buf.writer());
318 Sha256.hash(buf.items, &hash, .{});
319 self.code_directory.addSpecialHash(ents.slotType(), hash);
320
321 try blobs.append(.{ .entitlements = ents });
322 header.count += 1;
323 header.length += @sizeOf(macho.BlobIndex) + ents.size();
324 }
325
326 if (self.signature) |*sig| {
327 try blobs.append(.{ .signature = sig });
328 header.count += 1;
329 header.length += @sizeOf(macho.BlobIndex) + sig.size();
330 }
331
332 self.code_directory.inner.hashOffset =
333 @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
334 self.code_directory.inner.length = self.code_directory.size();
335 header.length += self.code_directory.size();
336
337 try writer.writeInt(u32, header.magic, .big);
338 try writer.writeInt(u32, header.length, .big);
339 try writer.writeInt(u32, header.count, .big);
340
341 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
342 for (blobs.items) |blob| {
343 try writer.writeInt(u32, blob.slotType(), .big);
344 try writer.writeInt(u32, offset, .big);
345 offset += blob.size();
346 }
347
348 for (blobs.items) |blob| {
349 try blob.write(writer);
350 }
351}
352
353pub fn size(self: CodeSignature) u32 {
354 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
355 if (self.requirements) |req| {
356 ssize += @sizeOf(macho.BlobIndex) + req.size();
357 }
358 if (self.entitlements) |ent| {
359 ssize += @sizeOf(macho.BlobIndex) + ent.size();
360 }
361 if (self.signature) |sig| {
362 ssize += @sizeOf(macho.BlobIndex) + sig.size();
363 }
364 return ssize;
365}
366
367pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
368 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
369 // Approx code slots
370 const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
371 ssize += total_pages * hash_size;
372 var n_special_slots: u32 = 0;
373 if (self.requirements) |req| {
374 ssize += @sizeOf(macho.BlobIndex) + req.size();
375 n_special_slots = @max(n_special_slots, req.slotType());
376 }
377 if (self.entitlements) |ent| {
378 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
379 n_special_slots = @max(n_special_slots, ent.slotType());
380 }
381 if (self.signature) |sig| {
382 ssize += @sizeOf(macho.BlobIndex) + sig.size();
383 }
384 ssize += n_special_slots * hash_size;
385 return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
386}
387
388pub fn clear(self: *CodeSignature, allocator: Allocator) void {
389 self.code_directory.deinit(allocator);
390 self.code_directory = CodeDirectory.init(self.page_size);
391}
src/link/MachO/DwarfInfo.zig+351-384
......@@ -2,377 +2,175 @@ debug_info: []const u8,
22debug_abbrev: []const u8,
33debug_str: []const u8,
44
5pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator {
6 return .{ .ctx = self };
5/// Abbreviation table indexed by offset in the .debug_abbrev bytestream
6abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{},
7/// List of compile units as they appear in the .debug_info bytestream
8compile_units: std.ArrayListUnmanaged(CompileUnit) = .{},
9
10pub fn init(dw: *DwarfInfo, allocator: Allocator) !void {
11 try dw.parseAbbrevTables(allocator);
12 try dw.parseCompileUnits(allocator);
713}
814
9const CompileUnitIterator = struct {
10 ctx: DwarfInfo,
11 pos: usize = 0,
12
13 pub fn next(self: *CompileUnitIterator) !?CompileUnit {
14 if (self.pos >= self.ctx.debug_info.len) return null;
15
16 var stream = std.io.fixedBufferStream(self.ctx.debug_info[self.pos..]);
17 var creader = std.io.countingReader(stream.reader());
18 const reader = creader.reader();
19
20 const cuh = try CompileUnit.Header.read(reader);
21 const total_length = cuh.length + @as(u64, if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32));
22 const offset = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
23
24 const cu = CompileUnit{
25 .cuh = cuh,
26 .debug_info_off = self.pos + offset,
27 };
28
29 self.pos += (math.cast(usize, total_length) orelse return error.Overflow);
30
31 return cu;
15pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void {
16 dw.abbrev_tables.deinit(allocator);
17 for (dw.compile_units.items) |*cu| {
18 cu.deinit(allocator);
3219 }
33};
34
35pub fn genSubprogramLookupByName(
36 self: DwarfInfo,
37 compile_unit: CompileUnit,
38 abbrev_lookup: AbbrevLookupTable,
39 lookup: *SubprogramLookupByName,
40) !void {
41 var abbrev_it = compile_unit.getAbbrevEntryIterator(self);
42 while (try abbrev_it.next(abbrev_lookup)) |entry| switch (entry.tag) {
43 dwarf.TAG.subprogram => {
44 var attr_it = entry.getAttributeIterator(self, compile_unit.cuh);
45
46 var name: ?[]const u8 = null;
47 var low_pc: ?u64 = null;
48 var high_pc: ?u64 = null;
49
50 while (try attr_it.next()) |attr| switch (attr.name) {
51 dwarf.AT.name => if (attr.getString(self, compile_unit.cuh)) |str| {
52 name = str;
53 },
54 dwarf.AT.low_pc => {
55 if (attr.getAddr(self, compile_unit.cuh)) |addr| {
56 low_pc = addr;
57 }
58 if (try attr.getConstant(self)) |constant| {
59 low_pc = @as(u64, @intCast(constant));
60 }
61 },
62 dwarf.AT.high_pc => {
63 if (attr.getAddr(self, compile_unit.cuh)) |addr| {
64 high_pc = addr;
65 }
66 if (try attr.getConstant(self)) |constant| {
67 high_pc = @as(u64, @intCast(constant));
68 }
69 },
70 else => {},
71 };
72
73 if (name == null or low_pc == null or high_pc == null) continue;
20 dw.compile_units.deinit(allocator);
21}
7422
75 try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? });
76 },
77 else => {},
78 };
23fn getString(dw: DwarfInfo, off: usize) [:0]const u8 {
24 assert(off < dw.debug_str.len);
25 return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.debug_str.ptr + off)), 0);
7926}
8027
81pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void {
82 const data = self.debug_abbrev[off..];
83 var stream = std.io.fixedBufferStream(data);
28fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator) !void {
29 const tracy = trace(@src());
30 defer tracy.end();
31
32 const debug_abbrev = dw.debug_abbrev;
33 var stream = std.io.fixedBufferStream(debug_abbrev);
8434 var creader = std.io.countingReader(stream.reader());
8535 const reader = creader.reader();
8636
8737 while (true) {
88 const kind = try leb.readULEB128(u64, reader);
38 if (creader.bytes_read >= debug_abbrev.len) break;
8939
90 if (kind == 0) break;
91
92 const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
93 _ = try leb.readULEB128(u64, reader); // TAG
94 _ = try reader.readByte(); // CHILDREN
40 try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1);
41 const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read));
42 assert(!table_gop.found_existing);
43 const table = table_gop.value_ptr;
44 table.* = .{};
9545
9646 while (true) {
97 const name = try leb.readULEB128(u64, reader);
98 const form = try leb.readULEB128(u64, reader);
99
100 if (name == 0 and form == 0) break;
101 }
102
103 const next_pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
104
105 try lookup.putNoClobber(kind, .{
106 .pos = pos,
107 .len = next_pos - pos - 2,
108 });
109 }
110}
47 const code = try leb.readULEB128(Code, reader);
48 if (code == 0) break;
49
50 try table.decls.ensureUnusedCapacity(allocator, 1);
51 const decl_gop = table.decls.getOrPutAssumeCapacity(code);
52 assert(!decl_gop.found_existing);
53 const decl = decl_gop.value_ptr;
54 decl.* = .{
55 .code = code,
56 .tag = undefined,
57 .children = false,
58 };
59 decl.tag = try leb.readULEB128(Tag, reader);
60 decl.children = (try reader.readByte()) > 0;
11161
112pub const CompileUnit = struct {
113 cuh: Header,
114 debug_info_off: usize,
115
116 pub const Header = struct {
117 is_64bit: bool,
118 length: u64,
119 version: u16,
120 debug_abbrev_offset: u64,
121 address_size: u8,
122
123 fn read(reader: anytype) !Header {
124 var length: u64 = try reader.readInt(u32, .little);
125
126 const is_64bit = length == 0xffffffff;
127 if (is_64bit) {
128 length = try reader.readInt(u64, .little);
62 while (true) {
63 const at = try leb.readULEB128(At, reader);
64 const form = try leb.readULEB128(Form, reader);
65 if (at == 0 and form == 0) break;
66
67 try decl.attrs.ensureUnusedCapacity(allocator, 1);
68 const attr_gop = decl.attrs.getOrPutAssumeCapacity(at);
69 assert(!attr_gop.found_existing);
70 const attr = attr_gop.value_ptr;
71 attr.* = .{
72 .at = at,
73 .form = form,
74 };
12975 }
130
131 const version = try reader.readInt(u16, .little);
132 const debug_abbrev_offset = if (is_64bit)
133 try reader.readInt(u64, .little)
134 else
135 try reader.readInt(u32, .little);
136 const address_size = try reader.readInt(u8, .little);
137
138 return Header{
139 .is_64bit = is_64bit,
140 .length = length,
141 .version = version,
142 .debug_abbrev_offset = debug_abbrev_offset,
143 .address_size = address_size,
144 };
14576 }
146 };
147
148 inline fn getDebugInfo(self: CompileUnit, ctx: DwarfInfo) []const u8 {
149 return ctx.debug_info[self.debug_info_off..][0..self.cuh.length];
150 }
151
152 pub fn getAbbrevEntryIterator(self: CompileUnit, ctx: DwarfInfo) AbbrevEntryIterator {
153 return .{ .cu = self, .ctx = ctx };
15477 }
155};
156
157const AbbrevEntryIterator = struct {
158 cu: CompileUnit,
159 ctx: DwarfInfo,
160 pos: usize = 0,
161
162 pub fn next(self: *AbbrevEntryIterator, lookup: AbbrevLookupTable) !?AbbrevEntry {
163 if (self.pos + self.cu.debug_info_off >= self.ctx.debug_info.len) return null;
164
165 const debug_info = self.ctx.debug_info[self.pos + self.cu.debug_info_off ..];
166 var stream = std.io.fixedBufferStream(debug_info);
167 var creader = std.io.countingReader(stream.reader());
168 const reader = creader.reader();
78}
16979
170 const kind = try leb.readULEB128(u64, reader);
171 self.pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
80fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator) !void {
81 const tracy = trace(@src());
82 defer tracy.end();
17283
173 if (kind == 0) {
174 return AbbrevEntry.null();
175 }
84 const debug_info = dw.debug_info;
85 var stream = std.io.fixedBufferStream(debug_info);
86 var creader = std.io.countingReader(stream.reader());
87 const reader = creader.reader();
17688
177 const abbrev_pos = lookup.get(kind) orelse return null;
178 const len = try findAbbrevEntrySize(
179 self.ctx,
180 abbrev_pos.pos,
181 abbrev_pos.len,
182 self.pos + self.cu.debug_info_off,
183 self.cu.cuh,
184 );
185 const entry = try getAbbrevEntry(
186 self.ctx,
187 abbrev_pos.pos,
188 abbrev_pos.len,
189 self.pos + self.cu.debug_info_off,
190 len,
191 );
192
193 self.pos += len;
194
195 return entry;
196 }
197};
89 while (true) {
90 if (creader.bytes_read == debug_info.len) break;
19891
199pub const AbbrevEntry = struct {
200 tag: u64,
201 children: u8,
202 debug_abbrev_off: usize,
203 debug_abbrev_len: usize,
204 debug_info_off: usize,
205 debug_info_len: usize,
206
207 fn @"null"() AbbrevEntry {
208 return .{
209 .tag = 0,
210 .children = dwarf.CHILDREN.no,
211 .debug_abbrev_off = 0,
212 .debug_abbrev_len = 0,
213 .debug_info_off = 0,
214 .debug_info_len = 0,
92 const cu = try dw.compile_units.addOne(allocator);
93 cu.* = .{
94 .header = undefined,
95 .pos = creader.bytes_read,
21596 };
216 }
217
218 pub fn hasChildren(self: AbbrevEntry) bool {
219 return self.children == dwarf.CHILDREN.yes;
220 }
221
222 inline fn getDebugInfo(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
223 return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
224 }
225
226 inline fn getDebugAbbrev(self: AbbrevEntry, ctx: DwarfInfo) []const u8 {
227 return ctx.debug_abbrev[self.debug_abbrev_off..][0..self.debug_abbrev_len];
228 }
229
230 pub fn getAttributeIterator(self: AbbrevEntry, ctx: DwarfInfo, cuh: CompileUnit.Header) AttributeIterator {
231 return .{ .entry = self, .ctx = ctx, .cuh = cuh };
232 }
233};
234
235pub const Attribute = struct {
236 name: u64,
237 form: u64,
238 debug_info_off: usize,
239 debug_info_len: usize,
24097
241 inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 {
242 return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
243 }
244
245 pub fn getString(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?[]const u8 {
246 const debug_info = self.getDebugInfo(ctx);
247
248 switch (self.form) {
249 dwarf.FORM.string => {
250 return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_info.ptr)), 0);
251 },
252 dwarf.FORM.strp => {
253 const off = if (cuh.is_64bit)
254 mem.readInt(u64, debug_info[0..8], .little)
255 else
256 mem.readInt(u32, debug_info[0..4], .little);
257 return ctx.getString(off);
258 },
259 else => return null,
98 var length: u64 = try reader.readInt(u32, .little);
99 const is_64bit = length == 0xffffffff;
100 if (is_64bit) {
101 length = try reader.readInt(u64, .little);
260102 }
103 cu.header.format = if (is_64bit) .dwarf64 else .dwarf32;
104 cu.header.length = length;
105 cu.header.version = try reader.readInt(u16, .little);
106 cu.header.debug_abbrev_offset = try readOffset(cu.header.format, reader);
107 cu.header.address_size = try reader.readInt(u8, .little);
108
109 const table = dw.abbrev_tables.get(cu.header.debug_abbrev_offset).?;
110 try dw.parseDie(allocator, cu, table, null, &creader);
261111 }
112}
262113
263 pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 {
264 const debug_info = self.getDebugInfo(ctx);
265 var stream = std.io.fixedBufferStream(debug_info);
266 const reader = stream.reader();
267
268 return switch (self.form) {
269 dwarf.FORM.data1 => debug_info[0],
270 dwarf.FORM.data2 => mem.readInt(u16, debug_info[0..2], .little),
271 dwarf.FORM.data4 => mem.readInt(u32, debug_info[0..4], .little),
272 dwarf.FORM.data8 => mem.readInt(u64, debug_info[0..8], .little),
273 dwarf.FORM.udata => try leb.readULEB128(u64, reader),
274 dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
275 else => null,
276 };
277 }
278
279 pub fn getAddr(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?u64 {
280 if (self.form != dwarf.FORM.addr) return null;
281 const debug_info = self.getDebugInfo(ctx);
282 return switch (cuh.address_size) {
283 1 => debug_info[0],
284 2 => mem.readInt(u16, debug_info[0..2], .little),
285 4 => mem.readInt(u32, debug_info[0..4], .little),
286 8 => mem.readInt(u64, debug_info[0..8], .little),
287 else => unreachable,
288 };
289 }
290};
291
292const AttributeIterator = struct {
293 entry: AbbrevEntry,
294 ctx: DwarfInfo,
295 cuh: CompileUnit.Header,
296 debug_abbrev_pos: usize = 0,
297 debug_info_pos: usize = 0,
114fn parseDie(
115 dw: *DwarfInfo,
116 allocator: Allocator,
117 cu: *CompileUnit,
118 table: AbbrevTable,
119 parent: ?u32,
120 creader: anytype,
121) anyerror!void {
122 const tracy = trace(@src());
123 defer tracy.end();
124
125 while (creader.bytes_read < cu.nextCompileUnitOffset()) {
126 const die = try cu.addDie(allocator);
127 cu.diePtr(die).* = .{ .code = undefined };
128 if (parent) |p| {
129 try cu.diePtr(p).children.append(allocator, die);
130 } else {
131 try cu.children.append(allocator, die);
132 }
298133
299 pub fn next(self: *AttributeIterator) !?Attribute {
300 const debug_abbrev = self.entry.getDebugAbbrev(self.ctx);
301 if (self.debug_abbrev_pos >= debug_abbrev.len) return null;
134 const code = try leb.readULEB128(Code, creader.reader());
135 cu.diePtr(die).code = code;
302136
303 var stream = std.io.fixedBufferStream(debug_abbrev[self.debug_abbrev_pos..]);
304 var creader = std.io.countingReader(stream.reader());
305 const reader = creader.reader();
137 if (code == 0) {
138 if (parent == null) continue;
139 return; // Close scope
140 }
306141
307 const name = try leb.readULEB128(u64, reader);
308 const form = try leb.readULEB128(u64, reader);
309
310 self.debug_abbrev_pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow);
311
312 const len = try findFormSize(
313 self.ctx,
314 form,
315 self.debug_info_pos + self.entry.debug_info_off,
316 self.cuh,
317 );
318 const attr = Attribute{
319 .name = name,
320 .form = form,
321 .debug_info_off = self.debug_info_pos + self.entry.debug_info_off,
322 .debug_info_len = len,
323 };
142 const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors
143 const data = dw.debug_info;
144 try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len);
324145
325 self.debug_info_pos += len;
146 for (decl.attrs.values()) |attr| {
147 const start = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
148 try advanceByFormSize(cu, attr.form, creader);
149 const end = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
150 cu.diePtr(die).values.appendAssumeCapacity(data[start..end]);
151 }
326152
327 return attr;
153 if (decl.children) {
154 // Open scope
155 try dw.parseDie(allocator, cu, table, die, creader);
156 }
328157 }
329};
330
331fn getAbbrevEntry(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, di_len: usize) !AbbrevEntry {
332 const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
333 var stream = std.io.fixedBufferStream(debug_abbrev);
334 var creader = std.io.countingReader(stream.reader());
335 const reader = creader.reader();
336
337 const tag = try leb.readULEB128(u64, reader);
338 const children = switch (tag) {
339 std.dwarf.TAG.const_type,
340 std.dwarf.TAG.packed_type,
341 std.dwarf.TAG.pointer_type,
342 std.dwarf.TAG.reference_type,
343 std.dwarf.TAG.restrict_type,
344 std.dwarf.TAG.rvalue_reference_type,
345 std.dwarf.TAG.shared_type,
346 std.dwarf.TAG.volatile_type,
347 => if (creader.bytes_read == da_len) std.dwarf.CHILDREN.no else try reader.readByte(),
348 else => try reader.readByte(),
349 };
350
351 const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
352
353 return AbbrevEntry{
354 .tag = tag,
355 .children = children,
356 .debug_abbrev_off = pos + da_off,
357 .debug_abbrev_len = da_len - pos,
358 .debug_info_off = di_off,
359 .debug_info_len = di_len,
360 };
361158}
362159
363fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize {
364 const debug_info = self.debug_info[di_off..];
365 var stream = std.io.fixedBufferStream(debug_info);
366 var creader = std.io.countingReader(stream.reader());
367 const reader = creader.reader();
160fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void {
161 const tracy = trace(@src());
162 defer tracy.end();
368163
164 const reader = creader.reader();
369165 switch (form) {
370166 dwarf.FORM.strp,
371167 dwarf.FORM.sec_offset,
372168 dwarf.FORM.ref_addr,
373 => return if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32),
169 => {
170 _ = try readOffset(cu.header.format, reader);
171 },
374172
375 dwarf.FORM.addr => return cuh.address_size,
173 dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}),
376174
377175 dwarf.FORM.block1,
378176 dwarf.FORM.block2,
......@@ -390,115 +188,284 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head
390188 while (i < len) : (i += 1) {
391189 _ = try reader.readByte();
392190 }
393 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
394191 },
395192
396193 dwarf.FORM.exprloc => {
397 const expr_len = try leb.readULEB128(u64, reader);
194 const len = try leb.readULEB128(u64, reader);
398195 var i: u64 = 0;
399 while (i < expr_len) : (i += 1) {
196 while (i < len) : (i += 1) {
400197 _ = try reader.readByte();
401198 }
402 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
403199 },
404 dwarf.FORM.flag_present => return 0,
200 dwarf.FORM.flag_present => {},
405201
406202 dwarf.FORM.data1,
407203 dwarf.FORM.ref1,
408204 dwarf.FORM.flag,
409 => return @sizeOf(u8),
205 => try reader.skipBytes(1, .{}),
410206
411207 dwarf.FORM.data2,
412208 dwarf.FORM.ref2,
413 => return @sizeOf(u16),
209 => try reader.skipBytes(2, .{}),
414210
415211 dwarf.FORM.data4,
416212 dwarf.FORM.ref4,
417 => return @sizeOf(u32),
213 => try reader.skipBytes(4, .{}),
418214
419215 dwarf.FORM.data8,
420216 dwarf.FORM.ref8,
421217 dwarf.FORM.ref_sig8,
422 => return @sizeOf(u64),
218 => try reader.skipBytes(8, .{}),
423219
424220 dwarf.FORM.udata,
425221 dwarf.FORM.ref_udata,
426222 => {
427223 _ = try leb.readULEB128(u64, reader);
428 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
429224 },
430225
431226 dwarf.FORM.sdata => {
432227 _ = try leb.readILEB128(i64, reader);
433 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
434228 },
435229
436230 dwarf.FORM.string => {
437 var count: usize = 0;
438231 while (true) {
439232 const byte = try reader.readByte();
440 count += 1;
441233 if (byte == 0x0) break;
442234 }
443 return count;
444235 },
445236
446237 else => {
447 // TODO figure out how to handle this
448 log.debug("unhandled DW_FORM_* value with identifier {x}", .{form});
238 // TODO better errors
239 log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
449240 return error.UnhandledDwFormValue;
450241 },
451242 }
452243}
453244
454fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize {
455 const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
456 var stream = std.io.fixedBufferStream(debug_abbrev);
457 var creader = std.io.countingReader(stream.reader());
458 const reader = creader.reader();
245fn readOffset(format: Format, reader: anytype) !u64 {
246 return switch (format) {
247 .dwarf32 => try reader.readInt(u32, .little),
248 .dwarf64 => try reader.readInt(u64, .little),
249 };
250}
459251
460 const tag = try leb.readULEB128(u64, reader);
461 switch (tag) {
462 std.dwarf.TAG.const_type,
463 std.dwarf.TAG.packed_type,
464 std.dwarf.TAG.pointer_type,
465 std.dwarf.TAG.reference_type,
466 std.dwarf.TAG.restrict_type,
467 std.dwarf.TAG.rvalue_reference_type,
468 std.dwarf.TAG.shared_type,
469 std.dwarf.TAG.volatile_type,
470 => if (creader.bytes_read != da_len) {
471 _ = try reader.readByte();
472 },
473 else => _ = try reader.readByte(),
252pub const AbbrevTable = struct {
253 /// Table of abbreviation declarations indexed by their assigned code value
254 decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{},
255
256 pub fn deinit(table: *AbbrevTable, gpa: Allocator) void {
257 for (table.decls.values()) |*decl| {
258 decl.deinit(gpa);
259 }
260 table.decls.deinit(gpa);
474261 }
262};
263
264pub const Decl = struct {
265 code: Code,
266 tag: Tag,
267 children: bool,
268
269 /// Table of attributes indexed by their AT value
270 attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{},
475271
476 var len: usize = 0;
477 while (creader.bytes_read < debug_abbrev.len) {
478 _ = try leb.readULEB128(u64, reader);
479 const form = try leb.readULEB128(u64, reader);
480 const form_len = try self.findFormSize(form, di_off + len, cuh);
481 len += form_len;
272 pub fn deinit(decl: *Decl, gpa: Allocator) void {
273 decl.attrs.deinit(gpa);
482274 }
275};
483276
484 return len;
485}
277pub const Attr = struct {
278 at: At,
279 form: Form,
280};
486281
487fn getString(self: DwarfInfo, off: u64) []const u8 {
488 assert(off < self.debug_str.len);
489 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0);
490}
282pub const At = u64;
283pub const Code = u64;
284pub const Form = u64;
285pub const Tag = u64;
286
287pub const CompileUnitHeader = struct {
288 format: Format,
289 length: u64,
290 version: u16,
291 debug_abbrev_offset: u64,
292 address_size: u8,
293};
491294
492const DwarfInfo = @This();
295pub const CompileUnit = struct {
296 header: CompileUnitHeader,
297 pos: u64,
298 dies: std.ArrayListUnmanaged(Die) = .{},
299 children: std.ArrayListUnmanaged(Die.Index) = .{},
300
301 pub fn deinit(cu: *CompileUnit, gpa: Allocator) void {
302 for (cu.dies.items) |*die| {
303 die.deinit(gpa);
304 }
305 cu.dies.deinit(gpa);
306 cu.children.deinit(gpa);
307 }
308
309 pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index {
310 const index = @as(Die.Index, @intCast(cu.dies.items.len));
311 _ = try cu.dies.addOne(gpa);
312 return index;
313 }
314
315 pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die {
316 return &cu.dies.items[index];
317 }
318
319 pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
320 assert(cu.dies.items.len > 0);
321 const die = cu.dies.items[0];
322 const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null;
323 return res.getString(cu.header.format, ctx);
324 }
325
326 pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
327 assert(cu.dies.items.len > 0);
328 const die = cu.dies.items[0];
329 const res = die.find(dwarf.AT.name, cu, ctx) orelse return null;
330 return res.getString(cu.header.format, ctx);
331 }
332
333 pub fn nextCompileUnitOffset(cu: CompileUnit) u64 {
334 return cu.pos + switch (cu.header.format) {
335 .dwarf32 => @as(u64, 4),
336 .dwarf64 => 12,
337 } + cu.header.length;
338 }
339};
340
341pub const Die = struct {
342 code: Code,
343 values: std.ArrayListUnmanaged([]const u8) = .{},
344 children: std.ArrayListUnmanaged(Die.Index) = .{},
345
346 pub fn deinit(die: *Die, gpa: Allocator) void {
347 die.values.deinit(gpa);
348 die.children.deinit(gpa);
349 }
350
351 pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue {
352 const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null;
353 const decl = table.decls.get(die.code).?;
354 const index = decl.attrs.getIndex(at) orelse return null;
355 const attr = decl.attrs.values()[index];
356 const value = die.values.items[index];
357 return .{ .attr = attr, .bytes = value };
358 }
359
360 pub const Index = u32;
361};
362
363pub const DieValue = struct {
364 attr: Attr,
365 bytes: []const u8,
366
367 pub fn getFlag(value: DieValue) ?bool {
368 return switch (value.attr.form) {
369 dwarf.FORM.flag => value.bytes[0] == 1,
370 dwarf.FORM.flag_present => true,
371 else => null,
372 };
373 }
374
375 pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
376 switch (value.attr.form) {
377 dwarf.FORM.string => {
378 return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0);
379 },
380 dwarf.FORM.strp => {
381 const off = switch (format) {
382 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
383 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
384 };
385 const off_u = std.math.cast(usize, off) orelse return error.Overflow;
386 return ctx.getString(off_u);
387 },
388 else => return null,
389 }
390 }
391
392 pub fn getSecOffset(value: DieValue, format: Format) ?u64 {
393 return switch (value.attr.form) {
394 dwarf.FORM.sec_offset => switch (format) {
395 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
396 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
397 },
398 else => null,
399 };
400 }
401
402 pub fn getConstant(value: DieValue) !?i128 {
403 var stream = std.io.fixedBufferStream(value.bytes);
404 const reader = stream.reader();
405 return switch (value.attr.form) {
406 dwarf.FORM.data1 => value.bytes[0],
407 dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little),
408 dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little),
409 dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little),
410 dwarf.FORM.udata => try leb.readULEB128(u64, reader),
411 dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
412 else => null,
413 };
414 }
415
416 pub fn getReference(value: DieValue, format: Format) !?u64 {
417 var stream = std.io.fixedBufferStream(value.bytes);
418 const reader = stream.reader();
419 return switch (value.attr.form) {
420 dwarf.FORM.ref1 => value.bytes[0],
421 dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little),
422 dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little),
423 dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little),
424 dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader),
425 dwarf.FORM.ref_addr => switch (format) {
426 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
427 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
428 },
429 else => null,
430 };
431 }
432
433 pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 {
434 return switch (value.attr.form) {
435 dwarf.FORM.addr => switch (header.address_size) {
436 1 => value.bytes[0],
437 2 => mem.readInt(u16, value.bytes[0..2], .little),
438 4 => mem.readInt(u32, value.bytes[0..4], .little),
439 8 => mem.readInt(u64, value.bytes[0..8], .little),
440 else => null,
441 },
442 else => null,
443 };
444 }
445
446 pub fn getExprloc(value: DieValue) !?[]const u8 {
447 if (value.attr.form != dwarf.FORM.exprloc) return null;
448 var stream = std.io.fixedBufferStream(value.bytes);
449 var creader = std.io.countingReader(stream.reader());
450 const reader = creader.reader();
451 const expr_len = try leb.readULEB128(u64, reader);
452 return value.bytes[creader.bytes_read..][0..expr_len];
453 }
454};
455
456pub const Format = enum {
457 dwarf32,
458 dwarf64,
459};
493460
494const std = @import("std");
495461const assert = std.debug.assert;
496462const dwarf = std.dwarf;
497463const leb = std.leb;
498const log = std.log.scoped(.macho);
499const math = std.math;
464const log = std.log.scoped(.link);
500465const mem = std.mem;
466const std = @import("std");
467const trace = @import("../../tracy.zig").trace;
501468
502469const Allocator = mem.Allocator;
503pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });
504pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });
470const DwarfInfo = @This();
471const MachO = @import("../MachO.zig");
src/link/MachO/Dylib.zig+633-319
......@@ -1,340 +1,252 @@
11path: []const u8,
2id: ?Id = null,
3weak: bool = false,
4/// Header is only set if Dylib is parsed directly from a binary and not a stub file.
5header: ?macho.mach_header_64 = null,
6
7/// Parsed symbol table represented as hash map of symbols'
8/// names. We can and should defer creating *Symbols until
9/// a symbol is referenced by an object file.
10///
11/// The value for each parsed symbol represents whether the
12/// symbol is defined as a weak symbol or strong.
13/// TODO when the referenced symbol is weak, ld64 marks it as
14/// N_REF_TO_WEAK but need to investigate if there's more to it
15/// such as weak binding entry or simply weak. For now, we generate
16/// standard bind or lazy bind.
17symbols: std.StringArrayHashMapUnmanaged(bool) = .{},
18
19pub const Id = struct {
20 name: []const u8,
21 timestamp: u32,
22 current_version: u32,
23 compatibility_version: u32,
24
25 pub fn default(allocator: Allocator, name: []const u8) !Id {
26 return Id{
27 .name = try allocator.dupe(u8, name),
28 .timestamp = 2,
29 .current_version = 0x10000,
30 .compatibility_version = 0x10000,
31 };
32 }
2data: []const u8,
3index: File.Index,
334
34 pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
35 return Id{
36 .name = try allocator.dupe(u8, name),
37 .timestamp = lc.dylib.timestamp,
38 .current_version = lc.dylib.current_version,
39 .compatibility_version = lc.dylib.compatibility_version,
40 };
41 }
42
43 pub fn deinit(id: Id, allocator: Allocator) void {
44 allocator.free(id.name);
45 }
46
47 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
48
49 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
50 id.current_version = try parseVersion(version);
51 }
52
53 pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
54 id.compatibility_version = try parseVersion(version);
55 }
56
57 fn parseVersion(version: anytype) ParseError!u32 {
58 const string = blk: {
59 switch (version) {
60 .int => |int| {
61 var out: u32 = 0;
62 const major = math.cast(u16, int) orelse return error.Overflow;
63 out += @as(u32, @intCast(major)) << 16;
64 return out;
65 },
66 .float => |float| {
67 var buf: [256]u8 = undefined;
68 break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
69 },
70 .string => |string| {
71 break :blk string;
72 },
73 }
74 };
75
76 var out: u32 = 0;
77 var values: [3][]const u8 = undefined;
78
79 var split = mem.splitScalar(u8, string, '.');
80 var count: u4 = 0;
81 while (split.next()) |value| {
82 if (count > 2) {
83 log.debug("malformed version field: {s}", .{string});
84 return 0x10000;
85 }
86 values[count] = value;
87 count += 1;
88 }
89
90 if (count > 2) {
91 out += try fmt.parseInt(u8, values[2], 10);
92 }
93 if (count > 1) {
94 out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;
95 }
96 out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
97
98 return out;
5header: ?macho.mach_header_64 = null,
6exports: std.MultiArrayList(Export) = .{},
7strtab: std.ArrayListUnmanaged(u8) = .{},
8id: ?Id = null,
9ordinal: u16 = 0,
10
11symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
12dependents: std.ArrayListUnmanaged(Id) = .{},
13rpaths: std.StringArrayHashMapUnmanaged(void) = .{},
14umbrella: File.Index = 0,
15platform: ?MachO.Platform = null,
16
17needed: bool,
18weak: bool,
19reexport: bool,
20explicit: bool,
21hoisted: bool = true,
22referenced: bool = false,
23
24output_symtab_ctx: MachO.SymtabCtx = .{},
25
26pub fn isDylib(path: []const u8, fat_arch: ?fat.Arch) !bool {
27 const file = try std.fs.cwd().openFile(path, .{});
28 defer file.close();
29 if (fat_arch) |arch| {
30 try file.seekTo(arch.offset);
9931 }
100};
101
102pub fn isDylib(file: std.fs.File, fat_offset: u64) bool {
103 const reader = file.reader();
104 const hdr = reader.readStruct(macho.mach_header_64) catch return false;
105 defer file.seekTo(fat_offset) catch {};
106 return hdr.filetype == macho.MH_DYLIB;
32 const header = file.reader().readStruct(macho.mach_header_64) catch return false;
33 return header.filetype == macho.MH_DYLIB;
10734}
10835
10936pub fn deinit(self: *Dylib, allocator: Allocator) void {
37 allocator.free(self.data);
11038 allocator.free(self.path);
111 for (self.symbols.keys()) |key| {
112 allocator.free(key);
113 }
39 self.exports.deinit(allocator);
40 self.strtab.deinit(allocator);
41 if (self.id) |*id| id.deinit(allocator);
11442 self.symbols.deinit(allocator);
115 if (self.id) |*id| {
43 for (self.dependents.items) |*id| {
11644 id.deinit(allocator);
11745 }
46 self.dependents.deinit(allocator);
47 self.rpaths.deinit(allocator);
11848}
11949
120pub fn parseFromBinary(
121 self: *Dylib,
122 allocator: Allocator,
123 dylib_id: u16,
124 dependent_libs: anytype,
125 name: []const u8,
126 data: []align(@alignOf(u64)) const u8,
127) !void {
128 var stream = std.io.fixedBufferStream(data);
50pub fn parse(self: *Dylib, macho_file: *MachO) !void {
51 const tracy = trace(@src());
52 defer tracy.end();
53
54 const gpa = macho_file.base.comp.gpa;
55 var stream = std.io.fixedBufferStream(self.data);
12956 const reader = stream.reader();
13057
131 log.debug("parsing shared library '{s}'", .{name});
58 log.debug("parsing dylib from binary", .{});
13259
13360 self.header = try reader.readStruct(macho.mach_header_64);
13461
135 const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
136 var it = LoadCommandIterator{
137 .ncmds = self.header.?.ncmds,
138 .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
62 const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
63 macho.CPU_TYPE_ARM64 => .aarch64,
64 macho.CPU_TYPE_X86_64 => .x86_64,
65 else => |x| {
66 try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
67 return error.InvalidCpuArch;
68 },
13969 };
140 while (it.next()) |cmd| {
141 switch (cmd.cmd()) {
142 .SYMTAB => {
143 const symtab_cmd = cmd.cast(macho.symtab_command).?;
144 const symtab = @as(
145 [*]const macho.nlist_64,
146 // Alignment is guaranteed as a dylib is a final linked image and has to have sections
147 // properly aligned in order to be correctly loaded by the loader.
148 @ptrCast(@alignCast(&data[symtab_cmd.symoff])),
149 )[0..symtab_cmd.nsyms];
150 const strtab = data[symtab_cmd.stroff..][0..symtab_cmd.strsize];
151
152 for (symtab) |sym| {
153 const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
154 if (!add_to_symtab) continue;
155
156 const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + sym.n_strx)), 0);
157 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
158 }
159 },
160 .ID_DYLIB => {
161 self.id = try Id.fromLoadCommand(
162 allocator,
163 cmd.cast(macho.dylib_command).?,
164 cmd.getDylibPathName(),
165 );
166 },
167 .REEXPORT_DYLIB => {
168 if (should_lookup_reexports) {
169 // Parse install_name to dependent dylib.
170 const id = try Id.fromLoadCommand(
171 allocator,
172 cmd.cast(macho.dylib_command).?,
173 cmd.getDylibPathName(),
174 );
175 try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id });
176 }
177 },
178 else => {},
179 }
70 if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
71 try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
72 return error.InvalidCpuArch;
18073 }
181}
18274
183/// Returns Platform composed from the first encountered build version type load command:
184/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
185pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform {
75 const lc_id = self.getLoadCommand(.ID_DYLIB) orelse {
76 try macho_file.reportParseError2(self.index, "missing LC_ID_DYLIB load command", .{});
77 return error.MalformedDylib;
78 };
79 self.id = try Id.fromLoadCommand(gpa, lc_id.cast(macho.dylib_command).?, lc_id.getDylibPathName());
80
18681 var it = LoadCommandIterator{
18782 .ncmds = self.header.?.ncmds,
188 .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
83 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
18984 };
190 while (it.next()) |cmd| {
191 switch (cmd.cmd()) {
192 .BUILD_VERSION,
193 .VERSION_MIN_MACOSX,
194 .VERSION_MIN_IPHONEOS,
195 .VERSION_MIN_TVOS,
196 .VERSION_MIN_WATCHOS,
197 => return Platform.fromLoadCommand(cmd),
198 else => {},
199 }
200 } else return null;
201}
202
203fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
204 const expanded = &[_][]const u8{
205 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
206 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
85 while (it.next()) |cmd| switch (cmd.cmd()) {
86 .REEXPORT_DYLIB => if (self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) {
87 const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName());
88 try self.dependents.append(gpa, id);
89 },
90 .DYLD_INFO_ONLY => {
91 const dyld_cmd = cmd.cast(macho.dyld_info_command).?;
92 const data = self.data[dyld_cmd.export_off..][0..dyld_cmd.export_size];
93 try self.parseTrie(data, macho_file);
94 },
95 .DYLD_EXPORTS_TRIE => {
96 const ld_cmd = cmd.cast(macho.linkedit_data_command).?;
97 const data = self.data[ld_cmd.dataoff..][0..ld_cmd.datasize];
98 try self.parseTrie(data, macho_file);
99 },
100 .RPATH => {
101 const path = cmd.getRpathPathName();
102 try self.rpaths.put(gpa, path, {});
103 },
104 else => {},
207105 };
208106
209 for (expanded) |sym| {
210 if (self.symbols.contains(sym)) continue;
211 try self.symbols.putNoClobber(allocator, sym, false);
212 }
213}
107 self.initPlatform();
214108
215fn addObjCIVarSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
216 const expanded = try std.fmt.allocPrint(allocator, "_OBJC_IVAR_$_{s}", .{sym_name});
217 if (self.symbols.contains(expanded)) return;
218 try self.symbols.putNoClobber(allocator, expanded, false);
219}
220
221fn addObjCEhTypeSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
222 const expanded = try std.fmt.allocPrint(allocator, "_OBJC_EHTYPE_$_{s}", .{sym_name});
223 if (self.symbols.contains(expanded)) return;
224 try self.symbols.putNoClobber(allocator, expanded, false);
109 if (self.platform) |platform| {
110 if (!macho_file.platform.eqlTarget(platform)) {
111 try macho_file.reportParseError2(self.index, "invalid platform: {}", .{
112 platform.fmtTarget(macho_file.getTarget().cpu.arch),
113 });
114 return error.InvalidTarget;
115 }
116 // TODO: this can cause the CI to fail so I'm commenting this check out so that
117 // I can work out the rest of the changes first
118 // if (macho_file.platform.version.order(platform.version) == .lt) {
119 // try macho_file.reportParseError2(self.index, "object file built for newer platform: {}: {} < {}", .{
120 // macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch),
121 // macho_file.platform.version,
122 // platform.version,
123 // });
124 // return error.InvalidTarget;
125 // }
126 }
225127}
226128
227fn addSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
228 if (self.symbols.contains(sym_name)) return;
229 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
230}
129const TrieIterator = struct {
130 data: []const u8,
131 pos: usize = 0,
231132
232fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
233 if (self.symbols.contains(sym_name)) return;
234 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true);
235}
133 fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) {
134 return std.io.fixedBufferStream(it.data[it.pos..]);
135 }
236136
237pub const TargetMatcher = struct {
238 allocator: Allocator,
239 cpu_arch: std.Target.Cpu.Arch,
240 os_tag: std.Target.Os.Tag,
241 abi: std.Target.Abi,
242 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
137 fn readULEB128(it: *TrieIterator) !u64 {
138 var stream = it.getStream();
139 var creader = std.io.countingReader(stream.reader());
140 const reader = creader.reader();
141 const value = try std.leb.readULEB128(u64, reader);
142 it.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow;
143 return value;
144 }
243145
244 pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher {
245 var self = TargetMatcher{
246 .allocator = allocator,
247 .cpu_arch = target.cpu.arch,
248 .os_tag = target.os.tag,
249 .abi = target.abi,
250 };
251 const apple_string = try toAppleTargetTriple(allocator, self.cpu_arch, self.os_tag, self.abi);
252 try self.target_strings.append(allocator, apple_string);
146 fn readString(it: *TrieIterator) ![:0]const u8 {
147 var stream = it.getStream();
148 const reader = stream.reader();
253149
254 if (self.abi == .simulator) {
255 // For Apple simulator targets, linking gets tricky as we need to link against the simulator
256 // hosts dylibs too.
257 const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none);
258 try self.target_strings.append(allocator, host_target);
150 var count: usize = 0;
151 while (true) : (count += 1) {
152 const byte = try reader.readByte();
153 if (byte == 0) break;
259154 }
260155
261 return self;
156 const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0];
157 it.pos += count + 1;
158 return str;
262159 }
263160
264 pub fn deinit(self: *TargetMatcher) void {
265 for (self.target_strings.items) |t| {
266 self.allocator.free(t);
267 }
268 self.target_strings.deinit(self.allocator);
161 fn readByte(it: *TrieIterator) !u8 {
162 var stream = it.getStream();
163 const value = try stream.reader().readByte();
164 it.pos += 1;
165 return value;
269166 }
167};
270168
271 inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 {
272 return switch (cpu_arch) {
273 .aarch64 => "arm64",
274 .x86_64 => "x86_64",
275 else => unreachable,
276 };
277 }
169pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void {
170 try self.exports.append(allocator, .{
171 .name = try self.insertString(allocator, name),
172 .flags = flags,
173 });
174}
278175
279 inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 {
280 return switch (abi) {
281 .none => null,
282 .simulator => "simulator",
283 .macabi => "maccatalyst",
284 else => unreachable,
176fn parseTrieNode(
177 self: *Dylib,
178 it: *TrieIterator,
179 allocator: Allocator,
180 arena: Allocator,
181 prefix: []const u8,
182) !void {
183 const tracy = trace(@src());
184 defer tracy.end();
185 const size = try it.readULEB128();
186 if (size > 0) {
187 const flags = try it.readULEB128();
188 const kind = flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK;
189 const out_flags = Export.Flags{
190 .abs = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE,
191 .tlv = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL,
192 .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
285193 };
286 }
287
288 pub fn toAppleTargetTriple(
289 allocator: Allocator,
290 cpu_arch: std.Target.Cpu.Arch,
291 os_tag: std.Target.Os.Tag,
292 abi: std.Target.Abi,
293 ) ![]const u8 {
294 const cpu_arch_s = fmtCpuArch(cpu_arch);
295 const os_tag_s = @tagName(os_tag);
296 if (fmtAbi(abi)) |abi_s| {
297 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch_s, os_tag_s, abi_s });
194 if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) {
195 _ = try it.readULEB128(); // dylib ordinal
196 const name = try it.readString();
197 try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags);
198 } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) {
199 _ = try it.readULEB128(); // stub offset
200 _ = try it.readULEB128(); // resolver offset
201 try self.addExport(allocator, prefix, out_flags);
202 } else {
203 _ = try it.readULEB128(); // VM offset
204 try self.addExport(allocator, prefix, out_flags);
298205 }
299 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s });
300206 }
301207
302 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
303 for (stack) |v| {
304 if (mem.eql(u8, v, needle)) return true;
305 }
306 return false;
307 }
208 const nedges = try it.readByte();
308209
309 pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
310 for (self.target_strings.items) |t| {
311 if (hasValue(targets, t)) return true;
312 }
313 return false;
210 for (0..nedges) |_| {
211 const label = try it.readString();
212 const off = try it.readULEB128();
213 const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
214 const curr = it.pos;
215 it.pos = math.cast(usize, off) orelse return error.Overflow;
216 try self.parseTrieNode(it, allocator, arena, prefix_label);
217 it.pos = curr;
314218 }
219}
315220
316 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
317 return hasValue(archs, fmtCpuArch(self.cpu_arch));
318 }
319};
221fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void {
222 const tracy = trace(@src());
223 defer tracy.end();
224 const gpa = macho_file.base.comp.gpa;
225 var arena = std.heap.ArenaAllocator.init(gpa);
226 defer arena.deinit();
227
228 var it: TrieIterator = .{ .data = data };
229 try self.parseTrieNode(&it, gpa, arena.allocator(), "");
230}
320231
321pub fn parseFromStub(
232pub fn parseTbd(
322233 self: *Dylib,
323 allocator: Allocator,
324 target: std.Target,
234 cpu_arch: std.Target.Cpu.Arch,
235 platform: MachO.Platform,
325236 lib_stub: LibStub,
326 dylib_id: u16,
327 dependent_libs: anytype,
328 name: []const u8,
237 macho_file: *MachO,
329238) !void {
330 if (lib_stub.inner.len == 0) return error.NotLibStub;
239 const tracy = trace(@src());
240 defer tracy.end();
241
242 const gpa = macho_file.base.comp.gpa;
331243
332 log.debug("parsing shared library from stub '{s}'", .{name});
244 log.debug("parsing dylib from stub", .{});
333245
334246 const umbrella_lib = lib_stub.inner[0];
335247
336248 {
337 var id = try Id.default(allocator, umbrella_lib.installName());
249 var id = try Id.default(gpa, umbrella_lib.installName());
338250 if (umbrella_lib.currentVersion()) |version| {
339251 try id.parseCurrentVersion(version);
340252 }
......@@ -344,21 +256,18 @@ pub fn parseFromStub(
344256 self.id = id;
345257 }
346258
347 var umbrella_libs = std.StringHashMap(void).init(allocator);
259 var umbrella_libs = std.StringHashMap(void).init(gpa);
348260 defer umbrella_libs.deinit();
349261
350262 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
351263
352 var matcher = try TargetMatcher.init(allocator, target);
264 self.platform = platform;
265
266 var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.toApplePlatform());
353267 defer matcher.deinit();
354268
355269 for (lib_stub.inner, 0..) |elem, stub_index| {
356 const targets = try elem.targets(allocator);
357 defer {
358 for (targets) |t| allocator.free(t);
359 allocator.free(targets);
360 }
361 if (!matcher.matchesTarget(targets)) continue;
270 if (!(try matcher.matchesTargetTbd(elem))) continue;
362271
363272 if (stub_index > 0) {
364273 // TODO I thought that we could switch on presence of `parent-umbrella` map;
......@@ -375,43 +284,42 @@ pub fn parseFromStub(
375284
376285 if (exp.symbols) |symbols| {
377286 for (symbols) |sym_name| {
378 try self.addSymbol(allocator, sym_name);
287 try self.addExport(gpa, sym_name, .{});
379288 }
380289 }
381290
382291 if (exp.weak_symbols) |symbols| {
383292 for (symbols) |sym_name| {
384 try self.addWeakSymbol(allocator, sym_name);
293 try self.addExport(gpa, sym_name, .{ .weak = true });
385294 }
386295 }
387296
388297 if (exp.objc_classes) |objc_classes| {
389298 for (objc_classes) |class_name| {
390 try self.addObjCClassSymbol(allocator, class_name);
299 try self.addObjCClass(gpa, class_name);
391300 }
392301 }
393302
394303 if (exp.objc_ivars) |objc_ivars| {
395304 for (objc_ivars) |ivar| {
396 try self.addObjCIVarSymbol(allocator, ivar);
305 try self.addObjCIVar(gpa, ivar);
397306 }
398307 }
399308
400309 if (exp.objc_eh_types) |objc_eh_types| {
401310 for (objc_eh_types) |eht| {
402 try self.addObjCEhTypeSymbol(allocator, eht);
311 try self.addObjCEhType(gpa, eht);
403312 }
404313 }
405314
406 // TODO track which libs were already parsed in different steps
407315 if (exp.re_exports) |re_exports| {
408316 for (re_exports) |lib| {
409317 if (umbrella_libs.contains(lib)) continue;
410318
411319 log.debug(" (found re-export '{s}')", .{lib});
412320
413 const dep_id = try Id.default(allocator, lib);
414 try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });
321 const dep_id = try Id.default(gpa, lib);
322 try self.dependents.append(gpa, dep_id);
415323 }
416324 }
417325 }
......@@ -424,31 +332,31 @@ pub fn parseFromStub(
424332
425333 if (exp.symbols) |symbols| {
426334 for (symbols) |sym_name| {
427 try self.addSymbol(allocator, sym_name);
335 try self.addExport(gpa, sym_name, .{});
428336 }
429337 }
430338
431339 if (exp.weak_symbols) |symbols| {
432340 for (symbols) |sym_name| {
433 try self.addWeakSymbol(allocator, sym_name);
341 try self.addExport(gpa, sym_name, .{ .weak = true });
434342 }
435343 }
436344
437345 if (exp.objc_classes) |classes| {
438346 for (classes) |sym_name| {
439 try self.addObjCClassSymbol(allocator, sym_name);
347 try self.addObjCClass(gpa, sym_name);
440348 }
441349 }
442350
443351 if (exp.objc_ivars) |objc_ivars| {
444352 for (objc_ivars) |ivar| {
445 try self.addObjCIVarSymbol(allocator, ivar);
353 try self.addObjCIVar(gpa, ivar);
446354 }
447355 }
448356
449357 if (exp.objc_eh_types) |objc_eh_types| {
450358 for (objc_eh_types) |eht| {
451 try self.addObjCEhTypeSymbol(allocator, eht);
359 try self.addObjCEhType(gpa, eht);
452360 }
453361 }
454362 }
......@@ -460,31 +368,31 @@ pub fn parseFromStub(
460368
461369 if (reexp.symbols) |symbols| {
462370 for (symbols) |sym_name| {
463 try self.addSymbol(allocator, sym_name);
371 try self.addExport(gpa, sym_name, .{});
464372 }
465373 }
466374
467375 if (reexp.weak_symbols) |symbols| {
468376 for (symbols) |sym_name| {
469 try self.addWeakSymbol(allocator, sym_name);
377 try self.addExport(gpa, sym_name, .{ .weak = true });
470378 }
471379 }
472380
473381 if (reexp.objc_classes) |classes| {
474382 for (classes) |sym_name| {
475 try self.addObjCClassSymbol(allocator, sym_name);
383 try self.addObjCClass(gpa, sym_name);
476384 }
477385 }
478386
479387 if (reexp.objc_ivars) |objc_ivars| {
480388 for (objc_ivars) |ivar| {
481 try self.addObjCIVarSymbol(allocator, ivar);
389 try self.addObjCIVar(gpa, ivar);
482390 }
483391 }
484392
485393 if (reexp.objc_eh_types) |objc_eh_types| {
486394 for (objc_eh_types) |eht| {
487 try self.addObjCEhTypeSymbol(allocator, eht);
395 try self.addObjCEhType(gpa, eht);
488396 }
489397 }
490398 }
......@@ -492,19 +400,19 @@ pub fn parseFromStub(
492400
493401 if (stub.objc_classes) |classes| {
494402 for (classes) |sym_name| {
495 try self.addObjCClassSymbol(allocator, sym_name);
403 try self.addObjCClass(gpa, sym_name);
496404 }
497405 }
498406
499407 if (stub.objc_ivars) |objc_ivars| {
500408 for (objc_ivars) |ivar| {
501 try self.addObjCIVarSymbol(allocator, ivar);
409 try self.addObjCIVar(gpa, ivar);
502410 }
503411 }
504412
505413 if (stub.objc_eh_types) |objc_eh_types| {
506414 for (objc_eh_types) |eht| {
507 try self.addObjCEhTypeSymbol(allocator, eht);
415 try self.addObjCEhType(gpa, eht);
508416 }
509417 }
510418 },
......@@ -514,10 +422,9 @@ pub fn parseFromStub(
514422 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
515423 // re-exports directly in the stub file.
516424 for (lib_stub.inner) |elem| {
517 if (elem == .v3) break;
425 if (elem == .v3) continue;
518426 const stub = elem.v4;
519427
520 // TODO track which libs were already parsed in different steps
521428 if (stub.reexported_libraries) |reexports| {
522429 for (reexports) |reexp| {
523430 if (!matcher.matchesTarget(reexp.targets)) continue;
......@@ -527,30 +434,437 @@ pub fn parseFromStub(
527434
528435 log.debug(" (found re-export '{s}')", .{lib});
529436
530 const dep_id = try Id.default(allocator, lib);
531 try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });
437 const dep_id = try Id.default(gpa, lib);
438 try self.dependents.append(gpa, dep_id);
532439 }
533440 }
534441 }
535442 }
536443}
537444
538const Dylib = @This();
445fn addObjCClass(self: *Dylib, allocator: Allocator, name: []const u8) !void {
446 try self.addObjCExport(allocator, "_OBJC_CLASS_", name);
447 try self.addObjCExport(allocator, "_OBJC_METACLASS_", name);
448}
449
450fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void {
451 try self.addObjCExport(allocator, "_OBJC_IVAR_", name);
452}
453
454fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void {
455 try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name);
456}
457
458fn addObjCExport(
459 self: *Dylib,
460 allocator: Allocator,
461 comptime prefix: []const u8,
462 name: []const u8,
463) !void {
464 const full_name = try std.fmt.allocPrint(allocator, prefix ++ "$_{s}", .{name});
465 defer allocator.free(full_name);
466 try self.addExport(allocator, full_name, .{});
467}
468
469pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void {
470 const gpa = macho_file.base.comp.gpa;
471
472 try self.symbols.ensureTotalCapacityPrecise(gpa, self.exports.items(.name).len);
473
474 for (self.exports.items(.name)) |noff| {
475 const name = self.getString(noff);
476 const off = try macho_file.strings.insert(gpa, name);
477 const gop = try macho_file.getOrCreateGlobal(off);
478 self.symbols.addOneAssumeCapacity().* = gop.index;
479 }
480}
481
482fn initPlatform(self: *Dylib) void {
483 var it = LoadCommandIterator{
484 .ncmds = self.header.?.ncmds,
485 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
486 };
487 self.platform = while (it.next()) |cmd| {
488 switch (cmd.cmd()) {
489 .BUILD_VERSION,
490 .VERSION_MIN_MACOSX,
491 .VERSION_MIN_IPHONEOS,
492 .VERSION_MIN_TVOS,
493 .VERSION_MIN_WATCHOS,
494 => break MachO.Platform.fromLoadCommand(cmd),
495 else => {},
496 }
497 } else null;
498}
499
500pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) void {
501 const tracy = trace(@src());
502 defer tracy.end();
503
504 if (!self.explicit and !self.hoisted) return;
505
506 for (self.symbols.items, self.exports.items(.flags)) |index, flags| {
507 const global = macho_file.getSymbol(index);
508 if (self.asFile().getSymbolRank(.{
509 .weak = flags.weak,
510 }) < global.getSymbolRank(macho_file)) {
511 global.value = 0;
512 global.atom = 0;
513 global.nlist_idx = 0;
514 global.file = self.index;
515 global.flags.weak = flags.weak;
516 global.flags.weak_ref = false;
517 global.flags.tlv = flags.tlv;
518 global.flags.dyn_ref = false;
519 global.flags.tentative = false;
520 global.visibility = .global;
521 }
522 }
523}
524
525pub fn resetGlobals(self: *Dylib, macho_file: *MachO) void {
526 for (self.symbols.items) |sym_index| {
527 const sym = macho_file.getSymbol(sym_index);
528 const name = sym.name;
529 sym.* = .{};
530 sym.name = name;
531 }
532}
533
534pub fn isAlive(self: Dylib, macho_file: *MachO) bool {
535 if (!macho_file.dead_strip_dylibs) return self.explicit or self.referenced or self.needed;
536 return self.referenced or self.needed;
537}
538
539pub fn markReferenced(self: *Dylib, macho_file: *MachO) void {
540 const tracy = trace(@src());
541 defer tracy.end();
542
543 for (self.symbols.items) |global_index| {
544 const global = macho_file.getSymbol(global_index);
545 const file_ptr = global.getFile(macho_file) orelse continue;
546 if (file_ptr.getIndex() != self.index) continue;
547 if (global.isLocal()) continue;
548 self.referenced = true;
549 break;
550 }
551}
552
553pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void {
554 const tracy = trace(@src());
555 defer tracy.end();
556
557 for (self.symbols.items) |global_index| {
558 const global = macho_file.getSymbol(global_index);
559 const file_ptr = global.getFile(macho_file) orelse continue;
560 if (file_ptr.getIndex() != self.index) continue;
561 if (global.isLocal()) continue;
562 assert(global.flags.import);
563 global.flags.output_symtab = true;
564 try global.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
565 self.output_symtab_ctx.nimports += 1;
566 self.output_symtab_ctx.strsize += @as(u32, @intCast(global.getName(macho_file).len + 1));
567 }
568}
569
570pub fn writeSymtab(self: Dylib, macho_file: *MachO) void {
571 const tracy = trace(@src());
572 defer tracy.end();
573
574 for (self.symbols.items) |global_index| {
575 const global = macho_file.getSymbol(global_index);
576 const file = global.getFile(macho_file) orelse continue;
577 if (file.getIndex() != self.index) continue;
578 const idx = global.getOutputSymtabIndex(macho_file) orelse continue;
579 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
580 macho_file.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
581 macho_file.strtab.appendAssumeCapacity(0);
582 const out_sym = &macho_file.symtab.items[idx];
583 out_sym.n_strx = n_strx;
584 global.setOutputSym(macho_file, out_sym);
585 }
586}
587
588pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib {
589 return macho_file.getFile(self.umbrella).?.dylib;
590}
591
592fn getLoadCommand(self: Dylib, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
593 var it = LoadCommandIterator{
594 .ncmds = self.header.?.ncmds,
595 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
596 };
597 while (it.next()) |cmd| {
598 if (cmd.cmd() == lc) return cmd;
599 } else return null;
600}
601
602fn insertString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 {
603 const off = @as(u32, @intCast(self.strtab.items.len));
604 try self.strtab.writer(allocator).print("{s}\x00", .{name});
605 return off;
606}
607
608pub inline fn getString(self: Dylib, off: u32) [:0]const u8 {
609 assert(off < self.strtab.items.len);
610 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
611}
612
613pub fn asFile(self: *Dylib) File {
614 return .{ .dylib = self };
615}
616
617pub fn format(
618 self: *Dylib,
619 comptime unused_fmt_string: []const u8,
620 options: std.fmt.FormatOptions,
621 writer: anytype,
622) !void {
623 _ = self;
624 _ = unused_fmt_string;
625 _ = options;
626 _ = writer;
627 @compileError("do not format dylib directly");
628}
629
630pub fn fmtSymtab(self: *Dylib, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
631 return .{ .data = .{
632 .dylib = self,
633 .macho_file = macho_file,
634 } };
635}
636
637const FormatContext = struct {
638 dylib: *Dylib,
639 macho_file: *MachO,
640};
641
642fn formatSymtab(
643 ctx: FormatContext,
644 comptime unused_fmt_string: []const u8,
645 options: std.fmt.FormatOptions,
646 writer: anytype,
647) !void {
648 _ = unused_fmt_string;
649 _ = options;
650 const dylib = ctx.dylib;
651 try writer.writeAll(" globals\n");
652 for (dylib.symbols.items) |index| {
653 const global = ctx.macho_file.getSymbol(index);
654 try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
655 }
656}
657
658pub const TargetMatcher = struct {
659 allocator: Allocator,
660 cpu_arch: std.Target.Cpu.Arch,
661 platform: macho.PLATFORM,
662 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
663
664 pub fn init(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) !TargetMatcher {
665 var self = TargetMatcher{
666 .allocator = allocator,
667 .cpu_arch = cpu_arch,
668 .platform = platform,
669 };
670 const apple_string = try targetToAppleString(allocator, cpu_arch, platform);
671 try self.target_strings.append(allocator, apple_string);
672
673 switch (platform) {
674 .IOSSIMULATOR, .TVOSSIMULATOR, .WATCHOSSIMULATOR => {
675 // For Apple simulator targets, linking gets tricky as we need to link against the simulator
676 // hosts dylibs too.
677 const host_target = try targetToAppleString(allocator, cpu_arch, .MACOS);
678 try self.target_strings.append(allocator, host_target);
679 },
680 else => {},
681 }
682
683 return self;
684 }
685
686 pub fn deinit(self: *TargetMatcher) void {
687 for (self.target_strings.items) |t| {
688 self.allocator.free(t);
689 }
690 self.target_strings.deinit(self.allocator);
691 }
692
693 inline fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 {
694 return switch (cpu_arch) {
695 .aarch64 => "arm64",
696 .x86_64 => "x86_64",
697 else => unreachable,
698 };
699 }
700
701 pub fn targetToAppleString(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) ![]const u8 {
702 const arch = cpuArchToAppleString(cpu_arch);
703 const plat = switch (platform) {
704 .MACOS => "macos",
705 .IOS => "ios",
706 .TVOS => "tvos",
707 .WATCHOS => "watchos",
708 .IOSSIMULATOR => "ios-simulator",
709 .TVOSSIMULATOR => "tvos-simulator",
710 .WATCHOSSIMULATOR => "watchos-simulator",
711 .BRIDGEOS => "bridgeos",
712 .MACCATALYST => "maccatalyst",
713 .DRIVERKIT => "driverkit",
714 else => unreachable,
715 };
716 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, plat });
717 }
718
719 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
720 for (stack) |v| {
721 if (mem.eql(u8, v, needle)) return true;
722 }
723 return false;
724 }
725
726 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
727 return hasValue(archs, cpuArchToAppleString(self.cpu_arch));
728 }
729
730 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
731 for (self.target_strings.items) |t| {
732 if (hasValue(targets, t)) return true;
733 }
734 return false;
735 }
736
737 pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
738 var arena = std.heap.ArenaAllocator.init(self.allocator);
739 defer arena.deinit();
740
741 const targets = switch (tbd) {
742 .v3 => |v3| blk: {
743 var targets = std.ArrayList([]const u8).init(arena.allocator());
744 for (v3.archs) |arch| {
745 const target = try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform });
746 try targets.append(target);
747 }
748 break :blk targets.items;
749 },
750 .v4 => |v4| v4.targets,
751 };
752
753 return self.matchesTarget(targets);
754 }
755};
756
757pub const Id = struct {
758 name: []const u8,
759 timestamp: u32,
760 current_version: u32,
761 compatibility_version: u32,
762
763 pub fn default(allocator: Allocator, name: []const u8) !Id {
764 return Id{
765 .name = try allocator.dupe(u8, name),
766 .timestamp = 2,
767 .current_version = 0x10000,
768 .compatibility_version = 0x10000,
769 };
770 }
771
772 pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
773 return Id{
774 .name = try allocator.dupe(u8, name),
775 .timestamp = lc.dylib.timestamp,
776 .current_version = lc.dylib.current_version,
777 .compatibility_version = lc.dylib.compatibility_version,
778 };
779 }
780
781 pub fn deinit(id: Id, allocator: Allocator) void {
782 allocator.free(id.name);
783 }
784
785 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
786
787 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
788 id.current_version = try parseVersion(version);
789 }
790
791 pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
792 id.compatibility_version = try parseVersion(version);
793 }
794
795 fn parseVersion(version: anytype) ParseError!u32 {
796 const string = blk: {
797 switch (version) {
798 .int => |int| {
799 var out: u32 = 0;
800 const major = math.cast(u16, int) orelse return error.Overflow;
801 out += @as(u32, @intCast(major)) << 16;
802 return out;
803 },
804 .float => |float| {
805 var buf: [256]u8 = undefined;
806 break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
807 },
808 .string => |string| {
809 break :blk string;
810 },
811 }
812 };
813
814 var out: u32 = 0;
815 var values: [3][]const u8 = undefined;
816
817 var split = mem.split(u8, string, ".");
818 var count: u4 = 0;
819 while (split.next()) |value| {
820 if (count > 2) {
821 log.debug("malformed version field: {s}", .{string});
822 return 0x10000;
823 }
824 values[count] = value;
825 count += 1;
826 }
827
828 if (count > 2) {
829 out += try fmt.parseInt(u8, values[2], 10);
830 }
831 if (count > 1) {
832 out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;
833 }
834 out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
835
836 return out;
837 }
838};
839
840const Export = struct {
841 name: u32,
842 flags: Flags,
843
844 const Flags = packed struct {
845 abs: bool = false,
846 weak: bool = false,
847 tlv: bool = false,
848 };
849};
539850
540const std = @import("std");
541851const assert = std.debug.assert;
852const fat = @import("fat.zig");
542853const fs = std.fs;
543854const fmt = std.fmt;
544855const log = std.log.scoped(.link);
545856const macho = std.macho;
546857const math = std.math;
547858const mem = std.mem;
548const fat = @import("fat.zig");
549859const tapi = @import("../tapi.zig");
860const trace = @import("../../tracy.zig").trace;
861const std = @import("std");
550862
551863const Allocator = mem.Allocator;
864const Dylib = @This();
865const File = @import("file.zig").File;
552866const LibStub = tapi.LibStub;
553867const LoadCommandIterator = macho.LoadCommandIterator;
554868const MachO = @import("../MachO.zig");
555const Platform = @import("load_commands.zig").Platform;
869const Symbol = @import("Symbol.zig");
556870const Tbd = tapi.Tbd;
src/link/MachO/InternalObject.zig created+249
......@@ -0,0 +1,249 @@
1index: File.Index,
2
3sections: std.MultiArrayList(Section) = .{},
4atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
5symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
6
7objc_methnames: std.ArrayListUnmanaged(u8) = .{},
8objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
9
10output_symtab_ctx: MachO.SymtabCtx = .{},
11
12pub fn deinit(self: *InternalObject, allocator: Allocator) void {
13 for (self.sections.items(.relocs)) |*relocs| {
14 relocs.deinit(allocator);
15 }
16 self.sections.deinit(allocator);
17 self.atoms.deinit(allocator);
18 self.symbols.deinit(allocator);
19 self.objc_methnames.deinit(allocator);
20}
21
22pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index {
23 const gpa = macho_file.base.comp.gpa;
24 try self.symbols.ensureUnusedCapacity(gpa, 1);
25 const off = try macho_file.strings.insert(gpa, name);
26 const gop = try macho_file.getOrCreateGlobal(off);
27 self.symbols.addOneAssumeCapacity().* = gop.index;
28 const sym = macho_file.getSymbol(gop.index);
29 sym.* = .{ .name = off, .file = self.index };
30 return gop.index;
31}
32
33/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
34pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 {
35 const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);
36 return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file);
37}
38
39fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {
40 const gpa = macho_file.base.comp.gpa;
41 const atom_index = try macho_file.addAtom();
42 try self.atoms.append(gpa, atom_index);
43
44 const name = try std.fmt.allocPrintZ(gpa, "__TEXT$__objc_methname${s}", .{methname});
45 defer gpa.free(name);
46 const atom = macho_file.getAtom(atom_index).?;
47 atom.atom_index = atom_index;
48 atom.name = try macho_file.strings.insert(gpa, name);
49 atom.file = self.index;
50 atom.size = methname.len + 1;
51 atom.alignment = .@"1";
52
53 const n_sect = try self.addSection(gpa, "__TEXT", "__objc_methname");
54 const sect = &self.sections.items(.header)[n_sect];
55 sect.flags = macho.S_CSTRING_LITERALS;
56 sect.size = atom.size;
57 sect.@"align" = 0;
58 atom.n_sect = n_sect;
59 self.sections.items(.extra)[n_sect].is_objc_methname = true;
60
61 sect.offset = @intCast(self.objc_methnames.items.len);
62 try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1);
63 self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable;
64
65 return atom_index;
66}
67
68fn addObjcSelrefsSection(
69 self: *InternalObject,
70 methname: []const u8,
71 methname_atom_index: Atom.Index,
72 macho_file: *MachO,
73) !Atom.Index {
74 const gpa = macho_file.base.comp.gpa;
75 const atom_index = try macho_file.addAtom();
76 try self.atoms.append(gpa, atom_index);
77
78 const name = try std.fmt.allocPrintZ(gpa, "__DATA$__objc_selrefs${s}", .{methname});
79 defer gpa.free(name);
80 const atom = macho_file.getAtom(atom_index).?;
81 atom.atom_index = atom_index;
82 atom.name = try macho_file.strings.insert(gpa, name);
83 atom.file = self.index;
84 atom.size = @sizeOf(u64);
85 atom.alignment = .@"8";
86
87 const n_sect = try self.addSection(gpa, "__DATA", "__objc_selrefs");
88 const sect = &self.sections.items(.header)[n_sect];
89 sect.flags = macho.S_LITERAL_POINTERS | macho.S_ATTR_NO_DEAD_STRIP;
90 sect.offset = 0;
91 sect.size = atom.size;
92 sect.@"align" = 3;
93 atom.n_sect = n_sect;
94 self.sections.items(.extra)[n_sect].is_objc_selref = true;
95
96 const relocs = &self.sections.items(.relocs)[n_sect];
97 try relocs.ensureUnusedCapacity(gpa, 1);
98 relocs.appendAssumeCapacity(.{
99 .tag = .local,
100 .offset = 0,
101 .target = methname_atom_index,
102 .addend = 0,
103 .type = .unsigned,
104 .meta = .{
105 .pcrel = false,
106 .length = 3,
107 .symbolnum = 0, // Only used when synthesising unwind records so can be anything
108 .has_subtractor = false,
109 },
110 });
111 atom.relocs = .{ .pos = 0, .len = 1 };
112
113 return atom_index;
114}
115
116pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
117 for (self.symbols.items) |sym_index| {
118 const sym = macho_file.getSymbol(sym_index);
119 if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
120 sym.flags.output_symtab = true;
121 if (sym.isLocal()) {
122 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
123 self.output_symtab_ctx.nlocals += 1;
124 } else if (sym.flags.@"export") {
125 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
126 self.output_symtab_ctx.nexports += 1;
127 } else {
128 assert(sym.flags.import);
129 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
130 self.output_symtab_ctx.nimports += 1;
131 }
132 self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
133 }
134}
135
136pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void {
137 for (self.symbols.items) |sym_index| {
138 const sym = macho_file.getSymbol(sym_index);
139 if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
140 const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
141 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
142 macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
143 macho_file.strtab.appendAssumeCapacity(0);
144 const out_sym = &macho_file.symtab.items[idx];
145 out_sym.n_strx = n_strx;
146 sym.setOutputSym(macho_file, out_sym);
147 }
148}
149
150fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
151 const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
152 self.sections.set(n_sect, .{
153 .header = .{
154 .sectname = MachO.makeStaticString(sectname),
155 .segname = MachO.makeStaticString(segname),
156 },
157 });
158 return n_sect;
159}
160
161pub fn getSectionData(self: *const InternalObject, index: u32) []const u8 {
162 const slice = self.sections.slice();
163 assert(index < slice.items(.header).len);
164 const sect = slice.items(.header)[index];
165 const extra = slice.items(.extra)[index];
166 if (extra.is_objc_methname) {
167 return self.objc_methnames.items[sect.offset..][0..sect.size];
168 } else if (extra.is_objc_selref) {
169 return &self.objc_selrefs;
170 } else @panic("ref to non-existent section");
171}
172
173pub fn asFile(self: *InternalObject) File {
174 return .{ .internal = self };
175}
176
177const FormatContext = struct {
178 self: *InternalObject,
179 macho_file: *MachO,
180};
181
182pub fn fmtAtoms(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
183 return .{ .data = .{
184 .self = self,
185 .macho_file = macho_file,
186 } };
187}
188
189fn formatAtoms(
190 ctx: FormatContext,
191 comptime unused_fmt_string: []const u8,
192 options: std.fmt.FormatOptions,
193 writer: anytype,
194) !void {
195 _ = unused_fmt_string;
196 _ = options;
197 try writer.writeAll(" atoms\n");
198 for (ctx.self.atoms.items) |atom_index| {
199 const atom = ctx.macho_file.getAtom(atom_index).?;
200 try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
201 }
202}
203
204pub fn fmtSymtab(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
205 return .{ .data = .{
206 .self = self,
207 .macho_file = macho_file,
208 } };
209}
210
211fn formatSymtab(
212 ctx: FormatContext,
213 comptime unused_fmt_string: []const u8,
214 options: std.fmt.FormatOptions,
215 writer: anytype,
216) !void {
217 _ = unused_fmt_string;
218 _ = options;
219 try writer.writeAll(" symbols\n");
220 for (ctx.self.symbols.items) |index| {
221 const global = ctx.macho_file.getSymbol(index);
222 try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
223 }
224}
225
226const Section = struct {
227 header: macho.section_64,
228 relocs: std.ArrayListUnmanaged(Relocation) = .{},
229 extra: Extra = .{},
230
231 const Extra = packed struct {
232 is_objc_methname: bool = false,
233 is_objc_selref: bool = false,
234 };
235};
236
237const assert = std.debug.assert;
238const macho = std.macho;
239const mem = std.mem;
240const std = @import("std");
241
242const Allocator = std.mem.Allocator;
243const Atom = @import("Atom.zig");
244const File = @import("file.zig").File;
245const InternalObject = @This();
246const MachO = @import("../MachO.zig");
247const Object = @import("Object.zig");
248const Relocation = @import("Relocation.zig");
249const Symbol = @import("Symbol.zig");
src/link/MachO/Object.zig+1969-916
......@@ -1,1130 +1,2183 @@
1//! Represents an input relocatable Object file.
2//! Each Object is fully loaded into memory for easier
3//! access into different data within.
4
5name: []const u8,
1archive: ?[]const u8 = null,
2path: []const u8,
63mtime: u64,
7contents: []align(@alignOf(u64)) const u8,
8
9header: macho.mach_header_64 = undefined,
10
11/// Symtab and strtab might not exist for empty object files so we use an optional
12/// to signal this.
13in_symtab: ?[]align(1) const macho.nlist_64 = null,
14in_strtab: ?[]const u8 = null,
15
16/// Output symtab is sorted so that we can easily reference symbols following each
17/// other in address space.
18/// The length of the symtab is at least of the input symtab length however there
19/// can be trailing section symbols.
20symtab: []macho.nlist_64 = undefined,
21/// Can be undefined as set together with in_symtab.
22source_symtab_lookup: []u32 = undefined,
23/// Can be undefined as set together with in_symtab.
24reverse_symtab_lookup: []u32 = undefined,
25/// Can be undefined as set together with in_symtab.
26source_address_lookup: []i64 = undefined,
27/// Can be undefined as set together with in_symtab.
28source_section_index_lookup: []Entry = undefined,
29/// Can be undefined as set together with in_symtab.
30strtab_lookup: []u32 = undefined,
31/// Can be undefined as set together with in_symtab.
32atom_by_index_table: []?Atom.Index = undefined,
33/// Can be undefined as set together with in_symtab.
34globals_lookup: []i64 = undefined,
35/// Can be undefined as set together with in_symtab.
36relocs_lookup: []Entry = undefined,
37
38/// All relocations sorted and flatened, sorted by address descending
39/// per section.
40relocations: std.ArrayListUnmanaged(macho.relocation_info) = .{},
41/// Beginning index to the relocations array for each input section
42/// defined within this Object file.
43section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{},
44
45/// Data-in-code records sorted by address.
46data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
4data: []const u8,
5index: File.Index,
6
7header: ?macho.mach_header_64 = null,
8sections: std.MultiArrayList(Section) = .{},
9symtab: std.MultiArrayList(Nlist) = .{},
10strtab: []const u8 = &[0]u8{},
4711
12symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
4813atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
49exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
5014
51eh_frame_sect_id: ?u8 = null,
52eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
53eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
15platform: ?MachO.Platform = null,
16dwarf_info: ?DwarfInfo = null,
17stab_files: std.ArrayListUnmanaged(StabFile) = .{},
5418
55unwind_info_sect_id: ?u8 = null,
56unwind_relocs_lookup: []Record = undefined,
57unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
19eh_frame_sect_index: ?u8 = null,
20compact_unwind_sect_index: ?u8 = null,
21cies: std.ArrayListUnmanaged(Cie) = .{},
22fdes: std.ArrayListUnmanaged(Fde) = .{},
23eh_frame_data: std.ArrayListUnmanaged(u8) = .{},
24unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{},
5825
59const Entry = struct {
60 start: u32 = 0,
61 len: u32 = 0,
62};
26alive: bool = true,
27hidden: bool = false,
6328
64const Record = struct {
65 dead: bool,
66 reloc: Entry,
67};
29dynamic_relocs: MachO.DynamicRelocs = .{},
30output_symtab_ctx: MachO.SymtabCtx = .{},
6831
69pub fn isObject(file: std.fs.File) bool {
70 const reader = file.reader();
71 const hdr = reader.readStruct(macho.mach_header_64) catch return false;
72 defer file.seekTo(0) catch {};
73 return hdr.filetype == macho.MH_OBJECT;
32pub fn isObject(path: []const u8) !bool {
33 const file = try std.fs.cwd().openFile(path, .{});
34 defer file.close();
35 const header = file.reader().readStruct(macho.mach_header_64) catch return false;
36 return header.filetype == macho.MH_OBJECT;
7437}
7538
76pub fn deinit(self: *Object, gpa: Allocator) void {
77 self.atoms.deinit(gpa);
78 self.exec_atoms.deinit(gpa);
79 gpa.free(self.name);
80 gpa.free(self.contents);
81 if (self.in_symtab) |_| {
82 gpa.free(self.source_symtab_lookup);
83 gpa.free(self.reverse_symtab_lookup);
84 gpa.free(self.source_address_lookup);
85 gpa.free(self.source_section_index_lookup);
86 gpa.free(self.strtab_lookup);
87 gpa.free(self.symtab);
88 gpa.free(self.atom_by_index_table);
89 gpa.free(self.globals_lookup);
90 gpa.free(self.relocs_lookup);
39pub fn deinit(self: *Object, allocator: Allocator) void {
40 for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
41 relocs.deinit(allocator);
42 sub.deinit(allocator);
9143 }
92 self.eh_frame_relocs_lookup.deinit(gpa);
93 self.eh_frame_records_lookup.deinit(gpa);
94 if (self.hasUnwindRecords()) {
95 gpa.free(self.unwind_relocs_lookup);
44 self.sections.deinit(allocator);
45 self.symtab.deinit(allocator);
46 self.symbols.deinit(allocator);
47 self.atoms.deinit(allocator);
48 self.cies.deinit(allocator);
49 self.fdes.deinit(allocator);
50 self.eh_frame_data.deinit(allocator);
51 self.unwind_records.deinit(allocator);
52 if (self.dwarf_info) |*dw| dw.deinit(allocator);
53 for (self.stab_files.items) |*sf| {
54 sf.stabs.deinit(allocator);
9655 }
97 self.unwind_records_lookup.deinit(gpa);
98 self.relocations.deinit(gpa);
99 self.section_relocs_lookup.deinit(gpa);
100 self.data_in_code.deinit(gpa);
56 self.stab_files.deinit(allocator);
57 allocator.free(self.data);
10158}
10259
103pub fn parse(self: *Object, allocator: Allocator) !void {
104 var stream = std.io.fixedBufferStream(self.contents);
60pub fn parse(self: *Object, macho_file: *MachO) !void {
61 const tracy = trace(@src());
62 defer tracy.end();
63
64 const gpa = macho_file.base.comp.gpa;
65 var stream = std.io.fixedBufferStream(self.data);
10566 const reader = stream.reader();
10667
10768 self.header = try reader.readStruct(macho.mach_header_64);
10869
109 var it = LoadCommandIterator{
110 .ncmds = self.header.ncmds,
111 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
70 const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
71 macho.CPU_TYPE_ARM64 => .aarch64,
72 macho.CPU_TYPE_X86_64 => .x86_64,
73 else => |x| {
74 try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
75 return error.InvalidCpuArch;
76 },
11277 };
113 const nsects = self.getSourceSections().len;
114
115 // Prepopulate relocations per section lookup table.
116 try self.section_relocs_lookup.resize(allocator, nsects);
117 @memset(self.section_relocs_lookup.items, 0);
118
119 // Parse symtab.
120 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
121 .SYMTAB => break cmd.cast(macho.symtab_command).?,
122 else => {},
123 } else return;
124
125 self.in_symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.contents.ptr + symtab.symoff))[0..symtab.nsyms];
126 self.in_strtab = self.contents[symtab.stroff..][0..symtab.strsize];
127
128 self.symtab = try allocator.alloc(macho.nlist_64, self.in_symtab.?.len + nsects);
129 self.source_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
130 self.reverse_symtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
131 self.strtab_lookup = try allocator.alloc(u32, self.in_symtab.?.len);
132 self.globals_lookup = try allocator.alloc(i64, self.in_symtab.?.len);
133 self.atom_by_index_table = try allocator.alloc(?Atom.Index, self.in_symtab.?.len + nsects);
134 self.relocs_lookup = try allocator.alloc(Entry, self.in_symtab.?.len + nsects);
135 // This is wasteful but we need to be able to lookup source symbol address after stripping and
136 // allocating of sections.
137 self.source_address_lookup = try allocator.alloc(i64, self.in_symtab.?.len);
138 self.source_section_index_lookup = try allocator.alloc(Entry, nsects);
139
140 for (self.symtab) |*sym| {
141 sym.* = .{
142 .n_value = 0,
143 .n_sect = 0,
144 .n_desc = 0,
145 .n_strx = 0,
146 .n_type = 0,
147 };
148 }
149
150 @memset(self.globals_lookup, -1);
151 @memset(self.atom_by_index_table, null);
152 @memset(self.source_section_index_lookup, .{});
153 @memset(self.relocs_lookup, .{});
154
155 // You would expect that the symbol table is at least pre-sorted based on symbol's type:
156 // local < extern defined < undefined. Unfortunately, this is not guaranteed! For instance,
157 // the GO compiler does not necessarily respect that therefore we sort immediately by type
158 // and address within.
159 var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(allocator, self.in_symtab.?.len);
160 defer sorted_all_syms.deinit();
161
162 for (self.in_symtab.?, 0..) |_, index| {
163 sorted_all_syms.appendAssumeCapacity(.{ .index = @as(u32, @intCast(index)) });
78 if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
79 try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
80 return error.InvalidCpuArch;
16481 }
16582
166 // We sort by type: defined < undefined, and
167 // afterwards by address in each group. Normally, dysymtab should
168 // be enough to guarantee the sort, but turns out not every compiler
169 // is kind enough to specify the symbols in the correct order.
170 mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan);
171
172 var prev_sect_id: u8 = 0;
173 var section_index_lookup: ?Entry = null;
174 for (sorted_all_syms.items, 0..) |sym_id, i| {
175 const sym = sym_id.getSymbol(self);
176
177 if (section_index_lookup) |*lookup| {
178 if (sym.n_sect != prev_sect_id or sym.undf()) {
179 self.source_section_index_lookup[prev_sect_id - 1] = lookup.*;
180 section_index_lookup = null;
181 } else {
182 lookup.len += 1;
83 if (self.getLoadCommand(.SEGMENT_64)) |lc| {
84 const sections = lc.getSections();
85 try self.sections.ensureUnusedCapacity(gpa, sections.len);
86 for (sections) |sect| {
87 const index = try self.sections.addOne(gpa);
88 self.sections.set(index, .{ .header = sect });
89
90 if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
91 self.eh_frame_sect_index = @intCast(index);
92 } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) {
93 self.compact_unwind_sect_index = @intCast(index);
18394 }
18495 }
185 if (sym.sect() and section_index_lookup == null) {
186 section_index_lookup = .{ .start = @as(u32, @intCast(i)), .len = 1 };
96 }
97 if (self.getLoadCommand(.SYMTAB)) |lc| {
98 const cmd = lc.cast(macho.symtab_command).?;
99 self.strtab = self.data[cmd.stroff..][0..cmd.strsize];
100
101 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.data.ptr + cmd.symoff))[0..cmd.nsyms];
102 try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
103 for (symtab) |nlist| {
104 self.symtab.appendAssumeCapacity(.{
105 .nlist = nlist,
106 .atom = 0,
107 .size = 0,
108 });
187109 }
110 }
188111
189 prev_sect_id = sym.n_sect;
112 const NlistIdx = struct {
113 nlist: macho.nlist_64,
114 idx: usize,
190115
191 self.symtab[i] = sym;
192 self.source_symtab_lookup[i] = sym_id.index;
193 self.reverse_symtab_lookup[sym_id.index] = @as(u32, @intCast(i));
194 self.source_address_lookup[i] = if (sym.undf()) -1 else @as(i64, @intCast(sym.n_value));
116 fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
117 if (!nl.ext()) {
118 const name = ctx.getString(nl.n_strx);
119 if (name.len == 0) return 5;
120 if (name[0] == 'l' or name[0] == 'L') return 4;
121 return 3;
122 }
123 return if (nl.weakDef()) 2 else 1;
124 }
195125
196 const sym_name_len = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.in_strtab.?.ptr + sym.n_strx)), 0).len + 1;
197 self.strtab_lookup[i] = @as(u32, @intCast(sym_name_len));
198 }
126 fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {
127 if (lhs.nlist.n_sect == rhs.nlist.n_sect) {
128 if (lhs.nlist.n_value == rhs.nlist.n_value) {
129 return rank(ctx, lhs.nlist) < rank(ctx, rhs.nlist);
130 }
131 return lhs.nlist.n_value < rhs.nlist.n_value;
132 }
133 return lhs.nlist.n_sect < rhs.nlist.n_sect;
134 }
135 };
199136
200 // If there were no undefined symbols, make sure we populate the
201 // source section index lookup for the last scanned section.
202 if (section_index_lookup) |lookup| {
203 self.source_section_index_lookup[prev_sect_id - 1] = lookup;
137 var nlists = try std.ArrayList(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
138 defer nlists.deinit();
139 for (self.symtab.items(.nlist), 0..) |nlist, i| {
140 if (nlist.stab() or !nlist.sect()) continue;
141 nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
204142 }
143 mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
205144
206 // Parse __TEXT,__eh_frame header if one exists
207 self.eh_frame_sect_id = self.getSourceSectionIndexByName("__TEXT", "__eh_frame");
208
209 // Parse __LD,__compact_unwind header if one exists
210 self.unwind_info_sect_id = self.getSourceSectionIndexByName("__LD", "__compact_unwind");
211 if (self.hasUnwindRecords()) {
212 self.unwind_relocs_lookup = try allocator.alloc(Record, self.getUnwindRecords().len);
213 @memset(self.unwind_relocs_lookup, .{ .dead = true, .reloc = .{} });
145 if (self.hasSubsections()) {
146 try self.initSubsections(nlists.items, macho_file);
147 } else {
148 try self.initSections(nlists.items, macho_file);
214149 }
215}
216150
217const SymbolAtIndex = struct {
218 index: u32,
151 try self.initLiteralSections(macho_file);
152 try self.linkNlistToAtom(macho_file);
219153
220 const Context = *const Object;
154 try self.sortAtoms(macho_file);
155 try self.initSymbols(macho_file);
156 try self.initSymbolStabs(nlists.items, macho_file);
157 try self.initRelocs(macho_file);
221158
222 fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 {
223 return ctx.in_symtab.?[self.index];
159 // Parse DWARF __TEXT,__eh_frame section
160 if (self.eh_frame_sect_index) |index| {
161 try self.initEhFrameRecords(index, macho_file);
224162 }
225163
226 fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 {
227 const off = self.getSymbol(ctx).n_strx;
228 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.in_strtab.?.ptr + off)), 0);
164 // Parse Apple's __LD,__compact_unwind section
165 if (self.compact_unwind_sect_index) |index| {
166 try self.initUnwindRecords(index, macho_file);
229167 }
230168
231 fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 {
232 const sym = self.getSymbol(ctx);
233 if (!sym.ext()) {
234 const sym_name = self.getSymbolName(ctx);
235 if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3;
236 return 2;
237 }
238 if (sym.weakDef() or sym.pext()) return 1;
239 return 0;
169 if (self.hasUnwindRecords() or self.hasEhFrameRecords()) {
170 try self.parseUnwindRecords(macho_file);
240171 }
241172
242 /// Performs lexicographic-like check.
243 /// * lhs and rhs defined
244 /// * if lhs == rhs
245 /// * if lhs.n_sect == rhs.n_sect
246 /// * ext < weak < local < temp
247 /// * lhs.n_sect < rhs.n_sect
248 /// * lhs < rhs
249 /// * !rhs is undefined
250 fn lessThan(ctx: Context, lhs_index: SymbolAtIndex, rhs_index: SymbolAtIndex) bool {
251 const lhs = lhs_index.getSymbol(ctx);
252 const rhs = rhs_index.getSymbol(ctx);
253 if (lhs.sect() and rhs.sect()) {
254 if (lhs.n_value == rhs.n_value) {
255 if (lhs.n_sect == rhs.n_sect) {
256 const lhs_senior = lhs_index.getSymbolSeniority(ctx);
257 const rhs_senior = rhs_index.getSymbolSeniority(ctx);
258 if (lhs_senior == rhs_senior) {
259 return lessThanByNStrx(ctx, lhs_index, rhs_index);
260 } else return lhs_senior < rhs_senior;
261 } else return lhs.n_sect < rhs.n_sect;
262 } else return lhs.n_value < rhs.n_value;
263 } else if (lhs.undf() and rhs.undf()) {
264 return lessThanByNStrx(ctx, lhs_index, rhs_index);
265 } else return rhs.undf();
266 }
173 self.initPlatform();
267174
268 fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool {
269 return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx;
175 if (self.platform) |platform| {
176 if (!macho_file.platform.eqlTarget(platform)) {
177 try macho_file.reportParseError2(self.index, "invalid platform: {}", .{
178 platform.fmtTarget(macho_file.getTarget().cpu.arch),
179 });
180 return error.InvalidTarget;
181 }
182 // TODO: this causes the CI to fail so I'm commenting this check out so that
183 // I can work out the rest of the changes first
184 // if (macho_file.platform.version.order(platform.version) == .lt) {
185 // try macho_file.reportParseError2(self.index, "object file built for newer platform: {}: {} < {}", .{
186 // macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch),
187 // macho_file.platform.version,
188 // platform.version,
189 // });
190 // return error.InvalidTarget;
191 // }
270192 }
271};
272193
273fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct {
274 index: u32,
275 len: u32,
276} {
277 const FirstMatch = struct {
278 n_sect: u8,
194 try self.initDwarfInfo(macho_file);
279195
280 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
281 return symbol.n_sect == pred.n_sect;
196 for (self.atoms.items) |atom_index| {
197 const atom = macho_file.getAtom(atom_index).?;
198 const isec = atom.getInputSection(macho_file);
199 if (mem.eql(u8, isec.sectName(), "__eh_frame") or
200 mem.eql(u8, isec.sectName(), "__compact_unwind") or
201 isec.attrs() & macho.S_ATTR_DEBUG != 0)
202 {
203 atom.flags.alive = false;
282204 }
283 };
284 const FirstNonMatch = struct {
285 n_sect: u8,
205 }
206}
286207
287 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
288 return symbol.n_sect != pred.n_sect;
289 }
208inline fn isLiteral(sect: macho.section_64) bool {
209 return switch (sect.type()) {
210 macho.S_CSTRING_LITERALS,
211 macho.S_4BYTE_LITERALS,
212 macho.S_8BYTE_LITERALS,
213 macho.S_16BYTE_LITERALS,
214 macho.S_LITERAL_POINTERS,
215 => true,
216 else => false,
290217 };
218}
291219
292 const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{
293 .n_sect = n_sect,
294 });
295 const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{
296 .n_sect = n_sect,
297 });
220fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
221 const tracy = trace(@src());
222 defer tracy.end();
223 const gpa = macho_file.base.comp.gpa;
224 const slice = self.sections.slice();
225 for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
226 if (isLiteral(sect)) continue;
227
228 const nlist_start = for (nlists, 0..) |nlist, i| {
229 if (nlist.nlist.n_sect - 1 == n_sect) break i;
230 } else nlists.len;
231 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
232 if (nlist.nlist.n_sect - 1 != n_sect) break i;
233 } else nlists.len;
234
235 if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) {
236 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
237 defer gpa.free(name);
238 const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr;
239 const atom_index = try self.addAtom(.{
240 .name = name,
241 .n_sect = @intCast(n_sect),
242 .off = 0,
243 .size = size,
244 .alignment = sect.@"align",
245 }, macho_file);
246 try subsections.append(gpa, .{
247 .atom = atom_index,
248 .off = 0,
249 });
250 }
298251
299 return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };
252 var idx: usize = nlist_start;
253 while (idx < nlist_end) {
254 const alias_start = idx;
255 const nlist = nlists[alias_start];
256
257 while (idx < nlist_end and
258 nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
259 {}
260
261 const size = if (idx < nlist_end)
262 nlists[idx].nlist.n_value - nlist.nlist.n_value
263 else
264 sect.addr + sect.size - nlist.nlist.n_value;
265 const alignment = if (nlist.nlist.n_value > 0)
266 @min(@ctz(nlist.nlist.n_value), sect.@"align")
267 else
268 sect.@"align";
269 const atom_index = try self.addAtom(.{
270 .name = self.getString(nlist.nlist.n_strx),
271 .n_sect = @intCast(n_sect),
272 .off = nlist.nlist.n_value - sect.addr,
273 .size = size,
274 .alignment = alignment,
275 }, macho_file);
276 try subsections.append(gpa, .{
277 .atom = atom_index,
278 .off = nlist.nlist.n_value - sect.addr,
279 });
280
281 for (alias_start..idx) |i| {
282 self.symtab.items(.size)[nlists[i].idx] = size;
283 }
284 }
285 }
300286}
301287
302fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr: u64) struct {
303 index: u32,
304 len: u32,
305} {
306 const Predicate = struct {
307 addr: u64,
308
309 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {
310 return symbol.n_value >= pred.addr;
288fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
289 const tracy = trace(@src());
290 defer tracy.end();
291 const gpa = macho_file.base.comp.gpa;
292 const slice = self.sections.slice();
293
294 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
295
296 for (slice.items(.header), 0..) |sect, n_sect| {
297 if (isLiteral(sect)) continue;
298
299 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
300 defer gpa.free(name);
301
302 const atom_index = try self.addAtom(.{
303 .name = name,
304 .n_sect = @intCast(n_sect),
305 .off = 0,
306 .size = sect.size,
307 .alignment = sect.@"align",
308 }, macho_file);
309 try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
310
311 const nlist_start = for (nlists, 0..) |nlist, i| {
312 if (nlist.nlist.n_sect - 1 == n_sect) break i;
313 } else nlists.len;
314 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
315 if (nlist.nlist.n_sect - 1 != n_sect) break i;
316 } else nlists.len;
317
318 var idx: usize = nlist_start;
319 while (idx < nlist_end) {
320 const nlist = nlists[idx];
321
322 while (idx < nlist_end and
323 nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
324 {}
325
326 const size = if (idx < nlist_end)
327 nlists[idx].nlist.n_value - nlist.nlist.n_value
328 else
329 sect.addr + sect.size - nlist.nlist.n_value;
330
331 for (nlist_start..idx) |i| {
332 self.symtab.items(.size)[nlists[i].idx] = size;
333 }
311334 }
312 };
335 }
336}
313337
314 const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{
315 .addr = start_addr,
316 });
317 const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{
318 .addr = end_addr,
319 });
338const AddAtomArgs = struct {
339 name: [:0]const u8,
340 n_sect: u8,
341 off: u64,
342 size: u64,
343 alignment: u32,
344};
320345
321 return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };
346fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {
347 const gpa = macho_file.base.comp.gpa;
348 const atom_index = try macho_file.addAtom();
349 const atom = macho_file.getAtom(atom_index).?;
350 atom.file = self.index;
351 atom.atom_index = atom_index;
352 atom.name = try macho_file.strings.insert(gpa, args.name);
353 atom.n_sect = args.n_sect;
354 atom.size = args.size;
355 atom.alignment = Atom.Alignment.fromLog2Units(args.alignment);
356 atom.off = args.off;
357 try self.atoms.append(gpa, atom_index);
358 return atom_index;
322359}
323360
324const SortedSection = struct {
325 header: macho.section_64,
326 id: u8,
327};
361fn initLiteralSections(self: *Object, macho_file: *MachO) !void {
362 const tracy = trace(@src());
363 defer tracy.end();
364 // TODO here we should split into equal-sized records, hash the contents, and then
365 // deduplicate - ICF.
366 // For now, we simply cover each literal section with one large atom.
367 const gpa = macho_file.base.comp.gpa;
368 const slice = self.sections.slice();
369
370 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
371
372 for (slice.items(.header), 0..) |sect, n_sect| {
373 if (!isLiteral(sect)) continue;
374
375 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
376 defer gpa.free(name);
377
378 const atom_index = try self.addAtom(.{
379 .name = name,
380 .n_sect = @intCast(n_sect),
381 .off = 0,
382 .size = sect.size,
383 .alignment = sect.@"align",
384 }, macho_file);
385 try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
386 }
387}
328388
329fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) bool {
330 _ = ctx;
331 if (lhs.header.addr == rhs.header.addr) {
332 return lhs.id < rhs.id;
389pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
390 const tracy = trace(@src());
391 defer tracy.end();
392 const slice = self.sections.slice();
393 for (slice.items(.header), slice.items(.subsections), 0..) |sect, subs, n_sect| {
394 if (subs.items.len == 0) continue;
395 if (sect.addr == addr) return subs.items[0].atom;
396 if (sect.addr < addr and addr < sect.addr + sect.size) {
397 return self.findAtomInSection(addr, @intCast(n_sect));
398 }
333399 }
334 return lhs.header.addr < rhs.header.addr;
400 return null;
335401}
336402
337pub const SplitIntoAtomsError = error{
338 OutOfMemory,
339 EndOfStream,
340 MissingEhFrameSection,
341 BadDwarfCfi,
342};
403fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index {
404 const tracy = trace(@src());
405 defer tracy.end();
406 const slice = self.sections.slice();
407 const sect = slice.items(.header)[n_sect];
408 const subsections = slice.items(.subsections)[n_sect];
409
410 var min: usize = 0;
411 var max: usize = subsections.items.len;
412 while (min < max) {
413 const idx = (min + max) / 2;
414 const sub = subsections.items[idx];
415 const sub_addr = sect.addr + sub.off;
416 const sub_size = if (idx + 1 < subsections.items.len)
417 subsections.items[idx + 1].off - sub.off
418 else
419 sect.size - sub.off;
420 if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
421 if (sub_addr < addr) {
422 min = idx + 1;
423 } else {
424 max = idx;
425 }
426 }
343427
344pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void {
345 const comp = macho_file.base.comp;
346 const gpa = comp.gpa;
347 log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name });
428 if (min < subsections.items.len) {
429 const sub = subsections.items[min];
430 const sub_addr = sect.addr + sub.off;
431 const sub_size = if (min + 1 < subsections.items.len)
432 subsections.items[min + 1].off - sub.off
433 else
434 sect.size - sub.off;
435 if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
436 }
348437
349 try self.splitRegularSections(macho_file, object_id);
350 try self.parseEhFrameSection(macho_file, object_id);
351 try self.parseUnwindInfo(macho_file, object_id);
352 try self.parseDataInCode(gpa);
438 return null;
353439}
354440
355/// Splits input regular sections into Atoms.
356/// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section
357/// into subsections where each subsection then represents an Atom.
358pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void {
359 const comp = macho_file.base.comp;
360 const gpa = comp.gpa;
361 const target = macho_file.base.comp.root_mod.resolved_target.result;
362
363 const sections = self.getSourceSections();
364 for (sections, 0..) |sect, id| {
365 if (sect.isDebug()) continue;
366 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse {
367 log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() });
368 continue;
369 };
370 if (sect.size == 0) continue;
371
372 const sect_id = @as(u8, @intCast(id));
373 const sym = self.getSectionAliasSymbolPtr(sect_id);
374 sym.* = .{
375 .n_strx = 0,
376 .n_type = macho.N_SECT,
377 .n_sect = out_sect_id + 1,
378 .n_desc = 0,
379 .n_value = sect.addr,
380 };
441fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
442 const tracy = trace(@src());
443 defer tracy.end();
444 for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| {
445 if (!nlist.stab() and nlist.sect()) {
446 if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| {
447 atom.* = atom_index;
448 } else {
449 try macho_file.reportParseError2(self.index, "symbol {s} not attached to any (sub)section", .{
450 self.getString(nlist.n_strx),
451 });
452 return error.MalformedObject;
453 }
454 }
381455 }
456}
382457
383 if (self.in_symtab == null) {
384 for (sections, 0..) |sect, id| {
385 if (sect.isDebug()) continue;
386 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
387 if (sect.size == 0) continue;
388
389 const sect_id: u8 = @intCast(id);
390 const sym_index = self.getSectionAliasSymbolIndex(sect_id);
391 const atom_index = try self.createAtomFromSubsection(
392 macho_file,
393 object_id,
394 sym_index,
395 sym_index,
396 1,
397 sect.size,
398 Alignment.fromLog2Units(sect.@"align"),
399 out_sect_id,
400 );
401 macho_file.addAtomToSection(atom_index);
458fn initSymbols(self: *Object, macho_file: *MachO) !void {
459 const tracy = trace(@src());
460 defer tracy.end();
461 const gpa = macho_file.base.comp.gpa;
462 const slice = self.symtab.slice();
463
464 try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len);
465
466 for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| {
467 if (nlist.ext()) {
468 const name = self.getString(nlist.n_strx);
469 const off = try macho_file.strings.insert(gpa, name);
470 const gop = try macho_file.getOrCreateGlobal(off);
471 self.symbols.addOneAssumeCapacity().* = gop.index;
472 continue;
402473 }
403 return;
404 }
405474
406 // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
407 // have to infer the start of undef section in the symtab ourselves.
408 const iundefsym = blk: {
409 const dysymtab = self.getDysymtab() orelse {
410 var iundefsym: usize = self.in_symtab.?.len;
411 while (iundefsym > 0) : (iundefsym -= 1) {
412 const sym = self.symtab[iundefsym - 1];
413 if (sym.sect()) break;
414 }
415 break :blk iundefsym;
475 const index = try macho_file.addSymbol();
476 self.symbols.appendAssumeCapacity(index);
477 const symbol = macho_file.getSymbol(index);
478 const name = self.getString(nlist.n_strx);
479 symbol.* = .{
480 .value = nlist.n_value,
481 .name = try macho_file.strings.insert(gpa, name),
482 .nlist_idx = @intCast(i),
483 .atom = 0,
484 .file = self.index,
416485 };
417 break :blk dysymtab.iundefsym;
418 };
419
420 // We only care about defined symbols, so filter every other out.
421 const symtab = try gpa.dupe(macho.nlist_64, self.symtab[0..iundefsym]);
422 defer gpa.free(symtab);
423486
424 const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
487 if (macho_file.getAtom(atom_index)) |atom| {
488 assert(!nlist.abs());
489 symbol.value -= atom.getInputAddress(macho_file);
490 symbol.atom = atom_index;
491 }
425492
426 // Sort section headers by address.
427 var sorted_sections = try gpa.alloc(SortedSection, sections.len);
428 defer gpa.free(sorted_sections);
493 symbol.flags.abs = nlist.abs();
494 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
429495
430 for (sections, 0..) |sect, id| {
431 sorted_sections[id] = .{ .header = sect, .id = @as(u8, @intCast(id)) };
496 if (nlist.sect() and
497 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
498 {
499 symbol.flags.tlv = true;
500 }
432501 }
502}
433503
434 mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress);
435
436 var sect_sym_index: u32 = 0;
437 for (sorted_sections) |section| {
438 const sect = section.header;
439 if (sect.isDebug()) continue;
440
441 const sect_id = section.id;
442 log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });
443
444 // Get output segment/section in the final artifact.
445 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;
446
447 log.debug(" output sect({d}, '{s},{s}')", .{
448 out_sect_id + 1,
449 macho_file.sections.items(.header)[out_sect_id].segName(),
450 macho_file.sections.items(.header)[out_sect_id].sectName(),
451 });
452
453 try self.parseRelocs(gpa, section.id);
454
455 const cpu_arch = target.cpu.arch;
456 const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1);
457 const sect_start_index = sect_sym_index + sect_loc.index;
458
459 sect_sym_index += sect_loc.len;
460
461 if (sect.size == 0) continue;
462 if (subsections_via_symbols and sect_loc.len > 0) {
463 // If the first nlist does not match the start of the section,
464 // then we need to encapsulate the memory range [section start, first symbol)
465 // as a temporary symbol and insert the matching Atom.
466 const first_sym = symtab[sect_start_index];
467 if (first_sym.n_value > sect.addr) {
468 const sym_index = self.getSectionAliasSymbolIndex(sect_id);
469 const atom_size = first_sym.n_value - sect.addr;
470 const atom_index = try self.createAtomFromSubsection(
471 macho_file,
472 object_id,
473 sym_index,
474 sym_index,
475 1,
476 atom_size,
477 Alignment.fromLog2Units(sect.@"align"),
478 out_sect_id,
479 );
480 if (!sect.isZerofill()) {
481 try self.cacheRelocs(macho_file, atom_index);
482 }
483 macho_file.addAtomToSection(atom_index);
484 }
504fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
505 const tracy = trace(@src());
506 defer tracy.end();
485507
486 var next_sym_index = sect_start_index;
487 while (next_sym_index < sect_start_index + sect_loc.len) {
488 const next_sym = symtab[next_sym_index];
489 const addr = next_sym.n_value;
490 const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1);
491 assert(atom_loc.len > 0);
492 const atom_sym_index = atom_loc.index + next_sym_index;
493 const nsyms_trailing = atom_loc.len;
494 next_sym_index += atom_loc.len;
495
496 const atom_size = if (next_sym_index < sect_start_index + sect_loc.len)
497 symtab[next_sym_index].n_value - addr
498 else
499 sect.addr + sect.size - addr;
508 const SymbolLookup = struct {
509 ctx: *const Object,
510 entries: @TypeOf(nlists),
500511
501 const atom_align = Alignment.fromLog2Units(if (addr > 0)
502 @min(@ctz(addr), sect.@"align")
503 else
504 sect.@"align");
505
506 const atom_index = try self.createAtomFromSubsection(
507 macho_file,
508 object_id,
509 atom_sym_index,
510 atom_sym_index,
511 nsyms_trailing,
512 atom_size,
513 atom_align,
514 out_sect_id,
515 );
516
517 // TODO rework this at the relocation level
518 if (cpu_arch == .x86_64 and addr == sect.addr) {
519 // In x86_64 relocs, it can so happen that the compiler refers to the same
520 // atom by both the actual assigned symbol and the start of the section. In this
521 // case, we need to link the two together so add an alias.
522 const alias_index = self.getSectionAliasSymbolIndex(sect_id);
523 self.atom_by_index_table[alias_index] = atom_index;
524 }
525 if (!sect.isZerofill()) {
526 try self.cacheRelocs(macho_file, atom_index);
527 }
528 macho_file.addAtomToSection(atom_index);
512 fn find(fs: @This(), addr: u64) ?Symbol.Index {
513 // TODO binary search since we have the list sorted
514 for (fs.entries) |nlist| {
515 if (nlist.nlist.n_value == addr) return fs.ctx.symbols.items[nlist.idx];
529516 }
530 } else {
531 const alias_index = self.getSectionAliasSymbolIndex(sect_id);
532 const atom_index = try self.createAtomFromSubsection(
533 macho_file,
534 object_id,
535 alias_index,
536 sect_start_index,
537 sect_loc.len,
538 sect.size,
539 Alignment.fromLog2Units(sect.@"align"),
540 out_sect_id,
541 );
542 if (!sect.isZerofill()) {
543 try self.cacheRelocs(macho_file, atom_index);
517 return null;
518 }
519 };
520
521 const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| {
522 if (nlist.stab()) break @intCast(i);
523 } else @intCast(self.symtab.items(.nlist).len);
524 const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| {
525 if (!nlist.stab()) break @intCast(i);
526 } else @intCast(self.symtab.items(.nlist).len);
527
528 if (start == end) return;
529
530 const gpa = macho_file.base.comp.gpa;
531 const syms = self.symtab.items(.nlist);
532 const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists };
533
534 var i: u32 = start;
535 while (i < end) : (i += 1) {
536 const open = syms[i];
537 if (open.n_type != macho.N_SO) {
538 try macho_file.reportParseError2(self.index, "unexpected symbol stab type 0x{x} as the first entry", .{
539 open.n_type,
540 });
541 return error.MalformedObject;
542 }
543
544 while (i < end and syms[i].n_type == macho.N_SO and syms[i].n_sect != 0) : (i += 1) {}
545
546 var sf: StabFile = .{ .comp_dir = i };
547 // TODO validate
548 i += 3;
549
550 while (i < end and syms[i].n_type != macho.N_SO) : (i += 1) {
551 const nlist = syms[i];
552 var stab: StabFile.Stab = .{};
553 switch (nlist.n_type) {
554 macho.N_BNSYM => {
555 stab.tag = .func;
556 stab.symbol = sym_lookup.find(nlist.n_value);
557 // TODO validate
558 i += 3;
559 },
560 macho.N_GSYM => {
561 stab.tag = .global;
562 stab.symbol = macho_file.getGlobalByName(self.getString(nlist.n_strx));
563 },
564 macho.N_STSYM => {
565 stab.tag = .static;
566 stab.symbol = sym_lookup.find(nlist.n_value);
567 },
568 else => {
569 try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{
570 nlist.n_type,
571 });
572 return error.MalformedObject;
573 },
544574 }
545 macho_file.addAtomToSection(atom_index);
575 try sf.stabs.append(gpa, stab);
546576 }
577
578 try self.stab_files.append(gpa, sf);
547579 }
548580}
549581
550fn createAtomFromSubsection(
551 self: *Object,
552 macho_file: *MachO,
553 object_id: u32,
554 sym_index: u32,
555 inner_sym_index: u32,
556 inner_nsyms_trailing: u32,
557 size: u64,
558 alignment: Alignment,
559 out_sect_id: u8,
560) !Atom.Index {
561 const comp = macho_file.base.comp;
562 const gpa = comp.gpa;
563 const atom_index = try macho_file.createAtom(sym_index, .{
564 .size = size,
565 .alignment = alignment,
566 });
567 const atom = macho_file.getAtomPtr(atom_index);
568 atom.inner_sym_index = inner_sym_index;
569 atom.inner_nsyms_trailing = inner_nsyms_trailing;
570 atom.file = object_id + 1;
571 self.symtab[sym_index].n_sect = out_sect_id + 1;
572
573 log.debug("creating ATOM(%{d}, '{s}') in sect({d}, '{s},{s}') in object({d})", .{
574 sym_index,
575 self.getSymbolName(sym_index),
576 out_sect_id + 1,
577 macho_file.sections.items(.header)[out_sect_id].segName(),
578 macho_file.sections.items(.header)[out_sect_id].sectName(),
579 object_id,
580 });
582fn sortAtoms(self: *Object, macho_file: *MachO) !void {
583 const lessThanAtom = struct {
584 fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool {
585 return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx);
586 }
587 }.lessThanAtom;
588 mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom);
589}
581590
582 try self.atoms.append(gpa, atom_index);
583 self.atom_by_index_table[sym_index] = atom_index;
591fn initRelocs(self: *Object, macho_file: *MachO) !void {
592 const tracy = trace(@src());
593 defer tracy.end();
594 const cpu_arch = macho_file.getTarget().cpu.arch;
595 const slice = self.sections.slice();
596
597 for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| {
598 if (sect.nreloc == 0) continue;
599 // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit
600 // debug symbol stabs in the relocatable. This made me curious why that is. For now,
601 // I shall comply, but I wanna compare with dsymutil.
602 if (sect.attrs() & macho.S_ATTR_DEBUG != 0 and
603 !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue;
604
605 switch (cpu_arch) {
606 .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
607 .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
608 else => unreachable,
609 }
584610
585 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
586 while (it.next()) |sym_loc| {
587 const inner = macho_file.getSymbolPtr(sym_loc);
588 inner.n_sect = out_sect_id + 1;
589 self.atom_by_index_table[sym_loc.sym_index] = atom_index;
611 mem.sort(Relocation, out.items, {}, Relocation.lessThan);
590612 }
591613
592 const out_sect = macho_file.sections.items(.header)[out_sect_id];
593 if (out_sect.isCode() and
594 mem.eql(u8, "__TEXT", out_sect.segName()) and
595 mem.eql(u8, "__text", out_sect.sectName()))
596 {
597 // TODO currently assuming a single section for executable machine code
598 try self.exec_atoms.append(gpa, atom_index);
599 }
614 for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| {
615 if (sect.isZerofill()) continue;
600616
601 return atom_index;
602}
617 var next_reloc: u32 = 0;
618 for (subsections.items) |subsection| {
619 const atom = macho_file.getAtom(subsection.atom).?;
620 if (!atom.flags.alive) continue;
621 if (next_reloc >= relocs.items.len) break;
622 const end_addr = atom.off + atom.size;
623 atom.relocs.pos = next_reloc;
603624
604fn filterRelocs(
605 relocs: []align(1) const macho.relocation_info,
606 start_addr: u64,
607 end_addr: u64,
608) Entry {
609 const Predicate = struct {
610 addr: u64,
625 while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {}
611626
612 pub fn predicate(self: @This(), rel: macho.relocation_info) bool {
613 return rel.r_address >= self.addr;
627 atom.relocs.len = next_reloc - atom.relocs.pos;
614628 }
615 };
616 const LPredicate = struct {
617 addr: u64,
629 }
630}
618631
619 pub fn predicate(self: @This(), rel: macho.relocation_info) bool {
620 return rel.r_address < self.addr;
632fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
633 const tracy = trace(@src());
634 defer tracy.end();
635 const gpa = macho_file.base.comp.gpa;
636 const nlists = self.symtab.items(.nlist);
637 const slice = self.sections.slice();
638 const sect = slice.items(.header)[sect_id];
639 const relocs = slice.items(.relocs)[sect_id];
640
641 const data = try self.getSectionData(sect_id);
642 try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len);
643 self.eh_frame_data.appendSliceAssumeCapacity(data);
644
645 // Check for non-personality relocs in FDEs and apply them
646 for (relocs.items, 0..) |rel, i| {
647 switch (rel.type) {
648 .unsigned => {
649 assert((rel.meta.length == 2 or rel.meta.length == 3) and rel.meta.has_subtractor); // TODO error
650 const S: i64 = switch (rel.tag) {
651 .local => rel.meta.symbolnum,
652 .@"extern" => @intCast(nlists[rel.meta.symbolnum].n_value),
653 };
654 const A = rel.addend;
655 const SUB: i64 = blk: {
656 const sub_rel = relocs.items[i - 1];
657 break :blk switch (sub_rel.tag) {
658 .local => sub_rel.meta.symbolnum,
659 .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value),
660 };
661 };
662 switch (rel.meta.length) {
663 0, 1 => unreachable,
664 2 => mem.writeInt(u32, self.eh_frame_data.items[rel.offset..][0..4], @bitCast(@as(i32, @truncate(S + A - SUB))), .little),
665 3 => mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little),
666 }
667 },
668 else => {},
621669 }
622 };
623
624 const start = MachO.bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr });
625 const len = MachO.lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr });
670 }
626671
627 return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) };
628}
672 var it = eh_frame.Iterator{ .data = self.eh_frame_data.items };
673 while (try it.next()) |rec| {
674 switch (rec.tag) {
675 .cie => try self.cies.append(gpa, .{
676 .offset = rec.offset,
677 .size = rec.size,
678 .file = self.index,
679 }),
680 .fde => try self.fdes.append(gpa, .{
681 .offset = rec.offset,
682 .size = rec.size,
683 .cie = undefined,
684 .file = self.index,
685 }),
686 }
687 }
629688
630/// Parse all relocs for the input section, and sort in descending order.
631/// Previously, I have wrongly assumed the compilers output relocations for each
632/// section in a sorted manner which is simply not true.
633fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {
634 const section = self.getSourceSection(sect_id);
635 const start = @as(u32, @intCast(self.relocations.items.len));
636 if (self.getSourceRelocs(section)) |relocs| {
637 try self.relocations.ensureUnusedCapacity(gpa, relocs.len);
638 self.relocations.appendUnalignedSliceAssumeCapacity(relocs);
639 mem.sort(macho.relocation_info, self.relocations.items[start..], {}, relocGreaterThan);
689 for (self.cies.items) |*cie| {
690 try cie.parse(macho_file);
640691 }
641 self.section_relocs_lookup.items[sect_id] = start;
642}
643692
644fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void {
645 const atom = macho_file.getAtom(atom_index);
646
647 const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
648 break :blk source_sym.n_sect - 1;
649 } else blk: {
650 // If there was no matching symbol present in the source symtab, this means
651 // we are dealing with either an entire section, or part of it, but also
652 // starting at the beginning.
653 const nbase = @as(u32, @intCast(self.in_symtab.?.len));
654 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
655 break :blk sect_id;
656 };
657 const source_sect = self.getSourceSection(source_sect_id);
658 assert(!source_sect.isZerofill());
659 const relocs = self.getRelocs(source_sect_id);
660
661 self.relocs_lookup[atom.sym_index] = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
662 const offset = source_sym.n_value - source_sect.addr;
663 break :blk filterRelocs(relocs, offset, offset + atom.size);
664 } else filterRelocs(relocs, 0, atom.size);
665}
693 for (self.fdes.items) |*fde| {
694 try fde.parse(macho_file);
695 }
666696
667fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
668 _ = ctx;
669 return lhs.r_address > rhs.r_address;
697 const sortFn = struct {
698 fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool {
699 return lhs.getAtom(ctx).getInputAddress(ctx) < rhs.getAtom(ctx).getInputAddress(ctx);
700 }
701 }.sortFn;
702
703 mem.sort(Fde, self.fdes.items, macho_file, sortFn);
704
705 // Parse and attach personality pointers to CIEs if any
706 for (relocs.items) |rel| {
707 switch (rel.type) {
708 .got => {
709 assert(rel.meta.length == 2 and rel.tag == .@"extern");
710 const cie = for (self.cies.items) |*cie| {
711 if (cie.offset <= rel.offset and rel.offset < cie.offset + cie.getSize()) break cie;
712 } else {
713 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
714 sect.segName(), sect.sectName(), rel.offset,
715 });
716 return error.MalformedObject;
717 };
718 cie.personality = .{ .index = @intCast(rel.target), .offset = rel.offset - cie.offset };
719 },
720 else => {},
721 }
722 }
670723}
671724
672fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void {
673 const sect_id = self.eh_frame_sect_id orelse return;
674 const sect = self.getSourceSection(sect_id);
725fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
726 const tracy = trace(@src());
727 defer tracy.end();
675728
676 log.debug("parsing __TEXT,__eh_frame section", .{});
729 const SymbolLookup = struct {
730 ctx: *const Object,
677731
678 const comp = macho_file.base.comp;
679 const gpa = comp.gpa;
732 fn find(fs: @This(), addr: u64) ?Symbol.Index {
733 for (fs.ctx.symbols.items, 0..) |sym_index, i| {
734 const nlist = fs.ctx.symtab.items(.nlist)[i];
735 if (nlist.ext() and nlist.n_value == addr) return sym_index;
736 }
737 return null;
738 }
739 };
680740
681 if (macho_file.eh_frame_section_index == null) {
682 macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{});
741 const gpa = macho_file.base.comp.gpa;
742 const data = try self.getSectionData(sect_id);
743 const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
744 const recs = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data.ptr))[0..nrecs];
745 const sym_lookup = SymbolLookup{ .ctx = self };
746
747 try self.unwind_records.resize(gpa, nrecs);
748
749 const header = self.sections.items(.header)[sect_id];
750 const relocs = self.sections.items(.relocs)[sect_id].items;
751 var reloc_idx: usize = 0;
752 for (recs, self.unwind_records.items, 0..) |rec, *out_index, rec_idx| {
753 const rec_start = rec_idx * @sizeOf(macho.compact_unwind_entry);
754 const rec_end = rec_start + @sizeOf(macho.compact_unwind_entry);
755 const reloc_start = reloc_idx;
756 while (reloc_idx < relocs.len and
757 relocs[reloc_idx].offset < rec_end) : (reloc_idx += 1)
758 {}
759
760 out_index.* = try macho_file.addUnwindRecord();
761 const out = macho_file.getUnwindRecord(out_index.*);
762 out.length = rec.rangeLength;
763 out.enc = .{ .enc = rec.compactUnwindEncoding };
764 out.file = self.index;
765
766 for (relocs[reloc_start..reloc_idx]) |rel| {
767 if (rel.type != .unsigned or rel.meta.length != 3) {
768 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
769 header.segName(), header.sectName(), rel.offset,
770 });
771 return error.MalformedObject;
772 }
773 assert(rel.type == .unsigned and rel.meta.length == 3); // TODO error
774 const offset = rel.offset - rec_start;
775 switch (offset) {
776 0 => switch (rel.tag) { // target symbol
777 .@"extern" => {
778 out.atom = self.symtab.items(.atom)[rel.meta.symbolnum];
779 out.atom_offset = @intCast(rec.rangeStart);
780 },
781 .local => if (self.findAtom(rec.rangeStart)) |atom_index| {
782 out.atom = atom_index;
783 const atom = out.getAtom(macho_file);
784 out.atom_offset = @intCast(rec.rangeStart - atom.getInputAddress(macho_file));
785 } else {
786 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
787 header.segName(), header.sectName(), rel.offset,
788 });
789 return error.MalformedObject;
790 },
791 },
792 16 => switch (rel.tag) { // personality function
793 .@"extern" => {
794 out.personality = rel.target;
795 },
796 .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| {
797 out.personality = sym_index;
798 } else {
799 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
800 header.segName(), header.sectName(), rel.offset,
801 });
802 return error.MalformedObject;
803 },
804 },
805 24 => switch (rel.tag) { // lsda
806 .@"extern" => {
807 out.lsda = self.symtab.items(.atom)[rel.meta.symbolnum];
808 out.lsda_offset = @intCast(rec.lsda);
809 },
810 .local => if (self.findAtom(rec.lsda)) |atom_index| {
811 out.lsda = atom_index;
812 const atom = out.getLsdaAtom(macho_file).?;
813 out.lsda_offset = @intCast(rec.lsda - atom.getInputAddress(macho_file));
814 } else {
815 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
816 header.segName(), header.sectName(), rel.offset,
817 });
818 return error.MalformedObject;
819 },
820 },
821 else => {},
822 }
823 }
683824 }
825}
684826
685 const target = macho_file.base.comp.root_mod.resolved_target.result;
686 const cpu_arch = target.cpu.arch;
687 try self.parseRelocs(gpa, sect_id);
688 const relocs = self.getRelocs(sect_id);
689
690 var it = self.getEhFrameRecordsIterator();
691 var record_count: u32 = 0;
692 while (try it.next()) |_| {
693 record_count += 1;
827fn parseUnwindRecords(self: *Object, macho_file: *MachO) !void {
828 // Synthesise missing unwind records.
829 // The logic here is as follows:
830 // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead
831 // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record
832 // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie
833 // 4. if an atom doesn't have either, synthesise a null unwind info record
834
835 const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null };
836
837 const gpa = macho_file.base.comp.gpa;
838 var superposition = std.AutoArrayHashMap(u64, Superposition).init(gpa);
839 defer superposition.deinit();
840
841 const slice = self.symtab.slice();
842 for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {
843 if (nlist.stab()) continue;
844 if (!nlist.sect()) continue;
845 const sect = self.sections.items(.header)[nlist.n_sect - 1];
846 if (sect.isCode() and sect.size > 0) {
847 try superposition.ensureUnusedCapacity(1);
848 const gop = superposition.getOrPutAssumeCapacity(nlist.n_value);
849 if (gop.found_existing) {
850 assert(gop.value_ptr.atom == atom and gop.value_ptr.size == size);
851 }
852 gop.value_ptr.* = .{ .atom = atom, .size = size };
853 }
694854 }
695855
696 try self.eh_frame_relocs_lookup.ensureTotalCapacity(gpa, record_count);
697 try self.eh_frame_records_lookup.ensureUnusedCapacity(gpa, record_count);
856 for (self.unwind_records.items) |rec_index| {
857 const rec = macho_file.getUnwindRecord(rec_index);
858 const atom = rec.getAtom(macho_file);
859 const addr = atom.getInputAddress(macho_file) + rec.atom_offset;
860 superposition.getPtr(addr).?.cu = rec_index;
861 }
698862
699 it.reset();
863 for (self.fdes.items, 0..) |fde, fde_index| {
864 const atom = fde.getAtom(macho_file);
865 const addr = atom.getInputAddress(macho_file) + fde.atom_offset;
866 superposition.getPtr(addr).?.fde = @intCast(fde_index);
867 }
700868
701 while (try it.next()) |record| {
702 const offset = it.pos - record.getSize();
703 const rel_pos: Entry = switch (cpu_arch) {
704 .aarch64 => filterRelocs(relocs, offset, offset + record.getSize()),
705 .x86_64 => .{},
706 else => unreachable,
707 };
708 self.eh_frame_relocs_lookup.putAssumeCapacityNoClobber(offset, .{
709 .dead = false,
710 .reloc = rel_pos,
711 });
712
713 if (record.tag == .fde) {
714 const reloc_target = blk: {
715 switch (cpu_arch) {
716 .aarch64 => {
717 assert(rel_pos.len > 0); // TODO convert to an error as the FDE eh frame is malformed
718 // Find function symbol that this record describes
719 const rel = for (relocs[rel_pos.start..][0..rel_pos.len]) |rel| {
720 if (rel.r_address - @as(i32, @intCast(offset)) == 8 and
721 @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED)
722 break rel;
723 } else unreachable;
724 const reloc_target = Atom.parseRelocTarget(macho_file, .{
725 .object_id = object_id,
726 .rel = rel,
727 .code = it.data[offset..],
728 .base_offset = @as(i32, @intCast(offset)),
729 });
730 break :blk reloc_target;
731 },
732 .x86_64 => {
733 const target_address = record.getTargetSymbolAddress(.{
734 .base_addr = sect.addr,
735 .base_offset = offset,
736 });
737 const target_sym_index = self.getSymbolByAddress(target_address, null);
738 const reloc_target = if (self.getGlobal(target_sym_index)) |global_index|
739 macho_file.globals.items[global_index]
740 else
741 SymbolWithLoc{ .sym_index = target_sym_index, .file = object_id + 1 };
742 break :blk reloc_target;
743 },
744 else => unreachable,
869 for (superposition.keys(), superposition.values()) |addr, meta| {
870 if (meta.fde) |fde_index| {
871 const fde = &self.fdes.items[fde_index];
872
873 if (meta.cu) |rec_index| {
874 const rec = macho_file.getUnwindRecord(rec_index);
875 if (!rec.enc.isDwarf(macho_file)) {
876 // Mark FDE dead
877 fde.alive = false;
878 } else {
879 // Tie FDE to unwind record
880 rec.fde = fde_index;
745881 }
746 };
747 if (reloc_target.getFile() != object_id) {
748 log.debug("FDE at offset {x} marked DEAD", .{offset});
749 self.eh_frame_relocs_lookup.getPtr(offset).?.dead = true;
750882 } else {
751 // You would think that we are done but turns out that the compilers may use
752 // whichever symbol alias they want for a target symbol. This in particular
753 // very problematic when using Zig's @export feature to re-export symbols under
754 // additional names. For that reason, we need to ensure we record aliases here
755 // too so that we can tie them with their matching unwind records and vice versa.
756 const aliases = self.getSymbolAliases(reloc_target.sym_index);
757 var i: u32 = 0;
758 while (i < aliases.len) : (i += 1) {
759 const actual_target = SymbolWithLoc{
760 .sym_index = i + aliases.start,
761 .file = reloc_target.file,
762 };
763 log.debug("FDE at offset {x} tracks {s}", .{
764 offset,
765 macho_file.getSymbolName(actual_target),
766 });
767 try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset);
883 // Synthesise new unwind info record
884 const rec_index = try macho_file.addUnwindRecord();
885 const rec = macho_file.getUnwindRecord(rec_index);
886 try self.unwind_records.append(gpa, rec_index);
887 rec.length = @intCast(meta.size);
888 rec.atom = fde.atom;
889 rec.atom_offset = fde.atom_offset;
890 rec.fde = fde_index;
891 rec.file = fde.file;
892 switch (macho_file.getTarget().cpu.arch) {
893 .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF),
894 .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF),
895 else => unreachable,
768896 }
769897 }
898 } else if (meta.cu == null and meta.fde == null) {
899 // Create a null record
900 const rec_index = try macho_file.addUnwindRecord();
901 const rec = macho_file.getUnwindRecord(rec_index);
902 const atom = macho_file.getAtom(meta.atom).?;
903 try self.unwind_records.append(gpa, rec_index);
904 rec.length = @intCast(meta.size);
905 rec.atom = meta.atom;
906 rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file));
907 rec.file = self.index;
770908 }
771909 }
772}
773910
774fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void {
775 const comp = macho_file.base.comp;
776 const gpa = comp.gpa;
777 const target = macho_file.base.comp.root_mod.resolved_target.result;
778 const cpu_arch = target.cpu.arch;
779 const sect_id = self.unwind_info_sect_id orelse {
780 // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,
781 // we will try fully synthesising unwind info records to somewhat match Apple ld's
782 // approach. However, we will only synthesise DWARF records and nothing more. For this reason,
783 // we still create the output `__TEXT,__unwind_info` section.
784 if (self.hasEhFrameRecords()) {
785 if (macho_file.unwind_info_section_index == null) {
786 macho_file.unwind_info_section_index = try macho_file.initSection(
787 "__TEXT",
788 "__unwind_info",
789 .{},
790 );
791 }
911 const sortFn = struct {
912 fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool {
913 const lhs = ctx.getUnwindRecord(lhs_index);
914 const rhs = ctx.getUnwindRecord(rhs_index);
915 const lhsa = lhs.getAtom(ctx);
916 const rhsa = rhs.getAtom(ctx);
917 return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset;
792918 }
793 return;
794 };
919 }.sortFn;
920 mem.sort(UnwindInfo.Record.Index, self.unwind_records.items, macho_file, sortFn);
921
922 // Associate unwind records to atoms
923 var next_cu: u32 = 0;
924 while (next_cu < self.unwind_records.items.len) {
925 const start = next_cu;
926 const rec_index = self.unwind_records.items[start];
927 const rec = macho_file.getUnwindRecord(rec_index);
928 while (next_cu < self.unwind_records.items.len and
929 macho_file.getUnwindRecord(self.unwind_records.items[next_cu]).atom == rec.atom) : (next_cu += 1)
930 {}
931
932 const atom = rec.getAtom(macho_file);
933 atom.unwind_records = .{ .pos = start, .len = next_cu - start };
934 }
935}
795936
796 log.debug("parsing unwind info in {s}", .{self.name});
937fn initPlatform(self: *Object) void {
938 var it = LoadCommandIterator{
939 .ncmds = self.header.?.ncmds,
940 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
941 };
942 self.platform = while (it.next()) |cmd| {
943 switch (cmd.cmd()) {
944 .BUILD_VERSION,
945 .VERSION_MIN_MACOSX,
946 .VERSION_MIN_IPHONEOS,
947 .VERSION_MIN_TVOS,
948 .VERSION_MIN_WATCHOS,
949 => break MachO.Platform.fromLoadCommand(cmd),
950 else => {},
951 }
952 } else null;
953}
797954
798 if (macho_file.unwind_info_section_index == null) {
799 macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{});
955/// Currently, we only check if a compile unit for this input object file exists
956/// and record that so that we can emit symbol stabs.
957/// TODO in the future, we want parse debug info and debug line sections so that
958/// we can provide nice error locations to the user.
959fn initDwarfInfo(self: *Object, macho_file: *MachO) !void {
960 const tracy = trace(@src());
961 defer tracy.end();
962
963 const gpa = macho_file.base.comp.gpa;
964
965 var debug_info_index: ?usize = null;
966 var debug_abbrev_index: ?usize = null;
967 var debug_str_index: ?usize = null;
968
969 for (self.sections.items(.header), 0..) |sect, index| {
970 if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
971 if (mem.eql(u8, sect.sectName(), "__debug_info")) debug_info_index = index;
972 if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) debug_abbrev_index = index;
973 if (mem.eql(u8, sect.sectName(), "__debug_str")) debug_str_index = index;
800974 }
801975
802 const unwind_records = self.getUnwindRecords();
976 if (debug_info_index == null or debug_abbrev_index == null) return;
803977
804 try self.unwind_records_lookup.ensureUnusedCapacity(gpa, @as(u32, @intCast(unwind_records.len)));
978 var dwarf_info = DwarfInfo{
979 .debug_info = try self.getSectionData(@intCast(debug_info_index.?)),
980 .debug_abbrev = try self.getSectionData(@intCast(debug_abbrev_index.?)),
981 .debug_str = if (debug_str_index) |index| try self.getSectionData(@intCast(index)) else "",
982 };
983 dwarf_info.init(gpa) catch {
984 try macho_file.reportParseError2(self.index, "invalid __DWARF info found", .{});
985 return error.MalformedObject;
986 };
987 self.dwarf_info = dwarf_info;
988}
805989
806 const needs_eh_frame = for (unwind_records) |record| {
807 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;
808 } else false;
990pub fn resolveSymbols(self: *Object, macho_file: *MachO) void {
991 const tracy = trace(@src());
992 defer tracy.end();
809993
810 if (needs_eh_frame and !self.hasEhFrameRecords()) return error.MissingEhFrameSection;
994 for (self.symbols.items, 0..) |index, i| {
995 const nlist_idx = @as(Symbol.Index, @intCast(i));
996 const nlist = self.symtab.items(.nlist)[nlist_idx];
997 const atom_index = self.symtab.items(.atom)[nlist_idx];
811998
812 try self.parseRelocs(gpa, sect_id);
813 const relocs = self.getRelocs(sect_id);
999 if (!nlist.ext()) continue;
1000 if (nlist.undf() and !nlist.tentative()) continue;
1001 if (nlist.sect()) {
1002 const atom = macho_file.getAtom(atom_index).?;
1003 if (!atom.flags.alive) continue;
1004 }
8141005
815 for (unwind_records, 0..) |record, record_id| {
816 const offset = record_id * @sizeOf(macho.compact_unwind_entry);
817 const rel_pos = filterRelocs(
818 relocs,
819 offset,
820 offset + @sizeOf(macho.compact_unwind_entry),
821 );
822 assert(rel_pos.len > 0); // TODO convert to an error as the unwind info is malformed
823 self.unwind_relocs_lookup[record_id] = .{
824 .dead = false,
825 .reloc = rel_pos,
826 };
1006 const symbol = macho_file.getSymbol(index);
1007 if (self.asFile().getSymbolRank(.{
1008 .archive = !self.alive,
1009 .weak = nlist.weakDef(),
1010 .tentative = nlist.tentative(),
1011 }) < symbol.getSymbolRank(macho_file)) {
1012 const value = if (nlist.sect()) blk: {
1013 const atom = macho_file.getAtom(atom_index).?;
1014 break :blk nlist.n_value - atom.getInputAddress(macho_file);
1015 } else nlist.n_value;
1016 symbol.value = value;
1017 symbol.atom = atom_index;
1018 symbol.nlist_idx = nlist_idx;
1019 symbol.file = self.index;
1020 symbol.flags.weak = nlist.weakDef();
1021 symbol.flags.abs = nlist.abs();
1022 symbol.flags.tentative = nlist.tentative();
1023 symbol.flags.weak_ref = false;
1024 symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
1025 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
1026 // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
1027 symbol.flags.interposable = false;
1028
1029 if (nlist.sect() and
1030 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
1031 {
1032 symbol.flags.tlv = true;
1033 }
1034 }
8271035
828 // Find function symbol that this record describes
829 const rel = relocs[rel_pos.start..][rel_pos.len - 1];
830 const reloc_target = Atom.parseRelocTarget(macho_file, .{
831 .object_id = object_id,
832 .rel = rel,
833 .code = mem.asBytes(&record),
834 .base_offset = @as(i32, @intCast(offset)),
835 });
836 if (reloc_target.getFile() != object_id) {
837 log.debug("unwind record {d} marked DEAD", .{record_id});
838 self.unwind_relocs_lookup[record_id].dead = true;
839 } else {
840 // You would think that we are done but turns out that the compilers may use
841 // whichever symbol alias they want for a target symbol. This in particular
842 // very problematic when using Zig's @export feature to re-export symbols under
843 // additional names. For that reason, we need to ensure we record aliases here
844 // too so that we can tie them with their matching unwind records and vice versa.
845 const aliases = self.getSymbolAliases(reloc_target.sym_index);
846 var i: u32 = 0;
847 while (i < aliases.len) : (i += 1) {
848 const actual_target = SymbolWithLoc{
849 .sym_index = i + aliases.start,
850 .file = reloc_target.file,
851 };
852 log.debug("unwind record {d} tracks {s}", .{
853 record_id,
854 macho_file.getSymbolName(actual_target),
855 });
856 try self.unwind_records_lookup.putNoClobber(gpa, actual_target, @intCast(record_id));
1036 // Regardless of who the winner is, we still merge symbol visibility here.
1037 if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
1038 if (symbol.visibility != .global) {
1039 symbol.visibility = .hidden;
8571040 }
1041 } else {
1042 symbol.visibility = .global;
8581043 }
8591044 }
8601045}
8611046
862pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 {
863 const symtab = self.in_symtab.?;
864 if (index >= symtab.len) return null;
865 const mapped_index = self.source_symtab_lookup[index];
866 return symtab[mapped_index];
1047pub fn resetGlobals(self: *Object, macho_file: *MachO) void {
1048 for (self.symbols.items, 0..) |sym_index, nlist_idx| {
1049 if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
1050 const sym = macho_file.getSymbol(sym_index);
1051 const name = sym.name;
1052 sym.* = .{};
1053 sym.name = name;
1054 }
8671055}
8681056
869pub fn getSourceSection(self: Object, index: u8) macho.section_64 {
870 const sections = self.getSourceSections();
871 assert(index < sections.len);
872 return sections[index];
1057pub fn markLive(self: *Object, macho_file: *MachO) void {
1058 const tracy = trace(@src());
1059 defer tracy.end();
1060
1061 for (self.symbols.items, 0..) |index, nlist_idx| {
1062 const nlist = self.symtab.items(.nlist)[nlist_idx];
1063 if (!nlist.ext()) continue;
1064
1065 const sym = macho_file.getSymbol(index);
1066 const file = sym.getFile(macho_file) orelse continue;
1067 const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
1068 if (should_keep and file == .object and !file.object.alive) {
1069 file.object.alive = true;
1070 file.object.markLive(macho_file);
1071 }
1072 }
8731073}
8741074
875pub fn getSourceSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 {
876 const index = self.getSourceSectionIndexByName(segname, sectname) orelse return null;
877 const sections = self.getSourceSections();
878 return sections[index];
1075pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void {
1076 for (self.symbols.items, 0..) |index, nlist_idx| {
1077 const sym = macho_file.getSymbol(index);
1078 if (sym.visibility != .global) continue;
1079 const file = sym.getFile(macho_file) orelse continue;
1080 if (file.getIndex() == self.index) continue;
1081
1082 const nlist = self.symtab.items(.nlist)[nlist_idx];
1083 if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
1084 const gop = try dupes.getOrPut(index);
1085 if (!gop.found_existing) {
1086 gop.value_ptr.* = .{};
1087 }
1088 try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
1089 }
1090 }
8791091}
8801092
881pub fn getSourceSectionIndexByName(self: Object, segname: []const u8, sectname: []const u8) ?u8 {
882 const sections = self.getSourceSections();
883 for (sections, 0..) |sect, i| {
884 if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName()))
885 return @as(u8, @intCast(i));
886 } else return null;
1093pub fn scanRelocs(self: Object, macho_file: *MachO) !void {
1094 const tracy = trace(@src());
1095 defer tracy.end();
1096
1097 for (self.atoms.items) |atom_index| {
1098 const atom = macho_file.getAtom(atom_index).?;
1099 if (!atom.flags.alive) continue;
1100 const sect = atom.getInputSection(macho_file);
1101 if (sect.isZerofill()) continue;
1102 try atom.scanRelocs(macho_file);
1103 }
1104
1105 for (self.unwind_records.items) |rec_index| {
1106 const rec = macho_file.getUnwindRecord(rec_index);
1107 if (!rec.alive) continue;
1108 if (rec.getFde(macho_file)) |fde| {
1109 if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| {
1110 sym.flags.needs_got = true;
1111 }
1112 } else if (rec.getPersonality(macho_file)) |sym| {
1113 sym.flags.needs_got = true;
1114 }
1115 }
8871116}
8881117
889pub fn getSourceSections(self: Object) []align(1) const macho.section_64 {
890 var it = LoadCommandIterator{
891 .ncmds = self.header.ncmds,
892 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
893 };
894 while (it.next()) |cmd| switch (cmd.cmd()) {
895 .SEGMENT_64 => {
896 return cmd.getSections();
1118pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
1119 const tracy = trace(@src());
1120 defer tracy.end();
1121 const gpa = macho_file.base.comp.gpa;
1122
1123 for (self.symbols.items, 0..) |index, i| {
1124 const sym = macho_file.getSymbol(index);
1125 if (!sym.flags.tentative) continue;
1126 const sym_file = sym.getFile(macho_file).?;
1127 if (sym_file.getIndex() != self.index) continue;
1128
1129 const nlist_idx = @as(Symbol.Index, @intCast(i));
1130 const nlist = &self.symtab.items(.nlist)[nlist_idx];
1131 const nlist_atom = &self.symtab.items(.atom)[nlist_idx];
1132
1133 const atom_index = try macho_file.addAtom();
1134 try self.atoms.append(gpa, atom_index);
1135
1136 const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)});
1137 defer gpa.free(name);
1138 const atom = macho_file.getAtom(atom_index).?;
1139 atom.atom_index = atom_index;
1140 atom.name = try macho_file.strings.insert(gpa, name);
1141 atom.file = self.index;
1142 atom.size = nlist.n_value;
1143 atom.alignment = Atom.Alignment.fromLog2Units((nlist.n_desc >> 8) & 0x0f);
1144
1145 const n_sect = try self.addSection(gpa, "__DATA", "__common");
1146 const sect = &self.sections.items(.header)[n_sect];
1147 sect.flags = macho.S_ZEROFILL;
1148 sect.size = atom.size;
1149 sect.@"align" = atom.alignment.toLog2Units();
1150 atom.n_sect = n_sect;
1151
1152 sym.value = 0;
1153 sym.atom = atom_index;
1154 sym.flags.weak = false;
1155 sym.flags.weak_ref = false;
1156 sym.flags.tentative = false;
1157 sym.visibility = .global;
1158
1159 nlist.n_value = 0;
1160 nlist.n_type = macho.N_EXT | macho.N_SECT;
1161 nlist.n_sect = 0;
1162 nlist.n_desc = 0;
1163 nlist_atom.* = atom_index;
1164 }
1165}
1166
1167fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
1168 const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
1169 self.sections.set(n_sect, .{
1170 .header = .{
1171 .sectname = MachO.makeStaticString(sectname),
1172 .segname = MachO.makeStaticString(segname),
8971173 },
898 else => {},
899 } else unreachable;
1174 });
1175 return n_sect;
9001176}
9011177
902pub fn parseDataInCode(self: *Object, gpa: Allocator) !void {
903 var it = LoadCommandIterator{
904 .ncmds = self.header.ncmds,
905 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
906 };
907 const cmd = while (it.next()) |cmd| {
908 switch (cmd.cmd()) {
909 .DATA_IN_CODE => break cmd.cast(macho.linkedit_data_command).?,
910 else => {},
1178pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
1179 const tracy = trace(@src());
1180 defer tracy.end();
1181
1182 for (self.symbols.items) |sym_index| {
1183 const sym = macho_file.getSymbol(sym_index);
1184 const file = sym.getFile(macho_file) orelse continue;
1185 if (file.getIndex() != self.index) continue;
1186 if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
1187 if (sym.isSymbolStab(macho_file)) continue;
1188 const name = sym.getName(macho_file);
1189 // TODO in -r mode, we actually want to merge symbol names and emit only one
1190 // work it out when emitting relocs
1191 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l') and !macho_file.base.isObject()) continue;
1192 sym.flags.output_symtab = true;
1193 if (sym.isLocal()) {
1194 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
1195 self.output_symtab_ctx.nlocals += 1;
1196 } else if (sym.flags.@"export") {
1197 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
1198 self.output_symtab_ctx.nexports += 1;
1199 } else {
1200 assert(sym.flags.import);
1201 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
1202 self.output_symtab_ctx.nimports += 1;
9111203 }
912 } else return;
913 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
914 const dice = @as([*]align(1) const macho.data_in_code_entry, @ptrCast(self.contents.ptr + cmd.dataoff))[0..ndice];
915 try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len);
916 self.data_in_code.appendUnalignedSliceAssumeCapacity(dice);
917 mem.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan);
918}
1204 self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
1205 }
9191206
920fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool {
921 _ = ctx;
922 return lhs.offset < rhs.offset;
1207 if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
1208 try self.calcStabsSize(macho_file);
9231209}
9241210
925fn getDysymtab(self: Object) ?macho.dysymtab_command {
926 var it = LoadCommandIterator{
927 .ncmds = self.header.ncmds,
928 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
929 };
930 while (it.next()) |cmd| {
931 switch (cmd.cmd()) {
932 .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?,
933 else => {},
1211pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
1212 if (self.dwarf_info) |dw| {
1213 // TODO handle multiple CUs
1214 const cu = dw.compile_units.items[0];
1215 const comp_dir = try cu.getCompileDir(dw) orelse return;
1216 const tu_name = try cu.getSourceFile(dw) orelse return;
1217
1218 self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
1219 self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir
1220 self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name
1221
1222 if (self.archive) |path| {
1223 self.output_symtab_ctx.strsize += @as(u32, @intCast(path.len + 1 + self.path.len + 1 + 1));
1224 } else {
1225 self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1));
9341226 }
935 } else return null;
1227
1228 for (self.symbols.items) |sym_index| {
1229 const sym = macho_file.getSymbol(sym_index);
1230 const file = sym.getFile(macho_file) orelse continue;
1231 if (file.getIndex() != self.index) continue;
1232 if (!sym.flags.output_symtab) continue;
1233 if (macho_file.base.isObject()) {
1234 const name = sym.getName(macho_file);
1235 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
1236 }
1237 const sect = macho_file.sections.items(.header)[sym.out_n_sect];
1238 if (sect.isCode()) {
1239 self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM
1240 } else if (sym.visibility == .global) {
1241 self.output_symtab_ctx.nstabs += 1; // N_GSYM
1242 } else {
1243 self.output_symtab_ctx.nstabs += 1; // N_STSYM
1244 }
1245 }
1246 } else {
1247 assert(self.hasSymbolStabs());
1248
1249 for (self.stab_files.items) |sf| {
1250 self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
1251 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getCompDir(self).len + 1)); // comp_dir
1252 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getTuName(self).len + 1)); // tu_name
1253 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getOsoPath(self).len + 1)); // path
1254
1255 for (sf.stabs.items) |stab| {
1256 const sym = stab.getSymbol(macho_file) orelse continue;
1257 const file = sym.getFile(macho_file).?;
1258 if (file.getIndex() != self.index) continue;
1259 if (!sym.flags.output_symtab) continue;
1260 const nstabs: u32 = switch (stab.tag) {
1261 .func => 4, // N_BNSYM, N_FUN, N_FUN, N_ENSYM
1262 .global => 1, // N_GSYM
1263 .static => 1, // N_STSYM
1264 };
1265 self.output_symtab_ctx.nstabs += nstabs;
1266 }
1267 }
1268 }
9361269}
9371270
938pub fn parseDwarfInfo(self: Object) DwarfInfo {
939 var di = DwarfInfo{
940 .debug_info = &[0]u8{},
941 .debug_abbrev = &[0]u8{},
942 .debug_str = &[0]u8{},
943 };
944 for (self.getSourceSections()) |sect| {
945 if (!sect.isDebug()) continue;
946 const sectname = sect.sectName();
947 if (mem.eql(u8, sectname, "__debug_info")) {
948 di.debug_info = self.getSectionContents(sect);
949 } else if (mem.eql(u8, sectname, "__debug_abbrev")) {
950 di.debug_abbrev = self.getSectionContents(sect);
951 } else if (mem.eql(u8, sectname, "__debug_str")) {
952 di.debug_str = self.getSectionContents(sect);
1271pub fn writeSymtab(self: Object, macho_file: *MachO) error{Overflow}!void {
1272 const tracy = trace(@src());
1273 defer tracy.end();
1274
1275 for (self.symbols.items) |sym_index| {
1276 const sym = macho_file.getSymbol(sym_index);
1277 const file = sym.getFile(macho_file) orelse continue;
1278 if (file.getIndex() != self.index) continue;
1279 const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
1280 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1281 macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
1282 macho_file.strtab.appendAssumeCapacity(0);
1283 const out_sym = &macho_file.symtab.items[idx];
1284 out_sym.n_strx = n_strx;
1285 sym.setOutputSym(macho_file, out_sym);
1286 }
1287
1288 if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
1289 try self.writeStabs(macho_file);
1290}
1291
1292pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void {
1293 const writeFuncStab = struct {
1294 inline fn writeFuncStab(
1295 n_strx: u32,
1296 n_sect: u8,
1297 n_value: u64,
1298 size: u64,
1299 index: u32,
1300 ctx: *MachO,
1301 ) void {
1302 ctx.symtab.items[index] = .{
1303 .n_strx = 0,
1304 .n_type = macho.N_BNSYM,
1305 .n_sect = n_sect,
1306 .n_desc = 0,
1307 .n_value = n_value,
1308 };
1309 ctx.symtab.items[index + 1] = .{
1310 .n_strx = n_strx,
1311 .n_type = macho.N_FUN,
1312 .n_sect = n_sect,
1313 .n_desc = 0,
1314 .n_value = n_value,
1315 };
1316 ctx.symtab.items[index + 2] = .{
1317 .n_strx = 0,
1318 .n_type = macho.N_FUN,
1319 .n_sect = 0,
1320 .n_desc = 0,
1321 .n_value = size,
1322 };
1323 ctx.symtab.items[index + 3] = .{
1324 .n_strx = 0,
1325 .n_type = macho.N_ENSYM,
1326 .n_sect = n_sect,
1327 .n_desc = 0,
1328 .n_value = size,
1329 };
1330 }
1331 }.writeFuncStab;
1332
1333 var index = self.output_symtab_ctx.istab;
1334
1335 if (self.dwarf_info) |dw| {
1336 // TODO handle multiple CUs
1337 const cu = dw.compile_units.items[0];
1338 const comp_dir = try cu.getCompileDir(dw) orelse return;
1339 const tu_name = try cu.getSourceFile(dw) orelse return;
1340
1341 // Open scope
1342 // N_SO comp_dir
1343 var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1344 macho_file.strtab.appendSliceAssumeCapacity(comp_dir);
1345 macho_file.strtab.appendAssumeCapacity(0);
1346 macho_file.symtab.items[index] = .{
1347 .n_strx = n_strx,
1348 .n_type = macho.N_SO,
1349 .n_sect = 0,
1350 .n_desc = 0,
1351 .n_value = 0,
1352 };
1353 index += 1;
1354 // N_SO tu_name
1355 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1356 macho_file.strtab.appendSliceAssumeCapacity(tu_name);
1357 macho_file.strtab.appendAssumeCapacity(0);
1358 macho_file.symtab.items[index] = .{
1359 .n_strx = n_strx,
1360 .n_type = macho.N_SO,
1361 .n_sect = 0,
1362 .n_desc = 0,
1363 .n_value = 0,
1364 };
1365 index += 1;
1366 // N_OSO path
1367 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1368 if (self.archive) |path| {
1369 macho_file.strtab.appendSliceAssumeCapacity(path);
1370 macho_file.strtab.appendAssumeCapacity('(');
1371 macho_file.strtab.appendSliceAssumeCapacity(self.path);
1372 macho_file.strtab.appendAssumeCapacity(')');
1373 macho_file.strtab.appendAssumeCapacity(0);
1374 } else {
1375 macho_file.strtab.appendSliceAssumeCapacity(self.path);
1376 macho_file.strtab.appendAssumeCapacity(0);
1377 }
1378 macho_file.symtab.items[index] = .{
1379 .n_strx = n_strx,
1380 .n_type = macho.N_OSO,
1381 .n_sect = 0,
1382 .n_desc = 1,
1383 .n_value = self.mtime,
1384 };
1385 index += 1;
1386
1387 for (self.symbols.items) |sym_index| {
1388 const sym = macho_file.getSymbol(sym_index);
1389 const file = sym.getFile(macho_file) orelse continue;
1390 if (file.getIndex() != self.index) continue;
1391 if (!sym.flags.output_symtab) continue;
1392 if (macho_file.base.isObject()) {
1393 const name = sym.getName(macho_file);
1394 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
1395 }
1396 const sect = macho_file.sections.items(.header)[sym.out_n_sect];
1397 const sym_n_strx = n_strx: {
1398 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
1399 const osym = macho_file.symtab.items[symtab_index];
1400 break :n_strx osym.n_strx;
1401 };
1402 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
1403 const sym_n_value = sym.getAddress(.{}, macho_file);
1404 const sym_size = sym.getSize(macho_file);
1405 if (sect.isCode()) {
1406 writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
1407 index += 4;
1408 } else if (sym.visibility == .global) {
1409 macho_file.symtab.items[index] = .{
1410 .n_strx = sym_n_strx,
1411 .n_type = macho.N_GSYM,
1412 .n_sect = sym_n_sect,
1413 .n_desc = 0,
1414 .n_value = 0,
1415 };
1416 index += 1;
1417 } else {
1418 macho_file.symtab.items[index] = .{
1419 .n_strx = sym_n_strx,
1420 .n_type = macho.N_STSYM,
1421 .n_sect = sym_n_sect,
1422 .n_desc = 0,
1423 .n_value = sym_n_value,
1424 };
1425 index += 1;
1426 }
1427 }
1428
1429 // Close scope
1430 // N_SO
1431 macho_file.symtab.items[index] = .{
1432 .n_strx = 0,
1433 .n_type = macho.N_SO,
1434 .n_sect = 0,
1435 .n_desc = 0,
1436 .n_value = 0,
1437 };
1438 } else {
1439 assert(self.hasSymbolStabs());
1440
1441 for (self.stab_files.items) |sf| {
1442 // Open scope
1443 // N_SO comp_dir
1444 var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1445 macho_file.strtab.appendSliceAssumeCapacity(sf.getCompDir(self));
1446 macho_file.strtab.appendAssumeCapacity(0);
1447 macho_file.symtab.items[index] = .{
1448 .n_strx = n_strx,
1449 .n_type = macho.N_SO,
1450 .n_sect = 0,
1451 .n_desc = 0,
1452 .n_value = 0,
1453 };
1454 index += 1;
1455 // N_SO tu_name
1456 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1457 macho_file.strtab.appendSliceAssumeCapacity(sf.getTuName(self));
1458 macho_file.strtab.appendAssumeCapacity(0);
1459 macho_file.symtab.items[index] = .{
1460 .n_strx = n_strx,
1461 .n_type = macho.N_SO,
1462 .n_sect = 0,
1463 .n_desc = 0,
1464 .n_value = 0,
1465 };
1466 index += 1;
1467 // N_OSO path
1468 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1469 macho_file.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self));
1470 macho_file.strtab.appendAssumeCapacity(0);
1471 macho_file.symtab.items[index] = .{
1472 .n_strx = n_strx,
1473 .n_type = macho.N_OSO,
1474 .n_sect = 0,
1475 .n_desc = 1,
1476 .n_value = sf.getOsoModTime(self),
1477 };
1478 index += 1;
1479
1480 for (sf.stabs.items) |stab| {
1481 const sym = stab.getSymbol(macho_file) orelse continue;
1482 const file = sym.getFile(macho_file).?;
1483 if (file.getIndex() != self.index) continue;
1484 if (!sym.flags.output_symtab) continue;
1485 const sym_n_strx = n_strx: {
1486 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
1487 const osym = macho_file.symtab.items[symtab_index];
1488 break :n_strx osym.n_strx;
1489 };
1490 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
1491 const sym_n_value = sym.getAddress(.{}, macho_file);
1492 const sym_size = sym.getSize(macho_file);
1493 switch (stab.tag) {
1494 .func => {
1495 writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
1496 index += 4;
1497 },
1498 .global => {
1499 macho_file.symtab.items[index] = .{
1500 .n_strx = sym_n_strx,
1501 .n_type = macho.N_GSYM,
1502 .n_sect = sym_n_sect,
1503 .n_desc = 0,
1504 .n_value = 0,
1505 };
1506 index += 1;
1507 },
1508 .static => {
1509 macho_file.symtab.items[index] = .{
1510 .n_strx = sym_n_strx,
1511 .n_type = macho.N_STSYM,
1512 .n_sect = sym_n_sect,
1513 .n_desc = 0,
1514 .n_value = sym_n_value,
1515 };
1516 index += 1;
1517 },
1518 }
1519 }
1520
1521 // Close scope
1522 // N_SO
1523 macho_file.symtab.items[index] = .{
1524 .n_strx = 0,
1525 .n_type = macho.N_SO,
1526 .n_sect = 0,
1527 .n_desc = 0,
1528 .n_value = 0,
1529 };
1530 index += 1;
9531531 }
9541532 }
955 return di;
9561533}
9571534
958/// Returns Platform composed from the first encountered build version type load command:
959/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
960pub fn getPlatform(self: Object) ?Platform {
1535fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
9611536 var it = LoadCommandIterator{
962 .ncmds = self.header.ncmds,
963 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
1537 .ncmds = self.header.?.ncmds,
1538 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
9641539 };
9651540 while (it.next()) |cmd| {
966 switch (cmd.cmd()) {
967 .BUILD_VERSION,
968 .VERSION_MIN_MACOSX,
969 .VERSION_MIN_IPHONEOS,
970 .VERSION_MIN_TVOS,
971 .VERSION_MIN_WATCHOS,
972 => return Platform.fromLoadCommand(cmd),
973 else => {},
974 }
1541 if (cmd.cmd() == lc) return cmd;
9751542 } else return null;
9761543}
9771544
978pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {
979 const size = @as(usize, @intCast(sect.size));
980 return self.contents[sect.offset..][0..size];
1545pub fn getSectionData(self: *const Object, index: u32) error{Overflow}![]const u8 {
1546 const slice = self.sections.slice();
1547 assert(index < slice.items(.header).len);
1548 const sect = slice.items(.header)[index];
1549 const off = math.cast(usize, sect.offset) orelse return error.Overflow;
1550 const size = math.cast(usize, sect.size) orelse return error.Overflow;
1551 return self.data[off..][0..size];
1552}
1553
1554pub fn getAtomData(self: *const Object, atom: Atom) error{Overflow}![]const u8 {
1555 const data = try self.getSectionData(atom.n_sect);
1556 const off = math.cast(usize, atom.off) orelse return error.Overflow;
1557 const size = math.cast(usize, atom.size) orelse return error.Overflow;
1558 return data[off..][0..size];
9811559}
9821560
983pub fn getSectionAliasSymbolIndex(self: Object, sect_id: u8) u32 {
984 const start = @as(u32, @intCast(self.in_symtab.?.len));
985 return start + sect_id;
1561pub fn getAtomRelocs(self: *const Object, atom: Atom) []const Relocation {
1562 const relocs = self.sections.items(.relocs)[atom.n_sect];
1563 return relocs.items[atom.relocs.pos..][0..atom.relocs.len];
9861564}
9871565
988pub fn getSectionAliasSymbol(self: *Object, sect_id: u8) macho.nlist_64 {
989 return self.symtab[self.getSectionAliasSymbolIndex(sect_id)];
1566fn getString(self: Object, off: u32) [:0]const u8 {
1567 assert(off < self.strtab.len);
1568 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
9901569}
9911570
992pub fn getSectionAliasSymbolPtr(self: *Object, sect_id: u8) *macho.nlist_64 {
993 return &self.symtab[self.getSectionAliasSymbolIndex(sect_id)];
1571pub fn hasUnwindRecords(self: Object) bool {
1572 return self.unwind_records.items.len > 0;
9941573}
9951574
996fn getSourceRelocs(self: Object, sect: macho.section_64) ?[]align(1) const macho.relocation_info {
997 if (sect.nreloc == 0) return null;
998 return @as([*]align(1) const macho.relocation_info, @ptrCast(self.contents.ptr + sect.reloff))[0..sect.nreloc];
1575pub fn hasEhFrameRecords(self: Object) bool {
1576 return self.cies.items.len > 0;
9991577}
10001578
1001pub fn getRelocs(self: Object, sect_id: u8) []const macho.relocation_info {
1002 const sect = self.getSourceSection(sect_id);
1003 const start = self.section_relocs_lookup.items[sect_id];
1004 const len = sect.nreloc;
1005 return self.relocations.items[start..][0..len];
1579/// TODO handle multiple CUs
1580pub fn hasDebugInfo(self: Object) bool {
1581 if (self.dwarf_info) |dw| {
1582 return dw.compile_units.items.len > 0;
1583 }
1584 return self.hasSymbolStabs();
10061585}
10071586
1008pub fn getSymbolName(self: Object, index: u32) []const u8 {
1009 const strtab = self.in_strtab.?;
1010 const sym = self.symtab[index];
1587fn hasSymbolStabs(self: Object) bool {
1588 return self.stab_files.items.len > 0;
1589}
10111590
1012 if (self.getSourceSymbol(index) == null) {
1013 assert(sym.n_strx == 0);
1014 return "";
1591pub fn hasObjc(self: Object) bool {
1592 for (self.symtab.items(.nlist)) |nlist| {
1593 const name = self.getString(nlist.n_strx);
1594 if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true;
1595 }
1596 for (self.sections.items(.header)) |sect| {
1597 if (mem.eql(u8, sect.segName(), "__DATA") and mem.eql(u8, sect.sectName(), "__objc_catlist")) return true;
1598 if (mem.eql(u8, sect.segName(), "__TEXT") and mem.eql(u8, sect.sectName(), "__swift")) return true;
10151599 }
1600 return false;
1601}
10161602
1017 const start = sym.n_strx;
1018 const len = self.strtab_lookup[index];
1603pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry {
1604 const lc = self.getLoadCommand(.DATA_IN_CODE) orelse return &[0]macho.data_in_code_entry{};
1605 const cmd = lc.cast(macho.linkedit_data_command).?;
1606 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
1607 const dice = @as(
1608 [*]align(1) const macho.data_in_code_entry,
1609 @ptrCast(self.data.ptr + cmd.dataoff),
1610 )[0..ndice];
1611 return dice;
1612}
10191613
1020 return strtab[start..][0 .. len - 1 :0];
1614pub inline fn hasSubsections(self: Object) bool {
1615 return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
10211616}
10221617
1023fn getSymbolAliases(self: Object, index: u32) Entry {
1024 const addr = self.source_address_lookup[index];
1025 var start = index;
1026 while (start > 0 and
1027 self.source_address_lookup[start - 1] == addr) : (start -= 1)
1028 {}
1029 const end: u32 = for (self.source_address_lookup[start..], start..) |saddr, i| {
1030 if (saddr != addr) break @as(u32, @intCast(i));
1031 } else @as(u32, @intCast(self.source_address_lookup.len));
1032 return .{ .start = start, .len = end - start };
1618pub fn asFile(self: *Object) File {
1619 return .{ .object = self };
10331620}
10341621
1035pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
1036 // Find containing atom
1037 const Predicate = struct {
1038 addr: i64,
1622pub fn format(
1623 self: *Object,
1624 comptime unused_fmt_string: []const u8,
1625 options: std.fmt.FormatOptions,
1626 writer: anytype,
1627) !void {
1628 _ = self;
1629 _ = unused_fmt_string;
1630 _ = options;
1631 _ = writer;
1632 @compileError("do not format objects directly");
1633}
10391634
1040 pub fn predicate(pred: @This(), other: i64) bool {
1041 return if (other == -1) true else other > pred.addr;
1042 }
1043 };
1635const FormatContext = struct {
1636 object: *Object,
1637 macho_file: *MachO,
1638};
10441639
1045 if (sect_hint) |sect_id| {
1046 if (self.source_section_index_lookup[sect_id].len > 0) {
1047 const lookup = self.source_section_index_lookup[sect_id];
1048 const target_sym_index = MachO.lsearch(
1049 i64,
1050 self.source_address_lookup[lookup.start..][0..lookup.len],
1051 Predicate{ .addr = @as(i64, @intCast(addr)) },
1052 );
1053 if (target_sym_index > 0) {
1054 // Hone in on the most senior alias of the target symbol.
1055 // See SymbolAtIndex.lessThan for more context.
1056 const aliases = self.getSymbolAliases(@intCast(lookup.start + target_sym_index - 1));
1057 return aliases.start;
1058 }
1059 }
1060 return self.getSectionAliasSymbolIndex(sect_id);
1640pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
1641 return .{ .data = .{
1642 .object = self,
1643 .macho_file = macho_file,
1644 } };
1645}
1646
1647fn formatAtoms(
1648 ctx: FormatContext,
1649 comptime unused_fmt_string: []const u8,
1650 options: std.fmt.FormatOptions,
1651 writer: anytype,
1652) !void {
1653 _ = unused_fmt_string;
1654 _ = options;
1655 const object = ctx.object;
1656 try writer.writeAll(" atoms\n");
1657 for (object.atoms.items) |atom_index| {
1658 const atom = ctx.macho_file.getAtom(atom_index).?;
1659 try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
10611660 }
1661}
10621662
1063 const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{
1064 .addr = @as(i64, @intCast(addr)),
1065 });
1066 assert(target_sym_index > 0);
1067 return @as(u32, @intCast(target_sym_index - 1));
1663pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatCies) {
1664 return .{ .data = .{
1665 .object = self,
1666 .macho_file = macho_file,
1667 } };
10681668}
10691669
1070pub fn getGlobal(self: Object, sym_index: u32) ?u32 {
1071 if (self.globals_lookup[sym_index] == -1) return null;
1072 return @as(u32, @intCast(self.globals_lookup[sym_index]));
1670fn formatCies(
1671 ctx: FormatContext,
1672 comptime unused_fmt_string: []const u8,
1673 options: std.fmt.FormatOptions,
1674 writer: anytype,
1675) !void {
1676 _ = unused_fmt_string;
1677 _ = options;
1678 const object = ctx.object;
1679 try writer.writeAll(" cies\n");
1680 for (object.cies.items, 0..) |cie, i| {
1681 try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.macho_file) });
1682 }
10731683}
10741684
1075pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index {
1076 return self.atom_by_index_table[sym_index];
1685pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatFdes) {
1686 return .{ .data = .{
1687 .object = self,
1688 .macho_file = macho_file,
1689 } };
10771690}
10781691
1079pub fn hasUnwindRecords(self: Object) bool {
1080 return self.unwind_info_sect_id != null;
1692fn formatFdes(
1693 ctx: FormatContext,
1694 comptime unused_fmt_string: []const u8,
1695 options: std.fmt.FormatOptions,
1696 writer: anytype,
1697) !void {
1698 _ = unused_fmt_string;
1699 _ = options;
1700 const object = ctx.object;
1701 try writer.writeAll(" fdes\n");
1702 for (object.fdes.items, 0..) |fde, i| {
1703 try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.macho_file) });
1704 }
10811705}
10821706
1083pub fn getUnwindRecords(self: Object) []align(1) const macho.compact_unwind_entry {
1084 const sect_id = self.unwind_info_sect_id orelse return &[0]macho.compact_unwind_entry{};
1085 const sect = self.getSourceSection(sect_id);
1086 const data = self.getSectionContents(sect);
1087 const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
1088 return @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries];
1707pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatUnwindRecords) {
1708 return .{ .data = .{
1709 .object = self,
1710 .macho_file = macho_file,
1711 } };
10891712}
10901713
1091pub fn hasEhFrameRecords(self: Object) bool {
1092 return self.eh_frame_sect_id != null;
1714fn formatUnwindRecords(
1715 ctx: FormatContext,
1716 comptime unused_fmt_string: []const u8,
1717 options: std.fmt.FormatOptions,
1718 writer: anytype,
1719) !void {
1720 _ = unused_fmt_string;
1721 _ = options;
1722 const object = ctx.object;
1723 const macho_file = ctx.macho_file;
1724 try writer.writeAll(" unwind records\n");
1725 for (object.unwind_records.items) |rec| {
1726 try writer.print(" rec({d}) : {}\n", .{ rec, macho_file.getUnwindRecord(rec).fmt(macho_file) });
1727 }
10931728}
10941729
1095pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator {
1096 const sect_id = self.eh_frame_sect_id orelse return .{ .data = &[0]u8{} };
1097 const sect = self.getSourceSection(sect_id);
1098 const data = self.getSectionContents(sect);
1099 return .{ .data = data };
1730pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
1731 return .{ .data = .{
1732 .object = self,
1733 .macho_file = macho_file,
1734 } };
11001735}
11011736
1102pub fn hasDataInCode(self: Object) bool {
1103 return self.data_in_code.items.len > 0;
1737fn formatSymtab(
1738 ctx: FormatContext,
1739 comptime unused_fmt_string: []const u8,
1740 options: std.fmt.FormatOptions,
1741 writer: anytype,
1742) !void {
1743 _ = unused_fmt_string;
1744 _ = options;
1745 const object = ctx.object;
1746 try writer.writeAll(" symbols\n");
1747 for (object.symbols.items) |index| {
1748 const sym = ctx.macho_file.getSymbol(index);
1749 try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)});
1750 }
11041751}
11051752
1106const Object = @This();
1753pub fn fmtPath(self: Object) std.fmt.Formatter(formatPath) {
1754 return .{ .data = self };
1755}
1756
1757fn formatPath(
1758 object: Object,
1759 comptime unused_fmt_string: []const u8,
1760 options: std.fmt.FormatOptions,
1761 writer: anytype,
1762) !void {
1763 _ = unused_fmt_string;
1764 _ = options;
1765 if (object.archive) |path| {
1766 try writer.writeAll(path);
1767 try writer.writeByte('(');
1768 try writer.writeAll(object.path);
1769 try writer.writeByte(')');
1770 } else try writer.writeAll(object.path);
1771}
1772
1773const Section = struct {
1774 header: macho.section_64,
1775 subsections: std.ArrayListUnmanaged(Subsection) = .{},
1776 relocs: std.ArrayListUnmanaged(Relocation) = .{},
1777};
1778
1779const Subsection = struct {
1780 atom: Atom.Index,
1781 off: u64,
1782};
1783
1784pub const Nlist = struct {
1785 nlist: macho.nlist_64,
1786 size: u64,
1787 atom: Atom.Index,
1788};
1789
1790const StabFile = struct {
1791 comp_dir: u32,
1792 stabs: std.ArrayListUnmanaged(Stab) = .{},
1793
1794 fn getCompDir(sf: StabFile, object: *const Object) [:0]const u8 {
1795 const nlist = object.symtab.items(.nlist)[sf.comp_dir];
1796 return object.getString(nlist.n_strx);
1797 }
1798
1799 fn getTuName(sf: StabFile, object: *const Object) [:0]const u8 {
1800 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1];
1801 return object.getString(nlist.n_strx);
1802 }
1803
1804 fn getOsoPath(sf: StabFile, object: *const Object) [:0]const u8 {
1805 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
1806 return object.getString(nlist.n_strx);
1807 }
1808
1809 fn getOsoModTime(sf: StabFile, object: *const Object) u64 {
1810 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
1811 return nlist.n_value;
1812 }
1813
1814 const Stab = struct {
1815 tag: enum { func, global, static } = .func,
1816 symbol: ?Symbol.Index = null,
1817
1818 fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol {
1819 return if (stab.symbol) |s| macho_file.getSymbol(s) else null;
1820 }
1821 };
1822};
1823
1824const x86_64 = struct {
1825 fn parseRelocs(
1826 self: *const Object,
1827 n_sect: u8,
1828 sect: macho.section_64,
1829 out: *std.ArrayListUnmanaged(Relocation),
1830 macho_file: *MachO,
1831 ) !void {
1832 const gpa = macho_file.base.comp.gpa;
1833
1834 const relocs = @as(
1835 [*]align(1) const macho.relocation_info,
1836 @ptrCast(self.data.ptr + sect.reloff),
1837 )[0..sect.nreloc];
1838 const code = try self.getSectionData(@intCast(n_sect));
1839
1840 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
1841
1842 var i: usize = 0;
1843 while (i < relocs.len) : (i += 1) {
1844 const rel = relocs[i];
1845 const rel_type: macho.reloc_type_x86_64 = @enumFromInt(rel.r_type);
1846 const rel_offset = @as(u32, @intCast(rel.r_address));
1847
1848 var addend = switch (rel.r_length) {
1849 0 => code[rel_offset],
1850 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
1851 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
1852 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
1853 };
1854 addend += switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
1855 .X86_64_RELOC_SIGNED_1 => 1,
1856 .X86_64_RELOC_SIGNED_2 => 2,
1857 .X86_64_RELOC_SIGNED_4 => 4,
1858 else => 0,
1859 };
1860
1861 const target = if (rel.r_extern == 0) blk: {
1862 const nsect = rel.r_symbolnum - 1;
1863 const taddr: i64 = if (rel.r_pcrel == 1)
1864 @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4
1865 else
1866 addend;
1867 const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
1868 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1869 sect.segName(), sect.sectName(), rel.r_address,
1870 });
1871 return error.MalformedObject;
1872 };
1873 addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
1874 break :blk target;
1875 } else self.symbols.items[rel.r_symbolnum];
1876
1877 const has_subtractor = if (i > 0 and
1878 @as(macho.reloc_type_x86_64, @enumFromInt(relocs[i - 1].r_type)) == .X86_64_RELOC_SUBTRACTOR)
1879 blk: {
1880 if (rel_type != .X86_64_RELOC_UNSIGNED) {
1881 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: X86_64_RELOC_SUBTRACTOR followed by {s}", .{
1882 sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
1883 });
1884 return error.MalformedObject;
1885 }
1886 break :blk true;
1887 } else false;
1888
1889 const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
1890 switch (err) {
1891 error.Pcrel => try macho_file.reportParseError2(
1892 self.index,
1893 "{s},{s}: 0x{x}: PC-relative {s} relocation",
1894 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1895 ),
1896 error.NonPcrel => try macho_file.reportParseError2(
1897 self.index,
1898 "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
1899 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1900 ),
1901 error.InvalidLength => try macho_file.reportParseError2(
1902 self.index,
1903 "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
1904 .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
1905 ),
1906 error.NonExtern => try macho_file.reportParseError2(
1907 self.index,
1908 "{s},{s}: 0x{x}: non-extern target in {s} relocation",
1909 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1910 ),
1911 }
1912 return error.MalformedObject;
1913 };
1914
1915 out.appendAssumeCapacity(.{
1916 .tag = if (rel.r_extern == 1) .@"extern" else .local,
1917 .offset = @as(u32, @intCast(rel.r_address)),
1918 .target = target,
1919 .addend = addend,
1920 .type = @"type",
1921 .meta = .{
1922 .pcrel = rel.r_pcrel == 1,
1923 .has_subtractor = has_subtractor,
1924 .length = rel.r_length,
1925 .symbolnum = rel.r_symbolnum,
1926 },
1927 });
1928 }
1929 }
1930
1931 fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_x86_64) !Relocation.Type {
1932 switch (rel_type) {
1933 .X86_64_RELOC_UNSIGNED => {
1934 if (rel.r_pcrel == 1) return error.Pcrel;
1935 if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
1936 return .unsigned;
1937 },
1938
1939 .X86_64_RELOC_SUBTRACTOR => {
1940 if (rel.r_pcrel == 1) return error.Pcrel;
1941 return .subtractor;
1942 },
1943
1944 .X86_64_RELOC_BRANCH,
1945 .X86_64_RELOC_GOT_LOAD,
1946 .X86_64_RELOC_GOT,
1947 .X86_64_RELOC_TLV,
1948 => {
1949 if (rel.r_pcrel == 0) return error.NonPcrel;
1950 if (rel.r_length != 2) return error.InvalidLength;
1951 if (rel.r_extern == 0) return error.NonExtern;
1952 return switch (rel_type) {
1953 .X86_64_RELOC_BRANCH => .branch,
1954 .X86_64_RELOC_GOT_LOAD => .got_load,
1955 .X86_64_RELOC_GOT => .got,
1956 .X86_64_RELOC_TLV => .tlv,
1957 else => unreachable,
1958 };
1959 },
1960
1961 .X86_64_RELOC_SIGNED,
1962 .X86_64_RELOC_SIGNED_1,
1963 .X86_64_RELOC_SIGNED_2,
1964 .X86_64_RELOC_SIGNED_4,
1965 => {
1966 if (rel.r_pcrel == 0) return error.NonPcrel;
1967 if (rel.r_length != 2) return error.InvalidLength;
1968 return switch (rel_type) {
1969 .X86_64_RELOC_SIGNED => .signed,
1970 .X86_64_RELOC_SIGNED_1 => .signed1,
1971 .X86_64_RELOC_SIGNED_2 => .signed2,
1972 .X86_64_RELOC_SIGNED_4 => .signed4,
1973 else => unreachable,
1974 };
1975 },
1976 }
1977 }
1978};
1979
1980const aarch64 = struct {
1981 fn parseRelocs(
1982 self: *const Object,
1983 n_sect: u8,
1984 sect: macho.section_64,
1985 out: *std.ArrayListUnmanaged(Relocation),
1986 macho_file: *MachO,
1987 ) !void {
1988 const gpa = macho_file.base.comp.gpa;
1989
1990 const relocs = @as(
1991 [*]align(1) const macho.relocation_info,
1992 @ptrCast(self.data.ptr + sect.reloff),
1993 )[0..sect.nreloc];
1994 const code = try self.getSectionData(@intCast(n_sect));
1995
1996 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
1997
1998 var i: usize = 0;
1999 while (i < relocs.len) : (i += 1) {
2000 var rel = relocs[i];
2001 const rel_offset = @as(u32, @intCast(rel.r_address));
2002
2003 var addend: i64 = 0;
2004
2005 switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
2006 .ARM64_RELOC_ADDEND => {
2007 addend = rel.r_symbolnum;
2008 i += 1;
2009 if (i >= relocs.len) {
2010 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: unterminated ARM64_RELOC_ADDEND", .{
2011 sect.segName(), sect.sectName(), rel_offset,
2012 });
2013 return error.MalformedObject;
2014 }
2015 rel = relocs[i];
2016 switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
2017 .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
2018 else => |x| {
2019 try macho_file.reportParseError2(
2020 self.index,
2021 "{s},{s}: 0x{x}: ARM64_RELOC_ADDEND followed by {s}",
2022 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(x) },
2023 );
2024 return error.MalformedObject;
2025 },
2026 }
2027 },
2028 .ARM64_RELOC_UNSIGNED => {
2029 addend = switch (rel.r_length) {
2030 0 => code[rel_offset],
2031 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
2032 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
2033 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
2034 };
2035 },
2036 else => {},
2037 }
2038
2039 const rel_type: macho.reloc_type_arm64 = @enumFromInt(rel.r_type);
2040
2041 const target = if (rel.r_extern == 0) blk: {
2042 const nsect = rel.r_symbolnum - 1;
2043 const taddr: i64 = if (rel.r_pcrel == 1)
2044 @as(i64, @intCast(sect.addr)) + rel.r_address + addend
2045 else
2046 addend;
2047 const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
2048 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
2049 sect.segName(), sect.sectName(), rel.r_address,
2050 });
2051 return error.MalformedObject;
2052 };
2053 addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
2054 break :blk target;
2055 } else self.symbols.items[rel.r_symbolnum];
2056
2057 const has_subtractor = if (i > 0 and
2058 @as(macho.reloc_type_arm64, @enumFromInt(relocs[i - 1].r_type)) == .ARM64_RELOC_SUBTRACTOR)
2059 blk: {
2060 if (rel_type != .ARM64_RELOC_UNSIGNED) {
2061 try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: ARM64_RELOC_SUBTRACTOR followed by {s}", .{
2062 sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
2063 });
2064 return error.MalformedObject;
2065 }
2066 break :blk true;
2067 } else false;
2068
2069 const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
2070 switch (err) {
2071 error.Pcrel => try macho_file.reportParseError2(
2072 self.index,
2073 "{s},{s}: 0x{x}: PC-relative {s} relocation",
2074 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2075 ),
2076 error.NonPcrel => try macho_file.reportParseError2(
2077 self.index,
2078 "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
2079 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2080 ),
2081 error.InvalidLength => try macho_file.reportParseError2(
2082 self.index,
2083 "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
2084 .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
2085 ),
2086 error.NonExtern => try macho_file.reportParseError2(
2087 self.index,
2088 "{s},{s}: 0x{x}: non-extern target in {s} relocation",
2089 .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2090 ),
2091 }
2092 return error.MalformedObject;
2093 };
2094
2095 out.appendAssumeCapacity(.{
2096 .tag = if (rel.r_extern == 1) .@"extern" else .local,
2097 .offset = @as(u32, @intCast(rel.r_address)),
2098 .target = target,
2099 .addend = addend,
2100 .type = @"type",
2101 .meta = .{
2102 .pcrel = rel.r_pcrel == 1,
2103 .has_subtractor = has_subtractor,
2104 .length = rel.r_length,
2105 .symbolnum = rel.r_symbolnum,
2106 },
2107 });
2108 }
2109 }
2110
2111 fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_arm64) !Relocation.Type {
2112 switch (rel_type) {
2113 .ARM64_RELOC_UNSIGNED => {
2114 if (rel.r_pcrel == 1) return error.Pcrel;
2115 if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
2116 return .unsigned;
2117 },
2118
2119 .ARM64_RELOC_SUBTRACTOR => {
2120 if (rel.r_pcrel == 1) return error.Pcrel;
2121 return .subtractor;
2122 },
2123
2124 .ARM64_RELOC_BRANCH26,
2125 .ARM64_RELOC_PAGE21,
2126 .ARM64_RELOC_GOT_LOAD_PAGE21,
2127 .ARM64_RELOC_TLVP_LOAD_PAGE21,
2128 .ARM64_RELOC_POINTER_TO_GOT,
2129 => {
2130 if (rel.r_pcrel == 0) return error.NonPcrel;
2131 if (rel.r_length != 2) return error.InvalidLength;
2132 if (rel.r_extern == 0) return error.NonExtern;
2133 return switch (rel_type) {
2134 .ARM64_RELOC_BRANCH26 => .branch,
2135 .ARM64_RELOC_PAGE21 => .page,
2136 .ARM64_RELOC_GOT_LOAD_PAGE21 => .got_load_page,
2137 .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp_page,
2138 .ARM64_RELOC_POINTER_TO_GOT => .got,
2139 else => unreachable,
2140 };
2141 },
2142
2143 .ARM64_RELOC_PAGEOFF12,
2144 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
2145 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
2146 => {
2147 if (rel.r_pcrel == 1) return error.Pcrel;
2148 if (rel.r_length != 2) return error.InvalidLength;
2149 if (rel.r_extern == 0) return error.NonExtern;
2150 return switch (rel_type) {
2151 .ARM64_RELOC_PAGEOFF12 => .pageoff,
2152 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got_load_pageoff,
2153 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp_pageoff,
2154 else => unreachable,
2155 };
2156 },
2157
2158 .ARM64_RELOC_ADDEND => unreachable, // We make it part of the addend field
2159 }
2160 }
2161};
11072162
1108const std = @import("std");
1109const build_options = @import("build_options");
11102163const assert = std.debug.assert;
1111const dwarf = std.dwarf;
11122164const eh_frame = @import("eh_frame.zig");
1113const fs = std.fs;
1114const io = std.io;
11152165const log = std.log.scoped(.link);
11162166const macho = std.macho;
11172167const math = std.math;
11182168const mem = std.mem;
1119const sort = std.sort;
11202169const trace = @import("../../tracy.zig").trace;
2170const std = @import("std");
11212171
11222172const Allocator = mem.Allocator;
11232173const Atom = @import("Atom.zig");
2174const Cie = eh_frame.Cie;
11242175const DwarfInfo = @import("DwarfInfo.zig");
2176const Fde = eh_frame.Fde;
2177const File = @import("file.zig").File;
11252178const LoadCommandIterator = macho.LoadCommandIterator;
11262179const MachO = @import("../MachO.zig");
1127const Platform = @import("load_commands.zig").Platform;
1128const SymbolWithLoc = MachO.SymbolWithLoc;
2180const Object = @This();
2181const Relocation = @import("Relocation.zig");
2182const Symbol = @import("Symbol.zig");
11292183const UnwindInfo = @import("UnwindInfo.zig");
1130const Alignment = Atom.Alignment;
src/link/MachO/Relocation.zig+99-223
......@@ -1,235 +1,69 @@
1//! Relocation used by the self-hosted backends to instruct the linker where and how to
2//! fixup the values when flushing the contents to file and/or memory.
3
4type: Type,
5target: SymbolWithLoc,
1tag: enum { @"extern", local },
62offset: u32,
3target: u32,
74addend: i64,
8pcrel: bool,
9length: u2,
10dirty: bool = true,
11
12pub const Type = enum {
13 // x86, x86_64
14 /// RIP-relative displacement to a GOT pointer
15 got,
16 /// RIP-relative displacement
17 signed,
18 /// RIP-relative displacement to a TLV thunk
19 tlv,
20
21 // aarch64
22 /// PC-relative distance to target page in GOT section
23 got_page,
24 /// Offset to a GOT pointer relative to the start of a page in GOT section
25 got_pageoff,
26 /// PC-relative distance to target page in a section
27 page,
28 /// Offset to a pointer relative to the start of a page in a section
29 pageoff,
30
31 // common
32 /// PC/RIP-relative displacement B/BL/CALL
33 branch,
34 /// Absolute pointer value
35 unsigned,
36 /// Relative offset to TLV initializer
37 tlv_initializer,
38};
39
40/// Returns true if and only if the reloc can be resolved.
41pub fn isResolvable(self: Relocation, macho_file: *MachO) bool {
42 _ = self.getTargetBaseAddress(macho_file) orelse return false;
43 return true;
5type: Type,
6meta: packed struct {
7 pcrel: bool,
8 has_subtractor: bool,
9 length: u2,
10 symbolnum: u24,
11},
12
13pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol {
14 assert(rel.tag == .@"extern");
15 return macho_file.getSymbol(rel.target);
4416}
4517
46pub fn isGotIndirection(self: Relocation) bool {
47 return switch (self.type) {
48 .got, .got_page, .got_pageoff => true,
49 else => false,
50 };
18pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom {
19 assert(rel.tag == .local);
20 return macho_file.getAtom(rel.target).?;
5121}
5222
53pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool {
54 return switch (self.type) {
55 .branch => macho_file.getSymbol(self.target).undf(),
56 else => false,
23pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
24 return switch (rel.tag) {
25 .local => rel.getTargetAtom(macho_file).value,
26 .@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file),
5727 };
5828}
5929
60pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 {
61 const target = macho_file.base.comp.root_mod.resolved_target.result;
62 if (self.isStubTrampoline(macho_file)) {
63 const index = macho_file.stub_table.lookup.get(self.target) orelse return null;
64 const header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
65 return header.addr +
66 index * @import("stubs.zig").stubSize(target.cpu.arch);
67 }
68 switch (self.type) {
69 .got, .got_page, .got_pageoff => {
70 const got_index = macho_file.got_table.lookup.get(self.target) orelse return null;
71 const header = macho_file.sections.items(.header)[macho_file.got_section_index.?];
72 return header.addr + got_index * @sizeOf(u64);
73 },
74 .tlv => {
75 const atom_index = macho_file.tlv_table.get(self.target) orelse return null;
76 const atom = macho_file.getAtom(atom_index);
77 return atom.getSymbol(macho_file).n_value;
78 },
79 else => {
80 const target_atom_index = macho_file.getAtomIndexForSymbol(self.target) orelse return null;
81 const target_atom = macho_file.getAtom(target_atom_index);
82 return target_atom.getSymbol(macho_file).n_value;
83 },
84 }
85}
86
87pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) void {
88 const target = macho_file.base.comp.root_mod.resolved_target.result;
89 const arch = target.cpu.arch;
90 const atom = macho_file.getAtom(atom_index);
91 const source_sym = atom.getSymbol(macho_file);
92 const source_addr = source_sym.n_value + self.offset;
93
94 const target_base_addr = self.getTargetBaseAddress(macho_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
95 const target_addr: i64 = switch (self.type) {
96 .tlv_initializer => blk: {
97 assert(self.addend == 0); // Addend here makes no sense.
98 const header = macho_file.sections.items(.header)[macho_file.thread_data_section_index.?];
99 break :blk @as(i64, @intCast(target_base_addr - header.addr));
100 },
101 else => @as(i64, @intCast(target_base_addr)) + self.addend,
30pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
31 return switch (rel.tag) {
32 .local => 0,
33 .@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file),
10234 };
103
104 relocs_log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
105 source_addr,
106 target_addr,
107 macho_file.getSymbolName(self.target),
108 @tagName(self.type),
109 });
110
111 switch (arch) {
112 .aarch64 => self.resolveAarch64(source_addr, target_addr, code),
113 .x86_64 => self.resolveX8664(source_addr, target_addr, code),
114 else => unreachable,
115 }
116}
117
118fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {
119 var buffer = code[self.offset..];
120 switch (self.type) {
121 .branch => {
122 const displacement = math.cast(
123 i28,
124 @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)),
125 ) orelse unreachable; // TODO codegen should never allow for jump larger than i28 displacement
126 var inst = aarch64.Instruction{
127 .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
128 aarch64.Instruction,
129 aarch64.Instruction.unconditional_branch_immediate,
130 ), buffer[0..4]),
131 };
132 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
133 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
134 },
135 .page, .got_page => {
136 const source_page = @as(i32, @intCast(source_addr >> 12));
137 const target_page = @as(i32, @intCast(target_addr >> 12));
138 const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page))));
139 var inst = aarch64.Instruction{
140 .pc_relative_address = mem.bytesToValue(meta.TagPayload(
141 aarch64.Instruction,
142 aarch64.Instruction.pc_relative_address,
143 ), buffer[0..4]),
144 };
145 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
146 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
147 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
148 },
149 .pageoff, .got_pageoff => {
150 const narrowed = @as(u12, @truncate(@as(u64, @intCast(target_addr))));
151 if (isArithmeticOp(buffer[0..4])) {
152 var inst = aarch64.Instruction{
153 .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
154 aarch64.Instruction,
155 aarch64.Instruction.add_subtract_immediate,
156 ), buffer[0..4]),
157 };
158 inst.add_subtract_immediate.imm12 = narrowed;
159 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
160 } else {
161 var inst = aarch64.Instruction{
162 .load_store_register = mem.bytesToValue(meta.TagPayload(
163 aarch64.Instruction,
164 aarch64.Instruction.load_store_register,
165 ), buffer[0..4]),
166 };
167 const offset: u12 = blk: {
168 if (inst.load_store_register.size == 0) {
169 if (inst.load_store_register.v == 1) {
170 // 128-bit SIMD is scaled by 16.
171 break :blk @divExact(narrowed, 16);
172 }
173 // Otherwise, 8-bit SIMD or ldrb.
174 break :blk narrowed;
175 } else {
176 const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable;
177 break :blk @divExact(narrowed, denom);
178 }
179 };
180 inst.load_store_register.offset = offset;
181 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
182 }
183 },
184 .tlv_initializer, .unsigned => switch (self.length) {
185 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little),
186 3 => mem.writeInt(u64, buffer[0..8], @as(u64, @bitCast(target_addr)), .little),
187 else => unreachable,
188 },
189 .got, .signed, .tlv => unreachable, // Invalid target architecture.
190 }
191}
192
193fn resolveX8664(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {
194 switch (self.type) {
195 .branch, .got, .tlv, .signed => {
196 const displacement = @as(i32, @intCast(@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 4));
197 mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @bitCast(displacement)), .little);
198 },
199 .tlv_initializer, .unsigned => {
200 switch (self.length) {
201 2 => {
202 mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little);
203 },
204 3 => {
205 mem.writeInt(u64, code[self.offset..][0..8], @as(u64, @bitCast(target_addr)), .little);
206 },
207 else => unreachable,
208 }
209 },
210 .got_page, .got_pageoff, .page, .pageoff => unreachable, // Invalid target architecture.
211 }
21235}
21336
214pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
215 const group_decode = @as(u5, @truncate(inst[3]));
216 return ((group_decode >> 2) == 4);
37pub fn getZigGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
38 return switch (rel.tag) {
39 .local => 0,
40 .@"extern" => rel.getTargetSymbol(macho_file).getZigGotAddress(macho_file),
41 };
21742}
21843
219pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 {
220 const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction));
221 return math.cast(i32, disp) orelse error.Overflow;
44pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 {
45 const addend: i64 = switch (rel.type) {
46 .signed => 0,
47 .signed1 => -1,
48 .signed2 => -2,
49 .signed4 => -4,
50 else => 0,
51 };
52 return switch (cpu_arch) {
53 .x86_64 => if (rel.meta.pcrel) addend - 4 else addend,
54 else => addend,
55 };
22256}
22357
224pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 {
225 const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
226 return math.cast(i28, disp) orelse error.Overflow;
58pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool {
59 _ = ctx;
60 return lhs.offset < rhs.offset;
22761}
22862
229pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 {
230 const source_page = @as(i32, @intCast(source_addr >> 12));
231 const target_page = @as(i32, @intCast(target_addr >> 12));
232 const pages = @as(i21, @intCast(target_page - source_page));
63pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 {
64 const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow;
65 const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow;
66 const pages = math.cast(i21, tpage - spage) orelse return error.Overflow;
23367 return pages;
23468}
23569
......@@ -242,8 +76,8 @@ pub const PageOffsetInstKind = enum {
24276 load_store_128,
24377};
24478
245pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
246 const narrowed = @as(u12, @truncate(target_addr));
79pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 {
80 const narrowed = @as(u12, @truncate(taddr));
24781 return switch (kind) {
24882 .arithmetic, .load_store_8 => narrowed,
24983 .load_store_16 => try math.divExact(u12, narrowed, 2),
......@@ -253,17 +87,59 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
25387 };
25488}
25589
256const Relocation = @This();
90pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
91 const group_decode = @as(u5, @truncate(inst[3]));
92 return ((group_decode >> 2) == 4);
93}
94
95pub const Type = enum {
96 // x86_64
97 /// RIP-relative displacement (X86_64_RELOC_SIGNED)
98 signed,
99 /// RIP-relative displacement (X86_64_RELOC_SIGNED_1)
100 signed1,
101 /// RIP-relative displacement (X86_64_RELOC_SIGNED_2)
102 signed2,
103 /// RIP-relative displacement (X86_64_RELOC_SIGNED_4)
104 signed4,
105 /// RIP-relative GOT load (X86_64_RELOC_GOT_LOAD)
106 got_load,
107 /// RIP-relative TLV load (X86_64_RELOC_TLV)
108 tlv,
109 /// Zig-specific __got_zig indirection
110 zig_got_load,
111
112 // arm64
113 /// PC-relative load (distance to page, ARM64_RELOC_PAGE21)
114 page,
115 /// Non-PC-relative offset to symbol (ARM64_RELOC_PAGEOFF12)
116 pageoff,
117 /// PC-relative GOT load (distance to page, ARM64_RELOC_GOT_LOAD_PAGE21)
118 got_load_page,
119 /// Non-PC-relative offset to GOT slot (ARM64_RELOC_GOT_LOAD_PAGEOFF12)
120 got_load_pageoff,
121 /// PC-relative TLV load (distance to page, ARM64_RELOC_TLVP_LOAD_PAGE21)
122 tlvp_page,
123 /// Non-PC-relative offset to TLV slot (ARM64_RELOC_TLVP_LOAD_PAGEOFF12)
124 tlvp_pageoff,
125
126 // common
127 /// PC-relative call/bl/b (X86_64_RELOC_BRANCH or ARM64_RELOC_BRANCH26)
128 branch,
129 /// PC-relative displacement to GOT pointer (X86_64_RELOC_GOT or ARM64_RELOC_POINTER_TO_GOT)
130 got,
131 /// Absolute subtractor value (X86_64_RELOC_SUBTRACTOR or ARM64_RELOC_SUBTRACTOR)
132 subtractor,
133 /// Absolute relocation (X86_64_RELOC_UNSIGNED or ARM64_RELOC_UNSIGNED)
134 unsigned,
135};
257136
258const std = @import("std");
259const aarch64 = @import("../../arch/aarch64/bits.zig");
260137const assert = std.debug.assert;
261const relocs_log = std.log.scoped(.link_relocs);
262138const macho = std.macho;
263139const math = std.math;
264const mem = std.mem;
265const meta = std.meta;
140const std = @import("std");
266141
267142const Atom = @import("Atom.zig");
268143const MachO = @import("../MachO.zig");
269const SymbolWithLoc = MachO.SymbolWithLoc;
144const Relocation = @This();
145const Symbol = @import("Symbol.zig");
src/link/MachO/Symbol.zig created+417
......@@ -0,0 +1,417 @@
1//! Represents a defined symbol.
2
3/// Allocated address value of this symbol.
4value: u64 = 0,
5
6/// Offset into the linker's intern table.
7name: u32 = 0,
8
9/// File where this symbol is defined.
10file: File.Index = 0,
11
12/// Atom containing this symbol if any.
13/// Index of 0 means there is no associated atom with this symbol.
14/// Use `getAtom` to get the pointer to the atom.
15atom: Atom.Index = 0,
16
17/// Assigned output section index for this atom.
18out_n_sect: u16 = 0,
19
20/// Index of the source nlist this symbol references.
21/// Use `getNlist` to pull the nlist from the relevant file.
22nlist_idx: Index = 0,
23
24/// Misc flags for the symbol packaged as packed struct for compression.
25flags: Flags = .{},
26
27visibility: Visibility = .local,
28
29extra: u32 = 0,
30
31pub fn isLocal(symbol: Symbol) bool {
32 return !(symbol.flags.import or symbol.flags.@"export");
33}
34
35pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
36 const file = symbol.getFile(macho_file) orelse return false;
37 return switch (file) {
38 .object => symbol.getNlist(macho_file).stab(),
39 else => false,
40 };
41}
42
43pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool {
44 const name = symbol.getName(macho_file);
45 return std.mem.indexOf(u8, name, "$tlv$init") != null;
46}
47
48pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
49 const file = symbol.getFile(macho_file).?;
50 const is_dylib_weak = switch (file) {
51 .dylib => |x| x.weak,
52 else => false,
53 };
54 return is_dylib_weak or symbol.flags.weak_ref;
55}
56
57pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
58 return macho_file.strings.getAssumeExists(symbol.name);
59}
60
61pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
62 return macho_file.getAtom(symbol.atom);
63}
64
65pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
66 return macho_file.getFile(symbol.file);
67}
68
69/// Asserts file is an object.
70pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
71 const file = symbol.getFile(macho_file).?;
72 return switch (file) {
73 .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
74 else => unreachable,
75 };
76}
77
78pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 {
79 const file = symbol.getFile(macho_file).?;
80 assert(file == .object);
81 return file.object.symtab.items(.size)[symbol.nlist_idx];
82}
83
84pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 {
85 assert(symbol.flags.import);
86 const file = symbol.getFile(macho_file) orelse return null;
87 return switch (file) {
88 .dylib => |x| x.ordinal,
89 else => null,
90 };
91}
92
93pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 {
94 const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32);
95 const in_archive = switch (file) {
96 .object => |x| !x.alive,
97 else => false,
98 };
99 return file.getSymbolRank(.{
100 .archive = in_archive,
101 .weak = symbol.flags.weak,
102 .tentative = symbol.flags.tentative,
103 });
104}
105
106pub fn getAddress(symbol: Symbol, opts: struct {
107 stubs: bool = true,
108}, macho_file: *MachO) u64 {
109 if (opts.stubs) {
110 if (symbol.flags.stubs) {
111 return symbol.getStubsAddress(macho_file);
112 } else if (symbol.flags.objc_stubs) {
113 return symbol.getObjcStubsAddress(macho_file);
114 }
115 }
116 if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value;
117 return symbol.value;
118}
119
120pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
121 if (!symbol.flags.has_got) return 0;
122 const extra = symbol.getExtra(macho_file).?;
123 return macho_file.got.getAddress(extra.got, macho_file);
124}
125
126pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
127 if (!symbol.flags.stubs) return 0;
128 const extra = symbol.getExtra(macho_file).?;
129 return macho_file.stubs.getAddress(extra.stubs, macho_file);
130}
131
132pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
133 if (!symbol.flags.objc_stubs) return 0;
134 const extra = symbol.getExtra(macho_file).?;
135 return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
136}
137
138pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
139 if (!symbol.flags.objc_stubs) return 0;
140 const extra = symbol.getExtra(macho_file).?;
141 const atom = macho_file.getAtom(extra.objc_selrefs).?;
142 assert(atom.flags.alive);
143 return atom.value;
144}
145
146pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
147 if (!symbol.flags.tlv_ptr) return 0;
148 const extra = symbol.getExtra(macho_file).?;
149 return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
150}
151
152const GetOrCreateZigGotEntryResult = struct {
153 found_existing: bool,
154 index: ZigGotSection.Index,
155};
156
157pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
158 assert(!macho_file.base.isRelocatable());
159 assert(symbol.flags.needs_zig_got);
160 if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got };
161 const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
162 return .{ .found_existing = false, .index = index };
163}
164
165pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
166 if (!symbol.flags.has_zig_got) return 0;
167 const extras = symbol.getExtra(macho_file).?;
168 return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
169}
170
171pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
172 if (!symbol.flags.output_symtab) return null;
173 assert(!symbol.isSymbolStab(macho_file));
174 const file = symbol.getFile(macho_file).?;
175 const symtab_ctx = switch (file) {
176 inline else => |x| x.output_symtab_ctx,
177 };
178 var idx = symbol.getExtra(macho_file).?.symtab;
179 if (symbol.isLocal()) {
180 idx += symtab_ctx.ilocal;
181 } else if (symbol.flags.@"export") {
182 idx += symtab_ctx.iexport;
183 } else {
184 assert(symbol.flags.import);
185 idx += symtab_ctx.iimport;
186 }
187 return idx;
188}
189
190const AddExtraOpts = struct {
191 got: ?u32 = null,
192 zig_got: ?u32 = null,
193 stubs: ?u32 = null,
194 objc_stubs: ?u32 = null,
195 objc_selrefs: ?u32 = null,
196 tlv_ptr: ?u32 = null,
197 symtab: ?u32 = null,
198};
199
200pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
201 if (symbol.getExtra(macho_file) == null) {
202 symbol.extra = try macho_file.addSymbolExtra(.{});
203 }
204 var extra = symbol.getExtra(macho_file).?;
205 inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
206 if (@field(opts, field.name)) |x| {
207 @field(extra, field.name) = x;
208 }
209 }
210 symbol.setExtra(extra, macho_file);
211}
212
213pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
214 return macho_file.getSymbolExtra(symbol.extra);
215}
216
217pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
218 macho_file.setSymbolExtra(symbol.extra, extra);
219}
220
221pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
222 if (symbol.isLocal()) {
223 out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
224 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
225 out.n_desc = 0;
226 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
227
228 switch (symbol.visibility) {
229 .hidden => out.n_type |= macho.N_PEXT,
230 else => {},
231 }
232 } else if (symbol.flags.@"export") {
233 assert(symbol.visibility == .global);
234 out.n_type = macho.N_EXT;
235 out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
236 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
237 out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
238 out.n_desc = 0;
239
240 if (symbol.flags.weak) {
241 out.n_desc |= macho.N_WEAK_DEF;
242 }
243 if (symbol.flags.dyn_ref) {
244 out.n_desc |= macho.REFERENCED_DYNAMICALLY;
245 }
246 } else {
247 assert(symbol.visibility == .global);
248 out.n_type = macho.N_EXT;
249 out.n_sect = 0;
250 out.n_value = 0;
251 out.n_desc = 0;
252
253 // TODO:
254 // const ord: u16 = if (macho_file.options.namespace == .flat)
255 // @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP))
256 // else if (symbol.getDylibOrdinal(macho_file)) |ord|
257 // ord
258 // else
259 // macho.BIND_SPECIAL_DYLIB_SELF;
260 const ord: u16 = if (symbol.getDylibOrdinal(macho_file)) |ord|
261 ord
262 else
263 macho.BIND_SPECIAL_DYLIB_SELF;
264 out.n_desc = macho.N_SYMBOL_RESOLVER * ord;
265
266 if (symbol.flags.weak) {
267 out.n_desc |= macho.N_WEAK_DEF;
268 }
269
270 if (symbol.weakRef(macho_file)) {
271 out.n_desc |= macho.N_WEAK_REF;
272 }
273 }
274}
275
276pub fn format(
277 symbol: Symbol,
278 comptime unused_fmt_string: []const u8,
279 options: std.fmt.FormatOptions,
280 writer: anytype,
281) !void {
282 _ = symbol;
283 _ = unused_fmt_string;
284 _ = options;
285 _ = writer;
286 @compileError("do not format symbols directly");
287}
288
289const FormatContext = struct {
290 symbol: Symbol,
291 macho_file: *MachO,
292};
293
294pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) {
295 return .{ .data = .{
296 .symbol = symbol,
297 .macho_file = macho_file,
298 } };
299}
300
301fn format2(
302 ctx: FormatContext,
303 comptime unused_fmt_string: []const u8,
304 options: std.fmt.FormatOptions,
305 writer: anytype,
306) !void {
307 _ = options;
308 _ = unused_fmt_string;
309 const symbol = ctx.symbol;
310 try writer.print("%{d} : {s} : @{x}", .{
311 symbol.nlist_idx,
312 symbol.getName(ctx.macho_file),
313 symbol.getAddress(.{}, ctx.macho_file),
314 });
315 if (symbol.getFile(ctx.macho_file)) |file| {
316 if (symbol.out_n_sect != 0) {
317 try writer.print(" : sect({d})", .{symbol.out_n_sect});
318 }
319 if (symbol.getAtom(ctx.macho_file)) |atom| {
320 try writer.print(" : atom({d})", .{atom.atom_index});
321 }
322 var buf: [2]u8 = .{'_'} ** 2;
323 if (symbol.flags.@"export") buf[0] = 'E';
324 if (symbol.flags.import) buf[1] = 'I';
325 try writer.print(" : {s}", .{&buf});
326 if (symbol.flags.weak) try writer.writeAll(" : weak");
327 if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab");
328 switch (file) {
329 .zig_object => |x| try writer.print(" : zig_object({d})", .{x.index}),
330 .internal => |x| try writer.print(" : internal({d})", .{x.index}),
331 .object => |x| try writer.print(" : object({d})", .{x.index}),
332 .dylib => |x| try writer.print(" : dylib({d})", .{x.index}),
333 }
334 } else try writer.writeAll(" : unresolved");
335}
336
337pub const Flags = packed struct {
338 /// Whether the symbol is imported at runtime.
339 import: bool = false,
340
341 /// Whether the symbol is exported at runtime.
342 @"export": bool = false,
343
344 /// Whether this symbol is weak.
345 weak: bool = false,
346
347 /// Whether this symbol is weakly referenced.
348 weak_ref: bool = false,
349
350 /// Whether this symbol is dynamically referenced.
351 dyn_ref: bool = false,
352
353 /// Whether this symbol was marked as N_NO_DEAD_STRIP.
354 no_dead_strip: bool = false,
355
356 /// Whether this symbol can be interposed at runtime.
357 interposable: bool = false,
358
359 /// Whether this symbol is absolute.
360 abs: bool = false,
361
362 /// Whether this symbol is a tentative definition.
363 tentative: bool = false,
364
365 /// Whether this symbol is a thread-local variable.
366 tlv: bool = false,
367
368 /// Whether the symbol makes into the output symtab or not.
369 output_symtab: bool = false,
370
371 /// Whether the symbol contains __got indirection.
372 needs_got: bool = false,
373 has_got: bool = false,
374
375 /// Whether the symbol contains __got_zig indirection.
376 needs_zig_got: bool = false,
377 has_zig_got: bool = false,
378
379 /// Whether the symbols contains __stubs indirection.
380 stubs: bool = false,
381
382 /// Whether the symbol has a TLV pointer.
383 tlv_ptr: bool = false,
384
385 /// Whether the symbol contains __objc_stubs indirection.
386 objc_stubs: bool = false,
387};
388
389pub const Visibility = enum {
390 global,
391 hidden,
392 local,
393};
394
395pub const Extra = struct {
396 got: u32 = 0,
397 zig_got: u32 = 0,
398 stubs: u32 = 0,
399 objc_stubs: u32 = 0,
400 objc_selrefs: u32 = 0,
401 tlv_ptr: u32 = 0,
402 symtab: u32 = 0,
403};
404
405pub const Index = u32;
406
407const assert = std.debug.assert;
408const macho = std.macho;
409const std = @import("std");
410
411const Atom = @import("Atom.zig");
412const File = @import("file.zig").File;
413const MachO = @import("../MachO.zig");
414const Nlist = Object.Nlist;
415const Object = @import("Object.zig");
416const Symbol = @This();
417const ZigGotSection = @import("synthetic.zig").ZigGotSection;
src/link/MachO/Trie.zig deleted-613
......@@ -1,613 +0,0 @@
1//! Represents export trie used in MachO executables and dynamic libraries.
2//! The purpose of an export trie is to encode as compactly as possible all
3//! export symbols for the loader `dyld`.
4//! The export trie encodes offset and other information using ULEB128
5//! encoding, and is part of the __LINKEDIT segment.
6//!
7//! Description from loader.h:
8//!
9//! The symbols exported by a dylib are encoded in a trie. This is a compact
10//! representation that factors out common prefixes. It also reduces LINKEDIT pages
11//! in RAM because it encodes all information (name, address, flags) in one small,
12//! contiguous range. The export area is a stream of nodes. The first node sequentially
13//! is the start node for the trie.
14//!
15//! Nodes for a symbol start with a uleb128 that is the length of the exported symbol
16//! information for the string so far. If there is no exported symbol, the node starts
17//! with a zero byte. If there is exported info, it follows the length.
18//!
19//! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded
20//! offset which is location of the content named by the symbol from the mach_header
21//! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags
22//! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string
23//! is zero length, then the symbol is re-export from the specified dylib with the same name.
24//! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two
25//! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers.
26//! The resolver is used by lazy pointers and must be called to get the actual address to use.
27//!
28//! After the optional exported symbol information is a byte of how many edges (0-255) that
29//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
30//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
31/// The root node of the trie.
32root: ?*Node = null,
33
34/// If you want to access nodes ordered in DFS fashion,
35/// you should call `finalize` first since the nodes
36/// in this container are not guaranteed to not be stale
37/// if more insertions took place after the last `finalize`
38/// call.
39ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
40
41/// The size of the trie in bytes.
42/// This value may be outdated if there were additional
43/// insertions performed after `finalize` was called.
44/// Call `finalize` before accessing this value to ensure
45/// it is up-to-date.
46size: u64 = 0,
47
48/// Number of nodes currently in the trie.
49node_count: usize = 0,
50
51trie_dirty: bool = true,
52
53/// Export symbol that is to be placed in the trie.
54pub const ExportSymbol = struct {
55 /// Name of the symbol.
56 name: []const u8,
57
58 /// Offset of this symbol's virtual memory address from the beginning
59 /// of the __TEXT segment.
60 vmaddr_offset: u64,
61
62 /// Export flags of this exported symbol.
63 export_flags: u64,
64};
65
66/// Insert a symbol into the trie, updating the prefixes in the process.
67/// This operation may change the layout of the trie by splicing edges in
68/// certain circumstances.
69pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
70 const node = try self.root.?.put(allocator, symbol.name);
71 node.terminal_info = .{
72 .vmaddr_offset = symbol.vmaddr_offset,
73 .export_flags = symbol.export_flags,
74 };
75 self.trie_dirty = true;
76}
77
78/// Finalizes this trie for writing to a byte stream.
79/// This step performs multiple passes through the trie ensuring
80/// there are no gaps after every `Node` is ULEB128 encoded.
81/// Call this method before trying to `write` the trie to a byte stream.
82pub fn finalize(self: *Trie, allocator: Allocator) !void {
83 if (!self.trie_dirty) return;
84
85 self.ordered_nodes.shrinkRetainingCapacity(0);
86 try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
87
88 var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
89 defer fifo.deinit();
90
91 try fifo.writeItem(self.root.?);
92
93 while (fifo.readItem()) |next| {
94 for (next.edges.items) |*edge| {
95 try fifo.writeItem(edge.to);
96 }
97 self.ordered_nodes.appendAssumeCapacity(next);
98 }
99
100 var more: bool = true;
101 while (more) {
102 self.size = 0;
103 more = false;
104 for (self.ordered_nodes.items) |node| {
105 const res = try node.finalize(self.size);
106 self.size += res.node_size;
107 if (res.updated) more = true;
108 }
109 }
110
111 self.trie_dirty = false;
112}
113
114const ReadError = error{
115 OutOfMemory,
116 EndOfStream,
117 Overflow,
118};
119
120/// Parse the trie from a byte stream.
121pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
122 return self.root.?.read(allocator, reader);
123}
124
125/// Write the trie to a byte stream.
126/// Panics if the trie was not finalized using `finalize` before calling this method.
127pub fn write(self: Trie, writer: anytype) !u64 {
128 assert(!self.trie_dirty);
129 var counting_writer = std.io.countingWriter(writer);
130 for (self.ordered_nodes.items) |node| {
131 try node.write(counting_writer.writer());
132 }
133 return counting_writer.bytes_written;
134}
135
136pub fn init(self: *Trie, allocator: Allocator) !void {
137 assert(self.root == null);
138 const root = try allocator.create(Node);
139 root.* = .{ .base = self };
140 self.root = root;
141 self.node_count += 1;
142}
143
144pub fn deinit(self: *Trie, allocator: Allocator) void {
145 if (self.root) |root| {
146 root.deinit(allocator);
147 allocator.destroy(root);
148 }
149 self.ordered_nodes.deinit(allocator);
150}
151
152test "Trie node count" {
153 const gpa = testing.allocator;
154 var trie: Trie = .{};
155 defer trie.deinit(gpa);
156 try trie.init(gpa);
157
158 try testing.expectEqual(trie.node_count, 0);
159 try testing.expect(trie.root == null);
160
161 try trie.put(gpa, .{
162 .name = "_main",
163 .vmaddr_offset = 0,
164 .export_flags = 0,
165 });
166 try testing.expectEqual(trie.node_count, 2);
167
168 // Inserting the same node shouldn't update the trie.
169 try trie.put(gpa, .{
170 .name = "_main",
171 .vmaddr_offset = 0,
172 .export_flags = 0,
173 });
174 try testing.expectEqual(trie.node_count, 2);
175
176 try trie.put(gpa, .{
177 .name = "__mh_execute_header",
178 .vmaddr_offset = 0x1000,
179 .export_flags = 0,
180 });
181 try testing.expectEqual(trie.node_count, 4);
182
183 // Inserting the same node shouldn't update the trie.
184 try trie.put(gpa, .{
185 .name = "__mh_execute_header",
186 .vmaddr_offset = 0x1000,
187 .export_flags = 0,
188 });
189 try testing.expectEqual(trie.node_count, 4);
190 try trie.put(gpa, .{
191 .name = "_main",
192 .vmaddr_offset = 0,
193 .export_flags = 0,
194 });
195 try testing.expectEqual(trie.node_count, 4);
196}
197
198test "Trie basic" {
199 const gpa = testing.allocator;
200 var trie: Trie = .{};
201 defer trie.deinit(gpa);
202 try trie.init(gpa);
203
204 // root --- _st ---> node
205 try trie.put(gpa, .{
206 .name = "_st",
207 .vmaddr_offset = 0,
208 .export_flags = 0,
209 });
210 try testing.expect(trie.root.?.edges.items.len == 1);
211 try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
212
213 {
214 // root --- _st ---> node --- art ---> node
215 try trie.put(gpa, .{
216 .name = "_start",
217 .vmaddr_offset = 0,
218 .export_flags = 0,
219 });
220 try testing.expect(trie.root.?.edges.items.len == 1);
221
222 const nextEdge = &trie.root.?.edges.items[0];
223 try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
224 try testing.expect(nextEdge.to.edges.items.len == 1);
225 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
226 }
227 {
228 // root --- _ ---> node --- st ---> node --- art ---> node
229 // |
230 // | --- main ---> node
231 try trie.put(gpa, .{
232 .name = "_main",
233 .vmaddr_offset = 0,
234 .export_flags = 0,
235 });
236 try testing.expect(trie.root.?.edges.items.len == 1);
237
238 const nextEdge = &trie.root.?.edges.items[0];
239 try testing.expect(mem.eql(u8, nextEdge.label, "_"));
240 try testing.expect(nextEdge.to.edges.items.len == 2);
241 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
242 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
243
244 const nextNextEdge = &nextEdge.to.edges.items[0];
245 try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
246 }
247}
248
249fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
250 assert(expected.len > 0);
251 if (mem.eql(u8, expected, given)) return;
252 const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
253 defer testing.allocator.free(expected_fmt);
254 const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
255 defer testing.allocator.free(given_fmt);
256 const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
257 const padding = try testing.allocator.alloc(u8, idx + 5);
258 defer testing.allocator.free(padding);
259 @memset(padding, ' ');
260 std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
261 return error.TestFailed;
262}
263
264test "write Trie to a byte stream" {
265 var gpa = testing.allocator;
266 var trie: Trie = .{};
267 defer trie.deinit(gpa);
268 try trie.init(gpa);
269
270 try trie.put(gpa, .{
271 .name = "__mh_execute_header",
272 .vmaddr_offset = 0,
273 .export_flags = 0,
274 });
275 try trie.put(gpa, .{
276 .name = "_main",
277 .vmaddr_offset = 0x1000,
278 .export_flags = 0,
279 });
280
281 try trie.finalize(gpa);
282 try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes.
283
284 const exp_buffer = [_]u8{
285 0x0, 0x1, // node root
286 0x5f, 0x0, 0x5, // edge '_'
287 0x0, 0x2, // non-terminal node
288 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
289 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
290 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
291 0x2, 0x0, 0x0, 0x0, // terminal node
292 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
293 };
294
295 const buffer = try gpa.alloc(u8, trie.size);
296 defer gpa.free(buffer);
297 var stream = std.io.fixedBufferStream(buffer);
298 {
299 _ = try trie.write(stream.writer());
300 try expectEqualHexStrings(&exp_buffer, buffer);
301 }
302 {
303 // Writing finalized trie again should yield the same result.
304 try stream.seekTo(0);
305 _ = try trie.write(stream.writer());
306 try expectEqualHexStrings(&exp_buffer, buffer);
307 }
308}
309
310test "parse Trie from byte stream" {
311 var gpa = testing.allocator;
312
313 const in_buffer = [_]u8{
314 0x0, 0x1, // node root
315 0x5f, 0x0, 0x5, // edge '_'
316 0x0, 0x2, // non-terminal node
317 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
318 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
319 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
320 0x2, 0x0, 0x0, 0x0, // terminal node
321 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
322 };
323
324 var in_stream = std.io.fixedBufferStream(&in_buffer);
325 var trie: Trie = .{};
326 defer trie.deinit(gpa);
327 try trie.init(gpa);
328 const nread = try trie.read(gpa, in_stream.reader());
329
330 try testing.expect(nread == in_buffer.len);
331
332 try trie.finalize(gpa);
333
334 const out_buffer = try gpa.alloc(u8, trie.size);
335 defer gpa.free(out_buffer);
336 var out_stream = std.io.fixedBufferStream(out_buffer);
337 _ = try trie.write(out_stream.writer());
338 try expectEqualHexStrings(&in_buffer, out_buffer);
339}
340
341test "ordering bug" {
342 var gpa = testing.allocator;
343 var trie: Trie = .{};
344 defer trie.deinit(gpa);
345 try trie.init(gpa);
346
347 try trie.put(gpa, .{
348 .name = "_asStr",
349 .vmaddr_offset = 0x558,
350 .export_flags = 0,
351 });
352 try trie.put(gpa, .{
353 .name = "_a",
354 .vmaddr_offset = 0x8008,
355 .export_flags = 0,
356 });
357 try trie.finalize(gpa);
358
359 const exp_buffer = [_]u8{
360 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
361 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
362 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
363 };
364
365 const buffer = try gpa.alloc(u8, trie.size);
366 defer gpa.free(buffer);
367 var stream = std.io.fixedBufferStream(buffer);
368 // Writing finalized trie again should yield the same result.
369 _ = try trie.write(stream.writer());
370 try expectEqualHexStrings(&exp_buffer, buffer);
371}
372
373pub const Node = struct {
374 base: *Trie,
375
376 /// Terminal info associated with this node.
377 /// If this node is not a terminal node, info is null.
378 terminal_info: ?struct {
379 /// Export flags associated with this exported symbol.
380 export_flags: u64,
381 /// VM address offset wrt to the section this symbol is defined against.
382 vmaddr_offset: u64,
383 } = null,
384
385 /// Offset of this node in the trie output byte stream.
386 trie_offset: ?u64 = null,
387
388 /// List of all edges originating from this node.
389 edges: std.ArrayListUnmanaged(Edge) = .{},
390
391 node_dirty: bool = true,
392
393 /// Edge connecting to nodes in the trie.
394 pub const Edge = struct {
395 from: *Node,
396 to: *Node,
397 label: []u8,
398
399 fn deinit(self: *Edge, allocator: Allocator) void {
400 self.to.deinit(allocator);
401 allocator.destroy(self.to);
402 allocator.free(self.label);
403 self.from = undefined;
404 self.to = undefined;
405 self.label = undefined;
406 }
407 };
408
409 fn deinit(self: *Node, allocator: Allocator) void {
410 for (self.edges.items) |*edge| {
411 edge.deinit(allocator);
412 }
413 self.edges.deinit(allocator);
414 }
415
416 /// Inserts a new node starting from `self`.
417 fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
418 // Check for match with edges from this node.
419 for (self.edges.items) |*edge| {
420 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
421 if (match == 0) continue;
422 if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
423
424 // Found a match, need to splice up nodes.
425 // From: A -> B
426 // To: A -> C -> B
427 const mid = try allocator.create(Node);
428 mid.* = .{ .base = self.base };
429 const to_label = try allocator.dupe(u8, edge.label[match..]);
430 allocator.free(edge.label);
431 const to_node = edge.to;
432 edge.to = mid;
433 edge.label = try allocator.dupe(u8, label[0..match]);
434 self.base.node_count += 1;
435
436 try mid.edges.append(allocator, .{
437 .from = mid,
438 .to = to_node,
439 .label = to_label,
440 });
441
442 return if (match == label.len) mid else mid.put(allocator, label[match..]);
443 }
444
445 // Add a new node.
446 const node = try allocator.create(Node);
447 node.* = .{ .base = self.base };
448 self.base.node_count += 1;
449
450 try self.edges.append(allocator, .{
451 .from = self,
452 .to = node,
453 .label = try allocator.dupe(u8, label),
454 });
455
456 return node;
457 }
458
459 /// Recursively parses the node from the input byte stream.
460 fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
461 self.node_dirty = true;
462 const trie_offset = try reader.context.getPos();
463 self.trie_offset = trie_offset;
464
465 var nread: usize = 0;
466
467 const node_size = try leb.readULEB128(u64, reader);
468 if (node_size > 0) {
469 const export_flags = try leb.readULEB128(u64, reader);
470 // TODO Parse special flags.
471 assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
472 export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
473
474 const vmaddr_offset = try leb.readULEB128(u64, reader);
475
476 self.terminal_info = .{
477 .export_flags = export_flags,
478 .vmaddr_offset = vmaddr_offset,
479 };
480 }
481
482 const nedges = try reader.readByte();
483 self.base.node_count += nedges;
484
485 nread += (try reader.context.getPos()) - trie_offset;
486
487 var i: usize = 0;
488 while (i < nedges) : (i += 1) {
489 const edge_start_pos = try reader.context.getPos();
490
491 const label = blk: {
492 var label_buf = std.ArrayList(u8).init(allocator);
493 while (true) {
494 const next = try reader.readByte();
495 if (next == @as(u8, 0))
496 break;
497 try label_buf.append(next);
498 }
499 break :blk try label_buf.toOwnedSlice();
500 };
501
502 const seek_to = try leb.readULEB128(u64, reader);
503 const return_pos = try reader.context.getPos();
504
505 nread += return_pos - edge_start_pos;
506 try reader.context.seekTo(seek_to);
507
508 const node = try allocator.create(Node);
509 node.* = .{ .base = self.base };
510
511 nread += try node.read(allocator, reader);
512 try self.edges.append(allocator, .{
513 .from = self,
514 .to = node,
515 .label = label,
516 });
517 try reader.context.seekTo(return_pos);
518 }
519
520 return nread;
521 }
522
523 /// Writes this node to a byte stream.
524 /// The children of this node *are* not written to the byte stream
525 /// recursively. To write all nodes to a byte stream in sequence,
526 /// iterate over `Trie.ordered_nodes` and call this method on each node.
527 /// This is one of the requirements of the MachO.
528 /// Panics if `finalize` was not called before calling this method.
529 fn write(self: Node, writer: anytype) !void {
530 assert(!self.node_dirty);
531 if (self.terminal_info) |info| {
532 // Terminal node info: encode export flags and vmaddr offset of this symbol.
533 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
534 var info_stream = std.io.fixedBufferStream(&info_buf);
535 // TODO Implement for special flags.
536 assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
537 info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
538 try leb.writeULEB128(info_stream.writer(), info.export_flags);
539 try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
540
541 // Encode the size of the terminal node info.
542 var size_buf: [@sizeOf(u64)]u8 = undefined;
543 var size_stream = std.io.fixedBufferStream(&size_buf);
544 try leb.writeULEB128(size_stream.writer(), info_stream.pos);
545
546 // Now, write them to the output stream.
547 try writer.writeAll(size_buf[0..size_stream.pos]);
548 try writer.writeAll(info_buf[0..info_stream.pos]);
549 } else {
550 // Non-terminal node is delimited by 0 byte.
551 try writer.writeByte(0);
552 }
553 // Write number of edges (max legal number of edges is 256).
554 try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
555
556 for (self.edges.items) |edge| {
557 // Write edge label and offset to next node in trie.
558 try writer.writeAll(edge.label);
559 try writer.writeByte(0);
560 try leb.writeULEB128(writer, edge.to.trie_offset.?);
561 }
562 }
563
564 const FinalizeResult = struct {
565 /// Current size of this node in bytes.
566 node_size: u64,
567
568 /// True if the trie offset of this node in the output byte stream
569 /// would need updating; false otherwise.
570 updated: bool,
571 };
572
573 /// Updates offset of this node in the output byte stream.
574 fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
575 var stream = std.io.countingWriter(std.io.null_writer);
576 const writer = stream.writer();
577
578 var node_size: u64 = 0;
579 if (self.terminal_info) |info| {
580 try leb.writeULEB128(writer, info.export_flags);
581 try leb.writeULEB128(writer, info.vmaddr_offset);
582 try leb.writeULEB128(writer, stream.bytes_written);
583 } else {
584 node_size += 1; // 0x0 for non-terminal nodes
585 }
586 node_size += 1; // 1 byte for edge count
587
588 for (self.edges.items) |edge| {
589 const next_node_offset = edge.to.trie_offset orelse 0;
590 node_size += edge.label.len + 1;
591 try leb.writeULEB128(writer, next_node_offset);
592 }
593
594 const trie_offset = self.trie_offset orelse 0;
595 const updated = offset_in_trie != trie_offset;
596 self.trie_offset = offset_in_trie;
597 self.node_dirty = false;
598 node_size += stream.bytes_written;
599
600 return FinalizeResult{ .node_size = node_size, .updated = updated };
601 }
602};
603
604const Trie = @This();
605
606const std = @import("std");
607const mem = std.mem;
608const leb = std.leb;
609const log = std.log.scoped(.link);
610const macho = std.macho;
611const testing = std.testing;
612const assert = std.debug.assert;
613const Allocator = mem.Allocator;
src/link/MachO/UnwindInfo.zig+450-578
......@@ -1,376 +1,132 @@
1gpa: Allocator,
2
31/// List of all unwind records gathered from all objects and sorted
4/// by source function address.
5records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{},
6records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{},
2/// by allocated relative function address within the section.
3records: std.ArrayListUnmanaged(Record.Index) = .{},
74
85/// List of all personalities referenced by either unwind info entries
96/// or __eh_frame entries.
10personalities: [max_personalities]SymbolWithLoc = undefined,
7personalities: [max_personalities]Symbol.Index = undefined,
118personalities_count: u2 = 0,
129
1310/// List of common encodings sorted in descending order with the most common first.
14common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined,
11common_encodings: [max_common_encodings]Encoding = undefined,
1512common_encodings_count: u7 = 0,
1613
1714/// List of record indexes containing an LSDA pointer.
18lsdas: std.ArrayListUnmanaged(RecordIndex) = .{},
19lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{},
15lsdas: std.ArrayListUnmanaged(u32) = .{},
16lsdas_lookup: std.ArrayListUnmanaged(u32) = .{},
2017
2118/// List of second level pages.
2219pages: std.ArrayListUnmanaged(Page) = .{},
2320
24/// Upper bound (exclusive) of all the record ranges
25end_boundary: u64 = 0,
26
27const RecordIndex = u32;
28
29const max_personalities = 3;
30const max_common_encodings = 127;
31const max_compact_encodings = 256;
32
33const second_level_page_bytes = 0x1000;
34const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
35
36const max_regular_second_level_entries =
37 (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
38 @sizeOf(macho.unwind_info_regular_second_level_entry);
39
40const max_compressed_second_level_entries =
41 (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
42 @sizeOf(u32);
43
44const compressed_entry_func_offset_mask = ~@as(u24, 0);
45
46const Page = struct {
47 kind: enum { regular, compressed },
48 start: RecordIndex,
49 count: u16,
50 page_encodings: [max_compact_encodings]RecordIndex = undefined,
51 page_encodings_count: u9 = 0,
52
53 fn appendPageEncoding(page: *Page, record_id: RecordIndex) void {
54 assert(page.page_encodings_count <= max_compact_encodings);
55 page.page_encodings[page.page_encodings_count] = record_id;
56 page.page_encodings_count += 1;
57 }
58
59 fn getPageEncoding(
60 page: *const Page,
61 info: *const UnwindInfo,
62 enc: macho.compact_unwind_encoding_t,
63 ) ?u8 {
64 comptime var index: u9 = 0;
65 inline while (index < max_compact_encodings) : (index += 1) {
66 if (index >= page.page_encodings_count) return null;
67 const record_id = page.page_encodings[index];
68 const record = info.records.items[record_id];
69 if (record.compactUnwindEncoding == enc) {
70 return @as(u8, @intCast(index));
71 }
72 }
73 return null;
74 }
75
76 fn format(
77 page: *const Page,
78 comptime unused_format_string: []const u8,
79 options: std.fmt.FormatOptions,
80 writer: anytype,
81 ) !void {
82 _ = page;
83 _ = unused_format_string;
84 _ = options;
85 _ = writer;
86 @compileError("do not format Page directly; use page.fmtDebug()");
87 }
88
89 const DumpCtx = struct {
90 page: *const Page,
91 info: *const UnwindInfo,
92 };
93
94 fn dump(
95 ctx: DumpCtx,
96 comptime unused_format_string: []const u8,
97 options: std.fmt.FormatOptions,
98 writer: anytype,
99 ) @TypeOf(writer).Error!void {
100 _ = options;
101 comptime assert(unused_format_string.len == 0);
102 try writer.writeAll("Page:\n");
103 try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
104 try writer.print(" entries: {d} - {d}\n", .{
105 ctx.page.start,
106 ctx.page.start + ctx.page.count,
107 });
108 try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
109 for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |record_id, i| {
110 const record = ctx.info.records.items[record_id];
111 const enc = record.compactUnwindEncoding;
112 try writer.print(" {d}: 0x{x:0>8}\n", .{ ctx.info.common_encodings_count + i, enc });
113 }
114 }
115
116 fn fmtDebug(page: *const Page, info: *const UnwindInfo) std.fmt.Formatter(dump) {
117 return .{ .data = .{
118 .page = page,
119 .info = info,
120 } };
121 }
122
123 fn write(page: *const Page, info: *const UnwindInfo, writer: anytype) !void {
124 switch (page.kind) {
125 .regular => {
126 try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
127 .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
128 .entryCount = page.count,
129 });
130
131 for (info.records.items[page.start..][0..page.count]) |record| {
132 try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
133 .functionOffset = @as(u32, @intCast(record.rangeStart)),
134 .encoding = record.compactUnwindEncoding,
135 });
136 }
137 },
138 .compressed => {
139 const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
140 @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
141 try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
142 .entryPageOffset = entry_offset,
143 .entryCount = page.count,
144 .encodingsPageOffset = @sizeOf(
145 macho.unwind_info_compressed_second_level_page_header,
146 ),
147 .encodingsCount = page.page_encodings_count,
148 });
149
150 for (page.page_encodings[0..page.page_encodings_count]) |record_id| {
151 const enc = info.records.items[record_id].compactUnwindEncoding;
152 try writer.writeInt(u32, enc, .little);
153 }
154
155 assert(page.count > 0);
156 const first_entry = info.records.items[page.start];
157 for (info.records.items[page.start..][0..page.count]) |record| {
158 const enc_index = blk: {
159 if (info.getCommonEncoding(record.compactUnwindEncoding)) |id| {
160 break :blk id;
161 }
162 const ncommon = info.common_encodings_count;
163 break :blk ncommon + page.getPageEncoding(info, record.compactUnwindEncoding).?;
164 };
165 const compressed = macho.UnwindInfoCompressedEntry{
166 .funcOffset = @as(u24, @intCast(record.rangeStart - first_entry.rangeStart)),
167 .encodingIndex = @as(u8, @intCast(enc_index)),
168 };
169 try writer.writeStruct(compressed);
170 }
171 },
172 }
173 }
174};
21pub fn deinit(info: *UnwindInfo, allocator: Allocator) void {
22 info.records.deinit(allocator);
23 info.pages.deinit(allocator);
24 info.lsdas.deinit(allocator);
25 info.lsdas_lookup.deinit(allocator);
26}
17527
176pub fn deinit(info: *UnwindInfo) void {
177 info.records.deinit(info.gpa);
178 info.records_lookup.deinit(info.gpa);
179 info.pages.deinit(info.gpa);
180 info.lsdas.deinit(info.gpa);
181 info.lsdas_lookup.deinit(info.gpa);
28fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool {
29 const cpu_arch = macho_file.getTarget().cpu.arch;
30 const lhs = macho_file.getUnwindRecord(lhs_index);
31 const rhs = macho_file.getUnwindRecord(rhs_index);
32 if (cpu_arch == .x86_64) {
33 if (lhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND) or
34 rhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND)) return false;
35 }
36 const lhs_per = lhs.personality orelse 0;
37 const rhs_per = rhs.personality orelse 0;
38 return lhs.enc.eql(rhs.enc) and
39 lhs_per == rhs_per and
40 lhs.fde == rhs.fde and
41 lhs.getLsdaAtom(macho_file) == null and rhs.getLsdaAtom(macho_file) == null;
18242}
18343
184pub fn scanRelocs(macho_file: *MachO) !void {
185 if (macho_file.unwind_info_section_index == null) return;
186
187 const target = macho_file.base.comp.root_mod.resolved_target.result;
188 const cpu_arch = target.cpu.arch;
189 for (macho_file.objects.items, 0..) |*object, object_id| {
190 const unwind_records = object.getUnwindRecords();
191 for (object.exec_atoms.items) |atom_index| {
192 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
193 while (inner_syms_it.next()) |sym| {
194 const record_id = object.unwind_records_lookup.get(sym) orelse continue;
195 if (object.unwind_relocs_lookup[record_id].dead) continue;
196 const record = unwind_records[record_id];
197 if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
198 if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
199 // Personality function; add GOT pointer.
200 const reloc_target = Atom.parseRelocTarget(macho_file, .{
201 .object_id = @as(u32, @intCast(object_id)),
202 .rel = rel,
203 .code = mem.asBytes(&record),
204 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
205 });
206 try macho_file.addGotEntry(reloc_target);
207 }
208 }
44pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
45 const gpa = macho_file.base.comp.gpa;
46
47 log.debug("generating unwind info", .{});
48
49 // Collect all unwind records
50 for (macho_file.sections.items(.atoms)) |atoms| {
51 for (atoms.items) |atom_index| {
52 const atom = macho_file.getAtom(atom_index) orelse continue;
53 if (!atom.flags.alive) continue;
54 const recs = atom.getUnwindRecords(macho_file);
55 try info.records.ensureUnusedCapacity(gpa, recs.len);
56 for (recs) |rec| {
57 if (!macho_file.getUnwindRecord(rec).alive) continue;
58 info.records.appendAssumeCapacity(rec);
20959 }
21060 }
21161 }
212}
213
214pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
215 if (macho_file.unwind_info_section_index == null) return;
216
217 const target = macho_file.base.comp.root_mod.resolved_target.result;
218 const cpu_arch = target.cpu.arch;
219
220 var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
221 defer records.deinit();
222
223 var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa);
224 defer sym_indexes.deinit();
225
226 // TODO handle dead stripping
227 for (macho_file.objects.items, 0..) |*object, object_id| {
228 log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id });
229 const unwind_records = object.getUnwindRecords();
230
231 // Contents of unwind records does not have to cover all symbol in executable section
232 // so we need insert them ourselves.
233 try records.ensureUnusedCapacity(object.exec_atoms.items.len);
234 try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
235
236 for (object.exec_atoms.items) |atom_index| {
237 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
238 var prev_symbol: ?SymbolWithLoc = null;
239 while (inner_syms_it.next()) |symbol| {
240 var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
241 if (object.unwind_relocs_lookup[record_id].dead) continue;
242 var record = unwind_records[record_id];
243
244 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
245 info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
246 } else {
247 if (getPersonalityFunctionReloc(
248 macho_file,
249 @as(u32, @intCast(object_id)),
250 record_id,
251 )) |rel| {
252 const reloc_target = Atom.parseRelocTarget(macho_file, .{
253 .object_id = @as(u32, @intCast(object_id)),
254 .rel = rel,
255 .code = mem.asBytes(&record),
256 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
257 });
258 const personality_index = info.getPersonalityFunction(reloc_target) orelse inner: {
259 const personality_index = info.personalities_count;
260 info.personalities[personality_index] = reloc_target;
261 info.personalities_count += 1;
262 break :inner personality_index;
263 };
264
265 record.personalityFunction = personality_index + 1;
266 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
267 }
268
269 if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
270 const reloc_target = Atom.parseRelocTarget(macho_file, .{
271 .object_id = @as(u32, @intCast(object_id)),
272 .rel = rel,
273 .code = mem.asBytes(&record),
274 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
275 });
276 record.lsda = @as(u64, @bitCast(reloc_target));
277 }
278 }
279 break :blk record;
280 } else blk: {
281 const sym = macho_file.getSymbol(symbol);
282 if (sym.n_desc == MachO.N_DEAD) continue;
283 if (prev_symbol) |prev_sym| {
284 const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
285 const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
286 if (prev_addr == curr_addr) continue;
287 }
288
289 if (!object.hasUnwindRecords()) {
290 if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
291 if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
292 var record = nullRecord();
293 info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
294 switch (cpu_arch) {
295 .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
296 .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
297 else => unreachable,
298 }
299 break :blk record;
300 }
301 }
302
303 break :blk nullRecord();
304 };
30562
306 const atom = macho_file.getAtom(atom_index);
307 const sym = macho_file.getSymbol(symbol);
308 assert(sym.n_desc != MachO.N_DEAD);
309 const size = if (inner_syms_it.next()) |next_sym| blk: {
310 // All this trouble to account for symbol aliases.
311 // TODO I think that remodelling the linker so that a Symbol references an Atom
312 // is the way to go, kinda like we do for ELF. We might also want to perhaps tag
313 // symbol aliases somehow so that they are excluded from everything except relocation
314 // resolution.
315 defer inner_syms_it.pos -= 1;
316 const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
317 const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
318 if (next_addr > curr_addr) break :blk next_addr - curr_addr;
319 break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
320 } else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
321 record.rangeStart = sym.n_value;
322 record.rangeLength = @as(u32, @intCast(size));
323
324 try records.append(record);
325 try sym_indexes.append(symbol);
326
327 prev_symbol = symbol;
63 // Encode records
64 for (info.records.items) |index| {
65 const rec = macho_file.getUnwindRecord(index);
66 if (rec.getFde(macho_file)) |fde| {
67 rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset));
68 if (fde.getLsdaAtom(macho_file)) |lsda| {
69 rec.lsda = lsda.atom_index;
70 rec.lsda_offset = fde.lsda_offset;
71 rec.enc.setHasLsda(true);
32872 }
73 const cie = fde.getCie(macho_file);
74 if (cie.getPersonality(macho_file)) |_| {
75 const personality_index = try info.getOrPutPersonalityFunction(cie.personality.?.index); // TODO handle error
76 rec.enc.setPersonalityIndex(personality_index + 1);
77 }
78 } else if (rec.getPersonality(macho_file)) |_| {
79 const personality_index = try info.getOrPutPersonalityFunction(rec.personality.?); // TODO handle error
80 rec.enc.setPersonalityIndex(personality_index + 1);
32981 }
33082 }
33183
332 // Record the ending boundary before folding.
333 assert(records.items.len > 0);
334 info.end_boundary = blk: {
335 const last_record = records.items[records.items.len - 1];
336 break :blk last_record.rangeStart + last_record.rangeLength;
337 };
84 // Sort by assigned relative address within each output section
85 const sortFn = struct {
86 fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool {
87 const lhs = ctx.getUnwindRecord(lhs_index);
88 const rhs = ctx.getUnwindRecord(rhs_index);
89 const lhsa = lhs.getAtom(ctx);
90 const rhsa = rhs.getAtom(ctx);
91 if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx);
92 return lhsa.out_n_sect < rhsa.out_n_sect;
93 }
94 }.sortFn;
95 mem.sort(Record.Index, info.records.items, macho_file, sortFn);
33896
339 // Fold records
340 try info.records.ensureTotalCapacity(info.gpa, records.items.len);
341 try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len)));
342
343 var maybe_prev: ?macho.compact_unwind_entry = null;
344 for (records.items, 0..) |record, i| {
345 const record_id = blk: {
346 if (maybe_prev) |prev| {
347 const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
348 if (is_dwarf or
349 (prev.compactUnwindEncoding != record.compactUnwindEncoding) or
350 (prev.personalityFunction != record.personalityFunction) or
351 record.lsda > 0)
352 {
353 const record_id = @as(RecordIndex, @intCast(info.records.items.len));
354 info.records.appendAssumeCapacity(record);
355 maybe_prev = record;
356 break :blk record_id;
357 } else {
358 break :blk @as(RecordIndex, @intCast(info.records.items.len - 1));
359 }
97 // Fold the records
98 // Any adjacent two records that share encoding can be folded into one.
99 {
100 var i: usize = 0;
101 var j: usize = 1;
102 while (j < info.records.items.len) : (j += 1) {
103 if (canFold(macho_file, info.records.items[i], info.records.items[j])) {
104 const rec = macho_file.getUnwindRecord(info.records.items[i]);
105 rec.length += macho_file.getUnwindRecord(info.records.items[j]).length + 1;
360106 } else {
361 const record_id = @as(RecordIndex, @intCast(info.records.items.len));
362 info.records.appendAssumeCapacity(record);
363 maybe_prev = record;
364 break :blk record_id;
107 i += 1;
108 info.records.items[i] = info.records.items[j];
365109 }
366 };
367 info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id);
110 }
111 info.records.shrinkAndFree(gpa, i + 1);
112 }
113
114 for (info.records.items) |rec_index| {
115 const rec = macho_file.getUnwindRecord(rec_index);
116 const atom = rec.getAtom(macho_file);
117 log.debug("@{x}-{x} : {s} : rec({d}) : {}", .{
118 rec.getAtomAddress(macho_file),
119 rec.getAtomAddress(macho_file) + rec.length,
120 atom.getName(macho_file),
121 rec_index,
122 rec.enc,
123 });
368124 }
369125
370126 // Calculate common encodings
371127 {
372128 const CommonEncWithCount = struct {
373 enc: macho.compact_unwind_encoding_t,
129 enc: Encoding,
374130 count: u32,
375131
376132 fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool {
......@@ -380,39 +136,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
380136 };
381137
382138 const Context = struct {
383 pub fn hash(ctx: @This(), key: macho.compact_unwind_encoding_t) u32 {
139 pub fn hash(ctx: @This(), key: Encoding) u32 {
384140 _ = ctx;
385 return key;
141 return key.enc;
386142 }
387143
388144 pub fn eql(
389145 ctx: @This(),
390 key1: macho.compact_unwind_encoding_t,
391 key2: macho.compact_unwind_encoding_t,
146 key1: Encoding,
147 key2: Encoding,
392148 b_index: usize,
393149 ) bool {
394150 _ = ctx;
395151 _ = b_index;
396 return key1 == key2;
152 return key1.eql(key2);
397153 }
398154 };
399155
400156 var common_encodings_counts = std.ArrayHashMap(
401 macho.compact_unwind_encoding_t,
157 Encoding,
402158 CommonEncWithCount,
403159 Context,
404160 false,
405 ).init(info.gpa);
161 ).init(gpa);
406162 defer common_encodings_counts.deinit();
407163
408 for (info.records.items) |record| {
409 assert(!isNull(record));
410 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue;
411 const enc = record.compactUnwindEncoding;
412 const gop = try common_encodings_counts.getOrPut(enc);
164 for (info.records.items) |rec_index| {
165 const rec = macho_file.getUnwindRecord(rec_index);
166 if (rec.enc.isDwarf(macho_file)) continue;
167 const gop = try common_encodings_counts.getOrPut(rec.enc);
413168 if (!gop.found_existing) {
414169 gop.value_ptr.* = .{
415 .enc = enc,
170 .enc = rec.enc,
416171 .count = 0,
417172 };
418173 }
......@@ -427,7 +182,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
427182 if (i >= max_common_encodings) break;
428183 if (slice[i].count < 2) continue;
429184 info.appendCommonEncoding(slice[i].enc);
430 log.debug("adding common encoding: {d} => 0x{x:0>8}", .{ i, slice[i].enc });
185 log.debug("adding common encoding: {d} => {}", .{ i, slice[i].enc });
431186 }
432187 }
433188
......@@ -435,8 +190,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
435190 {
436191 var i: u32 = 0;
437192 while (i < info.records.items.len) {
438 const range_start_max: u64 =
439 info.records.items[i].rangeStart + compressed_entry_func_offset_mask;
193 const rec = macho_file.getUnwindRecord(info.records.items[i]);
194 const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask;
440195 var encoding_count: u9 = info.common_encodings_count;
441196 var space_left: u32 = second_level_page_words -
442197 @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32);
......@@ -447,19 +202,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
447202 };
448203
449204 while (space_left >= 1 and i < info.records.items.len) {
450 const record = info.records.items[i];
451 const enc = record.compactUnwindEncoding;
452 const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
205 const next = macho_file.getUnwindRecord(info.records.items[i]);
206 const is_dwarf = next.enc.isDwarf(macho_file);
453207
454 if (record.rangeStart >= range_start_max) {
208 if (next.getAtomAddress(macho_file) >= range_start_max) {
455209 break;
456 } else if (info.getCommonEncoding(enc) != null or
457 page.getPageEncoding(info, enc) != null and !is_dwarf)
210 } else if (info.getCommonEncoding(next.enc) != null or
211 page.getPageEncoding(next.enc) != null and !is_dwarf)
458212 {
459213 i += 1;
460214 space_left -= 1;
461215 } else if (space_left >= 2 and encoding_count < max_compact_encodings) {
462 page.appendPageEncoding(i);
216 page.appendPageEncoding(next.enc);
463217 i += 1;
464218 space_left -= 2;
465219 encoding_count += 1;
......@@ -481,63 +235,26 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
481235 page.kind = .compressed;
482236 }
483237
484 log.debug("{}", .{page.fmtDebug(info)});
238 log.debug("{}", .{page.fmt(info.*)});
485239
486 try info.pages.append(info.gpa, page);
240 try info.pages.append(gpa, page);
487241 }
488242 }
489243
490 // Save indices of records requiring LSDA relocation
491 try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(info.records.items.len)));
492 for (info.records.items, 0..) |rec, i| {
493 info.lsdas_lookup.putAssumeCapacityNoClobber(@as(RecordIndex, @intCast(i)), @as(u32, @intCast(info.lsdas.items.len)));
494 if (rec.lsda == 0) continue;
495 try info.lsdas.append(info.gpa, @as(RecordIndex, @intCast(i)));
496 }
497}
498
499fn collectPersonalityFromDwarf(
500 info: *UnwindInfo,
501 macho_file: *MachO,
502 object_id: u32,
503 sym_loc: SymbolWithLoc,
504 record: *macho.compact_unwind_entry,
505) void {
506 const object = &macho_file.objects.items[object_id];
507 var it = object.getEhFrameRecordsIterator();
508 const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
509 it.seekTo(fde_offset);
510 const fde = (it.next() catch return).?; // We don't care about the error since we already handled it
511 const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
512 const cie_offset = fde_offset + 4 - cie_ptr;
513 it.seekTo(cie_offset);
514 const cie = (it.next() catch return).?; // We don't care about the error since we already handled it
515
516 if (cie.getPersonalityPointerReloc(
517 macho_file,
518 @as(u32, @intCast(object_id)),
519 cie_offset,
520 )) |target| {
521 const personality_index = info.getPersonalityFunction(target) orelse inner: {
522 const personality_index = info.personalities_count;
523 info.personalities[personality_index] = target;
524 info.personalities_count += 1;
525 break :inner personality_index;
526 };
527
528 record.personalityFunction = personality_index + 1;
529 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
244 // Save records having an LSDA pointer
245 log.debug("LSDA pointers:", .{});
246 try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len);
247 for (info.records.items, 0..) |index, i| {
248 const rec = macho_file.getUnwindRecord(index);
249 info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len));
250 if (rec.getLsdaAtom(macho_file)) |lsda| {
251 log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index });
252 try info.lsdas.append(gpa, @intCast(i));
253 }
530254 }
531255}
532256
533pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void {
534 const sect_id = macho_file.unwind_info_section_index orelse return;
535 const sect = &macho_file.sections.items(.header)[sect_id];
536 sect.@"align" = 2;
537 sect.size = info.calcRequiredSize();
538}
539
540fn calcRequiredSize(info: UnwindInfo) usize {
257pub fn calcSize(info: UnwindInfo) usize {
541258 var total_size: usize = 0;
542259 total_size += @sizeOf(macho.unwind_info_section_header);
543260 total_size +=
......@@ -549,59 +266,12 @@ fn calcRequiredSize(info: UnwindInfo) usize {
549266 return total_size;
550267}
551268
552pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
553 const sect_id = macho_file.unwind_info_section_index orelse return;
554 const sect = &macho_file.sections.items(.header)[sect_id];
555 const seg_id = macho_file.sections.items(.segment_index)[sect_id];
556 const seg = macho_file.segments.items[seg_id];
557
558 const text_sect_id = macho_file.text_section_index.?;
559 const text_sect = macho_file.sections.items(.header)[text_sect_id];
560
561 var personalities: [max_personalities]u32 = undefined;
562 const target = macho_file.base.comp.root_mod.resolved_target.result;
563 const cpu_arch = target.cpu.arch;
564
565 log.debug("Personalities:", .{});
566 for (info.personalities[0..info.personalities_count], 0..) |reloc_target, i| {
567 const addr = macho_file.getGotEntryAddress(reloc_target).?;
568 personalities[i] = @as(u32, @intCast(addr - seg.vmaddr));
569 log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], macho_file.getSymbolName(reloc_target) });
570 }
571
572 for (info.records.items) |*rec| {
573 // Finalize missing address values
574 rec.rangeStart += text_sect.addr - seg.vmaddr;
575 if (rec.personalityFunction > 0) {
576 const index = math.cast(usize, rec.personalityFunction - 1) orelse return error.Overflow;
577 rec.personalityFunction = personalities[index];
578 }
579
580 if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) {
581 const lsda_target = @as(SymbolWithLoc, @bitCast(rec.lsda));
582 if (lsda_target.getFile()) |_| {
583 const sym = macho_file.getSymbol(lsda_target);
584 rec.lsda = sym.n_value - seg.vmaddr;
585 }
586 }
587 }
588
589 for (info.records.items, 0..) |record, i| {
590 log.debug("Unwind record at offset 0x{x}", .{i * @sizeOf(macho.compact_unwind_entry)});
591 log.debug(" start: 0x{x}", .{record.rangeStart});
592 log.debug(" length: 0x{x}", .{record.rangeLength});
593 log.debug(" compact encoding: 0x{x:0>8}", .{record.compactUnwindEncoding});
594 log.debug(" personality: 0x{x}", .{record.personalityFunction});
595 log.debug(" LSDA: 0x{x}", .{record.lsda});
596 }
269pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
270 const seg = macho_file.getTextSegment();
271 const header = macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?];
597272
598 var buffer = std.ArrayList(u8).init(info.gpa);
599 defer buffer.deinit();
600
601 const size = info.calcRequiredSize();
602 try buffer.ensureTotalCapacityPrecise(size);
603
604 var cwriter = std.io.countingWriter(buffer.writer());
273 var stream = std.io.fixedBufferStream(buffer);
274 var cwriter = std.io.countingWriter(stream.writer());
605275 const writer = cwriter.writer();
606276
607277 const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header);
......@@ -621,203 +291,403 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
621291 });
622292
623293 try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count]));
624 try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count]));
625294
626 const pages_base_offset = @as(u32, @intCast(size - (info.pages.items.len * second_level_page_bytes)));
295 for (info.personalities[0..info.personalities_count]) |sym_index| {
296 const sym = macho_file.getSymbol(sym_index);
297 try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little);
298 }
299
300 const pages_base_offset = @as(u32, @intCast(header.size - (info.pages.items.len * second_level_page_bytes)));
627301 const lsda_base_offset = @as(u32, @intCast(pages_base_offset -
628302 (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));
629303 for (info.pages.items, 0..) |page, i| {
630304 assert(page.count > 0);
631 const first_entry = info.records.items[page.start];
305 const rec = macho_file.getUnwindRecord(info.records.items[page.start]);
632306 try writer.writeStruct(macho.unwind_info_section_header_index_entry{
633 .functionOffset = @as(u32, @intCast(first_entry.rangeStart)),
307 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
634308 .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)),
635309 .lsdaIndexArraySectionOffset = lsda_base_offset +
636 info.lsdas_lookup.get(page.start).? * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
310 info.lsdas_lookup.items[page.start] * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
637311 });
638312 }
639313
640 // Relocate end boundary address
641 const end_boundary = @as(u32, @intCast(info.end_boundary + text_sect.addr - seg.vmaddr));
314 const last_rec = macho_file.getUnwindRecord(info.records.items[info.records.items.len - 1]);
315 const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr));
642316 try writer.writeStruct(macho.unwind_info_section_header_index_entry{
643 .functionOffset = end_boundary,
317 .functionOffset = sentinel_address,
644318 .secondLevelPagesSectionOffset = 0,
645319 .lsdaIndexArraySectionOffset = lsda_base_offset +
646320 @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
647321 });
648322
649 for (info.lsdas.items) |record_id| {
650 const record = info.records.items[record_id];
323 for (info.lsdas.items) |index| {
324 const rec = macho_file.getUnwindRecord(info.records.items[index]);
651325 try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{
652 .functionOffset = @as(u32, @intCast(record.rangeStart)),
653 .lsdaOffset = @as(u32, @intCast(record.lsda)),
326 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
327 .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)),
654328 });
655329 }
656330
657331 for (info.pages.items) |page| {
658332 const start = cwriter.bytes_written;
659 try page.write(info, writer);
333 try page.write(info, macho_file, writer);
660334 const nwritten = cwriter.bytes_written - start;
661335 if (nwritten < second_level_page_bytes) {
662 const offset = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow;
663 try writer.writeByteNTimes(0, offset);
336 const padding = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow;
337 try writer.writeByteNTimes(0, padding);
664338 }
665339 }
666340
667 const padding = buffer.items.len - cwriter.bytes_written;
341 const padding = buffer.len - cwriter.bytes_written;
668342 if (padding > 0) {
669 const offset = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow;
670 @memset(buffer.items[offset..], 0);
671 }
672
673 try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
674}
675
676fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info {
677 const object = &macho_file.objects.items[object_id];
678 assert(object.hasUnwindRecords());
679 const rel_pos = object.unwind_relocs_lookup[record_id].reloc;
680 const relocs = object.getRelocs(object.unwind_info_sect_id.?);
681 return relocs[rel_pos.start..][0..rel_pos.len];
682}
683
684fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool {
685 const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
686 const rel_offset = rel.r_address - base_offset;
687 return rel_offset == 16;
688}
689
690pub fn getPersonalityFunctionReloc(
691 macho_file: *MachO,
692 object_id: u32,
693 record_id: usize,
694) ?macho.relocation_info {
695 const relocs = getRelocs(macho_file, object_id, record_id);
696 for (relocs) |rel| {
697 if (isPersonalityFunction(record_id, rel)) return rel;
343 const off = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow;
344 @memset(buffer[off..], 0);
698345 }
699 return null;
700346}
701347
702fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 {
348fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 {
703349 comptime var index: u2 = 0;
704350 inline while (index < max_personalities) : (index += 1) {
705 if (index >= info.personalities_count) return null;
706 if (info.personalities[index].eql(global_index)) {
351 if (info.personalities[index] == sym_index) {
352 return index;
353 } else if (index == info.personalities_count) {
354 info.personalities[index] = sym_index;
355 info.personalities_count += 1;
707356 return index;
708357 }
709358 }
710 return null;
711}
712
713fn isLsda(record_id: usize, rel: macho.relocation_info) bool {
714 const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
715 const rel_offset = rel.r_address - base_offset;
716 return rel_offset == 24;
359 return error.TooManyPersonalities;
717360}
718361
719pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info {
720 const relocs = getRelocs(macho_file, object_id, record_id);
721 for (relocs) |rel| {
722 if (isLsda(record_id, rel)) return rel;
723 }
724 return null;
725}
726
727pub fn isNull(rec: macho.compact_unwind_entry) bool {
728 return rec.rangeStart == 0 and
729 rec.rangeLength == 0 and
730 rec.compactUnwindEncoding == 0 and
731 rec.lsda == 0 and
732 rec.personalityFunction == 0;
733}
734
735inline fn nullRecord() macho.compact_unwind_entry {
736 return .{
737 .rangeStart = 0,
738 .rangeLength = 0,
739 .compactUnwindEncoding = 0,
740 .personalityFunction = 0,
741 .lsda = 0,
742 };
743}
744
745fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void {
362fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void {
746363 assert(info.common_encodings_count <= max_common_encodings);
747364 info.common_encodings[info.common_encodings_count] = enc;
748365 info.common_encodings_count += 1;
749366}
750367
751fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 {
368fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 {
752369 comptime var index: u7 = 0;
753370 inline while (index < max_common_encodings) : (index += 1) {
754371 if (index >= info.common_encodings_count) return null;
755 if (info.common_encodings[index] == enc) {
372 if (info.common_encodings[index].eql(enc)) {
756373 return index;
757374 }
758375 }
759376 return null;
760377}
761378
762pub const UnwindEncoding = struct {
763 pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 {
379pub const Encoding = extern struct {
380 enc: macho.compact_unwind_encoding_t,
381
382 pub fn getMode(enc: Encoding) u4 {
764383 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
765 return @as(u4, @truncate((enc & macho.UNWIND_ARM64_MODE_MASK) >> 24));
384 const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
385 return @as(u4, @truncate((enc.enc & macho.UNWIND_ARM64_MODE_MASK) >> shift));
766386 }
767387
768 pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool {
769 const mode = getMode(enc);
770 return switch (cpu_arch) {
388 pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool {
389 const mode = enc.getMode();
390 return switch (macho_file.getTarget().cpu.arch) {
771391 .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF,
772392 .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF,
773393 else => unreachable,
774394 };
775395 }
776396
777 pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void {
778 enc.* |= @as(u32, @intCast(@intFromEnum(mode))) << 24;
397 pub fn setMode(enc: *Encoding, mode: anytype) void {
398 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
399 const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
400 enc.enc |= @as(u32, @intCast(@intFromEnum(mode))) << shift;
779401 }
780402
781 pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool {
782 const has_lsda = @as(u1, @truncate((enc & macho.UNWIND_HAS_LSDA) >> 31));
403 pub fn hasLsda(enc: Encoding) bool {
404 const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
405 const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> shift));
783406 return has_lsda == 1;
784407 }
785408
786 pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void {
787 const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31;
788 enc.* |= mask;
409 pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void {
410 const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
411 const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << shift;
412 enc.enc |= mask;
789413 }
790414
791 pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 {
792 const index = @as(u2, @truncate((enc & macho.UNWIND_PERSONALITY_MASK) >> 28));
415 pub fn getPersonalityIndex(enc: Encoding) u2 {
416 const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
417 const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> shift));
793418 return index;
794419 }
795420
796 pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void {
797 const mask = @as(u32, @intCast(index)) << 28;
798 enc.* |= mask;
421 pub fn setPersonalityIndex(enc: *Encoding, index: u2) void {
422 const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
423 const mask = @as(u32, @intCast(index)) << shift;
424 enc.enc |= mask;
799425 }
800426
801 pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 {
802 assert(isDwarf(enc, cpu_arch));
803 const offset = @as(u24, @truncate(enc));
427 pub fn getDwarfSectionOffset(enc: Encoding) u24 {
428 const offset = @as(u24, @truncate(enc.enc));
804429 return offset;
805430 }
806431
807 pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void {
808 assert(isDwarf(enc.*, cpu_arch));
809 enc.* |= offset;
432 pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void {
433 enc.enc |= offset;
434 }
435
436 pub fn eql(enc: Encoding, other: Encoding) bool {
437 return enc.enc == other.enc;
438 }
439
440 pub fn format(
441 enc: Encoding,
442 comptime unused_fmt_string: []const u8,
443 options: std.fmt.FormatOptions,
444 writer: anytype,
445 ) !void {
446 _ = unused_fmt_string;
447 _ = options;
448 try writer.print("0x{x:0>8}", .{enc.enc});
810449 }
811450};
812451
813const UnwindInfo = @This();
452pub const Record = struct {
453 length: u32 = 0,
454 enc: Encoding = .{ .enc = 0 },
455 atom: Atom.Index = 0,
456 atom_offset: u32 = 0,
457 lsda: Atom.Index = 0,
458 lsda_offset: u32 = 0,
459 personality: ?Symbol.Index = null, // TODO make this zero-is-null
460 fde: Fde.Index = 0, // TODO actually make FDE at 0 an invalid FDE
461 file: File.Index = 0,
462 alive: bool = true,
463
464 pub fn getObject(rec: Record, macho_file: *MachO) *Object {
465 return macho_file.getFile(rec.file).?.object;
466 }
467
468 pub fn getAtom(rec: Record, macho_file: *MachO) *Atom {
469 return macho_file.getAtom(rec.atom).?;
470 }
471
472 pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom {
473 return macho_file.getAtom(rec.lsda);
474 }
475
476 pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol {
477 const personality = rec.personality orelse return null;
478 return macho_file.getSymbol(personality);
479 }
480
481 pub fn getFde(rec: Record, macho_file: *MachO) ?Fde {
482 if (!rec.enc.isDwarf(macho_file)) return null;
483 return rec.getObject(macho_file).fdes.items[rec.fde];
484 }
485
486 pub fn getFdePtr(rec: Record, macho_file: *MachO) ?*Fde {
487 if (!rec.enc.isDwarf(macho_file)) return null;
488 return &rec.getObject(macho_file).fdes.items[rec.fde];
489 }
490
491 pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 {
492 const atom = rec.getAtom(macho_file);
493 return atom.value + rec.atom_offset;
494 }
495
496 pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 {
497 const lsda = rec.getLsdaAtom(macho_file) orelse return 0;
498 return lsda.value + rec.lsda_offset;
499 }
500
501 pub fn format(
502 rec: Record,
503 comptime unused_fmt_string: []const u8,
504 options: std.fmt.FormatOptions,
505 writer: anytype,
506 ) !void {
507 _ = rec;
508 _ = unused_fmt_string;
509 _ = options;
510 _ = writer;
511 @compileError("do not format UnwindInfo.Records directly");
512 }
513
514 pub fn fmt(rec: Record, macho_file: *MachO) std.fmt.Formatter(format2) {
515 return .{ .data = .{
516 .rec = rec,
517 .macho_file = macho_file,
518 } };
519 }
520
521 const FormatContext = struct {
522 rec: Record,
523 macho_file: *MachO,
524 };
525
526 fn format2(
527 ctx: FormatContext,
528 comptime unused_fmt_string: []const u8,
529 options: std.fmt.FormatOptions,
530 writer: anytype,
531 ) !void {
532 _ = unused_fmt_string;
533 _ = options;
534 const rec = ctx.rec;
535 const macho_file = ctx.macho_file;
536 try writer.print("{x} : len({x})", .{
537 rec.enc.enc, rec.length,
538 });
539 if (rec.enc.isDwarf(macho_file)) try writer.print(" : fde({d})", .{rec.fde});
540 try writer.print(" : {s}", .{rec.getAtom(macho_file).getName(macho_file)});
541 if (!rec.alive) try writer.writeAll(" : [*]");
542 }
543
544 pub const Index = u32;
545};
546
547const max_personalities = 3;
548const max_common_encodings = 127;
549const max_compact_encodings = 256;
550
551const second_level_page_bytes = 0x1000;
552const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
553
554const max_regular_second_level_entries =
555 (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
556 @sizeOf(macho.unwind_info_regular_second_level_entry);
557
558const max_compressed_second_level_entries =
559 (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
560 @sizeOf(u32);
561
562const compressed_entry_func_offset_mask = ~@as(u24, 0);
563
564const Page = struct {
565 kind: enum { regular, compressed },
566 start: u32,
567 count: u16,
568 page_encodings: [max_compact_encodings]Encoding = undefined,
569 page_encodings_count: u9 = 0,
570
571 fn appendPageEncoding(page: *Page, enc: Encoding) void {
572 assert(page.page_encodings_count <= max_compact_encodings);
573 page.page_encodings[page.page_encodings_count] = enc;
574 page.page_encodings_count += 1;
575 }
576
577 fn getPageEncoding(page: Page, enc: Encoding) ?u8 {
578 comptime var index: u9 = 0;
579 inline while (index < max_compact_encodings) : (index += 1) {
580 if (index >= page.page_encodings_count) return null;
581 if (page.page_encodings[index].eql(enc)) {
582 return @as(u8, @intCast(index));
583 }
584 }
585 return null;
586 }
587
588 fn format(
589 page: *const Page,
590 comptime unused_format_string: []const u8,
591 options: std.fmt.FormatOptions,
592 writer: anytype,
593 ) !void {
594 _ = page;
595 _ = unused_format_string;
596 _ = options;
597 _ = writer;
598 @compileError("do not format Page directly; use page.fmt()");
599 }
600
601 const FormatPageContext = struct {
602 page: Page,
603 info: UnwindInfo,
604 };
605
606 fn format2(
607 ctx: FormatPageContext,
608 comptime unused_format_string: []const u8,
609 options: std.fmt.FormatOptions,
610 writer: anytype,
611 ) @TypeOf(writer).Error!void {
612 _ = options;
613 _ = unused_format_string;
614 try writer.writeAll("Page:\n");
615 try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
616 try writer.print(" entries: {d} - {d}\n", .{
617 ctx.page.start,
618 ctx.page.start + ctx.page.count,
619 });
620 try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
621 for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |enc, i| {
622 try writer.print(" {d}: {}\n", .{ ctx.info.common_encodings_count + i, enc });
623 }
624 }
625
626 fn fmt(page: Page, info: UnwindInfo) std.fmt.Formatter(format2) {
627 return .{ .data = .{
628 .page = page,
629 .info = info,
630 } };
631 }
632
633 fn write(page: Page, info: UnwindInfo, macho_file: *MachO, writer: anytype) !void {
634 const seg = macho_file.getTextSegment();
635
636 switch (page.kind) {
637 .regular => {
638 try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
639 .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
640 .entryCount = page.count,
641 });
642
643 for (info.records.items[page.start..][0..page.count]) |index| {
644 const rec = macho_file.getUnwindRecord(index);
645 try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
646 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
647 .encoding = rec.enc.enc,
648 });
649 }
650 },
651 .compressed => {
652 const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
653 @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
654 try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
655 .entryPageOffset = entry_offset,
656 .entryCount = page.count,
657 .encodingsPageOffset = @sizeOf(macho.unwind_info_compressed_second_level_page_header),
658 .encodingsCount = page.page_encodings_count,
659 });
660
661 for (page.page_encodings[0..page.page_encodings_count]) |enc| {
662 try writer.writeInt(u32, enc.enc, .little);
663 }
664
665 assert(page.count > 0);
666 const first_rec = macho_file.getUnwindRecord(info.records.items[page.start]);
667 for (info.records.items[page.start..][0..page.count]) |index| {
668 const rec = macho_file.getUnwindRecord(index);
669 const enc_index = blk: {
670 if (info.getCommonEncoding(rec.enc)) |id| break :blk id;
671 const ncommon = info.common_encodings_count;
672 break :blk ncommon + page.getPageEncoding(rec.enc).?;
673 };
674 const compressed = macho.UnwindInfoCompressedEntry{
675 .funcOffset = @as(u24, @intCast(rec.getAtomAddress(macho_file) - first_rec.getAtomAddress(macho_file))),
676 .encodingIndex = @as(u8, @intCast(enc_index)),
677 };
678 try writer.writeStruct(compressed);
679 }
680 },
681 }
682 }
683};
814684
815685const std = @import("std");
816686const assert = std.debug.assert;
817687const eh_frame = @import("eh_frame.zig");
818688const fs = std.fs;
819689const leb = std.leb;
820const log = std.log.scoped(.unwind_info);
690const log = std.log.scoped(.link);
821691const macho = std.macho;
822692const math = std.math;
823693const mem = std.mem;
......@@ -825,7 +695,9 @@ const trace = @import("../../tracy.zig").trace;
825695
826696const Allocator = mem.Allocator;
827697const Atom = @import("Atom.zig");
828const EhFrameRecord = eh_frame.EhFrameRecord;
698const Fde = eh_frame.Fde;
699const File = @import("file.zig").File;
829700const MachO = @import("../MachO.zig");
830701const Object = @import("Object.zig");
831const SymbolWithLoc = MachO.SymbolWithLoc;
702const Symbol = @import("Symbol.zig");
703const UnwindInfo = @This();
src/link/MachO/ZigObject.zig created+1471
......@@ -0,0 +1,1471 @@
1/// Externally owned memory.
2path: []const u8,
3index: File.Index,
4
5symtab: std.MultiArrayList(Nlist) = .{},
6
7symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
8atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
9globals_lookup: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{},
10
11/// Table of tracked LazySymbols.
12lazy_syms: LazySymbolTable = .{},
13
14/// Table of tracked Decls.
15decls: DeclTable = .{},
16
17/// Table of unnamed constants associated with a parent `Decl`.
18/// We store them here so that we can free the constants whenever the `Decl`
19/// needs updating or is freed.
20///
21/// For example,
22///
23/// ```zig
24/// const Foo = struct{
25/// a: u8,
26/// };
27///
28/// pub fn main() void {
29/// var foo = Foo{ .a = 1 };
30/// _ = foo;
31/// }
32/// ```
33///
34/// value assigned to label `foo` is an unnamed constant belonging/associated
35/// with `Decl` `main`, and lives as long as that `Decl`.
36unnamed_consts: UnnamedConstTable = .{},
37
38/// Table of tracked AnonDecls.
39anon_decls: AnonDeclTable = .{},
40
41/// TLV initializers indexed by Atom.Index.
42tlv_initializers: TlvInitializerTable = .{},
43
44/// A table of relocations.
45relocs: RelocationTable = .{},
46
47dynamic_relocs: MachO.DynamicRelocs = .{},
48output_symtab_ctx: MachO.SymtabCtx = .{},
49
50pub fn init(self: *ZigObject, macho_file: *MachO) !void {
51 const comp = macho_file.base.comp;
52 const gpa = comp.gpa;
53
54 try self.atoms.append(gpa, 0); // null input section
55}
56
57pub fn deinit(self: *ZigObject, allocator: Allocator) void {
58 self.symtab.deinit(allocator);
59 self.symbols.deinit(allocator);
60 self.atoms.deinit(allocator);
61 self.globals_lookup.deinit(allocator);
62
63 {
64 var it = self.decls.iterator();
65 while (it.next()) |entry| {
66 entry.value_ptr.exports.deinit(allocator);
67 }
68 self.decls.deinit(allocator);
69 }
70
71 self.lazy_syms.deinit(allocator);
72
73 {
74 var it = self.unnamed_consts.valueIterator();
75 while (it.next()) |syms| {
76 syms.deinit(allocator);
77 }
78 self.unnamed_consts.deinit(allocator);
79 }
80
81 {
82 var it = self.anon_decls.iterator();
83 while (it.next()) |entry| {
84 entry.value_ptr.exports.deinit(allocator);
85 }
86 self.anon_decls.deinit(allocator);
87 }
88
89 for (self.relocs.items) |*list| {
90 list.deinit(allocator);
91 }
92 self.relocs.deinit(allocator);
93
94 for (self.tlv_initializers.values()) |*tlv_init| {
95 tlv_init.deinit(allocator);
96 }
97 self.tlv_initializers.deinit(allocator);
98}
99
100fn addNlist(self: *ZigObject, allocator: Allocator) !Symbol.Index {
101 try self.symtab.ensureUnusedCapacity(allocator, 1);
102 const index = @as(Symbol.Index, @intCast(self.symtab.addOneAssumeCapacity()));
103 self.symtab.set(index, .{
104 .nlist = MachO.null_sym,
105 .size = 0,
106 .atom = 0,
107 });
108 return index;
109}
110
111pub fn addAtom(self: *ZigObject, macho_file: *MachO) !Symbol.Index {
112 const gpa = macho_file.base.comp.gpa;
113 const atom_index = try macho_file.addAtom();
114 const symbol_index = try macho_file.addSymbol();
115 const nlist_index = try self.addNlist(gpa);
116
117 try self.atoms.append(gpa, atom_index);
118 try self.symbols.append(gpa, symbol_index);
119
120 const atom = macho_file.getAtom(atom_index).?;
121 atom.file = self.index;
122 atom.atom_index = atom_index;
123
124 const symbol = macho_file.getSymbol(symbol_index);
125 symbol.file = self.index;
126 symbol.atom = atom_index;
127
128 self.symtab.items(.atom)[nlist_index] = atom_index;
129 symbol.nlist_idx = nlist_index;
130
131 const relocs_index = @as(u32, @intCast(self.relocs.items.len));
132 const relocs = try self.relocs.addOne(gpa);
133 relocs.* = .{};
134 atom.relocs = .{ .pos = relocs_index, .len = 0 };
135
136 return symbol_index;
137}
138
139/// Caller owns the memory.
140pub fn getAtomDataAlloc(
141 self: ZigObject,
142 macho_file: *MachO,
143 allocator: Allocator,
144 atom: Atom,
145) ![]u8 {
146 assert(atom.file == self.index);
147 const sect = macho_file.sections.items(.header)[atom.out_n_sect];
148 assert(!sect.isZerofill());
149
150 switch (sect.type()) {
151 macho.S_THREAD_LOCAL_REGULAR => {
152 const tlv = self.tlv_initializers.get(atom.atom_index).?;
153 const data = try allocator.dupe(u8, tlv.data);
154 return data;
155 },
156 macho.S_THREAD_LOCAL_VARIABLES => {
157 const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
158 const data = try allocator.alloc(u8, size);
159 @memset(data, 0);
160 return data;
161 },
162 else => {
163 const file_offset = sect.offset + atom.value - sect.addr;
164 const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
165 const data = try allocator.alloc(u8, size);
166 errdefer allocator.free(data);
167 const amt = try macho_file.base.file.?.preadAll(data, file_offset);
168 if (amt != data.len) return error.InputOutput;
169 return data;
170 },
171 }
172}
173
174pub fn getAtomRelocs(self: *ZigObject, atom: Atom) []const Relocation {
175 const relocs = self.relocs.items[atom.relocs.pos];
176 return relocs.items[0..atom.relocs.len];
177}
178
179pub fn freeAtomRelocs(self: *ZigObject, atom: Atom) void {
180 self.relocs.items[atom.relocs.pos].clearRetainingCapacity();
181}
182
183pub fn resolveSymbols(self: *ZigObject, macho_file: *MachO) void {
184 const tracy = trace(@src());
185 defer tracy.end();
186
187 for (self.symbols.items, 0..) |index, i| {
188 const nlist_idx = @as(Symbol.Index, @intCast(i));
189 const nlist = self.symtab.items(.nlist)[nlist_idx];
190 const atom_index = self.symtab.items(.atom)[nlist_idx];
191
192 if (!nlist.ext()) continue;
193 if (nlist.undf() and !nlist.tentative()) continue;
194 if (nlist.sect()) {
195 const atom = macho_file.getAtom(atom_index).?;
196 if (!atom.flags.alive) continue;
197 }
198
199 const symbol = macho_file.getSymbol(index);
200 if (self.asFile().getSymbolRank(.{
201 .archive = false,
202 .weak = nlist.weakDef(),
203 .tentative = nlist.tentative(),
204 }) < symbol.getSymbolRank(macho_file)) {
205 const value = if (nlist.sect()) blk: {
206 const atom = macho_file.getAtom(atom_index).?;
207 break :blk nlist.n_value - atom.getInputAddress(macho_file);
208 } else nlist.n_value;
209 symbol.value = value;
210 symbol.atom = atom_index;
211 symbol.nlist_idx = nlist_idx;
212 symbol.file = self.index;
213 symbol.flags.weak = nlist.weakDef();
214 symbol.flags.abs = nlist.abs();
215 symbol.flags.tentative = nlist.tentative();
216 symbol.flags.weak_ref = false;
217 symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
218 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
219 // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
220 symbol.flags.interposable = false;
221
222 if (nlist.sect() and
223 macho_file.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
224 {
225 symbol.flags.tlv = true;
226 }
227 }
228
229 // Regardless of who the winner is, we still merge symbol visibility here.
230 if (nlist.pext() or (nlist.weakDef() and nlist.weakRef())) {
231 if (symbol.visibility != .global) {
232 symbol.visibility = .hidden;
233 }
234 } else {
235 symbol.visibility = .global;
236 }
237 }
238}
239
240pub fn resetGlobals(self: *ZigObject, macho_file: *MachO) void {
241 for (self.symbols.items, 0..) |sym_index, nlist_idx| {
242 if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
243 const sym = macho_file.getSymbol(sym_index);
244 const name = sym.name;
245 sym.* = .{};
246 sym.name = name;
247 }
248}
249
250pub fn markLive(self: *ZigObject, macho_file: *MachO) void {
251 const tracy = trace(@src());
252 defer tracy.end();
253
254 for (self.symbols.items, 0..) |index, nlist_idx| {
255 const nlist = self.symtab.items(.nlist)[nlist_idx];
256 if (!nlist.ext()) continue;
257
258 const sym = macho_file.getSymbol(index);
259 const file = sym.getFile(macho_file) orelse continue;
260 const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
261 if (should_keep and file == .object and !file.object.alive) {
262 file.object.alive = true;
263 file.object.markLive(macho_file);
264 }
265 }
266}
267
268pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !void {
269 for (self.symbols.items, 0..) |index, nlist_idx| {
270 const sym = macho_file.getSymbol(index);
271 if (sym.visibility != .global) continue;
272 const file = sym.getFile(macho_file) orelse continue;
273 if (file.getIndex() == self.index) continue;
274
275 const nlist = self.symtab.items(.nlist)[nlist_idx];
276 if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
277 const gop = try dupes.getOrPut(index);
278 if (!gop.found_existing) {
279 gop.value_ptr.* = .{};
280 }
281 try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
282 }
283 }
284}
285
286pub fn scanRelocs(self: *ZigObject, macho_file: *MachO) !void {
287 for (self.atoms.items) |atom_index| {
288 const atom = macho_file.getAtom(atom_index) orelse continue;
289 if (!atom.flags.alive) continue;
290 const sect = atom.getInputSection(macho_file);
291 if (sect.isZerofill()) continue;
292 try atom.scanRelocs(macho_file);
293 }
294}
295
296pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) !void {
297 const tracy = trace(@src());
298 defer tracy.end();
299
300 for (self.symbols.items) |sym_index| {
301 const sym = macho_file.getSymbol(sym_index);
302 const file = sym.getFile(macho_file) orelse continue;
303 if (file.getIndex() != self.index) continue;
304 if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
305 sym.flags.output_symtab = true;
306 if (sym.isLocal()) {
307 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
308 self.output_symtab_ctx.nlocals += 1;
309 } else if (sym.flags.@"export") {
310 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
311 self.output_symtab_ctx.nexports += 1;
312 } else {
313 assert(sym.flags.import);
314 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
315 self.output_symtab_ctx.nimports += 1;
316 }
317 self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
318 }
319}
320
321pub fn writeSymtab(self: ZigObject, macho_file: *MachO) void {
322 const tracy = trace(@src());
323 defer tracy.end();
324
325 for (self.symbols.items) |sym_index| {
326 const sym = macho_file.getSymbol(sym_index);
327 const file = sym.getFile(macho_file) orelse continue;
328 if (file.getIndex() != self.index) continue;
329 const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
330 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
331 macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
332 macho_file.strtab.appendAssumeCapacity(0);
333 const out_sym = &macho_file.symtab.items[idx];
334 out_sym.n_strx = n_strx;
335 sym.setOutputSym(macho_file, out_sym);
336 }
337}
338
339pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.section_64 {
340 _ = self;
341 var sect = macho_file.sections.items(.header)[atom.out_n_sect];
342 sect.addr = 0;
343 sect.offset = 0;
344 sect.size = atom.size;
345 sect.@"align" = atom.alignment.toLog2Units();
346 return sect;
347}
348
349pub fn flushModule(self: *ZigObject, macho_file: *MachO) !void {
350 // Handle any lazy symbols that were emitted by incremental compilation.
351 if (self.lazy_syms.getPtr(.none)) |metadata| {
352 const zcu = macho_file.base.comp.module.?;
353
354 // Most lazy symbols can be updated on first use, but
355 // anyerror needs to wait for everything to be flushed.
356 if (metadata.text_state != .unused) self.updateLazySymbol(
357 macho_file,
358 link.File.LazySymbol.initDecl(.code, null, zcu),
359 metadata.text_symbol_index,
360 ) catch |err| return switch (err) {
361 error.CodegenFail => error.FlushFailure,
362 else => |e| e,
363 };
364 if (metadata.const_state != .unused) self.updateLazySymbol(
365 macho_file,
366 link.File.LazySymbol.initDecl(.const_data, null, zcu),
367 metadata.const_symbol_index,
368 ) catch |err| return switch (err) {
369 error.CodegenFail => error.FlushFailure,
370 else => |e| e,
371 };
372 }
373 for (self.lazy_syms.values()) |*metadata| {
374 if (metadata.text_state != .unused) metadata.text_state = .flushed;
375 if (metadata.const_state != .unused) metadata.const_state = .flushed;
376 }
377}
378
379pub fn getDeclVAddr(
380 self: *ZigObject,
381 macho_file: *MachO,
382 decl_index: InternPool.DeclIndex,
383 reloc_info: link.File.RelocInfo,
384) !u64 {
385 const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
386 const sym = macho_file.getSymbol(sym_index);
387 const vaddr = sym.getAddress(.{}, macho_file);
388 const parent_atom = macho_file.getSymbol(reloc_info.parent_atom_index).getAtom(macho_file).?;
389 try parent_atom.addReloc(macho_file, .{
390 .tag = .@"extern",
391 .offset = @intCast(reloc_info.offset),
392 .target = sym_index,
393 .addend = reloc_info.addend,
394 .type = .unsigned,
395 .meta = .{
396 .pcrel = false,
397 .has_subtractor = false,
398 .length = 3,
399 .symbolnum = 0,
400 },
401 });
402 return vaddr;
403}
404
405pub fn getAnonDeclVAddr(
406 self: *ZigObject,
407 macho_file: *MachO,
408 decl_val: InternPool.Index,
409 reloc_info: link.File.RelocInfo,
410) !u64 {
411 const sym_index = self.anon_decls.get(decl_val).?.symbol_index;
412 const sym = macho_file.getSymbol(sym_index);
413 const vaddr = sym.getAddress(.{}, macho_file);
414 const parent_atom = macho_file.getSymbol(reloc_info.parent_atom_index).getAtom(macho_file).?;
415 try parent_atom.addReloc(macho_file, .{
416 .tag = .@"extern",
417 .offset = @intCast(reloc_info.offset),
418 .target = sym_index,
419 .addend = reloc_info.addend,
420 .type = .unsigned,
421 .meta = .{
422 .pcrel = false,
423 .has_subtractor = false,
424 .length = 3,
425 .symbolnum = 0,
426 },
427 });
428 return vaddr;
429}
430
431pub fn lowerAnonDecl(
432 self: *ZigObject,
433 macho_file: *MachO,
434 decl_val: InternPool.Index,
435 explicit_alignment: Atom.Alignment,
436 src_loc: Module.SrcLoc,
437) !codegen.Result {
438 const gpa = macho_file.base.comp.gpa;
439 const mod = macho_file.base.comp.module.?;
440 const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val));
441 const decl_alignment = switch (explicit_alignment) {
442 .none => ty.abiAlignment(mod),
443 else => explicit_alignment,
444 };
445 if (self.anon_decls.get(decl_val)) |metadata| {
446 const existing_alignment = macho_file.getSymbol(metadata.symbol_index).getAtom(macho_file).?.alignment;
447 if (decl_alignment.order(existing_alignment).compare(.lte))
448 return .ok;
449 }
450
451 const val = Value.fromInterned(decl_val);
452 const tv = TypedValue{ .ty = ty, .val = val };
453 var name_buf: [32]u8 = undefined;
454 const name = std.fmt.bufPrint(&name_buf, "__anon_{d}", .{
455 @intFromEnum(decl_val),
456 }) catch unreachable;
457 const res = self.lowerConst(
458 macho_file,
459 name,
460 tv,
461 decl_alignment,
462 macho_file.zig_const_sect_index.?,
463 src_loc,
464 ) catch |err| switch (err) {
465 error.OutOfMemory => return error.OutOfMemory,
466 else => |e| return .{ .fail = try Module.ErrorMsg.create(
467 gpa,
468 src_loc,
469 "unable to lower constant value: {s}",
470 .{@errorName(e)},
471 ) },
472 };
473 const sym_index = switch (res) {
474 .ok => |sym_index| sym_index,
475 .fail => |em| return .{ .fail = em },
476 };
477 try self.anon_decls.put(gpa, decl_val, .{ .symbol_index = sym_index });
478 return .ok;
479}
480
481fn freeUnnamedConsts(self: *ZigObject, macho_file: *MachO, decl_index: InternPool.DeclIndex) void {
482 const gpa = macho_file.base.comp.gpa;
483 const unnamed_consts = self.unnamed_consts.getPtr(decl_index) orelse return;
484 for (unnamed_consts.items) |sym_index| {
485 self.freeDeclMetadata(macho_file, sym_index);
486 }
487 unnamed_consts.clearAndFree(gpa);
488}
489
490fn freeDeclMetadata(self: *ZigObject, macho_file: *MachO, sym_index: Symbol.Index) void {
491 _ = self;
492 const gpa = macho_file.base.comp.gpa;
493 const sym = macho_file.getSymbol(sym_index);
494 sym.getAtom(macho_file).?.free(macho_file);
495 log.debug("adding %{d} to local symbols free list", .{sym_index});
496 macho_file.symbols_free_list.append(gpa, sym_index) catch {};
497 macho_file.symbols.items[sym_index] = .{};
498 // TODO free GOT entry here
499}
500
501pub fn freeDecl(self: *ZigObject, macho_file: *MachO, decl_index: InternPool.DeclIndex) void {
502 const gpa = macho_file.base.comp.gpa;
503 const mod = macho_file.base.comp.module.?;
504 const decl = mod.declPtr(decl_index);
505
506 log.debug("freeDecl {*}", .{decl});
507
508 if (self.decls.fetchRemove(decl_index)) |const_kv| {
509 var kv = const_kv;
510 const sym_index = kv.value.symbol_index;
511 self.freeDeclMetadata(macho_file, sym_index);
512 self.freeUnnamedConsts(macho_file, decl_index);
513 kv.value.exports.deinit(gpa);
514 }
515
516 // TODO free decl in dSYM
517}
518
519pub fn updateFunc(
520 self: *ZigObject,
521 macho_file: *MachO,
522 mod: *Module,
523 func_index: InternPool.Index,
524 air: Air,
525 liveness: Liveness,
526) !void {
527 const tracy = trace(@src());
528 defer tracy.end();
529
530 const gpa = macho_file.base.comp.gpa;
531 const func = mod.funcInfo(func_index);
532 const decl_index = func.owner_decl;
533 const decl = mod.declPtr(decl_index);
534
535 const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
536 self.freeUnnamedConsts(macho_file, decl_index);
537 macho_file.getSymbol(sym_index).getAtom(macho_file).?.freeRelocs(macho_file);
538
539 var code_buffer = std.ArrayList(u8).init(gpa);
540 defer code_buffer.deinit();
541
542 var decl_state: ?Dwarf.DeclState = null; // TODO: Dwarf
543 defer if (decl_state) |*ds| ds.deinit();
544
545 const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
546 const res = try codegen.generateFunction(
547 &macho_file.base,
548 decl.srcLoc(mod),
549 func_index,
550 air,
551 liveness,
552 &code_buffer,
553 dio,
554 );
555
556 const code = switch (res) {
557 .ok => code_buffer.items,
558 .fail => |em| {
559 decl.analysis = .codegen_failure;
560 try mod.failed_decls.put(mod.gpa, decl_index, em);
561 return;
562 },
563 };
564
565 const sect_index = try self.getDeclOutputSection(macho_file, decl, code);
566 try self.updateDeclCode(macho_file, decl_index, sym_index, sect_index, code);
567
568 // if (decl_state) |*ds| {
569 // const sym = elf_file.symbol(sym_index);
570 // try self.dwarf.?.commitDeclState(
571 // mod,
572 // decl_index,
573 // sym.value,
574 // sym.atom(elf_file).?.size,
575 // ds,
576 // );
577 // }
578
579 // Since we updated the vaddr and the size, each corresponding export
580 // symbol also needs to be updated.
581 return self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
582}
583
584pub fn updateDecl(
585 self: *ZigObject,
586 macho_file: *MachO,
587 mod: *Module,
588 decl_index: InternPool.DeclIndex,
589) link.File.UpdateDeclError!void {
590 const tracy = trace(@src());
591 defer tracy.end();
592
593 const decl = mod.declPtr(decl_index);
594
595 if (decl.val.getExternFunc(mod)) |_| {
596 return;
597 }
598
599 if (decl.isExtern(mod)) {
600 // Extern variable gets a __got entry only
601 const variable = decl.getOwnedVariable(mod).?;
602 const name = mod.intern_pool.stringToSlice(decl.name);
603 const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name);
604 const index = try self.getGlobalSymbol(macho_file, name, lib_name);
605 const actual_index = self.symbols.items[index];
606 macho_file.getSymbol(actual_index).flags.needs_got = true;
607 return;
608 }
609
610 const sym_index = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
611 macho_file.getSymbol(sym_index).getAtom(macho_file).?.freeRelocs(macho_file);
612
613 const gpa = macho_file.base.comp.gpa;
614 var code_buffer = std.ArrayList(u8).init(gpa);
615 defer code_buffer.deinit();
616
617 var decl_state: ?Dwarf.DeclState = null; // TODO: Dwarf
618 defer if (decl_state) |*ds| ds.deinit();
619
620 const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val;
621 const dio: codegen.DebugInfoOutput = if (decl_state) |*ds| .{ .dwarf = ds } else .none;
622 const res =
623 try codegen.generateSymbol(&macho_file.base, decl.srcLoc(mod), .{
624 .ty = decl.ty,
625 .val = decl_val,
626 }, &code_buffer, dio, .{
627 .parent_atom_index = sym_index,
628 });
629
630 const code = switch (res) {
631 .ok => code_buffer.items,
632 .fail => |em| {
633 decl.analysis = .codegen_failure;
634 try mod.failed_decls.put(mod.gpa, decl_index, em);
635 return;
636 },
637 };
638 const sect_index = try self.getDeclOutputSection(macho_file, decl, code);
639 const is_threadlocal = switch (macho_file.sections.items(.header)[sect_index].type()) {
640 macho.S_THREAD_LOCAL_ZEROFILL, macho.S_THREAD_LOCAL_REGULAR => true,
641 else => false,
642 };
643 if (is_threadlocal) {
644 try self.updateTlv(macho_file, decl_index, sym_index, sect_index, code);
645 } else {
646 try self.updateDeclCode(macho_file, decl_index, sym_index, sect_index, code);
647 }
648
649 // if (decl_state) |*ds| {
650 // try self.d_sym.?.dwarf.commitDeclState(
651 // mod,
652 // decl_index,
653 // addr,
654 // self.getAtom(atom_index).size,
655 // ds,
656 // );
657 // }
658
659 // Since we updated the vaddr and the size, each corresponding export symbol also
660 // needs to be updated.
661 try self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index));
662}
663
664fn updateDeclCode(
665 self: *ZigObject,
666 macho_file: *MachO,
667 decl_index: InternPool.DeclIndex,
668 sym_index: Symbol.Index,
669 sect_index: u8,
670 code: []const u8,
671) !void {
672 const gpa = macho_file.base.comp.gpa;
673 const mod = macho_file.base.comp.module.?;
674 const decl = mod.declPtr(decl_index);
675 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
676
677 log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
678
679 const required_alignment = decl.getAlignment(mod);
680
681 const sect = &macho_file.sections.items(.header)[sect_index];
682 const sym = macho_file.getSymbol(sym_index);
683 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
684 const atom = sym.getAtom(macho_file).?;
685
686 sym.out_n_sect = sect_index;
687 atom.out_n_sect = sect_index;
688
689 sym.name = try macho_file.strings.insert(gpa, decl_name);
690 atom.flags.alive = true;
691 atom.name = sym.name;
692 nlist.n_strx = sym.name;
693 nlist.n_type = macho.N_SECT;
694 nlist.n_sect = sect_index + 1;
695 self.symtab.items(.size)[sym.nlist_idx] = code.len;
696
697 const old_size = atom.size;
698 const old_vaddr = atom.value;
699 atom.alignment = required_alignment;
700 atom.size = code.len;
701
702 if (old_size > 0) {
703 const capacity = atom.capacity(macho_file);
704 const need_realloc = code.len > capacity or !required_alignment.check(atom.value);
705
706 if (need_realloc) {
707 try atom.grow(macho_file);
708 log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, old_vaddr, atom.value });
709 if (old_vaddr != atom.value) {
710 sym.value = 0;
711 nlist.n_value = 0;
712
713 if (!macho_file.base.isRelocatable()) {
714 log.debug(" (updating offset table entry)", .{});
715 assert(sym.flags.has_zig_got);
716 const extra = sym.getExtra(macho_file).?;
717 try macho_file.zig_got.writeOne(macho_file, extra.zig_got);
718 }
719 }
720 } else if (code.len < old_size) {
721 atom.shrink(macho_file);
722 } else if (macho_file.getAtom(atom.next_index) == null) {
723 const needed_size = atom.value + code.len - sect.addr;
724 sect.size = needed_size;
725 }
726 } else {
727 try atom.allocate(macho_file);
728 errdefer self.freeDeclMetadata(macho_file, sym_index);
729
730 sym.value = 0;
731 sym.flags.needs_zig_got = true;
732 nlist.n_value = 0;
733
734 if (!macho_file.base.isRelocatable()) {
735 const gop = try sym.getOrCreateZigGotEntry(sym_index, macho_file);
736 try macho_file.zig_got.writeOne(macho_file, gop.index);
737 }
738 }
739
740 if (!sect.isZerofill()) {
741 const file_offset = sect.offset + atom.value - sect.addr;
742 try macho_file.base.file.?.pwriteAll(code, file_offset);
743 }
744}
745
746/// Lowering a TLV on macOS involves two stages:
747/// 1. first we lower the initializer into appopriate section (__thread_data or __thread_bss)
748/// 2. next, we create a corresponding threadlocal variable descriptor in __thread_vars
749fn updateTlv(
750 self: *ZigObject,
751 macho_file: *MachO,
752 decl_index: InternPool.DeclIndex,
753 sym_index: Symbol.Index,
754 sect_index: u8,
755 code: []const u8,
756) !void {
757 const mod = macho_file.base.comp.module.?;
758 const decl = mod.declPtr(decl_index);
759 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
760
761 log.debug("updateTlv {s} ({*})", .{ decl_name, decl });
762
763 const required_alignment = decl.getAlignment(mod);
764
765 // 1. Lower TLV initializer
766 const init_sym_index = try self.createTlvInitializer(
767 macho_file,
768 decl_name,
769 required_alignment,
770 sect_index,
771 code,
772 );
773
774 // 2. Create TLV descriptor
775 try self.createTlvDescriptor(macho_file, sym_index, init_sym_index, decl_name);
776}
777
778fn createTlvInitializer(
779 self: *ZigObject,
780 macho_file: *MachO,
781 name: []const u8,
782 alignment: Atom.Alignment,
783 sect_index: u8,
784 code: []const u8,
785) !Symbol.Index {
786 const gpa = macho_file.base.comp.gpa;
787 const sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{name});
788 defer gpa.free(sym_name);
789
790 const sym_index = try self.addAtom(macho_file);
791 const sym = macho_file.getSymbol(sym_index);
792 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
793 const atom = sym.getAtom(macho_file).?;
794
795 sym.out_n_sect = sect_index;
796 atom.out_n_sect = sect_index;
797
798 sym.value = 0;
799 sym.name = try macho_file.strings.insert(gpa, sym_name);
800 atom.flags.alive = true;
801 atom.name = sym.name;
802 nlist.n_strx = sym.name;
803 nlist.n_sect = sect_index + 1;
804 nlist.n_type = macho.N_SECT;
805 nlist.n_value = 0;
806 self.symtab.items(.size)[sym.nlist_idx] = code.len;
807
808 atom.alignment = alignment;
809 atom.size = code.len;
810
811 const slice = macho_file.sections.slice();
812 const header = slice.items(.header)[sect_index];
813 const atoms = &slice.items(.atoms)[sect_index];
814
815 const gop = try self.tlv_initializers.getOrPut(gpa, atom.atom_index);
816 assert(!gop.found_existing); // TODO incremental updates
817 gop.value_ptr.* = .{ .symbol_index = sym_index };
818
819 // We only store the data for the TLV if it's non-zerofill.
820 if (!header.isZerofill()) {
821 gop.value_ptr.data = try gpa.dupe(u8, code);
822 }
823
824 try atoms.append(gpa, atom.atom_index);
825
826 return sym_index;
827}
828
829fn createTlvDescriptor(
830 self: *ZigObject,
831 macho_file: *MachO,
832 sym_index: Symbol.Index,
833 init_sym_index: Symbol.Index,
834 name: []const u8,
835) !void {
836 const gpa = macho_file.base.comp.gpa;
837
838 const sym = macho_file.getSymbol(sym_index);
839 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
840 const atom = sym.getAtom(macho_file).?;
841 const alignment = Atom.Alignment.fromNonzeroByteUnits(@alignOf(u64));
842 const size: u64 = @sizeOf(u64) * 3;
843
844 const sect_index = macho_file.getSectionByName("__DATA", "__thread_vars") orelse
845 try macho_file.addSection("__DATA", "__thread_vars", .{
846 .flags = macho.S_THREAD_LOCAL_VARIABLES,
847 });
848 sym.out_n_sect = sect_index;
849 atom.out_n_sect = sect_index;
850
851 sym.value = 0;
852 sym.name = try macho_file.strings.insert(gpa, name);
853 atom.flags.alive = true;
854 atom.name = sym.name;
855 nlist.n_strx = sym.name;
856 nlist.n_sect = sect_index + 1;
857 nlist.n_type = macho.N_SECT;
858 nlist.n_value = 0;
859 self.symtab.items(.size)[sym.nlist_idx] = size;
860
861 atom.alignment = alignment;
862 atom.size = size;
863
864 const tlv_bootstrap_index = blk: {
865 const index = try self.getGlobalSymbol(macho_file, "_tlv_bootstrap", null);
866 break :blk self.symbols.items[index];
867 };
868 try atom.addReloc(macho_file, .{
869 .tag = .@"extern",
870 .offset = 0,
871 .target = tlv_bootstrap_index,
872 .addend = 0,
873 .type = .unsigned,
874 .meta = .{
875 .pcrel = false,
876 .has_subtractor = false,
877 .length = 3,
878 .symbolnum = 0,
879 },
880 });
881 try atom.addReloc(macho_file, .{
882 .tag = .@"extern",
883 .offset = 16,
884 .target = init_sym_index,
885 .addend = 0,
886 .type = .unsigned,
887 .meta = .{
888 .pcrel = false,
889 .has_subtractor = false,
890 .length = 3,
891 .symbolnum = 0,
892 },
893 });
894
895 try macho_file.sections.items(.atoms)[sect_index].append(gpa, atom.atom_index);
896}
897
898fn getDeclOutputSection(
899 self: *ZigObject,
900 macho_file: *MachO,
901 decl: *const Module.Decl,
902 code: []const u8,
903) error{OutOfMemory}!u8 {
904 _ = self;
905 const mod = macho_file.base.comp.module.?;
906 const any_non_single_threaded = macho_file.base.comp.config.any_non_single_threaded;
907 const sect_id: u8 = switch (decl.ty.zigTypeTag(mod)) {
908 .Fn => macho_file.zig_text_sect_index.?,
909 else => blk: {
910 if (decl.getOwnedVariable(mod)) |variable| {
911 if (variable.is_threadlocal and any_non_single_threaded) {
912 const is_all_zeroes = for (code) |byte| {
913 if (byte != 0) break false;
914 } else true;
915 if (is_all_zeroes) break :blk macho_file.getSectionByName("__DATA", "__thread_bss") orelse try macho_file.addSection(
916 "__DATA",
917 "__thread_bss",
918 .{ .flags = macho.S_THREAD_LOCAL_ZEROFILL },
919 );
920 break :blk macho_file.getSectionByName("__DATA", "__thread_data") orelse try macho_file.addSection(
921 "__DATA",
922 "__thread_data",
923 .{ .flags = macho.S_THREAD_LOCAL_REGULAR },
924 );
925 }
926
927 if (variable.is_const) break :blk macho_file.zig_const_sect_index.?;
928 if (Value.fromInterned(variable.init).isUndefDeep(mod)) {
929 // TODO: get the optimize_mode from the Module that owns the decl instead
930 // of using the root module here.
931 break :blk switch (macho_file.base.comp.root_mod.optimize_mode) {
932 .Debug, .ReleaseSafe => macho_file.zig_data_sect_index.?,
933 .ReleaseFast, .ReleaseSmall => macho_file.zig_bss_sect_index.?,
934 };
935 }
936
937 // TODO I blatantly copied the logic from the Wasm linker, but is there a less
938 // intrusive check for all zeroes than this?
939 const is_all_zeroes = for (code) |byte| {
940 if (byte != 0) break false;
941 } else true;
942 if (is_all_zeroes) break :blk macho_file.zig_bss_sect_index.?;
943 break :blk macho_file.zig_data_sect_index.?;
944 }
945 break :blk macho_file.zig_const_sect_index.?;
946 },
947 };
948 return sect_id;
949}
950
951pub fn lowerUnnamedConst(
952 self: *ZigObject,
953 macho_file: *MachO,
954 typed_value: TypedValue,
955 decl_index: InternPool.DeclIndex,
956) !u32 {
957 const gpa = macho_file.base.comp.gpa;
958 const mod = macho_file.base.comp.module.?;
959 const gop = try self.unnamed_consts.getOrPut(gpa, decl_index);
960 if (!gop.found_existing) {
961 gop.value_ptr.* = .{};
962 }
963 const unnamed_consts = gop.value_ptr;
964 const decl = mod.declPtr(decl_index);
965 const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
966 const index = unnamed_consts.items.len;
967 const name = try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
968 defer gpa.free(name);
969 const sym_index = switch (try self.lowerConst(
970 macho_file,
971 name,
972 typed_value,
973 typed_value.ty.abiAlignment(mod),
974 macho_file.zig_const_sect_index.?,
975 decl.srcLoc(mod),
976 )) {
977 .ok => |sym_index| sym_index,
978 .fail => |em| {
979 decl.analysis = .codegen_failure;
980 try mod.failed_decls.put(mod.gpa, decl_index, em);
981 log.err("{s}", .{em.msg});
982 return error.CodegenFail;
983 },
984 };
985 const sym = macho_file.getSymbol(sym_index);
986 try unnamed_consts.append(gpa, sym.atom);
987 return sym_index;
988}
989
990const LowerConstResult = union(enum) {
991 ok: Symbol.Index,
992 fail: *Module.ErrorMsg,
993};
994
995fn lowerConst(
996 self: *ZigObject,
997 macho_file: *MachO,
998 name: []const u8,
999 tv: TypedValue,
1000 required_alignment: Atom.Alignment,
1001 output_section_index: u8,
1002 src_loc: Module.SrcLoc,
1003) !LowerConstResult {
1004 const gpa = macho_file.base.comp.gpa;
1005
1006 var code_buffer = std.ArrayList(u8).init(gpa);
1007 defer code_buffer.deinit();
1008
1009 const sym_index = try self.addAtom(macho_file);
1010
1011 const res = try codegen.generateSymbol(&macho_file.base, src_loc, tv, &code_buffer, .{
1012 .none = {},
1013 }, .{
1014 .parent_atom_index = sym_index,
1015 });
1016 const code = switch (res) {
1017 .ok => code_buffer.items,
1018 .fail => |em| return .{ .fail = em },
1019 };
1020
1021 const sym = macho_file.getSymbol(sym_index);
1022 const name_str_index = try macho_file.strings.insert(gpa, name);
1023 sym.name = name_str_index;
1024 sym.out_n_sect = output_section_index;
1025
1026 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
1027 nlist.n_strx = name_str_index;
1028 nlist.n_type = macho.N_SECT;
1029 nlist.n_sect = output_section_index + 1;
1030 self.symtab.items(.size)[sym.nlist_idx] = code.len;
1031
1032 const atom = sym.getAtom(macho_file).?;
1033 atom.flags.alive = true;
1034 atom.name = name_str_index;
1035 atom.alignment = required_alignment;
1036 atom.size = code.len;
1037 atom.out_n_sect = output_section_index;
1038
1039 try atom.allocate(macho_file);
1040 // TODO rename and re-audit this method
1041 errdefer self.freeDeclMetadata(macho_file, sym_index);
1042
1043 sym.value = 0;
1044 nlist.n_value = 0;
1045
1046 const sect = macho_file.sections.items(.header)[output_section_index];
1047 const file_offset = sect.offset + atom.value - sect.addr;
1048 try macho_file.base.file.?.pwriteAll(code, file_offset);
1049
1050 return .{ .ok = sym_index };
1051}
1052
1053pub fn updateExports(
1054 self: *ZigObject,
1055 macho_file: *MachO,
1056 mod: *Module,
1057 exported: Module.Exported,
1058 exports: []const *Module.Export,
1059) link.File.UpdateExportsError!void {
1060 const tracy = trace(@src());
1061 defer tracy.end();
1062
1063 const gpa = macho_file.base.comp.gpa;
1064 const metadata = switch (exported) {
1065 .decl_index => |decl_index| blk: {
1066 _ = try self.getOrCreateMetadataForDecl(macho_file, decl_index);
1067 break :blk self.decls.getPtr(decl_index).?;
1068 },
1069 .value => |value| self.anon_decls.getPtr(value) orelse blk: {
1070 const first_exp = exports[0];
1071 const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.getSrcLoc(mod));
1072 switch (res) {
1073 .ok => {},
1074 .fail => |em| {
1075 // TODO maybe it's enough to return an error here and let Module.processExportsInner
1076 // handle the error?
1077 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
1078 mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em);
1079 return;
1080 },
1081 }
1082 break :blk self.anon_decls.getPtr(value).?;
1083 },
1084 };
1085 const sym_index = metadata.symbol_index;
1086 const nlist_idx = macho_file.getSymbol(sym_index).nlist_idx;
1087 const nlist = self.symtab.items(.nlist)[nlist_idx];
1088
1089 for (exports) |exp| {
1090 if (exp.opts.section.unwrap()) |section_name| {
1091 if (!mod.intern_pool.stringEqlSlice(section_name, "__text")) {
1092 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
1093 mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create(
1094 gpa,
1095 exp.getSrcLoc(mod),
1096 "Unimplemented: ExportOptions.section",
1097 .{},
1098 ));
1099 continue;
1100 }
1101 }
1102 if (exp.opts.linkage == .LinkOnce) {
1103 try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
1104 gpa,
1105 exp.getSrcLoc(mod),
1106 "Unimplemented: GlobalLinkage.LinkOnce",
1107 .{},
1108 ));
1109 continue;
1110 }
1111
1112 const exp_name = mod.intern_pool.stringToSlice(exp.opts.name);
1113 const global_nlist_index = if (metadata.@"export"(self, macho_file, exp_name)) |exp_index|
1114 exp_index.*
1115 else blk: {
1116 const global_nlist_index = try self.getGlobalSymbol(macho_file, exp_name, null);
1117 try metadata.exports.append(gpa, global_nlist_index);
1118 break :blk global_nlist_index;
1119 };
1120 const global_nlist = &self.symtab.items(.nlist)[global_nlist_index];
1121 global_nlist.n_value = nlist.n_value;
1122 global_nlist.n_sect = nlist.n_sect;
1123 global_nlist.n_type = macho.N_EXT | macho.N_SECT;
1124 self.symtab.items(.size)[global_nlist_index] = self.symtab.items(.size)[nlist_idx];
1125 self.symtab.items(.atom)[global_nlist_index] = self.symtab.items(.atom)[nlist_idx];
1126
1127 switch (exp.opts.linkage) {
1128 .Internal => {
1129 // Symbol should be hidden, or in MachO lingo, private extern.
1130 global_nlist.n_type |= macho.N_PEXT;
1131 },
1132 .Strong => {},
1133 .Weak => {
1134 // Weak linkage is specified as part of n_desc field.
1135 // Symbol's n_type is like for a symbol with strong linkage.
1136 global_nlist.n_desc |= macho.N_WEAK_DEF;
1137 },
1138 else => unreachable,
1139 }
1140 }
1141}
1142
1143fn updateLazySymbol(
1144 self: *ZigObject,
1145 macho_file: *MachO,
1146 lazy_sym: link.File.LazySymbol,
1147 symbol_index: Symbol.Index,
1148) !void {
1149 const gpa = macho_file.base.comp.gpa;
1150 const mod = macho_file.base.comp.module.?;
1151
1152 var required_alignment: Atom.Alignment = .none;
1153 var code_buffer = std.ArrayList(u8).init(gpa);
1154 defer code_buffer.deinit();
1155
1156 const name_str_index = blk: {
1157 const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{}", .{
1158 @tagName(lazy_sym.kind),
1159 lazy_sym.ty.fmt(mod),
1160 });
1161 defer gpa.free(name);
1162 break :blk try macho_file.strings.insert(gpa, name);
1163 };
1164
1165 const src = if (lazy_sym.ty.getOwnerDeclOrNull(mod)) |owner_decl|
1166 mod.declPtr(owner_decl).srcLoc(mod)
1167 else
1168 Module.SrcLoc{
1169 .file_scope = undefined,
1170 .parent_decl_node = undefined,
1171 .lazy = .unneeded,
1172 };
1173 const res = try codegen.generateLazySymbol(
1174 &macho_file.base,
1175 src,
1176 lazy_sym,
1177 &required_alignment,
1178 &code_buffer,
1179 .none,
1180 .{ .parent_atom_index = symbol_index },
1181 );
1182 const code = switch (res) {
1183 .ok => code_buffer.items,
1184 .fail => |em| {
1185 log.err("{s}", .{em.msg});
1186 return error.CodegenFail;
1187 },
1188 };
1189
1190 const output_section_index = switch (lazy_sym.kind) {
1191 .code => macho_file.zig_text_sect_index.?,
1192 .const_data => macho_file.zig_const_sect_index.?,
1193 };
1194 const sym = macho_file.getSymbol(symbol_index);
1195 sym.name = name_str_index;
1196 sym.out_n_sect = output_section_index;
1197
1198 const nlist = &self.symtab.items(.nlist)[sym.nlist_idx];
1199 nlist.n_strx = name_str_index;
1200 nlist.n_type = macho.N_SECT;
1201 nlist.n_sect = output_section_index + 1;
1202 self.symtab.items(.size)[sym.nlist_idx] = code.len;
1203
1204 const atom = sym.getAtom(macho_file).?;
1205 atom.flags.alive = true;
1206 atom.name = name_str_index;
1207 atom.alignment = required_alignment;
1208 atom.size = code.len;
1209 atom.out_n_sect = output_section_index;
1210
1211 try atom.allocate(macho_file);
1212 errdefer self.freeDeclMetadata(macho_file, symbol_index);
1213
1214 sym.value = 0;
1215 sym.flags.needs_zig_got = true;
1216 nlist.n_value = 0;
1217
1218 if (!macho_file.base.isRelocatable()) {
1219 const gop = try sym.getOrCreateZigGotEntry(symbol_index, macho_file);
1220 try macho_file.zig_got.writeOne(macho_file, gop.index);
1221 }
1222
1223 const sect = macho_file.sections.items(.header)[output_section_index];
1224 const file_offset = sect.offset + atom.value - sect.addr;
1225 try macho_file.base.file.?.pwriteAll(code, file_offset);
1226}
1227
1228/// Must be called only after a successful call to `updateDecl`.
1229pub fn updateDeclLineNumber(
1230 self: *ZigObject,
1231 mod: *Module,
1232 decl_index: InternPool.DeclIndex,
1233) !void {
1234 _ = self;
1235 _ = mod;
1236 _ = decl_index;
1237 // TODO: Dwarf
1238}
1239
1240pub fn deleteDeclExport(
1241 self: *ZigObject,
1242 macho_file: *MachO,
1243 decl_index: InternPool.DeclIndex,
1244 name: InternPool.NullTerminatedString,
1245) void {
1246 const metadata = self.decls.getPtr(decl_index) orelse return;
1247
1248 const mod = macho_file.base.comp.module.?;
1249 const exp_name = mod.intern_pool.stringToSlice(name);
1250 const nlist_index = metadata.@"export"(self, macho_file, exp_name) orelse return;
1251
1252 log.debug("deleting export '{s}'", .{exp_name});
1253
1254 const nlist = &self.symtab.items(.nlist)[nlist_index.*];
1255 self.symtab.items(.size)[nlist_index.*] = 0;
1256 _ = self.globals_lookup.remove(nlist.n_strx);
1257 const sym_index = macho_file.globals.get(nlist.n_strx).?;
1258 const sym = macho_file.getSymbol(sym_index);
1259 if (sym.file == self.index) {
1260 _ = macho_file.globals.swapRemove(nlist.n_strx);
1261 sym.* = .{};
1262 }
1263 nlist.* = MachO.null_sym;
1264}
1265
1266pub fn getGlobalSymbol(self: *ZigObject, macho_file: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
1267 _ = lib_name;
1268 const gpa = macho_file.base.comp.gpa;
1269 const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
1270 defer gpa.free(sym_name);
1271 const off = try macho_file.strings.insert(gpa, sym_name);
1272 const lookup_gop = try self.globals_lookup.getOrPut(gpa, off);
1273 if (!lookup_gop.found_existing) {
1274 const nlist_index = try self.addNlist(gpa);
1275 const nlist = &self.symtab.items(.nlist)[nlist_index];
1276 nlist.n_strx = off;
1277 nlist.n_type = macho.N_EXT;
1278 lookup_gop.value_ptr.* = nlist_index;
1279 const gop = try macho_file.getOrCreateGlobal(off);
1280 try self.symbols.append(gpa, gop.index);
1281 }
1282 return lookup_gop.value_ptr.*;
1283}
1284
1285pub fn getOrCreateMetadataForDecl(
1286 self: *ZigObject,
1287 macho_file: *MachO,
1288 decl_index: InternPool.DeclIndex,
1289) !Symbol.Index {
1290 const gpa = macho_file.base.comp.gpa;
1291 const gop = try self.decls.getOrPut(gpa, decl_index);
1292 if (!gop.found_existing) {
1293 const any_non_single_threaded = macho_file.base.comp.config.any_non_single_threaded;
1294 const sym_index = try self.addAtom(macho_file);
1295 const mod = macho_file.base.comp.module.?;
1296 const decl = mod.declPtr(decl_index);
1297 const sym = macho_file.getSymbol(sym_index);
1298 if (decl.getOwnedVariable(mod)) |variable| {
1299 if (variable.is_threadlocal and any_non_single_threaded) {
1300 sym.flags.tlv = true;
1301 }
1302 }
1303 if (!sym.flags.tlv) {
1304 sym.flags.needs_zig_got = true;
1305 }
1306 gop.value_ptr.* = .{ .symbol_index = sym_index };
1307 }
1308 return gop.value_ptr.symbol_index;
1309}
1310
1311pub fn getOrCreateMetadataForLazySymbol(
1312 self: *ZigObject,
1313 macho_file: *MachO,
1314 lazy_sym: link.File.LazySymbol,
1315) !Symbol.Index {
1316 const gpa = macho_file.base.comp.gpa;
1317 const mod = macho_file.base.comp.module.?;
1318 const gop = try self.lazy_syms.getOrPut(gpa, lazy_sym.getDecl(mod));
1319 errdefer _ = if (!gop.found_existing) self.lazy_syms.pop();
1320 if (!gop.found_existing) gop.value_ptr.* = .{};
1321 const metadata: struct {
1322 symbol_index: *Symbol.Index,
1323 state: *LazySymbolMetadata.State,
1324 } = switch (lazy_sym.kind) {
1325 .code => .{
1326 .symbol_index = &gop.value_ptr.text_symbol_index,
1327 .state = &gop.value_ptr.text_state,
1328 },
1329 .const_data => .{
1330 .symbol_index = &gop.value_ptr.const_symbol_index,
1331 .state = &gop.value_ptr.const_state,
1332 },
1333 };
1334 switch (metadata.state.*) {
1335 .unused => {
1336 const symbol_index = try self.addAtom(macho_file);
1337 const sym = macho_file.getSymbol(symbol_index);
1338 sym.flags.needs_zig_got = true;
1339 metadata.symbol_index.* = symbol_index;
1340 },
1341 .pending_flush => return metadata.symbol_index.*,
1342 .flushed => {},
1343 }
1344 metadata.state.* = .pending_flush;
1345 const symbol_index = metadata.symbol_index.*;
1346 // anyerror needs to be deferred until flushModule
1347 if (lazy_sym.getDecl(mod) != .none) try self.updateLazySymbol(macho_file, lazy_sym, symbol_index);
1348 return symbol_index;
1349}
1350
1351pub fn asFile(self: *ZigObject) File {
1352 return .{ .zig_object = self };
1353}
1354
1355pub fn fmtSymtab(self: *ZigObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
1356 return .{ .data = .{
1357 .self = self,
1358 .macho_file = macho_file,
1359 } };
1360}
1361
1362const FormatContext = struct {
1363 self: *ZigObject,
1364 macho_file: *MachO,
1365};
1366
1367fn formatSymtab(
1368 ctx: FormatContext,
1369 comptime unused_fmt_string: []const u8,
1370 options: std.fmt.FormatOptions,
1371 writer: anytype,
1372) !void {
1373 _ = unused_fmt_string;
1374 _ = options;
1375 try writer.writeAll(" symbols\n");
1376 for (ctx.self.symbols.items) |index| {
1377 const sym = ctx.macho_file.getSymbol(index);
1378 try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)});
1379 }
1380}
1381
1382pub fn fmtAtoms(self: *ZigObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
1383 return .{ .data = .{
1384 .self = self,
1385 .macho_file = macho_file,
1386 } };
1387}
1388
1389fn formatAtoms(
1390 ctx: FormatContext,
1391 comptime unused_fmt_string: []const u8,
1392 options: std.fmt.FormatOptions,
1393 writer: anytype,
1394) !void {
1395 _ = unused_fmt_string;
1396 _ = options;
1397 try writer.writeAll(" atoms\n");
1398 for (ctx.self.atoms.items) |atom_index| {
1399 const atom = ctx.macho_file.getAtom(atom_index) orelse continue;
1400 try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
1401 }
1402}
1403
1404const DeclMetadata = struct {
1405 symbol_index: Symbol.Index,
1406 /// A list of all exports aliases of this Decl.
1407 exports: std.ArrayListUnmanaged(Symbol.Index) = .{},
1408
1409 fn @"export"(m: DeclMetadata, zig_object: *ZigObject, macho_file: *MachO, name: []const u8) ?*u32 {
1410 for (m.exports.items) |*exp| {
1411 const nlist = zig_object.symtab.items(.nlist)[exp.*];
1412 const exp_name = macho_file.strings.getAssumeExists(nlist.n_strx);
1413 if (mem.eql(u8, name, exp_name)) return exp;
1414 }
1415 return null;
1416 }
1417};
1418
1419const LazySymbolMetadata = struct {
1420 const State = enum { unused, pending_flush, flushed };
1421 text_symbol_index: Symbol.Index = undefined,
1422 const_symbol_index: Symbol.Index = undefined,
1423 text_state: State = .unused,
1424 const_state: State = .unused,
1425};
1426
1427const TlvInitializer = struct {
1428 symbol_index: Symbol.Index,
1429 data: []const u8 = &[0]u8{},
1430
1431 fn deinit(tlv_init: *TlvInitializer, allocator: Allocator) void {
1432 allocator.free(tlv_init.data);
1433 }
1434};
1435
1436const DeclTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata);
1437const UnnamedConstTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Symbol.Index));
1438const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata);
1439const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata);
1440const RelocationTable = std.ArrayListUnmanaged(std.ArrayListUnmanaged(Relocation));
1441const TlvInitializerTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlvInitializer);
1442
1443const assert = std.debug.assert;
1444const builtin = @import("builtin");
1445const codegen = @import("../../codegen.zig");
1446const link = @import("../../link.zig");
1447const log = std.log.scoped(.link);
1448const macho = std.macho;
1449const mem = std.mem;
1450const trace = @import("../../tracy.zig").trace;
1451const std = @import("std");
1452
1453const Air = @import("../../Air.zig");
1454const Allocator = std.mem.Allocator;
1455const Archive = @import("Archive.zig");
1456const Atom = @import("Atom.zig");
1457const Dwarf = @import("../Dwarf.zig");
1458const File = @import("file.zig").File;
1459const InternPool = @import("../../InternPool.zig");
1460const Liveness = @import("../../Liveness.zig");
1461const MachO = @import("../MachO.zig");
1462const Nlist = Object.Nlist;
1463const Module = @import("../../Module.zig");
1464const Object = @import("Object.zig");
1465const Relocation = @import("Relocation.zig");
1466const Symbol = @import("Symbol.zig");
1467const StringTable = @import("../StringTable.zig");
1468const Type = @import("../../type.zig").Type;
1469const Value = @import("../../value.zig").Value;
1470const TypedValue = @import("../../TypedValue.zig");
1471const ZigObject = @This();
src/link/MachO/dead_strip.zig+145-431
......@@ -1,495 +1,209 @@
1//! An algorithm for dead stripping of unreferenced Atoms.
2
31pub fn gcAtoms(macho_file: *MachO) !void {
4 const comp = macho_file.base.comp;
5 const gpa = comp.gpa;
6
7 var arena = std.heap.ArenaAllocator.init(gpa);
8 defer arena.deinit();
9
10 var roots = AtomTable.init(arena.allocator());
11 try roots.ensureUnusedCapacity(@as(u32, @intCast(macho_file.globals.items.len)));
2 const gpa = macho_file.base.comp.gpa;
123
13 var alive = AtomTable.init(arena.allocator());
14 try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len)));
4 var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
5 defer objects.deinit();
6 for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index);
7 if (macho_file.internal_object) |index| objects.appendAssumeCapacity(index);
158
16 try collectRoots(macho_file, &roots);
17 mark(macho_file, roots, &alive);
18 prune(macho_file, alive);
19}
9 var roots = std.ArrayList(*Atom).init(gpa);
10 defer roots.deinit();
2011
21fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void {
22 const sym = macho_file.getSymbol(sym_loc);
23 assert(!sym.undf());
24 const object = &macho_file.objects.items[file];
25 const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error
26 log.debug("root(ATOM({d}, %{d}, {d}))", .{
27 atom_index,
28 macho_file.getAtom(atom_index).sym_index,
29 file,
30 });
31 _ = try roots.getOrPut(atom_index);
12 try collectRoots(&roots, objects.items, macho_file);
13 mark(roots.items, objects.items, macho_file);
14 prune(objects.items, macho_file);
3215}
3316
34fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void {
35 log.debug("collecting roots", .{});
36
37 const comp = macho_file.base.comp;
38
39 switch (comp.config.output_mode) {
40 .Exe => {
41 // Add entrypoint as GC root
42 if (macho_file.getEntryPoint()) |global| {
43 if (global.getFile()) |file| {
44 try addRoot(macho_file, roots, file, global);
45 } else {
46 assert(macho_file.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
47 }
48 }
49 },
50 else => |other| {
51 assert(other == .Lib);
52 // Add exports as GC roots
53 for (macho_file.globals.items) |global| {
54 const sym = macho_file.getSymbol(global);
55 if (sym.undf()) continue;
56 if (sym.n_desc == MachO.N_BOUNDARY) continue;
17fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
18 for (objects) |index| {
19 const object = macho_file.getFile(index).?;
20 for (object.getSymbols()) |sym_index| {
21 const sym = macho_file.getSymbol(sym_index);
22 const file = sym.getFile(macho_file) orelse continue;
23 if (file.getIndex() != index) continue;
24 if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global))
25 try markSymbol(sym, roots, macho_file);
26 }
5727
58 if (global.getFile()) |file| {
59 try addRoot(macho_file, roots, file, global);
60 }
28 for (object.getAtoms()) |atom_index| {
29 const atom = macho_file.getAtom(atom_index).?;
30 const isec = atom.getInputSection(macho_file);
31 switch (isec.type()) {
32 macho.S_MOD_INIT_FUNC_POINTERS,
33 macho.S_MOD_TERM_FUNC_POINTERS,
34 => if (markAtom(atom)) try roots.append(atom),
35
36 else => if (isec.isDontDeadStrip() and markAtom(atom)) {
37 try roots.append(atom);
38 },
6139 }
62 },
63 }
64
65 // Add all symbols force-defined by the user.
66 for (comp.force_undefined_symbols.keys()) |sym_name| {
67 const global_index = macho_file.resolver.get(sym_name).?;
68 const global = macho_file.globals.items[global_index];
69 const sym = macho_file.getSymbol(global);
70 assert(!sym.undf());
71 try addRoot(macho_file, roots, global.getFile().?, global);
40 }
7241 }
7342
74 for (macho_file.objects.items) |object| {
75 const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
76
77 for (object.atoms.items) |atom_index| {
78 const is_gc_root = blk: {
79 // Modelled after ld64 which treats each object file compiled without MH_SUBSECTIONS_VIA_SYMBOLS
80 // as a root.
81 if (!has_subsections) break :blk true;
82
83 const atom = macho_file.getAtom(atom_index);
84 const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
85 source_sym.n_sect - 1
86 else sect_id: {
87 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
88 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
89 break :sect_id sect_id;
90 };
91 const source_sect = object.getSourceSection(sect_id);
92 if (source_sect.isDontDeadStrip()) break :blk true;
93 switch (source_sect.type()) {
94 macho.S_MOD_INIT_FUNC_POINTERS,
95 macho.S_MOD_TERM_FUNC_POINTERS,
96 => break :blk true,
97 else => break :blk false,
98 }
99 };
100
101 if (is_gc_root) {
102 _ = try roots.getOrPut(atom_index);
103
104 log.debug("root(ATOM({d}, %{d}, {?d}))", .{
105 atom_index,
106 macho_file.getAtom(atom_index).sym_index,
107 macho_file.getAtom(atom_index).getFile(),
108 });
109 }
43 for (macho_file.objects.items) |index| {
44 for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| {
45 const cu = macho_file.getUnwindRecord(cu_index);
46 if (!cu.alive) continue;
47 if (cu.getFde(macho_file)) |fde| {
48 if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
49 } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
11050 }
11151 }
112}
113
114fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void {
115 if (alive.contains(atom_index)) return;
116
117 const atom = macho_file.getAtom(atom_index);
118 const sym_loc = atom.getSymbolWithLoc();
11952
120 log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
121
122 alive.putAssumeCapacityNoClobber(atom_index, {});
123
124 const target = macho_file.base.comp.root_mod.resolved_target.result;
125 const cpu_arch = target.cpu.arch;
126
127 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
128 const header = macho_file.sections.items(.header)[sym.n_sect - 1];
129 if (header.isZerofill()) return;
130
131 const code = Atom.getAtomCode(macho_file, atom_index);
132 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
133 const ctx = Atom.getRelocContext(macho_file, atom_index);
134
135 for (relocs) |rel| {
136 const reloc_target = switch (cpu_arch) {
137 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
138 .ARM64_RELOC_ADDEND => continue,
139 else => Atom.parseRelocTarget(macho_file, .{
140 .object_id = atom.getFile().?,
141 .rel = rel,
142 .code = code,
143 .base_offset = ctx.base_offset,
144 .base_addr = ctx.base_addr,
145 }),
146 },
147 .x86_64 => Atom.parseRelocTarget(macho_file, .{
148 .object_id = atom.getFile().?,
149 .rel = rel,
150 .code = code,
151 .base_offset = ctx.base_offset,
152 .base_addr = ctx.base_addr,
153 }),
154 else => unreachable,
155 };
156 const target_sym = macho_file.getSymbol(reloc_target);
157
158 if (target_sym.undf()) continue;
159 if (reloc_target.getFile() == null) {
160 const target_sym_name = macho_file.getSymbolName(reloc_target);
161 if (mem.eql(u8, "__mh_execute_header", target_sym_name)) continue;
162 if (mem.eql(u8, "___dso_handle", target_sym_name)) continue;
53 for (macho_file.undefined_symbols.items) |sym_index| {
54 const sym = macho_file.getSymbol(sym_index);
55 try markSymbol(sym, roots, macho_file);
56 }
16357
164 unreachable; // referenced symbol not found
58 for (&[_]?Symbol.Index{
59 macho_file.entry_index,
60 macho_file.dyld_stub_binder_index,
61 macho_file.objc_msg_send_index,
62 }) |index| {
63 if (index) |idx| {
64 const sym = macho_file.getSymbol(idx);
65 try markSymbol(sym, roots, macho_file);
16566 }
166
167 const object = macho_file.objects.items[reloc_target.getFile().?];
168 const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index).?;
169 log.debug(" following ATOM({d}, %{d}, {?d})", .{
170 target_atom_index,
171 macho_file.getAtom(target_atom_index).sym_index,
172 macho_file.getAtom(target_atom_index).getFile(),
173 });
174
175 markLive(macho_file, target_atom_index, alive);
17667 }
17768}
17869
179fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool {
180 const atom = macho_file.getAtom(atom_index);
181 const sym_loc = atom.getSymbolWithLoc();
182
183 log.debug("refersLive(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });
184
185 const target = macho_file.base.comp.root_mod.resolved_target.result;
186 const cpu_arch = target.cpu.arch;
187
188 const sym = macho_file.getSymbol(sym_loc);
189 const header = macho_file.sections.items(.header)[sym.n_sect - 1];
190 assert(!header.isZerofill());
191
192 const code = Atom.getAtomCode(macho_file, atom_index);
193 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
194 const ctx = Atom.getRelocContext(macho_file, atom_index);
195
196 for (relocs) |rel| {
197 const reloc_target = switch (cpu_arch) {
198 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
199 .ARM64_RELOC_ADDEND => continue,
200 else => Atom.parseRelocTarget(macho_file, .{
201 .object_id = atom.getFile().?,
202 .rel = rel,
203 .code = code,
204 .base_offset = ctx.base_offset,
205 .base_addr = ctx.base_addr,
206 }),
207 },
208 .x86_64 => Atom.parseRelocTarget(macho_file, .{
209 .object_id = atom.getFile().?,
210 .rel = rel,
211 .code = code,
212 .base_offset = ctx.base_offset,
213 .base_addr = ctx.base_addr,
214 }),
215 else => unreachable,
216 };
217
218 const object = macho_file.objects.items[reloc_target.getFile().?];
219 const target_atom_index = object.getAtomIndexForSymbol(reloc_target.sym_index) orelse {
220 log.debug("atom for symbol '{s}' not found; skipping...", .{macho_file.getSymbolName(reloc_target)});
221 continue;
222 };
223 if (alive.contains(target_atom_index)) {
224 log.debug(" refers live ATOM({d}, %{d}, {?d})", .{
225 target_atom_index,
226 macho_file.getAtom(target_atom_index).sym_index,
227 macho_file.getAtom(target_atom_index).getFile(),
228 });
229 return true;
230 }
231 }
70fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void {
71 const atom = sym.getAtom(macho_file) orelse return;
72 if (markAtom(atom)) try roots.append(atom);
73}
23274
233 return false;
75fn markAtom(atom: *Atom) bool {
76 const already_visited = atom.flags.visited;
77 atom.flags.visited = true;
78 return atom.flags.alive and !already_visited;
23479}
23580
236fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void {
237 var it = roots.keyIterator();
238 while (it.next()) |root| {
239 markLive(macho_file, root.*, alive);
81fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void {
82 for (roots) |root| {
83 markLive(root, macho_file);
24084 }
24185
24286 var loop: bool = true;
24387 while (loop) {
24488 loop = false;
24589
246 for (macho_file.objects.items) |object| {
247 for (object.atoms.items) |atom_index| {
248 if (alive.contains(atom_index)) continue;
249
250 const atom = macho_file.getAtom(atom_index);
251 const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|
252 source_sym.n_sect - 1
253 else blk: {
254 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
255 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
256 break :blk sect_id;
257 };
258 const source_sect = object.getSourceSection(sect_id);
259
260 if (source_sect.isDontDeadStripIfReferencesLive()) {
261 if (refersLive(macho_file, atom_index, alive.*)) {
262 markLive(macho_file, atom_index, alive);
263 loop = true;
264 }
90 for (objects) |index| {
91 for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
92 const atom = macho_file.getAtom(atom_index).?;
93 const isec = atom.getInputSection(macho_file);
94 if (isec.isDontDeadStripIfReferencesLive() and
95 !(mem.eql(u8, isec.sectName(), "__eh_frame") or
96 mem.eql(u8, isec.sectName(), "__compact_unwind") or
97 isec.attrs() & macho.S_ATTR_DEBUG != 0) and
98 !atom.flags.alive and refersLive(atom, macho_file))
99 {
100 markLive(atom, macho_file);
101 loop = true;
265102 }
266103 }
267104 }
268105 }
269
270 for (macho_file.objects.items, 0..) |_, object_id| {
271 // Traverse unwind and eh_frame records noting if the source symbol has been marked, and if so,
272 // marking all references as live.
273 markUnwindRecords(macho_file, @as(u32, @intCast(object_id)), alive);
274 }
275106}
276107
277fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void {
278 const object = &macho_file.objects.items[object_id];
279 const target = macho_file.base.comp.root_mod.resolved_target.result;
280 const cpu_arch = target.cpu.arch;
281
282 const unwind_records = object.getUnwindRecords();
108fn markLive(atom: *Atom, macho_file: *MachO) void {
109 assert(atom.flags.visited);
110 atom.flags.alive = true;
111 track_live_log.debug("{}marking live atom({d},{s})", .{
112 track_live_level,
113 atom.atom_index,
114 atom.getName(macho_file),
115 });
283116
284 for (object.exec_atoms.items) |atom_index| {
285 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
117 if (build_options.enable_logging)
118 track_live_level.incr();
286119
287 if (!object.hasUnwindRecords()) {
288 if (alive.contains(atom_index)) {
289 // Mark references live and continue.
290 markEhFrameRecords(macho_file, object_id, atom_index, alive);
291 } else {
292 while (inner_syms_it.next()) |sym| {
293 if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
294 // Mark dead and continue.
295 object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
296 }
297 }
298 }
299 continue;
120 for (atom.getRelocs(macho_file)) |rel| {
121 const target_atom = switch (rel.tag) {
122 .local => rel.getTargetAtom(macho_file),
123 .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
124 };
125 if (target_atom) |ta| {
126 if (markAtom(ta)) markLive(ta, macho_file);
300127 }
128 }
301129
302 while (inner_syms_it.next()) |sym| {
303 const record_id = object.unwind_records_lookup.get(sym) orelse continue;
304 if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
305 if (!alive.contains(atom_index)) {
306 // Mark the record dead and continue.
307 object.unwind_relocs_lookup[record_id].dead = true;
308 if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
309 object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
310 }
311 continue;
312 }
130 for (atom.getUnwindRecords(macho_file)) |cu_index| {
131 const cu = macho_file.getUnwindRecord(cu_index);
132 const cu_atom = cu.getAtom(macho_file);
133 if (markAtom(cu_atom)) markLive(cu_atom, macho_file);
313134
314 const record = unwind_records[record_id];
315 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
316 markEhFrameRecords(macho_file, object_id, atom_index, alive);
317 } else {
318 if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {
319 const reloc_target = Atom.parseRelocTarget(macho_file, .{
320 .object_id = object_id,
321 .rel = rel,
322 .code = mem.asBytes(&record),
323 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
324 });
325 const target_sym = macho_file.getSymbol(reloc_target);
326 if (!target_sym.undf()) {
327 const target_object = macho_file.objects.items[reloc_target.getFile().?];
328 const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
329 markLive(macho_file, target_atom_index, alive);
330 }
331 }
135 if (cu.getLsdaAtom(macho_file)) |lsda| {
136 if (markAtom(lsda)) markLive(lsda, macho_file);
137 }
138 if (cu.getFde(macho_file)) |fde| {
139 const fde_atom = fde.getAtom(macho_file);
140 if (markAtom(fde_atom)) markLive(fde_atom, macho_file);
332141
333 if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {
334 const reloc_target = Atom.parseRelocTarget(macho_file, .{
335 .object_id = object_id,
336 .rel = rel,
337 .code = mem.asBytes(&record),
338 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
339 });
340 const target_object = macho_file.objects.items[reloc_target.getFile().?];
341 const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
342 markLive(macho_file, target_atom_index, alive);
343 }
142 if (fde.getLsdaAtom(macho_file)) |lsda| {
143 if (markAtom(lsda)) markLive(lsda, macho_file);
344144 }
345145 }
346146 }
347147}
348148
349fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void {
350 const target = macho_file.base.comp.root_mod.resolved_target.result;
351 const cpu_arch = target.cpu.arch;
352 const object = &macho_file.objects.items[object_id];
353 var it = object.getEhFrameRecordsIterator();
354 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
355
356 while (inner_syms_it.next()) |sym| {
357 const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
358 it.seekTo(fde_offset);
359 const fde = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
360
361 const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
362 const cie_offset = fde_offset + 4 - cie_ptr;
363 it.seekTo(cie_offset);
364 const cie = (it.next() catch continue).?; // We don't care about the error at this point since it was already handled
365
366 switch (cpu_arch) {
367 .aarch64 => {
368 // Mark FDE references which should include any referenced LSDA record
369 const relocs = eh_frame.getRelocs(macho_file, object_id, fde_offset);
370 for (relocs) |rel| {
371 const reloc_target = Atom.parseRelocTarget(macho_file, .{
372 .object_id = object_id,
373 .rel = rel,
374 .code = fde.data,
375 .base_offset = @as(i32, @intCast(fde_offset)) + 4,
376 });
377 const target_sym = macho_file.getSymbol(reloc_target);
378 if (!target_sym.undf()) blk: {
379 const target_object = macho_file.objects.items[reloc_target.getFile().?];
380 const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index) orelse
381 break :blk;
382 markLive(macho_file, target_atom_index, alive);
383 }
384 }
385 },
386 .x86_64 => {
387 const sect = object.getSourceSection(object.eh_frame_sect_id.?);
388 const lsda_ptr = fde.getLsdaPointer(cie, .{
389 .base_addr = sect.addr,
390 .base_offset = fde_offset,
391 }) catch continue; // We don't care about the error at this point since it was already handled
392 if (lsda_ptr) |lsda_address| {
393 // Mark LSDA record as live
394 const sym_index = object.getSymbolByAddress(lsda_address, null);
395 const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
396 markLive(macho_file, target_atom_index, alive);
397 }
398 },
399 else => unreachable,
149fn refersLive(atom: *Atom, macho_file: *MachO) bool {
150 for (atom.getRelocs(macho_file)) |rel| {
151 const target_atom = switch (rel.tag) {
152 .local => rel.getTargetAtom(macho_file),
153 .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
154 };
155 if (target_atom) |ta| {
156 if (ta.flags.alive) return true;
400157 }
158 }
159 return false;
160}
401161
402 // Mark CIE references which should include any referenced personalities
403 // that are defined locally.
404 if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| {
405 const target_sym = macho_file.getSymbol(reloc_target);
406 if (!target_sym.undf()) {
407 const target_object = macho_file.objects.items[reloc_target.getFile().?];
408 const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;
409 markLive(macho_file, target_atom_index, alive);
162fn prune(objects: []const File.Index, macho_file: *MachO) void {
163 for (objects) |index| {
164 for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
165 const atom = macho_file.getAtom(atom_index).?;
166 if (atom.flags.alive and !atom.flags.visited) {
167 atom.flags.alive = false;
168 atom.markUnwindRecordsDead(macho_file);
410169 }
411170 }
412171 }
413172}
414173
415fn prune(macho_file: *MachO, alive: AtomTable) void {
416 log.debug("pruning dead atoms", .{});
417 for (macho_file.objects.items) |*object| {
418 var i: usize = 0;
419 while (i < object.atoms.items.len) {
420 const atom_index = object.atoms.items[i];
421 if (alive.contains(atom_index)) {
422 i += 1;
423 continue;
424 }
425
426 const atom = macho_file.getAtom(atom_index);
427 const sym_loc = atom.getSymbolWithLoc();
428
429 log.debug("prune(ATOM({d}, %{d}, {?d}))", .{
430 atom_index,
431 sym_loc.sym_index,
432 sym_loc.getFile(),
433 });
434 log.debug(" {s} in {s}", .{ macho_file.getSymbolName(sym_loc), object.name });
435
436 const sym = macho_file.getSymbolPtr(sym_loc);
437 const sect_id = sym.n_sect - 1;
438 var section = macho_file.sections.get(sect_id);
439 section.header.size -= atom.size;
440
441 if (atom.prev_index) |prev_index| {
442 const prev = macho_file.getAtomPtr(prev_index);
443 prev.next_index = atom.next_index;
444 } else {
445 if (atom.next_index) |next_index| {
446 section.first_atom_index = next_index;
447 }
448 }
449 if (atom.next_index) |next_index| {
450 const next = macho_file.getAtomPtr(next_index);
451 next.prev_index = atom.prev_index;
452 } else {
453 if (atom.prev_index) |prev_index| {
454 section.last_atom_index = prev_index;
455 } else {
456 assert(section.header.size == 0);
457 section.first_atom_index = null;
458 section.last_atom_index = null;
459 }
460 }
461
462 macho_file.sections.set(sect_id, section);
463 _ = object.atoms.swapRemove(i);
464
465 sym.n_desc = MachO.N_DEAD;
174const Level = struct {
175 value: usize = 0,
466176
467 var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
468 while (inner_sym_it.next()) |inner| {
469 const inner_sym = macho_file.getSymbolPtr(inner);
470 inner_sym.n_desc = MachO.N_DEAD;
471 }
177 fn incr(self: *@This()) void {
178 self.value += 1;
179 }
472180
473 if (Atom.getSectionAlias(macho_file, atom_index)) |alias| {
474 const alias_sym = macho_file.getSymbolPtr(alias);
475 alias_sym.n_desc = MachO.N_DEAD;
476 }
477 }
181 pub fn format(
182 self: *const @This(),
183 comptime unused_fmt_string: []const u8,
184 options: std.fmt.FormatOptions,
185 writer: anytype,
186 ) !void {
187 _ = unused_fmt_string;
188 _ = options;
189 try writer.writeByteNTimes(' ', self.value);
478190 }
479}
191};
192
193var track_live_level: Level = .{};
480194
481const std = @import("std");
482195const assert = std.debug.assert;
483const eh_frame = @import("eh_frame.zig");
196const build_options = @import("build_options");
484197const log = std.log.scoped(.dead_strip);
485198const macho = std.macho;
486199const math = std.math;
487200const mem = std.mem;
201const trace = @import("../../tracy.zig").trace;
202const track_live_log = std.log.scoped(.dead_strip_track_live);
203const std = @import("std");
488204
489205const Allocator = mem.Allocator;
490206const Atom = @import("Atom.zig");
207const File = @import("file.zig").File;
491208const MachO = @import("../MachO.zig");
492const SymbolWithLoc = MachO.SymbolWithLoc;
493const UnwindInfo = @import("UnwindInfo.zig");
494
495const AtomTable = std.AutoHashMap(Atom.Index, void);
209const Symbol = @import("Symbol.zig");
src/link/MachO/dyld_info/Rebase.zig+14-12
......@@ -1,7 +1,18 @@
1const Rebase = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const leb = std.leb;
6const log = std.log.scoped(.link_dyld_info);
7const macho = std.macho;
8const testing = std.testing;
9
10const Allocator = std.mem.Allocator;
11
112entries: std.ArrayListUnmanaged(Entry) = .{},
213buffer: std.ArrayListUnmanaged(u8) = .{},
314
4const Entry = struct {
15pub const Entry = struct {
516 offset: u64,
617 segment_id: u8,
718
......@@ -28,6 +39,8 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
2839
2940 const writer = rebase.buffer.writer(gpa);
3041
42 log.debug("rebase opcodes", .{});
43
3144 std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan);
3245
3346 try setTypePointer(writer);
......@@ -561,14 +574,3 @@ test "rebase - composite" {
561574 macho.REBASE_OPCODE_DONE,
562575 }, rebase.buffer.items);
563576}
564
565const Rebase = @This();
566
567const std = @import("std");
568const assert = std.debug.assert;
569const leb = std.leb;
570const log = std.log.scoped(.dyld_info);
571const macho = std.macho;
572const testing = std.testing;
573
574const Allocator = std.mem.Allocator;
src/link/MachO/dyld_info/Trie.zig created+612
......@@ -0,0 +1,612 @@
1//! Represents export trie used in MachO executables and dynamic libraries.
2//! The purpose of an export trie is to encode as compactly as possible all
3//! export symbols for the loader `dyld`.
4//! The export trie encodes offset and other information using ULEB128
5//! encoding, and is part of the __LINKEDIT segment.
6//!
7//! Description from loader.h:
8//!
9//! The symbols exported by a dylib are encoded in a trie. This is a compact
10//! representation that factors out common prefixes. It also reduces LINKEDIT pages
11//! in RAM because it encodes all information (name, address, flags) in one small,
12//! contiguous range. The export area is a stream of nodes. The first node sequentially
13//! is the start node for the trie.
14//!
15//! Nodes for a symbol start with a uleb128 that is the length of the exported symbol
16//! information for the string so far. If there is no exported symbol, the node starts
17//! with a zero byte. If there is exported info, it follows the length.
18//!
19//! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded
20//! offset which is location of the content named by the symbol from the mach_header
21//! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags
22//! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string
23//! is zero length, then the symbol is re-export from the specified dylib with the same name.
24//! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two
25//! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers.
26//! The resolver is used by lazy pointers and must be called to get the actual address to use.
27//!
28//! After the optional exported symbol information is a byte of how many edges (0-255) that
29//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
30//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
31const Trie = @This();
32
33const std = @import("std");
34const mem = std.mem;
35const leb = std.leb;
36const log = std.log.scoped(.macho);
37const macho = std.macho;
38const testing = std.testing;
39const assert = std.debug.assert;
40const Allocator = mem.Allocator;
41
42pub const Node = struct {
43 base: *Trie,
44
45 /// Terminal info associated with this node.
46 /// If this node is not a terminal node, info is null.
47 terminal_info: ?struct {
48 /// Export flags associated with this exported symbol.
49 export_flags: u64,
50 /// VM address offset wrt to the section this symbol is defined against.
51 vmaddr_offset: u64,
52 } = null,
53
54 /// Offset of this node in the trie output byte stream.
55 trie_offset: ?u64 = null,
56
57 /// List of all edges originating from this node.
58 edges: std.ArrayListUnmanaged(Edge) = .{},
59
60 node_dirty: bool = true,
61
62 /// Edge connecting to nodes in the trie.
63 pub const Edge = struct {
64 from: *Node,
65 to: *Node,
66 label: []u8,
67
68 fn deinit(self: *Edge, allocator: Allocator) void {
69 self.to.deinit(allocator);
70 allocator.destroy(self.to);
71 allocator.free(self.label);
72 self.from = undefined;
73 self.to = undefined;
74 self.label = undefined;
75 }
76 };
77
78 fn deinit(self: *Node, allocator: Allocator) void {
79 for (self.edges.items) |*edge| {
80 edge.deinit(allocator);
81 }
82 self.edges.deinit(allocator);
83 }
84
85 /// Inserts a new node starting from `self`.
86 fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
87 // Check for match with edges from this node.
88 for (self.edges.items) |*edge| {
89 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
90 if (match == 0) continue;
91 if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
92
93 // Found a match, need to splice up nodes.
94 // From: A -> B
95 // To: A -> C -> B
96 const mid = try allocator.create(Node);
97 mid.* = .{ .base = self.base };
98 const to_label = try allocator.dupe(u8, edge.label[match..]);
99 allocator.free(edge.label);
100 const to_node = edge.to;
101 edge.to = mid;
102 edge.label = try allocator.dupe(u8, label[0..match]);
103 self.base.node_count += 1;
104
105 try mid.edges.append(allocator, .{
106 .from = mid,
107 .to = to_node,
108 .label = to_label,
109 });
110
111 return if (match == label.len) mid else mid.put(allocator, label[match..]);
112 }
113
114 // Add a new node.
115 const node = try allocator.create(Node);
116 node.* = .{ .base = self.base };
117 self.base.node_count += 1;
118
119 try self.edges.append(allocator, .{
120 .from = self,
121 .to = node,
122 .label = try allocator.dupe(u8, label),
123 });
124
125 return node;
126 }
127
128 /// Recursively parses the node from the input byte stream.
129 fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
130 self.node_dirty = true;
131 const trie_offset = try reader.context.getPos();
132 self.trie_offset = trie_offset;
133
134 var nread: usize = 0;
135
136 const node_size = try leb.readULEB128(u64, reader);
137 if (node_size > 0) {
138 const export_flags = try leb.readULEB128(u64, reader);
139 // TODO Parse special flags.
140 assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
141 export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
142
143 const vmaddr_offset = try leb.readULEB128(u64, reader);
144
145 self.terminal_info = .{
146 .export_flags = export_flags,
147 .vmaddr_offset = vmaddr_offset,
148 };
149 }
150
151 const nedges = try reader.readByte();
152 self.base.node_count += nedges;
153
154 nread += (try reader.context.getPos()) - trie_offset;
155
156 var i: usize = 0;
157 while (i < nedges) : (i += 1) {
158 const edge_start_pos = try reader.context.getPos();
159
160 const label = blk: {
161 var label_buf = std.ArrayList(u8).init(allocator);
162 while (true) {
163 const next = try reader.readByte();
164 if (next == @as(u8, 0))
165 break;
166 try label_buf.append(next);
167 }
168 break :blk try label_buf.toOwnedSlice();
169 };
170
171 const seek_to = try leb.readULEB128(u64, reader);
172 const return_pos = try reader.context.getPos();
173
174 nread += return_pos - edge_start_pos;
175 try reader.context.seekTo(seek_to);
176
177 const node = try allocator.create(Node);
178 node.* = .{ .base = self.base };
179
180 nread += try node.read(allocator, reader);
181 try self.edges.append(allocator, .{
182 .from = self,
183 .to = node,
184 .label = label,
185 });
186 try reader.context.seekTo(return_pos);
187 }
188
189 return nread;
190 }
191
192 /// Writes this node to a byte stream.
193 /// The children of this node *are* not written to the byte stream
194 /// recursively. To write all nodes to a byte stream in sequence,
195 /// iterate over `Trie.ordered_nodes` and call this method on each node.
196 /// This is one of the requirements of the MachO.
197 /// Panics if `finalize` was not called before calling this method.
198 fn write(self: Node, writer: anytype) !void {
199 assert(!self.node_dirty);
200 if (self.terminal_info) |info| {
201 // Terminal node info: encode export flags and vmaddr offset of this symbol.
202 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
203 var info_stream = std.io.fixedBufferStream(&info_buf);
204 // TODO Implement for special flags.
205 assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
206 info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
207 try leb.writeULEB128(info_stream.writer(), info.export_flags);
208 try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
209
210 // Encode the size of the terminal node info.
211 var size_buf: [@sizeOf(u64)]u8 = undefined;
212 var size_stream = std.io.fixedBufferStream(&size_buf);
213 try leb.writeULEB128(size_stream.writer(), info_stream.pos);
214
215 // Now, write them to the output stream.
216 try writer.writeAll(size_buf[0..size_stream.pos]);
217 try writer.writeAll(info_buf[0..info_stream.pos]);
218 } else {
219 // Non-terminal node is delimited by 0 byte.
220 try writer.writeByte(0);
221 }
222 // Write number of edges (max legal number of edges is 256).
223 try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
224
225 for (self.edges.items) |edge| {
226 // Write edge label and offset to next node in trie.
227 try writer.writeAll(edge.label);
228 try writer.writeByte(0);
229 try leb.writeULEB128(writer, edge.to.trie_offset.?);
230 }
231 }
232
233 const FinalizeResult = struct {
234 /// Current size of this node in bytes.
235 node_size: u64,
236
237 /// True if the trie offset of this node in the output byte stream
238 /// would need updating; false otherwise.
239 updated: bool,
240 };
241
242 /// Updates offset of this node in the output byte stream.
243 fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
244 var stream = std.io.countingWriter(std.io.null_writer);
245 const writer = stream.writer();
246
247 var node_size: u64 = 0;
248 if (self.terminal_info) |info| {
249 try leb.writeULEB128(writer, info.export_flags);
250 try leb.writeULEB128(writer, info.vmaddr_offset);
251 try leb.writeULEB128(writer, stream.bytes_written);
252 } else {
253 node_size += 1; // 0x0 for non-terminal nodes
254 }
255 node_size += 1; // 1 byte for edge count
256
257 for (self.edges.items) |edge| {
258 const next_node_offset = edge.to.trie_offset orelse 0;
259 node_size += edge.label.len + 1;
260 try leb.writeULEB128(writer, next_node_offset);
261 }
262
263 const trie_offset = self.trie_offset orelse 0;
264 const updated = offset_in_trie != trie_offset;
265 self.trie_offset = offset_in_trie;
266 self.node_dirty = false;
267 node_size += stream.bytes_written;
268
269 return FinalizeResult{ .node_size = node_size, .updated = updated };
270 }
271};
272
273/// The root node of the trie.
274root: ?*Node = null,
275
276/// If you want to access nodes ordered in DFS fashion,
277/// you should call `finalize` first since the nodes
278/// in this container are not guaranteed to not be stale
279/// if more insertions took place after the last `finalize`
280/// call.
281ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
282
283/// The size of the trie in bytes.
284/// This value may be outdated if there were additional
285/// insertions performed after `finalize` was called.
286/// Call `finalize` before accessing this value to ensure
287/// it is up-to-date.
288size: u64 = 0,
289
290/// Number of nodes currently in the trie.
291node_count: usize = 0,
292
293trie_dirty: bool = true,
294
295/// Export symbol that is to be placed in the trie.
296pub const ExportSymbol = struct {
297 /// Name of the symbol.
298 name: []const u8,
299
300 /// Offset of this symbol's virtual memory address from the beginning
301 /// of the __TEXT segment.
302 vmaddr_offset: u64,
303
304 /// Export flags of this exported symbol.
305 export_flags: u64,
306};
307
308/// Insert a symbol into the trie, updating the prefixes in the process.
309/// This operation may change the layout of the trie by splicing edges in
310/// certain circumstances.
311pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
312 const node = try self.root.?.put(allocator, symbol.name);
313 node.terminal_info = .{
314 .vmaddr_offset = symbol.vmaddr_offset,
315 .export_flags = symbol.export_flags,
316 };
317 self.trie_dirty = true;
318}
319
320/// Finalizes this trie for writing to a byte stream.
321/// This step performs multiple passes through the trie ensuring
322/// there are no gaps after every `Node` is ULEB128 encoded.
323/// Call this method before trying to `write` the trie to a byte stream.
324pub fn finalize(self: *Trie, allocator: Allocator) !void {
325 if (!self.trie_dirty) return;
326
327 self.ordered_nodes.shrinkRetainingCapacity(0);
328 try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
329
330 var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
331 defer fifo.deinit();
332
333 try fifo.writeItem(self.root.?);
334
335 while (fifo.readItem()) |next| {
336 for (next.edges.items) |*edge| {
337 try fifo.writeItem(edge.to);
338 }
339 self.ordered_nodes.appendAssumeCapacity(next);
340 }
341
342 var more: bool = true;
343 while (more) {
344 self.size = 0;
345 more = false;
346 for (self.ordered_nodes.items) |node| {
347 const res = try node.finalize(self.size);
348 self.size += res.node_size;
349 if (res.updated) more = true;
350 }
351 }
352
353 self.trie_dirty = false;
354}
355
356const ReadError = error{
357 OutOfMemory,
358 EndOfStream,
359 Overflow,
360};
361
362/// Parse the trie from a byte stream.
363pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
364 return self.root.?.read(allocator, reader);
365}
366
367/// Write the trie to a byte stream.
368/// Panics if the trie was not finalized using `finalize` before calling this method.
369pub fn write(self: Trie, writer: anytype) !void {
370 assert(!self.trie_dirty);
371 for (self.ordered_nodes.items) |node| {
372 try node.write(writer);
373 }
374}
375
376pub fn init(self: *Trie, allocator: Allocator) !void {
377 assert(self.root == null);
378 const root = try allocator.create(Node);
379 root.* = .{ .base = self };
380 self.root = root;
381 self.node_count += 1;
382}
383
384pub fn deinit(self: *Trie, allocator: Allocator) void {
385 if (self.root) |root| {
386 root.deinit(allocator);
387 allocator.destroy(root);
388 }
389 self.ordered_nodes.deinit(allocator);
390}
391
392test "Trie node count" {
393 const gpa = testing.allocator;
394 var trie: Trie = .{};
395 defer trie.deinit(gpa);
396 try trie.init(gpa);
397
398 try testing.expectEqual(@as(usize, 1), trie.node_count);
399 try testing.expect(trie.root != null);
400
401 try trie.put(gpa, .{
402 .name = "_main",
403 .vmaddr_offset = 0,
404 .export_flags = 0,
405 });
406 try testing.expectEqual(@as(usize, 2), trie.node_count);
407
408 // Inserting the same node shouldn't update the trie.
409 try trie.put(gpa, .{
410 .name = "_main",
411 .vmaddr_offset = 0,
412 .export_flags = 0,
413 });
414 try testing.expectEqual(@as(usize, 2), trie.node_count);
415
416 try trie.put(gpa, .{
417 .name = "__mh_execute_header",
418 .vmaddr_offset = 0x1000,
419 .export_flags = 0,
420 });
421 try testing.expectEqual(@as(usize, 4), trie.node_count);
422
423 // Inserting the same node shouldn't update the trie.
424 try trie.put(gpa, .{
425 .name = "__mh_execute_header",
426 .vmaddr_offset = 0x1000,
427 .export_flags = 0,
428 });
429 try testing.expectEqual(@as(usize, 4), trie.node_count);
430 try trie.put(gpa, .{
431 .name = "_main",
432 .vmaddr_offset = 0,
433 .export_flags = 0,
434 });
435 try testing.expectEqual(@as(usize, 4), trie.node_count);
436}
437
438test "Trie basic" {
439 const gpa = testing.allocator;
440 var trie: Trie = .{};
441 defer trie.deinit(gpa);
442 try trie.init(gpa);
443
444 // root --- _st ---> node
445 try trie.put(gpa, .{
446 .name = "_st",
447 .vmaddr_offset = 0,
448 .export_flags = 0,
449 });
450 try testing.expect(trie.root.?.edges.items.len == 1);
451 try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
452
453 {
454 // root --- _st ---> node --- art ---> node
455 try trie.put(gpa, .{
456 .name = "_start",
457 .vmaddr_offset = 0,
458 .export_flags = 0,
459 });
460 try testing.expect(trie.root.?.edges.items.len == 1);
461
462 const nextEdge = &trie.root.?.edges.items[0];
463 try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
464 try testing.expect(nextEdge.to.edges.items.len == 1);
465 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
466 }
467 {
468 // root --- _ ---> node --- st ---> node --- art ---> node
469 // |
470 // | --- main ---> node
471 try trie.put(gpa, .{
472 .name = "_main",
473 .vmaddr_offset = 0,
474 .export_flags = 0,
475 });
476 try testing.expect(trie.root.?.edges.items.len == 1);
477
478 const nextEdge = &trie.root.?.edges.items[0];
479 try testing.expect(mem.eql(u8, nextEdge.label, "_"));
480 try testing.expect(nextEdge.to.edges.items.len == 2);
481 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
482 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
483
484 const nextNextEdge = &nextEdge.to.edges.items[0];
485 try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
486 }
487}
488
489fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
490 assert(expected.len > 0);
491 if (mem.eql(u8, expected, given)) return;
492 const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
493 defer testing.allocator.free(expected_fmt);
494 const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
495 defer testing.allocator.free(given_fmt);
496 const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
497 const padding = try testing.allocator.alloc(u8, idx + 5);
498 defer testing.allocator.free(padding);
499 @memset(padding, ' ');
500 std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
501 return error.TestFailed;
502}
503
504test "write Trie to a byte stream" {
505 var gpa = testing.allocator;
506 var trie: Trie = .{};
507 defer trie.deinit(gpa);
508 try trie.init(gpa);
509
510 try trie.put(gpa, .{
511 .name = "__mh_execute_header",
512 .vmaddr_offset = 0,
513 .export_flags = 0,
514 });
515 try trie.put(gpa, .{
516 .name = "_main",
517 .vmaddr_offset = 0x1000,
518 .export_flags = 0,
519 });
520
521 try trie.finalize(gpa);
522 try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
523
524 const exp_buffer = [_]u8{
525 0x0, 0x1, // node root
526 0x5f, 0x0, 0x5, // edge '_'
527 0x0, 0x2, // non-terminal node
528 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
529 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
530 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
531 0x2, 0x0, 0x0, 0x0, // terminal node
532 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
533 };
534
535 const buffer = try gpa.alloc(u8, trie.size);
536 defer gpa.free(buffer);
537 var stream = std.io.fixedBufferStream(buffer);
538 {
539 _ = try trie.write(stream.writer());
540 try expectEqualHexStrings(&exp_buffer, buffer);
541 }
542 {
543 // Writing finalized trie again should yield the same result.
544 try stream.seekTo(0);
545 _ = try trie.write(stream.writer());
546 try expectEqualHexStrings(&exp_buffer, buffer);
547 }
548}
549
550test "parse Trie from byte stream" {
551 const gpa = testing.allocator;
552
553 const in_buffer = [_]u8{
554 0x0, 0x1, // node root
555 0x5f, 0x0, 0x5, // edge '_'
556 0x0, 0x2, // non-terminal node
557 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
558 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
559 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
560 0x2, 0x0, 0x0, 0x0, // terminal node
561 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
562 };
563
564 var in_stream = std.io.fixedBufferStream(&in_buffer);
565 var trie: Trie = .{};
566 defer trie.deinit(gpa);
567 try trie.init(gpa);
568 const nread = try trie.read(gpa, in_stream.reader());
569
570 try testing.expect(nread == in_buffer.len);
571
572 try trie.finalize(gpa);
573
574 const out_buffer = try gpa.alloc(u8, trie.size);
575 defer gpa.free(out_buffer);
576 var out_stream = std.io.fixedBufferStream(out_buffer);
577 _ = try trie.write(out_stream.writer());
578 try expectEqualHexStrings(&in_buffer, out_buffer);
579}
580
581test "ordering bug" {
582 const gpa = testing.allocator;
583 var trie: Trie = .{};
584 defer trie.deinit(gpa);
585 try trie.init(gpa);
586
587 try trie.put(gpa, .{
588 .name = "_asStr",
589 .vmaddr_offset = 0x558,
590 .export_flags = 0,
591 });
592 try trie.put(gpa, .{
593 .name = "_a",
594 .vmaddr_offset = 0x8008,
595 .export_flags = 0,
596 });
597
598 try trie.finalize(gpa);
599
600 const exp_buffer = [_]u8{
601 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
602 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
603 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
604 };
605
606 const buffer = try gpa.alloc(u8, trie.size);
607 defer gpa.free(buffer);
608 var stream = std.io.fixedBufferStream(buffer);
609 // Writing finalized trie again should yield the same result.
610 _ = try trie.write(stream.writer());
611 try expectEqualHexStrings(&exp_buffer, buffer);
612}
src/link/MachO/dyld_info/bind.zig+353-613
......@@ -1,231 +1,397 @@
1pub fn Bind(comptime Ctx: type, comptime Target: type) type {
2 return struct {
3 entries: std.ArrayListUnmanaged(Entry) = .{},
4 buffer: std.ArrayListUnmanaged(u8) = .{},
5
6 const Self = @This();
7
8 const Entry = struct {
9 target: Target,
10 offset: u64,
11 segment_id: u8,
12 addend: i64,
13
14 pub fn lessThan(ctx: Ctx, entry: Entry, other: Entry) bool {
15 if (entry.segment_id == other.segment_id) {
16 if (entry.target.eql(other.target)) {
17 return entry.offset < other.offset;
18 }
19 const entry_name = ctx.getSymbolName(entry.target);
20 const other_name = ctx.getSymbolName(other.target);
21 return std.mem.lessThan(u8, entry_name, other_name);
22 }
23 return entry.segment_id < other.segment_id;
24 }
25 };
1const std = @import("std");
2const assert = std.debug.assert;
3const leb = std.leb;
4const log = std.log.scoped(.link_dyld_info);
5const macho = std.macho;
6const testing = std.testing;
267
27 pub fn deinit(self: *Self, gpa: Allocator) void {
28 self.entries.deinit(gpa);
29 self.buffer.deinit(gpa);
8const Allocator = std.mem.Allocator;
9const MachO = @import("../../MachO.zig");
10const Symbol = @import("../Symbol.zig");
11
12pub const Entry = struct {
13 target: Symbol.Index,
14 offset: u64,
15 segment_id: u8,
16 addend: i64,
17
18 pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool {
19 if (entry.segment_id == other.segment_id) {
20 if (entry.target == other.target) {
21 return entry.offset < other.offset;
22 }
23 const entry_name = ctx.getSymbol(entry.target).getName(ctx);
24 const other_name = ctx.getSymbol(other.target).getName(ctx);
25 return std.mem.lessThan(u8, entry_name, other_name);
3026 }
27 return entry.segment_id < other.segment_id;
28 }
29};
3130
32 pub fn size(self: Self) u64 {
33 return @as(u64, @intCast(self.buffer.items.len));
34 }
31pub const Bind = struct {
32 entries: std.ArrayListUnmanaged(Entry) = .{},
33 buffer: std.ArrayListUnmanaged(u8) = .{},
3534
36 pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
37 if (self.entries.items.len == 0) return;
35 const Self = @This();
3836
39 const writer = self.buffer.writer(gpa);
37 pub fn deinit(self: *Self, gpa: Allocator) void {
38 self.entries.deinit(gpa);
39 self.buffer.deinit(gpa);
40 }
4041
41 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
42 pub fn size(self: Self) u64 {
43 return @as(u64, @intCast(self.buffer.items.len));
44 }
4245
43 var start: usize = 0;
44 var seg_id: ?u8 = null;
45 for (self.entries.items, 0..) |entry, i| {
46 if (seg_id != null and seg_id.? == entry.segment_id) continue;
47 try finalizeSegment(self.entries.items[start..i], ctx, writer);
48 seg_id = entry.segment_id;
49 start = i;
50 }
46 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
47 if (self.entries.items.len == 0) return;
5148
52 try finalizeSegment(self.entries.items[start..], ctx, writer);
53 try done(writer);
49 const writer = self.buffer.writer(gpa);
50
51 log.debug("bind opcodes", .{});
52
53 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
54
55 var start: usize = 0;
56 var seg_id: ?u8 = null;
57 for (self.entries.items, 0..) |entry, i| {
58 if (seg_id != null and seg_id.? == entry.segment_id) continue;
59 try finalizeSegment(self.entries.items[start..i], ctx, writer);
60 seg_id = entry.segment_id;
61 start = i;
5462 }
5563
56 fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void {
57 if (entries.len == 0) return;
58
59 const seg_id = entries[0].segment_id;
60 try setSegmentOffset(seg_id, 0, writer);
61
62 var offset: u64 = 0;
63 var addend: i64 = 0;
64 var count: usize = 0;
65 var skip: u64 = 0;
66 var target: ?Target = null;
67
68 var state: enum {
69 start,
70 bind_single,
71 bind_times_skip,
72 } = .start;
73
74 var i: usize = 0;
75 while (i < entries.len) : (i += 1) {
76 const current = entries[i];
77 if (target == null or !target.?.eql(current.target)) {
78 switch (state) {
79 .start => {},
80 .bind_single => try doBind(writer),
81 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
82 }
83 state = .start;
84 target = current.target;
64 try finalizeSegment(self.entries.items[start..], ctx, writer);
65 try done(writer);
66 }
8567
86 const sym = ctx.getSymbol(current.target);
87 const name = ctx.getSymbolName(current.target);
88 const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
89 const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
68 fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
69 if (entries.len == 0) return;
9070
91 try setSymbol(name, flags, writer);
92 try setTypePointer(writer);
93 try setDylibOrdinal(ordinal, writer);
71 const seg_id = entries[0].segment_id;
72 try setSegmentOffset(seg_id, 0, writer);
9473
95 if (current.addend != addend) {
96 addend = current.addend;
97 try setAddend(addend, writer);
98 }
99 }
74 var offset: u64 = 0;
75 var addend: i64 = 0;
76 var count: usize = 0;
77 var skip: u64 = 0;
78 var target: ?Symbol.Index = null;
79
80 var state: enum {
81 start,
82 bind_single,
83 bind_times_skip,
84 } = .start;
10085
101 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
102 log.debug(" => {x}", .{current.offset});
86 var i: usize = 0;
87 while (i < entries.len) : (i += 1) {
88 const current = entries[i];
89 if (target == null or target.? != current.target) {
10390 switch (state) {
104 .start => {
105 if (current.offset < offset) {
106 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
107 offset = offset - (offset - current.offset);
108 } else if (current.offset > offset) {
109 const delta = current.offset - offset;
110 try addAddr(delta, writer);
111 offset += delta;
112 }
113 state = .bind_single;
114 offset += @sizeOf(u64);
115 count = 1;
116 },
117 .bind_single => {
118 if (current.offset == offset) {
119 try doBind(writer);
120 state = .start;
121 } else if (current.offset > offset) {
122 const delta = current.offset - offset;
123 state = .bind_times_skip;
124 skip = @as(u64, @intCast(delta));
125 offset += skip;
126 } else unreachable;
127 i -= 1;
128 },
129 .bind_times_skip => {
130 if (current.offset < offset) {
131 count -= 1;
132 if (count == 1) {
133 try doBindAddAddr(skip, writer);
134 } else {
135 try doBindTimesSkip(count, skip, writer);
136 }
137 state = .start;
138 offset = offset - (@sizeOf(u64) + skip);
139 i -= 2;
140 } else if (current.offset == offset) {
141 count += 1;
142 offset += @sizeOf(u64) + skip;
143 } else {
144 try doBindTimesSkip(count, skip, writer);
145 state = .start;
146 i -= 1;
147 }
148 },
91 .start => {},
92 .bind_single => try doBind(writer),
93 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
94 }
95 state = .start;
96 target = current.target;
97
98 const sym = ctx.getSymbol(current.target);
99 const name = sym.getName(ctx);
100 const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
101 const ordinal: i16 = ord: {
102 if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
103 if (sym.flags.import) {
104 // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
105 if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
106 }
107 if (ctx.undefined_treatment == .dynamic_lookup)
108 break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
109 break :ord macho.BIND_SPECIAL_DYLIB_SELF;
110 };
111
112 try setSymbol(name, flags, writer);
113 try setTypePointer(writer);
114 try setDylibOrdinal(ordinal, writer);
115
116 if (current.addend != addend) {
117 addend = current.addend;
118 try setAddend(addend, writer);
149119 }
150120 }
151121
122 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
123 log.debug(" => {x}", .{current.offset});
152124 switch (state) {
153 .start => unreachable,
154 .bind_single => try doBind(writer),
155 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
125 .start => {
126 if (current.offset < offset) {
127 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
128 offset = offset - (offset - current.offset);
129 } else if (current.offset > offset) {
130 const delta = current.offset - offset;
131 try addAddr(delta, writer);
132 offset += delta;
133 }
134 state = .bind_single;
135 offset += @sizeOf(u64);
136 count = 1;
137 },
138 .bind_single => {
139 if (current.offset == offset) {
140 try doBind(writer);
141 state = .start;
142 } else if (current.offset > offset) {
143 const delta = current.offset - offset;
144 state = .bind_times_skip;
145 skip = @as(u64, @intCast(delta));
146 offset += skip;
147 } else unreachable;
148 i -= 1;
149 },
150 .bind_times_skip => {
151 if (current.offset < offset) {
152 count -= 1;
153 if (count == 1) {
154 try doBindAddAddr(skip, writer);
155 } else {
156 try doBindTimesSkip(count, skip, writer);
157 }
158 state = .start;
159 offset = offset - (@sizeOf(u64) + skip);
160 i -= 2;
161 } else if (current.offset == offset) {
162 count += 1;
163 offset += @sizeOf(u64) + skip;
164 } else {
165 try doBindTimesSkip(count, skip, writer);
166 state = .start;
167 i -= 1;
168 }
169 },
156170 }
157171 }
158172
159 pub fn write(self: Self, writer: anytype) !void {
160 if (self.size() == 0) return;
161 try writer.writeAll(self.buffer.items);
173 switch (state) {
174 .start => unreachable,
175 .bind_single => try doBind(writer),
176 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
162177 }
163 };
164}
178 }
165179
166pub fn LazyBind(comptime Ctx: type, comptime Target: type) type {
167 return struct {
168 entries: std.ArrayListUnmanaged(Entry) = .{},
169 buffer: std.ArrayListUnmanaged(u8) = .{},
170 offsets: std.ArrayListUnmanaged(u32) = .{},
171
172 const Self = @This();
173
174 const Entry = struct {
175 target: Target,
176 offset: u64,
177 segment_id: u8,
178 addend: i64,
179 };
180
181 pub fn deinit(self: *Self, gpa: Allocator) void {
182 self.entries.deinit(gpa);
183 self.buffer.deinit(gpa);
184 self.offsets.deinit(gpa);
185 }
180 pub fn write(self: Self, writer: anytype) !void {
181 if (self.size() == 0) return;
182 try writer.writeAll(self.buffer.items);
183 }
184};
185
186pub const WeakBind = struct {
187 entries: std.ArrayListUnmanaged(Entry) = .{},
188 buffer: std.ArrayListUnmanaged(u8) = .{},
189
190 const Self = @This();
191
192 pub fn deinit(self: *Self, gpa: Allocator) void {
193 self.entries.deinit(gpa);
194 self.buffer.deinit(gpa);
195 }
196
197 pub fn size(self: Self) u64 {
198 return @as(u64, @intCast(self.buffer.items.len));
199 }
200
201 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
202 if (self.entries.items.len == 0) return;
203
204 const writer = self.buffer.writer(gpa);
205
206 log.debug("weak bind opcodes", .{});
207
208 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
186209
187 pub fn size(self: Self) u64 {
188 return @as(u64, @intCast(self.buffer.items.len));
210 var start: usize = 0;
211 var seg_id: ?u8 = null;
212 for (self.entries.items, 0..) |entry, i| {
213 if (seg_id != null and seg_id.? == entry.segment_id) continue;
214 try finalizeSegment(self.entries.items[start..i], ctx, writer);
215 seg_id = entry.segment_id;
216 start = i;
189217 }
190218
191 pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {
192 if (self.entries.items.len == 0) return;
219 try finalizeSegment(self.entries.items[start..], ctx, writer);
220 try done(writer);
221 }
222
223 fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
224 if (entries.len == 0) return;
193225
194 try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
226 const seg_id = entries[0].segment_id;
227 try setSegmentOffset(seg_id, 0, writer);
195228
196 var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
197 const writer = cwriter.writer();
229 var offset: u64 = 0;
230 var addend: i64 = 0;
231 var count: usize = 0;
232 var skip: u64 = 0;
233 var target: ?Symbol.Index = null;
198234
199 var addend: i64 = 0;
235 var state: enum {
236 start,
237 bind_single,
238 bind_times_skip,
239 } = .start;
200240
201 for (self.entries.items) |entry| {
202 self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));
241 var i: usize = 0;
242 while (i < entries.len) : (i += 1) {
243 const current = entries[i];
244 if (target == null or target.? != current.target) {
245 switch (state) {
246 .start => {},
247 .bind_single => try doBind(writer),
248 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
249 }
250 state = .start;
251 target = current.target;
203252
204 const sym = ctx.getSymbol(entry.target);
205 const name = ctx.getSymbolName(entry.target);
206 const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
207 const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
253 const sym = ctx.getSymbol(current.target);
254 const name = sym.getName(ctx);
255 const flags: u8 = 0; // TODO NON_WEAK_DEFINITION
208256
209 try setSegmentOffset(entry.segment_id, entry.offset, writer);
210257 try setSymbol(name, flags, writer);
211 try setDylibOrdinal(ordinal, writer);
258 try setTypePointer(writer);
212259
213 if (entry.addend != addend) {
214 try setAddend(entry.addend, writer);
215 addend = entry.addend;
260 if (current.addend != addend) {
261 addend = current.addend;
262 try setAddend(addend, writer);
216263 }
264 }
217265
218 try doBind(writer);
219 try done(writer);
266 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
267 log.debug(" => {x}", .{current.offset});
268 switch (state) {
269 .start => {
270 if (current.offset < offset) {
271 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
272 offset = offset - (offset - current.offset);
273 } else if (current.offset > offset) {
274 const delta = current.offset - offset;
275 try addAddr(delta, writer);
276 offset += delta;
277 }
278 state = .bind_single;
279 offset += @sizeOf(u64);
280 count = 1;
281 },
282 .bind_single => {
283 if (current.offset == offset) {
284 try doBind(writer);
285 state = .start;
286 } else if (current.offset > offset) {
287 const delta = current.offset - offset;
288 state = .bind_times_skip;
289 skip = @as(u64, @intCast(delta));
290 offset += skip;
291 } else unreachable;
292 i -= 1;
293 },
294 .bind_times_skip => {
295 if (current.offset < offset) {
296 count -= 1;
297 if (count == 1) {
298 try doBindAddAddr(skip, writer);
299 } else {
300 try doBindTimesSkip(count, skip, writer);
301 }
302 state = .start;
303 offset = offset - (@sizeOf(u64) + skip);
304 i -= 2;
305 } else if (current.offset == offset) {
306 count += 1;
307 offset += @sizeOf(u64) + skip;
308 } else {
309 try doBindTimesSkip(count, skip, writer);
310 state = .start;
311 i -= 1;
312 }
313 },
220314 }
221315 }
222316
223 pub fn write(self: Self, writer: anytype) !void {
224 if (self.size() == 0) return;
225 try writer.writeAll(self.buffer.items);
317 switch (state) {
318 .start => unreachable,
319 .bind_single => try doBind(writer),
320 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
226321 }
227 };
228}
322 }
323
324 pub fn write(self: Self, writer: anytype) !void {
325 if (self.size() == 0) return;
326 try writer.writeAll(self.buffer.items);
327 }
328};
329
330pub const LazyBind = struct {
331 entries: std.ArrayListUnmanaged(Entry) = .{},
332 buffer: std.ArrayListUnmanaged(u8) = .{},
333 offsets: std.ArrayListUnmanaged(u32) = .{},
334
335 const Self = @This();
336
337 pub fn deinit(self: *Self, gpa: Allocator) void {
338 self.entries.deinit(gpa);
339 self.buffer.deinit(gpa);
340 self.offsets.deinit(gpa);
341 }
342
343 pub fn size(self: Self) u64 {
344 return @as(u64, @intCast(self.buffer.items.len));
345 }
346
347 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
348 if (self.entries.items.len == 0) return;
349
350 try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
351
352 var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
353 const writer = cwriter.writer();
354
355 log.debug("lazy bind opcodes", .{});
356
357 var addend: i64 = 0;
358
359 for (self.entries.items) |entry| {
360 self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));
361
362 const sym = ctx.getSymbol(entry.target);
363 const name = sym.getName(ctx);
364 const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
365 const ordinal: i16 = ord: {
366 if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
367 if (sym.flags.import) {
368 // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
369 if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
370 }
371 if (ctx.undefined_treatment == .dynamic_lookup)
372 break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
373 break :ord macho.BIND_SPECIAL_DYLIB_SELF;
374 };
375
376 try setSegmentOffset(entry.segment_id, entry.offset, writer);
377 try setSymbol(name, flags, writer);
378 try setDylibOrdinal(ordinal, writer);
379
380 if (entry.addend != addend) {
381 try setAddend(entry.addend, writer);
382 addend = entry.addend;
383 }
384
385 try doBind(writer);
386 try done(writer);
387 }
388 }
389
390 pub fn write(self: Self, writer: anytype) !void {
391 if (self.size() == 0) return;
392 try writer.writeAll(self.buffer.items);
393 }
394};
229395
230396fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
231397 log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
......@@ -312,429 +478,3 @@ fn done(writer: anytype) !void {
312478 log.debug(">>> done", .{});
313479 try writer.writeByte(macho.BIND_OPCODE_DONE);
314480}
315
316const TestContext = struct {
317 symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
318 strtab: std.ArrayListUnmanaged(u8) = .{},
319
320 const Target = struct {
321 index: u32,
322
323 fn eql(this: Target, other: Target) bool {
324 return this.index == other.index;
325 }
326 };
327
328 fn deinit(ctx: *TestContext, gpa: Allocator) void {
329 ctx.symbols.deinit(gpa);
330 ctx.strtab.deinit(gpa);
331 }
332
333 fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void {
334 const n_strx = try ctx.addString(gpa, name);
335 var n_desc = @as(u16, @bitCast(ordinal * macho.N_SYMBOL_RESOLVER));
336 n_desc |= flags;
337 try ctx.symbols.append(gpa, .{
338 .n_value = 0,
339 .n_strx = n_strx,
340 .n_desc = n_desc,
341 .n_type = macho.N_EXT,
342 .n_sect = 0,
343 });
344 }
345
346 fn addString(ctx: *TestContext, gpa: Allocator, name: []const u8) !u32 {
347 const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
348 try ctx.strtab.appendSlice(gpa, name);
349 try ctx.strtab.append(gpa, 0);
350 return n_strx;
351 }
352
353 fn getSymbol(ctx: TestContext, target: Target) macho.nlist_64 {
354 return ctx.symbols.items[target.index];
355 }
356
357 fn getSymbolName(ctx: TestContext, target: Target) []const u8 {
358 const sym = ctx.getSymbol(target);
359 assert(sym.n_strx < ctx.strtab.items.len);
360 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + sym.n_strx)), 0);
361 }
362};
363
364fn generateTestContext() !TestContext {
365 const gpa = testing.allocator;
366 var ctx = TestContext{};
367 try ctx.addSymbol(gpa, "_import_1", 1, 0);
368 try ctx.addSymbol(gpa, "_import_2", 1, 0);
369 try ctx.addSymbol(gpa, "_import_3", 1, 0);
370 try ctx.addSymbol(gpa, "_import_4", 2, 0);
371 try ctx.addSymbol(gpa, "_import_5_weak", 2, macho.N_WEAK_REF);
372 try ctx.addSymbol(gpa, "_import_6", 2, 0);
373 return ctx;
374}
375
376test "bind - no entries" {
377 const gpa = testing.allocator;
378
379 var test_context = try generateTestContext();
380 defer test_context.deinit(gpa);
381
382 var bind = Bind(TestContext, TestContext.Target){};
383 defer bind.deinit(gpa);
384
385 try bind.finalize(gpa, test_context);
386 try testing.expectEqual(@as(u64, 0), bind.size());
387}
388
389test "bind - single entry" {
390 const gpa = testing.allocator;
391
392 var test_context = try generateTestContext();
393 defer test_context.deinit(gpa);
394
395 var bind = Bind(TestContext, TestContext.Target){};
396 defer bind.deinit(gpa);
397
398 try bind.entries.append(gpa, .{
399 .offset = 0x10,
400 .segment_id = 1,
401 .target = TestContext.Target{ .index = 0 },
402 .addend = 0,
403 });
404 try bind.finalize(gpa, test_context);
405 try testing.expectEqualSlices(u8, &[_]u8{
406 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
407 0x0,
408 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
409 0x5f,
410 0x69,
411 0x6d,
412 0x70,
413 0x6f,
414 0x72,
415 0x74,
416 0x5f,
417 0x31,
418 0x0,
419 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
420 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
421 macho.BIND_OPCODE_ADD_ADDR_ULEB,
422 0x10,
423 macho.BIND_OPCODE_DO_BIND,
424 macho.BIND_OPCODE_DONE,
425 }, bind.buffer.items);
426}
427
428test "bind - multiple occurrences within the same segment" {
429 const gpa = testing.allocator;
430
431 var test_context = try generateTestContext();
432 defer test_context.deinit(gpa);
433
434 var bind = Bind(TestContext, TestContext.Target){};
435 defer bind.deinit(gpa);
436
437 try bind.entries.append(gpa, .{
438 .offset = 0x10,
439 .segment_id = 1,
440 .target = TestContext.Target{ .index = 0 },
441 .addend = 0,
442 });
443 try bind.entries.append(gpa, .{
444 .offset = 0x18,
445 .segment_id = 1,
446 .target = TestContext.Target{ .index = 0 },
447 .addend = 0,
448 });
449 try bind.entries.append(gpa, .{
450 .offset = 0x20,
451 .segment_id = 1,
452 .target = TestContext.Target{ .index = 0 },
453 .addend = 0,
454 });
455 try bind.entries.append(gpa, .{
456 .offset = 0x28,
457 .segment_id = 1,
458 .target = TestContext.Target{ .index = 0 },
459 .addend = 0,
460 });
461
462 try bind.finalize(gpa, test_context);
463 try testing.expectEqualSlices(u8, &[_]u8{
464 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
465 0x0,
466 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
467 0x5f,
468 0x69,
469 0x6d,
470 0x70,
471 0x6f,
472 0x72,
473 0x74,
474 0x5f,
475 0x31,
476 0x0,
477 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
478 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
479 macho.BIND_OPCODE_ADD_ADDR_ULEB,
480 0x10,
481 macho.BIND_OPCODE_DO_BIND,
482 macho.BIND_OPCODE_DO_BIND,
483 macho.BIND_OPCODE_DO_BIND,
484 macho.BIND_OPCODE_DO_BIND,
485 macho.BIND_OPCODE_DONE,
486 }, bind.buffer.items);
487}
488
489test "bind - multiple occurrences with skip and addend" {
490 const gpa = testing.allocator;
491
492 var test_context = try generateTestContext();
493 defer test_context.deinit(gpa);
494
495 var bind = Bind(TestContext, TestContext.Target){};
496 defer bind.deinit(gpa);
497
498 try bind.entries.append(gpa, .{
499 .offset = 0x0,
500 .segment_id = 1,
501 .target = TestContext.Target{ .index = 0 },
502 .addend = 0x10,
503 });
504 try bind.entries.append(gpa, .{
505 .offset = 0x10,
506 .segment_id = 1,
507 .target = TestContext.Target{ .index = 0 },
508 .addend = 0x10,
509 });
510 try bind.entries.append(gpa, .{
511 .offset = 0x20,
512 .segment_id = 1,
513 .target = TestContext.Target{ .index = 0 },
514 .addend = 0x10,
515 });
516 try bind.entries.append(gpa, .{
517 .offset = 0x30,
518 .segment_id = 1,
519 .target = TestContext.Target{ .index = 0 },
520 .addend = 0x10,
521 });
522
523 try bind.finalize(gpa, test_context);
524 try testing.expectEqualSlices(u8, &[_]u8{
525 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
526 0x0,
527 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
528 0x5f,
529 0x69,
530 0x6d,
531 0x70,
532 0x6f,
533 0x72,
534 0x74,
535 0x5f,
536 0x31,
537 0x0,
538 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
539 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
540 macho.BIND_OPCODE_SET_ADDEND_SLEB,
541 0x10,
542 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
543 0x4,
544 0x8,
545 macho.BIND_OPCODE_DONE,
546 }, bind.buffer.items);
547}
548
549test "bind - complex" {
550 const gpa = testing.allocator;
551
552 var test_context = try generateTestContext();
553 defer test_context.deinit(gpa);
554
555 var bind = Bind(TestContext, TestContext.Target){};
556 defer bind.deinit(gpa);
557
558 try bind.entries.append(gpa, .{
559 .offset = 0x58,
560 .segment_id = 1,
561 .target = TestContext.Target{ .index = 0 },
562 .addend = 0,
563 });
564 try bind.entries.append(gpa, .{
565 .offset = 0x100,
566 .segment_id = 1,
567 .target = TestContext.Target{ .index = 1 },
568 .addend = 0x10,
569 });
570 try bind.entries.append(gpa, .{
571 .offset = 0x110,
572 .segment_id = 1,
573 .target = TestContext.Target{ .index = 1 },
574 .addend = 0x10,
575 });
576 try bind.entries.append(gpa, .{
577 .offset = 0x130,
578 .segment_id = 1,
579 .target = TestContext.Target{ .index = 1 },
580 .addend = 0x10,
581 });
582 try bind.entries.append(gpa, .{
583 .offset = 0x140,
584 .segment_id = 1,
585 .target = TestContext.Target{ .index = 1 },
586 .addend = 0x10,
587 });
588 try bind.entries.append(gpa, .{
589 .offset = 0x148,
590 .segment_id = 1,
591 .target = TestContext.Target{ .index = 2 },
592 .addend = 0,
593 });
594
595 try bind.finalize(gpa, test_context);
596 try testing.expectEqualSlices(u8, &[_]u8{
597 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
598 0x0,
599 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
600 0x5f,
601 0x69,
602 0x6d,
603 0x70,
604 0x6f,
605 0x72,
606 0x74,
607 0x5f,
608 0x31,
609 0x0,
610 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
611 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
612 macho.BIND_OPCODE_ADD_ADDR_ULEB,
613 0x58,
614 macho.BIND_OPCODE_DO_BIND,
615 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
616 0x5f,
617 0x69,
618 0x6d,
619 0x70,
620 0x6f,
621 0x72,
622 0x74,
623 0x5f,
624 0x32,
625 0x0,
626 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
627 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
628 macho.BIND_OPCODE_SET_ADDEND_SLEB,
629 0x10,
630 macho.BIND_OPCODE_ADD_ADDR_ULEB,
631 0xa0,
632 0x1,
633 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
634 0x2,
635 0x8,
636 macho.BIND_OPCODE_ADD_ADDR_ULEB,
637 0x10,
638 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
639 0x2,
640 0x8,
641 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
642 0x5f,
643 0x69,
644 0x6d,
645 0x70,
646 0x6f,
647 0x72,
648 0x74,
649 0x5f,
650 0x33,
651 0x0,
652 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
653 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
654 macho.BIND_OPCODE_SET_ADDEND_SLEB,
655 0x0,
656 macho.BIND_OPCODE_ADD_ADDR_ULEB,
657 0xf8,
658 0xff,
659 0xff,
660 0xff,
661 0xff,
662 0xff,
663 0xff,
664 0xff,
665 0xff,
666 0x1,
667 macho.BIND_OPCODE_DO_BIND,
668 macho.BIND_OPCODE_DONE,
669 }, bind.buffer.items);
670}
671
672test "lazy bind" {
673 const gpa = testing.allocator;
674
675 var test_context = try generateTestContext();
676 defer test_context.deinit(gpa);
677
678 var bind = LazyBind(TestContext, TestContext.Target){};
679 defer bind.deinit(gpa);
680
681 try bind.entries.append(gpa, .{
682 .offset = 0x10,
683 .segment_id = 1,
684 .target = TestContext.Target{ .index = 0 },
685 .addend = 0,
686 });
687 try bind.entries.append(gpa, .{
688 .offset = 0x20,
689 .segment_id = 2,
690 .target = TestContext.Target{ .index = 1 },
691 .addend = 0x10,
692 });
693
694 try bind.finalize(gpa, test_context);
695 try testing.expectEqualSlices(u8, &[_]u8{
696 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
697 0x10,
698 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
699 0x5f,
700 0x69,
701 0x6d,
702 0x70,
703 0x6f,
704 0x72,
705 0x74,
706 0x5f,
707 0x31,
708 0x0,
709 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
710 macho.BIND_OPCODE_DO_BIND,
711 macho.BIND_OPCODE_DONE,
712 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
713 0x20,
714 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
715 0x5f,
716 0x69,
717 0x6d,
718 0x70,
719 0x6f,
720 0x72,
721 0x74,
722 0x5f,
723 0x32,
724 0x0,
725 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
726 macho.BIND_OPCODE_SET_ADDEND_SLEB,
727 0x10,
728 macho.BIND_OPCODE_DO_BIND,
729 macho.BIND_OPCODE_DONE,
730 }, bind.buffer.items);
731}
732
733const std = @import("std");
734const assert = std.debug.assert;
735const leb = std.leb;
736const log = std.log.scoped(.dyld_info);
737const macho = std.macho;
738const testing = std.testing;
739
740const Allocator = std.mem.Allocator;
src/link/MachO/eh_frame.zig+467-558
......@@ -1,628 +1,537 @@
1pub fn scanRelocs(macho_file: *MachO) !void {
2 const comp = macho_file.base.comp;
3 const gpa = comp.gpa;
4
5 for (macho_file.objects.items, 0..) |*object, object_id| {
6 var cies = std.AutoHashMap(u32, void).init(gpa);
7 defer cies.deinit();
8
9 var it = object.getEhFrameRecordsIterator();
10
11 for (object.exec_atoms.items) |atom_index| {
12 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
13 while (inner_syms_it.next()) |sym| {
14 const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
15 if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
16 it.seekTo(fde_offset);
17 const fde = (it.next() catch continue).?; // We don't care about this error since we already handled it
18
19 const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset);
20 const cie_offset = fde_offset + 4 - cie_ptr;
21
22 if (!cies.contains(cie_offset)) {
23 try cies.putNoClobber(cie_offset, {});
24 it.seekTo(cie_offset);
25 const cie = (it.next() catch continue).?; // We don't care about this error since we already handled it
26 try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset);
1pub const Cie = struct {
2 /// Includes 4byte size cell.
3 offset: u32,
4 out_offset: u32 = 0,
5 size: u32,
6 lsda_size: ?enum { p32, p64 } = null,
7 personality: ?Personality = null,
8 file: File.Index = 0,
9 alive: bool = false,
10
11 pub fn parse(cie: *Cie, macho_file: *MachO) !void {
12 const tracy = trace(@src());
13 defer tracy.end();
14
15 const data = cie.getData(macho_file);
16 const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0);
17
18 if (aug[0] != 'z') return; // TODO should we error out?
19
20 var stream = std.io.fixedBufferStream(data[9 + aug.len + 1 ..]);
21 var creader = std.io.countingReader(stream.reader());
22 const reader = creader.reader();
23
24 _ = try leb.readULEB128(u64, reader); // code alignment factor
25 _ = try leb.readULEB128(u64, reader); // data alignment factor
26 _ = try leb.readULEB128(u64, reader); // return address register
27 _ = try leb.readULEB128(u64, reader); // augmentation data length
28
29 for (aug[1..]) |ch| switch (ch) {
30 'R' => {
31 const enc = try reader.readByte();
32 if (enc & 0xf != EH_PE.absptr or enc & EH_PE.pcrel == 0) {
33 @panic("unexpected pointer encoding"); // TODO error
2734 }
28 }
29 }
30 }
31}
32
33pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) error{OutOfMemory}!void {
34 const sect_id = macho_file.eh_frame_section_index orelse return;
35 const sect = &macho_file.sections.items(.header)[sect_id];
36 sect.@"align" = 3;
37 sect.size = 0;
38
39 const target = macho_file.base.comp.root_mod.resolved_target.result;
40 const cpu_arch = target.cpu.arch;
41 const comp = macho_file.base.comp;
42 const gpa = comp.gpa;
43 var size: u32 = 0;
44
45 for (macho_file.objects.items, 0..) |*object, object_id| {
46 var cies = std.AutoHashMap(u32, u32).init(gpa);
47 defer cies.deinit();
48
49 var eh_it = object.getEhFrameRecordsIterator();
50
51 for (object.exec_atoms.items) |atom_index| {
52 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
53 while (inner_syms_it.next()) |sym| {
54 const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
55 if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
56
57 const record_id = unwind_info.records_lookup.get(sym) orelse continue;
58 const record = unwind_info.records.items[record_id];
59
60 // TODO skip this check if no __compact_unwind is present
61 const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
62 if (!is_dwarf) continue;
63
64 eh_it.seekTo(fde_record_offset);
65 const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
66
67 const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
68 const cie_offset = fde_record_offset + 4 - cie_ptr;
69
70 const gop = try cies.getOrPut(cie_offset);
71 if (!gop.found_existing) {
72 eh_it.seekTo(cie_offset);
73 const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
74 gop.value_ptr.* = size;
75 size += source_cie_record.getSize();
35 },
36 'P' => {
37 const enc = try reader.readByte();
38 if (enc != EH_PE.pcrel | EH_PE.indirect | EH_PE.sdata4) {
39 @panic("unexpected personality pointer encoding"); // TODO error
7640 }
41 _ = try reader.readInt(u32, .little); // personality pointer
42 },
43 'L' => {
44 const enc = try reader.readByte();
45 switch (enc & 0xf) {
46 EH_PE.sdata4 => cie.lsda_size = .p32,
47 EH_PE.absptr => cie.lsda_size = .p64,
48 else => unreachable, // TODO error
49 }
50 },
51 else => @panic("unexpected augmentation string"), // TODO error
52 };
53 }
7754
78 size += source_fde_record.getSize();
79 }
80 }
55 pub inline fn getSize(cie: Cie) u32 {
56 return cie.size + 4;
57 }
8158
82 sect.size = size;
59 pub fn getObject(cie: Cie, macho_file: *MachO) *Object {
60 const file = macho_file.getFile(cie.file).?;
61 return file.object;
8362 }
84}
8563
86pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void {
87 const sect_id = macho_file.eh_frame_section_index orelse return;
88 const sect = macho_file.sections.items(.header)[sect_id];
89 const seg_id = macho_file.sections.items(.segment_index)[sect_id];
90 const seg = macho_file.segments.items[seg_id];
91
92 const target = macho_file.base.comp.root_mod.resolved_target.result;
93 const cpu_arch = target.cpu.arch;
94 const comp = macho_file.base.comp;
95 const gpa = comp.gpa;
96
97 var eh_records = std.AutoArrayHashMap(u32, EhFrameRecord(true)).init(gpa);
98 defer {
99 for (eh_records.values()) |*rec| {
100 rec.deinit(gpa);
101 }
102 eh_records.deinit();
64 pub fn getData(cie: Cie, macho_file: *MachO) []const u8 {
65 const object = cie.getObject(macho_file);
66 return object.eh_frame_data.items[cie.offset..][0..cie.getSize()];
10367 }
10468
105 var eh_frame_offset: u32 = 0;
69 pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
70 const personality = cie.personality orelse return null;
71 return macho_file.getSymbol(personality.index);
72 }
10673
107 for (macho_file.objects.items, 0..) |*object, object_id| {
108 try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len)));
74 pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool {
75 if (!std.mem.eql(u8, cie.getData(macho_file), other.getData(macho_file))) return false;
76 if (cie.personality != null and other.personality != null) {
77 if (cie.personality.?.index != other.personality.?.index) return false;
78 }
79 if (cie.personality != null or other.personality != null) return false;
80 return true;
81 }
10982
110 var cies = std.AutoHashMap(u32, u32).init(gpa);
111 defer cies.deinit();
83 pub fn format(
84 cie: Cie,
85 comptime unused_fmt_string: []const u8,
86 options: std.fmt.FormatOptions,
87 writer: anytype,
88 ) !void {
89 _ = cie;
90 _ = unused_fmt_string;
91 _ = options;
92 _ = writer;
93 @compileError("do not format CIEs directly");
94 }
11295
113 var eh_it = object.getEhFrameRecordsIterator();
96 pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Formatter(format2) {
97 return .{ .data = .{
98 .cie = cie,
99 .macho_file = macho_file,
100 } };
101 }
114102
115 for (object.exec_atoms.items) |atom_index| {
116 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
117 while (inner_syms_it.next()) |reloc_target| {
118 const fde_record_offset = object.eh_frame_records_lookup.get(reloc_target) orelse continue;
119 if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
103 const FormatContext = struct {
104 cie: Cie,
105 macho_file: *MachO,
106 };
120107
121 const record_id = unwind_info.records_lookup.get(reloc_target) orelse continue;
122 const record = &unwind_info.records.items[record_id];
108 fn format2(
109 ctx: FormatContext,
110 comptime unused_fmt_string: []const u8,
111 options: std.fmt.FormatOptions,
112 writer: anytype,
113 ) !void {
114 _ = unused_fmt_string;
115 _ = options;
116 const cie = ctx.cie;
117 try writer.print("@{x} : size({x})", .{
118 cie.offset,
119 cie.getSize(),
120 });
121 if (!cie.alive) try writer.writeAll(" : [*]");
122 }
123123
124 // TODO skip this check if no __compact_unwind is present
125 const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
126 if (!is_dwarf) continue;
124 pub const Index = u32;
127125
128 eh_it.seekTo(fde_record_offset);
129 const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
126 pub const Personality = struct {
127 index: Symbol.Index = 0,
128 offset: u32 = 0,
129 };
130};
130131
131 const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
132 const cie_offset = fde_record_offset + 4 - cie_ptr;
132pub const Fde = struct {
133 /// Includes 4byte size cell.
134 offset: u32,
135 out_offset: u32 = 0,
136 size: u32,
137 cie: Cie.Index,
138 atom: Atom.Index = 0,
139 atom_offset: u32 = 0,
140 lsda: Atom.Index = 0,
141 lsda_offset: u32 = 0,
142 lsda_ptr_offset: u32 = 0,
143 file: File.Index = 0,
144 alive: bool = true,
145
146 pub fn parse(fde: *Fde, macho_file: *MachO) !void {
147 const tracy = trace(@src());
148 defer tracy.end();
149
150 const data = fde.getData(macho_file);
151 const object = fde.getObject(macho_file);
152 const sect = object.sections.items(.header)[object.eh_frame_sect_index.?];
153
154 // Parse target atom index
155 const pc_begin = std.mem.readInt(i64, data[8..][0..8], .little);
156 const taddr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + 8)) + pc_begin);
157 fde.atom = object.findAtom(taddr) orelse {
158 try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid function reference in FDE", .{
159 sect.segName(), sect.sectName(), fde.offset + 8,
160 });
161 return error.MalformedObject;
162 };
163 const atom = fde.getAtom(macho_file);
164 fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file));
165
166 // Associate with a CIE
167 const cie_ptr = std.mem.readInt(u32, data[4..8], .little);
168 const cie_offset = fde.offset + 4 - cie_ptr;
169 const cie_index = for (object.cies.items, 0..) |cie, cie_index| {
170 if (cie.offset == cie_offset) break @as(Cie.Index, @intCast(cie_index));
171 } else null;
172 if (cie_index) |cie| {
173 fde.cie = cie;
174 } else {
175 try macho_file.reportParseError2(object.index, "no matching CIE found for FDE at offset {x}", .{
176 fde.offset,
177 });
178 return error.MalformedObject;
179 }
133180
134 const gop = try cies.getOrPut(cie_offset);
135 if (!gop.found_existing) {
136 eh_it.seekTo(cie_offset);
137 const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
138 var cie_record = try source_cie_record.toOwned(gpa);
139 try cie_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
140 .source_offset = cie_offset,
141 .out_offset = eh_frame_offset,
142 .sect_addr = sect.addr,
143 });
144 eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
145 gop.value_ptr.* = eh_frame_offset;
146 eh_frame_offset += cie_record.getSize();
147 }
181 const cie = fde.getCie(macho_file);
148182
149 var fde_record = try source_fde_record.toOwned(gpa);
150 try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{
151 .source_offset = fde_record_offset,
152 .out_offset = eh_frame_offset,
153 .sect_addr = sect.addr,
183 // Parse LSDA atom index if any
184 if (cie.lsda_size) |lsda_size| {
185 var stream = std.io.fixedBufferStream(data[24..]);
186 var creader = std.io.countingReader(stream.reader());
187 const reader = creader.reader();
188 _ = try leb.readULEB128(u64, reader); // augmentation length
189 fde.lsda_ptr_offset = @intCast(creader.bytes_read + 24);
190 const lsda_ptr = switch (lsda_size) {
191 .p32 => try reader.readInt(i32, .little),
192 .p64 => try reader.readInt(i64, .little),
193 };
194 const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr);
195 fde.lsda = object.findAtom(lsda_addr) orelse {
196 try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid LSDA reference in FDE", .{
197 sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset,
154198 });
155 fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
156
157 switch (cpu_arch) {
158 .aarch64 => {}, // relocs take care of LSDA pointers
159 .x86_64 => {
160 // We need to relocate target symbol address ourselves.
161 const atom_sym = macho_file.getSymbol(reloc_target);
162 try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
163 .base_addr = sect.addr,
164 .base_offset = eh_frame_offset,
165 });
166
167 // We need to parse LSDA pointer and relocate ourselves.
168 const cie_record = eh_records.get(
169 eh_frame_offset + 4 - fde_record.getCiePointer(),
170 ).?;
171 const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
172 const source_lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
173 .base_addr = eh_frame_sect.addr,
174 .base_offset = fde_record_offset,
175 }) catch continue; // We already handled this error
176 if (source_lsda_ptr) |ptr| {
177 const sym_index = object.getSymbolByAddress(ptr, null);
178 const sym = object.symtab[sym_index];
179 fde_record.setLsdaPointer(cie_record, sym.n_value, .{
180 .base_addr = sect.addr,
181 .base_offset = eh_frame_offset,
182 }) catch continue; // We already handled this error
183 }
184 },
185 else => unreachable,
186 }
187
188 eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
189
190 UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
191 &record.compactUnwindEncoding,
192 cpu_arch,
193 @as(u24, @intCast(eh_frame_offset)),
194 );
195
196 const cie_record = eh_records.get(
197 eh_frame_offset + 4 - fde_record.getCiePointer(),
198 ).?;
199 const lsda_ptr = fde_record.getLsdaPointer(cie_record, .{
200 .base_addr = sect.addr,
201 .base_offset = eh_frame_offset,
202 }) catch continue; // We already handled this error
203 if (lsda_ptr) |ptr| {
204 record.lsda = ptr - seg.vmaddr;
205 }
206
207 eh_frame_offset += fde_record.getSize();
208 }
199 return error.MalformedObject;
200 };
201 const lsda_atom = fde.getLsdaAtom(macho_file).?;
202 fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file));
209203 }
210204 }
211205
212 var buffer = std.ArrayList(u8).init(gpa);
213 defer buffer.deinit();
214 const writer = buffer.writer();
206 pub inline fn getSize(fde: Fde) u32 {
207 return fde.size + 4;
208 }
215209
216 for (eh_records.values()) |record| {
217 try writer.writeInt(u32, record.size, .little);
218 try buffer.appendSlice(record.data);
210 pub fn getObject(fde: Fde, macho_file: *MachO) *Object {
211 const file = macho_file.getFile(fde.file).?;
212 return file.object;
219213 }
220214
221 try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
222}
223const EhFrameRecordTag = enum { cie, fde };
215 pub fn getData(fde: Fde, macho_file: *MachO) []const u8 {
216 const object = fde.getObject(macho_file);
217 return object.eh_frame_data.items[fde.offset..][0..fde.getSize()];
218 }
224219
225pub fn EhFrameRecord(comptime is_mutable: bool) type {
226 return struct {
227 tag: EhFrameRecordTag,
228 size: u32,
229 data: if (is_mutable) []u8 else []const u8,
220 pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie {
221 const object = fde.getObject(macho_file);
222 return &object.cies.items[fde.cie];
223 }
230224
231 const Record = @This();
225 pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
226 return macho_file.getAtom(fde.atom).?;
227 }
232228
233 pub fn deinit(rec: *Record, gpa: Allocator) void {
234 comptime assert(is_mutable);
235 gpa.free(rec.data);
236 }
229 pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
230 return macho_file.getAtom(fde.lsda);
231 }
237232
238 pub fn toOwned(rec: Record, gpa: Allocator) Allocator.Error!EhFrameRecord(true) {
239 const data = try gpa.dupe(u8, rec.data);
240 return EhFrameRecord(true){
241 .tag = rec.tag,
242 .size = rec.size,
243 .data = data,
244 };
245 }
233 pub fn format(
234 fde: Fde,
235 comptime unused_fmt_string: []const u8,
236 options: std.fmt.FormatOptions,
237 writer: anytype,
238 ) !void {
239 _ = fde;
240 _ = unused_fmt_string;
241 _ = options;
242 _ = writer;
243 @compileError("do not format FDEs directly");
244 }
246245
247 pub inline fn getSize(rec: Record) u32 {
248 return 4 + rec.size;
249 }
246 pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Formatter(format2) {
247 return .{ .data = .{
248 .fde = fde,
249 .macho_file = macho_file,
250 } };
251 }
250252
251 pub fn scanRelocs(
252 rec: Record,
253 macho_file: *MachO,
254 object_id: u32,
255 source_offset: u32,
256 ) !void {
257 if (rec.getPersonalityPointerReloc(macho_file, object_id, source_offset)) |target| {
258 try macho_file.addGotEntry(target);
259 }
260 }
253 const FormatContext = struct {
254 fde: Fde,
255 macho_file: *MachO,
256 };
261257
262 pub fn getTargetSymbolAddress(rec: Record, ctx: struct {
263 base_addr: u64,
264 base_offset: u64,
265 }) u64 {
266 assert(rec.tag == .fde);
267 const addend = mem.readInt(i64, rec.data[4..][0..8], .little);
268 return @as(u64, @intCast(@as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)) + addend));
269 }
258 fn format2(
259 ctx: FormatContext,
260 comptime unused_fmt_string: []const u8,
261 options: std.fmt.FormatOptions,
262 writer: anytype,
263 ) !void {
264 _ = unused_fmt_string;
265 _ = options;
266 const fde = ctx.fde;
267 const macho_file = ctx.macho_file;
268 try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
269 fde.offset,
270 fde.getSize(),
271 fde.cie,
272 fde.getAtom(macho_file).getName(macho_file),
273 });
274 if (!fde.alive) try writer.writeAll(" : [*]");
275 }
270276
271 pub fn setTargetSymbolAddress(rec: *Record, value: u64, ctx: struct {
272 base_addr: u64,
273 base_offset: u64,
274 }) !void {
275 assert(rec.tag == .fde);
276 const addend = @as(i64, @intCast(value)) - @as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8));
277 mem.writeInt(i64, rec.data[4..][0..8], addend, .little);
278 }
277 pub const Index = u32;
278};
279279
280 pub fn getPersonalityPointerReloc(
281 rec: Record,
282 macho_file: *MachO,
283 object_id: u32,
284 source_offset: u32,
285 ) ?SymbolWithLoc {
286 const target = macho_file.base.comp.root_mod.resolved_target.result;
287 const cpu_arch = target.cpu.arch;
288 const relocs = getRelocs(macho_file, object_id, source_offset);
289 for (relocs) |rel| {
290 switch (cpu_arch) {
291 .aarch64 => {
292 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
293 switch (rel_type) {
294 .ARM64_RELOC_SUBTRACTOR,
295 .ARM64_RELOC_UNSIGNED,
296 => continue,
297 .ARM64_RELOC_POINTER_TO_GOT => {},
298 else => unreachable,
299 }
300 },
301 .x86_64 => {
302 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
303 switch (rel_type) {
304 .X86_64_RELOC_GOT => {},
305 else => unreachable,
306 }
307 },
308 else => unreachable,
309 }
310 const reloc_target = Atom.parseRelocTarget(macho_file, .{
311 .object_id = object_id,
312 .rel = rel,
313 .code = rec.data,
314 .base_offset = @as(i32, @intCast(source_offset)) + 4,
315 });
316 return reloc_target;
317 }
318 return null;
319 }
280pub const Iterator = struct {
281 data: []const u8,
282 pos: u32 = 0,
320283
321 pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct {
322 source_offset: u32,
323 out_offset: u32,
324 sect_addr: u64,
325 }) !void {
326 comptime assert(is_mutable);
327
328 const target = macho_file.base.comp.root_mod.resolved_target.result;
329 const cpu_arch = target.cpu.arch;
330 const relocs = getRelocs(macho_file, object_id, ctx.source_offset);
331
332 for (relocs) |rel| {
333 const reloc_target = Atom.parseRelocTarget(macho_file, .{
334 .object_id = object_id,
335 .rel = rel,
336 .code = rec.data,
337 .base_offset = @as(i32, @intCast(ctx.source_offset)) + 4,
338 });
339 const rel_offset = @as(u32, @intCast(rel.r_address - @as(i32, @intCast(ctx.source_offset)) - 4));
340 const source_addr = ctx.sect_addr + rel_offset + ctx.out_offset + 4;
341
342 switch (cpu_arch) {
343 .aarch64 => {
344 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
345 switch (rel_type) {
346 .ARM64_RELOC_SUBTRACTOR => {
347 // Address of the __eh_frame in the source object file
348 },
349 .ARM64_RELOC_POINTER_TO_GOT => {
350 const target_addr = macho_file.getGotEntryAddress(reloc_target).?;
351 const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
352 return error.Overflow;
353 mem.writeInt(i32, rec.data[rel_offset..][0..4], result, .little);
354 },
355 .ARM64_RELOC_UNSIGNED => {
356 assert(rel.r_extern == 1);
357 const target_addr = Atom.getRelocTargetAddress(macho_file, reloc_target, false);
358 const result = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));
359 mem.writeInt(i64, rec.data[rel_offset..][0..8], @as(i64, @intCast(result)), .little);
360 },
361 else => unreachable,
362 }
363 },
364 .x86_64 => {
365 const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type));
366 switch (rel_type) {
367 .X86_64_RELOC_GOT => {
368 const target_addr = macho_file.getGotEntryAddress(reloc_target).?;
369 const addend = mem.readInt(i32, rec.data[rel_offset..][0..4], .little);
370 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));
371 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);
372 mem.writeInt(i32, rec.data[rel_offset..][0..4], disp, .little);
373 },
374 else => unreachable,
375 }
376 },
377 else => unreachable,
378 }
379 }
380 }
284 pub const Record = struct {
285 tag: enum { fde, cie },
286 offset: u32,
287 size: u32,
288 };
381289
382 pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 {
383 assert(rec.tag == .fde);
384 const target = macho_file.base.comp.root_mod.resolved_target.result;
385 const cpu_arch = target.cpu.arch;
386 const addend = mem.readInt(u32, rec.data[0..4], .little);
387 switch (cpu_arch) {
388 .aarch64 => {
389 const relocs = getRelocs(macho_file, object_id, offset);
390 const maybe_rel = for (relocs) |rel| {
391 if (rel.r_address - @as(i32, @intCast(offset)) == 4 and
392 @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_SUBTRACTOR)
393 break rel;
394 } else null;
395 const rel = maybe_rel orelse return addend;
396 const object = &macho_file.objects.items[object_id];
397 const target_addr = object.in_symtab.?[rel.r_symbolnum].n_value;
398 const sect = object.getSourceSection(object.eh_frame_sect_id.?);
399 return @intCast(sect.addr + offset - target_addr + addend);
400 },
401 .x86_64 => return addend,
402 else => unreachable,
403 }
404 }
290 pub fn next(it: *Iterator) !?Record {
291 if (it.pos >= it.data.len) return null;
405292
406 pub fn getCiePointer(rec: Record) u32 {
407 assert(rec.tag == .fde);
408 return mem.readInt(u32, rec.data[0..4], .little);
409 }
293 var stream = std.io.fixedBufferStream(it.data[it.pos..]);
294 const reader = stream.reader();
410295
411 pub fn setCiePointer(rec: *Record, ptr: u32) void {
412 assert(rec.tag == .fde);
413 mem.writeInt(u32, rec.data[0..4], ptr, .little);
414 }
296 const size = try reader.readInt(u32, .little);
297 if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS");
415298
416 pub fn getAugmentationString(rec: Record) []const u8 {
417 assert(rec.tag == .cie);
418 return mem.sliceTo(@as([*:0]const u8, @ptrCast(rec.data.ptr + 5)), 0);
419 }
299 const id = try reader.readInt(u32, .little);
300 const record = Record{
301 .tag = if (id == 0) .cie else .fde,
302 .offset = it.pos,
303 .size = size,
304 };
305 it.pos += size + 4;
420306
421 pub fn getPersonalityPointer(rec: Record, ctx: struct {
422 base_addr: u64,
423 base_offset: u64,
424 }) !?u64 {
425 assert(rec.tag == .cie);
426 const aug_str = rec.getAugmentationString();
307 return record;
308 }
309};
427310
428 var stream = std.io.fixedBufferStream(rec.data[9 + aug_str.len ..]);
429 var creader = std.io.countingReader(stream.reader());
430 const reader = creader.reader();
311pub fn calcSize(macho_file: *MachO) !u32 {
312 const tracy = trace(@src());
313 defer tracy.end();
431314
432 for (aug_str, 0..) |ch, i| switch (ch) {
433 'z' => if (i > 0) {
434 return error.BadDwarfCfi;
435 } else {
436 _ = try leb.readULEB128(u64, reader);
437 },
438 'R' => {
439 _ = try reader.readByte();
440 },
441 'P' => {
442 const enc = try reader.readByte();
443 const offset = ctx.base_offset + 13 + aug_str.len + creader.bytes_read;
444 const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader);
445 return ptr;
446 },
447 'L' => {
448 _ = try reader.readByte();
449 },
450 'S', 'B', 'G' => {},
451 else => return error.BadDwarfCfi,
452 };
315 var offset: u32 = 0;
453316
454 return null;
455 }
317 var cies = std.ArrayList(Cie).init(macho_file.base.comp.gpa);
318 defer cies.deinit();
319
320 for (macho_file.objects.items) |index| {
321 const object = macho_file.getFile(index).?.object;
456322
457 pub fn getLsdaPointer(rec: Record, cie: Record, ctx: struct {
458 base_addr: u64,
459 base_offset: u64,
460 }) !?u64 {
461 assert(rec.tag == .fde);
462 const enc = (try cie.getLsdaEncoding()) orelse return null;
463 var stream = std.io.fixedBufferStream(rec.data[20..]);
464 const reader = stream.reader();
465 _ = try reader.readByte();
466 const offset = ctx.base_offset + 25;
467 const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader);
468 return ptr;
323 outer: for (object.cies.items) |*cie| {
324 for (cies.items) |other| {
325 if (other.eql(cie.*, macho_file)) {
326 // We already have a CIE record that has the exact same contents, so instead of
327 // duplicating them, we mark this one dead and set its output offset to be
328 // equal to that of the alive record. This way, we won't have to rewrite
329 // Fde.cie_index field when committing the records to file.
330 cie.out_offset = other.out_offset;
331 continue :outer;
332 }
333 }
334 cie.alive = true;
335 cie.out_offset = offset;
336 offset += cie.getSize();
337 try cies.append(cie.*);
469338 }
339 }
470340
471 pub fn setLsdaPointer(rec: *Record, cie: Record, value: u64, ctx: struct {
472 base_addr: u64,
473 base_offset: u64,
474 }) !void {
475 assert(rec.tag == .fde);
476 const enc = (try cie.getLsdaEncoding()) orelse unreachable;
477 var stream = std.io.fixedBufferStream(rec.data[21..]);
478 const writer = stream.writer();
479 const offset = ctx.base_offset + 25;
480 try setEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), value, writer);
341 for (macho_file.objects.items) |index| {
342 const object = macho_file.getFile(index).?.object;
343 for (object.fdes.items) |*fde| {
344 if (!fde.alive) continue;
345 fde.out_offset = offset;
346 offset += fde.getSize();
481347 }
348 }
482349
483 fn getLsdaEncoding(rec: Record) !?u8 {
484 assert(rec.tag == .cie);
485 const aug_str = rec.getAugmentationString();
350 return offset;
351}
486352
487 const base_offset = 9 + aug_str.len;
488 var stream = std.io.fixedBufferStream(rec.data[base_offset..]);
489 var creader = std.io.countingReader(stream.reader());
490 const reader = creader.reader();
353pub fn calcNumRelocs(macho_file: *MachO) u32 {
354 const tracy = trace(@src());
355 defer tracy.end();
491356
492 for (aug_str, 0..) |ch, i| switch (ch) {
493 'z' => if (i > 0) {
494 return error.BadDwarfCfi;
495 } else {
496 _ = try leb.readULEB128(u64, reader);
497 },
498 'R' => {
499 _ = try reader.readByte();
500 },
501 'P' => {
502 const enc = try reader.readByte();
503 _ = try getEncodedPointer(enc, 0, reader);
504 },
505 'L' => {
506 const enc = try reader.readByte();
507 return enc;
508 },
509 'S', 'B', 'G' => {},
510 else => return error.BadDwarfCfi,
511 };
357 var nreloc: u32 = 0;
512358
513 return null;
359 for (macho_file.objects.items) |index| {
360 const object = macho_file.getFile(index).?.object;
361 for (object.cies.items) |cie| {
362 if (!cie.alive) continue;
363 if (cie.getPersonality(macho_file)) |_| {
364 nreloc += 1; // personality
365 }
514366 }
367 }
515368
516 fn getEncodedPointer(enc: u8, pcrel_offset: i64, reader: anytype) !?u64 {
517 if (enc == EH_PE.omit) return null;
518
519 var ptr: i64 = switch (enc & 0x0F) {
520 EH_PE.absptr => @as(i64, @bitCast(try reader.readInt(u64, .little))),
521 EH_PE.udata2 => @as(i16, @bitCast(try reader.readInt(u16, .little))),
522 EH_PE.udata4 => @as(i32, @bitCast(try reader.readInt(u32, .little))),
523 EH_PE.udata8 => @as(i64, @bitCast(try reader.readInt(u64, .little))),
524 EH_PE.uleb128 => @as(i64, @bitCast(try leb.readULEB128(u64, reader))),
525 EH_PE.sdata2 => try reader.readInt(i16, .little),
526 EH_PE.sdata4 => try reader.readInt(i32, .little),
527 EH_PE.sdata8 => try reader.readInt(i64, .little),
528 EH_PE.sleb128 => try leb.readILEB128(i64, reader),
529 else => return null,
530 };
369 return nreloc;
370}
531371
532 switch (enc & 0x70) {
533 EH_PE.absptr => {},
534 EH_PE.pcrel => ptr += pcrel_offset,
535 EH_PE.datarel,
536 EH_PE.textrel,
537 EH_PE.funcrel,
538 EH_PE.aligned,
539 => return null,
540 else => return null,
541 }
372pub fn write(macho_file: *MachO, buffer: []u8) void {
373 const tracy = trace(@src());
374 defer tracy.end();
375
376 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
377 const addend: i64 = switch (macho_file.getTarget().cpu.arch) {
378 .x86_64 => 4,
379 else => 0,
380 };
542381
543 return @as(u64, @bitCast(ptr));
382 for (macho_file.objects.items) |index| {
383 const object = macho_file.getFile(index).?.object;
384 for (object.cies.items) |cie| {
385 if (!cie.alive) continue;
386
387 @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
388
389 if (cie.getPersonality(macho_file)) |sym| {
390 const offset = cie.out_offset + cie.personality.?.offset;
391 const saddr = sect.addr + offset;
392 const taddr = sym.getGotAddress(macho_file);
393 std.mem.writeInt(
394 i32,
395 buffer[offset..][0..4],
396 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
397 .little,
398 );
399 }
544400 }
401 }
402
403 for (macho_file.objects.items) |index| {
404 const object = macho_file.getFile(index).?.object;
405 for (object.fdes.items) |fde| {
406 if (!fde.alive) continue;
545407
546 fn setEncodedPointer(enc: u8, pcrel_offset: i64, value: u64, writer: anytype) !void {
547 if (enc == EH_PE.omit) return;
408 @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
548409
549 var actual = @as(i64, @intCast(value));
410 {
411 const offset = fde.out_offset + 4;
412 const value = offset - fde.getCie(macho_file).out_offset;
413 std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);
414 }
550415
551 switch (enc & 0x70) {
552 EH_PE.absptr => {},
553 EH_PE.pcrel => actual -= pcrel_offset,
554 EH_PE.datarel,
555 EH_PE.textrel,
556 EH_PE.funcrel,
557 EH_PE.aligned,
558 => unreachable,
559 else => unreachable,
416 {
417 const offset = fde.out_offset + 8;
418 const saddr = sect.addr + offset;
419 const taddr = fde.getAtom(macho_file).value;
420 std.mem.writeInt(
421 i64,
422 buffer[offset..][0..8],
423 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
424 .little,
425 );
560426 }
561427
562 switch (enc & 0x0F) {
563 EH_PE.absptr => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),
564 EH_PE.udata2 => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(actual)))), .little),
565 EH_PE.udata4 => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(actual)))), .little),
566 EH_PE.udata8 => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),
567 EH_PE.uleb128 => try leb.writeULEB128(writer, @as(u64, @bitCast(actual))),
568 EH_PE.sdata2 => try writer.writeInt(i16, @as(i16, @intCast(actual)), .little),
569 EH_PE.sdata4 => try writer.writeInt(i32, @as(i32, @intCast(actual)), .little),
570 EH_PE.sdata8 => try writer.writeInt(i64, actual, .little),
571 EH_PE.sleb128 => try leb.writeILEB128(writer, actual),
572 else => unreachable,
428 if (fde.getLsdaAtom(macho_file)) |atom| {
429 const offset = fde.out_offset + fde.lsda_ptr_offset;
430 const saddr = sect.addr + offset;
431 const taddr = atom.value + fde.lsda_offset;
432 switch (fde.getCie(macho_file).lsda_size.?) {
433 .p32 => std.mem.writeInt(
434 i32,
435 buffer[offset..][0..4],
436 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
437 .little,
438 ),
439 .p64 => std.mem.writeInt(
440 i64,
441 buffer[offset..][0..8],
442 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
443 .little,
444 ),
445 }
573446 }
574447 }
575 };
576}
577
578pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info {
579 const object = &macho_file.objects.items[object_id];
580 assert(object.hasEhFrameRecords());
581 const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse
582 return &[0]macho.relocation_info{};
583 const all_relocs = object.getRelocs(object.eh_frame_sect_id.?);
584 return all_relocs[urel.reloc.start..][0..urel.reloc.len];
448 }
585449}
586450
587pub const Iterator = struct {
588 data: []const u8,
589 pos: u32 = 0,
451pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void {
452 const tracy = trace(@src());
453 defer tracy.end();
590454
591 pub fn next(it: *Iterator) !?EhFrameRecord(false) {
592 if (it.pos >= it.data.len) return null;
593
594 var stream = std.io.fixedBufferStream(it.data[it.pos..]);
595 const reader = stream.reader();
455 const cpu_arch = macho_file.getTarget().cpu.arch;
456 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
457 const addend: i64 = switch (cpu_arch) {
458 .x86_64 => 4,
459 else => 0,
460 };
596461
597 const size = try reader.readInt(u32, .little);
598 if (size == 0xFFFFFFFF) {
599 log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{});
600 return error.BadDwarfCfi;
462 for (macho_file.objects.items) |index| {
463 const object = macho_file.getFile(index).?.object;
464 for (object.cies.items) |cie| {
465 if (!cie.alive) continue;
466
467 @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
468
469 if (cie.getPersonality(macho_file)) |sym| {
470 const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow;
471 const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
472 relocs.appendAssumeCapacity(.{
473 .r_address = r_address,
474 .r_symbolnum = r_symbolnum,
475 .r_length = 2,
476 .r_extern = 1,
477 .r_pcrel = 1,
478 .r_type = switch (cpu_arch) {
479 .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT),
480 .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT),
481 else => unreachable,
482 },
483 });
484 }
601485 }
486 }
602487
603 const id = try reader.readInt(u32, .little);
604 const tag: EhFrameRecordTag = if (id == 0) .cie else .fde;
605 const offset: u32 = 4;
606 const record = EhFrameRecord(false){
607 .tag = tag,
608 .size = size,
609 .data = it.data[it.pos + offset ..][0..size],
610 };
488 for (macho_file.objects.items) |index| {
489 const object = macho_file.getFile(index).?.object;
490 for (object.fdes.items) |fde| {
491 if (!fde.alive) continue;
611492
612 it.pos += size + offset;
493 @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
613494
614 return record;
615 }
495 {
496 const offset = fde.out_offset + 4;
497 const value = offset - fde.getCie(macho_file).out_offset;
498 std.mem.writeInt(u32, code[offset..][0..4], value, .little);
499 }
616500
617 pub fn reset(it: *Iterator) void {
618 it.pos = 0;
619 }
501 {
502 const offset = fde.out_offset + 8;
503 const saddr = sect.addr + offset;
504 const taddr = fde.getAtom(macho_file).value;
505 std.mem.writeInt(
506 i64,
507 code[offset..][0..8],
508 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
509 .little,
510 );
511 }
620512
621 pub fn seekTo(it: *Iterator, pos: u32) void {
622 assert(pos >= 0 and pos < it.data.len);
623 it.pos = pos;
513 if (fde.getLsdaAtom(macho_file)) |atom| {
514 const offset = fde.out_offset + fde.lsda_ptr_offset;
515 const saddr = sect.addr + offset;
516 const taddr = atom.value + fde.lsda_offset;
517 switch (fde.getCie(macho_file).lsda_size.?) {
518 .p32 => std.mem.writeInt(
519 i32,
520 code[offset..][0..4],
521 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
522 .little,
523 ),
524 .p64 => std.mem.writeInt(
525 i64,
526 code[offset..][0..8],
527 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
528 .little,
529 ),
530 }
531 }
532 }
624533 }
625};
534}
626535
627536pub const EH_PE = struct {
628537 pub const absptr = 0x00;
......@@ -643,17 +552,17 @@ pub const EH_PE = struct {
643552 pub const omit = 0xFF;
644553};
645554
646const std = @import("std");
647555const assert = std.debug.assert;
556const leb = std.leb;
648557const macho = std.macho;
649558const math = std.math;
650559const mem = std.mem;
651const leb = std.leb;
652const log = std.log.scoped(.eh_frame);
560const std = @import("std");
561const trace = @import("../../tracy.zig").trace;
653562
654const Allocator = mem.Allocator;
563const Allocator = std.mem.Allocator;
655564const Atom = @import("Atom.zig");
565const File = @import("file.zig").File;
656566const MachO = @import("../MachO.zig");
657const Relocation = @import("Relocation.zig");
658const SymbolWithLoc = MachO.SymbolWithLoc;
659const UnwindInfo = @import("UnwindInfo.zig");
567const Object = @import("Object.zig");
568const Symbol = @import("Symbol.zig");
src/link/MachO/fat.zig+23-20
......@@ -1,24 +1,34 @@
1pub fn isFatLibrary(file: std.fs.File) bool {
2 const reader = file.reader();
3 const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false;
4 defer file.seekTo(0) catch {};
1const std = @import("std");
2const assert = std.debug.assert;
3const builtin = @import("builtin");
4const log = std.log.scoped(.macho);
5const macho = std.macho;
6const mem = std.mem;
7const native_endian = builtin.target.cpu.arch.endian();
8
9const MachO = @import("../MachO.zig");
10
11pub fn isFatLibrary(path: []const u8) !bool {
12 const file = try std.fs.cwd().openFile(path, .{});
13 defer file.close();
14 const hdr = file.reader().readStructEndian(macho.fat_header, .big) catch return false;
515 return hdr.magic == macho.FAT_MAGIC;
616}
717
818pub const Arch = struct {
919 tag: std.Target.Cpu.Arch,
10 offset: u64,
20 offset: u32,
21 size: u32,
1122};
1223
13/// Caller owns the memory.
14pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
24pub fn parseArchs(path: []const u8, buffer: *[2]Arch) ![]const Arch {
25 const file = try std.fs.cwd().openFile(path, .{});
26 defer file.close();
1527 const reader = file.reader();
1628 const fat_header = try reader.readStructEndian(macho.fat_header, .big);
1729 assert(fat_header.magic == macho.FAT_MAGIC);
1830
19 var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch);
20 defer archs.deinit();
21
31 var count: usize = 0;
2232 var fat_arch_index: u32 = 0;
2333 while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
2434 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 {
2939 macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue,
3040 else => continue,
3141 };
32
33 archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset });
42 buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size };
43 count += 1;
3444 }
3545
36 return archs.toOwnedSlice();
46 return buffer[0..count];
3747}
38
39const std = @import("std");
40const assert = std.debug.assert;
41const log = std.log.scoped(.archive);
42const macho = std.macho;
43const mem = std.mem;
44const Allocator = mem.Allocator;
src/link/MachO/file.zig created+120
......@@ -0,0 +1,120 @@
1pub const File = union(enum) {
2 zig_object: *ZigObject,
3 internal: *InternalObject,
4 object: *Object,
5 dylib: *Dylib,
6
7 pub fn getIndex(file: File) Index {
8 return switch (file) {
9 inline else => |x| x.index,
10 };
11 }
12
13 pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) {
14 return .{ .data = file };
15 }
16
17 fn formatPath(
18 file: File,
19 comptime unused_fmt_string: []const u8,
20 options: std.fmt.FormatOptions,
21 writer: anytype,
22 ) !void {
23 _ = unused_fmt_string;
24 _ = options;
25 switch (file) {
26 .zig_object => |x| try writer.writeAll(x.path),
27 .internal => try writer.writeAll(""),
28 .object => |x| try writer.print("{}", .{x.fmtPath()}),
29 .dylib => |x| try writer.writeAll(x.path),
30 }
31 }
32
33 pub fn resolveSymbols(file: File, macho_file: *MachO) void {
34 switch (file) {
35 .internal => unreachable,
36 inline else => |x| x.resolveSymbols(macho_file),
37 }
38 }
39
40 pub fn resetGlobals(file: File, macho_file: *MachO) void {
41 switch (file) {
42 .internal => unreachable,
43 inline else => |x| x.resetGlobals(macho_file),
44 }
45 }
46
47 /// Encodes symbol rank so that the following ordering applies:
48 /// * strong in object
49 /// * weak in object
50 /// * tentative in object
51 /// * strong in archive/dylib
52 /// * weak in archive/dylib
53 /// * tentative in archive
54 /// * unclaimed
55 pub fn getSymbolRank(file: File, args: struct {
56 archive: bool = false,
57 weak: bool = false,
58 tentative: bool = false,
59 }) u32 {
60 if (file == .object and !args.archive) {
61 const base: u32 = blk: {
62 if (args.tentative) break :blk 3;
63 break :blk if (args.weak) 2 else 1;
64 };
65 return (base << 16) + file.getIndex();
66 }
67 const base: u32 = blk: {
68 if (args.tentative) break :blk 3;
69 break :blk if (args.weak) 2 else 1;
70 };
71 return base + (file.getIndex() << 24);
72 }
73
74 pub fn getSymbols(file: File) []const Symbol.Index {
75 return switch (file) {
76 inline else => |x| x.symbols.items,
77 };
78 }
79
80 pub fn getAtoms(file: File) []const Atom.Index {
81 return switch (file) {
82 .dylib => unreachable,
83 inline else => |x| x.atoms.items,
84 };
85 }
86
87 pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
88 return switch (file) {
89 inline else => |x| x.calcSymtabSize(macho_file),
90 };
91 }
92
93 pub fn writeSymtab(file: File, macho_file: *MachO) !void {
94 return switch (file) {
95 inline else => |x| x.writeSymtab(macho_file),
96 };
97 }
98
99 pub const Index = u32;
100
101 pub const Entry = union(enum) {
102 null: void,
103 zig_object: ZigObject,
104 internal: InternalObject,
105 object: Object,
106 dylib: Dylib,
107 };
108};
109
110const macho = std.macho;
111const std = @import("std");
112
113const Allocator = std.mem.Allocator;
114const Atom = @import("Atom.zig");
115const InternalObject = @import("InternalObject.zig");
116const MachO = @import("../MachO.zig");
117const Object = @import("Object.zig");
118const Dylib = @import("Dylib.zig");
119const Symbol = @import("Symbol.zig");
120const ZigObject = @import("ZigObject.zig");
src/link/MachO/hasher.zig+9-2
......@@ -9,6 +9,9 @@ pub fn ParallelHasher(comptime Hasher: type) type {
99 chunk_size: u64 = 0x4000,
1010 max_file_size: ?u64 = null,
1111 }) !void {
12 const tracy = trace(@src());
13 defer tracy.end();
14
1215 var wg: WaitGroup = .{};
1316
1417 const file_size = blk: {
......@@ -29,7 +32,10 @@ pub fn ParallelHasher(comptime Hasher: type) type {
2932
3033 for (out, results, 0..) |*out_buf, *result, i| {
3134 const fstart = i * chunk_size;
32 const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size;
35 const fsize = if (fstart + chunk_size > file_size)
36 file_size - fstart
37 else
38 chunk_size;
3339 wg.start();
3440 try self.thread_pool.spawn(worker, .{
3541 file,
......@@ -61,10 +67,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
6167 };
6268}
6369
64const std = @import("std");
6570const assert = std.debug.assert;
6671const fs = std.fs;
6772const mem = std.mem;
73const std = @import("std");
74const trace = @import("../../tracy.zig").trace;
6875
6976const Allocator = mem.Allocator;
7077const ThreadPool = std.Thread.Pool;
src/link/MachO/load_commands.zig+97-389
......@@ -1,4 +1,13 @@
1/// Default path to dyld.
1const std = @import("std");
2const assert = std.debug.assert;
3const log = std.log.scoped(.link);
4const macho = std.macho;
5const mem = std.mem;
6
7const Allocator = mem.Allocator;
8const Dylib = @import("Dylib.zig");
9const MachO = @import("../MachO.zig");
10
211pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
312
413fn 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
716 return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));
817}
918
10const CalcLCsSizeCtx = struct {
11 segments: []const macho.segment_command_64,
12 dylibs: []const Dylib,
13 referenced_dylibs: []u16,
14 wants_function_starts: bool = true,
15};
16
17fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
18 const comp = m.base.comp;
19 const gpa = comp.gpa;
20 var has_text_segment: bool = false;
19pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 {
2120 var sizeofcmds: u64 = 0;
22 for (ctx.segments) |seg| {
23 sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);
24 if (mem.eql(u8, seg.segName(), "__TEXT")) {
25 has_text_segment = true;
26 }
21
22 // LC_SEGMENT_64
23 sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len;
24 for (macho_file.segments.items) |seg| {
25 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
2726 }
2827
2928 // LC_DYLD_INFO_ONLY
3029 sizeofcmds += @sizeOf(macho.dyld_info_command);
3130 // LC_FUNCTION_STARTS
32 if (has_text_segment and ctx.wants_function_starts) {
33 sizeofcmds += @sizeOf(macho.linkedit_data_command);
34 }
31 sizeofcmds += @sizeOf(macho.linkedit_data_command);
3532 // LC_DATA_IN_CODE
3633 sizeofcmds += @sizeOf(macho.linkedit_data_command);
3734 // LC_SYMTAB
......@@ -45,15 +42,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
4542 false,
4643 );
4744 // LC_MAIN
48 if (comp.config.output_mode == .Exe) {
45 if (!macho_file.base.isDynLib()) {
4946 sizeofcmds += @sizeOf(macho.entry_point_command);
5047 }
5148 // LC_ID_DYLIB
52 if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) {
49 if (macho_file.base.isDynLib()) {
5350 sizeofcmds += blk: {
54 const emit = m.base.emit;
55 const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});
56 defer if (m.install_name == null) gpa.free(install_name);
51 const emit = macho_file.base.emit;
52 const install_name = macho_file.install_name orelse emit.sub_path;
5753 break :blk calcInstallNameLen(
5854 @sizeOf(macho.dylib_command),
5955 install_name,
......@@ -63,9 +59,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
6359 }
6460 // LC_RPATH
6561 {
66 var it = RpathIterator.init(gpa, m.base.rpath_list);
67 defer it.deinit();
68 while (try it.next()) |rpath| {
62 for (macho_file.base.rpath_list) |rpath| {
6963 sizeofcmds += calcInstallNameLen(
7064 @sizeOf(macho.rpath_command),
7165 rpath,
......@@ -75,24 +69,20 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
7569 }
7670 // LC_SOURCE_VERSION
7771 sizeofcmds += @sizeOf(macho.source_version_command);
78 // LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing
79 {
80 const target = comp.root_mod.resolved_target.result;
81 const platform = Platform.fromTarget(target);
82 if (platform.isBuildVersionCompatible()) {
83 // LC_BUILD_VERSION
84 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
85 } else if (platform.isVersionMinCompatible()) {
86 // LC_VERSION_MIN_
87 sizeofcmds += @sizeOf(macho.version_min_command);
88 }
72 if (macho_file.platform.isBuildVersionCompatible()) {
73 // LC_BUILD_VERSION
74 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
75 } else {
76 // LC_VERSION_MIN_*
77 sizeofcmds += @sizeOf(macho.version_min_command);
8978 }
9079 // LC_UUID
9180 sizeofcmds += @sizeOf(macho.uuid_command);
9281 // LC_LOAD_DYLIB
93 for (ctx.referenced_dylibs) |id| {
94 const dylib = ctx.dylibs[id];
95 const dylib_id = dylib.id orelse unreachable;
82 for (macho_file.dylibs.items) |index| {
83 const dylib = macho_file.getFile(index).?.dylib;
84 assert(dylib.isAlive(macho_file));
85 const dylib_id = dylib.id.?;
9686 sizeofcmds += calcInstallNameLen(
9787 @sizeOf(macho.dylib_command),
9888 dylib_id.name,
......@@ -100,19 +90,48 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
10090 );
10191 }
10292 // LC_CODE_SIGNATURE
103 if (m.requiresCodeSignature()) {
93 if (macho_file.requiresCodeSig()) {
10494 sizeofcmds += @sizeOf(macho.linkedit_data_command);
10595 }
10696
107 return @intCast(sizeofcmds);
97 return @as(u32, @intCast(sizeofcmds));
10898}
10999
110pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
111 var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size;
100pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
101 var sizeofcmds: u64 = 0;
102
103 // LC_SEGMENT_64
104 {
105 assert(macho_file.segments.items.len == 1);
106 sizeofcmds += @sizeOf(macho.segment_command_64);
107 const seg = macho_file.segments.items[0];
108 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
109 }
110
111 // LC_DATA_IN_CODE
112 sizeofcmds += @sizeOf(macho.linkedit_data_command);
113 // LC_SYMTAB
114 sizeofcmds += @sizeOf(macho.symtab_command);
115 // LC_DYSYMTAB
116 sizeofcmds += @sizeOf(macho.dysymtab_command);
117
118 if (macho_file.platform.isBuildVersionCompatible()) {
119 // LC_BUILD_VERSION
120 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
121 } else {
122 // LC_VERSION_MIN_*
123 sizeofcmds += @sizeOf(macho.version_min_command);
124 }
125
126 return @as(u32, @intCast(sizeofcmds));
127}
128
129pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 {
130 var padding: u32 = calcLoadCommandsSize(macho_file, false) + (macho_file.headerpad_size orelse 0);
112131 log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
113132
114 if (m.headerpad_max_install_names) {
115 const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true);
133 if (macho_file.headerpad_max_install_names) {
134 const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true);
116135 log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
117136 min_headerpad_size + @sizeOf(macho.mach_header_64),
118137 });
......@@ -125,34 +144,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
125144 return offset;
126145}
127146
128pub fn calcNumOfLCs(lc_buffer: []const u8) u32 {
129 var ncmds: u32 = 0;
130 var pos: usize = 0;
131 while (true) {
132 if (pos >= lc_buffer.len) break;
133 const cmd = @as(*align(1) const macho.load_command, @ptrCast(lc_buffer.ptr + pos)).*;
134 ncmds += 1;
135 pos += cmd.cmdsize;
136 }
137 return ncmds;
138}
139
140pub fn writeDylinkerLC(lc_writer: anytype) !void {
147pub fn writeDylinkerLC(writer: anytype) !void {
141148 const name_len = mem.sliceTo(default_dyld_path, 0).len;
142149 const cmdsize = @as(u32, @intCast(mem.alignForward(
143150 u64,
144151 @sizeOf(macho.dylinker_command) + name_len,
145152 @sizeOf(u64),
146153 )));
147 try lc_writer.writeStruct(macho.dylinker_command{
154 try writer.writeStruct(macho.dylinker_command{
148155 .cmd = .LOAD_DYLINKER,
149156 .cmdsize = cmdsize,
150157 .name = @sizeOf(macho.dylinker_command),
151158 });
152 try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0));
159 try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
153160 const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
154161 if (padding > 0) {
155 try lc_writer.writeByteNTimes(0, padding);
162 try writer.writeByteNTimes(0, padding);
156163 }
157164}
158165
......@@ -164,14 +171,14 @@ const WriteDylibLCCtx = struct {
164171 compatibility_version: u32 = 0x10000,
165172};
166173
167fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
174pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void {
168175 const name_len = ctx.name.len + 1;
169176 const cmdsize = @as(u32, @intCast(mem.alignForward(
170177 u64,
171178 @sizeOf(macho.dylib_command) + name_len,
172179 @sizeOf(u64),
173180 )));
174 try lc_writer.writeStruct(macho.dylib_command{
181 try writer.writeStruct(macho.dylib_command{
175182 .cmd = ctx.cmd,
176183 .cmdsize = cmdsize,
177184 .dylib = .{
......@@ -181,15 +188,15 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
181188 .compatibility_version = ctx.compatibility_version,
182189 },
183190 });
184 try lc_writer.writeAll(ctx.name);
185 try lc_writer.writeByte(0);
191 try writer.writeAll(ctx.name);
192 try writer.writeByte(0);
186193 const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
187194 if (padding > 0) {
188 try lc_writer.writeByteNTimes(0, padding);
195 try writer.writeByteNTimes(0, padding);
189196 }
190197}
191198
192pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void {
199pub fn writeDylibIdLC(macho_file: *MachO, writer: anytype) !void {
193200 const comp = macho_file.base.comp;
194201 const gpa = comp.gpa;
195202 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 {
212219 .name = install_name,
213220 .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)),
214221 .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)),
215 }, lc_writer);
222 }, writer);
216223}
217224
218const RpathIterator = struct {
219 buffer: []const []const u8,
220 table: std.StringHashMap(void),
221 count: usize = 0,
222
223 fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator {
224 return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) };
225 }
226
227 fn deinit(it: *RpathIterator) void {
228 it.table.deinit();
229 }
230
231 fn next(it: *RpathIterator) !?[]const u8 {
232 while (true) {
233 if (it.count >= it.buffer.len) return null;
234 const rpath = it.buffer[it.count];
235 it.count += 1;
236 const gop = try it.table.getOrPut(rpath);
237 if (gop.found_existing) continue;
238 return rpath;
239 }
240 }
241};
242
243pub fn writeRpathLCs(macho_file: *MachO, lc_writer: anytype) !void {
244 const comp = macho_file.base.comp;
245 const gpa = comp.gpa;
246
247 var it = RpathIterator.init(gpa, macho_file.base.rpath_list);
248 defer it.deinit();
249
250 while (try it.next()) |rpath| {
225pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void {
226 for (rpaths) |rpath| {
251227 const rpath_len = rpath.len + 1;
252228 const cmdsize = @as(u32, @intCast(mem.alignForward(
253229 u64,
254230 @sizeOf(macho.rpath_command) + rpath_len,
255231 @sizeOf(u64),
256232 )));
257 try lc_writer.writeStruct(macho.rpath_command{
233 try writer.writeStruct(macho.rpath_command{
258234 .cmdsize = cmdsize,
259235 .path = @sizeOf(macho.rpath_command),
260236 });
261 try lc_writer.writeAll(rpath);
262 try lc_writer.writeByte(0);
237 try writer.writeAll(rpath);
238 try writer.writeByte(0);
263239 const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
264240 if (padding > 0) {
265 try lc_writer.writeByteNTimes(0, padding);
241 try writer.writeByteNTimes(0, padding);
266242 }
267243 }
268244}
269245
270pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
246pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void {
271247 const cmd: macho.LC = switch (platform.os_tag) {
272248 .macos => .VERSION_MIN_MACOSX,
273249 .ios => .VERSION_MIN_IPHONEOS,
......@@ -275,298 +251,30 @@ pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion,
275251 .watchos => .VERSION_MIN_WATCHOS,
276252 else => unreachable,
277253 };
278 try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{
254 try writer.writeAll(mem.asBytes(&macho.version_min_command{
279255 .cmd = cmd,
280256 .version = platform.toAppleVersion(),
281 .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
257 .sdk = if (sdk_version) |ver|
258 MachO.semanticVersionToAppleVersion(ver)
259 else
260 platform.toAppleVersion(),
282261 }));
283262}
284263
285pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {
264pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void {
286265 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
287 try lc_writer.writeStruct(macho.build_version_command{
266 try writer.writeStruct(macho.build_version_command{
288267 .cmdsize = cmdsize,
289268 .platform = platform.toApplePlatform(),
290269 .minos = platform.toAppleVersion(),
291 .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),
270 .sdk = if (sdk_version) |ver|
271 MachO.semanticVersionToAppleVersion(ver)
272 else
273 platform.toAppleVersion(),
292274 .ntools = 1,
293275 });
294 try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{
276 try writer.writeAll(mem.asBytes(&macho.build_tool_version{
295277 .tool = .ZIG,
296278 .version = 0x0,
297279 }));
298280}
299
300pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void {
301 for (referenced) |index| {
302 const dylib = dylibs[index];
303 const dylib_id = dylib.id orelse unreachable;
304 try writeDylibLC(.{
305 .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
306 .name = dylib_id.name,
307 .timestamp = dylib_id.timestamp,
308 .current_version = dylib_id.current_version,
309 .compatibility_version = dylib_id.compatibility_version,
310 }, lc_writer);
311 }
312}
313
314pub const Platform = struct {
315 os_tag: std.Target.Os.Tag,
316 abi: std.Target.Abi,
317 version: std.SemanticVersion,
318
319 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
320 /// the extracted minimum platform version.
321 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
322 switch (lc.cmd()) {
323 .BUILD_VERSION => {
324 const cmd = lc.cast(macho.build_version_command).?;
325 return .{
326 .os_tag = switch (cmd.platform) {
327 .MACOS => .macos,
328 .IOS, .IOSSIMULATOR => .ios,
329 .TVOS, .TVOSSIMULATOR => .tvos,
330 .WATCHOS, .WATCHOSSIMULATOR => .watchos,
331 else => @panic("TODO"),
332 },
333 .abi = switch (cmd.platform) {
334 .IOSSIMULATOR,
335 .TVOSSIMULATOR,
336 .WATCHOSSIMULATOR,
337 => .simulator,
338 else => .none,
339 },
340 .version = appleVersionToSemanticVersion(cmd.minos),
341 };
342 },
343 .VERSION_MIN_MACOSX,
344 .VERSION_MIN_IPHONEOS,
345 .VERSION_MIN_TVOS,
346 .VERSION_MIN_WATCHOS,
347 => {
348 const cmd = lc.cast(macho.version_min_command).?;
349 return .{
350 .os_tag = switch (lc.cmd()) {
351 .VERSION_MIN_MACOSX => .macos,
352 .VERSION_MIN_IPHONEOS => .ios,
353 .VERSION_MIN_TVOS => .tvos,
354 .VERSION_MIN_WATCHOS => .watchos,
355 else => unreachable,
356 },
357 .abi = .none,
358 .version = appleVersionToSemanticVersion(cmd.version),
359 };
360 },
361 else => unreachable,
362 }
363 }
364
365 pub fn fromTarget(target: std.Target) Platform {
366 return .{
367 .os_tag = target.os.tag,
368 .abi = target.abi,
369 .version = target.os.version_range.semver.min,
370 };
371 }
372
373 pub fn toAppleVersion(plat: Platform) u32 {
374 return semanticVersionToAppleVersion(plat.version);
375 }
376
377 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
378 return switch (plat.os_tag) {
379 .macos => .MACOS,
380 .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
381 .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
382 .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
383 else => unreachable,
384 };
385 }
386
387 pub fn isBuildVersionCompatible(plat: Platform) bool {
388 inline for (supported_platforms) |sup_plat| {
389 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
390 return sup_plat[2] <= plat.toAppleVersion();
391 }
392 }
393 return false;
394 }
395
396 pub fn isVersionMinCompatible(plat: Platform) bool {
397 inline for (supported_platforms) |sup_plat| {
398 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
399 return sup_plat[3] <= plat.toAppleVersion();
400 }
401 }
402 return false;
403 }
404
405 pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
406 return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
407 }
408
409 const FmtCtx = struct {
410 platform: Platform,
411 cpu_arch: std.Target.Cpu.Arch,
412 };
413
414 pub fn formatTarget(
415 ctx: FmtCtx,
416 comptime unused_fmt_string: []const u8,
417 options: std.fmt.FormatOptions,
418 writer: anytype,
419 ) !void {
420 _ = unused_fmt_string;
421 _ = options;
422 try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
423 if (ctx.platform.abi != .none) {
424 try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
425 }
426 }
427
428 /// Caller owns the memory.
429 pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
430 var buffer = std.ArrayList(u8).init(gpa);
431 defer buffer.deinit();
432 try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
433 return buffer.toOwnedSlice();
434 }
435
436 pub fn eqlTarget(plat: Platform, other: Platform) bool {
437 return plat.os_tag == other.os_tag and plat.abi == other.abi;
438 }
439};
440
441const SupportedPlatforms = struct {
442 std.Target.Os.Tag,
443 std.Target.Abi,
444 u32, // Min platform version for which to emit LC_BUILD_VERSION
445 u32, // Min supported platform version
446};
447
448// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
449// zig fmt: off
450const supported_platforms = [_]SupportedPlatforms{
451 .{ .macos, .none, 0xA0E00, 0xA0800 },
452 .{ .ios, .none, 0xC0000, 0x70000 },
453 .{ .tvos, .none, 0xC0000, 0x70000 },
454 .{ .watchos, .none, 0x50000, 0x20000 },
455 .{ .ios, .simulator, 0xD0000, 0x80000 },
456 .{ .tvos, .simulator, 0xD0000, 0x80000 },
457 .{ .watchos, .simulator, 0x60000, 0x20000 },
458};
459// zig fmt: on
460
461inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
462 const major = version.major;
463 const minor = version.minor;
464 const patch = version.patch;
465 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
466}
467
468pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
469 return .{
470 .major = @as(u16, @truncate(version >> 16)),
471 .minor = @as(u8, @truncate(version >> 8)),
472 .patch = @as(u8, @truncate(version)),
473 };
474}
475
476pub fn inferSdkVersion(macho_file: *MachO) ?std.SemanticVersion {
477 const comp = macho_file.base.comp;
478 const gpa = comp.gpa;
479
480 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
481 defer arena_allocator.deinit();
482 const arena = arena_allocator.allocator();
483
484 const sdk_layout = macho_file.sdk_layout orelse return null;
485 const sdk_dir = switch (sdk_layout) {
486 .sdk => comp.sysroot.?,
487 .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
488 };
489 if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
490 return parseSdkVersion(ver);
491 } else |_| {
492 // Read from settings should always succeed when vendored.
493 if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
494 }
495
496 // infer from pathname
497 const stem = std.fs.path.stem(sdk_dir);
498 const start = for (stem, 0..) |c, i| {
499 if (std.ascii.isDigit(c)) break i;
500 } else stem.len;
501 const end = for (stem[start..], start..) |c, i| {
502 if (std.ascii.isDigit(c) or c == '.') continue;
503 break i;
504 } else stem.len;
505 return parseSdkVersion(stem[start..end]);
506}
507
508// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
509// Use property `MinimalDisplayName` to determine version.
510// The file/property is also available with vendored libc.
511fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
512 const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
513 const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
514 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
515 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
516 return error.SdkVersionFailure;
517}
518
519// Versions reported by Apple aren't exactly semantically valid as they usually omit
520// the patch component, so we parse SDK value by hand.
521fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
522 var parsed: std.SemanticVersion = .{
523 .major = 0,
524 .minor = 0,
525 .patch = 0,
526 };
527
528 const parseNext = struct {
529 fn parseNext(it: anytype) ?u16 {
530 const nn = it.next() orelse return null;
531 return std.fmt.parseInt(u16, nn, 10) catch null;
532 }
533 }.parseNext;
534
535 var it = std.mem.splitAny(u8, raw, ".");
536 parsed.major = parseNext(&it) orelse return null;
537 parsed.minor = parseNext(&it) orelse return null;
538 parsed.patch = parseNext(&it) orelse 0;
539 return parsed;
540}
541
542const expect = std.testing.expect;
543const expectEqual = std.testing.expectEqual;
544
545fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void {
546 const maybe_ver = parseSdkVersion(raw);
547 try expect(maybe_ver != null);
548 const ver = maybe_ver.?;
549 try expectEqual(exp.major, ver.major);
550 try expectEqual(exp.minor, ver.minor);
551 try expectEqual(exp.patch, ver.patch);
552}
553
554test "parseSdkVersion" {
555 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
556 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
557 try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
558
559 try expect(parseSdkVersion("11") == null);
560}
561
562const std = @import("std");
563const assert = std.debug.assert;
564const link = @import("../../link.zig");
565const log = std.log.scoped(.link);
566const macho = std.macho;
567const mem = std.mem;
568
569const Allocator = mem.Allocator;
570const Dylib = @import("Dylib.zig");
571const MachO = @import("../MachO.zig");
572const Compilation = @import("../../Compilation.zig");
src/link/MachO/relocatable.zig created+506
......@@ -0,0 +1,506 @@
1pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void {
2 const gpa = macho_file.base.comp.gpa;
3
4 var positionals = std.ArrayList(Compilation.LinkObject).init(gpa);
5 defer positionals.deinit();
6 try positionals.ensureUnusedCapacity(comp.objects.len);
7 positionals.appendSliceAssumeCapacity(comp.objects);
8
9 for (comp.c_object_table.keys()) |key| {
10 try positionals.append(.{ .path = key.status.success.object_path });
11 }
12
13 if (module_obj_path) |path| try positionals.append(.{ .path = path });
14
15 if (positionals.items.len == 1) {
16 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
17 // debug info segments/sections (this is apparently by design by Apple), we copy
18 // the *only* input file over.
19 // TODO: in the future, when we implement `dsymutil` alternative directly in the Zig
20 // compiler, investigate if we can get rid of this `if` prong here.
21 const path = positionals.items[0].path;
22 const in_file = try std.fs.cwd().openFile(path, .{});
23 const stat = try in_file.stat();
24 const amt = try in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size);
25 if (amt != stat.size) return error.InputOutput; // TODO: report an actual user error
26 return;
27 }
28
29 for (positionals.items) |obj| {
30 macho_file.parsePositional(obj.path, obj.must_link) catch |err| switch (err) {
31 error.MalformedObject,
32 error.MalformedArchive,
33 error.InvalidCpuArch,
34 error.InvalidTarget,
35 => continue, // already reported
36 error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}),
37 else => |e| try macho_file.reportParseError(
38 obj.path,
39 "unexpected error: parsing input file failed with error {s}",
40 .{@errorName(e)},
41 ),
42 };
43 }
44
45 if (comp.link_errors.items.len > 0) return error.FlushFailure;
46
47 try macho_file.addUndefinedGlobals();
48 try macho_file.resolveSymbols();
49 markExports(macho_file);
50 claimUnresolved(macho_file);
51 try initOutputSections(macho_file);
52 try macho_file.sortSections();
53 try macho_file.addAtomsToSections();
54 try calcSectionSizes(macho_file);
55
56 {
57 // For relocatable, we only ever need a single segment so create it now.
58 const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC;
59 try macho_file.segments.append(gpa, .{
60 .cmdsize = @sizeOf(macho.segment_command_64),
61 .segname = MachO.makeStaticString(""),
62 .maxprot = prot,
63 .initprot = prot,
64 });
65 const seg = &macho_file.segments.items[0];
66 seg.nsects = @intCast(macho_file.sections.items(.header).len);
67 seg.cmdsize += seg.nsects * @sizeOf(macho.section_64);
68 }
69
70 var off = try allocateSections(macho_file);
71
72 {
73 // Allocate the single segment.
74 assert(macho_file.segments.items.len == 1);
75 const seg = &macho_file.segments.items[0];
76 var vmaddr: u64 = 0;
77 var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
78 seg.vmaddr = vmaddr;
79 seg.fileoff = fileoff;
80
81 for (macho_file.sections.items(.header)) |header| {
82 vmaddr = header.addr + header.size;
83 if (!header.isZerofill()) {
84 fileoff = header.offset + header.size;
85 }
86 }
87
88 seg.vmsize = vmaddr - seg.vmaddr;
89 seg.filesize = fileoff - seg.fileoff;
90 }
91
92 macho_file.allocateAtoms();
93
94 state_log.debug("{}", .{macho_file.dumpState()});
95
96 try macho_file.calcSymtabSize();
97 try writeAtoms(macho_file);
98 try writeCompactUnwind(macho_file);
99 try writeEhFrame(macho_file);
100
101 off = mem.alignForward(u32, off, @alignOf(u64));
102 off = try macho_file.writeDataInCode(0, off);
103 off = mem.alignForward(u32, off, @alignOf(u64));
104 off = try macho_file.writeSymtab(off);
105 off = mem.alignForward(u32, off, @alignOf(u64));
106 off = try macho_file.writeStrtab(off);
107
108 const ncmds, const sizeofcmds = try writeLoadCommands(macho_file);
109 try writeHeader(macho_file, ncmds, sizeofcmds);
110}
111
112fn markExports(macho_file: *MachO) void {
113 for (macho_file.objects.items) |index| {
114 for (macho_file.getFile(index).?.getSymbols()) |sym_index| {
115 const sym = macho_file.getSymbol(sym_index);
116 const file = sym.getFile(macho_file) orelse continue;
117 if (sym.visibility != .global) continue;
118 if (file.getIndex() == index) {
119 sym.flags.@"export" = true;
120 }
121 }
122 }
123}
124
125fn claimUnresolved(macho_file: *MachO) void {
126 for (macho_file.objects.items) |index| {
127 const object = macho_file.getFile(index).?.object;
128
129 for (object.symbols.items, 0..) |sym_index, i| {
130 const nlist_idx = @as(Symbol.Index, @intCast(i));
131 const nlist = object.symtab.items(.nlist)[nlist_idx];
132 if (!nlist.ext()) continue;
133 if (!nlist.undf()) continue;
134
135 const sym = macho_file.getSymbol(sym_index);
136 if (sym.getFile(macho_file) != null) continue;
137
138 sym.value = 0;
139 sym.atom = 0;
140 sym.nlist_idx = nlist_idx;
141 sym.file = index;
142 sym.flags.weak_ref = nlist.weakRef();
143 sym.flags.import = true;
144 sym.visibility = .global;
145 }
146 }
147}
148
149fn initOutputSections(macho_file: *MachO) !void {
150 for (macho_file.objects.items) |index| {
151 const object = macho_file.getFile(index).?.object;
152 for (object.atoms.items) |atom_index| {
153 const atom = macho_file.getAtom(atom_index) orelse continue;
154 if (!atom.flags.alive) continue;
155 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
156 }
157 }
158
159 const needs_unwind_info = for (macho_file.objects.items) |index| {
160 if (macho_file.getFile(index).?.object.hasUnwindRecords()) break true;
161 } else false;
162 if (needs_unwind_info) {
163 macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{
164 .flags = macho.S_ATTR_DEBUG,
165 });
166 }
167
168 const needs_eh_frame = for (macho_file.objects.items) |index| {
169 if (macho_file.getFile(index).?.object.hasEhFrameRecords()) break true;
170 } else false;
171 if (needs_eh_frame) {
172 assert(needs_unwind_info);
173 macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{});
174 }
175}
176
177fn calcSectionSizes(macho_file: *MachO) !void {
178 const tracy = trace(@src());
179 defer tracy.end();
180
181 const slice = macho_file.sections.slice();
182 for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
183 if (atoms.items.len == 0) continue;
184 for (atoms.items) |atom_index| {
185 const atom = macho_file.getAtom(atom_index).?;
186 const atom_alignment = atom.alignment.toByteUnits(1);
187 const offset = mem.alignForward(u64, header.size, atom_alignment);
188 const padding = offset - header.size;
189 atom.value = offset;
190 header.size += padding + atom.size;
191 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
192 header.nreloc += atom.calcNumRelocs(macho_file);
193 }
194 }
195
196 if (macho_file.unwind_info_sect_index) |index| {
197 calcCompactUnwindSize(macho_file, index);
198 }
199
200 if (macho_file.eh_frame_sect_index) |index| {
201 const sect = &macho_file.sections.items(.header)[index];
202 sect.size = try eh_frame.calcSize(macho_file);
203 sect.@"align" = 3;
204 sect.nreloc = eh_frame.calcNumRelocs(macho_file);
205 }
206}
207
208fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void {
209 var size: u32 = 0;
210 var nreloc: u32 = 0;
211
212 for (macho_file.objects.items) |index| {
213 const object = macho_file.getFile(index).?.object;
214 for (object.unwind_records.items) |irec| {
215 const rec = macho_file.getUnwindRecord(irec);
216 if (!rec.alive) continue;
217 size += @sizeOf(macho.compact_unwind_entry);
218 nreloc += 1;
219 if (rec.getPersonality(macho_file)) |_| {
220 nreloc += 1;
221 }
222 if (rec.getLsdaAtom(macho_file)) |_| {
223 nreloc += 1;
224 }
225 }
226 }
227
228 const sect = &macho_file.sections.items(.header)[sect_index];
229 sect.size = size;
230 sect.nreloc = nreloc;
231 sect.@"align" = 3;
232}
233
234fn allocateSections(macho_file: *MachO) !u32 {
235 var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
236 var vmaddr: u64 = 0;
237 const slice = macho_file.sections.slice();
238
239 for (slice.items(.header)) |*header| {
240 const alignment = try math.powi(u32, 2, header.@"align");
241 vmaddr = mem.alignForward(u64, vmaddr, alignment);
242 header.addr = vmaddr;
243 vmaddr += header.size;
244
245 if (!header.isZerofill()) {
246 fileoff = mem.alignForward(u32, fileoff, alignment);
247 header.offset = fileoff;
248 fileoff += @intCast(header.size);
249 }
250 }
251
252 for (slice.items(.header)) |*header| {
253 if (header.nreloc == 0) continue;
254 header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
255 fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
256 }
257
258 return fileoff;
259}
260
261// We need to sort relocations in descending order to be compatible with Apple's linker.
262fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
263 _ = ctx;
264 return lhs.r_address > rhs.r_address;
265}
266
267fn writeAtoms(macho_file: *MachO) !void {
268 const tracy = trace(@src());
269 defer tracy.end();
270
271 const gpa = macho_file.base.comp.gpa;
272 const cpu_arch = macho_file.getTarget().cpu.arch;
273 const slice = macho_file.sections.slice();
274
275 for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
276 if (atoms.items.len == 0) continue;
277 if (header.isZerofill()) continue;
278
279 const size = math.cast(usize, header.size) orelse return error.Overflow;
280 const code = try gpa.alloc(u8, size);
281 defer gpa.free(code);
282 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
283 @memset(code, padding_byte);
284
285 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
286 defer relocs.deinit();
287
288 for (atoms.items) |atom_index| {
289 const atom = macho_file.getAtom(atom_index).?;
290 assert(atom.flags.alive);
291 const off = math.cast(usize, atom.value - header.addr) orelse return error.Overflow;
292 const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
293 const atom_data = try atom.getFile(macho_file).object.getAtomData(atom.*);
294 @memcpy(code[off..][0..atom_size], atom_data);
295 try atom.writeRelocs(macho_file, code[off..][0..atom_size], &relocs);
296 }
297
298 assert(relocs.items.len == header.nreloc);
299
300 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
301
302 // TODO scattered writes?
303 try macho_file.base.file.?.pwriteAll(code, header.offset);
304 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
305 }
306}
307
308fn writeCompactUnwind(macho_file: *MachO) !void {
309 const sect_index = macho_file.unwind_info_sect_index orelse return;
310 const gpa = macho_file.base.comp.gpa;
311 const header = macho_file.sections.items(.header)[sect_index];
312
313 const nrecs = math.cast(usize, @divExact(header.size, @sizeOf(macho.compact_unwind_entry))) orelse return error.Overflow;
314 var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs);
315 defer entries.deinit();
316
317 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
318 defer relocs.deinit();
319
320 const addReloc = struct {
321 fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info {
322 return .{
323 .r_address = offset,
324 .r_symbolnum = 0,
325 .r_pcrel = 0,
326 .r_length = 3,
327 .r_extern = 0,
328 .r_type = switch (cpu_arch) {
329 .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
330 .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
331 else => unreachable,
332 },
333 };
334 }
335 }.addReloc;
336
337 var offset: i32 = 0;
338 for (macho_file.objects.items) |index| {
339 const object = macho_file.getFile(index).?.object;
340 for (object.unwind_records.items) |irec| {
341 const rec = macho_file.getUnwindRecord(irec);
342 if (!rec.alive) continue;
343
344 var out: macho.compact_unwind_entry = .{
345 .rangeStart = 0,
346 .rangeLength = rec.length,
347 .compactUnwindEncoding = rec.enc.enc,
348 .personalityFunction = 0,
349 .lsda = 0,
350 };
351
352 {
353 // Function address
354 const atom = rec.getAtom(macho_file);
355 const addr = rec.getAtomAddress(macho_file);
356 out.rangeStart = addr;
357 var reloc = addReloc(offset, macho_file.getTarget().cpu.arch);
358 reloc.r_symbolnum = atom.out_n_sect + 1;
359 relocs.appendAssumeCapacity(reloc);
360 }
361
362 // Personality function
363 if (rec.getPersonality(macho_file)) |sym| {
364 const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
365 var reloc = addReloc(offset + 16, macho_file.getTarget().cpu.arch);
366 reloc.r_symbolnum = r_symbolnum;
367 reloc.r_extern = 1;
368 relocs.appendAssumeCapacity(reloc);
369 }
370
371 // LSDA address
372 if (rec.getLsdaAtom(macho_file)) |atom| {
373 const addr = rec.getLsdaAddress(macho_file);
374 out.lsda = addr;
375 var reloc = addReloc(offset + 24, macho_file.getTarget().cpu.arch);
376 reloc.r_symbolnum = atom.out_n_sect + 1;
377 relocs.appendAssumeCapacity(reloc);
378 }
379
380 entries.appendAssumeCapacity(out);
381 offset += @sizeOf(macho.compact_unwind_entry);
382 }
383 }
384
385 assert(entries.items.len == nrecs);
386 assert(relocs.items.len == header.nreloc);
387
388 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
389
390 // TODO scattered writes?
391 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(entries.items), header.offset);
392 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
393}
394
395fn writeEhFrame(macho_file: *MachO) !void {
396 const sect_index = macho_file.eh_frame_sect_index orelse return;
397 const gpa = macho_file.base.comp.gpa;
398 const header = macho_file.sections.items(.header)[sect_index];
399 const size = math.cast(usize, header.size) orelse return error.Overflow;
400
401 const code = try gpa.alloc(u8, size);
402 defer gpa.free(code);
403
404 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
405 defer relocs.deinit();
406
407 try eh_frame.writeRelocs(macho_file, code, &relocs);
408 assert(relocs.items.len == header.nreloc);
409
410 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
411
412 // TODO scattered writes?
413 try macho_file.base.file.?.pwriteAll(code, header.offset);
414 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
415}
416
417fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {
418 const gpa = macho_file.base.comp.gpa;
419 const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file);
420 const buffer = try gpa.alloc(u8, needed_size);
421 defer gpa.free(buffer);
422
423 var stream = std.io.fixedBufferStream(buffer);
424 var cwriter = std.io.countingWriter(stream.writer());
425 const writer = cwriter.writer();
426
427 var ncmds: usize = 0;
428
429 // Segment and section load commands
430 {
431 assert(macho_file.segments.items.len == 1);
432 const seg = macho_file.segments.items[0];
433 try writer.writeStruct(seg);
434 for (macho_file.sections.items(.header)) |header| {
435 try writer.writeStruct(header);
436 }
437 ncmds += 1;
438 }
439
440 try writer.writeStruct(macho_file.data_in_code_cmd);
441 ncmds += 1;
442 try writer.writeStruct(macho_file.symtab_cmd);
443 ncmds += 1;
444 try writer.writeStruct(macho_file.dysymtab_cmd);
445 ncmds += 1;
446
447 if (macho_file.platform.isBuildVersionCompatible()) {
448 try load_commands.writeBuildVersionLC(macho_file.platform, macho_file.sdk_version, writer);
449 ncmds += 1;
450 } else {
451 try load_commands.writeVersionMinLC(macho_file.platform, macho_file.sdk_version, writer);
452 ncmds += 1;
453 }
454
455 assert(cwriter.bytes_written == needed_size);
456
457 try macho_file.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
458
459 return .{ ncmds, buffer.len };
460}
461
462fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
463 var header: macho.mach_header_64 = .{};
464 header.filetype = macho.MH_OBJECT;
465
466 const subsections_via_symbols = for (macho_file.objects.items) |index| {
467 const object = macho_file.getFile(index).?.object;
468 if (object.hasSubsections()) break true;
469 } else false;
470 if (subsections_via_symbols) {
471 header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS;
472 }
473
474 switch (macho_file.getTarget().cpu.arch) {
475 .aarch64 => {
476 header.cputype = macho.CPU_TYPE_ARM64;
477 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
478 },
479 .x86_64 => {
480 header.cputype = macho.CPU_TYPE_X86_64;
481 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
482 },
483 else => {},
484 }
485
486 header.ncmds = @intCast(ncmds);
487 header.sizeofcmds = @intCast(sizeofcmds);
488
489 try macho_file.base.file.?.pwriteAll(mem.asBytes(&header), 0);
490}
491
492const assert = std.debug.assert;
493const eh_frame = @import("eh_frame.zig");
494const link = @import("../../link.zig");
495const load_commands = @import("load_commands.zig");
496const macho = std.macho;
497const math = std.math;
498const mem = std.mem;
499const state_log = std.log.scoped(.link_state);
500const std = @import("std");
501const trace = @import("../../tracy.zig").trace;
502
503const Atom = @import("Atom.zig");
504const Compilation = @import("../../Compilation.zig");
505const MachO = @import("../MachO.zig");
506const Symbol = @import("Symbol.zig");
src/link/MachO/stubs.zig deleted-169
......@@ -1,169 +0,0 @@
1pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
2 return switch (cpu_arch) {
3 .x86_64 => 15,
4 .aarch64 => 6 * @sizeOf(u32),
5 else => unreachable, // unhandled architecture type
6 };
7}
8
9pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 {
10 return switch (cpu_arch) {
11 .x86_64 => 10,
12 .aarch64 => 3 * @sizeOf(u32),
13 else => unreachable, // unhandled architecture type
14 };
15}
16
17pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 {
18 return switch (cpu_arch) {
19 .x86_64 => 6,
20 .aarch64 => 3 * @sizeOf(u32),
21 else => unreachable, // unhandled architecture type
22 };
23}
24
25pub inline fn stubAlignment(cpu_arch: std.Target.Cpu.Arch) u8 {
26 return switch (cpu_arch) {
27 .x86_64 => 1,
28 .aarch64 => 4,
29 else => unreachable, // unhandled architecture type
30 };
31}
32
33pub inline fn stubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u8 {
34 return switch (cpu_arch) {
35 .x86_64 => 1,
36 .aarch64 => 2 * @sizeOf(u32),
37 else => unreachable,
38 };
39}
40
41pub fn writeStubHelperPreambleCode(args: struct {
42 cpu_arch: std.Target.Cpu.Arch,
43 source_addr: u64,
44 dyld_private_addr: u64,
45 dyld_stub_binder_got_addr: u64,
46}, writer: anytype) !void {
47 switch (args.cpu_arch) {
48 .x86_64 => {
49 try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
50 {
51 const disp = try Relocation.calcPcRelativeDisplacementX86(
52 args.source_addr + 3,
53 args.dyld_private_addr,
54 0,
55 );
56 try writer.writeInt(i32, disp, .little);
57 }
58 try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
59 {
60 const disp = try Relocation.calcPcRelativeDisplacementX86(
61 args.source_addr + 11,
62 args.dyld_stub_binder_got_addr,
63 0,
64 );
65 try writer.writeInt(i32, disp, .little);
66 }
67 },
68 .aarch64 => {
69 {
70 const pages = Relocation.calcNumberOfPages(args.source_addr, args.dyld_private_addr);
71 try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
72 }
73 {
74 const off = try Relocation.calcPageOffset(args.dyld_private_addr, .arithmetic);
75 try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
76 }
77 try writer.writeInt(u32, aarch64.Instruction.stp(
78 .x16,
79 .x17,
80 aarch64.Register.sp,
81 aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
82 ).toU32(), .little);
83 {
84 const pages = Relocation.calcNumberOfPages(args.source_addr + 12, args.dyld_stub_binder_got_addr);
85 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
86 }
87 {
88 const off = try Relocation.calcPageOffset(args.dyld_stub_binder_got_addr, .load_store_64);
89 try writer.writeInt(u32, aarch64.Instruction.ldr(
90 .x16,
91 .x16,
92 aarch64.Instruction.LoadStoreOffset.imm(off),
93 ).toU32(), .little);
94 }
95 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
96 },
97 else => unreachable,
98 }
99}
100
101pub fn writeStubHelperCode(args: struct {
102 cpu_arch: std.Target.Cpu.Arch,
103 source_addr: u64,
104 target_addr: u64,
105}, writer: anytype) !void {
106 switch (args.cpu_arch) {
107 .x86_64 => {
108 try writer.writeAll(&.{ 0x68, 0x0, 0x0, 0x0, 0x0, 0xe9 });
109 {
110 const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 6, args.target_addr, 0);
111 try writer.writeInt(i32, disp, .little);
112 }
113 },
114 .aarch64 => {
115 const stub_size: u4 = 3 * @sizeOf(u32);
116 const literal = blk: {
117 const div_res = try std.math.divExact(u64, stub_size - @sizeOf(u32), 4);
118 break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
119 };
120 try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
121 .w16,
122 literal,
123 ).toU32(), .little);
124 {
125 const disp = try Relocation.calcPcRelativeDisplacementArm64(args.source_addr + 4, args.target_addr);
126 try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
127 }
128 try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
129 },
130 else => unreachable,
131 }
132}
133
134pub fn writeStubCode(args: struct {
135 cpu_arch: std.Target.Cpu.Arch,
136 source_addr: u64,
137 target_addr: u64,
138}, writer: anytype) !void {
139 switch (args.cpu_arch) {
140 .x86_64 => {
141 try writer.writeAll(&.{ 0xff, 0x25 });
142 {
143 const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 2, args.target_addr, 0);
144 try writer.writeInt(i32, disp, .little);
145 }
146 },
147 .aarch64 => {
148 {
149 const pages = Relocation.calcNumberOfPages(args.source_addr, args.target_addr);
150 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
151 }
152 {
153 const off = try Relocation.calcPageOffset(args.target_addr, .load_store_64);
154 try writer.writeInt(u32, aarch64.Instruction.ldr(
155 .x16,
156 .x16,
157 aarch64.Instruction.LoadStoreOffset.imm(off),
158 ).toU32(), .little);
159 }
160 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
161 },
162 else => unreachable,
163 }
164}
165
166const std = @import("std");
167const aarch64 = @import("../../arch/aarch64/bits.zig");
168
169const Relocation = @import("Relocation.zig");
src/link/MachO/synthetic.zig created+793
......@@ -0,0 +1,793 @@
1pub const ZigGotSection = struct {
2 entries: std.ArrayListUnmanaged(Symbol.Index) = .{},
3 dirty: bool = false,
4
5 pub const Index = u32;
6
7 pub fn deinit(zig_got: *ZigGotSection, allocator: Allocator) void {
8 zig_got.entries.deinit(allocator);
9 }
10
11 fn allocateEntry(zig_got: *ZigGotSection, allocator: Allocator) !Index {
12 try zig_got.entries.ensureUnusedCapacity(allocator, 1);
13 // TODO add free list
14 const index = @as(Index, @intCast(zig_got.entries.items.len));
15 _ = zig_got.entries.addOneAssumeCapacity();
16 zig_got.dirty = true;
17 return index;
18 }
19
20 pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index {
21 const comp = macho_file.base.comp;
22 const gpa = comp.gpa;
23 const index = try zig_got.allocateEntry(gpa);
24 const entry = &zig_got.entries.items[index];
25 entry.* = sym_index;
26 const symbol = macho_file.getSymbol(sym_index);
27 assert(symbol.flags.needs_zig_got);
28 symbol.flags.has_zig_got = true;
29 try symbol.addExtra(.{ .zig_got = index }, macho_file);
30 return index;
31 }
32
33 pub fn entryOffset(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
34 _ = zig_got;
35 const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?];
36 return sect.offset + @sizeOf(u64) * index;
37 }
38
39 pub fn entryAddress(zig_got: ZigGotSection, index: Index, macho_file: *MachO) u64 {
40 _ = zig_got;
41 const sect = macho_file.sections.items(.header)[macho_file.zig_got_sect_index.?];
42 return sect.addr + @sizeOf(u64) * index;
43 }
44
45 pub fn size(zig_got: ZigGotSection, macho_file: *MachO) usize {
46 _ = macho_file;
47 return @sizeOf(u64) * zig_got.entries.items.len;
48 }
49
50 pub fn writeOne(zig_got: *ZigGotSection, macho_file: *MachO, index: Index) !void {
51 if (zig_got.dirty) {
52 const needed_size = zig_got.size(macho_file);
53 try macho_file.growSection(macho_file.zig_got_sect_index.?, needed_size);
54 zig_got.dirty = false;
55 }
56 const off = zig_got.entryOffset(index, macho_file);
57 const entry = zig_got.entries.items[index];
58 const value = macho_file.getSymbol(entry).getAddress(.{ .stubs = false }, macho_file);
59
60 var buf: [8]u8 = undefined;
61 std.mem.writeInt(u64, &buf, value, .little);
62 try macho_file.base.file.?.pwriteAll(&buf, off);
63 }
64
65 pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void {
66 for (zig_got.entries.items) |entry| {
67 const symbol = macho_file.getSymbol(entry);
68 const value = symbol.address(.{ .stubs = false }, macho_file);
69 try writer.writeInt(u64, value, .little);
70 }
71 }
72
73 pub fn addDyldRelocs(zig_got: ZigGotSection, macho_file: *MachO) !void {
74 const tracy = trace(@src());
75 defer tracy.end();
76 const gpa = macho_file.base.comp.gpa;
77 const seg_id = macho_file.sections.items(.segment_id)[macho_file.zig_got_sect_index.?];
78 const seg = macho_file.segments.items[seg_id];
79
80 for (0..zig_got.entries.items.len) |idx| {
81 const addr = zig_got.entryAddress(@intCast(idx), macho_file);
82 try macho_file.rebase.entries.append(gpa, .{
83 .offset = addr - seg.vmaddr,
84 .segment_id = seg_id,
85 });
86 }
87 }
88
89 const FormatCtx = struct {
90 zig_got: ZigGotSection,
91 macho_file: *MachO,
92 };
93
94 pub fn fmt(zig_got: ZigGotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
95 return .{ .data = .{ .zig_got = zig_got, .macho_file = macho_file } };
96 }
97
98 pub fn format2(
99 ctx: FormatCtx,
100 comptime unused_fmt_string: []const u8,
101 options: std.fmt.FormatOptions,
102 writer: anytype,
103 ) !void {
104 _ = options;
105 _ = unused_fmt_string;
106 try writer.writeAll("__zig_got\n");
107 for (ctx.zig_got.entries.items, 0..) |entry, index| {
108 const symbol = ctx.macho_file.getSymbol(entry);
109 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
110 index,
111 ctx.zig_got.entryAddress(@intCast(index), ctx.macho_file),
112 entry,
113 symbol.getAddress(.{}, ctx.macho_file),
114 symbol.getName(ctx.macho_file),
115 });
116 }
117 }
118};
119
120pub const GotSection = struct {
121 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
122
123 pub const Index = u32;
124
125 pub fn deinit(got: *GotSection, allocator: Allocator) void {
126 got.symbols.deinit(allocator);
127 }
128
129 pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
130 const gpa = macho_file.base.comp.gpa;
131 const index = @as(Index, @intCast(got.symbols.items.len));
132 const entry = try got.symbols.addOne(gpa);
133 entry.* = sym_index;
134 const symbol = macho_file.getSymbol(sym_index);
135 symbol.flags.has_got = true;
136 try symbol.addExtra(.{ .got = index }, macho_file);
137 }
138
139 pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 {
140 assert(index < got.symbols.items.len);
141 const header = macho_file.sections.items(.header)[macho_file.got_sect_index.?];
142 return header.addr + index * @sizeOf(u64);
143 }
144
145 pub fn size(got: GotSection) usize {
146 return got.symbols.items.len * @sizeOf(u64);
147 }
148
149 pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
150 const tracy = trace(@src());
151 defer tracy.end();
152 const gpa = macho_file.base.comp.gpa;
153 const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
154 const seg = macho_file.segments.items[seg_id];
155
156 for (got.symbols.items, 0..) |sym_index, idx| {
157 const sym = macho_file.getSymbol(sym_index);
158 const addr = got.getAddress(@intCast(idx), macho_file);
159 const entry = bind.Entry{
160 .target = sym_index,
161 .offset = addr - seg.vmaddr,
162 .segment_id = seg_id,
163 .addend = 0,
164 };
165 if (sym.flags.import) {
166 try macho_file.bind.entries.append(gpa, entry);
167 if (sym.flags.weak) {
168 try macho_file.weak_bind.entries.append(gpa, entry);
169 }
170 } else {
171 try macho_file.rebase.entries.append(gpa, .{
172 .offset = addr - seg.vmaddr,
173 .segment_id = seg_id,
174 });
175 if (sym.flags.weak) {
176 try macho_file.weak_bind.entries.append(gpa, entry);
177 } else if (sym.flags.interposable) {
178 try macho_file.bind.entries.append(gpa, entry);
179 }
180 }
181 }
182 }
183
184 pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
185 const tracy = trace(@src());
186 defer tracy.end();
187 for (got.symbols.items) |sym_index| {
188 const sym = macho_file.getSymbol(sym_index);
189 const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file);
190 try writer.writeInt(u64, value, .little);
191 }
192 }
193
194 const FormatCtx = struct {
195 got: GotSection,
196 macho_file: *MachO,
197 };
198
199 pub fn fmt(got: GotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
200 return .{ .data = .{ .got = got, .macho_file = macho_file } };
201 }
202
203 pub fn format2(
204 ctx: FormatCtx,
205 comptime unused_fmt_string: []const u8,
206 options: std.fmt.FormatOptions,
207 writer: anytype,
208 ) !void {
209 _ = options;
210 _ = unused_fmt_string;
211 for (ctx.got.symbols.items, 0..) |entry, i| {
212 const symbol = ctx.macho_file.getSymbol(entry);
213 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
214 i,
215 symbol.getGotAddress(ctx.macho_file),
216 entry,
217 symbol.getAddress(.{}, ctx.macho_file),
218 symbol.getName(ctx.macho_file),
219 });
220 }
221 }
222};
223
224pub const StubsSection = struct {
225 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
226
227 pub const Index = u32;
228
229 pub fn deinit(stubs: *StubsSection, allocator: Allocator) void {
230 stubs.symbols.deinit(allocator);
231 }
232
233 pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
234 const gpa = macho_file.base.comp.gpa;
235 const index = @as(Index, @intCast(stubs.symbols.items.len));
236 const entry = try stubs.symbols.addOne(gpa);
237 entry.* = sym_index;
238 const symbol = macho_file.getSymbol(sym_index);
239 try symbol.addExtra(.{ .stubs = index }, macho_file);
240 }
241
242 pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 {
243 assert(index < stubs.symbols.items.len);
244 const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
245 return header.addr + index * header.reserved2;
246 }
247
248 pub fn size(stubs: StubsSection, macho_file: *MachO) usize {
249 const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
250 return stubs.symbols.items.len * header.reserved2;
251 }
252
253 pub fn write(stubs: StubsSection, macho_file: *MachO, writer: anytype) !void {
254 const tracy = trace(@src());
255 defer tracy.end();
256 const cpu_arch = macho_file.getTarget().cpu.arch;
257 const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
258
259 for (stubs.symbols.items, 0..) |sym_index, idx| {
260 const sym = macho_file.getSymbol(sym_index);
261 const source = sym.getAddress(.{ .stubs = true }, macho_file);
262 const target = laptr_sect.addr + idx * @sizeOf(u64);
263 switch (cpu_arch) {
264 .x86_64 => {
265 try writer.writeAll(&.{ 0xff, 0x25 });
266 try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
267 },
268 .aarch64 => {
269 // TODO relax if possible
270 const pages = try Relocation.calcNumberOfPages(source, target);
271 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
272 const off = try Relocation.calcPageOffset(target, .load_store_64);
273 try writer.writeInt(
274 u32,
275 aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
276 .little,
277 );
278 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
279 },
280 else => unreachable,
281 }
282 }
283 }
284
285 const FormatCtx = struct {
286 stubs: StubsSection,
287 macho_file: *MachO,
288 };
289
290 pub fn fmt(stubs: StubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
291 return .{ .data = .{ .stubs = stubs, .macho_file = macho_file } };
292 }
293
294 pub fn format2(
295 ctx: FormatCtx,
296 comptime unused_fmt_string: []const u8,
297 options: std.fmt.FormatOptions,
298 writer: anytype,
299 ) !void {
300 _ = options;
301 _ = unused_fmt_string;
302 for (ctx.stubs.symbols.items, 0..) |entry, i| {
303 const symbol = ctx.macho_file.getSymbol(entry);
304 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
305 i,
306 symbol.getStubsAddress(ctx.macho_file),
307 entry,
308 symbol.getAddress(.{}, ctx.macho_file),
309 symbol.getName(ctx.macho_file),
310 });
311 }
312 }
313};
314
315pub const StubsHelperSection = struct {
316 pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize {
317 return switch (cpu_arch) {
318 .x86_64 => 16,
319 .aarch64 => 6 * @sizeOf(u32),
320 else => 0,
321 };
322 }
323
324 pub inline fn entrySize(cpu_arch: std.Target.Cpu.Arch) usize {
325 return switch (cpu_arch) {
326 .x86_64 => 10,
327 .aarch64 => 3 * @sizeOf(u32),
328 else => 0,
329 };
330 }
331
332 pub fn size(stubs_helper: StubsHelperSection, macho_file: *MachO) usize {
333 const tracy = trace(@src());
334 defer tracy.end();
335 _ = stubs_helper;
336 const cpu_arch = macho_file.getTarget().cpu.arch;
337 var s: usize = preambleSize(cpu_arch);
338 for (macho_file.stubs.symbols.items) |sym_index| {
339 const sym = macho_file.getSymbol(sym_index);
340 if (sym.flags.weak) continue;
341 s += entrySize(cpu_arch);
342 }
343 return s;
344 }
345
346 pub fn write(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
347 const tracy = trace(@src());
348 defer tracy.end();
349
350 try stubs_helper.writePreamble(macho_file, writer);
351
352 const cpu_arch = macho_file.getTarget().cpu.arch;
353 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
354 const preamble_size = preambleSize(cpu_arch);
355 const entry_size = entrySize(cpu_arch);
356
357 var idx: usize = 0;
358 for (macho_file.stubs.symbols.items) |sym_index| {
359 const sym = macho_file.getSymbol(sym_index);
360 if (sym.flags.weak) continue;
361 const offset = macho_file.lazy_bind.offsets.items[idx];
362 const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx);
363 const target: i64 = @intCast(sect.addr);
364 switch (cpu_arch) {
365 .x86_64 => {
366 try writer.writeByte(0x68);
367 try writer.writeInt(u32, offset, .little);
368 try writer.writeByte(0xe9);
369 try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little);
370 },
371 .aarch64 => {
372 const literal = blk: {
373 const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4);
374 break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
375 };
376 try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
377 .w16,
378 literal,
379 ).toU32(), .little);
380 const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse
381 return error.Overflow;
382 try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
383 try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
384 },
385 else => unreachable,
386 }
387 idx += 1;
388 }
389 }
390
391 fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
392 _ = stubs_helper;
393 const cpu_arch = macho_file.getTarget().cpu.arch;
394 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
395 const dyld_private_addr = target: {
396 const sym = macho_file.getSymbol(macho_file.dyld_private_index.?);
397 break :target sym.getAddress(.{}, macho_file);
398 };
399 const dyld_stub_binder_addr = target: {
400 const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?);
401 break :target sym.getGotAddress(macho_file);
402 };
403 switch (cpu_arch) {
404 .x86_64 => {
405 try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
406 try writer.writeInt(i32, @intCast(dyld_private_addr - sect.addr - 3 - 4), .little);
407 try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
408 try writer.writeInt(i32, @intCast(dyld_stub_binder_addr - sect.addr - 11 - 4), .little);
409 try writer.writeByte(0x90);
410 },
411 .aarch64 => {
412 {
413 // TODO relax if possible
414 const pages = try Relocation.calcNumberOfPages(sect.addr, dyld_private_addr);
415 try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
416 const off = try Relocation.calcPageOffset(dyld_private_addr, .arithmetic);
417 try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
418 }
419 try writer.writeInt(u32, aarch64.Instruction.stp(
420 .x16,
421 .x17,
422 aarch64.Register.sp,
423 aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
424 ).toU32(), .little);
425 {
426 // TODO relax if possible
427 const pages = try Relocation.calcNumberOfPages(sect.addr + 12, dyld_stub_binder_addr);
428 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
429 const off = try Relocation.calcPageOffset(dyld_stub_binder_addr, .load_store_64);
430 try writer.writeInt(u32, aarch64.Instruction.ldr(
431 .x16,
432 .x16,
433 aarch64.Instruction.LoadStoreOffset.imm(off),
434 ).toU32(), .little);
435 }
436 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
437 },
438 else => unreachable,
439 }
440 }
441};
442
443pub const LaSymbolPtrSection = struct {
444 pub fn size(laptr: LaSymbolPtrSection, macho_file: *MachO) usize {
445 _ = laptr;
446 return macho_file.stubs.symbols.items.len * @sizeOf(u64);
447 }
448
449 pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
450 const tracy = trace(@src());
451 defer tracy.end();
452 _ = laptr;
453 const gpa = macho_file.base.comp.gpa;
454
455 const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
456 const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
457 const seg = macho_file.segments.items[seg_id];
458
459 for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
460 const sym = macho_file.getSymbol(sym_index);
461 const addr = sect.addr + idx * @sizeOf(u64);
462 const rebase_entry = Rebase.Entry{
463 .offset = addr - seg.vmaddr,
464 .segment_id = seg_id,
465 };
466 const bind_entry = bind.Entry{
467 .target = sym_index,
468 .offset = addr - seg.vmaddr,
469 .segment_id = seg_id,
470 .addend = 0,
471 };
472 if (sym.flags.import) {
473 if (sym.flags.weak) {
474 try macho_file.bind.entries.append(gpa, bind_entry);
475 try macho_file.weak_bind.entries.append(gpa, bind_entry);
476 } else {
477 try macho_file.lazy_bind.entries.append(gpa, bind_entry);
478 try macho_file.rebase.entries.append(gpa, rebase_entry);
479 }
480 } else {
481 if (sym.flags.weak) {
482 try macho_file.rebase.entries.append(gpa, rebase_entry);
483 try macho_file.weak_bind.entries.append(gpa, bind_entry);
484 } else if (sym.flags.interposable) {
485 try macho_file.lazy_bind.entries.append(gpa, bind_entry);
486 try macho_file.rebase.entries.append(gpa, rebase_entry);
487 }
488 }
489 }
490 }
491
492 pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
493 const tracy = trace(@src());
494 defer tracy.end();
495 _ = laptr;
496 const cpu_arch = macho_file.getTarget().cpu.arch;
497 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
498 var stub_helper_idx: u32 = 0;
499 for (macho_file.stubs.symbols.items) |sym_index| {
500 const sym = macho_file.getSymbol(sym_index);
501 if (sym.flags.weak) {
502 const value = sym.getAddress(.{ .stubs = false }, macho_file);
503 try writer.writeInt(u64, @intCast(value), .little);
504 } else {
505 const value = sect.addr + StubsHelperSection.preambleSize(cpu_arch) +
506 StubsHelperSection.entrySize(cpu_arch) * stub_helper_idx;
507 stub_helper_idx += 1;
508 try writer.writeInt(u64, @intCast(value), .little);
509 }
510 }
511 }
512};
513
514pub const TlvPtrSection = struct {
515 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
516
517 pub const Index = u32;
518
519 pub fn deinit(tlv: *TlvPtrSection, allocator: Allocator) void {
520 tlv.symbols.deinit(allocator);
521 }
522
523 pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
524 const gpa = macho_file.base.comp.gpa;
525 const index = @as(Index, @intCast(tlv.symbols.items.len));
526 const entry = try tlv.symbols.addOne(gpa);
527 entry.* = sym_index;
528 const symbol = macho_file.getSymbol(sym_index);
529 try symbol.addExtra(.{ .tlv_ptr = index }, macho_file);
530 }
531
532 pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 {
533 assert(index < tlv.symbols.items.len);
534 const header = macho_file.sections.items(.header)[macho_file.tlv_ptr_sect_index.?];
535 return header.addr + index * @sizeOf(u64) * 3;
536 }
537
538 pub fn size(tlv: TlvPtrSection) usize {
539 return tlv.symbols.items.len * @sizeOf(u64);
540 }
541
542 pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
543 const tracy = trace(@src());
544 defer tracy.end();
545 const gpa = macho_file.base.comp.gpa;
546 const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
547 const seg = macho_file.segments.items[seg_id];
548
549 for (tlv.symbols.items, 0..) |sym_index, idx| {
550 const sym = macho_file.getSymbol(sym_index);
551 const addr = tlv.getAddress(@intCast(idx), macho_file);
552 const entry = bind.Entry{
553 .target = sym_index,
554 .offset = addr - seg.vmaddr,
555 .segment_id = seg_id,
556 .addend = 0,
557 };
558 if (sym.flags.import) {
559 try macho_file.bind.entries.append(gpa, entry);
560 if (sym.flags.weak) {
561 try macho_file.weak_bind.entries.append(gpa, entry);
562 }
563 } else {
564 try macho_file.rebase.entries.append(gpa, .{
565 .offset = addr - seg.vmaddr,
566 .segment_id = seg_id,
567 });
568 if (sym.flags.weak) {
569 try macho_file.weak_bind.entries.append(gpa, entry);
570 } else if (sym.flags.interposable) {
571 try macho_file.bind.entries.append(gpa, entry);
572 }
573 }
574 }
575 }
576
577 pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
578 const tracy = trace(@src());
579 defer tracy.end();
580
581 for (tlv.symbols.items) |sym_index| {
582 const sym = macho_file.getSymbol(sym_index);
583 if (sym.flags.import) {
584 try writer.writeInt(u64, 0, .little);
585 } else {
586 try writer.writeInt(u64, sym.getAddress(.{}, macho_file), .little);
587 }
588 }
589 }
590
591 const FormatCtx = struct {
592 tlv: TlvPtrSection,
593 macho_file: *MachO,
594 };
595
596 pub fn fmt(tlv: TlvPtrSection, macho_file: *MachO) std.fmt.Formatter(format2) {
597 return .{ .data = .{ .tlv = tlv, .macho_file = macho_file } };
598 }
599
600 pub fn format2(
601 ctx: FormatCtx,
602 comptime unused_fmt_string: []const u8,
603 options: std.fmt.FormatOptions,
604 writer: anytype,
605 ) !void {
606 _ = options;
607 _ = unused_fmt_string;
608 for (ctx.tlv.symbols.items, 0..) |entry, i| {
609 const symbol = ctx.macho_file.getSymbol(entry);
610 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
611 i,
612 symbol.getTlvPtrAddress(ctx.macho_file),
613 entry,
614 symbol.getAddress(.{}, ctx.macho_file),
615 symbol.getName(ctx.macho_file),
616 });
617 }
618 }
619};
620
621pub const ObjcStubsSection = struct {
622 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
623
624 pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void {
625 objc.symbols.deinit(allocator);
626 }
627
628 pub fn entrySize(cpu_arch: std.Target.Cpu.Arch) u8 {
629 return switch (cpu_arch) {
630 .x86_64 => 13,
631 .aarch64 => 8 * @sizeOf(u32),
632 else => unreachable,
633 };
634 }
635
636 pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
637 const gpa = macho_file.base.comp.gpa;
638 const index = @as(Index, @intCast(objc.symbols.items.len));
639 const entry = try objc.symbols.addOne(gpa);
640 entry.* = sym_index;
641 const symbol = macho_file.getSymbol(sym_index);
642 try symbol.addExtra(.{ .objc_stubs = index }, macho_file);
643 }
644
645 pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 {
646 assert(index < objc.symbols.items.len);
647 const header = macho_file.sections.items(.header)[macho_file.objc_stubs_sect_index.?];
648 return header.addr + index * entrySize(macho_file.getTarget().cpu.arch);
649 }
650
651 pub fn size(objc: ObjcStubsSection, macho_file: *MachO) usize {
652 return objc.symbols.items.len * entrySize(macho_file.getTarget().cpu.arch);
653 }
654
655 pub fn write(objc: ObjcStubsSection, macho_file: *MachO, writer: anytype) !void {
656 const tracy = trace(@src());
657 defer tracy.end();
658
659 for (objc.symbols.items, 0..) |sym_index, idx| {
660 const sym = macho_file.getSymbol(sym_index);
661 const addr = objc.getAddress(@intCast(idx), macho_file);
662 switch (macho_file.getTarget().cpu.arch) {
663 .x86_64 => {
664 try writer.writeAll(&.{ 0x48, 0x8b, 0x35 });
665 {
666 const target = sym.getObjcSelrefsAddress(macho_file);
667 const source = addr;
668 try writer.writeInt(i32, @intCast(target - source - 3 - 4), .little);
669 }
670 try writer.writeAll(&.{ 0xff, 0x25 });
671 {
672 const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
673 const target = target_sym.getGotAddress(macho_file);
674 const source = addr + 7;
675 try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
676 }
677 },
678 .aarch64 => {
679 {
680 const target = sym.getObjcSelrefsAddress(macho_file);
681 const source = addr;
682 const pages = try Relocation.calcNumberOfPages(source, target);
683 try writer.writeInt(u32, aarch64.Instruction.adrp(.x1, pages).toU32(), .little);
684 const off = try Relocation.calcPageOffset(target, .load_store_64);
685 try writer.writeInt(
686 u32,
687 aarch64.Instruction.ldr(.x1, .x1, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
688 .little,
689 );
690 }
691 {
692 const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
693 const target = target_sym.getGotAddress(macho_file);
694 const source = addr + 2 * @sizeOf(u32);
695 const pages = try Relocation.calcNumberOfPages(source, target);
696 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
697 const off = try Relocation.calcPageOffset(target, .load_store_64);
698 try writer.writeInt(
699 u32,
700 aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
701 .little,
702 );
703 }
704 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
705 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
706 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
707 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
708 },
709 else => unreachable,
710 }
711 }
712 }
713
714 const FormatCtx = struct {
715 objc: ObjcStubsSection,
716 macho_file: *MachO,
717 };
718
719 pub fn fmt(objc: ObjcStubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
720 return .{ .data = .{ .objc = objc, .macho_file = macho_file } };
721 }
722
723 pub fn format2(
724 ctx: FormatCtx,
725 comptime unused_fmt_string: []const u8,
726 options: std.fmt.FormatOptions,
727 writer: anytype,
728 ) !void {
729 _ = options;
730 _ = unused_fmt_string;
731 for (ctx.objc.symbols.items, 0..) |entry, i| {
732 const symbol = ctx.macho_file.getSymbol(entry);
733 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
734 i,
735 symbol.getObjcStubsAddress(ctx.macho_file),
736 entry,
737 symbol.getAddress(.{}, ctx.macho_file),
738 symbol.getName(ctx.macho_file),
739 });
740 }
741 }
742
743 pub const Index = u32;
744};
745
746pub const Indsymtab = struct {
747 pub inline fn nsyms(ind: Indsymtab, macho_file: *MachO) u32 {
748 _ = ind;
749 return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len);
750 }
751
752 pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void {
753 const tracy = trace(@src());
754 defer tracy.end();
755
756 _ = ind;
757
758 for (macho_file.stubs.symbols.items) |sym_index| {
759 const sym = macho_file.getSymbol(sym_index);
760 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
761 }
762
763 for (macho_file.got.symbols.items) |sym_index| {
764 const sym = macho_file.getSymbol(sym_index);
765 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
766 }
767
768 for (macho_file.stubs.symbols.items) |sym_index| {
769 const sym = macho_file.getSymbol(sym_index);
770 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
771 }
772 }
773};
774
775pub const RebaseSection = Rebase;
776pub const BindSection = bind.Bind;
777pub const WeakBindSection = bind.WeakBind;
778pub const LazyBindSection = bind.LazyBind;
779pub const ExportTrieSection = Trie;
780
781const aarch64 = @import("../../arch/aarch64/bits.zig");
782const assert = std.debug.assert;
783const bind = @import("dyld_info/bind.zig");
784const math = std.math;
785const std = @import("std");
786const trace = @import("../../tracy.zig").trace;
787
788const Allocator = std.mem.Allocator;
789const MachO = @import("../MachO.zig");
790const Rebase = @import("dyld_info/Rebase.zig");
791const Relocation = @import("Relocation.zig");
792const Symbol = @import("Symbol.zig");
793const Trie = @import("dyld_info/Trie.zig");
src/link/MachO/thunks.zig+136-335
......@@ -1,374 +1,175 @@
1//! An algorithm for allocating output machine code section (aka `__TEXT,__text`),
2//! and insertion of range extending thunks. As such, this algorithm is only run
3//! for a target that requires range extenders such as arm64.
4//!
5//! The algorithm works pessimistically and assumes that any reference to an Atom in
6//! another output section is out of range.
7
8/// Branch instruction has 26 bits immediate but 4 byte aligned.
9const jump_bits = @bitSizeOf(i28);
10
11const max_distance = (1 << (jump_bits - 1));
12
13/// A branch will need an extender if its target is larger than
14/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
15/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
16/// and assume margin to be 5MiB.
17const max_allowed_distance = max_distance - 0x500_000;
18
19pub const Thunk = struct {
20 start_index: Atom.Index,
21 len: u32,
22
23 targets: std.MultiArrayList(Target) = .{},
24 lookup: std.AutoHashMapUnmanaged(Target, u32) = .{},
25
26 pub const Tag = enum {
27 stub,
28 atom,
29 };
30
31 pub const Target = struct {
32 tag: Tag,
33 target: SymbolWithLoc,
34 };
35
36 pub const Index = u32;
37
38 pub fn deinit(self: *Thunk, gpa: Allocator) void {
39 self.targets.deinit(gpa);
40 self.lookup.deinit(gpa);
41 }
42
43 pub fn getStartAtomIndex(self: Thunk) Atom.Index {
44 assert(self.len != 0);
45 return self.start_index;
46 }
47
48 pub fn getEndAtomIndex(self: Thunk) Atom.Index {
49 assert(self.len != 0);
50 return self.start_index + self.len - 1;
51 }
52
53 pub fn getSize(self: Thunk) u64 {
54 return 12 * self.len;
1pub fn createThunks(sect_id: u8, macho_file: *MachO) !void {
2 const tracy = trace(@src());
3 defer tracy.end();
4
5 const gpa = macho_file.base.comp.gpa;
6 const slice = macho_file.sections.slice();
7 const header = &slice.items(.header)[sect_id];
8 const atoms = slice.items(.atoms)[sect_id].items;
9 assert(atoms.len > 0);
10
11 for (atoms) |atom_index| {
12 macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1));
5513 }
5614
57 pub fn getAlignment() u32 {
58 return @alignOf(u32);
59 }
60
61 pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc {
62 const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null;
63 return macho_file.getAtom(atom_index).getSymbolWithLoc();
64 }
65};
66
67pub fn createThunks(macho_file: *MachO, sect_id: u8) !void {
68 const header = &macho_file.sections.items(.header)[sect_id];
69 if (header.size == 0) return;
70
71 const comp = macho_file.base.comp;
72 const gpa = comp.gpa;
73 const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id].?;
74
75 header.size = 0;
76 header.@"align" = 0;
77
78 var atom_count: u32 = 0;
79
80 {
81 var atom_index = first_atom_index;
82 while (true) {
83 const atom = macho_file.getAtom(atom_index);
84 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
85 sym.n_value = 0;
86 atom_count += 1;
87
88 if (atom.next_index) |next_index| {
89 atom_index = next_index;
90 } else break;
91 }
92 }
93
94 var allocated = std.AutoHashMap(Atom.Index, void).init(gpa);
95 defer allocated.deinit();
96 try allocated.ensureTotalCapacity(atom_count);
97
98 var group_start = first_atom_index;
99 var group_end = first_atom_index;
100 var offset: u64 = 0;
101
102 while (true) {
103 const group_start_atom = macho_file.getAtom(group_start);
104 log.debug("GROUP START at {d}", .{group_start});
105
106 while (true) {
107 const atom = macho_file.getAtom(group_end);
108 offset = atom.alignment.forward(offset);
109
110 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
111 sym.n_value = offset;
112 offset += atom.size;
113
114 macho_file.logAtom(group_end, log);
115
116 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
117
118 allocated.putAssumeCapacityNoClobber(group_end, {});
119
120 const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc());
121 if (offset - group_start_sym.n_value >= max_allowed_distance) break;
122
123 if (atom.next_index) |next_index| {
124 group_end = next_index;
125 } else break;
15 var i: usize = 0;
16 while (i < atoms.len) {
17 const start = i;
18 const start_atom = macho_file.getAtom(atoms[start]).?;
19 assert(start_atom.flags.alive);
20 start_atom.value = try advance(header, start_atom.size, start_atom.alignment);
21 i += 1;
22
23 while (i < atoms.len and
24 header.size - start_atom.value < max_allowed_distance) : (i += 1)
25 {
26 const atom_index = atoms[i];
27 const atom = macho_file.getAtom(atom_index).?;
28 assert(atom.flags.alive);
29 atom.value = try advance(header, atom.size, atom.alignment);
12630 }
127 log.debug("GROUP END at {d}", .{group_end});
128
129 // Insert thunk at group_end
130 const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len));
131 try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 });
13231
133 // Scan relocs in the group and create trampolines for any unreachable callsite.
134 var atom_index = group_start;
135 while (true) {
136 const atom = macho_file.getAtom(atom_index);
137 try scanRelocs(
138 macho_file,
139 atom_index,
140 allocated,
141 thunk_index,
142 group_end,
143 );
144
145 if (atom_index == group_end) break;
146
147 if (atom.next_index) |next_index| {
148 atom_index = next_index;
149 } else break;
32 // Insert a thunk at the group end
33 const thunk_index = try macho_file.addThunk();
34 const thunk = macho_file.getThunk(thunk_index);
35 thunk.out_n_sect = sect_id;
36
37 // Scan relocs in the group and create trampolines for any unreachable callsite
38 for (atoms[start..i]) |atom_index| {
39 const atom = macho_file.getAtom(atom_index).?;
40 log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) });
41 for (atom.getRelocs(macho_file)) |rel| {
42 if (rel.type != .branch) continue;
43 if (isReachable(atom, rel, macho_file)) continue;
44 try thunk.symbols.put(gpa, rel.target, {});
45 }
46 atom.thunk_index = thunk_index;
15047 }
15148
152 offset = mem.alignForward(u64, offset, Thunk.getAlignment());
153 allocateThunk(macho_file, thunk_index, offset, header);
154 offset += macho_file.thunks.items[thunk_index].getSize();
49 thunk.value = try advance(header, thunk.size(), .@"4");
15550
156 const thunk = macho_file.thunks.items[thunk_index];
157 if (thunk.len == 0) {
158 const group_end_atom = macho_file.getAtom(group_end);
159 if (group_end_atom.next_index) |next_index| {
160 group_start = next_index;
161 group_end = next_index;
162 } else break;
163 } else {
164 const thunk_end_atom_index = thunk.getEndAtomIndex();
165 const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index);
166 if (thunk_end_atom.next_index) |next_index| {
167 group_start = next_index;
168 group_end = next_index;
169 } else break;
170 }
51 log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) });
17152 }
172
173 header.size = @as(u32, @intCast(offset));
17453}
17554
176fn allocateThunk(
177 macho_file: *MachO,
178 thunk_index: Thunk.Index,
179 base_offset: u64,
180 header: *macho.section_64,
181) void {
182 const thunk = macho_file.thunks.items[thunk_index];
183 if (thunk.len == 0) return;
184
185 const first_atom_index = thunk.getStartAtomIndex();
186 const end_atom_index = thunk.getEndAtomIndex();
187
188 var atom_index = first_atom_index;
189 var offset = base_offset;
190 while (true) {
191 const atom = macho_file.getAtom(atom_index);
192 offset = mem.alignForward(u64, offset, Thunk.getAlignment());
193
194 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
195 sym.n_value = offset;
196 offset += atom.size;
197
198 macho_file.logAtom(atom_index, log);
199
200 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
201
202 if (end_atom_index == atom_index) break;
203
204 if (atom.next_index) |next_index| {
205 atom_index = next_index;
206 } else break;
207 }
55fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) !u64 {
56 const offset = alignment.forward(sect.size);
57 const padding = offset - sect.size;
58 sect.size += padding + size;
59 sect.@"align" = @max(sect.@"align", alignment.toLog2Units());
60 return offset;
20861}
20962
210fn scanRelocs(
211 macho_file: *MachO,
212 atom_index: Atom.Index,
213 allocated: std.AutoHashMap(Atom.Index, void),
214 thunk_index: Thunk.Index,
215 group_end: Atom.Index,
216) !void {
217 const atom = macho_file.getAtom(atom_index);
218 const object = macho_file.objects.items[atom.getFile().?];
219
220 const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {
221 const source_sect = object.getSourceSection(source_sym.n_sect - 1);
222 break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr));
223 } else 0;
224
225 const code = Atom.getAtomCode(macho_file, atom_index);
226 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
227 const ctx = Atom.getRelocContext(macho_file, atom_index);
63fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
64 const target = rel.getTargetSymbol(macho_file);
65 if (target.flags.stubs or target.flags.objc_stubs) return false;
66 if (atom.out_n_sect != target.out_n_sect) return false;
67 const target_atom = target.getAtom(macho_file).?;
68 if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
69 const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off));
70 const taddr: i64 = @intCast(rel.getTargetAddress(macho_file));
71 _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
72 return true;
73}
22874
229 for (relocs) |rel| {
230 if (!relocNeedsThunk(rel)) continue;
75pub const Thunk = struct {
76 value: u64 = 0,
77 out_n_sect: u8 = 0,
78 symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{},
23179
232 const target = Atom.parseRelocTarget(macho_file, .{
233 .object_id = atom.getFile().?,
234 .rel = rel,
235 .code = code,
236 .base_offset = ctx.base_offset,
237 .base_addr = ctx.base_addr,
238 });
239 if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue;
80 pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
81 thunk.symbols.deinit(allocator);
82 }
24083
241 log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{
242 rel.r_address - base_offset,
243 macho_file.getSymbolName(atom.getSymbolWithLoc()),
244 macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
245 macho_file.getSymbolName(target),
246 macho_file.getSymbol(target).n_value,
247 });
84 pub fn size(thunk: Thunk) usize {
85 return thunk.symbols.keys().len * trampoline_size;
86 }
24887
249 const comp = macho_file.base.comp;
250 const gpa = comp.gpa;
251 const target_sym = macho_file.getSymbol(target);
252 const thunk = &macho_file.thunks.items[thunk_index];
88 pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 {
89 return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size;
90 }
25391
254 const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom;
255 const thunk_target: Thunk.Target = .{ .tag = tag, .target = target };
256 const gop = try thunk.lookup.getOrPut(gpa, thunk_target);
257 if (!gop.found_existing) {
258 gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);
259 try thunk.targets.append(gpa, thunk_target);
92 pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void {
93 for (thunk.symbols.keys(), 0..) |sym_index, i| {
94 const sym = macho_file.getSymbol(sym_index);
95 const saddr = thunk.value + i * trampoline_size;
96 const taddr = sym.getAddress(.{}, macho_file);
97 const pages = try Relocation.calcNumberOfPages(saddr, taddr);
98 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
99 const off = try Relocation.calcPageOffset(taddr, .arithmetic);
100 try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
101 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
260102 }
261
262 try macho_file.thunk_table.put(gpa, atom_index, thunk_index);
263103 }
264}
265
266fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {
267 const thunk_atom_index = try createThunkAtom(macho_file);
268104
269 const thunk_atom = macho_file.getAtomPtr(thunk_atom_index);
270 const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();
271 const end_atom = macho_file.getAtomPtr(end_atom_index);
272
273 if (end_atom.next_index) |first_after_index| {
274 const first_after_atom = macho_file.getAtomPtr(first_after_index);
275 first_after_atom.prev_index = thunk_atom_index;
276 thunk_atom.next_index = first_after_index;
105 pub fn format(
106 thunk: Thunk,
107 comptime unused_fmt_string: []const u8,
108 options: std.fmt.FormatOptions,
109 writer: anytype,
110 ) !void {
111 _ = thunk;
112 _ = unused_fmt_string;
113 _ = options;
114 _ = writer;
115 @compileError("do not format Thunk directly");
277116 }
278117
279 end_atom.next_index = thunk_atom_index;
280 thunk_atom.prev_index = end_atom_index;
281
282 if (thunk.len == 0) {
283 thunk.start_index = thunk_atom_index;
118 pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) {
119 return .{ .data = .{
120 .thunk = thunk,
121 .macho_file = macho_file,
122 } };
284123 }
285124
286 thunk.len += 1;
287
288 return thunk_atom_index;
289}
290
291inline fn relocNeedsThunk(rel: macho.relocation_info) bool {
292 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
293 return rel_type == .ARM64_RELOC_BRANCH26;
294}
295
296fn isReachable(
297 macho_file: *MachO,
298 atom_index: Atom.Index,
299 rel: macho.relocation_info,
300 base_offset: i32,
301 target: SymbolWithLoc,
302 allocated: std.AutoHashMap(Atom.Index, void),
303) bool {
304 if (macho_file.stub_table.lookup.contains(target)) return false;
305
306 const source_atom = macho_file.getAtom(atom_index);
307 const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
308
309 const target_object = macho_file.objects.items[target.getFile().?];
310 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
311 const target_atom = macho_file.getAtom(target_atom_index);
312 const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
313
314 if (source_sym.n_sect != target_sym.n_sect) return false;
125 const FormatContext = struct {
126 thunk: Thunk,
127 macho_file: *MachO,
128 };
315129
316 if (!allocated.contains(target_atom_index)) return false;
130 fn format2(
131 ctx: FormatContext,
132 comptime unused_fmt_string: []const u8,
133 options: std.fmt.FormatOptions,
134 writer: anytype,
135 ) !void {
136 _ = options;
137 _ = unused_fmt_string;
138 const thunk = ctx.thunk;
139 const macho_file = ctx.macho_file;
140 try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() });
141 for (thunk.symbols.keys()) |index| {
142 const sym = macho_file.getSymbol(index);
143 try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value });
144 }
145 }
317146
318 const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset));
319 const target_addr = if (Atom.relocRequiresGot(macho_file, rel))
320 macho_file.getGotEntryAddress(target).?
321 else
322 Atom.getRelocTargetAddress(macho_file, target, false);
323 _ = Relocation.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch
324 return false;
147 const trampoline_size = 3 * @sizeOf(u32);
325148
326 return true;
327}
149 pub const Index = u32;
150};
328151
329fn createThunkAtom(macho_file: *MachO) !Atom.Index {
330 const sym_index = try macho_file.allocateSymbol();
331 const atom_index = try macho_file.createAtom(sym_index, .{
332 .size = @sizeOf(u32) * 3,
333 .alignment = .@"4",
334 });
335 const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index });
336 sym.n_type = macho.N_SECT;
337 sym.n_sect = macho_file.text_section_index.? + 1;
338 return atom_index;
339}
152/// Branch instruction has 26 bits immediate but is 4 byte aligned.
153const jump_bits = @bitSizeOf(i28);
154const max_distance = (1 << (jump_bits - 1));
340155
341pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void {
342 const slice = thunk.targets.slice();
343 for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| {
344 const atom = macho_file.getAtom(@intCast(atom_index));
345 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
346 const source_addr = sym.n_value;
347 const tag = slice.items(.tag)[target_index];
348 const target = slice.items(.target)[target_index];
349 const target_addr = switch (tag) {
350 .stub => macho_file.getStubsEntryAddress(target).?,
351 .atom => macho_file.getSymbol(target).n_value,
352 };
353 const pages = Relocation.calcNumberOfPages(source_addr, target_addr);
354 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
355 const off = try Relocation.calcPageOffset(target_addr, .arithmetic);
356 try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
357 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
358 }
359}
156/// A branch will need an extender if its target is larger than
157/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
158/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
159/// and assume margin to be 5MiB.
160const max_allowed_distance = max_distance - 0x500_000;
360161
361const std = @import("std");
162const aarch64 = @import("../../arch/aarch64/bits.zig");
362163const assert = std.debug.assert;
363const log = std.log.scoped(.thunks);
164const log = std.log.scoped(.link);
364165const macho = std.macho;
365166const math = std.math;
366167const mem = std.mem;
367
368const aarch64 = @import("../../arch/aarch64/bits.zig");
168const std = @import("std");
169const trace = @import("../../tracy.zig").trace;
369170
370171const Allocator = mem.Allocator;
371172const Atom = @import("Atom.zig");
372173const MachO = @import("../MachO.zig");
373174const Relocation = @import("Relocation.zig");
374const SymbolWithLoc = MachO.SymbolWithLoc;
175const Symbol = @import("Symbol.zig");
src/link/MachO/uuid.zig+6-2
......@@ -5,6 +5,9 @@
55/// TODO LLD also hashes the output filename to disambiguate between same builds with different
66/// output files. Should we also do that?
77pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void {
8 const tracy = trace(@src());
9 defer tracy.end();
10
811 const chunk_size: usize = 1024 * 1024;
912 const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;
1013 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 {
3538 out[8] = (out[8] & 0x3F) | 0x80;
3639}
3740
38const std = @import("std");
3941const fs = std.fs;
4042const mem = std.mem;
43const std = @import("std");
44const trace = @import("../../tracy.zig").trace;
4145
42const Allocator = mem.Allocator;
4346const Compilation = @import("../../Compilation.zig");
4447const Md5 = std.crypto.hash.Md5;
4548const Hasher = @import("hasher.zig").ParallelHasher;
49const ThreadPool = std.Thread.Pool;
src/link/MachO/zld.zig deleted-1230
......@@ -1,1230 +0,0 @@
1pub fn linkWithZld(
2 macho_file: *MachO,
3 arena: Allocator,
4 prog_node: *std.Progress.Node,
5) link.File.FlushError!void {
6 const tracy = trace(@src());
7 defer tracy.end();
8
9 const comp = macho_file.base.comp;
10 const gpa = comp.gpa;
11 const target = comp.root_mod.resolved_target.result;
12 const emit = macho_file.base.emit;
13
14 const directory = emit.directory; // Just an alias to make it shorter to type.
15 const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path});
16 const opt_zcu = comp.module;
17
18 // If there is no Zig code to compile, then we should skip flushing the output file because it
19 // will not be part of the linker line anyway.
20 const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
21 try macho_file.flushModule(arena, prog_node);
22
23 if (fs.path.dirname(full_out_path)) |dirname| {
24 break :blk try fs.path.join(arena, &.{ dirname, macho_file.base.zcu_object_sub_path.? });
25 } else {
26 break :blk macho_file.base.zcu_object_sub_path.?;
27 }
28 } else null;
29
30 var sub_prog_node = prog_node.start("MachO Flush", 0);
31 sub_prog_node.activate();
32 sub_prog_node.context.refresh();
33 defer sub_prog_node.end();
34
35 const output_mode = comp.config.output_mode;
36 const link_mode = comp.config.link_mode;
37 const cpu_arch = target.cpu.arch;
38 const is_lib = output_mode == .Lib;
39 const is_dyn_lib = link_mode == .Dynamic and is_lib;
40 const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
41 const stack_size = macho_file.base.stack_size;
42
43 const id_symlink_basename = "zld.id";
44
45 var man: Cache.Manifest = undefined;
46 defer if (!macho_file.base.disable_lld_caching) man.deinit();
47
48 var digest: [Cache.hex_digest_len]u8 = undefined;
49
50 const objects = comp.objects;
51
52 if (!macho_file.base.disable_lld_caching) {
53 man = comp.cache_parent.obtain();
54
55 // We are about to obtain this lock, so here we give other processes a chance first.
56 macho_file.base.releaseLock();
57
58 comptime assert(Compilation.link_hash_implementation_version == 11);
59
60 for (objects) |obj| {
61 _ = try man.addFile(obj.path, null);
62 man.hash.add(obj.must_link);
63 }
64 for (comp.c_object_table.keys()) |key| {
65 _ = try man.addFile(key.status.success.object_path, null);
66 }
67 try man.addOptionalFile(module_obj_path);
68 // We can skip hashing libc and libc++ components that we are in charge of building from Zig
69 // installation sources because they are always a product of the compiler version + target information.
70 man.hash.add(stack_size);
71 man.hash.add(macho_file.pagezero_vmsize);
72 man.hash.add(macho_file.headerpad_size);
73 man.hash.add(macho_file.headerpad_max_install_names);
74 man.hash.add(macho_file.base.gc_sections);
75 man.hash.add(macho_file.dead_strip_dylibs);
76 man.hash.add(comp.root_mod.strip);
77 try MachO.hashAddFrameworks(&man, macho_file.frameworks);
78 man.hash.addListOfBytes(macho_file.base.rpath_list);
79 if (is_dyn_lib) {
80 man.hash.addOptionalBytes(macho_file.install_name);
81 man.hash.addOptional(comp.version);
82 }
83 try link.hashAddSystemLibs(&man, comp.system_libs);
84 man.hash.addOptionalBytes(comp.sysroot);
85 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
86 try man.addOptionalFile(macho_file.entitlements);
87
88 // We don't actually care whether it's a cache hit or miss; we just
89 // need the digest and the lock.
90 _ = try man.hit();
91 digest = man.final();
92
93 var prev_digest_buf: [digest.len]u8 = undefined;
94 const prev_digest: []u8 = Cache.readSmallFile(
95 directory.handle,
96 id_symlink_basename,
97 &prev_digest_buf,
98 ) catch |err| blk: {
99 log.debug("MachO Zld new_digest={s} error: {s}", .{
100 std.fmt.fmtSliceHexLower(&digest),
101 @errorName(err),
102 });
103 // Handle this as a cache miss.
104 break :blk prev_digest_buf[0..0];
105 };
106 if (mem.eql(u8, prev_digest, &digest)) {
107 // Hot diggity dog! The output binary is already there.
108 log.debug("MachO Zld digest={s} match - skipping invocation", .{
109 std.fmt.fmtSliceHexLower(&digest),
110 });
111 macho_file.base.lock = man.toOwnedLock();
112 return;
113 }
114 log.debug("MachO Zld prev_digest={s} new_digest={s}", .{
115 std.fmt.fmtSliceHexLower(prev_digest),
116 std.fmt.fmtSliceHexLower(&digest),
117 });
118
119 // We are about to change the output file to be different, so we invalidate the build hash now.
120 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
121 error.FileNotFound => {},
122 else => |e| return e,
123 };
124 }
125
126 if (output_mode == .Obj) {
127 // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy
128 // here. TODO: think carefully about how we can avoid this redundant operation when doing
129 // build-obj. See also the corresponding TODO in linkAsArchive.
130 const the_object_path = blk: {
131 if (objects.len != 0) {
132 break :blk objects[0].path;
133 }
134
135 if (comp.c_object_table.count() != 0)
136 break :blk comp.c_object_table.keys()[0].status.success.object_path;
137
138 if (module_obj_path) |p|
139 break :blk p;
140
141 // TODO I think this is unreachable. Audit this situation when solving the above TODO
142 // regarding eliding redundant object -> object transformations.
143 return error.NoObjectsToLink;
144 };
145 // This can happen when using --enable-cache and using the stage1 backend. In this case
146 // we can skip the file copy.
147 if (!mem.eql(u8, the_object_path, full_out_path)) {
148 try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
149 }
150 } else {
151 const sub_path = emit.sub_path;
152
153 const old_file = macho_file.base.file; // TODO is this needed at all?
154 defer macho_file.base.file = old_file;
155
156 const file = try directory.handle.createFile(sub_path, .{
157 .truncate = true,
158 .read = true,
159 .mode = link.File.determineMode(false, output_mode, link_mode),
160 });
161 defer file.close();
162 macho_file.base.file = file;
163
164 // Index 0 is always a null symbol.
165 try macho_file.locals.append(gpa, .{
166 .n_strx = 0,
167 .n_type = 0,
168 .n_sect = 0,
169 .n_desc = 0,
170 .n_value = 0,
171 });
172 try macho_file.strtab.buffer.append(gpa, 0);
173
174 // Positional arguments to the linker such as object files and static archives.
175 var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
176 try positionals.ensureUnusedCapacity(objects.len);
177 positionals.appendSliceAssumeCapacity(objects);
178
179 for (comp.c_object_table.keys()) |key| {
180 try positionals.append(.{ .path = key.status.success.object_path });
181 }
182
183 if (module_obj_path) |p| {
184 try positionals.append(.{ .path = p });
185 }
186
187 if (comp.compiler_rt_lib) |lib| try positionals.append(.{ .path = lib.full_object_path });
188 if (comp.compiler_rt_obj) |obj| try positionals.append(.{ .path = obj.full_object_path });
189
190 // libc++ dep
191 if (comp.config.link_libcpp) {
192 try positionals.ensureUnusedCapacity(2);
193 positionals.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
194 positionals.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
195 }
196
197 var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
198
199 {
200 const vals = comp.system_libs.values();
201 try libs.ensureUnusedCapacity(vals.len);
202 for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
203 }
204
205 {
206 try libs.ensureUnusedCapacity(macho_file.frameworks.len);
207 for (macho_file.frameworks) |v| libs.putAssumeCapacity(v.path, .{
208 .needed = v.needed,
209 .weak = v.weak,
210 .path = v.path,
211 });
212 }
213
214 try macho_file.resolveLibSystem(arena, comp, &libs);
215
216 if (comp.verbose_link) {
217 var argv = std.ArrayList([]const u8).init(arena);
218
219 try argv.append("zig");
220 try argv.append("ld");
221
222 if (is_exe_or_dyn_lib) {
223 try argv.append("-dynamic");
224 }
225
226 if (is_dyn_lib) {
227 try argv.append("-dylib");
228
229 if (macho_file.install_name) |install_name| {
230 try argv.append("-install_name");
231 try argv.append(install_name);
232 }
233 }
234
235 {
236 const platform = Platform.fromTarget(target);
237 try argv.append("-platform_version");
238 try argv.append(@tagName(platform.os_tag));
239 try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
240
241 const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
242 if (sdk_version) |ver| {
243 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
244 } else {
245 try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
246 }
247 }
248
249 if (comp.sysroot) |syslibroot| {
250 try argv.append("-syslibroot");
251 try argv.append(syslibroot);
252 }
253
254 for (macho_file.base.rpath_list) |rpath| {
255 try argv.append("-rpath");
256 try argv.append(rpath);
257 }
258
259 try argv.appendSlice(&.{
260 "-pagezero_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.pagezero_vmsize}),
261 "-headerpad_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.headerpad_size}),
262 });
263
264 if (macho_file.headerpad_max_install_names) {
265 try argv.append("-headerpad_max_install_names");
266 }
267
268 if (macho_file.base.gc_sections) {
269 try argv.append("-dead_strip");
270 }
271
272 if (macho_file.dead_strip_dylibs) {
273 try argv.append("-dead_strip_dylibs");
274 }
275
276 if (macho_file.entry_name) |entry_name| {
277 try argv.appendSlice(&.{ "-e", entry_name });
278 }
279
280 for (objects) |obj| {
281 if (obj.must_link) {
282 try argv.append("-force_load");
283 }
284 try argv.append(obj.path);
285 }
286
287 for (comp.c_object_table.keys()) |key| {
288 try argv.append(key.status.success.object_path);
289 }
290
291 if (module_obj_path) |p| {
292 try argv.append(p);
293 }
294
295 if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
296 if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
297
298 if (comp.config.link_libcpp) {
299 try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
300 try argv.append(comp.libcxx_static_lib.?.full_object_path);
301 }
302
303 try argv.append("-o");
304 try argv.append(full_out_path);
305
306 try argv.append("-lSystem");
307
308 for (comp.system_libs.keys()) |l_name| {
309 const info = comp.system_libs.get(l_name).?;
310 const arg = if (info.needed)
311 try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
312 else if (info.weak)
313 try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
314 else
315 try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
316 try argv.append(arg);
317 }
318
319 for (macho_file.frameworks) |framework| {
320 const name = std.fs.path.stem(framework.path);
321 const arg = if (framework.needed)
322 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
323 else if (framework.weak)
324 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
325 else
326 try std.fmt.allocPrint(arena, "-framework {s}", .{name});
327 try argv.append(arg);
328 }
329
330 if (is_dyn_lib and macho_file.base.allow_shlib_undefined) {
331 try argv.append("-undefined");
332 try argv.append("dynamic_lookup");
333 }
334
335 Compilation.dump_argv(argv.items);
336 }
337
338 var dependent_libs = std.fifo.LinearFifo(MachO.DylibReExportInfo, .Dynamic).init(arena);
339
340 for (positionals.items) |obj| {
341 const in_file = try std.fs.cwd().openFile(obj.path, .{});
342 defer in_file.close();
343
344 var parse_ctx = MachO.ParseErrorCtx.init(gpa);
345 defer parse_ctx.deinit();
346
347 macho_file.parsePositional(
348 in_file,
349 obj.path,
350 obj.must_link,
351 &dependent_libs,
352 &parse_ctx,
353 ) catch |err| try macho_file.handleAndReportParseError(obj.path, err, &parse_ctx);
354 }
355
356 for (libs.keys(), libs.values()) |path, lib| {
357 const in_file = try std.fs.cwd().openFile(path, .{});
358 defer in_file.close();
359
360 var parse_ctx = MachO.ParseErrorCtx.init(gpa);
361 defer parse_ctx.deinit();
362
363 macho_file.parseLibrary(
364 in_file,
365 path,
366 lib,
367 false,
368 false,
369 null,
370 &dependent_libs,
371 &parse_ctx,
372 ) catch |err| try macho_file.handleAndReportParseError(path, err, &parse_ctx);
373 }
374
375 try macho_file.parseDependentLibs(&dependent_libs);
376
377 try macho_file.resolveSymbols();
378 if (macho_file.unresolved.count() > 0) {
379 try macho_file.reportUndefined();
380 return error.FlushFailure;
381 }
382
383 for (macho_file.objects.items, 0..) |*object, object_id| {
384 object.splitIntoAtoms(macho_file, @as(u32, @intCast(object_id))) catch |err| switch (err) {
385 error.MissingEhFrameSection => try macho_file.reportParseError(
386 object.name,
387 "missing section: '__TEXT,__eh_frame' is required but could not be found",
388 .{},
389 ),
390 error.BadDwarfCfi => try macho_file.reportParseError(
391 object.name,
392 "invalid DWARF: failed to parse '__TEXT,__eh_frame' section",
393 .{},
394 ),
395 else => |e| return e,
396 };
397 }
398
399 if (macho_file.base.gc_sections) {
400 try dead_strip.gcAtoms(macho_file);
401 }
402
403 try macho_file.createDyldPrivateAtom();
404 try macho_file.createTentativeDefAtoms();
405
406 if (comp.config.output_mode == .Exe) {
407 const global = macho_file.getEntryPoint().?;
408 if (macho_file.getSymbol(global).undf()) {
409 // We do one additional check here in case the entry point was found in one of the dylibs.
410 // (I actually have no idea what this would imply but it is a possible outcome and so we
411 // support it.)
412 try macho_file.addStubEntry(global);
413 }
414 }
415
416 for (macho_file.objects.items) |object| {
417 for (object.atoms.items) |atom_index| {
418 const atom = macho_file.getAtom(atom_index);
419 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
420 const header = macho_file.sections.items(.header)[sym.n_sect - 1];
421 if (header.isZerofill()) continue;
422
423 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
424 try Atom.scanAtomRelocs(macho_file, atom_index, relocs);
425 }
426 }
427
428 try eh_frame.scanRelocs(macho_file);
429 try UnwindInfo.scanRelocs(macho_file);
430
431 if (macho_file.dyld_stub_binder_index) |index|
432 try macho_file.addGotEntry(macho_file.globals.items[index]);
433
434 try calcSectionSizes(macho_file);
435
436 var unwind_info = UnwindInfo{ .gpa = gpa };
437 defer unwind_info.deinit();
438 try unwind_info.collect(macho_file);
439
440 try eh_frame.calcSectionSize(macho_file, &unwind_info);
441 unwind_info.calcSectionSize(macho_file);
442
443 try pruneAndSortSections(macho_file);
444 try createSegments(macho_file);
445 try allocateSegments(macho_file);
446
447 try macho_file.allocateSpecialSymbols();
448
449 if (build_options.enable_logging) {
450 macho_file.logSymtab();
451 macho_file.logSegments();
452 macho_file.logSections();
453 macho_file.logAtoms();
454 }
455
456 try writeAtoms(macho_file);
457 if (target.cpu.arch == .aarch64) try writeThunks(macho_file);
458 try writeDyldPrivateAtom(macho_file);
459
460 if (macho_file.stubs_section_index) |_| {
461 try writeStubs(macho_file);
462 try writeStubHelpers(macho_file);
463 try writeLaSymbolPtrs(macho_file);
464 }
465 if (macho_file.got_section_index) |sect_id|
466 try writePointerEntries(macho_file, sect_id, &macho_file.got_table);
467 if (macho_file.tlv_ptr_section_index) |sect_id|
468 try writePointerEntries(macho_file, sect_id, &macho_file.tlv_ptr_table);
469
470 try eh_frame.write(macho_file, &unwind_info);
471 try unwind_info.write(macho_file);
472 try macho_file.writeLinkeditSegmentData();
473
474 // If the last section of __DATA segment is zerofill section, we need to ensure
475 // that the free space between the end of the last non-zerofill section of __DATA
476 // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will
477 // copy-paste this space into memory for quicker zerofill operation.
478 if (macho_file.data_segment_cmd_index) |data_seg_id| blk: {
479 var physical_zerofill_start: ?u64 = null;
480 const section_indexes = macho_file.getSectionIndexes(data_seg_id);
481 for (macho_file.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
482 if (header.isZerofill() and header.size > 0) break;
483 physical_zerofill_start = header.offset + header.size;
484 } else break :blk;
485 const start = physical_zerofill_start orelse break :blk;
486 const linkedit = macho_file.getLinkeditSegmentPtr();
487 const size = math.cast(usize, linkedit.fileoff - start) orelse return error.Overflow;
488 if (size > 0) {
489 log.debug("zeroing out zerofill area of length {x} at {x}", .{ size, start });
490 const padding = try gpa.alloc(u8, size);
491 defer gpa.free(padding);
492 @memset(padding, 0);
493 try macho_file.base.file.?.pwriteAll(padding, start);
494 }
495 }
496
497 // Write code signature padding if required
498 var codesig: ?CodeSignature = if (macho_file.requiresCodeSignature()) blk: {
499 // Preallocate space for the code signature.
500 // We need to do this at this stage so that we have the load commands with proper values
501 // written out to the file.
502 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
503 // where the code signature goes into.
504 var codesig = CodeSignature.init(MachO.getPageSize(cpu_arch));
505 codesig.code_directory.ident = fs.path.basename(full_out_path);
506 if (macho_file.entitlements) |path| {
507 try codesig.addEntitlements(gpa, path);
508 }
509 try macho_file.writeCodeSignaturePadding(&codesig);
510 break :blk codesig;
511 } else null;
512 defer if (codesig) |*csig| csig.deinit(gpa);
513
514 // Write load commands
515 var lc_buffer = std.ArrayList(u8).init(arena);
516 const lc_writer = lc_buffer.writer();
517
518 try macho_file.writeSegmentHeaders(lc_writer);
519 try lc_writer.writeStruct(macho_file.dyld_info_cmd);
520 try lc_writer.writeStruct(macho_file.function_starts_cmd);
521 try lc_writer.writeStruct(macho_file.data_in_code_cmd);
522 try lc_writer.writeStruct(macho_file.symtab_cmd);
523 try lc_writer.writeStruct(macho_file.dysymtab_cmd);
524 try load_commands.writeDylinkerLC(lc_writer);
525
526 switch (output_mode) {
527 .Exe => blk: {
528 const seg_id = macho_file.header_segment_cmd_index.?;
529 const seg = macho_file.segments.items[seg_id];
530 const global = macho_file.getEntryPoint() orelse break :blk;
531 const sym = macho_file.getSymbol(global);
532
533 const addr: u64 = if (sym.undf())
534 // In this case, the symbol has been resolved in one of dylibs and so we point
535 // to the stub as its vmaddr value.
536 macho_file.getStubsEntryAddress(global).?
537 else
538 sym.n_value;
539
540 try lc_writer.writeStruct(macho.entry_point_command{
541 .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
542 .stacksize = macho_file.base.stack_size,
543 });
544 },
545 .Lib => if (link_mode == .Dynamic) {
546 try load_commands.writeDylibIdLC(macho_file, lc_writer);
547 },
548 else => {},
549 }
550
551 try load_commands.writeRpathLCs(macho_file, lc_writer);
552 try lc_writer.writeStruct(macho.source_version_command{
553 .version = 0,
554 });
555 {
556 const platform = Platform.fromTarget(target);
557 const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
558 if (platform.isBuildVersionCompatible()) {
559 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
560 } else {
561 try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
562 }
563 }
564
565 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
566 try lc_writer.writeStruct(macho_file.uuid_cmd);
567
568 try load_commands.writeLoadDylibLCs(
569 macho_file.dylibs.items,
570 macho_file.referenced_dylibs.keys(),
571 lc_writer,
572 );
573
574 if (codesig != null) {
575 try lc_writer.writeStruct(macho_file.codesig_cmd);
576 }
577
578 const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
579 try macho_file.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
580 try macho_file.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
581 try macho_file.writeUuid(comp, uuid_cmd_offset, codesig != null);
582
583 if (codesig) |*csig| {
584 try macho_file.writeCodeSignature(comp, csig); // code signing always comes last
585 try MachO.invalidateKernelCache(directory.handle, macho_file.base.emit.sub_path);
586 }
587 }
588
589 if (!macho_file.base.disable_lld_caching) {
590 // Update the file with the digest. If it fails we can continue; it only
591 // means that the next invocation will have an unnecessary cache miss.
592 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
593 log.debug("failed to save linking hash digest file: {s}", .{@errorName(err)});
594 };
595 // Again failure here only means an unnecessary cache miss.
596 if (man.have_exclusive_lock) {
597 man.writeManifest() catch |err| {
598 log.debug("failed to write cache manifest when linking: {s}", .{@errorName(err)});
599 };
600 }
601 // We hang on to this lock so that the output file path can be used without
602 // other processes clobbering it.
603 macho_file.base.lock = man.toOwnedLock();
604 }
605}
606
607fn createSegments(macho_file: *MachO) !void {
608 const comp = macho_file.base.comp;
609 const gpa = comp.gpa;
610 const target = macho_file.base.comp.root_mod.resolved_target.result;
611 const page_size = MachO.getPageSize(target.cpu.arch);
612 const aligned_pagezero_vmsize = mem.alignBackward(u64, macho_file.pagezero_vmsize, page_size);
613 if (macho_file.base.comp.config.output_mode != .Lib and aligned_pagezero_vmsize > 0) {
614 if (aligned_pagezero_vmsize != macho_file.pagezero_vmsize) {
615 log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{macho_file.pagezero_vmsize});
616 log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize});
617 }
618 macho_file.pagezero_segment_cmd_index = @intCast(macho_file.segments.items.len);
619 try macho_file.segments.append(gpa, .{
620 .cmdsize = @sizeOf(macho.segment_command_64),
621 .segname = MachO.makeStaticString("__PAGEZERO"),
622 .vmsize = aligned_pagezero_vmsize,
623 });
624 }
625
626 // __TEXT segment is non-optional
627 {
628 const protection = MachO.getSegmentMemoryProtection("__TEXT");
629 macho_file.text_segment_cmd_index = @intCast(macho_file.segments.items.len);
630 macho_file.header_segment_cmd_index = macho_file.text_segment_cmd_index.?;
631 try macho_file.segments.append(gpa, .{
632 .cmdsize = @sizeOf(macho.segment_command_64),
633 .segname = MachO.makeStaticString("__TEXT"),
634 .maxprot = protection,
635 .initprot = protection,
636 });
637 }
638
639 for (macho_file.sections.items(.header), 0..) |header, sect_id| {
640 if (header.size == 0) continue; // empty section
641
642 const segname = header.segName();
643 const segment_id = macho_file.getSegmentByName(segname) orelse blk: {
644 log.debug("creating segment '{s}'", .{segname});
645 const segment_id = @as(u8, @intCast(macho_file.segments.items.len));
646 const protection = MachO.getSegmentMemoryProtection(segname);
647 try macho_file.segments.append(gpa, .{
648 .cmdsize = @sizeOf(macho.segment_command_64),
649 .segname = MachO.makeStaticString(segname),
650 .maxprot = protection,
651 .initprot = protection,
652 });
653 break :blk segment_id;
654 };
655 const segment = &macho_file.segments.items[segment_id];
656 segment.cmdsize += @sizeOf(macho.section_64);
657 segment.nsects += 1;
658 macho_file.sections.items(.segment_index)[sect_id] = segment_id;
659 }
660
661 if (macho_file.getSegmentByName("__DATA_CONST")) |index| {
662 macho_file.data_const_segment_cmd_index = index;
663 }
664
665 if (macho_file.getSegmentByName("__DATA")) |index| {
666 macho_file.data_segment_cmd_index = index;
667 }
668
669 // __LINKEDIT always comes last
670 {
671 const protection = MachO.getSegmentMemoryProtection("__LINKEDIT");
672 macho_file.linkedit_segment_cmd_index = @intCast(macho_file.segments.items.len);
673 try macho_file.segments.append(gpa, .{
674 .cmdsize = @sizeOf(macho.segment_command_64),
675 .segname = MachO.makeStaticString("__LINKEDIT"),
676 .maxprot = protection,
677 .initprot = protection,
678 });
679 }
680}
681
682fn writeAtoms(macho_file: *MachO) !void {
683 const comp = macho_file.base.comp;
684 const gpa = comp.gpa;
685 const slice = macho_file.sections.slice();
686
687 for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
688 const header = slice.items(.header)[sect_id];
689 if (header.isZerofill()) continue;
690
691 var atom_index = first_atom_index orelse continue;
692
693 var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow);
694 defer gpa.free(buffer);
695 @memset(buffer, 0); // TODO with NOPs
696
697 log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() });
698
699 while (true) {
700 const atom = macho_file.getAtom(atom_index);
701 if (atom.getFile()) |file| {
702 const this_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
703 const padding_size: usize = if (atom.next_index) |next_index| blk: {
704 const next_sym = macho_file.getSymbol(macho_file.getAtom(next_index).getSymbolWithLoc());
705 const size = next_sym.n_value - (this_sym.n_value + atom.size);
706 break :blk math.cast(usize, size) orelse return error.Overflow;
707 } else 0;
708
709 log.debug(" (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{
710 atom.sym_index,
711 macho_file.getSymbolName(atom.getSymbolWithLoc()),
712 file,
713 });
714 if (padding_size > 0) {
715 log.debug(" (with padding {x})", .{padding_size});
716 }
717
718 const offset = math.cast(usize, this_sym.n_value - header.addr) orelse
719 return error.Overflow;
720 log.debug(" (at offset 0x{x})", .{offset});
721
722 const code = Atom.getAtomCode(macho_file, atom_index);
723 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
724 const size = math.cast(usize, atom.size) orelse return error.Overflow;
725 @memcpy(buffer[offset .. offset + size], code);
726 try Atom.resolveRelocs(
727 macho_file,
728 atom_index,
729 buffer[offset..][0..size],
730 relocs,
731 );
732 }
733
734 if (atom.next_index) |next_index| {
735 atom_index = next_index;
736 } else break;
737 }
738
739 log.debug(" (writing at file offset 0x{x})", .{header.offset});
740 try macho_file.base.file.?.pwriteAll(buffer, header.offset);
741 }
742}
743
744fn writeDyldPrivateAtom(macho_file: *MachO) !void {
745 const atom_index = macho_file.dyld_private_atom_index orelse return;
746 const atom = macho_file.getAtom(atom_index);
747 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
748 const sect_id = macho_file.data_section_index.?;
749 const header = macho_file.sections.items(.header)[sect_id];
750 const offset = sym.n_value - header.addr + header.offset;
751 log.debug("writing __dyld_private at offset 0x{x}", .{offset});
752 const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
753 try macho_file.base.file.?.pwriteAll(&buffer, offset);
754}
755
756fn writeThunks(macho_file: *MachO) !void {
757 const target = macho_file.base.comp.root_mod.resolved_target.result;
758 assert(target.cpu.arch == .aarch64);
759 const comp = macho_file.base.comp;
760 const gpa = comp.gpa;
761
762 const sect_id = macho_file.text_section_index orelse return;
763 const header = macho_file.sections.items(.header)[sect_id];
764
765 for (macho_file.thunks.items, 0..) |*thunk, i| {
766 if (thunk.getSize() == 0) continue;
767 const thunk_size = math.cast(usize, thunk.getSize()) orelse return error.Overflow;
768 var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk_size);
769 defer buffer.deinit();
770 try thunks.writeThunkCode(macho_file, thunk, buffer.writer());
771 const thunk_atom = macho_file.getAtom(thunk.getStartAtomIndex());
772 const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc());
773 const offset = thunk_sym.n_value - header.addr + header.offset;
774 log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset });
775 try macho_file.base.file.?.pwriteAll(buffer.items, offset);
776 }
777}
778
779fn writePointerEntries(macho_file: *MachO, sect_id: u8, table: anytype) !void {
780 const comp = macho_file.base.comp;
781 const gpa = comp.gpa;
782 const header = macho_file.sections.items(.header)[sect_id];
783 const capacity = math.cast(usize, header.size) orelse return error.Overflow;
784 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
785 defer buffer.deinit();
786 for (table.entries.items) |entry| {
787 const sym = macho_file.getSymbol(entry);
788 buffer.writer().writeInt(u64, sym.n_value, .little) catch unreachable;
789 }
790 log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset});
791 try macho_file.base.file.?.pwriteAll(buffer.items, header.offset);
792}
793
794fn writeStubs(macho_file: *MachO) !void {
795 const comp = macho_file.base.comp;
796 const gpa = comp.gpa;
797 const target = macho_file.base.comp.root_mod.resolved_target.result;
798 const cpu_arch = target.cpu.arch;
799 const stubs_header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
800 const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
801
802 const capacity = math.cast(usize, stubs_header.size) orelse return error.Overflow;
803 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
804 defer buffer.deinit();
805
806 for (0..macho_file.stub_table.count()) |index| {
807 try stubs.writeStubCode(.{
808 .cpu_arch = cpu_arch,
809 .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index,
810 .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64),
811 }, buffer.writer());
812 }
813
814 log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset});
815 try macho_file.base.file.?.pwriteAll(buffer.items, stubs_header.offset);
816}
817
818fn writeStubHelpers(macho_file: *MachO) !void {
819 const comp = macho_file.base.comp;
820 const gpa = comp.gpa;
821 const target = macho_file.base.comp.root_mod.resolved_target.result;
822 const cpu_arch = target.cpu.arch;
823 const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
824
825 const capacity = math.cast(usize, stub_helper_header.size) orelse return error.Overflow;
826 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
827 defer buffer.deinit();
828
829 {
830 const dyld_private_addr = blk: {
831 const atom = macho_file.getAtom(macho_file.dyld_private_atom_index.?);
832 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
833 break :blk sym.n_value;
834 };
835 const dyld_stub_binder_got_addr = blk: {
836 const sym_loc = macho_file.globals.items[macho_file.dyld_stub_binder_index.?];
837 break :blk macho_file.getGotEntryAddress(sym_loc).?;
838 };
839 try stubs.writeStubHelperPreambleCode(.{
840 .cpu_arch = cpu_arch,
841 .source_addr = stub_helper_header.addr,
842 .dyld_private_addr = dyld_private_addr,
843 .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
844 }, buffer.writer());
845 }
846
847 for (0..macho_file.stub_table.count()) |index| {
848 const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
849 stubs.stubHelperSize(cpu_arch) * index;
850 try stubs.writeStubHelperCode(.{
851 .cpu_arch = cpu_arch,
852 .source_addr = source_addr,
853 .target_addr = stub_helper_header.addr,
854 }, buffer.writer());
855 }
856
857 log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{
858 stub_helper_header.offset,
859 });
860 try macho_file.base.file.?.pwriteAll(buffer.items, stub_helper_header.offset);
861}
862
863fn writeLaSymbolPtrs(macho_file: *MachO) !void {
864 const comp = macho_file.base.comp;
865 const gpa = comp.gpa;
866 const target = macho_file.base.comp.root_mod.resolved_target.result;
867 const cpu_arch = target.cpu.arch;
868 const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
869 const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
870
871 const capacity = math.cast(usize, la_symbol_ptr_header.size) orelse return error.Overflow;
872 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
873 defer buffer.deinit();
874
875 for (0..macho_file.stub_table.count()) |index| {
876 const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
877 stubs.stubHelperSize(cpu_arch) * index;
878 buffer.writer().writeInt(u64, target_addr, .little) catch unreachable;
879 }
880
881 log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{
882 la_symbol_ptr_header.offset,
883 });
884 try macho_file.base.file.?.pwriteAll(buffer.items, la_symbol_ptr_header.offset);
885}
886
887fn pruneAndSortSections(macho_file: *MachO) !void {
888 const Entry = struct {
889 index: u8,
890
891 pub fn lessThan(ctx: *MachO, lhs: @This(), rhs: @This()) bool {
892 const lhs_header = ctx.sections.items(.header)[lhs.index];
893 const rhs_header = ctx.sections.items(.header)[rhs.index];
894 return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header);
895 }
896 };
897
898 const comp = macho_file.base.comp;
899 const gpa = comp.gpa;
900
901 var entries = try std.ArrayList(Entry).initCapacity(gpa, macho_file.sections.slice().len);
902 defer entries.deinit();
903
904 for (0..macho_file.sections.slice().len) |index| {
905 const section = macho_file.sections.get(index);
906 if (section.header.size == 0) {
907 log.debug("pruning section {s},{s} {?d}", .{
908 section.header.segName(),
909 section.header.sectName(),
910 section.first_atom_index,
911 });
912 for (&[_]*?u8{
913 &macho_file.text_section_index,
914 &macho_file.data_const_section_index,
915 &macho_file.data_section_index,
916 &macho_file.bss_section_index,
917 &macho_file.thread_vars_section_index,
918 &macho_file.thread_data_section_index,
919 &macho_file.thread_bss_section_index,
920 &macho_file.eh_frame_section_index,
921 &macho_file.unwind_info_section_index,
922 &macho_file.got_section_index,
923 &macho_file.tlv_ptr_section_index,
924 &macho_file.stubs_section_index,
925 &macho_file.stub_helper_section_index,
926 &macho_file.la_symbol_ptr_section_index,
927 }) |maybe_index| {
928 if (maybe_index.* != null and maybe_index.*.? == index) {
929 maybe_index.* = null;
930 }
931 }
932 continue;
933 }
934 entries.appendAssumeCapacity(.{ .index = @intCast(index) });
935 }
936
937 mem.sort(Entry, entries.items, macho_file, Entry.lessThan);
938
939 var slice = macho_file.sections.toOwnedSlice();
940 defer slice.deinit(gpa);
941
942 const backlinks = try gpa.alloc(u8, slice.len);
943 defer gpa.free(backlinks);
944 for (entries.items, 0..) |entry, i| {
945 backlinks[entry.index] = @as(u8, @intCast(i));
946 }
947
948 try macho_file.sections.ensureTotalCapacity(gpa, entries.items.len);
949 for (entries.items) |entry| {
950 macho_file.sections.appendAssumeCapacity(slice.get(entry.index));
951 }
952
953 for (&[_]*?u8{
954 &macho_file.text_section_index,
955 &macho_file.data_const_section_index,
956 &macho_file.data_section_index,
957 &macho_file.bss_section_index,
958 &macho_file.thread_vars_section_index,
959 &macho_file.thread_data_section_index,
960 &macho_file.thread_bss_section_index,
961 &macho_file.eh_frame_section_index,
962 &macho_file.unwind_info_section_index,
963 &macho_file.got_section_index,
964 &macho_file.tlv_ptr_section_index,
965 &macho_file.stubs_section_index,
966 &macho_file.stub_helper_section_index,
967 &macho_file.la_symbol_ptr_section_index,
968 }) |maybe_index| {
969 if (maybe_index.*) |*index| {
970 index.* = backlinks[index.*];
971 }
972 }
973}
974
975fn calcSectionSizes(macho_file: *MachO) !void {
976 const target = macho_file.base.comp.root_mod.resolved_target.result;
977 const slice = macho_file.sections.slice();
978 for (slice.items(.header), 0..) |*header, sect_id| {
979 if (header.size == 0) continue;
980 if (macho_file.text_section_index) |txt| {
981 if (txt == sect_id and target.cpu.arch == .aarch64) continue;
982 }
983
984 var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue;
985
986 header.size = 0;
987 header.@"align" = 0;
988
989 while (true) {
990 const atom = macho_file.getAtom(atom_index);
991 const atom_offset = atom.alignment.forward(header.size);
992 const padding = atom_offset - header.size;
993
994 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
995 sym.n_value = atom_offset;
996
997 header.size += padding + atom.size;
998 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
999
1000 atom_index = atom.next_index orelse break;
1001 }
1002 }
1003
1004 if (macho_file.text_section_index != null and target.cpu.arch == .aarch64) {
1005 // Create jump/branch range extenders if needed.
1006 try thunks.createThunks(macho_file, macho_file.text_section_index.?);
1007 }
1008
1009 // Update offsets of all symbols contained within each Atom.
1010 // We need to do this since our unwind info synthesiser relies on
1011 // traversing the symbols when synthesising unwind info and DWARF CFI records.
1012 for (slice.items(.first_atom_index)) |first_atom_index| {
1013 var atom_index = first_atom_index orelse continue;
1014
1015 while (true) {
1016 const atom = macho_file.getAtom(atom_index);
1017 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
1018
1019 if (atom.getFile() != null) {
1020 // Update each symbol contained within the atom
1021 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
1022 while (it.next()) |sym_loc| {
1023 const inner_sym = macho_file.getSymbolPtr(sym_loc);
1024 inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
1025 macho_file,
1026 atom_index,
1027 sym_loc.sym_index,
1028 );
1029 }
1030
1031 // If there is a section alias, update it now too
1032 if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
1033 const alias = macho_file.getSymbolPtr(sym_loc);
1034 alias.n_value = sym.n_value;
1035 }
1036 }
1037
1038 if (atom.next_index) |next_index| {
1039 atom_index = next_index;
1040 } else break;
1041 }
1042 }
1043
1044 if (macho_file.got_section_index) |sect_id| {
1045 const header = &macho_file.sections.items(.header)[sect_id];
1046 header.size = macho_file.got_table.count() * @sizeOf(u64);
1047 header.@"align" = 3;
1048 }
1049
1050 if (macho_file.tlv_ptr_section_index) |sect_id| {
1051 const header = &macho_file.sections.items(.header)[sect_id];
1052 header.size = macho_file.tlv_ptr_table.count() * @sizeOf(u64);
1053 header.@"align" = 3;
1054 }
1055
1056 const cpu_arch = target.cpu.arch;
1057
1058 if (macho_file.stubs_section_index) |sect_id| {
1059 const header = &macho_file.sections.items(.header)[sect_id];
1060 header.size = macho_file.stub_table.count() * stubs.stubSize(cpu_arch);
1061 header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
1062 }
1063
1064 if (macho_file.stub_helper_section_index) |sect_id| {
1065 const header = &macho_file.sections.items(.header)[sect_id];
1066 header.size = macho_file.stub_table.count() * stubs.stubHelperSize(cpu_arch) +
1067 stubs.stubHelperPreambleSize(cpu_arch);
1068 header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
1069 }
1070
1071 if (macho_file.la_symbol_ptr_section_index) |sect_id| {
1072 const header = &macho_file.sections.items(.header)[sect_id];
1073 header.size = macho_file.stub_table.count() * @sizeOf(u64);
1074 header.@"align" = 3;
1075 }
1076}
1077
1078fn allocateSegments(macho_file: *MachO) !void {
1079 for (macho_file.segments.items, 0..) |*segment, segment_index| {
1080 const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
1081 const base_size = if (is_text_segment)
1082 try load_commands.calcMinHeaderPad(macho_file, .{
1083 .segments = macho_file.segments.items,
1084 .dylibs = macho_file.dylibs.items,
1085 .referenced_dylibs = macho_file.referenced_dylibs.keys(),
1086 })
1087 else
1088 0;
1089 try allocateSegment(macho_file, @as(u8, @intCast(segment_index)), base_size);
1090 }
1091}
1092
1093fn getSegmentAllocBase(macho_file: *MachO, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } {
1094 if (segment_index > 0) {
1095 const prev_segment = macho_file.segments.items[segment_index - 1];
1096 return .{
1097 .vmaddr = prev_segment.vmaddr + prev_segment.vmsize,
1098 .fileoff = prev_segment.fileoff + prev_segment.filesize,
1099 };
1100 }
1101 return .{ .vmaddr = 0, .fileoff = 0 };
1102}
1103
1104fn allocateSegment(macho_file: *MachO, segment_index: u8, init_size: u64) !void {
1105 const target = macho_file.base.comp.root_mod.resolved_target.result;
1106 const segment = &macho_file.segments.items[segment_index];
1107
1108 if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation
1109
1110 const base = getSegmentAllocBase(macho_file, segment_index);
1111 segment.vmaddr = base.vmaddr;
1112 segment.fileoff = base.fileoff;
1113 segment.filesize = init_size;
1114 segment.vmsize = init_size;
1115
1116 // Allocate the sections according to their alignment at the beginning of the segment.
1117 const indexes = macho_file.getSectionIndexes(segment_index);
1118 var start = init_size;
1119
1120 const slice = macho_file.sections.slice();
1121 for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| {
1122 const alignment = try math.powi(u32, 2, header.@"align");
1123 const start_aligned = mem.alignForward(u64, start, alignment);
1124 const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1));
1125
1126 header.offset = if (header.isZerofill())
1127 0
1128 else
1129 @as(u32, @intCast(segment.fileoff + start_aligned));
1130 header.addr = segment.vmaddr + start_aligned;
1131
1132 if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| {
1133 var atom_index = first_atom_index;
1134
1135 log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{
1136 n_sect,
1137 header.segName(),
1138 header.sectName(),
1139 });
1140
1141 while (true) {
1142 const atom = macho_file.getAtom(atom_index);
1143 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
1144 sym.n_value += header.addr;
1145 sym.n_sect = n_sect;
1146
1147 log.debug(" ATOM(%{d}, '{s}') @{x}", .{
1148 atom.sym_index,
1149 macho_file.getSymbolName(atom.getSymbolWithLoc()),
1150 sym.n_value,
1151 });
1152
1153 if (atom.getFile() != null) {
1154 // Update each symbol contained within the atom
1155 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
1156 while (it.next()) |sym_loc| {
1157 const inner_sym = macho_file.getSymbolPtr(sym_loc);
1158 inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
1159 macho_file,
1160 atom_index,
1161 sym_loc.sym_index,
1162 );
1163 inner_sym.n_sect = n_sect;
1164 }
1165
1166 // If there is a section alias, update it now too
1167 if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
1168 const alias = macho_file.getSymbolPtr(sym_loc);
1169 alias.n_value = sym.n_value;
1170 alias.n_sect = n_sect;
1171 }
1172 }
1173
1174 if (atom.next_index) |next_index| {
1175 atom_index = next_index;
1176 } else break;
1177 }
1178 }
1179
1180 start = start_aligned + header.size;
1181
1182 if (!header.isZerofill()) {
1183 segment.filesize = start;
1184 }
1185 segment.vmsize = start;
1186 }
1187
1188 const page_size = MachO.getPageSize(target.cpu.arch);
1189 segment.filesize = mem.alignForward(u64, segment.filesize, page_size);
1190 segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size);
1191}
1192
1193const std = @import("std");
1194const build_options = @import("build_options");
1195const assert = std.debug.assert;
1196const dwarf = std.dwarf;
1197const fs = std.fs;
1198const log = std.log.scoped(.link);
1199const macho = std.macho;
1200const math = std.math;
1201const mem = std.mem;
1202
1203const aarch64 = @import("../../arch/aarch64/bits.zig");
1204const calcUuid = @import("uuid.zig").calcUuid;
1205const dead_strip = @import("dead_strip.zig");
1206const eh_frame = @import("eh_frame.zig");
1207const fat = @import("fat.zig");
1208const link = @import("../../link.zig");
1209const load_commands = @import("load_commands.zig");
1210const stubs = @import("stubs.zig");
1211const thunks = @import("thunks.zig");
1212const trace = @import("../../tracy.zig").trace;
1213
1214const Allocator = mem.Allocator;
1215const Archive = @import("Archive.zig");
1216const Atom = @import("Atom.zig");
1217const Cache = std.Build.Cache;
1218const CodeSignature = @import("CodeSignature.zig");
1219const Compilation = @import("../../Compilation.zig");
1220const Dylib = @import("Dylib.zig");
1221const MachO = @import("../MachO.zig");
1222const Md5 = std.crypto.hash.Md5;
1223const LibStub = @import("../tapi.zig").LibStub;
1224const Object = @import("Object.zig");
1225const Platform = load_commands.Platform;
1226const Section = MachO.Section;
1227const SymbolWithLoc = MachO.SymbolWithLoc;
1228const TableSection = @import("../table_section.zig").TableSection;
1229const Trie = @import("Trie.zig");
1230const UnwindInfo = @import("UnwindInfo.zig");
src/main.zig+2-3
......@@ -2823,9 +2823,7 @@ fn buildOutputType(
28232823 }
28242824 // After this point, resolved_frameworks is used instead of frameworks.
28252825
2826 if (create_module.resolved_options.output_mode == .Obj and
2827 (target.ofmt == .coff or target.ofmt == .macho))
2828 {
2826 if (create_module.resolved_options.output_mode == .Obj and target.ofmt == .coff) {
28292827 const total_obj_count = create_module.c_source_files.items.len +
28302828 @intFromBool(root_src_file != null) +
28312829 create_module.rc_source_files.items.len +
......@@ -3220,6 +3218,7 @@ fn buildOutputType(
32203218 .clang_passthrough_mode = clang_passthrough_mode,
32213219 .clang_preprocessor_mode = clang_preprocessor_mode,
32223220 .version = optional_version,
3221 .compatibility_version = compatibility_version,
32233222 .libc_installation = if (create_module.libc_installation) |*lci| lci else null,
32243223 .verbose_cc = verbose_cc,
32253224 .verbose_link = verbose_link,
test/link.zig-110
......@@ -89,114 +89,4 @@ pub const cases = [_]Case{
8989 .build_root = "test/link/wasm/type",
9090 .import = @import("link/wasm/type/build.zig"),
9191 },
92
93 // Mach-O Cases
94 .{
95 .build_root = "test/link/macho/bugs/13056",
96 .import = @import("link/macho/bugs/13056/build.zig"),
97 },
98 .{
99 .build_root = "test/link/macho/bugs/13457",
100 .import = @import("link/macho/bugs/13457/build.zig"),
101 },
102 .{
103 .build_root = "test/link/macho/bugs/16308",
104 .import = @import("link/macho/bugs/16308/build.zig"),
105 },
106 .{
107 .build_root = "test/link/macho/bugs/16628",
108 .import = @import("link/macho/bugs/16628/build.zig"),
109 },
110 .{
111 .build_root = "test/link/macho/dead_strip",
112 .import = @import("link/macho/dead_strip/build.zig"),
113 },
114 .{
115 .build_root = "test/link/macho/dead_strip_dylibs",
116 .import = @import("link/macho/dead_strip_dylibs/build.zig"),
117 },
118 .{
119 .build_root = "test/link/macho/dylib",
120 .import = @import("link/macho/dylib/build.zig"),
121 },
122 .{
123 .build_root = "test/link/macho/empty",
124 .import = @import("link/macho/empty/build.zig"),
125 },
126 .{
127 .build_root = "test/link/macho/entry",
128 .import = @import("link/macho/entry/build.zig"),
129 },
130 .{
131 .build_root = "test/link/macho/entry_in_archive",
132 .import = @import("link/macho/entry_in_archive/build.zig"),
133 },
134 .{
135 .build_root = "test/link/macho/entry_in_dylib",
136 .import = @import("link/macho/entry_in_dylib/build.zig"),
137 },
138 .{
139 .build_root = "test/link/macho/headerpad",
140 .import = @import("link/macho/headerpad/build.zig"),
141 },
142 .{
143 .build_root = "test/link/macho/linksection",
144 .import = @import("link/macho/linksection/build.zig"),
145 },
146 .{
147 .build_root = "test/link/macho/needed_framework",
148 .import = @import("link/macho/needed_framework/build.zig"),
149 },
150 .{
151 .build_root = "test/link/macho/needed_library",
152 .import = @import("link/macho/needed_library/build.zig"),
153 },
154 .{
155 .build_root = "test/link/macho/objc",
156 .import = @import("link/macho/objc/build.zig"),
157 },
158 .{
159 .build_root = "test/link/macho/objcpp",
160 .import = @import("link/macho/objcpp/build.zig"),
161 },
162 .{
163 .build_root = "test/link/macho/pagezero",
164 .import = @import("link/macho/pagezero/build.zig"),
165 },
166 .{
167 .build_root = "test/link/macho/reexports",
168 .import = @import("link/macho/reexports/build.zig"),
169 },
170 .{
171 .build_root = "test/link/macho/search_strategy",
172 .import = @import("link/macho/search_strategy/build.zig"),
173 },
174 .{
175 .build_root = "test/link/macho/stack_size",
176 .import = @import("link/macho/stack_size/build.zig"),
177 },
178 .{
179 .build_root = "test/link/macho/strict_validation",
180 .import = @import("link/macho/strict_validation/build.zig"),
181 },
182 .{
183 .build_root = "test/link/macho/tbdv3",
184 .import = @import("link/macho/tbdv3/build.zig"),
185 },
186 .{
187 .build_root = "test/link/macho/tls",
188 .import = @import("link/macho/tls/build.zig"),
189 },
190 .{
191 .build_root = "test/link/macho/unwind_info",
192 .import = @import("link/macho/unwind_info/build.zig"),
193 },
194 .{
195 .build_root = "test/link/macho/weak_library",
196 .import = @import("link/macho/weak_library/build.zig"),
197 },
198 .{
199 .build_root = "test/link/macho/weak_framework",
200 .import = @import("link/macho/weak_framework/build.zig"),
201 },
20292};
test/link/elf.zig+14-7
......@@ -2,7 +2,8 @@
22//! Currently, we support linking x86_64 Linux, but in the future we
33//! will progressively relax those to exercise more combinations.
44
5pub fn testAll(b: *Build) *Step {
5pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
6 _ = build_opts;
67 const elf_step = b.step("test-elf", "Run ELF tests");
78
89 const default_target = b.resolveTargetQuery(.{
......@@ -3609,12 +3610,17 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
36093610 exe.linkLibrary(dylib);
36103611 exe.linkLibC();
36113612
3612 expectLinkErrors(exe, test_step, .{ .exact = &.{
3613 "invalid token in LD script: '\\x00\\x00\\x00\\x0c\\x00\\x00\\x00/usr/lib/dyld\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d' (0:829)",
3614 "note: while parsing /?/liba.dylib",
3615 "unexpected error: parsing input file failed with error InvalidLdScript",
3616 "note: while parsing /?/liba.dylib",
3617 } });
3613 // TODO: improve the test harness to be able to selectively match lines in error output
3614 // while avoiding jankiness
3615 // expectLinkErrors(exe, test_step, .{ .exact = &.{
3616 // "error: invalid token in LD script: '\\x00\\x00\\x00\\x0c\\x00\\x00\\x00/usr/lib/dyld\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d' (0:989)",
3617 // "note: while parsing /?/liba.dylib",
3618 // "error: unexpected error: parsing input file failed with error InvalidLdScript",
3619 // "note: while parsing /?/liba.dylib",
3620 // } });
3621 expectLinkErrors(exe, test_step, .{
3622 .contains = "error: unexpected error: parsing input file failed with error InvalidLdScript",
3623 });
36183624
36193625 return test_step;
36203626}
......@@ -3896,6 +3902,7 @@ const link = @import("link.zig");
38963902const std = @import("std");
38973903
38983904const Build = std.Build;
3905const BuildOptions = link.BuildOptions;
38993906const Options = link.Options;
39003907const Step = Build.Step;
39013908const WriteFile = Step.WriteFile;
test/link/link.zig+65-96
......@@ -2,10 +2,26 @@ pub fn build(b: *Build) void {
22 const test_step = b.step("test-link", "Run link tests");
33 b.default_step = test_step;
44
5 test_step.dependOn(@import("elf.zig").testAll(b));
6 test_step.dependOn(@import("macho.zig").testAll(b));
5 const has_macos_sdk = b.option(bool, "has_macos_sdk", "whether the host provides a macOS SDK in system path");
6 const has_ios_sdk = b.option(bool, "has_ios_sdk", "whether the host provides a iOS SDK in system path");
7 const has_symlinks_windows = b.option(bool, "has_symlinks_windows", "whether the host is windows and has symlinks enabled");
8
9 const build_opts: BuildOptions = .{
10 .has_macos_sdk = has_macos_sdk orelse false,
11 .has_ios_sdk = has_ios_sdk orelse false,
12 .has_symlinks_windows = has_symlinks_windows orelse false,
13 };
14
15 test_step.dependOn(@import("elf.zig").testAll(b, build_opts));
16 test_step.dependOn(@import("macho.zig").testAll(b, build_opts));
717}
818
19pub const BuildOptions = struct {
20 has_macos_sdk: bool,
21 has_ios_sdk: bool,
22 has_symlinks_windows: bool,
23};
24
925pub const Options = struct {
1026 target: std.Build.ResolvedTarget,
1127 optimize: std.builtin.OptimizeMode = .Debug,
......@@ -30,121 +46,74 @@ const OverlayOptions = struct {
3046 c_source_flags: []const []const u8 = &.{},
3147 cpp_source_bytes: ?[]const u8 = null,
3248 cpp_source_flags: []const []const u8 = &.{},
49 objc_source_bytes: ?[]const u8 = null,
50 objc_source_flags: []const []const u8 = &.{},
51 objcpp_source_bytes: ?[]const u8 = null,
52 objcpp_source_flags: []const []const u8 = &.{},
3353 zig_source_bytes: ?[]const u8 = null,
3454 pic: ?bool = null,
3555 strip: ?bool = null,
3656};
3757
38pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Step.Compile {
39 const compile_step = b.addExecutable(.{
40 .name = overlay.name,
41 .root_source_file = rsf: {
42 const bytes = overlay.zig_source_bytes orelse break :rsf null;
43 break :rsf b.addWriteFiles().add("a.zig", bytes);
44 },
45 .target = base.target,
46 .optimize = base.optimize,
47 .use_llvm = base.use_llvm,
48 .use_lld = base.use_lld,
49 .pic = overlay.pic,
50 .strip = overlay.strip,
51 });
52 if (overlay.cpp_source_bytes) |bytes| {
53 compile_step.addCSourceFile(.{
54 .file = b.addWriteFiles().add("a.cpp", bytes),
55 .flags = overlay.cpp_source_flags,
56 });
57 }
58 if (overlay.c_source_bytes) |bytes| {
59 compile_step.addCSourceFile(.{
60 .file = b.addWriteFiles().add("a.c", bytes),
61 .flags = overlay.c_source_flags,
62 });
63 }
64 if (overlay.asm_source_bytes) |bytes| {
65 compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
66 }
67 return compile_step;
58pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile {
59 return addCompileStep(b, base, overlay, .exe);
6860}
6961
70pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Step.Compile {
71 const compile_step = b.addObject(.{
72 .name = overlay.name,
73 .root_source_file = rsf: {
74 const bytes = overlay.zig_source_bytes orelse break :rsf null;
75 break :rsf b.addWriteFiles().add("a.zig", bytes);
76 },
77 .target = base.target,
78 .optimize = base.optimize,
79 .use_llvm = base.use_llvm,
80 .use_lld = base.use_lld,
81 .pic = overlay.pic,
82 .strip = overlay.strip,
83 });
84 if (overlay.cpp_source_bytes) |bytes| {
85 compile_step.addCSourceFile(.{
86 .file = b.addWriteFiles().add("a.cpp", bytes),
87 .flags = overlay.cpp_source_flags,
88 });
89 }
90 if (overlay.c_source_bytes) |bytes| {
91 compile_step.addCSourceFile(.{
92 .file = b.addWriteFiles().add("a.c", bytes),
93 .flags = overlay.c_source_flags,
94 });
95 }
96 if (overlay.asm_source_bytes) |bytes| {
97 compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
98 }
99 return compile_step;
62pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
63 return addCompileStep(b, base, overlay, .obj);
10064}
10165
10266pub fn addStaticLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
103 const compile_step = b.addStaticLibrary(.{
67 return addCompileStep(b, base, overlay, .static_lib);
68}
69
70pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
71 return addCompileStep(b, base, overlay, .shared_lib);
72}
73
74fn addCompileStep(
75 b: *Build,
76 base: Options,
77 overlay: OverlayOptions,
78 kind: enum { exe, obj, shared_lib, static_lib },
79) *Compile {
80 const compile_step = Compile.create(b, .{
10481 .name = overlay.name,
105 .root_source_file = rsf: {
106 const bytes = overlay.zig_source_bytes orelse break :rsf null;
107 break :rsf b.addWriteFiles().add("a.zig", bytes);
82 .root_module = .{
83 .target = base.target,
84 .optimize = base.optimize,
85 .root_source_file = rsf: {
86 const bytes = overlay.zig_source_bytes orelse break :rsf null;
87 break :rsf b.addWriteFiles().add("a.zig", bytes);
88 },
89 .pic = overlay.pic,
90 .strip = overlay.strip,
10891 },
109 .target = base.target,
110 .optimize = base.optimize,
11192 .use_llvm = base.use_llvm,
11293 .use_lld = base.use_lld,
113 .pic = overlay.pic,
114 .strip = overlay.strip,
94 .kind = switch (kind) {
95 .exe => .exe,
96 .obj => .obj,
97 .shared_lib, .static_lib => .lib,
98 },
99 .linkage = switch (kind) {
100 .exe, .obj => null,
101 .shared_lib => .dynamic,
102 .static_lib => .static,
103 },
115104 });
116 if (overlay.cpp_source_bytes) |bytes| {
105 if (overlay.objcpp_source_bytes) |bytes| {
117106 compile_step.addCSourceFile(.{
118 .file = b.addWriteFiles().add("a.cpp", bytes),
119 .flags = overlay.cpp_source_flags,
107 .file = b.addWriteFiles().add("a.mm", bytes),
108 .flags = overlay.objcpp_source_flags,
120109 });
121110 }
122 if (overlay.c_source_bytes) |bytes| {
111 if (overlay.objc_source_bytes) |bytes| {
123112 compile_step.addCSourceFile(.{
124 .file = b.addWriteFiles().add("a.c", bytes),
125 .flags = overlay.c_source_flags,
113 .file = b.addWriteFiles().add("a.m", bytes),
114 .flags = overlay.objc_source_flags,
126115 });
127116 }
128 if (overlay.asm_source_bytes) |bytes| {
129 compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes));
130 }
131 return compile_step;
132}
133
134pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
135 const compile_step = b.addSharedLibrary(.{
136 .name = overlay.name,
137 .root_source_file = rsf: {
138 const bytes = overlay.zig_source_bytes orelse break :rsf null;
139 break :rsf b.addWriteFiles().add("a.zig", bytes);
140 },
141 .target = base.target,
142 .optimize = base.optimize,
143 .use_llvm = base.use_llvm,
144 .use_lld = base.use_lld,
145 .pic = overlay.pic,
146 .strip = overlay.strip,
147 });
148117 if (overlay.cpp_source_bytes) |bytes| {
149118 compile_step.addCSourceFile(.{
150119 .file = b.addWriteFiles().add("a.cpp", bytes),
test/link/macho.zig+2165-18
......@@ -1,18 +1,1254 @@
11//! Here we test our MachO linker for correctness and functionality.
2//! TODO migrate standalone tests from test/link/macho/* to here.
32
4pub fn testAll(b: *std.Build) *Step {
3pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
54 const macho_step = b.step("test-macho", "Run MachO tests");
65
7 macho_step.dependOn(testResolvingBoundarySymbols(b, .{
8 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
9 }));
6 const default_target = b.resolveTargetQuery(.{
7 .os_tag = .macos,
8 });
9 const x86_64_target = b.resolveTargetQuery(.{
10 .cpu_arch = .x86_64,
11 .os_tag = .macos,
12 });
13 const aarch64_target = b.resolveTargetQuery(.{
14 .cpu_arch = .aarch64,
15 .os_tag = .macos,
16 });
17
18 macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
19 macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
20 macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
21 macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
22 macho_step.dependOn(testHeaderWeakFlags(b, .{ .target = default_target }));
23 macho_step.dependOn(testHelloC(b, .{ .target = default_target }));
24 macho_step.dependOn(testHelloZig(b, .{ .target = default_target }));
25 macho_step.dependOn(testLargeBss(b, .{ .target = default_target }));
26 macho_step.dependOn(testLayout(b, .{ .target = default_target }));
27 macho_step.dependOn(testLinksection(b, .{ .target = default_target }));
28 macho_step.dependOn(testMhExecuteHeader(b, .{ .target = default_target }));
29 macho_step.dependOn(testNoDeadStrip(b, .{ .target = default_target }));
30 macho_step.dependOn(testNoExportsDylib(b, .{ .target = default_target }));
31 macho_step.dependOn(testPagezeroSize(b, .{ .target = default_target }));
32 macho_step.dependOn(testReexportsZig(b, .{ .target = default_target }));
33 macho_step.dependOn(testRelocatable(b, .{ .target = default_target }));
34 macho_step.dependOn(testRelocatableZig(b, .{ .target = default_target }));
35 macho_step.dependOn(testSectionBoundarySymbols(b, .{ .target = default_target }));
36 macho_step.dependOn(testSegmentBoundarySymbols(b, .{ .target = default_target }));
37 macho_step.dependOn(testStackSize(b, .{ .target = default_target }));
38 macho_step.dependOn(testTentative(b, .{ .target = default_target }));
39 macho_step.dependOn(testThunks(b, .{ .target = aarch64_target }));
40 macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
41 macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
42 macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));
43 macho_step.dependOn(testUnwindInfoNoSubsectionsX64(b, .{ .target = x86_64_target }));
44 macho_step.dependOn(testUnwindInfoNoSubsectionsArm64(b, .{ .target = aarch64_target }));
45 macho_step.dependOn(testWeakBind(b, .{ .target = x86_64_target }));
46
47 // Tests requiring symlinks when tested on Windows
48 if (build_opts.has_symlinks_windows) {
49 macho_step.dependOn(testEntryPointArchive(b, .{ .target = default_target }));
50 macho_step.dependOn(testEntryPointDylib(b, .{ .target = default_target }));
51 macho_step.dependOn(testDylib(b, .{ .target = default_target }));
52 macho_step.dependOn(testNeededLibrary(b, .{ .target = default_target }));
53 macho_step.dependOn(testSearchStrategy(b, .{ .target = default_target }));
54 macho_step.dependOn(testTbdv3(b, .{ .target = default_target }));
55 macho_step.dependOn(testTls(b, .{ .target = default_target }));
56 macho_step.dependOn(testTwoLevelNamespace(b, .{ .target = default_target }));
57 macho_step.dependOn(testWeakLibrary(b, .{ .target = default_target }));
58
59 // Tests requiring presence of macOS SDK in system path
60 if (build_opts.has_macos_sdk) {
61 macho_step.dependOn(testDeadStripDylibs(b, .{ .target = b.host }));
62 macho_step.dependOn(testHeaderpad(b, .{ .target = b.host }));
63 macho_step.dependOn(testLinkDirectlyCppTbd(b, .{ .target = b.host }));
64 macho_step.dependOn(testNeededFramework(b, .{ .target = b.host }));
65 macho_step.dependOn(testObjc(b, .{ .target = b.host }));
66 macho_step.dependOn(testObjcpp(b, .{ .target = b.host }));
67 macho_step.dependOn(testWeakFramework(b, .{ .target = b.host }));
68 }
69 }
70
71 return macho_step;
72}
73
74fn testDeadStrip(b: *Build, opts: Options) *Step {
75 const test_step = addTestStep(b, "macho-dead-strip", opts);
76
77 const obj = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
78 \\#include <stdio.h>
79 \\int two() { return 2; }
80 \\int live_var1 = 1;
81 \\int live_var2 = two();
82 \\int dead_var1 = 3;
83 \\int dead_var2 = 4;
84 \\void live_fn1() {}
85 \\void live_fn2() { live_fn1(); }
86 \\void dead_fn1() {}
87 \\void dead_fn2() { dead_fn1(); }
88 \\int main() {
89 \\ printf("%d %d\n", live_var1, live_var2);
90 \\ live_fn2();
91 \\}
92 });
93
94 {
95 const exe = addExecutable(b, opts, .{ .name = "no_dead_strip" });
96 exe.addObject(obj);
97 exe.link_gc_sections = false;
98
99 const check = exe.checkObject();
100 check.checkInSymtab();
101 check.checkContains("live_var1");
102 check.checkInSymtab();
103 check.checkContains("live_var2");
104 check.checkInSymtab();
105 check.checkContains("dead_var1");
106 check.checkInSymtab();
107 check.checkContains("dead_var2");
108 check.checkInSymtab();
109 check.checkContains("live_fn1");
110 check.checkInSymtab();
111 check.checkContains("live_fn2");
112 check.checkInSymtab();
113 check.checkContains("dead_fn1");
114 check.checkInSymtab();
115 check.checkContains("dead_fn2");
116 test_step.dependOn(&check.step);
117
118 const run = addRunArtifact(exe);
119 run.expectStdOutEqual("1 2\n");
120 test_step.dependOn(&run.step);
121 }
122
123 {
124 const exe = addExecutable(b, opts, .{ .name = "yes_dead_strip" });
125 exe.addObject(obj);
126 exe.link_gc_sections = true;
127
128 const check = exe.checkObject();
129 check.checkInSymtab();
130 check.checkContains("live_var1");
131 check.checkInSymtab();
132 check.checkContains("live_var2");
133 check.checkInSymtab();
134 check.checkNotPresent("dead_var1");
135 check.checkInSymtab();
136 check.checkNotPresent("dead_var2");
137 check.checkInSymtab();
138 check.checkContains("live_fn1");
139 check.checkInSymtab();
140 check.checkContains("live_fn2");
141 check.checkInSymtab();
142 check.checkNotPresent("dead_fn1");
143 check.checkInSymtab();
144 check.checkNotPresent("dead_fn2");
145 test_step.dependOn(&check.step);
146
147 const run = addRunArtifact(exe);
148 run.expectStdOutEqual("1 2\n");
149 test_step.dependOn(&run.step);
150 }
151
152 return test_step;
153}
154
155fn testDeadStripDylibs(b: *Build, opts: Options) *Step {
156 const test_step = addTestStep(b, "macho-dead-strip-dylibs", opts);
157
158 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
159 \\#include <objc/runtime.h>
160 \\int main() {
161 \\ if (objc_getClass("NSObject") == 0) {
162 \\ return -1;
163 \\ }
164 \\ if (objc_getClass("NSApplication") == 0) {
165 \\ return -2;
166 \\ }
167 \\ return 0;
168 \\}
169 });
170
171 {
172 const exe = addExecutable(b, opts, .{ .name = "main1" });
173 exe.addObject(main_o);
174 exe.root_module.linkFramework("Cocoa", .{});
175
176 const check = exe.checkObject();
177 check.checkInHeaders();
178 check.checkExact("cmd LOAD_DYLIB");
179 check.checkContains("Cocoa");
180 check.checkInHeaders();
181 check.checkExact("cmd LOAD_DYLIB");
182 check.checkContains("libobjc");
183 test_step.dependOn(&check.step);
184
185 const run = addRunArtifact(exe);
186 run.expectExitCode(0);
187 test_step.dependOn(&run.step);
188 }
189
190 {
191 const exe = addExecutable(b, opts, .{ .name = "main2" });
192 exe.addObject(main_o);
193 exe.root_module.linkFramework("Cocoa", .{});
194 exe.dead_strip_dylibs = true;
195
196 const run = addRunArtifact(exe);
197 run.expectExitCode(@as(u8, @bitCast(@as(i8, -2))));
198 test_step.dependOn(&run.step);
199 }
200
201 return test_step;
202}
203
204fn testDylib(b: *Build, opts: Options) *Step {
205 const test_step = addTestStep(b, "macho-dylib", opts);
206
207 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
208 \\#include<stdio.h>
209 \\char world[] = "world";
210 \\char* hello() {
211 \\ return "Hello";
212 \\}
213 });
214
215 const check = dylib.checkObject();
216 check.checkInHeaders();
217 check.checkExact("header");
218 check.checkNotPresent("PIE");
219 test_step.dependOn(&check.step);
220
221 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
222 \\#include<stdio.h>
223 \\char* hello();
224 \\extern char world[];
225 \\int main() {
226 \\ printf("%s %s", hello(), world);
227 \\ return 0;
228 \\}
229 });
230 exe.root_module.linkSystemLibrary("a", .{});
231 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
232 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
233
234 const run = addRunArtifact(exe);
235 run.expectStdOutEqual("Hello world");
236 test_step.dependOn(&run.step);
237
238 return test_step;
239}
240
241fn testEmptyObject(b: *Build, opts: Options) *Step {
242 const test_step = addTestStep(b, "macho-empty-object", opts);
243
244 const empty = addObject(b, opts, .{ .name = "empty", .c_source_bytes = "" });
245
246 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
247 \\#include <stdio.h>
248 \\int main() {
249 \\ printf("Hello world!");
250 \\}
251 });
252 exe.addObject(empty);
253
254 const run = addRunArtifact(exe);
255 run.expectStdOutEqual("Hello world!");
256 test_step.dependOn(&run.step);
257
258 return test_step;
259}
260
261fn testEmptyZig(b: *Build, opts: Options) *Step {
262 const test_step = addTestStep(b, "macho-empty-zig", opts);
263
264 const exe = addExecutable(b, opts, .{ .name = "empty", .zig_source_bytes = "pub fn main() void {}" });
265
266 const run = addRunArtifact(exe);
267 run.expectExitCode(0);
268 test_step.dependOn(&run.step);
269
270 return test_step;
271}
272
273fn testEntryPoint(b: *Build, opts: Options) *Step {
274 const test_step = addTestStep(b, "macho-entry-point", opts);
275
276 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
277 \\#include<stdio.h>
278 \\int non_main() {
279 \\ printf("%d", 42);
280 \\ return 0;
281 \\}
282 });
283 exe.entry = .{ .symbol_name = "_non_main" };
284
285 const run = addRunArtifact(exe);
286 run.expectStdOutEqual("42");
287 test_step.dependOn(&run.step);
288
289 const check = exe.checkObject();
290 check.checkInHeaders();
291 check.checkExact("segname __TEXT");
292 check.checkExtract("vmaddr {vmaddr}");
293 check.checkInHeaders();
294 check.checkExact("cmd MAIN");
295 check.checkExtract("entryoff {entryoff}");
296 check.checkInSymtab();
297 check.checkExtract("{n_value} (__TEXT,__text) external _non_main");
298 check.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
299 test_step.dependOn(&check.step);
300
301 return test_step;
302}
303
304fn testEntryPointArchive(b: *Build, opts: Options) *Step {
305 const test_step = addTestStep(b, "macho-entry-point-archive", opts);
306
307 const lib = addStaticLibrary(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
308
309 {
310 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
311 exe.root_module.linkSystemLibrary("main", .{});
312 exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
313
314 const run = addRunArtifact(exe);
315 run.expectExitCode(0);
316 test_step.dependOn(&run.step);
317 }
318
319 {
320 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
321 exe.root_module.linkSystemLibrary("main", .{});
322 exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
323 exe.link_gc_sections = true;
324
325 const run = addRunArtifact(exe);
326 run.expectExitCode(0);
327 test_step.dependOn(&run.step);
328 }
329
330 return test_step;
331}
332
333fn testEntryPointDylib(b: *Build, opts: Options) *Step {
334 const test_step = addTestStep(b, "macho-entry-point-dylib", opts);
335
336 const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
337 addCSourceBytes(dylib,
338 \\extern int my_main();
339 \\int bootstrap() {
340 \\ return my_main();
341 \\}
342 , &.{});
343 dylib.linker_allow_shlib_undefined = true;
344
345 const exe = addExecutable(b, opts, .{ .name = "main" });
346 addCSourceBytes(dylib,
347 \\#include<stdio.h>
348 \\int my_main() {
349 \\ fprintf(stdout, "Hello!\n");
350 \\ return 0;
351 \\}
352 , &.{});
353 exe.linkLibrary(dylib);
354 exe.entry = .{ .symbol_name = "_bootstrap" };
355 exe.forceUndefinedSymbol("_my_main");
356
357 const check = exe.checkObject();
358 check.checkInHeaders();
359 check.checkExact("segname __TEXT");
360 check.checkExtract("vmaddr {text_vmaddr}");
361 check.checkInHeaders();
362 check.checkExact("sectname __stubs");
363 check.checkExtract("addr {stubs_vmaddr}");
364 check.checkInHeaders();
365 check.checkExact("sectname __stubs");
366 check.checkExtract("size {stubs_vmsize}");
367 check.checkInHeaders();
368 check.checkExact("cmd MAIN");
369 check.checkExtract("entryoff {entryoff}");
370 check.checkComputeCompare("text_vmaddr entryoff +", .{
371 .op = .gte,
372 .value = .{ .variable = "stubs_vmaddr" }, // The entrypoint should be a synthetic stub
373 });
374 check.checkComputeCompare("text_vmaddr entryoff + stubs_vmaddr -", .{
375 .op = .lt,
376 .value = .{ .variable = "stubs_vmsize" }, // The entrypoint should be a synthetic stub
377 });
378 test_step.dependOn(&check.step);
379
380 const run = addRunArtifact(exe);
381 run.expectStdOutEqual("Hello!\n");
382 test_step.dependOn(&run.step);
383
384 return test_step;
385}
386
387fn testHeaderpad(b: *Build, opts: Options) *Step {
388 const test_step = addTestStep(b, "macho-headerpad", opts);
389
390 const addExe = struct {
391 fn addExe(bb: *Build, o: Options, name: []const u8) *Compile {
392 const exe = addExecutable(bb, o, .{
393 .name = name,
394 .c_source_bytes = "int main() { return 0; }",
395 });
396 exe.root_module.linkFramework("CoreFoundation", .{});
397 exe.root_module.linkFramework("Foundation", .{});
398 exe.root_module.linkFramework("Cocoa", .{});
399 exe.root_module.linkFramework("CoreGraphics", .{});
400 exe.root_module.linkFramework("CoreHaptics", .{});
401 exe.root_module.linkFramework("CoreAudio", .{});
402 exe.root_module.linkFramework("AVFoundation", .{});
403 exe.root_module.linkFramework("CoreImage", .{});
404 exe.root_module.linkFramework("CoreLocation", .{});
405 exe.root_module.linkFramework("CoreML", .{});
406 exe.root_module.linkFramework("CoreVideo", .{});
407 exe.root_module.linkFramework("CoreText", .{});
408 exe.root_module.linkFramework("CryptoKit", .{});
409 exe.root_module.linkFramework("GameKit", .{});
410 exe.root_module.linkFramework("SwiftUI", .{});
411 exe.root_module.linkFramework("StoreKit", .{});
412 exe.root_module.linkFramework("SpriteKit", .{});
413 return exe;
414 }
415 }.addExe;
416
417 {
418 const exe = addExe(b, opts, "main1");
419 exe.headerpad_max_install_names = true;
420
421 const check = exe.checkObject();
422 check.checkInHeaders();
423 check.checkExact("sectname __text");
424 check.checkExtract("offset {offset}");
425 switch (opts.target.result.cpu.arch) {
426 .aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
427 .x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
428 else => unreachable,
429 }
430 test_step.dependOn(&check.step);
431
432 const run = addRunArtifact(exe);
433 run.expectExitCode(0);
434 test_step.dependOn(&run.step);
435 }
436
437 {
438 const exe = addExe(b, opts, "main2");
439 exe.headerpad_size = 0x10000;
440
441 const check = exe.checkObject();
442 check.checkInHeaders();
443 check.checkExact("sectname __text");
444 check.checkExtract("offset {offset}");
445 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
446 test_step.dependOn(&check.step);
447
448 const run = addRunArtifact(exe);
449 run.expectExitCode(0);
450 test_step.dependOn(&run.step);
451 }
452
453 {
454 const exe = addExe(b, opts, "main3");
455 exe.headerpad_max_install_names = true;
456 exe.headerpad_size = 0x10000;
457
458 const check = exe.checkObject();
459 check.checkInHeaders();
460 check.checkExact("sectname __text");
461 check.checkExtract("offset {offset}");
462 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
463 test_step.dependOn(&check.step);
464
465 const run = addRunArtifact(exe);
466 run.expectExitCode(0);
467 test_step.dependOn(&run.step);
468 }
469
470 {
471 const exe = addExe(b, opts, "main4");
472 exe.headerpad_max_install_names = true;
473 exe.headerpad_size = 0x1000;
474
475 const check = exe.checkObject();
476 check.checkInHeaders();
477 check.checkExact("sectname __text");
478 check.checkExtract("offset {offset}");
479 switch (opts.target.result.cpu.arch) {
480 .aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
481 .x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
482 else => unreachable,
483 }
484 test_step.dependOn(&check.step);
485
486 const run = addRunArtifact(exe);
487 run.expectExitCode(0);
488 test_step.dependOn(&run.step);
489 }
490
491 return test_step;
492}
493
494// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-header-flags.s
495fn testHeaderWeakFlags(b: *Build, opts: Options) *Step {
496 const test_step = addTestStep(b, "macho-header-weak-flags", opts);
497
498 const obj1 = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
499 \\.globl _x
500 \\.weak_definition _x
501 \\_x:
502 \\ ret
503 });
504
505 const lib = addSharedLibrary(b, opts, .{ .name = "a" });
506 lib.addObject(obj1);
507
508 {
509 const exe = addExecutable(b, opts, .{ .name = "main1", .c_source_bytes = "int main() { return 0; }" });
510 exe.addObject(obj1);
511
512 const check = exe.checkObject();
513 check.checkInHeaders();
514 check.checkExact("header");
515 check.checkContains("WEAK_DEFINES");
516 check.checkInHeaders();
517 check.checkExact("header");
518 check.checkContains("BINDS_TO_WEAK");
519 check.checkInExports();
520 check.checkExtract("[WEAK] {vmaddr} _x");
521 test_step.dependOn(&check.step);
522 }
523
524 {
525 const obj = addObject(b, opts, .{ .name = "b" });
526
527 switch (opts.target.result.cpu.arch) {
528 .aarch64 => addAsmSourceBytes(obj,
529 \\.globl _main
530 \\_main:
531 \\ bl _x
532 \\ ret
533 ),
534 .x86_64 => addAsmSourceBytes(obj,
535 \\.globl _main
536 \\_main:
537 \\ callq _x
538 \\ ret
539 ),
540 else => unreachable,
541 }
542
543 const exe = addExecutable(b, opts, .{ .name = "main2" });
544 exe.linkLibrary(lib);
545 exe.addObject(obj);
546
547 const check = exe.checkObject();
548 check.checkInHeaders();
549 check.checkExact("header");
550 check.checkNotPresent("WEAK_DEFINES");
551 check.checkInHeaders();
552 check.checkExact("header");
553 check.checkContains("BINDS_TO_WEAK");
554 check.checkInExports();
555 check.checkNotPresent("[WEAK] {vmaddr} _x");
556 test_step.dependOn(&check.step);
557 }
558
559 {
560 const exe = addExecutable(b, opts, .{ .name = "main3", .asm_source_bytes =
561 \\.globl _main, _x
562 \\_x:
563 \\
564 \\_main:
565 \\ ret
566 });
567 exe.linkLibrary(lib);
568
569 const check = exe.checkObject();
570 check.checkInHeaders();
571 check.checkExact("header");
572 check.checkNotPresent("WEAK_DEFINES");
573 check.checkInHeaders();
574 check.checkExact("header");
575 check.checkNotPresent("BINDS_TO_WEAK");
576 test_step.dependOn(&check.step);
577 }
578
579 return test_step;
580}
581
582fn testHelloC(b: *Build, opts: Options) *Step {
583 const test_step = addTestStep(b, "macho-hello-c", opts);
584
585 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
586 \\#include <stdio.h>
587 \\int main() {
588 \\ printf("Hello world!\n");
589 \\ return 0;
590 \\}
591 });
592
593 const run = addRunArtifact(exe);
594 run.expectStdOutEqual("Hello world!\n");
595 test_step.dependOn(&run.step);
596
597 const check = exe.checkObject();
598 check.checkInHeaders();
599 check.checkExact("header");
600 check.checkContains("PIE");
601 test_step.dependOn(&check.step);
602
603 return test_step;
604}
605
606fn testHelloZig(b: *Build, opts: Options) *Step {
607 const test_step = addTestStep(b, "macho-hello-zig", opts);
608
609 const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
610 \\const std = @import("std");
611 \\pub fn main() void {
612 \\ std.io.getStdOut().writer().print("Hello world!\n", .{}) catch unreachable;
613 \\}
614 });
615
616 const run = addRunArtifact(exe);
617 run.expectStdOutEqual("Hello world!\n");
618 test_step.dependOn(&run.step);
619
620 return test_step;
621}
622
623fn testLargeBss(b: *Build, opts: Options) *Step {
624 const test_step = addTestStep(b, "macho-large-bss", opts);
625
626 // TODO this test used use a 4GB zerofill section but this actually fails and causes every
627 // linker I tried misbehave in different ways. This only happened on arm64. I thought that
628 // maybe S_GB_ZEROFILL section is an answer to this but it doesn't seem supported by dyld
629 // anymore. When I get some free time I will re-investigate this.
630 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
631 \\char arr[0x1000000];
632 \\int main() {
633 \\ return arr[2000];
634 \\}
635 });
636
637 const run = addRunArtifact(exe);
638 run.expectExitCode(0);
639 test_step.dependOn(&run.step);
640
641 return test_step;
642}
643
644fn testLayout(b: *Build, opts: Options) *Step {
645 const test_step = addTestStep(b, "macho-layout", opts);
646
647 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
648 \\#include <stdio.h>
649 \\int main() {
650 \\ printf("Hello world!");
651 \\ return 0;
652 \\}
653 });
654
655 const check = exe.checkObject();
656 check.checkInHeaders();
657 check.checkExact("cmd SEGMENT_64");
658 check.checkExact("segname __LINKEDIT");
659 check.checkExtract("fileoff {fileoff}");
660 check.checkExtract("filesz {filesz}");
661 check.checkInHeaders();
662 check.checkExact("cmd DYLD_INFO_ONLY");
663 check.checkExtract("rebaseoff {rebaseoff}");
664 check.checkExtract("rebasesize {rebasesize}");
665 check.checkExtract("bindoff {bindoff}");
666 check.checkExtract("bindsize {bindsize}");
667 check.checkExtract("lazybindoff {lazybindoff}");
668 check.checkExtract("lazybindsize {lazybindsize}");
669 check.checkExtract("exportoff {exportoff}");
670 check.checkExtract("exportsize {exportsize}");
671 check.checkInHeaders();
672 check.checkExact("cmd FUNCTION_STARTS");
673 check.checkExtract("dataoff {fstartoff}");
674 check.checkExtract("datasize {fstartsize}");
675 check.checkInHeaders();
676 check.checkExact("cmd DATA_IN_CODE");
677 check.checkExtract("dataoff {diceoff}");
678 check.checkExtract("datasize {dicesize}");
679 check.checkInHeaders();
680 check.checkExact("cmd SYMTAB");
681 check.checkExtract("symoff {symoff}");
682 check.checkExtract("nsyms {symnsyms}");
683 check.checkExtract("stroff {stroff}");
684 check.checkExtract("strsize {strsize}");
685 check.checkInHeaders();
686 check.checkExact("cmd DYSYMTAB");
687 check.checkExtract("indirectsymoff {dysymoff}");
688 check.checkExtract("nindirectsyms {dysymnsyms}");
689
690 switch (opts.target.result.cpu.arch) {
691 .aarch64 => {
692 check.checkInHeaders();
693 check.checkExact("cmd CODE_SIGNATURE");
694 check.checkExtract("dataoff {codesigoff}");
695 check.checkExtract("datasize {codesigsize}");
696 },
697 .x86_64 => {},
698 else => unreachable,
699 }
700
701 // DYLD_INFO_ONLY subsections are in order: rebase < bind < lazy < export,
702 // and there are no gaps between them
703 check.checkComputeCompare("rebaseoff rebasesize +", .{ .op = .eq, .value = .{ .variable = "bindoff" } });
704 check.checkComputeCompare("bindoff bindsize +", .{ .op = .eq, .value = .{ .variable = "lazybindoff" } });
705 check.checkComputeCompare("lazybindoff lazybindsize +", .{ .op = .eq, .value = .{ .variable = "exportoff" } });
706
707 // FUNCTION_STARTS directly follows DYLD_INFO_ONLY (no gap)
708 check.checkComputeCompare("exportoff exportsize +", .{ .op = .eq, .value = .{ .variable = "fstartoff" } });
709
710 // DATA_IN_CODE directly follows FUNCTION_STARTS (no gap)
711 check.checkComputeCompare("fstartoff fstartsize +", .{ .op = .eq, .value = .{ .variable = "diceoff" } });
712
713 // SYMTAB directly follows DATA_IN_CODE (no gap)
714 check.checkComputeCompare("diceoff dicesize +", .{ .op = .eq, .value = .{ .variable = "symoff" } });
715
716 // DYSYMTAB directly follows SYMTAB (no gap)
717 check.checkComputeCompare("symnsyms 16 symoff * +", .{ .op = .eq, .value = .{ .variable = "dysymoff" } });
718
719 // STRTAB follows DYSYMTAB with possible gap
720 check.checkComputeCompare("dysymnsyms 4 dysymoff * +", .{ .op = .lte, .value = .{ .variable = "stroff" } });
721
722 // all LINKEDIT sections apart from CODE_SIGNATURE are 8-bytes aligned
723 check.checkComputeCompare("rebaseoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
724 check.checkComputeCompare("bindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
725 check.checkComputeCompare("lazybindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
726 check.checkComputeCompare("exportoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
727 check.checkComputeCompare("fstartoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
728 check.checkComputeCompare("diceoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
729 check.checkComputeCompare("symoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
730 check.checkComputeCompare("stroff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
731 check.checkComputeCompare("dysymoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
732
733 switch (opts.target.result.cpu.arch) {
734 .aarch64 => {
735 // LINKEDIT segment does not extend beyond, or does not include, CODE_SIGNATURE data
736 check.checkComputeCompare("fileoff filesz codesigoff codesigsize + - -", .{
737 .op = .eq,
738 .value = .{ .literal = 0 },
739 });
740
741 // CODE_SIGNATURE data offset is 16-bytes aligned
742 check.checkComputeCompare("codesigoff 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
743 },
744 .x86_64 => {
745 // LINKEDIT segment does not extend beyond, or does not include, strtab data
746 check.checkComputeCompare("fileoff filesz stroff strsize + - -", .{
747 .op = .eq,
748 .value = .{ .literal = 0 },
749 });
750 },
751 else => unreachable,
752 }
753
754 test_step.dependOn(&check.step);
755
756 const run = addRunArtifact(exe);
757 run.expectStdOutEqual("Hello world!");
758 test_step.dependOn(&run.step);
759
760 return test_step;
761}
762
763fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
764 const test_step = addTestStep(b, "macho-link-directly-cpp-tbd", opts);
765
766 const sdk = std.zig.system.darwin.getSdk(b.allocator, opts.target.result) orelse
767 @panic("macOS SDK is required to run the test");
768
769 const exe = addExecutable(b, opts, .{
770 .name = "main",
771 .cpp_source_bytes =
772 \\#include <new>
773 \\#include <cstdio>
774 \\int main() {
775 \\ int *x = new int;
776 \\ *x = 5;
777 \\ fprintf(stderr, "x: %d\n", *x);
778 \\ delete x;
779 \\}
780 ,
781 .cpp_source_flags = &.{ "-nostdlib++", "-nostdinc++" },
782 });
783 exe.root_module.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
784 exe.root_module.addIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
785 exe.root_module.addObjectFile(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
786
787 const check = exe.checkObject();
788 check.checkInSymtab();
789 check.checkContains("[referenced dynamically] external __mh_execute_header");
790 test_step.dependOn(&check.step);
791
792 return test_step;
793}
794
795fn testLinksection(b: *Build, opts: Options) *Step {
796 const test_step = addTestStep(b, "macho-linksection", opts);
797
798 const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
799 \\export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
800 \\export fn testFn() linksection("__TEXT,__TestFn") callconv(.C) void {
801 \\ testGenericFn("A");
802 \\}
803 \\fn testGenericFn(comptime suffix: []const u8) linksection("__TEXT,__TestGenFn" ++ suffix) void {}
804 });
805
806 const check = obj.checkObject();
807 check.checkInSymtab();
808 check.checkContains("(__DATA,__TestGlobal) external _test_global");
809 check.checkInSymtab();
810 check.checkContains("(__TEXT,__TestFn) external _testFn");
811
812 if (opts.optimize == .Debug) {
813 check.checkInSymtab();
814 check.checkContains("(__TEXT,__TestGenFnA) _a.testGenericFn__anon_");
815 }
816
817 test_step.dependOn(&check.step);
818
819 return test_step;
820}
821
822fn testMhExecuteHeader(b: *Build, opts: Options) *Step {
823 const test_step = addTestStep(b, "macho-mh-execute-header", opts);
824
825 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
826
827 const check = exe.checkObject();
828 check.checkInSymtab();
829 check.checkContains("[referenced dynamically] external __mh_execute_header");
830 test_step.dependOn(&check.step);
831
832 return test_step;
833}
834
835fn testNoDeadStrip(b: *Build, opts: Options) *Step {
836 const test_step = addTestStep(b, "macho-no-dead-strip", opts);
837
838 const exe = addExecutable(b, opts, .{ .name = "name", .c_source_bytes =
839 \\__attribute__((used)) int bogus1 = 0;
840 \\int bogus2 = 0;
841 \\int foo = 42;
842 \\int main() {
843 \\ return foo - 42;
844 \\}
845 });
846 exe.link_gc_sections = true;
847
848 const check = exe.checkObject();
849 check.checkInSymtab();
850 check.checkContains("external _bogus1");
851 check.checkInSymtab();
852 check.checkNotPresent("external _bogus2");
853 test_step.dependOn(&check.step);
854
855 const run = addRunArtifact(exe);
856 run.expectExitCode(0);
857 test_step.dependOn(&run.step);
858
859 return test_step;
860}
861
862fn testNoExportsDylib(b: *Build, opts: Options) *Step {
863 const test_step = addTestStep(b, "macho-no-exports-dylib", opts);
864
865 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "static void abc() {}" });
866
867 const check = dylib.checkObject();
868 check.checkInSymtab();
869 check.checkNotPresent("external _abc");
870 test_step.dependOn(&check.step);
871
872 return test_step;
873}
874
875fn testNeededFramework(b: *Build, opts: Options) *Step {
876 const test_step = addTestStep(b, "macho-needed-framework", opts);
877
878 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
879 exe.root_module.linkFramework("Cocoa", .{ .needed = true });
880 exe.dead_strip_dylibs = true;
881
882 const check = exe.checkObject();
883 check.checkInHeaders();
884 check.checkExact("cmd LOAD_DYLIB");
885 check.checkContains("Cocoa");
886 test_step.dependOn(&check.step);
887
888 const run = addRunArtifact(exe);
889 run.expectExitCode(0);
890 test_step.dependOn(&run.step);
891
892 return test_step;
893}
894
895fn testNeededLibrary(b: *Build, opts: Options) *Step {
896 const test_step = addTestStep(b, "macho-needed-library", opts);
897
898 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int a = 42;" });
899
900 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
901 exe.root_module.linkSystemLibrary("a", .{ .needed = true });
902 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
903 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
904 exe.dead_strip_dylibs = true;
905
906 const check = exe.checkObject();
907 check.checkInHeaders();
908 check.checkExact("cmd LOAD_DYLIB");
909 check.checkContains("liba.dylib");
910 test_step.dependOn(&check.step);
911
912 const run = addRunArtifact(exe);
913 run.expectExitCode(0);
914 test_step.dependOn(&run.step);
915
916 return test_step;
917}
918
919fn testObjc(b: *Build, opts: Options) *Step {
920 const test_step = addTestStep(b, "macho-objc", opts);
921
922 const lib = addStaticLibrary(b, opts, .{ .name = "a", .objc_source_bytes =
923 \\#import <Foundation/Foundation.h>
924 \\@interface Foo : NSObject
925 \\@end
926 \\@implementation Foo
927 \\@end
928 });
929
930 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
931 exe.root_module.linkSystemLibrary("a", .{});
932 exe.root_module.linkFramework("Foundation", .{});
933 exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
934
935 const check = exe.checkObject();
936 check.checkInSymtab();
937 check.checkContains("_OBJC_");
938 test_step.dependOn(&check.step);
939
940 const run = addRunArtifact(exe);
941 run.expectExitCode(0);
942 test_step.dependOn(&run.step);
943
944 return test_step;
945}
946
947fn testObjcpp(b: *Build, opts: Options) *Step {
948 const test_step = addTestStep(b, "macho-objcpp", opts);
949
950 const foo_h = foo_h: {
951 const wf = WriteFile.create(b);
952 break :foo_h wf.add("Foo.h",
953 \\#import <Foundation/Foundation.h>
954 \\@interface Foo : NSObject
955 \\- (NSString *)name;
956 \\@end
957 );
958 };
959
960 const foo_o = addObject(b, opts, .{ .name = "foo", .objcpp_source_bytes =
961 \\#import "Foo.h"
962 \\@implementation Foo
963 \\- (NSString *)name
964 \\{
965 \\ NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
966 \\ return str;
967 \\}
968 \\@end
969 });
970 foo_o.root_module.addIncludePath(foo_h.dirname());
971 foo_o.linkLibCpp();
972
973 const exe = addExecutable(b, opts, .{ .name = "main", .objcpp_source_bytes =
974 \\#import "Foo.h"
975 \\#import <assert.h>
976 \\#include <iostream>
977 \\int main(int argc, char *argv[])
978 \\{
979 \\ @autoreleasepool {
980 \\ Foo *foo = [[Foo alloc] init];
981 \\ NSString *result = [foo name];
982 \\ std::cout << "Hello from C++ and " << [result UTF8String];
983 \\ assert([result isEqualToString:@"Zig"]);
984 \\ return 0;
985 \\ }
986 \\}
987 });
988 exe.root_module.addIncludePath(foo_h.dirname());
989 exe.addObject(foo_o);
990 exe.linkLibCpp();
991 exe.root_module.linkFramework("Foundation", .{});
992
993 const run = addRunArtifact(exe);
994 run.expectStdOutEqual("Hello from C++ and Zig");
995 test_step.dependOn(&run.step);
996
997 return test_step;
998}
999
1000fn testPagezeroSize(b: *Build, opts: Options) *Step {
1001 const test_step = addTestStep(b, "macho-pagezero-size", opts);
1002
1003 {
1004 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
1005 exe.pagezero_size = 0x4000;
1006
1007 const check = exe.checkObject();
1008 check.checkInHeaders();
1009 check.checkExact("LC 0");
1010 check.checkExact("segname __PAGEZERO");
1011 check.checkExact("vmaddr 0");
1012 check.checkExact("vmsize 4000");
1013 check.checkInHeaders();
1014 check.checkExact("segname __TEXT");
1015 check.checkExact("vmaddr 4000");
1016 test_step.dependOn(&check.step);
1017 }
1018
1019 {
1020 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
1021 exe.pagezero_size = 0;
1022
1023 const check = exe.checkObject();
1024 check.checkInHeaders();
1025 check.checkExact("LC 0");
1026 check.checkExact("segname __TEXT");
1027 check.checkExact("vmaddr 0");
1028 test_step.dependOn(&check.step);
1029 }
1030
1031 return test_step;
1032}
1033
1034fn testReexportsZig(b: *Build, opts: Options) *Step {
1035 const test_step = addTestStep(b, "macho-reexports-zig", opts);
1036
1037 const lib = addStaticLibrary(b, opts, .{ .name = "a", .zig_source_bytes =
1038 \\const x: i32 = 42;
1039 \\export fn foo() i32 {
1040 \\ return x;
1041 \\}
1042 \\comptime {
1043 \\ @export(foo, .{ .name = "bar", .linkage = .Strong });
1044 \\}
1045 });
1046
1047 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1048 \\extern int foo();
1049 \\extern int bar();
1050 \\int main() {
1051 \\ return bar() - foo();
1052 \\}
1053 });
1054 exe.linkLibrary(lib);
1055
1056 const run = addRunArtifact(exe);
1057 run.expectExitCode(0);
1058 test_step.dependOn(&run.step);
1059
1060 return test_step;
1061}
1062
1063fn testRelocatable(b: *Build, opts: Options) *Step {
1064 const test_step = addTestStep(b, "macho-relocatable", opts);
1065
1066 const a_o = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
1067 \\#include <stdexcept>
1068 \\int try_me() {
1069 \\ throw std::runtime_error("Oh no!");
1070 \\}
1071 });
1072 a_o.linkLibCpp();
1073
1074 const b_o = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
1075 \\extern int try_me();
1076 \\int try_again() {
1077 \\ return try_me();
1078 \\}
1079 });
1080
1081 const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
1082 \\#include <iostream>
1083 \\#include <stdexcept>
1084 \\extern int try_again();
1085 \\int main() {
1086 \\ try {
1087 \\ try_again();
1088 \\ } catch (const std::exception &e) {
1089 \\ std::cout << "exception=" << e.what();
1090 \\ }
1091 \\ return 0;
1092 \\}
1093 });
1094 main_o.linkLibCpp();
1095
1096 const exp_stdout = "exception=Oh no!";
1097
1098 {
1099 const c_o = addObject(b, opts, .{ .name = "c" });
1100 c_o.addObject(a_o);
1101 c_o.addObject(b_o);
1102
1103 const exe = addExecutable(b, opts, .{ .name = "main1" });
1104 exe.addObject(main_o);
1105 exe.addObject(c_o);
1106 exe.linkLibCpp();
1107
1108 const run = addRunArtifact(exe);
1109 run.expectStdOutEqual(exp_stdout);
1110 test_step.dependOn(&run.step);
1111 }
1112
1113 {
1114 const d_o = addObject(b, opts, .{ .name = "d" });
1115 d_o.addObject(a_o);
1116 d_o.addObject(b_o);
1117 d_o.addObject(main_o);
1118
1119 const exe = addExecutable(b, opts, .{ .name = "main2" });
1120 exe.addObject(d_o);
1121 exe.linkLibCpp();
1122
1123 const run = addRunArtifact(exe);
1124 run.expectStdOutEqual(exp_stdout);
1125 test_step.dependOn(&run.step);
1126 }
1127
1128 return test_step;
1129}
1130
1131fn testRelocatableZig(b: *Build, opts: Options) *Step {
1132 const test_step = addTestStep(b, "macho-relocatable-zig", opts);
1133
1134 const a_o = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
1135 \\const std = @import("std");
1136 \\export var foo: i32 = 0;
1137 \\export fn incrFoo() void {
1138 \\ foo += 1;
1139 \\ std.debug.print("incrFoo={d}\n", .{foo});
1140 \\}
1141 });
1142
1143 const b_o = addObject(b, opts, .{ .name = "b", .zig_source_bytes =
1144 \\const std = @import("std");
1145 \\extern var foo: i32;
1146 \\export fn decrFoo() void {
1147 \\ foo -= 1;
1148 \\ std.debug.print("decrFoo={d}\n", .{foo});
1149 \\}
1150 });
1151
1152 const main_o = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
1153 \\const std = @import("std");
1154 \\extern var foo: i32;
1155 \\extern fn incrFoo() void;
1156 \\extern fn decrFoo() void;
1157 \\pub fn main() void {
1158 \\ const init = foo;
1159 \\ incrFoo();
1160 \\ decrFoo();
1161 \\ if (init == foo) @panic("Oh no!");
1162 \\}
1163 });
1164
1165 const c_o = addObject(b, opts, .{ .name = "c" });
1166 c_o.addObject(a_o);
1167 c_o.addObject(b_o);
1168 c_o.addObject(main_o);
1169
1170 const exe = addExecutable(b, opts, .{ .name = "main" });
1171 exe.addObject(c_o);
1172
1173 const run = addRunArtifact(exe);
1174 run.addCheck(.{ .expect_stderr_match = b.dupe("incrFoo=1") });
1175 run.addCheck(.{ .expect_stderr_match = b.dupe("decrFoo=0") });
1176 run.addCheck(.{ .expect_stderr_match = b.dupe("panic: Oh no!") });
1177 test_step.dependOn(&run.step);
1178
1179 return test_step;
1180}
1181
1182fn testSearchStrategy(b: *Build, opts: Options) *Step {
1183 const test_step = addTestStep(b, "macho-search-strategy", opts);
1184
1185 const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes =
1186 \\#include<stdio.h>
1187 \\char world[] = "world";
1188 \\char* hello() {
1189 \\ return "Hello";
1190 \\}
1191 });
1192
1193 const liba = addStaticLibrary(b, opts, .{ .name = "a" });
1194 liba.addObject(obj);
1195
1196 const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
1197 dylib.addObject(obj);
1198
1199 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
1200 \\#include<stdio.h>
1201 \\char* hello();
1202 \\extern char world[];
1203 \\int main() {
1204 \\ printf("%s %s", hello(), world);
1205 \\ return 0;
1206 \\}
1207 });
1208
1209 {
1210 const exe = addExecutable(b, opts, .{ .name = "main" });
1211 exe.addObject(main_o);
1212 exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .mode_first });
1213 exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
1214 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
1215 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
1216
1217 const run = addRunArtifact(exe);
1218 run.expectStdOutEqual("Hello world");
1219 test_step.dependOn(&run.step);
1220
1221 const check = exe.checkObject();
1222 check.checkInHeaders();
1223 check.checkExact("cmd LOAD_DYLIB");
1224 check.checkContains("liba.dylib");
1225 test_step.dependOn(&check.step);
1226 }
1227
1228 {
1229 const exe = addExecutable(b, opts, .{ .name = "main" });
1230 exe.addObject(main_o);
1231 exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .paths_first });
1232 exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
1233 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
1234 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
1235
1236 const run = addRunArtifact(exe);
1237 run.expectStdOutEqual("Hello world");
1238 test_step.dependOn(&run.step);
1239
1240 const check = exe.checkObject();
1241 check.checkInHeaders();
1242 check.checkExact("cmd LOAD_DYLIB");
1243 check.checkNotPresent("liba.dylib");
1244 test_step.dependOn(&check.step);
1245 }
101246
11 return macho_step;
1247 return test_step;
121248}
131249
14fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step {
15 const test_step = addTestStep(b, "macho-resolving-boundary-symbols", opts);
1250fn testSectionBoundarySymbols(b: *Build, opts: Options) *Step {
1251 const test_step = addTestStep(b, "macho-section-boundary-symbols", opts);
161252
171253 const obj1 = addObject(b, opts, .{
181254 .name = "obj1",
......@@ -25,10 +1261,10 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step {
251261 .name = "main",
261262 .zig_source_bytes =
271263 \\const std = @import("std");
28 \\extern fn interop() [*:0]const u8;
1264 \\extern fn interop() ?[*:0]const u8;
291265 \\pub fn main() !void {
301266 \\ std.debug.print("All your {s} are belong to us.\n", .{
31 \\ std.mem.span(interop()),
1267 \\ if (interop()) |ptr| std.mem.span(ptr) else "(null)",
321268 \\ });
331269 \\}
341270 ,
......@@ -57,7 +1293,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step {
571293
581294 const check = exe.checkObject();
591295 check.checkInSymtab();
60 check.checkNotPresent("section$start$__DATA_CONST$__message_ptr");
1296 check.checkNotPresent("external section$start$__DATA_CONST$__message_ptr");
611297 test_step.dependOn(&check.step);
621298 }
631299
......@@ -65,7 +1301,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step {
651301 const obj3 = addObject(b, opts, .{
661302 .name = "obj3",
671303 .cpp_source_bytes =
68 \\extern const char* message_pointer __asm("section$start$__DATA$__message_ptr");
1304 \\extern const char* message_pointer __asm("section$start$__DATA_CONST$__not_present");
691305 \\extern "C" const char* interop() {
701306 \\ return message_pointer;
711307 \\}
......@@ -77,23 +1313,934 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step {
771313 exe.addObject(obj3);
781314 exe.addObject(main_o);
791315
80 expectLinkErrors(exe, test_step, .{ .exact = &.{
81 "section not found: __DATA,__message_ptr",
82 "note: while resolving section$start$__DATA$__message_ptr",
83 } });
1316 const run = b.addRunArtifact(exe);
1317 run.skip_foreign_checks = true;
1318 run.expectStdErrEqual("All your (null) are belong to us.\n");
1319 test_step.dependOn(&run.step);
1320
1321 const check = exe.checkObject();
1322 check.checkInSymtab();
1323 check.checkNotPresent("external section$start$__DATA_CONST$__not_present");
1324 test_step.dependOn(&check.step);
1325 }
1326
1327 return test_step;
1328}
1329
1330fn testSegmentBoundarySymbols(b: *Build, opts: Options) *Step {
1331 const test_step = addTestStep(b, "macho-segment-boundary-symbols", opts);
1332
1333 const obj1 = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
1334 \\constexpr const char* MESSAGE __attribute__((used, section("__DATA_CONST_1,__message_ptr"))) = "codebase";
1335 });
1336
1337 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
1338 \\#include <stdio.h>
1339 \\const char* interop();
1340 \\int main() {
1341 \\ printf("All your %s are belong to us.\n", interop());
1342 \\ return 0;
1343 \\}
1344 });
1345
1346 {
1347 const obj2 = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
1348 \\extern const char* message_pointer __asm("segment$start$__DATA_CONST_1");
1349 \\extern "C" const char* interop() {
1350 \\ return message_pointer;
1351 \\}
1352 });
1353
1354 const exe = addExecutable(b, opts, .{ .name = "main" });
1355 exe.addObject(obj1);
1356 exe.addObject(obj2);
1357 exe.addObject(main_o);
1358
1359 const run = addRunArtifact(exe);
1360 run.expectStdOutEqual("All your codebase are belong to us.\n");
1361 test_step.dependOn(&run.step);
1362
1363 const check = exe.checkObject();
1364 check.checkInSymtab();
1365 check.checkNotPresent("external segment$start$__DATA_CONST_1");
1366 test_step.dependOn(&check.step);
1367 }
1368
1369 {
1370 const obj2 = addObject(b, opts, .{ .name = "c", .cpp_source_bytes =
1371 \\extern const char* message_pointer __asm("segment$start$__DATA_1");
1372 \\extern "C" const char* interop() {
1373 \\ return message_pointer;
1374 \\}
1375 });
1376
1377 const exe = addExecutable(b, opts, .{ .name = "main2" });
1378 exe.addObject(obj1);
1379 exe.addObject(obj2);
1380 exe.addObject(main_o);
1381
1382 const check = exe.checkObject();
1383 check.checkInHeaders();
1384 check.checkExact("cmd SEGMENT_64");
1385 check.checkExact("segname __DATA_1");
1386 check.checkExtract("vmsize {vmsize}");
1387 check.checkExtract("filesz {filesz}");
1388 check.checkComputeCompare("vmsize", .{ .op = .eq, .value = .{ .literal = 0 } });
1389 check.checkComputeCompare("filesz", .{ .op = .eq, .value = .{ .literal = 0 } });
1390 check.checkInSymtab();
1391 check.checkNotPresent("external segment$start$__DATA_1");
1392 test_step.dependOn(&check.step);
1393 }
1394
1395 return test_step;
1396}
1397
1398fn testStackSize(b: *Build, opts: Options) *Step {
1399 const test_step = addTestStep(b, "macho-stack-size", opts);
1400
1401 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
1402 exe.stack_size = 0x100000000;
1403
1404 const run = addRunArtifact(exe);
1405 run.expectExitCode(0);
1406 test_step.dependOn(&run.step);
1407
1408 const check = exe.checkObject();
1409 check.checkInHeaders();
1410 check.checkExact("cmd MAIN");
1411 check.checkExact("stacksize 100000000");
1412 test_step.dependOn(&check.step);
1413
1414 return test_step;
1415}
1416
1417fn testTbdv3(b: *Build, opts: Options) *Step {
1418 const test_step = addTestStep(b, "macho-tbdv3", opts);
1419
1420 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int getFoo() { return 42; }" });
1421
1422 const tbd = tbd: {
1423 const wf = WriteFile.create(b);
1424 break :tbd wf.add("liba.tbd",
1425 \\--- !tapi-tbd-v3
1426 \\archs: [ arm64, x86_64 ]
1427 \\uuids: [ 'arm64: DEADBEEF', 'x86_64: BEEFDEAD' ]
1428 \\platform: macos
1429 \\install-name: @rpath/liba.dylib
1430 \\current-version: 0
1431 \\exports:
1432 \\ - archs: [ arm64, x86_64 ]
1433 \\ symbols: [ _getFoo ]
1434 );
1435 };
1436
1437 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1438 \\#include <stdio.h>
1439 \\int getFoo();
1440 \\int main() {
1441 \\ return getFoo() - 42;
1442 \\}
1443 });
1444 exe.root_module.linkSystemLibrary("a", .{});
1445 exe.root_module.addLibraryPath(tbd.dirname());
1446 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
1447
1448 const run = addRunArtifact(exe);
1449 run.expectExitCode(0);
1450 test_step.dependOn(&run.step);
1451
1452 return test_step;
1453}
1454
1455fn testTentative(b: *Build, opts: Options) *Step {
1456 const test_step = addTestStep(b, "macho-tentative", opts);
1457
1458 const exe = addExecutable(b, opts, .{ .name = "main" });
1459 addCSourceBytes(exe,
1460 \\int foo;
1461 \\int bar;
1462 \\int baz = 42;
1463 , &.{"-fcommon"});
1464 addCSourceBytes(exe,
1465 \\#include<stdio.h>
1466 \\int foo;
1467 \\int bar = 5;
1468 \\int baz;
1469 \\int main() {
1470 \\ printf("%d %d %d\n", foo, bar, baz);
1471 \\}
1472 , &.{"-fcommon"});
1473
1474 const run = addRunArtifact(exe);
1475 run.expectStdOutEqual("0 5 42\n");
1476 test_step.dependOn(&run.step);
1477
1478 return test_step;
1479}
1480
1481fn testThunks(b: *Build, opts: Options) *Step {
1482 const test_step = addTestStep(b, "macho-thunks", opts);
1483
1484 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1485 \\#include <stdio.h>
1486 \\__attribute__((aligned(0x8000000))) int bar() {
1487 \\ return 42;
1488 \\}
1489 \\int foobar();
1490 \\int foo() {
1491 \\ return bar() - foobar();
1492 \\}
1493 \\__attribute__((aligned(0x8000000))) int foobar() {
1494 \\ return 42;
1495 \\}
1496 \\int main() {
1497 \\ printf("bar=%d, foo=%d, foobar=%d", bar(), foo(), foobar());
1498 \\ return foo();
1499 \\}
1500 });
1501
1502 const run = addRunArtifact(exe);
1503 run.expectStdOutEqual("bar=42, foo=0, foobar=42");
1504 run.expectExitCode(0);
1505 test_step.dependOn(&run.step);
1506
1507 return test_step;
1508}
1509
1510fn testTls(b: *Build, opts: Options) *Step {
1511 const test_step = addTestStep(b, "macho-tls", opts);
1512
1513 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
1514 \\_Thread_local int a;
1515 \\int getA() {
1516 \\ return a;
1517 \\}
1518 });
1519
1520 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1521 \\#include<stdio.h>
1522 \\extern _Thread_local int a;
1523 \\extern int getA();
1524 \\int getA2() {
1525 \\ return a;
1526 \\}
1527 \\int main() {
1528 \\ a = 2;
1529 \\ printf("%d %d %d", a, getA(), getA2());
1530 \\ return 0;
1531 \\}
1532 });
1533 exe.root_module.linkSystemLibrary("a", .{});
1534 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
1535 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
1536
1537 const run = addRunArtifact(exe);
1538 run.expectStdOutEqual("2 2 2");
1539 test_step.dependOn(&run.step);
1540
1541 return test_step;
1542}
1543
1544fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
1545 const test_step = addTestStep(b, "macho-tls-large-tbss", opts);
1546
1547 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1548 \\#include <stdio.h>
1549 \\_Thread_local int x[0x8000];
1550 \\_Thread_local int y[0x8000];
1551 \\int main() {
1552 \\ x[0] = 3;
1553 \\ x[0x7fff] = 5;
1554 \\ printf("%d %d %d %d %d %d\n", x[0], x[1], x[0x7fff], y[0], y[1], y[0x7fff]);
1555 \\}
1556 });
1557
1558 const run = addRunArtifact(exe);
1559 run.expectStdOutEqual("3 0 5 0 0 0\n");
1560 test_step.dependOn(&run.step);
1561
1562 return test_step;
1563}
1564
1565fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {
1566 const test_step = addTestStep(b, "macho-two-level-namespace", opts);
1567
1568 const liba = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
1569 \\#include <stdio.h>
1570 \\int foo = 1;
1571 \\int* ptr_to_foo = &foo;
1572 \\int getFoo() {
1573 \\ return foo;
1574 \\}
1575 \\void printInA() {
1576 \\ printf("liba: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
1577 \\}
1578 });
1579
1580 {
1581 const check = liba.checkObject();
1582 check.checkInDyldLazyBind();
1583 check.checkNotPresent("(flat lookup) _getFoo");
1584 check.checkInIndirectSymtab();
1585 check.checkNotPresent("_getFoo");
1586 test_step.dependOn(&check.step);
1587 }
1588
1589 const libb = addSharedLibrary(b, opts, .{ .name = "b", .c_source_bytes =
1590 \\#include <stdio.h>
1591 \\int foo = 2;
1592 \\int* ptr_to_foo = &foo;
1593 \\int getFoo() {
1594 \\ return foo;
1595 \\}
1596 \\void printInB() {
1597 \\ printf("libb: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
1598 \\}
1599 });
1600
1601 {
1602 const check = libb.checkObject();
1603 check.checkInDyldLazyBind();
1604 check.checkNotPresent("(flat lookup) _getFoo");
1605 check.checkInIndirectSymtab();
1606 check.checkNotPresent("_getFoo");
1607 test_step.dependOn(&check.step);
1608 }
1609
1610 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
1611 \\#include <stdio.h>
1612 \\int getFoo();
1613 \\extern int* ptr_to_foo;
1614 \\void printInA();
1615 \\void printInB();
1616 \\int main() {
1617 \\ printf("main: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
1618 \\ printInA();
1619 \\ printInB();
1620 \\ return 0;
1621 \\}
1622 });
1623
1624 {
1625 const exe = addExecutable(b, opts, .{ .name = "main1" });
1626 exe.addObject(main_o);
1627 exe.root_module.linkSystemLibrary("a", .{});
1628 exe.root_module.linkSystemLibrary("b", .{});
1629 exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
1630 exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
1631 exe.root_module.addRPath(liba.getEmittedBinDirectory());
1632 exe.root_module.addRPath(libb.getEmittedBinDirectory());
1633
1634 const check = exe.checkObject();
1635 check.checkInSymtab();
1636 check.checkExact("(undefined) external _getFoo (from liba)");
1637 check.checkInSymtab();
1638 check.checkExact("(undefined) external _printInA (from liba)");
1639 check.checkInSymtab();
1640 check.checkExact("(undefined) external _printInB (from libb)");
1641 test_step.dependOn(&check.step);
1642
1643 const run = addRunArtifact(exe);
1644 run.expectStdOutEqual(
1645 \\main: getFoo()=1, ptr_to_foo=1
1646 \\liba: getFoo()=1, ptr_to_foo=1
1647 \\libb: getFoo()=2, ptr_to_foo=2
1648 \\
1649 );
1650 test_step.dependOn(&run.step);
1651 }
1652
1653 {
1654 const exe = addExecutable(b, opts, .{ .name = "main2" });
1655 exe.addObject(main_o);
1656 exe.root_module.linkSystemLibrary("b", .{});
1657 exe.root_module.linkSystemLibrary("a", .{});
1658 exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
1659 exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
1660 exe.root_module.addRPath(liba.getEmittedBinDirectory());
1661 exe.root_module.addRPath(libb.getEmittedBinDirectory());
1662
1663 const check = exe.checkObject();
1664 check.checkInSymtab();
1665 check.checkExact("(undefined) external _getFoo (from libb)");
1666 check.checkInSymtab();
1667 check.checkExact("(undefined) external _printInA (from liba)");
1668 check.checkInSymtab();
1669 check.checkExact("(undefined) external _printInB (from libb)");
1670 test_step.dependOn(&check.step);
1671
1672 const run = addRunArtifact(exe);
1673 run.expectStdOutEqual(
1674 \\main: getFoo()=2, ptr_to_foo=2
1675 \\liba: getFoo()=1, ptr_to_foo=1
1676 \\libb: getFoo()=2, ptr_to_foo=2
1677 \\
1678 );
1679 test_step.dependOn(&run.step);
1680 }
1681
1682 return test_step;
1683}
1684
1685fn testUndefinedFlag(b: *Build, opts: Options) *Step {
1686 const test_step = addTestStep(b, "macho-undefined-flag", opts);
1687
1688 const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "int foo = 42;" });
1689
1690 const lib = addStaticLibrary(b, opts, .{ .name = "a" });
1691 lib.addObject(obj);
1692
1693 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
1694
1695 {
1696 const exe = addExecutable(b, opts, .{ .name = "main1" });
1697 exe.addObject(main_o);
1698 exe.linkLibrary(lib);
1699 exe.forceUndefinedSymbol("_foo");
1700
1701 const run = addRunArtifact(exe);
1702 run.expectExitCode(0);
1703 test_step.dependOn(&run.step);
1704
1705 const check = exe.checkObject();
1706 check.checkInSymtab();
1707 check.checkContains("_foo");
1708 test_step.dependOn(&check.step);
1709 }
1710
1711 {
1712 const exe = addExecutable(b, opts, .{ .name = "main2" });
1713 exe.addObject(main_o);
1714 exe.linkLibrary(lib);
1715 exe.forceUndefinedSymbol("_foo");
1716 exe.link_gc_sections = true;
1717
1718 const run = addRunArtifact(exe);
1719 run.expectExitCode(0);
1720 test_step.dependOn(&run.step);
1721
1722 const check = exe.checkObject();
1723 check.checkInSymtab();
1724 check.checkContains("_foo");
1725 test_step.dependOn(&check.step);
1726 }
1727
1728 {
1729 const exe = addExecutable(b, opts, .{ .name = "main3" });
1730 exe.addObject(main_o);
1731 exe.addObject(obj);
1732
1733 const run = addRunArtifact(exe);
1734 run.expectExitCode(0);
1735 test_step.dependOn(&run.step);
1736
1737 const check = exe.checkObject();
1738 check.checkInSymtab();
1739 check.checkContains("_foo");
1740 test_step.dependOn(&check.step);
1741 }
1742
1743 {
1744 const exe = addExecutable(b, opts, .{ .name = "main4" });
1745 exe.addObject(main_o);
1746 exe.addObject(obj);
1747 exe.link_gc_sections = true;
1748
1749 const run = addRunArtifact(exe);
1750 run.expectExitCode(0);
1751 test_step.dependOn(&run.step);
1752
1753 const check = exe.checkObject();
1754 check.checkInSymtab();
1755 check.checkNotPresent("_foo");
1756 test_step.dependOn(&check.step);
1757 }
1758
1759 return test_step;
1760}
1761
1762fn testUnwindInfo(b: *Build, opts: Options) *Step {
1763 const test_step = addTestStep(b, "macho-unwind-info", opts);
1764
1765 const all_h = all_h: {
1766 const wf = WriteFile.create(b);
1767 break :all_h wf.add("all.h",
1768 \\#ifndef ALL
1769 \\#define ALL
1770 \\
1771 \\#include <cstddef>
1772 \\#include <string>
1773 \\#include <stdexcept>
1774 \\
1775 \\struct SimpleString {
1776 \\ SimpleString(size_t max_size);
1777 \\ ~SimpleString();
1778 \\
1779 \\ void print(const char* tag) const;
1780 \\ bool append_line(const char* x);
1781 \\
1782 \\private:
1783 \\ size_t max_size;
1784 \\ char* buffer;
1785 \\ size_t length;
1786 \\};
1787 \\
1788 \\struct SimpleStringOwner {
1789 \\ SimpleStringOwner(const char* x);
1790 \\ ~SimpleStringOwner();
1791 \\
1792 \\private:
1793 \\ SimpleString string;
1794 \\};
1795 \\
1796 \\class Error: public std::exception {
1797 \\public:
1798 \\ explicit Error(const char* msg) : msg{ msg } {}
1799 \\ virtual ~Error() noexcept {}
1800 \\ virtual const char* what() const noexcept {
1801 \\ return msg.c_str();
1802 \\ }
1803 \\
1804 \\protected:
1805 \\ std::string msg;
1806 \\};
1807 \\
1808 \\#endif
1809 );
1810 };
1811
1812 const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
1813 \\#include "all.h"
1814 \\#include <cstdio>
1815 \\
1816 \\void fn_c() {
1817 \\ SimpleStringOwner c{ "cccccccccc" };
1818 \\}
1819 \\
1820 \\void fn_b() {
1821 \\ SimpleStringOwner b{ "b" };
1822 \\ fn_c();
1823 \\}
1824 \\
1825 \\int main() {
1826 \\ try {
1827 \\ SimpleStringOwner a{ "a" };
1828 \\ fn_b();
1829 \\ SimpleStringOwner d{ "d" };
1830 \\ } catch (const Error& e) {
1831 \\ printf("Error: %s\n", e.what());
1832 \\ } catch(const std::exception& e) {
1833 \\ printf("Exception: %s\n", e.what());
1834 \\ }
1835 \\ return 0;
1836 \\}
1837 });
1838 main_o.root_module.addIncludePath(all_h.dirname());
1839 main_o.linkLibCpp();
1840
1841 const simple_string_o = addObject(b, opts, .{ .name = "simple_string", .cpp_source_bytes =
1842 \\#include "all.h"
1843 \\#include <cstdio>
1844 \\#include <cstring>
1845 \\
1846 \\SimpleString::SimpleString(size_t max_size)
1847 \\: max_size{ max_size }, length{} {
1848 \\ if (max_size == 0) {
1849 \\ throw Error{ "Max size must be at least 1." };
1850 \\ }
1851 \\ buffer = new char[max_size];
1852 \\ buffer[0] = 0;
1853 \\}
1854 \\
1855 \\SimpleString::~SimpleString() {
1856 \\ delete[] buffer;
1857 \\}
1858 \\
1859 \\void SimpleString::print(const char* tag) const {
1860 \\ printf("%s: %s", tag, buffer);
1861 \\}
1862 \\
1863 \\bool SimpleString::append_line(const char* x) {
1864 \\ const auto x_len = strlen(x);
1865 \\ if (x_len + length + 2 > max_size) return false;
1866 \\ std::strncpy(buffer + length, x, max_size - length);
1867 \\ length += x_len;
1868 \\ buffer[length++] = '\n';
1869 \\ buffer[length] = 0;
1870 \\ return true;
1871 \\}
1872 });
1873 simple_string_o.root_module.addIncludePath(all_h.dirname());
1874 simple_string_o.linkLibCpp();
1875
1876 const simple_string_owner_o = addObject(b, opts, .{ .name = "simple_string_owner", .cpp_source_bytes =
1877 \\#include "all.h"
1878 \\
1879 \\SimpleStringOwner::SimpleStringOwner(const char* x) : string{ 10 } {
1880 \\ if (!string.append_line(x)) {
1881 \\ throw Error{ "Not enough memory!" };
1882 \\ }
1883 \\ string.print("Constructed");
1884 \\}
1885 \\
1886 \\SimpleStringOwner::~SimpleStringOwner() {
1887 \\ string.print("About to destroy");
1888 \\}
1889 });
1890 simple_string_owner_o.root_module.addIncludePath(all_h.dirname());
1891 simple_string_owner_o.linkLibCpp();
1892
1893 const exp_stdout =
1894 \\Constructed: a
1895 \\Constructed: b
1896 \\About to destroy: b
1897 \\About to destroy: a
1898 \\Error: Not enough memory!
1899 \\
1900 ;
1901
1902 const exe = addExecutable(b, opts, .{ .name = "main" });
1903 exe.addObject(main_o);
1904 exe.addObject(simple_string_o);
1905 exe.addObject(simple_string_owner_o);
1906 exe.linkLibCpp();
1907
1908 const run = addRunArtifact(exe);
1909 run.expectStdOutEqual(exp_stdout);
1910 test_step.dependOn(&run.step);
1911
1912 const check = exe.checkObject();
1913 check.checkInSymtab();
1914 check.checkContains("(was private external) ___gxx_personality_v0");
1915 test_step.dependOn(&check.step);
1916
1917 return test_step;
1918}
1919
1920fn testUnwindInfoNoSubsectionsArm64(b: *Build, opts: Options) *Step {
1921 const test_step = addTestStep(b, "macho-unwind-info-no-subsections-arm64", opts);
1922
1923 const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
1924 \\.globl _foo
1925 \\.align 4
1926 \\_foo:
1927 \\ .cfi_startproc
1928 \\ stp x29, x30, [sp, #-32]!
1929 \\ .cfi_def_cfa_offset 32
1930 \\ .cfi_offset w30, -24
1931 \\ .cfi_offset w29, -32
1932 \\ mov x29, sp
1933 \\ .cfi_def_cfa w29, 32
1934 \\ bl _bar
1935 \\ ldp x29, x30, [sp], #32
1936 \\ .cfi_restore w29
1937 \\ .cfi_restore w30
1938 \\ .cfi_def_cfa_offset 0
1939 \\ ret
1940 \\ .cfi_endproc
1941 \\
1942 \\.globl _bar
1943 \\.align 4
1944 \\_bar:
1945 \\ .cfi_startproc
1946 \\ sub sp, sp, #32
1947 \\ .cfi_def_cfa_offset -32
1948 \\ stp x29, x30, [sp, #16]
1949 \\ .cfi_offset w30, -24
1950 \\ .cfi_offset w29, -32
1951 \\ mov x29, sp
1952 \\ .cfi_def_cfa w29, 32
1953 \\ mov w0, #4
1954 \\ ldp x29, x30, [sp, #16]
1955 \\ .cfi_restore w29
1956 \\ .cfi_restore w30
1957 \\ add sp, sp, #32
1958 \\ .cfi_def_cfa_offset 0
1959 \\ ret
1960 \\ .cfi_endproc
1961 });
1962
1963 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
1964 \\#include <stdio.h>
1965 \\int foo();
1966 \\int main() {
1967 \\ printf("%d\n", foo());
1968 \\ return 0;
1969 \\}
1970 });
1971 exe.addObject(a_o);
1972
1973 const run = addRunArtifact(exe);
1974 run.expectStdOutEqual("4\n");
1975 test_step.dependOn(&run.step);
1976
1977 return test_step;
1978}
1979
1980fn testUnwindInfoNoSubsectionsX64(b: *Build, opts: Options) *Step {
1981 const test_step = addTestStep(b, "macho-unwind-info-no-subsections-x64", opts);
1982
1983 const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
1984 \\.globl _foo
1985 \\_foo:
1986 \\ .cfi_startproc
1987 \\ push %rbp
1988 \\ .cfi_def_cfa_offset 8
1989 \\ .cfi_offset %rbp, -8
1990 \\ mov %rsp, %rbp
1991 \\ .cfi_def_cfa_register %rbp
1992 \\ call _bar
1993 \\ pop %rbp
1994 \\ .cfi_restore %rbp
1995 \\ .cfi_def_cfa_offset 0
1996 \\ ret
1997 \\ .cfi_endproc
1998 \\
1999 \\.globl _bar
2000 \\_bar:
2001 \\ .cfi_startproc
2002 \\ push %rbp
2003 \\ .cfi_def_cfa_offset 8
2004 \\ .cfi_offset %rbp, -8
2005 \\ mov %rsp, %rbp
2006 \\ .cfi_def_cfa_register %rbp
2007 \\ mov $4, %rax
2008 \\ pop %rbp
2009 \\ .cfi_restore %rbp
2010 \\ .cfi_def_cfa_offset 0
2011 \\ ret
2012 \\ .cfi_endproc
2013 });
2014
2015 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
2016 \\#include <stdio.h>
2017 \\int foo();
2018 \\int main() {
2019 \\ printf("%d\n", foo());
2020 \\ return 0;
2021 \\}
2022 });
2023 exe.addObject(a_o);
2024
2025 const run = addRunArtifact(exe);
2026 run.expectStdOutEqual("4\n");
2027 test_step.dependOn(&run.step);
2028
2029 return test_step;
2030}
2031
2032// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-binding.s
2033fn testWeakBind(b: *Build, opts: Options) *Step {
2034 const test_step = addTestStep(b, "macho-weak-bind", opts);
2035
2036 const lib = addSharedLibrary(b, opts, .{ .name = "foo", .asm_source_bytes =
2037 \\.globl _weak_dysym
2038 \\.weak_definition _weak_dysym
2039 \\_weak_dysym:
2040 \\ .quad 0x1234
2041 \\
2042 \\.globl _weak_dysym_for_gotpcrel
2043 \\.weak_definition _weak_dysym_for_gotpcrel
2044 \\_weak_dysym_for_gotpcrel:
2045 \\ .quad 0x1234
2046 \\
2047 \\.globl _weak_dysym_fn
2048 \\.weak_definition _weak_dysym_fn
2049 \\_weak_dysym_fn:
2050 \\ ret
2051 \\
2052 \\.section __DATA,__thread_vars,thread_local_variables
2053 \\
2054 \\.globl _weak_dysym_tlv
2055 \\.weak_definition _weak_dysym_tlv
2056 \\_weak_dysym_tlv:
2057 \\ .quad 0x1234
2058 });
2059
2060 {
2061 const check = lib.checkObject();
2062 check.checkInExports();
2063 check.checkExtract("[WEAK] {vmaddr1} _weak_dysym");
2064 check.checkExtract("[WEAK] {vmaddr2} _weak_dysym_for_gotpcrel");
2065 check.checkExtract("[WEAK] {vmaddr3} _weak_dysym_fn");
2066 check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_dysym_tlv");
2067 test_step.dependOn(&check.step);
2068 }
2069
2070 const exe = addExecutable(b, opts, .{ .name = "main", .asm_source_bytes =
2071 \\.globl _main, _weak_external, _weak_external_for_gotpcrel, _weak_external_fn
2072 \\.weak_definition _weak_external, _weak_external_for_gotpcrel, _weak_external_fn, _weak_internal, _weak_internal_for_gotpcrel, _weak_internal_fn
2073 \\
2074 \\_main:
2075 \\ mov _weak_dysym_for_gotpcrel@GOTPCREL(%rip), %rax
2076 \\ mov _weak_external_for_gotpcrel@GOTPCREL(%rip), %rax
2077 \\ mov _weak_internal_for_gotpcrel@GOTPCREL(%rip), %rax
2078 \\ mov _weak_tlv@TLVP(%rip), %rax
2079 \\ mov _weak_dysym_tlv@TLVP(%rip), %rax
2080 \\ mov _weak_internal_tlv@TLVP(%rip), %rax
2081 \\ callq _weak_dysym_fn
2082 \\ callq _weak_external_fn
2083 \\ callq _weak_internal_fn
2084 \\ mov $0, %rax
2085 \\ ret
2086 \\
2087 \\_weak_external:
2088 \\ .quad 0x1234
2089 \\
2090 \\_weak_external_for_gotpcrel:
2091 \\ .quad 0x1234
2092 \\
2093 \\_weak_external_fn:
2094 \\ ret
2095 \\
2096 \\_weak_internal:
2097 \\ .quad 0x1234
2098 \\
2099 \\_weak_internal_for_gotpcrel:
2100 \\ .quad 0x1234
2101 \\
2102 \\_weak_internal_fn:
2103 \\ ret
2104 \\
2105 \\.data
2106 \\ .quad _weak_dysym
2107 \\ .quad _weak_external + 2
2108 \\ .quad _weak_internal
2109 \\
2110 \\.tbss _weak_tlv$tlv$init, 4, 2
2111 \\.tbss _weak_internal_tlv$tlv$init, 4, 2
2112 \\
2113 \\.section __DATA,__thread_vars,thread_local_variables
2114 \\.globl _weak_tlv
2115 \\.weak_definition _weak_tlv, _weak_internal_tlv
2116 \\
2117 \\_weak_tlv:
2118 \\ .quad __tlv_bootstrap
2119 \\ .quad 0
2120 \\ .quad _weak_tlv$tlv$init
2121 \\
2122 \\_weak_internal_tlv:
2123 \\ .quad __tlv_bootstrap
2124 \\ .quad 0
2125 \\ .quad _weak_internal_tlv$tlv$init
2126 });
2127 exe.linkLibrary(lib);
2128
2129 {
2130 const check = exe.checkObject();
2131
2132 check.checkInExports();
2133 check.checkExtract("[WEAK] {vmaddr1} _weak_external");
2134 check.checkExtract("[WEAK] {vmaddr2} _weak_external_for_gotpcrel");
2135 check.checkExtract("[WEAK] {vmaddr3} _weak_external_fn");
2136 check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_tlv");
2137
2138 check.checkInDyldBind();
2139 check.checkContains("(libfoo.dylib) _weak_dysym_for_gotpcrel");
2140 check.checkContains("(libfoo.dylib) _weak_dysym_fn");
2141 check.checkContains("(libfoo.dylib) _weak_dysym");
2142 check.checkContains("(libfoo.dylib) _weak_dysym_tlv");
2143
2144 check.checkInDyldWeakBind();
2145 check.checkContains("_weak_external_for_gotpcrel");
2146 check.checkContains("_weak_dysym_for_gotpcrel");
2147 check.checkContains("_weak_external_fn");
2148 check.checkContains("_weak_dysym_fn");
2149 check.checkContains("_weak_dysym");
2150 check.checkContains("_weak_external");
2151 check.checkContains("_weak_tlv");
2152 check.checkContains("_weak_dysym_tlv");
2153
2154 test_step.dependOn(&check.step);
842155 }
852156
2157 const run = addRunArtifact(exe);
2158 run.expectExitCode(0);
2159 test_step.dependOn(&run.step);
2160
2161 return test_step;
2162}
2163
2164fn testWeakFramework(b: *Build, opts: Options) *Step {
2165 const test_step = addTestStep(b, "macho-weak-framework", opts);
2166
2167 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
2168 exe.root_module.linkFramework("Cocoa", .{ .weak = true });
2169
2170 const run = addRunArtifact(exe);
2171 run.expectExitCode(0);
2172 test_step.dependOn(&run.step);
2173
2174 const check = exe.checkObject();
2175 check.checkInHeaders();
2176 check.checkExact("cmd LOAD_WEAK_DYLIB");
2177 check.checkContains("Cocoa");
2178 test_step.dependOn(&check.step);
2179
2180 return test_step;
2181}
2182
2183fn testWeakLibrary(b: *Build, opts: Options) *Step {
2184 const test_step = addTestStep(b, "macho-weak-library", opts);
2185
2186 const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
2187 \\#include<stdio.h>
2188 \\int a = 42;
2189 \\const char* asStr() {
2190 \\ static char str[3];
2191 \\ sprintf(str, "%d", 42);
2192 \\ return str;
2193 \\}
2194 });
2195
2196 const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
2197 \\#include<stdio.h>
2198 \\extern int a;
2199 \\extern const char* asStr();
2200 \\int main() {
2201 \\ printf("%d %s", a, asStr());
2202 \\ return 0;
2203 \\}
2204 });
2205 exe.root_module.linkSystemLibrary("a", .{ .weak = true });
2206 exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
2207 exe.root_module.addRPath(dylib.getEmittedBinDirectory());
2208
2209 const check = exe.checkObject();
2210 check.checkInHeaders();
2211 check.checkExact("cmd LOAD_WEAK_DYLIB");
2212 check.checkContains("liba.dylib");
2213 check.checkInSymtab();
2214 check.checkExact("(undefined) weakref external _a (from liba)");
2215 check.checkInSymtab();
2216 check.checkExact("(undefined) weakref external _asStr (from liba)");
2217 test_step.dependOn(&check.step);
2218
2219 const run = addRunArtifact(exe);
2220 run.expectStdOutEqual("42 42");
2221 test_step.dependOn(&run.step);
2222
862223 return test_step;
872224}
882225
89fn addTestStep(b: *std.Build, comptime prefix: []const u8, opts: Options) *Step {
2226fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
902227 return link.addTestStep(b, "macho-" ++ prefix, opts);
912228}
922229
2230const addAsmSourceBytes = link.addAsmSourceBytes;
2231const addCSourceBytes = link.addCSourceBytes;
2232const addRunArtifact = link.addRunArtifact;
932233const addObject = link.addObject;
942234const addExecutable = link.addExecutable;
2235const addStaticLibrary = link.addStaticLibrary;
2236const addSharedLibrary = link.addSharedLibrary;
952237const expectLinkErrors = link.expectLinkErrors;
962238const link = @import("link.zig");
972239const std = @import("std");
2240
2241const Build = std.Build;
2242const BuildOptions = link.BuildOptions;
2243const Compile = Step.Compile;
982244const Options = link.Options;
99const Step = std.Build.Step;
2245const Step = Build.Step;
2246const WriteFile = Step.WriteFile;
test/link/macho/bugs/13056/build.zig deleted-38
......@@ -1,38 +0,0 @@
1const std = @import("std");
2
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
18 const sdk = std.zig.system.darwin.getSdk(b.allocator, target.result) orelse
19 @panic("macOS SDK is required to run the test");
20
21 const exe = b.addExecutable(.{
22 .name = "test",
23 .optimize = optimize,
24 .target = b.host,
25 });
26 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include" }) });
27 exe.addIncludePath(.{ .path = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
28 exe.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &.{
29 "-nostdlib++",
30 "-nostdinc++",
31 } });
32 exe.addObjectFile(.{ .path = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
33
34 const run_cmd = b.addRunArtifact(exe);
35 run_cmd.expectStdErrEqual("x: 5\n");
36
37 test_step.dependOn(&run_cmd.step);
38}
test/link/macho/bugs/13056/test.cpp deleted-10
......@@ -1,10 +0,0 @@
1// test.cpp
2#include <new>
3#include <cstdio>
4
5int main() {
6 int *x = new int;
7 *x = 5;
8 fprintf(stderr, "x: %d\n", *x);
9 delete x;
10}
test/link/macho/bugs/13457/build.zig deleted-30
......@@ -1,30 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const exe = b.addExecutable(.{
19 .name = "test",
20 .root_source_file = .{ .path = "main.zig" },
21 .optimize = optimize,
22 .target = target,
23 });
24
25 const run = b.addRunArtifact(exe);
26 run.skip_foreign_checks = true;
27 run.expectStdOutEqual("");
28
29 test_step.dependOn(&run.step);
30}
test/link/macho/bugs/13457/main.zig deleted-1
......@@ -1 +0,0 @@
1pub fn main() void {}
test/link/macho/bugs/16308/build.zig deleted-23
......@@ -1,23 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
10
11 const lib = b.addSharedLibrary(.{
12 .name = "a",
13 .root_source_file = .{ .path = "main.zig" },
14 .optimize = .Debug,
15 .target = target,
16 });
17
18 const check = lib.checkObject();
19 check.checkInSymtab();
20 check.checkNotPresent("external _abc");
21
22 test_step.dependOn(&check.step);
23}
test/link/macho/bugs/16308/main.zig deleted-1
......@@ -1 +0,0 @@
1fn abc() void {}
test/link/macho/bugs/16628/a_arm64.s deleted-37
......@@ -1,37 +0,0 @@
1.globl _foo
2.align 4
3_foo:
4 .cfi_startproc
5 stp x29, x30, [sp, #-32]!
6 .cfi_def_cfa_offset 32
7 .cfi_offset w30, -24
8 .cfi_offset w29, -32
9 mov x29, sp
10 .cfi_def_cfa w29, 32
11 bl _bar
12 ldp x29, x30, [sp], #32
13 .cfi_restore w29
14 .cfi_restore w30
15 .cfi_def_cfa_offset 0
16 ret
17 .cfi_endproc
18
19.globl _bar
20.align 4
21_bar:
22 .cfi_startproc
23 sub sp, sp, #32
24 .cfi_def_cfa_offset -32
25 stp x29, x30, [sp, #16]
26 .cfi_offset w30, -24
27 .cfi_offset w29, -32
28 mov x29, sp
29 .cfi_def_cfa w29, 32
30 mov w0, #4
31 ldp x29, x30, [sp, #16]
32 .cfi_restore w29
33 .cfi_restore w30
34 add sp, sp, #32
35 .cfi_def_cfa_offset 0
36 ret
37 .cfi_endproc
test/link/macho/bugs/16628/a_x64.s deleted-29
......@@ -1,29 +0,0 @@
1.globl _foo
2_foo:
3 .cfi_startproc
4 push %rbp
5 .cfi_def_cfa_offset 8
6 .cfi_offset %rbp, -8
7 mov %rsp, %rbp
8 .cfi_def_cfa_register %rbp
9 call _bar
10 pop %rbp
11 .cfi_restore %rbp
12 .cfi_def_cfa_offset 0
13 ret
14 .cfi_endproc
15
16.globl _bar
17_bar:
18 .cfi_startproc
19 push %rbp
20 .cfi_def_cfa_offset 8
21 .cfi_offset %rbp, -8
22 mov %rsp, %rbp
23 .cfi_def_cfa_register %rbp
24 mov $4, %rax
25 pop %rbp
26 .cfi_restore %rbp
27 .cfi_def_cfa_offset 0
28 ret
29 .cfi_endproc
test/link/macho/bugs/16628/build.zig deleted-42
......@@ -1,42 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const requires_symlinks = true;
5pub const requires_macos_sdk = false;
6
7pub fn build(b: *std.Build) void {
8 const test_step = b.step("test", "Test it");
9 b.default_step = test_step;
10
11 add(b, test_step, .Debug);
12 add(b, test_step, .ReleaseFast);
13 add(b, test_step, .ReleaseSmall);
14 add(b, test_step, .ReleaseSafe);
15}
16
17fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
18 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
19
20 const exe = b.addExecutable(.{
21 .name = "test",
22 .optimize = optimize,
23 .target = target,
24 });
25 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
26 switch (builtin.cpu.arch) {
27 .aarch64 => {
28 exe.addCSourceFile(.{ .file = .{ .path = "a_arm64.s" }, .flags = &[0][]const u8{} });
29 },
30 .x86_64 => {
31 exe.addCSourceFile(.{ .file = .{ .path = "a_x64.s" }, .flags = &[0][]const u8{} });
32 },
33 else => unreachable,
34 }
35 exe.linkLibC();
36
37 const run = b.addRunArtifact(exe);
38 run.skip_foreign_checks = true;
39 run.expectStdOutEqual("4\n");
40
41 test_step.dependOn(&run.step);
42}
test/link/macho/bugs/16628/main.c deleted-8
......@@ -1,8 +0,0 @@
1#include <stdio.h>
2
3int foo();
4
5int main() {
6 printf("%d\n", foo());
7 return 0;
8}
test/link/macho/dead_strip/build.zig deleted-58
......@@ -1,58 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const optimize: std.builtin.OptimizeMode = .Debug;
7 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
8
9 const test_step = b.step("test", "Test the program");
10 b.default_step = test_step;
11
12 {
13 // Without -dead_strip, we expect `iAmUnused` symbol present
14 const exe = createScenario(b, optimize, target, "no-gc");
15
16 const check = exe.checkObject();
17 check.checkInSymtab();
18 check.checkContains("(__TEXT,__text) external _iAmUnused");
19 test_step.dependOn(&check.step);
20
21 const run = b.addRunArtifact(exe);
22 run.skip_foreign_checks = true;
23 run.expectStdOutEqual("Hello!\n");
24 test_step.dependOn(&run.step);
25 }
26
27 {
28 // With -dead_strip, no `iAmUnused` symbol should be present
29 const exe = createScenario(b, optimize, target, "yes-gc");
30 exe.link_gc_sections = true;
31
32 const check = exe.checkObject();
33 check.checkInSymtab();
34 check.checkNotPresent("(__TEXT,__text) external _iAmUnused");
35 test_step.dependOn(&check.step);
36
37 const run = b.addRunArtifact(exe);
38 run.skip_foreign_checks = true;
39 run.expectStdOutEqual("Hello!\n");
40 test_step.dependOn(&run.step);
41 }
42}
43
44fn createScenario(
45 b: *std.Build,
46 optimize: std.builtin.OptimizeMode,
47 target: std.Build.ResolvedTarget,
48 name: []const u8,
49) *std.Build.Step.Compile {
50 const exe = b.addExecutable(.{
51 .name = name,
52 .optimize = optimize,
53 .target = target,
54 });
55 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
56 exe.linkLibC();
57 return exe;
58}
test/link/macho/dead_strip/main.c deleted-14
......@@ -1,14 +0,0 @@
1#include <stdio.h>
2
3void printMe() {
4 printf("Hello!\n");
5}
6
7int main(int argc, char* argv[]) {
8 printMe();
9 return 0;
10}
11
12void iAmUnused() {
13 printf("YOU SHALL NOT PASS!\n");
14}
test/link/macho/dead_strip_dylibs/build.zig deleted-61
......@@ -1,61 +0,0 @@
1const std = @import("std");
2
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 {
18 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
19 const exe = createScenario(b, optimize, "no-dead-strip");
20
21 const check = exe.checkObject();
22 check.checkInHeaders();
23 check.checkExact("cmd LOAD_DYLIB");
24 check.checkContains("Cocoa");
25
26 check.checkInHeaders();
27 check.checkExact("cmd LOAD_DYLIB");
28 check.checkContains("libobjc");
29
30 test_step.dependOn(&check.step);
31
32 const run_cmd = b.addRunArtifact(exe);
33 test_step.dependOn(&run_cmd.step);
34 }
35
36 {
37 // With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
38 const exe = createScenario(b, optimize, "yes-dead-strip");
39 exe.dead_strip_dylibs = true;
40
41 const run_cmd = b.addRunArtifact(exe);
42 run_cmd.expectExitCode(@as(u8, @bitCast(@as(i8, -2)))); // should fail
43 test_step.dependOn(&run_cmd.step);
44 }
45}
46
47fn createScenario(
48 b: *std.Build,
49 optimize: std.builtin.OptimizeMode,
50 name: []const u8,
51) *std.Build.Step.Compile {
52 const exe = b.addExecutable(.{
53 .name = name,
54 .optimize = optimize,
55 .target = b.host,
56 });
57 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
58 exe.linkLibC();
59 exe.linkFramework("Cocoa");
60 return exe;
61}
test/link/macho/dead_strip_dylibs/main.c deleted-11
......@@ -1,11 +0,0 @@
1#include <objc/runtime.h>
2
3int main(int argc, char* argv[]) {
4 if (objc_getClass("NSObject") == 0) {
5 return -1;
6 }
7 if (objc_getClass("NSApplication") == 0) {
8 return -2;
9 }
10 return 0;
11}
test/link/macho/dylib/a.c deleted-7
......@@ -1,7 +0,0 @@
1#include <stdio.h>
2
3char world[] = "world";
4
5char* hello() {
6 return "Hello";
7}
test/link/macho/dylib/build.zig deleted-65
......@@ -1,65 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const dylib = b.addSharedLibrary(.{
19 .name = "a",
20 .version = .{ .major = 1, .minor = 0, .patch = 0 },
21 .optimize = optimize,
22 .target = target,
23 });
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
25 dylib.linkLibC();
26
27 const check_dylib = dylib.checkObject();
28 check_dylib.checkInHeaders();
29 check_dylib.checkExact("cmd ID_DYLIB");
30 check_dylib.checkExact("name @rpath/liba.dylib");
31 check_dylib.checkExact("timestamp 2");
32 check_dylib.checkExact("current version 10000");
33 check_dylib.checkExact("compatibility version 10000");
34
35 test_step.dependOn(&check_dylib.step);
36
37 const exe = b.addExecutable(.{
38 .name = "main",
39 .optimize = optimize,
40 .target = target,
41 });
42 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
43 exe.linkSystemLibrary("a");
44 exe.addLibraryPath(dylib.getEmittedBinDirectory());
45 exe.addRPath(dylib.getEmittedBinDirectory());
46 exe.linkLibC();
47
48 const check_exe = exe.checkObject();
49 check_exe.checkInHeaders();
50 check_exe.checkExact("cmd LOAD_DYLIB");
51 check_exe.checkExact("name @rpath/liba.dylib");
52 check_exe.checkExact("timestamp 2");
53 check_exe.checkExact("current version 10000");
54 check_exe.checkExact("compatibility version 10000");
55
56 check_exe.checkInHeaders();
57 check_exe.checkExact("cmd RPATH");
58 check_exe.checkExactPath("path", dylib.getEmittedBinDirectory());
59 test_step.dependOn(&check_exe.step);
60
61 const run = b.addRunArtifact(exe);
62 run.skip_foreign_checks = true;
63 run.expectStdOutEqual("Hello world");
64 test_step.dependOn(&run.step);
65}
test/link/macho/dylib/main.c deleted-9
......@@ -1,9 +0,0 @@
1#include <stdio.h>
2
3char* hello();
4extern char world[];
5
6int main(int argc, char* argv[]) {
7 printf("%s %s", hello(), world);
8 return 0;
9}
test/link/macho/empty/build.zig deleted-31
......@@ -1,31 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const exe = b.addExecutable(.{
19 .name = "test",
20 .optimize = optimize,
21 .target = target,
22 });
23 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
24 exe.addCSourceFile(.{ .file = .{ .path = "empty.c" }, .flags = &[0][]const u8{} });
25 exe.linkLibC();
26
27 const run_cmd = b.addRunArtifact(exe);
28 run_cmd.skip_foreign_checks = true;
29 run_cmd.expectStdOutEqual("Hello!\n");
30 test_step.dependOn(&run_cmd.step);
31}
test/link/macho/empty/empty.c deleted
test/link/macho/empty/main.c deleted-6
......@@ -1,6 +0,0 @@
1#include <stdio.h>
2
3int main(int argc, char* argv[]) {
4 printf("Hello!\n");
5 return 0;
6}
test/link/macho/entry/build.zig deleted-45
......@@ -1,45 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const exe = b.addExecutable(.{
17 .name = "main",
18 .optimize = optimize,
19 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
20 });
21 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
22 exe.linkLibC();
23 exe.entry = .{ .symbol_name = "_non_main" };
24
25 const check_exe = exe.checkObject();
26
27 check_exe.checkInHeaders();
28 check_exe.checkExact("segname __TEXT");
29 check_exe.checkExtract("vmaddr {vmaddr}");
30
31 check_exe.checkInHeaders();
32 check_exe.checkExact("cmd MAIN");
33 check_exe.checkExtract("entryoff {entryoff}");
34
35 check_exe.checkInSymtab();
36 check_exe.checkExtract("{n_value} (__TEXT,__text) external _non_main");
37
38 check_exe.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
39 test_step.dependOn(&check_exe.step);
40
41 const run = b.addRunArtifact(exe);
42 run.skip_foreign_checks = true;
43 run.expectStdOutEqual("42");
44 test_step.dependOn(&run.step);
45}
test/link/macho/entry/main.c deleted-6
......@@ -1,6 +0,0 @@
1#include <stdio.h>
2
3int non_main() {
4 printf("%d", 42);
5 return 0;
6}
test/link/macho/entry_in_archive/build.zig deleted-36
......@@ -1,36 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const lib = b.addStaticLibrary(.{
17 .name = "main",
18 .optimize = optimize,
19 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
20 });
21 lib.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
22 lib.linkLibC();
23
24 const exe = b.addExecutable(.{
25 .name = "main",
26 .optimize = optimize,
27 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
28 });
29 exe.linkLibrary(lib);
30 exe.linkLibC();
31
32 const run = b.addRunArtifact(exe);
33 run.skip_foreign_checks = true;
34 run.expectExitCode(0);
35 test_step.dependOn(&run.step);
36}
test/link/macho/entry_in_archive/main.c deleted-5
......@@ -1,5 +0,0 @@
1#include <stdio.h>
2
3int main(int argc, char* argv[]) {
4 return 0;
5}
test/link/macho/entry_in_dylib/bootstrap.c deleted-5
......@@ -1,5 +0,0 @@
1extern int my_main();
2
3int bootstrap() {
4 return my_main();
5}
test/link/macho/entry_in_dylib/build.zig deleted-59
......@@ -1,59 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const lib = b.addSharedLibrary(.{
17 .name = "bootstrap",
18 .optimize = optimize,
19 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
20 });
21 lib.addCSourceFile(.{ .file = .{ .path = "bootstrap.c" }, .flags = &.{} });
22 lib.linkLibC();
23 lib.linker_allow_shlib_undefined = true;
24
25 const exe = b.addExecutable(.{
26 .name = "main",
27 .optimize = optimize,
28 .target = b.resolveTargetQuery(.{ .os_tag = .macos }),
29 });
30 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
31 exe.linkLibrary(lib);
32 exe.linkLibC();
33 exe.entry = .{ .symbol_name = "_bootstrap" };
34 exe.forceUndefinedSymbol("_my_main");
35
36 const check_exe = exe.checkObject();
37 check_exe.checkInHeaders();
38 check_exe.checkExact("segname __TEXT");
39 check_exe.checkExtract("vmaddr {text_vmaddr}");
40
41 check_exe.checkInHeaders();
42 check_exe.checkExact("sectname __stubs");
43 check_exe.checkExtract("addr {stubs_vmaddr}");
44
45 check_exe.checkInHeaders();
46 check_exe.checkExact("cmd MAIN");
47 check_exe.checkExtract("entryoff {entryoff}");
48
49 check_exe.checkComputeCompare("text_vmaddr entryoff +", .{
50 .op = .eq,
51 .value = .{ .variable = "stubs_vmaddr" }, // The entrypoint should be a synthetic stub
52 });
53 test_step.dependOn(&check_exe.step);
54
55 const run = b.addRunArtifact(exe);
56 run.skip_foreign_checks = true;
57 run.expectStdOutEqual("Hello!\n");
58 test_step.dependOn(&run.step);
59}
test/link/macho/entry_in_dylib/main.c deleted-6
......@@ -1,6 +0,0 @@
1#include <stdio.h>
2
3int my_main() {
4 fprintf(stdout, "Hello!\n");
5 return 0;
6}
test/link/macho/headerpad/build.zig deleted-137
......@@ -1,137 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const requires_symlinks = true;
5pub const requires_macos_sdk = true;
6
7pub fn build(b: *std.Build) void {
8 const test_step = b.step("test", "Test it");
9 b.default_step = test_step;
10
11 add(b, test_step, .Debug);
12 add(b, test_step, .ReleaseFast);
13 add(b, test_step, .ReleaseSmall);
14 add(b, test_step, .ReleaseSafe);
15}
16
17fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
18 {
19 // Test -headerpad_max_install_names
20 const exe = simpleExe(b, optimize, "headerpad_max_install_names");
21 exe.headerpad_max_install_names = true;
22
23 const check = exe.checkObject();
24 check.checkInHeaders();
25 check.checkExact("sectname __text");
26 check.checkExtract("offset {offset}");
27
28 switch (builtin.cpu.arch) {
29 .aarch64 => {
30 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
31 },
32 .x86_64 => {
33 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
34 },
35 else => unreachable,
36 }
37
38 test_step.dependOn(&check.step);
39
40 const run = b.addRunArtifact(exe);
41 test_step.dependOn(&run.step);
42 }
43
44 {
45 // Test -headerpad
46 const exe = simpleExe(b, optimize, "headerpad");
47 exe.headerpad_size = 0x10000;
48
49 const check = exe.checkObject();
50 check.checkInHeaders();
51 check.checkExact("sectname __text");
52 check.checkExtract("offset {offset}");
53 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
54
55 test_step.dependOn(&check.step);
56
57 const run = b.addRunArtifact(exe);
58 test_step.dependOn(&run.step);
59 }
60
61 {
62 // Test both flags with -headerpad overriding -headerpad_max_install_names
63 const exe = simpleExe(b, optimize, "headerpad_overriding");
64 exe.headerpad_max_install_names = true;
65 exe.headerpad_size = 0x10000;
66
67 const check = exe.checkObject();
68 check.checkInHeaders();
69 check.checkExact("sectname __text");
70 check.checkExtract("offset {offset}");
71 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
72
73 test_step.dependOn(&check.step);
74
75 const run = b.addRunArtifact(exe);
76 test_step.dependOn(&run.step);
77 }
78
79 {
80 // Test both flags with -headerpad_max_install_names overriding -headerpad
81 const exe = simpleExe(b, optimize, "headerpad_max_install_names_overriding");
82 exe.headerpad_size = 0x1000;
83 exe.headerpad_max_install_names = true;
84
85 const check = exe.checkObject();
86 check.checkInHeaders();
87 check.checkExact("sectname __text");
88 check.checkExtract("offset {offset}");
89
90 switch (builtin.cpu.arch) {
91 .aarch64 => {
92 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
93 },
94 .x86_64 => {
95 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
96 },
97 else => unreachable,
98 }
99
100 test_step.dependOn(&check.step);
101
102 const run = b.addRunArtifact(exe);
103 test_step.dependOn(&run.step);
104 }
105}
106
107fn simpleExe(
108 b: *std.Build,
109 optimize: std.builtin.OptimizeMode,
110 name: []const u8,
111) *std.Build.Step.Compile {
112 const exe = b.addExecutable(.{
113 .name = name,
114 .optimize = optimize,
115 .target = b.host,
116 });
117 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
118 exe.linkLibC();
119 exe.linkFramework("CoreFoundation");
120 exe.linkFramework("Foundation");
121 exe.linkFramework("Cocoa");
122 exe.linkFramework("CoreGraphics");
123 exe.linkFramework("CoreHaptics");
124 exe.linkFramework("CoreAudio");
125 exe.linkFramework("AVFoundation");
126 exe.linkFramework("CoreImage");
127 exe.linkFramework("CoreLocation");
128 exe.linkFramework("CoreML");
129 exe.linkFramework("CoreVideo");
130 exe.linkFramework("CoreText");
131 exe.linkFramework("CryptoKit");
132 exe.linkFramework("GameKit");
133 exe.linkFramework("SwiftUI");
134 exe.linkFramework("StoreKit");
135 exe.linkFramework("SpriteKit");
136 return exe;
137}
test/link/macho/headerpad/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/linksection/build.zig deleted-39
......@@ -1,39 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const obj = b.addObject(.{
19 .name = "test",
20 .root_source_file = .{ .path = "main.zig" },
21 .optimize = optimize,
22 .target = target,
23 });
24
25 const check = obj.checkObject();
26
27 check.checkInSymtab();
28 check.checkContains("(__DATA,__TestGlobal) external _test_global");
29
30 check.checkInSymtab();
31 check.checkContains("(__TEXT,__TestFn) external _testFn");
32
33 if (optimize == .Debug) {
34 check.checkInSymtab();
35 check.checkContains("(__TEXT,__TestGenFnA) _main.testGenericFn__anon_");
36 }
37
38 test_step.dependOn(&check.step);
39}
test/link/macho/linksection/main.zig deleted-5
......@@ -1,5 +0,0 @@
1export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
2export fn testFn() linksection("__TEXT,__TestFn") callconv(.C) void {
3 testGenericFn("A");
4}
5fn testGenericFn(comptime suffix: []const u8) linksection("__TEXT,__TestGenFn" ++ suffix) void {}
test/link/macho/needed_framework/build.zig deleted-37
......@@ -1,37 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 // -dead_strip_dylibs
18 // -needed_framework Cocoa
19 const exe = b.addExecutable(.{
20 .name = "test",
21 .optimize = optimize,
22 .target = b.host,
23 });
24 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
25 exe.linkLibC();
26 exe.linkFrameworkNeeded("Cocoa");
27 exe.dead_strip_dylibs = true;
28
29 const check = exe.checkObject();
30 check.checkInHeaders();
31 check.checkExact("cmd LOAD_DYLIB");
32 check.checkContains("Cocoa");
33 test_step.dependOn(&check.step);
34
35 const run_cmd = b.addRunArtifact(exe);
36 test_step.dependOn(&run_cmd.step);
37}
test/link/macho/needed_framework/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/needed_library/a.c deleted-1
......@@ -1 +0,0 @@
1int a = 42;
test/link/macho/needed_library/build.zig deleted-51
......@@ -1,51 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const dylib = b.addSharedLibrary(.{
19 .name = "a",
20 .version = .{ .major = 1, .minor = 0, .patch = 0 },
21 .optimize = optimize,
22 .target = target,
23 });
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
25 dylib.linkLibC();
26
27 // -dead_strip_dylibs
28 // -needed-la
29 const exe = b.addExecutable(.{
30 .name = "test",
31 .optimize = optimize,
32 .target = target,
33 });
34 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
35 exe.linkLibC();
36 exe.root_module.linkSystemLibrary("a", .{ .needed = true });
37 exe.addLibraryPath(dylib.getEmittedBinDirectory());
38 exe.addRPath(dylib.getEmittedBinDirectory());
39 exe.dead_strip_dylibs = true;
40
41 const check = exe.checkObject();
42 check.checkInHeaders();
43 check.checkExact("cmd LOAD_DYLIB");
44 check.checkExact("name @rpath/liba.dylib");
45 test_step.dependOn(&check.step);
46
47 const run = b.addRunArtifact(exe);
48 run.skip_foreign_checks = true;
49 run.expectStdOutEqual("");
50 test_step.dependOn(&run.step);
51}
test/link/macho/needed_library/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/objc/Foo.h deleted-7
......@@ -1,7 +0,0 @@
1#import <Foundation/Foundation.h>
2
3@interface Foo : NSObject
4
5- (NSString *)name;
6
7@end
test/link/macho/objc/Foo.m deleted-11
......@@ -1,11 +0,0 @@
1#import "Foo.h"
2
3@implementation Foo
4
5- (NSString *)name
6{
7 NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
8 return str;
9}
10
11@end
test/link/macho/objc/build.zig deleted-34
......@@ -1,34 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const exe = b.addExecutable(.{
18 .name = "test",
19 .optimize = optimize,
20 .target = b.host,
21 });
22 exe.addIncludePath(.{ .path = "." });
23 exe.addCSourceFile(.{ .file = .{ .path = "Foo.m" }, .flags = &[0][]const u8{} });
24 exe.addCSourceFile(.{ .file = .{ .path = "test.m" }, .flags = &[0][]const u8{} });
25 exe.linkLibC();
26 // TODO when we figure out how to ship framework stubs for cross-compilation,
27 // populate paths to the sysroot here.
28 exe.linkFramework("Foundation");
29
30 const run_cmd = b.addRunArtifact(exe);
31 run_cmd.skip_foreign_checks = true;
32 run_cmd.expectStdOutEqual("");
33 test_step.dependOn(&run_cmd.step);
34}
test/link/macho/objc/test.m deleted-12
......@@ -1,12 +0,0 @@
1#import "Foo.h"
2#import <assert.h>
3
4int main(int argc, char *argv[])
5{
6 @autoreleasepool {
7 Foo *foo = [[Foo alloc] init];
8 NSString *result = [foo name];
9 assert([result isEqualToString:@"Zig"]);
10 return 0;
11 }
12}
test/link/macho/objcpp/Foo.h deleted-7
......@@ -1,7 +0,0 @@
1#import <Foundation/Foundation.h>
2
3@interface Foo : NSObject
4
5- (NSString *)name;
6
7@end
test/link/macho/objcpp/Foo.mm deleted-11
......@@ -1,11 +0,0 @@
1#import "Foo.h"
2
3@implementation Foo
4
5- (NSString *)name
6{
7 NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
8 return str;
9}
10
11@end
test/link/macho/objcpp/build.zig deleted-35
......@@ -1,35 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const exe = b.addExecutable(.{
18 .name = "test",
19 .optimize = optimize,
20 .target = b.host,
21 });
22 b.default_step.dependOn(&exe.step);
23 exe.addIncludePath(.{ .path = "." });
24 exe.addCSourceFile(.{ .file = .{ .path = "Foo.mm" }, .flags = &[0][]const u8{} });
25 exe.addCSourceFile(.{ .file = .{ .path = "test.mm" }, .flags = &[0][]const u8{} });
26 exe.linkLibCpp();
27 // TODO when we figure out how to ship framework stubs for cross-compilation,
28 // populate paths to the sysroot here.
29 exe.linkFramework("Foundation");
30
31 const run_cmd = b.addRunArtifact(exe);
32 run_cmd.expectStdOutEqual("Hello from C++ and Zig");
33
34 test_step.dependOn(&run_cmd.step);
35}
test/link/macho/objcpp/test.mm deleted-14
......@@ -1,14 +0,0 @@
1#import "Foo.h"
2#import <assert.h>
3#include <iostream>
4
5int main(int argc, char *argv[])
6{
7 @autoreleasepool {
8 Foo *foo = [[Foo alloc] init];
9 NSString *result = [foo name];
10 std::cout << "Hello from C++ and " << [result UTF8String];
11 assert([result isEqualToString:@"Zig"]);
12 return 0;
13 }
14}
test/link/macho/pagezero/build.zig deleted-54
......@@ -1,54 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
11
12 {
13 const exe = b.addExecutable(.{
14 .name = "pagezero",
15 .optimize = optimize,
16 .target = target,
17 });
18 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
19 exe.linkLibC();
20 exe.pagezero_size = 0x4000;
21
22 const check = exe.checkObject();
23 check.checkInHeaders();
24 check.checkExact("LC 0");
25 check.checkExact("segname __PAGEZERO");
26 check.checkExact("vmaddr 0");
27 check.checkExact("vmsize 4000");
28
29 check.checkInHeaders();
30 check.checkExact("segname __TEXT");
31 check.checkExact("vmaddr 4000");
32
33 test_step.dependOn(&check.step);
34 }
35
36 {
37 const exe = b.addExecutable(.{
38 .name = "no_pagezero",
39 .optimize = optimize,
40 .target = target,
41 });
42 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
43 exe.linkLibC();
44 exe.pagezero_size = 0;
45
46 const check = exe.checkObject();
47 check.checkInHeaders();
48 check.checkExact("LC 0");
49 check.checkExact("segname __TEXT");
50 check.checkExact("vmaddr 0");
51
52 test_step.dependOn(&check.step);
53 }
54}
test/link/macho/pagezero/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/reexports/a.zig deleted-7
......@@ -1,7 +0,0 @@
1const x: i32 = 42;
2export fn foo() i32 {
3 return x;
4}
5comptime {
6 @export(foo, .{ .name = "bar", .linkage = .Strong });
7}
test/link/macho/reexports/build.zig deleted-38
......@@ -1,38 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const lib = b.addStaticLibrary(.{
19 .name = "a",
20 .root_source_file = .{ .path = "a.zig" },
21 .optimize = optimize,
22 .target = target,
23 });
24
25 const exe = b.addExecutable(.{
26 .name = "test",
27 .optimize = optimize,
28 .target = target,
29 });
30 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
31 exe.linkLibrary(lib);
32 exe.linkLibC();
33
34 const run = b.addRunArtifact(exe);
35 run.skip_foreign_checks = true;
36 run.expectExitCode(0);
37 test_step.dependOn(&run.step);
38}
test/link/macho/reexports/main.c deleted-5
......@@ -1,5 +0,0 @@
1extern int foo();
2extern int bar();
3int main() {
4 return bar() - foo();
5}
test/link/macho/search_strategy/a.c deleted-7
......@@ -1,7 +0,0 @@
1#include <stdio.h>
2
3char world[] = "world";
4
5char* hello() {
6 return "Hello";
7}
test/link/macho/search_strategy/build.zig deleted-84
......@@ -1,84 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 {
19 // -search_dylibs_first
20 const exe = createScenario(b, optimize, target, "search_dylibs_first", .mode_first);
21
22 const check = exe.checkObject();
23 check.checkInHeaders();
24 check.checkExact("cmd LOAD_DYLIB");
25 check.checkExact("name @rpath/libsearch_dylibs_first.dylib");
26 test_step.dependOn(&check.step);
27
28 const run = b.addRunArtifact(exe);
29 run.skip_foreign_checks = true;
30 run.expectStdOutEqual("Hello world");
31 test_step.dependOn(&run.step);
32 }
33
34 {
35 // -search_paths_first
36 const exe = createScenario(b, optimize, target, "search_paths_first", .paths_first);
37
38 const run = b.addRunArtifact(exe);
39 run.skip_foreign_checks = true;
40 run.expectStdOutEqual("Hello world");
41 test_step.dependOn(&run.step);
42 }
43}
44
45fn createScenario(
46 b: *std.Build,
47 optimize: std.builtin.OptimizeMode,
48 target: std.Build.ResolvedTarget,
49 name: []const u8,
50 search_strategy: std.Build.Module.SystemLib.SearchStrategy,
51) *std.Build.Step.Compile {
52 const static = b.addStaticLibrary(.{
53 .name = name,
54 .optimize = optimize,
55 .target = target,
56 });
57 static.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
58 static.linkLibC();
59
60 const dylib = b.addSharedLibrary(.{
61 .name = name,
62 .version = .{ .major = 1, .minor = 0, .patch = 0 },
63 .optimize = optimize,
64 .target = target,
65 });
66 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
67 dylib.linkLibC();
68
69 const exe = b.addExecutable(.{
70 .name = name,
71 .optimize = optimize,
72 .target = target,
73 });
74 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
75 exe.linkSystemLibrary2(name, .{
76 .use_pkg_config = .no,
77 .search_strategy = search_strategy,
78 });
79 exe.linkLibC();
80 exe.addLibraryPath(static.getEmittedBinDirectory());
81 exe.addLibraryPath(dylib.getEmittedBinDirectory());
82 exe.addRPath(dylib.getEmittedBinDirectory());
83 return exe;
84}
test/link/macho/search_strategy/main.c deleted-9
......@@ -1,9 +0,0 @@
1#include <stdio.h>
2
3char* hello();
4extern char world[];
5
6int main(int argc, char* argv[]) {
7 printf("%s %s", hello(), world);
8 return 0;
9}
test/link/macho/stack_size/build.zig deleted-37
......@@ -1,37 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const exe = b.addExecutable(.{
19 .name = "main",
20 .optimize = optimize,
21 .target = target,
22 });
23 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
24 exe.linkLibC();
25 exe.stack_size = 0x100000000;
26
27 const check_exe = exe.checkObject();
28 check_exe.checkInHeaders();
29 check_exe.checkExact("cmd MAIN");
30 check_exe.checkExact("stacksize 100000000");
31 test_step.dependOn(&check_exe.step);
32
33 const run = b.addRunArtifact(exe);
34 run.skip_foreign_checks = true;
35 run.expectStdOutEqual("");
36 test_step.dependOn(&run.step);
37}
test/link/macho/stack_size/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/strict_validation/build.zig deleted-137
......@@ -1,137 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const requires_symlinks = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
18
19 const exe = b.addExecutable(.{
20 .name = "main",
21 .root_source_file = .{ .path = "main.zig" },
22 .optimize = optimize,
23 .target = target,
24 });
25 exe.linkLibC();
26
27 const check_exe = exe.checkObject();
28
29 check_exe.checkInHeaders();
30 check_exe.checkExact("cmd SEGMENT_64");
31 check_exe.checkExact("segname __LINKEDIT");
32 check_exe.checkExtract("fileoff {fileoff}");
33 check_exe.checkExtract("filesz {filesz}");
34
35 check_exe.checkInHeaders();
36 check_exe.checkExact("cmd DYLD_INFO_ONLY");
37 check_exe.checkExtract("rebaseoff {rebaseoff}");
38 check_exe.checkExtract("rebasesize {rebasesize}");
39 check_exe.checkExtract("bindoff {bindoff}");
40 check_exe.checkExtract("bindsize {bindsize}");
41 check_exe.checkExtract("lazybindoff {lazybindoff}");
42 check_exe.checkExtract("lazybindsize {lazybindsize}");
43 check_exe.checkExtract("exportoff {exportoff}");
44 check_exe.checkExtract("exportsize {exportsize}");
45
46 check_exe.checkInHeaders();
47 check_exe.checkExact("cmd FUNCTION_STARTS");
48 check_exe.checkExtract("dataoff {fstartoff}");
49 check_exe.checkExtract("datasize {fstartsize}");
50
51 check_exe.checkInHeaders();
52 check_exe.checkExact("cmd DATA_IN_CODE");
53 check_exe.checkExtract("dataoff {diceoff}");
54 check_exe.checkExtract("datasize {dicesize}");
55
56 check_exe.checkInHeaders();
57 check_exe.checkExact("cmd SYMTAB");
58 check_exe.checkExtract("symoff {symoff}");
59 check_exe.checkExtract("nsyms {symnsyms}");
60 check_exe.checkExtract("stroff {stroff}");
61 check_exe.checkExtract("strsize {strsize}");
62
63 check_exe.checkInHeaders();
64 check_exe.checkExact("cmd DYSYMTAB");
65 check_exe.checkExtract("indirectsymoff {dysymoff}");
66 check_exe.checkExtract("nindirectsyms {dysymnsyms}");
67
68 switch (builtin.cpu.arch) {
69 .aarch64 => {
70 check_exe.checkInHeaders();
71 check_exe.checkExact("cmd CODE_SIGNATURE");
72 check_exe.checkExtract("dataoff {codesigoff}");
73 check_exe.checkExtract("datasize {codesigsize}");
74 },
75 .x86_64 => {},
76 else => unreachable,
77 }
78
79 // DYLD_INFO_ONLY subsections are in order: rebase < bind < lazy < export,
80 // and there are no gaps between them
81 check_exe.checkComputeCompare("rebaseoff rebasesize +", .{ .op = .eq, .value = .{ .variable = "bindoff" } });
82 check_exe.checkComputeCompare("bindoff bindsize +", .{ .op = .eq, .value = .{ .variable = "lazybindoff" } });
83 check_exe.checkComputeCompare("lazybindoff lazybindsize +", .{ .op = .eq, .value = .{ .variable = "exportoff" } });
84
85 // FUNCTION_STARTS directly follows DYLD_INFO_ONLY (no gap)
86 check_exe.checkComputeCompare("exportoff exportsize +", .{ .op = .eq, .value = .{ .variable = "fstartoff" } });
87
88 // DATA_IN_CODE directly follows FUNCTION_STARTS (no gap)
89 check_exe.checkComputeCompare("fstartoff fstartsize +", .{ .op = .eq, .value = .{ .variable = "diceoff" } });
90
91 // SYMTAB directly follows DATA_IN_CODE (no gap)
92 check_exe.checkComputeCompare("diceoff dicesize +", .{ .op = .eq, .value = .{ .variable = "symoff" } });
93
94 // DYSYMTAB directly follows SYMTAB (no gap)
95 check_exe.checkComputeCompare("symnsyms 16 symoff * +", .{ .op = .eq, .value = .{ .variable = "dysymoff" } });
96
97 // STRTAB follows DYSYMTAB with possible gap
98 check_exe.checkComputeCompare("dysymnsyms 4 dysymoff * +", .{ .op = .lte, .value = .{ .variable = "stroff" } });
99
100 // all LINKEDIT sections apart from CODE_SIGNATURE are 8-bytes aligned
101 check_exe.checkComputeCompare("rebaseoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
102 check_exe.checkComputeCompare("bindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
103 check_exe.checkComputeCompare("lazybindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
104 check_exe.checkComputeCompare("exportoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
105 check_exe.checkComputeCompare("fstartoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
106 check_exe.checkComputeCompare("diceoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
107 check_exe.checkComputeCompare("symoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
108 check_exe.checkComputeCompare("stroff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
109 check_exe.checkComputeCompare("dysymoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
110
111 switch (builtin.cpu.arch) {
112 .aarch64 => {
113 // LINKEDIT segment does not extend beyond, or does not include, CODE_SIGNATURE data
114 check_exe.checkComputeCompare("fileoff filesz codesigoff codesigsize + - -", .{
115 .op = .eq,
116 .value = .{ .literal = 0 },
117 });
118
119 // CODE_SIGNATURE data offset is 16-bytes aligned
120 check_exe.checkComputeCompare("codesigoff 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
121 },
122 .x86_64 => {
123 // LINKEDIT segment does not extend beyond, or does not include, strtab data
124 check_exe.checkComputeCompare("fileoff filesz stroff strsize + - -", .{
125 .op = .eq,
126 .value = .{ .literal = 0 },
127 });
128 },
129 else => unreachable,
130 }
131 test_step.dependOn(&check_exe.step);
132
133 const run = b.addRunArtifact(exe);
134 run.skip_foreign_checks = true;
135 run.expectStdOutEqual("Hello!\n");
136 test_step.dependOn(&run.step);
137}
test/link/macho/strict_validation/main.zig deleted-6
......@@ -1,6 +0,0 @@
1const std = @import("std");
2
3pub fn main() !void {
4 const stdout = std.io.getStdOut().writer();
5 try stdout.writeAll("Hello!\n");
6}
test/link/macho/tbdv3/a.c deleted-3
......@@ -1,3 +0,0 @@
1int getFoo() {
2 return 42;
3}
test/link/macho/tbdv3/build.zig deleted-57
......@@ -1,57 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const requires_symlinks = true;
5pub const requires_macos_sdk = false;
6
7pub fn build(b: *std.Build) void {
8 const test_step = b.step("test", "Test it");
9 b.default_step = test_step;
10
11 add(b, test_step, .Debug);
12 add(b, test_step, .ReleaseFast);
13 add(b, test_step, .ReleaseSmall);
14 add(b, test_step, .ReleaseSafe);
15}
16
17fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
18 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
19
20 const lib = b.addSharedLibrary(.{
21 .name = "a",
22 .version = .{ .major = 1, .minor = 0, .patch = 0 },
23 .optimize = optimize,
24 .target = target,
25 });
26 lib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
27 lib.linkLibC();
28
29 const tbd_file = b.addWriteFile("liba.tbd",
30 \\--- !tapi-tbd-v3
31 \\archs: [ arm64, x86_64 ]
32 \\uuids: [ 'arm64: DEADBEEF', 'x86_64: BEEFDEAD' ]
33 \\platform: macos
34 \\install-name: @rpath/liba.dylib
35 \\current-version: 0
36 \\exports:
37 \\ - archs: [ arm64, x86_64 ]
38 \\ symbols: [ _getFoo ]
39 );
40
41 const exe = b.addExecutable(.{
42 .name = "test",
43 .optimize = optimize,
44 .target = target,
45 });
46 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
47 exe.linkSystemLibrary("a");
48 exe.addLibraryPath(tbd_file.getDirectory());
49 exe.addRPath(lib.getEmittedBinDirectory());
50 exe.linkLibC();
51
52 const run = b.addRunArtifact(exe);
53 run.skip_foreign_checks = true;
54 run.expectExitCode(0);
55
56 test_step.dependOn(&run.step);
57}
test/link/macho/tbdv3/main.c deleted-7
......@@ -1,7 +0,0 @@
1#include <stdio.h>
2
3int getFoo();
4
5int main() {
6 return getFoo() - 42;
7}
test/link/macho/tls/a.c deleted-5
......@@ -1,5 +0,0 @@
1_Thread_local int a;
2
3int getA() {
4 return a;
5}
test/link/macho/tls/build.zig deleted-39
......@@ -1,39 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const lib = b.addSharedLibrary(.{
19 .name = "a",
20 .version = .{ .major = 1, .minor = 0, .patch = 0 },
21 .optimize = optimize,
22 .target = target,
23 });
24 lib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
25 lib.linkLibC();
26
27 const test_exe = b.addTest(.{
28 .root_source_file = .{ .path = "main.zig" },
29 .optimize = optimize,
30 .target = target,
31 });
32 test_exe.linkLibrary(lib);
33 test_exe.linkLibC();
34
35 const run = b.addRunArtifact(test_exe);
36 run.skip_foreign_checks = true;
37
38 test_step.dependOn(&run.step);
39}
test/link/macho/tls/main.zig deleted-15
......@@ -1,15 +0,0 @@
1const std = @import("std");
2
3extern threadlocal var a: i32;
4extern fn getA() i32;
5
6fn getA2() i32 {
7 return a;
8}
9
10test {
11 a = 2;
12 try std.testing.expect(getA() == 2);
13 try std.testing.expect(2 == getA2());
14 try std.testing.expect(getA() == getA2());
15}
test/link/macho/unwind_info/all.h deleted-41
......@@ -1,41 +0,0 @@
1#ifndef ALL
2#define ALL
3
4#include <cstddef>
5#include <string>
6#include <stdexcept>
7
8struct SimpleString {
9 SimpleString(size_t max_size);
10 ~SimpleString();
11
12 void print(const char* tag) const;
13 bool append_line(const char* x);
14
15private:
16 size_t max_size;
17 char* buffer;
18 size_t length;
19};
20
21struct SimpleStringOwner {
22 SimpleStringOwner(const char* x);
23 ~SimpleStringOwner();
24
25private:
26 SimpleString string;
27};
28
29class Error: public std::exception {
30public:
31 explicit Error(const char* msg) : msg{ msg } {}
32 virtual ~Error() noexcept {}
33 virtual const char* what() const noexcept {
34 return msg.c_str();
35 }
36
37protected:
38 std::string msg;
39};
40
41#endif
test/link/macho/unwind_info/build.zig deleted-88
......@@ -1,88 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const requires_symlinks = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
18
19 testUnwindInfo(b, test_step, optimize, target, false, "no-dead-strip");
20 testUnwindInfo(b, test_step, optimize, target, true, "yes-dead-strip");
21}
22
23fn testUnwindInfo(
24 b: *std.Build,
25 test_step: *std.Build.Step,
26 optimize: std.builtin.OptimizeMode,
27 target: std.Build.ResolvedTarget,
28 dead_strip: bool,
29 name: []const u8,
30) void {
31 const exe = createScenario(b, optimize, target, name);
32 exe.link_gc_sections = dead_strip;
33
34 const check = exe.checkObject();
35 check.checkInHeaders();
36 check.checkExact("segname __TEXT");
37 check.checkExact("sectname __gcc_except_tab");
38 check.checkExact("sectname __unwind_info");
39
40 switch (builtin.cpu.arch) {
41 .aarch64 => {
42 check.checkExact("sectname __eh_frame");
43 },
44 .x86_64 => {}, // We do not expect `__eh_frame` section on x86_64 in this case
45 else => unreachable,
46 }
47
48 check.checkInSymtab();
49 check.checkContains("(__TEXT,__text) private external ___gxx_personality_v0");
50 test_step.dependOn(&check.step);
51
52 const run = b.addRunArtifact(exe);
53 run.skip_foreign_checks = true;
54 run.expectStdOutEqual(
55 \\Constructed: a
56 \\Constructed: b
57 \\About to destroy: b
58 \\About to destroy: a
59 \\Error: Not enough memory!
60 \\
61 );
62
63 test_step.dependOn(&run.step);
64}
65
66fn createScenario(
67 b: *std.Build,
68 optimize: std.builtin.OptimizeMode,
69 target: std.Build.ResolvedTarget,
70 name: []const u8,
71) *std.Build.Step.Compile {
72 const exe = b.addExecutable(.{
73 .name = name,
74 .optimize = optimize,
75 .target = target,
76 });
77 b.default_step.dependOn(&exe.step);
78 exe.addIncludePath(.{ .path = "." });
79 exe.addCSourceFiles(.{
80 .files = &[_][]const u8{
81 "main.cpp",
82 "simple_string.cpp",
83 "simple_string_owner.cpp",
84 },
85 });
86 exe.linkLibCpp();
87 return exe;
88}
test/link/macho/unwind_info/main.cpp deleted-24
......@@ -1,24 +0,0 @@
1#include "all.h"
2#include <cstdio>
3
4void fn_c() {
5 SimpleStringOwner c{ "cccccccccc" };
6}
7
8void fn_b() {
9 SimpleStringOwner b{ "b" };
10 fn_c();
11}
12
13int main() {
14 try {
15 SimpleStringOwner a{ "a" };
16 fn_b();
17 SimpleStringOwner d{ "d" };
18 } catch (const Error& e) {
19 printf("Error: %s\n", e.what());
20 } catch(const std::exception& e) {
21 printf("Exception: %s\n", e.what());
22 }
23 return 0;
24}
test/link/macho/unwind_info/simple_string.cpp deleted-30
......@@ -1,30 +0,0 @@
1#include "all.h"
2#include <cstdio>
3#include <cstring>
4
5SimpleString::SimpleString(size_t max_size)
6: max_size{ max_size }, length{} {
7 if (max_size == 0) {
8 throw Error{ "Max size must be at least 1." };
9 }
10 buffer = new char[max_size];
11 buffer[0] = 0;
12}
13
14SimpleString::~SimpleString() {
15 delete[] buffer;
16}
17
18void SimpleString::print(const char* tag) const {
19 printf("%s: %s", tag, buffer);
20}
21
22bool SimpleString::append_line(const char* x) {
23 const auto x_len = strlen(x);
24 if (x_len + length + 2 > max_size) return false;
25 std::strncpy(buffer + length, x, max_size - length);
26 length += x_len;
27 buffer[length++] = '\n';
28 buffer[length] = 0;
29 return true;
30}
test/link/macho/unwind_info/simple_string_owner.cpp deleted-12
......@@ -1,12 +0,0 @@
1#include "all.h"
2
3SimpleStringOwner::SimpleStringOwner(const char* x) : string{ 10 } {
4 if (!string.append_line(x)) {
5 throw Error{ "Not enough memory!" };
6 }
7 string.print("Constructed");
8}
9
10SimpleStringOwner::~SimpleStringOwner() {
11 string.print("About to destroy");
12}
test/link/macho/weak_framework/build.zig deleted-34
......@@ -1,34 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
6pub fn build(b: *std.Build) void {
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const exe = b.addExecutable(.{
18 .name = "test",
19 .optimize = optimize,
20 .target = b.host,
21 });
22 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
23 exe.linkLibC();
24 exe.linkFrameworkWeak("Cocoa");
25
26 const check = exe.checkObject();
27 check.checkInHeaders();
28 check.checkExact("cmd LOAD_WEAK_DYLIB");
29 check.checkContains("Cocoa");
30 test_step.dependOn(&check.step);
31
32 const run_cmd = b.addRunArtifact(exe);
33 test_step.dependOn(&run_cmd.step);
34}
test/link/macho/weak_framework/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/weak_library/a.c deleted-9
......@@ -1,9 +0,0 @@
1#include <stdio.h>
2
3int a = 42;
4
5const char* asStr() {
6 static char str[3];
7 sprintf(str, "%d", 42);
8 return str;
9}
test/link/macho/weak_library/build.zig deleted-55
......@@ -1,55 +0,0 @@
1const std = @import("std");
2
3pub const requires_symlinks = true;
4
5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = b.resolveTargetQuery(.{ .os_tag = .macos });
17
18 const dylib = b.addSharedLibrary(.{
19 .name = "a",
20 .version = .{ .major = 1, .minor = 0, .patch = 0 },
21 .target = target,
22 .optimize = optimize,
23 });
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
25 dylib.linkLibC();
26 b.installArtifact(dylib);
27
28 const exe = b.addExecutable(.{
29 .name = "test",
30 .target = target,
31 .optimize = optimize,
32 });
33 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
34 exe.linkLibC();
35 exe.root_module.linkSystemLibrary("a", .{ .weak = true });
36 exe.addLibraryPath(dylib.getEmittedBinDirectory());
37 exe.addRPath(dylib.getEmittedBinDirectory());
38
39 const check = exe.checkObject();
40 check.checkInHeaders();
41 check.checkExact("cmd LOAD_WEAK_DYLIB");
42 check.checkExact("name @rpath/liba.dylib");
43
44 check.checkInSymtab();
45 check.checkExact("(undefined) weakref external _a (from liba)");
46
47 check.checkInSymtab();
48 check.checkExact("(undefined) weakref external _asStr (from liba)");
49 test_step.dependOn(&check.step);
50
51 const run = b.addRunArtifact(exe);
52 run.skip_foreign_checks = true;
53 run.expectStdOutEqual("42 42");
54 test_step.dependOn(&run.step);
55}
test/link/macho/weak_library/main.c deleted-9
......@@ -1,9 +0,0 @@
1#include <stdio.h>
2
3extern int a;
4extern const char* asStr();
5
6int main(int argc, char* argv[]) {
7 printf("%d %s", a, asStr());
8 return 0;
9}
test/tests.zig+28-15
......@@ -750,26 +750,39 @@ pub fn addLinkTests(
750750 const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
751751
752752 inline for (link.cases) |case| {
753 const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
754 case.import.requires_stage2;
755 const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
756 case.import.requires_symlinks;
757 const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
758 case.import.requires_macos_sdk;
759 const requires_ios_sdk = @hasDecl(case.import, "requires_ios_sdk") and
760 case.import.requires_ios_sdk;
761 const bad =
762 (requires_stage2 and omit_stage2) or
763 (requires_symlinks and omit_symlinks) or
764 (requires_macos_sdk and !enable_macos_sdk) or
765 (requires_ios_sdk and !enable_ios_sdk);
766 if (!bad) {
767 const dep = b.anonymousDependency(case.build_root, case.import, .{});
753 if (mem.eql(u8, @typeName(case.import), "test.link.link")) {
754 const dep = b.anonymousDependency(case.build_root, case.import, .{
755 .has_macos_sdk = enable_macos_sdk,
756 .has_ios_sdk = enable_ios_sdk,
757 .has_symlinks_windows = !omit_symlinks,
758 });
768759 const dep_step = dep.builder.default_step;
769760 assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
770761 const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
771762 dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
772763 step.dependOn(dep_step);
764 } else {
765 const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
766 case.import.requires_stage2;
767 const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
768 case.import.requires_symlinks;
769 const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
770 case.import.requires_macos_sdk;
771 const requires_ios_sdk = @hasDecl(case.import, "requires_ios_sdk") and
772 case.import.requires_ios_sdk;
773 const bad =
774 (requires_stage2 and omit_stage2) or
775 (requires_symlinks and omit_symlinks) or
776 (requires_macos_sdk and !enable_macos_sdk) or
777 (requires_ios_sdk and !enable_ios_sdk);
778 if (!bad) {
779 const dep = b.anonymousDependency(case.build_root, case.import, .{});
780 const dep_step = dep.builder.default_step;
781 assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
782 const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
783 dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
784 step.dependOn(dep_step);
785 }
773786 }
774787 }
775788