authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-07-05 08:16:23+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-07-18 09:13:07+02:00
loge2bfd6fc691a92f9dc36597a8febb03293b0f5ad
treeb10018453568aff9e88e7b0ffb065ada040abfb9
parent101299e85625faf29b4afce07ad1e3522ea75421

macho: revamp how we compute dyld relocs


8 files changed, 628 insertions(+), 780 deletions(-)

src/link/MachO.zig+18-112
......@@ -82,11 +82,11 @@ stubs_helper: StubsHelperSection = .{},
8282objc_stubs: ObjcStubsSection = .{},
8383la_symbol_ptr: LaSymbolPtrSection = .{},
8484tlv_ptr: TlvPtrSection = .{},
85rebase: RebaseSection = .{},
86bind: BindSection = .{},
87weak_bind: WeakBindSection = .{},
88lazy_bind: LazyBindSection = .{},
89export_trie: ExportTrieSection = .{},
85rebase: Rebase = .{},
86bind: Bind = .{},
87weak_bind: WeakBind = .{},
88lazy_bind: LazyBind = .{},
89export_trie: ExportTrie = .{},
9090unwind_info: UnwindInfo = .{},
9191
9292/// Tracked loadable segments during incremental linking.
......@@ -590,8 +590,6 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
590590 state_log.debug("{}", .{self.dumpState()});
591591 }
592592
593 try self.initDyldInfoSections();
594
595593 // Beyond this point, everything has been allocated a virtual address and we can resolve
596594 // the relocations, and commit objects to file.
597595 if (self.getZigObject()) |zo| {
......@@ -2500,87 +2498,6 @@ fn allocateLinkeditSegment(self: *MachO) !void {
25002498 seg.fileoff = mem.alignForward(u64, fileoff, page_size);
25012499}
25022500
2503fn initDyldInfoSections(self: *MachO) !void {
2504 const tracy = trace(@src());
2505 defer tracy.end();
2506
2507 const gpa = self.base.comp.gpa;
2508
2509 if (self.zig_got_sect_index != null) try self.zig_got.addDyldRelocs(self);
2510 if (self.got_sect_index != null) try self.got.addDyldRelocs(self);
2511 if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self);
2512 if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self);
2513 try self.initExportTrie();
2514
2515 var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1);
2516 defer objects.deinit();
2517 if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
2518 objects.appendSliceAssumeCapacity(self.objects.items);
2519
2520 var nrebases: usize = 0;
2521 var nbinds: usize = 0;
2522 var nweak_binds: usize = 0;
2523 for (objects.items) |index| {
2524 const ctx = switch (self.getFile(index).?) {
2525 .zig_object => |x| x.dynamic_relocs,
2526 .object => |x| x.dynamic_relocs,
2527 else => unreachable,
2528 };
2529 nrebases += ctx.rebase_relocs;
2530 nbinds += ctx.bind_relocs;
2531 nweak_binds += ctx.weak_bind_relocs;
2532 }
2533 if (self.getInternalObject()) |int| {
2534 nrebases += int.num_rebase_relocs;
2535 }
2536 try self.rebase.entries.ensureUnusedCapacity(gpa, nrebases);
2537 try self.bind.entries.ensureUnusedCapacity(gpa, nbinds);
2538 try self.weak_bind.entries.ensureUnusedCapacity(gpa, nweak_binds);
2539}
2540
2541fn initExportTrie(self: *MachO) !void {
2542 const tracy = trace(@src());
2543 defer tracy.end();
2544
2545 const gpa = self.base.comp.gpa;
2546 try self.export_trie.init(gpa);
2547
2548 const seg = self.getTextSegment();
2549 for (self.objects.items) |index| {
2550 for (self.getFile(index).?.getSymbols()) |sym_index| {
2551 const sym = self.getSymbol(sym_index);
2552 if (!sym.flags.@"export") continue;
2553 if (sym.getAtom(self)) |atom| if (!atom.flags.alive) continue;
2554 if (sym.getFile(self).?.getIndex() != index) continue;
2555 var flags: u64 = if (sym.flags.abs)
2556 macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
2557 else if (sym.flags.tlv)
2558 macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
2559 else
2560 macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
2561 if (sym.flags.weak) {
2562 flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
2563 self.weak_defines = true;
2564 self.binds_to_weak = true;
2565 }
2566 try self.export_trie.put(gpa, .{
2567 .name = sym.getName(self),
2568 .vmaddr_offset = sym.getAddress(.{ .stubs = false }, self) - seg.vmaddr,
2569 .export_flags = flags,
2570 });
2571 }
2572 }
2573
2574 if (self.mh_execute_header_index) |index| {
2575 const sym = self.getSymbol(index);
2576 try self.export_trie.put(gpa, .{
2577 .name = sym.getName(self),
2578 .vmaddr_offset = sym.getAddress(.{}, self) - seg.vmaddr,
2579 .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
2580 });
2581 }
2582}
2583
25842501fn writeAtoms(self: *MachO) !void {
25852502 const tracy = trace(@src());
25862503 defer tracy.end();
......@@ -2659,13 +2576,13 @@ fn writeUnwindInfo(self: *MachO) !void {
26592576fn finalizeDyldInfoSections(self: *MachO) !void {
26602577 const tracy = trace(@src());
26612578 defer tracy.end();
2662 const gpa = self.base.comp.gpa;
2663
2664 try self.rebase.finalize(gpa);
2665 try self.bind.finalize(gpa, self);
2666 try self.weak_bind.finalize(gpa, self);
2667 try self.lazy_bind.finalize(gpa, self);
2668 try self.export_trie.finalize(gpa);
2579 try self.rebase.updateSize(self);
2580 try self.bind.updateSize(self);
2581 try self.weak_bind.updateSize(self);
2582 if (self.la_symbol_ptr_sect_index) |_| {
2583 try self.lazy_bind.updateSize(self);
2584 }
2585 try self.export_trie.updateSize(self);
26692586}
26702587
26712588fn writeSyntheticSections(self: *MachO) !void {
......@@ -2742,25 +2659,14 @@ fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
27422659 const gpa = self.base.comp.gpa;
27432660 const cmd = &self.dyld_info_cmd;
27442661 var needed_size: u32 = 0;
2745
2746 cmd.rebase_off = needed_size;
2747 cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64));
27482662 needed_size += cmd.rebase_size;
2749
27502663 cmd.bind_off = needed_size;
2751 cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64));
27522664 needed_size += cmd.bind_size;
2753
27542665 cmd.weak_bind_off = needed_size;
2755 cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64));
27562666 needed_size += cmd.weak_bind_size;
2757
27582667 cmd.lazy_bind_off = needed_size;
2759 cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64));
27602668 needed_size += cmd.lazy_bind_size;
2761
27622669 cmd.export_off = needed_size;
2763 cmd.export_size = mem.alignForward(u32, @intCast(self.export_trie.size), @alignOf(u64));
27642670 needed_size += cmd.export_size;
27652671
27662672 const buffer = try gpa.alloc(u8, needed_size);
......@@ -2785,7 +2691,6 @@ fn writeDyldInfoSections(self: *MachO, off: u32) !u32 {
27852691 cmd.weak_bind_off += off;
27862692 cmd.lazy_bind_off += off;
27872693 cmd.export_off += off;
2788
27892694 try self.base.file.?.pwriteAll(buffer, off);
27902695
27912696 return off + needed_size;
......@@ -4831,6 +4736,7 @@ const mem = std.mem;
48314736const meta = std.meta;
48324737
48334738const aarch64 = @import("../arch/aarch64/bits.zig");
4739const bind = @import("MachO/dyld_info/bind.zig");
48344740const calcUuid = @import("MachO/uuid.zig").calcUuid;
48354741const codegen = @import("../codegen.zig");
48364742const dead_strip = @import("MachO/dead_strip.zig");
......@@ -4851,13 +4757,13 @@ const Alignment = Atom.Alignment;
48514757const Allocator = mem.Allocator;
48524758const Archive = @import("MachO/Archive.zig");
48534759pub const Atom = @import("MachO/Atom.zig");
4854const BindSection = synthetic.BindSection;
4760const Bind = bind.Bind;
48554761const Cache = std.Build.Cache;
48564762const CodeSignature = @import("MachO/CodeSignature.zig");
48574763const Compilation = @import("../Compilation.zig");
48584764pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
48594765const Dylib = @import("MachO/Dylib.zig");
4860const ExportTrieSection = synthetic.ExportTrieSection;
4766const ExportTrie = @import("MachO/dyld_info/Trie.zig");
48614767const File = @import("MachO/file.zig").File;
48624768const GotSection = synthetic.GotSection;
48634769const Hash = std.hash.Wyhash;
......@@ -4865,7 +4771,7 @@ const Indsymtab = synthetic.Indsymtab;
48654771const InternalObject = @import("MachO/InternalObject.zig");
48664772const ObjcStubsSection = synthetic.ObjcStubsSection;
48674773const Object = @import("MachO/Object.zig");
4868const LazyBindSection = synthetic.LazyBindSection;
4774const LazyBind = bind.LazyBind;
48694775const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
48704776const LibStub = tapi.LibStub;
48714777const Liveness = @import("../Liveness.zig");
......@@ -4875,7 +4781,7 @@ const Zcu = @import("../Zcu.zig");
48754781/// Deprecated.
48764782const Module = Zcu;
48774783const InternPool = @import("../InternPool.zig");
4878const RebaseSection = synthetic.RebaseSection;
4784const Rebase = @import("MachO/dyld_info/Rebase.zig");
48794785pub const Relocation = @import("MachO/Relocation.zig");
48804786const StringTable = @import("StringTable.zig");
48814787const StubsSection = synthetic.StubsSection;
......@@ -4885,6 +4791,6 @@ const Thunk = thunks.Thunk;
48854791const TlvPtrSection = synthetic.TlvPtrSection;
48864792const Value = @import("../Value.zig");
48874793const UnwindInfo = @import("MachO/UnwindInfo.zig");
4888const WeakBindSection = synthetic.WeakBindSection;
4794const WeakBind = bind.WeakBind;
48894795const ZigGotSection = synthetic.ZigGotSection;
48904796const ZigObject = @import("MachO/ZigObject.zig");
src/link/MachO/Atom.zig+1-36
......@@ -460,11 +460,6 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
460460 defer tracy.end();
461461 assert(self.flags.alive);
462462
463 const dynrel_ctx = switch (self.getFile(macho_file)) {
464 .zig_object => |x| &x.dynamic_relocs,
465 .object => |x| &x.dynamic_relocs,
466 else => unreachable,
467 };
468463 const relocs = self.getRelocs(macho_file);
469464
470465 for (relocs) |rel| {
......@@ -537,21 +532,15 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
537532 continue;
538533 }
539534 if (symbol.flags.import) {
540 dynrel_ctx.bind_relocs += 1;
541535 if (symbol.flags.weak) {
542 dynrel_ctx.weak_bind_relocs += 1;
543536 macho_file.binds_to_weak = true;
544537 }
545538 continue;
546539 }
547540 if (symbol.flags.@"export" and symbol.flags.weak) {
548 dynrel_ctx.weak_bind_relocs += 1;
549541 macho_file.binds_to_weak = true;
550 } else if (symbol.flags.interposable) {
551 dynrel_ctx.bind_relocs += 1;
552542 }
553543 }
554 dynrel_ctx.rebase_relocs += 1;
555544 }
556545 },
557546
......@@ -651,8 +640,6 @@ fn resolveRelocInner(
651640) ResolveError!void {
652641 const cpu_arch = macho_file.getTarget().cpu.arch;
653642 const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
654 const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
655 const seg = macho_file.segments.items[seg_id];
656643 const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset));
657644 const A = rel.addend + rel.getRelocAddend(cpu_arch);
658645 const S: i64 = @intCast(rel.getTargetAddress(macho_file));
......@@ -706,29 +693,8 @@ fn resolveRelocInner(
706693 try writer.writeInt(u64, @intCast(S - TLS), .little);
707694 return;
708695 }
709 const entry = bind.Entry{
710 .target = rel.target,
711 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
712 .segment_id = seg_id,
713 .addend = A,
714 };
715 if (sym.flags.import) {
716 macho_file.bind.entries.appendAssumeCapacity(entry);
717 if (sym.flags.weak) {
718 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
719 }
720 return;
721 }
722 if (sym.flags.@"export" and sym.flags.weak) {
723 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
724 } else if (sym.flags.interposable) {
725 macho_file.bind.entries.appendAssumeCapacity(entry);
726 }
696 if (sym.flags.import) return;
727697 }
728 macho_file.rebase.entries.appendAssumeCapacity(.{
729 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
730 .segment_id = seg_id,
731 });
732698 try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
733699 } else if (rel.meta.length == 2) {
734700 try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
......@@ -1239,7 +1205,6 @@ pub const Alignment = @import("../../InternPool.zig").Alignment;
12391205
12401206const aarch64 = @import("../aarch64.zig");
12411207const assert = std.debug.assert;
1242const bind = @import("dyld_info/bind.zig");
12431208const macho = std.macho;
12441209const math = std.math;
12451210const mem = std.mem;
src/link/MachO/Object.zig-1
......@@ -30,7 +30,6 @@ data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
3030alive: bool = true,
3131hidden: bool = false,
3232
33dynamic_relocs: MachO.DynamicRelocs = .{},
3433output_symtab_ctx: MachO.SymtabCtx = .{},
3534output_ar_state: Archive.ArState = .{},
3635
src/link/MachO/ZigObject.zig-1
......@@ -48,7 +48,6 @@ relocs: RelocationTable = .{},
4848
4949dwarf: ?Dwarf = null,
5050
51dynamic_relocs: MachO.DynamicRelocs = .{},
5251output_symtab_ctx: MachO.SymtabCtx = .{},
5352output_ar_state: Archive.ArState = .{},
5453
src/link/MachO/dyld_info/Rebase.zig+100-15
......@@ -1,14 +1,3 @@
1const Rebase = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const leb = std.leb;
6const log = std.log.scoped(.link_dyld_info);
7const macho = std.macho;
8const testing = std.testing;
9
10const Allocator = std.mem.Allocator;
11
121entries: std.ArrayListUnmanaged(Entry) = .{},
132buffer: std.ArrayListUnmanaged(u8) = .{},
143
......@@ -30,11 +19,94 @@ pub fn deinit(rebase: *Rebase, gpa: Allocator) void {
3019 rebase.buffer.deinit(gpa);
3120}
3221
33pub fn size(rebase: Rebase) u64 {
34 return @as(u64, @intCast(rebase.buffer.items.len));
22pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void {
23 const tracy = trace(@src());
24 defer tracy.end();
25
26 const gpa = macho_file.base.comp.gpa;
27
28 var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
29 defer objects.deinit();
30 objects.appendSliceAssumeCapacity(macho_file.objects.items);
31 if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
32
33 for (objects.items) |index| {
34 const file = macho_file.getFile(index).?;
35 for (file.getAtoms()) |atom_index| {
36 const atom = macho_file.getAtom(atom_index) orelse continue;
37 if (!atom.flags.alive) continue;
38 if (atom.getInputSection(macho_file).isZerofill()) continue;
39 const atom_addr = atom.getAddress(macho_file);
40 const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
41 const seg = macho_file.segments.items[seg_id];
42 for (atom.getRelocs(macho_file)) |rel| {
43 if (rel.type != .unsigned or rel.meta.length != 3) continue;
44 if (rel.tag == .@"extern") {
45 const sym = rel.getTargetSymbol(macho_file);
46 if (sym.isTlvInit(macho_file)) continue;
47 if (sym.flags.import) continue;
48 }
49 const rel_offset = rel.offset - atom.off;
50 try rebase.entries.append(gpa, .{
51 .offset = atom_addr + rel_offset - seg.vmaddr,
52 .segment_id = seg_id,
53 });
54 }
55 }
56 }
57
58 if (macho_file.got_sect_index) |sid| {
59 const seg_id = macho_file.sections.items(.segment_id)[sid];
60 const seg = macho_file.segments.items[seg_id];
61 for (macho_file.got.symbols.items, 0..) |ref, idx| {
62 const sym = macho_file.getSymbol(ref);
63 const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
64 if (!sym.flags.import) {
65 try rebase.entries.append(gpa, .{
66 .offset = addr - seg.vmaddr,
67 .segment_id = seg_id,
68 });
69 }
70 }
71 }
72
73 if (macho_file.la_symbol_ptr_sect_index) |sid| {
74 const sect = macho_file.sections.items(.header)[sid];
75 const seg_id = macho_file.sections.items(.segment_id)[sid];
76 const seg = macho_file.segments.items[seg_id];
77 for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
78 const sym = macho_file.getSymbol(ref);
79 const addr = sect.addr + idx * @sizeOf(u64);
80 const rebase_entry = Rebase.Entry{
81 .offset = addr - seg.vmaddr,
82 .segment_id = seg_id,
83 };
84 if ((sym.flags.import and !sym.flags.weak) or !sym.flags.import) {
85 try rebase.entries.append(gpa, rebase_entry);
86 }
87 }
88 }
89
90 if (macho_file.tlv_ptr_sect_index) |sid| {
91 const seg_id = macho_file.sections.items(.segment_id)[sid];
92 const seg = macho_file.segments.items[seg_id];
93 for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
94 const sym = macho_file.getSymbol(ref);
95 const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
96 if (!sym.flags.import) {
97 try rebase.entries.append(gpa, .{
98 .offset = addr - seg.vmaddr,
99 .segment_id = seg_id,
100 });
101 }
102 }
103 }
104
105 try rebase.finalize(gpa);
106 macho_file.dyld_info_cmd.rebase_size = mem.alignForward(u32, @intCast(rebase.buffer.items.len), @alignOf(u64));
35107}
36108
37pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
109fn finalize(rebase: *Rebase, gpa: Allocator) !void {
38110 if (rebase.entries.items.len == 0) return;
39111
40112 const writer = rebase.buffer.writer(gpa);
......@@ -198,7 +270,6 @@ fn done(writer: anytype) !void {
198270}
199271
200272pub fn write(rebase: Rebase, writer: anytype) !void {
201 if (rebase.size() == 0) return;
202273 try writer.writeAll(rebase.buffer.items);
203274}
204275
......@@ -574,3 +645,17 @@ test "rebase - composite" {
574645 macho.REBASE_OPCODE_DONE,
575646 }, rebase.buffer.items);
576647}
648
649const std = @import("std");
650const assert = std.debug.assert;
651const leb = std.leb;
652const log = std.log.scoped(.link_dyld_info);
653const macho = std.macho;
654const mem = std.mem;
655const testing = std.testing;
656const trace = @import("../../../tracy.zig").trace;
657
658const Allocator = mem.Allocator;
659const File = @import("../file.zig").File;
660const MachO = @import("../../MachO.zig");
661const Rebase = @This();
src/link/MachO/dyld_info/Trie.zig+266-455
......@@ -28,463 +28,312 @@
2828//! After the optional exported symbol information is a byte of how many edges (0-255) that
2929//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
3030//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
31const Trie = @This();
32
33const std = @import("std");
34const mem = std.mem;
35const leb = std.leb;
36const log = std.log.scoped(.macho);
37const macho = std.macho;
38const testing = std.testing;
39const assert = std.debug.assert;
40const Allocator = mem.Allocator;
41
42pub const Node = struct {
43 base: *Trie,
44
45 /// Terminal info associated with this node.
46 /// If this node is not a terminal node, info is null.
47 terminal_info: ?struct {
48 /// Export flags associated with this exported symbol.
49 export_flags: u64,
50 /// VM address offset wrt to the section this symbol is defined against.
51 vmaddr_offset: u64,
52 } = null,
53
54 /// Offset of this node in the trie output byte stream.
55 trie_offset: ?u64 = null,
56
57 /// List of all edges originating from this node.
58 edges: std.ArrayListUnmanaged(Edge) = .{},
59
60 node_dirty: bool = true,
61
62 /// Edge connecting to nodes in the trie.
63 pub const Edge = struct {
64 from: *Node,
65 to: *Node,
66 label: []u8,
67
68 fn deinit(self: *Edge, allocator: Allocator) void {
69 self.to.deinit(allocator);
70 allocator.destroy(self.to);
71 allocator.free(self.label);
72 self.from = undefined;
73 self.to = undefined;
74 self.label = undefined;
75 }
76 };
77
78 fn deinit(self: *Node, allocator: Allocator) void {
79 for (self.edges.items) |*edge| {
80 edge.deinit(allocator);
81 }
82 self.edges.deinit(allocator);
83 }
84
85 /// Inserts a new node starting from `self`.
86 fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
87 // Check for match with edges from this node.
88 for (self.edges.items) |*edge| {
89 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
90 if (match == 0) continue;
91 if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
92
93 // Found a match, need to splice up nodes.
94 // From: A -> B
95 // To: A -> C -> B
96 const mid = try allocator.create(Node);
97 mid.* = .{ .base = self.base };
98 const to_label = try allocator.dupe(u8, edge.label[match..]);
99 allocator.free(edge.label);
100 const to_node = edge.to;
101 edge.to = mid;
102 edge.label = try allocator.dupe(u8, label[0..match]);
103 self.base.node_count += 1;
104
105 try mid.edges.append(allocator, .{
106 .from = mid,
107 .to = to_node,
108 .label = to_label,
109 });
110
111 return if (match == label.len) mid else mid.put(allocator, label[match..]);
112 }
11331
114 // Add a new node.
115 const node = try allocator.create(Node);
116 node.* = .{ .base = self.base };
117 self.base.node_count += 1;
32/// The root node of the trie.
33root: ?Node.Index = null,
34buffer: std.ArrayListUnmanaged(u8) = .{},
35nodes: std.MultiArrayList(Node) = .{},
36edges: std.ArrayListUnmanaged(Edge) = .{},
11837
119 try self.edges.append(allocator, .{
120 .from = self,
121 .to = node,
122 .label = try allocator.dupe(u8, label),
123 });
38/// Insert a symbol into the trie, updating the prefixes in the process.
39/// This operation may change the layout of the trie by splicing edges in
40/// certain circumstances.
41fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
42 // const tracy = trace(@src());
43 // defer tracy.end();
44
45 const node_index = try self.putNode(self.root.?, allocator, symbol.name);
46 const slice = self.nodes.slice();
47 slice.items(.is_terminal)[node_index] = true;
48 slice.items(.vmaddr_offset)[node_index] = symbol.vmaddr_offset;
49 slice.items(.export_flags)[node_index] = symbol.export_flags;
50}
12451
125 return node;
52/// Inserts a new node starting at `node_index`.
53fn putNode(self: *Trie, node_index: Node.Index, allocator: Allocator, label: []const u8) !Node.Index {
54 // Check for match with edges from this node.
55 for (self.nodes.items(.edges)[node_index].items) |edge_index| {
56 const edge = &self.edges.items[edge_index];
57 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.node;
58 if (match == 0) continue;
59 if (match == edge.label.len) return self.putNode(edge.node, allocator, label[match..]);
60
61 // Found a match, need to splice up nodes.
62 // From: A -> B
63 // To: A -> C -> B
64 const mid_index = try self.addNode(allocator);
65 const to_label = edge.label[match..];
66 const to_node = edge.node;
67 edge.node = mid_index;
68 edge.label = label[0..match];
69
70 const new_edge_index = try self.addEdge(allocator);
71 const new_edge = &self.edges.items[new_edge_index];
72 new_edge.node = to_node;
73 new_edge.label = to_label;
74 try self.nodes.items(.edges)[mid_index].append(allocator, new_edge_index);
75
76 return if (match == label.len) mid_index else self.putNode(mid_index, allocator, label[match..]);
12677 }
12778
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;
79 // Add a new node.
80 const new_node_index = try self.addNode(allocator);
81 const new_edge_index = try self.addEdge(allocator);
82 const new_edge = &self.edges.items[new_edge_index];
83 new_edge.node = new_node_index;
84 new_edge.label = label;
85 try self.nodes.items(.edges)[node_index].append(allocator, new_edge_index);
15586
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 };
87 return new_node_index;
88}
17989
180 nread += try node.read(allocator, reader);
181 try self.edges.append(allocator, .{
182 .from = self,
183 .to = node,
184 .label = label,
90pub fn updateSize(self: *Trie, macho_file: *MachO) !void {
91 const tracy = trace(@src());
92 defer tracy.end();
93
94 const gpa = macho_file.base.comp.gpa;
95
96 try self.init(gpa);
97 // TODO
98 // try self.nodes.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
99 // try self.edges.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
100
101 const seg = macho_file.getTextSegment();
102 for (macho_file.objects.items) |index| {
103 for (macho_file.getFile(index).?.getSymbols()) |ref| {
104 const sym = macho_file.getSymbol(ref);
105 if (!sym.flags.@"export") continue;
106 if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
107 var flags: u64 = if (sym.flags.abs)
108 macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
109 else if (sym.flags.tlv)
110 macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
111 else
112 macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
113 if (sym.flags.weak) {
114 flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
115 macho_file.weak_defines = true;
116 macho_file.binds_to_weak = true;
117 }
118 try self.put(gpa, .{
119 .name = sym.getName(macho_file),
120 .vmaddr_offset = sym.getAddress(.{ .stubs = false }, macho_file) - seg.vmaddr,
121 .export_flags = flags,
185122 });
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.?);
230123 }
231124 }
232125
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 };
126 try self.finalize(gpa);
241127
242 /// Updates offset of this node in the output byte stream.
243 fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
244 var stream = std.io.countingWriter(std.io.null_writer);
245 const writer = stream.writer();
246
247 var node_size: u64 = 0;
248 if (self.terminal_info) |info| {
249 try leb.writeUleb128(writer, info.export_flags);
250 try leb.writeUleb128(writer, info.vmaddr_offset);
251 try leb.writeUleb128(writer, stream.bytes_written);
252 } else {
253 node_size += 1; // 0x0 for non-terminal nodes
254 }
255 node_size += 1; // 1 byte for edge count
256
257 for (self.edges.items) |edge| {
258 const next_node_offset = edge.to.trie_offset orelse 0;
259 node_size += edge.label.len + 1;
260 try leb.writeUleb128(writer, next_node_offset);
261 }
262
263 const trie_offset = self.trie_offset orelse 0;
264 const updated = offset_in_trie != trie_offset;
265 self.trie_offset = offset_in_trie;
266 self.node_dirty = false;
267 node_size += stream.bytes_written;
268
269 return FinalizeResult{ .node_size = node_size, .updated = updated };
270 }
271};
272
273/// The root node of the trie.
274root: ?*Node = null,
275
276/// If you want to access nodes ordered in DFS fashion,
277/// you should call `finalize` first since the nodes
278/// in this container are not guaranteed to not be stale
279/// if more insertions took place after the last `finalize`
280/// call.
281ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
282
283/// The size of the trie in bytes.
284/// This value may be outdated if there were additional
285/// insertions performed after `finalize` was called.
286/// Call `finalize` before accessing this value to ensure
287/// it is up-to-date.
288size: u64 = 0,
289
290/// Number of nodes currently in the trie.
291node_count: usize = 0,
292
293trie_dirty: bool = true,
294
295/// Export symbol that is to be placed in the trie.
296pub const ExportSymbol = struct {
297 /// Name of the symbol.
298 name: []const u8,
299
300 /// Offset of this symbol's virtual memory address from the beginning
301 /// of the __TEXT segment.
302 vmaddr_offset: u64,
303
304 /// Export flags of this exported symbol.
305 export_flags: u64,
306};
307
308/// Insert a symbol into the trie, updating the prefixes in the process.
309/// This operation may change the layout of the trie by splicing edges in
310/// certain circumstances.
311pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
312 const node = try self.root.?.put(allocator, symbol.name);
313 node.terminal_info = .{
314 .vmaddr_offset = symbol.vmaddr_offset,
315 .export_flags = symbol.export_flags,
316 };
317 self.trie_dirty = true;
128 macho_file.dyld_info_cmd.export_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
318129}
319130
320131/// Finalizes this trie for writing to a byte stream.
321132/// This step performs multiple passes through the trie ensuring
322133/// there are no gaps after every `Node` is ULEB128 encoded.
323134/// Call this method before trying to `write` the trie to a byte stream.
324pub fn finalize(self: *Trie, allocator: Allocator) !void {
325 if (!self.trie_dirty) return;
135fn finalize(self: *Trie, allocator: Allocator) !void {
136 const tracy = trace(@src());
137 defer tracy.end();
326138
327 self.ordered_nodes.shrinkRetainingCapacity(0);
328 try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
139 var ordered_nodes = std.ArrayList(Node.Index).init(allocator);
140 defer ordered_nodes.deinit();
141 try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);
329142
330 var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
143 var fifo = std.fifo.LinearFifo(Node.Index, .Dynamic).init(allocator);
331144 defer fifo.deinit();
332145
333146 try fifo.writeItem(self.root.?);
334147
335 while (fifo.readItem()) |next| {
336 for (next.edges.items) |*edge| {
337 try fifo.writeItem(edge.to);
148 while (fifo.readItem()) |next_index| {
149 const edges = &self.nodes.items(.edges)[next_index];
150 for (edges.items) |edge_index| {
151 const edge = self.edges.items[edge_index];
152 try fifo.writeItem(edge.node);
338153 }
339 self.ordered_nodes.appendAssumeCapacity(next);
154 ordered_nodes.appendAssumeCapacity(next_index);
340155 }
341156
342157 var more: bool = true;
158 var size: u32 = 0;
343159 while (more) {
344 self.size = 0;
160 size = 0;
345161 more = false;
346 for (self.ordered_nodes.items) |node| {
347 const res = try node.finalize(self.size);
348 self.size += res.node_size;
162 for (ordered_nodes.items) |node_index| {
163 const res = try self.finalizeNode(node_index, size);
164 size += res.node_size;
349165 if (res.updated) more = true;
350166 }
351167 }
352168
353 self.trie_dirty = false;
169 try self.buffer.ensureTotalCapacityPrecise(allocator, size);
170 for (ordered_nodes.items) |node_index| {
171 try self.writeNode(node_index, self.buffer.writer(allocator));
172 }
354173}
355174
356const ReadError = error{
357 OutOfMemory,
358 EndOfStream,
359 Overflow,
175const FinalizeNodeResult = struct {
176 /// Current size of this node in bytes.
177 node_size: u32,
178
179 /// True if the trie offset of this node in the output byte stream
180 /// would need updating; false otherwise.
181 updated: bool,
360182};
361183
362/// Parse the trie from a byte stream.
363pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
364 return self.root.?.read(allocator, reader);
365}
184/// Updates offset of this node in the output byte stream.
185fn finalizeNode(self: *Trie, node_index: Node.Index, offset_in_trie: u32) !FinalizeNodeResult {
186 var stream = std.io.countingWriter(std.io.null_writer);
187 const writer = stream.writer();
188 const slice = self.nodes.slice();
189
190 var node_size: u32 = 0;
191 if (slice.items(.is_terminal)[node_index]) {
192 const export_flags = slice.items(.export_flags)[node_index];
193 const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
194 try leb.writeULEB128(writer, export_flags);
195 try leb.writeULEB128(writer, vmaddr_offset);
196 try leb.writeULEB128(writer, stream.bytes_written);
197 } else {
198 node_size += 1; // 0x0 for non-terminal nodes
199 }
200 node_size += 1; // 1 byte for edge count
366201
367/// Write the trie to a byte stream.
368/// Panics if the trie was not finalized using `finalize` before calling this method.
369pub fn write(self: Trie, writer: anytype) !void {
370 assert(!self.trie_dirty);
371 for (self.ordered_nodes.items) |node| {
372 try node.write(writer);
202 for (slice.items(.edges)[node_index].items) |edge_index| {
203 const edge = &self.edges.items[edge_index];
204 const next_node_offset = slice.items(.trie_offset)[edge.node];
205 node_size += @intCast(edge.label.len + 1);
206 try leb.writeULEB128(writer, next_node_offset);
373207 }
208
209 const trie_offset = slice.items(.trie_offset)[node_index];
210 const updated = offset_in_trie != trie_offset;
211 slice.items(.trie_offset)[node_index] = offset_in_trie;
212 node_size += @intCast(stream.bytes_written);
213
214 return .{ .node_size = node_size, .updated = updated };
374215}
375216
376pub fn init(self: *Trie, allocator: Allocator) !void {
217fn init(self: *Trie, allocator: Allocator) !void {
377218 assert(self.root == null);
378 const root = try allocator.create(Node);
379 root.* = .{ .base = self };
380 self.root = root;
381 self.node_count += 1;
219 self.root = try self.addNode(allocator);
382220}
383221
384222pub fn deinit(self: *Trie, allocator: Allocator) void {
385 if (self.root) |root| {
386 root.deinit(allocator);
387 allocator.destroy(root);
223 for (self.nodes.items(.edges)) |*edges| {
224 edges.deinit(allocator);
388225 }
389 self.ordered_nodes.deinit(allocator);
226 self.nodes.deinit(allocator);
227 self.edges.deinit(allocator);
228 self.buffer.deinit(allocator);
390229}
391230
392test "Trie node count" {
393 const gpa = testing.allocator;
394 var trie: Trie = .{};
395 defer trie.deinit(gpa);
396 try trie.init(gpa);
231pub fn write(self: Trie, writer: anytype) !void {
232 if (self.buffer.items.len == 0) return;
233 try writer.writeAll(self.buffer.items);
234}
397235
398 try testing.expectEqual(@as(usize, 1), trie.node_count);
399 try testing.expect(trie.root != null);
236/// Writes this node to a byte stream.
237/// The children of this node *are* not written to the byte stream
238/// recursively. To write all nodes to a byte stream in sequence,
239/// iterate over `Trie.ordered_nodes` and call this method on each node.
240/// This is one of the requirements of the MachO.
241/// Panics if `finalize` was not called before calling this method.
242fn writeNode(self: *Trie, node_index: Node.Index, writer: anytype) !void {
243 const slice = self.nodes.slice();
244 const edges = slice.items(.edges)[node_index];
245 const is_terminal = slice.items(.is_terminal)[node_index];
246 const export_flags = slice.items(.export_flags)[node_index];
247 const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
248
249 if (is_terminal) {
250 // Terminal node info: encode export flags and vmaddr offset of this symbol.
251 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
252 var info_stream = std.io.fixedBufferStream(&info_buf);
253 // TODO Implement for special flags.
254 assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
255 export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
256 try leb.writeULEB128(info_stream.writer(), export_flags);
257 try leb.writeULEB128(info_stream.writer(), vmaddr_offset);
258
259 // Encode the size of the terminal node info.
260 var size_buf: [@sizeOf(u64)]u8 = undefined;
261 var size_stream = std.io.fixedBufferStream(&size_buf);
262 try leb.writeULEB128(size_stream.writer(), info_stream.pos);
263
264 // Now, write them to the output stream.
265 try writer.writeAll(size_buf[0..size_stream.pos]);
266 try writer.writeAll(info_buf[0..info_stream.pos]);
267 } else {
268 // Non-terminal node is delimited by 0 byte.
269 try writer.writeByte(0);
270 }
271 // Write number of edges (max legal number of edges is 256).
272 try writer.writeByte(@as(u8, @intCast(edges.items.len)));
273
274 for (edges.items) |edge_index| {
275 const edge = self.edges.items[edge_index];
276 // Write edge label and offset to next node in trie.
277 try writer.writeAll(edge.label);
278 try writer.writeByte(0);
279 try leb.writeULEB128(writer, slice.items(.trie_offset)[edge.node]);
280 }
281}
400282
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);
283fn addNode(self: *Trie, allocator: Allocator) !Node.Index {
284 const index: Node.Index = @intCast(try self.nodes.addOne(allocator));
285 self.nodes.set(index, .{});
286 return index;
287}
407288
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);
289fn addEdge(self: *Trie, allocator: Allocator) !Edge.Index {
290 const index: Edge.Index = @intCast(self.edges.items.len);
291 const edge = try self.edges.addOne(allocator);
292 edge.* = .{};
293 return index;
294}
415295
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);
296/// Export symbol that is to be placed in the trie.
297pub const ExportSymbol = struct {
298 /// Name of the symbol.
299 name: []const u8,
422300
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}
301 /// Offset of this symbol's virtual memory address from the beginning
302 /// of the __TEXT segment.
303 vmaddr_offset: u64,
437304
438test "Trie basic" {
439 const gpa = testing.allocator;
440 var trie: Trie = .{};
441 defer trie.deinit(gpa);
442 try trie.init(gpa);
305 /// Export flags of this exported symbol.
306 export_flags: u64,
307};
443308
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}
309const Node = struct {
310 is_terminal: bool = false,
311
312 /// Export flags associated with this exported symbol.
313 export_flags: u64 = 0,
314
315 /// VM address offset wrt to the section this symbol is defined against.
316 vmaddr_offset: u64 = 0,
317
318 /// Offset of this node in the trie output byte stream.
319 trie_offset: u32 = 0,
320
321 /// List of all edges originating from this node.
322 edges: std.ArrayListUnmanaged(Edge.Index) = .{},
323
324 const Index = u32;
325};
326
327/// Edge connecting nodes in the trie.
328const Edge = struct {
329 /// Target node in the trie.
330 node: Node.Index = 0,
331
332 /// Matching prefix.
333 label: []const u8 = "",
334
335 const Index = u32;
336};
488337
489338fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
490339 assert(expected.len > 0);
......@@ -502,7 +351,7 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
502351}
503352
504353test "write Trie to a byte stream" {
505 var gpa = testing.allocator;
354 const gpa = testing.allocator;
506355 var trie: Trie = .{};
507356 defer trie.deinit(gpa);
508357 try trie.init(gpa);
......@@ -519,7 +368,6 @@ test "write Trie to a byte stream" {
519368 });
520369
521370 try trie.finalize(gpa);
522 try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
523371
524372 const exp_buffer = [_]u8{
525373 0x0, 0x1, // node root
......@@ -531,51 +379,7 @@ test "write Trie to a byte stream" {
531379 0x2, 0x0, 0x0, 0x0, // terminal node
532380 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
533381 };
534
535 const buffer = try gpa.alloc(u8, trie.size);
536 defer gpa.free(buffer);
537 var stream = std.io.fixedBufferStream(buffer);
538 {
539 _ = try trie.write(stream.writer());
540 try expectEqualHexStrings(&exp_buffer, buffer);
541 }
542 {
543 // Writing finalized trie again should yield the same result.
544 try stream.seekTo(0);
545 _ = try trie.write(stream.writer());
546 try expectEqualHexStrings(&exp_buffer, buffer);
547 }
548}
549
550test "parse Trie from byte stream" {
551 const gpa = testing.allocator;
552
553 const in_buffer = [_]u8{
554 0x0, 0x1, // node root
555 0x5f, 0x0, 0x5, // edge '_'
556 0x0, 0x2, // non-terminal node
557 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
558 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
559 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
560 0x2, 0x0, 0x0, 0x0, // terminal node
561 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
562 };
563
564 var in_stream = std.io.fixedBufferStream(&in_buffer);
565 var trie: Trie = .{};
566 defer trie.deinit(gpa);
567 try trie.init(gpa);
568 const nread = try trie.read(gpa, in_stream.reader());
569
570 try testing.expect(nread == in_buffer.len);
571
572 try trie.finalize(gpa);
573
574 const out_buffer = try gpa.alloc(u8, trie.size);
575 defer gpa.free(out_buffer);
576 var out_stream = std.io.fixedBufferStream(out_buffer);
577 _ = try trie.write(out_stream.writer());
578 try expectEqualHexStrings(&in_buffer, out_buffer);
382 try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
579383}
580384
581385test "ordering bug" {
......@@ -602,11 +406,18 @@ test "ordering bug" {
602406 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
603407 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
604408 };
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);
409 try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
612410}
411
412const assert = std.debug.assert;
413const leb = std.leb;
414const log = std.log.scoped(.macho);
415const macho = std.macho;
416const mem = std.mem;
417const std = @import("std");
418const testing = std.testing;
419const trace = @import("../../../tracy.zig").trace;
420
421const Allocator = mem.Allocator;
422const MachO = @import("../../MachO.zig");
423const Trie = @This();
src/link/MachO/dyld_info/bind.zig+243-22
......@@ -1,14 +1,3 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const leb = std.leb;
4const log = std.log.scoped(.link_dyld_info);
5const macho = std.macho;
6const testing = std.testing;
7
8const Allocator = std.mem.Allocator;
9const MachO = @import("../../MachO.zig");
10const Symbol = @import("../Symbol.zig");
11
121pub const Entry = struct {
132 target: Symbol.Index,
143 offset: u64,
......@@ -39,11 +28,108 @@ pub const Bind = struct {
3928 self.buffer.deinit(gpa);
4029 }
4130
42 pub fn size(self: Self) u64 {
43 return @intCast(self.buffer.items.len);
31 pub fn updateSize(self: *Self, macho_file: *MachO) !void {
32 const tracy = trace(@src());
33 defer tracy.end();
34
35 const gpa = macho_file.base.comp.gpa;
36 const cpu_arch = macho_file.getTarget().cpu.arch;
37
38 var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
39 defer objects.deinit();
40 objects.appendSliceAssumeCapacity(macho_file.objects.items);
41 if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
42
43 for (objects.items) |index| {
44 const file = macho_file.getFile(index).?;
45 for (file.getAtoms()) |atom_index| {
46 const atom = macho_file.getAtom(atom_index) orelse continue;
47 if (!atom.flags.alive) continue;
48 if (atom.getInputSection(macho_file).isZerofill()) continue;
49 const atom_addr = atom.getAddress(macho_file);
50 const relocs = atom.getRelocs(macho_file);
51 const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
52 const seg = macho_file.segments.items[seg_id];
53 for (relocs) |rel| {
54 if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
55 const rel_offset = rel.offset - atom.off;
56 const addend = rel.addend + rel.getRelocAddend(cpu_arch);
57 const sym = rel.getTargetSymbol(macho_file);
58 if (sym.isTlvInit(macho_file)) continue;
59 const entry = Entry{
60 .target = rel.target,
61 .offset = atom_addr + rel_offset - seg.vmaddr,
62 .segment_id = seg_id,
63 .addend = addend,
64 };
65 if (sym.flags.import or (!(sym.flags.@"export" and sym.flags.weak) and sym.flags.interposable)) {
66 try self.entries.append(gpa, entry);
67 }
68 }
69 }
70 }
71
72 if (macho_file.got_sect_index) |sid| {
73 const seg_id = macho_file.sections.items(.segment_id)[sid];
74 const seg = macho_file.segments.items[seg_id];
75 for (macho_file.got.symbols.items, 0..) |ref, idx| {
76 const sym = macho_file.getSymbol(ref);
77 const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
78 const entry = Entry{
79 .target = ref,
80 .offset = addr - seg.vmaddr,
81 .segment_id = seg_id,
82 .addend = 0,
83 };
84 if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
85 try self.entries.append(gpa, entry);
86 }
87 }
88 }
89
90 if (macho_file.la_symbol_ptr_sect_index) |sid| {
91 const sect = macho_file.sections.items(.header)[sid];
92 const seg_id = macho_file.sections.items(.segment_id)[sid];
93 const seg = macho_file.segments.items[seg_id];
94 for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
95 const sym = macho_file.getSymbol(ref);
96 const addr = sect.addr + idx * @sizeOf(u64);
97 const bind_entry = Entry{
98 .target = ref,
99 .offset = addr - seg.vmaddr,
100 .segment_id = seg_id,
101 .addend = 0,
102 };
103 if (sym.flags.import and sym.flags.weak) {
104 try self.entries.append(gpa, bind_entry);
105 }
106 }
107 }
108
109 if (macho_file.tlv_ptr_sect_index) |sid| {
110 const seg_id = macho_file.sections.items(.segment_id)[sid];
111 const seg = macho_file.segments.items[seg_id];
112
113 for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
114 const sym = macho_file.getSymbol(ref);
115 const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
116 const entry = Entry{
117 .target = ref,
118 .offset = addr - seg.vmaddr,
119 .segment_id = seg_id,
120 .addend = 0,
121 };
122 if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
123 try self.entries.append(gpa, entry);
124 }
125 }
126 }
127
128 try self.finalize(gpa, macho_file);
129 macho_file.dyld_info_cmd.bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
44130 }
45131
46 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
132 fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
47133 if (self.entries.items.len == 0) return;
48134
49135 const writer = self.buffer.writer(gpa);
......@@ -178,7 +264,6 @@ pub const Bind = struct {
178264 }
179265
180266 pub fn write(self: Self, writer: anytype) !void {
181 if (self.size() == 0) return;
182267 try writer.writeAll(self.buffer.items);
183268 }
184269};
......@@ -194,11 +279,109 @@ pub const WeakBind = struct {
194279 self.buffer.deinit(gpa);
195280 }
196281
197 pub fn size(self: Self) u64 {
198 return @intCast(self.buffer.items.len);
282 pub fn updateSize(self: *Self, macho_file: *MachO) !void {
283 const tracy = trace(@src());
284 defer tracy.end();
285
286 const gpa = macho_file.base.comp.gpa;
287 const cpu_arch = macho_file.getTarget().cpu.arch;
288
289 var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
290 defer objects.deinit();
291 objects.appendSliceAssumeCapacity(macho_file.objects.items);
292 if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
293
294 for (objects.items) |index| {
295 const file = macho_file.getFile(index).?;
296 for (file.getAtoms()) |atom_index| {
297 const atom = macho_file.getAtom(atom_index) orelse continue;
298 if (!atom.flags.alive) continue;
299 if (atom.getInputSection(macho_file).isZerofill()) continue;
300 const atom_addr = atom.getAddress(macho_file);
301 const relocs = atom.getRelocs(macho_file);
302 const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
303 const seg = macho_file.segments.items[seg_id];
304 for (relocs) |rel| {
305 if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
306 const rel_offset = rel.offset - atom.off;
307 const addend = rel.addend + rel.getRelocAddend(cpu_arch);
308 const sym = rel.getTargetSymbol(macho_file);
309 if (sym.isTlvInit(macho_file)) continue;
310 const entry = Entry{
311 .target = rel.target,
312 .offset = atom_addr + rel_offset - seg.vmaddr,
313 .segment_id = seg_id,
314 .addend = addend,
315 };
316 if (!sym.isLocal() and sym.flags.weak) {
317 try self.entries.append(gpa, entry);
318 }
319 }
320 }
321 }
322
323 if (macho_file.got_sect_index) |sid| {
324 const seg_id = macho_file.sections.items(.segment_id)[sid];
325 const seg = macho_file.segments.items[seg_id];
326 for (macho_file.got.symbols.items, 0..) |ref, idx| {
327 const sym = macho_file.getSymbol(ref);
328 const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
329 const entry = Entry{
330 .target = ref,
331 .offset = addr - seg.vmaddr,
332 .segment_id = seg_id,
333 .addend = 0,
334 };
335 if (sym.flags.weak) {
336 try self.entries.append(gpa, entry);
337 }
338 }
339 }
340
341 if (macho_file.la_symbol_ptr_sect_index) |sid| {
342 const sect = macho_file.sections.items(.header)[sid];
343 const seg_id = macho_file.sections.items(.segment_id)[sid];
344 const seg = macho_file.segments.items[seg_id];
345
346 for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
347 const sym = macho_file.getSymbol(ref);
348 const addr = sect.addr + idx * @sizeOf(u64);
349 const bind_entry = Entry{
350 .target = ref,
351 .offset = addr - seg.vmaddr,
352 .segment_id = seg_id,
353 .addend = 0,
354 };
355 if (sym.flags.weak) {
356 try self.entries.append(gpa, bind_entry);
357 }
358 }
359 }
360
361 if (macho_file.tlv_ptr_sect_index) |sid| {
362 const seg_id = macho_file.sections.items(.segment_id)[sid];
363 const seg = macho_file.segments.items[seg_id];
364
365 for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
366 const sym = macho_file.getSymbol(ref);
367 const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
368 const entry = Entry{
369 .target = ref,
370 .offset = addr - seg.vmaddr,
371 .segment_id = seg_id,
372 .addend = 0,
373 };
374 if (sym.flags.weak) {
375 try self.entries.append(gpa, entry);
376 }
377 }
378 }
379
380 try self.finalize(gpa, macho_file);
381 macho_file.dyld_info_cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
199382 }
200383
201 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
384 fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
202385 if (self.entries.items.len == 0) return;
203386
204387 const writer = self.buffer.writer(gpa);
......@@ -322,7 +505,6 @@ pub const WeakBind = struct {
322505 }
323506
324507 pub fn write(self: Self, writer: anytype) !void {
325 if (self.size() == 0) return;
326508 try writer.writeAll(self.buffer.items);
327509 }
328510};
......@@ -340,11 +522,36 @@ pub const LazyBind = struct {
340522 self.offsets.deinit(gpa);
341523 }
342524
343 pub fn size(self: Self) u64 {
344 return @intCast(self.buffer.items.len);
525 pub fn updateSize(self: *Self, macho_file: *MachO) !void {
526 const tracy = trace(@src());
527 defer tracy.end();
528
529 const gpa = macho_file.base.comp.gpa;
530
531 const sid = macho_file.la_symbol_ptr_sect_index.?;
532 const sect = macho_file.sections.items(.header)[sid];
533 const seg_id = macho_file.sections.items(.segment_id)[sid];
534 const seg = macho_file.segments.items[seg_id];
535
536 for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
537 const sym = macho_file.getSymbol(ref);
538 const addr = sect.addr + idx * @sizeOf(u64);
539 const bind_entry = Entry{
540 .target = ref,
541 .offset = addr - seg.vmaddr,
542 .segment_id = seg_id,
543 .addend = 0,
544 };
545 if ((sym.flags.import and !sym.flags.weak) or (sym.flags.interposable and !sym.flags.weak)) {
546 try self.entries.append(gpa, bind_entry);
547 }
548 }
549
550 try self.finalize(gpa, macho_file);
551 macho_file.dyld_info_cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
345552 }
346553
347 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
554 fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
348555 try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
349556
350557 const writer = self.buffer.writer(gpa);
......@@ -474,3 +681,17 @@ fn done(writer: anytype) !void {
474681 log.debug(">>> done", .{});
475682 try writer.writeByte(macho.BIND_OPCODE_DONE);
476683}
684
685const assert = std.debug.assert;
686const leb = std.leb;
687const log = std.log.scoped(.link_dyld_info);
688const macho = std.macho;
689const mem = std.mem;
690const testing = std.testing;
691const trace = @import("../../../tracy.zig").trace;
692const std = @import("std");
693
694const Allocator = mem.Allocator;
695const File = @import("../file.zig").File;
696const MachO = @import("../../MachO.zig");
697const Symbol = @import("../Symbol.zig");
src/link/MachO/synthetic.zig-138
......@@ -70,22 +70,6 @@ pub const ZigGotSection = struct {
7070 }
7171 }
7272
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
8973 const FormatCtx = struct {
9074 zig_got: ZigGotSection,
9175 macho_file: *MachO,
......@@ -146,41 +130,6 @@ pub const GotSection = struct {
146130 return got.symbols.items.len * @sizeOf(u64);
147131 }
148132
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
184133 pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
185134 const tracy = trace(@src());
186135 defer tracy.end();
......@@ -446,49 +395,6 @@ pub const LaSymbolPtrSection = struct {
446395 return macho_file.stubs.symbols.items.len * @sizeOf(u64);
447396 }
448397
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
492398 pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
493399 const tracy = trace(@src());
494400 defer tracy.end();
......@@ -539,41 +445,6 @@ pub const TlvPtrSection = struct {
539445 return tlv.symbols.items.len * @sizeOf(u64);
540446 }
541447
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
577448 pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
578449 const tracy = trace(@src());
579450 defer tracy.end();
......@@ -772,21 +643,12 @@ pub const Indsymtab = struct {
772643 }
773644};
774645
775pub const RebaseSection = Rebase;
776pub const BindSection = bind.Bind;
777pub const WeakBindSection = bind.WeakBind;
778pub const LazyBindSection = bind.LazyBind;
779pub const ExportTrieSection = Trie;
780
781646const aarch64 = @import("../aarch64.zig");
782647const assert = std.debug.assert;
783const bind = @import("dyld_info/bind.zig");
784648const math = std.math;
785649const std = @import("std");
786650const trace = @import("../../tracy.zig").trace;
787651
788652const Allocator = std.mem.Allocator;
789653const MachO = @import("../MachO.zig");
790const Rebase = @import("dyld_info/Rebase.zig");
791654const Symbol = @import("Symbol.zig");
792const Trie = @import("dyld_info/Trie.zig");