| author | |
| committer | |
| log | dcaf43674e35372e1d28ab12c4c4ff9af9f3d646 |
| tree | a4bb41d3e608d9a5f93d0c4521bf083a3d925e25 |
| parent | 92211135f1424aaca0de131cfe3646248730b1ca |
| parent | 0fd0b765fa84a40446663928db1d3f9a63b7a98d |
| signature |
macho: upstream a complete rewrite of the MachO linker117 files changed, 16195 insertions(+), 15404 deletions(-)
CMakeLists.txt+8-3| ... | @@ -603,20 +603,25 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -603,20 +603,25 @@ set(ZIG_STAGE2_SOURCES |
| 603 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" | 603 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" |
| 604 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" | 604 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" |
| 605 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" | 605 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" |
| 606 | "${CMAKE_SOURCE_DIR}/src/link/MachO/InternalObject.zig" | ||
| 606 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" | 607 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" |
| 607 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig" | 608 | "${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" |
| 609 | "${CMAKE_SOURCE_DIR}/src/link/MachO/UnwindInfo.zig" | 610 | "${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" | ||
| 610 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig" | 613 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig" |
| 611 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig" | 614 | "${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" |
| 613 | "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig" | 616 | "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig" |
| 614 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" | 617 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" |
| 618 | "${CMAKE_SOURCE_DIR}/src/link/MachO/file.zig" | ||
| 615 | "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" | 619 | "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" |
| 616 | "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" | 620 | "${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" | ||
| 617 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" | 623 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" |
| 618 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" | 624 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" |
| 619 | "${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig" | ||
| 620 | "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" | 625 | "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" |
| 621 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" | 626 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" |
| 622 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" | 627 | "${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 | ... | @@ -623,7 +623,7 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerStepOptions) *std.Build.St |
| 623 | .root_source_file = .{ .path = "src/main.zig" }, | 623 | .root_source_file = .{ .path = "src/main.zig" }, |
| 624 | .target = options.target, | 624 | .target = options.target, |
| 625 | .optimize = options.optimize, | 625 | .optimize = options.optimize, |
| 626 | .max_rss = 7_000_000_000, | 626 | .max_rss = 8_000_000_000, |
| 627 | .strip = options.strip, | 627 | .strip = options.strip, |
| 628 | .sanitize_thread = options.sanitize_thread, | 628 | .sanitize_thread = options.sanitize_thread, |
| 629 | .single_threaded = options.single_threaded, | 629 | .single_threaded = options.single_threaded, |
lib/std/macho.zig+16| ... | @@ -1240,6 +1240,22 @@ pub const FAT_MAGIC_64 = 0xcafebabf; | ... | @@ -1240,6 +1240,22 @@ pub const FAT_MAGIC_64 = 0xcafebabf; |
| 1240 | /// NXSwapLong(FAT_MAGIC_64) | 1240 | /// NXSwapLong(FAT_MAGIC_64) |
| 1241 | pub const FAT_CIGAM_64 = 0xbfbafeca; | 1241 | pub const FAT_CIGAM_64 = 0xbfbafeca; |
| 1242 | 1242 | ||
| 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). | ||
| 1246 | pub 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. | ||
| 1249 | pub 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. | ||
| 1252 | pub 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. | ||
| 1255 | pub const SG_PROTECTED_VERSION_1 = 0x8; | ||
| 1256 | /// This segment is made read-only after fixups | ||
| 1257 | pub const SG_READ_ONLY = 0x10; | ||
| 1258 | |||
| 1243 | /// The flags field of a section structure is separated into two parts a section | 1259 | /// The flags field of a section structure is separated into two parts a section |
| 1244 | /// type and section attributes. The section types are mutually exclusive (it | 1260 | /// type and section attributes. The section types are mutually exclusive (it |
| 1245 | /// can only have one type) but the section attributes are not (it may have more | 1261 | /// 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 | ... | @@ -1542,6 +1542,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1542 | .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, | 1542 | .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, |
| 1543 | .frameworks = options.frameworks, | 1543 | .frameworks = options.frameworks, |
| 1544 | .lib_dirs = options.lib_dirs, | 1544 | .lib_dirs = options.lib_dirs, |
| 1545 | .framework_dirs = options.framework_dirs, | ||
| 1545 | .rpath_list = options.rpath_list, | 1546 | .rpath_list = options.rpath_list, |
| 1546 | .symbol_wrap_set = options.symbol_wrap_set, | 1547 | .symbol_wrap_set = options.symbol_wrap_set, |
| 1547 | .allow_shlib_undefined = options.linker_allow_shlib_undefined, | 1548 | .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 | ... | @@ -4013,10 +4013,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type |
| 4013 | .import => unreachable, | 4013 | .import => unreachable, |
| 4014 | }; | 4014 | }; |
| 4015 | const atom_index = switch (self.bin_file.tag) { | 4015 | const atom_index = switch (self.bin_file.tag) { |
| 4016 | .macho => blk: { | 4016 | .macho => { |
| 4017 | const macho_file = self.bin_file.cast(link.File.MachO).?; | 4017 | // const macho_file = self.bin_file.cast(link.File.MachO).?; |
| 4018 | const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); | 4018 | // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); |
| 4019 | break :blk macho_file.getAtom(atom).getSymbolIndex().?; | 4019 | // break :blk macho_file.getAtom(atom).getSymbolIndex().?; |
| 4020 | @panic("TODO store"); | ||
| 4020 | }, | 4021 | }, |
| 4021 | .coff => blk: { | 4022 | .coff => blk: { |
| 4022 | const coff_file = self.bin_file.cast(link.File.Coff).?; | 4023 | 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 | ... | @@ -4321,14 +4322,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier |
| 4321 | const got_addr = @as(u32, @intCast(sym.zigGotAddress(elf_file))); | 4322 | const got_addr = @as(u32, @intCast(sym.zigGotAddress(elf_file))); |
| 4322 | try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr }); | 4323 | try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr }); |
| 4323 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 4324 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 4324 | const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl); | 4325 | _ = macho_file; |
| 4325 | const sym_index = macho_file.getAtom(atom).getSymbolIndex().?; | 4326 | @panic("TODO airCall"); |
| 4326 | try self.genSetReg(Type.u64, .x30, .{ | 4327 | // const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl); |
| 4327 | .linker_load = .{ | 4328 | // const sym_index = macho_file.getAtom(atom).getSymbolIndex().?; |
| 4328 | .type = .got, | 4329 | // try self.genSetReg(Type.u64, .x30, .{ |
| 4329 | .sym_index = sym_index, | 4330 | // .linker_load = .{ |
| 4330 | }, | 4331 | // .type = .got, |
| 4331 | }); | 4332 | // .sym_index = sym_index, |
| 4333 | // }, | ||
| 4334 | // }); | ||
| 4332 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { | 4335 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { |
| 4333 | const atom = try coff_file.getOrCreateAtomForDecl(func.owner_decl); | 4336 | const atom = try coff_file.getOrCreateAtomForDecl(func.owner_decl); |
| 4334 | const sym_index = coff_file.getAtom(atom).getSymbolIndex().?; | 4337 | const sym_index = coff_file.getAtom(atom).getSymbolIndex().?; |
| ... | @@ -4352,18 +4355,20 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier | ... | @@ -4352,18 +4355,20 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier |
| 4352 | const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name); | 4355 | const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name); |
| 4353 | const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name); | 4356 | const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name); |
| 4354 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 4357 | if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 4355 | const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name); | 4358 | _ = macho_file; |
| 4356 | const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); | 4359 | @panic("TODO airCall"); |
| 4357 | const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; | 4360 | // const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name); |
| 4358 | _ = try self.addInst(.{ | 4361 | // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); |
| 4359 | .tag = .call_extern, | 4362 | // const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; |
| 4360 | .data = .{ | 4363 | // _ = try self.addInst(.{ |
| 4361 | .relocation = .{ | 4364 | // .tag = .call_extern, |
| 4362 | .atom_index = atom_index, | 4365 | // .data = .{ |
| 4363 | .sym_index = sym_index, | 4366 | // .relocation = .{ |
| 4364 | }, | 4367 | // .atom_index = atom_index, |
| 4365 | }, | 4368 | // .sym_index = sym_index, |
| 4366 | }); | 4369 | // }, |
| 4370 | // }, | ||
| 4371 | // }); | ||
| 4367 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { | 4372 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { |
| 4368 | const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name); | 4373 | const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name); |
| 4369 | try self.genSetReg(Type.u64, .x30, .{ | 4374 | try self.genSetReg(Type.u64, .x30, .{ |
| ... | @@ -5532,10 +5537,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro | ... | @@ -5532,10 +5537,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro |
| 5532 | .import => unreachable, | 5537 | .import => unreachable, |
| 5533 | }; | 5538 | }; |
| 5534 | const atom_index = switch (self.bin_file.tag) { | 5539 | const atom_index = switch (self.bin_file.tag) { |
| 5535 | .macho => blk: { | 5540 | .macho => { |
| 5536 | const macho_file = self.bin_file.cast(link.File.MachO).?; | 5541 | // const macho_file = self.bin_file.cast(link.File.MachO).?; |
| 5537 | const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); | 5542 | // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); |
| 5538 | break :blk macho_file.getAtom(atom).getSymbolIndex().?; | 5543 | // break :blk macho_file.getAtom(atom).getSymbolIndex().?; |
| 5544 | @panic("TODO genSetStack"); | ||
| 5539 | }, | 5545 | }, |
| 5540 | .coff => blk: { | 5546 | .coff => blk: { |
| 5541 | const coff_file = self.bin_file.cast(link.File.Coff).?; | 5547 | 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 | ... | @@ -5653,10 +5659,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 5653 | .import => .load_memory_import, | 5659 | .import => .load_memory_import, |
| 5654 | }; | 5660 | }; |
| 5655 | const atom_index = switch (self.bin_file.tag) { | 5661 | const atom_index = switch (self.bin_file.tag) { |
| 5656 | .macho => blk: { | 5662 | .macho => { |
| 5657 | const macho_file = self.bin_file.cast(link.File.MachO).?; | 5663 | @panic("TODO genSetReg"); |
| 5658 | const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); | 5664 | // const macho_file = self.bin_file.cast(link.File.MachO).?; |
| 5659 | break :blk macho_file.getAtom(atom).getSymbolIndex().?; | 5665 | // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); |
| 5666 | // break :blk macho_file.getAtom(atom).getSymbolIndex().?; | ||
| 5660 | }, | 5667 | }, |
| 5661 | .coff => blk: { | 5668 | .coff => blk: { |
| 5662 | const coff_file = self.bin_file.cast(link.File.Coff).?; | 5669 | 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 | ... | @@ -5850,10 +5857,11 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I |
| 5850 | .import => unreachable, | 5857 | .import => unreachable, |
| 5851 | }; | 5858 | }; |
| 5852 | const atom_index = switch (self.bin_file.tag) { | 5859 | const atom_index = switch (self.bin_file.tag) { |
| 5853 | .macho => blk: { | 5860 | .macho => { |
| 5854 | const macho_file = self.bin_file.cast(link.File.MachO).?; | 5861 | @panic("TODO genSetStackArgument"); |
| 5855 | const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); | 5862 | // const macho_file = self.bin_file.cast(link.File.MachO).?; |
| 5856 | break :blk macho_file.getAtom(atom).getSymbolIndex().?; | 5863 | // const atom = try macho_file.getOrCreateAtomForDecl(self.owner_decl); |
| 5864 | // break :blk macho_file.getAtom(atom).getSymbolIndex().?; | ||
| 5857 | }, | 5865 | }, |
| 5858 | .coff => blk: { | 5866 | .coff => blk: { |
| 5859 | const coff_file = self.bin_file.cast(link.File.Coff).?; | 5867 | 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 { | ... | @@ -677,6 +677,7 @@ fn mirDebugEpilogueBegin(emit: *Emit) !void { |
| 677 | fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void { | 677 | fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 678 | assert(emit.mir.instructions.items(.tag)[inst] == .call_extern); | 678 | assert(emit.mir.instructions.items(.tag)[inst] == .call_extern); |
| 679 | const relocation = emit.mir.instructions.items(.data)[inst].relocation; | 679 | const relocation = emit.mir.instructions.items(.data)[inst].relocation; |
| 680 | _ = relocation; | ||
| 680 | 681 | ||
| 681 | const offset = blk: { | 682 | const offset = blk: { |
| 682 | const offset = @as(u32, @intCast(emit.code.items.len)); | 683 | const offset = @as(u32, @intCast(emit.code.items.len)); |
| ... | @@ -684,19 +685,22 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void { | ... | @@ -684,19 +685,22 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 684 | try emit.writeInstruction(Instruction.bl(0)); | 685 | try emit.writeInstruction(Instruction.bl(0)); |
| 685 | break :blk offset; | 686 | break :blk offset; |
| 686 | }; | 687 | }; |
| 688 | _ = offset; | ||
| 687 | 689 | ||
| 688 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { | 690 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { |
| 689 | // Add relocation to the decl. | 691 | _ = macho_file; |
| 690 | const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?; | 692 | @panic("TODO mirCallExtern"); |
| 691 | const target = macho_file.getGlobalByIndex(relocation.sym_index); | 693 | // // Add relocation to the decl. |
| 692 | try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ | 694 | // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index }).?; |
| 693 | .type = .branch, | 695 | // const target = macho_file.getGlobalByIndex(relocation.sym_index); |
| 694 | .target = target, | 696 | // try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ |
| 695 | .offset = offset, | 697 | // .type = .branch, |
| 696 | .addend = 0, | 698 | // .target = target, |
| 697 | .pcrel = true, | 699 | // .offset = offset, |
| 698 | .length = 2, | 700 | // .addend = 0, |
| 699 | }); | 701 | // .pcrel = true, |
| 702 | // .length = 2, | ||
| 703 | // }); | ||
| 700 | } else if (emit.bin_file.cast(link.File.Coff)) |_| { | 704 | } else if (emit.bin_file.cast(link.File.Coff)) |_| { |
| 701 | unreachable; // Calling imports is handled via `.load_memory_import` | 705 | unreachable; // Calling imports is handled via `.load_memory_import` |
| 702 | } else { | 706 | } else { |
| ... | @@ -900,32 +904,34 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void { | ... | @@ -900,32 +904,34 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 900 | } | 904 | } |
| 901 | 905 | ||
| 902 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { | 906 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { |
| 903 | const Atom = link.File.MachO.Atom; | 907 | _ = macho_file; |
| 904 | const Relocation = Atom.Relocation; | 908 | @panic("TODO mirLoadMemoryPie"); |
| 905 | const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?; | 909 | // const Atom = link.File.MachO.Atom; |
| 906 | try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{ | 910 | // const Relocation = Atom.Relocation; |
| 907 | .target = .{ .sym_index = data.sym_index }, | 911 | // const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index }).?; |
| 908 | .offset = offset, | 912 | // try Atom.addRelocations(macho_file, atom_index, &[_]Relocation{ .{ |
| 909 | .addend = 0, | 913 | // .target = .{ .sym_index = data.sym_index }, |
| 910 | .pcrel = true, | 914 | // .offset = offset, |
| 911 | .length = 2, | 915 | // .addend = 0, |
| 912 | .type = switch (tag) { | 916 | // .pcrel = true, |
| 913 | .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page, | 917 | // .length = 2, |
| 914 | .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page, | 918 | // .type = switch (tag) { |
| 915 | else => unreachable, | 919 | // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_page, |
| 916 | }, | 920 | // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.page, |
| 917 | }, .{ | 921 | // else => unreachable, |
| 918 | .target = .{ .sym_index = data.sym_index }, | 922 | // }, |
| 919 | .offset = offset + 4, | 923 | // }, .{ |
| 920 | .addend = 0, | 924 | // .target = .{ .sym_index = data.sym_index }, |
| 921 | .pcrel = false, | 925 | // .offset = offset + 4, |
| 922 | .length = 2, | 926 | // .addend = 0, |
| 923 | .type = switch (tag) { | 927 | // .pcrel = false, |
| 924 | .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff, | 928 | // .length = 2, |
| 925 | .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff, | 929 | // .type = switch (tag) { |
| 926 | else => unreachable, | 930 | // .load_memory_got, .load_memory_ptr_got => Relocation.Type.got_pageoff, |
| 927 | }, | 931 | // .load_memory_direct, .load_memory_ptr_direct => Relocation.Type.pageoff, |
| 928 | } }); | 932 | // else => unreachable, |
| 933 | // }, | ||
| 934 | // } }); | ||
| 929 | } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { | 935 | } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { |
| 930 | const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index, .file = null }).?; | 936 | const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = data.atom_index, .file = null }).?; |
| 931 | const target = switch (tag) { | 937 | const target = switch (tag) { |
src/arch/x86_64/CodeGen.zig+54-74| ... | @@ -139,8 +139,7 @@ const Owner = union(enum) { | ... | @@ -139,8 +139,7 @@ const Owner = union(enum) { |
| 139 | if (ctx.bin_file.cast(link.File.Elf)) |elf_file| { | 139 | if (ctx.bin_file.cast(link.File.Elf)) |elf_file| { |
| 140 | return elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); | 140 | return elf_file.zigObjectPtr().?.getOrCreateMetadataForDecl(elf_file, decl_index); |
| 141 | } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| { | 141 | } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| { |
| 142 | const atom = try macho_file.getOrCreateAtomForDecl(decl_index); | 142 | return macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index); |
| 143 | return macho_file.getAtom(atom).getSymbolIndex().?; | ||
| 144 | } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| { | 143 | } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| { |
| 145 | const atom = try coff_file.getOrCreateAtomForDecl(decl_index); | 144 | const atom = try coff_file.getOrCreateAtomForDecl(decl_index); |
| 146 | return coff_file.getAtom(atom).getSymbolIndex().?; | 145 | return coff_file.getAtom(atom).getSymbolIndex().?; |
| ... | @@ -153,9 +152,8 @@ const Owner = union(enum) { | ... | @@ -153,9 +152,8 @@ const Owner = union(enum) { |
| 153 | return elf_file.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(elf_file, lazy_sym) catch |err| | 152 | return elf_file.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(elf_file, lazy_sym) catch |err| |
| 154 | ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); | 153 | ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); |
| 155 | } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| { | 154 | } else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| { |
| 156 | const atom = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err| | 155 | return macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, lazy_sym) catch |err| |
| 157 | return ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); | 156 | ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); |
| 158 | return macho_file.getAtom(atom).getSymbolIndex().?; | ||
| 159 | } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| { | 157 | } else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| { |
| 160 | const atom = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err| | 158 | const atom = coff_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err| |
| 161 | return ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); | 159 | return ctx.fail("{s} creating lazy symbol", .{@errorName(err)}); |
| ... | @@ -10951,9 +10949,9 @@ fn genCall(self: *Self, info: union(enum) { | ... | @@ -10951,9 +10949,9 @@ fn genCall(self: *Self, info: union(enum) { |
| 10951 | try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index }); | 10949 | try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index }); |
| 10952 | try self.asmRegister(.{ ._, .call }, .rax); | 10950 | try self.asmRegister(.{ ._, .call }, .rax); |
| 10953 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 10951 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 10954 | const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl); | 10952 | const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, func.owner_decl); |
| 10955 | const sym_index = macho_file.getAtom(atom).getSymbolIndex().?; | 10953 | const sym = macho_file.getSymbol(sym_index); |
| 10956 | try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index }); | 10954 | try self.genSetReg(.rax, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }); |
| 10957 | try self.asmRegister(.{ ._, .call }, .rax); | 10955 | try self.asmRegister(.{ ._, .call }, .rax); |
| 10958 | } else if (self.bin_file.cast(link.File.Plan9)) |p9| { | 10956 | } else if (self.bin_file.cast(link.File.Plan9)) |p9| { |
| 10959 | const atom_index = try p9.seeDecl(func.owner_decl); | 10957 | 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 | ... | @@ -13509,24 +13507,27 @@ fn genSetReg(self: *Self, dst_reg: Register, ty: Type, src_mcv: MCValue) InnerEr |
| 13509 | }, | 13507 | }, |
| 13510 | .lea_symbol => |sym_index| { | 13508 | .lea_symbol => |sym_index| { |
| 13511 | const atom_index = try self.owner.getSymbolIndex(self); | 13509 | const atom_index = try self.owner.getSymbolIndex(self); |
| 13512 | if (self.bin_file.cast(link.File.Elf)) |_| { | 13510 | switch (self.bin_file.tag) { |
| 13513 | try self.asmRegisterMemory( | 13511 | .elf, .macho => { |
| 13514 | .{ ._, .lea }, | 13512 | try self.asmRegisterMemory( |
| 13515 | dst_reg.to64(), | 13513 | .{ ._, .lea }, |
| 13516 | .{ | 13514 | dst_reg.to64(), |
| 13517 | .base = .{ .reloc = .{ | 13515 | .{ |
| 13518 | .atom_index = atom_index, | 13516 | .base = .{ .reloc = .{ |
| 13519 | .sym_index = sym_index.sym, | 13517 | .atom_index = atom_index, |
| 13520 | } }, | 13518 | .sym_index = sym_index.sym, |
| 13521 | .mod = .{ .rm = .{ | 13519 | } }, |
| 13522 | .size = .qword, | 13520 | .mod = .{ .rm = .{ |
| 13523 | .disp = sym_index.off, | 13521 | .size = .qword, |
| 13524 | } }, | 13522 | .disp = sym_index.off, |
| 13525 | }, | 13523 | } }, |
| 13526 | ); | 13524 | }, |
| 13527 | } else return self.fail("TODO emit symbol sequence on {s}", .{ | 13525 | ); |
| 13528 | @tagName(self.bin_file.tag), | 13526 | }, |
| 13529 | }); | 13527 | else => return self.fail("TODO emit symbol sequence on {s}", .{ |
| 13528 | @tagName(self.bin_file.tag), | ||
| 13529 | }), | ||
| 13530 | } | ||
| 13530 | }, | 13531 | }, |
| 13531 | .lea_direct, .lea_got => |sym_index| { | 13532 | .lea_direct, .lea_got => |sym_index| { |
| 13532 | const atom_index = try self.owner.getSymbolIndex(self); | 13533 | 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 | ... | @@ -13550,30 +13551,7 @@ fn genSetReg(self: *Self, dst_reg: Register, ty: Type, src_mcv: MCValue) InnerEr |
| 13550 | } }, | 13551 | } }, |
| 13551 | }); | 13552 | }); |
| 13552 | }, | 13553 | }, |
| 13553 | .lea_tlv => |sym_index| { | 13554 | .lea_tlv => unreachable, // TODO: remove this |
| 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 | }, | ||
| 13577 | .air_ref => |src_ref| try self.genSetReg(dst_reg, ty, try self.resolveInst(src_ref)), | 13555 | .air_ref => |src_ref| try self.genSetReg(dst_reg, ty, try self.resolveInst(src_ref)), |
| 13578 | } | 13556 | } |
| 13579 | } | 13557 | } |
| ... | @@ -13810,13 +13788,12 @@ fn genExternSymbolRef( | ... | @@ -13810,13 +13788,12 @@ fn genExternSymbolRef( |
| 13810 | else => unreachable, | 13788 | else => unreachable, |
| 13811 | } | 13789 | } |
| 13812 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 13790 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 13813 | const global_index = try macho_file.getGlobalSymbol(callee, lib); | ||
| 13814 | _ = try self.addInst(.{ | 13791 | _ = try self.addInst(.{ |
| 13815 | .tag = .call, | 13792 | .tag = .call, |
| 13816 | .ops = .extern_fn_reloc, | 13793 | .ops = .extern_fn_reloc, |
| 13817 | .data = .{ .reloc = .{ | 13794 | .data = .{ .reloc = .{ |
| 13818 | .atom_index = atom_index, | 13795 | .atom_index = atom_index, |
| 13819 | .sym_index = link.File.MachO.global_symbol_bit | global_index, | 13796 | .sym_index = try macho_file.getGlobalSymbol(callee, lib), |
| 13820 | } }, | 13797 | } }, |
| 13821 | }); | 13798 | }); |
| 13822 | } else return self.fail("TODO implement calling extern functions", .{}); | 13799 | } else return self.fail("TODO implement calling extern functions", .{}); |
| ... | @@ -13906,12 +13883,12 @@ fn genLazySymbolRef( | ... | @@ -13906,12 +13883,12 @@ fn genLazySymbolRef( |
| 13906 | else => unreachable, | 13883 | else => unreachable, |
| 13907 | } | 13884 | } |
| 13908 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 13885 | } 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| |
| 13910 | return self.fail("{s} creating lazy symbol", .{@errorName(err)}); | 13887 | 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); |
| 13912 | switch (tag) { | 13889 | switch (tag) { |
| 13913 | .lea, .call => try self.genSetReg(reg, Type.usize, .{ .lea_got = sym_index }), | 13890 | .lea, .call => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }), |
| 13914 | .mov => try self.genSetReg(reg, Type.usize, .{ .load_got = sym_index }), | 13891 | .mov => try self.genSetReg(reg, Type.usize, .{ .load_symbol = .{ .sym = sym.nlist_idx } }), |
| 13915 | else => unreachable, | 13892 | else => unreachable, |
| 13916 | } | 13893 | } |
| 13917 | switch (tag) { | 13894 | switch (tag) { |
| ... | @@ -16074,24 +16051,27 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue { | ... | @@ -16074,24 +16051,27 @@ fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue { |
| 16074 | if (!gop.found_existing) gop.value_ptr.* = InstTracking.init(init: { | 16051 | if (!gop.found_existing) gop.value_ptr.* = InstTracking.init(init: { |
| 16075 | const const_mcv = try self.genTypedValue(.{ .ty = ty, .val = Value.fromInterned(ip_index) }); | 16052 | const const_mcv = try self.genTypedValue(.{ .ty = ty, .val = Value.fromInterned(ip_index) }); |
| 16076 | switch (const_mcv) { | 16053 | switch (const_mcv) { |
| 16077 | .lea_tlv => |tlv_sym| if (self.bin_file.cast(link.File.Elf)) |_| { | 16054 | .lea_tlv => |tlv_sym| switch (self.bin_file.tag) { |
| 16078 | if (self.mod.pic) { | 16055 | .elf, .macho => { |
| 16079 | try self.spillRegisters(&.{ .rdi, .rax }); | 16056 | if (self.mod.pic) { |
| 16080 | } else { | 16057 | try self.spillRegisters(&.{ .rdi, .rax }); |
| 16081 | try self.spillRegisters(&.{.rax}); | 16058 | } else { |
| 16082 | } | 16059 | try self.spillRegisters(&.{.rax}); |
| 16083 | const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{ | 16060 | } |
| 16084 | .size = 8, | 16061 | const frame_index = try self.allocFrameIndex(FrameAlloc.init(.{ |
| 16085 | .alignment = .@"8", | 16062 | .size = 8, |
| 16086 | })); | 16063 | .alignment = .@"8", |
| 16087 | try self.genSetMem( | 16064 | })); |
| 16088 | .{ .frame = frame_index }, | 16065 | try self.genSetMem( |
| 16089 | 0, | 16066 | .{ .frame = frame_index }, |
| 16090 | Type.usize, | 16067 | 0, |
| 16091 | .{ .lea_symbol = .{ .sym = tlv_sym } }, | 16068 | Type.usize, |
| 16092 | ); | 16069 | .{ .lea_symbol = .{ .sym = tlv_sym } }, |
| 16093 | break :init .{ .load_frame = .{ .index = frame_index } }; | 16070 | ); |
| 16094 | } else break :init const_mcv, | 16071 | break :init .{ .load_frame = .{ .index = frame_index } }; |
| 16072 | }, | ||
| 16073 | else => break :init const_mcv, | ||
| 16074 | }, | ||
| 16095 | else => break :init const_mcv, | 16075 | else => break :init const_mcv, |
| 16096 | } | 16076 | } |
| 16097 | }); | 16077 | }); |
src/arch/x86_64/Emit.zig+47-32| ... | @@ -50,19 +50,20 @@ pub fn emitMir(emit: *Emit) Error!void { | ... | @@ -50,19 +50,20 @@ pub fn emitMir(emit: *Emit) Error!void { |
| 50 | }); | 50 | }); |
| 51 | } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| { | 51 | } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| { |
| 52 | // Add relocation to the decl. | 52 | // Add relocation to the decl. |
| 53 | const atom_index = | 53 | const atom = macho_file.getSymbol(symbol.atom_index).getAtom(macho_file).?; |
| 54 | macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?; | 54 | const sym_index = macho_file.getZigObject().?.symbols.items[symbol.sym_index]; |
| 55 | const target = if (link.File.MachO.global_symbol_bit & symbol.sym_index != 0) | 55 | try atom.addReloc(macho_file, .{ |
| 56 | macho_file.getGlobalByIndex(link.File.MachO.global_symbol_mask & symbol.sym_index) | 56 | .tag = .@"extern", |
| 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, | ||
| 62 | .offset = end_offset - 4, | 57 | .offset = end_offset - 4, |
| 58 | .target = sym_index, | ||
| 63 | .addend = 0, | 59 | .addend = 0, |
| 64 | .pcrel = true, | 60 | .type = .branch, |
| 65 | .length = 2, | 61 | .meta = .{ |
| 62 | .pcrel = true, | ||
| 63 | .has_subtractor = false, | ||
| 64 | .length = 2, | ||
| 65 | .symbolnum = 0, | ||
| 66 | }, | ||
| 66 | }); | 67 | }); |
| 67 | } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| { | 68 | } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| { |
| 68 | // Add relocation to the decl. | 69 | // Add relocation to the decl. |
| ... | @@ -149,33 +150,47 @@ pub fn emitMir(emit: *Emit) Error!void { | ... | @@ -149,33 +150,47 @@ pub fn emitMir(emit: *Emit) Error!void { |
| 149 | }); | 150 | }); |
| 150 | } | 151 | } |
| 151 | } | 152 | } |
| 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 | }); | ||
| 152 | } else unreachable, | 186 | } else unreachable, |
| 153 | .linker_got, | 187 | .linker_got, |
| 154 | .linker_direct, | 188 | .linker_direct, |
| 155 | .linker_import, | 189 | .linker_import, |
| 156 | .linker_tlv, | ||
| 157 | => |symbol| if (emit.lower.bin_file.cast(link.File.Elf)) |_| { | 190 | => |symbol| if (emit.lower.bin_file.cast(link.File.Elf)) |_| { |
| 158 | unreachable; | 191 | unreachable; |
| 159 | } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| { | 192 | } else if (emit.lower.bin_file.cast(link.File.MachO)) |_| { |
| 160 | const atom_index = | 193 | unreachable; |
| 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 | }); | ||
| 179 | } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| { | 194 | } else if (emit.lower.bin_file.cast(link.File.Coff)) |coff_file| { |
| 180 | const atom_index = coff_file.getAtomIndexForSymbol(.{ | 195 | const atom_index = coff_file.getAtomIndexForSymbol(.{ |
| 181 | .sym_index = symbol.atom_index, | 196 | .sym_index = symbol.atom_index, |
src/arch/x86_64/Lower.zig+99-78| ... | @@ -14,7 +14,7 @@ result_relocs_len: u8 = undefined, | ... | @@ -14,7 +14,7 @@ result_relocs_len: u8 = undefined, |
| 14 | result_insts: [ | 14 | result_insts: [ |
| 15 | std.mem.max(usize, &.{ | 15 | std.mem.max(usize, &.{ |
| 16 | 1, // non-pseudo instructions | 16 | 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 |
| 18 | 2, // cmovcc: cmovcc \ cmovcc | 18 | 2, // cmovcc: cmovcc \ cmovcc |
| 19 | 3, // setcc: setcc \ setcc \ logicop | 19 | 3, // setcc: setcc \ setcc \ logicop |
| 20 | 2, // jcc: jcc \ jcc | 20 | 2, // jcc: jcc \ jcc |
| ... | @@ -32,7 +32,7 @@ result_relocs: [ | ... | @@ -32,7 +32,7 @@ result_relocs: [ |
| 32 | 2, // jcc: jcc \ jcc | 32 | 2, // jcc: jcc \ jcc |
| 33 | 2, // test \ jcc \ probe \ sub \ jmp | 33 | 2, // test \ jcc \ probe \ sub \ jmp |
| 34 | 1, // probe \ sub \ jcc | 34 | 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 |
| 36 | }) | 36 | }) |
| 37 | ]Reloc = undefined, | 37 | ]Reloc = undefined, |
| 38 | 38 | ||
| ... | @@ -62,7 +62,6 @@ pub const Reloc = struct { | ... | @@ -62,7 +62,6 @@ pub const Reloc = struct { |
| 62 | linker_got: bits.Symbol, | 62 | linker_got: bits.Symbol, |
| 63 | linker_direct: bits.Symbol, | 63 | linker_direct: bits.Symbol, |
| 64 | linker_import: bits.Symbol, | 64 | linker_import: bits.Symbol, |
| 65 | linker_tlv: bits.Symbol, | ||
| 66 | }; | 65 | }; |
| 67 | }; | 66 | }; |
| 68 | 67 | ||
| ... | @@ -326,18 +325,6 @@ fn reloc(lower: *Lower, target: Reloc.Target) Immediate { | ... | @@ -326,18 +325,6 @@ fn reloc(lower: *Lower, target: Reloc.Target) Immediate { |
| 326 | return Immediate.s(0); | 325 | return Immediate.s(0); |
| 327 | } | 326 | } |
| 328 | 327 | ||
| 329 | fn 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 | |||
| 335 | fn 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 | |||
| 341 | fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void { | 328 | fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void { |
| 342 | const is_obj_or_static_lib = switch (lower.output_mode) { | 329 | const is_obj_or_static_lib = switch (lower.output_mode) { |
| 343 | .Exe => false, | 330 | .Exe => false, |
| ... | @@ -359,80 +346,115 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) | ... | @@ -359,80 +346,115 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) |
| 359 | assert(mem_op.sib.disp == 0); | 346 | assert(mem_op.sib.disp == 0); |
| 360 | assert(mem_op.sib.scale_index.scale == 0); | 347 | assert(mem_op.sib.scale_index.scale == 0); |
| 361 | 348 | ||
| 362 | if (isTls(sym, lower.bin_file)) { | 349 | if (lower.bin_file.cast(link.File.Elf)) |elf_file| { |
| 363 | // TODO handle extern TLS vars, i.e., emit GD model | 350 | const sym_index = elf_file.zigObjectPtr().?.symbol(sym.sym_index); |
| 364 | if (lower.pic) { | 351 | const elf_sym = elf_file.symbol(sym_index); |
| 365 | // Here, we currently assume local dynamic TLS vars, and so | 352 | |
| 366 | // we emit LD model. | 353 | if (elf_sym.flags.is_tls) { |
| 367 | _ = lower.reloc(.{ .linker_tlsld = sym }); | 354 | // TODO handle extern TLS vars, i.e., emit GD model |
| 368 | lower.result_insts[lower.result_insts_len] = | 355 | if (lower.pic) { |
| 369 | try Instruction.new(.none, .lea, &[_]Operand{ | 356 | // Here, we currently assume local dynamic TLS vars, and so |
| 370 | .{ .reg = .rdi }, | 357 | // we emit LD model. |
| 371 | .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }, | 358 | _ = lower.reloc(.{ .linker_tlsld = sym }); |
| 372 | }); | 359 | lower.result_insts[lower.result_insts_len] = |
| 373 | lower.result_insts_len += 1; | 360 | try Instruction.new(.none, .lea, &[_]Operand{ |
| 374 | if (lower.bin_file.cast(link.File.Elf)) |elf_file| { | 361 | .{ .reg = .rdi }, |
| 362 | .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }, | ||
| 363 | }); | ||
| 364 | lower.result_insts_len += 1; | ||
| 375 | _ = lower.reloc(.{ .linker_extern_fn = .{ | 365 | _ = lower.reloc(.{ .linker_extern_fn = .{ |
| 376 | .atom_index = sym.atom_index, | 366 | .atom_index = sym.atom_index, |
| 377 | .sym_index = try elf_file.getGlobalSymbol("__tls_get_addr", null), | 367 | .sym_index = try elf_file.getGlobalSymbol("__tls_get_addr", null), |
| 378 | } }); | 368 | } }); |
| 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 | }) }; | ||
| 379 | } | 394 | } |
| 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 }); | ||
| 380 | lower.result_insts[lower.result_insts_len] = | 431 | lower.result_insts[lower.result_insts_len] = |
| 381 | try Instruction.new(.none, .call, &[_]Operand{ | 432 | try Instruction.new(.none, .mov, &[_]Operand{ |
| 382 | .{ .imm = Immediate.s(0) }, | 433 | .{ .reg = .rdi }, |
| 434 | .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }, | ||
| 383 | }); | 435 | }); |
| 384 | lower.result_insts_len += 1; | 436 | 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. | ||
| 393 | lower.result_insts[lower.result_insts_len] = | 437 | lower.result_insts[lower.result_insts_len] = |
| 394 | try Instruction.new(.none, .mov, &[_]Operand{ | 438 | try Instruction.new(.none, .call, &[_]Operand{ |
| 395 | .{ .reg = .rax }, | 439 | .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .rdi } }) }, |
| 396 | .{ .mem = Memory.sib(.qword, .{ .base = .{ .reg = .fs } }) }, | ||
| 397 | }); | 440 | }); |
| 398 | lower.result_insts_len += 1; | 441 | lower.result_insts_len += 1; |
| 399 | _ = lower.reloc(.{ .linker_reloc = sym }); | 442 | emit_mnemonic = .mov; |
| 400 | emit_mnemonic = .lea; | 443 | break :op .{ .reg = .rax }; |
| 401 | break :op .{ .mem = Memory.sib(mem_op.sib.ptr_size, .{ | ||
| 402 | .base = .{ .reg = .rax }, | ||
| 403 | .disp = std.math.minInt(i32), | ||
| 404 | }) }; | ||
| 405 | } | 444 | } |
| 406 | } | ||
| 407 | 445 | ||
| 408 | _ = lower.reloc(.{ .linker_reloc = sym }); | 446 | _ = lower.reloc(.{ .linker_reloc = sym }); |
| 409 | break :op if (lower.pic) switch (mnemonic) { | 447 | break :op switch (mnemonic) { |
| 410 | .lea => { | 448 | .lea => { |
| 411 | break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; | 449 | break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; |
| 412 | }, | 450 | }, |
| 413 | .mov => { | 451 | .mov => { |
| 414 | if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) emit_mnemonic = .lea; | 452 | if (is_obj_or_static_lib and macho_sym.flags.needs_zig_got) emit_mnemonic = .lea; |
| 415 | break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; | 453 | break :op .{ .mem = Memory.rip(mem_op.sib.ptr_size, 0) }; |
| 416 | }, | 454 | }, |
| 417 | else => unreachable, | 455 | else => unreachable, |
| 418 | } else switch (mnemonic) { | 456 | }; |
| 419 | .call => break :op if (is_obj_or_static_lib and needsZigGot(sym, lower.bin_file)) .{ | 457 | } |
| 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 | }; | ||
| 436 | }, | 458 | }, |
| 437 | }, | 459 | }, |
| 438 | }; | 460 | }; |
| ... | @@ -584,14 +606,13 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { | ... | @@ -584,14 +606,13 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void { |
| 584 | .extern_fn_reloc => &.{ | 606 | .extern_fn_reloc => &.{ |
| 585 | .{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc }) }, | 607 | .{ .imm = lower.reloc(.{ .linker_extern_fn = inst.data.reloc }) }, |
| 586 | }, | 608 | }, |
| 587 | .got_reloc, .direct_reloc, .import_reloc, .tlv_reloc => ops: { | 609 | .got_reloc, .direct_reloc, .import_reloc => ops: { |
| 588 | const reg = inst.data.rx.r1; | 610 | const reg = inst.data.rx.r1; |
| 589 | const extra = lower.mir.extraData(bits.Symbol, inst.data.rx.payload).data; | 611 | const extra = lower.mir.extraData(bits.Symbol, inst.data.rx.payload).data; |
| 590 | _ = lower.reloc(switch (inst.ops) { | 612 | _ = lower.reloc(switch (inst.ops) { |
| 591 | .got_reloc => .{ .linker_got = extra }, | 613 | .got_reloc => .{ .linker_got = extra }, |
| 592 | .direct_reloc => .{ .linker_direct = extra }, | 614 | .direct_reloc => .{ .linker_direct = extra }, |
| 593 | .import_reloc => .{ .linker_import = extra }, | 615 | .import_reloc => .{ .linker_import = extra }, |
| 594 | .tlv_reloc => .{ .linker_tlv = extra }, | ||
| 595 | else => unreachable, | 616 | else => unreachable, |
| 596 | }); | 617 | }); |
| 597 | break :ops &.{ | 618 | break :ops &.{ |
src/codegen.zig+17-10| ... | @@ -985,19 +985,21 @@ fn genDeclRef( | ... | @@ -985,19 +985,21 @@ fn genDeclRef( |
| 985 | return GenResult.mcv(.{ .load_symbol = sym.esym_index }); | 985 | return GenResult.mcv(.{ .load_symbol = sym.esym_index }); |
| 986 | } else if (lf.cast(link.File.MachO)) |macho_file| { | 986 | } else if (lf.cast(link.File.MachO)) |macho_file| { |
| 987 | if (is_extern) { | 987 | if (is_extern) { |
| 988 | // TODO make this part of getGlobalSymbol | ||
| 989 | const name = zcu.intern_pool.stringToSlice(decl.name); | 988 | const name = zcu.intern_pool.stringToSlice(decl.name); |
| 990 | const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name}); | 989 | const lib_name = if (decl.getOwnedVariable(zcu)) |ov| |
| 991 | defer gpa.free(sym_name); | 990 | zcu.intern_pool.stringToSliceUnwrap(ov.lib_name) |
| 992 | const global_index = try macho_file.addUndefined(sym_name, .{ .add_got = true }); | 991 | else |
| 993 | return GenResult.mcv(.{ .load_got = link.File.MachO.global_symbol_bit | global_index }); | 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 }); | ||
| 994 | } | 996 | } |
| 995 | const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index); | 997 | const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index); |
| 996 | const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; | 998 | const sym = macho_file.getSymbol(sym_index); |
| 997 | if (is_threadlocal) { | 999 | if (is_threadlocal) { |
| 998 | return GenResult.mcv(.{ .load_tlv = sym_index }); | 1000 | return GenResult.mcv(.{ .load_tlv = sym.nlist_idx }); |
| 999 | } | 1001 | } |
| 1000 | return GenResult.mcv(.{ .load_got = sym_index }); | 1002 | return GenResult.mcv(.{ .load_symbol = sym.nlist_idx }); |
| 1001 | } else if (lf.cast(link.File.Coff)) |coff_file| { | 1003 | } else if (lf.cast(link.File.Coff)) |coff_file| { |
| 1002 | if (is_extern) { | 1004 | if (is_extern) { |
| 1003 | const name = zcu.intern_pool.stringToSlice(decl.name); | 1005 | const name = zcu.intern_pool.stringToSlice(decl.name); |
| ... | @@ -1041,7 +1043,12 @@ fn genUnnamedConst( | ... | @@ -1041,7 +1043,12 @@ fn genUnnamedConst( |
| 1041 | const local = elf_file.symbol(local_sym_index); | 1043 | const local = elf_file.symbol(local_sym_index); |
| 1042 | return GenResult.mcv(.{ .load_symbol = local.esym_index }); | 1044 | return GenResult.mcv(.{ .load_symbol = local.esym_index }); |
| 1043 | }, | 1045 | }, |
| 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 => { | ||
| 1045 | return GenResult.mcv(.{ .load_direct = local_sym_index }); | 1052 | return GenResult.mcv(.{ .load_direct = local_sym_index }); |
| 1046 | }, | 1053 | }, |
| 1047 | .plan9 => { | 1054 | .plan9 => { |
src/link.zig+1| ... | @@ -133,6 +133,7 @@ pub const File = struct { | ... | @@ -133,6 +133,7 @@ pub const File = struct { |
| 133 | 133 | ||
| 134 | // TODO: remove this. libraries are resolved by the frontend. | 134 | // TODO: remove this. libraries are resolved by the frontend. |
| 135 | lib_dirs: []const []const u8, | 135 | lib_dirs: []const []const u8, |
| 136 | framework_dirs: []const []const u8, | ||
| 136 | rpath_list: []const []const u8, | 137 | rpath_list: []const []const u8, |
| 137 | 138 | ||
| 138 | /// (Zig compiler development) Enable dumping of linker's state as JSON. | 139 | /// (Zig compiler development) Enable dumping of linker's state as JSON. |
src/link/MachO.zig+3430-4799| ... | @@ -1,5 +1,4 @@ | ... | @@ -1,5 +1,4 @@ |
| 1 | base: File, | 1 | base: link.File, |
| 2 | entry_name: ?[]const u8, | ||
| 3 | 2 | ||
| 4 | /// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. | 3 | /// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path. |
| 5 | llvm_object: ?*LlvmObject = null, | 4 | llvm_object: ?*LlvmObject = null, |
| ... | @@ -7,7 +6,28 @@ llvm_object: ?*LlvmObject = null, | ... | @@ -7,7 +6,28 @@ llvm_object: ?*LlvmObject = null, |
| 7 | /// Debug symbols bundle (or dSym). | 6 | /// Debug symbols bundle (or dSym). |
| 8 | d_sym: ?DebugSymbols = null, | 7 | d_sym: ?DebugSymbols = null, |
| 9 | 8 | ||
| 10 | mode: 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. | ||
| 12 | files: std.MultiArrayList(File.Entry) = .{}, | ||
| 13 | zig_object: ?File.Index = null, | ||
| 14 | internal_object: ?File.Index = null, | ||
| 15 | objects: std.ArrayListUnmanaged(File.Index) = .{}, | ||
| 16 | dylibs: std.ArrayListUnmanaged(File.Index) = .{}, | ||
| 17 | |||
| 18 | segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{}, | ||
| 19 | sections: std.MultiArrayList(Section) = .{}, | ||
| 20 | |||
| 21 | symbols: std.ArrayListUnmanaged(Symbol) = .{}, | ||
| 22 | symbols_extra: std.ArrayListUnmanaged(u32) = .{}, | ||
| 23 | symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 24 | globals: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{}, | ||
| 25 | /// This table will be populated after `scanRelocs` has run. | ||
| 26 | /// Key is symbol index. | ||
| 27 | undefs: std.AutoHashMapUnmanaged(Symbol.Index, std.ArrayListUnmanaged(Atom.Index)) = .{}, | ||
| 28 | /// Global symbols we need to resolve for the link to succeed. | ||
| 29 | undefined_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 30 | boundary_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 11 | 31 | ||
| 12 | dyld_info_cmd: macho.dyld_info_command = .{}, | 32 | dyld_info_cmd: macho.dyld_info_command = .{}, |
| 13 | symtab_cmd: macho.symtab_command = .{}, | 33 | symtab_cmd: macho.symtab_command = .{}, |
| ... | @@ -17,133 +37,90 @@ data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE }, | ... | @@ -17,133 +37,90 @@ data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE }, |
| 17 | uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 }, | 37 | uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 }, |
| 18 | codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE }, | 38 | codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE }, |
| 19 | 39 | ||
| 20 | objects: std.ArrayListUnmanaged(Object) = .{}, | 40 | pagezero_seg_index: ?u8 = null, |
| 21 | archives: std.ArrayListUnmanaged(Archive) = .{}, | 41 | text_seg_index: ?u8 = null, |
| 22 | dylibs: std.ArrayListUnmanaged(Dylib) = .{}, | 42 | linkedit_seg_index: ?u8 = null, |
| 23 | dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, | 43 | text_sect_index: ?u8 = null, |
| 24 | referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{}, | 44 | data_sect_index: ?u8 = null, |
| 25 | 45 | got_sect_index: ?u8 = null, | |
| 26 | segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{}, | 46 | stubs_sect_index: ?u8 = null, |
| 27 | sections: std.MultiArrayList(Section) = .{}, | 47 | stubs_helper_sect_index: ?u8 = null, |
| 28 | 48 | la_symbol_ptr_sect_index: ?u8 = null, | |
| 29 | pagezero_segment_cmd_index: ?u8 = null, | 49 | tlv_ptr_sect_index: ?u8 = null, |
| 30 | header_segment_cmd_index: ?u8 = null, | 50 | eh_frame_sect_index: ?u8 = null, |
| 31 | text_segment_cmd_index: ?u8 = null, | 51 | unwind_info_sect_index: ?u8 = null, |
| 32 | data_const_segment_cmd_index: ?u8 = null, | 52 | objc_stubs_sect_index: ?u8 = null, |
| 33 | data_segment_cmd_index: ?u8 = null, | 53 | |
| 34 | linkedit_segment_cmd_index: ?u8 = null, | 54 | mh_execute_header_index: ?Symbol.Index = null, |
| 35 | 55 | mh_dylib_header_index: ?Symbol.Index = null, | |
| 36 | text_section_index: ?u8 = null, | 56 | dyld_private_index: ?Symbol.Index = null, |
| 37 | data_const_section_index: ?u8 = null, | 57 | dyld_stub_binder_index: ?Symbol.Index = null, |
| 38 | data_section_index: ?u8 = null, | 58 | dso_handle_index: ?Symbol.Index = null, |
| 39 | bss_section_index: ?u8 = null, | 59 | objc_msg_send_index: ?Symbol.Index = null, |
| 40 | thread_vars_section_index: ?u8 = null, | 60 | entry_index: ?Symbol.Index = null, |
| 41 | thread_data_section_index: ?u8 = null, | ||
| 42 | thread_bss_section_index: ?u8 = null, | ||
| 43 | eh_frame_section_index: ?u8 = null, | ||
| 44 | unwind_info_section_index: ?u8 = null, | ||
| 45 | stubs_section_index: ?u8 = null, | ||
| 46 | stub_helper_section_index: ?u8 = null, | ||
| 47 | got_section_index: ?u8 = null, | ||
| 48 | la_symbol_ptr_section_index: ?u8 = null, | ||
| 49 | tlv_ptr_section_index: ?u8 = null, | ||
| 50 | |||
| 51 | locals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | ||
| 52 | globals: std.ArrayListUnmanaged(SymbolWithLoc) = .{}, | ||
| 53 | resolver: std.StringHashMapUnmanaged(u32) = .{}, | ||
| 54 | unresolved: std.AutoArrayHashMapUnmanaged(u32, void) = .{}, | ||
| 55 | |||
| 56 | locals_free_list: std.ArrayListUnmanaged(u32) = .{}, | ||
| 57 | globals_free_list: std.ArrayListUnmanaged(u32) = .{}, | ||
| 58 | |||
| 59 | dyld_stub_binder_index: ?u32 = null, | ||
| 60 | dyld_private_atom_index: ?Atom.Index = null, | ||
| 61 | |||
| 62 | strtab: StringTable = .{}, | ||
| 63 | |||
| 64 | got_table: TableSection(SymbolWithLoc) = .{}, | ||
| 65 | stub_table: TableSection(SymbolWithLoc) = .{}, | ||
| 66 | tlv_ptr_table: TableSection(SymbolWithLoc) = .{}, | ||
| 67 | |||
| 68 | thunk_table: std.AutoHashMapUnmanaged(Atom.Index, thunks.Thunk.Index) = .{}, | ||
| 69 | thunks: std.ArrayListUnmanaged(thunks.Thunk) = .{}, | ||
| 70 | |||
| 71 | segment_table_dirty: bool = false, | ||
| 72 | got_table_count_dirty: bool = false, | ||
| 73 | got_table_contents_dirty: bool = false, | ||
| 74 | stub_table_count_dirty: bool = false, | ||
| 75 | stub_table_contents_dirty: bool = false, | ||
| 76 | stub_helper_preamble_allocated: bool = false, | ||
| 77 | 61 | ||
| 78 | /// List of atoms that are either synthetic or map directly to the Zig source program. | 62 | /// List of atoms that are either synthetic or map directly to the Zig source program. |
| 79 | atoms: std.ArrayListUnmanaged(Atom) = .{}, | 63 | atoms: std.ArrayListUnmanaged(Atom) = .{}, |
| 80 | 64 | thunks: std.ArrayListUnmanaged(Thunk) = .{}, | |
| 81 | /// Table of atoms indexed by the symbol index. | 65 | unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record) = .{}, |
| 82 | atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{}, | 66 | |
| 83 | 67 | /// String interning table | |
| 84 | /// Table of unnamed constants associated with a parent `Decl`. | 68 | strings: StringTable = .{}, |
| 85 | /// We store them here so that we can free the constants whenever the `Decl` | 69 | |
| 86 | /// needs updating or is freed. | 70 | /// Output synthetic sections |
| 87 | /// | 71 | symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, |
| 88 | /// For example, | 72 | strtab: std.ArrayListUnmanaged(u8) = .{}, |
| 89 | /// | 73 | indsymtab: Indsymtab = .{}, |
| 90 | /// ```zig | 74 | got: GotSection = .{}, |
| 91 | /// const Foo = struct{ | 75 | zig_got: ZigGotSection = .{}, |
| 92 | /// a: u8, | 76 | stubs: StubsSection = .{}, |
| 93 | /// }; | 77 | stubs_helper: StubsHelperSection = .{}, |
| 94 | /// | 78 | objc_stubs: ObjcStubsSection = .{}, |
| 95 | /// pub fn main() void { | 79 | la_symbol_ptr: LaSymbolPtrSection = .{}, |
| 96 | /// var foo = Foo{ .a = 1 }; | 80 | tlv_ptr: TlvPtrSection = .{}, |
| 97 | /// _ = foo; | 81 | rebase: RebaseSection = .{}, |
| 98 | /// } | 82 | bind: BindSection = .{}, |
| 99 | /// ``` | 83 | weak_bind: WeakBindSection = .{}, |
| 100 | /// | 84 | lazy_bind: LazyBindSection = .{}, |
| 101 | /// value assigned to label `foo` is an unnamed constant belonging/associated | 85 | export_trie: ExportTrieSection = .{}, |
| 102 | /// with `Decl` `main`, and lives as long as that `Decl`. | 86 | unwind_info: UnwindInfo = .{}, |
| 103 | unnamed_const_atoms: UnnamedConstTable = .{}, | 87 | |
| 104 | anon_decls: AnonDeclTable = .{}, | 88 | /// Tracked loadable segments during incremental linking. |
| 105 | 89 | zig_text_seg_index: ?u8 = null, | |
| 106 | /// A table of relocations indexed by the owning them `Atom`. | 90 | zig_got_seg_index: ?u8 = null, |
| 107 | /// Note that once we refactor `Atom`'s lifetime and ownership rules, | 91 | zig_const_seg_index: ?u8 = null, |
| 108 | /// this will be a table indexed by index into the list of Atoms. | 92 | zig_data_seg_index: ?u8 = null, |
| 109 | relocs: RelocationTable = .{}, | 93 | zig_bss_seg_index: ?u8 = null, |
| 110 | /// TODO I do not have time to make this right but this will go once | 94 | |
| 111 | /// MachO linker is rewritten more-or-less to feature the same resolution | 95 | /// Tracked section headers with incremental updates to Zig object. |
| 112 | /// mechanism as the ELF linker. | 96 | zig_text_sect_index: ?u8 = null, |
| 113 | actions: ActionTable = .{}, | 97 | zig_got_sect_index: ?u8 = null, |
| 114 | 98 | zig_const_sect_index: ?u8 = null, | |
| 115 | /// A table of rebases indexed by the owning them `Atom`. | 99 | zig_data_sect_index: ?u8 = null, |
| 116 | /// Note that once we refactor `Atom`'s lifetime and ownership rules, | 100 | zig_bss_sect_index: ?u8 = null, |
| 117 | /// this will be a table indexed by index into the list of Atoms. | 101 | |
| 118 | rebases: RebaseTable = .{}, | 102 | has_tlv: bool = false, |
| 119 | 103 | binds_to_weak: bool = false, | |
| 120 | /// A table of bindings indexed by the owning them `Atom`. | 104 | weak_defines: bool = false, |
| 121 | /// Note that once we refactor `Atom`'s lifetime and ownership rules, | 105 | |
| 122 | /// this will be a table indexed by index into the list of Atoms. | 106 | /// Options |
| 123 | bindings: BindingTable = .{}, | 107 | /// SDK layout |
| 124 | |||
| 125 | /// Table of tracked LazySymbols. | ||
| 126 | lazy_syms: LazySymbolTable = .{}, | ||
| 127 | |||
| 128 | /// Table of tracked Decls. | ||
| 129 | decls: DeclTable = .{}, | ||
| 130 | |||
| 131 | /// Table of threadlocal variables descriptors. | ||
| 132 | /// They are emitted in the `__thread_vars` section. | ||
| 133 | tlv_table: TlvSymbolTable = .{}, | ||
| 134 | |||
| 135 | /// Hot-code swapping state. | ||
| 136 | hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, | ||
| 137 | |||
| 138 | sdk_layout: ?SdkLayout, | 108 | sdk_layout: ?SdkLayout, |
| 139 | /// Size of the __PAGEZERO segment. | 109 | /// Size of the __PAGEZERO segment. |
| 140 | pagezero_vmsize: u64, | 110 | pagezero_size: ?u64, |
| 141 | /// Minimum space for future expansion of the load commands. | 111 | /// Minimum space for future expansion of the load commands. |
| 142 | headerpad_size: u32, | 112 | headerpad_size: ?u32, |
| 143 | /// Set enough space as if all paths were MATPATHLEN. | 113 | /// Set enough space as if all paths were MATPATHLEN. |
| 144 | headerpad_max_install_names: bool, | 114 | headerpad_max_install_names: bool, |
| 145 | /// Remove dylibs that are unreachable by the entry point or exported symbols. | 115 | /// Remove dylibs that are unreachable by the entry point or exported symbols. |
| 146 | dead_strip_dylibs: bool, | 116 | dead_strip_dylibs: bool, |
| 117 | /// Treatment of undefined symbols | ||
| 118 | undefined_treatment: UndefinedTreatment, | ||
| 119 | /// Resolved list of library search directories | ||
| 120 | lib_dirs: []const []const u8, | ||
| 121 | /// Resolved list of framework search directories | ||
| 122 | framework_dirs: []const []const u8, | ||
| 123 | /// List of input frameworks | ||
| 147 | frameworks: []const Framework, | 124 | frameworks: []const Framework, |
| 148 | /// Install name for the dylib. | 125 | /// Install name for the dylib. |
| 149 | /// TODO: unify with soname | 126 | /// TODO: unify with soname |
| ... | @@ -151,6 +128,18 @@ install_name: ?[]const u8, | ... | @@ -151,6 +128,18 @@ install_name: ?[]const u8, |
| 151 | /// Path to entitlements file. | 128 | /// Path to entitlements file. |
| 152 | entitlements: ?[]const u8, | 129 | entitlements: ?[]const u8, |
| 153 | compatibility_version: ?std.SemanticVersion, | 130 | compatibility_version: ?std.SemanticVersion, |
| 131 | /// Entry name | ||
| 132 | entry_name: ?[]const u8, | ||
| 133 | platform: Platform, | ||
| 134 | sdk_version: ?std.SemanticVersion, | ||
| 135 | /// When set to true, the linker will hoist all dylibs including system dependent dylibs. | ||
| 136 | no_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 | ||
| 139 | force_load_objc: bool = true, | ||
| 140 | |||
| 141 | /// Hot-code swapping state. | ||
| 142 | hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, | ||
| 154 | 143 | ||
| 155 | /// When adding a new field, remember to update `hashAddFrameworks`. | 144 | /// When adding a new field, remember to update `hashAddFrameworks`. |
| 156 | pub const Framework = struct { | 145 | pub const Framework = struct { |
| ... | @@ -167,14 +156,6 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void { | ... | @@ -167,14 +156,6 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void { |
| 167 | } | 156 | } |
| 168 | } | 157 | } |
| 169 | 158 | ||
| 170 | /// The filesystem layout of darwin SDK elements. | ||
| 171 | pub 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 | |||
| 178 | pub fn createEmpty( | 159 | pub fn createEmpty( |
| 179 | arena: Allocator, | 160 | arena: Allocator, |
| 180 | comp: *Compilation, | 161 | comp: *Compilation, |
| ... | @@ -183,27 +164,22 @@ pub fn createEmpty( | ... | @@ -183,27 +164,22 @@ pub fn createEmpty( |
| 183 | ) !*MachO { | 164 | ) !*MachO { |
| 184 | const target = comp.root_mod.resolved_target.result; | 165 | const target = comp.root_mod.resolved_target.result; |
| 185 | assert(target.ofmt == .macho); | 166 | assert(target.ofmt == .macho); |
| 186 | const use_llvm = comp.config.use_llvm; | 167 | |
| 187 | const gpa = comp.gpa; | 168 | const gpa = comp.gpa; |
| 169 | const use_llvm = comp.config.use_llvm; | ||
| 170 | const opt_zcu = comp.module; | ||
| 188 | const optimize_mode = comp.root_mod.optimize_mode; | 171 | const optimize_mode = comp.root_mod.optimize_mode; |
| 189 | const output_mode = comp.config.output_mode; | 172 | const output_mode = comp.config.output_mode; |
| 190 | const link_mode = comp.config.link_mode; | 173 | const link_mode = comp.config.link_mode; |
| 191 | 174 | ||
| 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". | ||
| 200 | // If using LLVM to generate the object file for the zig compilation unit, | 175 | // If using LLVM to generate the object file for the zig compilation unit, |
| 201 | // we need a place to put the object file so that it can be subsequently | 176 | // we need a place to put the object file so that it can be subsequently |
| 202 | // handled. | 177 | // handled. |
| 203 | const zcu_object_sub_path = if (mode != .zld and !use_llvm) | 178 | const zcu_object_sub_path = if (!use_llvm) |
| 204 | null | 179 | null |
| 205 | else | 180 | else |
| 206 | try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); | 181 | try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path}); |
| 182 | const allow_shlib_undefined = options.allow_shlib_undefined orelse false; | ||
| 207 | 183 | ||
| 208 | const self = try arena.create(MachO); | 184 | const self = try arena.create(MachO); |
| 209 | self.* = .{ | 185 | self.* = .{ |
| ... | @@ -215,15 +191,14 @@ pub fn createEmpty( | ... | @@ -215,15 +191,14 @@ pub fn createEmpty( |
| 215 | .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), | 191 | .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), |
| 216 | .print_gc_sections = options.print_gc_sections, | 192 | .print_gc_sections = options.print_gc_sections, |
| 217 | .stack_size = options.stack_size orelse 16777216, | 193 | .stack_size = options.stack_size orelse 16777216, |
| 218 | .allow_shlib_undefined = options.allow_shlib_undefined orelse false, | 194 | .allow_shlib_undefined = allow_shlib_undefined, |
| 219 | .file = null, | 195 | .file = null, |
| 220 | .disable_lld_caching = options.disable_lld_caching, | 196 | .disable_lld_caching = options.disable_lld_caching, |
| 221 | .build_id = options.build_id, | 197 | .build_id = options.build_id, |
| 222 | .rpath_list = options.rpath_list, | 198 | .rpath_list = options.rpath_list, |
| 223 | }, | 199 | }, |
| 224 | .mode = mode, | 200 | .pagezero_size = options.pagezero_size, |
| 225 | .pagezero_vmsize = options.pagezero_size orelse default_pagezero_vmsize, | 201 | .headerpad_size = options.headerpad_size, |
| 226 | .headerpad_size = options.headerpad_size orelse default_headerpad_size, | ||
| 227 | .headerpad_max_install_names = options.headerpad_max_install_names, | 202 | .headerpad_max_install_names = options.headerpad_max_install_names, |
| 228 | .dead_strip_dylibs = options.dead_strip_dylibs, | 203 | .dead_strip_dylibs = options.dead_strip_dylibs, |
| 229 | .sdk_layout = options.darwin_sdk_layout, | 204 | .sdk_layout = options.darwin_sdk_layout, |
| ... | @@ -237,68 +212,77 @@ pub fn createEmpty( | ... | @@ -237,68 +212,77 @@ pub fn createEmpty( |
| 237 | .enabled => default_entry_symbol_name, | 212 | .enabled => default_entry_symbol_name, |
| 238 | .named => |name| name, | 213 | .named => |name| name, |
| 239 | }, | 214 | }, |
| 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, | ||
| 240 | }; | 220 | }; |
| 241 | if (use_llvm and comp.config.have_zcu) { | 221 | if (use_llvm and comp.config.have_zcu) { |
| 242 | self.llvm_object = try LlvmObject.create(arena, comp); | 222 | self.llvm_object = try LlvmObject.create(arena, comp); |
| 243 | } | 223 | } |
| 244 | errdefer self.base.destroy(); | 224 | errdefer self.base.destroy(); |
| 245 | 225 | ||
| 246 | log.debug("selected linker mode '{s}'", .{@tagName(self.mode)}); | 226 | self.base.file = try emit.directory.handle.createFile(emit.sub_path, .{ |
| 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, .{ | ||
| 254 | .truncate = true, | 227 | .truncate = true, |
| 255 | .read = true, | 228 | .read = true, |
| 256 | .mode = link.File.determineMode(false, output_mode, link_mode), | 229 | .mode = link.File.determineMode(false, output_mode, link_mode), |
| 257 | }); | 230 | }); |
| 258 | self.base.file = file; | ||
| 259 | 231 | ||
| 260 | if (comp.config.debug_format != .strip and comp.module != null) { | 232 | // Append null file |
| 261 | // Create dSYM bundle. | 233 | try self.files.append(gpa, .null); |
| 262 | log.debug("creating {s}.dSYM bundle", .{emit.sub_path}); | 234 | // Atom at index 0 is reserved as null atom |
| 263 | 235 | try self.atoms.append(gpa, .{}); | |
| 264 | const d_sym_path = try std.fmt.allocPrint( | 236 | // Append empty string to string tables |
| 265 | arena, | 237 | try self.strings.buffer.append(gpa, 0); |
| 266 | "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF", | 238 | try self.strtab.append(gpa, 0); |
| 267 | .{emit.sub_path}, | 239 | // Append null symbols |
| 268 | ); | 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 | }); | ||
| 269 | 258 | ||
| 270 | var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{}); | 259 | // TODO init dwarf |
| 271 | defer d_sym_bundle.close(); | ||
| 272 | 260 | ||
| 273 | const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{ | 261 | // if (comp.config.debug_format != .strip) { |
| 274 | .truncate = false, | 262 | // // Create dSYM bundle. |
| 275 | .read = true, | 263 | // log.debug("creating {s}.dSYM bundle", .{emit.sub_path}); |
| 276 | }); | ||
| 277 | 264 | ||
| 278 | self.d_sym = .{ | 265 | // const d_sym_path = try std.fmt.allocPrint( |
| 279 | .allocator = gpa, | 266 | // arena, |
| 280 | .dwarf = link.File.Dwarf.init(&self.base, .dwarf32), | 267 | // "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF", |
| 281 | .file = d_sym_file, | 268 | // .{emit.sub_path}, |
| 282 | }; | 269 | // ); |
| 283 | } | ||
| 284 | 270 | ||
| 285 | // Index 0 is always a null symbol. | 271 | // var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{}); |
| 286 | try self.locals.append(gpa, .{ | 272 | // defer d_sym_bundle.close(); |
| 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); | ||
| 294 | 273 | ||
| 295 | try self.populateMissingMetadata(.{ | 274 | // const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{ |
| 296 | .symbol_count_hint = options.symbol_count_hint, | 275 | // .truncate = false, |
| 297 | .program_code_size_hint = options.program_code_size_hint, | 276 | // .read = true, |
| 298 | }); | 277 | // }); |
| 299 | 278 | ||
| 300 | if (self.d_sym) |*d_sym| { | 279 | // self.d_sym = .{ |
| 301 | try d_sym.populateMissingMetadata(self); | 280 | // .allocator = gpa, |
| 281 | // .dwarf = link.File.Dwarf.init(&self.base, .dwarf32), | ||
| 282 | // .file = d_sym_file, | ||
| 283 | // }; | ||
| 284 | // } | ||
| 285 | } | ||
| 302 | } | 286 | } |
| 303 | 287 | ||
| 304 | return self; | 288 | return self; |
| ... | @@ -315,27 +299,75 @@ pub fn open( | ... | @@ -315,27 +299,75 @@ pub fn open( |
| 315 | return createEmpty(arena, comp, emit, options); | 299 | return createEmpty(arena, comp, emit, options); |
| 316 | } | 300 | } |
| 317 | 301 | ||
| 318 | pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { | 302 | pub fn deinit(self: *MachO) void { |
| 319 | const comp = self.base.comp; | 303 | const gpa = self.base.comp.gpa; |
| 320 | const gpa = comp.gpa; | ||
| 321 | const output_mode = comp.config.output_mode; | ||
| 322 | 304 | ||
| 323 | if (output_mode == .Lib and comp.config.link_mode == .Static) { | 305 | if (self.llvm_object) |llvm_object| llvm_object.deinit(); |
| 324 | if (build_options.have_llvm) { | 306 | |
| 325 | return self.base.linkAsArchive(arena, prog_node); | 307 | if (self.d_sym) |*d_sym| { |
| 326 | } else { | 308 | d_sym.deinit(); |
| 327 | try comp.link_errors.ensureUnusedCapacity(gpa, 1); | 309 | } |
| 328 | comp.link_errors.appendAssumeCapacity(.{ | 310 | |
| 329 | .msg = try gpa.dupe(u8, "TODO: non-LLVM archiver for MachO object files"), | 311 | for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) { |
| 330 | }); | 312 | .null => {}, |
| 331 | return error.FlushFailure; | 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); | ||
| 332 | } | 336 | } |
| 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); | ||
| 333 | } | 360 | } |
| 361 | self.thunks.deinit(gpa); | ||
| 362 | self.unwind_records.deinit(gpa); | ||
| 363 | } | ||
| 334 | 364 | ||
| 335 | switch (self.mode) { | 365 | pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { |
| 336 | .zld => return zld.linkWithZld(self, arena, prog_node), | 366 | // TODO: I think this is just a temp and can be removed once we can emit static archives |
| 337 | .incremental => return self.flushModule(arena, prog_node), | 367 | if (self.base.isStaticLib() and build_options.have_llvm) { |
| 368 | return self.base.linkAsArchive(arena, prog_node); | ||
| 338 | } | 369 | } |
| 370 | try self.flushModule(arena, prog_node); | ||
| 339 | } | 371 | } |
| 340 | 372 | ||
| 341 | pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { | 373 | pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { |
| ... | @@ -347,278 +379,499 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node | ... | @@ -347,278 +379,499 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node |
| 347 | 379 | ||
| 348 | if (self.llvm_object) |llvm_object| { | 380 | if (self.llvm_object) |llvm_object| { |
| 349 | try self.base.emitLlvmObject(arena, llvm_object, prog_node); | 381 | 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; | ||
| 351 | } | 384 | } |
| 352 | 385 | ||
| 353 | var sub_prog_node = prog_node.start("MachO Flush", 0); | 386 | var sub_prog_node = prog_node.start("MachO Flush", 0); |
| 354 | sub_prog_node.activate(); | 387 | sub_prog_node.activate(); |
| 355 | defer sub_prog_node.end(); | 388 | defer sub_prog_node.end(); |
| 356 | 389 | ||
| 357 | const output_mode = comp.config.output_mode; | ||
| 358 | const module = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented; | ||
| 359 | const target = comp.root_mod.resolved_target.result; | 390 | 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; | ||
| 360 | 401 | ||
| 361 | if (self.lazy_syms.getPtr(.none)) |metadata| { | 402 | // --verbose-link |
| 362 | // Most lazy symbols can be updated on first use, but | 403 | if (comp.verbose_link) try self.dumpArgv(comp); |
| 363 | // anyerror needs to wait for everything to be flushed. | 404 | |
| 364 | if (metadata.text_state != .unused) self.updateLazySymbolAtom( | 405 | if (self.getZigObject()) |zo| try zo.flushModule(self); |
| 365 | File.LazySymbol.initDecl(.code, null, module), | 406 | if (self.base.isStaticLib()) return self.flushStaticLib(comp, module_obj_path); |
| 366 | metadata.text_atom, | 407 | if (self.base.isObject()) return relocatable.flush(self, comp, module_obj_path); |
| 367 | self.text_section_index.?, | 408 | |
| 368 | ) catch |err| return switch (err) { | 409 | var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); |
| 369 | error.CodegenFail => error.FlushFailure, | 410 | defer positionals.deinit(); |
| 370 | else => |e| e, | 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 | ), | ||
| 371 | }; | 439 | }; |
| 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); | ||
| 388 | } | 440 | } |
| 389 | 441 | ||
| 390 | var libs = std.StringArrayHashMap(link.SystemLib).init(arena); | 442 | var system_libs = std.ArrayList(SystemLib).init(gpa); |
| 391 | try self.resolveLibSystem(arena, comp, &libs); | 443 | defer system_libs.deinit(); |
| 392 | |||
| 393 | self.base.releaseLock(); | ||
| 394 | 444 | ||
| 395 | for (self.dylibs.items) |*dylib| { | 445 | // libs |
| 396 | dylib.deinit(gpa); | 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 | }); | ||
| 397 | } | 453 | } |
| 398 | self.dylibs.clearRetainingCapacity(); | ||
| 399 | self.dylibs_map.clearRetainingCapacity(); | ||
| 400 | self.referenced_dylibs.clearRetainingCapacity(); | ||
| 401 | 454 | ||
| 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 | } | ||
| 403 | 464 | ||
| 404 | for (libs.keys(), libs.values()) |path, lib| { | 465 | // libc++ dep |
| 405 | const in_file = try std.fs.cwd().openFile(path, .{}); | 466 | if (comp.config.link_libcpp) { |
| 406 | defer in_file.close(); | 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 | } | ||
| 407 | 471 | ||
| 408 | var parse_ctx = ParseErrorCtx.init(gpa); | 472 | // libc/libSystem dep |
| 409 | defer parse_ctx.deinit(); | 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 | }; | ||
| 410 | 477 | ||
| 411 | self.parseLibrary( | 478 | for (system_libs.items) |lib| { |
| 412 | in_file, | 479 | self.parseLibrary(lib, false) catch |err| switch (err) { |
| 413 | path, | 480 | error.MalformedArchive, |
| 414 | lib, | 481 | error.MalformedDylib, |
| 415 | false, | 482 | error.InvalidCpuArch, |
| 416 | false, | 483 | => continue, // already reported |
| 417 | null, | 484 | error.UnknownFileType => try self.reportParseError(lib.path, "unknown file type for a library", .{}), |
| 418 | &dependent_libs, | 485 | else => |e| try self.reportParseError( |
| 419 | &parse_ctx, | 486 | lib.path, |
| 420 | ) catch |err| try self.handleAndReportParseError(path, err, &parse_ctx); | 487 | "unexpected error: parsing library failed with error {s}", |
| 488 | .{@errorName(e)}, | ||
| 489 | ), | ||
| 490 | }; | ||
| 421 | } | 491 | } |
| 422 | 492 | ||
| 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 | } | ||
| 424 | 514 | ||
| 425 | try self.resolveSymbols(); | 515 | if (comp.link_errors.items.len > 0) return error.FlushFailure; |
| 426 | 516 | ||
| 427 | if (self.getEntryPoint() == null) { | 517 | for (self.dylibs.items) |index| { |
| 428 | comp.link_error_flags.no_entry_point_found = true; | 518 | self.getFile(index).?.dylib.umbrella = index; |
| 429 | } | ||
| 430 | if (self.unresolved.count() > 0) { | ||
| 431 | try self.reportUndefined(); | ||
| 432 | return error.FlushFailure; | ||
| 433 | } | 519 | } |
| 434 | 520 | ||
| 435 | { | 521 | if (self.dylibs.items.len > 0) { |
| 436 | var it = self.actions.iterator(); | 522 | self.parseDependentDylibs() catch |err| { |
| 437 | while (it.next()) |entry| { | 523 | switch (err) { |
| 438 | const global_index = entry.key_ptr.*; | 524 | error.MissingLibraryDependencies => {}, |
| 439 | const global = self.globals.items[global_index]; | 525 | else => |e| try self.reportUnexpectedError( |
| 440 | const flags = entry.value_ptr.*; | 526 | "unexpected error while parsing dependent libraries: {s}", |
| 441 | if (flags.add_got) try self.addGotEntry(global); | 527 | .{@errorName(e)}, |
| 442 | if (flags.add_stub) try self.addStubEntry(global); | 528 | ), |
| 443 | } | 529 | } |
| 530 | return error.FlushFailure; | ||
| 531 | }; | ||
| 444 | } | 532 | } |
| 445 | 533 | ||
| 446 | try self.createDyldPrivateAtom(); | 534 | for (self.dylibs.items) |index| { |
| 447 | try self.writeStubHelperPreamble(); | 535 | const dylib = self.getFile(index).?.dylib; |
| 448 | 536 | if (!dylib.explicit and !dylib.hoisted) continue; | |
| 449 | if (output_mode == .Exe and self.getEntryPoint() != null) { | 537 | try dylib.initSymbols(self); |
| 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 | } | ||
| 457 | } | 538 | } |
| 458 | 539 | ||
| 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 | } | ||
| 460 | 545 | ||
| 461 | for (self.relocs.keys()) |atom_index| { | 546 | try self.addUndefinedGlobals(); |
| 462 | const relocs = self.relocs.get(atom_index).?; | 547 | try self.resolveSymbols(); |
| 463 | const needs_update = for (relocs.items) |reloc| { | 548 | try self.resolveSyntheticSymbols(); |
| 464 | if (reloc.dirty) break true; | ||
| 465 | } else false; | ||
| 466 | 549 | ||
| 467 | if (!needs_update) continue; | 550 | try self.convertTentativeDefinitions(); |
| 551 | try self.createObjcSections(); | ||
| 552 | try self.claimUnresolved(); | ||
| 468 | 553 | ||
| 469 | const atom = self.getAtom(atom_index); | 554 | if (self.base.gc_sections) { |
| 470 | const sym = atom.getSymbol(self); | 555 | try dead_strip.gcAtoms(self); |
| 471 | const section = self.sections.get(sym.n_sect - 1).header; | 556 | } |
| 472 | const file_offset = section.offset + sym.n_value - section.addr; | ||
| 473 | 557 | ||
| 474 | var code = std.ArrayList(u8).init(gpa); | 558 | self.checkDuplicates() catch |err| switch (err) { |
| 475 | defer code.deinit(); | 559 | error.HasDuplicates => return error.FlushFailure, |
| 476 | try code.resize(math.cast(usize, atom.size) orelse return error.Overflow); | 560 | else => |e| { |
| 561 | try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{}); | ||
| 562 | return e; | ||
| 563 | }, | ||
| 564 | }; | ||
| 477 | 565 | ||
| 478 | const amt = try self.base.file.?.preadAll(code.items, file_offset); | 566 | try self.markImportsAndExports(); |
| 479 | if (amt != code.items.len) return error.InputOutput; | 567 | self.deadStripDylibs(); |
| 480 | 568 | ||
| 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); | ||
| 482 | } | 572 | } |
| 483 | 573 | ||
| 484 | // Update GOT if it got moved in memory. | 574 | self.scanRelocs() catch |err| switch (err) { |
| 485 | if (self.got_table_contents_dirty) { | 575 | error.HasUndefinedSymbols => return error.FlushFailure, |
| 486 | for (self.got_table.entries.items, 0..) |entry, i| { | 576 | else => |e| { |
| 487 | if (!self.got_table.lookup.contains(entry)) continue; | 577 | try self.reportUnexpectedError("unexpected error while scanning relocations", .{}); |
| 488 | // TODO: write all in one go rather than incrementally. | 578 | return e; |
| 489 | try self.writeOffsetTableEntry(i); | 579 | }, |
| 490 | } | 580 | }; |
| 491 | self.got_table_contents_dirty = false; | ||
| 492 | } | ||
| 493 | 581 | ||
| 494 | // Update stubs if we moved any section in memory. | 582 | try self.initOutputSections(); |
| 495 | // TODO: we probably don't need to update all sections if only one got moved. | 583 | try self.initSyntheticSections(); |
| 496 | if (self.stub_table_contents_dirty) { | 584 | try self.sortSections(); |
| 497 | for (self.stub_table.entries.items, 0..) |entry, i| { | 585 | try self.addAtomsToSections(); |
| 498 | if (!self.stub_table.lookup.contains(entry)) continue; | 586 | try self.calcSectionSizes(); |
| 499 | // TODO: write all in one go rather than incrementally. | 587 | try self.generateUnwindInfo(); |
| 500 | try self.writeStubTableEntry(i); | 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); | ||
| 501 | } | 638 | } |
| 502 | self.stub_table_contents_dirty = false; | ||
| 503 | } | ||
| 504 | 639 | ||
| 505 | if (build_options.enable_logging) { | 640 | if (has_resolve_error) return error.FlushFailure; |
| 506 | self.logSymtab(); | ||
| 507 | self.logSections(); | ||
| 508 | self.logAtoms(); | ||
| 509 | } | 641 | } |
| 510 | 642 | ||
| 511 | try self.writeLinkeditSegmentData(); | 643 | self.writeAtoms() catch |err| switch (err) { |
| 512 | 644 | error.ResolveFailed => return error.FlushFailure, | |
| 513 | var codesig: ?CodeSignature = if (self.requiresCodeSignature()) blk: { | 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: { | ||
| 514 | // Preallocate space for the code signature. | 671 | // Preallocate space for the code signature. |
| 515 | // We need to do this at this stage so that we have the load commands with proper values | 672 | // We need to do this at this stage so that we have the load commands with proper values |
| 516 | // written out to the file. | 673 | // written out to the file. |
| 517 | // The most important here is to have the correct vm and filesize of the __LINKEDIT segment | 674 | // The most important here is to have the correct vm and filesize of the __LINKEDIT segment |
| 518 | // where the code signature goes into. | 675 | // where the code signature goes into. |
| 519 | var codesig = CodeSignature.init(getPageSize(target.cpu.arch)); | 676 | var codesig = CodeSignature.init(self.getPageSize()); |
| 520 | codesig.code_directory.ident = self.base.emit.sub_path; | 677 | codesig.code_directory.ident = fs.path.basename(full_out_path); |
| 521 | if (self.entitlements) |path| { | 678 | if (self.entitlements) |path| try codesig.addEntitlements(gpa, path); |
| 522 | try codesig.addEntitlements(gpa, path); | ||
| 523 | } | ||
| 524 | try self.writeCodeSignaturePadding(&codesig); | 679 | try self.writeCodeSignaturePadding(&codesig); |
| 525 | break :blk codesig; | 680 | break :blk codesig; |
| 526 | } else null; | 681 | } else null; |
| 527 | defer if (codesig) |*csig| csig.deinit(gpa); | 682 | defer if (codesig) |*csig| csig.deinit(gpa); |
| 528 | 683 | ||
| 529 | // Write load commands | 684 | self.getLinkeditSegment().vmsize = mem.alignForward( |
| 530 | var lc_buffer = std.ArrayList(u8).init(arena); | 685 | u64, |
| 531 | const lc_writer = lc_buffer.writer(); | 686 | self.getLinkeditSegment().filesize, |
| 532 | 687 | self.getPageSize(), | |
| 533 | try self.writeSegmentHeaders(lc_writer); | 688 | ); |
| 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; | ||
| 552 | 689 | ||
| 553 | try lc_writer.writeStruct(macho.entry_point_command{ | 690 | const ncmds, const sizeofcmds, const uuid_cmd_offset = try self.writeLoadCommands(); |
| 554 | .entryoff = @as(u32, @intCast(addr - seg.vmaddr)), | 691 | try self.writeHeader(ncmds, sizeofcmds); |
| 555 | .stacksize = self.base.stack_size, | 692 | try self.writeUuid(uuid_cmd_offset, self.requiresCodeSig()); |
| 556 | }); | 693 | |
| 557 | }, | 694 | if (codesig) |*csig| { |
| 558 | .Lib => if (comp.config.link_mode == .Dynamic) { | 695 | try self.writeCodeSignature(csig); // code signing always comes last |
| 559 | try load_commands.writeDylibIdLC(self, lc_writer); | 696 | const emit = self.base.emit; |
| 560 | }, | 697 | try invalidateKernelCache(emit.directory.handle, emit.sub_path); |
| 561 | else => {}, | ||
| 562 | } | 698 | } |
| 699 | } | ||
| 563 | 700 | ||
| 564 | try load_commands.writeRpathLCs(self, lc_writer); | 701 | /// --verbose-link output |
| 565 | try lc_writer.writeStruct(macho.source_version_command{ | 702 | fn dumpArgv(self: *MachO, comp: *Compilation) !void { |
| 566 | .version = 0, | 703 | const gpa = self.base.comp.gpa; |
| 567 | }); | 704 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); |
| 568 | { | 705 | defer arena_allocator.deinit(); |
| 569 | const platform = Platform.fromTarget(target); | 706 | const arena = arena_allocator.allocator(); |
| 570 | const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(self); | 707 | |
| 571 | if (platform.isBuildVersionCompatible()) { | 708 | const directory = self.base.emit.directory; |
| 572 | try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer); | 709 | const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); |
| 573 | } else if (platform.isVersionMinCompatible()) { | 710 | const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: { |
| 574 | try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer); | 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; | ||
| 575 | } | 715 | } |
| 576 | } | 716 | } else null; |
| 577 | 717 | ||
| 578 | const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len)); | 718 | var argv = std.ArrayList([]const u8).init(arena); |
| 579 | try lc_writer.writeStruct(self.uuid_cmd); | ||
| 580 | 719 | ||
| 581 | try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer); | 720 | try argv.append("zig"); |
| 582 | 721 | ||
| 583 | if (codesig != null) { | 722 | if (self.base.isStaticLib()) { |
| 584 | try lc_writer.writeStruct(self.codesig_cmd); | 723 | try argv.append("ar"); |
| 724 | } else { | ||
| 725 | try argv.append("ld"); | ||
| 585 | } | 726 | } |
| 586 | 727 | ||
| 587 | const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); | 728 | if (self.base.isObject()) { |
| 588 | try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); | 729 | try argv.append("-r"); |
| 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); | ||
| 596 | } | 730 | } |
| 597 | 731 | ||
| 598 | if (self.d_sym) |*d_sym| { | 732 | try argv.append("-o"); |
| 599 | // Flush debug symbols bundle. | 733 | try argv.append(full_out_path); |
| 600 | try d_sym.flushModule(self); | ||
| 601 | } | ||
| 602 | } | ||
| 603 | 734 | ||
| 604 | /// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. | 735 | if (self.base.isRelocatable()) { |
| 605 | /// Any change to the binary will effectively invalidate the kernel's cache | 736 | for (comp.objects) |obj| { |
| 606 | /// resulting in a SIGKILL on each subsequent run. Since when doing incremental | 737 | try argv.append(obj.path); |
| 607 | /// linking we're modifying a binary in-place, this will end up with the kernel | 738 | } |
| 608 | /// killing it on every subsequent run. To circumvent it, we will copy the file | 739 | |
| 609 | /// into a new inode, remove the original file, and rename the copy to match | 740 | for (comp.c_object_table.keys()) |key| { |
| 610 | /// the original file. This is super messy, but there doesn't seem any other | 741 | try argv.append(key.status.success.object_path); |
| 611 | /// way to please the XNU. | 742 | } |
| 612 | pub fn invalidateKernelCache(dir: std.fs.Dir, sub_path: []const u8) !void { | 743 | |
| 613 | if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) { | 744 | if (module_obj_path) |p| { |
| 614 | try dir.copyFile(sub_path, dir, sub_path, .{}); | 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 | } | ||
| 615 | } | 862 | } |
| 863 | |||
| 864 | Compilation.dump_argv(argv.items); | ||
| 616 | } | 865 | } |
| 617 | 866 | ||
| 618 | inline fn conformUuid(out: *[Md5.digest_length]u8) void { | 867 | fn flushStaticLib(self: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { |
| 619 | // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats | 868 | _ = comp; |
| 620 | out[6] = (out[6] & 0x0F) | (3 << 4); | 869 | _ = module_obj_path; |
| 621 | out[8] = (out[8] & 0x3F) | 0x80; | 870 | |
| 871 | var err = try self.addErrorWithNotes(0); | ||
| 872 | try err.addMsg(self, "TODO implement flushStaticLib", .{}); | ||
| 873 | |||
| 874 | return error.FlushFailure; | ||
| 622 | } | 875 | } |
| 623 | 876 | ||
| 624 | pub fn resolveLibSystem( | 877 | pub fn resolveLibSystem( |
| ... | @@ -643,13 +896,12 @@ pub fn resolveLibSystem( | ... | @@ -643,13 +896,12 @@ pub fn resolveLibSystem( |
| 643 | }; | 896 | }; |
| 644 | 897 | ||
| 645 | try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); | 898 | try self.reportMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); |
| 646 | return; | 899 | return error.MissingLibSystem; |
| 647 | } | 900 | } |
| 648 | 901 | ||
| 649 | const libsystem_path = try arena.dupe(u8, test_path.items); | 902 | const libsystem_path = try arena.dupe(u8, test_path.items); |
| 650 | try out_libs.put(libsystem_path, .{ | 903 | try out_libs.append(.{ |
| 651 | .needed = true, | 904 | .needed = true, |
| 652 | .weak = false, | ||
| 653 | .path = libsystem_path, | 905 | .path = libsystem_path, |
| 654 | }); | 906 | }); |
| 655 | } | 907 | } |
| ... | @@ -700,3288 +952,1819 @@ fn accessLibPath( | ... | @@ -700,3288 +952,1819 @@ fn accessLibPath( |
| 700 | } | 952 | } |
| 701 | 953 | ||
| 702 | const ParseError = error{ | 954 | const ParseError = error{ |
| 703 | UnknownFileType, | 955 | MalformedObject, |
| 956 | MalformedArchive, | ||
| 957 | MalformedDylib, | ||
| 958 | MalformedTbd, | ||
| 959 | NotLibStub, | ||
| 960 | InvalidCpuArch, | ||
| 704 | InvalidTarget, | 961 | InvalidTarget, |
| 705 | InvalidTargetFatLibrary, | 962 | InvalidTargetFatLibrary, |
| 706 | DylibAlreadyExists, | ||
| 707 | IncompatibleDylibVersion, | 963 | IncompatibleDylibVersion, |
| 708 | OutOfMemory, | 964 | OutOfMemory, |
| 709 | Overflow, | 965 | Overflow, |
| 710 | InputOutput, | 966 | InputOutput, |
| 711 | MalformedArchive, | ||
| 712 | NotLibStub, | ||
| 713 | EndOfStream, | 967 | EndOfStream, |
| 714 | FileSystem, | 968 | FileSystem, |
| 715 | NotSupported, | 969 | NotSupported, |
| 970 | Unhandled, | ||
| 971 | UnknownFileType, | ||
| 716 | } || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError || tapi.TapiError; | 972 | } || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError || tapi.TapiError; |
| 717 | 973 | ||
| 718 | pub fn parsePositional( | 974 | pub fn parsePositional(self: *MachO, path: []const u8, must_link: bool) ParseError!void { |
| 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 { | ||
| 726 | const tracy = trace(@src()); | 975 | const tracy = trace(@src()); |
| 727 | defer tracy.end(); | 976 | 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 | } | ||
| 728 | 983 | ||
| 729 | if (Object.isObject(file)) { | 984 | fn parseLibrary(self: *MachO, lib: SystemLib, must_link: bool) ParseError!void { |
| 730 | try self.parseObject(file, path, ctx); | 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); | ||
| 731 | } else { | 998 | } else { |
| 732 | try self.parseLibrary(file, path, .{ | 999 | _ = self.parseTbd(lib, true) catch |err| switch (err) { |
| 733 | .path = null, | 1000 | error.MalformedTbd => return error.UnknownFileType, |
| 734 | .needed = false, | 1001 | else => |e| return e, |
| 735 | .weak = false, | 1002 | }; |
| 736 | }, must_link, false, null, dependent_libs, ctx); | ||
| 737 | } | 1003 | } |
| 738 | } | 1004 | } |
| 739 | 1005 | ||
| 740 | fn parseObject( | 1006 | fn parseObject(self: *MachO, path: []const u8) ParseError!void { |
| 741 | self: *MachO, | ||
| 742 | file: std.fs.File, | ||
| 743 | path: []const u8, | ||
| 744 | ctx: *ParseErrorCtx, | ||
| 745 | ) ParseError!void { | ||
| 746 | const tracy = trace(@src()); | 1007 | const tracy = trace(@src()); |
| 747 | defer tracy.end(); | 1008 | defer tracy.end(); |
| 748 | 1009 | ||
| 749 | const gpa = self.base.comp.gpa; | 1010 | 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(); | ||
| 751 | const mtime: u64 = mtime: { | 1013 | const mtime: u64 = mtime: { |
| 752 | const stat = file.stat() catch break :mtime 0; | 1014 | const stat = file.stat() catch break :mtime 0; |
| 753 | break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); | 1015 | break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); |
| 754 | }; | 1016 | }; |
| 755 | const file_stat = try file.stat(); | 1017 | const data = try file.readToEndAlloc(gpa, std.math.maxInt(u32)); |
| 756 | const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow; | 1018 | const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); |
| 757 | const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null); | 1019 | self.files.set(index, .{ .object = .{ |
| 758 | 1020 | .path = try gpa.dupe(u8, path), | |
| 759 | var object = Object{ | ||
| 760 | .name = try gpa.dupe(u8, path), | ||
| 761 | .mtime = mtime, | 1021 | .mtime = mtime, |
| 762 | .contents = contents, | 1022 | .data = data, |
| 763 | }; | 1023 | .index = index, |
| 764 | errdefer object.deinit(gpa); | 1024 | } }); |
| 765 | try object.parse(gpa); | 1025 | try self.objects.append(gpa, index); |
| 766 | 1026 | ||
| 767 | const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) { | 1027 | const object = self.getFile(index).?.object; |
| 768 | macho.CPU_TYPE_ARM64 => .aarch64, | 1028 | try object.parse(self); |
| 769 | macho.CPU_TYPE_X86_64 => .x86_64, | 1029 | } |
| 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); | ||
| 775 | 1030 | ||
| 776 | if (this_cpu_arch != detected_cpu_arch or | 1031 | fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch { |
| 777 | (detected_platform != null and !detected_platform.?.eqlTarget(this_platform))) | 1032 | var buffer: [2]fat.Arch = undefined; |
| 778 | { | 1033 | const fat_archs = try fat.parseArchs(path, &buffer); |
| 779 | const platform = detected_platform orelse this_platform; | 1034 | const cpu_arch = self.getTarget().cpu.arch; |
| 780 | try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch)); | 1035 | for (fat_archs) |arch| { |
| 781 | return error.InvalidTarget; | 1036 | if (arch.tag == cpu_arch) return arch; |
| 782 | } | 1037 | } |
| 783 | 1038 | try self.reportParseError(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); | |
| 784 | try self.objects.append(gpa, object); | 1039 | return error.InvalidCpuArch; |
| 785 | } | 1040 | } |
| 786 | 1041 | ||
| 787 | pub fn parseLibrary( | 1042 | fn parseArchive(self: *MachO, lib: SystemLib, must_link: bool, fat_arch: ?fat.Arch) ParseError!void { |
| 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 { | ||
| 798 | const tracy = trace(@src()); | 1043 | const tracy = trace(@src()); |
| 799 | defer tracy.end(); | 1044 | defer tracy.end(); |
| 800 | 1045 | ||
| 801 | const target = self.base.comp.root_mod.resolved_target.result; | 1046 | const gpa = self.base.comp.gpa; |
| 802 | 1047 | ||
| 803 | if (fat.isFatLibrary(file)) { | 1048 | const file = try std.fs.cwd().openFile(lib.path, .{}); |
| 804 | const offset = try self.parseFatLibrary(file, target.cpu.arch, ctx); | 1049 | defer file.close(); |
| 805 | try file.seekTo(offset); | 1050 | |
| 806 | 1051 | const data = if (fat_arch) |arch| blk: { | |
| 807 | if (Archive.isArchive(file, offset)) { | 1052 | try file.seekTo(arch.offset); |
| 808 | try self.parseArchive(path, offset, must_link, ctx); | 1053 | const data = try gpa.alloc(u8, arch.size); |
| 809 | } else if (Dylib.isDylib(file, offset)) { | 1054 | const nread = try file.readAll(data); |
| 810 | try self.parseDylib(file, path, offset, dependent_libs, .{ | 1055 | if (nread != arch.size) return error.InputOutput; |
| 811 | .needed = lib.needed, | 1056 | break :blk data; |
| 812 | .weak = lib.weak, | 1057 | } else try file.readToEndAlloc(gpa, std.math.maxInt(u32)); |
| 813 | .dependent = is_dependent, | 1058 | |
| 814 | .reexport_info = reexport_info, | 1059 | var archive = Archive{ .path = try gpa.dupe(u8, lib.path), .data = data }; |
| 815 | }, ctx); | 1060 | defer archive.deinit(gpa); |
| 816 | } else return error.UnknownFileType; | 1061 | try archive.parse(self); |
| 817 | } else if (Archive.isArchive(file, 0)) { | 1062 | |
| 818 | try self.parseArchive(path, 0, must_link, ctx); | 1063 | var has_parse_error = false; |
| 819 | } else if (Dylib.isDylib(file, 0)) { | 1064 | for (archive.objects.items) |extracted| { |
| 820 | try self.parseDylib(file, path, 0, dependent_libs, .{ | 1065 | const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); |
| 821 | .needed = lib.needed, | 1066 | self.files.set(index, .{ .object = extracted }); |
| 822 | .weak = lib.weak, | 1067 | const object = &self.files.items(.data)[index].object; |
| 823 | .dependent = is_dependent, | 1068 | object.index = index; |
| 824 | .reexport_info = reexport_info, | 1069 | object.alive = must_link or lib.needed; // TODO: or self.options.all_load; |
| 825 | }, ctx); | 1070 | object.hidden = lib.hidden; |
| 826 | } else { | 1071 | object.parse(self) catch |err| switch (err) { |
| 827 | self.parseLibStub(file, path, dependent_libs, .{ | 1072 | error.MalformedObject, |
| 828 | .needed = lib.needed, | 1073 | error.InvalidCpuArch, |
| 829 | .weak = lib.weak, | 1074 | error.InvalidTarget, |
| 830 | .dependent = is_dependent, | 1075 | => has_parse_error = true, |
| 831 | .reexport_info = reexport_info, | ||
| 832 | }, ctx) catch |err| switch (err) { | ||
| 833 | error.NotLibStub, error.UnexpectedToken => return error.UnknownFileType, | ||
| 834 | else => |e| return e, | 1076 | else => |e| return e, |
| 835 | }; | 1077 | }; |
| 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()); | ||
| 836 | } | 1083 | } |
| 1084 | if (has_parse_error) return error.MalformedArchive; | ||
| 837 | } | 1085 | } |
| 838 | 1086 | ||
| 839 | pub fn parseFatLibrary( | 1087 | fn parseDylib(self: *MachO, lib: SystemLib, explicit: bool, fat_arch: ?fat.Arch) ParseError!File.Index { |
| 840 | self: *MachO, | 1088 | const tracy = trace(@src()); |
| 841 | file: std.fs.File, | 1089 | defer tracy.end(); |
| 842 | cpu_arch: std.Target.Cpu.Arch, | 1090 | |
| 843 | ctx: *ParseErrorCtx, | ||
| 844 | ) ParseError!u64 { | ||
| 845 | const gpa = self.base.comp.gpa; | 1091 | const gpa = self.base.comp.gpa; |
| 846 | 1092 | ||
| 847 | const fat_archs = try fat.parseArchs(gpa, file); | 1093 | const file = try std.fs.cwd().openFile(lib.path, .{}); |
| 848 | defer gpa.free(fat_archs); | 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); | ||
| 849 | 1118 | ||
| 850 | const offset = for (fat_archs) |arch| { | 1119 | return index; |
| 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; | ||
| 860 | } | 1120 | } |
| 861 | 1121 | ||
| 862 | fn parseArchive( | 1122 | fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index { |
| 863 | self: *MachO, | 1123 | const tracy = trace(@src()); |
| 864 | path: []const u8, | 1124 | defer tracy.end(); |
| 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); | ||
| 883 | 1125 | ||
| 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(); | ||
| 885 | 1129 | ||
| 886 | // Verify arch and platform | 1130 | var lib_stub = LibStub.loadFromFile(gpa, file) catch return error.MalformedTbd; // TODO actually handle different errors |
| 887 | if (archive.toc.values().len > 0) { | 1131 | defer lib_stub.deinit(); |
| 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); | ||
| 893 | 1132 | ||
| 894 | const detected_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) { | 1133 | const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); |
| 895 | macho.CPU_TYPE_ARM64 => .aarch64, | 1134 | self.files.set(index, .{ .dylib = .{ |
| 896 | macho.CPU_TYPE_X86_64 => .x86_64, | 1135 | .path = try gpa.dupe(u8, lib.path), |
| 897 | else => unreachable, | 1136 | .data = &[0]u8{}, |
| 898 | }; | 1137 | .index = index, |
| 899 | const detected_platform = object.getPlatform(); | 1138 | .needed = lib.needed, |
| 900 | const this_cpu_arch = target.cpu.arch; | 1139 | .weak = lib.weak, |
| 901 | const this_platform = Platform.fromTarget(target); | 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); | ||
| 902 | 1146 | ||
| 903 | if (this_cpu_arch != detected_cpu_arch or | 1147 | return index; |
| 904 | (detected_platform != null and !detected_platform.?.eqlTarget(this_platform))) | 1148 | } |
| 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 | } | ||
| 911 | 1149 | ||
| 912 | if (must_link) { | 1150 | /// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final |
| 913 | // Get all offsets from the ToC | 1151 | /// image unless overriden by -no_implicit_dylibs. |
| 914 | var offsets = std.AutoArrayHashMap(u32, void).init(gpa); | 1152 | fn isHoisted(self: *MachO, install_name: []const u8) bool { |
| 915 | defer offsets.deinit(); | 1153 | if (self.no_implicit_dylibs) return true; |
| 916 | for (archive.toc.values()) |offs| { | 1154 | if (std.fs.path.dirname(install_name)) |dirname| { |
| 917 | for (offs.items) |off| { | 1155 | if (mem.startsWith(u8, dirname, "/usr/lib")) return true; |
| 918 | _ = try offsets.getOrPut(off); | 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; | ||
| 919 | } | 1160 | } |
| 920 | } | 1161 | } |
| 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); | ||
| 927 | } | 1162 | } |
| 1163 | return false; | ||
| 928 | } | 1164 | } |
| 929 | 1165 | ||
| 930 | pub const DylibReExportInfo = struct { | 1166 | fn accessPath(path: []const u8) !bool { |
| 931 | id: Dylib.Id, | 1167 | std.fs.cwd().access(path, .{}) catch |err| switch (err) { |
| 932 | parent: u16, | 1168 | error.FileNotFound => return false, |
| 933 | }; | 1169 | else => |e| return e, |
| 934 | |||
| 935 | const DylibOpts = struct { | ||
| 936 | reexport_info: ?DylibReExportInfo = null, | ||
| 937 | dependent: bool = false, | ||
| 938 | needed: bool = false, | ||
| 939 | weak: bool = false, | ||
| 940 | }; | ||
| 941 | |||
| 942 | fn 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, | ||
| 974 | }; | 1170 | }; |
| 975 | const detected_platform = dylib.getPlatform(contents); | 1171 | return true; |
| 976 | const this_cpu_arch = target.cpu.arch; | 1172 | } |
| 977 | const this_platform = Platform.fromTarget(target); | ||
| 978 | 1173 | ||
| 979 | if (this_cpu_arch != detected_cpu_arch or | 1174 | fn resolveLib(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 { |
| 980 | (detected_platform != null and !detected_platform.?.eqlTarget(this_platform))) | 1175 | const path = try std.fmt.allocPrint(arena, "lib{s}", .{name}); |
| 981 | { | 1176 | for (search_dirs) |dir| { |
| 982 | const platform = detected_platform orelse this_platform; | 1177 | for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| { |
| 983 | try ctx.detected_targets.append(try platform.allocPrintTarget(ctx.arena(), detected_cpu_arch)); | 1178 | const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext }); |
| 984 | return error.InvalidTarget; | 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 | } | ||
| 985 | } | 1182 | } |
| 1183 | return null; | ||
| 1184 | } | ||
| 986 | 1185 | ||
| 987 | try self.addDylib(dylib, dylib_options, ctx); | 1186 | fn 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; | ||
| 988 | } | 1197 | } |
| 989 | 1198 | ||
| 990 | fn parseLibStub( | 1199 | fn parseDependentDylibs(self: *MachO) !void { |
| 991 | self: *MachO, | 1200 | const tracy = trace(@src()); |
| 992 | file: std.fs.File, | 1201 | defer tracy.end(); |
| 993 | path: []const u8, | 1202 | |
| 994 | dependent_libs: anytype, | ||
| 995 | dylib_options: DylibOpts, | ||
| 996 | ctx: *ParseErrorCtx, | ||
| 997 | ) ParseError!void { | ||
| 998 | const gpa = self.base.comp.gpa; | 1203 | 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 | } | ||
| 1000 | 1251 | ||
| 1001 | var lib_stub = try LibStub.loadFromFile(gpa, file); | 1252 | if (std.fs.path.isAbsolute(id.name)) { |
| 1002 | defer lib_stub.deinit(); | 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 | } | ||
| 1003 | 1262 | ||
| 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 | } | ||
| 1005 | 1279 | ||
| 1006 | // Verify target | 1280 | var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; |
| 1007 | { | 1281 | const full_path = std.fs.realpath(id.name, &buffer) catch { |
| 1008 | var matcher = try Dylib.TargetMatcher.init(gpa, target); | 1282 | dependents.appendAssumeCapacity(0); |
| 1009 | defer matcher.deinit(); | 1283 | continue; |
| 1010 | 1284 | }; | |
| 1011 | const first_tbd = lib_stub.inner[0]; | 1285 | break :full_path full_path; |
| 1012 | const targets = try first_tbd.targets(gpa); | 1286 | }; |
| 1013 | defer { | 1287 | const lib = SystemLib{ |
| 1014 | for (targets) |t| gpa.free(t); | 1288 | .path = full_path, |
| 1015 | gpa.free(targets); | 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); | ||
| 1016 | } | 1308 | } |
| 1017 | if (!matcher.matchesTarget(targets)) { | 1309 | |
| 1018 | try ctx.detected_targets.ensureUnusedCapacity(targets.len); | 1310 | const dylib = self.getFile(dylib_index).?.dylib; |
| 1019 | for (targets) |t| { | 1311 | for (dylib.dependents.items, dependents.items) |id, file_index| { |
| 1020 | ctx.detected_targets.appendAssumeCapacity(try ctx.arena().dupe(u8, t)); | 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; | ||
| 1021 | } | 1336 | } |
| 1022 | return error.InvalidTarget; | ||
| 1023 | } | 1337 | } |
| 1024 | } | 1338 | } |
| 1025 | 1339 | ||
| 1026 | var dylib = Dylib{ .path = try gpa.dupe(u8, path), .weak = dylib_options.weak }; | 1340 | if (has_errors) return error.MissingLibraryDependencies; |
| 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); | ||
| 1039 | } | 1341 | } |
| 1040 | 1342 | ||
| 1041 | fn addDylib(self: *MachO, dylib: Dylib, dylib_options: DylibOpts, ctx: *ParseErrorCtx) ParseError!void { | 1343 | pub fn addUndefinedGlobals(self: *MachO) !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 | |||
| 1053 | const gpa = self.base.comp.gpa; | 1344 | 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; | ||
| 1056 | 1345 | ||
| 1057 | gop.value_ptr.* = @as(u16, @intCast(self.dylibs.items.len)); | 1346 | try self.undefined_symbols.ensureUnusedCapacity(gpa, self.base.comp.force_undefined_symbols.keys().len); |
| 1058 | try self.dylibs.append(gpa, dylib); | 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 | } | ||
| 1059 | 1352 | ||
| 1060 | const should_link_dylib_even_if_unreachable = blk: { | 1353 | if (!self.base.isDynLib() and self.entry_name != null) { |
| 1061 | if (self.dead_strip_dylibs and !dylib_options.needed) break :blk false; | 1354 | const off = try self.strings.insert(gpa, self.entry_name.?); |
| 1062 | break :blk !(dylib_options.dependent or self.referenced_dylibs.contains(gop.value_ptr.*)); | 1355 | const gop = try self.getOrCreateGlobal(off); |
| 1063 | }; | 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 | } | ||
| 1064 | 1364 | ||
| 1065 | if (should_link_dylib_even_if_unreachable) { | 1365 | { |
| 1066 | try self.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {}); | 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; | ||
| 1067 | } | 1369 | } |
| 1068 | } | 1370 | } |
| 1069 | 1371 | ||
| 1070 | pub 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. | ||
| 1379 | pub fn resolveSymbols(self: *MachO) !void { | ||
| 1071 | const tracy = trace(@src()); | 1380 | const tracy = trace(@src()); |
| 1072 | defer tracy.end(); | 1381 | defer tracy.end(); |
| 1073 | 1382 | ||
| 1074 | // At this point, we can now parse dependents of dylibs preserving the inclusion order of: | 1383 | // Resolve symbols in the ZigObject. For now, we assume that it's always live. |
| 1075 | // 1) anything on the linker line is parsed first | 1384 | if (self.getZigObject()) |zo| zo.asFile().resolveSymbols(self); |
| 1076 | // 2) afterwards, we parse dependents of the included dylibs | 1385 | // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). |
| 1077 | // TODO this should not be performed if the user specifies `-flat_namespace` flag. | 1386 | for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self); |
| 1078 | // See ld64 manpages. | 1387 | for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self); |
| 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; | ||
| 1086 | 1388 | ||
| 1087 | const parent = &self.dylibs.items[dep_id.parent]; | 1389 | // Mark live objects. |
| 1088 | const weak = parent.weak; | 1390 | self.markLive(); |
| 1089 | const dirname = fs.path.dirname(dep_id.id.name) orelse ""; | ||
| 1090 | const stem = fs.path.stem(dep_id.id.name); | ||
| 1091 | 1391 | ||
| 1092 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | 1392 | // Reset state of all globals after marking live objects. |
| 1093 | defer arena_allocator.deinit(); | 1393 | if (self.getZigObject()) |zo| zo.asFile().resetGlobals(self); |
| 1094 | const arena = arena_allocator.allocator(); | 1394 | for (self.objects.items) |index| self.getFile(index).?.resetGlobals(self); |
| 1395 | for (self.dylibs.items) |index| self.getFile(index).?.resetGlobals(self); | ||
| 1095 | 1396 | ||
| 1096 | var test_path = std.ArrayList(u8).init(arena); | 1397 | // Prune dead objects. |
| 1097 | var checked_paths = std.ArrayList([]const u8).init(arena); | 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 | } | ||
| 1098 | 1405 | ||
| 1099 | success: { | 1406 | // Re-resolve the symbols. |
| 1100 | if (comp.sysroot) |root| { | 1407 | if (self.getZigObject()) |zo| zo.resolveSymbols(self); |
| 1101 | const dir = try fs.path.join(arena, &[_][]const u8{ root, dirname }); | 1408 | for (self.objects.items) |index| self.getFile(index).?.resolveSymbols(self); |
| 1102 | if (try accessLibPath(gpa, &test_path, &checked_paths, dir, stem)) break :success; | 1409 | for (self.dylibs.items) |index| self.getFile(index).?.resolveSymbols(self); |
| 1103 | } | 1410 | } |
| 1104 | 1411 | ||
| 1105 | if (try accessLibPath(gpa, &test_path, &checked_paths, dirname, stem)) break :success; | 1412 | fn markLive(self: *MachO) void { |
| 1413 | const tracy = trace(@src()); | ||
| 1414 | defer tracy.end(); | ||
| 1106 | 1415 | ||
| 1107 | try self.reportMissingLibraryError( | 1416 | for (self.undefined_symbols.items) |index| { |
| 1108 | checked_paths.items, | 1417 | if (self.getSymbol(index).getFile(self)) |file| { |
| 1109 | "missing dynamic library dependency: '{s}'", | 1418 | if (file == .object) file.object.alive = true; |
| 1110 | .{dep_id.id.name}, | ||
| 1111 | ); | ||
| 1112 | continue; | ||
| 1113 | } | 1419 | } |
| 1114 | 1420 | } | |
| 1115 | const full_path = test_path.items; | 1421 | if (self.entry_index) |index| { |
| 1116 | const file = try std.fs.cwd().openFile(full_path, .{}); | 1422 | const sym = self.getSymbol(index); |
| 1117 | defer file.close(); | 1423 | if (sym.getFile(self)) |file| { |
| 1118 | 1424 | if (file == .object) file.object.alive = true; | |
| 1119 | log.debug("parsing dependency {s} at fully resolved path {s}", .{ dep_id.id.name, full_path }); | 1425 | } |
| 1120 | 1426 | } | |
| 1121 | var parse_ctx = ParseErrorCtx.init(gpa); | 1427 | if (self.getZigObject()) |zo| zo.markLive(self); |
| 1122 | defer parse_ctx.deinit(); | 1428 | for (self.objects.items) |index| { |
| 1123 | 1429 | const object = self.getFile(index).?.object; | |
| 1124 | self.parseLibrary(file, full_path, .{ | 1430 | if (object.alive) object.markLive(self); |
| 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 | ||
| 1133 | } | 1431 | } |
| 1134 | } | 1432 | } |
| 1135 | 1433 | ||
| 1136 | pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []u8) !void { | 1434 | fn resolveSyntheticSymbols(self: *MachO) !void { |
| 1137 | const atom = self.getAtom(atom_index); | 1435 | const internal = self.getInternalObject() orelse return; |
| 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 }); | ||
| 1142 | 1436 | ||
| 1143 | // Gather relocs which can be resolved. | 1437 | if (!self.base.isDynLib()) { |
| 1144 | const gpa = self.base.comp.gpa; | 1438 | self.mh_execute_header_index = try internal.addSymbol("__mh_execute_header", self); |
| 1145 | var relocs = std.ArrayList(*Relocation).init(gpa); | 1439 | const sym = self.getSymbol(self.mh_execute_header_index.?); |
| 1146 | defer relocs.deinit(); | 1440 | sym.flags.@"export" = true; |
| 1147 | 1441 | sym.flags.dyn_ref = true; | |
| 1148 | if (self.relocs.getPtr(atom_index)) |rels| { | 1442 | sym.visibility = .global; |
| 1149 | try relocs.ensureTotalCapacityPrecise(rels.items.len); | 1443 | } else { |
| 1150 | for (rels.items) |*reloc| { | 1444 | self.mh_dylib_header_index = try internal.addSymbol("__mh_dylib_header", self); |
| 1151 | if (reloc.isResolvable(self) and reloc.dirty) { | ||
| 1152 | relocs.appendAssumeCapacity(reloc); | ||
| 1153 | } | ||
| 1154 | } | ||
| 1155 | } | 1445 | } |
| 1156 | 1446 | ||
| 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); | ||
| 1158 | 1449 | ||
| 1159 | if (is_hot_update_compatible) { | 1450 | { |
| 1160 | if (self.hot_state.mach_task) |task| { | 1451 | const gpa = self.base.comp.gpa; |
| 1161 | self.writeToMemory(task, section.segment_index, sym.n_value, code) catch |err| { | 1452 | var boundary_symbols = std.AutoHashMap(Symbol.Index, void).init(gpa); |
| 1162 | log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)}); | 1453 | defer boundary_symbols.deinit(); |
| 1163 | }; | 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 | } | ||
| 1164 | } | 1469 | } |
| 1165 | } | ||
| 1166 | 1470 | ||
| 1167 | try self.base.file.?.pwriteAll(code, file_offset); | 1471 | try self.boundary_symbols.ensureTotalCapacityPrecise(gpa, boundary_symbols.count()); |
| 1168 | 1472 | ||
| 1169 | // Now we can mark the relocs as resolved. | 1473 | var it = boundary_symbols.iterator(); |
| 1170 | while (relocs.popOrNull()) |reloc| { | 1474 | while (it.next()) |entry| { |
| 1171 | reloc.dirty = false; | 1475 | _ = try internal.addSymbol(self.getSymbol(entry.key_ptr.*).getName(self), self); |
| 1476 | self.boundary_symbols.appendAssumeCapacity(entry.key_ptr.*); | ||
| 1477 | } | ||
| 1172 | } | 1478 | } |
| 1173 | } | 1479 | } |
| 1174 | 1480 | ||
| 1175 | fn writeToMemory(self: *MachO, task: std.os.darwin.MachTask, segment_index: u8, addr: u64, code: []const u8) !void { | 1481 | fn convertTentativeDefinitions(self: *MachO) !void { |
| 1176 | const segment = self.segments.items[segment_index]; | 1482 | for (self.objects.items) |index| { |
| 1177 | const target = self.base.comp.root_mod.resolved_target.result; | 1483 | try self.getFile(index).?.object.convertTentativeDefinitions(self); |
| 1178 | const cpu_arch = target.cpu.arch; | 1484 | } |
| 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; | ||
| 1184 | } | 1485 | } |
| 1185 | 1486 | ||
| 1186 | fn writeOffsetTableEntry(self: *MachO, index: usize) !void { | 1487 | fn createObjcSections(self: *MachO) !void { |
| 1187 | const sect_id = self.got_section_index.?; | 1488 | const gpa = self.base.comp.gpa; |
| 1188 | 1489 | var objc_msgsend_syms = std.AutoArrayHashMap(Symbol.Index, void).init(gpa); | |
| 1189 | if (self.got_table_count_dirty) { | 1490 | defer objc_msgsend_syms.deinit(); |
| 1190 | const needed_size = self.got_table.entries.items.len * @sizeOf(u64); | 1491 | |
| 1191 | try self.growSection(sect_id, needed_size); | 1492 | for (self.objects.items) |index| { |
| 1192 | self.got_table_count_dirty = false; | 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 | } | ||
| 1193 | } | 1507 | } |
| 1194 | 1508 | ||
| 1195 | const header = &self.sections.items(.header)[sect_id]; | 1509 | for (objc_msgsend_syms.keys()) |sym_index| { |
| 1196 | const segment_index = self.sections.items(.segment_index)[sect_id]; | 1510 | const sym = self.getSymbol(sym_index); |
| 1197 | const entry = self.got_table.entries.items[index]; | 1511 | sym.value = 0; |
| 1198 | const entry_value = self.getSymbol(entry).n_value; | 1512 | sym.atom = 0; |
| 1199 | const entry_offset = index * @sizeOf(u64); | 1513 | sym.nlist_idx = 0; |
| 1200 | const file_offset = header.offset + entry_offset; | 1514 | sym.file = self.internal_object.?; |
| 1201 | const vmaddr = header.addr + entry_offset; | 1515 | sym.flags = .{}; |
| 1202 | 1516 | sym.visibility = .hidden; | |
| 1203 | log.debug("writing GOT entry {d}: @{x} => {x}", .{ index, vmaddr, entry_value }); | 1517 | const object = self.getInternalObject().?; |
| 1204 | 1518 | const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?; | |
| 1205 | var buf: [@sizeOf(u64)]u8 = undefined; | 1519 | const selrefs_index = try object.addObjcMsgsendSections(name, self); |
| 1206 | mem.writeInt(u64, &buf, entry_value, .little); | 1520 | try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self); |
| 1207 | try self.base.file.?.pwriteAll(&buf, file_offset); | 1521 | try object.symbols.append(gpa, sym_index); |
| 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 | } | ||
| 1215 | } | 1522 | } |
| 1216 | } | 1523 | } |
| 1217 | 1524 | ||
| 1218 | fn writeStubHelperPreamble(self: *MachO) !void { | 1525 | fn claimUnresolved(self: *MachO) error{OutOfMemory}!void { |
| 1219 | if (self.stub_helper_preamble_allocated) return; | ||
| 1220 | |||
| 1221 | const gpa = self.base.comp.gpa; | 1526 | 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.?]; | ||
| 1236 | 1527 | ||
| 1237 | try stubs.writeStubHelperPreambleCode(.{ | 1528 | var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1); |
| 1238 | .cpu_arch = cpu_arch, | 1529 | defer objects.deinit(); |
| 1239 | .source_addr = header.addr, | 1530 | if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); |
| 1240 | .dyld_private_addr = dyld_private_addr, | 1531 | objects.appendSliceAssumeCapacity(self.objects.items); |
| 1241 | .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr, | ||
| 1242 | }, buf.writer()); | ||
| 1243 | try self.base.file.?.pwriteAll(buf.items, header.offset); | ||
| 1244 | 1532 | ||
| 1245 | self.stub_helper_preamble_allocated = true; | 1533 | for (objects.items) |index| { |
| 1246 | } | 1534 | const file = self.getFile(index).?; |
| 1247 | 1535 | ||
| 1248 | fn writeStubTableEntry(self: *MachO, index: usize) !void { | 1536 | for (file.getSymbols(), 0..) |sym_index, i| { |
| 1249 | const target = self.base.comp.root_mod.resolved_target.result; | 1537 | const nlist_idx = @as(Symbol.Index, @intCast(i)); |
| 1250 | const stubs_sect_id = self.stubs_section_index.?; | 1538 | const nlist = switch (file) { |
| 1251 | const stub_helper_sect_id = self.stub_helper_section_index.?; | 1539 | .object => |x| x.symtab.items(.nlist)[nlist_idx], |
| 1252 | const laptr_sect_id = self.la_symbol_ptr_section_index.?; | 1540 | .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], |
| 1541 | else => unreachable, | ||
| 1542 | }; | ||
| 1543 | if (!nlist.ext()) continue; | ||
| 1544 | if (!nlist.undf()) continue; | ||
| 1253 | 1545 | ||
| 1254 | const cpu_arch = target.cpu.arch; | 1546 | const sym = self.getSymbol(sym_index); |
| 1255 | const stub_entry_size = stubs.stubSize(cpu_arch); | 1547 | if (sym.getFile(self) != null) continue; |
| 1256 | const stub_helper_entry_size = stubs.stubHelperSize(cpu_arch); | ||
| 1257 | const stub_helper_preamble_size = stubs.stubHelperPreambleSize(cpu_arch); | ||
| 1258 | 1548 | ||
| 1259 | if (self.stub_table_count_dirty) { | 1549 | const is_import = switch (self.undefined_treatment) { |
| 1260 | // We grow all 3 sections one by one. | 1550 | .@"error" => false, |
| 1261 | { | 1551 | .warn, .suppress => nlist.weakRef(), |
| 1262 | const needed_size = stub_entry_size * self.stub_table.entries.items.len; | 1552 | .dynamic_lookup => true, |
| 1263 | try self.growSection(stubs_sect_id, needed_size); | 1553 | }; |
| 1264 | } | 1554 | if (is_import) { |
| 1265 | { | 1555 | sym.value = 0; |
| 1266 | const needed_size = stub_helper_preamble_size + stub_helper_entry_size * self.stub_table.entries.items.len; | 1556 | sym.atom = 0; |
| 1267 | try self.growSection(stub_helper_sect_id, needed_size); | 1557 | sym.nlist_idx = 0; |
| 1268 | } | 1558 | sym.file = self.internal_object.?; |
| 1269 | { | 1559 | sym.flags.weak = false; |
| 1270 | const needed_size = @sizeOf(u64) * self.stub_table.entries.items.len; | 1560 | sym.flags.weak_ref = nlist.weakRef(); |
| 1271 | try self.growSection(laptr_sect_id, needed_size); | 1561 | sym.flags.import = is_import; |
| 1562 | sym.visibility = .global; | ||
| 1563 | try self.getInternalObject().?.symbols.append(self.base.comp.gpa, sym_index); | ||
| 1564 | } | ||
| 1272 | } | 1565 | } |
| 1273 | self.stub_table_count_dirty = false; | ||
| 1274 | } | 1566 | } |
| 1567 | } | ||
| 1275 | 1568 | ||
| 1569 | fn checkDuplicates(self: *MachO) !void { | ||
| 1276 | const gpa = self.base.comp.gpa; | 1570 | const gpa = self.base.comp.gpa; |
| 1277 | 1571 | ||
| 1278 | const stubs_header = self.sections.items(.header)[stubs_sect_id]; | 1572 | var dupes = std.AutoArrayHashMap(Symbol.Index, std.ArrayListUnmanaged(File.Index)).init(gpa); |
| 1279 | const stub_helper_header = self.sections.items(.header)[stub_helper_sect_id]; | 1573 | defer { |
| 1280 | const laptr_header = self.sections.items(.header)[laptr_sect_id]; | 1574 | for (dupes.values()) |*list| { |
| 1281 | 1575 | list.deinit(gpa); | |
| 1282 | const entry = self.stub_table.entries.items[index]; | 1576 | } |
| 1283 | const stub_addr: u64 = stubs_header.addr + stub_entry_size * index; | 1577 | dupes.deinit(); |
| 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); | ||
| 1299 | } | 1578 | } |
| 1300 | 1579 | ||
| 1301 | { | 1580 | if (self.getZigObject()) |zo| { |
| 1302 | var buf = try std.ArrayList(u8).initCapacity(gpa, stub_helper_entry_size); | 1581 | try zo.checkDuplicates(&dupes, self); |
| 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); | ||
| 1311 | } | 1582 | } |
| 1312 | 1583 | ||
| 1313 | { | 1584 | for (self.objects.items) |index| { |
| 1314 | var buf: [@sizeOf(u64)]u8 = undefined; | 1585 | try self.getFile(index).?.object.checkDuplicates(&dupes, self); |
| 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); | ||
| 1318 | } | 1586 | } |
| 1319 | 1587 | ||
| 1320 | // TODO: generating new stub entry will require pulling the address of the symbol from the | 1588 | try self.reportDuplicates(dupes); |
| 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 | } | ||
| 1327 | } | 1589 | } |
| 1328 | 1590 | ||
| 1329 | fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void { | 1591 | fn markImportsAndExports(self: *MachO) error{OutOfMemory}!void { |
| 1330 | log.debug("marking relocs dirty by target: {}", .{target}); | 1592 | const gpa = self.base.comp.gpa; |
| 1331 | // TODO: reverse-lookup might come in handy here | 1593 | var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1); |
| 1332 | for (self.relocs.values()) |*relocs| { | 1594 | defer objects.deinit(); |
| 1333 | for (relocs.items) |*reloc| { | 1595 | if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); |
| 1334 | if (!reloc.target.eql(target)) continue; | 1596 | objects.appendSliceAssumeCapacity(self.objects.items); |
| 1335 | reloc.dirty = true; | 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 | } | ||
| 1336 | } | 1610 | } |
| 1337 | } | 1611 | } |
| 1338 | } | ||
| 1339 | 1612 | ||
| 1340 | fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void { | 1613 | for (self.undefined_symbols.items) |index| { |
| 1341 | log.debug("marking relocs dirty by address: {x}", .{addr}); | 1614 | const sym = self.getSymbol(index); |
| 1342 | 1615 | if (sym.getFile(self)) |file| { | |
| 1343 | const got_moved = blk: { | 1616 | if (sym.visibility != .global) continue; |
| 1344 | const sect_id = self.got_section_index orelse break :blk false; | 1617 | if (file == .dylib and !sym.flags.abs) sym.flags.import = true; |
| 1345 | break :blk self.sections.items(.header)[sect_id].addr > addr; | 1618 | } |
| 1346 | }; | 1619 | } |
| 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 | }; | ||
| 1351 | 1620 | ||
| 1352 | for (self.relocs.values()) |*relocs| { | 1621 | for (&[_]?Symbol.Index{ |
| 1353 | for (relocs.items) |*reloc| { | 1622 | self.entry_index, |
| 1354 | if (reloc.isGotIndirection()) { | 1623 | self.dyld_stub_binder_index, |
| 1355 | reloc.dirty = reloc.dirty or got_moved; | 1624 | self.objc_msg_send_index, |
| 1356 | } else if (reloc.isStubTrampoline(self)) { | 1625 | }) |index| { |
| 1357 | reloc.dirty = reloc.dirty or stubs_moved; | 1626 | if (index) |idx| { |
| 1358 | } else { | 1627 | const sym = self.getSymbol(idx); |
| 1359 | const target_addr = reloc.getTargetBaseAddress(self) orelse continue; | 1628 | if (sym.getFile(self)) |file| { |
| 1360 | if (target_addr > addr) reloc.dirty = true; | 1629 | if (file == .dylib) sym.flags.import = true; |
| 1361 | } | 1630 | } |
| 1362 | } | 1631 | } |
| 1363 | } | 1632 | } |
| 1633 | } | ||
| 1364 | 1634 | ||
| 1365 | // TODO: dirty only really affected GOT cells | 1635 | fn deadStripDylibs(self: *MachO) void { |
| 1366 | for (self.got_table.entries.items) |entry| { | 1636 | for (&[_]?Symbol.Index{ |
| 1367 | const target_addr = self.getSymbol(entry).n_value; | 1637 | self.entry_index, |
| 1368 | if (target_addr > addr) { | 1638 | self.dyld_stub_binder_index, |
| 1369 | self.got_table_contents_dirty = true; | 1639 | self.objc_msg_send_index, |
| 1370 | break; | 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 | } | ||
| 1371 | } | 1646 | } |
| 1372 | } | 1647 | } |
| 1373 | 1648 | ||
| 1374 | { | 1649 | for (self.dylibs.items) |index| { |
| 1375 | const stubs_addr = self.getSegment(self.stubs_section_index.?).vmaddr; | 1650 | self.getFile(index).?.dylib.markReferenced(self); |
| 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 | |||
| 1383 | pub 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 | }); | ||
| 1401 | } | 1651 | } |
| 1402 | 1652 | ||
| 1403 | for (self.globals.items) |global| { | 1653 | var i: usize = 0; |
| 1404 | const sym = self.getSymbolPtr(global); | 1654 | while (i < self.dylibs.items.len) { |
| 1405 | if (sym.n_desc != N_BOUNDARY) continue; | 1655 | const index = self.dylibs.items[i]; |
| 1406 | if (self.getSectionBoundarySymbol(global)) |bsym| { | 1656 | if (!self.getFile(index).?.dylib.isAlive(self)) { |
| 1407 | const sect_id = self.getSectionByName(bsym.segname, bsym.sectname) orelse { | 1657 | _ = self.dylibs.orderedRemove(i); |
| 1408 | try self.reportUnresolvedBoundarySymbol(self.getSymbolName(global), "section not found: {s},{s}", .{ | 1658 | } else i += 1; |
| 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 | } | ||
| 1446 | } | 1659 | } |
| 1447 | } | 1660 | } |
| 1448 | 1661 | ||
| 1449 | const CreateAtomOpts = struct { | 1662 | fn scanRelocs(self: *MachO) !void { |
| 1450 | size: u64 = 0, | 1663 | const tracy = trace(@src()); |
| 1451 | alignment: Alignment = .@"1", | 1664 | defer tracy.end(); |
| 1452 | }; | ||
| 1453 | |||
| 1454 | pub 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 | |||
| 1466 | pub fn createTentativeDefAtoms(self: *MachO) !void { | ||
| 1467 | const gpa = self.base.comp.gpa; | ||
| 1468 | 1665 | ||
| 1469 | for (self.globals.items) |global| { | 1666 | if (self.getZigObject()) |zo| try zo.scanRelocs(self); |
| 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; | ||
| 1474 | 1667 | ||
| 1475 | log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{ | 1668 | for (self.objects.items) |index| { |
| 1476 | global.sym_index, self.getSymbolName(global), global.file, | 1669 | try self.getFile(index).?.object.scanRelocs(self); |
| 1477 | }); | 1670 | } |
| 1478 | 1671 | ||
| 1479 | // Convert any tentative definition into a regular symbol and allocate | 1672 | try self.reportUndefs(); |
| 1480 | // text blocks for each tentative definition. | ||
| 1481 | const size = sym.n_value; | ||
| 1482 | const alignment = (sym.n_desc >> 8) & 0x0f; | ||
| 1483 | 1673 | ||
| 1484 | if (self.bss_section_index == null) { | 1674 | if (self.entry_index) |index| { |
| 1485 | self.bss_section_index = try self.initSection("__DATA", "__bss", .{ | 1675 | const sym = self.getSymbol(index); |
| 1486 | .flags = macho.S_ZEROFILL, | 1676 | if (sym.getFile(self) != null) { |
| 1487 | }); | 1677 | if (sym.flags.import) sym.flags.stubs = true; |
| 1488 | } | 1678 | } |
| 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; | ||
| 1511 | } | 1679 | } |
| 1512 | } | ||
| 1513 | |||
| 1514 | pub 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); | ||
| 1524 | 1680 | ||
| 1525 | if (self.data_section_index == null) { | 1681 | if (self.dyld_stub_binder_index) |index| { |
| 1526 | self.data_section_index = try self.initSection("__DATA", "__data", .{}); | 1682 | const sym = self.getSymbol(index); |
| 1683 | if (sym.getFile(self) != null) sym.flags.needs_got = true; | ||
| 1527 | } | 1684 | } |
| 1528 | 1685 | ||
| 1529 | const atom = self.getAtom(atom_index); | 1686 | if (self.objc_msg_send_index) |index| { |
| 1530 | const sym = atom.getSymbolPtr(self); | 1687 | const sym = self.getSymbol(index); |
| 1531 | sym.n_type = macho.N_SECT; | 1688 | if (sym.getFile(self) != null) |
| 1532 | sym.n_sect = self.data_section_index.? + 1; | 1689 | sym.flags.needs_got = true; // TODO is it always needed, or only if we are synthesising fast stubs? |
| 1533 | self.dyld_private_atom_index = atom_index; | 1690 | } |
| 1534 | 1691 | ||
| 1535 | switch (self.mode) { | 1692 | for (self.symbols.items, 0..) |*symbol, i| { |
| 1536 | .zld => self.addAtomToSection(atom_index), | 1693 | const index = @as(Symbol.Index, @intCast(i)); |
| 1537 | .incremental => { | 1694 | if (symbol.flags.needs_got) { |
| 1538 | sym.n_value = try self.allocateAtom(atom_index, atom.size, .@"8"); | 1695 | log.debug("'{s}' needs GOT", .{symbol.getName(self)}); |
| 1539 | log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value}); | 1696 | try self.got.addSymbol(index, self); |
| 1540 | var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); | 1697 | } |
| 1541 | try self.writeAtom(atom_index, &buffer); | 1698 | if (symbol.flags.stubs) { |
| 1542 | }, | 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 | } | ||
| 1543 | } | 1710 | } |
| 1544 | } | 1711 | } |
| 1545 | 1712 | ||
| 1546 | fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index { | 1713 | fn reportUndefs(self: *MachO) !void { |
| 1547 | const gpa = self.base.comp.gpa; | 1714 | const tracy = trace(@src()); |
| 1548 | const size = 3 * @sizeOf(u64); | 1715 | defer tracy.end(); |
| 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 | }); | ||
| 1571 | 1716 | ||
| 1572 | var code: [size]u8 = undefined; | 1717 | switch (self.undefined_treatment) { |
| 1573 | @memset(&code, 0); | 1718 | .dynamic_lookup, .suppress => return, |
| 1574 | try self.writeAtom(atom_index, &code); | 1719 | .@"error", .warn => {}, |
| 1720 | } | ||
| 1575 | 1721 | ||
| 1576 | return atom_index; | 1722 | const max_notes = 4; |
| 1577 | } | ||
| 1578 | 1723 | ||
| 1579 | pub fn createMhExecuteHeaderSymbol(self: *MachO) !void { | 1724 | var has_undefs = false; |
| 1580 | const output_mode = self.base.comp.config.output_mode; | 1725 | var it = self.undefs.iterator(); |
| 1581 | if (output_mode != .Exe) return; | 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); | ||
| 1582 | 1730 | ||
| 1583 | const gpa = self.base.comp.gpa; | 1731 | var err = try self.addErrorWithNotes(nnotes); |
| 1584 | const sym_index = try self.allocateSymbol(); | 1732 | try err.addMsg(self, "undefined symbol: {s}", .{undef_sym.getName(self)}); |
| 1585 | const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; | 1733 | has_undefs = true; |
| 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 | }; | ||
| 1594 | 1734 | ||
| 1595 | const gop = try self.getOrPutGlobalPtr("__mh_execute_header"); | 1735 | var inote: usize = 0; |
| 1596 | if (gop.found_existing) { | 1736 | while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { |
| 1597 | const global = gop.value_ptr.*; | 1737 | const atom = self.getAtom(notes.items[inote]).?; |
| 1598 | if (global.getFile()) |file| { | 1738 | const file = atom.getFile(self); |
| 1599 | const global_object = &self.objects.items[file]; | 1739 | try err.addNote(self, "referenced by {}:{s}", .{ file.fmtPath(), atom.getName(self) }); |
| 1600 | global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?; | ||
| 1601 | } | 1740 | } |
| 1602 | } | ||
| 1603 | gop.value_ptr.* = sym_loc; | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | pub 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 | } | ||
| 1629 | 1741 | ||
| 1630 | pub fn resolveSymbols(self: *MachO) !void { | 1742 | if (notes.items.len > max_notes) { |
| 1631 | const comp = self.base.comp; | 1743 | const remaining = notes.items.len - max_notes; |
| 1632 | const output_mode = comp.config.output_mode; | 1744 | try err.addNote(self, "referenced {d} more times", .{remaining}); |
| 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, .{}); | ||
| 1639 | } | 1745 | } |
| 1640 | } | 1746 | } |
| 1641 | 1747 | ||
| 1642 | // Force resolution of any symbols requested by the user. | 1748 | for (self.undefined_symbols.items) |index| { |
| 1643 | for (comp.force_undefined_symbols.keys()) |sym_name| { | 1749 | const sym = self.getSymbol(index); |
| 1644 | _ = try self.addUndefined(sym_name, .{}); | 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", .{}); | ||
| 1645 | } | 1755 | } |
| 1646 | 1756 | ||
| 1647 | for (self.objects.items, 0..) |_, object_id| { | 1757 | if (self.entry_index) |index| { |
| 1648 | try self.resolveSymbolsInObject(@as(u32, @intCast(object_id))); | 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 | } | ||
| 1649 | } | 1765 | } |
| 1650 | 1766 | ||
| 1651 | try self.resolveSymbolsInArchives(); | 1767 | if (self.dyld_stub_binder_index) |index| { |
| 1652 | 1768 | const sym = self.getSymbol(index); | |
| 1653 | // Finally, force resolution of dyld_stub_binder if there are imports | 1769 | if (sym.getFile(self) == null and self.stubs_sect_index != null) { |
| 1654 | // requested. | 1770 | has_undefs = true; |
| 1655 | if (self.unresolved.count() > 0 and self.dyld_stub_binder_index == null) { | 1771 | var err = try self.addErrorWithNotes(1); |
| 1656 | self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .{ .add_got = true }); | 1772 | try err.addMsg(self, "undefined symbol: {s}", .{sym.getName(self)}); |
| 1657 | } | 1773 | try err.addNote(self, "implicit -u command line option", .{}); |
| 1658 | if (comp.config.any_non_single_threaded and self.mode == .incremental) { | 1774 | } |
| 1659 | _ = try self.addUndefined("__tlv_bootstrap", .{}); | ||
| 1660 | } | 1775 | } |
| 1661 | 1776 | ||
| 1662 | try self.resolveSymbolsInDylibs(); | 1777 | if (self.objc_msg_send_index) |index| { |
| 1663 | 1778 | const sym = self.getSymbol(index); | |
| 1664 | try self.createMhExecuteHeaderSymbol(); | 1779 | if (sym.getFile(self) == null and self.objc_stubs_sect_index != null) { |
| 1665 | try self.createDsoHandleSymbol(); | 1780 | has_undefs = true; |
| 1666 | try self.resolveSymbolsAtLoading(); | 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 | } | ||
| 1667 | 1786 | ||
| 1668 | // Final stop, check if unresolved contain any of the special magic boundary symbols | 1787 | if (has_undefs) return error.HasUndefinedSymbols; |
| 1669 | // * section$start$ | ||
| 1670 | // * section$stop$ | ||
| 1671 | // * segment$start$ | ||
| 1672 | // * segment$stop$ | ||
| 1673 | try self.resolveBoundarySymbols(); | ||
| 1674 | } | 1788 | } |
| 1675 | 1789 | ||
| 1676 | fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { | 1790 | fn initOutputSections(self: *MachO) !void { |
| 1677 | const gpa = self.base.comp.gpa; | 1791 | for (self.objects.items) |index| { |
| 1678 | const sym = self.getSymbol(current); | 1792 | const object = self.getFile(index).?.object; |
| 1679 | const sym_name = self.getSymbolName(current); | 1793 | for (object.atoms.items) |atom_index| { |
| 1680 | 1794 | const atom = self.getAtom(atom_index) orelse continue; | |
| 1681 | const gop = try self.getOrPutGlobalPtr(sym_name); | 1795 | if (!atom.flags.alive) continue; |
| 1682 | if (!gop.found_existing) { | 1796 | atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self); |
| 1683 | gop.value_ptr.* = current; | ||
| 1684 | if (sym.undf() and !sym.tentative()) { | ||
| 1685 | try self.unresolved.putNoClobber(gpa, self.getGlobalIndex(sym_name).?, {}); | ||
| 1686 | } | 1797 | } |
| 1687 | return; | 1798 | } |
| 1688 | } | 1799 | if (self.getInternalObject()) |object| { |
| 1689 | const global_index = self.getGlobalIndex(sym_name).?; | 1800 | for (object.atoms.items) |atom_index| { |
| 1690 | const global = gop.value_ptr.*; | 1801 | const atom = self.getAtom(atom_index) orelse continue; |
| 1691 | const global_sym = self.getSymbol(global); | 1802 | if (!atom.flags.alive) continue; |
| 1692 | 1803 | atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self); | |
| 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); | ||
| 1725 | } | 1804 | } |
| 1726 | return error.MultipleSymbolDefinitions; | ||
| 1727 | } | 1805 | } |
| 1728 | 1806 | if (self.text_sect_index == null) { | |
| 1729 | if (current.getFile()) |file| { | 1807 | self.text_sect_index = try self.addSection("__TEXT", "__text", .{ |
| 1730 | const object = &self.objects.items[file]; | 1808 | .alignment = switch (self.getTarget().cpu.arch) { |
| 1731 | object.globals_lookup[current.sym_index] = global_index; | 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 | }); | ||
| 1732 | } | 1816 | } |
| 1733 | 1817 | if (self.data_sect_index == null) { | |
| 1734 | if (global_is_strong) return; | 1818 | self.data_sect_index = try self.addSection("__DATA", "__data", .{}); |
| 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; | ||
| 1738 | } | 1819 | } |
| 1739 | if (sym.undf() and !sym.tentative()) return; | 1820 | } |
| 1740 | 1821 | ||
| 1741 | if (global.getFile()) |file| { | 1822 | fn initSyntheticSections(self: *MachO) !void { |
| 1742 | const global_object = &self.objects.items[file]; | 1823 | const cpu_arch = self.getTarget().cpu.arch; |
| 1743 | global_object.globals_lookup[global.sym_index] = global_index; | ||
| 1744 | } | ||
| 1745 | _ = self.unresolved.swapRemove(global_index); | ||
| 1746 | 1824 | ||
| 1747 | gop.value_ptr.* = current; | 1825 | if (self.got.symbols.items.len > 0) { |
| 1748 | } | 1826 | self.got_sect_index = try self.addSection("__DATA_CONST", "__got", .{ |
| 1749 | 1827 | .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, | |
| 1750 | fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void { | 1828 | .reserved1 = @intCast(self.stubs.symbols.items.len), |
| 1751 | const object = &self.objects.items[object_id]; | 1829 | }); |
| 1752 | const in_symtab = object.in_symtab orelse return; | 1830 | } |
| 1753 | 1831 | ||
| 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 | } | ||
| 1755 | 1851 | ||
| 1756 | var sym_index: u32 = 0; | 1852 | if (self.objc_stubs.symbols.items.len > 0) { |
| 1757 | while (sym_index < in_symtab.len) : (sym_index += 1) { | 1853 | self.objc_stubs_sect_index = try self.addSection("__TEXT", "__objc_stubs", .{ |
| 1758 | const sym = &object.symtab[sym_index]; | 1854 | .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, |
| 1759 | const sym_name = object.getSymbolName(sym_index); | 1855 | }); |
| 1760 | const sym_with_loc = SymbolWithLoc{ | 1856 | } |
| 1761 | .sym_index = sym_index, | ||
| 1762 | .file = object_id + 1, | ||
| 1763 | }; | ||
| 1764 | 1857 | ||
| 1765 | if (sym.stab() or sym.indr() or sym.abs()) { | 1858 | if (self.tlv_ptr.symbols.items.len > 0) { |
| 1766 | try self.reportUnhandledSymbolType(sym_with_loc); | 1859 | self.tlv_ptr_sect_index = try self.addSection("__DATA", "__thread_ptrs", .{ |
| 1767 | continue; | 1860 | .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS, |
| 1768 | } | 1861 | }); |
| 1862 | } | ||
| 1769 | 1863 | ||
| 1770 | if (sym.sect() and !sym.ext()) { | 1864 | const needs_unwind_info = for (self.objects.items) |index| { |
| 1771 | log.debug("symbol '{s}' local to object {s}; skipping...", .{ | 1865 | if (self.getFile(index).?.object.hasUnwindRecords()) break true; |
| 1772 | sym_name, | 1866 | } else false; |
| 1773 | object.name, | 1867 | if (needs_unwind_info) { |
| 1774 | }); | 1868 | self.unwind_info_sect_index = try self.addSection("__TEXT", "__unwind_info", .{}); |
| 1775 | continue; | 1869 | } |
| 1776 | } | ||
| 1777 | 1870 | ||
| 1778 | self.resolveGlobalSymbol(.{ | 1871 | const needs_eh_frame = for (self.objects.items) |index| { |
| 1779 | .sym_index = sym_index, | 1872 | if (self.getFile(index).?.object.hasEhFrameRecords()) break true; |
| 1780 | .file = object_id + 1, | 1873 | } else false; |
| 1781 | }) catch |err| switch (err) { | 1874 | if (needs_eh_frame) { |
| 1782 | error.MultipleSymbolDefinitions => return error.FlushFailure, | 1875 | assert(needs_unwind_info); |
| 1783 | else => |e| return e, | 1876 | self.eh_frame_sect_index = try self.addSection("__TEXT", "__eh_frame", .{}); |
| 1784 | }; | ||
| 1785 | } | 1877 | } |
| 1786 | } | ||
| 1787 | 1878 | ||
| 1788 | fn resolveSymbolsInArchives(self: *MachO) !void { | 1879 | for (self.boundary_symbols.items) |sym_index| { |
| 1789 | if (self.archives.items.len == 0) return; | 1880 | const gpa = self.base.comp.gpa; |
| 1881 | const sym = self.getSymbol(sym_index); | ||
| 1882 | const name = sym.getName(self); | ||
| 1790 | 1883 | ||
| 1791 | const gpa = self.base.comp.gpa; | 1884 | if (eatPrefix(name, "segment$start$")) |segname| { |
| 1792 | var next_sym: usize = 0; | 1885 | if (self.getSegmentByName(segname) == null) { // TODO check segname is valid |
| 1793 | loop: while (next_sym < self.unresolved.count()) { | 1886 | const prot = getSegmentProt(segname); |
| 1794 | const global = self.globals.items[self.unresolved.keys()[next_sym]]; | 1887 | _ = try self.segments.append(gpa, .{ |
| 1795 | const sym_name = self.getSymbolName(global); | 1888 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 1796 | 1889 | .segname = makeStaticString(segname), | |
| 1797 | for (self.archives.items) |archive| { | 1890 | .initprot = prot, |
| 1798 | // Check if the entry exists in a static archive. | 1891 | .maxprot = prot, |
| 1799 | const offsets = archive.toc.get(sym_name) orelse { | 1892 | }); |
| 1800 | // No hit. | 1893 | } |
| 1801 | continue; | 1894 | } else if (eatPrefix(name, "segment$stop$")) |segname| { |
| 1802 | }; | 1895 | if (self.getSegmentByName(segname) == null) { // TODO check segname is valid |
| 1803 | assert(offsets.items.len > 0); | 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 | } | ||
| 1804 | 1921 | ||
| 1805 | const object_id = @as(u16, @intCast(self.objects.items.len)); | 1922 | fn getSegmentProt(segname: []const u8) macho.vm_prot_t { |
| 1806 | const object = try archive.parseObject(gpa, offsets.items[0]); | 1923 | if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE; |
| 1807 | try self.objects.append(gpa, object); | 1924 | if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC; |
| 1808 | try self.resolveSymbolsInObject(object_id); | 1925 | if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ; |
| 1926 | return macho.PROT.READ | macho.PROT.WRITE; | ||
| 1927 | } | ||
| 1809 | 1928 | ||
| 1810 | continue :loop; | 1929 | fn getSegmentRank(segname: []const u8) u8 { |
| 1811 | } | 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 | } | ||
| 1812 | 1938 | ||
| 1813 | next_sym += 1; | 1939 | fn 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; | ||
| 1814 | } | 1945 | } |
| 1946 | return lhs_rank < rhs_rank; | ||
| 1815 | } | 1947 | } |
| 1816 | 1948 | ||
| 1817 | fn resolveSymbolsInDylibs(self: *MachO) !void { | 1949 | fn getSectionRank(section: macho.section_64) u8 { |
| 1818 | if (self.dylibs.items.len == 0) return; | 1950 | if (section.isCode()) { |
| 1819 | 1951 | if (mem.eql(u8, "__text", section.sectName())) return 0x0; | |
| 1820 | const gpa = self.base.comp.gpa; | 1952 | if (section.type() == macho.S_SYMBOL_STUBS) return 0x1; |
| 1821 | var next_sym: usize = 0; | 1953 | return 0x2; |
| 1822 | loop: while (next_sym < self.unresolved.count()) { | 1954 | } |
| 1823 | const global_index = self.unresolved.keys()[next_sym]; | 1955 | switch (section.type()) { |
| 1824 | const global = self.globals.items[global_index]; | 1956 | macho.S_NON_LAZY_SYMBOL_POINTERS, |
| 1825 | const sym = self.getSymbolPtr(global); | 1957 | macho.S_LAZY_SYMBOL_POINTERS, |
| 1826 | const sym_name = self.getSymbolName(global); | 1958 | => return 0x0, |
| 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; | ||
| 1839 | 1959 | ||
| 1840 | if (dylib.weak) { | 1960 | macho.S_MOD_INIT_FUNC_POINTERS => return 0x1, |
| 1841 | sym.n_desc |= macho.N_WEAK_REF; | 1961 | macho.S_MOD_TERM_FUNC_POINTERS => return 0x2, |
| 1842 | } | 1962 | macho.S_ZEROFILL => return 0xf, |
| 1963 | macho.S_THREAD_LOCAL_REGULAR => return 0xd, | ||
| 1964 | macho.S_THREAD_LOCAL_ZEROFILL => return 0xe, | ||
| 1843 | 1965 | ||
| 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 | } | ||
| 1845 | 1974 | ||
| 1846 | continue :loop; | 1975 | fn 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; | ||
| 1847 | } | 1981 | } |
| 1848 | 1982 | return lhs_rank < rhs_rank; | |
| 1849 | next_sym += 1; | ||
| 1850 | } | 1983 | } |
| 1984 | return segmentLessThan(ctx, lhs.segName(), rhs.segName()); | ||
| 1851 | } | 1985 | } |
| 1852 | 1986 | ||
| 1853 | fn resolveSymbolsAtLoading(self: *MachO) !void { | 1987 | pub fn sortSections(self: *MachO) !void { |
| 1854 | const output_mode = self.base.comp.config.output_mode; | 1988 | const Entry = struct { |
| 1855 | const is_lib = output_mode == .Lib; | 1989 | index: u8, |
| 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); | ||
| 1864 | 1990 | ||
| 1865 | if (sym.discarded()) { | 1991 | pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool { |
| 1866 | sym.* = .{ | 1992 | return sectionLessThan( |
| 1867 | .n_strx = 0, | 1993 | {}, |
| 1868 | .n_type = macho.N_UNDF, | 1994 | macho_file.sections.items(.header)[lhs.index], |
| 1869 | .n_sect = 0, | 1995 | macho_file.sections.items(.header)[rhs.index], |
| 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))), | ||
| 1879 | ); | 1996 | ); |
| 1880 | sym.n_type = macho.N_EXT; | ||
| 1881 | sym.n_desc = n_desc; | ||
| 1882 | _ = self.unresolved.swapRemove(global_index); | ||
| 1883 | continue; | ||
| 1884 | } | 1997 | } |
| 1998 | }; | ||
| 1885 | 1999 | ||
| 1886 | next_sym += 1; | ||
| 1887 | } | ||
| 1888 | } | ||
| 1889 | |||
| 1890 | fn resolveBoundarySymbols(self: *MachO) !void { | ||
| 1891 | const gpa = self.base.comp.gpa; | 2000 | 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 | } | ||
| 1916 | 2001 | ||
| 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) }); | ||
| 1918 | } | 2006 | } |
| 1919 | } | ||
| 1920 | 2007 | ||
| 1921 | pub fn deinit(self: *MachO) void { | 2008 | mem.sort(Entry, entries.items, self, Entry.lessThan); |
| 1922 | const gpa = self.base.comp.gpa; | ||
| 1923 | 2009 | ||
| 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 | } | ||
| 1925 | 2015 | ||
| 1926 | if (self.d_sym) |*d_sym| { | 2016 | var slice = self.sections.toOwnedSlice(); |
| 1927 | d_sym.deinit(); | 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)); | ||
| 1928 | } | 2022 | } |
| 1929 | 2023 | ||
| 1930 | self.got_table.deinit(gpa); | 2024 | if (self.getZigObject()) |zo| { |
| 1931 | self.stub_table.deinit(gpa); | 2025 | for (zo.atoms.items) |atom_index| { |
| 1932 | self.tlv_ptr_table.deinit(gpa); | 2026 | const atom = self.getAtom(atom_index) orelse continue; |
| 1933 | self.thunk_table.deinit(gpa); | 2027 | if (!atom.flags.alive) continue; |
| 2028 | atom.out_n_sect = backlinks[atom.out_n_sect]; | ||
| 2029 | } | ||
| 1934 | 2030 | ||
| 1935 | for (self.thunks.items) |*thunk| { | 2031 | for (zo.symtab.items(.nlist)) |*sym| { |
| 1936 | thunk.deinit(gpa); | 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 | } | ||
| 1937 | } | 2044 | } |
| 1938 | self.thunks.deinit(gpa); | ||
| 1939 | 2045 | ||
| 1940 | self.strtab.deinit(gpa); | 2046 | for (self.objects.items) |index| { |
| 1941 | self.locals.deinit(gpa); | 2047 | for (self.getFile(index).?.object.atoms.items) |atom_index| { |
| 1942 | self.globals.deinit(gpa); | 2048 | const atom = self.getAtom(atom_index) orelse continue; |
| 1943 | self.locals_free_list.deinit(gpa); | 2049 | if (!atom.flags.alive) continue; |
| 1944 | self.globals_free_list.deinit(gpa); | 2050 | atom.out_n_sect = backlinks[atom.out_n_sect]; |
| 1945 | self.unresolved.deinit(gpa); | 2051 | } |
| 2052 | } | ||
| 1946 | 2053 | ||
| 1947 | { | 2054 | if (self.getInternalObject()) |object| { |
| 1948 | var it = self.resolver.keyIterator(); | 2055 | for (object.atoms.items) |atom_index| { |
| 1949 | while (it.next()) |key_ptr| { | 2056 | const atom = self.getAtom(atom_index) orelse continue; |
| 1950 | gpa.free(key_ptr.*); | 2057 | if (!atom.flags.alive) continue; |
| 2058 | atom.out_n_sect = backlinks[atom.out_n_sect]; | ||
| 1951 | } | 2059 | } |
| 1952 | self.resolver.deinit(gpa); | ||
| 1953 | } | 2060 | } |
| 1954 | 2061 | ||
| 1955 | for (self.objects.items) |*object| { | 2062 | for (&[_]*?u8{ |
| 1956 | object.deinit(gpa); | 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 | } | ||
| 1957 | } | 2077 | } |
| 1958 | self.objects.deinit(gpa); | 2078 | } |
| 1959 | for (self.archives.items) |*archive| { | 2079 | |
| 1960 | archive.deinit(gpa); | 2080 | pub 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 | } | ||
| 1961 | } | 2099 | } |
| 1962 | self.archives.deinit(gpa); | 2100 | if (self.getInternalObject()) |object| { |
| 1963 | for (self.dylibs.items) |*dylib| { | 2101 | for (object.atoms.items) |atom_index| { |
| 1964 | dylib.deinit(gpa); | 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 | } | ||
| 1965 | } | 2114 | } |
| 1966 | self.dylibs.deinit(gpa); | 2115 | } |
| 1967 | self.dylibs_map.deinit(gpa); | ||
| 1968 | self.referenced_dylibs.deinit(gpa); | ||
| 1969 | 2116 | ||
| 1970 | self.segments.deinit(gpa); | 2117 | fn calcSectionSizes(self: *MachO) !void { |
| 2118 | const tracy = trace(@src()); | ||
| 2119 | defer tracy.end(); | ||
| 1971 | 2120 | ||
| 1972 | for (self.sections.items(.free_list)) |*list| { | 2121 | const cpu_arch = self.getTarget().cpu.arch; |
| 1973 | list.deinit(gpa); | 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; | ||
| 1974 | } | 2127 | } |
| 1975 | self.sections.deinit(gpa); | ||
| 1976 | 2128 | ||
| 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; | ||
| 1978 | 2149 | ||
| 1979 | for (self.decls.values()) |*m| { | 2150 | // Create jump/branch range extenders if needed. |
| 1980 | m.exports.deinit(gpa); | 2151 | try thunks.createThunks(@intCast(i), self); |
| 2152 | } | ||
| 1981 | } | 2153 | } |
| 1982 | self.decls.deinit(gpa); | ||
| 1983 | 2154 | ||
| 1984 | self.lazy_syms.deinit(gpa); | 2155 | if (self.got_sect_index) |idx| { |
| 1985 | self.tlv_table.deinit(gpa); | 2156 | const header = &self.sections.items(.header)[idx]; |
| 2157 | header.size = self.got.size(); | ||
| 2158 | header.@"align" = 3; | ||
| 2159 | } | ||
| 1986 | 2160 | ||
| 1987 | for (self.unnamed_const_atoms.values()) |*atoms| { | 2161 | if (self.stubs_sect_index) |idx| { |
| 1988 | atoms.deinit(gpa); | 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 | }; | ||
| 1989 | } | 2169 | } |
| 1990 | self.unnamed_const_atoms.deinit(gpa); | ||
| 1991 | 2170 | ||
| 1992 | { | 2171 | if (self.stubs_helper_sect_index) |idx| { |
| 1993 | var it = self.anon_decls.iterator(); | 2172 | const header = &self.sections.items(.header)[idx]; |
| 1994 | while (it.next()) |entry| { | 2173 | header.size = self.stubs_helper.size(self); |
| 1995 | entry.value_ptr.exports.deinit(gpa); | 2174 | header.@"align" = 2; |
| 1996 | } | ||
| 1997 | self.anon_decls.deinit(gpa); | ||
| 1998 | } | 2175 | } |
| 1999 | 2176 | ||
| 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 | } | ||
| 2001 | 2182 | ||
| 2002 | for (self.relocs.values()) |*relocs| { | 2183 | if (self.tlv_ptr_sect_index) |idx| { |
| 2003 | relocs.deinit(gpa); | 2184 | const header = &self.sections.items(.header)[idx]; |
| 2185 | header.size = self.tlv_ptr.size(); | ||
| 2186 | header.@"align" = 3; | ||
| 2004 | } | 2187 | } |
| 2005 | self.relocs.deinit(gpa); | ||
| 2006 | self.actions.deinit(gpa); | ||
| 2007 | 2188 | ||
| 2008 | for (self.rebases.values()) |*rebases| { | 2189 | if (self.objc_stubs_sect_index) |idx| { |
| 2009 | rebases.deinit(gpa); | 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 | }; | ||
| 2010 | } | 2197 | } |
| 2011 | self.rebases.deinit(gpa); | 2198 | } |
| 2199 | |||
| 2200 | fn generateUnwindInfo(self: *MachO) !void { | ||
| 2201 | const tracy = trace(@src()); | ||
| 2202 | defer tracy.end(); | ||
| 2012 | 2203 | ||
| 2013 | for (self.bindings.values()) |*bindings| { | 2204 | if (self.eh_frame_sect_index) |index| { |
| 2014 | bindings.deinit(gpa); | 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; | ||
| 2015 | } | 2220 | } |
| 2016 | self.bindings.deinit(gpa); | ||
| 2017 | } | 2221 | } |
| 2018 | 2222 | ||
| 2019 | fn freeAtom(self: *MachO, atom_index: Atom.Index) void { | 2223 | fn initSegments(self: *MachO) !void { |
| 2020 | const gpa = self.base.comp.gpa; | 2224 | const gpa = self.base.comp.gpa; |
| 2021 | log.debug("freeAtom {d}", .{atom_index}); | 2225 | const slice = self.sections.slice(); |
| 2022 | |||
| 2023 | // Remove any relocs and base relocs associated with this Atom | ||
| 2024 | Atom.freeRelocations(self, atom_index); | ||
| 2025 | 2226 | ||
| 2026 | const atom = self.getAtom(atom_index); | 2227 | // Add __PAGEZERO if required |
| 2027 | const sect_id = atom.getSymbol(self).n_sect - 1; | 2228 | const pagezero_size = self.pagezero_size orelse default_pagezero_size; |
| 2028 | const free_list = &self.sections.items(.free_list)[sect_id]; | 2229 | const aligned_pagezero_size = mem.alignBackward(u64, pagezero_size, self.getPageSize()); |
| 2029 | var already_have_free_list_node = false; | 2230 | if (!self.base.isDynLib() and aligned_pagezero_size > 0) { |
| 2030 | { | 2231 | if (aligned_pagezero_size != pagezero_size) { |
| 2031 | var i: usize = 0; | 2232 | // TODO convert into a warning |
| 2032 | // TODO turn free_list into a hash map | 2233 | log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size}); |
| 2033 | while (i < free_list.items.len) { | 2234 | log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size}); |
| 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; | ||
| 2042 | } | 2235 | } |
| 2236 | _ = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size }); | ||
| 2043 | } | 2237 | } |
| 2044 | 2238 | ||
| 2045 | const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sect_id]; | 2239 | // __TEXT segment is non-optional |
| 2046 | if (maybe_last_atom_index.*) |last_atom_index| { | 2240 | _ = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") }); |
| 2047 | if (last_atom_index == atom_index) { | 2241 | |
| 2048 | if (atom.prev_index) |prev_index| { | 2242 | // Next, create segments required by sections |
| 2049 | // TODO shrink the section size here | 2243 | for (slice.items(.header)) |header| { |
| 2050 | maybe_last_atom_index.* = prev_index; | 2244 | const segname = header.segName(); |
| 2051 | } else { | 2245 | if (self.getSegmentByName(segname) == null) { |
| 2052 | maybe_last_atom_index.* = null; | 2246 | const flags: u32 = if (mem.startsWith(u8, segname, "__DATA_CONST")) macho.SG_READ_ONLY else 0; |
| 2053 | } | 2247 | _ = try self.addSegment(segname, .{ .prot = getSegmentProt(segname), .flags = flags }); |
| 2054 | } | 2248 | } |
| 2055 | } | 2249 | } |
| 2056 | 2250 | ||
| 2057 | if (atom.prev_index) |prev_index| { | 2251 | // Add __LINKEDIT |
| 2058 | const prev = self.getAtomPtr(prev_index); | 2252 | _ = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") }); |
| 2059 | prev.next_index = atom.next_index; | ||
| 2060 | 2253 | ||
| 2061 | if (!already_have_free_list_node and prev.*.freeListEligible(self)) { | 2254 | // Sort segments |
| 2062 | // The free list is heuristics, it doesn't have to be perfect, so we can ignore | 2255 | const sortFn = struct { |
| 2063 | // the OOM here. | 2256 | fn sortFn(ctx: void, lhs: macho.segment_command_64, rhs: macho.segment_command_64) bool { |
| 2064 | free_list.append(gpa, prev_index) catch {}; | 2257 | return segmentLessThan(ctx, lhs.segName(), rhs.segName()); |
| 2065 | } | 2258 | } |
| 2066 | } else { | 2259 | }.sortFn; |
| 2067 | self.getAtomPtr(atom_index).prev_index = null; | 2260 | mem.sort(macho.segment_command_64, self.segments.items, {}, sortFn); |
| 2068 | } | 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 | |||
| 2292 | fn 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; | ||
| 2069 | 2301 | ||
| 2070 | if (atom.next_index) |next_index| { | 2302 | const page_size = self.getPageSize(); |
| 2071 | self.getAtomPtr(next_index).prev_index = atom.prev_index; | 2303 | const slice = self.sections.slice(); |
| 2072 | } else { | 2304 | const last_index = for (slice.items(.header), 0..) |header, i| { |
| 2073 | self.getAtomPtr(atom_index).next_index = null; | 2305 | if (mem.indexOf(u8, header.segName(), "ZIG")) |_| break i; |
| 2074 | } | 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 | } | ||
| 2075 | 2313 | ||
| 2076 | // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. | 2314 | const alignment = try math.powi(u32, 2, header.@"align"); |
| 2077 | const sym_index = atom.getSymbolIndex().?; | ||
| 2078 | 2315 | ||
| 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; | ||
| 2080 | 2319 | ||
| 2081 | // Try freeing GOT atom if this decl had one | 2320 | if (!header.isZerofill()) { |
| 2082 | self.got_table.freeEntry(gpa, .{ .sym_index = sym_index }); | 2321 | fileoff = mem.alignForward(u32, fileoff, alignment); |
| 2322 | header.offset = fileoff; | ||
| 2323 | fileoff += @intCast(header.size); | ||
| 2324 | } | ||
| 2083 | 2325 | ||
| 2084 | if (self.d_sym) |*d_sym| { | 2326 | prev_seg_id = curr_seg_id; |
| 2085 | d_sym.swapRemoveRelocs(sym_index); | ||
| 2086 | } | 2327 | } |
| 2087 | 2328 | ||
| 2088 | self.locals.items[sym_index].n_type = 0; | 2329 | fileoff = mem.alignForward(u32, fileoff, page_size); |
| 2089 | _ = self.atom_by_index_table.remove(sym_index); | 2330 | for (slice.items(.header)[last_index..], slice.items(.segment_id)[last_index..]) |*header, seg_id| { |
| 2090 | log.debug(" adding local symbol index {d} to free list", .{sym_index}); | 2331 | if (header.isZerofill()) continue; |
| 2091 | self.getAtomPtr(atom_index).sym_index = 0; | 2332 | if (header.offset < fileoff) { |
| 2092 | } | 2333 | const existing_size = header.size; |
| 2334 | header.size = 0; | ||
| 2093 | 2335 | ||
| 2094 | fn shrinkAtom(self: *MachO, atom_index: Atom.Index, new_block_size: u64) void { | 2336 | // Must move the entire section. |
| 2095 | _ = self; | 2337 | const new_offset = self.findFreeSpace(existing_size, page_size); |
| 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 | } | ||
| 2101 | 2338 | ||
| 2102 | fn growAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: Alignment) !u64 { | 2339 | log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{ |
| 2103 | const atom = self.getAtom(atom_index); | 2340 | header.segName(), |
| 2104 | const sym = atom.getSymbol(self); | 2341 | header.sectName(), |
| 2105 | const align_ok = alignment.check(sym.n_value); | 2342 | new_offset, |
| 2106 | const need_realloc = !align_ok or new_atom_size > atom.capacity(self); | 2343 | new_offset + existing_size, |
| 2107 | if (!need_realloc) return sym.n_value; | 2344 | }); |
| 2108 | return self.allocateAtom(atom_index, new_atom_size, alignment); | ||
| 2109 | } | ||
| 2110 | 2345 | ||
| 2111 | pub fn allocateSymbol(self: *MachO) !u32 { | 2346 | try self.copyRangeAllZeroOut(header.offset, new_offset, existing_size); |
| 2112 | const gpa = self.base.comp.gpa; | ||
| 2113 | try self.locals.ensureUnusedCapacity(gpa, 1); | ||
| 2114 | 2347 | ||
| 2115 | const index = blk: { | 2348 | header.offset = @intCast(new_offset); |
| 2116 | if (self.locals_free_list.popOrNull()) |index| { | 2349 | header.size = existing_size; |
| 2117 | log.debug(" (reusing symbol index {d})", .{index}); | 2350 | self.segments.items[seg_id].fileoff = new_offset; |
| 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; | ||
| 2124 | } | 2351 | } |
| 2125 | }; | 2352 | } |
| 2353 | } | ||
| 2126 | 2354 | ||
| 2127 | self.locals.items[index] = .{ | 2355 | /// We allocate segments in a separate step to also consider segments that have no sections. |
| 2128 | .n_strx = 0, | 2356 | fn allocateSegments(self: *MachO) void { |
| 2129 | .n_type = 0, | 2357 | const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0; |
| 2130 | .n_sect = 0, | 2358 | const last_index = for (self.segments.items, 0..) |seg, i| { |
| 2131 | .n_desc = 0, | 2359 | if (mem.indexOf(u8, seg.segName(), "ZIG")) |_| break i; |
| 2132 | .n_value = 0, | 2360 | } else self.segments.items.len; |
| 2133 | }; | ||
| 2134 | 2361 | ||
| 2135 | return index; | 2362 | var vmaddr: u64 = if (self.pagezero_seg_index) |index| |
| 2136 | } | 2363 | self.segments.items[index].vmaddr + self.segments.items[index].vmsize |
| 2364 | else | ||
| 2365 | 0; | ||
| 2366 | var fileoff: u64 = 0; | ||
| 2137 | 2367 | ||
| 2138 | fn allocateGlobal(self: *MachO) !u32 { | 2368 | const page_size = self.getPageSize(); |
| 2139 | const gpa = self.base.comp.gpa; | 2369 | const slice = self.sections.slice(); |
| 2140 | try self.globals.ensureUnusedCapacity(gpa, 1); | ||
| 2141 | 2370 | ||
| 2142 | const index = blk: { | 2371 | var next_sect_id: u8 = 0; |
| 2143 | if (self.globals_free_list.popOrNull()) |index| { | 2372 | for (self.segments.items[first_index..last_index], first_index..last_index) |*seg, seg_id| { |
| 2144 | log.debug(" (reusing global index {d})", .{index}); | 2373 | seg.vmaddr = vmaddr; |
| 2145 | break :blk index; | 2374 | seg.fileoff = fileoff; |
| 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 | }; | ||
| 2153 | 2375 | ||
| 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]; | ||
| 2155 | 2379 | ||
| 2156 | return index; | 2380 | if (seg_id != sid) break; |
| 2157 | } | ||
| 2158 | 2381 | ||
| 2159 | pub fn addGotEntry(self: *MachO, reloc_target: SymbolWithLoc) !void { | 2382 | vmaddr = header.addr + header.size; |
| 2160 | if (self.got_table.lookup.contains(reloc_target)) return; | 2383 | if (!header.isZerofill()) { |
| 2161 | const gpa = self.base.comp.gpa; | 2384 | fileoff = header.offset + header.size; |
| 2162 | const got_index = try self.got_table.allocateEntry(gpa, reloc_target); | 2385 | } |
| 2163 | if (self.got_section_index == null) { | 2386 | } |
| 2164 | self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{ | 2387 | |
| 2165 | .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, | 2388 | seg.vmsize = vmaddr - seg.vmaddr; |
| 2166 | }); | 2389 | seg.filesize = fileoff - seg.fileoff; |
| 2167 | } | 2390 | |
| 2168 | if (self.mode == .incremental) { | 2391 | vmaddr = mem.alignForward(u64, vmaddr, page_size); |
| 2169 | try self.writeOffsetTableEntry(got_index); | 2392 | fileoff = mem.alignForward(u64, fileoff, page_size); |
| 2170 | self.got_table_count_dirty = true; | ||
| 2171 | self.markRelocsDirtyByTarget(reloc_target); | ||
| 2172 | } | 2393 | } |
| 2173 | } | 2394 | } |
| 2174 | 2395 | ||
| 2175 | pub fn addStubEntry(self: *MachO, reloc_target: SymbolWithLoc) !void { | 2396 | pub fn allocateAtoms(self: *MachO) void { |
| 2176 | if (self.stub_table.lookup.contains(reloc_target)) return; | 2397 | const slice = self.sections.slice(); |
| 2177 | const comp = self.base.comp; | 2398 | for (slice.items(.header), slice.items(.atoms)) |header, atoms| { |
| 2178 | const gpa = comp.gpa; | 2399 | if (atoms.items.len == 0) continue; |
| 2179 | const cpu_arch = comp.root_mod.resolved_target.result.cpu.arch; | 2400 | for (atoms.items) |atom_index| { |
| 2180 | const stub_index = try self.stub_table.allocateEntry(gpa, reloc_target); | 2401 | const atom = self.getAtom(atom_index).?; |
| 2181 | if (self.stubs_section_index == null) { | 2402 | assert(atom.flags.alive); |
| 2182 | self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{ | 2403 | atom.value += header.addr; |
| 2183 | .flags = macho.S_SYMBOL_STUBS | | 2404 | } |
| 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 | }); | ||
| 2196 | } | 2405 | } |
| 2197 | if (self.mode == .incremental) { | 2406 | |
| 2198 | try self.writeStubTableEntry(stub_index); | 2407 | for (self.thunks.items) |*thunk| { |
| 2199 | self.stub_table_count_dirty = true; | 2408 | const header = self.sections.items(.header)[thunk.out_n_sect]; |
| 2200 | self.markRelocsDirtyByTarget(reloc_target); | 2409 | thunk.value += header.addr; |
| 2201 | } | 2410 | } |
| 2202 | } | 2411 | } |
| 2203 | 2412 | ||
| 2204 | pub fn addTlvPtrEntry(self: *MachO, reloc_target: SymbolWithLoc) !void { | 2413 | fn allocateSyntheticSymbols(self: *MachO) void { |
| 2205 | if (self.tlv_ptr_table.lookup.contains(reloc_target)) return; | 2414 | const text_seg = self.getTextSegment(); |
| 2206 | const gpa = self.base.comp.gpa; | 2415 | |
| 2207 | _ = try self.tlv_ptr_table.allocateEntry(gpa, reloc_target); | 2416 | if (self.mh_execute_header_index) |index| { |
| 2208 | if (self.tlv_ptr_section_index == null) { | 2417 | const global = self.getSymbol(index); |
| 2209 | self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{ | 2418 | global.value = text_seg.vmaddr; |
| 2210 | .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS, | ||
| 2211 | }); | ||
| 2212 | } | 2419 | } |
| 2213 | } | ||
| 2214 | 2420 | ||
| 2215 | pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { | 2421 | if (self.data_sect_index) |idx| { |
| 2216 | if (build_options.skip_non_native and builtin.object_format != .macho) { | 2422 | const sect = self.sections.items(.header)[idx]; |
| 2217 | @panic("Attempted to compile for object format that was disabled by build configuration"); | 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 | } | ||
| 2218 | } | 2434 | } |
| 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(); | ||
| 2222 | 2435 | ||
| 2223 | const func = mod.funcInfo(func_index); | 2436 | for (self.boundary_symbols.items) |sym_index| { |
| 2224 | const decl_index = func.owner_decl; | 2437 | const sym = self.getSymbol(sym_index); |
| 2225 | const decl = mod.declPtr(decl_index); | 2438 | const name = sym.getName(self); |
| 2226 | 2439 | ||
| 2227 | const atom_index = try self.getOrCreateAtomForDecl(decl_index); | 2440 | sym.flags.@"export" = false; |
| 2228 | self.freeUnnamedConsts(decl_index); | 2441 | sym.value = text_seg.vmaddr; |
| 2229 | Atom.freeRelocations(self, atom_index); | ||
| 2230 | 2442 | ||
| 2231 | const gpa = self.base.comp.gpa; | 2443 | if (mem.startsWith(u8, name, "segment$start$")) { |
| 2232 | var code_buffer = std.ArrayList(u8).init(gpa); | 2444 | const segname = name["segment$start$".len..]; |
| 2233 | defer code_buffer.deinit(); | 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 | } | ||
| 2234 | 2477 | ||
| 2235 | var decl_state = if (self.d_sym) |*d_sym| | 2478 | if (self.objc_stubs.symbols.items.len > 0) { |
| 2236 | try d_sym.dwarf.initDeclState(mod, decl_index) | 2479 | const addr = self.sections.items(.header)[self.objc_stubs_sect_index.?].addr; |
| 2237 | else | ||
| 2238 | null; | ||
| 2239 | defer if (decl_state) |*ds| ds.deinit(); | ||
| 2240 | 2480 | ||
| 2241 | const res = if (decl_state) |*ds| | 2481 | for (self.objc_stubs.symbols.items, 0..) |sym_index, idx| { |
| 2242 | try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .{ | 2482 | const sym = self.getSymbol(sym_index); |
| 2243 | .dwarf = ds, | 2483 | sym.value = addr + idx * ObjcStubsSection.entrySize(self.getTarget().cpu.arch); |
| 2244 | }) | 2484 | sym.out_n_sect = self.objc_stubs_sect_index.?; |
| 2245 | else | 2485 | } |
| 2246 | try codegen.generateFunction(&self.base, decl.srcLoc(mod), func_index, air, liveness, &code_buffer, .none); | 2486 | } |
| 2247 | 2487 | } | |
| 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 | }; | ||
| 2256 | 2488 | ||
| 2257 | const addr = try self.updateDeclCode(decl_index, code); | 2489 | fn allocateLinkeditSegment(self: *MachO) !void { |
| 2490 | var fileoff: u64 = 0; | ||
| 2491 | var vmaddr: u64 = 0; | ||
| 2258 | 2492 | ||
| 2259 | if (decl_state) |*ds| { | 2493 | for (self.segments.items) |seg| { |
| 2260 | try self.d_sym.?.dwarf.commitDeclState( | 2494 | if (fileoff < seg.fileoff + seg.filesize) fileoff = seg.fileoff + seg.filesize; |
| 2261 | mod, | 2495 | if (vmaddr < seg.vmaddr + seg.vmsize) vmaddr = seg.vmaddr + seg.vmsize; |
| 2262 | decl_index, | ||
| 2263 | addr, | ||
| 2264 | self.getAtom(atom_index).size, | ||
| 2265 | ds, | ||
| 2266 | ); | ||
| 2267 | } | 2496 | } |
| 2268 | 2497 | ||
| 2269 | // Since we updated the vaddr and the size, each corresponding export symbol also | 2498 | const page_size = self.getPageSize(); |
| 2270 | // needs to be updated. | 2499 | const seg = self.getLinkeditSegment(); |
| 2271 | try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); | 2500 | seg.vmaddr = mem.alignForward(u64, vmaddr, page_size); |
| 2501 | seg.fileoff = mem.alignForward(u64, fileoff, page_size); | ||
| 2272 | } | 2502 | } |
| 2273 | 2503 | ||
| 2274 | pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 { | 2504 | fn initDyldInfoSections(self: *MachO) !void { |
| 2505 | const tracy = trace(@src()); | ||
| 2506 | defer tracy.end(); | ||
| 2507 | |||
| 2275 | const gpa = self.base.comp.gpa; | 2508 | const gpa = self.base.comp.gpa; |
| 2276 | const mod = self.base.comp.module.?; | 2509 | |
| 2277 | const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index); | 2510 | if (self.zig_got_sect_index != null) try self.zig_got.addDyldRelocs(self); |
| 2278 | if (!gop.found_existing) { | 2511 | if (self.got_sect_index != null) try self.got.addDyldRelocs(self); |
| 2279 | gop.value_ptr.* = .{}; | 2512 | if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self); |
| 2280 | } | 2513 | if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self); |
| 2281 | const unnamed_consts = gop.value_ptr; | 2514 | try self.initExportTrie(); |
| 2282 | const decl = mod.declPtr(decl_index); | 2515 | |
| 2283 | const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)); | 2516 | var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1); |
| 2284 | const index = unnamed_consts.items.len; | 2517 | defer objects.deinit(); |
| 2285 | const name = try std.fmt.allocPrint(gpa, "___unnamed_{s}_{d}", .{ decl_name, index }); | 2518 | if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); |
| 2286 | defer gpa.free(name); | 2519 | objects.appendSliceAssumeCapacity(self.objects.items); |
| 2287 | const atom_index = switch (try self.lowerConst(name, typed_value, typed_value.ty.abiAlignment(mod), self.data_const_section_index.?, decl.srcLoc(mod))) { | 2520 | |
| 2288 | .ok => |atom_index| atom_index, | 2521 | var nrebases: usize = 0; |
| 2289 | .fail => |em| { | 2522 | var nbinds: usize = 0; |
| 2290 | decl.analysis = .codegen_failure; | 2523 | var nweak_binds: usize = 0; |
| 2291 | try mod.failed_decls.put(mod.gpa, decl_index, em); | 2524 | for (objects.items) |index| { |
| 2292 | log.debug("{s}", .{em.msg}); | 2525 | const ctx = switch (self.getFile(index).?) { |
| 2293 | return error.CodegenFail; | 2526 | .zig_object => |x| x.dynamic_relocs, |
| 2294 | }, | 2527 | .object => |x| x.dynamic_relocs, |
| 2295 | }; | 2528 | else => unreachable, |
| 2296 | try unnamed_consts.append(gpa, atom_index); | 2529 | }; |
| 2297 | const atom = self.getAtomPtr(atom_index); | 2530 | nrebases += ctx.rebase_relocs; |
| 2298 | return atom.getSymbolIndex().?; | 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); | ||
| 2299 | } | 2537 | } |
| 2300 | 2538 | ||
| 2301 | const LowerConstResult = union(enum) { | 2539 | fn initExportTrie(self: *MachO) !void { |
| 2302 | ok: Atom.Index, | 2540 | const tracy = trace(@src()); |
| 2303 | fail: *Module.ErrorMsg, | 2541 | defer tracy.end(); |
| 2304 | }; | ||
| 2305 | 2542 | ||
| 2306 | fn 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 { | ||
| 2314 | const gpa = self.base.comp.gpa; | 2543 | 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 | } | ||
| 2315 | 2571 | ||
| 2316 | var code_buffer = std.ArrayList(u8).init(gpa); | 2572 | if (self.mh_execute_header_index) |index| { |
| 2317 | defer code_buffer.deinit(); | 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 | } | ||
| 2318 | 2581 | ||
| 2319 | log.debug("allocating symbol indexes for {s}", .{name}); | 2582 | fn writeAtoms(self: *MachO) !void { |
| 2583 | const tracy = trace(@src()); | ||
| 2584 | defer tracy.end(); | ||
| 2320 | 2585 | ||
| 2321 | const sym_index = try self.allocateSymbol(); | 2586 | const gpa = self.base.comp.gpa; |
| 2322 | const atom_index = try self.createAtom(sym_index, .{}); | 2587 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 2323 | try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index); | 2588 | defer arena.deinit(); |
| 2324 | 2589 | ||
| 2325 | const res = try codegen.generateSymbol(&self.base, src_loc, tv, &code_buffer, .none, .{ | 2590 | const cpu_arch = self.getTarget().cpu.arch; |
| 2326 | .parent_atom_index = self.getAtom(atom_index).getSymbolIndex().?, | 2591 | const slice = self.sections.slice(); |
| 2327 | }); | ||
| 2328 | const code = switch (res) { | ||
| 2329 | .ok => code_buffer.items, | ||
| 2330 | .fail => |em| return .{ .fail = em }, | ||
| 2331 | }; | ||
| 2332 | 2592 | ||
| 2333 | const atom = self.getAtomPtr(atom_index); | 2593 | var has_resolve_error = false; |
| 2334 | atom.size = code.len; | 2594 | for (slice.items(.header), slice.items(.atoms)) |header, atoms| { |
| 2335 | // TODO: work out logic for disambiguating functions from function pointers | 2595 | if (atoms.items.len == 0) continue; |
| 2336 | // const sect_id = self.getDeclOutputSection(decl_index); | 2596 | if (header.isZerofill()) continue; |
| 2337 | const symbol = atom.getSymbolPtr(self); | 2597 | |
| 2338 | const name_str_index = try self.strtab.insert(gpa, name); | 2598 | const size = math.cast(usize, header.size) orelse return error.Overflow; |
| 2339 | symbol.n_strx = name_str_index; | 2599 | const buffer = try gpa.alloc(u8, size); |
| 2340 | symbol.n_type = macho.N_SECT; | 2600 | defer gpa.free(buffer); |
| 2341 | symbol.n_sect = sect_id + 1; | 2601 | const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; |
| 2342 | symbol.n_value = try self.allocateAtom(atom_index, code.len, required_alignment); | 2602 | @memset(buffer, padding_byte); |
| 2343 | errdefer self.freeAtom(atom_index); | 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 | } | ||
| 2344 | 2620 | ||
| 2345 | log.debug("allocated atom for {s} at 0x{x}", .{ name, symbol.n_value }); | 2621 | try self.base.file.?.pwriteAll(buffer, header.offset); |
| 2346 | log.debug(" (required alignment 0x{x})", .{required_alignment}); | 2622 | } |
| 2347 | 2623 | ||
| 2348 | try self.writeAtom(atom_index, code); | 2624 | for (self.thunks.items) |thunk| { |
| 2349 | self.markRelocsDirtyByTarget(atom.getSymbolWithLoc()); | 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 | } | ||
| 2350 | 2633 | ||
| 2351 | return .{ .ok = atom_index }; | 2634 | if (has_resolve_error) return error.ResolveFailed; |
| 2352 | } | 2635 | } |
| 2353 | 2636 | ||
| 2354 | pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void { | 2637 | fn writeUnwindInfo(self: *MachO) !void { |
| 2355 | if (build_options.skip_non_native and builtin.object_format != .macho) { | 2638 | const tracy = trace(@src()); |
| 2356 | @panic("Attempted to compile for object format that was disabled by build configuration"); | 2639 | defer tracy.end(); |
| 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 | |||
| 2441 | fn 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 | |||
| 2511 | pub 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 | |||
| 2543 | fn 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 | |||
| 2638 | pub 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 | |||
| 2654 | fn 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 | |||
| 2693 | fn 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 | |||
| 2764 | pub 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 | |||
| 2770 | pub 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 | |||
| 2902 | pub 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 | |||
| 2938 | fn 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 | |||
| 2947 | pub 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 | |||
| 2967 | pub 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 | |||
| 2986 | pub 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 | |||
| 3037 | pub 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 | |||
| 3056 | const PopulateMissingMetadataOptions = struct { | ||
| 3057 | symbol_count_hint: u64, | ||
| 3058 | program_code_size_hint: u64, | ||
| 3059 | }; | ||
| 3060 | |||
| 3061 | fn 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 | |||
| 3212 | fn 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 | |||
| 3226 | const InitSectionOpts = struct { | ||
| 3227 | flags: u32 = macho.S_REGULAR, | ||
| 3228 | reserved1: u32 = 0, | ||
| 3229 | reserved2: u32 = 0, | ||
| 3230 | }; | ||
| 3231 | |||
| 3232 | pub 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 | |||
| 3249 | fn 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 | |||
| 3307 | fn 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 | |||
| 3353 | fn 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 | |||
| 3391 | pub 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 | |||
| 3408 | fn 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 | |||
| 3530 | pub 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 | |||
| 3538 | pub 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 | |||
| 3565 | pub 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 | |||
| 3593 | fn 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 | |||
| 3616 | fn 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; | ||
| 3710 | 2640 | ||
| 3711 | const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr)); | 2641 | const gpa = self.base.comp.gpa; |
| 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}); | ||
| 3715 | 2642 | ||
| 3716 | try rebase.entries.append(gpa, .{ | 2643 | if (self.eh_frame_sect_index) |index| { |
| 3717 | .offset = offset, | 2644 | const header = self.sections.items(.header)[index]; |
| 3718 | .segment_id = segment_id, | 2645 | const size = math.cast(usize, header.size) orelse return error.Overflow; |
| 3719 | }); | 2646 | const buffer = try gpa.alloc(u8, size); |
| 3720 | } | 2647 | defer gpa.free(buffer); |
| 3721 | } | 2648 | eh_frame.write(self, buffer); |
| 2649 | try self.base.file.?.pwriteAll(buffer, header.offset); | ||
| 3722 | } | 2650 | } |
| 3723 | 2651 | ||
| 3724 | try rebase.finalize(gpa); | 2652 | if (self.unwind_info_sect_index) |index| { |
| 3725 | } | 2653 | const header = self.sections.items(.header)[index]; |
| 3726 | 2654 | const size = math.cast(usize, header.size) orelse return error.Overflow; | |
| 3727 | fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void { | 2655 | const buffer = try gpa.alloc(u8, size); |
| 3728 | const gpa = self.base.comp.gpa; | 2656 | defer gpa.free(buffer); |
| 3729 | const header = self.sections.items(.header)[sect_id]; | 2657 | try self.unwind_info.write(self, buffer); |
| 3730 | const segment_index = self.sections.items(.segment_index)[sect_id]; | 2658 | try self.base.file.?.pwriteAll(buffer, header.offset); |
| 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 | }); | ||
| 3755 | } | 2659 | } |
| 3756 | } | 2660 | } |
| 3757 | 2661 | ||
| 3758 | fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void { | 2662 | fn finalizeDyldInfoSections(self: *MachO) !void { |
| 2663 | const tracy = trace(@src()); | ||
| 2664 | defer tracy.end(); | ||
| 3759 | const gpa = self.base.comp.gpa; | 2665 | 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); | ||
| 3842 | 2666 | ||
| 3843 | for (relocs) |rel| { | 2667 | try self.rebase.finalize(gpa); |
| 3844 | switch (cpu_arch) { | 2668 | try self.bind.finalize(gpa, self); |
| 3845 | .aarch64 => { | 2669 | try self.weak_bind.finalize(gpa, self); |
| 3846 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | 2670 | try self.lazy_bind.finalize(gpa, self); |
| 3847 | if (rel_type != .ARM64_RELOC_UNSIGNED) continue; | 2671 | try self.export_trie.finalize(gpa); |
| 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); | ||
| 3895 | } | 2672 | } |
| 3896 | 2673 | ||
| 3897 | fn collectLazyBindData(self: *MachO, bind: anytype) !void { | 2674 | fn writeSyntheticSections(self: *MachO) !void { |
| 3898 | const sect_id = self.la_symbol_ptr_section_index orelse return; | 2675 | const tracy = trace(@src()); |
| 3899 | const gpa = self.base.comp.gpa; | 2676 | defer tracy.end(); |
| 3900 | try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table); | ||
| 3901 | try bind.finalize(gpa, self); | ||
| 3902 | } | ||
| 3903 | 2677 | ||
| 3904 | fn collectExportData(self: *MachO, trie: *Trie) !void { | ||
| 3905 | const gpa = self.base.comp.gpa; | 2678 | const gpa = self.base.comp.gpa; |
| 3906 | 2679 | ||
| 3907 | // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER. | 2680 | if (self.got_sect_index) |sect_id| { |
| 3908 | log.debug("generating export trie", .{}); | 2681 | const header = self.sections.items(.header)[sect_id]; |
| 3909 | 2682 | const size = math.cast(usize, header.size) orelse return error.Overflow; | |
| 3910 | const exec_segment = self.segments.items[self.header_segment_cmd_index.?]; | 2683 | var buffer = try std.ArrayList(u8).initCapacity(gpa, size); |
| 3911 | const base_address = exec_segment.vmaddr; | 2684 | defer buffer.deinit(); |
| 3912 | 2685 | try self.got.write(self, buffer.writer()); | |
| 3913 | for (self.globals.items) |global| { | 2686 | assert(buffer.items.len == header.size); |
| 3914 | const sym = self.getSymbol(global); | 2687 | try self.base.file.?.pwriteAll(buffer.items, header.offset); |
| 3915 | 2688 | } | |
| 3916 | if (sym.undf()) continue; | 2689 | |
| 3917 | assert(sym.ext()); | 2690 | if (self.stubs_sect_index) |sect_id| { |
| 3918 | if (sym.n_desc == N_DEAD) continue; | 2691 | const header = self.sections.items(.header)[sect_id]; |
| 3919 | if (sym.n_desc == N_BOUNDARY) continue; | 2692 | const size = math.cast(usize, header.size) orelse return error.Overflow; |
| 3920 | 2693 | var buffer = try std.ArrayList(u8).initCapacity(gpa, size); | |
| 3921 | const sym_name = self.getSymbolName(global); | 2694 | defer buffer.deinit(); |
| 3922 | log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value }); | 2695 | try self.stubs.write(self, buffer.writer()); |
| 3923 | try trie.put(gpa, .{ | 2696 | assert(buffer.items.len == header.size); |
| 3924 | .name = sym_name, | 2697 | try self.base.file.?.pwriteAll(buffer.items, header.offset); |
| 3925 | .vmaddr_offset = sym.n_value - base_address, | 2698 | } |
| 3926 | .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, | 2699 | |
| 3927 | }); | 2700 | if (self.stubs_helper_sect_index) |sect_id| { |
| 3928 | } | 2701 | const header = self.sections.items(.header)[sect_id]; |
| 3929 | 2702 | const size = math.cast(usize, header.size) orelse return error.Overflow; | |
| 3930 | try trie.finalize(gpa); | 2703 | var buffer = try std.ArrayList(u8).initCapacity(gpa, size); |
| 3931 | } | 2704 | defer buffer.deinit(); |
| 3932 | 2705 | try self.stubs_helper.write(self, buffer.writer()); | |
| 3933 | fn writeDyldInfoData(self: *MachO) !void { | 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 | |||
| 2741 | fn writeDyldInfoSections(self: *MachO, off: u32) !u32 { | ||
| 3934 | const tracy = trace(@src()); | 2742 | const tracy = trace(@src()); |
| 3935 | defer tracy.end(); | 2743 | defer tracy.end(); |
| 3936 | 2744 | ||
| 3937 | const gpa = self.base.comp.gpa; | 2745 | const gpa = self.base.comp.gpa; |
| 2746 | const cmd = &self.dyld_info_cmd; | ||
| 2747 | var needed_size: u32 = 0; | ||
| 3938 | 2748 | ||
| 3939 | var rebase = Rebase{}; | 2749 | cmd.rebase_off = needed_size; |
| 3940 | defer rebase.deinit(gpa); | 2750 | cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64)); |
| 3941 | try self.collectRebaseData(&rebase); | 2751 | needed_size += cmd.rebase_size; |
| 3942 | 2752 | ||
| 3943 | var bind = Bind{}; | 2753 | cmd.bind_off = needed_size; |
| 3944 | defer bind.deinit(gpa); | 2754 | cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64)); |
| 3945 | try self.collectBindData(&bind, self.bindings); | 2755 | needed_size += cmd.bind_size; |
| 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 | }); | ||
| 3975 | 2756 | ||
| 3976 | const export_off = lazy_bind_off + lazy_bind_size_aligned; | 2757 | cmd.weak_bind_off = needed_size; |
| 3977 | const export_size = trie.size; | 2758 | cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64)); |
| 3978 | const export_size_aligned = mem.alignForward(u64, export_size, @alignOf(u64)); | 2759 | needed_size += cmd.weak_bind_size; |
| 3979 | log.debug("writing export trie from 0x{x} to 0x{x}", .{ export_off, export_off + export_size_aligned }); | ||
| 3980 | 2760 | ||
| 3981 | const needed_size = math.cast(usize, export_off + export_size_aligned - rebase_off) orelse | 2761 | cmd.lazy_bind_off = needed_size; |
| 3982 | return error.Overflow; | 2762 | cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64)); |
| 3983 | link_seg.filesize = needed_size; | 2763 | needed_size += cmd.lazy_bind_size; |
| 3984 | assert(mem.isAlignedGeneric(u64, link_seg.fileoff + link_seg.filesize, @alignOf(u64))); | 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; | ||
| 3985 | 2768 | ||
| 3986 | const buffer = try gpa.alloc(u8, needed_size); | 2769 | const buffer = try gpa.alloc(u8, needed_size); |
| 3987 | defer gpa.free(buffer); | 2770 | defer gpa.free(buffer); |
| ... | @@ -3990,689 +2773,374 @@ fn writeDyldInfoData(self: *MachO) !void { | ... | @@ -3990,689 +2773,374 @@ fn writeDyldInfoData(self: *MachO) !void { |
| 3990 | var stream = std.io.fixedBufferStream(buffer); | 2773 | var stream = std.io.fixedBufferStream(buffer); |
| 3991 | const writer = stream.writer(); | 2774 | const writer = stream.writer(); |
| 3992 | 2775 | ||
| 3993 | try rebase.write(writer); | 2776 | try self.rebase.write(writer); |
| 3994 | try stream.seekTo(bind_off - rebase_off); | 2777 | try stream.seekTo(cmd.bind_off); |
| 3995 | 2778 | try self.bind.write(writer); | |
| 3996 | try bind.write(writer); | 2779 | try stream.seekTo(cmd.weak_bind_off); |
| 3997 | try stream.seekTo(lazy_bind_off - rebase_off); | 2780 | try self.weak_bind.write(writer); |
| 3998 | 2781 | try stream.seekTo(cmd.lazy_bind_off); | |
| 3999 | try lazy_bind.write(writer); | 2782 | try self.lazy_bind.write(writer); |
| 4000 | try stream.seekTo(export_off - rebase_off); | 2783 | try stream.seekTo(cmd.export_off); |
| 4001 | 2784 | try self.export_trie.write(writer); | |
| 4002 | _ = try trie.write(writer); | ||
| 4003 | 2785 | ||
| 4004 | log.debug("writing dyld info from 0x{x} to 0x{x}", .{ | 2786 | cmd.rebase_off += off; |
| 4005 | rebase_off, | 2787 | cmd.bind_off += off; |
| 4006 | rebase_off + needed_size, | 2788 | cmd.weak_bind_off += off; |
| 4007 | }); | 2789 | cmd.lazy_bind_off += off; |
| 2790 | cmd.export_off += off; | ||
| 4008 | 2791 | ||
| 4009 | try self.base.file.?.pwriteAll(buffer, rebase_off); | 2792 | try self.base.file.?.pwriteAll(buffer, off); |
| 4010 | try self.populateLazyBindOffsetsInStubHelper(lazy_bind); | ||
| 4011 | 2793 | ||
| 4012 | self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off)); | 2794 | return off + needed_size; |
| 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)); | ||
| 4020 | } | 2795 | } |
| 4021 | 2796 | ||
| 4022 | fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: anytype) !void { | 2797 | fn writeFunctionStarts(self: *MachO, off: u32) !u32 { |
| 4023 | if (lazy_bind.size() == 0) return; | 2798 | // TODO actually write it out |
| 4024 | 2799 | const cmd = &self.function_starts_cmd; | |
| 4025 | const stub_helper_section_index = self.stub_helper_section_index.?; | 2800 | cmd.dataoff = off; |
| 4026 | // assert(ctx.stub_helper_preamble_allocated); | 2801 | return off; |
| 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 | } | ||
| 4048 | } | 2802 | } |
| 4049 | 2803 | ||
| 4050 | const asc_u64 = std.sort.asc(u64); | 2804 | pub fn writeDataInCode(self: *MachO, base_address: u64, off: u32) !u32 { |
| 4051 | 2805 | const cmd = &self.data_in_code_cmd; | |
| 4052 | fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void { | 2806 | cmd.dataoff = off; |
| 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 | } | ||
| 4060 | 2807 | ||
| 4061 | fn writeFunctionStarts(self: *MachO) !void { | ||
| 4062 | const gpa = self.base.comp.gpa; | 2808 | 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(); | ||
| 4064 | 2811 | ||
| 4065 | // We need to sort by address first | 2812 | for (self.objects.items) |index| { |
| 4066 | var addresses = std.ArrayList(u64).init(gpa); | 2813 | const object = self.getFile(index).?.object; |
| 4067 | defer addresses.deinit(); | 2814 | const in_dices = object.getDataInCode(); |
| 4068 | 2815 | ||
| 4069 | for (self.objects.items) |object| { | 2816 | try dices.ensureUnusedCapacity(in_dices.len); |
| 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); | ||
| 4074 | 2817 | ||
| 4075 | var it = Atom.getInnerSymbolsIterator(self, atom_index); | 2818 | var next_dice: usize = 0; |
| 4076 | while (it.next()) |inner_sym_loc| { | 2819 | for (object.atoms.items) |atom_index| { |
| 4077 | try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses); | 2820 | if (next_dice >= in_dices.len) break; |
| 4078 | } | 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 | }; | ||
| 4079 | } | 2839 | } |
| 4080 | } | 2840 | } |
| 4081 | 2841 | ||
| 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; | ||
| 4083 | 2844 | ||
| 4084 | var offsets = std.ArrayList(u32).init(gpa); | 2845 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(dices.items), cmd.dataoff); |
| 4085 | defer offsets.deinit(); | ||
| 4086 | try offsets.ensureTotalCapacityPrecise(addresses.items.len); | ||
| 4087 | 2846 | ||
| 4088 | var last_off: u32 = 0; | 2847 | return off + needed_size; |
| 4089 | for (addresses.items) |addr| { | 2848 | } |
| 4090 | const offset = @as(u32, @intCast(addr - seg.vmaddr)); | ||
| 4091 | const diff = offset - last_off; | ||
| 4092 | 2849 | ||
| 4093 | if (diff == 0) continue; | 2850 | pub fn calcSymtabSize(self: *MachO) !void { |
| 2851 | const tracy = trace(@src()); | ||
| 2852 | defer tracy.end(); | ||
| 2853 | const gpa = self.base.comp.gpa; | ||
| 4094 | 2854 | ||
| 4095 | offsets.appendAssumeCapacity(diff); | 2855 | var nlocals: u32 = 0; |
| 4096 | last_off = offset; | 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; | ||
| 4097 | } | 2894 | } |
| 4098 | 2895 | ||
| 4099 | var buffer = std.ArrayList(u8).init(gpa); | 2896 | { |
| 4100 | defer buffer.deinit(); | 2897 | const cmd = &self.symtab_cmd; |
| 4101 | 2898 | cmd.nsyms = nlocals + nstabs + nexports + nimports; | |
| 4102 | const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64))); | 2899 | cmd.strsize = strsize + 1; |
| 4103 | try buffer.ensureTotalCapacity(max_size); | ||
| 4104 | |||
| 4105 | for (offsets.items) |offset| { | ||
| 4106 | try std.leb.writeULEB128(buffer.writer(), offset); | ||
| 4107 | } | 2900 | } |
| 4108 | 2901 | ||
| 4109 | const link_seg = self.getLinkeditSegmentPtr(); | 2902 | { |
| 4110 | const offset = link_seg.fileoff + link_seg.filesize; | 2903 | const cmd = &self.dysymtab_cmd; |
| 4111 | assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64))); | 2904 | cmd.ilocalsym = 0; |
| 4112 | const needed_size = buffer.items.len; | 2905 | cmd.nlocalsym = nlocals + nstabs; |
| 4113 | const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64)); | 2906 | cmd.iextdefsym = nlocals + nstabs; |
| 4114 | const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow; | 2907 | cmd.nextdefsym = nexports; |
| 4115 | if (padding > 0) { | 2908 | cmd.iundefsym = nlocals + nstabs + nexports; |
| 4116 | try buffer.ensureUnusedCapacity(padding); | 2909 | cmd.nundefsym = nimports; |
| 4117 | buffer.appendNTimesAssumeCapacity(0, padding); | ||
| 4118 | } | 2910 | } |
| 4119 | link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff; | 2911 | } |
| 4120 | |||
| 4121 | log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned }); | ||
| 4122 | 2912 | ||
| 4123 | try self.base.file.?.pwriteAll(buffer.items, offset); | 2913 | pub 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; | ||
| 4124 | 2919 | ||
| 4125 | self.function_starts_cmd.dataoff = @as(u32, @intCast(offset)); | 2920 | try self.symtab.resize(gpa, cmd.nsyms); |
| 4126 | self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned)); | 2921 | try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1); |
| 4127 | } | ||
| 4128 | 2922 | ||
| 4129 | fn filterDataInCode( | 2923 | if (self.getZigObject()) |zo| { |
| 4130 | dices: []const macho.data_in_code_entry, | 2924 | zo.writeSymtab(self); |
| 4131 | start_addr: u64, | 2925 | } |
| 4132 | end_addr: u64, | 2926 | for (self.objects.items) |index| { |
| 4133 | ) []const macho.data_in_code_entry { | 2927 | try self.getFile(index).?.writeSymtab(self); |
| 4134 | const Predicate = struct { | 2928 | } |
| 4135 | addr: u64, | 2929 | for (self.dylibs.items) |index| { |
| 2930 | try self.getFile(index).?.writeSymtab(self); | ||
| 2931 | } | ||
| 2932 | if (self.getInternalObject()) |internal| { | ||
| 2933 | internal.writeSymtab(self); | ||
| 2934 | } | ||
| 4136 | 2935 | ||
| 4137 | pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool { | 2936 | assert(self.strtab.items.len == cmd.strsize); |
| 4138 | return dice.offset >= self.addr; | ||
| 4139 | } | ||
| 4140 | }; | ||
| 4141 | 2937 | ||
| 4142 | const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr }); | 2938 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff); |
| 4143 | const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start; | ||
| 4144 | 2939 | ||
| 4145 | return dices[start..end]; | 2940 | return off + cmd.nsyms * @sizeOf(macho.nlist_64); |
| 4146 | } | 2941 | } |
| 4147 | 2942 | ||
| 4148 | pub fn writeDataInCode(self: *MachO) !void { | 2943 | fn writeIndsymtab(self: *MachO, off: u32) !u32 { |
| 4149 | const gpa = self.base.comp.gpa; | 2944 | const gpa = self.base.comp.gpa; |
| 4150 | var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa); | 2945 | const cmd = &self.dysymtab_cmd; |
| 4151 | defer out_dice.deinit(); | 2946 | cmd.indirectsymoff = off; |
| 4152 | 2947 | cmd.nindirectsyms = self.indsymtab.nsyms(self); | |
| 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 | } | ||
| 4204 | 2948 | ||
| 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()); | ||
| 4206 | 2953 | ||
| 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); | ||
| 4208 | 2956 | ||
| 4209 | self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset)); | 2957 | return off + needed_size; |
| 4210 | self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned)); | ||
| 4211 | } | 2958 | } |
| 4212 | 2959 | ||
| 4213 | fn writeSymtabs(self: *MachO) !void { | 2960 | pub fn writeStrtab(self: *MachO, off: u32) !u32 { |
| 4214 | var ctx = try self.writeSymtab(); | 2961 | const cmd = &self.symtab_cmd; |
| 4215 | defer ctx.imports_table.deinit(); | 2962 | cmd.stroff = off; |
| 4216 | try self.writeDysymtab(ctx); | 2963 | try self.base.file.?.pwriteAll(self.strtab.items, cmd.stroff); |
| 4217 | try self.writeStrtab(); | 2964 | return off + cmd.strsize; |
| 4218 | } | 2965 | } |
| 4219 | 2966 | ||
| 4220 | fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void { | 2967 | fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } { |
| 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 | ||
| 4227 | const gpa = self.base.comp.gpa; | 2968 | const gpa = self.base.comp.gpa; |
| 4228 | var out_sym = sym; | 2969 | const needed_size = load_commands.calcLoadCommandsSize(self, false); |
| 4229 | out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(sym_loc)); | 2970 | const buffer = try gpa.alloc(u8, needed_size); |
| 4230 | try locals.append(out_sym); | 2971 | defer gpa.free(buffer); |
| 4231 | } | ||
| 4232 | |||
| 4233 | fn 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(); | ||
| 4239 | 2972 | ||
| 4240 | for (0..self.locals.items.len) |sym_id| { | 2973 | var stream = std.io.fixedBufferStream(buffer); |
| 4241 | try self.addLocalToSymtab(.{ .sym_index = @intCast(sym_id) }, &locals); | 2974 | var cwriter = std.io.countingWriter(stream.writer()); |
| 4242 | } | 2975 | const writer = cwriter.writer(); |
| 4243 | 2976 | ||
| 4244 | for (self.objects.items) |object| { | 2977 | var ncmds: usize = 0; |
| 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); | ||
| 4249 | 2978 | ||
| 4250 | var it = Atom.getInnerSymbolsIterator(self, atom_index); | 2979 | // Segment and section load commands |
| 4251 | while (it.next()) |inner_sym_loc| { | 2980 | { |
| 4252 | try self.addLocalToSymtab(inner_sym_loc, &locals); | 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); | ||
| 4253 | } | 2987 | } |
| 2988 | sect_id += seg.nsects; | ||
| 4254 | } | 2989 | } |
| 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; | ||
| 4255 | } | 3018 | } |
| 4256 | 3019 | ||
| 4257 | var exports = std.ArrayList(macho.nlist_64).init(gpa); | 3020 | if (self.base.isDynLib()) { |
| 4258 | defer exports.deinit(); | 3021 | try load_commands.writeDylibIdLC(self, writer); |
| 4259 | 3022 | ncmds += 1; | |
| 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); | ||
| 4268 | } | 3023 | } |
| 4269 | 3024 | ||
| 4270 | var imports = std.ArrayList(macho.nlist_64).init(gpa); | 3025 | try load_commands.writeRpathLCs(self.base.rpath_list, writer); |
| 4271 | defer imports.deinit(); | 3026 | ncmds += self.base.rpath_list.len; |
| 4272 | 3027 | ||
| 4273 | var imports_table = std.AutoHashMap(SymbolWithLoc, u32).init(gpa); | 3028 | try writer.writeStruct(macho.source_version_command{ .version = 0 }); |
| 3029 | ncmds += 1; | ||
| 4274 | 3030 | ||
| 4275 | for (self.globals.items) |global| { | 3031 | if (self.platform.isBuildVersionCompatible()) { |
| 4276 | const sym = self.getSymbol(global); | 3032 | try load_commands.writeBuildVersionLC(self.platform, self.sdk_version, writer); |
| 4277 | if (sym.n_strx == 0) continue; // no name, skip | 3033 | ncmds += 1; |
| 4278 | if (!sym.undf()) continue; // not an import, skip | 3034 | } else { |
| 4279 | if (sym.n_desc == N_DEAD) continue; | 3035 | try load_commands.writeVersionMinLC(self.platform, self.sdk_version, writer); |
| 4280 | if (sym.n_desc == N_BOUNDARY) continue; | 3036 | ncmds += 1; |
| 4281 | const new_index = @as(u32, @intCast(imports.items.len)); | 3037 | } |
| 4282 | var out_sym = sym; | 3038 | |
| 4283 | out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global)); | 3039 | const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + cwriter.bytes_written; |
| 4284 | try imports.append(out_sym); | 3040 | try writer.writeStruct(self.uuid_cmd); |
| 4285 | try imports_table.putNoClobber(global, new_index); | 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; | ||
| 4286 | } | 3060 | } |
| 4287 | 3061 | ||
| 4288 | // We generate stabs last in order to ensure that the strtab always has debug info | 3062 | if (self.requiresCodeSig()) { |
| 4289 | // strings trailing | 3063 | try writer.writeStruct(self.codesig_cmd); |
| 4290 | if (comp.config.debug_format != .strip) { | 3064 | ncmds += 1; |
| 4291 | for (self.objects.items) |object| { | ||
| 4292 | assert(self.d_sym == null); // TODO | ||
| 4293 | try self.generateSymbolStabs(object, &locals); | ||
| 4294 | } | ||
| 4295 | } | 3065 | } |
| 4296 | 3066 | ||
| 4297 | const nlocals = @as(u32, @intCast(locals.items.len)); | 3067 | assert(cwriter.bytes_written == needed_size); |
| 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); | ||
| 4318 | 3068 | ||
| 4319 | self.symtab_cmd.symoff = @as(u32, @intCast(offset)); | 3069 | try self.base.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); |
| 4320 | self.symtab_cmd.nsyms = nsyms; | ||
| 4321 | 3070 | ||
| 4322 | return SymtabCtx{ | 3071 | return .{ ncmds, buffer.len, uuid_cmd_offset }; |
| 4323 | .nlocalsym = nlocals, | ||
| 4324 | .nextdefsym = nexports, | ||
| 4325 | .nundefsym = nimports, | ||
| 4326 | .imports_table = imports_table, | ||
| 4327 | }; | ||
| 4328 | } | 3072 | } |
| 4329 | 3073 | ||
| 4330 | // TODO this function currently skips generating symbol stabs in case errors are encountered in DWARF data. | 3074 | fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void { |
| 4331 | // I think we should actually report those errors to the user and let them decide if they want to strip debug info | 3075 | var header: macho.mach_header_64 = .{}; |
| 4332 | // in that case or not. | 3076 | header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK; |
| 4333 | fn 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); | ||
| 4374 | 3077 | ||
| 4375 | blk: { | 3078 | // TODO: if (self.options.namespace == .two_level) { |
| 4376 | while (attr_it.next() catch break :blk) |attr| switch (attr.name) { | 3079 | header.flags |= macho.MH_TWOLEVEL; |
| 4377 | dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue, | 3080 | // } |
| 4378 | dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue, | ||
| 4379 | else => continue, | ||
| 4380 | }; | ||
| 4381 | } | ||
| 4382 | 3081 | ||
| 4383 | if (maybe_tu_name == null or maybe_tu_comp_dir == null) { | 3082 | switch (self.getTarget().cpu.arch) { |
| 4384 | log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name}); | 3083 | .aarch64 => { |
| 4385 | return; | 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 => {}, | ||
| 4386 | } | 3092 | } |
| 4387 | 3093 | ||
| 4388 | const tu_name = maybe_tu_name.?; | 3094 | if (self.base.isDynLib()) { |
| 4389 | const tu_comp_dir = maybe_tu_comp_dir.?; | 3095 | header.filetype = macho.MH_DYLIB; |
| 4390 | 3096 | } else { | |
| 4391 | // Open scope | 3097 | header.filetype = macho.MH_EXECUTE; |
| 4392 | try locals.ensureUnusedCapacity(3); | 3098 | header.flags |= macho.MH_PIE; |
| 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 | } | ||
| 4449 | } | 3099 | } |
| 4450 | 3100 | ||
| 4451 | // Close scope | 3101 | const has_reexports = for (self.dylibs.items) |index| { |
| 4452 | try locals.append(.{ | 3102 | if (self.getFile(index).?.dylib.reexport) break true; |
| 4453 | .n_strx = 0, | 3103 | } else false; |
| 4454 | .n_type = macho.N_SO, | 3104 | if (!has_reexports) { |
| 4455 | .n_sect = 0, | 3105 | header.flags |= macho.MH_NO_REEXPORTED_DYLIBS; |
| 4456 | .n_desc = 0, | ||
| 4457 | .n_value = 0, | ||
| 4458 | }); | ||
| 4459 | } | ||
| 4460 | |||
| 4461 | fn 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]; | ||
| 4500 | } | 3106 | } |
| 4501 | 3107 | ||
| 4502 | const size: u64 = size: { | 3108 | if (self.has_tlv) { |
| 4503 | if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) { | 3109 | header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; |
| 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 | |||
| 4553 | pub 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 | |||
| 4575 | const SymtabCtx = struct { | ||
| 4576 | nlocalsym: u32, | ||
| 4577 | nextdefsym: u32, | ||
| 4578 | nundefsym: u32, | ||
| 4579 | imports_table: std.AutoHashMap(SymbolWithLoc, u32), | ||
| 4580 | }; | ||
| 4581 | |||
| 4582 | pub 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 | } | ||
| 4613 | } | 3110 | } |
| 4614 | 3111 | if (self.binds_to_weak) { | |
| 4615 | if (self.got_section_index) |sect_id| { | 3112 | header.flags |= macho.MH_BINDS_TO_WEAK; |
| 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 | } | ||
| 4627 | } | 3113 | } |
| 4628 | 3114 | if (self.weak_defines) { | |
| 4629 | if (self.la_symbol_ptr_section_index) |sect_id| { | 3115 | header.flags |= macho.MH_WEAK_DEFINES; |
| 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 | } | ||
| 4638 | } | 3116 | } |
| 4639 | 3117 | ||
| 4640 | const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow; | 3118 | header.ncmds = @intCast(ncmds); |
| 4641 | if (padding > 0) { | 3119 | header.sizeofcmds = @intCast(sizeofcmds); |
| 4642 | buf.appendNTimesAssumeCapacity(0, padding); | ||
| 4643 | } | ||
| 4644 | 3120 | ||
| 4645 | assert(buf.items.len == needed_size_aligned); | 3121 | log.debug("writing Mach-O header {}", .{header}); |
| 4646 | try self.base.file.?.pwriteAll(buf.items, offset); | ||
| 4647 | 3122 | ||
| 4648 | self.dysymtab_cmd.nlocalsym = ctx.nlocalsym; | 3123 | try self.base.file.?.pwriteAll(mem.asBytes(&header), 0); |
| 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; | ||
| 4655 | } | 3124 | } |
| 4656 | 3125 | ||
| 4657 | pub fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void { | 3126 | fn writeUuid(self: *MachO, uuid_cmd_offset: u64, has_codesig: bool) !void { |
| 4658 | const file_size = if (!has_codesig) blk: { | 3127 | const file_size = if (!has_codesig) blk: { |
| 4659 | const seg = self.getLinkeditSegmentPtr(); | 3128 | const seg = self.getLinkeditSegment(); |
| 4660 | break :blk seg.fileoff + seg.filesize; | 3129 | break :blk seg.fileoff + seg.filesize; |
| 4661 | } else self.codesig_cmd.dataoff; | 3130 | } 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); |
| 4663 | const offset = uuid_cmd_offset + @sizeOf(macho.load_command); | 3132 | const offset = uuid_cmd_offset + @sizeOf(macho.load_command); |
| 4664 | try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset); | 3133 | try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset); |
| 4665 | } | 3134 | } |
| 4666 | 3135 | ||
| 4667 | pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { | 3136 | pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { |
| 4668 | const target = self.base.comp.root_mod.resolved_target.result; | 3137 | const seg = self.getLinkeditSegment(); |
| 4669 | const seg = self.getLinkeditSegmentPtr(); | ||
| 4670 | // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file | 3138 | // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file |
| 4671 | // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 | 3139 | // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 |
| 4672 | const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16); | 3140 | const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16); |
| 4673 | const needed_size = code_sig.estimateSize(offset); | 3141 | const needed_size = code_sig.estimateSize(offset); |
| 4674 | seg.filesize = offset + needed_size - seg.fileoff; | 3142 | 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()); |
| 4676 | log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); | 3144 | log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); |
| 4677 | // Pad out the space. We need to do this to calculate valid hashes for everything in the file | 3145 | // Pad out the space. We need to do this to calculate valid hashes for everything in the file |
| 4678 | // except for code signature data. | 3146 | // except for code signature data. |
| ... | @@ -4682,22 +3150,19 @@ pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { | ... | @@ -4682,22 +3150,19 @@ pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { |
| 4682 | self.codesig_cmd.datasize = @as(u32, @intCast(needed_size)); | 3150 | self.codesig_cmd.datasize = @as(u32, @intCast(needed_size)); |
| 4683 | } | 3151 | } |
| 4684 | 3152 | ||
| 4685 | pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void { | 3153 | pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void { |
| 4686 | const output_mode = self.base.comp.config.output_mode; | 3154 | const seg = self.getTextSegment(); |
| 4687 | const seg_id = self.header_segment_cmd_index.?; | ||
| 4688 | const seg = self.segments.items[seg_id]; | ||
| 4689 | const offset = self.codesig_cmd.dataoff; | 3155 | const offset = self.codesig_cmd.dataoff; |
| 4690 | 3156 | ||
| 4691 | const gpa = self.base.comp.gpa; | 3157 | var buffer = std.ArrayList(u8).init(self.base.comp.gpa); |
| 4692 | var buffer = std.ArrayList(u8).init(gpa); | ||
| 4693 | defer buffer.deinit(); | 3158 | defer buffer.deinit(); |
| 4694 | try buffer.ensureTotalCapacityPrecise(code_sig.size()); | 3159 | try buffer.ensureTotalCapacityPrecise(code_sig.size()); |
| 4695 | try code_sig.writeAdhocSignature(comp, .{ | 3160 | try code_sig.writeAdhocSignature(self, .{ |
| 4696 | .file = self.base.file.?, | 3161 | .file = self.base.file.?, |
| 4697 | .exec_seg_base = seg.fileoff, | 3162 | .exec_seg_base = seg.fileoff, |
| 4698 | .exec_seg_limit = seg.filesize, | 3163 | .exec_seg_limit = seg.filesize, |
| 4699 | .file_size = offset, | 3164 | .file_size = offset, |
| 4700 | .output_mode = output_mode, | 3165 | .dylib = self.base.isDynLib(), |
| 4701 | }, buffer.writer()); | 3166 | }, buffer.writer()); |
| 4702 | assert(buffer.items.len == code_sig.size()); | 3167 | assert(buffer.items.len == code_sig.size()); |
| 4703 | 3168 | ||
| ... | @@ -4709,51 +3174,79 @@ pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *Cod | ... | @@ -4709,51 +3174,79 @@ pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *Cod |
| 4709 | try self.base.file.?.pwriteAll(buffer.items, offset); | 3174 | try self.base.file.?.pwriteAll(buffer.items, offset); |
| 4710 | } | 3175 | } |
| 4711 | 3176 | ||
| 4712 | /// Writes Mach-O file header. | 3177 | pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { |
| 4713 | pub fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void { | 3178 | if (build_options.skip_non_native and builtin.object_format != .macho) { |
| 4714 | const output_mode = self.base.comp.config.output_mode; | 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 | } | ||
| 4715 | 3184 | ||
| 4716 | var header: macho.mach_header_64 = .{}; | 3185 | pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 { |
| 4717 | header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL; | 3186 | return self.getZigObject().?.lowerUnnamedConst(self, typed_value, decl_index); |
| 3187 | } | ||
| 4718 | 3188 | ||
| 4719 | const target = self.base.comp.root_mod.resolved_target.result; | 3189 | pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) !void { |
| 4720 | switch (target.cpu.arch) { | 3190 | if (build_options.skip_non_native and builtin.object_format != .macho) { |
| 4721 | .aarch64 => { | 3191 | @panic("Attempted to compile for object format that was disabled by build configuration"); |
| 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, | ||
| 4730 | } | 3192 | } |
| 3193 | if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(mod, decl_index); | ||
| 3194 | return self.getZigObject().?.updateDecl(self, mod, decl_index); | ||
| 3195 | } | ||
| 4731 | 3196 | ||
| 4732 | switch (output_mode) { | 3197 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: InternPool.DeclIndex) !void { |
| 4733 | .Exe => { | 3198 | if (self.llvm_object) |_| return; |
| 4734 | header.filetype = macho.MH_EXECUTE; | 3199 | return self.getZigObject().?.updateDeclLineNumber(module, decl_index); |
| 4735 | }, | 3200 | } |
| 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 | } | ||
| 4743 | 3201 | ||
| 4744 | if (self.thread_vars_section_index) |sect_id| { | 3202 | pub fn updateExports( |
| 4745 | header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; | 3203 | self: *MachO, |
| 4746 | if (self.sections.items(.header)[sect_id].size > 0) { | 3204 | mod: *Module, |
| 4747 | header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; | 3205 | exported: Module.Exported, |
| 4748 | } | 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"); | ||
| 4749 | } | 3210 | } |
| 3211 | if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); | ||
| 3212 | return self.getZigObject().?.updateExports(self, mod, exported, exports); | ||
| 3213 | } | ||
| 4750 | 3214 | ||
| 4751 | header.ncmds = ncmds; | 3215 | pub fn deleteDeclExport( |
| 4752 | header.sizeofcmds = sizeofcmds; | 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 | } | ||
| 4753 | 3223 | ||
| 4754 | log.debug("writing Mach-O header {}", .{header}); | 3224 | pub 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 | } | ||
| 4755 | 3228 | ||
| 4756 | try self.base.file.?.pwriteAll(mem.asBytes(&header), 0); | 3229 | pub 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 | |||
| 3234 | pub 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 | |||
| 3243 | pub 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 | |||
| 3248 | pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 { | ||
| 3249 | return self.getZigObject().?.getGlobalSymbol(self, name, lib_name); | ||
| 4757 | } | 3250 | } |
| 4758 | 3251 | ||
| 4759 | pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { | 3252 | pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { |
| ... | @@ -4761,33 +3254,55 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { | ... | @@ -4761,33 +3254,55 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { |
| 4761 | } | 3254 | } |
| 4762 | 3255 | ||
| 4763 | fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { | 3256 | fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { |
| 4764 | // TODO: header and load commands have to be part of the __TEXT segment | 3257 | // Conservatively commit one page size as reserved space for the headers as we |
| 4765 | const header_size = self.segments.items[self.header_segment_cmd_index.?].filesize; | 3258 | // expect it to grow and everything else be moved in flush anyhow. |
| 3259 | const header_size = self.getPageSize(); | ||
| 4766 | if (start < header_size) | 3260 | if (start < header_size) |
| 4767 | return header_size; | 3261 | return header_size; |
| 4768 | 3262 | ||
| 4769 | const end = start + padToIdeal(size); | 3263 | const end = start + padToIdeal(size); |
| 4770 | 3264 | ||
| 4771 | for (self.sections.items(.header)) |header| { | 3265 | for (self.sections.items(.header)) |header| { |
| 4772 | const tight_size = header.size; | 3266 | if (header.isZerofill()) continue; |
| 4773 | const increased_size = padToIdeal(tight_size); | 3267 | const increased_size = padToIdeal(header.size); |
| 4774 | const test_end = header.offset + increased_size; | 3268 | const test_end = header.offset + increased_size; |
| 4775 | if (end > header.offset and start < test_end) { | 3269 | if (end > header.offset and start < test_end) { |
| 4776 | return test_end; | 3270 | return test_end; |
| 4777 | } | 3271 | } |
| 4778 | } | 3272 | } |
| 4779 | 3273 | ||
| 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 | |||
| 4780 | return null; | 3282 | return null; |
| 4781 | } | 3283 | } |
| 4782 | 3284 | ||
| 4783 | fn allocatedSize(self: *MachO, start: u64) u64 { | 3285 | fn allocatedSize(self: *MachO, start: u64) u64 { |
| 4784 | if (start == 0) | 3286 | if (start == 0) return 0; |
| 4785 | return 0; | ||
| 4786 | var min_pos: u64 = std.math.maxInt(u64); | 3287 | var min_pos: u64 = std.math.maxInt(u64); |
| 4787 | for (self.sections.items(.header)) |header| { | 3288 | for (self.sections.items(.header)) |header| { |
| 4788 | if (header.offset <= start) continue; | 3289 | if (header.offset <= start) continue; |
| 4789 | if (header.offset < min_pos) min_pos = header.offset; | 3290 | if (header.offset < min_pos) min_pos = header.offset; |
| 4790 | } | 3291 | } |
| 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 | |||
| 3299 | fn 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 | } | ||
| 4791 | return min_pos - start; | 3306 | return min_pos - start; |
| 4792 | } | 3307 | } |
| 4793 | 3308 | ||
| ... | @@ -4799,452 +3314,534 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { | ... | @@ -4799,452 +3314,534 @@ fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 { |
| 4799 | return start; | 3314 | return start; |
| 4800 | } | 3315 | } |
| 4801 | 3316 | ||
| 4802 | pub fn allocatedVirtualSize(self: *MachO, start: u64) u64 { | 3317 | /// Like File.copyRangeAll but also ensures the source region is zeroed out after copy. |
| 4803 | if (start == 0) | 3318 | /// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader. |
| 4804 | return 0; | 3319 | fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { |
| 4805 | var min_pos: u64 = std.math.maxInt(u64); | 3320 | const gpa = self.base.comp.gpa; |
| 4806 | for (self.sections.items(.segment_index)) |seg_id| { | 3321 | const file = self.base.file.?; |
| 4807 | const segment = self.segments.items[seg_id]; | 3322 | const amt = try file.copyRangeAll(old_offset, file, new_offset, size); |
| 4808 | if (segment.vmaddr <= start) continue; | 3323 | if (amt != size) return error.InputOutput; |
| 4809 | if (segment.vmaddr < min_pos) min_pos = segment.vmaddr; | 3324 | const size_u = math.cast(usize, size) orelse return error.Overflow; |
| 4810 | } | 3325 | const zeroes = try gpa.alloc(u8, size_u); |
| 4811 | return min_pos - start; | 3326 | defer gpa.free(zeroes); |
| 3327 | @memset(zeroes, 0); | ||
| 3328 | try file.pwriteAll(zeroes, old_offset); | ||
| 4812 | } | 3329 | } |
| 4813 | 3330 | ||
| 4814 | pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void { | 3331 | const InitMetadataOptions = struct { |
| 4815 | if (!is_hot_update_compatible) return; | 3332 | symbol_count_hint: u64, |
| 3333 | program_code_size_hint: u64, | ||
| 3334 | }; | ||
| 4816 | 3335 | ||
| 4817 | const mach_task = try std.os.darwin.machTaskForPid(pid); | 3336 | // TODO: move to ZigObject |
| 4818 | log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task }); | 3337 | fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { |
| 4819 | self.hot_state.mach_task = mach_task; | 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 | }; | ||
| 4820 | 3343 | ||
| 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 | } | ||
| 4822 | 3355 | ||
| 4823 | // TODO enable ones we register for exceptions | 3356 | { |
| 4824 | // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0); | 3357 | const filesize = options.symbol_count_hint * @sizeOf(u64); |
| 4825 | } | 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 | } | ||
| 4826 | 3367 | ||
| 4827 | pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void { | 3368 | { |
| 4828 | if (!is_hot_update_compatible) return; | 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 | } | ||
| 4829 | 3379 | ||
| 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 | } | ||
| 4831 | 3391 | ||
| 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 | } | ||
| 4833 | 3403 | ||
| 4834 | // TODO see comment in ptraceAttach | 3404 | const appendSect = struct { |
| 4835 | // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0); | 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; | ||
| 4836 | 3414 | ||
| 4837 | self.hot_state.mach_task = null; | 3415 | { |
| 4838 | } | 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 | } | ||
| 4839 | 3426 | ||
| 4840 | pub fn addUndefined(self: *MachO, name: []const u8, flags: RelocFlags) !u32 { | 3427 | if (!self.base.isRelocatable()) { |
| 4841 | const gpa = self.base.comp.gpa; | 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 | } | ||
| 4842 | 3433 | ||
| 4843 | const gop = try self.getOrPutGlobalPtr(name); | 3434 | { |
| 4844 | const global_index = self.getGlobalIndex(name).?; | 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 | } | ||
| 4845 | 3438 | ||
| 4846 | if (gop.found_existing) { | 3439 | { |
| 4847 | try self.updateRelocActions(global_index, flags); | 3440 | self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{}); |
| 4848 | return global_index; | 3441 | appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?); |
| 4849 | } | 3442 | } |
| 4850 | 3443 | ||
| 4851 | const sym_index = try self.allocateSymbol(); | 3444 | { |
| 4852 | const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; | 3445 | self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{ |
| 4853 | gop.value_ptr.* = sym_loc; | 3446 | .flags = macho.S_ZEROFILL, |
| 3447 | }); | ||
| 3448 | appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?); | ||
| 3449 | } | ||
| 3450 | } | ||
| 4854 | 3451 | ||
| 4855 | const sym = self.getSymbolPtr(sym_loc); | 3452 | pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { |
| 4856 | sym.n_strx = try self.strtab.insert(gpa, name); | 3453 | const sect = &self.sections.items(.header)[sect_index]; |
| 4857 | sym.n_type = macho.N_EXT | macho.N_UNDF; | 3454 | const seg_id = self.sections.items(.segment_id)[sect_index]; |
| 3455 | const seg = &self.segments.items[seg_id]; | ||
| 4858 | 3456 | ||
| 4859 | try self.unresolved.putNoClobber(gpa, global_index, {}); | 3457 | if (needed_size > self.allocatedSize(sect.offset) and !sect.isZerofill()) { |
| 4860 | try self.updateRelocActions(global_index, flags); | 3458 | const existing_size = sect.size; |
| 3459 | sect.size = 0; | ||
| 4861 | 3460 | ||
| 4862 | return global_index; | 3461 | // Must move the entire section. |
| 4863 | } | 3462 | const new_offset = self.findFreeSpace(needed_size, self.getPageSize()); |
| 4864 | 3463 | ||
| 4865 | fn updateRelocActions(self: *MachO, global_index: u32, flags: RelocFlags) !void { | 3464 | log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{ |
| 4866 | const gpa = self.base.comp.gpa; | 3465 | sect.segName(), |
| 4867 | const act_gop = try self.actions.getOrPut(gpa, global_index); | 3466 | sect.sectName(), |
| 4868 | if (!act_gop.found_existing) { | 3467 | new_offset, |
| 4869 | act_gop.value_ptr.* = .{}; | 3468 | new_offset + existing_size, |
| 4870 | } | 3469 | }); |
| 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 | } | ||
| 4874 | 3470 | ||
| 4875 | pub fn makeStaticString(bytes: []const u8) [16]u8 { | 3471 | try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size); |
| 4876 | var buf = [_]u8{0} ** 16; | ||
| 4877 | @memcpy(buf[0..bytes.len], bytes); | ||
| 4878 | return buf; | ||
| 4879 | } | ||
| 4880 | 3472 | ||
| 4881 | pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 { | 3473 | sect.offset = @intCast(new_offset); |
| 4882 | for (self.segments.items, 0..) |seg, i| { | 3474 | seg.fileoff = new_offset; |
| 4883 | if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i)); | 3475 | } |
| 4884 | } else return null; | ||
| 4885 | } | ||
| 4886 | 3476 | ||
| 4887 | pub fn getSegment(self: MachO, sect_id: u8) macho.segment_command_64 { | 3477 | sect.size = needed_size; |
| 4888 | const index = self.sections.items(.segment_index)[sect_id]; | 3478 | if (!sect.isZerofill()) { |
| 4889 | return self.segments.items[index]; | 3479 | seg.filesize = needed_size; |
| 4890 | } | 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 | } | ||
| 4891 | 3492 | ||
| 4892 | pub fn getSegmentPtr(self: *MachO, sect_id: u8) *macho.segment_command_64 { | 3493 | seg.vmsize = needed_size; |
| 4893 | const index = self.sections.items(.segment_index)[sect_id]; | ||
| 4894 | return &self.segments.items[index]; | ||
| 4895 | } | 3494 | } |
| 4896 | 3495 | ||
| 4897 | pub fn getLinkeditSegmentPtr(self: *MachO) *macho.segment_command_64 { | 3496 | pub fn getTarget(self: MachO) std.Target { |
| 4898 | const index = self.linkedit_segment_cmd_index.?; | 3497 | return self.base.comp.root_mod.resolved_target.result; |
| 4899 | return &self.segments.items[index]; | ||
| 4900 | } | 3498 | } |
| 4901 | 3499 | ||
| 4902 | pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 { | 3500 | /// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. |
| 4903 | // TODO investigate caching with a hashmap | 3501 | /// Any change to the binary will effectively invalidate the kernel's cache |
| 4904 | for (self.sections.items(.header), 0..) |header, i| { | 3502 | /// resulting in a SIGKILL on each subsequent run. Since when doing incremental |
| 4905 | if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname)) | 3503 | /// linking we're modifying a binary in-place, this will end up with the kernel |
| 4906 | return @as(u8, @intCast(i)); | 3504 | /// killing it on every subsequent run. To circumvent it, we will copy the file |
| 4907 | } else return null; | 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. | ||
| 3508 | pub 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 | } | ||
| 4908 | } | 3512 | } |
| 4909 | 3513 | ||
| 4910 | pub fn getSectionIndexes(self: MachO, segment_index: u8) struct { start: u8, end: u8 } { | 3514 | inline fn conformUuid(out: *[Md5.digest_length]u8) void { |
| 4911 | var start: u8 = 0; | 3515 | // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats |
| 4912 | const nsects = for (self.segments.items, 0..) |seg, i| { | 3516 | out[6] = (out[6] & 0x0F) | (3 << 4); |
| 4913 | if (i == segment_index) break @as(u8, @intCast(seg.nsects)); | 3517 | out[8] = (out[8] & 0x3F) | 0x80; |
| 4914 | start += @as(u8, @intCast(seg.nsects)); | ||
| 4915 | } else 0; | ||
| 4916 | return .{ .start = start, .end = start + nsects }; | ||
| 4917 | } | 3518 | } |
| 4918 | 3519 | ||
| 4919 | pub fn symbolIsTemp(self: *MachO, sym_with_loc: SymbolWithLoc) bool { | 3520 | pub inline fn getPageSize(self: MachO) u16 { |
| 4920 | const sym = self.getSymbol(sym_with_loc); | 3521 | return switch (self.getTarget().cpu.arch) { |
| 4921 | if (!sym.sect()) return false; | 3522 | .aarch64 => 0x4000, |
| 4922 | if (sym.ext()) return false; | 3523 | .x86_64 => 0x1000, |
| 4923 | const sym_name = self.getSymbolName(sym_with_loc); | 3524 | else => unreachable, |
| 4924 | return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L"); | 3525 | }; |
| 4925 | } | 3526 | } |
| 4926 | 3527 | ||
| 4927 | /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. | 3528 | pub fn requiresCodeSig(self: MachO) bool { |
| 4928 | pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 { | 3529 | if (self.entitlements) |_| return true; |
| 4929 | if (sym_with_loc.getFile()) |file| { | 3530 | // if (self.options.adhoc_codesign) |cs| return cs; |
| 4930 | const object = &self.objects.items[file]; | 3531 | return switch (self.getTarget().cpu.arch) { |
| 4931 | return &object.symtab[sym_with_loc.sym_index]; | 3532 | .aarch64 => true, |
| 4932 | } else { | 3533 | else => false, |
| 4933 | return &self.locals.items[sym_with_loc.sym_index]; | 3534 | }; |
| 4934 | } | ||
| 4935 | } | 3535 | } |
| 4936 | 3536 | ||
| 4937 | /// Returns symbol described by `sym_with_loc` descriptor. | 3537 | inline fn requiresThunks(self: MachO) bool { |
| 4938 | pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 { | 3538 | return self.getTarget().cpu.arch == .aarch64; |
| 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 | } | ||
| 4945 | } | 3539 | } |
| 4946 | 3540 | ||
| 4947 | /// Returns name of the symbol described by `sym_with_loc` descriptor. | 3541 | pub fn addSegment(self: *MachO, name: []const u8, opts: struct { |
| 4948 | pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 { | 3542 | vmaddr: u64 = 0, |
| 4949 | if (sym_with_loc.getFile()) |file| { | 3543 | vmsize: u64 = 0, |
| 4950 | const object = self.objects.items[file]; | 3544 | fileoff: u64 = 0, |
| 4951 | return object.getSymbolName(sym_with_loc.sym_index); | 3545 | filesize: u64 = 0, |
| 4952 | } else { | 3546 | prot: macho.vm_prot_t = macho.PROT.NONE, |
| 4953 | const sym = self.locals.items[sym_with_loc.sym_index]; | 3547 | flags: u32 = 0, |
| 4954 | return self.strtab.get(sym.n_strx).?; | 3548 | }) error{OutOfMemory}!u8 { |
| 4955 | } | 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; | ||
| 4956 | } | 3563 | } |
| 4957 | 3564 | ||
| 4958 | const BoundarySymbolKind = enum { | 3565 | const AddSectionOpts = struct { |
| 4959 | start, | 3566 | alignment: u32 = 0, |
| 4960 | stop, | 3567 | flags: u32 = macho.S_REGULAR, |
| 3568 | reserved1: u32 = 0, | ||
| 3569 | reserved2: u32 = 0, | ||
| 4961 | }; | 3570 | }; |
| 4962 | 3571 | ||
| 4963 | const SectionBoundarySymbol = struct { | 3572 | pub fn addSection( |
| 4964 | kind: BoundarySymbolKind, | 3573 | self: *MachO, |
| 4965 | segname: []const u8, | 3574 | segname: []const u8, |
| 4966 | sectname: []const u8, | 3575 | sectname: []const u8, |
| 4967 | }; | 3576 | opts: AddSectionOpts, |
| 4968 | 3577 | ) !u8 { | |
| 4969 | pub fn getSectionBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SectionBoundarySymbol { | 3578 | const gpa = self.base.comp.gpa; |
| 4970 | const sym_name = self.getSymbolName(sym_with_loc); | 3579 | const index = @as(u8, @intCast(try self.sections.addOne(gpa))); |
| 4971 | if (mem.startsWith(u8, sym_name, "section$")) { | 3580 | self.sections.set(index, .{ |
| 4972 | const trailing = sym_name["section$".len..]; | 3581 | .segment_id = 0, // Segments will be created automatically later down the pipeline. |
| 4973 | const kind: BoundarySymbolKind = kind: { | 3582 | .header = .{ |
| 4974 | if (mem.startsWith(u8, trailing, "start$")) break :kind .start; | 3583 | .sectname = makeStaticString(sectname), |
| 4975 | if (mem.startsWith(u8, trailing, "stop$")) break :kind .stop; | 3584 | .segname = makeStaticString(segname), |
| 4976 | return null; | 3585 | .@"align" = opts.alignment, |
| 4977 | }; | 3586 | .flags = opts.flags, |
| 4978 | const names = trailing[@tagName(kind).len + 1 ..]; | 3587 | .reserved1 = opts.reserved1, |
| 4979 | const sep_idx = mem.indexOf(u8, names, "$") orelse return null; | 3588 | .reserved2 = opts.reserved2, |
| 4980 | const segname = names[0..sep_idx]; | 3589 | }, |
| 4981 | const sectname = names[sep_idx + 1 ..]; | 3590 | }); |
| 4982 | return .{ .kind = kind, .segname = segname, .sectname = sectname }; | 3591 | return index; |
| 4983 | } | ||
| 4984 | return null; | ||
| 4985 | } | 3592 | } |
| 4986 | 3593 | ||
| 4987 | const SegmentBoundarySymbol = struct { | 3594 | pub fn makeStaticString(bytes: []const u8) [16]u8 { |
| 4988 | kind: BoundarySymbolKind, | 3595 | var buf = [_]u8{0} ** 16; |
| 4989 | segname: []const u8, | 3596 | @memcpy(buf[0..bytes.len], bytes); |
| 4990 | }; | 3597 | return buf; |
| 3598 | } | ||
| 4991 | 3599 | ||
| 4992 | pub fn getSegmentBoundarySymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) ?SegmentBoundarySymbol { | 3600 | pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 { |
| 4993 | const sym_name = self.getSymbolName(sym_with_loc); | 3601 | for (self.segments.items, 0..) |seg, i| { |
| 4994 | if (mem.startsWith(u8, sym_name, "segment$")) { | 3602 | if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i)); |
| 4995 | const trailing = sym_name["segment$".len..]; | 3603 | } else return null; |
| 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; | ||
| 5005 | } | 3604 | } |
| 5006 | 3605 | ||
| 5007 | /// Returns pointer to the global entry for `name` if one exists. | 3606 | pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 { |
| 5008 | pub fn getGlobalPtr(self: *MachO, name: []const u8) ?*SymbolWithLoc { | 3607 | for (self.sections.items(.header), 0..) |header, i| { |
| 5009 | const global_index = self.resolver.get(name) orelse return null; | 3608 | if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname)) |
| 5010 | return &self.globals.items[global_index]; | 3609 | return @as(u8, @intCast(i)); |
| 3610 | } else return null; | ||
| 5011 | } | 3611 | } |
| 5012 | 3612 | ||
| 5013 | /// Returns the global entry for `name` if one exists. | 3613 | pub fn getTlsAddress(self: MachO) u64 { |
| 5014 | pub fn getGlobal(self: *const MachO, name: []const u8) ?SymbolWithLoc { | 3614 | for (self.sections.items(.header)) |header| switch (header.type()) { |
| 5015 | const global_index = self.resolver.get(name) orelse return null; | 3615 | macho.S_THREAD_LOCAL_REGULAR, |
| 5016 | return self.globals.items[global_index]; | 3616 | macho.S_THREAD_LOCAL_ZEROFILL, |
| 3617 | => return header.addr, | ||
| 3618 | else => {}, | ||
| 3619 | }; | ||
| 3620 | return 0; | ||
| 5017 | } | 3621 | } |
| 5018 | 3622 | ||
| 5019 | /// Returns the index of the global entry for `name` if one exists. | 3623 | pub inline fn getTextSegment(self: *MachO) *macho.segment_command_64 { |
| 5020 | pub fn getGlobalIndex(self: *const MachO, name: []const u8) ?u32 { | 3624 | return &self.segments.items[self.text_seg_index.?]; |
| 5021 | return self.resolver.get(name); | ||
| 5022 | } | 3625 | } |
| 5023 | 3626 | ||
| 5024 | /// Returns global entry at `index`. | 3627 | pub inline fn getLinkeditSegment(self: *MachO) *macho.segment_command_64 { |
| 5025 | pub fn getGlobalByIndex(self: *const MachO, index: u32) SymbolWithLoc { | 3628 | return &self.segments.items[self.linkedit_seg_index.?]; |
| 5026 | assert(index < self.globals.items.len); | ||
| 5027 | return self.globals.items[index]; | ||
| 5028 | } | 3629 | } |
| 5029 | 3630 | ||
| 5030 | const GetOrPutGlobalPtrResult = struct { | 3631 | pub fn getFile(self: *MachO, index: File.Index) ?File { |
| 5031 | found_existing: bool, | 3632 | const tag = self.files.items(.tags)[index]; |
| 5032 | value_ptr: *SymbolWithLoc, | 3633 | return switch (tag) { |
| 5033 | }; | 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 | } | ||
| 5034 | 3641 | ||
| 5035 | /// Used only for disambiguating local from global at relocation level. | 3642 | pub fn getZigObject(self: *MachO) ?*ZigObject { |
| 5036 | /// TODO this must go away. | 3643 | const index = self.zig_object orelse return null; |
| 5037 | pub const global_symbol_bit: u32 = 0x80000000; | 3644 | return self.getFile(index).?.zig_object; |
| 5038 | pub const global_symbol_mask: u32 = 0x7fffffff; | 3645 | } |
| 5039 | 3646 | ||
| 5040 | /// Return pointer to the global entry for `name` if one exists. | 3647 | pub fn getInternalObject(self: *MachO) ?*InternalObject { |
| 5041 | /// Puts a new global entry for `name` if one doesn't exist, and | 3648 | const index = self.internal_object orelse return null; |
| 5042 | /// returns a pointer to it. | 3649 | return self.getFile(index).?.internal; |
| 5043 | pub 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 }; | ||
| 5053 | } | 3650 | } |
| 5054 | 3651 | ||
| 5055 | pub fn getAtom(self: *MachO, atom_index: Atom.Index) Atom { | 3652 | pub fn addAtom(self: *MachO) error{OutOfMemory}!Atom.Index { |
| 5056 | assert(atom_index < self.atoms.items.len); | 3653 | const index = @as(Atom.Index, @intCast(self.atoms.items.len)); |
| 5057 | return self.atoms.items[atom_index]; | 3654 | const atom = try self.atoms.addOne(self.base.comp.gpa); |
| 3655 | atom.* = .{}; | ||
| 3656 | return index; | ||
| 5058 | } | 3657 | } |
| 5059 | 3658 | ||
| 5060 | pub fn getAtomPtr(self: *MachO, atom_index: Atom.Index) *Atom { | 3659 | pub fn getAtom(self: *MachO, index: Atom.Index) ?*Atom { |
| 5061 | assert(atom_index < self.atoms.items.len); | 3660 | if (index == 0) return null; |
| 5062 | return &self.atoms.items[atom_index]; | 3661 | assert(index < self.atoms.items.len); |
| 3662 | return &self.atoms.items[index]; | ||
| 5063 | } | 3663 | } |
| 5064 | 3664 | ||
| 5065 | /// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor. | 3665 | pub fn addSymbol(self: *MachO) !Symbol.Index { |
| 5066 | /// Returns null on failure. | 3666 | const index = @as(Symbol.Index, @intCast(self.symbols.items.len)); |
| 5067 | pub fn getAtomIndexForSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) ?Atom.Index { | 3667 | const symbol = try self.symbols.addOne(self.base.comp.gpa); |
| 5068 | assert(sym_with_loc.getFile() == null); | 3668 | symbol.* = .{}; |
| 5069 | return self.atom_by_index_table.get(sym_with_loc.sym_index); | 3669 | return index; |
| 5070 | } | 3670 | } |
| 5071 | 3671 | ||
| 5072 | pub fn getGotEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 { | 3672 | pub fn getSymbol(self: *MachO, index: Symbol.Index) *Symbol { |
| 5073 | const index = self.got_table.lookup.get(sym_with_loc) orelse return null; | 3673 | assert(index < self.symbols.items.len); |
| 5074 | const header = self.sections.items(.header)[self.got_section_index.?]; | 3674 | return &self.symbols.items[index]; |
| 5075 | return header.addr + @sizeOf(u64) * index; | ||
| 5076 | } | 3675 | } |
| 5077 | 3676 | ||
| 5078 | pub fn getTlvPtrEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 { | 3677 | pub fn addSymbolExtra(self: *MachO, extra: Symbol.Extra) !u32 { |
| 5079 | const index = self.tlv_ptr_table.lookup.get(sym_with_loc) orelse return null; | 3678 | const fields = @typeInfo(Symbol.Extra).Struct.fields; |
| 5080 | const header = self.sections.items(.header)[self.tlv_ptr_section_index.?]; | 3679 | try self.symbols_extra.ensureUnusedCapacity(self.base.comp.gpa, fields.len); |
| 5081 | return header.addr + @sizeOf(u64) * index; | 3680 | return self.addSymbolExtraAssumeCapacity(extra); |
| 5082 | } | 3681 | } |
| 5083 | 3682 | ||
| 5084 | pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 { | 3683 | pub fn addSymbolExtraAssumeCapacity(self: *MachO, extra: Symbol.Extra) u32 { |
| 5085 | const target = self.base.comp.root_mod.resolved_target.result; | 3684 | const index = @as(u32, @intCast(self.symbols_extra.items.len)); |
| 5086 | const index = self.stub_table.lookup.get(sym_with_loc) orelse return null; | 3685 | const fields = @typeInfo(Symbol.Extra).Struct.fields; |
| 5087 | const header = self.sections.items(.header)[self.stubs_section_index.?]; | 3686 | inline for (fields) |field| { |
| 5088 | return header.addr + stubs.stubSize(target.cpu.arch) * index; | 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; | ||
| 5089 | } | 3693 | } |
| 5090 | 3694 | ||
| 5091 | /// Returns symbol location corresponding to the set entrypoint if any. | 3695 | pub fn getSymbolExtra(self: MachO, index: u32) ?Symbol.Extra { |
| 5092 | /// Asserts output mode is executable. | 3696 | if (index == 0) return null; |
| 5093 | pub fn getEntryPoint(self: MachO) ?SymbolWithLoc { | 3697 | const fields = @typeInfo(Symbol.Extra).Struct.fields; |
| 5094 | const entry_name = self.entry_name orelse return null; | 3698 | var i: usize = index; |
| 5095 | const global = self.getGlobal(entry_name) orelse return null; | 3699 | var result: Symbol.Extra = undefined; |
| 5096 | return global; | 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; | ||
| 5097 | } | 3708 | } |
| 5098 | 3709 | ||
| 5099 | pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols { | 3710 | pub fn setSymbolExtra(self: *MachO, index: u32, extra: Symbol.Extra) void { |
| 5100 | if (self.d_sym == null) return null; | 3711 | assert(index > 0); |
| 5101 | return &self.d_sym.?; | 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 | } | ||
| 5102 | } | 3719 | } |
| 5103 | 3720 | ||
| 5104 | pub inline fn getPageSize(cpu_arch: std.Target.Cpu.Arch) u16 { | 3721 | const GetOrCreateGlobalResult = struct { |
| 5105 | return switch (cpu_arch) { | 3722 | found_existing: bool, |
| 5106 | .aarch64 => 0x4000, | 3723 | index: Symbol.Index, |
| 5107 | .x86_64 => 0x1000, | 3724 | }; |
| 5108 | else => unreachable, | 3725 | |
| 3726 | pub 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.*, | ||
| 5109 | }; | 3738 | }; |
| 5110 | } | 3739 | } |
| 5111 | 3740 | ||
| 5112 | pub fn requiresCodeSignature(m: *MachO) bool { | 3741 | pub fn getGlobalByName(self: *MachO, name: []const u8) ?Symbol.Index { |
| 5113 | if (m.entitlements) |_| return true; | 3742 | const off = self.strings.getOffset(name) orelse return null; |
| 5114 | const comp = m.base.comp; | 3743 | return self.globals.get(off); |
| 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; | ||
| 5121 | } | 3744 | } |
| 5122 | 3745 | ||
| 5123 | pub fn getSegmentPrecedence(segname: []const u8) u4 { | 3746 | pub fn addUnwindRecord(self: *MachO) !UnwindInfo.Record.Index { |
| 5124 | if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0; | 3747 | const index = @as(UnwindInfo.Record.Index, @intCast(self.unwind_records.items.len)); |
| 5125 | if (mem.eql(u8, segname, "__TEXT")) return 0x1; | 3748 | const rec = try self.unwind_records.addOne(self.base.comp.gpa); |
| 5126 | if (mem.eql(u8, segname, "__DATA_CONST")) return 0x2; | 3749 | rec.* = .{}; |
| 5127 | if (mem.eql(u8, segname, "__DATA")) return 0x3; | 3750 | return index; |
| 5128 | if (mem.eql(u8, segname, "__LINKEDIT")) return 0x5; | 3751 | } |
| 5129 | return 0x4; | 3752 | |
| 3753 | pub 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]; | ||
| 5130 | } | 3756 | } |
| 5131 | 3757 | ||
| 5132 | pub fn getSegmentMemoryProtection(segname: []const u8) macho.vm_prot_t { | 3758 | pub fn addThunk(self: *MachO) !Thunk.Index { |
| 5133 | if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE; | 3759 | const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); |
| 5134 | if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC; | 3760 | const thunk = try self.thunks.addOne(self.base.comp.gpa); |
| 5135 | if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ; | 3761 | thunk.* = .{}; |
| 5136 | return macho.PROT.READ | macho.PROT.WRITE; | 3762 | return index; |
| 5137 | } | 3763 | } |
| 5138 | 3764 | ||
| 5139 | pub fn getSectionPrecedence(header: macho.section_64) u8 { | 3765 | pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk { |
| 5140 | const segment_precedence: u4 = getSegmentPrecedence(header.segName()); | 3766 | assert(index < self.thunks.items.len); |
| 5141 | const section_precedence: u4 = blk: { | 3767 | return &self.thunks.items[index]; |
| 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; | ||
| 5164 | } | 3768 | } |
| 5165 | 3769 | ||
| 5166 | pub const ParseErrorCtx = struct { | 3770 | pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 { |
| 5167 | arena_allocator: std.heap.ArenaAllocator, | 3771 | if (mem.startsWith(u8, path, prefix)) return path[prefix.len..]; |
| 5168 | detected_dylib_id: struct { | 3772 | return null; |
| 5169 | parent: u16, | 3773 | } |
| 5170 | required_version: u32, | ||
| 5171 | found_version: u32, | ||
| 5172 | }, | ||
| 5173 | detected_targets: std.ArrayList([]const u8), | ||
| 5174 | 3774 | ||
| 5175 | pub fn init(gpa: Allocator) ParseErrorCtx { | 3775 | const ErrorWithNotes = struct { |
| 5176 | return .{ | 3776 | /// Allocated index in comp.link_errors array. |
| 5177 | .arena_allocator = std.heap.ArenaAllocator.init(gpa), | 3777 | index: usize, |
| 5178 | .detected_dylib_id = undefined, | 3778 | |
| 5179 | .detected_targets = std.ArrayList([]const u8).init(gpa), | 3779 | /// Next available note slot. |
| 5180 | }; | 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; | ||
| 5181 | } | 3806 | } |
| 3807 | }; | ||
| 5182 | 3808 | ||
| 5183 | pub fn deinit(ctx: *ParseErrorCtx) void { | 3809 | pub fn addErrorWithNotes(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes { |
| 5184 | ctx.arena_allocator.deinit(); | 3810 | const comp = self.base.comp; |
| 5185 | ctx.detected_targets.deinit(); | 3811 | const gpa = comp.gpa; |
| 5186 | } | 3812 | try comp.link_errors.ensureUnusedCapacity(gpa, 1); |
| 3813 | return self.addErrorWithNotesAssumeCapacity(note_count); | ||
| 3814 | } | ||
| 5187 | 3815 | ||
| 5188 | pub fn arena(ctx: *ParseErrorCtx) Allocator { | 3816 | fn addErrorWithNotesAssumeCapacity(self: *MachO, note_count: usize) error{OutOfMemory}!ErrorWithNotes { |
| 5189 | return ctx.arena_allocator.allocator(); | 3817 | const comp = self.base.comp; |
| 5190 | } | 3818 | const gpa = comp.gpa; |
| 5191 | }; | 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 | } | ||
| 5192 | 3824 | ||
| 5193 | pub fn handleAndReportParseError( | 3825 | pub fn reportParseError( |
| 5194 | self: *MachO, | 3826 | self: *MachO, |
| 5195 | path: []const u8, | 3827 | path: []const u8, |
| 5196 | err: ParseError, | 3828 | comptime format: []const u8, |
| 5197 | ctx: *const ParseErrorCtx, | 3829 | args: anytype, |
| 5198 | ) error{OutOfMemory}!void { | 3830 | ) error{OutOfMemory}!void { |
| 5199 | const target = self.base.comp.root_mod.resolved_target.result; | 3831 | var err = try self.addErrorWithNotes(1); |
| 5200 | const gpa = self.base.comp.gpa; | 3832 | try err.addMsg(self, format, args); |
| 5201 | const cpu_arch = target.cpu.arch; | 3833 | try err.addNote(self, "while parsing {s}", .{path}); |
| 5202 | switch (err) { | 3834 | } |
| 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 | } | ||
| 5231 | 3835 | ||
| 5232 | switch (err) { | 3836 | pub fn reportParseError2( |
| 5233 | error.InvalidTarget => try self.reportParseError( | 3837 | self: *MachO, |
| 5234 | path, | 3838 | file_index: File.Index, |
| 5235 | "invalid target: expected '{}', but found '{s}'", | 3839 | comptime format: []const u8, |
| 5236 | .{ Platform.fromTarget(target).fmtTarget(cpu_arch), targets_string.items }, | 3840 | args: anytype, |
| 5237 | ), | 3841 | ) error{OutOfMemory}!void { |
| 5238 | error.InvalidTargetFatLibrary => try self.reportParseError( | 3842 | var err = try self.addErrorWithNotes(1); |
| 5239 | path, | 3843 | try err.addMsg(self, format, args); |
| 5240 | "invalid architecture in universal library: expected '{s}', but found '{s}'", | 3844 | try err.addNote(self, "while parsing {}", .{self.getFile(file_index).?.fmtPath()}); |
| 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 | } | ||
| 5248 | } | 3845 | } |
| 5249 | 3846 | ||
| 5250 | fn reportMissingLibraryError( | 3847 | fn reportMissingLibraryError( |
| ... | @@ -5253,523 +3850,522 @@ fn reportMissingLibraryError( | ... | @@ -5253,523 +3850,522 @@ fn reportMissingLibraryError( |
| 5253 | comptime format: []const u8, | 3850 | comptime format: []const u8, |
| 5254 | args: anytype, | 3851 | args: anytype, |
| 5255 | ) error{OutOfMemory}!void { | 3852 | ) error{OutOfMemory}!void { |
| 5256 | const comp = self.base.comp; | 3853 | var err = try self.addErrorWithNotes(checked_paths.len); |
| 5257 | const gpa = comp.gpa; | 3854 | try err.addMsg(self, format, args); |
| 5258 | try comp.link_errors.ensureUnusedCapacity(gpa, 1); | 3855 | for (checked_paths) |path| { |
| 5259 | const notes = try gpa.alloc(File.ErrorMsg, checked_paths.len); | 3856 | try err.addNote(self, "tried {s}", .{path}); |
| 5260 | errdefer gpa.free(notes); | 3857 | } |
| 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 | }); | ||
| 5268 | } | 3858 | } |
| 5269 | 3859 | ||
| 5270 | fn reportDependencyError( | 3860 | fn reportDependencyError( |
| 5271 | self: *MachO, | 3861 | self: *MachO, |
| 5272 | parent: []const u8, | 3862 | parent: File.Index, |
| 5273 | path: ?[]const u8, | 3863 | path: ?[]const u8, |
| 5274 | comptime format: []const u8, | 3864 | comptime format: []const u8, |
| 5275 | args: anytype, | 3865 | args: anytype, |
| 5276 | ) error{OutOfMemory}!void { | 3866 | ) error{OutOfMemory}!void { |
| 5277 | const comp = self.base.comp; | 3867 | var err = try self.addErrorWithNotes(2); |
| 5278 | const gpa = comp.gpa; | 3868 | try err.addMsg(self, format, args); |
| 5279 | try comp.link_errors.ensureUnusedCapacity(gpa, 1); | ||
| 5280 | var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2); | ||
| 5281 | defer notes.deinit(); | ||
| 5282 | if (path) |p| { | 3869 | 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}); |
| 5284 | } | 3871 | } |
| 5285 | notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "a dependency of {s}", .{parent}) }); | 3872 | try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()}); |
| 5286 | comp.link_errors.appendAssumeCapacity(.{ | ||
| 5287 | .msg = try std.fmt.allocPrint(gpa, format, args), | ||
| 5288 | .notes = try notes.toOwnedSlice(), | ||
| 5289 | }); | ||
| 5290 | } | 3873 | } |
| 5291 | 3874 | ||
| 5292 | pub fn reportParseError( | 3875 | pub fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void { |
| 5293 | self: *MachO, | 3876 | var err = try self.addErrorWithNotes(1); |
| 5294 | path: []const u8, | 3877 | try err.addMsg(self, format, args); |
| 5295 | comptime format: []const u8, | 3878 | try err.addNote(self, "please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{}); |
| 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 | }); | ||
| 5308 | } | 3879 | } |
| 5309 | 3880 | ||
| 5310 | pub fn reportUnresolvedBoundarySymbol( | 3881 | fn reportDuplicates(self: *MachO, dupes: anytype) error{ HasDuplicates, OutOfMemory }!void { |
| 5311 | self: *MachO, | 3882 | const tracy = trace(@src()); |
| 5312 | sym_name: []const u8, | 3883 | defer tracy.end(); |
| 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 | } | ||
| 5327 | 3884 | ||
| 5328 | pub fn reportUndefined(self: *MachO) error{OutOfMemory}!void { | 3885 | const max_notes = 3; |
| 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); | ||
| 5333 | 3886 | ||
| 5334 | for (self.unresolved.keys()) |global_index| { | 3887 | var has_dupes = false; |
| 5335 | const global = self.globals.items[global_index]; | 3888 | var it = dupes.iterator(); |
| 5336 | const sym_name = self.getSymbolName(global); | 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); | ||
| 5337 | 3893 | ||
| 5338 | var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 1); | 3894 | var err = try self.addErrorWithNotes(nnotes + 1); |
| 5339 | defer notes.deinit(); | 3895 | try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.getName(self)}); |
| 3896 | try err.addNote(self, "defined by {}", .{sym.getFile(self).?.fmtPath()}); | ||
| 5340 | 3897 | ||
| 5341 | if (global.getFile()) |file| { | 3898 | var inote: usize = 0; |
| 5342 | const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{ | 3899 | while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { |
| 5343 | self.objects.items[file].name, | 3900 | const file = self.getFile(notes.items[inote]).?; |
| 5344 | }); | 3901 | try err.addNote(self, "defined by {}", .{file.fmtPath()}); |
| 5345 | notes.appendAssumeCapacity(.{ .msg = note }); | ||
| 5346 | } | 3902 | } |
| 5347 | 3903 | ||
| 5348 | var err_msg = File.ErrorMsg{ | 3904 | if (notes.items.len > max_notes) { |
| 5349 | .msg = try std.fmt.allocPrint(gpa, "undefined reference to symbol {s}", .{sym_name}), | 3905 | const remaining = notes.items.len - max_notes; |
| 5350 | }; | 3906 | try err.addNote(self, "defined {d} more times", .{remaining}); |
| 5351 | err_msg.notes = try notes.toOwnedSlice(); | 3907 | } |
| 5352 | 3908 | ||
| 5353 | comp.link_errors.appendAssumeCapacity(err_msg); | 3909 | has_dupes = true; |
| 5354 | } | 3910 | } |
| 3911 | |||
| 3912 | if (has_dupes) return error.HasDuplicates; | ||
| 5355 | } | 3913 | } |
| 5356 | 3914 | ||
| 5357 | fn reportSymbolCollision( | 3915 | pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols { |
| 5358 | self: *MachO, | 3916 | if (self.d_sym) |*ds| { |
| 5359 | first: SymbolWithLoc, | 3917 | return ds; |
| 5360 | other: SymbolWithLoc, | 3918 | } else return null; |
| 5361 | ) error{OutOfMemory}!void { | 3919 | } |
| 5362 | const comp = self.base.comp; | ||
| 5363 | const gpa = comp.gpa; | ||
| 5364 | try comp.link_errors.ensureUnusedCapacity(gpa, 1); | ||
| 5365 | 3920 | ||
| 5366 | var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2); | 3921 | pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void { |
| 5367 | defer notes.deinit(); | 3922 | if (!is_hot_update_compatible) return; |
| 5368 | 3923 | ||
| 5369 | if (first.getFile()) |file| { | 3924 | const mach_task = try std.os.darwin.machTaskForPid(pid); |
| 5370 | const note = try std.fmt.allocPrint(gpa, "first definition in {s}", .{ | 3925 | log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task }); |
| 5371 | self.objects.items[file].name, | 3926 | self.hot_state.mach_task = mach_task; |
| 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 | } | ||
| 5381 | 3927 | ||
| 5382 | var err_msg = File.ErrorMsg{ .msg = try std.fmt.allocPrint(gpa, "symbol {s} defined multiple times", .{ | 3928 | // TODO start exception handler in another thread |
| 5383 | self.getSymbolName(first), | ||
| 5384 | }) }; | ||
| 5385 | err_msg.notes = try notes.toOwnedSlice(); | ||
| 5386 | 3929 | ||
| 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); | ||
| 5388 | } | 3932 | } |
| 5389 | 3933 | ||
| 5390 | fn reportUnhandledSymbolType(self: *MachO, sym_with_loc: SymbolWithLoc) error{OutOfMemory}!void { | 3934 | pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void { |
| 5391 | const comp = self.base.comp; | 3935 | if (!is_hot_update_compatible) return; |
| 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); | ||
| 5397 | 3936 | ||
| 5398 | const file = sym_with_loc.getFile().?; | 3937 | _ = pid; |
| 5399 | notes[0] = .{ .msg = try std.fmt.allocPrint(gpa, "defined in {s}", .{self.objects.items[file].name}) }; | ||
| 5400 | 3938 | ||
| 5401 | const sym = self.getSymbol(sym_with_loc); | 3939 | // TODO stop exception handler |
| 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 | } | ||
| 5419 | 3940 | ||
| 5420 | /// Binary search | 3941 | // TODO see comment in ptraceAttach |
| 5421 | pub fn bsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize { | 3942 | // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0); |
| 5422 | if (!@hasDecl(@TypeOf(predicate), "predicate")) | ||
| 5423 | @compileError("Predicate is required to define fn predicate(@This(), T) bool"); | ||
| 5424 | 3943 | ||
| 5425 | var min: usize = 0; | 3944 | self.hot_state.mach_task = null; |
| 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; | ||
| 5437 | } | 3945 | } |
| 5438 | 3946 | ||
| 5439 | /// Linear search | 3947 | pub fn dumpState(self: *MachO) std.fmt.Formatter(fmtDumpState) { |
| 5440 | pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize { | 3948 | return .{ .data = self }; |
| 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; | ||
| 5449 | } | 3949 | } |
| 5450 | 3950 | ||
| 5451 | pub fn logSegments(self: *MachO) void { | 3951 | fn fmtDumpState( |
| 5452 | log.debug("segments:", .{}); | 3952 | self: *MachO, |
| 5453 | for (self.segments.items, 0..) |segment, i| { | 3953 | comptime unused_fmt_string: []const u8, |
| 5454 | log.debug(" segment({d}): {s} @{x} ({x}), sizeof({x})", .{ | 3954 | options: std.fmt.FormatOptions, |
| 5455 | i, | 3955 | writer: anytype, |
| 5456 | segment.segName(), | 3956 | ) !void { |
| 5457 | segment.fileoff, | 3957 | _ = options; |
| 5458 | segment.vmaddr, | 3958 | _ = unused_fmt_string; |
| 5459 | segment.vmsize, | 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), | ||
| 5460 | }); | 3964 | }); |
| 5461 | } | 3965 | } |
| 5462 | } | 3966 | for (self.objects.items) |index| { |
| 5463 | 3967 | const object = self.getFile(index).?.object; | |
| 5464 | pub fn logSections(self: *MachO) void { | 3968 | try writer.print("object({d}) : {} : has_debug({})", .{ |
| 5465 | log.debug("sections:", .{}); | 3969 | index, |
| 5466 | for (self.sections.items(.header), 0..) |header, i| { | 3970 | object.fmtPath(), |
| 5467 | log.debug(" sect({d}): {s},{s} @{x} ({x}), sizeof({x})", .{ | 3971 | object.hasDebugInfo(), |
| 5468 | i + 1, | 3972 | }); |
| 5469 | header.segName(), | 3973 | if (!object.alive) try writer.writeAll(" : ([*])"); |
| 5470 | header.sectName(), | 3974 | try writer.writeByte('\n'); |
| 5471 | header.offset, | 3975 | try writer.print("{}{}{}{}{}\n", .{ |
| 5472 | header.addr, | 3976 | object.fmtAtoms(self), |
| 5473 | header.size, | 3977 | object.fmtCies(self), |
| 3978 | object.fmtFdes(self), | ||
| 3979 | object.fmtUnwindRecords(self), | ||
| 3980 | object.fmtSymtab(self), | ||
| 5474 | }); | 3981 | }); |
| 5475 | } | 3982 | } |
| 5476 | } | 3983 | for (self.dylibs.items) |index| { |
| 5477 | 3984 | const dylib = self.getFile(index).?.dylib; | |
| 5478 | fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 { | 3985 | try writer.print("dylib({d}) : {s} : needed({}) : weak({})", .{ |
| 5479 | if (sym.sect()) { | 3986 | index, |
| 5480 | buf[0] = 's'; | 3987 | dylib.path, |
| 5481 | } | 3988 | dylib.needed, |
| 5482 | if (sym.ext()) { | 3989 | dylib.weak, |
| 5483 | if (sym.weakDef() or sym.pext()) { | 3990 | }); |
| 5484 | buf[1] = 'w'; | 3991 | if (!dylib.isAlive(self)) try writer.writeAll(" : ([*])"); |
| 5485 | } else { | 3992 | try writer.writeByte('\n'); |
| 5486 | buf[1] = 'e'; | 3993 | try writer.print("{}\n", .{dylib.fmtSymtab(self)}); |
| 5487 | } | ||
| 5488 | } | 3994 | } |
| 5489 | if (sym.tentative()) { | 3995 | if (self.getInternalObject()) |internal| { |
| 5490 | buf[2] = 't'; | 3996 | try writer.print("internal({d}) : internal\n", .{internal.index}); |
| 3997 | try writer.print("{}{}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) }); | ||
| 5491 | } | 3998 | } |
| 5492 | if (sym.undf()) { | 3999 | try writer.writeAll("thunks\n"); |
| 5493 | buf[3] = 'u'; | 4000 | for (self.thunks.items, 0..) |thunk, index| { |
| 4001 | try writer.print("thunk({d}) : {}\n", .{ index, thunk.fmt(self) }); | ||
| 5494 | } | 4002 | } |
| 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()}); | ||
| 5496 | } | 4011 | } |
| 5497 | 4012 | ||
| 5498 | pub fn logSymtab(self: *MachO) void { | 4013 | fn fmtSections(self: *MachO) std.fmt.Formatter(formatSections) { |
| 5499 | var buf: [4]u8 = undefined; | 4014 | return .{ .data = self }; |
| 5500 | 4015 | } | |
| 5501 | const scoped_log = std.log.scoped(.symtab); | ||
| 5502 | 4016 | ||
| 5503 | scoped_log.debug("locals:", .{}); | 4017 | fn formatSections( |
| 5504 | for (self.objects.items, 0..) |object, id| { | 4018 | self: *MachO, |
| 5505 | scoped_log.debug(" object({d}): {s}", .{ id, object.name }); | 4019 | comptime unused_fmt_string: []const u8, |
| 5506 | if (object.in_symtab == null) continue; | 4020 | options: std.fmt.FormatOptions, |
| 5507 | for (object.symtab, 0..) |sym, sym_id| { | 4021 | writer: anytype, |
| 5508 | @memset(&buf, '_'); | 4022 | ) !void { |
| 5509 | scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{ | 4023 | _ = options; |
| 5510 | sym_id, | 4024 | _ = unused_fmt_string; |
| 5511 | object.getSymbolName(@as(u32, @intCast(sym_id))), | 4025 | const slice = self.sections.slice(); |
| 5512 | sym.n_value, | 4026 | for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| { |
| 5513 | sym.n_sect, | 4027 | try writer.print("sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x})\n", .{ |
| 5514 | logSymAttributes(sym, &buf), | 4028 | i, seg_id, header.segName(), header.sectName(), header.offset, header.addr, |
| 5515 | }); | 4029 | header.@"align", header.size, |
| 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), | ||
| 5527 | }); | 4030 | }); |
| 5528 | } | 4031 | } |
| 4032 | } | ||
| 5529 | 4033 | ||
| 5530 | scoped_log.debug("exports:", .{}); | 4034 | fn fmtSegments(self: *MachO) std.fmt.Formatter(formatSegments) { |
| 5531 | for (self.globals.items, 0..) |global, i| { | 4035 | return .{ .data = self }; |
| 5532 | const sym = self.getSymbol(global); | 4036 | } |
| 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 | } | ||
| 5545 | 4037 | ||
| 5546 | scoped_log.debug("imports:", .{}); | 4038 | fn formatSegments( |
| 5547 | for (self.globals.items, 0..) |global, i| { | 4039 | self: *MachO, |
| 5548 | const sym = self.getSymbol(global); | 4040 | comptime unused_fmt_string: []const u8, |
| 5549 | if (!sym.undf()) continue; | 4041 | options: std.fmt.FormatOptions, |
| 5550 | if (sym.n_desc == N_DEAD) continue; | 4042 | writer: anytype, |
| 5551 | if (sym.n_desc == N_BOUNDARY) continue; | 4043 | ) !void { |
| 5552 | const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER); | 4044 | _ = options; |
| 5553 | scoped_log.debug(" %{d}: {s} @{x} in ord({d}), {s}", .{ | 4045 | _ = unused_fmt_string; |
| 5554 | i, | 4046 | for (self.segments.items, 0..) |seg, i| { |
| 5555 | self.getSymbolName(global), | 4047 | try writer.print("seg({d}) : {s} : @{x}-{x} ({x}-{x})\n", .{ |
| 5556 | sym.n_value, | 4048 | i, seg.segName(), seg.vmaddr, seg.vmaddr + seg.vmsize, |
| 5557 | ord, | 4049 | seg.fileoff, seg.fileoff + seg.filesize, |
| 5558 | logSymAttributes(sym, &buf), | ||
| 5559 | }); | 4050 | }); |
| 5560 | } | 4051 | } |
| 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 | } | ||
| 5592 | } | 4052 | } |
| 5593 | 4053 | ||
| 5594 | pub fn logAtoms(self: *MachO) void { | 4054 | pub fn fmtSectType(tt: u8) std.fmt.Formatter(formatSectType) { |
| 5595 | log.debug("atoms:", .{}); | 4055 | return .{ .data = tt }; |
| 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 | } | ||
| 5612 | } | 4056 | } |
| 5613 | 4057 | ||
| 5614 | pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void { | 4058 | fn formatSectType( |
| 5615 | if (!build_options.enable_logging) return; | 4059 | tt: u8, |
| 5616 | 4060 | comptime unused_fmt_string: []const u8, | |
| 5617 | const atom = self.getAtom(atom_index); | 4061 | options: std.fmt.FormatOptions, |
| 5618 | const sym = self.getSymbol(atom.getSymbolWithLoc()); | 4062 | writer: anytype, |
| 5619 | const sym_name = self.getSymbolName(atom.getSymbolWithLoc()); | 4063 | ) !void { |
| 5620 | logger.debug(" ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{ | 4064 | _ = options; |
| 5621 | atom_index, | 4065 | _ = unused_fmt_string; |
| 5622 | atom.sym_index, | 4066 | const name = switch (tt) { |
| 5623 | sym_name, | 4067 | macho.S_REGULAR => "REGULAR", |
| 5624 | sym.n_value, | 4068 | macho.S_ZEROFILL => "ZEROFILL", |
| 5625 | atom.size, | 4069 | macho.S_CSTRING_LITERALS => "CSTRING_LITERALS", |
| 5626 | atom.alignment, | 4070 | macho.S_4BYTE_LITERALS => "4BYTE_LITERALS", |
| 5627 | atom.getFile(), | 4071 | macho.S_8BYTE_LITERALS => "8BYTE_LITERALS", |
| 5628 | sym.n_sect, | 4072 | macho.S_16BYTE_LITERALS => "16BYTE_LITERALS", |
| 5629 | }); | 4073 | macho.S_LITERAL_POINTERS => "LITERAL_POINTERS", |
| 5630 | 4074 | macho.S_NON_LAZY_SYMBOL_POINTERS => "NON_LAZY_SYMBOL_POINTERS", | |
| 5631 | if (atom.getFile() != null) { | 4075 | macho.S_LAZY_SYMBOL_POINTERS => "LAZY_SYMBOL_POINTERS", |
| 5632 | var it = Atom.getInnerSymbolsIterator(self, atom_index); | 4076 | macho.S_SYMBOL_STUBS => "SYMBOL_STUBS", |
| 5633 | while (it.next()) |sym_loc| { | 4077 | macho.S_MOD_INIT_FUNC_POINTERS => "MOD_INIT_FUNC_POINTERS", |
| 5634 | const inner = self.getSymbol(sym_loc); | 4078 | macho.S_MOD_TERM_FUNC_POINTERS => "MOD_TERM_FUNC_POINTERS", |
| 5635 | const inner_name = self.getSymbolName(sym_loc); | 4079 | macho.S_COALESCED => "COALESCED", |
| 5636 | const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index); | 4080 | macho.S_GB_ZEROFILL => "GB_ZEROFILL", |
| 5637 | 4081 | macho.S_INTERPOSING => "INTERPOSING", | |
| 5638 | logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{ | 4082 | macho.S_DTRACE_DOF => "DTRACE_DOF", |
| 5639 | sym_loc.sym_index, | 4083 | macho.S_THREAD_LOCAL_REGULAR => "THREAD_LOCAL_REGULAR", |
| 5640 | inner_name, | 4084 | macho.S_THREAD_LOCAL_ZEROFILL => "THREAD_LOCAL_ZEROFILL", |
| 5641 | inner.n_value, | 4085 | macho.S_THREAD_LOCAL_VARIABLES => "THREAD_LOCAL_VARIABLES", |
| 5642 | offset, | 4086 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS => "THREAD_LOCAL_VARIABLE_POINTERS", |
| 5643 | }); | 4087 | macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => "THREAD_LOCAL_INIT_FUNCTION_POINTERS", |
| 5644 | } | 4088 | macho.S_INIT_FUNC_OFFSETS => "INIT_FUNC_OFFSETS", |
| 5645 | 4089 | else => |x| return writer.print("UNKNOWN({x})", .{x}), | |
| 5646 | if (Atom.getSectionAlias(self, atom_index)) |sym_loc| { | 4090 | }; |
| 5647 | const alias = self.getSymbol(sym_loc); | 4091 | try writer.print("{s}", .{name}); |
| 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 | } | ||
| 5658 | } | 4092 | } |
| 5659 | 4093 | ||
| 4094 | const is_hot_update_compatible = switch (builtin.target.os.tag) { | ||
| 4095 | .macos => true, | ||
| 4096 | else => false, | ||
| 4097 | }; | ||
| 4098 | |||
| 5660 | const default_entry_symbol_name = "_main"; | 4099 | const default_entry_symbol_name = "_main"; |
| 5661 | 4100 | ||
| 5662 | pub const base_tag: File.Tag = File.Tag.macho; | 4101 | pub const base_tag: link.File.Tag = link.File.Tag.macho; |
| 5663 | pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1))); | 4102 | pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1))); |
| 5664 | pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2))); | 4103 | pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2))); |
| 5665 | 4104 | ||
| 5666 | /// Mode of operation of the linker. | 4105 | const Section = struct { |
| 5667 | pub 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 | |||
| 5677 | pub const Section = struct { | ||
| 5678 | header: macho.section_64, | 4106 | header: macho.section_64, |
| 5679 | segment_index: u8, | 4107 | segment_id: u8, |
| 5680 | first_atom_index: ?Atom.Index = null, | 4108 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, |
| 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. | ||
| 5698 | free_list: std.ArrayListUnmanaged(Atom.Index) = .{}, | 4109 | free_list: std.ArrayListUnmanaged(Atom.Index) = .{}, |
| 4110 | last_atom_index: Atom.Index = 0, | ||
| 5699 | }; | 4111 | }; |
| 5700 | 4112 | ||
| 5701 | const is_hot_update_compatible = switch (builtin.target.os.tag) { | 4113 | const HotUpdateState = struct { |
| 5702 | .macos => true, | 4114 | mach_task: ?std.os.darwin.MachTask = null, |
| 5703 | else => false, | 4115 | }; |
| 4116 | |||
| 4117 | pub const DynamicRelocs = struct { | ||
| 4118 | rebase_relocs: u32 = 0, | ||
| 4119 | bind_relocs: u32 = 0, | ||
| 4120 | weak_bind_relocs: u32 = 0, | ||
| 5704 | }; | 4121 | }; |
| 5705 | 4122 | ||
| 5706 | const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata); | 4123 | pub 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 | }; | ||
| 5707 | 4134 | ||
| 5708 | const LazySymbolMetadata = struct { | 4135 | pub const null_sym = macho.nlist_64{ |
| 5709 | const State = enum { unused, pending_flush, flushed }; | 4136 | .n_strx = 0, |
| 5710 | text_atom: Atom.Index = undefined, | 4137 | .n_type = 0, |
| 5711 | data_const_atom: Atom.Index = undefined, | 4138 | .n_sect = 0, |
| 5712 | text_state: State = .unused, | 4139 | .n_desc = 0, |
| 5713 | data_const_state: State = .unused, | 4140 | .n_value = 0, |
| 5714 | }; | 4141 | }; |
| 5715 | 4142 | ||
| 5716 | const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index); | 4143 | pub 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 | } | ||
| 5717 | 4193 | ||
| 5718 | const DeclMetadata = struct { | 4194 | pub fn fromTarget(target: std.Target) Platform { |
| 5719 | atom: Atom.Index, | 4195 | return .{ |
| 5720 | section: u8, | 4196 | .os_tag = target.os.tag, |
| 5721 | /// A list of all exports aliases of this Decl. | 4197 | .abi = target.abi, |
| 5722 | /// TODO do we actually need this at all? | 4198 | .version = target.os.version_range.semver.min, |
| 5723 | exports: std.ArrayListUnmanaged(u32) = .{}, | 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 | } | ||
| 5724 | 4215 | ||
| 5725 | fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 { | 4216 | pub fn isBuildVersionCompatible(plat: Platform) bool { |
| 5726 | for (m.exports.items) |exp| { | 4217 | inline for (supported_platforms) |sup_plat| { |
| 5727 | if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp; | 4218 | if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { |
| 4219 | return sup_plat[2] <= plat.toAppleVersion(); | ||
| 4220 | } | ||
| 5728 | } | 4221 | } |
| 5729 | return null; | 4222 | return false; |
| 5730 | } | 4223 | } |
| 5731 | 4224 | ||
| 5732 | fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 { | 4225 | pub fn isVersionMinCompatible(plat: Platform) bool { |
| 5733 | for (m.exports.items) |*exp| { | 4226 | inline for (supported_platforms) |sup_plat| { |
| 5734 | if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp; | 4227 | if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { |
| 4228 | return sup_plat[3] <= plat.toAppleVersion(); | ||
| 4229 | } | ||
| 5735 | } | 4230 | } |
| 5736 | return null; | 4231 | return false; |
| 5737 | } | 4232 | } |
| 5738 | }; | ||
| 5739 | 4233 | ||
| 5740 | const DeclTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata); | 4234 | pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) { |
| 5741 | const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata); | 4235 | return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } }; |
| 5742 | const BindingTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Binding)); | 4236 | } |
| 5743 | const UnnamedConstTable = std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Atom.Index)); | ||
| 5744 | const RebaseTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32)); | ||
| 5745 | const RelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation)); | ||
| 5746 | const ActionTable = std.AutoHashMapUnmanaged(u32, RelocFlags); | ||
| 5747 | |||
| 5748 | pub const RelocFlags = packed struct { | ||
| 5749 | add_got: bool = false, | ||
| 5750 | add_stub: bool = false, | ||
| 5751 | }; | ||
| 5752 | 4237 | ||
| 5753 | pub const SymbolWithLoc = extern struct { | 4238 | const FmtCtx = struct { |
| 5754 | // Index into the respective symbol table. | 4239 | platform: Platform, |
| 5755 | sym_index: u32, | 4240 | cpu_arch: std.Target.Cpu.Arch, |
| 4241 | }; | ||
| 5756 | 4242 | ||
| 5757 | // 0 means it's a synthetic global. | 4243 | pub fn formatTarget( |
| 5758 | file: u32 = 0, | 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 | } | ||
| 5759 | 4256 | ||
| 5760 | pub fn getFile(self: SymbolWithLoc) ?u32 { | 4257 | /// Caller owns the memory. |
| 5761 | if (self.file == 0) return null; | 4258 | pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 { |
| 5762 | return self.file - 1; | 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(); | ||
| 5763 | } | 4263 | } |
| 5764 | 4264 | ||
| 5765 | pub fn eql(self: SymbolWithLoc, other: SymbolWithLoc) bool { | 4265 | pub fn eqlTarget(plat: Platform, other: Platform) bool { |
| 5766 | return self.file == other.file and self.sym_index == other.sym_index; | 4266 | return plat.os_tag == other.os_tag and plat.abi == other.abi; |
| 5767 | } | 4267 | } |
| 5768 | }; | 4268 | }; |
| 5769 | 4269 | ||
| 5770 | const HotUpdateState = struct { | 4270 | const SupportedPlatforms = struct { |
| 5771 | mach_task: ?std.os.darwin.MachTask = null, | 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 | ||
| 4279 | const 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 }, | ||
| 5772 | }; | 4287 | }; |
| 4288 | // zig fmt: on | ||
| 4289 | |||
| 4290 | pub 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 | |||
| 4297 | pub 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 | |||
| 4305 | fn 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. | ||
| 4339 | fn 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. | ||
| 4349 | fn 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 | } | ||
| 5773 | 4369 | ||
| 5774 | /// When allocating, the ideal_capacity is calculated by | 4370 | /// When allocating, the ideal_capacity is calculated by |
| 5775 | /// actual_capacity + (actual_capacity / ideal_factor) | 4371 | /// actual_capacity + (actual_capacity / ideal_factor) |
| ... | @@ -5783,13 +4379,37 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size); | ... | @@ -5783,13 +4379,37 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size); |
| 5783 | 4379 | ||
| 5784 | /// Default virtual memory offset corresponds to the size of __PAGEZERO segment and | 4380 | /// Default virtual memory offset corresponds to the size of __PAGEZERO segment and |
| 5785 | /// start of __TEXT segment. | 4381 | /// start of __TEXT segment. |
| 5786 | pub const default_pagezero_vmsize: u64 = 0x100000000; | 4382 | pub const default_pagezero_size: u64 = 0x100000000; |
| 5787 | 4383 | ||
| 5788 | /// We commit 0x1000 = 4096 bytes of space to the header and | 4384 | /// We commit 0x1000 = 4096 bytes of space to the header and |
| 5789 | /// the table of load commands. This should be plenty for any | 4385 | /// the table of load commands. This should be plenty for any |
| 5790 | /// potential future extensions. | 4386 | /// potential future extensions. |
| 5791 | pub const default_headerpad_size: u32 = 0x1000; | 4387 | pub const default_headerpad_size: u32 = 0x1000; |
| 5792 | 4388 | ||
| 4389 | const 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. | ||
| 4399 | pub 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 | |||
| 4406 | const UndefinedTreatment = enum { | ||
| 4407 | @"error", | ||
| 4408 | warn, | ||
| 4409 | suppress, | ||
| 4410 | dynamic_lookup, | ||
| 4411 | }; | ||
| 4412 | |||
| 5793 | const MachO = @This(); | 4413 | const MachO = @This(); |
| 5794 | 4414 | ||
| 5795 | const std = @import("std"); | 4415 | const std = @import("std"); |
| ... | @@ -5799,6 +4419,7 @@ const assert = std.debug.assert; | ... | @@ -5799,6 +4419,7 @@ const assert = std.debug.assert; |
| 5799 | const dwarf = std.dwarf; | 4419 | const dwarf = std.dwarf; |
| 5800 | const fs = std.fs; | 4420 | const fs = std.fs; |
| 5801 | const log = std.log.scoped(.link); | 4421 | const log = std.log.scoped(.link); |
| 4422 | const state_log = std.log.scoped(.link_state); | ||
| 5802 | const macho = std.macho; | 4423 | const macho = std.macho; |
| 5803 | const math = std.math; | 4424 | const math = std.math; |
| 5804 | const mem = std.mem; | 4425 | const mem = std.mem; |
| ... | @@ -5808,46 +4429,56 @@ const aarch64 = @import("../arch/aarch64/bits.zig"); | ... | @@ -5808,46 +4429,56 @@ const aarch64 = @import("../arch/aarch64/bits.zig"); |
| 5808 | const calcUuid = @import("MachO/uuid.zig").calcUuid; | 4429 | const calcUuid = @import("MachO/uuid.zig").calcUuid; |
| 5809 | const codegen = @import("../codegen.zig"); | 4430 | const codegen = @import("../codegen.zig"); |
| 5810 | const dead_strip = @import("MachO/dead_strip.zig"); | 4431 | const dead_strip = @import("MachO/dead_strip.zig"); |
| 4432 | const eh_frame = @import("MachO/eh_frame.zig"); | ||
| 5811 | const fat = @import("MachO/fat.zig"); | 4433 | const fat = @import("MachO/fat.zig"); |
| 5812 | const link = @import("../link.zig"); | 4434 | const link = @import("../link.zig"); |
| 5813 | const llvm_backend = @import("../codegen/llvm.zig"); | 4435 | const llvm_backend = @import("../codegen/llvm.zig"); |
| 5814 | const load_commands = @import("MachO/load_commands.zig"); | 4436 | const load_commands = @import("MachO/load_commands.zig"); |
| 5815 | const stubs = @import("MachO/stubs.zig"); | 4437 | const relocatable = @import("MachO/relocatable.zig"); |
| 5816 | const tapi = @import("tapi.zig"); | 4438 | const tapi = @import("tapi.zig"); |
| 5817 | const target_util = @import("../target.zig"); | 4439 | const target_util = @import("../target.zig"); |
| 5818 | const thunks = @import("MachO/thunks.zig"); | 4440 | const thunks = @import("MachO/thunks.zig"); |
| 5819 | const trace = @import("../tracy.zig").trace; | 4441 | const trace = @import("../tracy.zig").trace; |
| 5820 | const zld = @import("MachO/zld.zig"); | 4442 | const synthetic = @import("MachO/synthetic.zig"); |
| 5821 | 4443 | ||
| 5822 | const Air = @import("../Air.zig"); | 4444 | const Air = @import("../Air.zig"); |
| 4445 | const Alignment = Atom.Alignment; | ||
| 5823 | const Allocator = mem.Allocator; | 4446 | const Allocator = mem.Allocator; |
| 5824 | const Archive = @import("MachO/Archive.zig"); | 4447 | const Archive = @import("MachO/Archive.zig"); |
| 5825 | pub const Atom = @import("MachO/Atom.zig"); | 4448 | pub const Atom = @import("MachO/Atom.zig"); |
| 4449 | const BindSection = synthetic.BindSection; | ||
| 5826 | const Cache = std.Build.Cache; | 4450 | const Cache = std.Build.Cache; |
| 5827 | const CodeSignature = @import("MachO/CodeSignature.zig"); | 4451 | const CodeSignature = @import("MachO/CodeSignature.zig"); |
| 5828 | const Compilation = @import("../Compilation.zig"); | 4452 | const Compilation = @import("../Compilation.zig"); |
| 4453 | pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); | ||
| 5829 | const Dwarf = File.Dwarf; | 4454 | const Dwarf = File.Dwarf; |
| 5830 | const DwarfInfo = @import("MachO/DwarfInfo.zig"); | 4455 | const DwarfInfo = @import("MachO/DwarfInfo.zig"); |
| 5831 | const Dylib = @import("MachO/Dylib.zig"); | 4456 | const Dylib = @import("MachO/Dylib.zig"); |
| 5832 | const File = link.File; | 4457 | const ExportTrieSection = synthetic.ExportTrieSection; |
| 4458 | const File = @import("MachO/file.zig").File; | ||
| 4459 | const GotSection = synthetic.GotSection; | ||
| 4460 | const Indsymtab = synthetic.Indsymtab; | ||
| 4461 | const InternalObject = @import("MachO/InternalObject.zig"); | ||
| 4462 | const ObjcStubsSection = synthetic.ObjcStubsSection; | ||
| 5833 | const Object = @import("MachO/Object.zig"); | 4463 | const Object = @import("MachO/Object.zig"); |
| 4464 | const LazyBindSection = synthetic.LazyBindSection; | ||
| 4465 | const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; | ||
| 5834 | const LibStub = tapi.LibStub; | 4466 | const LibStub = tapi.LibStub; |
| 5835 | const Liveness = @import("../Liveness.zig"); | 4467 | const Liveness = @import("../Liveness.zig"); |
| 5836 | const LlvmObject = @import("../codegen/llvm.zig").Object; | 4468 | const LlvmObject = @import("../codegen/llvm.zig").Object; |
| 5837 | const Md5 = std.crypto.hash.Md5; | 4469 | const Md5 = std.crypto.hash.Md5; |
| 5838 | const Module = @import("../Module.zig"); | 4470 | const Module = @import("../Module.zig"); |
| 5839 | const InternPool = @import("../InternPool.zig"); | 4471 | const InternPool = @import("../InternPool.zig"); |
| 5840 | const Platform = load_commands.Platform; | 4472 | const RebaseSection = synthetic.RebaseSection; |
| 5841 | const Relocation = @import("MachO/Relocation.zig"); | 4473 | pub const Relocation = @import("MachO/Relocation.zig"); |
| 5842 | const StringTable = @import("StringTable.zig"); | 4474 | const StringTable = @import("StringTable.zig"); |
| 5843 | const TableSection = @import("table_section.zig").TableSection; | 4475 | const StubsSection = synthetic.StubsSection; |
| 5844 | const Trie = @import("MachO/Trie.zig"); | 4476 | const StubsHelperSection = synthetic.StubsHelperSection; |
| 5845 | const Type = @import("../type.zig").Type; | 4477 | const Symbol = @import("MachO/Symbol.zig"); |
| 4478 | const Thunk = thunks.Thunk; | ||
| 4479 | const TlvPtrSection = synthetic.TlvPtrSection; | ||
| 5846 | const TypedValue = @import("../TypedValue.zig"); | 4480 | const TypedValue = @import("../TypedValue.zig"); |
| 5847 | const Value = @import("../value.zig").Value; | 4481 | const UnwindInfo = @import("MachO/UnwindInfo.zig"); |
| 5848 | const Alignment = Atom.Alignment; | 4482 | const WeakBindSection = synthetic.WeakBindSection; |
| 5849 | 4483 | const ZigGotSection = synthetic.ZigGotSection; | |
| 5850 | pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); | 4484 | const ZigObject = @import("MachO/ZigObject.zig"); |
| 5851 | pub const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, SymbolWithLoc); | ||
| 5852 | pub const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, SymbolWithLoc); | ||
| 5853 | pub const Rebase = @import("MachO/dyld_info/Rebase.zig"); |
src/link/MachO/Archive.zig+77-148| ... | @@ -1,20 +1,15 @@ | ... | @@ -1,20 +1,15 @@ |
| 1 | file: fs.File, | 1 | path: []const u8, |
| 2 | fat_offset: u64, | 2 | data: []const u8, |
| 3 | name: []const u8, | ||
| 4 | header: ar_hdr = undefined, | ||
| 5 | 3 | ||
| 6 | /// Parsed table of contents. | 4 | objects: std.ArrayListUnmanaged(Object) = .{}, |
| 7 | /// Each symbol name points to a list of all definition | ||
| 8 | /// sites within the current static archive. | ||
| 9 | toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{}, | ||
| 10 | 5 | ||
| 11 | // Archive files start with the ARMAG identifying string. Then follows a | 6 | // Archive files start with the ARMAG identifying string. Then follows a |
| 12 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' | 7 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' |
| 13 | // member indicates, for each member file. | 8 | // member indicates, for each member file. |
| 14 | /// String that begins an archive file. | 9 | /// String that begins an archive file. |
| 15 | const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; | 10 | pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; |
| 16 | /// Size of that string. | 11 | /// Size of that string. |
| 17 | const SARMAG: u4 = 8; | 12 | pub const SARMAG: u4 = 8; |
| 18 | 13 | ||
| 19 | /// String in ar_fmag at the end of each header. | 14 | /// String in ar_fmag at the end of each header. |
| 20 | const ARFMAG: *const [2:0]u8 = "`\n"; | 15 | const ARFMAG: *const [2:0]u8 = "`\n"; |
| ... | @@ -41,177 +36,111 @@ const ar_hdr = extern struct { | ... | @@ -41,177 +36,111 @@ const ar_hdr = extern struct { |
| 41 | /// Always contains ARFMAG. | 36 | /// Always contains ARFMAG. |
| 42 | ar_fmag: [2]u8, | 37 | ar_fmag: [2]u8, |
| 43 | 38 | ||
| 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 | |||
| 63 | fn date(self: ar_hdr) !u64 { | 39 | 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)}); |
| 65 | return std.fmt.parseInt(u64, value, 10); | 41 | return std.fmt.parseInt(u64, value, 10); |
| 66 | } | 42 | } |
| 67 | 43 | ||
| 68 | fn size(self: ar_hdr) !u32 { | 44 | 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)}); |
| 70 | return std.fmt.parseInt(u32, value, 10); | 46 | return std.fmt.parseInt(u32, value, 10); |
| 71 | } | 47 | } |
| 72 | 48 | ||
| 73 | fn getValue(raw: []const u8) []const u8 { | 49 | fn name(self: *const ar_hdr) ?[]const u8 { |
| 74 | return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); | 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]; | ||
| 75 | } | 54 | } |
| 76 | }; | ||
| 77 | 55 | ||
| 78 | pub fn isArchive(file: fs.File, fat_offset: u64) bool { | 56 | fn nameLength(self: ar_hdr) !?u32 { |
| 79 | const reader = file.reader(); | 57 | const value = &self.ar_name; |
| 80 | const magic = reader.readBytesNoEof(SARMAG) catch return false; | 58 | if (!mem.startsWith(u8, value, "#1/")) return null; |
| 81 | defer file.seekTo(fat_offset) catch {}; | 59 | const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); |
| 82 | return mem.eql(u8, &magic, ARMAG); | 60 | return try std.fmt.parseInt(u32, trimmed, 10); |
| 83 | } | ||
| 84 | |||
| 85 | pub 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); | ||
| 92 | } | 61 | } |
| 93 | self.toc.deinit(allocator); | 62 | }; |
| 94 | allocator.free(self.name); | ||
| 95 | } | ||
| 96 | |||
| 97 | pub 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 | } | ||
| 107 | 63 | ||
| 108 | fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 { | 64 | pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { |
| 109 | var name: []u8 = undefined; | 65 | const file = try std.fs.cwd().openFile(path, .{}); |
| 110 | switch (name_or_length) { | 66 | defer file.close(); |
| 111 | .Name => |n| { | 67 | if (fat_arch) |arch| { |
| 112 | name = try allocator.dupe(u8, n); | 68 | try file.seekTo(arch.offset); |
| 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 | }, | ||
| 121 | } | 69 | } |
| 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; | ||
| 123 | } | 73 | } |
| 124 | 74 | ||
| 125 | fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void { | 75 | pub fn deinit(self: *Archive, allocator: Allocator) void { |
| 126 | const symtab_size = try reader.readInt(u32, .little); | 76 | allocator.free(self.data); |
| 127 | const symtab = try allocator.alloc(u8, symtab_size); | 77 | allocator.free(self.path); |
| 128 | defer allocator.free(symtab); | 78 | self.objects.deinit(allocator); |
| 129 | 79 | } | |
| 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 | }; | ||
| 134 | 80 | ||
| 135 | const strtab_size = try reader.readInt(u32, .little); | 81 | pub fn parse(self: *Archive, macho_file: *MachO) !void { |
| 136 | const strtab = try allocator.alloc(u8, strtab_size); | 82 | const gpa = macho_file.base.comp.gpa; |
| 137 | defer allocator.free(strtab); | ||
| 138 | 83 | ||
| 139 | reader.readNoEof(strtab) catch { | 84 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 140 | log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size}); | 85 | defer arena.deinit(); |
| 141 | return error.MalformedArchive; | ||
| 142 | }; | ||
| 143 | 86 | ||
| 144 | var symtab_stream = std.io.fixedBufferStream(symtab); | 87 | var stream = std.io.fixedBufferStream(self.data); |
| 145 | var symtab_reader = symtab_stream.reader(); | 88 | const reader = stream.reader(); |
| 89 | _ = try reader.readBytesNoEof(SARMAG); | ||
| 146 | 90 | ||
| 147 | while (true) { | 91 | while (true) { |
| 148 | const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) { | 92 | if (stream.pos >= self.data.len) break; |
| 149 | error.EndOfStream => break, | 93 | if (!mem.isAligned(stream.pos, 2)) stream.pos += 1; |
| 150 | else => |e| return e, | ||
| 151 | }; | ||
| 152 | const object_offset = try symtab_reader.readInt(u32, .little); | ||
| 153 | 94 | ||
| 154 | const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0); | 95 | const hdr = try reader.readStruct(ar_hdr); |
| 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); | ||
| 158 | 96 | ||
| 159 | if (!res.found_existing) { | 97 | if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { |
| 160 | res.value_ptr.* = .{}; | 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; | ||
| 161 | } | 102 | } |
| 162 | 103 | ||
| 163 | try res.value_ptr.append(allocator, object_offset); | 104 | var size = try hdr.size(); |
| 164 | } | 105 | const name = name: { |
| 165 | } | 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 | } | ||
| 166 | 119 | ||
| 167 | pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object { | 120 | if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; |
| 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 | } | ||
| 195 | 121 | ||
| 196 | var object = Object{ | 122 | const object = Object{ |
| 197 | .name = name, | 123 | .archive = try gpa.dupe(u8, self.path), |
| 198 | .mtime = object_header.date() catch 0, | 124 | .path = try gpa.dupe(u8, name), |
| 199 | .contents = contents, | 125 | .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]), |
| 200 | }; | 126 | .index = undefined, |
| 127 | .alive = false, | ||
| 128 | .mtime = hdr.date() catch 0, | ||
| 129 | }; | ||
| 201 | 130 | ||
| 202 | try object.parse(gpa); | 131 | log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path }); |
| 203 | 132 | ||
| 204 | return object; | 133 | try self.objects.append(gpa, object); |
| 134 | } | ||
| 205 | } | 135 | } |
| 206 | 136 | ||
| 207 | const Archive = @This(); | 137 | const fat = @import("fat.zig"); |
| 208 | |||
| 209 | const std = @import("std"); | ||
| 210 | const assert = std.debug.assert; | ||
| 211 | const fs = std.fs; | ||
| 212 | const log = std.log.scoped(.link); | 138 | const log = std.log.scoped(.link); |
| 213 | const macho = std.macho; | 139 | const macho = std.macho; |
| 214 | const mem = std.mem; | 140 | const mem = std.mem; |
| 141 | const std = @import("std"); | ||
| 215 | 142 | ||
| 216 | const Allocator = mem.Allocator; | 143 | const Allocator = mem.Allocator; |
| 144 | const Archive = @This(); | ||
| 145 | const MachO = @import("../MachO.zig"); | ||
| 217 | const Object = @import("Object.zig"); | 146 | const Object = @import("Object.zig"); |
src/link/MachO/Atom.zig+980-1085| ... | @@ -1,1271 +1,1166 @@ | ... | @@ -1,1271 +1,1166 @@ |
| 1 | /// Each Atom always gets a symbol with the fully qualified name. | 1 | /// Address allocated for this Atom. |
| 2 | /// The symbol can reside in any object file context structure in `symtab` array | 2 | value: u64 = 0, |
| 3 | /// (see `Object`), or if the symbol is a synthetic symbol such as a GOT cell or | 3 | |
| 4 | /// a stub trampoline, it can be found in the linkers `locals` arraylist. | 4 | /// Name of this Atom. |
| 5 | /// If this field is 0 and file is 0, it means the codegen size = 0 and there is no symbol or | 5 | name: u32 = 0, |
| 6 | /// offset table entry. | 6 | |
| 7 | sym_index: u32 = 0, | 7 | /// Index into linker's input file table. |
| 8 | 8 | file: File.Index = 0, | |
| 9 | /// 0 means an Atom is a synthetic Atom such as a GOT cell defined by the linker. | 9 | |
| 10 | /// Otherwise, it is the index into appropriate object file (indexing from 1). | 10 | /// Size of this atom |
| 11 | /// Prefer using `getFile()` helper to get the file index out rather than using | ||
| 12 | /// the field directly. | ||
| 13 | file: 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. | ||
| 21 | inner_sym_index: u32 = 0, | ||
| 22 | inner_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. | ||
| 27 | size: u64 = 0, | 11 | size: u64 = 0, |
| 28 | 12 | ||
| 29 | /// Alignment of this atom as a power of 2. | 13 | /// Alignment of this atom as a power of two. |
| 30 | /// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned. | ||
| 31 | alignment: Alignment = .@"1", | 14 | alignment: Alignment = .@"1", |
| 32 | 15 | ||
| 33 | /// Points to the previous and next neighbours | 16 | /// Index of the input section. |
| 34 | /// TODO use the same trick as with symbols: reserve index 0 as null atom | 17 | n_sect: u32 = 0, |
| 35 | next_index: ?Index = null, | ||
| 36 | prev_index: ?Index = null, | ||
| 37 | 18 | ||
| 38 | pub const Alignment = @import("../../InternPool.zig").Alignment; | 19 | /// Index of the output section. |
| 20 | out_n_sect: u8 = 0, | ||
| 39 | 21 | ||
| 40 | pub const Index = u32; | 22 | /// Offset within the parent section pointed to by n_sect. |
| 23 | /// off + size <= parent section size. | ||
| 24 | off: u64 = 0, | ||
| 41 | 25 | ||
| 42 | pub const Binding = struct { | 26 | /// Relocations of this atom. |
| 43 | target: SymbolWithLoc, | 27 | relocs: Loc = .{}, |
| 44 | offset: u64, | ||
| 45 | }; | ||
| 46 | 28 | ||
| 47 | /// Returns `null` if the Atom is a synthetic Atom. | 29 | /// Index of this atom in the linker's atoms table. |
| 48 | /// Otherwise, returns an index into an array of Objects. | 30 | atom_index: Index = 0, |
| 49 | pub fn getFile(self: Atom) ?u32 { | ||
| 50 | if (self.file == 0) return null; | ||
| 51 | return self.file - 1; | ||
| 52 | } | ||
| 53 | 31 | ||
| 54 | pub fn getSymbolIndex(self: Atom) ?u32 { | 32 | /// Index of the thunk for this atom. |
| 55 | if (self.getFile() == null and self.sym_index == 0) return null; | 33 | thunk_index: Thunk.Index = 0, |
| 56 | return self.sym_index; | 34 | |
| 35 | /// Unwind records associated with this atom. | ||
| 36 | unwind_records: Loc = .{}, | ||
| 37 | |||
| 38 | flags: 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`. | ||
| 42 | prev_index: Index = 0, | ||
| 43 | next_index: Index = 0, | ||
| 44 | |||
| 45 | pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 { | ||
| 46 | return macho_file.strings.getAssumeExists(self.name); | ||
| 57 | } | 47 | } |
| 58 | 48 | ||
| 59 | /// Returns symbol referencing this atom. | 49 | pub fn getFile(self: Atom, macho_file: *MachO) File { |
| 60 | pub fn getSymbol(self: Atom, macho_file: *MachO) macho.nlist_64 { | 50 | return macho_file.getFile(self.file).?; |
| 61 | return self.getSymbolPtr(macho_file).*; | ||
| 62 | } | 51 | } |
| 63 | 52 | ||
| 64 | /// Returns pointer-to-symbol referencing this atom. | 53 | pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation { |
| 65 | pub fn getSymbolPtr(self: Atom, macho_file: *MachO) *macho.nlist_64 { | 54 | return switch (self.getFile(macho_file)) { |
| 66 | const sym_index = self.getSymbolIndex().?; | 55 | .zig_object => |x| x.getAtomRelocs(self), |
| 67 | return macho_file.getSymbolPtr(.{ .sym_index = sym_index, .file = self.file }); | 56 | .object => |x| x.getAtomRelocs(self), |
| 57 | else => unreachable, | ||
| 58 | }; | ||
| 68 | } | 59 | } |
| 69 | 60 | ||
| 70 | pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { | 61 | pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 { |
| 71 | const sym_index = self.getSymbolIndex().?; | 62 | return switch (self.getFile(macho_file)) { |
| 72 | return .{ .sym_index = sym_index, .file = self.file }; | 63 | .zig_object => |x| x.getInputSection(self, macho_file), |
| 64 | .object => |x| x.sections.items(.header)[self.n_sect], | ||
| 65 | else => unreachable, | ||
| 66 | }; | ||
| 73 | } | 67 | } |
| 74 | 68 | ||
| 75 | /// Returns the name of this atom. | 69 | pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 { |
| 76 | pub fn getName(self: Atom, macho_file: *MachO) []const u8 { | 70 | return self.getInputSection(macho_file).addr + self.off; |
| 77 | const sym_index = self.getSymbolIndex().?; | ||
| 78 | return macho_file.getSymbolName(.{ .sym_index = sym_index, .file = self.file }); | ||
| 79 | } | 71 | } |
| 80 | 72 | ||
| 81 | /// Returns how much room there is to grow in virtual address space. | 73 | pub fn getPriority(self: Atom, macho_file: *MachO) u64 { |
| 82 | /// File offset relocation happens transparently, so it is not included in | 74 | const file = self.getFile(macho_file); |
| 83 | /// this calculation. | 75 | return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect)); |
| 84 | pub 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 | } | ||
| 95 | } | 76 | } |
| 96 | 77 | ||
| 97 | pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { | 78 | pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index { |
| 98 | // No need to keep a free list node for the last atom. | 79 | return switch (self.getFile(macho_file)) { |
| 99 | const next_index = self.next_index orelse return false; | 80 | .dylib => unreachable, |
| 100 | const next = macho_file.getAtom(next_index); | 81 | .zig_object, .internal => &[0]UnwindInfo.Record.Index{}, |
| 101 | const self_sym = self.getSymbol(macho_file); | 82 | .object => |x| x.unwind_records.items[self.unwind_records.pos..][0..self.unwind_records.len], |
| 102 | const next_sym = next.getSymbol(macho_file); | 83 | }; |
| 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; | ||
| 108 | } | 84 | } |
| 109 | 85 | ||
| 110 | pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 { | 86 | pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void { |
| 111 | const segname = sect.segName(); | 87 | for (self.getUnwindRecords(macho_file)) |cu_index| { |
| 112 | const sectname = sect.sectName(); | 88 | const cu = macho_file.getUnwindRecord(cu_index); |
| 113 | const res: ?u8 = blk: { | 89 | cu.alive = false; |
| 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 | } | ||
| 120 | 90 | ||
| 121 | // We handle unwind info separately. | 91 | if (cu.getFdePtr(macho_file)) |fde| { |
| 122 | if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) { | 92 | fde.alive = false; |
| 123 | break :blk null; | ||
| 124 | } | ||
| 125 | if (mem.eql(u8, "__LD", segname) and mem.eql(u8, "__compact_unwind", sectname)) { | ||
| 126 | break :blk null; | ||
| 127 | } | 93 | } |
| 94 | } | ||
| 95 | } | ||
| 128 | 96 | ||
| 129 | if (sect.isCode()) { | 97 | pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { |
| 130 | if (macho_file.text_section_index == null) { | 98 | return macho_file.getThunk(self.thunk_index); |
| 131 | macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{ | 99 | } |
| 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 | } | ||
| 139 | 100 | ||
| 140 | if (sect.isDebug()) { | 101 | pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { |
| 141 | break :blk null; | 102 | const segname, const sectname, const flags = blk: { |
| 142 | } | 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 | }; | ||
| 143 | 108 | ||
| 144 | switch (sect.type()) { | 109 | switch (sect.type()) { |
| 145 | macho.S_4BYTE_LITERALS, | 110 | macho.S_4BYTE_LITERALS, |
| 146 | macho.S_8BYTE_LITERALS, | 111 | macho.S_8BYTE_LITERALS, |
| 147 | macho.S_16BYTE_LITERALS, | 112 | macho.S_16BYTE_LITERALS, |
| 148 | => { | 113 | => break :blk .{ "__TEXT", "__const", macho.S_REGULAR }, |
| 149 | break :blk macho_file.getSectionByName("__TEXT", "__const") orelse | 114 | |
| 150 | try macho_file.initSection("__TEXT", "__const", .{}); | ||
| 151 | }, | ||
| 152 | macho.S_CSTRING_LITERALS => { | 115 | macho.S_CSTRING_LITERALS => { |
| 153 | if (mem.startsWith(u8, sectname, "__objc")) { | 116 | if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{ |
| 154 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 117 | sect.segName(), sect.sectName(), macho.S_REGULAR, |
| 155 | try macho_file.initSection(segname, sectname, .{}); | 118 | }; |
| 156 | } | 119 | break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS }; |
| 157 | break :blk macho_file.getSectionByName("__TEXT", "__cstring") orelse | ||
| 158 | try macho_file.initSection("__TEXT", "__cstring", .{ | ||
| 159 | .flags = macho.S_CSTRING_LITERALS, | ||
| 160 | }); | ||
| 161 | }, | 120 | }, |
| 121 | |||
| 162 | macho.S_MOD_INIT_FUNC_POINTERS, | 122 | macho.S_MOD_INIT_FUNC_POINTERS, |
| 163 | macho.S_MOD_TERM_FUNC_POINTERS, | 123 | macho.S_MOD_TERM_FUNC_POINTERS, |
| 164 | => { | 124 | => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags }, |
| 165 | break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse | 125 | |
| 166 | try macho_file.initSection("__DATA_CONST", sectname, .{ | ||
| 167 | .flags = sect.flags, | ||
| 168 | }); | ||
| 169 | }, | ||
| 170 | macho.S_LITERAL_POINTERS, | 126 | macho.S_LITERAL_POINTERS, |
| 171 | macho.S_ZEROFILL, | 127 | macho.S_ZEROFILL, |
| 128 | macho.S_GB_ZEROFILL, | ||
| 172 | macho.S_THREAD_LOCAL_VARIABLES, | 129 | macho.S_THREAD_LOCAL_VARIABLES, |
| 173 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS, | 130 | macho.S_THREAD_LOCAL_VARIABLE_POINTERS, |
| 174 | macho.S_THREAD_LOCAL_REGULAR, | 131 | macho.S_THREAD_LOCAL_REGULAR, |
| 175 | macho.S_THREAD_LOCAL_ZEROFILL, | 132 | macho.S_THREAD_LOCAL_ZEROFILL, |
| 176 | => { | 133 | => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, |
| 177 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 134 | |
| 178 | try macho_file.initSection(segname, sectname, .{ | 135 | macho.S_COALESCED => break :blk .{ |
| 179 | .flags = sect.flags, | 136 | sect.segName(), |
| 180 | }); | 137 | sect.sectName(), |
| 181 | }, | 138 | macho.S_REGULAR, |
| 182 | macho.S_COALESCED => { | ||
| 183 | break :blk macho_file.getSectionByName(segname, sectname) orelse | ||
| 184 | try macho_file.initSection(segname, sectname, .{}); | ||
| 185 | }, | 139 | }, |
| 140 | |||
| 186 | macho.S_REGULAR => { | 141 | macho.S_REGULAR => { |
| 187 | if (mem.eql(u8, segname, "__TEXT")) { | 142 | const segname = sect.segName(); |
| 188 | if (mem.eql(u8, sectname, "__rodata") or | 143 | const sectname = sect.sectName(); |
| 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 | } | ||
| 198 | if (mem.eql(u8, segname, "__DATA")) { | 144 | if (mem.eql(u8, segname, "__DATA")) { |
| 199 | if (mem.eql(u8, sectname, "__const") or | 145 | if (mem.eql(u8, sectname, "__const") or |
| 200 | mem.eql(u8, sectname, "__cfstring") or | 146 | mem.eql(u8, sectname, "__cfstring") or |
| 201 | mem.eql(u8, sectname, "__objc_classlist") or | 147 | mem.eql(u8, sectname, "__objc_classlist") or |
| 202 | mem.eql(u8, sectname, "__objc_imageinfo")) | 148 | mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{ |
| 203 | { | 149 | "__DATA_CONST", |
| 204 | break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse | 150 | sectname, |
| 205 | try macho_file.initSection("__DATA_CONST", sectname, .{}); | 151 | macho.S_REGULAR, |
| 206 | } else if (mem.eql(u8, sectname, "__data")) { | 152 | }; |
| 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 | } | ||
| 212 | } | 153 | } |
| 213 | break :blk macho_file.getSectionByName(segname, sectname) orelse | 154 | break :blk .{ segname, sectname, sect.flags }; |
| 214 | try macho_file.initSection(segname, sectname, .{}); | ||
| 215 | }, | 155 | }, |
| 216 | else => break :blk null, | ||
| 217 | } | ||
| 218 | }; | ||
| 219 | 156 | ||
| 220 | // TODO we can do this directly in the selection logic above. | 157 | else => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, |
| 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; | ||
| 240 | } | 158 | } |
| 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; | ||
| 241 | } | 167 | } |
| 242 | if (macho_file.bss_section_index == null) { | 168 | if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) { |
| 243 | if (macho_file.getSectionByName("__DATA", "__bss")) |index| { | 169 | macho_file.data_sect_index = osec; |
| 244 | macho_file.bss_section_index = index; | ||
| 245 | } | ||
| 246 | } | 170 | } |
| 171 | return osec; | ||
| 172 | } | ||
| 247 | 173 | ||
| 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. | ||
| 177 | pub 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; | ||
| 249 | } | 180 | } |
| 250 | 181 | ||
| 251 | pub fn addRelocation(macho_file: *MachO, atom_index: Index, reloc: Relocation) !void { | 182 | pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { |
| 252 | return addRelocations(macho_file, atom_index, &[_]Relocation{reloc}); | 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; | ||
| 253 | } | 190 | } |
| 254 | 191 | ||
| 255 | pub fn addRelocations(macho_file: *MachO, atom_index: Index, relocs: []const Relocation) !void { | 192 | pub fn allocate(self: *Atom, macho_file: *MachO) !void { |
| 256 | const comp = macho_file.base.comp; | 193 | const sect = &macho_file.sections.items(.header)[self.out_n_sect]; |
| 257 | const gpa = comp.gpa; | 194 | const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect]; |
| 258 | const gop = try macho_file.relocs.getOrPut(gpa, atom_index); | 195 | const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect]; |
| 259 | if (!gop.found_existing) { | 196 | const new_atom_ideal_capacity = MachO.padToIdeal(self.size); |
| 260 | gop.value_ptr.* = .{}; | 197 | |
| 261 | } | 198 | // We use these to indicate our intention to update metadata, placing the new atom, |
| 262 | try gop.value_ptr.ensureUnusedCapacity(gpa, relocs.len); | 199 | // and possibly removing a free list node. |
| 263 | for (relocs) |reloc| { | 200 | // It would be simpler to do it inside the for loop below, but that would cause a |
| 264 | log.debug(" (adding reloc of type {s} to target %{d})", .{ | 201 | // problem if an error was returned later in the function. So this action |
| 265 | @tagName(reloc.type), | 202 | // is actually carried out at the end of the function, when errors are no longer possible. |
| 266 | reloc.target.sym_index, | 203 | var atom_placement: ?Atom.Index = null; |
| 267 | }); | 204 | var free_list_removal: ?usize = null; |
| 268 | gop.value_ptr.appendAssumeCapacity(reloc); | 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 | // } | ||
| 269 | } | 283 | } |
| 270 | } | 284 | sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units()); |
| 271 | 285 | ||
| 272 | pub fn addRebase(macho_file: *MachO, atom_index: Index, offset: u32) !void { | 286 | // This function can also reallocate an atom. |
| 273 | const comp = macho_file.base.comp; | 287 | // In this case we need to "unplug" it from its previous location before |
| 274 | const gpa = comp.gpa; | 288 | // plugging it in to its new location. |
| 275 | const atom = macho_file.getAtom(atom_index); | 289 | if (macho_file.getAtom(self.prev_index)) |prev| { |
| 276 | log.debug(" (adding rebase at offset 0x{x} in %{?d})", .{ offset, atom.getSymbolIndex() }); | 290 | prev.next_index = self.next_index; |
| 277 | const gop = try macho_file.rebases.getOrPut(gpa, atom_index); | 291 | } |
| 278 | if (!gop.found_existing) { | 292 | if (macho_file.getAtom(self.next_index)) |next| { |
| 279 | gop.value_ptr.* = .{}; | 293 | next.prev_index = self.prev_index; |
| 280 | } | 294 | } |
| 281 | try gop.value_ptr.append(gpa, offset); | ||
| 282 | } | ||
| 283 | 295 | ||
| 284 | pub fn addBinding(macho_file: *MachO, atom_index: Index, binding: Binding) !void { | 296 | if (atom_placement) |big_atom_index| { |
| 285 | const comp = macho_file.base.comp; | 297 | const big_atom = macho_file.getAtom(big_atom_index).?; |
| 286 | const gpa = comp.gpa; | 298 | self.prev_index = big_atom_index; |
| 287 | const atom = macho_file.getAtom(atom_index); | 299 | self.next_index = big_atom.next_index; |
| 288 | log.debug(" (adding binding to symbol {s} at offset 0x{x} in %{?d})", .{ | 300 | big_atom.next_index = self.atom_index; |
| 289 | macho_file.getSymbolName(binding.target), | 301 | } else { |
| 290 | binding.offset, | 302 | self.prev_index = 0; |
| 291 | atom.getSymbolIndex(), | 303 | self.next_index = 0; |
| 292 | }); | 304 | } |
| 293 | const gop = try macho_file.bindings.getOrPut(gpa, atom_index); | 305 | if (free_list_removal) |i| { |
| 294 | if (!gop.found_existing) { | 306 | _ = free_list.swapRemove(i); |
| 295 | gop.value_ptr.* = .{}; | ||
| 296 | } | 307 | } |
| 297 | try gop.value_ptr.append(gpa, binding); | 308 | |
| 309 | self.flags.alive = true; | ||
| 298 | } | 310 | } |
| 299 | 311 | ||
| 300 | pub fn resolveRelocations( | 312 | pub fn shrink(self: *Atom, macho_file: *MachO) void { |
| 301 | macho_file: *MachO, | 313 | _ = self; |
| 302 | atom_index: Index, | 314 | _ = macho_file; |
| 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 | } | ||
| 310 | } | 315 | } |
| 311 | 316 | ||
| 312 | pub fn freeRelocations(macho_file: *MachO, atom_index: Index) void { | 317 | pub fn grow(self: *Atom, macho_file: *MachO) !void { |
| 313 | const comp = macho_file.base.comp; | 318 | if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file)) |
| 314 | const gpa = comp.gpa; | 319 | try self.allocate(macho_file); |
| 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); | ||
| 321 | } | 320 | } |
| 322 | 321 | ||
| 323 | const InnerSymIterator = struct { | 322 | pub fn free(self: *Atom, macho_file: *MachO) void { |
| 324 | sym_index: u32, | 323 | log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.getName(macho_file) }); |
| 325 | nsyms: u32, | ||
| 326 | file: u32, | ||
| 327 | pos: u32 = 0, | ||
| 328 | 324 | ||
| 329 | pub fn next(it: *@This()) ?SymbolWithLoc { | 325 | const comp = macho_file.base.comp; |
| 330 | if (it.pos == it.nsyms) return null; | 326 | const gpa = comp.gpa; |
| 331 | const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file }; | 327 | const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect]; |
| 332 | it.pos += 1; | 328 | const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect]; |
| 333 | return res; | 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 | } | ||
| 334 | } | 343 | } |
| 335 | }; | ||
| 336 | |||
| 337 | /// Returns an iterator over potentially contained symbols. | ||
| 338 | /// Panics when called on a synthetic Atom. | ||
| 339 | pub 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 | } | ||
| 348 | 344 | ||
| 349 | /// Returns a section alias symbol if one is defined. | 345 | if (macho_file.getAtom(last_atom_index.*)) |last_atom| { |
| 350 | /// An alias symbol is used to represent the start of an input section | 346 | if (last_atom.atom_index == self.atom_index) { |
| 351 | /// if there were no symbols defined within that range. | 347 | if (macho_file.getAtom(self.prev_index)) |_| { |
| 352 | /// Alias symbols are only used on x86_64. | 348 | // TODO shrink the section size here |
| 353 | pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc { | 349 | last_atom_index.* = self.prev_index; |
| 354 | const atom = macho_file.getAtom(atom_index); | 350 | } else { |
| 355 | assert(atom.getFile() != null); | 351 | last_atom_index.* = 0; |
| 356 | 352 | } | |
| 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 | }; | ||
| 367 | } | 353 | } |
| 368 | } | 354 | } |
| 369 | return null; | ||
| 370 | } | ||
| 371 | 355 | ||
| 372 | /// Given an index into a contained symbol within, calculates an offset wrt | 356 | if (macho_file.getAtom(self.prev_index)) |prev| { |
| 373 | /// the start of this Atom. | 357 | prev.next_index = self.next_index; |
| 374 | pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 { | 358 | if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) { |
| 375 | const atom = macho_file.getAtom(atom_index); | 359 | // The free list is heuristics, it doesn't have to be perfect, so we can |
| 376 | assert(atom.getFile() != null); | 360 | // ignore the OOM here. |
| 377 | 361 | free_list.append(gpa, prev.atom_index) catch {}; | |
| 378 | if (atom.sym_index == sym_index) return 0; | 362 | } |
| 379 | 363 | } else { | |
| 380 | const object = macho_file.objects.items[atom.getFile().?]; | 364 | self.prev_index = 0; |
| 381 | const source_sym = object.getSourceSymbol(sym_index).?; | 365 | } |
| 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 | } | ||
| 392 | 366 | ||
| 393 | pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void { | 367 | if (macho_file.getAtom(self.next_index)) |next| { |
| 394 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 368 | next.prev_index = self.prev_index; |
| 395 | const arch = target.cpu.arch; | 369 | } else { |
| 396 | const atom = macho_file.getAtom(atom_index); | 370 | self.next_index = 0; |
| 397 | assert(atom.getFile() != null); // synthetic atoms do not have relocs | 371 | } |
| 398 | 372 | ||
| 399 | return switch (arch) { | 373 | // TODO create relocs free list |
| 400 | .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs), | 374 | self.freeRelocs(macho_file); |
| 401 | .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs), | 375 | // TODO figure out how to free input section mappind in ZigModule |
| 402 | else => unreachable, | 376 | // const zig_object = macho_file.zigObjectPtr().? |
| 403 | }; | 377 | // assert(zig_object.atoms.swapRemove(self.atom_index)); |
| 378 | self.* = .{}; | ||
| 404 | } | 379 | } |
| 405 | 380 | ||
| 406 | const RelocContext = struct { | 381 | pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void { |
| 407 | base_addr: i64 = 0, | 382 | const gpa = macho_file.base.comp.gpa; |
| 408 | base_offset: i32 = 0, | 383 | const file = self.getFile(macho_file); |
| 409 | }; | 384 | assert(file == .zig_object); |
| 410 | 385 | const rels = &file.zig_object.relocs.items[self.relocs.pos]; | |
| 411 | pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext { | 386 | try rels.append(gpa, reloc); |
| 412 | const atom = macho_file.getAtom(atom_index); | 387 | self.relocs.len += 1; |
| 413 | assert(atom.getFile() != null); // synthetic atoms do not have relocs | 388 | } |
| 414 | 389 | ||
| 415 | const object = macho_file.objects.items[atom.getFile().?]; | 390 | pub fn freeRelocs(self: *Atom, macho_file: *MachO) void { |
| 416 | if (object.getSourceSymbol(atom.sym_index)) |source_sym| { | 391 | self.getFile(macho_file).zig_object.freeAtomRelocs(self.*); |
| 417 | const source_sect = object.getSourceSection(source_sym.n_sect - 1); | 392 | self.relocs.len = 0; |
| 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 | }; | ||
| 430 | } | 393 | } |
| 431 | 394 | ||
| 432 | pub fn parseRelocTarget(macho_file: *MachO, ctx: struct { | 395 | pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { |
| 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 { | ||
| 439 | const tracy = trace(@src()); | 396 | const tracy = trace(@src()); |
| 440 | defer tracy.end(); | 397 | defer tracy.end(); |
| 398 | assert(self.flags.alive); | ||
| 441 | 399 | ||
| 442 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 400 | const dynrel_ctx = switch (self.getFile(macho_file)) { |
| 443 | const object = &macho_file.objects.items[ctx.object_id]; | 401 | .zig_object => |x| &x.dynamic_relocs, |
| 444 | log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name }); | 402 | .object => |x| &x.dynamic_relocs, |
| 445 | 403 | else => unreachable, | |
| 446 | const sym_index = if (ctx.rel.r_extern == 0) sym_index: { | 404 | }; |
| 447 | const sect_id = @as(u8, @intCast(ctx.rel.r_symbolnum - 1)); | 405 | const relocs = self.getRelocs(macho_file); |
| 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 | |||
| 490 | pub 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 | } | ||
| 502 | 406 | ||
| 503 | fn scanAtomRelocsArm64( | ||
| 504 | macho_file: *MachO, | ||
| 505 | atom_index: Index, | ||
| 506 | relocs: []align(1) const macho.relocation_info, | ||
| 507 | ) !void { | ||
| 508 | for (relocs) |rel| { | 407 | for (relocs) |rel| { |
| 509 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | 408 | if (try self.reportUndefSymbol(rel, macho_file)) continue; |
| 510 | 409 | ||
| 511 | switch (rel_type) { | 410 | switch (rel.type) { |
| 512 | .ARM64_RELOC_ADDEND, .ARM64_RELOC_SUBTRACTOR => continue, | 411 | .branch => { |
| 513 | else => {}, | 412 | const symbol = rel.getTargetSymbol(macho_file); |
| 514 | } | 413 | if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { |
| 515 | 414 | symbol.flags.stubs = true; | |
| 516 | if (rel.r_extern == 0) continue; | 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 | }, | ||
| 517 | 422 | ||
| 518 | const atom = macho_file.getAtom(atom_index); | 423 | .got_load, |
| 519 | const object = &macho_file.objects.items[atom.getFile().?]; | 424 | .got_load_page, |
| 520 | const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum]; | 425 | .got_load_pageoff, |
| 521 | const sym_loc = SymbolWithLoc{ | 426 | => { |
| 522 | .sym_index = sym_index, | 427 | const symbol = rel.getTargetSymbol(macho_file); |
| 523 | .file = atom.file, | 428 | if (symbol.flags.import or |
| 524 | }; | 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 | }, | ||
| 525 | 439 | ||
| 526 | const target = if (object.getGlobal(sym_index)) |global_index| | 440 | .zig_got_load => { |
| 527 | macho_file.globals.items[global_index] | 441 | assert(rel.getTargetSymbol(macho_file).flags.has_zig_got); |
| 528 | else | 442 | }, |
| 529 | sym_loc; | ||
| 530 | 443 | ||
| 531 | switch (rel_type) { | 444 | .got => { |
| 532 | .ARM64_RELOC_BRANCH26 => { | 445 | rel.getTargetSymbol(macho_file).flags.needs_got = true; |
| 533 | // TODO rewrite relocation | ||
| 534 | const sym = macho_file.getSymbol(target); | ||
| 535 | if (sym.undf()) try macho_file.addStubEntry(target); | ||
| 536 | }, | 446 | }, |
| 537 | .ARM64_RELOC_GOT_LOAD_PAGE21, | 447 | |
| 538 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | 448 | .tlv, |
| 539 | .ARM64_RELOC_POINTER_TO_GOT, | 449 | .tlvp_page, |
| 450 | .tlvp_pageoff, | ||
| 540 | => { | 451 | => { |
| 541 | // TODO rewrite relocation | 452 | const symbol = rel.getTargetSymbol(macho_file); |
| 542 | try macho_file.addGotEntry(target); | 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 | } | ||
| 543 | }, | 466 | }, |
| 544 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | 467 | |
| 545 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | 468 | .unsigned => { |
| 546 | => { | 469 | if (rel.meta.length == 3) { // TODO this really should check if this is pointer width |
| 547 | const sym = macho_file.getSymbol(target); | 470 | if (rel.tag == .@"extern") { |
| 548 | if (sym.undf()) try macho_file.addTlvPtrEntry(target); | 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 | } | ||
| 549 | }, | 493 | }, |
| 550 | else => {}, | 494 | |
| 495 | .signed, | ||
| 496 | .signed1, | ||
| 497 | .signed2, | ||
| 498 | .signed4, | ||
| 499 | .page, | ||
| 500 | .pageoff, | ||
| 501 | .subtractor, | ||
| 502 | => {}, | ||
| 551 | } | 503 | } |
| 552 | } | 504 | } |
| 553 | } | 505 | } |
| 554 | 506 | ||
| 555 | fn scanAtomRelocsX86( | 507 | fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool { |
| 556 | macho_file: *MachO, | 508 | if (rel.tag == .local) return false; |
| 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)); | ||
| 562 | 509 | ||
| 563 | switch (rel_type) { | 510 | const sym = rel.getTargetSymbol(macho_file); |
| 564 | .X86_64_RELOC_SUBTRACTOR => continue, | 511 | if (sym.getFile(macho_file) == null) { |
| 565 | else => {}, | 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.* = .{}; | ||
| 566 | } | 516 | } |
| 517 | try gop.value_ptr.append(gpa, self.atom_index); | ||
| 518 | return true; | ||
| 519 | } | ||
| 567 | 520 | ||
| 568 | if (rel.r_extern == 0) continue; | 521 | return false; |
| 522 | } | ||
| 569 | 523 | ||
| 570 | const atom = macho_file.getAtom(atom_index); | 524 | pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { |
| 571 | const object = &macho_file.objects.items[atom.getFile().?]; | 525 | const tracy = trace(@src()); |
| 572 | const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum]; | 526 | defer tracy.end(); |
| 573 | const sym_loc = SymbolWithLoc{ | ||
| 574 | .sym_index = sym_index, | ||
| 575 | .file = atom.file, | ||
| 576 | }; | ||
| 577 | 527 | ||
| 578 | const target = if (object.getGlobal(sym_index)) |global_index| | 528 | assert(!self.getInputSection(macho_file).isZerofill()); |
| 579 | macho_file.globals.items[global_index] | 529 | const file = self.getFile(macho_file); |
| 580 | else | 530 | const name = self.getName(macho_file); |
| 581 | sym_loc; | 531 | const relocs = self.getRelocs(macho_file); |
| 582 | 532 | ||
| 583 | switch (rel_type) { | 533 | relocs_log.debug("{x}: {s}", .{ self.value, name }); |
| 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 | } | ||
| 601 | 534 | ||
| 602 | pub fn resolveRelocs( | 535 | var stream = std.io.fixedBufferStream(buffer); |
| 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 | }); | ||
| 617 | 536 | ||
| 618 | const ctx = getRelocContext(macho_file, atom_index); | 537 | var i: usize = 0; |
| 619 | 538 | while (i < relocs.len) : (i += 1) { | |
| 620 | return switch (arch) { | 539 | const rel = relocs[i]; |
| 621 | .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx), | 540 | const rel_offset = rel.offset - self.off; |
| 622 | .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx), | 541 | const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null; |
| 623 | else => unreachable, | ||
| 624 | }; | ||
| 625 | } | ||
| 626 | 542 | ||
| 627 | pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) u64 { | 543 | if (rel.tag == .@"extern") { |
| 628 | const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse { | 544 | if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue; |
| 629 | // If there is no atom for target, we still need to check for special, atom-less | 545 | } |
| 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 | }); | ||
| 642 | 546 | ||
| 643 | const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc()); | 547 | try stream.seekTo(rel_offset); |
| 644 | assert(target_sym.n_desc != MachO.N_DEAD); | 548 | self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| { |
| 645 | 549 | switch (err) { | |
| 646 | // If `target` is contained within the target atom, pull its address value. | 550 | error.RelaxFail => { |
| 647 | const offset = if (target_atom.getFile() != null) blk: { | 551 | try macho_file.reportParseError2( |
| 648 | const object = macho_file.objects.items[target_atom.getFile().?]; | 552 | file.getIndex(), |
| 649 | break :blk if (object.getSourceSymbol(target.sym_index)) |_| | 553 | "{s}: 0x{x}: failed to relax relocation: in {s}", |
| 650 | Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index) | 554 | .{ name, rel.offset, @tagName(rel.type) }, |
| 651 | else | 555 | ); |
| 652 | 0; // section alias | 556 | return error.ResolveFailed; |
| 653 | } else 0; | 557 | }, |
| 654 | const base_address: u64 = if (is_tlv) base_address: { | 558 | else => |e| return e, |
| 655 | // For TLV relocations, the value specified as a relocation is the displacement from the | 559 | } |
| 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; | ||
| 667 | }; | 560 | }; |
| 668 | break :base_address macho_file.sections.items(.header)[sect_id].addr; | 561 | } |
| 669 | } else 0; | ||
| 670 | return target_sym.n_value + offset - base_address; | ||
| 671 | } | 562 | } |
| 672 | 563 | ||
| 673 | fn resolveRelocsArm64( | 564 | const ResolveError = error{ |
| 674 | macho_file: *MachO, | 565 | RelaxFail, |
| 675 | atom_index: Index, | 566 | NoSpaceLeft, |
| 676 | atom_code: []u8, | 567 | DivisionByZero, |
| 677 | atom_relocs: []align(1) const macho.relocation_info, | 568 | UnexpectedRemainder, |
| 678 | context: RelocContext, | 569 | Overflow, |
| 679 | ) !void { | 570 | }; |
| 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; | ||
| 685 | 571 | ||
| 686 | for (atom_relocs) |rel| { | 572 | fn resolveRelocInner( |
| 687 | const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); | 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 | } | ||
| 688 | 611 | ||
| 689 | switch (rel_type) { | 612 | switch (rel.type) { |
| 690 | .ARM64_RELOC_ADDEND => { | 613 | .subtractor => {}, |
| 691 | assert(addend == null); | 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 | }, | ||
| 692 | 652 | ||
| 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 | }, | ||
| 694 | 659 | ||
| 695 | addend = rel.r_symbolnum; | 660 | .branch => { |
| 696 | continue; | 661 | assert(rel.meta.length == 2); |
| 697 | }, | 662 | assert(rel.meta.pcrel); |
| 698 | .ARM64_RELOC_SUBTRACTOR => { | 663 | assert(rel.tag == .@"extern"); |
| 699 | assert(subtractor == null); | 664 | |
| 700 | 665 | switch (cpu_arch) { | |
| 701 | relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{ | 666 | .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little), |
| 702 | @tagName(rel_type), | 667 | .aarch64 => { |
| 703 | rel.r_address, | 668 | const disp: i28 = math.cast(i28, S + A - P) orelse blk: { |
| 704 | rel.r_symbolnum, | 669 | const thunk = self.getThunk(macho_file); |
| 705 | atom.getFile(), | 670 | const S_: i64 = @intCast(thunk.getAddress(rel.target)); |
| 706 | }); | 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 | }, | ||
| 707 | 685 | ||
| 708 | subtractor = parseRelocTarget(macho_file, .{ | 686 | .got_load => { |
| 709 | .object_id = atom.getFile().?, | 687 | assert(rel.tag == .@"extern"); |
| 710 | .rel = rel, | 688 | assert(rel.meta.length == 2); |
| 711 | .code = atom_code, | 689 | assert(rel.meta.pcrel); |
| 712 | .base_addr = context.base_addr, | 690 | if (rel.getTargetSymbol(macho_file).flags.has_got) { |
| 713 | .base_offset = context.base_offset, | 691 | try writer.writeInt(i32, @intCast(G + A - P), .little); |
| 714 | }); | 692 | } else { |
| 715 | continue; | 693 | try x86_64.relaxGotLoad(code[rel_offset - 3 ..]); |
| 716 | }, | 694 | try writer.writeInt(i32, @intCast(S + A - P), .little); |
| 717 | else => {}, | 695 | } |
| 718 | } | 696 | }, |
| 719 | 697 | ||
| 720 | const target = parseRelocTarget(macho_file, .{ | 698 | .zig_got_load => { |
| 721 | .object_id = atom.getFile().?, | 699 | assert(rel.tag == .@"extern"); |
| 722 | .rel = rel, | 700 | assert(rel.meta.length == 2); |
| 723 | .code = atom_code, | 701 | assert(rel.meta.pcrel); |
| 724 | .base_addr = context.base_addr, | 702 | switch (cpu_arch) { |
| 725 | .base_offset = context.base_offset, | 703 | .x86_64 => try writer.writeInt(i32, @intCast(ZIG_GOT + A - P), .little), |
| 726 | }); | 704 | .aarch64 => @panic("TODO resolve __got_zig indirection reloc"), |
| 727 | const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset)); | 705 | else => unreachable, |
| 728 | 706 | } | |
| 729 | relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{ | 707 | }, |
| 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 | }; | ||
| 754 | 708 | ||
| 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 | }, | ||
| 756 | 722 | ||
| 757 | switch (rel_type) { | 723 | .signed, .signed1, .signed2, .signed4 => { |
| 758 | .ARM64_RELOC_BRANCH26 => { | 724 | assert(rel.meta.length == 2); |
| 759 | relocs_log.debug(" source {s} (object({?})), target {s}", .{ | 725 | assert(rel.meta.pcrel); |
| 760 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | 726 | try writer.writeInt(i32, @intCast(S + A - P), .little); |
| 761 | atom.getFile(), | 727 | }, |
| 762 | macho_file.getSymbolName(target), | ||
| 763 | }); | ||
| 764 | 728 | ||
| 765 | const displacement = if (Relocation.calcPcRelativeDisplacementArm64( | 729 | .page, |
| 766 | source_addr, | 730 | .got_load_page, |
| 767 | target_addr, | 731 | .tlvp_page, |
| 768 | )) |disp| blk: { | 732 | => { |
| 769 | relocs_log.debug(" | target_addr = 0x{x}", .{target_addr}); | 733 | assert(rel.tag == .@"extern"); |
| 770 | break :blk disp; | 734 | assert(rel.meta.length == 2); |
| 771 | } else |_| blk: { | 735 | assert(rel.meta.pcrel); |
| 772 | const thunk_index = macho_file.thunk_table.get(atom_index).?; | 736 | const sym = rel.getTargetSymbol(macho_file); |
| 773 | const thunk = macho_file.thunks.items[thunk_index]; | 737 | const source = math.cast(u64, P) orelse return error.Overflow; |
| 774 | const thunk_sym_loc = if (macho_file.getSymbol(target).undf()) | 738 | const target = target: { |
| 775 | thunk.getTrampoline(macho_file, .stub, target).? | 739 | const target = switch (rel.type) { |
| 776 | else | 740 | .page => S + A, |
| 777 | thunk.getTrampoline(macho_file, .atom, target).?; | 741 | .got_load_page => G + A, |
| 778 | const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value; | 742 | .tlvp_page => if (sym.flags.tlv_ptr) blk: { |
| 779 | relocs_log.debug(" | target_addr = 0x{x} (thunk)", .{thunk_addr}); | 743 | const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); |
| 780 | break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr); | 744 | break :blk S_ + A; |
| 745 | } else S + A, | ||
| 746 | else => unreachable, | ||
| 781 | }; | 747 | }; |
| 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 | }, | ||
| 782 | 761 | ||
| 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); | ||
| 784 | var inst = aarch64.Instruction{ | 770 | var inst = aarch64.Instruction{ |
| 785 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( | 771 | .add_subtract_immediate = mem.bytesToValue(std.meta.TagPayload( |
| 786 | aarch64.Instruction, | 772 | aarch64.Instruction, |
| 787 | aarch64.Instruction.unconditional_branch_immediate, | 773 | aarch64.Instruction.add_subtract_immediate, |
| 788 | ), code), | 774 | ), inst_code), |
| 789 | }; | 775 | }; |
| 790 | inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2)))); | 776 | inst.add_subtract_immediate.imm12 = off; |
| 791 | mem.writeInt(u32, code, inst.toU32(), .little); | 777 | try writer.writeInt(u32, inst.toU32(), .little); |
| 792 | }, | 778 | } else { |
| 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]; | ||
| 804 | var inst = aarch64.Instruction{ | 779 | var inst = aarch64.Instruction{ |
| 805 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( | 780 | .load_store_register = mem.bytesToValue(std.meta.TagPayload( |
| 806 | aarch64.Instruction, | 781 | aarch64.Instruction, |
| 807 | aarch64.Instruction.pc_relative_address, | 782 | aarch64.Instruction.load_store_register, |
| 808 | ), code), | 783 | ), inst_code), |
| 809 | }; | 784 | }; |
| 810 | inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2)); | 785 | const off = try Relocation.calcPageOffset(target, switch (inst.load_store_register.size) { |
| 811 | inst.pc_relative_address.immlo = @as(u2, @truncate(pages)); | 786 | 0 => if (inst.load_store_register.v == 1) |
| 812 | mem.writeInt(u32, code, inst.toU32(), .little); | 787 | Relocation.PageOffsetInstKind.load_store_128 |
| 813 | addend = null; | 788 | else |
| 814 | }, | 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 | }, | ||
| 815 | 798 | ||
| 816 | .ARM64_RELOC_PAGEOFF12 => { | 799 | .got_load_pageoff => { |
| 817 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0))); | 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 | }, | ||
| 818 | 814 | ||
| 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 | }; | ||
| 820 | 828 | ||
| 821 | const code = atom_code[rel_offset..][0..4]; | 829 | const RegInfo = struct { |
| 822 | if (Relocation.isArithmeticOp(code)) { | 830 | rd: u5, |
| 823 | const off = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic); | 831 | rn: u5, |
| 824 | var inst = aarch64.Instruction{ | 832 | size: u2, |
| 825 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( | 833 | }; |
| 826 | aarch64.Instruction, | 834 | |
| 827 | aarch64.Instruction.add_subtract_immediate, | 835 | const inst_code = code[rel_offset..][0..4]; |
| 828 | ), code), | 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, | ||
| 829 | }; | 846 | }; |
| 830 | inst.add_subtract_immediate.imm12 = off; | ||
| 831 | mem.writeInt(u32, code, inst.toU32(), .little); | ||
| 832 | } else { | 847 | } else { |
| 833 | var inst = aarch64.Instruction{ | 848 | const inst = mem.bytesToValue(std.meta.TagPayload( |
| 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( | ||
| 863 | aarch64.Instruction, | 849 | aarch64.Instruction, |
| 864 | aarch64.Instruction.load_store_register, | 850 | aarch64.Instruction.load_store_register, |
| 865 | ), code), | 851 | ), inst_code); |
| 866 | }; | 852 | break :blk .{ |
| 867 | inst.load_store_register.offset = off; | 853 | .rd = inst.rt, |
| 868 | mem.writeInt(u32, code, inst.toU32(), .little); | 854 | .rn = inst.rn, |
| 869 | addend = null; | 855 | .size = inst.size, |
| 870 | }, | 856 | }; |
| 871 | 857 | } | |
| 872 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { | 858 | }; |
| 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}); | ||
| 877 | 859 | ||
| 878 | const RegInfo = struct { | 860 | var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{ |
| 879 | rd: u5, | 861 | .load_store_register = .{ |
| 880 | rn: u5, | 862 | .rt = reg_info.rd, |
| 881 | size: u2, | 863 | .rn = reg_info.rn, |
| 882 | }; | 864 | .offset = try Relocation.calcPageOffset(target, .load_store_64), |
| 883 | const reg_info: RegInfo = blk: { | 865 | .opc = 0b01, |
| 884 | if (Relocation.isArithmeticOp(code)) { | 866 | .op1 = 0b01, |
| 885 | const inst = mem.bytesToValue(meta.TagPayload( | 867 | .v = 0, |
| 886 | aarch64.Instruction, | 868 | .size = reg_info.size, |
| 887 | aarch64.Instruction.add_subtract_immediate, | 869 | }, |
| 888 | ), code); | 870 | } else aarch64.Instruction{ |
| 889 | break :blk .{ | 871 | .add_subtract_immediate = .{ |
| 890 | .rd = inst.rd, | 872 | .rd = reg_info.rd, |
| 891 | .rn = inst.rn, | 873 | .rn = reg_info.rn, |
| 892 | .size = inst.sf, | 874 | .imm12 = try Relocation.calcPageOffset(target, .arithmetic), |
| 893 | }; | 875 | .sh = 0, |
| 894 | } else { | 876 | .s = 0, |
| 895 | const inst = mem.bytesToValue(meta.TagPayload( | 877 | .op = 0, |
| 896 | aarch64.Instruction, | 878 | .sf = @as(u1, @truncate(reg_info.size)), |
| 897 | aarch64.Instruction.load_store_register, | 879 | }, |
| 898 | ), code); | 880 | }; |
| 899 | break :blk .{ | 881 | try writer.writeInt(u32, inst.toU32(), .little); |
| 900 | .rd = inst.rt, | 882 | }, |
| 901 | .rn = inst.rn, | 883 | } |
| 902 | .size = inst.size, | 884 | } |
| 903 | }; | ||
| 904 | } | ||
| 905 | }; | ||
| 906 | 885 | ||
| 907 | var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{ | 886 | const x86_64 = struct { |
| 908 | .load_store_register = .{ | 887 | fn relaxGotLoad(code: []u8) error{RelaxFail}!void { |
| 909 | .rt = reg_info.rd, | 888 | const old_inst = disassemble(code) orelse return error.RelaxFail; |
| 910 | .rn = reg_info.rn, | 889 | switch (old_inst.encoding.mnemonic) { |
| 911 | .offset = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64), | 890 | .mov => { |
| 912 | .opc = 0b01, | 891 | const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail; |
| 913 | .op1 = 0b01, | 892 | relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); |
| 914 | .v = 0, | 893 | encode(&.{inst}, code) catch return error.RelaxFail; |
| 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; | ||
| 930 | }, | 894 | }, |
| 895 | else => return error.RelaxFail, | ||
| 896 | } | ||
| 897 | } | ||
| 931 | 898 | ||
| 932 | .ARM64_RELOC_POINTER_TO_GOT => { | 899 | fn relaxTlv(code: []u8) error{RelaxFail}!void { |
| 933 | relocs_log.debug(" | target_addr = 0x{x}", .{target_addr}); | 900 | const old_inst = disassemble(code) orelse return error.RelaxFail; |
| 934 | const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse | 901 | switch (old_inst.encoding.mnemonic) { |
| 935 | return error.Overflow; | 902 | .mov => { |
| 936 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @bitCast(result)), .little); | 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; | ||
| 937 | }, | 906 | }, |
| 907 | else => return error.RelaxFail, | ||
| 908 | } | ||
| 909 | } | ||
| 938 | 910 | ||
| 939 | .ARM64_RELOC_UNSIGNED => { | 911 | fn disassemble(code: []const u8) ?Instruction { |
| 940 | var ptr_addend = if (rel.r_length == 3) | 912 | var disas = Disassembler.init(code); |
| 941 | mem.readInt(i64, atom_code[rel_offset..][0..8], .little) | 913 | const inst = disas.next() catch return null; |
| 942 | else | 914 | return inst; |
| 943 | mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | 915 | } |
| 944 | 916 | ||
| 945 | if (rel.r_extern == 0) { | 917 | fn encode(insts: []const Instruction, code: []u8) !void { |
| 946 | const base_addr = if (target.sym_index >= object.source_address_lookup.len) | 918 | var stream = std.io.fixedBufferStream(code); |
| 947 | @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr)) | 919 | const writer = stream.writer(); |
| 948 | else | 920 | for (insts) |inst| { |
| 949 | object.source_address_lookup[target.sym_index]; | 921 | try inst.encode(writer, .{}); |
| 950 | ptr_addend -= base_addr; | 922 | } |
| 951 | } | 923 | } |
| 952 | 924 | ||
| 953 | const result = blk: { | 925 | const bits = @import("../../arch/x86_64/bits.zig"); |
| 954 | if (subtractor) |sub| { | 926 | const encoder = @import("../../arch/x86_64/encoder.zig"); |
| 955 | const sym = macho_file.getSymbol(sub); | 927 | const Disassembler = @import("../../arch/x86_64/Disassembler.zig"); |
| 956 | break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend; | 928 | const Immediate = bits.Immediate; |
| 957 | } else { | 929 | const Instruction = encoder.Instruction; |
| 958 | break :blk @as(i64, @intCast(target_addr)) + ptr_addend; | 930 | }; |
| 959 | } | ||
| 960 | }; | ||
| 961 | relocs_log.debug(" | target_addr = 0x{x}", .{result}); | ||
| 962 | 931 | ||
| 963 | if (rel.r_length == 3) { | 932 | pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 { |
| 964 | mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little); | 933 | const relocs = self.getRelocs(macho_file); |
| 965 | } else { | 934 | switch (macho_file.getTarget().cpu.arch) { |
| 966 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little); | 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 => {}, | ||
| 967 | } | 944 | } |
| 968 | 945 | } | |
| 969 | subtractor = null; | 946 | return nreloc; |
| 970 | }, | 947 | }, |
| 971 | 948 | .x86_64 => return @intCast(relocs.len), | |
| 972 | .ARM64_RELOC_ADDEND => unreachable, | 949 | else => unreachable, |
| 973 | .ARM64_RELOC_SUBTRACTOR => unreachable, | ||
| 974 | } | ||
| 975 | } | 950 | } |
| 976 | } | 951 | } |
| 977 | 952 | ||
| 978 | fn resolveRelocsX86( | 953 | pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void { |
| 979 | macho_file: *MachO, | 954 | const tracy = trace(@src()); |
| 980 | atom_index: Index, | 955 | defer tracy.end(); |
| 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); | ||
| 996 | 956 | ||
| 997 | relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{ | 957 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 998 | @tagName(rel_type), | 958 | const relocs = self.getRelocs(macho_file); |
| 999 | rel.r_address, | 959 | const sect = macho_file.sections.items(.header)[self.out_n_sect]; |
| 1000 | rel.r_symbolnum, | 960 | var stream = std.io.fixedBufferStream(code); |
| 1001 | atom.getFile(), | ||
| 1002 | }); | ||
| 1003 | 961 | ||
| 1004 | subtractor = parseRelocTarget(macho_file, .{ | 962 | for (relocs) |rel| { |
| 1005 | .object_id = atom.getFile().?, | 963 | const rel_offset = rel.offset - self.off; |
| 1006 | .rel = rel, | 964 | const r_address: i32 = math.cast(i32, self.value + rel_offset - sect.addr) orelse return error.Overflow; |
| 1007 | .code = atom_code, | 965 | const r_symbolnum = r_symbolnum: { |
| 1008 | .base_addr = context.base_addr, | 966 | const r_symbolnum: u32 = switch (rel.tag) { |
| 1009 | .base_offset = context.base_offset, | 967 | .local => rel.getTargetAtom(macho_file).out_n_sect + 1, |
| 1010 | }); | 968 | .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?, |
| 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; | ||
| 1047 | }; | 969 | }; |
| 1048 | break :blk getRelocTargetAddress(macho_file, target, is_tlv); | 970 | break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow; |
| 1049 | }; | 971 | }; |
| 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 | } | ||
| 1050 | 978 | ||
| 1051 | relocs_log.debug(" | source_addr = 0x{x}", .{source_addr}); | 979 | try stream.seekTo(rel_offset); |
| 1052 | 980 | ||
| 1053 | switch (rel_type) { | 981 | switch (cpu_arch) { |
| 1054 | .X86_64_RELOC_BRANCH => { | 982 | .aarch64 => { |
| 1055 | const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | 983 | if (rel.type == .unsigned) switch (rel.meta.length) { |
| 1056 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | 984 | 0, 1 => unreachable, |
| 1057 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 985 | 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), |
| 1058 | const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0); | 986 | 3 => try stream.writer().writeInt(i64, addend, .little), |
| 1059 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | 987 | } else if (addend > 0) { |
| 1060 | }, | 988 | buffer.appendAssumeCapacity(.{ |
| 1061 | 989 | .r_address = r_address, | |
| 1062 | .X86_64_RELOC_GOT, | 990 | .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow), |
| 1063 | .X86_64_RELOC_GOT_LOAD, | 991 | .r_pcrel = 0, |
| 1064 | => { | 992 | .r_length = 2, |
| 1065 | const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little); | 993 | .r_extern = 0, |
| 1066 | const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend)); | 994 | .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), |
| 1067 | relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr}); | 995 | }); |
| 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; | ||
| 1081 | } | 996 | } |
| 1082 | 997 | ||
| 1083 | mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little); | 998 | const r_type: macho.reloc_type_arm64 = switch (rel.type) { |
| 1084 | }, | 999 | .page => .ARM64_RELOC_PAGE21, |
| 1085 | 1000 | .pageoff => .ARM64_RELOC_PAGEOFF12, | |
| 1086 | .X86_64_RELOC_SIGNED, | 1001 | .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21, |
| 1087 | .X86_64_RELOC_SIGNED_1, | 1002 | .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12, |
| 1088 | .X86_64_RELOC_SIGNED_2, | 1003 | .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21, |
| 1089 | .X86_64_RELOC_SIGNED_4, | 1004 | .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, |
| 1090 | => { | 1005 | .branch => .ARM64_RELOC_BRANCH26, |
| 1091 | const correction: u3 = switch (rel_type) { | 1006 | .got => .ARM64_RELOC_POINTER_TO_GOT, |
| 1092 | .X86_64_RELOC_SIGNED => 0, | 1007 | .subtractor => .ARM64_RELOC_SUBTRACTOR, |
| 1093 | .X86_64_RELOC_SIGNED_1 => 1, | 1008 | .unsigned => .ARM64_RELOC_UNSIGNED, |
| 1094 | .X86_64_RELOC_SIGNED_2 => 2, | 1009 | |
| 1095 | .X86_64_RELOC_SIGNED_4 => 4, | 1010 | .zig_got_load, |
| 1096 | else => unreachable, | 1011 | .signed, |
| 1012 | .signed1, | ||
| 1013 | .signed2, | ||
| 1014 | .signed4, | ||
| 1015 | .got_load, | ||
| 1016 | .tlv, | ||
| 1017 | => unreachable, | ||
| 1097 | }; | 1018 | }; |
| 1098 | var addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little) + correction; | 1019 | buffer.appendAssumeCapacity(.{ |
| 1099 | 1020 | .r_address = r_address, | |
| 1100 | if (rel.r_extern == 0) { | 1021 | .r_symbolnum = r_symbolnum, |
| 1101 | const base_addr = if (target.sym_index >= object.source_address_lookup.len) | 1022 | .r_pcrel = @intFromBool(rel.meta.pcrel), |
| 1102 | @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr)) | 1023 | .r_extern = @intFromBool(r_extern), |
| 1103 | else | 1024 | .r_length = rel.meta.length, |
| 1104 | object.source_address_lookup[target.sym_index]; | 1025 | .r_type = @intFromEnum(r_type), |
| 1105 | addend += @as(i32, @intCast(@as(i64, @intCast(context.base_addr)) + rel.r_address + 4 - | 1026 | }); |
| 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); | ||
| 1115 | }, | 1027 | }, |
| 1116 | 1028 | .x86_64 => { | |
| 1117 | .X86_64_RELOC_UNSIGNED => { | 1029 | if (rel.meta.pcrel) { |
| 1118 | var addend = if (rel.r_length == 3) | 1030 | if (rel.tag == .local) { |
| 1119 | mem.readInt(i64, atom_code[rel_offset..][0..8], .little) | 1031 | addend -= @as(i64, @intCast(self.value + rel_offset)); |
| 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; | ||
| 1135 | } else { | 1032 | } else { |
| 1136 | break :blk @as(i64, @intCast(target_addr)) + addend; | 1033 | addend += 4; |
| 1137 | } | 1034 | } |
| 1138 | }; | 1035 | } |
| 1139 | relocs_log.debug(" | target_addr = 0x{x}", .{result}); | 1036 | switch (rel.meta.length) { |
| 1140 | 1037 | 0, 1 => unreachable, | |
| 1141 | if (rel.r_length == 3) { | 1038 | 2 => try stream.writer().writeInt(i32, @truncate(addend), .little), |
| 1142 | mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little); | 1039 | 3 => try stream.writer().writeInt(i64, addend, .little), |
| 1143 | } else { | ||
| 1144 | mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little); | ||
| 1145 | } | 1040 | } |
| 1146 | 1041 | ||
| 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 | }); | ||
| 1148 | }, | 1071 | }, |
| 1149 | 1072 | else => unreachable, | |
| 1150 | .X86_64_RELOC_SUBTRACTOR => unreachable, | ||
| 1151 | } | 1073 | } |
| 1152 | } | 1074 | } |
| 1153 | } | 1075 | } |
| 1154 | 1076 | ||
| 1155 | pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 { | 1077 | pub fn format( |
| 1156 | const atom = macho_file.getAtom(atom_index); | 1078 | atom: Atom, |
| 1157 | assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code. | 1079 | comptime unused_fmt_string: []const u8, |
| 1158 | const object = macho_file.objects.items[atom.getFile().?]; | 1080 | options: std.fmt.FormatOptions, |
| 1159 | const source_sym = object.getSourceSymbol(atom.sym_index) orelse { | 1081 | writer: anytype, |
| 1160 | // If there was no matching symbol present in the source symtab, this means | 1082 | ) !void { |
| 1161 | // we are dealing with either an entire section, or part of it, but also | 1083 | _ = atom; |
| 1162 | // starting at the beginning. | 1084 | _ = unused_fmt_string; |
| 1163 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | 1085 | _ = options; |
| 1164 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | 1086 | _ = writer; |
| 1165 | const source_sect = object.getSourceSection(sect_id); | 1087 | @compileError("do not format Atom directly"); |
| 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]; | ||
| 1177 | } | 1088 | } |
| 1178 | 1089 | ||
| 1179 | pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info { | 1090 | pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 1180 | const atom = macho_file.getAtom(atom_index); | 1091 | return .{ .data = .{ |
| 1181 | assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs. | 1092 | .atom = atom, |
| 1182 | const object = macho_file.objects.items[atom.getFile().?]; | 1093 | .macho_file = macho_file, |
| 1183 | const cache = object.relocs_lookup[atom.sym_index]; | 1094 | } }; |
| 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]; | ||
| 1199 | } | 1095 | } |
| 1200 | 1096 | ||
| 1201 | pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool { | 1097 | const FormatContext = struct { |
| 1202 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 1098 | atom: Atom, |
| 1203 | switch (target.cpu.arch) { | 1099 | macho_file: *MachO, |
| 1204 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | 1100 | }; |
| 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 | } | ||
| 1220 | 1101 | ||
| 1221 | pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool { | 1102 | fn format2( |
| 1222 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 1103 | ctx: FormatContext, |
| 1223 | switch (target.cpu.arch) { | 1104 | comptime unused_fmt_string: []const u8, |
| 1224 | .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) { | 1105 | options: std.fmt.FormatOptions, |
| 1225 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | 1106 | writer: anytype, |
| 1226 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | 1107 | ) !void { |
| 1227 | => return true, | 1108 | _ = options; |
| 1228 | else => return false, | 1109 | _ = unused_fmt_string; |
| 1229 | }, | 1110 | const atom = ctx.atom; |
| 1230 | .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) { | 1111 | const macho_file = ctx.macho_file; |
| 1231 | .X86_64_RELOC_TLV => return true, | 1112 | try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{ |
| 1232 | else => return false, | 1113 | atom.atom_index, atom.getName(macho_file), atom.value, |
| 1233 | }, | 1114 | atom.out_n_sect, atom.alignment, atom.size, |
| 1234 | else => unreachable, | 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(" }"); | ||
| 1235 | } | 1127 | } |
| 1236 | } | 1128 | } |
| 1237 | 1129 | ||
| 1238 | pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool { | 1130 | pub const Index = u32; |
| 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 | } | ||
| 1252 | 1131 | ||
| 1253 | const Atom = @This(); | 1132 | pub 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 | |||
| 1140 | pub const Loc = struct { | ||
| 1141 | pos: u32 = 0, | ||
| 1142 | len: u32 = 0, | ||
| 1143 | }; | ||
| 1144 | |||
| 1145 | pub const Alignment = @import("../../InternPool.zig").Alignment; | ||
| 1254 | 1146 | ||
| 1255 | const std = @import("std"); | ||
| 1256 | const build_options = @import("build_options"); | ||
| 1257 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | 1147 | const aarch64 = @import("../../arch/aarch64/bits.zig"); |
| 1258 | const assert = std.debug.assert; | 1148 | const assert = std.debug.assert; |
| 1259 | const log = std.log.scoped(.link); | 1149 | const bind = @import("dyld_info/bind.zig"); |
| 1260 | const relocs_log = std.log.scoped(.link_relocs); | ||
| 1261 | const macho = std.macho; | 1150 | const macho = std.macho; |
| 1262 | const math = std.math; | 1151 | const math = std.math; |
| 1263 | const mem = std.mem; | 1152 | const mem = std.mem; |
| 1264 | const meta = std.meta; | 1153 | const log = std.log.scoped(.link); |
| 1154 | const relocs_log = std.log.scoped(.link_relocs); | ||
| 1155 | const std = @import("std"); | ||
| 1265 | const trace = @import("../../tracy.zig").trace; | 1156 | const trace = @import("../../tracy.zig").trace; |
| 1266 | 1157 | ||
| 1267 | const Allocator = mem.Allocator; | 1158 | const Allocator = mem.Allocator; |
| 1268 | const Arch = std.Target.Cpu.Arch; | 1159 | const Atom = @This(); |
| 1160 | const File = @import("file.zig").File; | ||
| 1269 | const MachO = @import("../MachO.zig"); | 1161 | const MachO = @import("../MachO.zig"); |
| 1270 | pub const Relocation = @import("Relocation.zig"); | 1162 | const Object = @import("Object.zig"); |
| 1271 | const SymbolWithLoc = MachO.SymbolWithLoc; | 1163 | const Relocation = @import("Relocation.zig"); |
| 1164 | const Symbol = @import("Symbol.zig"); | ||
| 1165 | const Thunk = @import("thunks.zig").Thunk; | ||
| 1166 | const UnwindInfo = @import("UnwindInfo.zig"); |
src/link/MachO/CodeSignature.zig+183-184| ... | @@ -1,175 +1,16 @@ | ... | @@ -1,175 +1,16 @@ |
| 1 | page_size: u16, | 1 | const CodeSignature = @This(); |
| 2 | code_directory: CodeDirectory, | ||
| 3 | requirements: ?Requirements = null, | ||
| 4 | entitlements: ?Entitlements = null, | ||
| 5 | signature: ?Signature = null, | ||
| 6 | |||
| 7 | pub fn init(page_size: u16) CodeSignature { | ||
| 8 | return .{ | ||
| 9 | .page_size = page_size, | ||
| 10 | .code_directory = CodeDirectory.init(page_size), | ||
| 11 | }; | ||
| 12 | } | ||
| 13 | |||
| 14 | pub 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 | |||
| 27 | pub 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 | |||
| 34 | pub 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 | |||
| 42 | pub 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 | |||
| 134 | pub 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 | |||
| 148 | pub 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 | } | ||
| 168 | 2 | ||
| 169 | pub fn clear(self: *CodeSignature, allocator: Allocator) void { | 3 | const std = @import("std"); |
| 170 | self.code_directory.deinit(allocator); | 4 | const assert = std.debug.assert; |
| 171 | self.code_directory = CodeDirectory.init(self.page_size); | 5 | const fs = std.fs; |
| 172 | } | 6 | const log = std.log.scoped(.link); |
| 7 | const macho = std.macho; | ||
| 8 | const mem = std.mem; | ||
| 9 | const testing = std.testing; | ||
| 10 | const Allocator = mem.Allocator; | ||
| 11 | const Hasher = @import("hasher.zig").ParallelHasher; | ||
| 12 | const MachO = @import("../MachO.zig"); | ||
| 13 | const Sha256 = std.crypto.hash.sha2.Sha256; | ||
| 173 | 14 | ||
| 174 | const hash_size = Sha256.digest_length; | 15 | const hash_size = Sha256.digest_length; |
| 175 | 16 | ||
| ... | @@ -257,7 +98,7 @@ const CodeDirectory = struct { | ... | @@ -257,7 +98,7 @@ const CodeDirectory = struct { |
| 257 | fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { | 98 | fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { |
| 258 | assert(index > 0); | 99 | assert(index > 0); |
| 259 | self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); | 100 | self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); |
| 260 | self.special_slots[index - 1] = hash; | 101 | @memcpy(&self.special_slots[index - 1], &hash); |
| 261 | } | 102 | } |
| 262 | 103 | ||
| 263 | fn slotType(self: CodeDirectory) u32 { | 104 | fn slotType(self: CodeDirectory) u32 { |
| ... | @@ -376,17 +217,175 @@ const Signature = struct { | ... | @@ -376,17 +217,175 @@ const Signature = struct { |
| 376 | } | 217 | } |
| 377 | }; | 218 | }; |
| 378 | 219 | ||
| 379 | const CodeSignature = @This(); | 220 | page_size: u16, |
| 221 | code_directory: CodeDirectory, | ||
| 222 | requirements: ?Requirements = null, | ||
| 223 | entitlements: ?Entitlements = null, | ||
| 224 | signature: ?Signature = null, | ||
| 380 | 225 | ||
| 381 | const std = @import("std"); | 226 | pub fn init(page_size: u16) CodeSignature { |
| 382 | const assert = std.debug.assert; | 227 | return .{ |
| 383 | const fs = std.fs; | 228 | .page_size = page_size, |
| 384 | const log = std.log.scoped(.link); | 229 | .code_directory = CodeDirectory.init(page_size), |
| 385 | const macho = std.macho; | 230 | }; |
| 386 | const mem = std.mem; | 231 | } |
| 387 | const testing = std.testing; | ||
| 388 | 232 | ||
| 389 | const Allocator = mem.Allocator; | 233 | pub fn deinit(self: *CodeSignature, allocator: Allocator) void { |
| 390 | const Compilation = @import("../../Compilation.zig"); | 234 | self.code_directory.deinit(allocator); |
| 391 | const Hasher = @import("hasher.zig").ParallelHasher; | 235 | if (self.requirements) |*req| { |
| 392 | const Sha256 = std.crypto.hash.sha2.Sha256; | 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 | |||
| 246 | pub 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 | |||
| 253 | pub 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 | |||
| 261 | pub 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 | |||
| 353 | pub 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 | |||
| 367 | pub 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 | |||
| 388 | pub 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, | ... | @@ -2,377 +2,175 @@ debug_info: []const u8, |
| 2 | debug_abbrev: []const u8, | 2 | debug_abbrev: []const u8, |
| 3 | debug_str: []const u8, | 3 | debug_str: []const u8, |
| 4 | 4 | ||
| 5 | pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator { | 5 | /// Abbreviation table indexed by offset in the .debug_abbrev bytestream |
| 6 | return .{ .ctx = self }; | 6 | abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{}, |
| 7 | /// List of compile units as they appear in the .debug_info bytestream | ||
| 8 | compile_units: std.ArrayListUnmanaged(CompileUnit) = .{}, | ||
| 9 | |||
| 10 | pub fn init(dw: *DwarfInfo, allocator: Allocator) !void { | ||
| 11 | try dw.parseAbbrevTables(allocator); | ||
| 12 | try dw.parseCompileUnits(allocator); | ||
| 7 | } | 13 | } |
| 8 | 14 | ||
| 9 | const CompileUnitIterator = struct { | 15 | pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void { |
| 10 | ctx: DwarfInfo, | 16 | dw.abbrev_tables.deinit(allocator); |
| 11 | pos: usize = 0, | 17 | for (dw.compile_units.items) |*cu| { |
| 12 | 18 | cu.deinit(allocator); | |
| 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; | ||
| 32 | } | 19 | } |
| 33 | }; | 20 | dw.compile_units.deinit(allocator); |
| 34 | 21 | } | |
| 35 | pub 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; | ||
| 74 | 22 | ||
| 75 | try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? }); | 23 | fn getString(dw: DwarfInfo, off: usize) [:0]const u8 { |
| 76 | }, | 24 | assert(off < dw.debug_str.len); |
| 77 | else => {}, | 25 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.debug_str.ptr + off)), 0); |
| 78 | }; | ||
| 79 | } | 26 | } |
| 80 | 27 | ||
| 81 | pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void { | 28 | fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator) !void { |
| 82 | const data = self.debug_abbrev[off..]; | 29 | const tracy = trace(@src()); |
| 83 | var stream = std.io.fixedBufferStream(data); | 30 | defer tracy.end(); |
| 31 | |||
| 32 | const debug_abbrev = dw.debug_abbrev; | ||
| 33 | var stream = std.io.fixedBufferStream(debug_abbrev); | ||
| 84 | var creader = std.io.countingReader(stream.reader()); | 34 | var creader = std.io.countingReader(stream.reader()); |
| 85 | const reader = creader.reader(); | 35 | const reader = creader.reader(); |
| 86 | 36 | ||
| 87 | while (true) { | 37 | while (true) { |
| 88 | const kind = try leb.readULEB128(u64, reader); | 38 | if (creader.bytes_read >= debug_abbrev.len) break; |
| 89 | 39 | ||
| 90 | if (kind == 0) break; | 40 | try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1); |
| 91 | 41 | const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read)); | |
| 92 | const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | 42 | assert(!table_gop.found_existing); |
| 93 | _ = try leb.readULEB128(u64, reader); // TAG | 43 | const table = table_gop.value_ptr; |
| 94 | _ = try reader.readByte(); // CHILDREN | 44 | table.* = .{}; |
| 95 | 45 | ||
| 96 | while (true) { | 46 | while (true) { |
| 97 | const name = try leb.readULEB128(u64, reader); | 47 | const code = try leb.readULEB128(Code, reader); |
| 98 | const form = try leb.readULEB128(u64, reader); | 48 | if (code == 0) break; |
| 99 | 49 | ||
| 100 | if (name == 0 and form == 0) break; | 50 | try table.decls.ensureUnusedCapacity(allocator, 1); |
| 101 | } | 51 | const decl_gop = table.decls.getOrPutAssumeCapacity(code); |
| 102 | 52 | assert(!decl_gop.found_existing); | |
| 103 | const next_pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow; | 53 | const decl = decl_gop.value_ptr; |
| 104 | 54 | decl.* = .{ | |
| 105 | try lookup.putNoClobber(kind, .{ | 55 | .code = code, |
| 106 | .pos = pos, | 56 | .tag = undefined, |
| 107 | .len = next_pos - pos - 2, | 57 | .children = false, |
| 108 | }); | 58 | }; |
| 109 | } | 59 | decl.tag = try leb.readULEB128(Tag, reader); |
| 110 | } | 60 | decl.children = (try reader.readByte()) > 0; |
| 111 | 61 | ||
| 112 | pub const CompileUnit = struct { | 62 | while (true) { |
| 113 | cuh: Header, | 63 | const at = try leb.readULEB128(At, reader); |
| 114 | debug_info_off: usize, | 64 | const form = try leb.readULEB128(Form, reader); |
| 115 | 65 | if (at == 0 and form == 0) break; | |
| 116 | pub const Header = struct { | 66 | |
| 117 | is_64bit: bool, | 67 | try decl.attrs.ensureUnusedCapacity(allocator, 1); |
| 118 | length: u64, | 68 | const attr_gop = decl.attrs.getOrPutAssumeCapacity(at); |
| 119 | version: u16, | 69 | assert(!attr_gop.found_existing); |
| 120 | debug_abbrev_offset: u64, | 70 | const attr = attr_gop.value_ptr; |
| 121 | address_size: u8, | 71 | attr.* = .{ |
| 122 | 72 | .at = at, | |
| 123 | fn read(reader: anytype) !Header { | 73 | .form = form, |
| 124 | var length: u64 = try reader.readInt(u32, .little); | 74 | }; |
| 125 | |||
| 126 | const is_64bit = length == 0xffffffff; | ||
| 127 | if (is_64bit) { | ||
| 128 | length = try reader.readInt(u64, .little); | ||
| 129 | } | 75 | } |
| 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 | }; | ||
| 145 | } | 76 | } |
| 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 }; | ||
| 154 | } | 77 | } |
| 155 | }; | 78 | } |
| 156 | |||
| 157 | const 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(); | ||
| 169 | 79 | ||
| 170 | const kind = try leb.readULEB128(u64, reader); | 80 | fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator) !void { |
| 171 | self.pos += (math.cast(usize, creader.bytes_read) orelse return error.Overflow); | 81 | const tracy = trace(@src()); |
| 82 | defer tracy.end(); | ||
| 172 | 83 | ||
| 173 | if (kind == 0) { | 84 | const debug_info = dw.debug_info; |
| 174 | return AbbrevEntry.null(); | 85 | var stream = std.io.fixedBufferStream(debug_info); |
| 175 | } | 86 | var creader = std.io.countingReader(stream.reader()); |
| 87 | const reader = creader.reader(); | ||
| 176 | 88 | ||
| 177 | const abbrev_pos = lookup.get(kind) orelse return null; | 89 | while (true) { |
| 178 | const len = try findAbbrevEntrySize( | 90 | if (creader.bytes_read == debug_info.len) break; |
| 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 | }; | ||
| 198 | 91 | ||
| 199 | pub const AbbrevEntry = struct { | 92 | const cu = try dw.compile_units.addOne(allocator); |
| 200 | tag: u64, | 93 | cu.* = .{ |
| 201 | children: u8, | 94 | .header = undefined, |
| 202 | debug_abbrev_off: usize, | 95 | .pos = creader.bytes_read, |
| 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, | ||
| 215 | }; | 96 | }; |
| 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 | |||
| 235 | pub const Attribute = struct { | ||
| 236 | name: u64, | ||
| 237 | form: u64, | ||
| 238 | debug_info_off: usize, | ||
| 239 | debug_info_len: usize, | ||
| 240 | 97 | ||
| 241 | inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 { | 98 | var length: u64 = try reader.readInt(u32, .little); |
| 242 | return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len]; | 99 | const is_64bit = length == 0xffffffff; |
| 243 | } | 100 | if (is_64bit) { |
| 244 | 101 | length = try reader.readInt(u64, .little); | |
| 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, | ||
| 260 | } | 102 | } |
| 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); | ||
| 261 | } | 111 | } |
| 112 | } | ||
| 262 | 113 | ||
| 263 | pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 { | 114 | fn parseDie( |
| 264 | const debug_info = self.getDebugInfo(ctx); | 115 | dw: *DwarfInfo, |
| 265 | var stream = std.io.fixedBufferStream(debug_info); | 116 | allocator: Allocator, |
| 266 | const reader = stream.reader(); | 117 | cu: *CompileUnit, |
| 267 | 118 | table: AbbrevTable, | |
| 268 | return switch (self.form) { | 119 | parent: ?u32, |
| 269 | dwarf.FORM.data1 => debug_info[0], | 120 | creader: anytype, |
| 270 | dwarf.FORM.data2 => mem.readInt(u16, debug_info[0..2], .little), | 121 | ) anyerror!void { |
| 271 | dwarf.FORM.data4 => mem.readInt(u32, debug_info[0..4], .little), | 122 | const tracy = trace(@src()); |
| 272 | dwarf.FORM.data8 => mem.readInt(u64, debug_info[0..8], .little), | 123 | defer tracy.end(); |
| 273 | dwarf.FORM.udata => try leb.readULEB128(u64, reader), | 124 | |
| 274 | dwarf.FORM.sdata => try leb.readILEB128(i64, reader), | 125 | while (creader.bytes_read < cu.nextCompileUnitOffset()) { |
| 275 | else => null, | 126 | const die = try cu.addDie(allocator); |
| 276 | }; | 127 | cu.diePtr(die).* = .{ .code = undefined }; |
| 277 | } | 128 | if (parent) |p| { |
| 278 | 129 | try cu.diePtr(p).children.append(allocator, die); | |
| 279 | pub fn getAddr(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?u64 { | 130 | } else { |
| 280 | if (self.form != dwarf.FORM.addr) return null; | 131 | try cu.children.append(allocator, die); |
| 281 | const debug_info = self.getDebugInfo(ctx); | 132 | } |
| 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 | |||
| 292 | const AttributeIterator = struct { | ||
| 293 | entry: AbbrevEntry, | ||
| 294 | ctx: DwarfInfo, | ||
| 295 | cuh: CompileUnit.Header, | ||
| 296 | debug_abbrev_pos: usize = 0, | ||
| 297 | debug_info_pos: usize = 0, | ||
| 298 | 133 | ||
| 299 | pub fn next(self: *AttributeIterator) !?Attribute { | 134 | const code = try leb.readULEB128(Code, creader.reader()); |
| 300 | const debug_abbrev = self.entry.getDebugAbbrev(self.ctx); | 135 | cu.diePtr(die).code = code; |
| 301 | if (self.debug_abbrev_pos >= debug_abbrev.len) return null; | ||
| 302 | 136 | ||
| 303 | var stream = std.io.fixedBufferStream(debug_abbrev[self.debug_abbrev_pos..]); | 137 | if (code == 0) { |
| 304 | var creader = std.io.countingReader(stream.reader()); | 138 | if (parent == null) continue; |
| 305 | const reader = creader.reader(); | 139 | return; // Close scope |
| 140 | } | ||
| 306 | 141 | ||
| 307 | const name = try leb.readULEB128(u64, reader); | 142 | const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors |
| 308 | const form = try leb.readULEB128(u64, reader); | 143 | const data = dw.debug_info; |
| 309 | 144 | try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len); | |
| 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 | }; | ||
| 324 | 145 | ||
| 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 | } | ||
| 326 | 152 | ||
| 327 | return attr; | 153 | if (decl.children) { |
| 154 | // Open scope | ||
| 155 | try dw.parseDie(allocator, cu, table, die, creader); | ||
| 156 | } | ||
| 328 | } | 157 | } |
| 329 | }; | ||
| 330 | |||
| 331 | fn 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 | }; | ||
| 361 | } | 158 | } |
| 362 | 159 | ||
| 363 | fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize { | 160 | fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void { |
| 364 | const debug_info = self.debug_info[di_off..]; | 161 | const tracy = trace(@src()); |
| 365 | var stream = std.io.fixedBufferStream(debug_info); | 162 | defer tracy.end(); |
| 366 | var creader = std.io.countingReader(stream.reader()); | ||
| 367 | const reader = creader.reader(); | ||
| 368 | 163 | ||
| 164 | const reader = creader.reader(); | ||
| 369 | switch (form) { | 165 | switch (form) { |
| 370 | dwarf.FORM.strp, | 166 | dwarf.FORM.strp, |
| 371 | dwarf.FORM.sec_offset, | 167 | dwarf.FORM.sec_offset, |
| 372 | dwarf.FORM.ref_addr, | 168 | dwarf.FORM.ref_addr, |
| 373 | => return if (cuh.is_64bit) @sizeOf(u64) else @sizeOf(u32), | 169 | => { |
| 170 | _ = try readOffset(cu.header.format, reader); | ||
| 171 | }, | ||
| 374 | 172 | ||
| 375 | dwarf.FORM.addr => return cuh.address_size, | 173 | dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}), |
| 376 | 174 | ||
| 377 | dwarf.FORM.block1, | 175 | dwarf.FORM.block1, |
| 378 | dwarf.FORM.block2, | 176 | dwarf.FORM.block2, |
| ... | @@ -390,115 +188,284 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head | ... | @@ -390,115 +188,284 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head |
| 390 | while (i < len) : (i += 1) { | 188 | while (i < len) : (i += 1) { |
| 391 | _ = try reader.readByte(); | 189 | _ = try reader.readByte(); |
| 392 | } | 190 | } |
| 393 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 394 | }, | 191 | }, |
| 395 | 192 | ||
| 396 | dwarf.FORM.exprloc => { | 193 | dwarf.FORM.exprloc => { |
| 397 | const expr_len = try leb.readULEB128(u64, reader); | 194 | const len = try leb.readULEB128(u64, reader); |
| 398 | var i: u64 = 0; | 195 | var i: u64 = 0; |
| 399 | while (i < expr_len) : (i += 1) { | 196 | while (i < len) : (i += 1) { |
| 400 | _ = try reader.readByte(); | 197 | _ = try reader.readByte(); |
| 401 | } | 198 | } |
| 402 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 403 | }, | 199 | }, |
| 404 | dwarf.FORM.flag_present => return 0, | 200 | dwarf.FORM.flag_present => {}, |
| 405 | 201 | ||
| 406 | dwarf.FORM.data1, | 202 | dwarf.FORM.data1, |
| 407 | dwarf.FORM.ref1, | 203 | dwarf.FORM.ref1, |
| 408 | dwarf.FORM.flag, | 204 | dwarf.FORM.flag, |
| 409 | => return @sizeOf(u8), | 205 | => try reader.skipBytes(1, .{}), |
| 410 | 206 | ||
| 411 | dwarf.FORM.data2, | 207 | dwarf.FORM.data2, |
| 412 | dwarf.FORM.ref2, | 208 | dwarf.FORM.ref2, |
| 413 | => return @sizeOf(u16), | 209 | => try reader.skipBytes(2, .{}), |
| 414 | 210 | ||
| 415 | dwarf.FORM.data4, | 211 | dwarf.FORM.data4, |
| 416 | dwarf.FORM.ref4, | 212 | dwarf.FORM.ref4, |
| 417 | => return @sizeOf(u32), | 213 | => try reader.skipBytes(4, .{}), |
| 418 | 214 | ||
| 419 | dwarf.FORM.data8, | 215 | dwarf.FORM.data8, |
| 420 | dwarf.FORM.ref8, | 216 | dwarf.FORM.ref8, |
| 421 | dwarf.FORM.ref_sig8, | 217 | dwarf.FORM.ref_sig8, |
| 422 | => return @sizeOf(u64), | 218 | => try reader.skipBytes(8, .{}), |
| 423 | 219 | ||
| 424 | dwarf.FORM.udata, | 220 | dwarf.FORM.udata, |
| 425 | dwarf.FORM.ref_udata, | 221 | dwarf.FORM.ref_udata, |
| 426 | => { | 222 | => { |
| 427 | _ = try leb.readULEB128(u64, reader); | 223 | _ = try leb.readULEB128(u64, reader); |
| 428 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 429 | }, | 224 | }, |
| 430 | 225 | ||
| 431 | dwarf.FORM.sdata => { | 226 | dwarf.FORM.sdata => { |
| 432 | _ = try leb.readILEB128(i64, reader); | 227 | _ = try leb.readILEB128(i64, reader); |
| 433 | return math.cast(usize, creader.bytes_read) orelse error.Overflow; | ||
| 434 | }, | 228 | }, |
| 435 | 229 | ||
| 436 | dwarf.FORM.string => { | 230 | dwarf.FORM.string => { |
| 437 | var count: usize = 0; | ||
| 438 | while (true) { | 231 | while (true) { |
| 439 | const byte = try reader.readByte(); | 232 | const byte = try reader.readByte(); |
| 440 | count += 1; | ||
| 441 | if (byte == 0x0) break; | 233 | if (byte == 0x0) break; |
| 442 | } | 234 | } |
| 443 | return count; | ||
| 444 | }, | 235 | }, |
| 445 | 236 | ||
| 446 | else => { | 237 | else => { |
| 447 | // TODO figure out how to handle this | 238 | // TODO better errors |
| 448 | log.debug("unhandled DW_FORM_* value with identifier {x}", .{form}); | 239 | log.err("unhandled DW_FORM_* value with identifier {x}", .{form}); |
| 449 | return error.UnhandledDwFormValue; | 240 | return error.UnhandledDwFormValue; |
| 450 | }, | 241 | }, |
| 451 | } | 242 | } |
| 452 | } | 243 | } |
| 453 | 244 | ||
| 454 | fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize { | 245 | fn readOffset(format: Format, reader: anytype) !u64 { |
| 455 | const debug_abbrev = self.debug_abbrev[da_off..][0..da_len]; | 246 | return switch (format) { |
| 456 | var stream = std.io.fixedBufferStream(debug_abbrev); | 247 | .dwarf32 => try reader.readInt(u32, .little), |
| 457 | var creader = std.io.countingReader(stream.reader()); | 248 | .dwarf64 => try reader.readInt(u64, .little), |
| 458 | const reader = creader.reader(); | 249 | }; |
| 250 | } | ||
| 459 | 251 | ||
| 460 | const tag = try leb.readULEB128(u64, reader); | 252 | pub const AbbrevTable = struct { |
| 461 | switch (tag) { | 253 | /// Table of abbreviation declarations indexed by their assigned code value |
| 462 | std.dwarf.TAG.const_type, | 254 | decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{}, |
| 463 | std.dwarf.TAG.packed_type, | 255 | |
| 464 | std.dwarf.TAG.pointer_type, | 256 | pub fn deinit(table: *AbbrevTable, gpa: Allocator) void { |
| 465 | std.dwarf.TAG.reference_type, | 257 | for (table.decls.values()) |*decl| { |
| 466 | std.dwarf.TAG.restrict_type, | 258 | decl.deinit(gpa); |
| 467 | std.dwarf.TAG.rvalue_reference_type, | 259 | } |
| 468 | std.dwarf.TAG.shared_type, | 260 | table.decls.deinit(gpa); |
| 469 | std.dwarf.TAG.volatile_type, | ||
| 470 | => if (creader.bytes_read != da_len) { | ||
| 471 | _ = try reader.readByte(); | ||
| 472 | }, | ||
| 473 | else => _ = try reader.readByte(), | ||
| 474 | } | 261 | } |
| 262 | }; | ||
| 263 | |||
| 264 | pub 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) = .{}, | ||
| 475 | 271 | ||
| 476 | var len: usize = 0; | 272 | pub fn deinit(decl: *Decl, gpa: Allocator) void { |
| 477 | while (creader.bytes_read < debug_abbrev.len) { | 273 | decl.attrs.deinit(gpa); |
| 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; | ||
| 482 | } | 274 | } |
| 275 | }; | ||
| 483 | 276 | ||
| 484 | return len; | 277 | pub const Attr = struct { |
| 485 | } | 278 | at: At, |
| 279 | form: Form, | ||
| 280 | }; | ||
| 486 | 281 | ||
| 487 | fn getString(self: DwarfInfo, off: u64) []const u8 { | 282 | pub const At = u64; |
| 488 | assert(off < self.debug_str.len); | 283 | pub const Code = u64; |
| 489 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0); | 284 | pub const Form = u64; |
| 490 | } | 285 | pub const Tag = u64; |
| 286 | |||
| 287 | pub const CompileUnitHeader = struct { | ||
| 288 | format: Format, | ||
| 289 | length: u64, | ||
| 290 | version: u16, | ||
| 291 | debug_abbrev_offset: u64, | ||
| 292 | address_size: u8, | ||
| 293 | }; | ||
| 491 | 294 | ||
| 492 | const DwarfInfo = @This(); | 295 | pub 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 | |||
| 341 | pub 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 | |||
| 363 | pub 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 | |||
| 456 | pub const Format = enum { | ||
| 457 | dwarf32, | ||
| 458 | dwarf64, | ||
| 459 | }; | ||
| 493 | 460 | ||
| 494 | const std = @import("std"); | ||
| 495 | const assert = std.debug.assert; | 461 | const assert = std.debug.assert; |
| 496 | const dwarf = std.dwarf; | 462 | const dwarf = std.dwarf; |
| 497 | const leb = std.leb; | 463 | const leb = std.leb; |
| 498 | const log = std.log.scoped(.macho); | 464 | const log = std.log.scoped(.link); |
| 499 | const math = std.math; | ||
| 500 | const mem = std.mem; | 465 | const mem = std.mem; |
| 466 | const std = @import("std"); | ||
| 467 | const trace = @import("../../tracy.zig").trace; | ||
| 501 | 468 | ||
| 502 | const Allocator = mem.Allocator; | 469 | const Allocator = mem.Allocator; |
| 503 | pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize }); | 470 | const DwarfInfo = @This(); |
| 504 | pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 }); | 471 | const MachO = @import("../MachO.zig"); |
src/link/MachO/Dylib.zig+633-319| ... | @@ -1,340 +1,252 @@ | ... | @@ -1,340 +1,252 @@ |
| 1 | path: []const u8, | 1 | path: []const u8, |
| 2 | id: ?Id = null, | 2 | data: []const u8, |
| 3 | weak: bool = false, | 3 | index: File.Index, |
| 4 | /// Header is only set if Dylib is parsed directly from a binary and not a stub file. | ||
| 5 | header: ?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. | ||
| 17 | symbols: std.StringArrayHashMapUnmanaged(bool) = .{}, | ||
| 18 | |||
| 19 | pub 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 | } | ||
| 33 | 4 | ||
| 34 | pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id { | 5 | header: ?macho.mach_header_64 = null, |
| 35 | return Id{ | 6 | exports: std.MultiArrayList(Export) = .{}, |
| 36 | .name = try allocator.dupe(u8, name), | 7 | strtab: std.ArrayListUnmanaged(u8) = .{}, |
| 37 | .timestamp = lc.dylib.timestamp, | 8 | id: ?Id = null, |
| 38 | .current_version = lc.dylib.current_version, | 9 | ordinal: u16 = 0, |
| 39 | .compatibility_version = lc.dylib.compatibility_version, | 10 | |
| 40 | }; | 11 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, |
| 41 | } | 12 | dependents: std.ArrayListUnmanaged(Id) = .{}, |
| 42 | 13 | rpaths: std.StringArrayHashMapUnmanaged(void) = .{}, | |
| 43 | pub fn deinit(id: Id, allocator: Allocator) void { | 14 | umbrella: File.Index = 0, |
| 44 | allocator.free(id.name); | 15 | platform: ?MachO.Platform = null, |
| 45 | } | 16 | |
| 46 | 17 | needed: bool, | |
| 47 | pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; | 18 | weak: bool, |
| 48 | 19 | reexport: bool, | |
| 49 | pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { | 20 | explicit: bool, |
| 50 | id.current_version = try parseVersion(version); | 21 | hoisted: bool = true, |
| 51 | } | 22 | referenced: bool = false, |
| 52 | 23 | ||
| 53 | pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void { | 24 | output_symtab_ctx: MachO.SymtabCtx = .{}, |
| 54 | id.compatibility_version = try parseVersion(version); | 25 | |
| 55 | } | 26 | pub fn isDylib(path: []const u8, fat_arch: ?fat.Arch) !bool { |
| 56 | 27 | const file = try std.fs.cwd().openFile(path, .{}); | |
| 57 | fn parseVersion(version: anytype) ParseError!u32 { | 28 | defer file.close(); |
| 58 | const string = blk: { | 29 | if (fat_arch) |arch| { |
| 59 | switch (version) { | 30 | try file.seekTo(arch.offset); |
| 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; | ||
| 99 | } | 31 | } |
| 100 | }; | 32 | const header = file.reader().readStruct(macho.mach_header_64) catch return false; |
| 101 | 33 | return header.filetype == macho.MH_DYLIB; | |
| 102 | pub 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; | ||
| 107 | } | 34 | } |
| 108 | 35 | ||
| 109 | pub fn deinit(self: *Dylib, allocator: Allocator) void { | 36 | pub fn deinit(self: *Dylib, allocator: Allocator) void { |
| 37 | allocator.free(self.data); | ||
| 110 | allocator.free(self.path); | 38 | allocator.free(self.path); |
| 111 | for (self.symbols.keys()) |key| { | 39 | self.exports.deinit(allocator); |
| 112 | allocator.free(key); | 40 | self.strtab.deinit(allocator); |
| 113 | } | 41 | if (self.id) |*id| id.deinit(allocator); |
| 114 | self.symbols.deinit(allocator); | 42 | self.symbols.deinit(allocator); |
| 115 | if (self.id) |*id| { | 43 | for (self.dependents.items) |*id| { |
| 116 | id.deinit(allocator); | 44 | id.deinit(allocator); |
| 117 | } | 45 | } |
| 46 | self.dependents.deinit(allocator); | ||
| 47 | self.rpaths.deinit(allocator); | ||
| 118 | } | 48 | } |
| 119 | 49 | ||
| 120 | pub fn parseFromBinary( | 50 | pub fn parse(self: *Dylib, macho_file: *MachO) !void { |
| 121 | self: *Dylib, | 51 | const tracy = trace(@src()); |
| 122 | allocator: Allocator, | 52 | defer tracy.end(); |
| 123 | dylib_id: u16, | 53 | |
| 124 | dependent_libs: anytype, | 54 | const gpa = macho_file.base.comp.gpa; |
| 125 | name: []const u8, | 55 | var stream = std.io.fixedBufferStream(self.data); |
| 126 | data: []align(@alignOf(u64)) const u8, | ||
| 127 | ) !void { | ||
| 128 | var stream = std.io.fixedBufferStream(data); | ||
| 129 | const reader = stream.reader(); | 56 | const reader = stream.reader(); |
| 130 | 57 | ||
| 131 | log.debug("parsing shared library '{s}'", .{name}); | 58 | log.debug("parsing dylib from binary", .{}); |
| 132 | 59 | ||
| 133 | self.header = try reader.readStruct(macho.mach_header_64); | 60 | self.header = try reader.readStruct(macho.mach_header_64); |
| 134 | 61 | ||
| 135 | const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; | 62 | const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { |
| 136 | var it = LoadCommandIterator{ | 63 | macho.CPU_TYPE_ARM64 => .aarch64, |
| 137 | .ncmds = self.header.?.ncmds, | 64 | macho.CPU_TYPE_X86_64 => .x86_64, |
| 138 | .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | 65 | else => |x| { |
| 66 | try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x}); | ||
| 67 | return error.InvalidCpuArch; | ||
| 68 | }, | ||
| 139 | }; | 69 | }; |
| 140 | while (it.next()) |cmd| { | 70 | if (macho_file.getTarget().cpu.arch != this_cpu_arch) { |
| 141 | switch (cmd.cmd()) { | 71 | try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)}); |
| 142 | .SYMTAB => { | 72 | return error.InvalidCpuArch; |
| 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 | } | ||
| 180 | } | 73 | } |
| 181 | } | ||
| 182 | 74 | ||
| 183 | /// Returns Platform composed from the first encountered build version type load command: | 75 | const lc_id = self.getLoadCommand(.ID_DYLIB) orelse { |
| 184 | /// either LC_BUILD_VERSION or LC_VERSION_MIN_*. | 76 | try macho_file.reportParseError2(self.index, "missing LC_ID_DYLIB load command", .{}); |
| 185 | pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform { | 77 | return error.MalformedDylib; |
| 78 | }; | ||
| 79 | self.id = try Id.fromLoadCommand(gpa, lc_id.cast(macho.dylib_command).?, lc_id.getDylibPathName()); | ||
| 80 | |||
| 186 | var it = LoadCommandIterator{ | 81 | var it = LoadCommandIterator{ |
| 187 | .ncmds = self.header.?.ncmds, | 82 | .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], |
| 189 | }; | 84 | }; |
| 190 | while (it.next()) |cmd| { | 85 | while (it.next()) |cmd| switch (cmd.cmd()) { |
| 191 | switch (cmd.cmd()) { | 86 | .REEXPORT_DYLIB => if (self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) { |
| 192 | .BUILD_VERSION, | 87 | const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName()); |
| 193 | .VERSION_MIN_MACOSX, | 88 | try self.dependents.append(gpa, id); |
| 194 | .VERSION_MIN_IPHONEOS, | 89 | }, |
| 195 | .VERSION_MIN_TVOS, | 90 | .DYLD_INFO_ONLY => { |
| 196 | .VERSION_MIN_WATCHOS, | 91 | const dyld_cmd = cmd.cast(macho.dyld_info_command).?; |
| 197 | => return Platform.fromLoadCommand(cmd), | 92 | const data = self.data[dyld_cmd.export_off..][0..dyld_cmd.export_size]; |
| 198 | else => {}, | 93 | try self.parseTrie(data, macho_file); |
| 199 | } | 94 | }, |
| 200 | } else return null; | 95 | .DYLD_EXPORTS_TRIE => { |
| 201 | } | 96 | const ld_cmd = cmd.cast(macho.linkedit_data_command).?; |
| 202 | 97 | const data = self.data[ld_cmd.dataoff..][0..ld_cmd.datasize]; | |
| 203 | fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 98 | try self.parseTrie(data, macho_file); |
| 204 | const expanded = &[_][]const u8{ | 99 | }, |
| 205 | try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}), | 100 | .RPATH => { |
| 206 | try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}), | 101 | const path = cmd.getRpathPathName(); |
| 102 | try self.rpaths.put(gpa, path, {}); | ||
| 103 | }, | ||
| 104 | else => {}, | ||
| 207 | }; | 105 | }; |
| 208 | 106 | ||
| 209 | for (expanded) |sym| { | 107 | self.initPlatform(); |
| 210 | if (self.symbols.contains(sym)) continue; | ||
| 211 | try self.symbols.putNoClobber(allocator, sym, false); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | 108 | ||
| 215 | fn addObjCIVarSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 109 | if (self.platform) |platform| { |
| 216 | const expanded = try std.fmt.allocPrint(allocator, "_OBJC_IVAR_$_{s}", .{sym_name}); | 110 | if (!macho_file.platform.eqlTarget(platform)) { |
| 217 | if (self.symbols.contains(expanded)) return; | 111 | try macho_file.reportParseError2(self.index, "invalid platform: {}", .{ |
| 218 | try self.symbols.putNoClobber(allocator, expanded, false); | 112 | platform.fmtTarget(macho_file.getTarget().cpu.arch), |
| 219 | } | 113 | }); |
| 220 | 114 | return error.InvalidTarget; | |
| 221 | fn addObjCEhTypeSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 115 | } |
| 222 | const expanded = try std.fmt.allocPrint(allocator, "_OBJC_EHTYPE_$_{s}", .{sym_name}); | 116 | // TODO: this can cause the CI to fail so I'm commenting this check out so that |
| 223 | if (self.symbols.contains(expanded)) return; | 117 | // I can work out the rest of the changes first |
| 224 | try self.symbols.putNoClobber(allocator, expanded, false); | 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 | } | ||
| 225 | } | 127 | } |
| 226 | 128 | ||
| 227 | fn addSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 129 | const TrieIterator = struct { |
| 228 | if (self.symbols.contains(sym_name)) return; | 130 | data: []const u8, |
| 229 | try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false); | 131 | pos: usize = 0, |
| 230 | } | ||
| 231 | 132 | ||
| 232 | fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void { | 133 | fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) { |
| 233 | if (self.symbols.contains(sym_name)) return; | 134 | return std.io.fixedBufferStream(it.data[it.pos..]); |
| 234 | try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true); | 135 | } |
| 235 | } | ||
| 236 | 136 | ||
| 237 | pub const TargetMatcher = struct { | 137 | fn readULEB128(it: *TrieIterator) !u64 { |
| 238 | allocator: Allocator, | 138 | var stream = it.getStream(); |
| 239 | cpu_arch: std.Target.Cpu.Arch, | 139 | var creader = std.io.countingReader(stream.reader()); |
| 240 | os_tag: std.Target.Os.Tag, | 140 | const reader = creader.reader(); |
| 241 | abi: std.Target.Abi, | 141 | const value = try std.leb.readULEB128(u64, reader); |
| 242 | target_strings: std.ArrayListUnmanaged([]const u8) = .{}, | 142 | it.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow; |
| 143 | return value; | ||
| 144 | } | ||
| 243 | 145 | ||
| 244 | pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher { | 146 | fn readString(it: *TrieIterator) ![:0]const u8 { |
| 245 | var self = TargetMatcher{ | 147 | var stream = it.getStream(); |
| 246 | .allocator = allocator, | 148 | const reader = stream.reader(); |
| 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); | ||
| 253 | 149 | ||
| 254 | if (self.abi == .simulator) { | 150 | var count: usize = 0; |
| 255 | // For Apple simulator targets, linking gets tricky as we need to link against the simulator | 151 | while (true) : (count += 1) { |
| 256 | // hosts dylibs too. | 152 | const byte = try reader.readByte(); |
| 257 | const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none); | 153 | if (byte == 0) break; |
| 258 | try self.target_strings.append(allocator, host_target); | ||
| 259 | } | 154 | } |
| 260 | 155 | ||
| 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; | ||
| 262 | } | 159 | } |
| 263 | 160 | ||
| 264 | pub fn deinit(self: *TargetMatcher) void { | 161 | fn readByte(it: *TrieIterator) !u8 { |
| 265 | for (self.target_strings.items) |t| { | 162 | var stream = it.getStream(); |
| 266 | self.allocator.free(t); | 163 | const value = try stream.reader().readByte(); |
| 267 | } | 164 | it.pos += 1; |
| 268 | self.target_strings.deinit(self.allocator); | 165 | return value; |
| 269 | } | 166 | } |
| 167 | }; | ||
| 270 | 168 | ||
| 271 | inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 { | 169 | pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void { |
| 272 | return switch (cpu_arch) { | 170 | try self.exports.append(allocator, .{ |
| 273 | .aarch64 => "arm64", | 171 | .name = try self.insertString(allocator, name), |
| 274 | .x86_64 => "x86_64", | 172 | .flags = flags, |
| 275 | else => unreachable, | 173 | }); |
| 276 | }; | 174 | } |
| 277 | } | ||
| 278 | 175 | ||
| 279 | inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 { | 176 | fn parseTrieNode( |
| 280 | return switch (abi) { | 177 | self: *Dylib, |
| 281 | .none => null, | 178 | it: *TrieIterator, |
| 282 | .simulator => "simulator", | 179 | allocator: Allocator, |
| 283 | .macabi => "maccatalyst", | 180 | arena: Allocator, |
| 284 | else => unreachable, | 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, | ||
| 285 | }; | 193 | }; |
| 286 | } | 194 | if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) { |
| 287 | 195 | _ = try it.readULEB128(); // dylib ordinal | |
| 288 | pub fn toAppleTargetTriple( | 196 | const name = try it.readString(); |
| 289 | allocator: Allocator, | 197 | try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags); |
| 290 | cpu_arch: std.Target.Cpu.Arch, | 198 | } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) { |
| 291 | os_tag: std.Target.Os.Tag, | 199 | _ = try it.readULEB128(); // stub offset |
| 292 | abi: std.Target.Abi, | 200 | _ = try it.readULEB128(); // resolver offset |
| 293 | ) ![]const u8 { | 201 | try self.addExport(allocator, prefix, out_flags); |
| 294 | const cpu_arch_s = fmtCpuArch(cpu_arch); | 202 | } else { |
| 295 | const os_tag_s = @tagName(os_tag); | 203 | _ = try it.readULEB128(); // VM offset |
| 296 | if (fmtAbi(abi)) |abi_s| { | 204 | try self.addExport(allocator, prefix, out_flags); |
| 297 | return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ cpu_arch_s, os_tag_s, abi_s }); | ||
| 298 | } | 205 | } |
| 299 | return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s }); | ||
| 300 | } | 206 | } |
| 301 | 207 | ||
| 302 | fn hasValue(stack: []const []const u8, needle: []const u8) bool { | 208 | const nedges = try it.readByte(); |
| 303 | for (stack) |v| { | ||
| 304 | if (mem.eql(u8, v, needle)) return true; | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | 209 | ||
| 309 | pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { | 210 | for (0..nedges) |_| { |
| 310 | for (self.target_strings.items) |t| { | 211 | const label = try it.readString(); |
| 311 | if (hasValue(targets, t)) return true; | 212 | const off = try it.readULEB128(); |
| 312 | } | 213 | const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label }); |
| 313 | return false; | 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; | ||
| 314 | } | 218 | } |
| 219 | } | ||
| 315 | 220 | ||
| 316 | fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { | 221 | fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void { |
| 317 | return hasValue(archs, fmtCpuArch(self.cpu_arch)); | 222 | const tracy = trace(@src()); |
| 318 | } | 223 | defer tracy.end(); |
| 319 | }; | 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 | } | ||
| 320 | 231 | ||
| 321 | pub fn parseFromStub( | 232 | pub fn parseTbd( |
| 322 | self: *Dylib, | 233 | self: *Dylib, |
| 323 | allocator: Allocator, | 234 | cpu_arch: std.Target.Cpu.Arch, |
| 324 | target: std.Target, | 235 | platform: MachO.Platform, |
| 325 | lib_stub: LibStub, | 236 | lib_stub: LibStub, |
| 326 | dylib_id: u16, | 237 | macho_file: *MachO, |
| 327 | dependent_libs: anytype, | ||
| 328 | name: []const u8, | ||
| 329 | ) !void { | 238 | ) !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; | ||
| 331 | 243 | ||
| 332 | log.debug("parsing shared library from stub '{s}'", .{name}); | 244 | log.debug("parsing dylib from stub", .{}); |
| 333 | 245 | ||
| 334 | const umbrella_lib = lib_stub.inner[0]; | 246 | const umbrella_lib = lib_stub.inner[0]; |
| 335 | 247 | ||
| 336 | { | 248 | { |
| 337 | var id = try Id.default(allocator, umbrella_lib.installName()); | 249 | var id = try Id.default(gpa, umbrella_lib.installName()); |
| 338 | if (umbrella_lib.currentVersion()) |version| { | 250 | if (umbrella_lib.currentVersion()) |version| { |
| 339 | try id.parseCurrentVersion(version); | 251 | try id.parseCurrentVersion(version); |
| 340 | } | 252 | } |
| ... | @@ -344,21 +256,18 @@ pub fn parseFromStub( | ... | @@ -344,21 +256,18 @@ pub fn parseFromStub( |
| 344 | self.id = id; | 256 | self.id = id; |
| 345 | } | 257 | } |
| 346 | 258 | ||
| 347 | var umbrella_libs = std.StringHashMap(void).init(allocator); | 259 | var umbrella_libs = std.StringHashMap(void).init(gpa); |
| 348 | defer umbrella_libs.deinit(); | 260 | defer umbrella_libs.deinit(); |
| 349 | 261 | ||
| 350 | log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); | 262 | log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); |
| 351 | 263 | ||
| 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()); | ||
| 353 | defer matcher.deinit(); | 267 | defer matcher.deinit(); |
| 354 | 268 | ||
| 355 | for (lib_stub.inner, 0..) |elem, stub_index| { | 269 | for (lib_stub.inner, 0..) |elem, stub_index| { |
| 356 | const targets = try elem.targets(allocator); | 270 | if (!(try matcher.matchesTargetTbd(elem))) continue; |
| 357 | defer { | ||
| 358 | for (targets) |t| allocator.free(t); | ||
| 359 | allocator.free(targets); | ||
| 360 | } | ||
| 361 | if (!matcher.matchesTarget(targets)) continue; | ||
| 362 | 271 | ||
| 363 | if (stub_index > 0) { | 272 | if (stub_index > 0) { |
| 364 | // TODO I thought that we could switch on presence of `parent-umbrella` map; | 273 | // TODO I thought that we could switch on presence of `parent-umbrella` map; |
| ... | @@ -375,43 +284,42 @@ pub fn parseFromStub( | ... | @@ -375,43 +284,42 @@ pub fn parseFromStub( |
| 375 | 284 | ||
| 376 | if (exp.symbols) |symbols| { | 285 | if (exp.symbols) |symbols| { |
| 377 | for (symbols) |sym_name| { | 286 | for (symbols) |sym_name| { |
| 378 | try self.addSymbol(allocator, sym_name); | 287 | try self.addExport(gpa, sym_name, .{}); |
| 379 | } | 288 | } |
| 380 | } | 289 | } |
| 381 | 290 | ||
| 382 | if (exp.weak_symbols) |symbols| { | 291 | if (exp.weak_symbols) |symbols| { |
| 383 | for (symbols) |sym_name| { | 292 | for (symbols) |sym_name| { |
| 384 | try self.addWeakSymbol(allocator, sym_name); | 293 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 385 | } | 294 | } |
| 386 | } | 295 | } |
| 387 | 296 | ||
| 388 | if (exp.objc_classes) |objc_classes| { | 297 | if (exp.objc_classes) |objc_classes| { |
| 389 | for (objc_classes) |class_name| { | 298 | for (objc_classes) |class_name| { |
| 390 | try self.addObjCClassSymbol(allocator, class_name); | 299 | try self.addObjCClass(gpa, class_name); |
| 391 | } | 300 | } |
| 392 | } | 301 | } |
| 393 | 302 | ||
| 394 | if (exp.objc_ivars) |objc_ivars| { | 303 | if (exp.objc_ivars) |objc_ivars| { |
| 395 | for (objc_ivars) |ivar| { | 304 | for (objc_ivars) |ivar| { |
| 396 | try self.addObjCIVarSymbol(allocator, ivar); | 305 | try self.addObjCIVar(gpa, ivar); |
| 397 | } | 306 | } |
| 398 | } | 307 | } |
| 399 | 308 | ||
| 400 | if (exp.objc_eh_types) |objc_eh_types| { | 309 | if (exp.objc_eh_types) |objc_eh_types| { |
| 401 | for (objc_eh_types) |eht| { | 310 | for (objc_eh_types) |eht| { |
| 402 | try self.addObjCEhTypeSymbol(allocator, eht); | 311 | try self.addObjCEhType(gpa, eht); |
| 403 | } | 312 | } |
| 404 | } | 313 | } |
| 405 | 314 | ||
| 406 | // TODO track which libs were already parsed in different steps | ||
| 407 | if (exp.re_exports) |re_exports| { | 315 | if (exp.re_exports) |re_exports| { |
| 408 | for (re_exports) |lib| { | 316 | for (re_exports) |lib| { |
| 409 | if (umbrella_libs.contains(lib)) continue; | 317 | if (umbrella_libs.contains(lib)) continue; |
| 410 | 318 | ||
| 411 | log.debug(" (found re-export '{s}')", .{lib}); | 319 | log.debug(" (found re-export '{s}')", .{lib}); |
| 412 | 320 | ||
| 413 | const dep_id = try Id.default(allocator, lib); | 321 | const dep_id = try Id.default(gpa, lib); |
| 414 | try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); | 322 | try self.dependents.append(gpa, dep_id); |
| 415 | } | 323 | } |
| 416 | } | 324 | } |
| 417 | } | 325 | } |
| ... | @@ -424,31 +332,31 @@ pub fn parseFromStub( | ... | @@ -424,31 +332,31 @@ pub fn parseFromStub( |
| 424 | 332 | ||
| 425 | if (exp.symbols) |symbols| { | 333 | if (exp.symbols) |symbols| { |
| 426 | for (symbols) |sym_name| { | 334 | for (symbols) |sym_name| { |
| 427 | try self.addSymbol(allocator, sym_name); | 335 | try self.addExport(gpa, sym_name, .{}); |
| 428 | } | 336 | } |
| 429 | } | 337 | } |
| 430 | 338 | ||
| 431 | if (exp.weak_symbols) |symbols| { | 339 | if (exp.weak_symbols) |symbols| { |
| 432 | for (symbols) |sym_name| { | 340 | for (symbols) |sym_name| { |
| 433 | try self.addWeakSymbol(allocator, sym_name); | 341 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 434 | } | 342 | } |
| 435 | } | 343 | } |
| 436 | 344 | ||
| 437 | if (exp.objc_classes) |classes| { | 345 | if (exp.objc_classes) |classes| { |
| 438 | for (classes) |sym_name| { | 346 | for (classes) |sym_name| { |
| 439 | try self.addObjCClassSymbol(allocator, sym_name); | 347 | try self.addObjCClass(gpa, sym_name); |
| 440 | } | 348 | } |
| 441 | } | 349 | } |
| 442 | 350 | ||
| 443 | if (exp.objc_ivars) |objc_ivars| { | 351 | if (exp.objc_ivars) |objc_ivars| { |
| 444 | for (objc_ivars) |ivar| { | 352 | for (objc_ivars) |ivar| { |
| 445 | try self.addObjCIVarSymbol(allocator, ivar); | 353 | try self.addObjCIVar(gpa, ivar); |
| 446 | } | 354 | } |
| 447 | } | 355 | } |
| 448 | 356 | ||
| 449 | if (exp.objc_eh_types) |objc_eh_types| { | 357 | if (exp.objc_eh_types) |objc_eh_types| { |
| 450 | for (objc_eh_types) |eht| { | 358 | for (objc_eh_types) |eht| { |
| 451 | try self.addObjCEhTypeSymbol(allocator, eht); | 359 | try self.addObjCEhType(gpa, eht); |
| 452 | } | 360 | } |
| 453 | } | 361 | } |
| 454 | } | 362 | } |
| ... | @@ -460,31 +368,31 @@ pub fn parseFromStub( | ... | @@ -460,31 +368,31 @@ pub fn parseFromStub( |
| 460 | 368 | ||
| 461 | if (reexp.symbols) |symbols| { | 369 | if (reexp.symbols) |symbols| { |
| 462 | for (symbols) |sym_name| { | 370 | for (symbols) |sym_name| { |
| 463 | try self.addSymbol(allocator, sym_name); | 371 | try self.addExport(gpa, sym_name, .{}); |
| 464 | } | 372 | } |
| 465 | } | 373 | } |
| 466 | 374 | ||
| 467 | if (reexp.weak_symbols) |symbols| { | 375 | if (reexp.weak_symbols) |symbols| { |
| 468 | for (symbols) |sym_name| { | 376 | for (symbols) |sym_name| { |
| 469 | try self.addWeakSymbol(allocator, sym_name); | 377 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 470 | } | 378 | } |
| 471 | } | 379 | } |
| 472 | 380 | ||
| 473 | if (reexp.objc_classes) |classes| { | 381 | if (reexp.objc_classes) |classes| { |
| 474 | for (classes) |sym_name| { | 382 | for (classes) |sym_name| { |
| 475 | try self.addObjCClassSymbol(allocator, sym_name); | 383 | try self.addObjCClass(gpa, sym_name); |
| 476 | } | 384 | } |
| 477 | } | 385 | } |
| 478 | 386 | ||
| 479 | if (reexp.objc_ivars) |objc_ivars| { | 387 | if (reexp.objc_ivars) |objc_ivars| { |
| 480 | for (objc_ivars) |ivar| { | 388 | for (objc_ivars) |ivar| { |
| 481 | try self.addObjCIVarSymbol(allocator, ivar); | 389 | try self.addObjCIVar(gpa, ivar); |
| 482 | } | 390 | } |
| 483 | } | 391 | } |
| 484 | 392 | ||
| 485 | if (reexp.objc_eh_types) |objc_eh_types| { | 393 | if (reexp.objc_eh_types) |objc_eh_types| { |
| 486 | for (objc_eh_types) |eht| { | 394 | for (objc_eh_types) |eht| { |
| 487 | try self.addObjCEhTypeSymbol(allocator, eht); | 395 | try self.addObjCEhType(gpa, eht); |
| 488 | } | 396 | } |
| 489 | } | 397 | } |
| 490 | } | 398 | } |
| ... | @@ -492,19 +400,19 @@ pub fn parseFromStub( | ... | @@ -492,19 +400,19 @@ pub fn parseFromStub( |
| 492 | 400 | ||
| 493 | if (stub.objc_classes) |classes| { | 401 | if (stub.objc_classes) |classes| { |
| 494 | for (classes) |sym_name| { | 402 | for (classes) |sym_name| { |
| 495 | try self.addObjCClassSymbol(allocator, sym_name); | 403 | try self.addObjCClass(gpa, sym_name); |
| 496 | } | 404 | } |
| 497 | } | 405 | } |
| 498 | 406 | ||
| 499 | if (stub.objc_ivars) |objc_ivars| { | 407 | if (stub.objc_ivars) |objc_ivars| { |
| 500 | for (objc_ivars) |ivar| { | 408 | for (objc_ivars) |ivar| { |
| 501 | try self.addObjCIVarSymbol(allocator, ivar); | 409 | try self.addObjCIVar(gpa, ivar); |
| 502 | } | 410 | } |
| 503 | } | 411 | } |
| 504 | 412 | ||
| 505 | if (stub.objc_eh_types) |objc_eh_types| { | 413 | if (stub.objc_eh_types) |objc_eh_types| { |
| 506 | for (objc_eh_types) |eht| { | 414 | for (objc_eh_types) |eht| { |
| 507 | try self.addObjCEhTypeSymbol(allocator, eht); | 415 | try self.addObjCEhType(gpa, eht); |
| 508 | } | 416 | } |
| 509 | } | 417 | } |
| 510 | }, | 418 | }, |
| ... | @@ -514,10 +422,9 @@ pub fn parseFromStub( | ... | @@ -514,10 +422,9 @@ pub fn parseFromStub( |
| 514 | // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include | 422 | // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include |
| 515 | // re-exports directly in the stub file. | 423 | // re-exports directly in the stub file. |
| 516 | for (lib_stub.inner) |elem| { | 424 | for (lib_stub.inner) |elem| { |
| 517 | if (elem == .v3) break; | 425 | if (elem == .v3) continue; |
| 518 | const stub = elem.v4; | 426 | const stub = elem.v4; |
| 519 | 427 | ||
| 520 | // TODO track which libs were already parsed in different steps | ||
| 521 | if (stub.reexported_libraries) |reexports| { | 428 | if (stub.reexported_libraries) |reexports| { |
| 522 | for (reexports) |reexp| { | 429 | for (reexports) |reexp| { |
| 523 | if (!matcher.matchesTarget(reexp.targets)) continue; | 430 | if (!matcher.matchesTarget(reexp.targets)) continue; |
| ... | @@ -527,30 +434,437 @@ pub fn parseFromStub( | ... | @@ -527,30 +434,437 @@ pub fn parseFromStub( |
| 527 | 434 | ||
| 528 | log.debug(" (found re-export '{s}')", .{lib}); | 435 | log.debug(" (found re-export '{s}')", .{lib}); |
| 529 | 436 | ||
| 530 | const dep_id = try Id.default(allocator, lib); | 437 | const dep_id = try Id.default(gpa, lib); |
| 531 | try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); | 438 | try self.dependents.append(gpa, dep_id); |
| 532 | } | 439 | } |
| 533 | } | 440 | } |
| 534 | } | 441 | } |
| 535 | } | 442 | } |
| 536 | } | 443 | } |
| 537 | 444 | ||
| 538 | const Dylib = @This(); | 445 | fn 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 | |||
| 450 | fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void { | ||
| 451 | try self.addObjCExport(allocator, "_OBJC_IVAR_", name); | ||
| 452 | } | ||
| 453 | |||
| 454 | fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void { | ||
| 455 | try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name); | ||
| 456 | } | ||
| 457 | |||
| 458 | fn 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 | |||
| 469 | pub 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 | |||
| 482 | fn 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 | |||
| 500 | pub 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 | |||
| 525 | pub 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 | |||
| 534 | pub 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 | |||
| 539 | pub 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 | |||
| 553 | pub 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 | |||
| 570 | pub 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 | |||
| 588 | pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib { | ||
| 589 | return macho_file.getFile(self.umbrella).?.dylib; | ||
| 590 | } | ||
| 591 | |||
| 592 | fn 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 | |||
| 602 | fn 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 | |||
| 608 | pub 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 | |||
| 613 | pub fn asFile(self: *Dylib) File { | ||
| 614 | return .{ .dylib = self }; | ||
| 615 | } | ||
| 616 | |||
| 617 | pub 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 | |||
| 630 | pub 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 | |||
| 637 | const FormatContext = struct { | ||
| 638 | dylib: *Dylib, | ||
| 639 | macho_file: *MachO, | ||
| 640 | }; | ||
| 641 | |||
| 642 | fn 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 | |||
| 658 | pub 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 | |||
| 757 | pub 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 | |||
| 840 | const 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 | }; | ||
| 539 | 850 | ||
| 540 | const std = @import("std"); | ||
| 541 | const assert = std.debug.assert; | 851 | const assert = std.debug.assert; |
| 852 | const fat = @import("fat.zig"); | ||
| 542 | const fs = std.fs; | 853 | const fs = std.fs; |
| 543 | const fmt = std.fmt; | 854 | const fmt = std.fmt; |
| 544 | const log = std.log.scoped(.link); | 855 | const log = std.log.scoped(.link); |
| 545 | const macho = std.macho; | 856 | const macho = std.macho; |
| 546 | const math = std.math; | 857 | const math = std.math; |
| 547 | const mem = std.mem; | 858 | const mem = std.mem; |
| 548 | const fat = @import("fat.zig"); | ||
| 549 | const tapi = @import("../tapi.zig"); | 859 | const tapi = @import("../tapi.zig"); |
| 860 | const trace = @import("../../tracy.zig").trace; | ||
| 861 | const std = @import("std"); | ||
| 550 | 862 | ||
| 551 | const Allocator = mem.Allocator; | 863 | const Allocator = mem.Allocator; |
| 864 | const Dylib = @This(); | ||
| 865 | const File = @import("file.zig").File; | ||
| 552 | const LibStub = tapi.LibStub; | 866 | const LibStub = tapi.LibStub; |
| 553 | const LoadCommandIterator = macho.LoadCommandIterator; | 867 | const LoadCommandIterator = macho.LoadCommandIterator; |
| 554 | const MachO = @import("../MachO.zig"); | 868 | const MachO = @import("../MachO.zig"); |
| 555 | const Platform = @import("load_commands.zig").Platform; | 869 | const Symbol = @import("Symbol.zig"); |
| 556 | const Tbd = tapi.Tbd; | 870 | const Tbd = tapi.Tbd; |
src/link/MachO/InternalObject.zig created+249| ... | @@ -0,0 +1,249 @@ | ||
| 1 | index: File.Index, | ||
| 2 | |||
| 3 | sections: std.MultiArrayList(Section) = .{}, | ||
| 4 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 5 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 6 | |||
| 7 | objc_methnames: std.ArrayListUnmanaged(u8) = .{}, | ||
| 8 | objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64), | ||
| 9 | |||
| 10 | output_symtab_ctx: MachO.SymtabCtx = .{}, | ||
| 11 | |||
| 12 | pub 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 | |||
| 22 | pub 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. | ||
| 34 | pub 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 | |||
| 39 | fn 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 | |||
| 68 | fn 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 | |||
| 116 | pub 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 | |||
| 136 | pub 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 | |||
| 150 | fn 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 | |||
| 161 | pub 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 | |||
| 173 | pub fn asFile(self: *InternalObject) File { | ||
| 174 | return .{ .internal = self }; | ||
| 175 | } | ||
| 176 | |||
| 177 | const FormatContext = struct { | ||
| 178 | self: *InternalObject, | ||
| 179 | macho_file: *MachO, | ||
| 180 | }; | ||
| 181 | |||
| 182 | pub 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 | |||
| 189 | fn 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 | |||
| 204 | pub 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 | |||
| 211 | fn 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 | |||
| 226 | const 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 | |||
| 237 | const assert = std.debug.assert; | ||
| 238 | const macho = std.macho; | ||
| 239 | const mem = std.mem; | ||
| 240 | const std = @import("std"); | ||
| 241 | |||
| 242 | const Allocator = std.mem.Allocator; | ||
| 243 | const Atom = @import("Atom.zig"); | ||
| 244 | const File = @import("file.zig").File; | ||
| 245 | const InternalObject = @This(); | ||
| 246 | const MachO = @import("../MachO.zig"); | ||
| 247 | const Object = @import("Object.zig"); | ||
| 248 | const Relocation = @import("Relocation.zig"); | ||
| 249 | const Symbol = @import("Symbol.zig"); | ||
src/link/MachO/Object.zig+1969-916| ... | @@ -1,1130 +1,2183 @@ | ... | @@ -1,1130 +1,2183 @@ |
| 1 | //! Represents an input relocatable Object file. | 1 | archive: ?[]const u8 = null, |
| 2 | //! Each Object is fully loaded into memory for easier | 2 | path: []const u8, |
| 3 | //! access into different data within. | ||
| 4 | |||
| 5 | name: []const u8, | ||
| 6 | mtime: u64, | 3 | mtime: u64, |
| 7 | contents: []align(@alignOf(u64)) const u8, | 4 | data: []const u8, |
| 8 | 5 | index: File.Index, | |
| 9 | header: macho.mach_header_64 = undefined, | 6 | |
| 10 | 7 | header: ?macho.mach_header_64 = null, | |
| 11 | /// Symtab and strtab might not exist for empty object files so we use an optional | 8 | sections: std.MultiArrayList(Section) = .{}, |
| 12 | /// to signal this. | 9 | symtab: std.MultiArrayList(Nlist) = .{}, |
| 13 | in_symtab: ?[]align(1) const macho.nlist_64 = null, | 10 | strtab: []const u8 = &[0]u8{}, |
| 14 | in_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. | ||
| 20 | symtab: []macho.nlist_64 = undefined, | ||
| 21 | /// Can be undefined as set together with in_symtab. | ||
| 22 | source_symtab_lookup: []u32 = undefined, | ||
| 23 | /// Can be undefined as set together with in_symtab. | ||
| 24 | reverse_symtab_lookup: []u32 = undefined, | ||
| 25 | /// Can be undefined as set together with in_symtab. | ||
| 26 | source_address_lookup: []i64 = undefined, | ||
| 27 | /// Can be undefined as set together with in_symtab. | ||
| 28 | source_section_index_lookup: []Entry = undefined, | ||
| 29 | /// Can be undefined as set together with in_symtab. | ||
| 30 | strtab_lookup: []u32 = undefined, | ||
| 31 | /// Can be undefined as set together with in_symtab. | ||
| 32 | atom_by_index_table: []?Atom.Index = undefined, | ||
| 33 | /// Can be undefined as set together with in_symtab. | ||
| 34 | globals_lookup: []i64 = undefined, | ||
| 35 | /// Can be undefined as set together with in_symtab. | ||
| 36 | relocs_lookup: []Entry = undefined, | ||
| 37 | |||
| 38 | /// All relocations sorted and flatened, sorted by address descending | ||
| 39 | /// per section. | ||
| 40 | relocations: std.ArrayListUnmanaged(macho.relocation_info) = .{}, | ||
| 41 | /// Beginning index to the relocations array for each input section | ||
| 42 | /// defined within this Object file. | ||
| 43 | section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{}, | ||
| 44 | |||
| 45 | /// Data-in-code records sorted by address. | ||
| 46 | data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, | ||
| 47 | 11 | ||
| 12 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 48 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | 13 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, |
| 49 | exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 50 | 14 | ||
| 51 | eh_frame_sect_id: ?u8 = null, | 15 | platform: ?MachO.Platform = null, |
| 52 | eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{}, | 16 | dwarf_info: ?DwarfInfo = null, |
| 53 | eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, | 17 | stab_files: std.ArrayListUnmanaged(StabFile) = .{}, |
| 54 | 18 | ||
| 55 | unwind_info_sect_id: ?u8 = null, | 19 | eh_frame_sect_index: ?u8 = null, |
| 56 | unwind_relocs_lookup: []Record = undefined, | 20 | compact_unwind_sect_index: ?u8 = null, |
| 57 | unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{}, | 21 | cies: std.ArrayListUnmanaged(Cie) = .{}, |
| 22 | fdes: std.ArrayListUnmanaged(Fde) = .{}, | ||
| 23 | eh_frame_data: std.ArrayListUnmanaged(u8) = .{}, | ||
| 24 | unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{}, | ||
| 58 | 25 | ||
| 59 | const Entry = struct { | 26 | alive: bool = true, |
| 60 | start: u32 = 0, | 27 | hidden: bool = false, |
| 61 | len: u32 = 0, | ||
| 62 | }; | ||
| 63 | 28 | ||
| 64 | const Record = struct { | 29 | dynamic_relocs: MachO.DynamicRelocs = .{}, |
| 65 | dead: bool, | 30 | output_symtab_ctx: MachO.SymtabCtx = .{}, |
| 66 | reloc: Entry, | ||
| 67 | }; | ||
| 68 | 31 | ||
| 69 | pub fn isObject(file: std.fs.File) bool { | 32 | pub fn isObject(path: []const u8) !bool { |
| 70 | const reader = file.reader(); | 33 | const file = try std.fs.cwd().openFile(path, .{}); |
| 71 | const hdr = reader.readStruct(macho.mach_header_64) catch return false; | 34 | defer file.close(); |
| 72 | defer file.seekTo(0) catch {}; | 35 | const header = file.reader().readStruct(macho.mach_header_64) catch return false; |
| 73 | return hdr.filetype == macho.MH_OBJECT; | 36 | return header.filetype == macho.MH_OBJECT; |
| 74 | } | 37 | } |
| 75 | 38 | ||
| 76 | pub fn deinit(self: *Object, gpa: Allocator) void { | 39 | pub fn deinit(self: *Object, allocator: Allocator) void { |
| 77 | self.atoms.deinit(gpa); | 40 | for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| { |
| 78 | self.exec_atoms.deinit(gpa); | 41 | relocs.deinit(allocator); |
| 79 | gpa.free(self.name); | 42 | sub.deinit(allocator); |
| 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); | ||
| 91 | } | 43 | } |
| 92 | self.eh_frame_relocs_lookup.deinit(gpa); | 44 | self.sections.deinit(allocator); |
| 93 | self.eh_frame_records_lookup.deinit(gpa); | 45 | self.symtab.deinit(allocator); |
| 94 | if (self.hasUnwindRecords()) { | 46 | self.symbols.deinit(allocator); |
| 95 | gpa.free(self.unwind_relocs_lookup); | 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); | ||
| 96 | } | 55 | } |
| 97 | self.unwind_records_lookup.deinit(gpa); | 56 | self.stab_files.deinit(allocator); |
| 98 | self.relocations.deinit(gpa); | 57 | allocator.free(self.data); |
| 99 | self.section_relocs_lookup.deinit(gpa); | ||
| 100 | self.data_in_code.deinit(gpa); | ||
| 101 | } | 58 | } |
| 102 | 59 | ||
| 103 | pub fn parse(self: *Object, allocator: Allocator) !void { | 60 | pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 104 | var stream = std.io.fixedBufferStream(self.contents); | 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); | ||
| 105 | const reader = stream.reader(); | 66 | const reader = stream.reader(); |
| 106 | 67 | ||
| 107 | self.header = try reader.readStruct(macho.mach_header_64); | 68 | self.header = try reader.readStruct(macho.mach_header_64); |
| 108 | 69 | ||
| 109 | var it = LoadCommandIterator{ | 70 | const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { |
| 110 | .ncmds = self.header.ncmds, | 71 | macho.CPU_TYPE_ARM64 => .aarch64, |
| 111 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 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 | }, | ||
| 112 | }; | 77 | }; |
| 113 | const nsects = self.getSourceSections().len; | 78 | if (macho_file.getTarget().cpu.arch != this_cpu_arch) { |
| 114 | 79 | try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)}); | |
| 115 | // Prepopulate relocations per section lookup table. | 80 | return error.InvalidCpuArch; |
| 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)) }); | ||
| 164 | } | 81 | } |
| 165 | 82 | ||
| 166 | // We sort by type: defined < undefined, and | 83 | if (self.getLoadCommand(.SEGMENT_64)) |lc| { |
| 167 | // afterwards by address in each group. Normally, dysymtab should | 84 | const sections = lc.getSections(); |
| 168 | // be enough to guarantee the sort, but turns out not every compiler | 85 | try self.sections.ensureUnusedCapacity(gpa, sections.len); |
| 169 | // is kind enough to specify the symbols in the correct order. | 86 | for (sections) |sect| { |
| 170 | mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan); | 87 | const index = try self.sections.addOne(gpa); |
| 171 | 88 | self.sections.set(index, .{ .header = sect }); | |
| 172 | var prev_sect_id: u8 = 0; | 89 | |
| 173 | var section_index_lookup: ?Entry = null; | 90 | if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| 174 | for (sorted_all_syms.items, 0..) |sym_id, i| { | 91 | self.eh_frame_sect_index = @intCast(index); |
| 175 | const sym = sym_id.getSymbol(self); | 92 | } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) { |
| 176 | 93 | self.compact_unwind_sect_index = @intCast(index); | |
| 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; | ||
| 183 | } | 94 | } |
| 184 | } | 95 | } |
| 185 | if (sym.sect() and section_index_lookup == null) { | 96 | } |
| 186 | section_index_lookup = .{ .start = @as(u32, @intCast(i)), .len = 1 }; | 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 | }); | ||
| 187 | } | 109 | } |
| 110 | } | ||
| 188 | 111 | ||
| 189 | prev_sect_id = sym.n_sect; | 112 | const NlistIdx = struct { |
| 113 | nlist: macho.nlist_64, | ||
| 114 | idx: usize, | ||
| 190 | 115 | ||
| 191 | self.symtab[i] = sym; | 116 | fn rank(ctx: *const Object, nl: macho.nlist_64) u8 { |
| 192 | self.source_symtab_lookup[i] = sym_id.index; | 117 | if (!nl.ext()) { |
| 193 | self.reverse_symtab_lookup[sym_id.index] = @as(u32, @intCast(i)); | 118 | const name = ctx.getString(nl.n_strx); |
| 194 | self.source_address_lookup[i] = if (sym.undf()) -1 else @as(i64, @intCast(sym.n_value)); | 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 | } | ||
| 195 | 125 | ||
| 196 | const sym_name_len = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.in_strtab.?.ptr + sym.n_strx)), 0).len + 1; | 126 | fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool { |
| 197 | self.strtab_lookup[i] = @as(u32, @intCast(sym_name_len)); | 127 | if (lhs.nlist.n_sect == rhs.nlist.n_sect) { |
| 198 | } | 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 | }; | ||
| 199 | 136 | ||
| 200 | // If there were no undefined symbols, make sure we populate the | 137 | var nlists = try std.ArrayList(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len); |
| 201 | // source section index lookup for the last scanned section. | 138 | defer nlists.deinit(); |
| 202 | if (section_index_lookup) |lookup| { | 139 | for (self.symtab.items(.nlist), 0..) |nlist, i| { |
| 203 | self.source_section_index_lookup[prev_sect_id - 1] = lookup; | 140 | if (nlist.stab() or !nlist.sect()) continue; |
| 141 | nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i }); | ||
| 204 | } | 142 | } |
| 143 | mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan); | ||
| 205 | 144 | ||
| 206 | // Parse __TEXT,__eh_frame header if one exists | 145 | if (self.hasSubsections()) { |
| 207 | self.eh_frame_sect_id = self.getSourceSectionIndexByName("__TEXT", "__eh_frame"); | 146 | try self.initSubsections(nlists.items, macho_file); |
| 208 | 147 | } else { | |
| 209 | // Parse __LD,__compact_unwind header if one exists | 148 | try self.initSections(nlists.items, macho_file); |
| 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 = .{} }); | ||
| 214 | } | 149 | } |
| 215 | } | ||
| 216 | 150 | ||
| 217 | const SymbolAtIndex = struct { | 151 | try self.initLiteralSections(macho_file); |
| 218 | index: u32, | 152 | try self.linkNlistToAtom(macho_file); |
| 219 | 153 | ||
| 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); | ||
| 221 | 158 | ||
| 222 | fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 { | 159 | // Parse DWARF __TEXT,__eh_frame section |
| 223 | return ctx.in_symtab.?[self.index]; | 160 | if (self.eh_frame_sect_index) |index| { |
| 161 | try self.initEhFrameRecords(index, macho_file); | ||
| 224 | } | 162 | } |
| 225 | 163 | ||
| 226 | fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 { | 164 | // Parse Apple's __LD,__compact_unwind section |
| 227 | const off = self.getSymbol(ctx).n_strx; | 165 | if (self.compact_unwind_sect_index) |index| { |
| 228 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.in_strtab.?.ptr + off)), 0); | 166 | try self.initUnwindRecords(index, macho_file); |
| 229 | } | 167 | } |
| 230 | 168 | ||
| 231 | fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 { | 169 | if (self.hasUnwindRecords() or self.hasEhFrameRecords()) { |
| 232 | const sym = self.getSymbol(ctx); | 170 | try self.parseUnwindRecords(macho_file); |
| 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; | ||
| 240 | } | 171 | } |
| 241 | 172 | ||
| 242 | /// Performs lexicographic-like check. | 173 | self.initPlatform(); |
| 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 | } | ||
| 267 | 174 | ||
| 268 | fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool { | 175 | if (self.platform) |platform| { |
| 269 | return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx; | 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 | // } | ||
| 270 | } | 192 | } |
| 271 | }; | ||
| 272 | 193 | ||
| 273 | fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct { | 194 | try self.initDwarfInfo(macho_file); |
| 274 | index: u32, | ||
| 275 | len: u32, | ||
| 276 | } { | ||
| 277 | const FirstMatch = struct { | ||
| 278 | n_sect: u8, | ||
| 279 | 195 | ||
| 280 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 196 | for (self.atoms.items) |atom_index| { |
| 281 | return symbol.n_sect == pred.n_sect; | 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; | ||
| 282 | } | 204 | } |
| 283 | }; | 205 | } |
| 284 | const FirstNonMatch = struct { | 206 | } |
| 285 | n_sect: u8, | ||
| 286 | 207 | ||
| 287 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 208 | inline fn isLiteral(sect: macho.section_64) bool { |
| 288 | return symbol.n_sect != pred.n_sect; | 209 | return switch (sect.type()) { |
| 289 | } | 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, | ||
| 290 | }; | 217 | }; |
| 218 | } | ||
| 291 | 219 | ||
| 292 | const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{ | 220 | fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { |
| 293 | .n_sect = n_sect, | 221 | const tracy = trace(@src()); |
| 294 | }); | 222 | defer tracy.end(); |
| 295 | const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{ | 223 | const gpa = macho_file.base.comp.gpa; |
| 296 | .n_sect = n_sect, | 224 | const slice = self.sections.slice(); |
| 297 | }); | 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 | } | ||
| 298 | 251 | ||
| 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 | } | ||
| 300 | } | 286 | } |
| 301 | 287 | ||
| 302 | fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr: u64) struct { | 288 | fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void { |
| 303 | index: u32, | 289 | const tracy = trace(@src()); |
| 304 | len: u32, | 290 | defer tracy.end(); |
| 305 | } { | 291 | const gpa = macho_file.base.comp.gpa; |
| 306 | const Predicate = struct { | 292 | const slice = self.sections.slice(); |
| 307 | addr: u64, | 293 | |
| 308 | 294 | try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); | |
| 309 | pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool { | 295 | |
| 310 | return symbol.n_value >= pred.addr; | 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 | } | ||
| 311 | } | 334 | } |
| 312 | }; | 335 | } |
| 336 | } | ||
| 313 | 337 | ||
| 314 | const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{ | 338 | const AddAtomArgs = struct { |
| 315 | .addr = start_addr, | 339 | name: [:0]const u8, |
| 316 | }); | 340 | n_sect: u8, |
| 317 | const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{ | 341 | off: u64, |
| 318 | .addr = end_addr, | 342 | size: u64, |
| 319 | }); | 343 | alignment: u32, |
| 344 | }; | ||
| 320 | 345 | ||
| 321 | return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) }; | 346 | fn 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; | ||
| 322 | } | 359 | } |
| 323 | 360 | ||
| 324 | const SortedSection = struct { | 361 | fn initLiteralSections(self: *Object, macho_file: *MachO) !void { |
| 325 | header: macho.section_64, | 362 | const tracy = trace(@src()); |
| 326 | id: u8, | 363 | defer tracy.end(); |
| 327 | }; | 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 | } | ||
| 328 | 388 | ||
| 329 | fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) bool { | 389 | pub fn findAtom(self: Object, addr: u64) ?Atom.Index { |
| 330 | _ = ctx; | 390 | const tracy = trace(@src()); |
| 331 | if (lhs.header.addr == rhs.header.addr) { | 391 | defer tracy.end(); |
| 332 | return lhs.id < rhs.id; | 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 | } | ||
| 333 | } | 399 | } |
| 334 | return lhs.header.addr < rhs.header.addr; | 400 | return null; |
| 335 | } | 401 | } |
| 336 | 402 | ||
| 337 | pub const SplitIntoAtomsError = error{ | 403 | fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index { |
| 338 | OutOfMemory, | 404 | const tracy = trace(@src()); |
| 339 | EndOfStream, | 405 | defer tracy.end(); |
| 340 | MissingEhFrameSection, | 406 | const slice = self.sections.slice(); |
| 341 | BadDwarfCfi, | 407 | const sect = slice.items(.header)[n_sect]; |
| 342 | }; | 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 | } | ||
| 343 | 427 | ||
| 344 | pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void { | 428 | if (min < subsections.items.len) { |
| 345 | const comp = macho_file.base.comp; | 429 | const sub = subsections.items[min]; |
| 346 | const gpa = comp.gpa; | 430 | const sub_addr = sect.addr + sub.off; |
| 347 | log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name }); | 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 | } | ||
| 348 | 437 | ||
| 349 | try self.splitRegularSections(macho_file, object_id); | 438 | return null; |
| 350 | try self.parseEhFrameSection(macho_file, object_id); | ||
| 351 | try self.parseUnwindInfo(macho_file, object_id); | ||
| 352 | try self.parseDataInCode(gpa); | ||
| 353 | } | 439 | } |
| 354 | 440 | ||
| 355 | /// Splits input regular sections into Atoms. | 441 | fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { |
| 356 | /// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section | 442 | const tracy = trace(@src()); |
| 357 | /// into subsections where each subsection then represents an Atom. | 443 | defer tracy.end(); |
| 358 | pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void { | 444 | for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| { |
| 359 | const comp = macho_file.base.comp; | 445 | if (!nlist.stab() and nlist.sect()) { |
| 360 | const gpa = comp.gpa; | 446 | if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| { |
| 361 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 447 | atom.* = atom_index; |
| 362 | 448 | } else { | |
| 363 | const sections = self.getSourceSections(); | 449 | try macho_file.reportParseError2(self.index, "symbol {s} not attached to any (sub)section", .{ |
| 364 | for (sections, 0..) |sect, id| { | 450 | self.getString(nlist.n_strx), |
| 365 | if (sect.isDebug()) continue; | 451 | }); |
| 366 | const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse { | 452 | return error.MalformedObject; |
| 367 | log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() }); | 453 | } |
| 368 | continue; | 454 | } |
| 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 | }; | ||
| 381 | } | 455 | } |
| 456 | } | ||
| 382 | 457 | ||
| 383 | if (self.in_symtab == null) { | 458 | fn initSymbols(self: *Object, macho_file: *MachO) !void { |
| 384 | for (sections, 0..) |sect, id| { | 459 | const tracy = trace(@src()); |
| 385 | if (sect.isDebug()) continue; | 460 | defer tracy.end(); |
| 386 | const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue; | 461 | const gpa = macho_file.base.comp.gpa; |
| 387 | if (sect.size == 0) continue; | 462 | const slice = self.symtab.slice(); |
| 388 | 463 | ||
| 389 | const sect_id: u8 = @intCast(id); | 464 | try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len); |
| 390 | const sym_index = self.getSectionAliasSymbolIndex(sect_id); | 465 | |
| 391 | const atom_index = try self.createAtomFromSubsection( | 466 | for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| { |
| 392 | macho_file, | 467 | if (nlist.ext()) { |
| 393 | object_id, | 468 | const name = self.getString(nlist.n_strx); |
| 394 | sym_index, | 469 | const off = try macho_file.strings.insert(gpa, name); |
| 395 | sym_index, | 470 | const gop = try macho_file.getOrCreateGlobal(off); |
| 396 | 1, | 471 | self.symbols.addOneAssumeCapacity().* = gop.index; |
| 397 | sect.size, | 472 | continue; |
| 398 | Alignment.fromLog2Units(sect.@"align"), | ||
| 399 | out_sect_id, | ||
| 400 | ); | ||
| 401 | macho_file.addAtomToSection(atom_index); | ||
| 402 | } | 473 | } |
| 403 | return; | ||
| 404 | } | ||
| 405 | 474 | ||
| 406 | // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we | 475 | const index = try macho_file.addSymbol(); |
| 407 | // have to infer the start of undef section in the symtab ourselves. | 476 | self.symbols.appendAssumeCapacity(index); |
| 408 | const iundefsym = blk: { | 477 | const symbol = macho_file.getSymbol(index); |
| 409 | const dysymtab = self.getDysymtab() orelse { | 478 | const name = self.getString(nlist.n_strx); |
| 410 | var iundefsym: usize = self.in_symtab.?.len; | 479 | symbol.* = .{ |
| 411 | while (iundefsym > 0) : (iundefsym -= 1) { | 480 | .value = nlist.n_value, |
| 412 | const sym = self.symtab[iundefsym - 1]; | 481 | .name = try macho_file.strings.insert(gpa, name), |
| 413 | if (sym.sect()) break; | 482 | .nlist_idx = @intCast(i), |
| 414 | } | 483 | .atom = 0, |
| 415 | break :blk iundefsym; | 484 | .file = self.index, |
| 416 | }; | 485 | }; |
| 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); | ||
| 423 | 486 | ||
| 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 | } | ||
| 425 | 492 | ||
| 426 | // Sort section headers by address. | 493 | symbol.flags.abs = nlist.abs(); |
| 427 | var sorted_sections = try gpa.alloc(SortedSection, sections.len); | 494 | symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); |
| 428 | defer gpa.free(sorted_sections); | ||
| 429 | 495 | ||
| 430 | for (sections, 0..) |sect, id| { | 496 | if (nlist.sect() and |
| 431 | sorted_sections[id] = .{ .header = sect, .id = @as(u8, @intCast(id)) }; | 497 | self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) |
| 498 | { | ||
| 499 | symbol.flags.tlv = true; | ||
| 500 | } | ||
| 432 | } | 501 | } |
| 502 | } | ||
| 433 | 503 | ||
| 434 | mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress); | 504 | fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { |
| 435 | 505 | const tracy = trace(@src()); | |
| 436 | var sect_sym_index: u32 = 0; | 506 | defer tracy.end(); |
| 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 | } | ||
| 485 | 507 | ||
| 486 | var next_sym_index = sect_start_index; | 508 | const SymbolLookup = struct { |
| 487 | while (next_sym_index < sect_start_index + sect_loc.len) { | 509 | ctx: *const Object, |
| 488 | const next_sym = symtab[next_sym_index]; | 510 | entries: @TypeOf(nlists), |
| 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; | ||
| 500 | 511 | ||
| 501 | const atom_align = Alignment.fromLog2Units(if (addr > 0) | 512 | fn find(fs: @This(), addr: u64) ?Symbol.Index { |
| 502 | @min(@ctz(addr), sect.@"align") | 513 | // TODO binary search since we have the list sorted |
| 503 | else | 514 | for (fs.entries) |nlist| { |
| 504 | sect.@"align"); | 515 | if (nlist.nlist.n_value == addr) return fs.ctx.symbols.items[nlist.idx]; |
| 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); | ||
| 529 | } | 516 | } |
| 530 | } else { | 517 | return null; |
| 531 | const alias_index = self.getSectionAliasSymbolIndex(sect_id); | 518 | } |
| 532 | const atom_index = try self.createAtomFromSubsection( | 519 | }; |
| 533 | macho_file, | 520 | |
| 534 | object_id, | 521 | const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| { |
| 535 | alias_index, | 522 | if (nlist.stab()) break @intCast(i); |
| 536 | sect_start_index, | 523 | } else @intCast(self.symtab.items(.nlist).len); |
| 537 | sect_loc.len, | 524 | const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| { |
| 538 | sect.size, | 525 | if (!nlist.stab()) break @intCast(i); |
| 539 | Alignment.fromLog2Units(sect.@"align"), | 526 | } else @intCast(self.symtab.items(.nlist).len); |
| 540 | out_sect_id, | 527 | |
| 541 | ); | 528 | if (start == end) return; |
| 542 | if (!sect.isZerofill()) { | 529 | |
| 543 | try self.cacheRelocs(macho_file, atom_index); | 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 | }, | ||
| 544 | } | 574 | } |
| 545 | macho_file.addAtomToSection(atom_index); | 575 | try sf.stabs.append(gpa, stab); |
| 546 | } | 576 | } |
| 577 | |||
| 578 | try self.stab_files.append(gpa, sf); | ||
| 547 | } | 579 | } |
| 548 | } | 580 | } |
| 549 | 581 | ||
| 550 | fn createAtomFromSubsection( | 582 | fn sortAtoms(self: *Object, macho_file: *MachO) !void { |
| 551 | self: *Object, | 583 | const lessThanAtom = struct { |
| 552 | macho_file: *MachO, | 584 | fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool { |
| 553 | object_id: u32, | 585 | return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx); |
| 554 | sym_index: u32, | 586 | } |
| 555 | inner_sym_index: u32, | 587 | }.lessThanAtom; |
| 556 | inner_nsyms_trailing: u32, | 588 | mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom); |
| 557 | size: u64, | 589 | } |
| 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 | }); | ||
| 581 | 590 | ||
| 582 | try self.atoms.append(gpa, atom_index); | 591 | fn initRelocs(self: *Object, macho_file: *MachO) !void { |
| 583 | self.atom_by_index_table[sym_index] = atom_index; | 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 | } | ||
| 584 | 610 | ||
| 585 | var it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 611 | mem.sort(Relocation, out.items, {}, Relocation.lessThan); |
| 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; | ||
| 590 | } | 612 | } |
| 591 | 613 | ||
| 592 | const out_sect = macho_file.sections.items(.header)[out_sect_id]; | 614 | for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| { |
| 593 | if (out_sect.isCode() and | 615 | if (sect.isZerofill()) continue; |
| 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 | } | ||
| 600 | 616 | ||
| 601 | return atom_index; | 617 | var next_reloc: u32 = 0; |
| 602 | } | 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; | ||
| 603 | 624 | ||
| 604 | fn filterRelocs( | 625 | while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {} |
| 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, | ||
| 611 | 626 | ||
| 612 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | 627 | atom.relocs.len = next_reloc - atom.relocs.pos; |
| 613 | return rel.r_address >= self.addr; | ||
| 614 | } | 628 | } |
| 615 | }; | 629 | } |
| 616 | const LPredicate = struct { | 630 | } |
| 617 | addr: u64, | ||
| 618 | 631 | ||
| 619 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | 632 | fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { |
| 620 | return rel.r_address < self.addr; | 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 => {}, | ||
| 621 | } | 669 | } |
| 622 | }; | 670 | } |
| 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 }); | ||
| 626 | 671 | ||
| 627 | return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) }; | 672 | var it = eh_frame.Iterator{ .data = self.eh_frame_data.items }; |
| 628 | } | 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 | } | ||
| 629 | 688 | ||
| 630 | /// Parse all relocs for the input section, and sort in descending order. | 689 | for (self.cies.items) |*cie| { |
| 631 | /// Previously, I have wrongly assumed the compilers output relocations for each | 690 | try cie.parse(macho_file); |
| 632 | /// section in a sorted manner which is simply not true. | ||
| 633 | fn 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); | ||
| 640 | } | 691 | } |
| 641 | self.section_relocs_lookup.items[sect_id] = start; | ||
| 642 | } | ||
| 643 | 692 | ||
| 644 | fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void { | 693 | for (self.fdes.items) |*fde| { |
| 645 | const atom = macho_file.getAtom(atom_index); | 694 | try fde.parse(macho_file); |
| 646 | 695 | } | |
| 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 | } | ||
| 666 | 696 | ||
| 667 | fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { | 697 | const sortFn = struct { |
| 668 | _ = ctx; | 698 | fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool { |
| 669 | return lhs.r_address > rhs.r_address; | 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 | } | ||
| 670 | } | 723 | } |
| 671 | 724 | ||
| 672 | fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void { | 725 | fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { |
| 673 | const sect_id = self.eh_frame_sect_id orelse return; | 726 | const tracy = trace(@src()); |
| 674 | const sect = self.getSourceSection(sect_id); | 727 | defer tracy.end(); |
| 675 | 728 | ||
| 676 | log.debug("parsing __TEXT,__eh_frame section", .{}); | 729 | const SymbolLookup = struct { |
| 730 | ctx: *const Object, | ||
| 677 | 731 | ||
| 678 | const comp = macho_file.base.comp; | 732 | fn find(fs: @This(), addr: u64) ?Symbol.Index { |
| 679 | const gpa = comp.gpa; | 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 | }; | ||
| 680 | 740 | ||
| 681 | if (macho_file.eh_frame_section_index == null) { | 741 | const gpa = macho_file.base.comp.gpa; |
| 682 | macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{}); | 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 | } | ||
| 683 | } | 824 | } |
| 825 | } | ||
| 684 | 826 | ||
| 685 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 827 | fn parseUnwindRecords(self: *Object, macho_file: *MachO) !void { |
| 686 | const cpu_arch = target.cpu.arch; | 828 | // Synthesise missing unwind records. |
| 687 | try self.parseRelocs(gpa, sect_id); | 829 | // The logic here is as follows: |
| 688 | const relocs = self.getRelocs(sect_id); | 830 | // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead |
| 689 | 831 | // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record | |
| 690 | var it = self.getEhFrameRecordsIterator(); | 832 | // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie |
| 691 | var record_count: u32 = 0; | 833 | // 4. if an atom doesn't have either, synthesise a null unwind info record |
| 692 | while (try it.next()) |_| { | 834 | |
| 693 | record_count += 1; | 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 | } | ||
| 694 | } | 854 | } |
| 695 | 855 | ||
| 696 | try self.eh_frame_relocs_lookup.ensureTotalCapacity(gpa, record_count); | 856 | for (self.unwind_records.items) |rec_index| { |
| 697 | try self.eh_frame_records_lookup.ensureUnusedCapacity(gpa, record_count); | 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 | } | ||
| 698 | 862 | ||
| 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 | } | ||
| 700 | 868 | ||
| 701 | while (try it.next()) |record| { | 869 | for (superposition.keys(), superposition.values()) |addr, meta| { |
| 702 | const offset = it.pos - record.getSize(); | 870 | if (meta.fde) |fde_index| { |
| 703 | const rel_pos: Entry = switch (cpu_arch) { | 871 | const fde = &self.fdes.items[fde_index]; |
| 704 | .aarch64 => filterRelocs(relocs, offset, offset + record.getSize()), | 872 | |
| 705 | .x86_64 => .{}, | 873 | if (meta.cu) |rec_index| { |
| 706 | else => unreachable, | 874 | const rec = macho_file.getUnwindRecord(rec_index); |
| 707 | }; | 875 | if (!rec.enc.isDwarf(macho_file)) { |
| 708 | self.eh_frame_relocs_lookup.putAssumeCapacityNoClobber(offset, .{ | 876 | // Mark FDE dead |
| 709 | .dead = false, | 877 | fde.alive = false; |
| 710 | .reloc = rel_pos, | 878 | } else { |
| 711 | }); | 879 | // Tie FDE to unwind record |
| 712 | 880 | rec.fde = fde_index; | |
| 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, | ||
| 745 | } | 881 | } |
| 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; | ||
| 750 | } else { | 882 | } else { |
| 751 | // You would think that we are done but turns out that the compilers may use | 883 | // Synthesise new unwind info record |
| 752 | // whichever symbol alias they want for a target symbol. This in particular | 884 | const rec_index = try macho_file.addUnwindRecord(); |
| 753 | // very problematic when using Zig's @export feature to re-export symbols under | 885 | const rec = macho_file.getUnwindRecord(rec_index); |
| 754 | // additional names. For that reason, we need to ensure we record aliases here | 886 | try self.unwind_records.append(gpa, rec_index); |
| 755 | // too so that we can tie them with their matching unwind records and vice versa. | 887 | rec.length = @intCast(meta.size); |
| 756 | const aliases = self.getSymbolAliases(reloc_target.sym_index); | 888 | rec.atom = fde.atom; |
| 757 | var i: u32 = 0; | 889 | rec.atom_offset = fde.atom_offset; |
| 758 | while (i < aliases.len) : (i += 1) { | 890 | rec.fde = fde_index; |
| 759 | const actual_target = SymbolWithLoc{ | 891 | rec.file = fde.file; |
| 760 | .sym_index = i + aliases.start, | 892 | switch (macho_file.getTarget().cpu.arch) { |
| 761 | .file = reloc_target.file, | 893 | .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF), |
| 762 | }; | 894 | .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF), |
| 763 | log.debug("FDE at offset {x} tracks {s}", .{ | 895 | else => unreachable, |
| 764 | offset, | ||
| 765 | macho_file.getSymbolName(actual_target), | ||
| 766 | }); | ||
| 767 | try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset); | ||
| 768 | } | 896 | } |
| 769 | } | 897 | } |
| 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; | ||
| 770 | } | 908 | } |
| 771 | } | 909 | } |
| 772 | } | ||
| 773 | 910 | ||
| 774 | fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void { | 911 | const sortFn = struct { |
| 775 | const comp = macho_file.base.comp; | 912 | fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool { |
| 776 | const gpa = comp.gpa; | 913 | const lhs = ctx.getUnwindRecord(lhs_index); |
| 777 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 914 | const rhs = ctx.getUnwindRecord(rhs_index); |
| 778 | const cpu_arch = target.cpu.arch; | 915 | const lhsa = lhs.getAtom(ctx); |
| 779 | const sect_id = self.unwind_info_sect_id orelse { | 916 | const rhsa = rhs.getAtom(ctx); |
| 780 | // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`, | 917 | return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset; |
| 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 | } | ||
| 792 | } | 918 | } |
| 793 | return; | 919 | }.sortFn; |
| 794 | }; | 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 | } | ||
| 795 | 936 | ||
| 796 | log.debug("parsing unwind info in {s}", .{self.name}); | 937 | fn 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 | } | ||
| 797 | 954 | ||
| 798 | if (macho_file.unwind_info_section_index == null) { | 955 | /// Currently, we only check if a compile unit for this input object file exists |
| 799 | macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{}); | 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. | ||
| 959 | fn 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; | ||
| 800 | } | 974 | } |
| 801 | 975 | ||
| 802 | const unwind_records = self.getUnwindRecords(); | 976 | if (debug_info_index == null or debug_abbrev_index == null) return; |
| 803 | 977 | ||
| 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 | } | ||
| 805 | 989 | ||
| 806 | const needs_eh_frame = for (unwind_records) |record| { | 990 | pub fn resolveSymbols(self: *Object, macho_file: *MachO) void { |
| 807 | if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true; | 991 | const tracy = trace(@src()); |
| 808 | } else false; | 992 | defer tracy.end(); |
| 809 | 993 | ||
| 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]; | ||
| 811 | 998 | ||
| 812 | try self.parseRelocs(gpa, sect_id); | 999 | if (!nlist.ext()) continue; |
| 813 | const relocs = self.getRelocs(sect_id); | 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 | } | ||
| 814 | 1005 | ||
| 815 | for (unwind_records, 0..) |record, record_id| { | 1006 | const symbol = macho_file.getSymbol(index); |
| 816 | const offset = record_id * @sizeOf(macho.compact_unwind_entry); | 1007 | if (self.asFile().getSymbolRank(.{ |
| 817 | const rel_pos = filterRelocs( | 1008 | .archive = !self.alive, |
| 818 | relocs, | 1009 | .weak = nlist.weakDef(), |
| 819 | offset, | 1010 | .tentative = nlist.tentative(), |
| 820 | offset + @sizeOf(macho.compact_unwind_entry), | 1011 | }) < symbol.getSymbolRank(macho_file)) { |
| 821 | ); | 1012 | const value = if (nlist.sect()) blk: { |
| 822 | assert(rel_pos.len > 0); // TODO convert to an error as the unwind info is malformed | 1013 | const atom = macho_file.getAtom(atom_index).?; |
| 823 | self.unwind_relocs_lookup[record_id] = .{ | 1014 | break :blk nlist.n_value - atom.getInputAddress(macho_file); |
| 824 | .dead = false, | 1015 | } else nlist.n_value; |
| 825 | .reloc = rel_pos, | 1016 | symbol.value = value; |
| 826 | }; | 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 | } | ||
| 827 | 1035 | ||
| 828 | // Find function symbol that this record describes | 1036 | // Regardless of who the winner is, we still merge symbol visibility here. |
| 829 | const rel = relocs[rel_pos.start..][rel_pos.len - 1]; | 1037 | if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) { |
| 830 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 1038 | if (symbol.visibility != .global) { |
| 831 | .object_id = object_id, | 1039 | symbol.visibility = .hidden; |
| 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)); | ||
| 857 | } | 1040 | } |
| 1041 | } else { | ||
| 1042 | symbol.visibility = .global; | ||
| 858 | } | 1043 | } |
| 859 | } | 1044 | } |
| 860 | } | 1045 | } |
| 861 | 1046 | ||
| 862 | pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 { | 1047 | pub fn resetGlobals(self: *Object, macho_file: *MachO) void { |
| 863 | const symtab = self.in_symtab.?; | 1048 | for (self.symbols.items, 0..) |sym_index, nlist_idx| { |
| 864 | if (index >= symtab.len) return null; | 1049 | if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue; |
| 865 | const mapped_index = self.source_symtab_lookup[index]; | 1050 | const sym = macho_file.getSymbol(sym_index); |
| 866 | return symtab[mapped_index]; | 1051 | const name = sym.name; |
| 1052 | sym.* = .{}; | ||
| 1053 | sym.name = name; | ||
| 1054 | } | ||
| 867 | } | 1055 | } |
| 868 | 1056 | ||
| 869 | pub fn getSourceSection(self: Object, index: u8) macho.section_64 { | 1057 | pub fn markLive(self: *Object, macho_file: *MachO) void { |
| 870 | const sections = self.getSourceSections(); | 1058 | const tracy = trace(@src()); |
| 871 | assert(index < sections.len); | 1059 | defer tracy.end(); |
| 872 | return sections[index]; | 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 | } | ||
| 873 | } | 1073 | } |
| 874 | 1074 | ||
| 875 | pub fn getSourceSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 { | 1075 | pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void { |
| 876 | const index = self.getSourceSectionIndexByName(segname, sectname) orelse return null; | 1076 | for (self.symbols.items, 0..) |index, nlist_idx| { |
| 877 | const sections = self.getSourceSections(); | 1077 | const sym = macho_file.getSymbol(index); |
| 878 | return sections[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 | } | ||
| 879 | } | 1091 | } |
| 880 | 1092 | ||
| 881 | pub fn getSourceSectionIndexByName(self: Object, segname: []const u8, sectname: []const u8) ?u8 { | 1093 | pub fn scanRelocs(self: Object, macho_file: *MachO) !void { |
| 882 | const sections = self.getSourceSections(); | 1094 | const tracy = trace(@src()); |
| 883 | for (sections, 0..) |sect, i| { | 1095 | defer tracy.end(); |
| 884 | if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName())) | 1096 | |
| 885 | return @as(u8, @intCast(i)); | 1097 | for (self.atoms.items) |atom_index| { |
| 886 | } else return null; | 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 | } | ||
| 887 | } | 1116 | } |
| 888 | 1117 | ||
| 889 | pub fn getSourceSections(self: Object) []align(1) const macho.section_64 { | 1118 | pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { |
| 890 | var it = LoadCommandIterator{ | 1119 | const tracy = trace(@src()); |
| 891 | .ncmds = self.header.ncmds, | 1120 | defer tracy.end(); |
| 892 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1121 | const gpa = macho_file.base.comp.gpa; |
| 893 | }; | 1122 | |
| 894 | while (it.next()) |cmd| switch (cmd.cmd()) { | 1123 | for (self.symbols.items, 0..) |index, i| { |
| 895 | .SEGMENT_64 => { | 1124 | const sym = macho_file.getSymbol(index); |
| 896 | return cmd.getSections(); | 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 | |||
| 1167 | fn 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), | ||
| 897 | }, | 1173 | }, |
| 898 | else => {}, | 1174 | }); |
| 899 | } else unreachable; | 1175 | return n_sect; |
| 900 | } | 1176 | } |
| 901 | 1177 | ||
| 902 | pub fn parseDataInCode(self: *Object, gpa: Allocator) !void { | 1178 | pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { |
| 903 | var it = LoadCommandIterator{ | 1179 | const tracy = trace(@src()); |
| 904 | .ncmds = self.header.ncmds, | 1180 | defer tracy.end(); |
| 905 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1181 | |
| 906 | }; | 1182 | for (self.symbols.items) |sym_index| { |
| 907 | const cmd = while (it.next()) |cmd| { | 1183 | const sym = macho_file.getSymbol(sym_index); |
| 908 | switch (cmd.cmd()) { | 1184 | const file = sym.getFile(macho_file) orelse continue; |
| 909 | .DATA_IN_CODE => break cmd.cast(macho.linkedit_data_command).?, | 1185 | if (file.getIndex() != self.index) continue; |
| 910 | else => {}, | 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; | ||
| 911 | } | 1203 | } |
| 912 | } else return; | 1204 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); |
| 913 | const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry)); | 1205 | } |
| 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 | } | ||
| 919 | 1206 | ||
| 920 | fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool { | 1207 | if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) |
| 921 | _ = ctx; | 1208 | try self.calcStabsSize(macho_file); |
| 922 | return lhs.offset < rhs.offset; | ||
| 923 | } | 1209 | } |
| 924 | 1210 | ||
| 925 | fn getDysymtab(self: Object) ?macho.dysymtab_command { | 1211 | pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void { |
| 926 | var it = LoadCommandIterator{ | 1212 | if (self.dwarf_info) |dw| { |
| 927 | .ncmds = self.header.ncmds, | 1213 | // TODO handle multiple CUs |
| 928 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1214 | const cu = dw.compile_units.items[0]; |
| 929 | }; | 1215 | const comp_dir = try cu.getCompileDir(dw) orelse return; |
| 930 | while (it.next()) |cmd| { | 1216 | const tu_name = try cu.getSourceFile(dw) orelse return; |
| 931 | switch (cmd.cmd()) { | 1217 | |
| 932 | .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?, | 1218 | self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO |
| 933 | else => {}, | 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)); | ||
| 934 | } | 1226 | } |
| 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 | } | ||
| 936 | } | 1269 | } |
| 937 | 1270 | ||
| 938 | pub fn parseDwarfInfo(self: Object) DwarfInfo { | 1271 | pub fn writeSymtab(self: Object, macho_file: *MachO) error{Overflow}!void { |
| 939 | var di = DwarfInfo{ | 1272 | const tracy = trace(@src()); |
| 940 | .debug_info = &[0]u8{}, | 1273 | defer tracy.end(); |
| 941 | .debug_abbrev = &[0]u8{}, | 1274 | |
| 942 | .debug_str = &[0]u8{}, | 1275 | for (self.symbols.items) |sym_index| { |
| 943 | }; | 1276 | const sym = macho_file.getSymbol(sym_index); |
| 944 | for (self.getSourceSections()) |sect| { | 1277 | const file = sym.getFile(macho_file) orelse continue; |
| 945 | if (!sect.isDebug()) continue; | 1278 | if (file.getIndex() != self.index) continue; |
| 946 | const sectname = sect.sectName(); | 1279 | const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; |
| 947 | if (mem.eql(u8, sectname, "__debug_info")) { | 1280 | const n_strx = @as(u32, @intCast(macho_file.strtab.items.len)); |
| 948 | di.debug_info = self.getSectionContents(sect); | 1281 | macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file)); |
| 949 | } else if (mem.eql(u8, sectname, "__debug_abbrev")) { | 1282 | macho_file.strtab.appendAssumeCapacity(0); |
| 950 | di.debug_abbrev = self.getSectionContents(sect); | 1283 | const out_sym = &macho_file.symtab.items[idx]; |
| 951 | } else if (mem.eql(u8, sectname, "__debug_str")) { | 1284 | out_sym.n_strx = n_strx; |
| 952 | di.debug_str = self.getSectionContents(sect); | 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 | |||
| 1292 | pub 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; | ||
| 953 | } | 1531 | } |
| 954 | } | 1532 | } |
| 955 | return di; | ||
| 956 | } | 1533 | } |
| 957 | 1534 | ||
| 958 | /// Returns Platform composed from the first encountered build version type load command: | 1535 | fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand { |
| 959 | /// either LC_BUILD_VERSION or LC_VERSION_MIN_*. | ||
| 960 | pub fn getPlatform(self: Object) ?Platform { | ||
| 961 | var it = LoadCommandIterator{ | 1536 | var it = LoadCommandIterator{ |
| 962 | .ncmds = self.header.ncmds, | 1537 | .ncmds = self.header.?.ncmds, |
| 963 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], | 1538 | .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], |
| 964 | }; | 1539 | }; |
| 965 | while (it.next()) |cmd| { | 1540 | while (it.next()) |cmd| { |
| 966 | switch (cmd.cmd()) { | 1541 | if (cmd.cmd() == lc) return 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 | } | ||
| 975 | } else return null; | 1542 | } else return null; |
| 976 | } | 1543 | } |
| 977 | 1544 | ||
| 978 | pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 { | 1545 | pub fn getSectionData(self: *const Object, index: u32) error{Overflow}![]const u8 { |
| 979 | const size = @as(usize, @intCast(sect.size)); | 1546 | const slice = self.sections.slice(); |
| 980 | return self.contents[sect.offset..][0..size]; | 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 | |||
| 1554 | pub 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]; | ||
| 981 | } | 1559 | } |
| 982 | 1560 | ||
| 983 | pub fn getSectionAliasSymbolIndex(self: Object, sect_id: u8) u32 { | 1561 | pub fn getAtomRelocs(self: *const Object, atom: Atom) []const Relocation { |
| 984 | const start = @as(u32, @intCast(self.in_symtab.?.len)); | 1562 | const relocs = self.sections.items(.relocs)[atom.n_sect]; |
| 985 | return start + sect_id; | 1563 | return relocs.items[atom.relocs.pos..][0..atom.relocs.len]; |
| 986 | } | 1564 | } |
| 987 | 1565 | ||
| 988 | pub fn getSectionAliasSymbol(self: *Object, sect_id: u8) macho.nlist_64 { | 1566 | fn getString(self: Object, off: u32) [:0]const u8 { |
| 989 | return self.symtab[self.getSectionAliasSymbolIndex(sect_id)]; | 1567 | assert(off < self.strtab.len); |
| 1568 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0); | ||
| 990 | } | 1569 | } |
| 991 | 1570 | ||
| 992 | pub fn getSectionAliasSymbolPtr(self: *Object, sect_id: u8) *macho.nlist_64 { | 1571 | pub fn hasUnwindRecords(self: Object) bool { |
| 993 | return &self.symtab[self.getSectionAliasSymbolIndex(sect_id)]; | 1572 | return self.unwind_records.items.len > 0; |
| 994 | } | 1573 | } |
| 995 | 1574 | ||
| 996 | fn getSourceRelocs(self: Object, sect: macho.section_64) ?[]align(1) const macho.relocation_info { | 1575 | pub fn hasEhFrameRecords(self: Object) bool { |
| 997 | if (sect.nreloc == 0) return null; | 1576 | return self.cies.items.len > 0; |
| 998 | return @as([*]align(1) const macho.relocation_info, @ptrCast(self.contents.ptr + sect.reloff))[0..sect.nreloc]; | ||
| 999 | } | 1577 | } |
| 1000 | 1578 | ||
| 1001 | pub fn getRelocs(self: Object, sect_id: u8) []const macho.relocation_info { | 1579 | /// TODO handle multiple CUs |
| 1002 | const sect = self.getSourceSection(sect_id); | 1580 | pub fn hasDebugInfo(self: Object) bool { |
| 1003 | const start = self.section_relocs_lookup.items[sect_id]; | 1581 | if (self.dwarf_info) |dw| { |
| 1004 | const len = sect.nreloc; | 1582 | return dw.compile_units.items.len > 0; |
| 1005 | return self.relocations.items[start..][0..len]; | 1583 | } |
| 1584 | return self.hasSymbolStabs(); | ||
| 1006 | } | 1585 | } |
| 1007 | 1586 | ||
| 1008 | pub fn getSymbolName(self: Object, index: u32) []const u8 { | 1587 | fn hasSymbolStabs(self: Object) bool { |
| 1009 | const strtab = self.in_strtab.?; | 1588 | return self.stab_files.items.len > 0; |
| 1010 | const sym = self.symtab[index]; | 1589 | } |
| 1011 | 1590 | ||
| 1012 | if (self.getSourceSymbol(index) == null) { | 1591 | pub fn hasObjc(self: Object) bool { |
| 1013 | assert(sym.n_strx == 0); | 1592 | for (self.symtab.items(.nlist)) |nlist| { |
| 1014 | return ""; | 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; | ||
| 1015 | } | 1599 | } |
| 1600 | return false; | ||
| 1601 | } | ||
| 1016 | 1602 | ||
| 1017 | const start = sym.n_strx; | 1603 | pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry { |
| 1018 | const len = self.strtab_lookup[index]; | 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 | } | ||
| 1019 | 1613 | ||
| 1020 | return strtab[start..][0 .. len - 1 :0]; | 1614 | pub inline fn hasSubsections(self: Object) bool { |
| 1615 | return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; | ||
| 1021 | } | 1616 | } |
| 1022 | 1617 | ||
| 1023 | fn getSymbolAliases(self: Object, index: u32) Entry { | 1618 | pub fn asFile(self: *Object) File { |
| 1024 | const addr = self.source_address_lookup[index]; | 1619 | return .{ .object = self }; |
| 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 }; | ||
| 1033 | } | 1620 | } |
| 1034 | 1621 | ||
| 1035 | pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 { | 1622 | pub fn format( |
| 1036 | // Find containing atom | 1623 | self: *Object, |
| 1037 | const Predicate = struct { | 1624 | comptime unused_fmt_string: []const u8, |
| 1038 | addr: i64, | 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 | } | ||
| 1039 | 1634 | ||
| 1040 | pub fn predicate(pred: @This(), other: i64) bool { | 1635 | const FormatContext = struct { |
| 1041 | return if (other == -1) true else other > pred.addr; | 1636 | object: *Object, |
| 1042 | } | 1637 | macho_file: *MachO, |
| 1043 | }; | 1638 | }; |
| 1044 | 1639 | ||
| 1045 | if (sect_hint) |sect_id| { | 1640 | pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatAtoms) { |
| 1046 | if (self.source_section_index_lookup[sect_id].len > 0) { | 1641 | return .{ .data = .{ |
| 1047 | const lookup = self.source_section_index_lookup[sect_id]; | 1642 | .object = self, |
| 1048 | const target_sym_index = MachO.lsearch( | 1643 | .macho_file = macho_file, |
| 1049 | i64, | 1644 | } }; |
| 1050 | self.source_address_lookup[lookup.start..][0..lookup.len], | 1645 | } |
| 1051 | Predicate{ .addr = @as(i64, @intCast(addr)) }, | 1646 | |
| 1052 | ); | 1647 | fn formatAtoms( |
| 1053 | if (target_sym_index > 0) { | 1648 | ctx: FormatContext, |
| 1054 | // Hone in on the most senior alias of the target symbol. | 1649 | comptime unused_fmt_string: []const u8, |
| 1055 | // See SymbolAtIndex.lessThan for more context. | 1650 | options: std.fmt.FormatOptions, |
| 1056 | const aliases = self.getSymbolAliases(@intCast(lookup.start + target_sym_index - 1)); | 1651 | writer: anytype, |
| 1057 | return aliases.start; | 1652 | ) !void { |
| 1058 | } | 1653 | _ = unused_fmt_string; |
| 1059 | } | 1654 | _ = options; |
| 1060 | return self.getSectionAliasSymbolIndex(sect_id); | 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)}); | ||
| 1061 | } | 1660 | } |
| 1661 | } | ||
| 1062 | 1662 | ||
| 1063 | const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{ | 1663 | pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatCies) { |
| 1064 | .addr = @as(i64, @intCast(addr)), | 1664 | return .{ .data = .{ |
| 1065 | }); | 1665 | .object = self, |
| 1066 | assert(target_sym_index > 0); | 1666 | .macho_file = macho_file, |
| 1067 | return @as(u32, @intCast(target_sym_index - 1)); | 1667 | } }; |
| 1068 | } | 1668 | } |
| 1069 | 1669 | ||
| 1070 | pub fn getGlobal(self: Object, sym_index: u32) ?u32 { | 1670 | fn formatCies( |
| 1071 | if (self.globals_lookup[sym_index] == -1) return null; | 1671 | ctx: FormatContext, |
| 1072 | return @as(u32, @intCast(self.globals_lookup[sym_index])); | 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 | } | ||
| 1073 | } | 1683 | } |
| 1074 | 1684 | ||
| 1075 | pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index { | 1685 | pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatFdes) { |
| 1076 | return self.atom_by_index_table[sym_index]; | 1686 | return .{ .data = .{ |
| 1687 | .object = self, | ||
| 1688 | .macho_file = macho_file, | ||
| 1689 | } }; | ||
| 1077 | } | 1690 | } |
| 1078 | 1691 | ||
| 1079 | pub fn hasUnwindRecords(self: Object) bool { | 1692 | fn formatFdes( |
| 1080 | return self.unwind_info_sect_id != null; | 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 | } | ||
| 1081 | } | 1705 | } |
| 1082 | 1706 | ||
| 1083 | pub fn getUnwindRecords(self: Object) []align(1) const macho.compact_unwind_entry { | 1707 | pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatUnwindRecords) { |
| 1084 | const sect_id = self.unwind_info_sect_id orelse return &[0]macho.compact_unwind_entry{}; | 1708 | return .{ .data = .{ |
| 1085 | const sect = self.getSourceSection(sect_id); | 1709 | .object = self, |
| 1086 | const data = self.getSectionContents(sect); | 1710 | .macho_file = macho_file, |
| 1087 | const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry)); | 1711 | } }; |
| 1088 | return @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries]; | ||
| 1089 | } | 1712 | } |
| 1090 | 1713 | ||
| 1091 | pub fn hasEhFrameRecords(self: Object) bool { | 1714 | fn formatUnwindRecords( |
| 1092 | return self.eh_frame_sect_id != null; | 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 | } | ||
| 1093 | } | 1728 | } |
| 1094 | 1729 | ||
| 1095 | pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator { | 1730 | pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatSymtab) { |
| 1096 | const sect_id = self.eh_frame_sect_id orelse return .{ .data = &[0]u8{} }; | 1731 | return .{ .data = .{ |
| 1097 | const sect = self.getSourceSection(sect_id); | 1732 | .object = self, |
| 1098 | const data = self.getSectionContents(sect); | 1733 | .macho_file = macho_file, |
| 1099 | return .{ .data = data }; | 1734 | } }; |
| 1100 | } | 1735 | } |
| 1101 | 1736 | ||
| 1102 | pub fn hasDataInCode(self: Object) bool { | 1737 | fn formatSymtab( |
| 1103 | return self.data_in_code.items.len > 0; | 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 | } | ||
| 1104 | } | 1751 | } |
| 1105 | 1752 | ||
| 1106 | const Object = @This(); | 1753 | pub fn fmtPath(self: Object) std.fmt.Formatter(formatPath) { |
| 1754 | return .{ .data = self }; | ||
| 1755 | } | ||
| 1756 | |||
| 1757 | fn 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 | |||
| 1773 | const Section = struct { | ||
| 1774 | header: macho.section_64, | ||
| 1775 | subsections: std.ArrayListUnmanaged(Subsection) = .{}, | ||
| 1776 | relocs: std.ArrayListUnmanaged(Relocation) = .{}, | ||
| 1777 | }; | ||
| 1778 | |||
| 1779 | const Subsection = struct { | ||
| 1780 | atom: Atom.Index, | ||
| 1781 | off: u64, | ||
| 1782 | }; | ||
| 1783 | |||
| 1784 | pub const Nlist = struct { | ||
| 1785 | nlist: macho.nlist_64, | ||
| 1786 | size: u64, | ||
| 1787 | atom: Atom.Index, | ||
| 1788 | }; | ||
| 1789 | |||
| 1790 | const 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 | |||
| 1824 | const 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 | |||
| 1980 | const 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 | }; | ||
| 1107 | 2162 | ||
| 1108 | const std = @import("std"); | ||
| 1109 | const build_options = @import("build_options"); | ||
| 1110 | const assert = std.debug.assert; | 2163 | const assert = std.debug.assert; |
| 1111 | const dwarf = std.dwarf; | ||
| 1112 | const eh_frame = @import("eh_frame.zig"); | 2164 | const eh_frame = @import("eh_frame.zig"); |
| 1113 | const fs = std.fs; | ||
| 1114 | const io = std.io; | ||
| 1115 | const log = std.log.scoped(.link); | 2165 | const log = std.log.scoped(.link); |
| 1116 | const macho = std.macho; | 2166 | const macho = std.macho; |
| 1117 | const math = std.math; | 2167 | const math = std.math; |
| 1118 | const mem = std.mem; | 2168 | const mem = std.mem; |
| 1119 | const sort = std.sort; | ||
| 1120 | const trace = @import("../../tracy.zig").trace; | 2169 | const trace = @import("../../tracy.zig").trace; |
| 2170 | const std = @import("std"); | ||
| 1121 | 2171 | ||
| 1122 | const Allocator = mem.Allocator; | 2172 | const Allocator = mem.Allocator; |
| 1123 | const Atom = @import("Atom.zig"); | 2173 | const Atom = @import("Atom.zig"); |
| 2174 | const Cie = eh_frame.Cie; | ||
| 1124 | const DwarfInfo = @import("DwarfInfo.zig"); | 2175 | const DwarfInfo = @import("DwarfInfo.zig"); |
| 2176 | const Fde = eh_frame.Fde; | ||
| 2177 | const File = @import("file.zig").File; | ||
| 1125 | const LoadCommandIterator = macho.LoadCommandIterator; | 2178 | const LoadCommandIterator = macho.LoadCommandIterator; |
| 1126 | const MachO = @import("../MachO.zig"); | 2179 | const MachO = @import("../MachO.zig"); |
| 1127 | const Platform = @import("load_commands.zig").Platform; | 2180 | const Object = @This(); |
| 1128 | const SymbolWithLoc = MachO.SymbolWithLoc; | 2181 | const Relocation = @import("Relocation.zig"); |
| 2182 | const Symbol = @import("Symbol.zig"); | ||
| 1129 | const UnwindInfo = @import("UnwindInfo.zig"); | 2183 | const UnwindInfo = @import("UnwindInfo.zig"); |
| 1130 | const Alignment = Atom.Alignment; |
src/link/MachO/Relocation.zig+99-223| ... | @@ -1,235 +1,69 @@ | ... | @@ -1,235 +1,69 @@ |
| 1 | //! Relocation used by the self-hosted backends to instruct the linker where and how to | 1 | tag: enum { @"extern", local }, |
| 2 | //! fixup the values when flushing the contents to file and/or memory. | ||
| 3 | |||
| 4 | type: Type, | ||
| 5 | target: SymbolWithLoc, | ||
| 6 | offset: u32, | 2 | offset: u32, |
| 3 | target: u32, | ||
| 7 | addend: i64, | 4 | addend: i64, |
| 8 | pcrel: bool, | 5 | type: Type, |
| 9 | length: u2, | 6 | meta: packed struct { |
| 10 | dirty: bool = true, | 7 | pcrel: bool, |
| 11 | 8 | has_subtractor: bool, | |
| 12 | pub const Type = enum { | 9 | length: u2, |
| 13 | // x86, x86_64 | 10 | symbolnum: u24, |
| 14 | /// RIP-relative displacement to a GOT pointer | 11 | }, |
| 15 | got, | 12 | |
| 16 | /// RIP-relative displacement | 13 | pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol { |
| 17 | signed, | 14 | assert(rel.tag == .@"extern"); |
| 18 | /// RIP-relative displacement to a TLV thunk | 15 | return macho_file.getSymbol(rel.target); |
| 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. | ||
| 41 | pub fn isResolvable(self: Relocation, macho_file: *MachO) bool { | ||
| 42 | _ = self.getTargetBaseAddress(macho_file) orelse return false; | ||
| 43 | return true; | ||
| 44 | } | 16 | } |
| 45 | 17 | ||
| 46 | pub fn isGotIndirection(self: Relocation) bool { | 18 | pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom { |
| 47 | return switch (self.type) { | 19 | assert(rel.tag == .local); |
| 48 | .got, .got_page, .got_pageoff => true, | 20 | return macho_file.getAtom(rel.target).?; |
| 49 | else => false, | ||
| 50 | }; | ||
| 51 | } | 21 | } |
| 52 | 22 | ||
| 53 | pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool { | 23 | pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 { |
| 54 | return switch (self.type) { | 24 | return switch (rel.tag) { |
| 55 | .branch => macho_file.getSymbol(self.target).undf(), | 25 | .local => rel.getTargetAtom(macho_file).value, |
| 56 | else => false, | 26 | .@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file), |
| 57 | }; | 27 | }; |
| 58 | } | 28 | } |
| 59 | 29 | ||
| 60 | pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 { | 30 | pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 { |
| 61 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 31 | return switch (rel.tag) { |
| 62 | if (self.isStubTrampoline(macho_file)) { | 32 | .local => 0, |
| 63 | const index = macho_file.stub_table.lookup.get(self.target) orelse return null; | 33 | .@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file), |
| 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 | |||
| 87 | pub 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, | ||
| 102 | }; | 34 | }; |
| 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 | |||
| 118 | fn 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 | |||
| 193 | fn 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 | } | ||
| 212 | } | 35 | } |
| 213 | 36 | ||
| 214 | pub inline fn isArithmeticOp(inst: *const [4]u8) bool { | 37 | pub fn getZigGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 { |
| 215 | const group_decode = @as(u5, @truncate(inst[3])); | 38 | return switch (rel.tag) { |
| 216 | return ((group_decode >> 2) == 4); | 39 | .local => 0, |
| 40 | .@"extern" => rel.getTargetSymbol(macho_file).getZigGotAddress(macho_file), | ||
| 41 | }; | ||
| 217 | } | 42 | } |
| 218 | 43 | ||
| 219 | pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 { | 44 | pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 { |
| 220 | const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction)); | 45 | const addend: i64 = switch (rel.type) { |
| 221 | return math.cast(i32, disp) orelse error.Overflow; | 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 | }; | ||
| 222 | } | 56 | } |
| 223 | 57 | ||
| 224 | pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 { | 58 | pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool { |
| 225 | const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)); | 59 | _ = ctx; |
| 226 | return math.cast(i28, disp) orelse error.Overflow; | 60 | return lhs.offset < rhs.offset; |
| 227 | } | 61 | } |
| 228 | 62 | ||
| 229 | pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 { | 63 | pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 { |
| 230 | const source_page = @as(i32, @intCast(source_addr >> 12)); | 64 | const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow; |
| 231 | const target_page = @as(i32, @intCast(target_addr >> 12)); | 65 | const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow; |
| 232 | const pages = @as(i21, @intCast(target_page - source_page)); | 66 | const pages = math.cast(i21, tpage - spage) orelse return error.Overflow; |
| 233 | return pages; | 67 | return pages; |
| 234 | } | 68 | } |
| 235 | 69 | ||
| ... | @@ -242,8 +76,8 @@ pub const PageOffsetInstKind = enum { | ... | @@ -242,8 +76,8 @@ pub const PageOffsetInstKind = enum { |
| 242 | load_store_128, | 76 | load_store_128, |
| 243 | }; | 77 | }; |
| 244 | 78 | ||
| 245 | pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { | 79 | pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 { |
| 246 | const narrowed = @as(u12, @truncate(target_addr)); | 80 | const narrowed = @as(u12, @truncate(taddr)); |
| 247 | return switch (kind) { | 81 | return switch (kind) { |
| 248 | .arithmetic, .load_store_8 => narrowed, | 82 | .arithmetic, .load_store_8 => narrowed, |
| 249 | .load_store_16 => try math.divExact(u12, narrowed, 2), | 83 | .load_store_16 => try math.divExact(u12, narrowed, 2), |
| ... | @@ -253,17 +87,59 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { | ... | @@ -253,17 +87,59 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 { |
| 253 | }; | 87 | }; |
| 254 | } | 88 | } |
| 255 | 89 | ||
| 256 | const Relocation = @This(); | 90 | pub 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 | |||
| 95 | pub 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 | }; | ||
| 257 | 136 | ||
| 258 | const std = @import("std"); | ||
| 259 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 260 | const assert = std.debug.assert; | 137 | const assert = std.debug.assert; |
| 261 | const relocs_log = std.log.scoped(.link_relocs); | ||
| 262 | const macho = std.macho; | 138 | const macho = std.macho; |
| 263 | const math = std.math; | 139 | const math = std.math; |
| 264 | const mem = std.mem; | 140 | const std = @import("std"); |
| 265 | const meta = std.meta; | ||
| 266 | 141 | ||
| 267 | const Atom = @import("Atom.zig"); | 142 | const Atom = @import("Atom.zig"); |
| 268 | const MachO = @import("../MachO.zig"); | 143 | const MachO = @import("../MachO.zig"); |
| 269 | const SymbolWithLoc = MachO.SymbolWithLoc; | 144 | const Relocation = @This(); |
| 145 | const 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. | ||
| 4 | value: u64 = 0, | ||
| 5 | |||
| 6 | /// Offset into the linker's intern table. | ||
| 7 | name: u32 = 0, | ||
| 8 | |||
| 9 | /// File where this symbol is defined. | ||
| 10 | file: 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. | ||
| 15 | atom: Atom.Index = 0, | ||
| 16 | |||
| 17 | /// Assigned output section index for this atom. | ||
| 18 | out_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. | ||
| 22 | nlist_idx: Index = 0, | ||
| 23 | |||
| 24 | /// Misc flags for the symbol packaged as packed struct for compression. | ||
| 25 | flags: Flags = .{}, | ||
| 26 | |||
| 27 | visibility: Visibility = .local, | ||
| 28 | |||
| 29 | extra: u32 = 0, | ||
| 30 | |||
| 31 | pub fn isLocal(symbol: Symbol) bool { | ||
| 32 | return !(symbol.flags.import or symbol.flags.@"export"); | ||
| 33 | } | ||
| 34 | |||
| 35 | pub 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 | |||
| 43 | pub 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 | |||
| 48 | pub 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 | |||
| 57 | pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 { | ||
| 58 | return macho_file.strings.getAssumeExists(symbol.name); | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom { | ||
| 62 | return macho_file.getAtom(symbol.atom); | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File { | ||
| 66 | return macho_file.getFile(symbol.file); | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Asserts file is an object. | ||
| 70 | pub 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 | |||
| 78 | pub 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 | |||
| 84 | pub 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 | |||
| 93 | pub 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 | |||
| 106 | pub 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 | |||
| 120 | pub 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 | |||
| 126 | pub 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 | |||
| 132 | pub 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 | |||
| 138 | pub 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 | |||
| 146 | pub 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 | |||
| 152 | const GetOrCreateZigGotEntryResult = struct { | ||
| 153 | found_existing: bool, | ||
| 154 | index: ZigGotSection.Index, | ||
| 155 | }; | ||
| 156 | |||
| 157 | pub 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 | |||
| 165 | pub 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 | |||
| 171 | pub 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 | |||
| 190 | const 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 | |||
| 200 | pub 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 | |||
| 213 | pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra { | ||
| 214 | return macho_file.getSymbolExtra(symbol.extra); | ||
| 215 | } | ||
| 216 | |||
| 217 | pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void { | ||
| 218 | macho_file.setSymbolExtra(symbol.extra, extra); | ||
| 219 | } | ||
| 220 | |||
| 221 | pub 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 | |||
| 276 | pub 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 | |||
| 289 | const FormatContext = struct { | ||
| 290 | symbol: Symbol, | ||
| 291 | macho_file: *MachO, | ||
| 292 | }; | ||
| 293 | |||
| 294 | pub 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 | |||
| 301 | fn 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 | |||
| 337 | pub 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 | |||
| 389 | pub const Visibility = enum { | ||
| 390 | global, | ||
| 391 | hidden, | ||
| 392 | local, | ||
| 393 | }; | ||
| 394 | |||
| 395 | pub 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 | |||
| 405 | pub const Index = u32; | ||
| 406 | |||
| 407 | const assert = std.debug.assert; | ||
| 408 | const macho = std.macho; | ||
| 409 | const std = @import("std"); | ||
| 410 | |||
| 411 | const Atom = @import("Atom.zig"); | ||
| 412 | const File = @import("file.zig").File; | ||
| 413 | const MachO = @import("../MachO.zig"); | ||
| 414 | const Nlist = Object.Nlist; | ||
| 415 | const Object = @import("Object.zig"); | ||
| 416 | const Symbol = @This(); | ||
| 417 | const 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. | ||
| 32 | root: ?*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. | ||
| 39 | ordered_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. | ||
| 46 | size: u64 = 0, | ||
| 47 | |||
| 48 | /// Number of nodes currently in the trie. | ||
| 49 | node_count: usize = 0, | ||
| 50 | |||
| 51 | trie_dirty: bool = true, | ||
| 52 | |||
| 53 | /// Export symbol that is to be placed in the trie. | ||
| 54 | pub 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. | ||
| 69 | pub 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. | ||
| 82 | pub 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 | |||
| 114 | const ReadError = error{ | ||
| 115 | OutOfMemory, | ||
| 116 | EndOfStream, | ||
| 117 | Overflow, | ||
| 118 | }; | ||
| 119 | |||
| 120 | /// Parse the trie from a byte stream. | ||
| 121 | pub 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. | ||
| 127 | pub 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 | |||
| 136 | pub 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 | |||
| 144 | pub 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 | |||
| 152 | test "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 | |||
| 198 | test "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 | |||
| 249 | fn 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 | |||
| 264 | test "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 | |||
| 310 | test "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 | |||
| 341 | test "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 | |||
| 373 | pub 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 | |||
| 604 | const Trie = @This(); | ||
| 605 | |||
| 606 | const std = @import("std"); | ||
| 607 | const mem = std.mem; | ||
| 608 | const leb = std.leb; | ||
| 609 | const log = std.log.scoped(.link); | ||
| 610 | const macho = std.macho; | ||
| 611 | const testing = std.testing; | ||
| 612 | const assert = std.debug.assert; | ||
| 613 | const Allocator = mem.Allocator; | ||
src/link/MachO/UnwindInfo.zig+450-578| ... | @@ -1,376 +1,132 @@ | ... | @@ -1,376 +1,132 @@ |
| 1 | gpa: Allocator, | ||
| 2 | |||
| 3 | /// List of all unwind records gathered from all objects and sorted | 1 | /// List of all unwind records gathered from all objects and sorted |
| 4 | /// by source function address. | 2 | /// by allocated relative function address within the section. |
| 5 | records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{}, | 3 | records: std.ArrayListUnmanaged(Record.Index) = .{}, |
| 6 | records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{}, | ||
| 7 | 4 | ||
| 8 | /// List of all personalities referenced by either unwind info entries | 5 | /// List of all personalities referenced by either unwind info entries |
| 9 | /// or __eh_frame entries. | 6 | /// or __eh_frame entries. |
| 10 | personalities: [max_personalities]SymbolWithLoc = undefined, | 7 | personalities: [max_personalities]Symbol.Index = undefined, |
| 11 | personalities_count: u2 = 0, | 8 | personalities_count: u2 = 0, |
| 12 | 9 | ||
| 13 | /// List of common encodings sorted in descending order with the most common first. | 10 | /// List of common encodings sorted in descending order with the most common first. |
| 14 | common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined, | 11 | common_encodings: [max_common_encodings]Encoding = undefined, |
| 15 | common_encodings_count: u7 = 0, | 12 | common_encodings_count: u7 = 0, |
| 16 | 13 | ||
| 17 | /// List of record indexes containing an LSDA pointer. | 14 | /// List of record indexes containing an LSDA pointer. |
| 18 | lsdas: std.ArrayListUnmanaged(RecordIndex) = .{}, | 15 | lsdas: std.ArrayListUnmanaged(u32) = .{}, |
| 19 | lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{}, | 16 | lsdas_lookup: std.ArrayListUnmanaged(u32) = .{}, |
| 20 | 17 | ||
| 21 | /// List of second level pages. | 18 | /// List of second level pages. |
| 22 | pages: std.ArrayListUnmanaged(Page) = .{}, | 19 | pages: std.ArrayListUnmanaged(Page) = .{}, |
| 23 | 20 | ||
| 24 | /// Upper bound (exclusive) of all the record ranges | 21 | pub fn deinit(info: *UnwindInfo, allocator: Allocator) void { |
| 25 | end_boundary: u64 = 0, | 22 | info.records.deinit(allocator); |
| 26 | 23 | info.pages.deinit(allocator); | |
| 27 | const RecordIndex = u32; | 24 | info.lsdas.deinit(allocator); |
| 28 | 25 | info.lsdas_lookup.deinit(allocator); | |
| 29 | const max_personalities = 3; | 26 | } |
| 30 | const max_common_encodings = 127; | ||
| 31 | const max_compact_encodings = 256; | ||
| 32 | |||
| 33 | const second_level_page_bytes = 0x1000; | ||
| 34 | const second_level_page_words = second_level_page_bytes / @sizeOf(u32); | ||
| 35 | |||
| 36 | const 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 | |||
| 40 | const max_compressed_second_level_entries = | ||
| 41 | (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) / | ||
| 42 | @sizeOf(u32); | ||
| 43 | |||
| 44 | const compressed_entry_func_offset_mask = ~@as(u24, 0); | ||
| 45 | |||
| 46 | const 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 | }; | ||
| 175 | 27 | ||
| 176 | pub fn deinit(info: *UnwindInfo) void { | 28 | fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { |
| 177 | info.records.deinit(info.gpa); | 29 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 178 | info.records_lookup.deinit(info.gpa); | 30 | const lhs = macho_file.getUnwindRecord(lhs_index); |
| 179 | info.pages.deinit(info.gpa); | 31 | const rhs = macho_file.getUnwindRecord(rhs_index); |
| 180 | info.lsdas.deinit(info.gpa); | 32 | if (cpu_arch == .x86_64) { |
| 181 | info.lsdas_lookup.deinit(info.gpa); | 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; | ||
| 182 | } | 42 | } |
| 183 | 43 | ||
| 184 | pub fn scanRelocs(macho_file: *MachO) !void { | 44 | pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void { |
| 185 | if (macho_file.unwind_info_section_index == null) return; | 45 | const gpa = macho_file.base.comp.gpa; |
| 186 | 46 | ||
| 187 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 47 | log.debug("generating unwind info", .{}); |
| 188 | const cpu_arch = target.cpu.arch; | 48 | |
| 189 | for (macho_file.objects.items, 0..) |*object, object_id| { | 49 | // Collect all unwind records |
| 190 | const unwind_records = object.getUnwindRecords(); | 50 | for (macho_file.sections.items(.atoms)) |atoms| { |
| 191 | for (object.exec_atoms.items) |atom_index| { | 51 | for (atoms.items) |atom_index| { |
| 192 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 52 | const atom = macho_file.getAtom(atom_index) orelse continue; |
| 193 | while (inner_syms_it.next()) |sym| { | 53 | if (!atom.flags.alive) continue; |
| 194 | const record_id = object.unwind_records_lookup.get(sym) orelse continue; | 54 | const recs = atom.getUnwindRecords(macho_file); |
| 195 | if (object.unwind_relocs_lookup[record_id].dead) continue; | 55 | try info.records.ensureUnusedCapacity(gpa, recs.len); |
| 196 | const record = unwind_records[record_id]; | 56 | for (recs) |rec| { |
| 197 | if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { | 57 | if (!macho_file.getUnwindRecord(rec).alive) continue; |
| 198 | if (getPersonalityFunctionReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| { | 58 | info.records.appendAssumeCapacity(rec); |
| 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 | } | ||
| 209 | } | 59 | } |
| 210 | } | 60 | } |
| 211 | } | 61 | } |
| 212 | } | ||
| 213 | |||
| 214 | pub 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 | }; | ||
| 305 | 62 | ||
| 306 | const atom = macho_file.getAtom(atom_index); | 63 | // Encode records |
| 307 | const sym = macho_file.getSymbol(symbol); | 64 | for (info.records.items) |index| { |
| 308 | assert(sym.n_desc != MachO.N_DEAD); | 65 | const rec = macho_file.getUnwindRecord(index); |
| 309 | const size = if (inner_syms_it.next()) |next_sym| blk: { | 66 | if (rec.getFde(macho_file)) |fde| { |
| 310 | // All this trouble to account for symbol aliases. | 67 | rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset)); |
| 311 | // TODO I think that remodelling the linker so that a Symbol references an Atom | 68 | if (fde.getLsdaAtom(macho_file)) |lsda| { |
| 312 | // is the way to go, kinda like we do for ELF. We might also want to perhaps tag | 69 | rec.lsda = lsda.atom_index; |
| 313 | // symbol aliases somehow so that they are excluded from everything except relocation | 70 | rec.lsda_offset = fde.lsda_offset; |
| 314 | // resolution. | 71 | rec.enc.setHasLsda(true); |
| 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; | ||
| 328 | } | 72 | } |
| 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); | ||
| 329 | } | 81 | } |
| 330 | } | 82 | } |
| 331 | 83 | ||
| 332 | // Record the ending boundary before folding. | 84 | // Sort by assigned relative address within each output section |
| 333 | assert(records.items.len > 0); | 85 | const sortFn = struct { |
| 334 | info.end_boundary = blk: { | 86 | fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { |
| 335 | const last_record = records.items[records.items.len - 1]; | 87 | const lhs = ctx.getUnwindRecord(lhs_index); |
| 336 | break :blk last_record.rangeStart + last_record.rangeLength; | 88 | const rhs = ctx.getUnwindRecord(rhs_index); |
| 337 | }; | 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); | ||
| 338 | 96 | ||
| 339 | // Fold records | 97 | // Fold the records |
| 340 | try info.records.ensureTotalCapacity(info.gpa, records.items.len); | 98 | // Any adjacent two records that share encoding can be folded into one. |
| 341 | try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len))); | 99 | { |
| 342 | 100 | var i: usize = 0; | |
| 343 | var maybe_prev: ?macho.compact_unwind_entry = null; | 101 | var j: usize = 1; |
| 344 | for (records.items, 0..) |record, i| { | 102 | while (j < info.records.items.len) : (j += 1) { |
| 345 | const record_id = blk: { | 103 | if (canFold(macho_file, info.records.items[i], info.records.items[j])) { |
| 346 | if (maybe_prev) |prev| { | 104 | const rec = macho_file.getUnwindRecord(info.records.items[i]); |
| 347 | const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | 105 | rec.length += macho_file.getUnwindRecord(info.records.items[j]).length + 1; |
| 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 | } | ||
| 360 | } else { | 106 | } else { |
| 361 | const record_id = @as(RecordIndex, @intCast(info.records.items.len)); | 107 | i += 1; |
| 362 | info.records.appendAssumeCapacity(record); | 108 | info.records.items[i] = info.records.items[j]; |
| 363 | maybe_prev = record; | ||
| 364 | break :blk record_id; | ||
| 365 | } | 109 | } |
| 366 | }; | 110 | } |
| 367 | info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id); | 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 | }); | ||
| 368 | } | 124 | } |
| 369 | 125 | ||
| 370 | // Calculate common encodings | 126 | // Calculate common encodings |
| 371 | { | 127 | { |
| 372 | const CommonEncWithCount = struct { | 128 | const CommonEncWithCount = struct { |
| 373 | enc: macho.compact_unwind_encoding_t, | 129 | enc: Encoding, |
| 374 | count: u32, | 130 | count: u32, |
| 375 | 131 | ||
| 376 | fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool { | 132 | fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool { |
| ... | @@ -380,39 +136,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -380,39 +136,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 380 | }; | 136 | }; |
| 381 | 137 | ||
| 382 | const Context = struct { | 138 | 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 { |
| 384 | _ = ctx; | 140 | _ = ctx; |
| 385 | return key; | 141 | return key.enc; |
| 386 | } | 142 | } |
| 387 | 143 | ||
| 388 | pub fn eql( | 144 | pub fn eql( |
| 389 | ctx: @This(), | 145 | ctx: @This(), |
| 390 | key1: macho.compact_unwind_encoding_t, | 146 | key1: Encoding, |
| 391 | key2: macho.compact_unwind_encoding_t, | 147 | key2: Encoding, |
| 392 | b_index: usize, | 148 | b_index: usize, |
| 393 | ) bool { | 149 | ) bool { |
| 394 | _ = ctx; | 150 | _ = ctx; |
| 395 | _ = b_index; | 151 | _ = b_index; |
| 396 | return key1 == key2; | 152 | return key1.eql(key2); |
| 397 | } | 153 | } |
| 398 | }; | 154 | }; |
| 399 | 155 | ||
| 400 | var common_encodings_counts = std.ArrayHashMap( | 156 | var common_encodings_counts = std.ArrayHashMap( |
| 401 | macho.compact_unwind_encoding_t, | 157 | Encoding, |
| 402 | CommonEncWithCount, | 158 | CommonEncWithCount, |
| 403 | Context, | 159 | Context, |
| 404 | false, | 160 | false, |
| 405 | ).init(info.gpa); | 161 | ).init(gpa); |
| 406 | defer common_encodings_counts.deinit(); | 162 | defer common_encodings_counts.deinit(); |
| 407 | 163 | ||
| 408 | for (info.records.items) |record| { | 164 | for (info.records.items) |rec_index| { |
| 409 | assert(!isNull(record)); | 165 | const rec = macho_file.getUnwindRecord(rec_index); |
| 410 | if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue; | 166 | if (rec.enc.isDwarf(macho_file)) continue; |
| 411 | const enc = record.compactUnwindEncoding; | 167 | const gop = try common_encodings_counts.getOrPut(rec.enc); |
| 412 | const gop = try common_encodings_counts.getOrPut(enc); | ||
| 413 | if (!gop.found_existing) { | 168 | if (!gop.found_existing) { |
| 414 | gop.value_ptr.* = .{ | 169 | gop.value_ptr.* = .{ |
| 415 | .enc = enc, | 170 | .enc = rec.enc, |
| 416 | .count = 0, | 171 | .count = 0, |
| 417 | }; | 172 | }; |
| 418 | } | 173 | } |
| ... | @@ -427,7 +182,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -427,7 +182,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 427 | if (i >= max_common_encodings) break; | 182 | if (i >= max_common_encodings) break; |
| 428 | if (slice[i].count < 2) continue; | 183 | if (slice[i].count < 2) continue; |
| 429 | info.appendCommonEncoding(slice[i].enc); | 184 | 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 }); |
| 431 | } | 186 | } |
| 432 | } | 187 | } |
| 433 | 188 | ||
| ... | @@ -435,8 +190,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -435,8 +190,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 435 | { | 190 | { |
| 436 | var i: u32 = 0; | 191 | var i: u32 = 0; |
| 437 | while (i < info.records.items.len) { | 192 | while (i < info.records.items.len) { |
| 438 | const range_start_max: u64 = | 193 | const rec = macho_file.getUnwindRecord(info.records.items[i]); |
| 439 | info.records.items[i].rangeStart + compressed_entry_func_offset_mask; | 194 | const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask; |
| 440 | var encoding_count: u9 = info.common_encodings_count; | 195 | var encoding_count: u9 = info.common_encodings_count; |
| 441 | var space_left: u32 = second_level_page_words - | 196 | var space_left: u32 = second_level_page_words - |
| 442 | @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32); | 197 | @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32); |
| ... | @@ -447,19 +202,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -447,19 +202,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 447 | }; | 202 | }; |
| 448 | 203 | ||
| 449 | while (space_left >= 1 and i < info.records.items.len) { | 204 | while (space_left >= 1 and i < info.records.items.len) { |
| 450 | const record = info.records.items[i]; | 205 | const next = macho_file.getUnwindRecord(info.records.items[i]); |
| 451 | const enc = record.compactUnwindEncoding; | 206 | const is_dwarf = next.enc.isDwarf(macho_file); |
| 452 | const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | ||
| 453 | 207 | ||
| 454 | if (record.rangeStart >= range_start_max) { | 208 | if (next.getAtomAddress(macho_file) >= range_start_max) { |
| 455 | break; | 209 | break; |
| 456 | } else if (info.getCommonEncoding(enc) != null or | 210 | } else if (info.getCommonEncoding(next.enc) != null or |
| 457 | page.getPageEncoding(info, enc) != null and !is_dwarf) | 211 | page.getPageEncoding(next.enc) != null and !is_dwarf) |
| 458 | { | 212 | { |
| 459 | i += 1; | 213 | i += 1; |
| 460 | space_left -= 1; | 214 | space_left -= 1; |
| 461 | } else if (space_left >= 2 and encoding_count < max_compact_encodings) { | 215 | } else if (space_left >= 2 and encoding_count < max_compact_encodings) { |
| 462 | page.appendPageEncoding(i); | 216 | page.appendPageEncoding(next.enc); |
| 463 | i += 1; | 217 | i += 1; |
| 464 | space_left -= 2; | 218 | space_left -= 2; |
| 465 | encoding_count += 1; | 219 | encoding_count += 1; |
| ... | @@ -481,63 +235,26 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -481,63 +235,26 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void { |
| 481 | page.kind = .compressed; | 235 | page.kind = .compressed; |
| 482 | } | 236 | } |
| 483 | 237 | ||
| 484 | log.debug("{}", .{page.fmtDebug(info)}); | 238 | log.debug("{}", .{page.fmt(info.*)}); |
| 485 | 239 | ||
| 486 | try info.pages.append(info.gpa, page); | 240 | try info.pages.append(gpa, page); |
| 487 | } | 241 | } |
| 488 | } | 242 | } |
| 489 | 243 | ||
| 490 | // Save indices of records requiring LSDA relocation | 244 | // Save records having an LSDA pointer |
| 491 | try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(info.records.items.len))); | 245 | log.debug("LSDA pointers:", .{}); |
| 492 | for (info.records.items, 0..) |rec, i| { | 246 | try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len); |
| 493 | info.lsdas_lookup.putAssumeCapacityNoClobber(@as(RecordIndex, @intCast(i)), @as(u32, @intCast(info.lsdas.items.len))); | 247 | for (info.records.items, 0..) |index, i| { |
| 494 | if (rec.lsda == 0) continue; | 248 | const rec = macho_file.getUnwindRecord(index); |
| 495 | try info.lsdas.append(info.gpa, @as(RecordIndex, @intCast(i))); | 249 | info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len)); |
| 496 | } | 250 | if (rec.getLsdaAtom(macho_file)) |lsda| { |
| 497 | } | 251 | log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index }); |
| 498 | 252 | try info.lsdas.append(gpa, @intCast(i)); | |
| 499 | fn collectPersonalityFromDwarf( | 253 | } |
| 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); | ||
| 530 | } | 254 | } |
| 531 | } | 255 | } |
| 532 | 256 | ||
| 533 | pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void { | 257 | pub fn calcSize(info: UnwindInfo) usize { |
| 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 | |||
| 540 | fn calcRequiredSize(info: UnwindInfo) usize { | ||
| 541 | var total_size: usize = 0; | 258 | var total_size: usize = 0; |
| 542 | total_size += @sizeOf(macho.unwind_info_section_header); | 259 | total_size += @sizeOf(macho.unwind_info_section_header); |
| 543 | total_size += | 260 | total_size += |
| ... | @@ -549,59 +266,12 @@ fn calcRequiredSize(info: UnwindInfo) usize { | ... | @@ -549,59 +266,12 @@ fn calcRequiredSize(info: UnwindInfo) usize { |
| 549 | return total_size; | 266 | return total_size; |
| 550 | } | 267 | } |
| 551 | 268 | ||
| 552 | pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { | 269 | pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void { |
| 553 | const sect_id = macho_file.unwind_info_section_index orelse return; | 270 | const seg = macho_file.getTextSegment(); |
| 554 | const sect = &macho_file.sections.items(.header)[sect_id]; | 271 | const header = macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?]; |
| 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 | } | ||
| 597 | 272 | ||
| 598 | var buffer = std.ArrayList(u8).init(info.gpa); | 273 | var stream = std.io.fixedBufferStream(buffer); |
| 599 | defer buffer.deinit(); | 274 | var cwriter = std.io.countingWriter(stream.writer()); |
| 600 | |||
| 601 | const size = info.calcRequiredSize(); | ||
| 602 | try buffer.ensureTotalCapacityPrecise(size); | ||
| 603 | |||
| 604 | var cwriter = std.io.countingWriter(buffer.writer()); | ||
| 605 | const writer = cwriter.writer(); | 275 | const writer = cwriter.writer(); |
| 606 | 276 | ||
| 607 | const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header); | 277 | const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header); |
| ... | @@ -621,203 +291,403 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { | ... | @@ -621,203 +291,403 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void { |
| 621 | }); | 291 | }); |
| 622 | 292 | ||
| 623 | try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); | 293 | try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); |
| 624 | try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count])); | ||
| 625 | 294 | ||
| 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))); | ||
| 627 | const lsda_base_offset = @as(u32, @intCast(pages_base_offset - | 301 | const lsda_base_offset = @as(u32, @intCast(pages_base_offset - |
| 628 | (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry)))); | 302 | (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry)))); |
| 629 | for (info.pages.items, 0..) |page, i| { | 303 | for (info.pages.items, 0..) |page, i| { |
| 630 | assert(page.count > 0); | 304 | assert(page.count > 0); |
| 631 | const first_entry = info.records.items[page.start]; | 305 | const rec = macho_file.getUnwindRecord(info.records.items[page.start]); |
| 632 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ | 306 | 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)), |
| 634 | .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)), | 308 | .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)), |
| 635 | .lsdaIndexArraySectionOffset = lsda_base_offset + | 309 | .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), |
| 637 | }); | 311 | }); |
| 638 | } | 312 | } |
| 639 | 313 | ||
| 640 | // Relocate end boundary address | 314 | const last_rec = macho_file.getUnwindRecord(info.records.items[info.records.items.len - 1]); |
| 641 | const end_boundary = @as(u32, @intCast(info.end_boundary + text_sect.addr - seg.vmaddr)); | 315 | const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr)); |
| 642 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ | 316 | try writer.writeStruct(macho.unwind_info_section_header_index_entry{ |
| 643 | .functionOffset = end_boundary, | 317 | .functionOffset = sentinel_address, |
| 644 | .secondLevelPagesSectionOffset = 0, | 318 | .secondLevelPagesSectionOffset = 0, |
| 645 | .lsdaIndexArraySectionOffset = lsda_base_offset + | 319 | .lsdaIndexArraySectionOffset = lsda_base_offset + |
| 646 | @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), | 320 | @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), |
| 647 | }); | 321 | }); |
| 648 | 322 | ||
| 649 | for (info.lsdas.items) |record_id| { | 323 | for (info.lsdas.items) |index| { |
| 650 | const record = info.records.items[record_id]; | 324 | const rec = macho_file.getUnwindRecord(info.records.items[index]); |
| 651 | try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ | 325 | try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ |
| 652 | .functionOffset = @as(u32, @intCast(record.rangeStart)), | 326 | .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), |
| 653 | .lsdaOffset = @as(u32, @intCast(record.lsda)), | 327 | .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)), |
| 654 | }); | 328 | }); |
| 655 | } | 329 | } |
| 656 | 330 | ||
| 657 | for (info.pages.items) |page| { | 331 | for (info.pages.items) |page| { |
| 658 | const start = cwriter.bytes_written; | 332 | const start = cwriter.bytes_written; |
| 659 | try page.write(info, writer); | 333 | try page.write(info, macho_file, writer); |
| 660 | const nwritten = cwriter.bytes_written - start; | 334 | const nwritten = cwriter.bytes_written - start; |
| 661 | if (nwritten < second_level_page_bytes) { | 335 | if (nwritten < second_level_page_bytes) { |
| 662 | const offset = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow; | 336 | const padding = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow; |
| 663 | try writer.writeByteNTimes(0, offset); | 337 | try writer.writeByteNTimes(0, padding); |
| 664 | } | 338 | } |
| 665 | } | 339 | } |
| 666 | 340 | ||
| 667 | const padding = buffer.items.len - cwriter.bytes_written; | 341 | const padding = buffer.len - cwriter.bytes_written; |
| 668 | if (padding > 0) { | 342 | if (padding > 0) { |
| 669 | const offset = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow; | 343 | const off = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow; |
| 670 | @memset(buffer.items[offset..], 0); | 344 | @memset(buffer[off..], 0); |
| 671 | } | ||
| 672 | |||
| 673 | try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset); | ||
| 674 | } | ||
| 675 | |||
| 676 | fn 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 | |||
| 684 | fn 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 | |||
| 690 | pub 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; | ||
| 698 | } | 345 | } |
| 699 | return null; | ||
| 700 | } | 346 | } |
| 701 | 347 | ||
| 702 | fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 { | 348 | fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 { |
| 703 | comptime var index: u2 = 0; | 349 | comptime var index: u2 = 0; |
| 704 | inline while (index < max_personalities) : (index += 1) { | 350 | inline while (index < max_personalities) : (index += 1) { |
| 705 | if (index >= info.personalities_count) return null; | 351 | if (info.personalities[index] == sym_index) { |
| 706 | if (info.personalities[index].eql(global_index)) { | 352 | return index; |
| 353 | } else if (index == info.personalities_count) { | ||
| 354 | info.personalities[index] = sym_index; | ||
| 355 | info.personalities_count += 1; | ||
| 707 | return index; | 356 | return index; |
| 708 | } | 357 | } |
| 709 | } | 358 | } |
| 710 | return null; | 359 | return error.TooManyPersonalities; |
| 711 | } | ||
| 712 | |||
| 713 | fn 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; | ||
| 717 | } | 360 | } |
| 718 | 361 | ||
| 719 | pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info { | 362 | fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void { |
| 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 | |||
| 727 | pub 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 | |||
| 735 | inline 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 | |||
| 745 | fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void { | ||
| 746 | assert(info.common_encodings_count <= max_common_encodings); | 363 | assert(info.common_encodings_count <= max_common_encodings); |
| 747 | info.common_encodings[info.common_encodings_count] = enc; | 364 | info.common_encodings[info.common_encodings_count] = enc; |
| 748 | info.common_encodings_count += 1; | 365 | info.common_encodings_count += 1; |
| 749 | } | 366 | } |
| 750 | 367 | ||
| 751 | fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 { | 368 | fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 { |
| 752 | comptime var index: u7 = 0; | 369 | comptime var index: u7 = 0; |
| 753 | inline while (index < max_common_encodings) : (index += 1) { | 370 | inline while (index < max_common_encodings) : (index += 1) { |
| 754 | if (index >= info.common_encodings_count) return null; | 371 | if (index >= info.common_encodings_count) return null; |
| 755 | if (info.common_encodings[index] == enc) { | 372 | if (info.common_encodings[index].eql(enc)) { |
| 756 | return index; | 373 | return index; |
| 757 | } | 374 | } |
| 758 | } | 375 | } |
| 759 | return null; | 376 | return null; |
| 760 | } | 377 | } |
| 761 | 378 | ||
| 762 | pub const UnwindEncoding = struct { | 379 | pub const Encoding = extern struct { |
| 763 | pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 { | 380 | enc: macho.compact_unwind_encoding_t, |
| 381 | |||
| 382 | pub fn getMode(enc: Encoding) u4 { | ||
| 764 | comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK); | 383 | 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)); | ||
| 766 | } | 386 | } |
| 767 | 387 | ||
| 768 | pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool { | 388 | pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool { |
| 769 | const mode = getMode(enc); | 389 | const mode = enc.getMode(); |
| 770 | return switch (cpu_arch) { | 390 | return switch (macho_file.getTarget().cpu.arch) { |
| 771 | .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF, | 391 | .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF, |
| 772 | .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF, | 392 | .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF, |
| 773 | else => unreachable, | 393 | else => unreachable, |
| 774 | }; | 394 | }; |
| 775 | } | 395 | } |
| 776 | 396 | ||
| 777 | pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void { | 397 | pub fn setMode(enc: *Encoding, mode: anytype) void { |
| 778 | enc.* |= @as(u32, @intCast(@intFromEnum(mode))) << 24; | 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; | ||
| 779 | } | 401 | } |
| 780 | 402 | ||
| 781 | pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool { | 403 | pub fn hasLsda(enc: Encoding) bool { |
| 782 | const has_lsda = @as(u1, @truncate((enc & macho.UNWIND_HAS_LSDA) >> 31)); | 404 | const shift = comptime @ctz(macho.UNWIND_HAS_LSDA); |
| 405 | const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> shift)); | ||
| 783 | return has_lsda == 1; | 406 | return has_lsda == 1; |
| 784 | } | 407 | } |
| 785 | 408 | ||
| 786 | pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void { | 409 | pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void { |
| 787 | const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31; | 410 | const shift = comptime @ctz(macho.UNWIND_HAS_LSDA); |
| 788 | enc.* |= mask; | 411 | const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << shift; |
| 412 | enc.enc |= mask; | ||
| 789 | } | 413 | } |
| 790 | 414 | ||
| 791 | pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 { | 415 | pub fn getPersonalityIndex(enc: Encoding) u2 { |
| 792 | const index = @as(u2, @truncate((enc & macho.UNWIND_PERSONALITY_MASK) >> 28)); | 416 | const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK); |
| 417 | const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> shift)); | ||
| 793 | return index; | 418 | return index; |
| 794 | } | 419 | } |
| 795 | 420 | ||
| 796 | pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void { | 421 | pub fn setPersonalityIndex(enc: *Encoding, index: u2) void { |
| 797 | const mask = @as(u32, @intCast(index)) << 28; | 422 | const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK); |
| 798 | enc.* |= mask; | 423 | const mask = @as(u32, @intCast(index)) << shift; |
| 424 | enc.enc |= mask; | ||
| 799 | } | 425 | } |
| 800 | 426 | ||
| 801 | pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 { | 427 | pub fn getDwarfSectionOffset(enc: Encoding) u24 { |
| 802 | assert(isDwarf(enc, cpu_arch)); | 428 | const offset = @as(u24, @truncate(enc.enc)); |
| 803 | const offset = @as(u24, @truncate(enc)); | ||
| 804 | return offset; | 429 | return offset; |
| 805 | } | 430 | } |
| 806 | 431 | ||
| 807 | pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void { | 432 | pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void { |
| 808 | assert(isDwarf(enc.*, cpu_arch)); | 433 | enc.enc |= offset; |
| 809 | 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}); | ||
| 810 | } | 449 | } |
| 811 | }; | 450 | }; |
| 812 | 451 | ||
| 813 | const UnwindInfo = @This(); | 452 | pub 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 | |||
| 547 | const max_personalities = 3; | ||
| 548 | const max_common_encodings = 127; | ||
| 549 | const max_compact_encodings = 256; | ||
| 550 | |||
| 551 | const second_level_page_bytes = 0x1000; | ||
| 552 | const second_level_page_words = second_level_page_bytes / @sizeOf(u32); | ||
| 553 | |||
| 554 | const 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 | |||
| 558 | const max_compressed_second_level_entries = | ||
| 559 | (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) / | ||
| 560 | @sizeOf(u32); | ||
| 561 | |||
| 562 | const compressed_entry_func_offset_mask = ~@as(u24, 0); | ||
| 563 | |||
| 564 | const 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 | }; | ||
| 814 | 684 | ||
| 815 | const std = @import("std"); | 685 | const std = @import("std"); |
| 816 | const assert = std.debug.assert; | 686 | const assert = std.debug.assert; |
| 817 | const eh_frame = @import("eh_frame.zig"); | 687 | const eh_frame = @import("eh_frame.zig"); |
| 818 | const fs = std.fs; | 688 | const fs = std.fs; |
| 819 | const leb = std.leb; | 689 | const leb = std.leb; |
| 820 | const log = std.log.scoped(.unwind_info); | 690 | const log = std.log.scoped(.link); |
| 821 | const macho = std.macho; | 691 | const macho = std.macho; |
| 822 | const math = std.math; | 692 | const math = std.math; |
| 823 | const mem = std.mem; | 693 | const mem = std.mem; |
| ... | @@ -825,7 +695,9 @@ const trace = @import("../../tracy.zig").trace; | ... | @@ -825,7 +695,9 @@ const trace = @import("../../tracy.zig").trace; |
| 825 | 695 | ||
| 826 | const Allocator = mem.Allocator; | 696 | const Allocator = mem.Allocator; |
| 827 | const Atom = @import("Atom.zig"); | 697 | const Atom = @import("Atom.zig"); |
| 828 | const EhFrameRecord = eh_frame.EhFrameRecord; | 698 | const Fde = eh_frame.Fde; |
| 699 | const File = @import("file.zig").File; | ||
| 829 | const MachO = @import("../MachO.zig"); | 700 | const MachO = @import("../MachO.zig"); |
| 830 | const Object = @import("Object.zig"); | 701 | const Object = @import("Object.zig"); |
| 831 | const SymbolWithLoc = MachO.SymbolWithLoc; | 702 | const Symbol = @import("Symbol.zig"); |
| 703 | const UnwindInfo = @This(); |
src/link/MachO/ZigObject.zig created+1471| ... | @@ -0,0 +1,1471 @@ | ||
| 1 | /// Externally owned memory. | ||
| 2 | path: []const u8, | ||
| 3 | index: File.Index, | ||
| 4 | |||
| 5 | symtab: std.MultiArrayList(Nlist) = .{}, | ||
| 6 | |||
| 7 | symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, | ||
| 8 | atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, | ||
| 9 | globals_lookup: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{}, | ||
| 10 | |||
| 11 | /// Table of tracked LazySymbols. | ||
| 12 | lazy_syms: LazySymbolTable = .{}, | ||
| 13 | |||
| 14 | /// Table of tracked Decls. | ||
| 15 | decls: 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`. | ||
| 36 | unnamed_consts: UnnamedConstTable = .{}, | ||
| 37 | |||
| 38 | /// Table of tracked AnonDecls. | ||
| 39 | anon_decls: AnonDeclTable = .{}, | ||
| 40 | |||
| 41 | /// TLV initializers indexed by Atom.Index. | ||
| 42 | tlv_initializers: TlvInitializerTable = .{}, | ||
| 43 | |||
| 44 | /// A table of relocations. | ||
| 45 | relocs: RelocationTable = .{}, | ||
| 46 | |||
| 47 | dynamic_relocs: MachO.DynamicRelocs = .{}, | ||
| 48 | output_symtab_ctx: MachO.SymtabCtx = .{}, | ||
| 49 | |||
| 50 | pub 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 | |||
| 57 | pub 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 | |||
| 100 | fn 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 | |||
| 111 | pub 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. | ||
| 140 | pub 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 | |||
| 174 | pub 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 | |||
| 179 | pub fn freeAtomRelocs(self: *ZigObject, atom: Atom) void { | ||
| 180 | self.relocs.items[atom.relocs.pos].clearRetainingCapacity(); | ||
| 181 | } | ||
| 182 | |||
| 183 | pub 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 | |||
| 240 | pub 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 | |||
| 250 | pub 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 | |||
| 268 | pub 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 | |||
| 286 | pub 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 | |||
| 296 | pub 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 | |||
| 321 | pub 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 | |||
| 339 | pub 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 | |||
| 349 | pub 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 | |||
| 379 | pub 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 | |||
| 405 | pub 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 | |||
| 431 | pub 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 | |||
| 481 | fn 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 | |||
| 490 | fn 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 | |||
| 501 | pub 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 | |||
| 519 | pub 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 | |||
| 584 | pub 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 | |||
| 664 | fn 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 | ||
| 749 | fn 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 | |||
| 778 | fn 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 | |||
| 829 | fn 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 | |||
| 898 | fn 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 | |||
| 951 | pub 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 | |||
| 990 | const LowerConstResult = union(enum) { | ||
| 991 | ok: Symbol.Index, | ||
| 992 | fail: *Module.ErrorMsg, | ||
| 993 | }; | ||
| 994 | |||
| 995 | fn 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 | |||
| 1053 | pub 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 | |||
| 1143 | fn 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`. | ||
| 1229 | pub 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 | |||
| 1240 | pub 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 | |||
| 1266 | pub 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 | |||
| 1285 | pub 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 | |||
| 1311 | pub 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 | |||
| 1351 | pub fn asFile(self: *ZigObject) File { | ||
| 1352 | return .{ .zig_object = self }; | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | pub 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 | |||
| 1362 | const FormatContext = struct { | ||
| 1363 | self: *ZigObject, | ||
| 1364 | macho_file: *MachO, | ||
| 1365 | }; | ||
| 1366 | |||
| 1367 | fn 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 | |||
| 1382 | pub 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 | |||
| 1389 | fn 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 | |||
| 1404 | const 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 | |||
| 1419 | const 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 | |||
| 1427 | const 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 | |||
| 1436 | const DeclTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, DeclMetadata); | ||
| 1437 | const UnnamedConstTable = std.AutoHashMapUnmanaged(InternPool.DeclIndex, std.ArrayListUnmanaged(Symbol.Index)); | ||
| 1438 | const AnonDeclTable = std.AutoHashMapUnmanaged(InternPool.Index, DeclMetadata); | ||
| 1439 | const LazySymbolTable = std.AutoArrayHashMapUnmanaged(InternPool.OptionalDeclIndex, LazySymbolMetadata); | ||
| 1440 | const RelocationTable = std.ArrayListUnmanaged(std.ArrayListUnmanaged(Relocation)); | ||
| 1441 | const TlvInitializerTable = std.AutoArrayHashMapUnmanaged(Atom.Index, TlvInitializer); | ||
| 1442 | |||
| 1443 | const assert = std.debug.assert; | ||
| 1444 | const builtin = @import("builtin"); | ||
| 1445 | const codegen = @import("../../codegen.zig"); | ||
| 1446 | const link = @import("../../link.zig"); | ||
| 1447 | const log = std.log.scoped(.link); | ||
| 1448 | const macho = std.macho; | ||
| 1449 | const mem = std.mem; | ||
| 1450 | const trace = @import("../../tracy.zig").trace; | ||
| 1451 | const std = @import("std"); | ||
| 1452 | |||
| 1453 | const Air = @import("../../Air.zig"); | ||
| 1454 | const Allocator = std.mem.Allocator; | ||
| 1455 | const Archive = @import("Archive.zig"); | ||
| 1456 | const Atom = @import("Atom.zig"); | ||
| 1457 | const Dwarf = @import("../Dwarf.zig"); | ||
| 1458 | const File = @import("file.zig").File; | ||
| 1459 | const InternPool = @import("../../InternPool.zig"); | ||
| 1460 | const Liveness = @import("../../Liveness.zig"); | ||
| 1461 | const MachO = @import("../MachO.zig"); | ||
| 1462 | const Nlist = Object.Nlist; | ||
| 1463 | const Module = @import("../../Module.zig"); | ||
| 1464 | const Object = @import("Object.zig"); | ||
| 1465 | const Relocation = @import("Relocation.zig"); | ||
| 1466 | const Symbol = @import("Symbol.zig"); | ||
| 1467 | const StringTable = @import("../StringTable.zig"); | ||
| 1468 | const Type = @import("../../type.zig").Type; | ||
| 1469 | const Value = @import("../../value.zig").Value; | ||
| 1470 | const TypedValue = @import("../../TypedValue.zig"); | ||
| 1471 | const ZigObject = @This(); | ||
src/link/MachO/dead_strip.zig+145-431| ... | @@ -1,495 +1,209 @@ | ... | @@ -1,495 +1,209 @@ |
| 1 | //! An algorithm for dead stripping of unreferenced Atoms. | ||
| 2 | |||
| 3 | pub fn gcAtoms(macho_file: *MachO) !void { | 1 | pub fn gcAtoms(macho_file: *MachO) !void { |
| 4 | const comp = macho_file.base.comp; | 2 | const gpa = macho_file.base.comp.gpa; |
| 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))); | ||
| 12 | 3 | ||
| 13 | var alive = AtomTable.init(arena.allocator()); | 4 | var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1); |
| 14 | try alive.ensureTotalCapacity(@as(u32, @intCast(macho_file.atoms.items.len))); | 5 | defer objects.deinit(); |
| 6 | for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index); | ||
| 7 | if (macho_file.internal_object) |index| objects.appendAssumeCapacity(index); | ||
| 15 | 8 | ||
| 16 | try collectRoots(macho_file, &roots); | 9 | var roots = std.ArrayList(*Atom).init(gpa); |
| 17 | mark(macho_file, roots, &alive); | 10 | defer roots.deinit(); |
| 18 | prune(macho_file, alive); | ||
| 19 | } | ||
| 20 | 11 | ||
| 21 | fn addRoot(macho_file: *MachO, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void { | 12 | try collectRoots(&roots, objects.items, macho_file); |
| 22 | const sym = macho_file.getSymbol(sym_loc); | 13 | mark(roots.items, objects.items, macho_file); |
| 23 | assert(!sym.undf()); | 14 | prune(objects.items, macho_file); |
| 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); | ||
| 32 | } | 15 | } |
| 33 | 16 | ||
| 34 | fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void { | 17 | fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void { |
| 35 | log.debug("collecting roots", .{}); | 18 | for (objects) |index| { |
| 36 | 19 | const object = macho_file.getFile(index).?; | |
| 37 | const comp = macho_file.base.comp; | 20 | for (object.getSymbols()) |sym_index| { |
| 38 | 21 | const sym = macho_file.getSymbol(sym_index); | |
| 39 | switch (comp.config.output_mode) { | 22 | const file = sym.getFile(macho_file) orelse continue; |
| 40 | .Exe => { | 23 | if (file.getIndex() != index) continue; |
| 41 | // Add entrypoint as GC root | 24 | if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global)) |
| 42 | if (macho_file.getEntryPoint()) |global| { | 25 | try markSymbol(sym, roots, macho_file); |
| 43 | if (global.getFile()) |file| { | 26 | } |
| 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; | ||
| 57 | 27 | ||
| 58 | if (global.getFile()) |file| { | 28 | for (object.getAtoms()) |atom_index| { |
| 59 | try addRoot(macho_file, roots, file, global); | 29 | const atom = macho_file.getAtom(atom_index).?; |
| 60 | } | 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 | }, | ||
| 61 | } | 39 | } |
| 62 | }, | 40 | } |
| 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); | ||
| 72 | } | 41 | } |
| 73 | 42 | ||
| 74 | for (macho_file.objects.items) |object| { | 43 | for (macho_file.objects.items) |index| { |
| 75 | const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; | 44 | for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| { |
| 76 | 45 | const cu = macho_file.getUnwindRecord(cu_index); | |
| 77 | for (object.atoms.items) |atom_index| { | 46 | if (!cu.alive) continue; |
| 78 | const is_gc_root = blk: { | 47 | if (cu.getFde(macho_file)) |fde| { |
| 79 | // Modelled after ld64 which treats each object file compiled without MH_SUBSECTIONS_VIA_SYMBOLS | 48 | if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file); |
| 80 | // as a root. | 49 | } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file); |
| 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 | } | ||
| 110 | } | 50 | } |
| 111 | } | 51 | } |
| 112 | } | ||
| 113 | |||
| 114 | fn 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(); | ||
| 119 | 52 | ||
| 120 | log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() }); | 53 | for (macho_file.undefined_symbols.items) |sym_index| { |
| 121 | 54 | const sym = macho_file.getSymbol(sym_index); | |
| 122 | alive.putAssumeCapacityNoClobber(atom_index, {}); | 55 | try markSymbol(sym, roots, macho_file); |
| 123 | 56 | } | |
| 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; | ||
| 163 | 57 | ||
| 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); | ||
| 165 | } | 66 | } |
| 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); | ||
| 176 | } | 67 | } |
| 177 | } | 68 | } |
| 178 | 69 | ||
| 179 | fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool { | 70 | fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void { |
| 180 | const atom = macho_file.getAtom(atom_index); | 71 | const atom = sym.getAtom(macho_file) orelse return; |
| 181 | const sym_loc = atom.getSymbolWithLoc(); | 72 | if (markAtom(atom)) try roots.append(atom); |
| 182 | 73 | } | |
| 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 | } | ||
| 232 | 74 | ||
| 233 | return false; | 75 | fn markAtom(atom: *Atom) bool { |
| 76 | const already_visited = atom.flags.visited; | ||
| 77 | atom.flags.visited = true; | ||
| 78 | return atom.flags.alive and !already_visited; | ||
| 234 | } | 79 | } |
| 235 | 80 | ||
| 236 | fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void { | 81 | fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void { |
| 237 | var it = roots.keyIterator(); | 82 | for (roots) |root| { |
| 238 | while (it.next()) |root| { | 83 | markLive(root, macho_file); |
| 239 | markLive(macho_file, root.*, alive); | ||
| 240 | } | 84 | } |
| 241 | 85 | ||
| 242 | var loop: bool = true; | 86 | var loop: bool = true; |
| 243 | while (loop) { | 87 | while (loop) { |
| 244 | loop = false; | 88 | loop = false; |
| 245 | 89 | ||
| 246 | for (macho_file.objects.items) |object| { | 90 | for (objects) |index| { |
| 247 | for (object.atoms.items) |atom_index| { | 91 | for (macho_file.getFile(index).?.getAtoms()) |atom_index| { |
| 248 | if (alive.contains(atom_index)) continue; | 92 | const atom = macho_file.getAtom(atom_index).?; |
| 249 | 93 | const isec = atom.getInputSection(macho_file); | |
| 250 | const atom = macho_file.getAtom(atom_index); | 94 | if (isec.isDontDeadStripIfReferencesLive() and |
| 251 | const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym| | 95 | !(mem.eql(u8, isec.sectName(), "__eh_frame") or |
| 252 | source_sym.n_sect - 1 | 96 | mem.eql(u8, isec.sectName(), "__compact_unwind") or |
| 253 | else blk: { | 97 | isec.attrs() & macho.S_ATTR_DEBUG != 0) and |
| 254 | const nbase = @as(u32, @intCast(object.in_symtab.?.len)); | 98 | !atom.flags.alive and refersLive(atom, macho_file)) |
| 255 | const sect_id = @as(u8, @intCast(atom.sym_index - nbase)); | 99 | { |
| 256 | break :blk sect_id; | 100 | markLive(atom, macho_file); |
| 257 | }; | 101 | loop = true; |
| 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 | } | ||
| 265 | } | 102 | } |
| 266 | } | 103 | } |
| 267 | } | 104 | } |
| 268 | } | 105 | } |
| 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 | } | ||
| 275 | } | 106 | } |
| 276 | 107 | ||
| 277 | fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void { | 108 | fn markLive(atom: *Atom, macho_file: *MachO) void { |
| 278 | const object = &macho_file.objects.items[object_id]; | 109 | assert(atom.flags.visited); |
| 279 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 110 | atom.flags.alive = true; |
| 280 | const cpu_arch = target.cpu.arch; | 111 | track_live_log.debug("{}marking live atom({d},{s})", .{ |
| 281 | 112 | track_live_level, | |
| 282 | const unwind_records = object.getUnwindRecords(); | 113 | atom.atom_index, |
| 114 | atom.getName(macho_file), | ||
| 115 | }); | ||
| 283 | 116 | ||
| 284 | for (object.exec_atoms.items) |atom_index| { | 117 | if (build_options.enable_logging) |
| 285 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 118 | track_live_level.incr(); |
| 286 | 119 | ||
| 287 | if (!object.hasUnwindRecords()) { | 120 | for (atom.getRelocs(macho_file)) |rel| { |
| 288 | if (alive.contains(atom_index)) { | 121 | const target_atom = switch (rel.tag) { |
| 289 | // Mark references live and continue. | 122 | .local => rel.getTargetAtom(macho_file), |
| 290 | markEhFrameRecords(macho_file, object_id, atom_index, alive); | 123 | .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), |
| 291 | } else { | 124 | }; |
| 292 | while (inner_syms_it.next()) |sym| { | 125 | if (target_atom) |ta| { |
| 293 | if (object.eh_frame_records_lookup.get(sym)) |fde_offset| { | 126 | if (markAtom(ta)) markLive(ta, macho_file); |
| 294 | // Mark dead and continue. | ||
| 295 | object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | } | ||
| 299 | continue; | ||
| 300 | } | 127 | } |
| 128 | } | ||
| 301 | 129 | ||
| 302 | while (inner_syms_it.next()) |sym| { | 130 | for (atom.getUnwindRecords(macho_file)) |cu_index| { |
| 303 | const record_id = object.unwind_records_lookup.get(sym) orelse continue; | 131 | const cu = macho_file.getUnwindRecord(cu_index); |
| 304 | if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do | 132 | const cu_atom = cu.getAtom(macho_file); |
| 305 | if (!alive.contains(atom_index)) { | 133 | if (markAtom(cu_atom)) markLive(cu_atom, macho_file); |
| 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 | } | ||
| 313 | 134 | ||
| 314 | const record = unwind_records[record_id]; | 135 | if (cu.getLsdaAtom(macho_file)) |lsda| { |
| 315 | if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { | 136 | if (markAtom(lsda)) markLive(lsda, macho_file); |
| 316 | markEhFrameRecords(macho_file, object_id, atom_index, alive); | 137 | } |
| 317 | } else { | 138 | if (cu.getFde(macho_file)) |fde| { |
| 318 | if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| { | 139 | const fde_atom = fde.getAtom(macho_file); |
| 319 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 140 | if (markAtom(fde_atom)) markLive(fde_atom, 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 | } | ||
| 332 | 141 | ||
| 333 | if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| { | 142 | if (fde.getLsdaAtom(macho_file)) |lsda| { |
| 334 | const reloc_target = Atom.parseRelocTarget(macho_file, .{ | 143 | if (markAtom(lsda)) markLive(lsda, 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 | } | ||
| 344 | } | 144 | } |
| 345 | } | 145 | } |
| 346 | } | 146 | } |
| 347 | } | 147 | } |
| 348 | 148 | ||
| 349 | fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void { | 149 | fn refersLive(atom: *Atom, macho_file: *MachO) bool { |
| 350 | const target = macho_file.base.comp.root_mod.resolved_target.result; | 150 | for (atom.getRelocs(macho_file)) |rel| { |
| 351 | const cpu_arch = target.cpu.arch; | 151 | const target_atom = switch (rel.tag) { |
| 352 | const object = &macho_file.objects.items[object_id]; | 152 | .local => rel.getTargetAtom(macho_file), |
| 353 | var it = object.getEhFrameRecordsIterator(); | 153 | .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), |
| 354 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 154 | }; |
| 355 | 155 | if (target_atom) |ta| { | |
| 356 | while (inner_syms_it.next()) |sym| { | 156 | if (ta.flags.alive) return true; |
| 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, | ||
| 400 | } | 157 | } |
| 158 | } | ||
| 159 | return false; | ||
| 160 | } | ||
| 401 | 161 | ||
| 402 | // Mark CIE references which should include any referenced personalities | 162 | fn prune(objects: []const File.Index, macho_file: *MachO) void { |
| 403 | // that are defined locally. | 163 | for (objects) |index| { |
| 404 | if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| { | 164 | for (macho_file.getFile(index).?.getAtoms()) |atom_index| { |
| 405 | const target_sym = macho_file.getSymbol(reloc_target); | 165 | const atom = macho_file.getAtom(atom_index).?; |
| 406 | if (!target_sym.undf()) { | 166 | if (atom.flags.alive and !atom.flags.visited) { |
| 407 | const target_object = macho_file.objects.items[reloc_target.getFile().?]; | 167 | atom.flags.alive = false; |
| 408 | const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?; | 168 | atom.markUnwindRecordsDead(macho_file); |
| 409 | markLive(macho_file, target_atom_index, alive); | ||
| 410 | } | 169 | } |
| 411 | } | 170 | } |
| 412 | } | 171 | } |
| 413 | } | 172 | } |
| 414 | 173 | ||
| 415 | fn prune(macho_file: *MachO, alive: AtomTable) void { | 174 | const Level = struct { |
| 416 | log.debug("pruning dead atoms", .{}); | 175 | value: usize = 0, |
| 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; | ||
| 466 | 176 | ||
| 467 | var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 177 | fn incr(self: *@This()) void { |
| 468 | while (inner_sym_it.next()) |inner| { | 178 | self.value += 1; |
| 469 | const inner_sym = macho_file.getSymbolPtr(inner); | 179 | } |
| 470 | inner_sym.n_desc = MachO.N_DEAD; | ||
| 471 | } | ||
| 472 | 180 | ||
| 473 | if (Atom.getSectionAlias(macho_file, atom_index)) |alias| { | 181 | pub fn format( |
| 474 | const alias_sym = macho_file.getSymbolPtr(alias); | 182 | self: *const @This(), |
| 475 | alias_sym.n_desc = MachO.N_DEAD; | 183 | comptime unused_fmt_string: []const u8, |
| 476 | } | 184 | options: std.fmt.FormatOptions, |
| 477 | } | 185 | writer: anytype, |
| 186 | ) !void { | ||
| 187 | _ = unused_fmt_string; | ||
| 188 | _ = options; | ||
| 189 | try writer.writeByteNTimes(' ', self.value); | ||
| 478 | } | 190 | } |
| 479 | } | 191 | }; |
| 192 | |||
| 193 | var track_live_level: Level = .{}; | ||
| 480 | 194 | ||
| 481 | const std = @import("std"); | ||
| 482 | const assert = std.debug.assert; | 195 | const assert = std.debug.assert; |
| 483 | const eh_frame = @import("eh_frame.zig"); | 196 | const build_options = @import("build_options"); |
| 484 | const log = std.log.scoped(.dead_strip); | 197 | const log = std.log.scoped(.dead_strip); |
| 485 | const macho = std.macho; | 198 | const macho = std.macho; |
| 486 | const math = std.math; | 199 | const math = std.math; |
| 487 | const mem = std.mem; | 200 | const mem = std.mem; |
| 201 | const trace = @import("../../tracy.zig").trace; | ||
| 202 | const track_live_log = std.log.scoped(.dead_strip_track_live); | ||
| 203 | const std = @import("std"); | ||
| 488 | 204 | ||
| 489 | const Allocator = mem.Allocator; | 205 | const Allocator = mem.Allocator; |
| 490 | const Atom = @import("Atom.zig"); | 206 | const Atom = @import("Atom.zig"); |
| 207 | const File = @import("file.zig").File; | ||
| 491 | const MachO = @import("../MachO.zig"); | 208 | const MachO = @import("../MachO.zig"); |
| 492 | const SymbolWithLoc = MachO.SymbolWithLoc; | 209 | const Symbol = @import("Symbol.zig"); |
| 493 | const UnwindInfo = @import("UnwindInfo.zig"); | ||
| 494 | |||
| 495 | const AtomTable = std.AutoHashMap(Atom.Index, void); |
src/link/MachO/dyld_info/Rebase.zig+14-12| ... | @@ -1,7 +1,18 @@ | ... | @@ -1,7 +1,18 @@ |
| 1 | const Rebase = @This(); | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const assert = std.debug.assert; | ||
| 5 | const leb = std.leb; | ||
| 6 | const log = std.log.scoped(.link_dyld_info); | ||
| 7 | const macho = std.macho; | ||
| 8 | const testing = std.testing; | ||
| 9 | |||
| 10 | const Allocator = std.mem.Allocator; | ||
| 11 | |||
| 1 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 12 | entries: std.ArrayListUnmanaged(Entry) = .{}, |
| 2 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 13 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 3 | 14 | ||
| 4 | const Entry = struct { | 15 | pub const Entry = struct { |
| 5 | offset: u64, | 16 | offset: u64, |
| 6 | segment_id: u8, | 17 | segment_id: u8, |
| 7 | 18 | ||
| ... | @@ -28,6 +39,8 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void { | ... | @@ -28,6 +39,8 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void { |
| 28 | 39 | ||
| 29 | const writer = rebase.buffer.writer(gpa); | 40 | const writer = rebase.buffer.writer(gpa); |
| 30 | 41 | ||
| 42 | log.debug("rebase opcodes", .{}); | ||
| 43 | |||
| 31 | std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan); | 44 | std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan); |
| 32 | 45 | ||
| 33 | try setTypePointer(writer); | 46 | try setTypePointer(writer); |
| ... | @@ -561,14 +574,3 @@ test "rebase - composite" { | ... | @@ -561,14 +574,3 @@ test "rebase - composite" { |
| 561 | macho.REBASE_OPCODE_DONE, | 574 | macho.REBASE_OPCODE_DONE, |
| 562 | }, rebase.buffer.items); | 575 | }, rebase.buffer.items); |
| 563 | } | 576 | } |
| 564 | |||
| 565 | const Rebase = @This(); | ||
| 566 | |||
| 567 | const std = @import("std"); | ||
| 568 | const assert = std.debug.assert; | ||
| 569 | const leb = std.leb; | ||
| 570 | const log = std.log.scoped(.dyld_info); | ||
| 571 | const macho = std.macho; | ||
| 572 | const testing = std.testing; | ||
| 573 | |||
| 574 | const 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. | ||
| 31 | const Trie = @This(); | ||
| 32 | |||
| 33 | const std = @import("std"); | ||
| 34 | const mem = std.mem; | ||
| 35 | const leb = std.leb; | ||
| 36 | const log = std.log.scoped(.macho); | ||
| 37 | const macho = std.macho; | ||
| 38 | const testing = std.testing; | ||
| 39 | const assert = std.debug.assert; | ||
| 40 | const Allocator = mem.Allocator; | ||
| 41 | |||
| 42 | pub 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. | ||
| 274 | root: ?*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. | ||
| 281 | ordered_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. | ||
| 288 | size: u64 = 0, | ||
| 289 | |||
| 290 | /// Number of nodes currently in the trie. | ||
| 291 | node_count: usize = 0, | ||
| 292 | |||
| 293 | trie_dirty: bool = true, | ||
| 294 | |||
| 295 | /// Export symbol that is to be placed in the trie. | ||
| 296 | pub 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. | ||
| 311 | pub 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. | ||
| 324 | pub 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 | |||
| 356 | const ReadError = error{ | ||
| 357 | OutOfMemory, | ||
| 358 | EndOfStream, | ||
| 359 | Overflow, | ||
| 360 | }; | ||
| 361 | |||
| 362 | /// Parse the trie from a byte stream. | ||
| 363 | pub 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. | ||
| 369 | pub 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 | |||
| 376 | pub 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 | |||
| 384 | pub 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 | |||
| 392 | test "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 | |||
| 438 | test "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 | |||
| 489 | fn 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 | |||
| 504 | test "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 | |||
| 550 | test "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 | |||
| 581 | test "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 @@ | ... | @@ -1,231 +1,397 @@ |
| 1 | pub fn Bind(comptime Ctx: type, comptime Target: type) type { | 1 | const std = @import("std"); |
| 2 | return struct { | 2 | const assert = std.debug.assert; |
| 3 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 3 | const leb = std.leb; |
| 4 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 4 | const log = std.log.scoped(.link_dyld_info); |
| 5 | 5 | const macho = std.macho; | |
| 6 | const Self = @This(); | 6 | const testing = std.testing; |
| 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 | }; | ||
| 26 | 7 | ||
| 27 | pub fn deinit(self: *Self, gpa: Allocator) void { | 8 | const Allocator = std.mem.Allocator; |
| 28 | self.entries.deinit(gpa); | 9 | const MachO = @import("../../MachO.zig"); |
| 29 | self.buffer.deinit(gpa); | 10 | const Symbol = @import("../Symbol.zig"); |
| 11 | |||
| 12 | pub 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); | ||
| 30 | } | 26 | } |
| 27 | return entry.segment_id < other.segment_id; | ||
| 28 | } | ||
| 29 | }; | ||
| 31 | 30 | ||
| 32 | pub fn size(self: Self) u64 { | 31 | pub const Bind = struct { |
| 33 | return @as(u64, @intCast(self.buffer.items.len)); | 32 | entries: std.ArrayListUnmanaged(Entry) = .{}, |
| 34 | } | 33 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 35 | 34 | ||
| 36 | pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void { | 35 | const Self = @This(); |
| 37 | if (self.entries.items.len == 0) return; | ||
| 38 | 36 | ||
| 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 | } | ||
| 40 | 41 | ||
| 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 | } | ||
| 42 | 45 | ||
| 43 | var start: usize = 0; | 46 | pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { |
| 44 | var seg_id: ?u8 = null; | 47 | if (self.entries.items.len == 0) return; |
| 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 | } | ||
| 51 | 48 | ||
| 52 | try finalizeSegment(self.entries.items[start..], ctx, writer); | 49 | const writer = self.buffer.writer(gpa); |
| 53 | try done(writer); | 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; | ||
| 54 | } | 62 | } |
| 55 | 63 | ||
| 56 | fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void { | 64 | try finalizeSegment(self.entries.items[start..], ctx, writer); |
| 57 | if (entries.len == 0) return; | 65 | try done(writer); |
| 58 | 66 | } | |
| 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; | ||
| 85 | 67 | ||
| 86 | const sym = ctx.getSymbol(current.target); | 68 | fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void { |
| 87 | const name = ctx.getSymbolName(current.target); | 69 | if (entries.len == 0) return; |
| 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); | ||
| 90 | 70 | ||
| 91 | try setSymbol(name, flags, writer); | 71 | const seg_id = entries[0].segment_id; |
| 92 | try setTypePointer(writer); | 72 | try setSegmentOffset(seg_id, 0, writer); |
| 93 | try setDylibOrdinal(ordinal, writer); | ||
| 94 | 73 | ||
| 95 | if (current.addend != addend) { | 74 | var offset: u64 = 0; |
| 96 | addend = current.addend; | 75 | var addend: i64 = 0; |
| 97 | try setAddend(addend, writer); | 76 | var count: usize = 0; |
| 98 | } | 77 | var skip: u64 = 0; |
| 99 | } | 78 | var target: ?Symbol.Index = null; |
| 79 | |||
| 80 | var state: enum { | ||
| 81 | start, | ||
| 82 | bind_single, | ||
| 83 | bind_times_skip, | ||
| 84 | } = .start; | ||
| 100 | 85 | ||
| 101 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); | 86 | var i: usize = 0; |
| 102 | log.debug(" => {x}", .{current.offset}); | 87 | while (i < entries.len) : (i += 1) { |
| 88 | const current = entries[i]; | ||
| 89 | if (target == null or target.? != current.target) { | ||
| 103 | switch (state) { | 90 | switch (state) { |
| 104 | .start => { | 91 | .start => {}, |
| 105 | if (current.offset < offset) { | 92 | .bind_single => try doBind(writer), |
| 106 | try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer); | 93 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), |
| 107 | offset = offset - (offset - current.offset); | 94 | } |
| 108 | } else if (current.offset > offset) { | 95 | state = .start; |
| 109 | const delta = current.offset - offset; | 96 | target = current.target; |
| 110 | try addAddr(delta, writer); | 97 | |
| 111 | offset += delta; | 98 | const sym = ctx.getSymbol(current.target); |
| 112 | } | 99 | const name = sym.getName(ctx); |
| 113 | state = .bind_single; | 100 | const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; |
| 114 | offset += @sizeOf(u64); | 101 | const ordinal: i16 = ord: { |
| 115 | count = 1; | 102 | if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 116 | }, | 103 | if (sym.flags.import) { |
| 117 | .bind_single => { | 104 | // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 118 | if (current.offset == offset) { | 105 | if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord); |
| 119 | try doBind(writer); | 106 | } |
| 120 | state = .start; | 107 | if (ctx.undefined_treatment == .dynamic_lookup) |
| 121 | } else if (current.offset > offset) { | 108 | break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; |
| 122 | const delta = current.offset - offset; | 109 | break :ord macho.BIND_SPECIAL_DYLIB_SELF; |
| 123 | state = .bind_times_skip; | 110 | }; |
| 124 | skip = @as(u64, @intCast(delta)); | 111 | |
| 125 | offset += skip; | 112 | try setSymbol(name, flags, writer); |
| 126 | } else unreachable; | 113 | try setTypePointer(writer); |
| 127 | i -= 1; | 114 | try setDylibOrdinal(ordinal, writer); |
| 128 | }, | 115 | |
| 129 | .bind_times_skip => { | 116 | if (current.addend != addend) { |
| 130 | if (current.offset < offset) { | 117 | addend = current.addend; |
| 131 | count -= 1; | 118 | try setAddend(addend, writer); |
| 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 | }, | ||
| 149 | } | 119 | } |
| 150 | } | 120 | } |
| 151 | 121 | ||
| 122 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); | ||
| 123 | log.debug(" => {x}", .{current.offset}); | ||
| 152 | switch (state) { | 124 | switch (state) { |
| 153 | .start => unreachable, | 125 | .start => { |
| 154 | .bind_single => try doBind(writer), | 126 | if (current.offset < offset) { |
| 155 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | 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 | }, | ||
| 156 | } | 170 | } |
| 157 | } | 171 | } |
| 158 | 172 | ||
| 159 | pub fn write(self: Self, writer: anytype) !void { | 173 | switch (state) { |
| 160 | if (self.size() == 0) return; | 174 | .start => unreachable, |
| 161 | try writer.writeAll(self.buffer.items); | 175 | .bind_single => try doBind(writer), |
| 176 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 162 | } | 177 | } |
| 163 | }; | 178 | } |
| 164 | } | ||
| 165 | 179 | ||
| 166 | pub fn LazyBind(comptime Ctx: type, comptime Target: type) type { | 180 | pub fn write(self: Self, writer: anytype) !void { |
| 167 | return struct { | 181 | if (self.size() == 0) return; |
| 168 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 182 | try writer.writeAll(self.buffer.items); |
| 169 | buffer: std.ArrayListUnmanaged(u8) = .{}, | 183 | } |
| 170 | offsets: std.ArrayListUnmanaged(u32) = .{}, | 184 | }; |
| 171 | 185 | ||
| 172 | const Self = @This(); | 186 | pub const WeakBind = struct { |
| 173 | 187 | entries: std.ArrayListUnmanaged(Entry) = .{}, | |
| 174 | const Entry = struct { | 188 | buffer: std.ArrayListUnmanaged(u8) = .{}, |
| 175 | target: Target, | 189 | |
| 176 | offset: u64, | 190 | const Self = @This(); |
| 177 | segment_id: u8, | 191 | |
| 178 | addend: i64, | 192 | pub fn deinit(self: *Self, gpa: Allocator) void { |
| 179 | }; | 193 | self.entries.deinit(gpa); |
| 180 | 194 | self.buffer.deinit(gpa); | |
| 181 | pub fn deinit(self: *Self, gpa: Allocator) void { | 195 | } |
| 182 | self.entries.deinit(gpa); | 196 | |
| 183 | self.buffer.deinit(gpa); | 197 | pub fn size(self: Self) u64 { |
| 184 | self.offsets.deinit(gpa); | 198 | return @as(u64, @intCast(self.buffer.items.len)); |
| 185 | } | 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); | ||
| 186 | 209 | ||
| 187 | pub fn size(self: Self) u64 { | 210 | var start: usize = 0; |
| 188 | return @as(u64, @intCast(self.buffer.items.len)); | 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; | ||
| 189 | } | 217 | } |
| 190 | 218 | ||
| 191 | pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void { | 219 | try finalizeSegment(self.entries.items[start..], ctx, writer); |
| 192 | if (self.entries.items.len == 0) return; | 220 | try done(writer); |
| 221 | } | ||
| 222 | |||
| 223 | fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void { | ||
| 224 | if (entries.len == 0) return; | ||
| 193 | 225 | ||
| 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); | ||
| 195 | 228 | ||
| 196 | var cwriter = std.io.countingWriter(self.buffer.writer(gpa)); | 229 | var offset: u64 = 0; |
| 197 | const writer = cwriter.writer(); | 230 | var addend: i64 = 0; |
| 231 | var count: usize = 0; | ||
| 232 | var skip: u64 = 0; | ||
| 233 | var target: ?Symbol.Index = null; | ||
| 198 | 234 | ||
| 199 | var addend: i64 = 0; | 235 | var state: enum { |
| 236 | start, | ||
| 237 | bind_single, | ||
| 238 | bind_times_skip, | ||
| 239 | } = .start; | ||
| 200 | 240 | ||
| 201 | for (self.entries.items) |entry| { | 241 | var i: usize = 0; |
| 202 | self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written))); | 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; | ||
| 203 | 252 | ||
| 204 | const sym = ctx.getSymbol(entry.target); | 253 | const sym = ctx.getSymbol(current.target); |
| 205 | const name = ctx.getSymbolName(entry.target); | 254 | const name = sym.getName(ctx); |
| 206 | const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; | 255 | const flags: u8 = 0; // TODO NON_WEAK_DEFINITION |
| 207 | const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); | ||
| 208 | 256 | ||
| 209 | try setSegmentOffset(entry.segment_id, entry.offset, writer); | ||
| 210 | try setSymbol(name, flags, writer); | 257 | try setSymbol(name, flags, writer); |
| 211 | try setDylibOrdinal(ordinal, writer); | 258 | try setTypePointer(writer); |
| 212 | 259 | ||
| 213 | if (entry.addend != addend) { | 260 | if (current.addend != addend) { |
| 214 | try setAddend(entry.addend, writer); | 261 | addend = current.addend; |
| 215 | addend = entry.addend; | 262 | try setAddend(addend, writer); |
| 216 | } | 263 | } |
| 264 | } | ||
| 217 | 265 | ||
| 218 | try doBind(writer); | 266 | log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) }); |
| 219 | try done(writer); | 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 | }, | ||
| 220 | } | 314 | } |
| 221 | } | 315 | } |
| 222 | 316 | ||
| 223 | pub fn write(self: Self, writer: anytype) !void { | 317 | switch (state) { |
| 224 | if (self.size() == 0) return; | 318 | .start => unreachable, |
| 225 | try writer.writeAll(self.buffer.items); | 319 | .bind_single => try doBind(writer), |
| 320 | .bind_times_skip => try doBindTimesSkip(count, skip, writer), | ||
| 226 | } | 321 | } |
| 227 | }; | 322 | } |
| 228 | } | 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 | |||
| 330 | pub 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 | }; | ||
| 229 | 395 | ||
| 230 | fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void { | 396 | fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void { |
| 231 | log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset }); | 397 | log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset }); |
| ... | @@ -312,429 +478,3 @@ fn done(writer: anytype) !void { | ... | @@ -312,429 +478,3 @@ fn done(writer: anytype) !void { |
| 312 | log.debug(">>> done", .{}); | 478 | log.debug(">>> done", .{}); |
| 313 | try writer.writeByte(macho.BIND_OPCODE_DONE); | 479 | try writer.writeByte(macho.BIND_OPCODE_DONE); |
| 314 | } | 480 | } |
| 315 | |||
| 316 | const 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 | |||
| 364 | fn 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 | |||
| 376 | test "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 | |||
| 389 | test "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 | |||
| 428 | test "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 | |||
| 489 | test "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 | |||
| 549 | test "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 | |||
| 672 | test "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 | |||
| 733 | const std = @import("std"); | ||
| 734 | const assert = std.debug.assert; | ||
| 735 | const leb = std.leb; | ||
| 736 | const log = std.log.scoped(.dyld_info); | ||
| 737 | const macho = std.macho; | ||
| 738 | const testing = std.testing; | ||
| 739 | |||
| 740 | const Allocator = std.mem.Allocator; |
src/link/MachO/eh_frame.zig+467-558| ... | @@ -1,628 +1,537 @@ | ... | @@ -1,628 +1,537 @@ |
| 1 | pub fn scanRelocs(macho_file: *MachO) !void { | 1 | pub const Cie = struct { |
| 2 | const comp = macho_file.base.comp; | 2 | /// Includes 4byte size cell. |
| 3 | const gpa = comp.gpa; | 3 | offset: u32, |
| 4 | 4 | out_offset: u32 = 0, | |
| 5 | for (macho_file.objects.items, 0..) |*object, object_id| { | 5 | size: u32, |
| 6 | var cies = std.AutoHashMap(u32, void).init(gpa); | 6 | lsda_size: ?enum { p32, p64 } = null, |
| 7 | defer cies.deinit(); | 7 | personality: ?Personality = null, |
| 8 | 8 | file: File.Index = 0, | |
| 9 | var it = object.getEhFrameRecordsIterator(); | 9 | alive: bool = false, |
| 10 | 10 | ||
| 11 | for (object.exec_atoms.items) |atom_index| { | 11 | pub fn parse(cie: *Cie, macho_file: *MachO) !void { |
| 12 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 12 | const tracy = trace(@src()); |
| 13 | while (inner_syms_it.next()) |sym| { | 13 | defer tracy.end(); |
| 14 | const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; | 14 | |
| 15 | if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue; | 15 | const data = cie.getData(macho_file); |
| 16 | it.seekTo(fde_offset); | 16 | const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0); |
| 17 | const fde = (it.next() catch continue).?; // We don't care about this error since we already handled it | 17 | |
| 18 | 18 | if (aug[0] != 'z') return; // TODO should we error out? | |
| 19 | const cie_ptr = fde.getCiePointerSource(@intCast(object_id), macho_file, fde_offset); | 19 | |
| 20 | const cie_offset = fde_offset + 4 - cie_ptr; | 20 | var stream = std.io.fixedBufferStream(data[9 + aug.len + 1 ..]); |
| 21 | 21 | var creader = std.io.countingReader(stream.reader()); | |
| 22 | if (!cies.contains(cie_offset)) { | 22 | const reader = creader.reader(); |
| 23 | try cies.putNoClobber(cie_offset, {}); | 23 | |
| 24 | it.seekTo(cie_offset); | 24 | _ = try leb.readULEB128(u64, reader); // code alignment factor |
| 25 | const cie = (it.next() catch continue).?; // We don't care about this error since we already handled it | 25 | _ = try leb.readULEB128(u64, reader); // data alignment factor |
| 26 | try cie.scanRelocs(macho_file, @as(u32, @intCast(object_id)), cie_offset); | 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 | ||
| 27 | } | 34 | } |
| 28 | } | 35 | }, |
| 29 | } | 36 | 'P' => { |
| 30 | } | 37 | const enc = try reader.readByte(); |
| 31 | } | 38 | if (enc != EH_PE.pcrel | EH_PE.indirect | EH_PE.sdata4) { |
| 32 | 39 | @panic("unexpected personality pointer encoding"); // TODO error | |
| 33 | pub 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(); | ||
| 76 | } | 40 | } |
| 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 | } | ||
| 77 | 54 | ||
| 78 | size += source_fde_record.getSize(); | 55 | pub inline fn getSize(cie: Cie) u32 { |
| 79 | } | 56 | return cie.size + 4; |
| 80 | } | 57 | } |
| 81 | 58 | ||
| 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; | ||
| 83 | } | 62 | } |
| 84 | } | ||
| 85 | 63 | ||
| 86 | pub fn write(macho_file: *MachO, unwind_info: *UnwindInfo) !void { | 64 | pub fn getData(cie: Cie, macho_file: *MachO) []const u8 { |
| 87 | const sect_id = macho_file.eh_frame_section_index orelse return; | 65 | const object = cie.getObject(macho_file); |
| 88 | const sect = macho_file.sections.items(.header)[sect_id]; | 66 | return object.eh_frame_data.items[cie.offset..][0..cie.getSize()]; |
| 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(); | ||
| 103 | } | 67 | } |
| 104 | 68 | ||
| 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 | } | ||
| 106 | 73 | ||
| 107 | for (macho_file.objects.items, 0..) |*object, object_id| { | 74 | pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool { |
| 108 | try eh_records.ensureUnusedCapacity(2 * @as(u32, @intCast(object.exec_atoms.items.len))); | 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 | } | ||
| 109 | 82 | ||
| 110 | var cies = std.AutoHashMap(u32, u32).init(gpa); | 83 | pub fn format( |
| 111 | defer cies.deinit(); | 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 | } | ||
| 112 | 95 | ||
| 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 | } | ||
| 114 | 102 | ||
| 115 | for (object.exec_atoms.items) |atom_index| { | 103 | const FormatContext = struct { |
| 116 | var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index); | 104 | cie: Cie, |
| 117 | while (inner_syms_it.next()) |reloc_target| { | 105 | macho_file: *MachO, |
| 118 | const fde_record_offset = object.eh_frame_records_lookup.get(reloc_target) orelse continue; | 106 | }; |
| 119 | if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue; | ||
| 120 | 107 | ||
| 121 | const record_id = unwind_info.records_lookup.get(reloc_target) orelse continue; | 108 | fn format2( |
| 122 | const record = &unwind_info.records.items[record_id]; | 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 | } | ||
| 123 | 123 | ||
| 124 | // TODO skip this check if no __compact_unwind is present | 124 | pub const Index = u32; |
| 125 | const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); | ||
| 126 | if (!is_dwarf) continue; | ||
| 127 | 125 | ||
| 128 | eh_it.seekTo(fde_record_offset); | 126 | pub const Personality = struct { |
| 129 | const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error | 127 | index: Symbol.Index = 0, |
| 128 | offset: u32 = 0, | ||
| 129 | }; | ||
| 130 | }; | ||
| 130 | 131 | ||
| 131 | const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset); | 132 | pub const Fde = struct { |
| 132 | const cie_offset = fde_record_offset + 4 - cie_ptr; | 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 | } | ||
| 133 | 180 | ||
| 134 | const gop = try cies.getOrPut(cie_offset); | 181 | const cie = fde.getCie(macho_file); |
| 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 | } | ||
| 148 | 182 | ||
| 149 | var fde_record = try source_fde_record.toOwned(gpa); | 183 | // Parse LSDA atom index if any |
| 150 | try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{ | 184 | if (cie.lsda_size) |lsda_size| { |
| 151 | .source_offset = fde_record_offset, | 185 | var stream = std.io.fixedBufferStream(data[24..]); |
| 152 | .out_offset = eh_frame_offset, | 186 | var creader = std.io.countingReader(stream.reader()); |
| 153 | .sect_addr = sect.addr, | 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, | ||
| 154 | }); | 198 | }); |
| 155 | fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*); | 199 | return error.MalformedObject; |
| 156 | 200 | }; | |
| 157 | switch (cpu_arch) { | 201 | const lsda_atom = fde.getLsdaAtom(macho_file).?; |
| 158 | .aarch64 => {}, // relocs take care of LSDA pointers | 202 | fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file)); |
| 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 | } | ||
| 209 | } | 203 | } |
| 210 | } | 204 | } |
| 211 | 205 | ||
| 212 | var buffer = std.ArrayList(u8).init(gpa); | 206 | pub inline fn getSize(fde: Fde) u32 { |
| 213 | defer buffer.deinit(); | 207 | return fde.size + 4; |
| 214 | const writer = buffer.writer(); | 208 | } |
| 215 | 209 | ||
| 216 | for (eh_records.values()) |record| { | 210 | pub fn getObject(fde: Fde, macho_file: *MachO) *Object { |
| 217 | try writer.writeInt(u32, record.size, .little); | 211 | const file = macho_file.getFile(fde.file).?; |
| 218 | try buffer.appendSlice(record.data); | 212 | return file.object; |
| 219 | } | 213 | } |
| 220 | 214 | ||
| 221 | try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset); | 215 | pub fn getData(fde: Fde, macho_file: *MachO) []const u8 { |
| 222 | } | 216 | const object = fde.getObject(macho_file); |
| 223 | const EhFrameRecordTag = enum { cie, fde }; | 217 | return object.eh_frame_data.items[fde.offset..][0..fde.getSize()]; |
| 218 | } | ||
| 224 | 219 | ||
| 225 | pub fn EhFrameRecord(comptime is_mutable: bool) type { | 220 | pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie { |
| 226 | return struct { | 221 | const object = fde.getObject(macho_file); |
| 227 | tag: EhFrameRecordTag, | 222 | return &object.cies.items[fde.cie]; |
| 228 | size: u32, | 223 | } |
| 229 | data: if (is_mutable) []u8 else []const u8, | ||
| 230 | 224 | ||
| 231 | const Record = @This(); | 225 | pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom { |
| 226 | return macho_file.getAtom(fde.atom).?; | ||
| 227 | } | ||
| 232 | 228 | ||
| 233 | pub fn deinit(rec: *Record, gpa: Allocator) void { | 229 | pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom { |
| 234 | comptime assert(is_mutable); | 230 | return macho_file.getAtom(fde.lsda); |
| 235 | gpa.free(rec.data); | 231 | } |
| 236 | } | ||
| 237 | 232 | ||
| 238 | pub fn toOwned(rec: Record, gpa: Allocator) Allocator.Error!EhFrameRecord(true) { | 233 | pub fn format( |
| 239 | const data = try gpa.dupe(u8, rec.data); | 234 | fde: Fde, |
| 240 | return EhFrameRecord(true){ | 235 | comptime unused_fmt_string: []const u8, |
| 241 | .tag = rec.tag, | 236 | options: std.fmt.FormatOptions, |
| 242 | .size = rec.size, | 237 | writer: anytype, |
| 243 | .data = data, | 238 | ) !void { |
| 244 | }; | 239 | _ = fde; |
| 245 | } | 240 | _ = unused_fmt_string; |
| 241 | _ = options; | ||
| 242 | _ = writer; | ||
| 243 | @compileError("do not format FDEs directly"); | ||
| 244 | } | ||
| 246 | 245 | ||
| 247 | pub inline fn getSize(rec: Record) u32 { | 246 | pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 248 | return 4 + rec.size; | 247 | return .{ .data = .{ |
| 249 | } | 248 | .fde = fde, |
| 249 | .macho_file = macho_file, | ||
| 250 | } }; | ||
| 251 | } | ||
| 250 | 252 | ||
| 251 | pub fn scanRelocs( | 253 | const FormatContext = struct { |
| 252 | rec: Record, | 254 | fde: Fde, |
| 253 | macho_file: *MachO, | 255 | macho_file: *MachO, |
| 254 | object_id: u32, | 256 | }; |
| 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 | } | ||
| 261 | 257 | ||
| 262 | pub fn getTargetSymbolAddress(rec: Record, ctx: struct { | 258 | fn format2( |
| 263 | base_addr: u64, | 259 | ctx: FormatContext, |
| 264 | base_offset: u64, | 260 | comptime unused_fmt_string: []const u8, |
| 265 | }) u64 { | 261 | options: std.fmt.FormatOptions, |
| 266 | assert(rec.tag == .fde); | 262 | writer: anytype, |
| 267 | const addend = mem.readInt(i64, rec.data[4..][0..8], .little); | 263 | ) !void { |
| 268 | return @as(u64, @intCast(@as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)) + addend)); | 264 | _ = unused_fmt_string; |
| 269 | } | 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 | } | ||
| 270 | 276 | ||
| 271 | pub fn setTargetSymbolAddress(rec: *Record, value: u64, ctx: struct { | 277 | pub const Index = u32; |
| 272 | base_addr: u64, | 278 | }; |
| 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 | } | ||
| 279 | 279 | ||
| 280 | pub fn getPersonalityPointerReloc( | 280 | pub const Iterator = struct { |
| 281 | rec: Record, | 281 | data: []const u8, |
| 282 | macho_file: *MachO, | 282 | pos: u32 = 0, |
| 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 | } | ||
| 320 | 283 | ||
| 321 | pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct { | 284 | pub const Record = struct { |
| 322 | source_offset: u32, | 285 | tag: enum { fde, cie }, |
| 323 | out_offset: u32, | 286 | offset: u32, |
| 324 | sect_addr: u64, | 287 | size: u32, |
| 325 | }) !void { | 288 | }; |
| 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 | } | ||
| 381 | 289 | ||
| 382 | pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 { | 290 | pub fn next(it: *Iterator) !?Record { |
| 383 | assert(rec.tag == .fde); | 291 | if (it.pos >= it.data.len) return null; |
| 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 | } | ||
| 405 | 292 | ||
| 406 | pub fn getCiePointer(rec: Record) u32 { | 293 | var stream = std.io.fixedBufferStream(it.data[it.pos..]); |
| 407 | assert(rec.tag == .fde); | 294 | const reader = stream.reader(); |
| 408 | return mem.readInt(u32, rec.data[0..4], .little); | ||
| 409 | } | ||
| 410 | 295 | ||
| 411 | pub fn setCiePointer(rec: *Record, ptr: u32) void { | 296 | const size = try reader.readInt(u32, .little); |
| 412 | assert(rec.tag == .fde); | 297 | if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS"); |
| 413 | mem.writeInt(u32, rec.data[0..4], ptr, .little); | ||
| 414 | } | ||
| 415 | 298 | ||
| 416 | pub fn getAugmentationString(rec: Record) []const u8 { | 299 | const id = try reader.readInt(u32, .little); |
| 417 | assert(rec.tag == .cie); | 300 | const record = Record{ |
| 418 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(rec.data.ptr + 5)), 0); | 301 | .tag = if (id == 0) .cie else .fde, |
| 419 | } | 302 | .offset = it.pos, |
| 303 | .size = size, | ||
| 304 | }; | ||
| 305 | it.pos += size + 4; | ||
| 420 | 306 | ||
| 421 | pub fn getPersonalityPointer(rec: Record, ctx: struct { | 307 | return record; |
| 422 | base_addr: u64, | 308 | } |
| 423 | base_offset: u64, | 309 | }; |
| 424 | }) !?u64 { | ||
| 425 | assert(rec.tag == .cie); | ||
| 426 | const aug_str = rec.getAugmentationString(); | ||
| 427 | 310 | ||
| 428 | var stream = std.io.fixedBufferStream(rec.data[9 + aug_str.len ..]); | 311 | pub fn calcSize(macho_file: *MachO) !u32 { |
| 429 | var creader = std.io.countingReader(stream.reader()); | 312 | const tracy = trace(@src()); |
| 430 | const reader = creader.reader(); | 313 | defer tracy.end(); |
| 431 | 314 | ||
| 432 | for (aug_str, 0..) |ch, i| switch (ch) { | 315 | var offset: u32 = 0; |
| 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 | }; | ||
| 453 | 316 | ||
| 454 | return null; | 317 | var cies = std.ArrayList(Cie).init(macho_file.base.comp.gpa); |
| 455 | } | 318 | defer cies.deinit(); |
| 319 | |||
| 320 | for (macho_file.objects.items) |index| { | ||
| 321 | const object = macho_file.getFile(index).?.object; | ||
| 456 | 322 | ||
| 457 | pub fn getLsdaPointer(rec: Record, cie: Record, ctx: struct { | 323 | outer: for (object.cies.items) |*cie| { |
| 458 | base_addr: u64, | 324 | for (cies.items) |other| { |
| 459 | base_offset: u64, | 325 | if (other.eql(cie.*, macho_file)) { |
| 460 | }) !?u64 { | 326 | // We already have a CIE record that has the exact same contents, so instead of |
| 461 | assert(rec.tag == .fde); | 327 | // duplicating them, we mark this one dead and set its output offset to be |
| 462 | const enc = (try cie.getLsdaEncoding()) orelse return null; | 328 | // equal to that of the alive record. This way, we won't have to rewrite |
| 463 | var stream = std.io.fixedBufferStream(rec.data[20..]); | 329 | // Fde.cie_index field when committing the records to file. |
| 464 | const reader = stream.reader(); | 330 | cie.out_offset = other.out_offset; |
| 465 | _ = try reader.readByte(); | 331 | continue :outer; |
| 466 | const offset = ctx.base_offset + 25; | 332 | } |
| 467 | const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader); | 333 | } |
| 468 | return ptr; | 334 | cie.alive = true; |
| 335 | cie.out_offset = offset; | ||
| 336 | offset += cie.getSize(); | ||
| 337 | try cies.append(cie.*); | ||
| 469 | } | 338 | } |
| 339 | } | ||
| 470 | 340 | ||
| 471 | pub fn setLsdaPointer(rec: *Record, cie: Record, value: u64, ctx: struct { | 341 | for (macho_file.objects.items) |index| { |
| 472 | base_addr: u64, | 342 | const object = macho_file.getFile(index).?.object; |
| 473 | base_offset: u64, | 343 | for (object.fdes.items) |*fde| { |
| 474 | }) !void { | 344 | if (!fde.alive) continue; |
| 475 | assert(rec.tag == .fde); | 345 | fde.out_offset = offset; |
| 476 | const enc = (try cie.getLsdaEncoding()) orelse unreachable; | 346 | offset += fde.getSize(); |
| 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); | ||
| 481 | } | 347 | } |
| 348 | } | ||
| 482 | 349 | ||
| 483 | fn getLsdaEncoding(rec: Record) !?u8 { | 350 | return offset; |
| 484 | assert(rec.tag == .cie); | 351 | } |
| 485 | const aug_str = rec.getAugmentationString(); | ||
| 486 | 352 | ||
| 487 | const base_offset = 9 + aug_str.len; | 353 | pub fn calcNumRelocs(macho_file: *MachO) u32 { |
| 488 | var stream = std.io.fixedBufferStream(rec.data[base_offset..]); | 354 | const tracy = trace(@src()); |
| 489 | var creader = std.io.countingReader(stream.reader()); | 355 | defer tracy.end(); |
| 490 | const reader = creader.reader(); | ||
| 491 | 356 | ||
| 492 | for (aug_str, 0..) |ch, i| switch (ch) { | 357 | var nreloc: u32 = 0; |
| 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 | }; | ||
| 512 | 358 | ||
| 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 | } | ||
| 514 | } | 366 | } |
| 367 | } | ||
| 515 | 368 | ||
| 516 | fn getEncodedPointer(enc: u8, pcrel_offset: i64, reader: anytype) !?u64 { | 369 | return nreloc; |
| 517 | if (enc == EH_PE.omit) return null; | 370 | } |
| 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 | }; | ||
| 531 | 371 | ||
| 532 | switch (enc & 0x70) { | 372 | pub fn write(macho_file: *MachO, buffer: []u8) void { |
| 533 | EH_PE.absptr => {}, | 373 | const tracy = trace(@src()); |
| 534 | EH_PE.pcrel => ptr += pcrel_offset, | 374 | defer tracy.end(); |
| 535 | EH_PE.datarel, | 375 | |
| 536 | EH_PE.textrel, | 376 | const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; |
| 537 | EH_PE.funcrel, | 377 | const addend: i64 = switch (macho_file.getTarget().cpu.arch) { |
| 538 | EH_PE.aligned, | 378 | .x86_64 => 4, |
| 539 | => return null, | 379 | else => 0, |
| 540 | else => return null, | 380 | }; |
| 541 | } | ||
| 542 | 381 | ||
| 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 | } | ||
| 544 | } | 400 | } |
| 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; | ||
| 545 | 407 | ||
| 546 | fn setEncodedPointer(enc: u8, pcrel_offset: i64, value: u64, writer: anytype) !void { | 408 | @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file)); |
| 547 | if (enc == EH_PE.omit) return; | ||
| 548 | 409 | ||
| 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 | } | ||
| 550 | 415 | ||
| 551 | switch (enc & 0x70) { | 416 | { |
| 552 | EH_PE.absptr => {}, | 417 | const offset = fde.out_offset + 8; |
| 553 | EH_PE.pcrel => actual -= pcrel_offset, | 418 | const saddr = sect.addr + offset; |
| 554 | EH_PE.datarel, | 419 | const taddr = fde.getAtom(macho_file).value; |
| 555 | EH_PE.textrel, | 420 | std.mem.writeInt( |
| 556 | EH_PE.funcrel, | 421 | i64, |
| 557 | EH_PE.aligned, | 422 | buffer[offset..][0..8], |
| 558 | => unreachable, | 423 | @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)), |
| 559 | else => unreachable, | 424 | .little, |
| 425 | ); | ||
| 560 | } | 426 | } |
| 561 | 427 | ||
| 562 | switch (enc & 0x0F) { | 428 | if (fde.getLsdaAtom(macho_file)) |atom| { |
| 563 | EH_PE.absptr => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little), | 429 | const offset = fde.out_offset + fde.lsda_ptr_offset; |
| 564 | EH_PE.udata2 => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(actual)))), .little), | 430 | const saddr = sect.addr + offset; |
| 565 | EH_PE.udata4 => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(actual)))), .little), | 431 | const taddr = atom.value + fde.lsda_offset; |
| 566 | EH_PE.udata8 => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little), | 432 | switch (fde.getCie(macho_file).lsda_size.?) { |
| 567 | EH_PE.uleb128 => try leb.writeULEB128(writer, @as(u64, @bitCast(actual))), | 433 | .p32 => std.mem.writeInt( |
| 568 | EH_PE.sdata2 => try writer.writeInt(i16, @as(i16, @intCast(actual)), .little), | 434 | i32, |
| 569 | EH_PE.sdata4 => try writer.writeInt(i32, @as(i32, @intCast(actual)), .little), | 435 | buffer[offset..][0..4], |
| 570 | EH_PE.sdata8 => try writer.writeInt(i64, actual, .little), | 436 | @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend), |
| 571 | EH_PE.sleb128 => try leb.writeILEB128(writer, actual), | 437 | .little, |
| 572 | else => unreachable, | 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 | } | ||
| 573 | } | 446 | } |
| 574 | } | 447 | } |
| 575 | }; | 448 | } |
| 576 | } | ||
| 577 | |||
| 578 | pub 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]; | ||
| 585 | } | 449 | } |
| 586 | 450 | ||
| 587 | pub const Iterator = struct { | 451 | pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void { |
| 588 | data: []const u8, | 452 | const tracy = trace(@src()); |
| 589 | pos: u32 = 0, | 453 | defer tracy.end(); |
| 590 | 454 | ||
| 591 | pub fn next(it: *Iterator) !?EhFrameRecord(false) { | 455 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 592 | if (it.pos >= it.data.len) return null; | 456 | const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; |
| 593 | 457 | const addend: i64 = switch (cpu_arch) { | |
| 594 | var stream = std.io.fixedBufferStream(it.data[it.pos..]); | 458 | .x86_64 => 4, |
| 595 | const reader = stream.reader(); | 459 | else => 0, |
| 460 | }; | ||
| 596 | 461 | ||
| 597 | const size = try reader.readInt(u32, .little); | 462 | for (macho_file.objects.items) |index| { |
| 598 | if (size == 0xFFFFFFFF) { | 463 | const object = macho_file.getFile(index).?.object; |
| 599 | log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{}); | 464 | for (object.cies.items) |cie| { |
| 600 | return error.BadDwarfCfi; | 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 | } | ||
| 601 | } | 485 | } |
| 486 | } | ||
| 602 | 487 | ||
| 603 | const id = try reader.readInt(u32, .little); | 488 | for (macho_file.objects.items) |index| { |
| 604 | const tag: EhFrameRecordTag = if (id == 0) .cie else .fde; | 489 | const object = macho_file.getFile(index).?.object; |
| 605 | const offset: u32 = 4; | 490 | for (object.fdes.items) |fde| { |
| 606 | const record = EhFrameRecord(false){ | 491 | if (!fde.alive) continue; |
| 607 | .tag = tag, | ||
| 608 | .size = size, | ||
| 609 | .data = it.data[it.pos + offset ..][0..size], | ||
| 610 | }; | ||
| 611 | 492 | ||
| 612 | it.pos += size + offset; | 493 | @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file)); |
| 613 | 494 | ||
| 614 | return record; | 495 | { |
| 615 | } | 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 | } | ||
| 616 | 500 | ||
| 617 | pub fn reset(it: *Iterator) void { | 501 | { |
| 618 | it.pos = 0; | 502 | const offset = fde.out_offset + 8; |
| 619 | } | 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 | } | ||
| 620 | 512 | ||
| 621 | pub fn seekTo(it: *Iterator, pos: u32) void { | 513 | if (fde.getLsdaAtom(macho_file)) |atom| { |
| 622 | assert(pos >= 0 and pos < it.data.len); | 514 | const offset = fde.out_offset + fde.lsda_ptr_offset; |
| 623 | it.pos = pos; | 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 | } | ||
| 624 | } | 533 | } |
| 625 | }; | 534 | } |
| 626 | 535 | ||
| 627 | pub const EH_PE = struct { | 536 | pub const EH_PE = struct { |
| 628 | pub const absptr = 0x00; | 537 | pub const absptr = 0x00; |
| ... | @@ -643,17 +552,17 @@ pub const EH_PE = struct { | ... | @@ -643,17 +552,17 @@ pub const EH_PE = struct { |
| 643 | pub const omit = 0xFF; | 552 | pub const omit = 0xFF; |
| 644 | }; | 553 | }; |
| 645 | 554 | ||
| 646 | const std = @import("std"); | ||
| 647 | const assert = std.debug.assert; | 555 | const assert = std.debug.assert; |
| 556 | const leb = std.leb; | ||
| 648 | const macho = std.macho; | 557 | const macho = std.macho; |
| 649 | const math = std.math; | 558 | const math = std.math; |
| 650 | const mem = std.mem; | 559 | const mem = std.mem; |
| 651 | const leb = std.leb; | 560 | const std = @import("std"); |
| 652 | const log = std.log.scoped(.eh_frame); | 561 | const trace = @import("../../tracy.zig").trace; |
| 653 | 562 | ||
| 654 | const Allocator = mem.Allocator; | 563 | const Allocator = std.mem.Allocator; |
| 655 | const Atom = @import("Atom.zig"); | 564 | const Atom = @import("Atom.zig"); |
| 565 | const File = @import("file.zig").File; | ||
| 656 | const MachO = @import("../MachO.zig"); | 566 | const MachO = @import("../MachO.zig"); |
| 657 | const Relocation = @import("Relocation.zig"); | 567 | const Object = @import("Object.zig"); |
| 658 | const SymbolWithLoc = MachO.SymbolWithLoc; | 568 | const Symbol = @import("Symbol.zig"); |
| 659 | const UnwindInfo = @import("UnwindInfo.zig"); |
src/link/MachO/fat.zig+23-20| ... | @@ -1,24 +1,34 @@ | ... | @@ -1,24 +1,34 @@ |
| 1 | pub fn isFatLibrary(file: std.fs.File) bool { | 1 | const std = @import("std"); |
| 2 | const reader = file.reader(); | 2 | const assert = std.debug.assert; |
| 3 | const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false; | 3 | const builtin = @import("builtin"); |
| 4 | defer file.seekTo(0) catch {}; | 4 | const log = std.log.scoped(.macho); |
| 5 | const macho = std.macho; | ||
| 6 | const mem = std.mem; | ||
| 7 | const native_endian = builtin.target.cpu.arch.endian(); | ||
| 8 | |||
| 9 | const MachO = @import("../MachO.zig"); | ||
| 10 | |||
| 11 | pub 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; | ||
| 5 | return hdr.magic == macho.FAT_MAGIC; | 15 | return hdr.magic == macho.FAT_MAGIC; |
| 6 | } | 16 | } |
| 7 | 17 | ||
| 8 | pub const Arch = struct { | 18 | pub const Arch = struct { |
| 9 | tag: std.Target.Cpu.Arch, | 19 | tag: std.Target.Cpu.Arch, |
| 10 | offset: u64, | 20 | offset: u32, |
| 21 | size: u32, | ||
| 11 | }; | 22 | }; |
| 12 | 23 | ||
| 13 | /// Caller owns the memory. | 24 | pub fn parseArchs(path: []const u8, buffer: *[2]Arch) ![]const Arch { |
| 14 | pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch { | 25 | const file = try std.fs.cwd().openFile(path, .{}); |
| 26 | defer file.close(); | ||
| 15 | const reader = file.reader(); | 27 | const reader = file.reader(); |
| 16 | const fat_header = try reader.readStructEndian(macho.fat_header, .big); | 28 | const fat_header = try reader.readStructEndian(macho.fat_header, .big); |
| 17 | assert(fat_header.magic == macho.FAT_MAGIC); | 29 | assert(fat_header.magic == macho.FAT_MAGIC); |
| 18 | 30 | ||
| 19 | var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch); | 31 | var count: usize = 0; |
| 20 | defer archs.deinit(); | ||
| 21 | |||
| 22 | var fat_arch_index: u32 = 0; | 32 | var fat_arch_index: u32 = 0; |
| 23 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { | 33 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { |
| 24 | const fat_arch = try reader.readStructEndian(macho.fat_arch, .big); | 34 | 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 { | ... | @@ -29,16 +39,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch { |
| 29 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, | 39 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, |
| 30 | else => continue, | 40 | else => continue, |
| 31 | }; | 41 | }; |
| 32 | 42 | buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size }; | |
| 33 | archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset }); | 43 | count += 1; |
| 34 | } | 44 | } |
| 35 | 45 | ||
| 36 | return archs.toOwnedSlice(); | 46 | return buffer[0..count]; |
| 37 | } | 47 | } |
| 38 | |||
| 39 | const std = @import("std"); | ||
| 40 | const assert = std.debug.assert; | ||
| 41 | const log = std.log.scoped(.archive); | ||
| 42 | const macho = std.macho; | ||
| 43 | const mem = std.mem; | ||
| 44 | const Allocator = mem.Allocator; |
src/link/MachO/file.zig created+120| ... | @@ -0,0 +1,120 @@ | ||
| 1 | pub 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 | |||
| 110 | const macho = std.macho; | ||
| 111 | const std = @import("std"); | ||
| 112 | |||
| 113 | const Allocator = std.mem.Allocator; | ||
| 114 | const Atom = @import("Atom.zig"); | ||
| 115 | const InternalObject = @import("InternalObject.zig"); | ||
| 116 | const MachO = @import("../MachO.zig"); | ||
| 117 | const Object = @import("Object.zig"); | ||
| 118 | const Dylib = @import("Dylib.zig"); | ||
| 119 | const Symbol = @import("Symbol.zig"); | ||
| 120 | const ZigObject = @import("ZigObject.zig"); | ||
src/link/MachO/hasher.zig+9-2| ... | @@ -9,6 +9,9 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -9,6 +9,9 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 9 | chunk_size: u64 = 0x4000, | 9 | chunk_size: u64 = 0x4000, |
| 10 | max_file_size: ?u64 = null, | 10 | max_file_size: ?u64 = null, |
| 11 | }) !void { | 11 | }) !void { |
| 12 | const tracy = trace(@src()); | ||
| 13 | defer tracy.end(); | ||
| 14 | |||
| 12 | var wg: WaitGroup = .{}; | 15 | var wg: WaitGroup = .{}; |
| 13 | 16 | ||
| 14 | const file_size = blk: { | 17 | const file_size = blk: { |
| ... | @@ -29,7 +32,10 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -29,7 +32,10 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 29 | 32 | ||
| 30 | for (out, results, 0..) |*out_buf, *result, i| { | 33 | for (out, results, 0..) |*out_buf, *result, i| { |
| 31 | const fstart = i * chunk_size; | 34 | 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; | ||
| 33 | wg.start(); | 39 | wg.start(); |
| 34 | try self.thread_pool.spawn(worker, .{ | 40 | try self.thread_pool.spawn(worker, .{ |
| 35 | file, | 41 | file, |
| ... | @@ -61,10 +67,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { | ... | @@ -61,10 +67,11 @@ pub fn ParallelHasher(comptime Hasher: type) type { |
| 61 | }; | 67 | }; |
| 62 | } | 68 | } |
| 63 | 69 | ||
| 64 | const std = @import("std"); | ||
| 65 | const assert = std.debug.assert; | 70 | const assert = std.debug.assert; |
| 66 | const fs = std.fs; | 71 | const fs = std.fs; |
| 67 | const mem = std.mem; | 72 | const mem = std.mem; |
| 73 | const std = @import("std"); | ||
| 74 | const trace = @import("../../tracy.zig").trace; | ||
| 68 | 75 | ||
| 69 | const Allocator = mem.Allocator; | 76 | const Allocator = mem.Allocator; |
| 70 | const ThreadPool = std.Thread.Pool; | 77 | const ThreadPool = std.Thread.Pool; |
src/link/MachO/load_commands.zig+97-389| ... | @@ -1,4 +1,13 @@ | ... | @@ -1,4 +1,13 @@ |
| 1 | /// Default path to dyld. | 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; | ||
| 3 | const log = std.log.scoped(.link); | ||
| 4 | const macho = std.macho; | ||
| 5 | const mem = std.mem; | ||
| 6 | |||
| 7 | const Allocator = mem.Allocator; | ||
| 8 | const Dylib = @import("Dylib.zig"); | ||
| 9 | const MachO = @import("../MachO.zig"); | ||
| 10 | |||
| 2 | pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; | 11 | pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; |
| 3 | 12 | ||
| 4 | fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { | 13 | fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { |
| ... | @@ -7,31 +16,19 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool | ... | @@ -7,31 +16,19 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool |
| 7 | return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64)); | 16 | return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64)); |
| 8 | } | 17 | } |
| 9 | 18 | ||
| 10 | const CalcLCsSizeCtx = struct { | 19 | pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 { |
| 11 | segments: []const macho.segment_command_64, | ||
| 12 | dylibs: []const Dylib, | ||
| 13 | referenced_dylibs: []u16, | ||
| 14 | wants_function_starts: bool = true, | ||
| 15 | }; | ||
| 16 | |||
| 17 | fn 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; | ||
| 21 | var sizeofcmds: u64 = 0; | 20 | var sizeofcmds: u64 = 0; |
| 22 | for (ctx.segments) |seg| { | 21 | |
| 23 | sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); | 22 | // LC_SEGMENT_64 |
| 24 | if (mem.eql(u8, seg.segName(), "__TEXT")) { | 23 | sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len; |
| 25 | has_text_segment = true; | 24 | for (macho_file.segments.items) |seg| { |
| 26 | } | 25 | sizeofcmds += seg.nsects * @sizeOf(macho.section_64); |
| 27 | } | 26 | } |
| 28 | 27 | ||
| 29 | // LC_DYLD_INFO_ONLY | 28 | // LC_DYLD_INFO_ONLY |
| 30 | sizeofcmds += @sizeOf(macho.dyld_info_command); | 29 | sizeofcmds += @sizeOf(macho.dyld_info_command); |
| 31 | // LC_FUNCTION_STARTS | 30 | // LC_FUNCTION_STARTS |
| 32 | if (has_text_segment and ctx.wants_function_starts) { | 31 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 33 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | ||
| 34 | } | ||
| 35 | // LC_DATA_IN_CODE | 32 | // LC_DATA_IN_CODE |
| 36 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | 33 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 37 | // LC_SYMTAB | 34 | // LC_SYMTAB |
| ... | @@ -45,15 +42,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -45,15 +42,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 45 | false, | 42 | false, |
| 46 | ); | 43 | ); |
| 47 | // LC_MAIN | 44 | // LC_MAIN |
| 48 | if (comp.config.output_mode == .Exe) { | 45 | if (!macho_file.base.isDynLib()) { |
| 49 | sizeofcmds += @sizeOf(macho.entry_point_command); | 46 | sizeofcmds += @sizeOf(macho.entry_point_command); |
| 50 | } | 47 | } |
| 51 | // LC_ID_DYLIB | 48 | // LC_ID_DYLIB |
| 52 | if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) { | 49 | if (macho_file.base.isDynLib()) { |
| 53 | sizeofcmds += blk: { | 50 | sizeofcmds += blk: { |
| 54 | const emit = m.base.emit; | 51 | const emit = macho_file.base.emit; |
| 55 | const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path}); | 52 | const install_name = macho_file.install_name orelse emit.sub_path; |
| 56 | defer if (m.install_name == null) gpa.free(install_name); | ||
| 57 | break :blk calcInstallNameLen( | 53 | break :blk calcInstallNameLen( |
| 58 | @sizeOf(macho.dylib_command), | 54 | @sizeOf(macho.dylib_command), |
| 59 | install_name, | 55 | install_name, |
| ... | @@ -63,9 +59,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -63,9 +59,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 63 | } | 59 | } |
| 64 | // LC_RPATH | 60 | // LC_RPATH |
| 65 | { | 61 | { |
| 66 | var it = RpathIterator.init(gpa, m.base.rpath_list); | 62 | for (macho_file.base.rpath_list) |rpath| { |
| 67 | defer it.deinit(); | ||
| 68 | while (try it.next()) |rpath| { | ||
| 69 | sizeofcmds += calcInstallNameLen( | 63 | sizeofcmds += calcInstallNameLen( |
| 70 | @sizeOf(macho.rpath_command), | 64 | @sizeOf(macho.rpath_command), |
| 71 | rpath, | 65 | rpath, |
| ... | @@ -75,24 +69,20 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -75,24 +69,20 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 75 | } | 69 | } |
| 76 | // LC_SOURCE_VERSION | 70 | // LC_SOURCE_VERSION |
| 77 | sizeofcmds += @sizeOf(macho.source_version_command); | 71 | sizeofcmds += @sizeOf(macho.source_version_command); |
| 78 | // LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing | 72 | if (macho_file.platform.isBuildVersionCompatible()) { |
| 79 | { | 73 | // LC_BUILD_VERSION |
| 80 | const target = comp.root_mod.resolved_target.result; | 74 | sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); |
| 81 | const platform = Platform.fromTarget(target); | 75 | } else { |
| 82 | if (platform.isBuildVersionCompatible()) { | 76 | // LC_VERSION_MIN_* |
| 83 | // LC_BUILD_VERSION | 77 | sizeofcmds += @sizeOf(macho.version_min_command); |
| 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 | } | ||
| 89 | } | 78 | } |
| 90 | // LC_UUID | 79 | // LC_UUID |
| 91 | sizeofcmds += @sizeOf(macho.uuid_command); | 80 | sizeofcmds += @sizeOf(macho.uuid_command); |
| 92 | // LC_LOAD_DYLIB | 81 | // LC_LOAD_DYLIB |
| 93 | for (ctx.referenced_dylibs) |id| { | 82 | for (macho_file.dylibs.items) |index| { |
| 94 | const dylib = ctx.dylibs[id]; | 83 | const dylib = macho_file.getFile(index).?.dylib; |
| 95 | const dylib_id = dylib.id orelse unreachable; | 84 | assert(dylib.isAlive(macho_file)); |
| 85 | const dylib_id = dylib.id.?; | ||
| 96 | sizeofcmds += calcInstallNameLen( | 86 | sizeofcmds += calcInstallNameLen( |
| 97 | @sizeOf(macho.dylib_command), | 87 | @sizeOf(macho.dylib_command), |
| 98 | dylib_id.name, | 88 | dylib_id.name, |
| ... | @@ -100,19 +90,48 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { | ... | @@ -100,19 +90,48 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { |
| 100 | ); | 90 | ); |
| 101 | } | 91 | } |
| 102 | // LC_CODE_SIGNATURE | 92 | // LC_CODE_SIGNATURE |
| 103 | if (m.requiresCodeSignature()) { | 93 | if (macho_file.requiresCodeSig()) { |
| 104 | sizeofcmds += @sizeOf(macho.linkedit_data_command); | 94 | sizeofcmds += @sizeOf(macho.linkedit_data_command); |
| 105 | } | 95 | } |
| 106 | 96 | ||
| 107 | return @intCast(sizeofcmds); | 97 | return @as(u32, @intCast(sizeofcmds)); |
| 108 | } | 98 | } |
| 109 | 99 | ||
| 110 | pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { | 100 | pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 { |
| 111 | var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size; | 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 | |||
| 129 | pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 { | ||
| 130 | var padding: u32 = calcLoadCommandsSize(macho_file, false) + (macho_file.headerpad_size orelse 0); | ||
| 112 | log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); | 131 | log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); |
| 113 | 132 | ||
| 114 | if (m.headerpad_max_install_names) { | 133 | if (macho_file.headerpad_max_install_names) { |
| 115 | const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true); | 134 | const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true); |
| 116 | log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ | 135 | log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ |
| 117 | min_headerpad_size + @sizeOf(macho.mach_header_64), | 136 | min_headerpad_size + @sizeOf(macho.mach_header_64), |
| 118 | }); | 137 | }); |
| ... | @@ -125,34 +144,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { | ... | @@ -125,34 +144,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 { |
| 125 | return offset; | 144 | return offset; |
| 126 | } | 145 | } |
| 127 | 146 | ||
| 128 | pub fn calcNumOfLCs(lc_buffer: []const u8) u32 { | 147 | pub fn writeDylinkerLC(writer: anytype) !void { |
| 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 | |||
| 140 | pub fn writeDylinkerLC(lc_writer: anytype) !void { | ||
| 141 | const name_len = mem.sliceTo(default_dyld_path, 0).len; | 148 | const name_len = mem.sliceTo(default_dyld_path, 0).len; |
| 142 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 149 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 143 | u64, | 150 | u64, |
| 144 | @sizeOf(macho.dylinker_command) + name_len, | 151 | @sizeOf(macho.dylinker_command) + name_len, |
| 145 | @sizeOf(u64), | 152 | @sizeOf(u64), |
| 146 | ))); | 153 | ))); |
| 147 | try lc_writer.writeStruct(macho.dylinker_command{ | 154 | try writer.writeStruct(macho.dylinker_command{ |
| 148 | .cmd = .LOAD_DYLINKER, | 155 | .cmd = .LOAD_DYLINKER, |
| 149 | .cmdsize = cmdsize, | 156 | .cmdsize = cmdsize, |
| 150 | .name = @sizeOf(macho.dylinker_command), | 157 | .name = @sizeOf(macho.dylinker_command), |
| 151 | }); | 158 | }); |
| 152 | try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0)); | 159 | try writer.writeAll(mem.sliceTo(default_dyld_path, 0)); |
| 153 | const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; | 160 | const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; |
| 154 | if (padding > 0) { | 161 | if (padding > 0) { |
| 155 | try lc_writer.writeByteNTimes(0, padding); | 162 | try writer.writeByteNTimes(0, padding); |
| 156 | } | 163 | } |
| 157 | } | 164 | } |
| 158 | 165 | ||
| ... | @@ -164,14 +171,14 @@ const WriteDylibLCCtx = struct { | ... | @@ -164,14 +171,14 @@ const WriteDylibLCCtx = struct { |
| 164 | compatibility_version: u32 = 0x10000, | 171 | compatibility_version: u32 = 0x10000, |
| 165 | }; | 172 | }; |
| 166 | 173 | ||
| 167 | fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { | 174 | pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void { |
| 168 | const name_len = ctx.name.len + 1; | 175 | const name_len = ctx.name.len + 1; |
| 169 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 176 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 170 | u64, | 177 | u64, |
| 171 | @sizeOf(macho.dylib_command) + name_len, | 178 | @sizeOf(macho.dylib_command) + name_len, |
| 172 | @sizeOf(u64), | 179 | @sizeOf(u64), |
| 173 | ))); | 180 | ))); |
| 174 | try lc_writer.writeStruct(macho.dylib_command{ | 181 | try writer.writeStruct(macho.dylib_command{ |
| 175 | .cmd = ctx.cmd, | 182 | .cmd = ctx.cmd, |
| 176 | .cmdsize = cmdsize, | 183 | .cmdsize = cmdsize, |
| 177 | .dylib = .{ | 184 | .dylib = .{ |
| ... | @@ -181,15 +188,15 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { | ... | @@ -181,15 +188,15 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { |
| 181 | .compatibility_version = ctx.compatibility_version, | 188 | .compatibility_version = ctx.compatibility_version, |
| 182 | }, | 189 | }, |
| 183 | }); | 190 | }); |
| 184 | try lc_writer.writeAll(ctx.name); | 191 | try writer.writeAll(ctx.name); |
| 185 | try lc_writer.writeByte(0); | 192 | try writer.writeByte(0); |
| 186 | const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; | 193 | const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; |
| 187 | if (padding > 0) { | 194 | if (padding > 0) { |
| 188 | try lc_writer.writeByteNTimes(0, padding); | 195 | try writer.writeByteNTimes(0, padding); |
| 189 | } | 196 | } |
| 190 | } | 197 | } |
| 191 | 198 | ||
| 192 | pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void { | 199 | pub fn writeDylibIdLC(macho_file: *MachO, writer: anytype) !void { |
| 193 | const comp = macho_file.base.comp; | 200 | const comp = macho_file.base.comp; |
| 194 | const gpa = comp.gpa; | 201 | const gpa = comp.gpa; |
| 195 | assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic); | 202 | 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 { | ... | @@ -212,62 +219,31 @@ pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void { |
| 212 | .name = install_name, | 219 | .name = install_name, |
| 213 | .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)), | 220 | .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)), |
| 214 | .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)), | 221 | .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)), |
| 215 | }, lc_writer); | 222 | }, writer); |
| 216 | } | 223 | } |
| 217 | 224 | ||
| 218 | const RpathIterator = struct { | 225 | pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void { |
| 219 | buffer: []const []const u8, | 226 | for (rpaths) |rpath| { |
| 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 | |||
| 243 | pub 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| { | ||
| 251 | const rpath_len = rpath.len + 1; | 227 | const rpath_len = rpath.len + 1; |
| 252 | const cmdsize = @as(u32, @intCast(mem.alignForward( | 228 | const cmdsize = @as(u32, @intCast(mem.alignForward( |
| 253 | u64, | 229 | u64, |
| 254 | @sizeOf(macho.rpath_command) + rpath_len, | 230 | @sizeOf(macho.rpath_command) + rpath_len, |
| 255 | @sizeOf(u64), | 231 | @sizeOf(u64), |
| 256 | ))); | 232 | ))); |
| 257 | try lc_writer.writeStruct(macho.rpath_command{ | 233 | try writer.writeStruct(macho.rpath_command{ |
| 258 | .cmdsize = cmdsize, | 234 | .cmdsize = cmdsize, |
| 259 | .path = @sizeOf(macho.rpath_command), | 235 | .path = @sizeOf(macho.rpath_command), |
| 260 | }); | 236 | }); |
| 261 | try lc_writer.writeAll(rpath); | 237 | try writer.writeAll(rpath); |
| 262 | try lc_writer.writeByte(0); | 238 | try writer.writeByte(0); |
| 263 | const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; | 239 | const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; |
| 264 | if (padding > 0) { | 240 | if (padding > 0) { |
| 265 | try lc_writer.writeByteNTimes(0, padding); | 241 | try writer.writeByteNTimes(0, padding); |
| 266 | } | 242 | } |
| 267 | } | 243 | } |
| 268 | } | 244 | } |
| 269 | 245 | ||
| 270 | pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { | 246 | pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void { |
| 271 | const cmd: macho.LC = switch (platform.os_tag) { | 247 | const cmd: macho.LC = switch (platform.os_tag) { |
| 272 | .macos => .VERSION_MIN_MACOSX, | 248 | .macos => .VERSION_MIN_MACOSX, |
| 273 | .ios => .VERSION_MIN_IPHONEOS, | 249 | .ios => .VERSION_MIN_IPHONEOS, |
| ... | @@ -275,298 +251,30 @@ pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, | ... | @@ -275,298 +251,30 @@ pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, |
| 275 | .watchos => .VERSION_MIN_WATCHOS, | 251 | .watchos => .VERSION_MIN_WATCHOS, |
| 276 | else => unreachable, | 252 | else => unreachable, |
| 277 | }; | 253 | }; |
| 278 | try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{ | 254 | try writer.writeAll(mem.asBytes(&macho.version_min_command{ |
| 279 | .cmd = cmd, | 255 | .cmd = cmd, |
| 280 | .version = platform.toAppleVersion(), | 256 | .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(), | ||
| 282 | })); | 261 | })); |
| 283 | } | 262 | } |
| 284 | 263 | ||
| 285 | pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void { | 264 | pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void { |
| 286 | const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); | 265 | 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{ |
| 288 | .cmdsize = cmdsize, | 267 | .cmdsize = cmdsize, |
| 289 | .platform = platform.toApplePlatform(), | 268 | .platform = platform.toApplePlatform(), |
| 290 | .minos = platform.toAppleVersion(), | 269 | .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(), | ||
| 292 | .ntools = 1, | 274 | .ntools = 1, |
| 293 | }); | 275 | }); |
| 294 | try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ | 276 | try writer.writeAll(mem.asBytes(&macho.build_tool_version{ |
| 295 | .tool = .ZIG, | 277 | .tool = .ZIG, |
| 296 | .version = 0x0, | 278 | .version = 0x0, |
| 297 | })); | 279 | })); |
| 298 | } | 280 | } |
| 299 | |||
| 300 | pub 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 | |||
| 314 | pub 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 | |||
| 441 | const 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 | ||
| 450 | const 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 | |||
| 461 | inline 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 | |||
| 468 | pub 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 | |||
| 476 | pub 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. | ||
| 511 | fn 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. | ||
| 521 | fn 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 | |||
| 542 | const expect = std.testing.expect; | ||
| 543 | const expectEqual = std.testing.expectEqual; | ||
| 544 | |||
| 545 | fn 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 | |||
| 554 | test "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 | |||
| 562 | const std = @import("std"); | ||
| 563 | const assert = std.debug.assert; | ||
| 564 | const link = @import("../../link.zig"); | ||
| 565 | const log = std.log.scoped(.link); | ||
| 566 | const macho = std.macho; | ||
| 567 | const mem = std.mem; | ||
| 568 | |||
| 569 | const Allocator = mem.Allocator; | ||
| 570 | const Dylib = @import("Dylib.zig"); | ||
| 571 | const MachO = @import("../MachO.zig"); | ||
| 572 | const Compilation = @import("../../Compilation.zig"); |
src/link/MachO/relocatable.zig created+506| ... | @@ -0,0 +1,506 @@ | ||
| 1 | pub 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 | |||
| 112 | fn 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 | |||
| 125 | fn 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 | |||
| 149 | fn 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 | |||
| 177 | fn 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 | |||
| 208 | fn 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 | |||
| 234 | fn 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. | ||
| 262 | fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { | ||
| 263 | _ = ctx; | ||
| 264 | return lhs.r_address > rhs.r_address; | ||
| 265 | } | ||
| 266 | |||
| 267 | fn 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 | |||
| 308 | fn 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 | |||
| 395 | fn 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 | |||
| 417 | fn 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 | |||
| 462 | fn 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 | |||
| 492 | const assert = std.debug.assert; | ||
| 493 | const eh_frame = @import("eh_frame.zig"); | ||
| 494 | const link = @import("../../link.zig"); | ||
| 495 | const load_commands = @import("load_commands.zig"); | ||
| 496 | const macho = std.macho; | ||
| 497 | const math = std.math; | ||
| 498 | const mem = std.mem; | ||
| 499 | const state_log = std.log.scoped(.link_state); | ||
| 500 | const std = @import("std"); | ||
| 501 | const trace = @import("../../tracy.zig").trace; | ||
| 502 | |||
| 503 | const Atom = @import("Atom.zig"); | ||
| 504 | const Compilation = @import("../../Compilation.zig"); | ||
| 505 | const MachO = @import("../MachO.zig"); | ||
| 506 | const Symbol = @import("Symbol.zig"); | ||
src/link/MachO/stubs.zig deleted-169| ... | @@ -1,169 +0,0 @@ | ||
| 1 | pub 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 | |||
| 9 | pub 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 | |||
| 17 | pub 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 | |||
| 25 | pub 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 | |||
| 33 | pub 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 | |||
| 41 | pub 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 | |||
| 101 | pub 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 | |||
| 134 | pub 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 | |||
| 166 | const std = @import("std"); | ||
| 167 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 168 | |||
| 169 | const Relocation = @import("Relocation.zig"); | ||
src/link/MachO/synthetic.zig created+793| ... | @@ -0,0 +1,793 @@ | ||
| 1 | pub 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 | |||
| 120 | pub 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 | |||
| 224 | pub 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 | |||
| 315 | pub 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 | |||
| 443 | pub 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 | |||
| 514 | pub 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 | |||
| 621 | pub 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 | |||
| 746 | pub 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 | |||
| 775 | pub const RebaseSection = Rebase; | ||
| 776 | pub const BindSection = bind.Bind; | ||
| 777 | pub const WeakBindSection = bind.WeakBind; | ||
| 778 | pub const LazyBindSection = bind.LazyBind; | ||
| 779 | pub const ExportTrieSection = Trie; | ||
| 780 | |||
| 781 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 782 | const assert = std.debug.assert; | ||
| 783 | const bind = @import("dyld_info/bind.zig"); | ||
| 784 | const math = std.math; | ||
| 785 | const std = @import("std"); | ||
| 786 | const trace = @import("../../tracy.zig").trace; | ||
| 787 | |||
| 788 | const Allocator = std.mem.Allocator; | ||
| 789 | const MachO = @import("../MachO.zig"); | ||
| 790 | const Rebase = @import("dyld_info/Rebase.zig"); | ||
| 791 | const Relocation = @import("Relocation.zig"); | ||
| 792 | const Symbol = @import("Symbol.zig"); | ||
| 793 | const Trie = @import("dyld_info/Trie.zig"); | ||
src/link/MachO/thunks.zig+136-335| ... | @@ -1,374 +1,175 @@ | ... | @@ -1,374 +1,175 @@ |
| 1 | //! An algorithm for allocating output machine code section (aka `__TEXT,__text`), | 1 | pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { |
| 2 | //! and insertion of range extending thunks. As such, this algorithm is only run | 2 | const tracy = trace(@src()); |
| 3 | //! for a target that requires range extenders such as arm64. | 3 | defer tracy.end(); |
| 4 | //! | 4 | |
| 5 | //! The algorithm works pessimistically and assumes that any reference to an Atom in | 5 | const gpa = macho_file.base.comp.gpa; |
| 6 | //! another output section is out of range. | 6 | const slice = macho_file.sections.slice(); |
| 7 | 7 | const header = &slice.items(.header)[sect_id]; | |
| 8 | /// Branch instruction has 26 bits immediate but 4 byte aligned. | 8 | const atoms = slice.items(.atoms)[sect_id].items; |
| 9 | const jump_bits = @bitSizeOf(i28); | 9 | assert(atoms.len > 0); |
| 10 | 10 | ||
| 11 | const max_distance = (1 << (jump_bits - 1)); | 11 | for (atoms) |atom_index| { |
| 12 | 12 | macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1)); | |
| 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. | ||
| 17 | const max_allowed_distance = max_distance - 0x500_000; | ||
| 18 | |||
| 19 | pub 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; | ||
| 55 | } | 13 | } |
| 56 | 14 | ||
| 57 | pub fn getAlignment() u32 { | 15 | var i: usize = 0; |
| 58 | return @alignOf(u32); | 16 | while (i < atoms.len) { |
| 59 | } | 17 | const start = i; |
| 60 | 18 | const start_atom = macho_file.getAtom(atoms[start]).?; | |
| 61 | pub fn getTrampoline(self: Thunk, macho_file: *MachO, tag: Tag, target: SymbolWithLoc) ?SymbolWithLoc { | 19 | assert(start_atom.flags.alive); |
| 62 | const atom_index = self.lookup.get(.{ .tag = tag, .target = target }) orelse return null; | 20 | start_atom.value = try advance(header, start_atom.size, start_atom.alignment); |
| 63 | return macho_file.getAtom(atom_index).getSymbolWithLoc(); | 21 | i += 1; |
| 64 | } | 22 | |
| 65 | }; | 23 | while (i < atoms.len and |
| 66 | 24 | header.size - start_atom.value < max_allowed_distance) : (i += 1) | |
| 67 | pub fn createThunks(macho_file: *MachO, sect_id: u8) !void { | 25 | { |
| 68 | const header = &macho_file.sections.items(.header)[sect_id]; | 26 | const atom_index = atoms[i]; |
| 69 | if (header.size == 0) return; | 27 | const atom = macho_file.getAtom(atom_index).?; |
| 70 | 28 | assert(atom.flags.alive); | |
| 71 | const comp = macho_file.base.comp; | 29 | atom.value = try advance(header, atom.size, atom.alignment); |
| 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; | ||
| 126 | } | 30 | } |
| 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 }); | ||
| 132 | 31 | ||
| 133 | // Scan relocs in the group and create trampolines for any unreachable callsite. | 32 | // Insert a thunk at the group end |
| 134 | var atom_index = group_start; | 33 | const thunk_index = try macho_file.addThunk(); |
| 135 | while (true) { | 34 | const thunk = macho_file.getThunk(thunk_index); |
| 136 | const atom = macho_file.getAtom(atom_index); | 35 | thunk.out_n_sect = sect_id; |
| 137 | try scanRelocs( | 36 | |
| 138 | macho_file, | 37 | // Scan relocs in the group and create trampolines for any unreachable callsite |
| 139 | atom_index, | 38 | for (atoms[start..i]) |atom_index| { |
| 140 | allocated, | 39 | const atom = macho_file.getAtom(atom_index).?; |
| 141 | thunk_index, | 40 | log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) }); |
| 142 | group_end, | 41 | for (atom.getRelocs(macho_file)) |rel| { |
| 143 | ); | 42 | if (rel.type != .branch) continue; |
| 144 | 43 | if (isReachable(atom, rel, macho_file)) continue; | |
| 145 | if (atom_index == group_end) break; | 44 | try thunk.symbols.put(gpa, rel.target, {}); |
| 146 | 45 | } | |
| 147 | if (atom.next_index) |next_index| { | 46 | atom.thunk_index = thunk_index; |
| 148 | atom_index = next_index; | ||
| 149 | } else break; | ||
| 150 | } | 47 | } |
| 151 | 48 | ||
| 152 | offset = mem.alignForward(u64, offset, Thunk.getAlignment()); | 49 | thunk.value = try advance(header, thunk.size(), .@"4"); |
| 153 | allocateThunk(macho_file, thunk_index, offset, header); | ||
| 154 | offset += macho_file.thunks.items[thunk_index].getSize(); | ||
| 155 | 50 | ||
| 156 | const thunk = macho_file.thunks.items[thunk_index]; | 51 | log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); |
| 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 | } | ||
| 171 | } | 52 | } |
| 172 | |||
| 173 | header.size = @as(u32, @intCast(offset)); | ||
| 174 | } | 53 | } |
| 175 | 54 | ||
| 176 | fn allocateThunk( | 55 | fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) !u64 { |
| 177 | macho_file: *MachO, | 56 | const offset = alignment.forward(sect.size); |
| 178 | thunk_index: Thunk.Index, | 57 | const padding = offset - sect.size; |
| 179 | base_offset: u64, | 58 | sect.size += padding + size; |
| 180 | header: *macho.section_64, | 59 | sect.@"align" = @max(sect.@"align", alignment.toLog2Units()); |
| 181 | ) void { | 60 | return offset; |
| 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 | } | ||
| 208 | } | 61 | } |
| 209 | 62 | ||
| 210 | fn scanRelocs( | 63 | fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { |
| 211 | macho_file: *MachO, | 64 | const target = rel.getTargetSymbol(macho_file); |
| 212 | atom_index: Atom.Index, | 65 | if (target.flags.stubs or target.flags.objc_stubs) return false; |
| 213 | allocated: std.AutoHashMap(Atom.Index, void), | 66 | if (atom.out_n_sect != target.out_n_sect) return false; |
| 214 | thunk_index: Thunk.Index, | 67 | const target_atom = target.getAtom(macho_file).?; |
| 215 | group_end: Atom.Index, | 68 | if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; |
| 216 | ) !void { | 69 | const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off)); |
| 217 | const atom = macho_file.getAtom(atom_index); | 70 | const taddr: i64 = @intCast(rel.getTargetAddress(macho_file)); |
| 218 | const object = macho_file.objects.items[atom.getFile().?]; | 71 | _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; |
| 219 | 72 | return true; | |
| 220 | const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: { | 73 | } |
| 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); | ||
| 228 | 74 | ||
| 229 | for (relocs) |rel| { | 75 | pub const Thunk = struct { |
| 230 | if (!relocNeedsThunk(rel)) continue; | 76 | value: u64 = 0, |
| 77 | out_n_sect: u8 = 0, | ||
| 78 | symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{}, | ||
| 231 | 79 | ||
| 232 | const target = Atom.parseRelocTarget(macho_file, .{ | 80 | pub fn deinit(thunk: *Thunk, allocator: Allocator) void { |
| 233 | .object_id = atom.getFile().?, | 81 | thunk.symbols.deinit(allocator); |
| 234 | .rel = rel, | 82 | } |
| 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; | ||
| 240 | 83 | ||
| 241 | log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{ | 84 | pub fn size(thunk: Thunk) usize { |
| 242 | rel.r_address - base_offset, | 85 | return thunk.symbols.keys().len * trampoline_size; |
| 243 | macho_file.getSymbolName(atom.getSymbolWithLoc()), | 86 | } |
| 244 | macho_file.getSymbol(atom.getSymbolWithLoc()).n_value, | ||
| 245 | macho_file.getSymbolName(target), | ||
| 246 | macho_file.getSymbol(target).n_value, | ||
| 247 | }); | ||
| 248 | 87 | ||
| 249 | const comp = macho_file.base.comp; | 88 | pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 { |
| 250 | const gpa = comp.gpa; | 89 | return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size; |
| 251 | const target_sym = macho_file.getSymbol(target); | 90 | } |
| 252 | const thunk = &macho_file.thunks.items[thunk_index]; | ||
| 253 | 91 | ||
| 254 | const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom; | 92 | pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { |
| 255 | const thunk_target: Thunk.Target = .{ .tag = tag, .target = target }; | 93 | for (thunk.symbols.keys(), 0..) |sym_index, i| { |
| 256 | const gop = try thunk.lookup.getOrPut(gpa, thunk_target); | 94 | const sym = macho_file.getSymbol(sym_index); |
| 257 | if (!gop.found_existing) { | 95 | const saddr = thunk.value + i * trampoline_size; |
| 258 | gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end); | 96 | const taddr = sym.getAddress(.{}, macho_file); |
| 259 | try thunk.targets.append(gpa, thunk_target); | 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); | ||
| 260 | } | 102 | } |
| 261 | |||
| 262 | try macho_file.thunk_table.put(gpa, atom_index, thunk_index); | ||
| 263 | } | 103 | } |
| 264 | } | ||
| 265 | |||
| 266 | fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index { | ||
| 267 | const thunk_atom_index = try createThunkAtom(macho_file); | ||
| 268 | 104 | ||
| 269 | const thunk_atom = macho_file.getAtomPtr(thunk_atom_index); | 105 | pub fn format( |
| 270 | const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex(); | 106 | thunk: Thunk, |
| 271 | const end_atom = macho_file.getAtomPtr(end_atom_index); | 107 | comptime unused_fmt_string: []const u8, |
| 272 | 108 | options: std.fmt.FormatOptions, | |
| 273 | if (end_atom.next_index) |first_after_index| { | 109 | writer: anytype, |
| 274 | const first_after_atom = macho_file.getAtomPtr(first_after_index); | 110 | ) !void { |
| 275 | first_after_atom.prev_index = thunk_atom_index; | 111 | _ = thunk; |
| 276 | thunk_atom.next_index = first_after_index; | 112 | _ = unused_fmt_string; |
| 113 | _ = options; | ||
| 114 | _ = writer; | ||
| 115 | @compileError("do not format Thunk directly"); | ||
| 277 | } | 116 | } |
| 278 | 117 | ||
| 279 | end_atom.next_index = thunk_atom_index; | 118 | pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) { |
| 280 | thunk_atom.prev_index = end_atom_index; | 119 | return .{ .data = .{ |
| 281 | 120 | .thunk = thunk, | |
| 282 | if (thunk.len == 0) { | 121 | .macho_file = macho_file, |
| 283 | thunk.start_index = thunk_atom_index; | 122 | } }; |
| 284 | } | 123 | } |
| 285 | 124 | ||
| 286 | thunk.len += 1; | 125 | const FormatContext = struct { |
| 287 | 126 | thunk: Thunk, | |
| 288 | return thunk_atom_index; | 127 | macho_file: *MachO, |
| 289 | } | 128 | }; |
| 290 | |||
| 291 | inline 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 | |||
| 296 | fn 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; | ||
| 315 | 129 | ||
| 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 | } | ||
| 317 | 146 | ||
| 318 | const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset)); | 147 | const trampoline_size = 3 * @sizeOf(u32); |
| 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; | ||
| 325 | 148 | ||
| 326 | return true; | 149 | pub const Index = u32; |
| 327 | } | 150 | }; |
| 328 | 151 | ||
| 329 | fn createThunkAtom(macho_file: *MachO) !Atom.Index { | 152 | /// Branch instruction has 26 bits immediate but is 4 byte aligned. |
| 330 | const sym_index = try macho_file.allocateSymbol(); | 153 | const jump_bits = @bitSizeOf(i28); |
| 331 | const atom_index = try macho_file.createAtom(sym_index, .{ | 154 | const max_distance = (1 << (jump_bits - 1)); |
| 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 | } | ||
| 340 | 155 | ||
| 341 | pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void { | 156 | /// A branch will need an extender if its target is larger than |
| 342 | const slice = thunk.targets.slice(); | 157 | /// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. |
| 343 | for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| { | 158 | /// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold |
| 344 | const atom = macho_file.getAtom(@intCast(atom_index)); | 159 | /// and assume margin to be 5MiB. |
| 345 | const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); | 160 | const max_allowed_distance = max_distance - 0x500_000; |
| 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 | } | ||
| 360 | 161 | ||
| 361 | const std = @import("std"); | 162 | const aarch64 = @import("../../arch/aarch64/bits.zig"); |
| 362 | const assert = std.debug.assert; | 163 | const assert = std.debug.assert; |
| 363 | const log = std.log.scoped(.thunks); | 164 | const log = std.log.scoped(.link); |
| 364 | const macho = std.macho; | 165 | const macho = std.macho; |
| 365 | const math = std.math; | 166 | const math = std.math; |
| 366 | const mem = std.mem; | 167 | const mem = std.mem; |
| 367 | 168 | const std = @import("std"); | |
| 368 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | 169 | const trace = @import("../../tracy.zig").trace; |
| 369 | 170 | ||
| 370 | const Allocator = mem.Allocator; | 171 | const Allocator = mem.Allocator; |
| 371 | const Atom = @import("Atom.zig"); | 172 | const Atom = @import("Atom.zig"); |
| 372 | const MachO = @import("../MachO.zig"); | 173 | const MachO = @import("../MachO.zig"); |
| 373 | const Relocation = @import("Relocation.zig"); | 174 | const Relocation = @import("Relocation.zig"); |
| 374 | const SymbolWithLoc = MachO.SymbolWithLoc; | 175 | const Symbol = @import("Symbol.zig"); |
src/link/MachO/uuid.zig+6-2| ... | @@ -5,6 +5,9 @@ | ... | @@ -5,6 +5,9 @@ |
| 5 | /// TODO LLD also hashes the output filename to disambiguate between same builds with different | 5 | /// TODO LLD also hashes the output filename to disambiguate between same builds with different |
| 6 | /// output files. Should we also do that? | 6 | /// output files. Should we also do that? |
| 7 | pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | 7 | pub 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 | |||
| 8 | const chunk_size: usize = 1024 * 1024; | 11 | const chunk_size: usize = 1024 * 1024; |
| 9 | const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; | 12 | const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; |
| 10 | const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; | 13 | 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 { | ... | @@ -35,11 +38,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void { |
| 35 | out[8] = (out[8] & 0x3F) | 0x80; | 38 | out[8] = (out[8] & 0x3F) | 0x80; |
| 36 | } | 39 | } |
| 37 | 40 | ||
| 38 | const std = @import("std"); | ||
| 39 | const fs = std.fs; | 41 | const fs = std.fs; |
| 40 | const mem = std.mem; | 42 | const mem = std.mem; |
| 43 | const std = @import("std"); | ||
| 44 | const trace = @import("../../tracy.zig").trace; | ||
| 41 | 45 | ||
| 42 | const Allocator = mem.Allocator; | ||
| 43 | const Compilation = @import("../../Compilation.zig"); | 46 | const Compilation = @import("../../Compilation.zig"); |
| 44 | const Md5 = std.crypto.hash.Md5; | 47 | const Md5 = std.crypto.hash.Md5; |
| 45 | const Hasher = @import("hasher.zig").ParallelHasher; | 48 | const Hasher = @import("hasher.zig").ParallelHasher; |
| 49 | const ThreadPool = std.Thread.Pool; |
src/link/MachO/zld.zig deleted-1230| ... | @@ -1,1230 +0,0 @@ | ||
| 1 | pub 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 | |||
| 607 | fn 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 | |||
| 682 | fn 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 | |||
| 744 | fn 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 | |||
| 756 | fn 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 | |||
| 779 | fn 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 | |||
| 794 | fn 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 | |||
| 818 | fn 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 | |||
| 863 | fn 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 | |||
| 887 | fn 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 | |||
| 975 | fn 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 | |||
| 1078 | fn 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 | |||
| 1093 | fn 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 | |||
| 1104 | fn 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 | |||
| 1193 | const std = @import("std"); | ||
| 1194 | const build_options = @import("build_options"); | ||
| 1195 | const assert = std.debug.assert; | ||
| 1196 | const dwarf = std.dwarf; | ||
| 1197 | const fs = std.fs; | ||
| 1198 | const log = std.log.scoped(.link); | ||
| 1199 | const macho = std.macho; | ||
| 1200 | const math = std.math; | ||
| 1201 | const mem = std.mem; | ||
| 1202 | |||
| 1203 | const aarch64 = @import("../../arch/aarch64/bits.zig"); | ||
| 1204 | const calcUuid = @import("uuid.zig").calcUuid; | ||
| 1205 | const dead_strip = @import("dead_strip.zig"); | ||
| 1206 | const eh_frame = @import("eh_frame.zig"); | ||
| 1207 | const fat = @import("fat.zig"); | ||
| 1208 | const link = @import("../../link.zig"); | ||
| 1209 | const load_commands = @import("load_commands.zig"); | ||
| 1210 | const stubs = @import("stubs.zig"); | ||
| 1211 | const thunks = @import("thunks.zig"); | ||
| 1212 | const trace = @import("../../tracy.zig").trace; | ||
| 1213 | |||
| 1214 | const Allocator = mem.Allocator; | ||
| 1215 | const Archive = @import("Archive.zig"); | ||
| 1216 | const Atom = @import("Atom.zig"); | ||
| 1217 | const Cache = std.Build.Cache; | ||
| 1218 | const CodeSignature = @import("CodeSignature.zig"); | ||
| 1219 | const Compilation = @import("../../Compilation.zig"); | ||
| 1220 | const Dylib = @import("Dylib.zig"); | ||
| 1221 | const MachO = @import("../MachO.zig"); | ||
| 1222 | const Md5 = std.crypto.hash.Md5; | ||
| 1223 | const LibStub = @import("../tapi.zig").LibStub; | ||
| 1224 | const Object = @import("Object.zig"); | ||
| 1225 | const Platform = load_commands.Platform; | ||
| 1226 | const Section = MachO.Section; | ||
| 1227 | const SymbolWithLoc = MachO.SymbolWithLoc; | ||
| 1228 | const TableSection = @import("../table_section.zig").TableSection; | ||
| 1229 | const Trie = @import("Trie.zig"); | ||
| 1230 | const UnwindInfo = @import("UnwindInfo.zig"); | ||
src/main.zig+2-3| ... | @@ -2823,9 +2823,7 @@ fn buildOutputType( | ... | @@ -2823,9 +2823,7 @@ fn buildOutputType( |
| 2823 | } | 2823 | } |
| 2824 | // After this point, resolved_frameworks is used instead of frameworks. | 2824 | // After this point, resolved_frameworks is used instead of frameworks. |
| 2825 | 2825 | ||
| 2826 | if (create_module.resolved_options.output_mode == .Obj and | 2826 | if (create_module.resolved_options.output_mode == .Obj and target.ofmt == .coff) { |
| 2827 | (target.ofmt == .coff or target.ofmt == .macho)) | ||
| 2828 | { | ||
| 2829 | const total_obj_count = create_module.c_source_files.items.len + | 2827 | const total_obj_count = create_module.c_source_files.items.len + |
| 2830 | @intFromBool(root_src_file != null) + | 2828 | @intFromBool(root_src_file != null) + |
| 2831 | create_module.rc_source_files.items.len + | 2829 | create_module.rc_source_files.items.len + |
| ... | @@ -3220,6 +3218,7 @@ fn buildOutputType( | ... | @@ -3220,6 +3218,7 @@ fn buildOutputType( |
| 3220 | .clang_passthrough_mode = clang_passthrough_mode, | 3218 | .clang_passthrough_mode = clang_passthrough_mode, |
| 3221 | .clang_preprocessor_mode = clang_preprocessor_mode, | 3219 | .clang_preprocessor_mode = clang_preprocessor_mode, |
| 3222 | .version = optional_version, | 3220 | .version = optional_version, |
| 3221 | .compatibility_version = compatibility_version, | ||
| 3223 | .libc_installation = if (create_module.libc_installation) |*lci| lci else null, | 3222 | .libc_installation = if (create_module.libc_installation) |*lci| lci else null, |
| 3224 | .verbose_cc = verbose_cc, | 3223 | .verbose_cc = verbose_cc, |
| 3225 | .verbose_link = verbose_link, | 3224 | .verbose_link = verbose_link, |
test/link.zig-110| ... | @@ -89,114 +89,4 @@ pub const cases = [_]Case{ | ... | @@ -89,114 +89,4 @@ pub const cases = [_]Case{ |
| 89 | .build_root = "test/link/wasm/type", | 89 | .build_root = "test/link/wasm/type", |
| 90 | .import = @import("link/wasm/type/build.zig"), | 90 | .import = @import("link/wasm/type/build.zig"), |
| 91 | }, | 91 | }, |
| 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 | }, | ||
| 202 | }; | 92 | }; |
test/link/elf.zig+14-7| ... | @@ -2,7 +2,8 @@ | ... | @@ -2,7 +2,8 @@ |
| 2 | //! Currently, we support linking x86_64 Linux, but in the future we | 2 | //! Currently, we support linking x86_64 Linux, but in the future we |
| 3 | //! will progressively relax those to exercise more combinations. | 3 | //! will progressively relax those to exercise more combinations. |
| 4 | 4 | ||
| 5 | pub fn testAll(b: *Build) *Step { | 5 | pub fn testAll(b: *Build, build_opts: BuildOptions) *Step { |
| 6 | _ = build_opts; | ||
| 6 | const elf_step = b.step("test-elf", "Run ELF tests"); | 7 | const elf_step = b.step("test-elf", "Run ELF tests"); |
| 7 | 8 | ||
| 8 | const default_target = b.resolveTargetQuery(.{ | 9 | const default_target = b.resolveTargetQuery(.{ |
| ... | @@ -3609,12 +3610,17 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step { | ... | @@ -3609,12 +3610,17 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step { |
| 3609 | exe.linkLibrary(dylib); | 3610 | exe.linkLibrary(dylib); |
| 3610 | exe.linkLibC(); | 3611 | exe.linkLibC(); |
| 3611 | 3612 | ||
| 3612 | expectLinkErrors(exe, test_step, .{ .exact = &.{ | 3613 | // TODO: improve the test harness to be able to selectively match lines in error output |
| 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 | // while avoiding jankiness |
| 3614 | "note: while parsing /?/liba.dylib", | 3615 | // expectLinkErrors(exe, test_step, .{ .exact = &.{ |
| 3615 | "unexpected error: parsing input file failed with error InvalidLdScript", | 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)", |
| 3616 | "note: while parsing /?/liba.dylib", | 3617 | // "note: while parsing /?/liba.dylib", |
| 3617 | } }); | 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 | }); | ||
| 3618 | 3624 | ||
| 3619 | return test_step; | 3625 | return test_step; |
| 3620 | } | 3626 | } |
| ... | @@ -3896,6 +3902,7 @@ const link = @import("link.zig"); | ... | @@ -3896,6 +3902,7 @@ const link = @import("link.zig"); |
| 3896 | const std = @import("std"); | 3902 | const std = @import("std"); |
| 3897 | 3903 | ||
| 3898 | const Build = std.Build; | 3904 | const Build = std.Build; |
| 3905 | const BuildOptions = link.BuildOptions; | ||
| 3899 | const Options = link.Options; | 3906 | const Options = link.Options; |
| 3900 | const Step = Build.Step; | 3907 | const Step = Build.Step; |
| 3901 | const WriteFile = Step.WriteFile; | 3908 | const WriteFile = Step.WriteFile; |
test/link/link.zig+65-96| ... | @@ -2,10 +2,26 @@ pub fn build(b: *Build) void { | ... | @@ -2,10 +2,26 @@ pub fn build(b: *Build) void { |
| 2 | const test_step = b.step("test-link", "Run link tests"); | 2 | const test_step = b.step("test-link", "Run link tests"); |
| 3 | b.default_step = test_step; | 3 | b.default_step = test_step; |
| 4 | 4 | ||
| 5 | test_step.dependOn(@import("elf.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 | test_step.dependOn(@import("macho.zig").testAll(b)); | 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)); | ||
| 7 | } | 17 | } |
| 8 | 18 | ||
| 19 | pub const BuildOptions = struct { | ||
| 20 | has_macos_sdk: bool, | ||
| 21 | has_ios_sdk: bool, | ||
| 22 | has_symlinks_windows: bool, | ||
| 23 | }; | ||
| 24 | |||
| 9 | pub const Options = struct { | 25 | pub const Options = struct { |
| 10 | target: std.Build.ResolvedTarget, | 26 | target: std.Build.ResolvedTarget, |
| 11 | optimize: std.builtin.OptimizeMode = .Debug, | 27 | optimize: std.builtin.OptimizeMode = .Debug, |
| ... | @@ -30,121 +46,74 @@ const OverlayOptions = struct { | ... | @@ -30,121 +46,74 @@ const OverlayOptions = struct { |
| 30 | c_source_flags: []const []const u8 = &.{}, | 46 | c_source_flags: []const []const u8 = &.{}, |
| 31 | cpp_source_bytes: ?[]const u8 = null, | 47 | cpp_source_bytes: ?[]const u8 = null, |
| 32 | cpp_source_flags: []const []const u8 = &.{}, | 48 | 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 = &.{}, | ||
| 33 | zig_source_bytes: ?[]const u8 = null, | 53 | zig_source_bytes: ?[]const u8 = null, |
| 34 | pic: ?bool = null, | 54 | pic: ?bool = null, |
| 35 | strip: ?bool = null, | 55 | strip: ?bool = null, |
| 36 | }; | 56 | }; |
| 37 | 57 | ||
| 38 | pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Step.Compile { | 58 | pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile { |
| 39 | const compile_step = b.addExecutable(.{ | 59 | return addCompileStep(b, base, overlay, .exe); |
| 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; | ||
| 68 | } | 60 | } |
| 69 | 61 | ||
| 70 | pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Step.Compile { | 62 | pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Compile { |
| 71 | const compile_step = b.addObject(.{ | 63 | return addCompileStep(b, base, overlay, .obj); |
| 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; | ||
| 100 | } | 64 | } |
| 101 | 65 | ||
| 102 | pub fn addStaticLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile { | 66 | pub 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 | |||
| 70 | pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile { | ||
| 71 | return addCompileStep(b, base, overlay, .shared_lib); | ||
| 72 | } | ||
| 73 | |||
| 74 | fn 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, .{ | ||
| 104 | .name = overlay.name, | 81 | .name = overlay.name, |
| 105 | .root_source_file = rsf: { | 82 | .root_module = .{ |
| 106 | const bytes = overlay.zig_source_bytes orelse break :rsf null; | 83 | .target = base.target, |
| 107 | break :rsf b.addWriteFiles().add("a.zig", bytes); | 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, | ||
| 108 | }, | 91 | }, |
| 109 | .target = base.target, | ||
| 110 | .optimize = base.optimize, | ||
| 111 | .use_llvm = base.use_llvm, | 92 | .use_llvm = base.use_llvm, |
| 112 | .use_lld = base.use_lld, | 93 | .use_lld = base.use_lld, |
| 113 | .pic = overlay.pic, | 94 | .kind = switch (kind) { |
| 114 | .strip = overlay.strip, | 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 | }, | ||
| 115 | }); | 104 | }); |
| 116 | if (overlay.cpp_source_bytes) |bytes| { | 105 | if (overlay.objcpp_source_bytes) |bytes| { |
| 117 | compile_step.addCSourceFile(.{ | 106 | compile_step.addCSourceFile(.{ |
| 118 | .file = b.addWriteFiles().add("a.cpp", bytes), | 107 | .file = b.addWriteFiles().add("a.mm", bytes), |
| 119 | .flags = overlay.cpp_source_flags, | 108 | .flags = overlay.objcpp_source_flags, |
| 120 | }); | 109 | }); |
| 121 | } | 110 | } |
| 122 | if (overlay.c_source_bytes) |bytes| { | 111 | if (overlay.objc_source_bytes) |bytes| { |
| 123 | compile_step.addCSourceFile(.{ | 112 | compile_step.addCSourceFile(.{ |
| 124 | .file = b.addWriteFiles().add("a.c", bytes), | 113 | .file = b.addWriteFiles().add("a.m", bytes), |
| 125 | .flags = overlay.c_source_flags, | 114 | .flags = overlay.objc_source_flags, |
| 126 | }); | 115 | }); |
| 127 | } | 116 | } |
| 128 | if (overlay.asm_source_bytes) |bytes| { | ||
| 129 | compile_step.addAssemblyFile(b.addWriteFiles().add("a.s", bytes)); | ||
| 130 | } | ||
| 131 | return compile_step; | ||
| 132 | } | ||
| 133 | |||
| 134 | pub 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 | }); | ||
| 148 | if (overlay.cpp_source_bytes) |bytes| { | 117 | if (overlay.cpp_source_bytes) |bytes| { |
| 149 | compile_step.addCSourceFile(.{ | 118 | compile_step.addCSourceFile(.{ |
| 150 | .file = b.addWriteFiles().add("a.cpp", bytes), | 119 | .file = b.addWriteFiles().add("a.cpp", bytes), |
test/link/macho.zig+2165-18| ... | @@ -1,18 +1,1254 @@ | ... | @@ -1,18 +1,1254 @@ |
| 1 | //! Here we test our MachO linker for correctness and functionality. | 1 | //! Here we test our MachO linker for correctness and functionality. |
| 2 | //! TODO migrate standalone tests from test/link/macho/* to here. | ||
| 3 | 2 | ||
| 4 | pub fn testAll(b: *std.Build) *Step { | 3 | pub fn testAll(b: *Build, build_opts: BuildOptions) *Step { |
| 5 | const macho_step = b.step("test-macho", "Run MachO tests"); | 4 | const macho_step = b.step("test-macho", "Run MachO tests"); |
| 6 | 5 | ||
| 7 | macho_step.dependOn(testResolvingBoundarySymbols(b, .{ | 6 | const default_target = b.resolveTargetQuery(.{ |
| 8 | .target = b.resolveTargetQuery(.{ .os_tag = .macos }), | 7 | .os_tag = .macos, |
| 9 | })); | 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 | |||
| 74 | fn 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 | |||
| 155 | fn 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 | |||
| 204 | fn 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 | |||
| 241 | fn 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 | |||
| 261 | fn 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 | |||
| 273 | fn 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 | |||
| 304 | fn 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 | |||
| 333 | fn 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 | |||
| 387 | fn 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 | ||
| 495 | fn 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 | |||
| 582 | fn 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 | |||
| 606 | fn 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 | |||
| 623 | fn 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 | |||
| 644 | fn 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 | |||
| 763 | fn 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 | |||
| 795 | fn 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 | |||
| 822 | fn 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 | |||
| 835 | fn 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 | |||
| 862 | fn 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 | |||
| 875 | fn 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 | |||
| 895 | fn 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 | |||
| 919 | fn 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 | |||
| 947 | fn 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 | |||
| 1000 | fn 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 | |||
| 1034 | fn 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 | |||
| 1063 | fn 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 | |||
| 1131 | fn 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 | |||
| 1182 | fn 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 | } | ||
| 10 | 1246 | ||
| 11 | return macho_step; | 1247 | return test_step; |
| 12 | } | 1248 | } |
| 13 | 1249 | ||
| 14 | fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { | 1250 | fn testSectionBoundarySymbols(b: *Build, opts: Options) *Step { |
| 15 | const test_step = addTestStep(b, "macho-resolving-boundary-symbols", opts); | 1251 | const test_step = addTestStep(b, "macho-section-boundary-symbols", opts); |
| 16 | 1252 | ||
| 17 | const obj1 = addObject(b, opts, .{ | 1253 | const obj1 = addObject(b, opts, .{ |
| 18 | .name = "obj1", | 1254 | .name = "obj1", |
| ... | @@ -25,10 +1261,10 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { | ... | @@ -25,10 +1261,10 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { |
| 25 | .name = "main", | 1261 | .name = "main", |
| 26 | .zig_source_bytes = | 1262 | .zig_source_bytes = |
| 27 | \\const std = @import("std"); | 1263 | \\const std = @import("std"); |
| 28 | \\extern fn interop() [*:0]const u8; | 1264 | \\extern fn interop() ?[*:0]const u8; |
| 29 | \\pub fn main() !void { | 1265 | \\pub fn main() !void { |
| 30 | \\ std.debug.print("All your {s} are belong to us.\n", .{ | 1266 | \\ 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)", |
| 32 | \\ }); | 1268 | \\ }); |
| 33 | \\} | 1269 | \\} |
| 34 | , | 1270 | , |
| ... | @@ -57,7 +1293,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { | ... | @@ -57,7 +1293,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { |
| 57 | 1293 | ||
| 58 | const check = exe.checkObject(); | 1294 | const check = exe.checkObject(); |
| 59 | check.checkInSymtab(); | 1295 | check.checkInSymtab(); |
| 60 | check.checkNotPresent("section$start$__DATA_CONST$__message_ptr"); | 1296 | check.checkNotPresent("external section$start$__DATA_CONST$__message_ptr"); |
| 61 | test_step.dependOn(&check.step); | 1297 | test_step.dependOn(&check.step); |
| 62 | } | 1298 | } |
| 63 | 1299 | ||
| ... | @@ -65,7 +1301,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { | ... | @@ -65,7 +1301,7 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { |
| 65 | const obj3 = addObject(b, opts, .{ | 1301 | const obj3 = addObject(b, opts, .{ |
| 66 | .name = "obj3", | 1302 | .name = "obj3", |
| 67 | .cpp_source_bytes = | 1303 | .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"); |
| 69 | \\extern "C" const char* interop() { | 1305 | \\extern "C" const char* interop() { |
| 70 | \\ return message_pointer; | 1306 | \\ return message_pointer; |
| 71 | \\} | 1307 | \\} |
| ... | @@ -77,23 +1313,934 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { | ... | @@ -77,23 +1313,934 @@ fn testResolvingBoundarySymbols(b: *std.Build, opts: Options) *Step { |
| 77 | exe.addObject(obj3); | 1313 | exe.addObject(obj3); |
| 78 | exe.addObject(main_o); | 1314 | exe.addObject(main_o); |
| 79 | 1315 | ||
| 80 | expectLinkErrors(exe, test_step, .{ .exact = &.{ | 1316 | const run = b.addRunArtifact(exe); |
| 81 | "section not found: __DATA,__message_ptr", | 1317 | run.skip_foreign_checks = true; |
| 82 | "note: while resolving section$start$__DATA$__message_ptr", | 1318 | run.expectStdErrEqual("All your (null) are belong to us.\n"); |
| 83 | } }); | 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 | |||
| 1330 | fn 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 | |||
| 1398 | fn 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 | |||
| 1417 | fn 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 | |||
| 1455 | fn 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 | |||
| 1481 | fn 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 | |||
| 1510 | fn 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 | |||
| 1544 | fn 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 | |||
| 1565 | fn 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 | |||
| 1685 | fn 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 | |||
| 1762 | fn 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 | |||
| 1920 | fn 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 | |||
| 1980 | fn 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 | ||
| 2033 | fn 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); | ||
| 84 | } | 2155 | } |
| 85 | 2156 | ||
| 2157 | const run = addRunArtifact(exe); | ||
| 2158 | run.expectExitCode(0); | ||
| 2159 | test_step.dependOn(&run.step); | ||
| 2160 | |||
| 2161 | return test_step; | ||
| 2162 | } | ||
| 2163 | |||
| 2164 | fn 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 | |||
| 2183 | fn 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 | |||
| 86 | return test_step; | 2223 | return test_step; |
| 87 | } | 2224 | } |
| 88 | 2225 | ||
| 89 | fn addTestStep(b: *std.Build, comptime prefix: []const u8, opts: Options) *Step { | 2226 | fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step { |
| 90 | return link.addTestStep(b, "macho-" ++ prefix, opts); | 2227 | return link.addTestStep(b, "macho-" ++ prefix, opts); |
| 91 | } | 2228 | } |
| 92 | 2229 | ||
| 2230 | const addAsmSourceBytes = link.addAsmSourceBytes; | ||
| 2231 | const addCSourceBytes = link.addCSourceBytes; | ||
| 2232 | const addRunArtifact = link.addRunArtifact; | ||
| 93 | const addObject = link.addObject; | 2233 | const addObject = link.addObject; |
| 94 | const addExecutable = link.addExecutable; | 2234 | const addExecutable = link.addExecutable; |
| 2235 | const addStaticLibrary = link.addStaticLibrary; | ||
| 2236 | const addSharedLibrary = link.addSharedLibrary; | ||
| 95 | const expectLinkErrors = link.expectLinkErrors; | 2237 | const expectLinkErrors = link.expectLinkErrors; |
| 96 | const link = @import("link.zig"); | 2238 | const link = @import("link.zig"); |
| 97 | const std = @import("std"); | 2239 | const std = @import("std"); |
| 2240 | |||
| 2241 | const Build = std.Build; | ||
| 2242 | const BuildOptions = link.BuildOptions; | ||
| 2243 | const Compile = Step.Compile; | ||
| 98 | const Options = link.Options; | 2244 | const Options = link.Options; |
| 99 | const Step = std.Build.Step; | 2245 | const Step = Build.Step; |
| 2246 | const WriteFile = Step.WriteFile; |
test/link/macho/bugs/13056/build.zig deleted-38| ... | @@ -1,38 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_macos_sdk = true; | ||
| 4 | pub const requires_symlinks = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 | |||
| 5 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | pub fn main() void {} | ||
test/link/macho/bugs/16308/build.zig deleted-23| ... | @@ -1,23 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 @@ | ||
| 1 | fn 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub const requires_symlinks = true; | ||
| 5 | pub const requires_macos_sdk = false; | ||
| 6 | |||
| 7 | pub 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 | |||
| 17 | fn 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 | |||
| 3 | int foo(); | ||
| 4 | |||
| 5 | int main() { | ||
| 6 | printf("%d\n", foo()); | ||
| 7 | return 0; | ||
| 8 | } | ||
test/link/macho/dead_strip/build.zig deleted-58| ... | @@ -1,58 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 44 | fn 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 | |||
| 3 | void printMe() { | ||
| 4 | printf("Hello!\n"); | ||
| 5 | } | ||
| 6 | |||
| 7 | int main(int argc, char* argv[]) { | ||
| 8 | printMe(); | ||
| 9 | return 0; | ||
| 10 | } | ||
| 11 | |||
| 12 | void iAmUnused() { | ||
| 13 | printf("YOU SHALL NOT PASS!\n"); | ||
| 14 | } | ||
test/link/macho/dead_strip_dylibs/build.zig deleted-61| ... | @@ -1,61 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_macos_sdk = true; | ||
| 4 | pub const requires_symlinks = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 | |||
| 47 | fn 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 | |||
| 3 | int 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 | |||
| 3 | char world[] = "world"; | ||
| 4 | |||
| 5 | char* hello() { | ||
| 6 | return "Hello"; | ||
| 7 | } | ||
test/link/macho/dylib/build.zig deleted-65| ... | @@ -1,65 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 3 | char* hello(); | ||
| 4 | extern char world[]; | ||
| 5 | |||
| 6 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 deletedtest/link/macho/empty/main.c deleted-6| ... | @@ -1,6 +0,0 @@ | ||
| 1 | #include <stdio.h> | ||
| 2 | |||
| 3 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 3 | int non_main() { | ||
| 4 | printf("%d", 42); | ||
| 5 | return 0; | ||
| 6 | } | ||
test/link/macho/entry_in_archive/build.zig deleted-36| ... | @@ -1,36 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 3 | int main(int argc, char* argv[]) { | ||
| 4 | return 0; | ||
| 5 | } | ||
test/link/macho/entry_in_dylib/bootstrap.c deleted-5| ... | @@ -1,5 +0,0 @@ | ||
| 1 | extern int my_main(); | ||
| 2 | |||
| 3 | int bootstrap() { | ||
| 4 | return my_main(); | ||
| 5 | } | ||
test/link/macho/entry_in_dylib/build.zig deleted-59| ... | @@ -1,59 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 3 | int my_main() { | ||
| 4 | fprintf(stdout, "Hello!\n"); | ||
| 5 | return 0; | ||
| 6 | } | ||
test/link/macho/headerpad/build.zig deleted-137| ... | @@ -1,137 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub const requires_symlinks = true; | ||
| 5 | pub const requires_macos_sdk = true; | ||
| 6 | |||
| 7 | pub 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 | |||
| 17 | fn 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 | |||
| 107 | fn 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 @@ | ||
| 1 | int main(int argc, char* argv[]) { | ||
| 2 | return 0; | ||
| 3 | } | ||
test/link/macho/linksection/build.zig deleted-39| ... | @@ -1,39 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined; | ||
| 2 | export fn testFn() linksection("__TEXT,__TestFn") callconv(.C) void { | ||
| 3 | testGenericFn("A"); | ||
| 4 | } | ||
| 5 | fn testGenericFn(comptime suffix: []const u8) linksection("__TEXT,__TestGenFn" ++ suffix) void {} | ||
test/link/macho/needed_framework/build.zig deleted-37| ... | @@ -1,37 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | pub const requires_macos_sdk = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 @@ | ||
| 1 | int main(int argc, char* argv[]) { | ||
| 2 | return 0; | ||
| 3 | } | ||
test/link/macho/needed_library/a.c deleted-1| ... | @@ -1 +0,0 @@ | ||
| 1 | int a = 42; | ||
test/link/macho/needed_library/build.zig deleted-51| ... | @@ -1,51 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | pub const requires_macos_sdk = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 | |||
| 4 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | pub const requires_macos_sdk = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 | |||
| 5 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 @@ | ||
| 1 | int main(int argc, char* argv[]) { | ||
| 2 | return 0; | ||
| 3 | } | ||
test/link/macho/reexports/a.zig deleted-7| ... | @@ -1,7 +0,0 @@ | ||
| 1 | const x: i32 = 42; | ||
| 2 | export fn foo() i32 { | ||
| 3 | return x; | ||
| 4 | } | ||
| 5 | comptime { | ||
| 6 | @export(foo, .{ .name = "bar", .linkage = .Strong }); | ||
| 7 | } | ||
test/link/macho/reexports/build.zig deleted-38| ... | @@ -1,38 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | extern int foo(); | ||
| 2 | extern int bar(); | ||
| 3 | int main() { | ||
| 4 | return bar() - foo(); | ||
| 5 | } | ||
test/link/macho/search_strategy/a.c deleted-7| ... | @@ -1,7 +0,0 @@ | ||
| 1 | #include <stdio.h> | ||
| 2 | |||
| 3 | char world[] = "world"; | ||
| 4 | |||
| 5 | char* hello() { | ||
| 6 | return "Hello"; | ||
| 7 | } | ||
test/link/macho/search_strategy/build.zig deleted-84| ... | @@ -1,84 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 45 | fn 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 | |||
| 3 | char* hello(); | ||
| 4 | extern char world[]; | ||
| 5 | |||
| 6 | int 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | int main(int argc, char* argv[]) { | ||
| 2 | return 0; | ||
| 3 | } | ||
test/link/macho/strict_validation/build.zig deleted-137| ... | @@ -1,137 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub const requires_symlinks = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub 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 @@ | ||
| 1 | int getFoo() { | ||
| 2 | return 42; | ||
| 3 | } | ||
test/link/macho/tbdv3/build.zig deleted-57| ... | @@ -1,57 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub const requires_symlinks = true; | ||
| 5 | pub const requires_macos_sdk = false; | ||
| 6 | |||
| 7 | pub 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 | |||
| 17 | fn 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 | |||
| 3 | int getFoo(); | ||
| 4 | |||
| 5 | int main() { | ||
| 6 | return getFoo() - 42; | ||
| 7 | } | ||
test/link/macho/tls/a.c deleted-5| ... | @@ -1,5 +0,0 @@ | ||
| 1 | _Thread_local int a; | ||
| 2 | |||
| 3 | int getA() { | ||
| 4 | return a; | ||
| 5 | } | ||
test/link/macho/tls/build.zig deleted-39| ... | @@ -1,39 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | extern threadlocal var a: i32; | ||
| 4 | extern fn getA() i32; | ||
| 5 | |||
| 6 | fn getA2() i32 { | ||
| 7 | return a; | ||
| 8 | } | ||
| 9 | |||
| 10 | test { | ||
| 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 | |||
| 8 | struct 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 | |||
| 15 | private: | ||
| 16 | size_t max_size; | ||
| 17 | char* buffer; | ||
| 18 | size_t length; | ||
| 19 | }; | ||
| 20 | |||
| 21 | struct SimpleStringOwner { | ||
| 22 | SimpleStringOwner(const char* x); | ||
| 23 | ~SimpleStringOwner(); | ||
| 24 | |||
| 25 | private: | ||
| 26 | SimpleString string; | ||
| 27 | }; | ||
| 28 | |||
| 29 | class Error: public std::exception { | ||
| 30 | public: | ||
| 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 | |||
| 37 | protected: | ||
| 38 | std::string msg; | ||
| 39 | }; | ||
| 40 | |||
| 41 | #endif | ||
test/link/macho/unwind_info/build.zig deleted-88| ... | @@ -1,88 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub const requires_symlinks = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 | |||
| 23 | fn 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 | |||
| 66 | fn 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 | |||
| 4 | void fn_c() { | ||
| 5 | SimpleStringOwner c{ "cccccccccc" }; | ||
| 6 | } | ||
| 7 | |||
| 8 | void fn_b() { | ||
| 9 | SimpleStringOwner b{ "b" }; | ||
| 10 | fn_c(); | ||
| 11 | } | ||
| 12 | |||
| 13 | int 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 | |||
| 5 | SimpleString::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 | |||
| 14 | SimpleString::~SimpleString() { | ||
| 15 | delete[] buffer; | ||
| 16 | } | ||
| 17 | |||
| 18 | void SimpleString::print(const char* tag) const { | ||
| 19 | printf("%s: %s", tag, buffer); | ||
| 20 | } | ||
| 21 | |||
| 22 | bool 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 | |||
| 3 | SimpleStringOwner::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 | |||
| 10 | SimpleStringOwner::~SimpleStringOwner() { | ||
| 11 | string.print("About to destroy"); | ||
| 12 | } | ||
test/link/macho/weak_framework/build.zig deleted-34| ... | @@ -1,34 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | pub const requires_macos_sdk = true; | ||
| 5 | |||
| 6 | pub 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 | |||
| 16 | fn 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 @@ | ||
| 1 | int 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 | |||
| 3 | int a = 42; | ||
| 4 | |||
| 5 | const 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const requires_symlinks = true; | ||
| 4 | |||
| 5 | pub 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 | |||
| 15 | fn 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 | |||
| 3 | extern int a; | ||
| 4 | extern const char* asStr(); | ||
| 5 | |||
| 6 | int 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( | ... | @@ -750,26 +750,39 @@ pub fn addLinkTests( |
| 750 | const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows; | 750 | const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows; |
| 751 | 751 | ||
| 752 | inline for (link.cases) |case| { | 752 | inline for (link.cases) |case| { |
| 753 | const requires_stage2 = @hasDecl(case.import, "requires_stage2") and | 753 | if (mem.eql(u8, @typeName(case.import), "test.link.link")) { |
| 754 | case.import.requires_stage2; | 754 | const dep = b.anonymousDependency(case.build_root, case.import, .{ |
| 755 | const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and | 755 | .has_macos_sdk = enable_macos_sdk, |
| 756 | case.import.requires_symlinks; | 756 | .has_ios_sdk = enable_ios_sdk, |
| 757 | const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and | 757 | .has_symlinks_windows = !omit_symlinks, |
| 758 | case.import.requires_macos_sdk; | 758 | }); |
| 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, .{}); | ||
| 768 | const dep_step = dep.builder.default_step; | 759 | const dep_step = dep.builder.default_step; |
| 769 | assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); | 760 | assert(mem.startsWith(u8, dep.builder.dep_prefix, "test.")); |
| 770 | const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; | 761 | const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..]; |
| 771 | dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); | 762 | dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name }); |
| 772 | step.dependOn(dep_step); | 763 | 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 | } | ||
| 773 | } | 786 | } |
| 774 | } | 787 | } |
| 775 | 788 |