authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-01-09 14:59:34+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-01-24 12:34:38+01:00
log2f94dc939e8acf0dab4cf1f3cf01b132ac6fdd3c
tree5d9ccf480d9194c6b09fcd519e872e2df9c7ae0e
parent92211135f1424aaca0de131cfe3646248730b1ca

macho: copy over new implementation sources from zld


27 files changed, 8104 insertions(+), 8302 deletions(-)

CMakeLists.txt+7-3
...@@ -603,20 +603,24 @@ set(ZIG_STAGE2_SOURCES...@@ -603,20 +603,24 @@ 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/dead_strip.zig"
610 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig"612 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/bind.zig"
611 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig"613 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Rebase.zig"
612 "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig"614 "${CMAKE_SOURCE_DIR}/src/link/MachO/dyld_info/Trie.zig"
613 "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig"615 "${CMAKE_SOURCE_DIR}/src/link/MachO/eh_frame.zig"
614 "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"616 "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig"
617 "${CMAKE_SOURCE_DIR}/src/link/MachO/file.zig"
615 "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig"618 "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig"
616 "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"619 "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig"
620 "${CMAKE_SOURCE_DIR}/src/link/MachO/relocatable.zig"
621 "${CMAKE_SOURCE_DIR}/src/link/MachO/synthetic.zig"
617 "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"622 "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig"
618 "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig"623 "${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"624 "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig"
621 "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"625 "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"
622 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"626 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
src/link/MachO/Archive.zig+62-151
...@@ -1,20 +1,15 @@...@@ -1,20 +1,15 @@
1file: fs.File,1path: []const u8,
2fat_offset: u64,2data: []const u8,
3name: []const u8,
4header: ar_hdr = undefined,
53
6/// Parsed table of contents.4objects: std.ArrayListUnmanaged(Object) = .{},
7/// Each symbol name points to a list of all definition
8/// sites within the current static archive.
9toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{},
105
11// Archive files start with the ARMAG identifying string. Then follows a6// 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.
15const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";10pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
16/// Size of that string.11/// Size of that string.
17const SARMAG: u4 = 8;12pub const SARMAG: u4 = 8;
1813
19/// String in ar_fmag at the end of each header.14/// String in ar_fmag at the end of each header.
20const ARFMAG: *const [2:0]u8 = "`\n";15const ARFMAG: *const [2:0]u8 = "`\n";
...@@ -41,177 +36,93 @@ const ar_hdr = extern struct {...@@ -41,177 +36,93 @@ const ar_hdr = extern struct {
41 /// Always contains ARFMAG.36 /// Always contains ARFMAG.
42 ar_fmag: [2]u8,37 ar_fmag: [2]u8,
4338
44 const NameOrLength = union(enum) {
45 Name: []const u8,
46 Length: u32,
47 };
48 fn nameOrLength(self: ar_hdr) !NameOrLength {
49 const value = getValue(&self.ar_name);
50 const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
51 const len = value.len;
52 if (slash_index == len - 1) {
53 // Name stored directly
54 return NameOrLength{ .Name = value };
55 } else {
56 // Name follows the header directly and its length is encoded in
57 // the name field.
58 const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
59 return NameOrLength{ .Length = length };
60 }
61 }
62
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 }
6743
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 }
7248
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};
7755
78pub 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
85pub fn deinit(self: *Archive, allocator: Allocator) void {
86 self.file.close();
87 for (self.toc.keys()) |*key| {
88 allocator.free(key.*);
89 }
90 for (self.toc.values()) |*value| {
91 value.deinit(allocator);
92 }61 }
93 self.toc.deinit(allocator);62};
94 allocator.free(self.name);
95}
96
97pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void {
98 _ = try reader.readBytesNoEof(SARMAG);
99 self.header = try reader.readStruct(ar_hdr);
100 const name_or_length = try self.header.nameOrLength();
101 const embedded_name = try parseName(allocator, name_or_length, reader);
102 log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name });
103 defer allocator.free(embedded_name);
104
105 try self.parseTableOfContents(allocator, reader);
106}
10763
108fn parseName(allocator: Allocator, name_or_length: ar_hdr.NameOrLength, reader: anytype) ![]u8 {64pub fn deinit(self: *Archive, allocator: Allocator) void {
109 var name: []u8 = undefined;65 self.objects.deinit(allocator);
110 switch (name_or_length) {
111 .Name => |n| {
112 name = try allocator.dupe(u8, n);
113 },
114 .Length => |len| {
115 var n = try allocator.alloc(u8, len);
116 defer allocator.free(n);
117 try reader.readNoEof(n);
118 const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len;
119 name = try allocator.dupe(u8, n[0..actual_len]);
120 },
121 }
122 return name;
123}66}
12467
125fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {68pub fn parse(self: *Archive, arena: Allocator, macho_file: *MachO) !void {
126 const symtab_size = try reader.readInt(u32, .little);69 const gpa = macho_file.base.allocator;
127 const symtab = try allocator.alloc(u8, symtab_size);
128 defer allocator.free(symtab);
129
130 reader.readNoEof(symtab) catch {
131 log.debug("incomplete symbol table: expected symbol table of length 0x{x}", .{symtab_size});
132 return error.MalformedArchive;
133 };
13470
135 const strtab_size = try reader.readInt(u32, .little);71 var stream = std.io.fixedBufferStream(self.data);
136 const strtab = try allocator.alloc(u8, strtab_size);72 const reader = stream.reader();
137 defer allocator.free(strtab);
138
139 reader.readNoEof(strtab) catch {
140 log.debug("incomplete symbol table: expected string table of length 0x{x}", .{strtab_size});
141 return error.MalformedArchive;
142 };
143
144 var symtab_stream = std.io.fixedBufferStream(symtab);
145 var symtab_reader = symtab_stream.reader();
14673
147 while (true) {74 while (true) {
148 const n_strx = symtab_reader.readInt(u32, .little) catch |err| switch (err) {75 if (stream.pos >= self.data.len) break;
149 error.EndOfStream => break,76 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);
15377
154 const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + n_strx)), 0);78 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);
15879
159 if (!res.found_existing) {80 if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
160 res.value_ptr.* = .{};81 macho_file.base.fatal("{s}: invalid header delimiter: expected '{s}', found '{s}'", .{
82 self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
83 });
84 return error.ParseFailed;
161 }85 }
16286
163 try res.value_ptr.append(allocator, object_offset);87 var size = try hdr.size();
164 }88 const name = name: {
165}89 if (hdr.name()) |n| break :name try arena.dupe(u8, n);
90 if (try hdr.nameLength()) |len| {
91 size -= len;
92 const buf = try arena.alloc(u8, len);
93 try reader.readNoEof(buf);
94 const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
95 break :name buf[0..actual_len];
96 }
97 unreachable;
98 };
99 defer {
100 _ = stream.seekBy(size) catch {};
101 }
166102
167pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object {103 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 }
195104
196 var object = Object{105 const object = Object{
197 .name = name,106 .archive = self.path,
198 .mtime = object_header.date() catch 0,107 .path = name,
199 .contents = contents,108 .data = self.data[stream.pos..][0..size],
200 };109 .index = undefined,
110 .alive = false,
111 .mtime = hdr.date() catch 0,
112 };
201113
202 try object.parse(gpa);114 log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
203115
204 return object;116 try self.objects.append(gpa, object);
117 }
205}118}
206119
207const Archive = @This();
208
209const std = @import("std");
210const assert = std.debug.assert;
211const fs = std.fs;
212const log = std.log.scoped(.link);120const log = std.log.scoped(.link);
213const macho = std.macho;121const macho = std.macho;
214const mem = std.mem;122const mem = std.mem;
123const std = @import("std");
215124
216const Allocator = mem.Allocator;125const Allocator = mem.Allocator;
126const Archive = @This();
127const MachO = @import("../MachO.zig");
217const Object = @import("Object.zig");128const Object = @import("Object.zig");
src/link/MachO/Atom.zig+751-1117
...@@ -1,1271 +1,905 @@...@@ -1,1271 +1,905 @@
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` array2value: u64 = 0,
3/// (see `Object`), or if the symbol is a synthetic symbol such as a GOT cell or3
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 or5name: u32 = 0,
6/// offset table entry.6
7sym_index: u32 = 0,7/// Index into linker's input file table.
88file: 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.
13file: u32 = 0,
14
15/// If this Atom is not a synthetic Atom, i.e., references a subsection in an
16/// Object file, `inner_sym_index` and `inner_nsyms_trailing` tell where and if
17/// this Atom contains any additional symbol references that fall within this Atom's
18/// address range. These could for example be an alias symbol which can be used
19/// internally by the relocation records, or if the Object file couldn't be split
20/// into subsections, this Atom may encompass an entire input section.
21inner_sym_index: u32 = 0,
22inner_nsyms_trailing: u32 = 0,
23
24/// Size and alignment of this atom
25/// Unlike in Elf, we need to store the size of this symbol as part of
26/// the atom since macho.nlist_64 lacks this information.
27size: u64 = 0,11size: u64 = 0,
2812
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.14alignment: u32 = 0,
31alignment: Alignment = .@"1",
3215
33/// Points to the previous and next neighbours16/// Index of the input section.
34/// TODO use the same trick as with symbols: reserve index 0 as null atom17n_sect: u32 = 0,
35next_index: ?Index = null,
36prev_index: ?Index = null,
3718
38pub const Alignment = @import("../../InternPool.zig").Alignment;19/// Index of the output section.
20out_n_sect: u8 = 0,
3921
40pub const Index = u32;22/// Offset within the parent section pointed to by n_sect.
23/// off + size <= parent section size.
24off: u64 = 0,
4125
42pub const Binding = struct {26/// Relocations of this atom.
43 target: SymbolWithLoc,27relocs: Loc = .{},
44 offset: u64,28
45};29/// Index of this atom in the linker's atoms table.
30atom_index: Index = 0,
4631
47/// Returns `null` if the Atom is a synthetic Atom.32/// Index of the thunk for this atom.
48/// Otherwise, returns an index into an array of Objects.33thunk_index: Thunk.Index = 0,
49pub fn getFile(self: Atom) ?u32 {34
50 if (self.file == 0) return null;35/// Unwind records associated with this atom.
51 return self.file - 1;36unwind_records: Loc = .{},
37
38flags: Flags = .{},
39
40pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 {
41 return macho_file.string_intern.getAssumeExists(self.name);
52}42}
5343
54pub fn getSymbolIndex(self: Atom) ?u32 {44pub fn getFile(self: Atom, macho_file: *MachO) File {
55 if (self.getFile() == null and self.sym_index == 0) return null;45 return macho_file.getFile(self.file).?;
56 return self.sym_index;
57}46}
5847
59/// Returns symbol referencing this atom.48pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 {
60pub fn getSymbol(self: Atom, macho_file: *MachO) macho.nlist_64 {49 return switch (self.getFile(macho_file)) {
61 return self.getSymbolPtr(macho_file).*;50 .dylib => unreachable,
51 inline else => |x| x.sections.items(.header)[self.n_sect],
52 };
62}53}
6354
64/// Returns pointer-to-symbol referencing this atom.55pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 {
65pub fn getSymbolPtr(self: Atom, macho_file: *MachO) *macho.nlist_64 {56 return self.getInputSection(macho_file).addr + self.off;
66 const sym_index = self.getSymbolIndex().?;
67 return macho_file.getSymbolPtr(.{ .sym_index = sym_index, .file = self.file });
68}57}
6958
70pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc {59pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
71 const sym_index = self.getSymbolIndex().?;60 const file = self.getFile(macho_file);
72 return .{ .sym_index = sym_index, .file = self.file };61 return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect));
73}62}
7463
75/// Returns the name of this atom.64pub fn getCode(self: Atom, macho_file: *MachO) []const u8 {
76pub fn getName(self: Atom, macho_file: *MachO) []const u8 {65 const code = switch (self.getFile(macho_file)) {
77 const sym_index = self.getSymbolIndex().?;66 .dylib => unreachable,
78 return macho_file.getSymbolName(.{ .sym_index = sym_index, .file = self.file });67 inline else => |x| x.getSectionData(self.n_sect),
68 };
69 return code[self.off..][0..self.size];
79}70}
8071
81/// Returns how much room there is to grow in virtual address space.72pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
82/// File offset relocation happens transparently, so it is not included in73 const relocs = switch (self.getFile(macho_file)) {
83/// this calculation.74 .dylib => unreachable,
84pub fn capacity(self: Atom, macho_file: *MachO) u64 {75 inline else => |x| x.sections.items(.relocs)[self.n_sect],
85 const self_sym = self.getSymbol(macho_file);76 };
86 if (self.next_index) |next_index| {77 return relocs.items[self.relocs.pos..][0..self.relocs.len];
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}78}
9679
97pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {80pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
98 // No need to keep a free list node for the last atom.81 return switch (self.getFile(macho_file)) {
99 const next_index = self.next_index orelse return false;82 .dylib => unreachable,
100 const next = macho_file.getAtom(next_index);83 .internal => &[0]UnwindInfo.Record.Index{},
101 const self_sym = self.getSymbol(macho_file);84 .object => |x| x.unwind_records.items[self.unwind_records.pos..][0..self.unwind_records.len],
102 const next_sym = next.getSymbol(macho_file);85 };
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}86}
10987
110pub fn getOutputSection(macho_file: *MachO, sect: macho.section_64) !?u8 {88pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
111 const segname = sect.segName();89 for (self.getUnwindRecords(macho_file)) |cu_index| {
112 const sectname = sect.sectName();90 const cu = macho_file.getUnwindRecord(cu_index);
113 const res: ?u8 = blk: {91 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 }
12092
121 // We handle unwind info separately.93 if (cu.getFdePtr(macho_file)) |fde| {
122 if (mem.eql(u8, "__TEXT", segname) and mem.eql(u8, "__eh_frame", sectname)) {94 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 }95 }
96 }
97}
12898
129 if (sect.isCode()) {99pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
130 if (macho_file.text_section_index == null) {100 return macho_file.getThunk(self.thunk_index);
131 macho_file.text_section_index = try macho_file.initSection("__TEXT", "__text", .{101}
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 }
139102
140 if (sect.isDebug()) {103pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
141 break :blk null;104 const segname, const sectname, const flags = blk: {
142 }105 if (sect.isCode()) break :blk .{
106 "__TEXT",
107 "__text",
108 macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
109 };
143110
144 switch (sect.type()) {111 switch (sect.type()) {
145 macho.S_4BYTE_LITERALS,112 macho.S_4BYTE_LITERALS,
146 macho.S_8BYTE_LITERALS,113 macho.S_8BYTE_LITERALS,
147 macho.S_16BYTE_LITERALS,114 macho.S_16BYTE_LITERALS,
148 => {115 => break :blk .{ "__TEXT", "__const", macho.S_REGULAR },
149 break :blk macho_file.getSectionByName("__TEXT", "__const") orelse116
150 try macho_file.initSection("__TEXT", "__const", .{});
151 },
152 macho.S_CSTRING_LITERALS => {117 macho.S_CSTRING_LITERALS => {
153 if (mem.startsWith(u8, sectname, "__objc")) {118 if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{
154 break :blk macho_file.getSectionByName(segname, sectname) orelse119 sect.segName(), sect.sectName(), macho.S_REGULAR,
155 try macho_file.initSection(segname, sectname, .{});120 };
156 }121 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 },122 },
123
162 macho.S_MOD_INIT_FUNC_POINTERS,124 macho.S_MOD_INIT_FUNC_POINTERS,
163 macho.S_MOD_TERM_FUNC_POINTERS,125 macho.S_MOD_TERM_FUNC_POINTERS,
164 => {126 => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags },
165 break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse127
166 try macho_file.initSection("__DATA_CONST", sectname, .{
167 .flags = sect.flags,
168 });
169 },
170 macho.S_LITERAL_POINTERS,128 macho.S_LITERAL_POINTERS,
171 macho.S_ZEROFILL,129 macho.S_ZEROFILL,
130 macho.S_GB_ZEROFILL,
172 macho.S_THREAD_LOCAL_VARIABLES,131 macho.S_THREAD_LOCAL_VARIABLES,
173 macho.S_THREAD_LOCAL_VARIABLE_POINTERS,132 macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
174 macho.S_THREAD_LOCAL_REGULAR,133 macho.S_THREAD_LOCAL_REGULAR,
175 macho.S_THREAD_LOCAL_ZEROFILL,134 macho.S_THREAD_LOCAL_ZEROFILL,
176 => {135 => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
177 break :blk macho_file.getSectionByName(segname, sectname) orelse136
178 try macho_file.initSection(segname, sectname, .{137 macho.S_COALESCED => break :blk .{
179 .flags = sect.flags,138 sect.segName(),
180 });139 sect.sectName(),
181 },140 macho.S_REGULAR,
182 macho.S_COALESCED => {
183 break :blk macho_file.getSectionByName(segname, sectname) orelse
184 try macho_file.initSection(segname, sectname, .{});
185 },141 },
142
186 macho.S_REGULAR => {143 macho.S_REGULAR => {
187 if (mem.eql(u8, segname, "__TEXT")) {144 const segname = sect.segName();
188 if (mem.eql(u8, sectname, "__rodata") or145 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")) {146 if (mem.eql(u8, segname, "__DATA")) {
199 if (mem.eql(u8, sectname, "__const") or147 if (mem.eql(u8, sectname, "__const") or
200 mem.eql(u8, sectname, "__cfstring") or148 mem.eql(u8, sectname, "__cfstring") or
201 mem.eql(u8, sectname, "__objc_classlist") or149 mem.eql(u8, sectname, "__objc_classlist") or
202 mem.eql(u8, sectname, "__objc_imageinfo"))150 mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{
203 {151 "__DATA_CONST",
204 break :blk macho_file.getSectionByName("__DATA_CONST", sectname) orelse152 sectname,
205 try macho_file.initSection("__DATA_CONST", sectname, .{});153 macho.S_REGULAR,
206 } else if (mem.eql(u8, sectname, "__data")) {154 };
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 }155 }
213 break :blk macho_file.getSectionByName(segname, sectname) orelse156 break :blk .{ segname, sectname, sect.flags };
214 try macho_file.initSection(segname, sectname, .{});
215 },157 },
216 else => break :blk null,
217 }
218 };
219158
220 // TODO we can do this directly in the selection logic above.159 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 }
241 }
242 if (macho_file.bss_section_index == null) {
243 if (macho_file.getSectionByName("__DATA", "__bss")) |index| {
244 macho_file.bss_section_index = index;
245 }160 }
246 }
247
248 return res;
249}
250
251pub fn addRelocation(macho_file: *MachO, atom_index: Index, reloc: Relocation) !void {
252 return addRelocations(macho_file, atom_index, &[_]Relocation{reloc});
253}
254
255pub fn addRelocations(macho_file: *MachO, atom_index: Index, relocs: []const Relocation) !void {
256 const comp = macho_file.base.comp;
257 const gpa = comp.gpa;
258 const gop = try macho_file.relocs.getOrPut(gpa, atom_index);
259 if (!gop.found_existing) {
260 gop.value_ptr.* = .{};
261 }
262 try gop.value_ptr.ensureUnusedCapacity(gpa, relocs.len);
263 for (relocs) |reloc| {
264 log.debug(" (adding reloc of type {s} to target %{d})", .{
265 @tagName(reloc.type),
266 reloc.target.sym_index,
267 });
268 gop.value_ptr.appendAssumeCapacity(reloc);
269 }
270}
271
272pub fn addRebase(macho_file: *MachO, atom_index: Index, offset: u32) !void {
273 const comp = macho_file.base.comp;
274 const gpa = comp.gpa;
275 const atom = macho_file.getAtom(atom_index);
276 log.debug(" (adding rebase at offset 0x{x} in %{?d})", .{ offset, atom.getSymbolIndex() });
277 const gop = try macho_file.rebases.getOrPut(gpa, atom_index);
278 if (!gop.found_existing) {
279 gop.value_ptr.* = .{};
280 }
281 try gop.value_ptr.append(gpa, offset);
282}
283
284pub fn addBinding(macho_file: *MachO, atom_index: Index, binding: Binding) !void {
285 const comp = macho_file.base.comp;
286 const gpa = comp.gpa;
287 const atom = macho_file.getAtom(atom_index);
288 log.debug(" (adding binding to symbol {s} at offset 0x{x} in %{?d})", .{
289 macho_file.getSymbolName(binding.target),
290 binding.offset,
291 atom.getSymbolIndex(),
292 });
293 const gop = try macho_file.bindings.getOrPut(gpa, atom_index);
294 if (!gop.found_existing) {
295 gop.value_ptr.* = .{};
296 }
297 try gop.value_ptr.append(gpa, binding);
298}
299
300pub fn resolveRelocations(
301 macho_file: *MachO,
302 atom_index: Index,
303 relocs: []*const Relocation,
304 code: []u8,
305) void {
306 relocs_log.debug("relocating '{s}'", .{macho_file.getAtom(atom_index).getName(macho_file)});
307 for (relocs) |reloc| {
308 reloc.resolve(macho_file, atom_index, code);
309 }
310}
311
312pub fn freeRelocations(macho_file: *MachO, atom_index: Index) void {
313 const comp = macho_file.base.comp;
314 const gpa = comp.gpa;
315 var removed_relocs = macho_file.relocs.fetchOrderedRemove(atom_index);
316 if (removed_relocs) |*relocs| relocs.value.deinit(gpa);
317 var removed_rebases = macho_file.rebases.fetchOrderedRemove(atom_index);
318 if (removed_rebases) |*rebases| rebases.value.deinit(gpa);
319 var removed_bindings = macho_file.bindings.fetchOrderedRemove(atom_index);
320 if (removed_bindings) |*bindings| bindings.value.deinit(gpa);
321}
322
323const InnerSymIterator = struct {
324 sym_index: u32,
325 nsyms: u32,
326 file: u32,
327 pos: u32 = 0,
328
329 pub fn next(it: *@This()) ?SymbolWithLoc {
330 if (it.pos == it.nsyms) return null;
331 const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file };
332 it.pos += 1;
333 return res;
334 }
335};
336
337/// Returns an iterator over potentially contained symbols.
338/// Panics when called on a synthetic Atom.
339pub fn getInnerSymbolsIterator(macho_file: *MachO, atom_index: Index) InnerSymIterator {
340 const atom = macho_file.getAtom(atom_index);
341 assert(atom.getFile() != null);
342 return .{
343 .sym_index = atom.inner_sym_index,
344 .nsyms = atom.inner_nsyms_trailing,
345 .file = atom.file,
346 };161 };
347}162 const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(
348163 segname,
349/// Returns a section alias symbol if one is defined.164 sectname,
350/// An alias symbol is used to represent the start of an input section165 .{ .flags = flags },
351/// if there were no symbols defined within that range.166 );
352/// Alias symbols are only used on x86_64.167 if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) {
353pub fn getSectionAlias(macho_file: *MachO, atom_index: Index) ?SymbolWithLoc {168 macho_file.data_sect_index = osec;
354 const atom = macho_file.getAtom(atom_index);
355 assert(atom.getFile() != null);
356
357 const object = macho_file.objects.items[atom.getFile().?];
358 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
359 const ntotal = @as(u32, @intCast(object.symtab.len));
360 var sym_index: u32 = nbase;
361 while (sym_index < ntotal) : (sym_index += 1) {
362 if (object.getAtomIndexForSymbol(sym_index)) |other_atom_index| {
363 if (other_atom_index == atom_index) return SymbolWithLoc{
364 .sym_index = sym_index,
365 .file = atom.file,
366 };
367 }
368 }169 }
369 return null;170 return osec;
370}171}
371172
372/// Given an index into a contained symbol within, calculates an offset wrt173pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
373/// the start of this Atom.
374pub fn calcInnerSymbolOffset(macho_file: *MachO, atom_index: Index, sym_index: u32) u64 {
375 const atom = macho_file.getAtom(atom_index);
376 assert(atom.getFile() != null);
377
378 if (atom.sym_index == sym_index) return 0;
379
380 const object = macho_file.objects.items[atom.getFile().?];
381 const source_sym = object.getSourceSymbol(sym_index).?;
382 const base_addr = if (object.getSourceSymbol(atom.sym_index)) |sym|
383 sym.n_value
384 else blk: {
385 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
386 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
387 const source_sect = object.getSourceSection(sect_id);
388 break :blk source_sect.addr;
389 };
390 return source_sym.n_value - base_addr;
391}
392
393pub fn scanAtomRelocs(macho_file: *MachO, atom_index: Index, relocs: []align(1) const macho.relocation_info) !void {
394 const target = macho_file.base.comp.root_mod.resolved_target.result;
395 const arch = target.cpu.arch;
396 const atom = macho_file.getAtom(atom_index);
397 assert(atom.getFile() != null); // synthetic atoms do not have relocs
398
399 return switch (arch) {
400 .aarch64 => scanAtomRelocsArm64(macho_file, atom_index, relocs),
401 .x86_64 => scanAtomRelocsX86(macho_file, atom_index, relocs),
402 else => unreachable,
403 };
404}
405
406const RelocContext = struct {
407 base_addr: i64 = 0,
408 base_offset: i32 = 0,
409};
410
411pub fn getRelocContext(macho_file: *MachO, atom_index: Index) RelocContext {
412 const atom = macho_file.getAtom(atom_index);
413 assert(atom.getFile() != null); // synthetic atoms do not have relocs
414
415 const object = macho_file.objects.items[atom.getFile().?];
416 if (object.getSourceSymbol(atom.sym_index)) |source_sym| {
417 const source_sect = object.getSourceSection(source_sym.n_sect - 1);
418 return .{
419 .base_addr = @as(i64, @intCast(source_sect.addr)),
420 .base_offset = @as(i32, @intCast(source_sym.n_value - source_sect.addr)),
421 };
422 }
423 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
424 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
425 const source_sect = object.getSourceSection(sect_id);
426 return .{
427 .base_addr = @as(i64, @intCast(source_sect.addr)),
428 .base_offset = 0,
429 };
430}
431
432pub fn parseRelocTarget(macho_file: *MachO, ctx: struct {
433 object_id: u32,
434 rel: macho.relocation_info,
435 code: []const u8,
436 base_addr: i64 = 0,
437 base_offset: i32 = 0,
438}) SymbolWithLoc {
439 const tracy = trace(@src());174 const tracy = trace(@src());
440 defer tracy.end();175 defer tracy.end();
441176
442 const target = macho_file.base.comp.root_mod.resolved_target.result;177 const object = self.getFile(macho_file).object;
443 const object = &macho_file.objects.items[ctx.object_id];178 const relocs = self.getRelocs(macho_file);
444 log.debug("parsing reloc target in object({d}) '{s}' ", .{ ctx.object_id, object.name });
445
446 const sym_index = if (ctx.rel.r_extern == 0) sym_index: {
447 const sect_id = @as(u8, @intCast(ctx.rel.r_symbolnum - 1));
448 const rel_offset = @as(u32, @intCast(ctx.rel.r_address - ctx.base_offset));
449
450 const address_in_section = if (ctx.rel.r_pcrel == 0) blk: {
451 break :blk if (ctx.rel.r_length == 3)
452 mem.readInt(u64, ctx.code[rel_offset..][0..8], .little)
453 else
454 mem.readInt(u32, ctx.code[rel_offset..][0..4], .little);
455 } else blk: {
456 assert(target.cpu.arch == .x86_64);
457 const correction: u3 = switch (@as(macho.reloc_type_x86_64, @enumFromInt(ctx.rel.r_type))) {
458 .X86_64_RELOC_SIGNED => 0,
459 .X86_64_RELOC_SIGNED_1 => 1,
460 .X86_64_RELOC_SIGNED_2 => 2,
461 .X86_64_RELOC_SIGNED_4 => 4,
462 else => unreachable,
463 };
464 const addend = mem.readInt(i32, ctx.code[rel_offset..][0..4], .little);
465 const target_address = @as(i64, @intCast(ctx.base_addr)) + ctx.rel.r_address + 4 + correction + addend;
466 break :blk @as(u64, @intCast(target_address));
467 };
468
469 // Find containing atom
470 log.debug(" | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id });
471 break :sym_index object.getSymbolByAddress(address_in_section, sect_id);
472 } else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
473
474 const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };
475 const sym = macho_file.getSymbol(sym_loc);
476 const reloc_target = if (sym.sect() and !sym.ext())
477 sym_loc
478 else if (object.getGlobal(sym_index)) |global_index|
479 macho_file.globals.items[global_index]
480 else
481 sym_loc;
482 log.debug(" | target %{d} ('{s}') in object({?d})", .{
483 reloc_target.sym_index,
484 macho_file.getSymbolName(reloc_target),
485 reloc_target.getFile(),
486 });
487 return reloc_target;
488}
489
490pub fn getRelocTargetAtomIndex(macho_file: *MachO, target: SymbolWithLoc) ?Index {
491 if (target.getFile() == null) {
492 const target_sym_name = macho_file.getSymbolName(target);
493 if (mem.eql(u8, "__mh_execute_header", target_sym_name)) return null;
494 if (mem.eql(u8, "___dso_handle", target_sym_name)) return null;
495179
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
503fn scanAtomRelocsArm64(
504 macho_file: *MachO,
505 atom_index: Index,
506 relocs: []align(1) const macho.relocation_info,
507) !void {
508 for (relocs) |rel| {180 for (relocs) |rel| {
509 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));181 if (try self.reportUndefSymbol(rel, macho_file)) continue;
510182
511 switch (rel_type) {183 switch (rel.type) {
512 .ARM64_RELOC_ADDEND, .ARM64_RELOC_SUBTRACTOR => continue,184 .branch => {
513 else => {},185 const symbol = rel.getTargetSymbol(macho_file);
514 }186 if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) {
515187 symbol.flags.stubs = true;
516 if (rel.r_extern == 0) continue;188 if (symbol.flags.weak) {
517189 macho_file.binds_to_weak = true;
518 const atom = macho_file.getAtom(atom_index);190 }
519 const object = &macho_file.objects.items[atom.getFile().?];191 } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) {
520 const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];192 symbol.flags.objc_stubs = true;
521 const sym_loc = SymbolWithLoc{193 }
522 .sym_index = sym_index,194 },
523 .file = atom.file,
524 };
525195
526 const target = if (object.getGlobal(sym_index)) |global_index|196 .got_load,
527 macho_file.globals.items[global_index]197 .got_load_page,
528 else198 .got_load_pageoff,
529 sym_loc;199 => {
200 const symbol = rel.getTargetSymbol(macho_file);
201 if (symbol.flags.import or
202 (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable)) or
203 macho_file.options.cpu_arch.? == .aarch64) // TODO relax on arm64
204 {
205 symbol.flags.got = true;
206 if (symbol.flags.weak) {
207 macho_file.binds_to_weak = true;
208 }
209 }
210 },
530211
531 switch (rel_type) {212 .got => {
532 .ARM64_RELOC_BRANCH26 => {213 rel.getTargetSymbol(macho_file).flags.got = true;
533 // TODO rewrite relocation
534 const sym = macho_file.getSymbol(target);
535 if (sym.undf()) try macho_file.addStubEntry(target);
536 },214 },
537 .ARM64_RELOC_GOT_LOAD_PAGE21,215
538 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,216 .tlv,
539 .ARM64_RELOC_POINTER_TO_GOT,217 .tlvp_page,
218 .tlvp_pageoff,
540 => {219 => {
541 // TODO rewrite relocation220 const symbol = rel.getTargetSymbol(macho_file);
542 try macho_file.addGotEntry(target);221 if (!symbol.flags.tlv) {
222 macho_file.base.fatal(
223 "{}: {s}: illegal thread-local variable reference to regular symbol {s}",
224 .{ object.fmtPath(), self.getName(macho_file), symbol.getName(macho_file) },
225 );
226 }
227 if (symbol.flags.import or (symbol.flags.@"export" and (symbol.flags.weak or symbol.flags.interposable))) {
228 symbol.flags.tlv_ptr = true;
229 if (symbol.flags.weak) {
230 macho_file.binds_to_weak = true;
231 }
232 }
543 },233 },
544 .ARM64_RELOC_TLVP_LOAD_PAGE21,234
545 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,235 .unsigned => {
546 => {236 if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
547 const sym = macho_file.getSymbol(target);237 if (rel.tag == .@"extern") {
548 if (sym.undf()) try macho_file.addTlvPtrEntry(target);238 const symbol = rel.getTargetSymbol(macho_file);
239 if (symbol.isTlvInit(macho_file)) {
240 macho_file.has_tlv = true;
241 continue;
242 }
243 if (symbol.flags.import) {
244 object.num_bind_relocs += 1;
245 if (symbol.flags.weak) {
246 object.num_weak_bind_relocs += 1;
247 macho_file.binds_to_weak = true;
248 }
249 continue;
250 }
251 if (symbol.flags.@"export") {
252 if (symbol.flags.weak) {
253 object.num_weak_bind_relocs += 1;
254 macho_file.binds_to_weak = true;
255 } else if (symbol.flags.interposable) {
256 object.num_bind_relocs += 1;
257 }
258 }
259 }
260 object.num_rebase_relocs += 1;
261 }
549 },262 },
263
550 else => {},264 else => {},
551 }265 }
552 }266 }
553}267}
554268
555fn scanAtomRelocsX86(269fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
556 macho_file: *MachO,270 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));
562271
563 switch (rel_type) {272 const sym = rel.getTargetSymbol(macho_file);
564 .X86_64_RELOC_SUBTRACTOR => continue,273 if (sym.getFile(macho_file) == null) {
565 else => {},274 const gpa = macho_file.base.allocator;
275 const gop = try macho_file.undefs.getOrPut(gpa, rel.target);
276 if (!gop.found_existing) {
277 gop.value_ptr.* = .{};
566 }278 }
279 try gop.value_ptr.append(gpa, self.atom_index);
280 return true;
281 }
567282
568 if (rel.r_extern == 0) continue;283 return false;
284}
569285
570 const atom = macho_file.getAtom(atom_index);286pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
571 const object = &macho_file.objects.items[atom.getFile().?];287 const tracy = trace(@src());
572 const sym_index = object.reverse_symtab_lookup[rel.r_symbolnum];288 defer tracy.end();
573 const sym_loc = SymbolWithLoc{
574 .sym_index = sym_index,
575 .file = atom.file,
576 };
577289
578 const target = if (object.getGlobal(sym_index)) |global_index|290 assert(!self.getInputSection(macho_file).isZerofill());
579 macho_file.globals.items[global_index]291 const relocs = self.getRelocs(macho_file);
580 else292 const file = self.getFile(macho_file);
581 sym_loc;293 const name = self.getName(macho_file);
294 @memcpy(buffer, self.getCode(macho_file));
582295
583 switch (rel_type) {296 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}
601297
602pub fn resolveRelocs(298 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 });
617299
618 const ctx = getRelocContext(macho_file, atom_index);300 var i: usize = 0;
301 while (i < relocs.len) : (i += 1) {
302 const rel = relocs[i];
303 const rel_offset = rel.offset - self.off;
304 const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
619305
620 return switch (arch) {306 if (rel.tag == .@"extern") {
621 .aarch64 => resolveRelocsArm64(macho_file, atom_index, atom_code, atom_relocs, ctx),307 if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue;
622 .x86_64 => resolveRelocsX86(macho_file, atom_index, atom_code, atom_relocs, ctx),308 }
623 else => unreachable,
624 };
625}
626
627pub fn getRelocTargetAddress(macho_file: *MachO, target: SymbolWithLoc, is_tlv: bool) u64 {
628 const target_atom_index = getRelocTargetAtomIndex(macho_file, target) orelse {
629 // If there is no atom for target, we still need to check for special, atom-less
630 // symbols such as `___dso_handle`.
631 const target_name = macho_file.getSymbolName(target);
632 const atomless_sym = macho_file.getSymbol(target);
633 log.debug(" | atomless target '{s}'", .{target_name});
634 return atomless_sym.n_value;
635 };
636 const target_atom = macho_file.getAtom(target_atom_index);
637 log.debug(" | target ATOM(%{d}, '{s}') in object({?})", .{
638 target_atom.sym_index,
639 macho_file.getSymbolName(target_atom.getSymbolWithLoc()),
640 target_atom.getFile(),
641 });
642309
643 const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());310 try stream.seekTo(rel_offset);
644 assert(target_sym.n_desc != MachO.N_DEAD);311 self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| {
645312 switch (err) {
646 // If `target` is contained within the target atom, pull its address value.313 error.RelaxFail => macho_file.base.fatal(
647 const offset = if (target_atom.getFile() != null) blk: {314 "{}: {s}: 0x{x}: failed to relax relocation: in {s}",
648 const object = macho_file.objects.items[target_atom.getFile().?];315 .{ file.fmtPath(), name, rel.offset, @tagName(rel.type) },
649 break :blk if (object.getSourceSymbol(target.sym_index)) |_|316 ),
650 Atom.calcInnerSymbolOffset(macho_file, target_atom_index, target.sym_index)317 else => |e| return e,
651 else318 }
652 0; // section alias319 return error.ResolveFailed;
653 } else 0;
654 const base_address: u64 = if (is_tlv) base_address: {
655 // For TLV relocations, the value specified as a relocation is the displacement from the
656 // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first
657 // defined TLV template init section in the following order:
658 // * wrt to __thread_data if defined, then
659 // * wrt to __thread_bss
660 // TODO remember to check what the mechanism was prior to HAS_TLV_INITIALIZERS in earlier versions of macOS
661 const sect_id: u16 = sect_id: {
662 if (macho_file.thread_data_section_index) |i| {
663 break :sect_id i;
664 } else if (macho_file.thread_bss_section_index) |i| {
665 break :sect_id i;
666 } else break :base_address 0;
667 };320 };
668 break :base_address macho_file.sections.items(.header)[sect_id].addr;321 }
669 } else 0;
670 return target_sym.n_value + offset - base_address;
671}322}
672323
673fn resolveRelocsArm64(324const ResolveError = error{
674 macho_file: *MachO,325 RelaxFail,
675 atom_index: Index,326 NoSpaceLeft,
676 atom_code: []u8,327 DivisionByZero,
677 atom_relocs: []align(1) const macho.relocation_info,328 UnexpectedRemainder,
678 context: RelocContext,329 Overflow,
679) !void {330};
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
686 for (atom_relocs) |rel| {
687 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
688
689 switch (rel_type) {
690 .ARM64_RELOC_ADDEND => {
691 assert(addend == null);
692331
693 relocs_log.debug(" RELA({s}) @ {x} => {x}", .{ @tagName(rel_type), rel.r_address, rel.r_symbolnum });332fn resolveRelocInner(
333 self: Atom,
334 rel: Relocation,
335 subtractor: ?Relocation,
336 code: []u8,
337 macho_file: *MachO,
338 writer: anytype,
339) ResolveError!void {
340 const cpu_arch = macho_file.options.cpu_arch.?;
341 const rel_offset = rel.offset - self.off;
342 const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
343 const seg = macho_file.segments.items[seg_id];
344 const P = @as(i64, @intCast(self.value)) + @as(i64, @intCast(rel_offset));
345 const A = rel.addend + rel.getRelocAddend(cpu_arch);
346 const S: i64 = @intCast(rel.getTargetAddress(macho_file));
347 const G: i64 = @intCast(rel.getGotTargetAddress(macho_file));
348 const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
349 const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0;
350
351 switch (rel.tag) {
352 .local => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] atom({d})", .{
353 P,
354 rel_offset,
355 @tagName(rel.type),
356 S + A - SUB,
357 rel.getTargetAtom(macho_file).atom_index,
358 }),
359 .@"extern" => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] G({x}) ({s})", .{
360 P,
361 rel_offset,
362 @tagName(rel.type),
363 S + A - SUB,
364 G + A,
365 rel.getTargetSymbol(macho_file).getName(macho_file),
366 }),
367 }
694368
695 addend = rel.r_symbolnum;369 switch (rel.type) {
696 continue;370 .subtractor => {},
697 },371
698 .ARM64_RELOC_SUBTRACTOR => {372 .unsigned => {
699 assert(subtractor == null);373 assert(!rel.meta.pcrel);
700374 if (rel.meta.length == 3) {
701 relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{375 if (rel.tag == .@"extern") {
702 @tagName(rel_type),376 const sym = rel.getTargetSymbol(macho_file);
703 rel.r_address,377 if (sym.isTlvInit(macho_file)) {
704 rel.r_symbolnum,378 try writer.writeInt(u64, @intCast(S - TLS), .little);
705 atom.getFile(),379 return;
380 }
381 const entry = bind.Entry{
382 .target = rel.target,
383 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
384 .segment_id = seg_id,
385 .addend = A,
386 };
387 if (sym.flags.import) {
388 macho_file.bind.entries.appendAssumeCapacity(entry);
389 if (sym.flags.weak) {
390 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
391 }
392 return;
393 }
394 if (sym.flags.@"export") {
395 if (sym.flags.weak) {
396 macho_file.weak_bind.entries.appendAssumeCapacity(entry);
397 } else if (sym.flags.interposable) {
398 macho_file.bind.entries.appendAssumeCapacity(entry);
399 }
400 }
401 }
402 macho_file.rebase.entries.appendAssumeCapacity(.{
403 .offset = @as(u64, @intCast(P)) - seg.vmaddr,
404 .segment_id = seg_id,
706 });405 });
406 try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
407 } else if (rel.meta.length == 2) {
408 try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
409 } else unreachable;
410 },
707411
708 subtractor = parseRelocTarget(macho_file, .{412 .got => {
709 .object_id = atom.getFile().?,413 assert(rel.tag == .@"extern");
710 .rel = rel,414 assert(rel.meta.length == 2);
711 .code = atom_code,415 assert(rel.meta.pcrel);
712 .base_addr = context.base_addr,416 try writer.writeInt(i32, @intCast(G + A - P), .little);
713 .base_offset = context.base_offset,417 },
714 });
715 continue;
716 },
717 else => {},
718 }
719418
720 const target = parseRelocTarget(macho_file, .{419 .branch => {
721 .object_id = atom.getFile().?,420 assert(rel.meta.length == 2);
722 .rel = rel,421 assert(rel.meta.pcrel);
723 .code = atom_code,422 assert(rel.tag == .@"extern");
724 .base_addr = context.base_addr,423
725 .base_offset = context.base_offset,424 switch (cpu_arch) {
726 });425 .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little),
727 const rel_offset = @as(u32, @intCast(rel.r_address - context.base_offset));426 .aarch64 => {
728427 const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
729 relocs_log.debug(" RELA({s}) @ {x} => %{d} ('{s}') in object({?})", .{428 const thunk = self.getThunk(macho_file);
730 @tagName(rel_type),429 const S_: i64 = @intCast(thunk.getAddress(rel.target));
731 rel.r_address,430 break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
732 target.sym_index,431 };
733 macho_file.getSymbolName(target),432 var inst = aarch64.Instruction{
734 target.getFile(),433 .unconditional_branch_immediate = mem.bytesToValue(std.meta.TagPayload(
735 });434 aarch64.Instruction,
736435 aarch64.Instruction.unconditional_branch_immediate,
737 const source_addr = blk: {436 ), code[rel_offset..][0..4]),
738 const source_sym = macho_file.getSymbol(atom.getSymbolWithLoc());437 };
739 break :blk source_sym.n_value + rel_offset;438 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(disp >> 2))));
740 };439 try writer.writeInt(u32, inst.toU32(), .little);
741 const target_addr = blk: {440 },
742 if (relocRequiresGot(macho_file, rel)) break :blk macho_file.getGotEntryAddress(target).?;441 else => unreachable,
743 if (relocIsTlv(macho_file, rel) and macho_file.getSymbol(target).undf())442 }
744 break :blk macho_file.getTlvPtrEntryAddress(target).?;443 },
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 };
754444
755 relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});445 .got_load => {
446 assert(rel.tag == .@"extern");
447 assert(rel.meta.length == 2);
448 assert(rel.meta.pcrel);
449 if (rel.getTargetSymbol(macho_file).flags.got) {
450 try writer.writeInt(i32, @intCast(G + A - P), .little);
451 } else {
452 try relaxGotLoad(code[rel_offset - 3 ..]);
453 try writer.writeInt(i32, @intCast(S + A - P), .little);
454 }
455 },
756456
757 switch (rel_type) {457 .tlv => {
758 .ARM64_RELOC_BRANCH26 => {458 assert(rel.tag == .@"extern");
759 relocs_log.debug(" source {s} (object({?})), target {s}", .{459 assert(rel.meta.length == 2);
760 macho_file.getSymbolName(atom.getSymbolWithLoc()),460 assert(rel.meta.pcrel);
761 atom.getFile(),461 const sym = rel.getTargetSymbol(macho_file);
762 macho_file.getSymbolName(target),462 if (sym.flags.tlv_ptr) {
763 });463 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
464 try writer.writeInt(i32, @intCast(S_ + A - P), .little);
465 } else {
466 try relaxTlv(code[rel_offset - 3 ..]);
467 try writer.writeInt(i32, @intCast(S + A - P), .little);
468 }
469 },
764470
765 const displacement = if (Relocation.calcPcRelativeDisplacementArm64(471 .signed, .signed1, .signed2, .signed4 => {
766 source_addr,472 assert(rel.meta.length == 2);
767 target_addr,473 assert(rel.meta.pcrel);
768 )) |disp| blk: {474 try writer.writeInt(i32, @intCast(S + A - P), .little);
769 relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});475 },
770 break :blk disp;476
771 } else |_| blk: {477 .page,
772 const thunk_index = macho_file.thunk_table.get(atom_index).?;478 .got_load_page,
773 const thunk = macho_file.thunks.items[thunk_index];479 .tlvp_page,
774 const thunk_sym_loc = if (macho_file.getSymbol(target).undf())480 => {
775 thunk.getTrampoline(macho_file, .stub, target).?481 assert(rel.tag == .@"extern");
776 else482 assert(rel.meta.length == 2);
777 thunk.getTrampoline(macho_file, .atom, target).?;483 assert(rel.meta.pcrel);
778 const thunk_addr = macho_file.getSymbol(thunk_sym_loc).n_value;484 const sym = rel.getTargetSymbol(macho_file);
779 relocs_log.debug(" | target_addr = 0x{x} (thunk)", .{thunk_addr});485 const source = math.cast(u64, P) orelse return error.Overflow;
780 break :blk try Relocation.calcPcRelativeDisplacementArm64(source_addr, thunk_addr);486 const target = target: {
487 const target = switch (rel.type) {
488 .page => S + A,
489 .got_load_page => G + A,
490 .tlvp_page => if (sym.flags.tlv_ptr) blk: {
491 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
492 break :blk S_ + A;
493 } else S + A,
494 else => unreachable,
781 };495 };
496 break :target math.cast(u64, target) orelse return error.Overflow;
497 };
498 const pages = @as(u21, @bitCast(try Relocation.calcNumberOfPages(source, target)));
499 var inst = aarch64.Instruction{
500 .pc_relative_address = mem.bytesToValue(std.meta.TagPayload(
501 aarch64.Instruction,
502 aarch64.Instruction.pc_relative_address,
503 ), code[rel_offset..][0..4]),
504 };
505 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
506 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
507 try writer.writeInt(u32, inst.toU32(), .little);
508 },
782509
783 const code = atom_code[rel_offset..][0..4];510 .pageoff => {
511 assert(rel.tag == .@"extern");
512 assert(rel.meta.length == 2);
513 assert(!rel.meta.pcrel);
514 const target = math.cast(u64, S + A) orelse return error.Overflow;
515 const inst_code = code[rel_offset..][0..4];
516 if (Relocation.isArithmeticOp(inst_code)) {
517 const off = try Relocation.calcPageOffset(target, .arithmetic);
784 var inst = aarch64.Instruction{518 var inst = aarch64.Instruction{
785 .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(519 .add_subtract_immediate = mem.bytesToValue(std.meta.TagPayload(
786 aarch64.Instruction,520 aarch64.Instruction,
787 aarch64.Instruction.unconditional_branch_immediate,521 aarch64.Instruction.add_subtract_immediate,
788 ), code),522 ), inst_code),
789 };523 };
790 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));524 inst.add_subtract_immediate.imm12 = off;
791 mem.writeInt(u32, code, inst.toU32(), .little);525 try writer.writeInt(u32, inst.toU32(), .little);
792 },526 } 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{527 var inst = aarch64.Instruction{
805 .pc_relative_address = mem.bytesToValue(meta.TagPayload(528 .load_store_register = mem.bytesToValue(std.meta.TagPayload(
806 aarch64.Instruction,529 aarch64.Instruction,
807 aarch64.Instruction.pc_relative_address,530 aarch64.Instruction.load_store_register,
808 ), code),531 ), inst_code),
809 };532 };
810 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));533 const off = try Relocation.calcPageOffset(target, switch (inst.load_store_register.size) {
811 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));534 0 => if (inst.load_store_register.v == 1)
812 mem.writeInt(u32, code, inst.toU32(), .little);535 Relocation.PageOffsetInstKind.load_store_128
813 addend = null;536 else
814 },537 Relocation.PageOffsetInstKind.load_store_8,
538 1 => .load_store_16,
539 2 => .load_store_32,
540 3 => .load_store_64,
541 });
542 inst.load_store_register.offset = off;
543 try writer.writeInt(u32, inst.toU32(), .little);
544 }
545 },
815546
816 .ARM64_RELOC_PAGEOFF12 => {547 .got_load_pageoff => {
817 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));548 assert(rel.tag == .@"extern");
549 assert(rel.meta.length == 2);
550 assert(!rel.meta.pcrel);
551 const target = math.cast(u64, G + A) orelse return error.Overflow;
552 const off = try Relocation.calcPageOffset(target, .load_store_64);
553 var inst: aarch64.Instruction = .{
554 .load_store_register = mem.bytesToValue(std.meta.TagPayload(
555 aarch64.Instruction,
556 aarch64.Instruction.load_store_register,
557 ), code[rel_offset..][0..4]),
558 };
559 inst.load_store_register.offset = off;
560 try writer.writeInt(u32, inst.toU32(), .little);
561 },
818562
819 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});563 .tlvp_pageoff => {
564 assert(rel.tag == .@"extern");
565 assert(rel.meta.length == 2);
566 assert(!rel.meta.pcrel);
567
568 const sym = rel.getTargetSymbol(macho_file);
569 const target = target: {
570 const target = if (sym.flags.tlv_ptr) blk: {
571 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
572 break :blk S_ + A;
573 } else S + A;
574 break :target math.cast(u64, target) orelse return error.Overflow;
575 };
820576
821 const code = atom_code[rel_offset..][0..4];577 const RegInfo = struct {
822 if (Relocation.isArithmeticOp(code)) {578 rd: u5,
823 const off = try Relocation.calcPageOffset(adjusted_target_addr, .arithmetic);579 rn: u5,
824 var inst = aarch64.Instruction{580 size: u2,
825 .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(581 };
826 aarch64.Instruction,582
827 aarch64.Instruction.add_subtract_immediate,583 const inst_code = code[rel_offset..][0..4];
828 ), code),584 const reg_info: RegInfo = blk: {
585 if (Relocation.isArithmeticOp(inst_code)) {
586 const inst = mem.bytesToValue(std.meta.TagPayload(
587 aarch64.Instruction,
588 aarch64.Instruction.add_subtract_immediate,
589 ), inst_code);
590 break :blk .{
591 .rd = inst.rd,
592 .rn = inst.rn,
593 .size = inst.sf,
829 };594 };
830 inst.add_subtract_immediate.imm12 = off;
831 mem.writeInt(u32, code, inst.toU32(), .little);
832 } else {595 } else {
833 var inst = aarch64.Instruction{596 const inst = mem.bytesToValue(std.meta.TagPayload(
834 .load_store_register = mem.bytesToValue(meta.TagPayload(597 aarch64.Instruction,
835 aarch64.Instruction,598 aarch64.Instruction.load_store_register,
836 aarch64.Instruction.load_store_register,599 ), inst_code);
837 ), code),600 break :blk .{
601 .rd = inst.rt,
602 .rn = inst.rn,
603 .size = inst.size,
838 };604 };
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 }605 }
851 addend = null;606 };
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)));
857607
858 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});608 var inst = if (sym.flags.tlv_ptr) aarch64.Instruction{
609 .load_store_register = .{
610 .rt = reg_info.rd,
611 .rn = reg_info.rn,
612 .offset = try Relocation.calcPageOffset(target, .load_store_64),
613 .opc = 0b01,
614 .op1 = 0b01,
615 .v = 0,
616 .size = reg_info.size,
617 },
618 } else aarch64.Instruction{
619 .add_subtract_immediate = .{
620 .rd = reg_info.rd,
621 .rn = reg_info.rn,
622 .imm12 = try Relocation.calcPageOffset(target, .arithmetic),
623 .sh = 0,
624 .s = 0,
625 .op = 0,
626 .sf = @as(u1, @truncate(reg_info.size)),
627 },
628 };
629 try writer.writeInt(u32, inst.toU32(), .little);
630 },
631 }
632}
859633
860 const off = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64);634fn relaxGotLoad(code: []u8) error{RelaxFail}!void {
861 var inst: aarch64.Instruction = .{635 const old_inst = disassemble(code) orelse return error.RelaxFail;
862 .load_store_register = mem.bytesToValue(meta.TagPayload(636 switch (old_inst.encoding.mnemonic) {
863 aarch64.Instruction,637 .mov => {
864 aarch64.Instruction.load_store_register,638 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
865 ), code),639 relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
866 };640 encode(&.{inst}, code) catch return error.RelaxFail;
867 inst.load_store_register.offset = off;641 },
868 mem.writeInt(u32, code, inst.toU32(), .little);642 else => return error.RelaxFail,
869 addend = null;643 }
870 },644}
871645
872 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {646fn relaxTlv(code: []u8) error{RelaxFail}!void {
873 const code = atom_code[rel_offset..][0..4];647 const old_inst = disassemble(code) orelse return error.RelaxFail;
874 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + (addend orelse 0)));648 switch (old_inst.encoding.mnemonic) {
649 .mov => {
650 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops) catch return error.RelaxFail;
651 relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
652 encode(&.{inst}, code) catch return error.RelaxFail;
653 },
654 else => return error.RelaxFail,
655 }
656}
875657
876 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});658fn disassemble(code: []const u8) ?Instruction {
659 var disas = Disassembler.init(code);
660 const inst = disas.next() catch return null;
661 return inst;
662}
877663
878 const RegInfo = struct {664fn encode(insts: []const Instruction, code: []u8) !void {
879 rd: u5,665 var stream = std.io.fixedBufferStream(code);
880 rn: u5,666 const writer = stream.writer();
881 size: u2,667 for (insts) |inst| {
882 };668 try inst.encode(writer, .{});
883 const reg_info: RegInfo = blk: {669 }
884 if (Relocation.isArithmeticOp(code)) {670}
885 const inst = mem.bytesToValue(meta.TagPayload(
886 aarch64.Instruction,
887 aarch64.Instruction.add_subtract_immediate,
888 ), code);
889 break :blk .{
890 .rd = inst.rd,
891 .rn = inst.rn,
892 .size = inst.sf,
893 };
894 } else {
895 const inst = mem.bytesToValue(meta.TagPayload(
896 aarch64.Instruction,
897 aarch64.Instruction.load_store_register,
898 ), code);
899 break :blk .{
900 .rd = inst.rt,
901 .rn = inst.rn,
902 .size = inst.size,
903 };
904 }
905 };
906671
907 var inst = if (macho_file.tlv_ptr_table.lookup.contains(target)) aarch64.Instruction{672pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
908 .load_store_register = .{673 switch (macho_file.options.cpu_arch.?) {
909 .rt = reg_info.rd,674 .aarch64 => {
910 .rn = reg_info.rn,675 var nreloc: u32 = 0;
911 .offset = try Relocation.calcPageOffset(adjusted_target_addr, .load_store_64),676 for (self.getRelocs(macho_file)) |rel| {
912 .opc = 0b01,677 nreloc += 1;
913 .op1 = 0b01,678 switch (rel.type) {
914 .v = 0,679 .page, .pageoff => if (rel.addend > 0) {
915 .size = reg_info.size,680 nreloc += 1;
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 },681 },
927 };682 else => {},
928 mem.writeInt(u32, code, inst.toU32(), .little);
929 addend = null;
930 },
931
932 .ARM64_RELOC_POINTER_TO_GOT => {
933 relocs_log.debug(" | target_addr = 0x{x}", .{target_addr});
934 const result = math.cast(i32, @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr))) orelse
935 return error.Overflow;
936 mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @bitCast(result)), .little);
937 },
938
939 .ARM64_RELOC_UNSIGNED => {
940 var ptr_addend = if (rel.r_length == 3)
941 mem.readInt(i64, atom_code[rel_offset..][0..8], .little)
942 else
943 mem.readInt(i32, atom_code[rel_offset..][0..4], .little);
944
945 if (rel.r_extern == 0) {
946 const base_addr = if (target.sym_index >= object.source_address_lookup.len)
947 @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))
948 else
949 object.source_address_lookup[target.sym_index];
950 ptr_addend -= base_addr;
951 }683 }
952684 }
953 const result = blk: {685 return nreloc;
954 if (subtractor) |sub| {686 },
955 const sym = macho_file.getSymbol(sub);687 .x86_64 => return @intCast(self.getRelocs(macho_file).len),
956 break :blk @as(i64, @intCast(target_addr)) - @as(i64, @intCast(sym.n_value)) + ptr_addend;688 else => unreachable,
957 } else {
958 break :blk @as(i64, @intCast(target_addr)) + ptr_addend;
959 }
960 };
961 relocs_log.debug(" | target_addr = 0x{x}", .{result});
962
963 if (rel.r_length == 3) {
964 mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);
965 } else {
966 mem.writeInt(u32, atom_code[rel_offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(result)))), .little);
967 }
968
969 subtractor = null;
970 },
971
972 .ARM64_RELOC_ADDEND => unreachable,
973 .ARM64_RELOC_SUBTRACTOR => unreachable,
974 }
975 }689 }
976}690}
977691
978fn resolveRelocsX86(692pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void {
979 macho_file: *MachO,693 const tracy = trace(@src());
980 atom_index: Index,694 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
997 relocs_log.debug(" RELA({s}) @ {x} => %{d} in object({?d})", .{
998 @tagName(rel_type),
999 rel.r_address,
1000 rel.r_symbolnum,
1001 atom.getFile(),
1002 });
1003695
1004 subtractor = parseRelocTarget(macho_file, .{696 const cpu_arch = macho_file.options.cpu_arch.?;
1005 .object_id = atom.getFile().?,697 const relocs = self.getRelocs(macho_file);
1006 .rel = rel,698 const sect = macho_file.sections.items(.header)[self.out_n_sect];
1007 .code = atom_code,699 var stream = std.io.fixedBufferStream(code);
1008 .base_addr = context.base_addr,
1009 .base_offset = context.base_offset,
1010 });
1011 continue;
1012 },
1013 else => {},
1014 }
1015700
1016 const target = parseRelocTarget(macho_file, .{701 for (relocs) |rel| {
1017 .object_id = atom.getFile().?,702 const rel_offset = rel.offset - self.off;
1018 .rel = rel,703 const r_address: i32 = math.cast(i32, self.value + rel_offset - sect.addr) orelse return error.Overflow;
1019 .code = atom_code,704 const r_symbolnum = r_symbolnum: {
1020 .base_addr = context.base_addr,705 const r_symbolnum: u32 = switch (rel.tag) {
1021 .base_offset = context.base_offset,706 .local => rel.getTargetAtom(macho_file).out_n_sect + 1,
1022 });707 .@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?,
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 };708 };
1048 break :blk getRelocTargetAddress(macho_file, target, is_tlv);709 break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow;
1049 };710 };
711 const r_extern = rel.tag == .@"extern";
712 var addend = rel.addend + rel.getRelocAddend(cpu_arch);
713 if (rel.tag == .local) {
714 const target: i64 = @intCast(rel.getTargetAddress(macho_file));
715 addend += target;
716 }
1050717
1051 relocs_log.debug(" | source_addr = 0x{x}", .{source_addr});718 try stream.seekTo(rel_offset);
1052719
1053 switch (rel_type) {720 switch (cpu_arch) {
1054 .X86_64_RELOC_BRANCH => {721 .aarch64 => {
1055 const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);722 if (rel.type == .unsigned) switch (rel.meta.length) {
1056 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));723 0, 1 => unreachable,
1057 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});724 2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
1058 const disp = try Relocation.calcPcRelativeDisplacementX86(source_addr, adjusted_target_addr, 0);725 3 => try stream.writer().writeInt(i64, addend, .little),
1059 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);726 } else if (addend > 0) {
1060 },727 buffer.appendAssumeCapacity(.{
1061728 .r_address = r_address,
1062 .X86_64_RELOC_GOT,729 .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow),
1063 .X86_64_RELOC_GOT_LOAD,730 .r_pcrel = 0,
1064 => {731 .r_length = 2,
1065 const addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little);732 .r_extern = 0,
1066 const adjusted_target_addr = @as(u64, @intCast(@as(i64, @intCast(target_addr)) + addend));733 .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND),
1067 relocs_log.debug(" | target_addr = 0x{x}", .{adjusted_target_addr});734 });
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 }735 }
1082736
1083 mem.writeInt(i32, atom_code[rel_offset..][0..4], disp, .little);737 const r_type: macho.reloc_type_arm64 = switch (rel.type) {
1084 },738 .page => .ARM64_RELOC_PAGE21,
1085739 .pageoff => .ARM64_RELOC_PAGEOFF12,
1086 .X86_64_RELOC_SIGNED,740 .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21,
1087 .X86_64_RELOC_SIGNED_1,741 .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
1088 .X86_64_RELOC_SIGNED_2,742 .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21,
1089 .X86_64_RELOC_SIGNED_4,743 .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
1090 => {744 .branch => .ARM64_RELOC_BRANCH26,
1091 const correction: u3 = switch (rel_type) {745 .got => .ARM64_RELOC_POINTER_TO_GOT,
1092 .X86_64_RELOC_SIGNED => 0,746 .subtractor => .ARM64_RELOC_SUBTRACTOR,
1093 .X86_64_RELOC_SIGNED_1 => 1,747 .unsigned => .ARM64_RELOC_UNSIGNED,
1094 .X86_64_RELOC_SIGNED_2 => 2,748
1095 .X86_64_RELOC_SIGNED_4 => 4,749 .signed,
1096 else => unreachable,750 .signed1,
751 .signed2,
752 .signed4,
753 .got_load,
754 .tlv,
755 => unreachable,
1097 };756 };
1098 var addend = mem.readInt(i32, atom_code[rel_offset..][0..4], .little) + correction;757 buffer.appendAssumeCapacity(.{
1099758 .r_address = r_address,
1100 if (rel.r_extern == 0) {759 .r_symbolnum = r_symbolnum,
1101 const base_addr = if (target.sym_index >= object.source_address_lookup.len)760 .r_pcrel = @intFromBool(rel.meta.pcrel),
1102 @as(i64, @intCast(object.getSourceSection(@as(u8, @intCast(rel.r_symbolnum - 1))).addr))761 .r_extern = @intFromBool(r_extern),
1103 else762 .r_length = rel.meta.length,
1104 object.source_address_lookup[target.sym_index];763 .r_type = @intFromEnum(r_type),
1105 addend += @as(i32, @intCast(@as(i64, @intCast(context.base_addr)) + rel.r_address + 4 -764 });
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 },765 },
1116766 .x86_64 => {
1117 .X86_64_RELOC_UNSIGNED => {767 if (rel.meta.pcrel) {
1118 var addend = if (rel.r_length == 3)768 if (rel.tag == .local) {
1119 mem.readInt(i64, atom_code[rel_offset..][0..8], .little)769 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 {770 } else {
1136 break :blk @as(i64, @intCast(target_addr)) + addend;771 addend += 4;
1137 }772 }
1138 };773 }
1139 relocs_log.debug(" | target_addr = 0x{x}", .{result});774 switch (rel.meta.length) {
1140775 0, 1 => unreachable,
1141 if (rel.r_length == 3) {776 2 => try stream.writer().writeInt(i32, @truncate(addend), .little),
1142 mem.writeInt(u64, atom_code[rel_offset..][0..8], @as(u64, @bitCast(result)), .little);777 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 }778 }
1146779
1147 subtractor = null;780 const r_type: macho.reloc_type_x86_64 = switch (rel.type) {
781 .signed => .X86_64_RELOC_SIGNED,
782 .signed1 => .X86_64_RELOC_SIGNED_1,
783 .signed2 => .X86_64_RELOC_SIGNED_2,
784 .signed4 => .X86_64_RELOC_SIGNED_4,
785 .got_load => .X86_64_RELOC_GOT_LOAD,
786 .tlv => .X86_64_RELOC_TLV,
787 .branch => .X86_64_RELOC_BRANCH,
788 .got => .X86_64_RELOC_GOT,
789 .subtractor => .X86_64_RELOC_SUBTRACTOR,
790 .unsigned => .X86_64_RELOC_UNSIGNED,
791
792 .page,
793 .pageoff,
794 .got_load_page,
795 .got_load_pageoff,
796 .tlvp_page,
797 .tlvp_pageoff,
798 => unreachable,
799 };
800 buffer.appendAssumeCapacity(.{
801 .r_address = r_address,
802 .r_symbolnum = r_symbolnum,
803 .r_pcrel = @intFromBool(rel.meta.pcrel),
804 .r_extern = @intFromBool(r_extern),
805 .r_length = rel.meta.length,
806 .r_type = @intFromEnum(r_type),
807 });
1148 },808 },
1149809 else => unreachable,
1150 .X86_64_RELOC_SUBTRACTOR => unreachable,
1151 }810 }
1152 }811 }
1153}812}
1154813
1155pub fn getAtomCode(macho_file: *MachO, atom_index: Index) []const u8 {814pub fn format(
1156 const atom = macho_file.getAtom(atom_index);815 atom: Atom,
1157 assert(atom.getFile() != null); // Synthetic atom shouldn't need to inquire for code.816 comptime unused_fmt_string: []const u8,
1158 const object = macho_file.objects.items[atom.getFile().?];817 options: std.fmt.FormatOptions,
1159 const source_sym = object.getSourceSymbol(atom.sym_index) orelse {818 writer: anytype,
1160 // If there was no matching symbol present in the source symtab, this means819) !void {
1161 // we are dealing with either an entire section, or part of it, but also820 _ = atom;
1162 // starting at the beginning.821 _ = unused_fmt_string;
1163 const nbase = @as(u32, @intCast(object.in_symtab.?.len));822 _ = options;
1164 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));823 _ = writer;
1165 const source_sect = object.getSourceSection(sect_id);824 @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}825}
1178826
1179pub fn getAtomRelocs(macho_file: *MachO, atom_index: Index) []const macho.relocation_info {827pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) {
1180 const atom = macho_file.getAtom(atom_index);828 return .{ .data = .{
1181 assert(atom.getFile() != null); // Synthetic atom shouldn't need to unique for relocs.829 .atom = atom,
1182 const object = macho_file.objects.items[atom.getFile().?];830 .macho_file = macho_file,
1183 const cache = object.relocs_lookup[atom.sym_index];831 } };
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}832}
1200833
1201pub fn relocRequiresGot(macho_file: *MachO, rel: macho.relocation_info) bool {834const FormatContext = struct {
1202 const target = macho_file.base.comp.root_mod.resolved_target.result;835 atom: Atom,
1203 switch (target.cpu.arch) {836 macho_file: *MachO,
1204 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {837};
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}
1220838
1221pub fn relocIsTlv(macho_file: *MachO, rel: macho.relocation_info) bool {839fn format2(
1222 const target = macho_file.base.comp.root_mod.resolved_target.result;840 ctx: FormatContext,
1223 switch (target.cpu.arch) {841 comptime unused_fmt_string: []const u8,
1224 .aarch64 => switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {842 options: std.fmt.FormatOptions,
1225 .ARM64_RELOC_TLVP_LOAD_PAGE21,843 writer: anytype,
1226 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,844) !void {
1227 => return true,845 _ = options;
1228 else => return false,846 _ = unused_fmt_string;
1229 },847 const atom = ctx.atom;
1230 .x86_64 => switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {848 const macho_file = ctx.macho_file;
1231 .X86_64_RELOC_TLV => return true,849 try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{
1232 else => return false,850 atom.atom_index, atom.getName(macho_file), atom.value,
1233 },851 atom.out_n_sect, atom.alignment, atom.size,
1234 else => unreachable,852 atom.thunk_index,
853 });
854 if (!atom.flags.alive) try writer.writeAll(" : [*]");
855 if (atom.unwind_records.len > 0) {
856 try writer.writeAll(" : unwind{ ");
857 for (atom.getUnwindRecords(macho_file), atom.unwind_records.pos..) |index, i| {
858 const rec = macho_file.getUnwindRecord(index);
859 try writer.print("{d}", .{index});
860 if (!rec.alive) try writer.writeAll("([*])");
861 if (i < atom.unwind_records.pos + atom.unwind_records.len - 1) try writer.writeAll(", ");
862 }
863 try writer.writeAll(" }");
1235 }864 }
1236}865}
1237866
1238pub fn relocIsStub(macho_file: *MachO, rel: macho.relocation_info) bool {867pub 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}
1252868
1253const Atom = @This();869pub const Flags = packed struct {
870 /// Specifies whether this atom is alive or has been garbage collected.
871 alive: bool = true,
1254872
1255const std = @import("std");873 /// Specifies if the atom has been visited during garbage collection.
1256const build_options = @import("build_options");874 visited: bool = false,
1257const aarch64 = @import("../../arch/aarch64/bits.zig");875};
876
877pub const Loc = struct {
878 pos: usize = 0,
879 len: usize = 0,
880};
881
882const aarch64 = @import("../aarch64.zig");
1258const assert = std.debug.assert;883const assert = std.debug.assert;
1259const log = std.log.scoped(.link);884const bind = @import("dyld_info/bind.zig");
1260const relocs_log = std.log.scoped(.link_relocs);885const dis_x86_64 = @import("dis_x86_64");
1261const macho = std.macho;886const macho = std.macho;
1262const math = std.math;887const math = std.math;
1263const mem = std.mem;888const mem = std.mem;
1264const meta = std.meta;889const log = std.log.scoped(.link);
1265const trace = @import("../../tracy.zig").trace;890const relocs_log = std.log.scoped(.relocs);
891const std = @import("std");
892const trace = @import("../tracy.zig").trace;
1266893
1267const Allocator = mem.Allocator;894const Allocator = mem.Allocator;
1268const Arch = std.Target.Cpu.Arch;895const Atom = @This();
896const Disassembler = dis_x86_64.Disassembler;
897const File = @import("file.zig").File;
898const Instruction = dis_x86_64.Instruction;
899const Immediate = dis_x86_64.Immediate;
1269const MachO = @import("../MachO.zig");900const MachO = @import("../MachO.zig");
1270pub const Relocation = @import("Relocation.zig");901const Object = @import("Object.zig");
1271const SymbolWithLoc = MachO.SymbolWithLoc;902const Relocation = @import("Relocation.zig");
903const Symbol = @import("Symbol.zig");
904const Thunk = @import("thunks.zig").Thunk;
905const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/CodeSignature.zig+184-184
...@@ -1,175 +1,17 @@...@@ -1,175 +1,17 @@
1page_size: u16,1const CodeSignature = @This();
2code_directory: CodeDirectory,
3requirements: ?Requirements = null,
4entitlements: ?Entitlements = null,
5signature: ?Signature = null,
6
7pub fn init(page_size: u16) CodeSignature {
8 return .{
9 .page_size = page_size,
10 .code_directory = CodeDirectory.init(page_size),
11 };
12}
13
14pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
15 self.code_directory.deinit(allocator);
16 if (self.requirements) |*req| {
17 req.deinit(allocator);
18 }
19 if (self.entitlements) |*ents| {
20 ents.deinit(allocator);
21 }
22 if (self.signature) |*sig| {
23 sig.deinit(allocator);
24 }
25}
26
27pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
28 const file = try fs.cwd().openFile(path, .{});
29 defer file.close();
30 const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
31 self.entitlements = .{ .inner = inner };
32}
33
34pub const WriteOpts = struct {
35 file: fs.File,
36 exec_seg_base: u64,
37 exec_seg_limit: u64,
38 file_size: u32,
39 output_mode: std.builtin.OutputMode,
40};
41
42pub fn writeAdhocSignature(
43 self: *CodeSignature,
44 comp: *const Compilation,
45 opts: WriteOpts,
46 writer: anytype,
47) !void {
48 const gpa = comp.gpa;
49
50 var header: macho.SuperBlob = .{
51 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
52 .length = @sizeOf(macho.SuperBlob),
53 .count = 0,
54 };
55
56 var blobs = std.ArrayList(Blob).init(gpa);
57 defer blobs.deinit();
58
59 self.code_directory.inner.execSegBase = opts.exec_seg_base;
60 self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
61 self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
62 self.code_directory.inner.codeLimit = opts.file_size;
63
64 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
65
66 try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
67 self.code_directory.code_slots.items.len = total_pages;
68 self.code_directory.inner.nCodeSlots = total_pages;
69
70 // Calculate hash for each page (in file) and write it to the buffer
71 var hasher = Hasher(Sha256){ .allocator = gpa, .thread_pool = comp.thread_pool };
72 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
73 .chunk_size = self.page_size,
74 .max_file_size = opts.file_size,
75 });
76
77 try blobs.append(.{ .code_directory = &self.code_directory });
78 header.length += @sizeOf(macho.BlobIndex);
79 header.count += 1;
80
81 var hash: [hash_size]u8 = undefined;
82
83 if (self.requirements) |*req| {
84 var buf = std.ArrayList(u8).init(gpa);
85 defer buf.deinit();
86 try req.write(buf.writer());
87 Sha256.hash(buf.items, &hash, .{});
88 self.code_directory.addSpecialHash(req.slotType(), hash);
89
90 try blobs.append(.{ .requirements = req });
91 header.count += 1;
92 header.length += @sizeOf(macho.BlobIndex) + req.size();
93 }
94
95 if (self.entitlements) |*ents| {
96 var buf = std.ArrayList(u8).init(gpa);
97 defer buf.deinit();
98 try ents.write(buf.writer());
99 Sha256.hash(buf.items, &hash, .{});
100 self.code_directory.addSpecialHash(ents.slotType(), hash);
101
102 try blobs.append(.{ .entitlements = ents });
103 header.count += 1;
104 header.length += @sizeOf(macho.BlobIndex) + ents.size();
105 }
106
107 if (self.signature) |*sig| {
108 try blobs.append(.{ .signature = sig });
109 header.count += 1;
110 header.length += @sizeOf(macho.BlobIndex) + sig.size();
111 }
112
113 self.code_directory.inner.hashOffset =
114 @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
115 self.code_directory.inner.length = self.code_directory.size();
116 header.length += self.code_directory.size();
117
118 try writer.writeInt(u32, header.magic, .big);
119 try writer.writeInt(u32, header.length, .big);
120 try writer.writeInt(u32, header.count, .big);
121
122 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
123 for (blobs.items) |blob| {
124 try writer.writeInt(u32, blob.slotType(), .big);
125 try writer.writeInt(u32, offset, .big);
126 offset += blob.size();
127 }
128
129 for (blobs.items) |blob| {
130 try blob.write(writer);
131 }
132}
133
134pub fn size(self: CodeSignature) u32 {
135 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
136 if (self.requirements) |req| {
137 ssize += @sizeOf(macho.BlobIndex) + req.size();
138 }
139 if (self.entitlements) |ent| {
140 ssize += @sizeOf(macho.BlobIndex) + ent.size();
141 }
142 if (self.signature) |sig| {
143 ssize += @sizeOf(macho.BlobIndex) + sig.size();
144 }
145 return ssize;
146}
147
148pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
149 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
150 // Approx code slots
151 const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
152 ssize += total_pages * hash_size;
153 var n_special_slots: u32 = 0;
154 if (self.requirements) |req| {
155 ssize += @sizeOf(macho.BlobIndex) + req.size();
156 n_special_slots = @max(n_special_slots, req.slotType());
157 }
158 if (self.entitlements) |ent| {
159 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
160 n_special_slots = @max(n_special_slots, ent.slotType());
161 }
162 if (self.signature) |sig| {
163 ssize += @sizeOf(macho.BlobIndex) + sig.size();
164 }
165 ssize += n_special_slots * hash_size;
166 return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
167}
1682
169pub fn clear(self: *CodeSignature, allocator: Allocator) void {3const std = @import("std");
170 self.code_directory.deinit(allocator);4const assert = std.debug.assert;
171 self.code_directory = CodeDirectory.init(self.page_size);5const fs = std.fs;
172}6const log = std.log.scoped(.link);
7const macho = std.macho;
8const mem = std.mem;
9const testing = std.testing;
10const Allocator = mem.Allocator;
11const Hasher = @import("hasher.zig").ParallelHasher;
12const MachO = @import("../MachO.zig");
13const Sha256 = std.crypto.hash.sha2.Sha256;
14const Zld = @import("../Zld.zig");
17315
174const hash_size = Sha256.digest_length;16const hash_size = Sha256.digest_length;
17517
...@@ -257,7 +99,7 @@ const CodeDirectory = struct {...@@ -257,7 +99,7 @@ const CodeDirectory = struct {
257 fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {99 fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
258 assert(index > 0);100 assert(index > 0);
259 self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);101 self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);
260 self.special_slots[index - 1] = hash;102 @memcpy(&self.special_slots[index - 1], &hash);
261 }103 }
262104
263 fn slotType(self: CodeDirectory) u32 {105 fn slotType(self: CodeDirectory) u32 {
...@@ -376,17 +218,175 @@ const Signature = struct {...@@ -376,17 +218,175 @@ const Signature = struct {
376 }218 }
377};219};
378220
379const CodeSignature = @This();221page_size: u16,
222code_directory: CodeDirectory,
223requirements: ?Requirements = null,
224entitlements: ?Entitlements = null,
225signature: ?Signature = null,
380226
381const std = @import("std");227pub fn init(page_size: u16) CodeSignature {
382const assert = std.debug.assert;228 return .{
383const fs = std.fs;229 .page_size = page_size,
384const log = std.log.scoped(.link);230 .code_directory = CodeDirectory.init(page_size),
385const macho = std.macho;231 };
386const mem = std.mem;232}
387const testing = std.testing;
388233
389const Allocator = mem.Allocator;234pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
390const Compilation = @import("../../Compilation.zig");235 self.code_directory.deinit(allocator);
391const Hasher = @import("hasher.zig").ParallelHasher;236 if (self.requirements) |*req| {
392const Sha256 = std.crypto.hash.sha2.Sha256;237 req.deinit(allocator);
238 }
239 if (self.entitlements) |*ents| {
240 ents.deinit(allocator);
241 }
242 if (self.signature) |*sig| {
243 sig.deinit(allocator);
244 }
245}
246
247pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
248 const file = try fs.cwd().openFile(path, .{});
249 defer file.close();
250 const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
251 self.entitlements = .{ .inner = inner };
252}
253
254pub const WriteOpts = struct {
255 file: fs.File,
256 exec_seg_base: u64,
257 exec_seg_limit: u64,
258 file_size: u32,
259 dylib: bool,
260};
261
262pub fn writeAdhocSignature(
263 self: *CodeSignature,
264 macho_file: *MachO,
265 opts: WriteOpts,
266 writer: anytype,
267) !void {
268 const allocator = macho_file.base.allocator;
269
270 var header: macho.SuperBlob = .{
271 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
272 .length = @sizeOf(macho.SuperBlob),
273 .count = 0,
274 };
275
276 var blobs = std.ArrayList(Blob).init(allocator);
277 defer blobs.deinit();
278
279 self.code_directory.inner.execSegBase = opts.exec_seg_base;
280 self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
281 self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0;
282 self.code_directory.inner.codeLimit = opts.file_size;
283
284 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
285
286 try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
287 self.code_directory.code_slots.items.len = total_pages;
288 self.code_directory.inner.nCodeSlots = total_pages;
289
290 // Calculate hash for each page (in file) and write it to the buffer
291 var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.thread_pool };
292 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
293 .chunk_size = self.page_size,
294 .max_file_size = opts.file_size,
295 });
296
297 try blobs.append(.{ .code_directory = &self.code_directory });
298 header.length += @sizeOf(macho.BlobIndex);
299 header.count += 1;
300
301 var hash: [hash_size]u8 = undefined;
302
303 if (self.requirements) |*req| {
304 var buf = std.ArrayList(u8).init(allocator);
305 defer buf.deinit();
306 try req.write(buf.writer());
307 Sha256.hash(buf.items, &hash, .{});
308 self.code_directory.addSpecialHash(req.slotType(), hash);
309
310 try blobs.append(.{ .requirements = req });
311 header.count += 1;
312 header.length += @sizeOf(macho.BlobIndex) + req.size();
313 }
314
315 if (self.entitlements) |*ents| {
316 var buf = std.ArrayList(u8).init(allocator);
317 defer buf.deinit();
318 try ents.write(buf.writer());
319 Sha256.hash(buf.items, &hash, .{});
320 self.code_directory.addSpecialHash(ents.slotType(), hash);
321
322 try blobs.append(.{ .entitlements = ents });
323 header.count += 1;
324 header.length += @sizeOf(macho.BlobIndex) + ents.size();
325 }
326
327 if (self.signature) |*sig| {
328 try blobs.append(.{ .signature = sig });
329 header.count += 1;
330 header.length += @sizeOf(macho.BlobIndex) + sig.size();
331 }
332
333 self.code_directory.inner.hashOffset =
334 @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
335 self.code_directory.inner.length = self.code_directory.size();
336 header.length += self.code_directory.size();
337
338 try writer.writeInt(u32, header.magic, .big);
339 try writer.writeInt(u32, header.length, .big);
340 try writer.writeInt(u32, header.count, .big);
341
342 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
343 for (blobs.items) |blob| {
344 try writer.writeInt(u32, blob.slotType(), .big);
345 try writer.writeInt(u32, offset, .big);
346 offset += blob.size();
347 }
348
349 for (blobs.items) |blob| {
350 try blob.write(writer);
351 }
352}
353
354pub fn size(self: CodeSignature) u32 {
355 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
356 if (self.requirements) |req| {
357 ssize += @sizeOf(macho.BlobIndex) + req.size();
358 }
359 if (self.entitlements) |ent| {
360 ssize += @sizeOf(macho.BlobIndex) + ent.size();
361 }
362 if (self.signature) |sig| {
363 ssize += @sizeOf(macho.BlobIndex) + sig.size();
364 }
365 return ssize;
366}
367
368pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
369 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
370 // Approx code slots
371 const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
372 ssize += total_pages * hash_size;
373 var n_special_slots: u32 = 0;
374 if (self.requirements) |req| {
375 ssize += @sizeOf(macho.BlobIndex) + req.size();
376 n_special_slots = @max(n_special_slots, req.slotType());
377 }
378 if (self.entitlements) |ent| {
379 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
380 n_special_slots = @max(n_special_slots, ent.slotType());
381 }
382 if (self.signature) |sig| {
383 ssize += @sizeOf(macho.BlobIndex) + sig.size();
384 }
385 ssize += n_special_slots * hash_size;
386 return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
387}
388
389pub fn clear(self: *CodeSignature, allocator: Allocator) void {
390 self.code_directory.deinit(allocator);
391 self.code_directory = CodeDirectory.init(self.page_size);
392}
src/link/MachO/DwarfInfo.zig+351-387
...@@ -2,377 +2,175 @@ debug_info: []const u8,...@@ -2,377 +2,175 @@ debug_info: []const u8,
2debug_abbrev: []const u8,2debug_abbrev: []const u8,
3debug_str: []const u8,3debug_str: []const u8,
44
5pub fn getCompileUnitIterator(self: DwarfInfo) CompileUnitIterator {5/// Abbreviation table indexed by offset in the .debug_abbrev bytestream
6 return .{ .ctx = self };6abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{},
7/// List of compile units as they appear in the .debug_info bytestream
8compile_units: std.ArrayListUnmanaged(CompileUnit) = .{},
9
10pub fn init(dw: *DwarfInfo, allocator: Allocator) !void {
11 try dw.parseAbbrevTables(allocator);
12 try dw.parseCompileUnits(allocator);
7}13}
814
9const CompileUnitIterator = struct {15pub 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| {
1218 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);
3421}
35pub fn genSubprogramLookupByName(
36 self: DwarfInfo,
37 compile_unit: CompileUnit,
38 abbrev_lookup: AbbrevLookupTable,
39 lookup: *SubprogramLookupByName,
40) !void {
41 var abbrev_it = compile_unit.getAbbrevEntryIterator(self);
42 while (try abbrev_it.next(abbrev_lookup)) |entry| switch (entry.tag) {
43 dwarf.TAG.subprogram => {
44 var attr_it = entry.getAttributeIterator(self, compile_unit.cuh);
45
46 var name: ?[]const u8 = null;
47 var low_pc: ?u64 = null;
48 var high_pc: ?u64 = null;
49
50 while (try attr_it.next()) |attr| switch (attr.name) {
51 dwarf.AT.name => if (attr.getString(self, compile_unit.cuh)) |str| {
52 name = str;
53 },
54 dwarf.AT.low_pc => {
55 if (attr.getAddr(self, compile_unit.cuh)) |addr| {
56 low_pc = addr;
57 }
58 if (try attr.getConstant(self)) |constant| {
59 low_pc = @as(u64, @intCast(constant));
60 }
61 },
62 dwarf.AT.high_pc => {
63 if (attr.getAddr(self, compile_unit.cuh)) |addr| {
64 high_pc = addr;
65 }
66 if (try attr.getConstant(self)) |constant| {
67 high_pc = @as(u64, @intCast(constant));
68 }
69 },
70 else => {},
71 };
72
73 if (name == null or low_pc == null or high_pc == null) continue;
7422
75 try lookup.putNoClobber(name.?, .{ .addr = low_pc.?, .size = high_pc.? });23fn getString(dw: DwarfInfo, off: u64) [: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}
8027
81pub fn genAbbrevLookupByKind(self: DwarfInfo, off: usize, lookup: *AbbrevLookupTable) !void {28fn 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();
8636
87 while (true) {37 while (true) {
88 const kind = try leb.readULEB128(u64, reader);38 if (creader.bytes_read >= debug_abbrev.len) break;
8939
90 if (kind == 0) break;40 try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1);
9141 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); // TAG43 const table = table_gop.value_ptr;
94 _ = try reader.readByte(); // CHILDREN44 table.* = .{};
9545
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;
9949
100 if (name == 0 and form == 0) break;50 try table.decls.ensureUnusedCapacity(allocator, 1);
101 }51 const decl_gop = table.decls.getOrPutAssumeCapacity(code);
10252 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;
10454 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;
11161
112pub 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);
11565 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.* = .{
12272 .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
157const AbbrevEntryIterator = struct {
158 cu: CompileUnit,
159 ctx: DwarfInfo,
160 pos: usize = 0,
161
162 pub fn next(self: *AbbrevEntryIterator, lookup: AbbrevLookupTable) !?AbbrevEntry {
163 if (self.pos + self.cu.debug_info_off >= self.ctx.debug_info.len) return null;
164
165 const debug_info = self.ctx.debug_info[self.pos + self.cu.debug_info_off ..];
166 var stream = std.io.fixedBufferStream(debug_info);
167 var creader = std.io.countingReader(stream.reader());
168 const reader = creader.reader();
16979
170 const kind = try leb.readULEB128(u64, reader);80fn 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();
17283
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();
17688
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};
19891
199pub 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
235pub const Attribute = struct {
236 name: u64,
237 form: u64,
238 debug_info_off: usize,
239 debug_info_len: usize,
240
241 inline fn getDebugInfo(self: Attribute, ctx: DwarfInfo) []const u8 {
242 return ctx.debug_info[self.debug_info_off..][0..self.debug_info_len];
243 }
244
245 pub fn getString(self: Attribute, ctx: DwarfInfo, cuh: CompileUnit.Header) ?[]const u8 {
246 const debug_info = self.getDebugInfo(ctx);
24797
248 switch (self.form) {98 var length: u64 = try reader.readInt(u32, .little);
249 dwarf.FORM.string => {99 const is_64bit = length == 0xffffffff;
250 return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_info.ptr)), 0);100 if (is_64bit) {
251 },101 length = try reader.readInt(u64, .little);
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}
262113
263 pub fn getConstant(self: Attribute, ctx: DwarfInfo) !?i128 {114fn 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,
267118 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| {
278129 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
292const AttributeIterator = struct {
293 entry: AbbrevEntry,
294 ctx: DwarfInfo,
295 cuh: CompileUnit.Header,
296 debug_abbrev_pos: usize = 0,
297 debug_info_pos: usize = 0,
298133
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;
302136
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 }
306141
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;
309144 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 };
324145
325 self.debug_info_pos += len;146 for (decl.attrs.values()) |attr| {
147 const start = creader.bytes_read;
148 try advanceByFormSize(cu, attr.form, creader);
149 const end = creader.bytes_read;
150 cu.diePtr(die).values.appendAssumeCapacity(data[start..end]);
151 }
326152
327 return attr;153 if (decl.children) {
154 // Open scope
155 try dw.parseDie(allocator, cu, table, die, creader);
156 }
328 }157 }
329};
330
331fn getAbbrevEntry(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, di_len: usize) !AbbrevEntry {
332 const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];
333 var stream = std.io.fixedBufferStream(debug_abbrev);
334 var creader = std.io.countingReader(stream.reader());
335 const reader = creader.reader();
336
337 const tag = try leb.readULEB128(u64, reader);
338 const children = switch (tag) {
339 std.dwarf.TAG.const_type,
340 std.dwarf.TAG.packed_type,
341 std.dwarf.TAG.pointer_type,
342 std.dwarf.TAG.reference_type,
343 std.dwarf.TAG.restrict_type,
344 std.dwarf.TAG.rvalue_reference_type,
345 std.dwarf.TAG.shared_type,
346 std.dwarf.TAG.volatile_type,
347 => if (creader.bytes_read == da_len) std.dwarf.CHILDREN.no else try reader.readByte(),
348 else => try reader.readByte(),
349 };
350
351 const pos = math.cast(usize, creader.bytes_read) orelse return error.Overflow;
352
353 return AbbrevEntry{
354 .tag = tag,
355 .children = children,
356 .debug_abbrev_off = pos + da_off,
357 .debug_abbrev_len = da_len - pos,
358 .debug_info_off = di_off,
359 .debug_info_len = di_len,
360 };
361}158}
362159
363fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Header) !usize {160fn 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();
368163
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 },
374172
375 dwarf.FORM.addr => return cuh.address_size,173 dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}),
376174
377 dwarf.FORM.block1,175 dwarf.FORM.block1,
378 dwarf.FORM.block2,176 dwarf.FORM.block2,
...@@ -386,119 +184,285 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head...@@ -386,119 +184,285 @@ fn findFormSize(self: DwarfInfo, form: u64, di_off: usize, cuh: CompileUnit.Head
386 dwarf.FORM.block => try leb.readULEB128(u64, reader),184 dwarf.FORM.block => try leb.readULEB128(u64, reader),
387 else => unreachable,185 else => unreachable,
388 };186 };
389 var i: u64 = 0;187 for (0..len) |_| {
390 while (i < len) : (i += 1) {
391 _ = try reader.readByte();188 _ = try reader.readByte();
392 }189 }
393 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
394 },190 },
395191
396 dwarf.FORM.exprloc => {192 dwarf.FORM.exprloc => {
397 const expr_len = try leb.readULEB128(u64, reader);193 const len = try leb.readULEB128(u64, reader);
398 var i: u64 = 0;194 for (0..len) |_| {
399 while (i < expr_len) : (i += 1) {
400 _ = try reader.readByte();195 _ = try reader.readByte();
401 }196 }
402 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
403 },197 },
404 dwarf.FORM.flag_present => return 0,198 dwarf.FORM.flag_present => {},
405199
406 dwarf.FORM.data1,200 dwarf.FORM.data1,
407 dwarf.FORM.ref1,201 dwarf.FORM.ref1,
408 dwarf.FORM.flag,202 dwarf.FORM.flag,
409 => return @sizeOf(u8),203 => try reader.skipBytes(1, .{}),
410204
411 dwarf.FORM.data2,205 dwarf.FORM.data2,
412 dwarf.FORM.ref2,206 dwarf.FORM.ref2,
413 => return @sizeOf(u16),207 => try reader.skipBytes(2, .{}),
414208
415 dwarf.FORM.data4,209 dwarf.FORM.data4,
416 dwarf.FORM.ref4,210 dwarf.FORM.ref4,
417 => return @sizeOf(u32),211 => try reader.skipBytes(4, .{}),
418212
419 dwarf.FORM.data8,213 dwarf.FORM.data8,
420 dwarf.FORM.ref8,214 dwarf.FORM.ref8,
421 dwarf.FORM.ref_sig8,215 dwarf.FORM.ref_sig8,
422 => return @sizeOf(u64),216 => try reader.skipBytes(8, .{}),
423217
424 dwarf.FORM.udata,218 dwarf.FORM.udata,
425 dwarf.FORM.ref_udata,219 dwarf.FORM.ref_udata,
426 => {220 => {
427 _ = try leb.readULEB128(u64, reader);221 _ = try leb.readULEB128(u64, reader);
428 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
429 },222 },
430223
431 dwarf.FORM.sdata => {224 dwarf.FORM.sdata => {
432 _ = try leb.readILEB128(i64, reader);225 _ = try leb.readILEB128(i64, reader);
433 return math.cast(usize, creader.bytes_read) orelse error.Overflow;
434 },226 },
435227
436 dwarf.FORM.string => {228 dwarf.FORM.string => {
437 var count: usize = 0;
438 while (true) {229 while (true) {
439 const byte = try reader.readByte();230 const byte = try reader.readByte();
440 count += 1;
441 if (byte == 0x0) break;231 if (byte == 0x0) break;
442 }232 }
443 return count;
444 },233 },
445234
446 else => {235 else => {
447 // TODO figure out how to handle this236 // TODO better errors
448 log.debug("unhandled DW_FORM_* value with identifier {x}", .{form});237 log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
449 return error.UnhandledDwFormValue;238 return error.UnhandledDwFormValue;
450 },239 },
451 }240 }
452}241}
453242
454fn findAbbrevEntrySize(self: DwarfInfo, da_off: usize, da_len: usize, di_off: usize, cuh: CompileUnit.Header) !usize {243fn readOffset(format: Format, reader: anytype) !u64 {
455 const debug_abbrev = self.debug_abbrev[da_off..][0..da_len];244 return switch (format) {
456 var stream = std.io.fixedBufferStream(debug_abbrev);245 .dwarf32 => try reader.readInt(u32, .little),
457 var creader = std.io.countingReader(stream.reader());246 .dwarf64 => try reader.readInt(u64, .little),
458 const reader = creader.reader();247 };
248}
459249
460 const tag = try leb.readULEB128(u64, reader);250pub const AbbrevTable = struct {
461 switch (tag) {251 /// Table of abbreviation declarations indexed by their assigned code value
462 std.dwarf.TAG.const_type,252 decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{},
463 std.dwarf.TAG.packed_type,253
464 std.dwarf.TAG.pointer_type,254 pub fn deinit(table: *AbbrevTable, gpa: Allocator) void {
465 std.dwarf.TAG.reference_type,255 for (table.decls.values()) |*decl| {
466 std.dwarf.TAG.restrict_type,256 decl.deinit(gpa);
467 std.dwarf.TAG.rvalue_reference_type,257 }
468 std.dwarf.TAG.shared_type,258 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 }259 }
260};
475261
476 var len: usize = 0;262pub const Decl = struct {
477 while (creader.bytes_read < debug_abbrev.len) {263 code: Code,
478 _ = try leb.readULEB128(u64, reader);264 tag: Tag,
479 const form = try leb.readULEB128(u64, reader);265 children: bool,
480 const form_len = try self.findFormSize(form, di_off + len, cuh);266
481 len += form_len;267 /// Table of attributes indexed by their AT value
268 attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{},
269
270 pub fn deinit(decl: *Decl, gpa: Allocator) void {
271 decl.attrs.deinit(gpa);
482 }272 }
273};
483274
484 return len;275pub const Attr = struct {
485}276 at: At,
277 form: Form,
278};
486279
487fn getString(self: DwarfInfo, off: u64) []const u8 {280pub const At = u64;
488 assert(off < self.debug_str.len);281pub const Code = u64;
489 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.debug_str.ptr + @as(usize, @intCast(off)))), 0);282pub const Form = u64;
490}283pub const Tag = u64;
284
285pub const CompileUnitHeader = struct {
286 format: Format,
287 length: u64,
288 version: u16,
289 debug_abbrev_offset: u64,
290 address_size: u8,
291};
491292
492const DwarfInfo = @This();293pub const CompileUnit = struct {
294 header: CompileUnitHeader,
295 pos: usize,
296 dies: std.ArrayListUnmanaged(Die) = .{},
297 children: std.ArrayListUnmanaged(Die.Index) = .{},
298
299 pub fn deinit(cu: *CompileUnit, gpa: Allocator) void {
300 for (cu.dies.items) |*die| {
301 die.deinit(gpa);
302 }
303 cu.dies.deinit(gpa);
304 cu.children.deinit(gpa);
305 }
306
307 pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index {
308 const index = @as(Die.Index, @intCast(cu.dies.items.len));
309 _ = try cu.dies.addOne(gpa);
310 return index;
311 }
312
313 pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die {
314 return &cu.dies.items[index];
315 }
316
317 pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 {
318 assert(cu.dies.items.len > 0);
319 const die = cu.dies.items[0];
320 const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null;
321 return res.getString(cu.header.format, ctx);
322 }
323
324 pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) ?[:0]const u8 {
325 assert(cu.dies.items.len > 0);
326 const die = cu.dies.items[0];
327 const res = die.find(dwarf.AT.name, cu, ctx) orelse return null;
328 return res.getString(cu.header.format, ctx);
329 }
330
331 pub fn nextCompileUnitOffset(cu: CompileUnit) u64 {
332 return cu.pos + switch (cu.header.format) {
333 .dwarf32 => @as(u64, 4),
334 .dwarf64 => 12,
335 } + cu.header.length;
336 }
337};
338
339pub const Die = struct {
340 code: Code,
341 values: std.ArrayListUnmanaged([]const u8) = .{},
342 children: std.ArrayListUnmanaged(Die.Index) = .{},
343
344 pub fn deinit(die: *Die, gpa: Allocator) void {
345 die.values.deinit(gpa);
346 die.children.deinit(gpa);
347 }
348
349 pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue {
350 const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null;
351 const decl = table.decls.get(die.code).?;
352 const index = decl.attrs.getIndex(at) orelse return null;
353 const attr = decl.attrs.values()[index];
354 const value = die.values.items[index];
355 return .{ .attr = attr, .bytes = value };
356 }
357
358 pub const Index = u32;
359};
360
361pub const DieValue = struct {
362 attr: Attr,
363 bytes: []const u8,
364
365 pub fn getFlag(value: DieValue) ?bool {
366 return switch (value.attr.form) {
367 dwarf.FORM.flag => value.bytes[0] == 1,
368 dwarf.FORM.flag_present => true,
369 else => null,
370 };
371 }
372
373 pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) ?[:0]const u8 {
374 switch (value.attr.form) {
375 dwarf.FORM.string => {
376 return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0);
377 },
378 dwarf.FORM.strp => {
379 const off = switch (format) {
380 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
381 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
382 };
383 return ctx.getString(off);
384 },
385 else => return null,
386 }
387 }
388
389 pub fn getSecOffset(value: DieValue, format: Format) ?u64 {
390 return switch (value.attr.form) {
391 dwarf.FORM.sec_offset => switch (format) {
392 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
393 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
394 },
395 else => null,
396 };
397 }
398
399 pub fn getConstant(value: DieValue) !?i128 {
400 var stream = std.io.fixedBufferStream(value.bytes);
401 const reader = stream.reader();
402 return switch (value.attr.form) {
403 dwarf.FORM.data1 => value.bytes[0],
404 dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little),
405 dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little),
406 dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little),
407 dwarf.FORM.udata => try leb.readULEB128(u64, reader),
408 dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
409 else => null,
410 };
411 }
412
413 pub fn getReference(value: DieValue, format: Format) !?u64 {
414 var stream = std.io.fixedBufferStream(value.bytes);
415 const reader = stream.reader();
416 return switch (value.attr.form) {
417 dwarf.FORM.ref1 => value.bytes[0],
418 dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little),
419 dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little),
420 dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little),
421 dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader),
422 dwarf.FORM.ref_addr => switch (format) {
423 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
424 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
425 },
426 else => null,
427 };
428 }
429
430 pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 {
431 return switch (value.attr.form) {
432 dwarf.FORM.addr => switch (header.address_size) {
433 1 => value.bytes[0],
434 2 => mem.readInt(u16, value.bytes[0..2], .little),
435 4 => mem.readInt(u32, value.bytes[0..4], .little),
436 8 => mem.readInt(u64, value.bytes[0..8], .little),
437 else => null,
438 },
439 else => null,
440 };
441 }
442
443 pub fn getExprloc(value: DieValue) !?[]const u8 {
444 if (value.attr.form != dwarf.FORM.exprloc) return null;
445 var stream = std.io.fixedBufferStream(value.bytes);
446 var creader = std.io.countingReader(stream.reader());
447 const reader = creader.reader();
448 const expr_len = try leb.readULEB128(u64, reader);
449 return value.bytes[creader.bytes_read..][0..expr_len];
450 }
451};
452
453pub const Format = enum {
454 dwarf32,
455 dwarf64,
456};
493457
494const std = @import("std");
495const assert = std.debug.assert;458const assert = std.debug.assert;
496const dwarf = std.dwarf;459const dwarf = std.dwarf;
497const leb = std.leb;460const leb = std.leb;
498const log = std.log.scoped(.macho);461const log = std.log.scoped(.link);
499const math = std.math;
500const mem = std.mem;462const mem = std.mem;
463const std = @import("std");
464const trace = @import("../tracy.zig").trace;
501465
502const Allocator = mem.Allocator;466const Allocator = mem.Allocator;
503pub const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize });467const DwarfInfo = @This();
504pub const SubprogramLookupByName = std.StringHashMap(struct { addr: u64, size: u64 });468const MachO = @import("../MachO.zig");
src/link/MachO/Dylib.zig+593-321
...@@ -1,340 +1,207 @@...@@ -1,340 +1,207 @@
1path: []const u8,1path: []const u8,
2id: ?Id = null,2data: []const u8,
3weak: bool = false,3index: File.Index,
4/// Header is only set if Dylib is parsed directly from a binary and not a stub file.
5header: ?macho.mach_header_64 = null,
6
7/// Parsed symbol table represented as hash map of symbols'
8/// names. We can and should defer creating *Symbols until
9/// a symbol is referenced by an object file.
10///
11/// The value for each parsed symbol represents whether the
12/// symbol is defined as a weak symbol or strong.
13/// TODO when the referenced symbol is weak, ld64 marks it as
14/// N_REF_TO_WEAK but need to investigate if there's more to it
15/// such as weak binding entry or simply weak. For now, we generate
16/// standard bind or lazy bind.
17symbols: std.StringArrayHashMapUnmanaged(bool) = .{},
18
19pub const Id = struct {
20 name: []const u8,
21 timestamp: u32,
22 current_version: u32,
23 compatibility_version: u32,
24
25 pub fn default(allocator: Allocator, name: []const u8) !Id {
26 return Id{
27 .name = try allocator.dupe(u8, name),
28 .timestamp = 2,
29 .current_version = 0x10000,
30 .compatibility_version = 0x10000,
31 };
32 }
33
34 pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
35 return Id{
36 .name = try allocator.dupe(u8, name),
37 .timestamp = lc.dylib.timestamp,
38 .current_version = lc.dylib.current_version,
39 .compatibility_version = lc.dylib.compatibility_version,
40 };
41 }
42
43 pub fn deinit(id: Id, allocator: Allocator) void {
44 allocator.free(id.name);
45 }
46
47 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
48
49 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
50 id.current_version = try parseVersion(version);
51 }
52
53 pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
54 id.compatibility_version = try parseVersion(version);
55 }
56
57 fn parseVersion(version: anytype) ParseError!u32 {
58 const string = blk: {
59 switch (version) {
60 .int => |int| {
61 var out: u32 = 0;
62 const major = math.cast(u16, int) orelse return error.Overflow;
63 out += @as(u32, @intCast(major)) << 16;
64 return out;
65 },
66 .float => |float| {
67 var buf: [256]u8 = undefined;
68 break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
69 },
70 .string => |string| {
71 break :blk string;
72 },
73 }
74 };
75
76 var out: u32 = 0;
77 var values: [3][]const u8 = undefined;
784
79 var split = mem.splitScalar(u8, string, '.');5header: ?macho.mach_header_64 = null,
80 var count: u4 = 0;6exports: std.MultiArrayList(Export) = .{},
81 while (split.next()) |value| {7strtab: std.ArrayListUnmanaged(u8) = .{},
82 if (count > 2) {8id: ?Id = null,
83 log.debug("malformed version field: {s}", .{string});9ordinal: u16 = 0,
84 return 0x10000;
85 }
86 values[count] = value;
87 count += 1;
88 }
8910
90 if (count > 2) {11symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
91 out += try fmt.parseInt(u8, values[2], 10);12dependents: std.ArrayListUnmanaged(Id) = .{},
92 }13rpaths: std.StringArrayHashMapUnmanaged(void) = .{},
93 if (count > 1) {14umbrella: File.Index = 0,
94 out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;15platform: ?MachO.Options.Platform = null,
95 }
96 out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
9716
98 return out;17needed: bool,
99 }18weak: bool,
100};19reexport: bool,
20explicit: bool,
21hoisted: bool = true,
22referenced: bool = false,
10123
102pub fn isDylib(file: std.fs.File, fat_offset: u64) bool {24output_symtab_ctx: MachO.SymtabCtx = .{},
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}
10825
109pub fn deinit(self: *Dylib, allocator: Allocator) void {26pub fn deinit(self: *Dylib, allocator: Allocator) void {
110 allocator.free(self.path);27 self.exports.deinit(allocator);
111 for (self.symbols.keys()) |key| {28 self.strtab.deinit(allocator);
112 allocator.free(key);29 if (self.id) |*id| id.deinit(allocator);
113 }
114 self.symbols.deinit(allocator);30 self.symbols.deinit(allocator);
115 if (self.id) |*id| {31 for (self.dependents.items) |*id| {
116 id.deinit(allocator);32 id.deinit(allocator);
117 }33 }
34 self.dependents.deinit(allocator);
35 self.rpaths.deinit(allocator);
118}36}
11937
120pub fn parseFromBinary(38pub fn parse(self: *Dylib, macho_file: *MachO) !void {
121 self: *Dylib,39 const tracy = trace(@src());
122 allocator: Allocator,40 defer tracy.end();
123 dylib_id: u16,41
124 dependent_libs: anytype,42 const gpa = macho_file.base.allocator;
125 name: []const u8,43 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();44 const reader = stream.reader();
13045
131 log.debug("parsing shared library '{s}'", .{name});46 log.debug("parsing dylib from binary", .{});
13247
133 self.header = try reader.readStruct(macho.mach_header_64);48 self.header = try reader.readStruct(macho.mach_header_64);
13449
135 const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;50 const lc_id = self.getLoadCommand(.ID_DYLIB) orelse {
136 var it = LoadCommandIterator{51 macho_file.base.fatal("{s}: missing LC_ID_DYLIB load command", .{self.path});
137 .ncmds = self.header.?.ncmds,52 return error.ParseFailed;
138 .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
139 };53 };
140 while (it.next()) |cmd| {54 self.id = try Id.fromLoadCommand(gpa, lc_id.cast(macho.dylib_command).?, lc_id.getDylibPathName());
141 switch (cmd.cmd()) {
142 .SYMTAB => {
143 const symtab_cmd = cmd.cast(macho.symtab_command).?;
144 const symtab = @as(
145 [*]const macho.nlist_64,
146 // Alignment is guaranteed as a dylib is a final linked image and has to have sections
147 // properly aligned in order to be correctly loaded by the loader.
148 @ptrCast(@alignCast(&data[symtab_cmd.symoff])),
149 )[0..symtab_cmd.nsyms];
150 const strtab = data[symtab_cmd.stroff..][0..symtab_cmd.strsize];
151
152 for (symtab) |sym| {
153 const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
154 if (!add_to_symtab) continue;
155
156 const sym_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + sym.n_strx)), 0);
157 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
158 }
159 },
160 .ID_DYLIB => {
161 self.id = try Id.fromLoadCommand(
162 allocator,
163 cmd.cast(macho.dylib_command).?,
164 cmd.getDylibPathName(),
165 );
166 },
167 .REEXPORT_DYLIB => {
168 if (should_lookup_reexports) {
169 // Parse install_name to dependent dylib.
170 const id = try Id.fromLoadCommand(
171 allocator,
172 cmd.cast(macho.dylib_command).?,
173 cmd.getDylibPathName(),
174 );
175 try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id });
176 }
177 },
178 else => {},
179 }
180 }
181}
18255
183/// Returns Platform composed from the first encountered build version type load command:
184/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.
185pub fn getPlatform(self: Dylib, data: []align(@alignOf(u64)) const u8) ?Platform {
186 var it = LoadCommandIterator{56 var it = LoadCommandIterator{
187 .ncmds = self.header.?.ncmds,57 .ncmds = self.header.?.ncmds,
188 .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],58 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
189 };59 };
190 while (it.next()) |cmd| {60 while (it.next()) |cmd| switch (cmd.cmd()) {
191 switch (cmd.cmd()) {61 .REEXPORT_DYLIB => if (self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) {
192 .BUILD_VERSION,62 const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName());
193 .VERSION_MIN_MACOSX,63 try self.dependents.append(gpa, id);
194 .VERSION_MIN_IPHONEOS,64 },
195 .VERSION_MIN_TVOS,65 .DYLD_INFO_ONLY => {
196 .VERSION_MIN_WATCHOS,66 const dyld_cmd = cmd.cast(macho.dyld_info_command).?;
197 => return Platform.fromLoadCommand(cmd),67 const data = self.data[dyld_cmd.export_off..][0..dyld_cmd.export_size];
198 else => {},68 try self.parseTrie(data, macho_file);
199 }69 },
200 } else return null;70 .DYLD_EXPORTS_TRIE => {
201}71 const ld_cmd = cmd.cast(macho.linkedit_data_command).?;
20272 const data = self.data[ld_cmd.dataoff..][0..ld_cmd.datasize];
203fn addObjCClassSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {73 try self.parseTrie(data, macho_file);
204 const expanded = &[_][]const u8{74 },
205 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),75 .RPATH => {
206 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),76 const path = cmd.getRpathPathName();
77 try self.rpaths.put(gpa, path, {});
78 },
79 else => {},
207 };80 };
20881
209 for (expanded) |sym| {82 self.initPlatform();
210 if (self.symbols.contains(sym)) continue;
211 try self.symbols.putNoClobber(allocator, sym, false);
212 }
213}
214
215fn addObjCIVarSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
216 const expanded = try std.fmt.allocPrint(allocator, "_OBJC_IVAR_$_{s}", .{sym_name});
217 if (self.symbols.contains(expanded)) return;
218 try self.symbols.putNoClobber(allocator, expanded, false);
219}83}
22084
221fn addObjCEhTypeSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {85const TrieIterator = struct {
222 const expanded = try std.fmt.allocPrint(allocator, "_OBJC_EHTYPE_$_{s}", .{sym_name});86 data: []const u8,
223 if (self.symbols.contains(expanded)) return;87 pos: usize = 0,
224 try self.symbols.putNoClobber(allocator, expanded, false);
225}
226
227fn addSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {
228 if (self.symbols.contains(sym_name)) return;
229 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), false);
230}
23188
232fn addWeakSymbol(self: *Dylib, allocator: Allocator, sym_name: []const u8) !void {89 fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) {
233 if (self.symbols.contains(sym_name)) return;90 return std.io.fixedBufferStream(it.data[it.pos..]);
234 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), true);91 }
235}
23692
237pub const TargetMatcher = struct {93 fn readULEB128(it: *TrieIterator) !u64 {
238 allocator: Allocator,94 var stream = it.getStream();
239 cpu_arch: std.Target.Cpu.Arch,95 var creader = std.io.countingReader(stream.reader());
240 os_tag: std.Target.Os.Tag,96 const reader = creader.reader();
241 abi: std.Target.Abi,97 const value = try std.leb.readULEB128(u64, reader);
242 target_strings: std.ArrayListUnmanaged([]const u8) = .{},98 it.pos += creader.bytes_read;
99 return value;
100 }
243101
244 pub fn init(allocator: Allocator, target: std.Target) !TargetMatcher {102 fn readString(it: *TrieIterator) ![:0]const u8 {
245 var self = TargetMatcher{103 var stream = it.getStream();
246 .allocator = allocator,104 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);
253105
254 if (self.abi == .simulator) {106 var count: usize = 0;
255 // For Apple simulator targets, linking gets tricky as we need to link against the simulator107 while (true) : (count += 1) {
256 // hosts dylibs too.108 const byte = try reader.readByte();
257 const host_target = try toAppleTargetTriple(allocator, self.cpu_arch, .macos, .none);109 if (byte == 0) break;
258 try self.target_strings.append(allocator, host_target);
259 }110 }
260111
261 return self;112 const str = @as([*:0]const u8, @ptrCast(it.data.ptr + it.pos))[0..count :0];
113 it.pos += count + 1;
114 return str;
262 }115 }
263116
264 pub fn deinit(self: *TargetMatcher) void {117 fn readByte(it: *TrieIterator) !u8 {
265 for (self.target_strings.items) |t| {118 var stream = it.getStream();
266 self.allocator.free(t);119 const value = try stream.reader().readByte();
267 }120 it.pos += 1;
268 self.target_strings.deinit(self.allocator);121 return value;
269 }122 }
123};
270124
271 inline fn fmtCpuArch(cpu_arch: std.Target.Cpu.Arch) []const u8 {125pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void {
272 return switch (cpu_arch) {126 try self.exports.append(allocator, .{
273 .aarch64 => "arm64",127 .name = try self.insertString(allocator, name),
274 .x86_64 => "x86_64",128 .flags = flags,
275 else => unreachable,129 });
276 };130}
277 }
278131
279 inline fn fmtAbi(abi: std.Target.Abi) ?[]const u8 {132fn parseTrieNode(
280 return switch (abi) {133 self: *Dylib,
281 .none => null,134 it: *TrieIterator,
282 .simulator => "simulator",135 allocator: Allocator,
283 .macabi => "maccatalyst",136 arena: Allocator,
284 else => unreachable,137 prefix: []const u8,
138) !void {
139 const tracy = trace(@src());
140 defer tracy.end();
141 const size = try it.readULEB128();
142 if (size > 0) {
143 const flags = try it.readULEB128();
144 const kind = flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK;
145 const out_flags = Export.Flags{
146 .abs = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE,
147 .tlv = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL,
148 .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
285 };149 };
286 }150 if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) {
287151 _ = try it.readULEB128(); // dylib ordinal
288 pub fn toAppleTargetTriple(152 const name = try it.readString();
289 allocator: Allocator,153 try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags);
290 cpu_arch: std.Target.Cpu.Arch,154 } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) {
291 os_tag: std.Target.Os.Tag,155 _ = try it.readULEB128(); // stub offset
292 abi: std.Target.Abi,156 _ = try it.readULEB128(); // resolver offset
293 ) ![]const u8 {157 try self.addExport(allocator, prefix, out_flags);
294 const cpu_arch_s = fmtCpuArch(cpu_arch);158 } else {
295 const os_tag_s = @tagName(os_tag);159 _ = try it.readULEB128(); // VM offset
296 if (fmtAbi(abi)) |abi_s| {160 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 }161 }
299 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ cpu_arch_s, os_tag_s });
300 }162 }
301163
302 fn hasValue(stack: []const []const u8, needle: []const u8) bool {164 const nedges = try it.readByte();
303 for (stack) |v| {
304 if (mem.eql(u8, v, needle)) return true;
305 }
306 return false;
307 }
308165
309 pub fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {166 for (0..nedges) |_| {
310 for (self.target_strings.items) |t| {167 const label = try it.readString();
311 if (hasValue(targets, t)) return true;168 const off = try it.readULEB128();
312 }169 const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
313 return false;170 const curr = it.pos;
171 it.pos = off;
172 try self.parseTrieNode(it, allocator, arena, prefix_label);
173 it.pos = curr;
314 }174 }
175}
315176
316 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {177fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void {
317 return hasValue(archs, fmtCpuArch(self.cpu_arch));178 const tracy = trace(@src());
318 }179 defer tracy.end();
319};180 const gpa = macho_file.base.allocator;
181 var arena = std.heap.ArenaAllocator.init(gpa);
182 defer arena.deinit();
183
184 var it: TrieIterator = .{ .data = data };
185 try self.parseTrieNode(&it, gpa, arena.allocator(), "");
186}
320187
321pub fn parseFromStub(188pub fn parseTbd(
322 self: *Dylib,189 self: *Dylib,
323 allocator: Allocator,190 cpu_arch: std.Target.Cpu.Arch,
324 target: std.Target,191 platform: ?MachO.Options.Platform,
325 lib_stub: LibStub,192 lib_stub: LibStub,
326 dylib_id: u16,193 macho_file: *MachO,
327 dependent_libs: anytype,
328 name: []const u8,
329) !void {194) !void {
330 if (lib_stub.inner.len == 0) return error.NotLibStub;195 const tracy = trace(@src());
196 defer tracy.end();
197 const gpa = macho_file.base.allocator;
331198
332 log.debug("parsing shared library from stub '{s}'", .{name});199 log.debug("parsing dylib from stub", .{});
333200
334 const umbrella_lib = lib_stub.inner[0];201 const umbrella_lib = lib_stub.inner[0];
335202
336 {203 {
337 var id = try Id.default(allocator, umbrella_lib.installName());204 var id = try Id.default(gpa, umbrella_lib.installName());
338 if (umbrella_lib.currentVersion()) |version| {205 if (umbrella_lib.currentVersion()) |version| {
339 try id.parseCurrentVersion(version);206 try id.parseCurrentVersion(version);
340 }207 }
...@@ -344,21 +211,21 @@ pub fn parseFromStub(...@@ -344,21 +211,21 @@ pub fn parseFromStub(
344 self.id = id;211 self.id = id;
345 }212 }
346213
347 var umbrella_libs = std.StringHashMap(void).init(allocator);214 var umbrella_libs = std.StringHashMap(void).init(gpa);
348 defer umbrella_libs.deinit();215 defer umbrella_libs.deinit();
349216
350 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});217 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
351218
352 var matcher = try TargetMatcher.init(allocator, target);219 self.platform = platform orelse .{
220 .platform = .MACOS,
221 .version = .{ .value = 0 },
222 };
223
224 var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.platform);
353 defer matcher.deinit();225 defer matcher.deinit();
354226
355 for (lib_stub.inner, 0..) |elem, stub_index| {227 for (lib_stub.inner, 0..) |elem, stub_index| {
356 const targets = try elem.targets(allocator);228 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;
362229
363 if (stub_index > 0) {230 if (stub_index > 0) {
364 // TODO I thought that we could switch on presence of `parent-umbrella` map;231 // TODO I thought that we could switch on presence of `parent-umbrella` map;
...@@ -375,43 +242,42 @@ pub fn parseFromStub(...@@ -375,43 +242,42 @@ pub fn parseFromStub(
375242
376 if (exp.symbols) |symbols| {243 if (exp.symbols) |symbols| {
377 for (symbols) |sym_name| {244 for (symbols) |sym_name| {
378 try self.addSymbol(allocator, sym_name);245 try self.addExport(gpa, sym_name, .{});
379 }246 }
380 }247 }
381248
382 if (exp.weak_symbols) |symbols| {249 if (exp.weak_symbols) |symbols| {
383 for (symbols) |sym_name| {250 for (symbols) |sym_name| {
384 try self.addWeakSymbol(allocator, sym_name);251 try self.addExport(gpa, sym_name, .{ .weak = true });
385 }252 }
386 }253 }
387254
388 if (exp.objc_classes) |objc_classes| {255 if (exp.objc_classes) |objc_classes| {
389 for (objc_classes) |class_name| {256 for (objc_classes) |class_name| {
390 try self.addObjCClassSymbol(allocator, class_name);257 try self.addObjCClass(gpa, class_name);
391 }258 }
392 }259 }
393260
394 if (exp.objc_ivars) |objc_ivars| {261 if (exp.objc_ivars) |objc_ivars| {
395 for (objc_ivars) |ivar| {262 for (objc_ivars) |ivar| {
396 try self.addObjCIVarSymbol(allocator, ivar);263 try self.addObjCIVar(gpa, ivar);
397 }264 }
398 }265 }
399266
400 if (exp.objc_eh_types) |objc_eh_types| {267 if (exp.objc_eh_types) |objc_eh_types| {
401 for (objc_eh_types) |eht| {268 for (objc_eh_types) |eht| {
402 try self.addObjCEhTypeSymbol(allocator, eht);269 try self.addObjCEhType(gpa, eht);
403 }270 }
404 }271 }
405272
406 // TODO track which libs were already parsed in different steps
407 if (exp.re_exports) |re_exports| {273 if (exp.re_exports) |re_exports| {
408 for (re_exports) |lib| {274 for (re_exports) |lib| {
409 if (umbrella_libs.contains(lib)) continue;275 if (umbrella_libs.contains(lib)) continue;
410276
411 log.debug(" (found re-export '{s}')", .{lib});277 log.debug(" (found re-export '{s}')", .{lib});
412278
413 const dep_id = try Id.default(allocator, lib);279 const dep_id = try Id.default(gpa, lib);
414 try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });280 try self.dependents.append(gpa, dep_id);
415 }281 }
416 }282 }
417 }283 }
...@@ -424,31 +290,31 @@ pub fn parseFromStub(...@@ -424,31 +290,31 @@ pub fn parseFromStub(
424290
425 if (exp.symbols) |symbols| {291 if (exp.symbols) |symbols| {
426 for (symbols) |sym_name| {292 for (symbols) |sym_name| {
427 try self.addSymbol(allocator, sym_name);293 try self.addExport(gpa, sym_name, .{});
428 }294 }
429 }295 }
430296
431 if (exp.weak_symbols) |symbols| {297 if (exp.weak_symbols) |symbols| {
432 for (symbols) |sym_name| {298 for (symbols) |sym_name| {
433 try self.addWeakSymbol(allocator, sym_name);299 try self.addExport(gpa, sym_name, .{ .weak = true });
434 }300 }
435 }301 }
436302
437 if (exp.objc_classes) |classes| {303 if (exp.objc_classes) |classes| {
438 for (classes) |sym_name| {304 for (classes) |sym_name| {
439 try self.addObjCClassSymbol(allocator, sym_name);305 try self.addObjCClass(gpa, sym_name);
440 }306 }
441 }307 }
442308
443 if (exp.objc_ivars) |objc_ivars| {309 if (exp.objc_ivars) |objc_ivars| {
444 for (objc_ivars) |ivar| {310 for (objc_ivars) |ivar| {
445 try self.addObjCIVarSymbol(allocator, ivar);311 try self.addObjCIVar(gpa, ivar);
446 }312 }
447 }313 }
448314
449 if (exp.objc_eh_types) |objc_eh_types| {315 if (exp.objc_eh_types) |objc_eh_types| {
450 for (objc_eh_types) |eht| {316 for (objc_eh_types) |eht| {
451 try self.addObjCEhTypeSymbol(allocator, eht);317 try self.addObjCEhType(gpa, eht);
452 }318 }
453 }319 }
454 }320 }
...@@ -460,31 +326,31 @@ pub fn parseFromStub(...@@ -460,31 +326,31 @@ pub fn parseFromStub(
460326
461 if (reexp.symbols) |symbols| {327 if (reexp.symbols) |symbols| {
462 for (symbols) |sym_name| {328 for (symbols) |sym_name| {
463 try self.addSymbol(allocator, sym_name);329 try self.addExport(gpa, sym_name, .{});
464 }330 }
465 }331 }
466332
467 if (reexp.weak_symbols) |symbols| {333 if (reexp.weak_symbols) |symbols| {
468 for (symbols) |sym_name| {334 for (symbols) |sym_name| {
469 try self.addWeakSymbol(allocator, sym_name);335 try self.addExport(gpa, sym_name, .{ .weak = true });
470 }336 }
471 }337 }
472338
473 if (reexp.objc_classes) |classes| {339 if (reexp.objc_classes) |classes| {
474 for (classes) |sym_name| {340 for (classes) |sym_name| {
475 try self.addObjCClassSymbol(allocator, sym_name);341 try self.addObjCClass(gpa, sym_name);
476 }342 }
477 }343 }
478344
479 if (reexp.objc_ivars) |objc_ivars| {345 if (reexp.objc_ivars) |objc_ivars| {
480 for (objc_ivars) |ivar| {346 for (objc_ivars) |ivar| {
481 try self.addObjCIVarSymbol(allocator, ivar);347 try self.addObjCIVar(gpa, ivar);
482 }348 }
483 }349 }
484350
485 if (reexp.objc_eh_types) |objc_eh_types| {351 if (reexp.objc_eh_types) |objc_eh_types| {
486 for (objc_eh_types) |eht| {352 for (objc_eh_types) |eht| {
487 try self.addObjCEhTypeSymbol(allocator, eht);353 try self.addObjCEhType(gpa, eht);
488 }354 }
489 }355 }
490 }356 }
...@@ -492,19 +358,19 @@ pub fn parseFromStub(...@@ -492,19 +358,19 @@ pub fn parseFromStub(
492358
493 if (stub.objc_classes) |classes| {359 if (stub.objc_classes) |classes| {
494 for (classes) |sym_name| {360 for (classes) |sym_name| {
495 try self.addObjCClassSymbol(allocator, sym_name);361 try self.addObjCClass(gpa, sym_name);
496 }362 }
497 }363 }
498364
499 if (stub.objc_ivars) |objc_ivars| {365 if (stub.objc_ivars) |objc_ivars| {
500 for (objc_ivars) |ivar| {366 for (objc_ivars) |ivar| {
501 try self.addObjCIVarSymbol(allocator, ivar);367 try self.addObjCIVar(gpa, ivar);
502 }368 }
503 }369 }
504370
505 if (stub.objc_eh_types) |objc_eh_types| {371 if (stub.objc_eh_types) |objc_eh_types| {
506 for (objc_eh_types) |eht| {372 for (objc_eh_types) |eht| {
507 try self.addObjCEhTypeSymbol(allocator, eht);373 try self.addObjCEhType(gpa, eht);
508 }374 }
509 }375 }
510 },376 },
...@@ -514,10 +380,9 @@ pub fn parseFromStub(...@@ -514,10 +380,9 @@ pub fn parseFromStub(
514 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include380 // 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.381 // re-exports directly in the stub file.
516 for (lib_stub.inner) |elem| {382 for (lib_stub.inner) |elem| {
517 if (elem == .v3) break;383 if (elem == .v3) continue;
518 const stub = elem.v4;384 const stub = elem.v4;
519385
520 // TODO track which libs were already parsed in different steps
521 if (stub.reexported_libraries) |reexports| {386 if (stub.reexported_libraries) |reexports| {
522 for (reexports) |reexp| {387 for (reexports) |reexp| {
523 if (!matcher.matchesTarget(reexp.targets)) continue;388 if (!matcher.matchesTarget(reexp.targets)) continue;
...@@ -527,30 +392,437 @@ pub fn parseFromStub(...@@ -527,30 +392,437 @@ pub fn parseFromStub(
527392
528 log.debug(" (found re-export '{s}')", .{lib});393 log.debug(" (found re-export '{s}')", .{lib});
529394
530 const dep_id = try Id.default(allocator, lib);395 const dep_id = try Id.default(gpa, lib);
531 try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id });396 try self.dependents.append(gpa, dep_id);
532 }397 }
533 }398 }
534 }399 }
535 }400 }
536}401}
537402
538const Dylib = @This();403fn addObjCClass(self: *Dylib, allocator: Allocator, name: []const u8) !void {
404 try self.addObjCExport(allocator, "_OBJC_CLASS_", name);
405 try self.addObjCExport(allocator, "_OBJC_METACLASS_", name);
406}
407
408fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void {
409 try self.addObjCExport(allocator, "_OBJC_IVAR_", name);
410}
411
412fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void {
413 try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name);
414}
415
416fn addObjCExport(
417 self: *Dylib,
418 allocator: Allocator,
419 comptime prefix: []const u8,
420 name: []const u8,
421) !void {
422 const full_name = try std.fmt.allocPrint(allocator, prefix ++ "$_{s}", .{name});
423 defer allocator.free(full_name);
424 try self.addExport(allocator, full_name, .{});
425}
426
427pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void {
428 const gpa = macho_file.base.allocator;
429
430 try self.symbols.ensureTotalCapacityPrecise(gpa, self.exports.items(.name).len);
431
432 for (self.exports.items(.name)) |noff| {
433 const name = self.getString(noff);
434 const off = try macho_file.string_intern.insert(gpa, name);
435 const gop = try macho_file.getOrCreateGlobal(off);
436 self.symbols.addOneAssumeCapacity().* = gop.index;
437 }
438}
439
440fn initPlatform(self: *Dylib) void {
441 var it = LoadCommandIterator{
442 .ncmds = self.header.?.ncmds,
443 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
444 };
445 self.platform = while (it.next()) |cmd| {
446 switch (cmd.cmd()) {
447 .BUILD_VERSION,
448 .VERSION_MIN_MACOSX,
449 .VERSION_MIN_IPHONEOS,
450 .VERSION_MIN_TVOS,
451 .VERSION_MIN_WATCHOS,
452 => break MachO.Options.Platform.fromLoadCommand(cmd),
453 else => {},
454 }
455 } else null;
456}
457
458pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) void {
459 const tracy = trace(@src());
460 defer tracy.end();
461
462 if (!self.explicit and !self.hoisted) return;
463
464 for (self.symbols.items, self.exports.items(.flags)) |index, flags| {
465 const global = macho_file.getSymbol(index);
466 if (self.asFile().getSymbolRank(.{
467 .weak = flags.weak,
468 }) < global.getSymbolRank(macho_file)) {
469 global.value = 0;
470 global.atom = 0;
471 global.nlist_idx = 0;
472 global.file = self.index;
473 global.flags.weak = flags.weak;
474 global.flags.weak_ref = false;
475 global.flags.tlv = flags.tlv;
476 global.flags.dyn_ref = false;
477 global.flags.tentative = false;
478 global.visibility = .global;
479 }
480 }
481}
482
483pub fn resetGlobals(self: *Dylib, macho_file: *MachO) void {
484 for (self.symbols.items) |sym_index| {
485 const sym = macho_file.getSymbol(sym_index);
486 const name = sym.name;
487 sym.* = .{};
488 sym.name = name;
489 }
490}
491
492pub fn isAlive(self: Dylib, macho_file: *MachO) bool {
493 if (!macho_file.options.dead_strip_dylibs) return self.explicit or self.referenced or self.needed;
494 return self.referenced or self.needed;
495}
496
497pub fn markReferenced(self: *Dylib, macho_file: *MachO) void {
498 const tracy = trace(@src());
499 defer tracy.end();
500
501 for (self.symbols.items) |global_index| {
502 const global = macho_file.getSymbol(global_index);
503 const file_ptr = global.getFile(macho_file) orelse continue;
504 if (file_ptr.getIndex() != self.index) continue;
505 if (global.isLocal()) continue;
506 self.referenced = true;
507 break;
508 }
509}
510
511pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void {
512 const tracy = trace(@src());
513 defer tracy.end();
514
515 for (self.symbols.items) |global_index| {
516 const global = macho_file.getSymbol(global_index);
517 const file_ptr = global.getFile(macho_file) orelse continue;
518 if (file_ptr.getIndex() != self.index) continue;
519 if (global.isLocal()) continue;
520 assert(global.flags.import);
521 global.flags.output_symtab = true;
522 try global.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
523 self.output_symtab_ctx.nimports += 1;
524 self.output_symtab_ctx.strsize += @as(u32, @intCast(global.getName(macho_file).len + 1));
525 }
526}
527
528pub fn writeSymtab(self: Dylib, macho_file: *MachO) void {
529 const tracy = trace(@src());
530 defer tracy.end();
531
532 for (self.symbols.items) |global_index| {
533 const global = macho_file.getSymbol(global_index);
534 const file = global.getFile(macho_file) orelse continue;
535 if (file.getIndex() != self.index) continue;
536 const idx = global.getOutputSymtabIndex(macho_file) orelse continue;
537 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
538 macho_file.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
539 macho_file.strtab.appendAssumeCapacity(0);
540 const out_sym = &macho_file.symtab.items[idx];
541 out_sym.n_strx = n_strx;
542 global.setOutputSym(macho_file, out_sym);
543 }
544}
545
546pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib {
547 return macho_file.getFile(self.umbrella).?.dylib;
548}
549
550fn getLoadCommand(self: Dylib, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
551 var it = LoadCommandIterator{
552 .ncmds = self.header.?.ncmds,
553 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
554 };
555 while (it.next()) |cmd| {
556 if (cmd.cmd() == lc) return cmd;
557 } else return null;
558}
559
560fn insertString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 {
561 const off = @as(u32, @intCast(self.strtab.items.len));
562 try self.strtab.writer(allocator).print("{s}\x00", .{name});
563 return off;
564}
565
566pub inline fn getString(self: Dylib, off: u32) [:0]const u8 {
567 assert(off < self.strtab.items.len);
568 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
569}
570
571pub fn asFile(self: *Dylib) File {
572 return .{ .dylib = self };
573}
574
575pub fn format(
576 self: *Dylib,
577 comptime unused_fmt_string: []const u8,
578 options: std.fmt.FormatOptions,
579 writer: anytype,
580) !void {
581 _ = self;
582 _ = unused_fmt_string;
583 _ = options;
584 _ = writer;
585 @compileError("do not format dylib directly");
586}
587
588pub fn fmtSymtab(self: *Dylib, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
589 return .{ .data = .{
590 .dylib = self,
591 .macho_file = macho_file,
592 } };
593}
594
595const FormatContext = struct {
596 dylib: *Dylib,
597 macho_file: *MachO,
598};
599
600fn formatSymtab(
601 ctx: FormatContext,
602 comptime unused_fmt_string: []const u8,
603 options: std.fmt.FormatOptions,
604 writer: anytype,
605) !void {
606 _ = unused_fmt_string;
607 _ = options;
608 const dylib = ctx.dylib;
609 try writer.writeAll(" globals\n");
610 for (dylib.symbols.items) |index| {
611 const global = ctx.macho_file.getSymbol(index);
612 try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
613 }
614}
615
616pub const TargetMatcher = struct {
617 allocator: Allocator,
618 cpu_arch: std.Target.Cpu.Arch,
619 platform: macho.PLATFORM,
620 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
621
622 pub fn init(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) !TargetMatcher {
623 var self = TargetMatcher{
624 .allocator = allocator,
625 .cpu_arch = cpu_arch,
626 .platform = platform,
627 };
628 const apple_string = try targetToAppleString(allocator, cpu_arch, platform);
629 try self.target_strings.append(allocator, apple_string);
630
631 switch (platform) {
632 .IOSSIMULATOR, .TVOSSIMULATOR, .WATCHOSSIMULATOR => {
633 // For Apple simulator targets, linking gets tricky as we need to link against the simulator
634 // hosts dylibs too.
635 const host_target = try targetToAppleString(allocator, cpu_arch, .MACOS);
636 try self.target_strings.append(allocator, host_target);
637 },
638 else => {},
639 }
640
641 return self;
642 }
643
644 pub fn deinit(self: *TargetMatcher) void {
645 for (self.target_strings.items) |t| {
646 self.allocator.free(t);
647 }
648 self.target_strings.deinit(self.allocator);
649 }
650
651 inline fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 {
652 return switch (cpu_arch) {
653 .aarch64 => "arm64",
654 .x86_64 => "x86_64",
655 else => unreachable,
656 };
657 }
658
659 pub fn targetToAppleString(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) ![]const u8 {
660 const arch = cpuArchToAppleString(cpu_arch);
661 const plat = switch (platform) {
662 .MACOS => "macos",
663 .IOS => "ios",
664 .TVOS => "tvos",
665 .WATCHOS => "watchos",
666 .IOSSIMULATOR => "ios-simulator",
667 .TVOSSIMULATOR => "tvos-simulator",
668 .WATCHOSSIMULATOR => "watchos-simulator",
669 .BRIDGEOS => "bridgeos",
670 .MACCATALYST => "maccatalyst",
671 .DRIVERKIT => "driverkit",
672 else => unreachable,
673 };
674 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, plat });
675 }
676
677 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
678 for (stack) |v| {
679 if (mem.eql(u8, v, needle)) return true;
680 }
681 return false;
682 }
683
684 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
685 return hasValue(archs, cpuArchToAppleString(self.cpu_arch));
686 }
687
688 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
689 for (self.target_strings.items) |t| {
690 if (hasValue(targets, t)) return true;
691 }
692 return false;
693 }
694
695 pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool {
696 var arena = std.heap.ArenaAllocator.init(self.allocator);
697 defer arena.deinit();
698
699 const targets = switch (tbd) {
700 .v3 => |v3| blk: {
701 var targets = std.ArrayList([]const u8).init(arena.allocator());
702 for (v3.archs) |arch| {
703 const target = try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform });
704 try targets.append(target);
705 }
706 break :blk targets.items;
707 },
708 .v4 => |v4| v4.targets,
709 };
710
711 return self.matchesTarget(targets);
712 }
713};
714
715pub const Id = struct {
716 name: []const u8,
717 timestamp: u32,
718 current_version: u32,
719 compatibility_version: u32,
720
721 pub fn default(allocator: Allocator, name: []const u8) !Id {
722 return Id{
723 .name = try allocator.dupe(u8, name),
724 .timestamp = 2,
725 .current_version = 0x10000,
726 .compatibility_version = 0x10000,
727 };
728 }
729
730 pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id {
731 return Id{
732 .name = try allocator.dupe(u8, name),
733 .timestamp = lc.dylib.timestamp,
734 .current_version = lc.dylib.current_version,
735 .compatibility_version = lc.dylib.compatibility_version,
736 };
737 }
738
739 pub fn deinit(id: Id, allocator: Allocator) void {
740 allocator.free(id.name);
741 }
742
743 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
744
745 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
746 id.current_version = try parseVersion(version);
747 }
748
749 pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void {
750 id.compatibility_version = try parseVersion(version);
751 }
752
753 fn parseVersion(version: anytype) ParseError!u32 {
754 const string = blk: {
755 switch (version) {
756 .int => |int| {
757 var out: u32 = 0;
758 const major = math.cast(u16, int) orelse return error.Overflow;
759 out += @as(u32, @intCast(major)) << 16;
760 return out;
761 },
762 .float => |float| {
763 var buf: [256]u8 = undefined;
764 break :blk try fmt.bufPrint(&buf, "{d:.2}", .{float});
765 },
766 .string => |string| {
767 break :blk string;
768 },
769 }
770 };
771
772 var out: u32 = 0;
773 var values: [3][]const u8 = undefined;
774
775 var split = mem.split(u8, string, ".");
776 var count: u4 = 0;
777 while (split.next()) |value| {
778 if (count > 2) {
779 log.debug("malformed version field: {s}", .{string});
780 return 0x10000;
781 }
782 values[count] = value;
783 count += 1;
784 }
785
786 if (count > 2) {
787 out += try fmt.parseInt(u8, values[2], 10);
788 }
789 if (count > 1) {
790 out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8;
791 }
792 out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16;
793
794 return out;
795 }
796};
797
798const Export = struct {
799 name: u32,
800 flags: Flags,
801
802 const Flags = packed struct {
803 abs: bool = false,
804 weak: bool = false,
805 tlv: bool = false,
806 };
807};
539808
540const std = @import("std");
541const assert = std.debug.assert;809const assert = std.debug.assert;
810const fat = @import("fat.zig");
542const fs = std.fs;811const fs = std.fs;
543const fmt = std.fmt;812const fmt = std.fmt;
544const log = std.log.scoped(.link);813const log = std.log.scoped(.link);
545const macho = std.macho;814const macho = std.macho;
546const math = std.math;815const math = std.math;
547const mem = std.mem;816const mem = std.mem;
548const fat = @import("fat.zig");
549const tapi = @import("../tapi.zig");817const tapi = @import("../tapi.zig");
818const trace = @import("../tracy.zig").trace;
819const std = @import("std");
550820
551const Allocator = mem.Allocator;821const Allocator = mem.Allocator;
822const Dylib = @This();
823const File = @import("file.zig").File;
552const LibStub = tapi.LibStub;824const LibStub = tapi.LibStub;
553const LoadCommandIterator = macho.LoadCommandIterator;825const LoadCommandIterator = macho.LoadCommandIterator;
554const MachO = @import("../MachO.zig");826const MachO = @import("../MachO.zig");
555const Platform = @import("load_commands.zig").Platform;827const Symbol = @import("Symbol.zig");
556const Tbd = tapi.Tbd;828const Tbd = tapi.Tbd;
src/link/MachO/InternalObject.zig created+249
...@@ -0,0 +1,249 @@
1index: File.Index,
2
3sections: std.MultiArrayList(Section) = .{},
4atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
5symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
6
7objc_methnames: std.ArrayListUnmanaged(u8) = .{},
8objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
9
10output_symtab_ctx: MachO.SymtabCtx = .{},
11
12pub fn deinit(self: *InternalObject, allocator: Allocator) void {
13 for (self.sections.items(.relocs)) |*relocs| {
14 relocs.deinit(allocator);
15 }
16 self.sections.deinit(allocator);
17 self.atoms.deinit(allocator);
18 self.symbols.deinit(allocator);
19 self.objc_methnames.deinit(allocator);
20}
21
22pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index {
23 const gpa = macho_file.base.allocator;
24 try self.symbols.ensureUnusedCapacity(gpa, 1);
25 const off = try macho_file.string_intern.insert(gpa, name);
26 const gop = try macho_file.getOrCreateGlobal(off);
27 self.symbols.addOneAssumeCapacity().* = gop.index;
28 const sym = macho_file.getSymbol(gop.index);
29 sym.* = .{ .name = off, .file = self.index };
30 return gop.index;
31}
32
33/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
34pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 {
35 const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);
36 return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file);
37}
38
39fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {
40 const gpa = macho_file.base.allocator;
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.string_intern.insert(gpa, name);
49 atom.file = self.index;
50 atom.size = methname.len + 1;
51 atom.alignment = 0;
52
53 const n_sect = try self.addSection(gpa, "__TEXT", "__objc_methname");
54 const sect = &self.sections.items(.header)[n_sect];
55 sect.flags = macho.S_CSTRING_LITERALS;
56 sect.size = atom.size;
57 sect.@"align" = 0;
58 atom.n_sect = n_sect;
59 self.sections.items(.extra)[n_sect].is_objc_methname = true;
60
61 sect.offset = @intCast(self.objc_methnames.items.len);
62 try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1);
63 self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable;
64
65 return atom_index;
66}
67
68fn addObjcSelrefsSection(
69 self: *InternalObject,
70 methname: []const u8,
71 methname_atom_index: Atom.Index,
72 macho_file: *MachO,
73) !Atom.Index {
74 const gpa = macho_file.base.allocator;
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.string_intern.insert(gpa, name);
83 atom.file = self.index;
84 atom.size = @sizeOf(u64);
85 atom.alignment = 3;
86
87 const n_sect = try self.addSection(gpa, "__DATA", "__objc_selrefs");
88 const sect = &self.sections.items(.header)[n_sect];
89 sect.flags = macho.S_LITERAL_POINTERS | macho.S_ATTR_NO_DEAD_STRIP;
90 sect.offset = 0;
91 sect.size = atom.size;
92 sect.@"align" = 3;
93 atom.n_sect = n_sect;
94 self.sections.items(.extra)[n_sect].is_objc_selref = true;
95
96 const relocs = &self.sections.items(.relocs)[n_sect];
97 try relocs.ensureUnusedCapacity(gpa, 1);
98 relocs.appendAssumeCapacity(.{
99 .tag = .local,
100 .offset = 0,
101 .target = methname_atom_index,
102 .addend = 0,
103 .type = .unsigned,
104 .meta = .{
105 .pcrel = false,
106 .length = 3,
107 .symbolnum = 0, // Only used when synthesising unwind records so can be anything
108 .has_subtractor = false,
109 },
110 });
111 atom.relocs = .{ .pos = 0, .len = 1 };
112
113 return atom_index;
114}
115
116pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
117 for (self.symbols.items) |sym_index| {
118 const sym = macho_file.getSymbol(sym_index);
119 if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
120 sym.flags.output_symtab = true;
121 if (sym.isLocal()) {
122 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
123 self.output_symtab_ctx.nlocals += 1;
124 } else if (sym.flags.@"export") {
125 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
126 self.output_symtab_ctx.nexports += 1;
127 } else {
128 assert(sym.flags.import);
129 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
130 self.output_symtab_ctx.nimports += 1;
131 }
132 self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
133 }
134}
135
136pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void {
137 for (self.symbols.items) |sym_index| {
138 const sym = macho_file.getSymbol(sym_index);
139 if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
140 const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
141 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
142 macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
143 macho_file.strtab.appendAssumeCapacity(0);
144 const out_sym = &macho_file.symtab.items[idx];
145 out_sym.n_strx = n_strx;
146 sym.setOutputSym(macho_file, out_sym);
147 }
148}
149
150fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
151 const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
152 self.sections.set(n_sect, .{
153 .header = .{
154 .sectname = MachO.makeStaticString(sectname),
155 .segname = MachO.makeStaticString(segname),
156 },
157 });
158 return n_sect;
159}
160
161pub fn getSectionData(self: *const InternalObject, index: u32) []const u8 {
162 const slice = self.sections.slice();
163 assert(index < slice.items(.header).len);
164 const sect = slice.items(.header)[index];
165 const extra = slice.items(.extra)[index];
166 if (extra.is_objc_methname) {
167 return self.objc_methnames.items[sect.offset..][0..sect.size];
168 } else if (extra.is_objc_selref) {
169 return &self.objc_selrefs;
170 } else @panic("ref to non-existent section");
171}
172
173pub fn asFile(self: *InternalObject) File {
174 return .{ .internal = self };
175}
176
177const FormatContext = struct {
178 self: *InternalObject,
179 macho_file: *MachO,
180};
181
182pub fn fmtAtoms(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
183 return .{ .data = .{
184 .self = self,
185 .macho_file = macho_file,
186 } };
187}
188
189fn formatAtoms(
190 ctx: FormatContext,
191 comptime unused_fmt_string: []const u8,
192 options: std.fmt.FormatOptions,
193 writer: anytype,
194) !void {
195 _ = unused_fmt_string;
196 _ = options;
197 try writer.writeAll(" atoms\n");
198 for (ctx.self.atoms.items) |atom_index| {
199 const atom = ctx.macho_file.getAtom(atom_index).?;
200 try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
201 }
202}
203
204pub fn fmtSymtab(self: *InternalObject, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
205 return .{ .data = .{
206 .self = self,
207 .macho_file = macho_file,
208 } };
209}
210
211fn formatSymtab(
212 ctx: FormatContext,
213 comptime unused_fmt_string: []const u8,
214 options: std.fmt.FormatOptions,
215 writer: anytype,
216) !void {
217 _ = unused_fmt_string;
218 _ = options;
219 try writer.writeAll(" symbols\n");
220 for (ctx.self.symbols.items) |index| {
221 const global = ctx.macho_file.getSymbol(index);
222 try writer.print(" {}\n", .{global.fmt(ctx.macho_file)});
223 }
224}
225
226const Section = struct {
227 header: macho.section_64,
228 relocs: std.ArrayListUnmanaged(Relocation) = .{},
229 extra: Extra = .{},
230
231 const Extra = packed struct {
232 is_objc_methname: bool = false,
233 is_objc_selref: bool = false,
234 };
235};
236
237const assert = std.debug.assert;
238const macho = std.macho;
239const mem = std.mem;
240const std = @import("std");
241
242const Allocator = std.mem.Allocator;
243const Atom = @import("Atom.zig");
244const File = @import("file.zig").File;
245const InternalObject = @This();
246const MachO = @import("../MachO.zig");
247const Object = @import("Object.zig");
248const Relocation = @import("Relocation.zig");
249const Symbol = @import("Symbol.zig");
src/link/MachO/Object.zig+1892-929
...@@ -1,1130 +1,2093 @@...@@ -1,1130 +1,2093 @@
1//! Represents an input relocatable Object file.1archive: ?[]const u8 = null,
2//! Each Object is fully loaded into memory for easier2path: []const u8,
3//! access into different data within.
4
5name: []const u8,
6mtime: u64,3mtime: u64,
7contents: []align(@alignOf(u64)) const u8,4data: []const u8,
85index: File.Index,
9header: macho.mach_header_64 = undefined,
10
11/// Symtab and strtab might not exist for empty object files so we use an optional
12/// to signal this.
13in_symtab: ?[]align(1) const macho.nlist_64 = null,
14in_strtab: ?[]const u8 = null,
15
16/// Output symtab is sorted so that we can easily reference symbols following each
17/// other in address space.
18/// The length of the symtab is at least of the input symtab length however there
19/// can be trailing section symbols.
20symtab: []macho.nlist_64 = undefined,
21/// Can be undefined as set together with in_symtab.
22source_symtab_lookup: []u32 = undefined,
23/// Can be undefined as set together with in_symtab.
24reverse_symtab_lookup: []u32 = undefined,
25/// Can be undefined as set together with in_symtab.
26source_address_lookup: []i64 = undefined,
27/// Can be undefined as set together with in_symtab.
28source_section_index_lookup: []Entry = undefined,
29/// Can be undefined as set together with in_symtab.
30strtab_lookup: []u32 = undefined,
31/// Can be undefined as set together with in_symtab.
32atom_by_index_table: []?Atom.Index = undefined,
33/// Can be undefined as set together with in_symtab.
34globals_lookup: []i64 = undefined,
35/// Can be undefined as set together with in_symtab.
36relocs_lookup: []Entry = undefined,
37
38/// All relocations sorted and flatened, sorted by address descending
39/// per section.
40relocations: std.ArrayListUnmanaged(macho.relocation_info) = .{},
41/// Beginning index to the relocations array for each input section
42/// defined within this Object file.
43section_relocs_lookup: std.ArrayListUnmanaged(u32) = .{},
44
45/// Data-in-code records sorted by address.
46data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
47
48atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
49exec_atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
50
51eh_frame_sect_id: ?u8 = null,
52eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
53eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
546
55unwind_info_sect_id: ?u8 = null,7header: ?macho.mach_header_64 = null,
56unwind_relocs_lookup: []Record = undefined,8sections: std.MultiArrayList(Section) = .{},
57unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},9symtab: std.MultiArrayList(Nlist) = .{},
10strtab: []const u8 = &[0]u8{},
5811
59const Entry = struct {12symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
60 start: u32 = 0,13atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
61 len: u32 = 0,
62};
63
64const Record = struct {
65 dead: bool,
66 reloc: Entry,
67};
68
69pub fn isObject(file: std.fs.File) bool {
70 const reader = file.reader();
71 const hdr = reader.readStruct(macho.mach_header_64) catch return false;
72 defer file.seekTo(0) catch {};
73 return hdr.filetype == macho.MH_OBJECT;
74}
7514
76pub fn deinit(self: *Object, gpa: Allocator) void {15platform: ?MachO.Options.Platform = null,
77 self.atoms.deinit(gpa);16dwarf_info: ?DwarfInfo = null,
78 self.exec_atoms.deinit(gpa);17stab_files: std.ArrayListUnmanaged(StabFile) = .{},
79 gpa.free(self.name);18
80 gpa.free(self.contents);19eh_frame_sect_index: ?u8 = null,
81 if (self.in_symtab) |_| {20compact_unwind_sect_index: ?u8 = null,
82 gpa.free(self.source_symtab_lookup);21cies: std.ArrayListUnmanaged(Cie) = .{},
83 gpa.free(self.reverse_symtab_lookup);22fdes: std.ArrayListUnmanaged(Fde) = .{},
84 gpa.free(self.source_address_lookup);23eh_frame_data: std.ArrayListUnmanaged(u8) = .{},
85 gpa.free(self.source_section_index_lookup);24unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record.Index) = .{},
86 gpa.free(self.strtab_lookup);25
87 gpa.free(self.symtab);26alive: bool = true,
88 gpa.free(self.atom_by_index_table);27hidden: bool = false,
89 gpa.free(self.globals_lookup);28num_rebase_relocs: u32 = 0,
90 gpa.free(self.relocs_lookup);29num_bind_relocs: u32 = 0,
30num_weak_bind_relocs: u32 = 0,
31
32output_symtab_ctx: MachO.SymtabCtx = .{},
33
34pub fn deinit(self: *Object, allocator: Allocator) void {
35 for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
36 relocs.deinit(allocator);
37 sub.deinit(allocator);
91 }38 }
92 self.eh_frame_relocs_lookup.deinit(gpa);39 self.sections.deinit(allocator);
93 self.eh_frame_records_lookup.deinit(gpa);40 self.symtab.deinit(allocator);
94 if (self.hasUnwindRecords()) {41 self.symbols.deinit(allocator);
95 gpa.free(self.unwind_relocs_lookup);42 self.atoms.deinit(allocator);
43 self.cies.deinit(allocator);
44 self.fdes.deinit(allocator);
45 self.eh_frame_data.deinit(allocator);
46 self.unwind_records.deinit(allocator);
47 if (self.dwarf_info) |*dw| dw.deinit(allocator);
48 for (self.stab_files.items) |*sf| {
49 sf.stabs.deinit(allocator);
96 }50 }
97 self.unwind_records_lookup.deinit(gpa);51 self.stab_files.deinit(allocator);
98 self.relocations.deinit(gpa);
99 self.section_relocs_lookup.deinit(gpa);
100 self.data_in_code.deinit(gpa);
101}52}
10253
103pub fn parse(self: *Object, allocator: Allocator) !void {54pub fn parse(self: *Object, macho_file: *MachO) !void {
104 var stream = std.io.fixedBufferStream(self.contents);55 const tracy = trace(@src());
56 defer tracy.end();
57
58 const gpa = macho_file.base.allocator;
59 var stream = std.io.fixedBufferStream(self.data);
105 const reader = stream.reader();60 const reader = stream.reader();
10661
107 self.header = try reader.readStruct(macho.mach_header_64);62 self.header = try reader.readStruct(macho.mach_header_64);
10863
109 var it = LoadCommandIterator{64 if (self.getLoadCommand(.SEGMENT_64)) |lc| {
110 .ncmds = self.header.ncmds,65 const sections = lc.getSections();
111 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],66 try self.sections.ensureUnusedCapacity(gpa, sections.len);
112 };67 for (sections) |sect| {
113 const nsects = self.getSourceSections().len;68 const index = try self.sections.addOne(gpa);
11469 self.sections.set(index, .{ .header = sect });
115 // Prepopulate relocations per section lookup table.70
116 try self.section_relocs_lookup.resize(allocator, nsects);71 if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
117 @memset(self.section_relocs_lookup.items, 0);72 self.eh_frame_sect_index = @intCast(index);
11873 } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) {
119 // Parse symtab.74 self.compact_unwind_sect_index = @intCast(index);
120 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {75 }
121 .SYMTAB => break cmd.cast(macho.symtab_command).?,76 }
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 }77 }
14978 if (self.getLoadCommand(.SYMTAB)) |lc| {
150 @memset(self.globals_lookup, -1);79 const cmd = lc.cast(macho.symtab_command).?;
151 @memset(self.atom_by_index_table, null);80 self.strtab = self.data[cmd.stroff..][0..cmd.strsize];
152 @memset(self.source_section_index_lookup, .{});81
153 @memset(self.relocs_lookup, .{});82 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(self.data.ptr + cmd.symoff))[0..cmd.nsyms];
15483 try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
155 // You would expect that the symbol table is at least pre-sorted based on symbol's type:84 for (symtab) |nlist| {
156 // local < extern defined < undefined. Unfortunately, this is not guaranteed! For instance,85 self.symtab.appendAssumeCapacity(.{
157 // the GO compiler does not necessarily respect that therefore we sort immediately by type86 .nlist = nlist,
158 // and address within.87 .atom = 0,
159 var sorted_all_syms = try std.ArrayList(SymbolAtIndex).initCapacity(allocator, self.in_symtab.?.len);88 .size = 0,
160 defer sorted_all_syms.deinit();89 });
16190 }
162 for (self.in_symtab.?, 0..) |_, index| {
163 sorted_all_syms.appendAssumeCapacity(.{ .index = @as(u32, @intCast(index)) });
164 }91 }
16592
166 // We sort by type: defined < undefined, and93 const NlistIdx = struct {
167 // afterwards by address in each group. Normally, dysymtab should94 nlist: macho.nlist_64,
168 // be enough to guarantee the sort, but turns out not every compiler95 idx: usize,
169 // is kind enough to specify the symbols in the correct order.96
170 mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan);97 fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
17198 if (!nl.ext()) {
172 var prev_sect_id: u8 = 0;99 const name = ctx.getString(nl.n_strx);
173 var section_index_lookup: ?Entry = null;100 if (name.len == 0) return 5;
174 for (sorted_all_syms.items, 0..) |sym_id, i| {101 if (name[0] == 'l' or name[0] == 'L') return 4;
175 const sym = sym_id.getSymbol(self);102 return 3;
176
177 if (section_index_lookup) |*lookup| {
178 if (sym.n_sect != prev_sect_id or sym.undf()) {
179 self.source_section_index_lookup[prev_sect_id - 1] = lookup.*;
180 section_index_lookup = null;
181 } else {
182 lookup.len += 1;
183 }103 }
104 return if (nl.weakDef()) 2 else 1;
184 }105 }
185 if (sym.sect() and section_index_lookup == null) {106
186 section_index_lookup = .{ .start = @as(u32, @intCast(i)), .len = 1 };107 fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {
108 if (lhs.nlist.n_sect == rhs.nlist.n_sect) {
109 if (lhs.nlist.n_value == rhs.nlist.n_value) {
110 return rank(ctx, lhs.nlist) < rank(ctx, rhs.nlist);
111 }
112 return lhs.nlist.n_value < rhs.nlist.n_value;
113 }
114 return lhs.nlist.n_sect < rhs.nlist.n_sect;
187 }115 }
116 };
188117
189 prev_sect_id = sym.n_sect;118 var nlists = try std.ArrayList(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
119 defer nlists.deinit();
120 for (self.symtab.items(.nlist), 0..) |nlist, i| {
121 if (nlist.stab() or !nlist.sect()) continue;
122 nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
123 }
124 mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
190125
191 self.symtab[i] = sym;126 if (self.hasSubsections()) {
192 self.source_symtab_lookup[i] = sym_id.index;127 try self.initSubsections(nlists.items, macho_file);
193 self.reverse_symtab_lookup[sym_id.index] = @as(u32, @intCast(i));128 } else {
194 self.source_address_lookup[i] = if (sym.undf()) -1 else @as(i64, @intCast(sym.n_value));129 try self.initSections(nlists.items, macho_file);
130 }
195131
196 const sym_name_len = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.in_strtab.?.ptr + sym.n_strx)), 0).len + 1;132 try self.initLiteralSections(macho_file);
197 self.strtab_lookup[i] = @as(u32, @intCast(sym_name_len));133 try self.linkNlistToAtom(macho_file);
134
135 try self.sortAtoms(macho_file);
136 try self.initSymbols(macho_file);
137 try self.initSymbolStabs(nlists.items, macho_file);
138 try self.initRelocs(macho_file);
139
140 if (self.eh_frame_sect_index) |index| {
141 try self.initEhFrameRecords(index, macho_file);
198 }142 }
199143
200 // If there were no undefined symbols, make sure we populate the144 if (self.compact_unwind_sect_index) |index| {
201 // source section index lookup for the last scanned section.145 try self.initUnwindRecords(index, macho_file);
202 if (section_index_lookup) |lookup| {
203 self.source_section_index_lookup[prev_sect_id - 1] = lookup;
204 }146 }
205147
206 // Parse __TEXT,__eh_frame header if one exists148 self.initPlatform();
207 self.eh_frame_sect_id = self.getSourceSectionIndexByName("__TEXT", "__eh_frame");149 try self.initDwarfInfo(macho_file);
150
151 for (self.atoms.items) |atom_index| {
152 const atom = macho_file.getAtom(atom_index).?;
153 const isec = atom.getInputSection(macho_file);
154 if (mem.eql(u8, isec.sectName(), "__eh_frame") or
155 mem.eql(u8, isec.sectName(), "__compact_unwind") or
156 isec.attrs() & macho.S_ATTR_DEBUG != 0)
157 {
158 atom.flags.alive = false;
159 }
160 }
161}
208162
209 // Parse __LD,__compact_unwind header if one exists163inline fn isLiteral(sect: macho.section_64) bool {
210 self.unwind_info_sect_id = self.getSourceSectionIndexByName("__LD", "__compact_unwind");164 return switch (sect.type()) {
211 if (self.hasUnwindRecords()) {165 macho.S_CSTRING_LITERALS,
212 self.unwind_relocs_lookup = try allocator.alloc(Record, self.getUnwindRecords().len);166 macho.S_4BYTE_LITERALS,
213 @memset(self.unwind_relocs_lookup, .{ .dead = true, .reloc = .{} });167 macho.S_8BYTE_LITERALS,
168 macho.S_16BYTE_LITERALS,
169 macho.S_LITERAL_POINTERS,
170 => true,
171 else => false,
172 };
173}
174
175fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
176 const tracy = trace(@src());
177 defer tracy.end();
178 const gpa = macho_file.base.allocator;
179 const slice = self.sections.slice();
180 for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
181 if (isLiteral(sect)) continue;
182
183 const nlist_start = for (nlists, 0..) |nlist, i| {
184 if (nlist.nlist.n_sect - 1 == n_sect) break i;
185 } else nlists.len;
186 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
187 if (nlist.nlist.n_sect - 1 != n_sect) break i;
188 } else nlists.len;
189
190 if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) {
191 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
192 defer gpa.free(name);
193 const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr;
194 const atom_index = try self.addAtom(.{
195 .name = name,
196 .n_sect = @intCast(n_sect),
197 .off = 0,
198 .size = size,
199 .alignment = sect.@"align",
200 }, macho_file);
201 try subsections.append(gpa, .{
202 .atom = atom_index,
203 .off = 0,
204 });
205 }
206
207 var idx: usize = nlist_start;
208 while (idx < nlist_end) {
209 const alias_start = idx;
210 const nlist = nlists[alias_start];
211
212 while (idx < nlist_end and
213 nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
214 {}
215
216 const size = if (idx < nlist_end)
217 nlists[idx].nlist.n_value - nlist.nlist.n_value
218 else
219 sect.addr + sect.size - nlist.nlist.n_value;
220 const alignment = if (nlist.nlist.n_value > 0)
221 @min(@ctz(nlist.nlist.n_value), sect.@"align")
222 else
223 sect.@"align";
224 const atom_index = try self.addAtom(.{
225 .name = self.getString(nlist.nlist.n_strx),
226 .n_sect = @intCast(n_sect),
227 .off = nlist.nlist.n_value - sect.addr,
228 .size = size,
229 .alignment = alignment,
230 }, macho_file);
231 try subsections.append(gpa, .{
232 .atom = atom_index,
233 .off = nlist.nlist.n_value - sect.addr,
234 });
235
236 for (alias_start..idx) |i| {
237 self.symtab.items(.size)[nlists[i].idx] = size;
238 }
239 }
214 }240 }
215}241}
216242
217const SymbolAtIndex = struct {243fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
218 index: u32,244 const tracy = trace(@src());
245 defer tracy.end();
246 const gpa = macho_file.base.allocator;
247 const slice = self.sections.slice();
248
249 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
250
251 for (slice.items(.header), 0..) |sect, n_sect| {
252 if (isLiteral(sect)) continue;
253
254 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
255 defer gpa.free(name);
256
257 const atom_index = try self.addAtom(.{
258 .name = name,
259 .n_sect = @intCast(n_sect),
260 .off = 0,
261 .size = sect.size,
262 .alignment = sect.@"align",
263 }, macho_file);
264 try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
265
266 const nlist_start = for (nlists, 0..) |nlist, i| {
267 if (nlist.nlist.n_sect - 1 == n_sect) break i;
268 } else nlists.len;
269 const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
270 if (nlist.nlist.n_sect - 1 != n_sect) break i;
271 } else nlists.len;
272
273 var idx: usize = nlist_start;
274 while (idx < nlist_end) {
275 const nlist = nlists[idx];
276
277 while (idx < nlist_end and
278 nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
279 {}
280
281 const size = if (idx < nlist_end)
282 nlists[idx].nlist.n_value - nlist.nlist.n_value
283 else
284 sect.addr + sect.size - nlist.nlist.n_value;
285
286 for (nlist_start..idx) |i| {
287 self.symtab.items(.size)[nlists[i].idx] = size;
288 }
289 }
290 }
291}
219292
220 const Context = *const Object;293const AddAtomArgs = struct {
294 name: [:0]const u8,
295 n_sect: u8,
296 off: u64,
297 size: u64,
298 alignment: u32,
299};
221300
222 fn getSymbol(self: SymbolAtIndex, ctx: Context) macho.nlist_64 {301fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {
223 return ctx.in_symtab.?[self.index];302 const gpa = macho_file.base.allocator;
303 const atom_index = try macho_file.addAtom();
304 const atom = macho_file.getAtom(atom_index).?;
305 atom.file = self.index;
306 atom.atom_index = atom_index;
307 atom.name = try macho_file.string_intern.insert(gpa, args.name);
308 atom.n_sect = args.n_sect;
309 atom.size = args.size;
310 atom.alignment = args.alignment;
311 atom.off = args.off;
312 try self.atoms.append(gpa, atom_index);
313 return atom_index;
314}
315
316fn initLiteralSections(self: *Object, macho_file: *MachO) !void {
317 const tracy = trace(@src());
318 defer tracy.end();
319 // TODO here we should split into equal-sized records, hash the contents, and then
320 // deduplicate - ICF.
321 // For now, we simply cover each literal section with one large atom.
322 const gpa = macho_file.base.allocator;
323 const slice = self.sections.slice();
324
325 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
326
327 for (slice.items(.header), 0..) |sect, n_sect| {
328 if (!isLiteral(sect)) continue;
329
330 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
331 defer gpa.free(name);
332
333 const atom_index = try self.addAtom(.{
334 .name = name,
335 .n_sect = @intCast(n_sect),
336 .off = 0,
337 .size = sect.size,
338 .alignment = sect.@"align",
339 }, macho_file);
340 try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
224 }341 }
342}
225343
226 fn getSymbolName(self: SymbolAtIndex, ctx: Context) []const u8 {344pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
227 const off = self.getSymbol(ctx).n_strx;345 const tracy = trace(@src());
228 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.in_strtab.?.ptr + off)), 0);346 defer tracy.end();
347 const slice = self.sections.slice();
348 for (slice.items(.header), slice.items(.subsections), 0..) |sect, subs, n_sect| {
349 if (subs.items.len == 0) continue;
350 if (sect.addr == addr) return subs.items[0].atom;
351 if (sect.addr < addr and addr < sect.addr + sect.size) {
352 return self.findAtomInSection(addr, @intCast(n_sect));
353 }
229 }354 }
355 return null;
356}
230357
231 fn getSymbolSeniority(self: SymbolAtIndex, ctx: Context) u2 {358fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index {
232 const sym = self.getSymbol(ctx);359 const tracy = trace(@src());
233 if (!sym.ext()) {360 defer tracy.end();
234 const sym_name = self.getSymbolName(ctx);361 const slice = self.sections.slice();
235 if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3;362 const sect = slice.items(.header)[n_sect];
236 return 2;363 const subsections = slice.items(.subsections)[n_sect];
364
365 var min: usize = 0;
366 var max: usize = subsections.items.len;
367 while (min < max) {
368 const idx = (min + max) / 2;
369 const sub = subsections.items[idx];
370 const sub_addr = sect.addr + sub.off;
371 const sub_size = if (idx + 1 < subsections.items.len)
372 subsections.items[idx + 1].off - sub.off
373 else
374 sect.size - sub.off;
375 if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
376 if (sub_addr < addr) {
377 min = idx + 1;
378 } else {
379 max = idx;
237 }380 }
238 if (sym.weakDef() or sym.pext()) return 1;
239 return 0;
240 }381 }
241382
242 /// Performs lexicographic-like check.383 if (min < subsections.items.len) {
243 /// * lhs and rhs defined384 const sub = subsections.items[min];
244 /// * if lhs == rhs385 const sub_addr = sect.addr + sub.off;
245 /// * if lhs.n_sect == rhs.n_sect386 const sub_size = if (min + 1 < subsections.items.len)
246 /// * ext < weak < local < temp387 subsections.items[min + 1].off - sub.off
247 /// * lhs.n_sect < rhs.n_sect388 else
248 /// * lhs < rhs389 sect.size - sub.off;
249 /// * !rhs is undefined390 if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
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 }391 }
267392
268 fn lessThanByNStrx(ctx: Context, lhs: SymbolAtIndex, rhs: SymbolAtIndex) bool {393 return null;
269 return lhs.getSymbol(ctx).n_strx < rhs.getSymbol(ctx).n_strx;394}
395
396fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
397 const tracy = trace(@src());
398 defer tracy.end();
399 for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| {
400 if (!nlist.stab() and nlist.sect()) {
401 if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| {
402 atom.* = atom_index;
403 } else {
404 macho_file.base.fatal("{}: symbol {s} not attached to any (sub)section", .{
405 self.fmtPath(), self.getString(nlist.n_strx),
406 });
407 return error.ParseFailed;
408 }
409 }
270 }410 }
271};411}
272412
273fn filterSymbolsBySection(symbols: []macho.nlist_64, n_sect: u8) struct {413fn initSymbols(self: *Object, macho_file: *MachO) !void {
274 index: u32,414 const tracy = trace(@src());
275 len: u32,415 defer tracy.end();
276} {416 const gpa = macho_file.base.allocator;
277 const FirstMatch = struct {417 const slice = self.symtab.slice();
278 n_sect: u8,418
419 try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len);
279420
280 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {421 for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| {
281 return symbol.n_sect == pred.n_sect;422 if (nlist.ext()) {
423 const name = self.getString(nlist.n_strx);
424 const off = try macho_file.string_intern.insert(gpa, name);
425 const gop = try macho_file.getOrCreateGlobal(off);
426 self.symbols.addOneAssumeCapacity().* = gop.index;
427 continue;
282 }428 }
283 };
284 const FirstNonMatch = struct {
285 n_sect: u8,
286429
287 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {430 const index = try macho_file.addSymbol();
288 return symbol.n_sect != pred.n_sect;431 self.symbols.appendAssumeCapacity(index);
432 const symbol = macho_file.getSymbol(index);
433 const name = self.getString(nlist.n_strx);
434 symbol.* = .{
435 .value = nlist.n_value,
436 .name = try macho_file.string_intern.insert(gpa, name),
437 .nlist_idx = @intCast(i),
438 .atom = 0,
439 .file = self.index,
440 };
441
442 if (macho_file.getAtom(atom_index)) |atom| {
443 assert(!nlist.abs());
444 symbol.value -= atom.getInputAddress(macho_file);
445 symbol.atom = atom_index;
289 }446 }
290 };
291447
292 const index = MachO.lsearch(macho.nlist_64, symbols, FirstMatch{448 symbol.flags.abs = nlist.abs();
293 .n_sect = n_sect,449 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
294 });
295 const len = MachO.lsearch(macho.nlist_64, symbols[index..], FirstNonMatch{
296 .n_sect = n_sect,
297 });
298450
299 return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };451 if (nlist.sect() and
452 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
453 {
454 symbol.flags.tlv = true;
455 }
456 }
300}457}
301458
302fn filterSymbolsByAddress(symbols: []macho.nlist_64, start_addr: u64, end_addr: u64) struct {459fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void {
303 index: u32,460 const tracy = trace(@src());
304 len: u32,461 defer tracy.end();
305} {
306 const Predicate = struct {
307 addr: u64,
308462
309 pub fn predicate(pred: @This(), symbol: macho.nlist_64) bool {463 const SymbolLookup = struct {
310 return symbol.n_value >= pred.addr;464 ctx: *const Object,
465 entries: @TypeOf(nlists),
466
467 fn find(fs: @This(), addr: u64) ?Symbol.Index {
468 // TODO binary search since we have the list sorted
469 for (fs.entries) |nlist| {
470 if (nlist.nlist.n_value == addr) return fs.ctx.symbols.items[nlist.idx];
471 }
472 return null;
311 }473 }
312 };474 };
313475
314 const index = MachO.lsearch(macho.nlist_64, symbols, Predicate{476 const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| {
315 .addr = start_addr,477 if (nlist.stab()) break @intCast(i);
316 });478 } else @intCast(self.symtab.items(.nlist).len);
317 const len = MachO.lsearch(macho.nlist_64, symbols[index..], Predicate{479 const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| {
318 .addr = end_addr,480 if (!nlist.stab()) break @intCast(i);
319 });481 } else @intCast(self.symtab.items(.nlist).len);
482
483 if (start == end) return;
484
485 const gpa = macho_file.base.allocator;
486 const syms = self.symtab.items(.nlist);
487 const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists };
488
489 var i: u32 = start;
490 while (i < end) : (i += 1) {
491 const open = syms[i];
492 if (open.n_type != macho.N_SO) {
493 macho_file.base.fatal("{}: unexpected symbol stab type 0x{x} as the first entry", .{
494 self.fmtPath(),
495 open.n_type,
496 });
497 return error.ParseFailed;
498 }
499
500 while (i < end and syms[i].n_type == macho.N_SO and syms[i].n_sect != 0) : (i += 1) {}
501
502 var sf: StabFile = .{ .comp_dir = i };
503 // TODO validate
504 i += 3;
505
506 while (i < end and syms[i].n_type != macho.N_SO) : (i += 1) {
507 const nlist = syms[i];
508 var stab: StabFile.Stab = .{};
509 switch (nlist.n_type) {
510 macho.N_BNSYM => {
511 stab.tag = .func;
512 stab.symbol = sym_lookup.find(nlist.n_value);
513 // TODO validate
514 i += 3;
515 },
516 macho.N_GSYM => {
517 stab.tag = .global;
518 stab.symbol = macho_file.getGlobalByName(self.getString(nlist.n_strx));
519 },
520 macho.N_STSYM => {
521 stab.tag = .static;
522 stab.symbol = sym_lookup.find(nlist.n_value);
523 },
524 else => {
525 macho_file.base.fatal("{}: unhandled symbol stab type 0x{x}", .{
526 self.fmtPath(),
527 nlist.n_type,
528 });
529 return error.ParseFailed;
530 },
531 }
532 try sf.stabs.append(gpa, stab);
533 }
320534
321 return .{ .index = @as(u32, @intCast(index)), .len = @as(u32, @intCast(len)) };535 try self.stab_files.append(gpa, sf);
536 }
322}537}
323538
324const SortedSection = struct {539fn sortAtoms(self: *Object, macho_file: *MachO) !void {
325 header: macho.section_64,540 const lessThanAtom = struct {
326 id: u8,541 fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool {
327};542 return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx);
543 }
544 }.lessThanAtom;
545 mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom);
546}
547
548fn initRelocs(self: *Object, macho_file: *MachO) !void {
549 const tracy = trace(@src());
550 defer tracy.end();
551 const cpu_arch = macho_file.options.cpu_arch.?;
552 const slice = self.sections.slice();
553
554 for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| {
555 if (sect.nreloc == 0) continue;
556 // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit
557 // debug symbol stabs in the relocatable. This made me curious why that is. For now,
558 // I shall comply, but I wanna compare with dsymutil.
559 if (sect.attrs() & macho.S_ATTR_DEBUG != 0 and
560 !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue;
561
562 switch (cpu_arch) {
563 .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
564 .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file),
565 else => unreachable,
566 }
328567
329fn sectionLessThanByAddress(ctx: void, lhs: SortedSection, rhs: SortedSection) bool {568 mem.sort(Relocation, out.items, {}, Relocation.lessThan);
330 _ = ctx;
331 if (lhs.header.addr == rhs.header.addr) {
332 return lhs.id < rhs.id;
333 }569 }
334 return lhs.header.addr < rhs.header.addr;
335}
336570
337pub const SplitIntoAtomsError = error{571 for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| {
338 OutOfMemory,572 if (sect.isZerofill()) continue;
339 EndOfStream,573
340 MissingEhFrameSection,574 var next_reloc: usize = 0;
341 BadDwarfCfi,575 for (subsections.items) |subsection| {
342};576 const atom = macho_file.getAtom(subsection.atom).?;
577 if (!atom.flags.alive) continue;
578 if (next_reloc >= relocs.items.len) break;
579 const end_addr = atom.off + atom.size;
580 atom.relocs.pos = next_reloc;
343581
344pub fn splitIntoAtoms(self: *Object, macho_file: *MachO, object_id: u32) SplitIntoAtomsError!void {582 while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {}
345 const comp = macho_file.base.comp;
346 const gpa = comp.gpa;
347 log.debug("splitting object({d}, {s}) into atoms", .{ object_id, self.name });
348583
349 try self.splitRegularSections(macho_file, object_id);584 atom.relocs.len = next_reloc - atom.relocs.pos;
350 try self.parseEhFrameSection(macho_file, object_id);585 }
351 try self.parseUnwindInfo(macho_file, object_id);586 }
352 try self.parseDataInCode(gpa);
353}587}
354588
355/// Splits input regular sections into Atoms.589fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
356/// If the Object was compiled with `MH_SUBSECTIONS_VIA_SYMBOLS`, splits section590 const tracy = trace(@src());
357/// into subsections where each subsection then represents an Atom.591 defer tracy.end();
358pub fn splitRegularSections(self: *Object, macho_file: *MachO, object_id: u32) !void {592 const gpa = macho_file.base.allocator;
359 const comp = macho_file.base.comp;593 const nlists = self.symtab.items(.nlist);
360 const gpa = comp.gpa;594 const slice = self.sections.slice();
361 const target = macho_file.base.comp.root_mod.resolved_target.result;595 const sect = slice.items(.header)[sect_id];
362596 const relocs = slice.items(.relocs)[sect_id];
363 const sections = self.getSourceSections();597
364 for (sections, 0..) |sect, id| {598 const data = self.getSectionData(sect_id);
365 if (sect.isDebug()) continue;599 try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len);
366 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse {600 self.eh_frame_data.appendSliceAssumeCapacity(data);
367 log.debug(" unhandled section '{s},{s}'", .{ sect.segName(), sect.sectName() });601
368 continue;602 // Check for non-personality relocs in FDEs and apply them
369 };603 for (relocs.items, 0..) |rel, i| {
370 if (sect.size == 0) continue;604 switch (rel.type) {
605 .unsigned => {
606 assert((rel.meta.length == 2 or rel.meta.length == 3) and rel.meta.has_subtractor); // TODO error
607 const S: i64 = switch (rel.tag) {
608 .local => rel.meta.symbolnum,
609 .@"extern" => @intCast(nlists[rel.meta.symbolnum].n_value),
610 };
611 const A = rel.addend;
612 const SUB: i64 = blk: {
613 const sub_rel = relocs.items[i - 1];
614 break :blk switch (sub_rel.tag) {
615 .local => sub_rel.meta.symbolnum,
616 .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value),
617 };
618 };
619 switch (rel.meta.length) {
620 0, 1 => unreachable,
621 2 => mem.writeInt(u32, self.eh_frame_data.items[rel.offset..][0..4], @bitCast(@as(i32, @truncate(S + A - SUB))), .little),
622 3 => mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little),
623 }
624 },
625 else => {},
626 }
627 }
371628
372 const sect_id = @as(u8, @intCast(id));629 var it = eh_frame.Iterator{ .data = self.eh_frame_data.items };
373 const sym = self.getSectionAliasSymbolPtr(sect_id);630 while (try it.next()) |rec| {
374 sym.* = .{631 switch (rec.tag) {
375 .n_strx = 0,632 .cie => try self.cies.append(gpa, .{
376 .n_type = macho.N_SECT,633 .offset = rec.offset,
377 .n_sect = out_sect_id + 1,634 .size = rec.size,
378 .n_desc = 0,635 .file = self.index,
379 .n_value = sect.addr,636 }),
380 };637 .fde => try self.fdes.append(gpa, .{
638 .offset = rec.offset,
639 .size = rec.size,
640 .cie = undefined,
641 .file = self.index,
642 }),
643 }
644 }
645
646 for (self.cies.items) |*cie| {
647 try cie.parse(macho_file);
381 }648 }
382649
383 if (self.in_symtab == null) {650 for (self.fdes.items) |*fde| {
384 for (sections, 0..) |sect, id| {651 try fde.parse(macho_file);
385 if (sect.isDebug()) continue;652 }
386 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;653
387 if (sect.size == 0) continue;654 const sortFn = struct {
388655 fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool {
389 const sect_id: u8 = @intCast(id);656 return lhs.getAtom(ctx).getInputAddress(ctx) < rhs.getAtom(ctx).getInputAddress(ctx);
390 const sym_index = self.getSectionAliasSymbolIndex(sect_id);657 }
391 const atom_index = try self.createAtomFromSubsection(658 }.sortFn;
392 macho_file,659
393 object_id,660 mem.sort(Fde, self.fdes.items, macho_file, sortFn);
394 sym_index,661
395 sym_index,662 // Parse and attach personality pointers to CIEs if any
396 1,663 for (relocs.items) |rel| {
397 sect.size,664 switch (rel.type) {
398 Alignment.fromLog2Units(sect.@"align"),665 .got => {
399 out_sect_id,666 assert(rel.meta.length == 2 and rel.tag == .@"extern");
400 );667 const cie = for (self.cies.items) |*cie| {
401 macho_file.addAtomToSection(atom_index);668 if (cie.offset <= rel.offset and rel.offset < cie.offset + cie.getSize()) break cie;
669 } else {
670 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
671 self.fmtPath(), sect.segName(), sect.sectName(), rel.offset,
672 });
673 return error.ParseFailed;
674 };
675 cie.personality = .{ .index = @intCast(rel.target), .offset = rel.offset - cie.offset };
676 },
677 else => {},
402 }678 }
403 return;
404 }679 }
680}
681
682fn initUnwindRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void {
683 const tracy = trace(@src());
684 defer tracy.end();
685
686 const SymbolLookup = struct {
687 ctx: *const Object,
405688
406 // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we689 fn find(fs: @This(), addr: u64) ?Symbol.Index {
407 // have to infer the start of undef section in the symtab ourselves.690 for (fs.ctx.symbols.items, 0..) |sym_index, i| {
408 const iundefsym = blk: {691 const nlist = fs.ctx.symtab.items(.nlist)[i];
409 const dysymtab = self.getDysymtab() orelse {692 if (nlist.ext() and nlist.n_value == addr) return sym_index;
410 var iundefsym: usize = self.in_symtab.?.len;
411 while (iundefsym > 0) : (iundefsym -= 1) {
412 const sym = self.symtab[iundefsym - 1];
413 if (sym.sect()) break;
414 }693 }
415 break :blk iundefsym;694 return null;
416 };695 }
417 break :blk dysymtab.iundefsym;
418 };696 };
419697
420 // We only care about defined symbols, so filter every other out.698 const gpa = macho_file.base.allocator;
421 const symtab = try gpa.dupe(macho.nlist_64, self.symtab[0..iundefsym]);699 const data = self.getSectionData(sect_id);
422 defer gpa.free(symtab);700 const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
423701 const recs = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data.ptr))[0..nrecs];
424 const subsections_via_symbols = self.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;702 const sym_lookup = SymbolLookup{ .ctx = self };
703
704 try self.unwind_records.resize(gpa, nrecs);
705
706 const header = self.sections.items(.header)[sect_id];
707 const relocs = self.sections.items(.relocs)[sect_id].items;
708 var reloc_idx: usize = 0;
709 for (recs, self.unwind_records.items, 0..) |rec, *out_index, rec_idx| {
710 const rec_start = rec_idx * @sizeOf(macho.compact_unwind_entry);
711 const rec_end = rec_start + @sizeOf(macho.compact_unwind_entry);
712 const reloc_start = reloc_idx;
713 while (reloc_idx < relocs.len and
714 relocs[reloc_idx].offset < rec_end) : (reloc_idx += 1)
715 {}
716
717 out_index.* = try macho_file.addUnwindRecord();
718 const out = macho_file.getUnwindRecord(out_index.*);
719 out.length = rec.rangeLength;
720 out.enc = .{ .enc = rec.compactUnwindEncoding };
721 out.file = self.index;
722
723 for (relocs[reloc_start..reloc_idx]) |rel| {
724 if (rel.type != .unsigned or rel.meta.length != 3) {
725 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
726 self.fmtPath(), header.segName(), header.sectName(), rel.offset,
727 });
728 return error.ParseFailed;
729 }
730 assert(rel.type == .unsigned and rel.meta.length == 3); // TODO error
731 const offset = rel.offset - rec_start;
732 switch (offset) {
733 0 => switch (rel.tag) { // target symbol
734 .@"extern" => {
735 out.atom = self.symtab.items(.atom)[rel.meta.symbolnum];
736 out.atom_offset = @intCast(rec.rangeStart);
737 },
738 .local => if (self.findAtom(rec.rangeStart)) |atom_index| {
739 out.atom = atom_index;
740 const atom = out.getAtom(macho_file);
741 out.atom_offset = @intCast(rec.rangeStart - atom.getInputAddress(macho_file));
742 } else {
743 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
744 self.fmtPath(), header.segName(), header.sectName(), rel.offset,
745 });
746 return error.ParseFailed;
747 },
748 },
749 16 => switch (rel.tag) { // personality function
750 .@"extern" => {
751 out.personality = rel.target;
752 },
753 .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| {
754 out.personality = sym_index;
755 } else {
756 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
757 self.fmtPath(), header.segName(), header.sectName(), rel.offset,
758 });
759 return error.ParseFailed;
760 },
761 },
762 24 => switch (rel.tag) { // lsda
763 .@"extern" => {
764 out.lsda = self.symtab.items(.atom)[rel.meta.symbolnum];
765 out.lsda_offset = @intCast(rec.lsda);
766 },
767 .local => if (self.findAtom(rec.lsda)) |atom_index| {
768 out.lsda = atom_index;
769 const atom = out.getLsdaAtom(macho_file).?;
770 out.lsda_offset = @intCast(rec.lsda - atom.getInputAddress(macho_file));
771 } else {
772 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
773 self.fmtPath(), header.segName(), header.sectName(), rel.offset,
774 });
775 return error.ParseFailed;
776 },
777 },
778 else => {},
779 }
780 }
781 }
425782
426 // Sort section headers by address.783 if (!macho_file.options.relocatable) try self.synthesiseNullUnwindRecords(macho_file);
427 var sorted_sections = try gpa.alloc(SortedSection, sections.len);
428 defer gpa.free(sorted_sections);
429784
430 for (sections, 0..) |sect, id| {785 const sortFn = struct {
431 sorted_sections[id] = .{ .header = sect, .id = @as(u8, @intCast(id)) };786 fn sortFn(ctx: *MachO, lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool {
787 const lhs = ctx.getUnwindRecord(lhs_index);
788 const rhs = ctx.getUnwindRecord(rhs_index);
789 const lhsa = lhs.getAtom(ctx);
790 const rhsa = rhs.getAtom(ctx);
791 return lhsa.getInputAddress(ctx) + lhs.atom_offset < rhsa.getInputAddress(ctx) + rhs.atom_offset;
792 }
793 }.sortFn;
794 mem.sort(UnwindInfo.Record.Index, self.unwind_records.items, macho_file, sortFn);
795
796 // Associate unwind records to atoms
797 var next_cu: u32 = 0;
798 while (next_cu < self.unwind_records.items.len) {
799 const start = next_cu;
800 const rec_index = self.unwind_records.items[start];
801 const rec = macho_file.getUnwindRecord(rec_index);
802 while (next_cu < self.unwind_records.items.len and
803 macho_file.getUnwindRecord(self.unwind_records.items[next_cu]).atom == rec.atom) : (next_cu += 1)
804 {}
805
806 const atom = rec.getAtom(macho_file);
807 atom.unwind_records = .{ .pos = start, .len = next_cu - start };
432 }808 }
809}
433810
434 mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress);811fn synthesiseNullUnwindRecords(self: *Object, macho_file: *MachO) !void {
435812 // Synthesise missing unwind records.
436 var sect_sym_index: u32 = 0;813 // The logic here is as follows:
437 for (sorted_sections) |section| {814 // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead
438 const sect = section.header;815 // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record
439 if (sect.isDebug()) continue;816 // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie
440817 // 4. if an atom doesn't have either, synthesise a null unwind info record
441 const sect_id = section.id;818
442 log.debug("splitting section '{s},{s}' into atoms", .{ sect.segName(), sect.sectName() });819 const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null };
443820
444 // Get output segment/section in the final artifact.821 const gpa = macho_file.base.allocator;
445 const out_sect_id = (try Atom.getOutputSection(macho_file, sect)) orelse continue;822 var superposition = std.AutoArrayHashMap(u64, Superposition).init(gpa);
446823 defer superposition.deinit();
447 log.debug(" output sect({d}, '{s},{s}')", .{824
448 out_sect_id + 1,825 const slice = self.symtab.slice();
449 macho_file.sections.items(.header)[out_sect_id].segName(),826 for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {
450 macho_file.sections.items(.header)[out_sect_id].sectName(),827 if (nlist.stab()) continue;
451 });828 if (!nlist.sect()) continue;
452829 const sect = self.sections.items(.header)[nlist.n_sect - 1];
453 try self.parseRelocs(gpa, section.id);830 if (sect.isCode()) {
454831 try superposition.ensureUnusedCapacity(1);
455 const cpu_arch = target.cpu.arch;832 const gop = superposition.getOrPutAssumeCapacity(nlist.n_value);
456 const sect_loc = filterSymbolsBySection(symtab[sect_sym_index..], sect_id + 1);833 if (gop.found_existing) {
457 const sect_start_index = sect_sym_index + sect_loc.index;834 assert(gop.value_ptr.atom == atom and gop.value_ptr.size == size);
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 }835 }
836 gop.value_ptr.* = .{ .atom = atom, .size = size };
837 }
838 }
485839
486 var next_sym_index = sect_start_index;840 for (self.unwind_records.items) |rec_index| {
487 while (next_sym_index < sect_start_index + sect_loc.len) {841 const rec = macho_file.getUnwindRecord(rec_index);
488 const next_sym = symtab[next_sym_index];842 const atom = rec.getAtom(macho_file);
489 const addr = next_sym.n_value;843 const addr = atom.getInputAddress(macho_file) + rec.atom_offset;
490 const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1);844 superposition.getPtr(addr).?.cu = rec_index;
491 assert(atom_loc.len > 0);845 }
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;
500846
501 const atom_align = Alignment.fromLog2Units(if (addr > 0)847 for (self.fdes.items, 0..) |fde, fde_index| {
502 @min(@ctz(addr), sect.@"align")848 const atom = fde.getAtom(macho_file);
503 else849 const addr = atom.getInputAddress(macho_file) + fde.atom_offset;
504 sect.@"align");850 superposition.getPtr(addr).?.fde = @intCast(fde_index);
505851 }
506 const atom_index = try self.createAtomFromSubsection(852
507 macho_file,853 for (superposition.keys(), superposition.values()) |addr, meta| {
508 object_id,854 if (meta.fde) |fde_index| {
509 atom_sym_index,855 const fde = &self.fdes.items[fde_index];
510 atom_sym_index,856
511 nsyms_trailing,857 if (meta.cu) |rec_index| {
512 atom_size,858 const rec = macho_file.getUnwindRecord(rec_index);
513 atom_align,859 if (!rec.enc.isDwarf(macho_file)) {
514 out_sect_id,860 // Mark FDE dead
515 );861 fde.alive = false;
516862 } else {
517 // TODO rework this at the relocation level863 // Tie FDE to unwind record
518 if (cpu_arch == .x86_64 and addr == sect.addr) {864 rec.fde = fde_index;
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 }865 }
525 if (!sect.isZerofill()) {866 } else {
526 try self.cacheRelocs(macho_file, atom_index);867 // Synthesise new unwind info record
868 const fde_data = fde.getData(macho_file);
869 const atom_size = mem.readInt(u64, fde_data[16..][0..8], .little);
870 const rec_index = try macho_file.addUnwindRecord();
871 const rec = macho_file.getUnwindRecord(rec_index);
872 try self.unwind_records.append(gpa, rec_index);
873 rec.length = @intCast(atom_size);
874 rec.atom = fde.atom;
875 rec.atom_offset = fde.atom_offset;
876 rec.fde = fde_index;
877 rec.file = fde.file;
878 switch (macho_file.options.cpu_arch.?) {
879 .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF),
880 .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF),
881 else => unreachable,
527 }882 }
528 macho_file.addAtomToSection(atom_index);
529 }883 }
530 } else {884 } else if (meta.cu == null and meta.fde == null) {
531 const alias_index = self.getSectionAliasSymbolIndex(sect_id);885 // Create a null record
532 const atom_index = try self.createAtomFromSubsection(886 const rec_index = try macho_file.addUnwindRecord();
533 macho_file,887 const rec = macho_file.getUnwindRecord(rec_index);
534 object_id,888 const atom = macho_file.getAtom(meta.atom).?;
535 alias_index,889 try self.unwind_records.append(gpa, rec_index);
536 sect_start_index,890 rec.length = @intCast(meta.size);
537 sect_loc.len,891 rec.atom = meta.atom;
538 sect.size,892 rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file));
539 Alignment.fromLog2Units(sect.@"align"),893 rec.file = self.index;
540 out_sect_id,
541 );
542 if (!sect.isZerofill()) {
543 try self.cacheRelocs(macho_file, atom_index);
544 }
545 macho_file.addAtomToSection(atom_index);
546 }894 }
547 }895 }
548}896}
549897
550fn createAtomFromSubsection(898fn initPlatform(self: *Object) void {
551 self: *Object,899 var it = LoadCommandIterator{
552 macho_file: *MachO,900 .ncmds = self.header.?.ncmds,
553 object_id: u32,901 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
554 sym_index: u32,902 };
555 inner_sym_index: u32,903 self.platform = while (it.next()) |cmd| {
556 inner_nsyms_trailing: u32,904 switch (cmd.cmd()) {
557 size: u64,905 .BUILD_VERSION,
558 alignment: Alignment,906 .VERSION_MIN_MACOSX,
559 out_sect_id: u8,907 .VERSION_MIN_IPHONEOS,
560) !Atom.Index {908 .VERSION_MIN_TVOS,
561 const comp = macho_file.base.comp;909 .VERSION_MIN_WATCHOS,
562 const gpa = comp.gpa;910 => break MachO.Options.Platform.fromLoadCommand(cmd),
563 const atom_index = try macho_file.createAtom(sym_index, .{911 else => {},
564 .size = size,912 }
565 .alignment = alignment,913 } else null;
566 });914}
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
582 try self.atoms.append(gpa, atom_index);
583 self.atom_by_index_table[sym_index] = atom_index;
584915
585 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);916/// Currently, we only check if a compile unit for this input object file exists
586 while (it.next()) |sym_loc| {917/// and record that so that we can emit symbol stabs.
587 const inner = macho_file.getSymbolPtr(sym_loc);918/// TODO in the future, we want parse debug info and debug line sections so that
588 inner.n_sect = out_sect_id + 1;919/// we can provide nice error locations to the user.
589 self.atom_by_index_table[sym_loc.sym_index] = atom_index;920fn initDwarfInfo(self: *Object, macho_file: *MachO) !void {
921 const tracy = trace(@src());
922 defer tracy.end();
923
924 const gpa = macho_file.base.allocator;
925
926 var debug_info_index: ?usize = null;
927 var debug_abbrev_index: ?usize = null;
928 var debug_str_index: ?usize = null;
929
930 for (self.sections.items(.header), 0..) |sect, index| {
931 if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
932 if (mem.eql(u8, sect.sectName(), "__debug_info")) debug_info_index = index;
933 if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) debug_abbrev_index = index;
934 if (mem.eql(u8, sect.sectName(), "__debug_str")) debug_str_index = index;
590 }935 }
591936
592 const out_sect = macho_file.sections.items(.header)[out_sect_id];937 if (debug_info_index == null or debug_abbrev_index == null) return;
593 if (out_sect.isCode() and
594 mem.eql(u8, "__TEXT", out_sect.segName()) and
595 mem.eql(u8, "__text", out_sect.sectName()))
596 {
597 // TODO currently assuming a single section for executable machine code
598 try self.exec_atoms.append(gpa, atom_index);
599 }
600938
601 return atom_index;939 var dwarf_info = DwarfInfo{
940 .debug_info = self.getSectionData(@intCast(debug_info_index.?)),
941 .debug_abbrev = self.getSectionData(@intCast(debug_abbrev_index.?)),
942 .debug_str = if (debug_str_index) |index| self.getSectionData(@intCast(index)) else "",
943 };
944 dwarf_info.init(gpa) catch {
945 macho_file.base.fatal("{}: invalid __DWARF info found", .{self.fmtPath()});
946 return error.ParseFailed;
947 };
948 self.dwarf_info = dwarf_info;
602}949}
603950
604fn filterRelocs(951pub fn resolveSymbols(self: *Object, macho_file: *MachO) void {
605 relocs: []align(1) const macho.relocation_info,952 const tracy = trace(@src());
606 start_addr: u64,953 defer tracy.end();
607 end_addr: u64,
608) Entry {
609 const Predicate = struct {
610 addr: u64,
611954
612 pub fn predicate(self: @This(), rel: macho.relocation_info) bool {955 for (self.symbols.items, 0..) |index, i| {
613 return rel.r_address >= self.addr;956 const nlist_idx = @as(Symbol.Index, @intCast(i));
614 }957 const nlist = self.symtab.items(.nlist)[nlist_idx];
615 };958 const atom_index = self.symtab.items(.atom)[nlist_idx];
616 const LPredicate = struct {
617 addr: u64,
618959
619 pub fn predicate(self: @This(), rel: macho.relocation_info) bool {960 if (!nlist.ext()) continue;
620 return rel.r_address < self.addr;961 if (nlist.undf() and !nlist.tentative()) continue;
962 if (nlist.sect()) {
963 const atom = macho_file.getAtom(atom_index).?;
964 if (!atom.flags.alive) continue;
621 }965 }
622 };
623966
624 const start = MachO.bsearch(macho.relocation_info, relocs, Predicate{ .addr = end_addr });967 const symbol = macho_file.getSymbol(index);
625 const len = MachO.lsearch(macho.relocation_info, relocs[start..], LPredicate{ .addr = start_addr });968 if (self.asFile().getSymbolRank(.{
969 .archive = !self.alive,
970 .weak = nlist.weakDef(),
971 .tentative = nlist.tentative(),
972 }) < symbol.getSymbolRank(macho_file)) {
973 const value = if (nlist.sect()) blk: {
974 const atom = macho_file.getAtom(atom_index).?;
975 break :blk nlist.n_value - atom.getInputAddress(macho_file);
976 } else nlist.n_value;
977 symbol.value = value;
978 symbol.atom = atom_index;
979 symbol.nlist_idx = nlist_idx;
980 symbol.file = self.index;
981 symbol.flags.weak = nlist.weakDef();
982 symbol.flags.abs = nlist.abs();
983 symbol.flags.tentative = nlist.tentative();
984 symbol.flags.weak_ref = false;
985 symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0;
986 symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip();
987 symbol.flags.interposable = macho_file.options.dylib and macho_file.options.namespace == .flat and !nlist.pext();
988
989 if (nlist.sect() and
990 self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
991 {
992 symbol.flags.tlv = true;
993 }
994 }
626995
627 return .{ .start = @as(u32, @intCast(start)), .len = @as(u32, @intCast(len)) };996 // Regardless of who the winner is, we still merge symbol visibility here.
997 if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) {
998 if (symbol.visibility != .global) {
999 symbol.visibility = .hidden;
1000 }
1001 } else {
1002 symbol.visibility = .global;
1003 }
1004 }
628}1005}
6291006
630/// Parse all relocs for the input section, and sort in descending order.1007pub fn resetGlobals(self: *Object, macho_file: *MachO) void {
631/// Previously, I have wrongly assumed the compilers output relocations for each1008 for (self.symbols.items, 0..) |sym_index, nlist_idx| {
632/// section in a sorted manner which is simply not true.1009 if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue;
633fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {1010 const sym = macho_file.getSymbol(sym_index);
634 const section = self.getSourceSection(sect_id);1011 const name = sym.name;
635 const start = @as(u32, @intCast(self.relocations.items.len));1012 sym.* = .{};
636 if (self.getSourceRelocs(section)) |relocs| {1013 sym.name = name;
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 }1014 }
641 self.section_relocs_lookup.items[sect_id] = start;
642}1015}
6431016
644fn cacheRelocs(self: *Object, macho_file: *MachO, atom_index: Atom.Index) !void {1017pub fn markLive(self: *Object, macho_file: *MachO) void {
645 const atom = macho_file.getAtom(atom_index);1018 const tracy = trace(@src());
6461019 defer tracy.end();
647 const source_sect_id = if (self.getSourceSymbol(atom.sym_index)) |source_sym| blk: {1020
648 break :blk source_sym.n_sect - 1;1021 for (self.symbols.items, 0..) |index, nlist_idx| {
649 } else blk: {1022 const nlist = self.symtab.items(.nlist)[nlist_idx];
650 // If there was no matching symbol present in the source symtab, this means1023 if (!nlist.ext()) continue;
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}
6661024
667fn relocGreaterThan(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {1025 const sym = macho_file.getSymbol(index);
668 _ = ctx;1026 const file = sym.getFile(macho_file) orelse continue;
669 return lhs.r_address > rhs.r_address;1027 const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative);
1028 if (should_keep and file == .object and !file.object.alive) {
1029 file.object.alive = true;
1030 file.object.markLive(macho_file);
1031 }
1032 }
670}1033}
6711034
672fn parseEhFrameSection(self: *Object, macho_file: *MachO, object_id: u32) !void {1035pub fn scanRelocs(self: Object, macho_file: *MachO) !void {
673 const sect_id = self.eh_frame_sect_id orelse return;1036 const tracy = trace(@src());
674 const sect = self.getSourceSection(sect_id);1037 defer tracy.end();
6751038
676 log.debug("parsing __TEXT,__eh_frame section", .{});1039 for (self.atoms.items) |atom_index| {
1040 const atom = macho_file.getAtom(atom_index).?;
1041 if (!atom.flags.alive) continue;
1042 const sect = atom.getInputSection(macho_file);
1043 if (sect.isZerofill()) continue;
1044 try atom.scanRelocs(macho_file);
1045 }
6771046
678 const comp = macho_file.base.comp;1047 for (self.unwind_records.items) |rec_index| {
679 const gpa = comp.gpa;1048 const rec = macho_file.getUnwindRecord(rec_index);
1049 if (!rec.alive) continue;
1050 if (rec.getFde(macho_file)) |fde| {
1051 if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| {
1052 sym.flags.got = true;
1053 }
1054 } else if (rec.getPersonality(macho_file)) |sym| {
1055 sym.flags.got = true;
1056 }
1057 }
1058}
6801059
681 if (macho_file.eh_frame_section_index == null) {1060pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
682 macho_file.eh_frame_section_index = try macho_file.initSection("__TEXT", "__eh_frame", .{});1061 const tracy = trace(@src());
1062 defer tracy.end();
1063 const gpa = macho_file.base.allocator;
1064
1065 for (self.symbols.items, 0..) |index, i| {
1066 const sym = macho_file.getSymbol(index);
1067 if (!sym.flags.tentative) continue;
1068 const sym_file = sym.getFile(macho_file).?;
1069 if (sym_file.getIndex() != self.index) continue;
1070
1071 const nlist_idx = @as(Symbol.Index, @intCast(i));
1072 const nlist = &self.symtab.items(.nlist)[nlist_idx];
1073 const nlist_atom = &self.symtab.items(.atom)[nlist_idx];
1074
1075 const atom_index = try macho_file.addAtom();
1076 try self.atoms.append(gpa, atom_index);
1077
1078 const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)});
1079 defer gpa.free(name);
1080 const atom = macho_file.getAtom(atom_index).?;
1081 atom.atom_index = atom_index;
1082 atom.name = try macho_file.string_intern.insert(gpa, name);
1083 atom.file = self.index;
1084 atom.size = nlist.n_value;
1085 atom.alignment = (nlist.n_desc >> 8) & 0x0f;
1086
1087 const n_sect = try self.addSection(gpa, "__DATA", "__common");
1088 const sect = &self.sections.items(.header)[n_sect];
1089 sect.flags = macho.S_ZEROFILL;
1090 sect.size = atom.size;
1091 sect.@"align" = atom.alignment;
1092 atom.n_sect = n_sect;
1093
1094 sym.value = 0;
1095 sym.atom = atom_index;
1096 sym.flags.weak = false;
1097 sym.flags.weak_ref = false;
1098 sym.flags.tentative = false;
1099 sym.visibility = .global;
1100
1101 nlist.n_value = 0;
1102 nlist.n_type = macho.N_EXT | macho.N_SECT;
1103 nlist.n_sect = 0;
1104 nlist.n_desc = 0;
1105 nlist_atom.* = atom_index;
683 }1106 }
1107}
6841108
685 const target = macho_file.base.comp.root_mod.resolved_target.result;1109fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 {
686 const cpu_arch = target.cpu.arch;1110 const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator)));
687 try self.parseRelocs(gpa, sect_id);1111 self.sections.set(n_sect, .{
688 const relocs = self.getRelocs(sect_id);1112 .header = .{
1113 .sectname = MachO.makeStaticString(sectname),
1114 .segname = MachO.makeStaticString(segname),
1115 },
1116 });
1117 return n_sect;
1118}
6891119
690 var it = self.getEhFrameRecordsIterator();1120pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
691 var record_count: u32 = 0;1121 const tracy = trace(@src());
692 while (try it.next()) |_| {1122 defer tracy.end();
693 record_count += 1;1123
1124 for (self.symbols.items) |sym_index| {
1125 const sym = macho_file.getSymbol(sym_index);
1126 const file = sym.getFile(macho_file) orelse continue;
1127 if (file.getIndex() != self.index) continue;
1128 if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
1129 if (sym.isSymbolStab(macho_file)) continue;
1130 const name = sym.getName(macho_file);
1131 // TODO in -r mode, we actually want to merge symbol names and emit only one
1132 // work it out when emitting relocs
1133 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l') and !macho_file.options.relocatable) continue;
1134 sym.flags.output_symtab = true;
1135 if (sym.isLocal()) {
1136 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
1137 self.output_symtab_ctx.nlocals += 1;
1138 } else if (sym.flags.@"export") {
1139 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
1140 self.output_symtab_ctx.nexports += 1;
1141 } else {
1142 assert(sym.flags.import);
1143 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
1144 self.output_symtab_ctx.nimports += 1;
1145 }
1146 self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
694 }1147 }
6951148
696 try self.eh_frame_relocs_lookup.ensureTotalCapacity(gpa, record_count);1149 if (!macho_file.options.strip and self.hasDebugInfo()) self.calcStabsSize(macho_file);
697 try self.eh_frame_records_lookup.ensureUnusedCapacity(gpa, record_count);1150}
6981151
699 it.reset();1152pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {
1153 if (self.dwarf_info) |dw| {
1154 // TODO handle multiple CUs
1155 const cu = dw.compile_units.items[0];
1156 const comp_dir = cu.getCompileDir(dw) orelse return;
1157 const tu_name = cu.getSourceFile(dw) orelse return;
7001158
701 while (try it.next()) |record| {1159 self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
702 const offset = it.pos - record.getSize();1160 self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir
703 const rel_pos: Entry = switch (cpu_arch) {1161 self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name
704 .aarch64 => filterRelocs(relocs, offset, offset + record.getSize()),1162
705 .x86_64 => .{},1163 if (self.archive) |path| {
706 else => unreachable,1164 self.output_symtab_ctx.strsize += @as(u32, @intCast(path.len + 1 + self.path.len + 1 + 1));
707 };1165 } else {
708 self.eh_frame_relocs_lookup.putAssumeCapacityNoClobber(offset, .{1166 self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1));
709 .dead = false,1167 }
710 .reloc = rel_pos,1168
711 });1169 for (self.symbols.items) |sym_index| {
7121170 const sym = macho_file.getSymbol(sym_index);
713 if (record.tag == .fde) {1171 const file = sym.getFile(macho_file) orelse continue;
714 const reloc_target = blk: {1172 if (file.getIndex() != self.index) continue;
715 switch (cpu_arch) {1173 if (!sym.flags.output_symtab) continue;
716 .aarch64 => {1174 if (macho_file.options.relocatable) {
717 assert(rel_pos.len > 0); // TODO convert to an error as the FDE eh frame is malformed1175 const name = sym.getName(macho_file);
718 // Find function symbol that this record describes1176 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
719 const rel = for (relocs[rel_pos.start..][0..rel_pos.len]) |rel| {1177 }
720 if (rel.r_address - @as(i32, @intCast(offset)) == 8 and1178 const sect = macho_file.sections.items(.header)[sym.out_n_sect];
721 @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)) == .ARM64_RELOC_UNSIGNED)1179 if (sect.isCode()) {
722 break rel;1180 self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM
723 } else unreachable;1181 } else if (sym.visibility == .global) {
724 const reloc_target = Atom.parseRelocTarget(macho_file, .{1182 self.output_symtab_ctx.nstabs += 1; // N_GSYM
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 }
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 {1183 } else {
751 // You would think that we are done but turns out that the compilers may use1184 self.output_symtab_ctx.nstabs += 1; // N_STSYM
752 // whichever symbol alias they want for a target symbol. This in particular1185 }
753 // very problematic when using Zig's @export feature to re-export symbols under1186 }
754 // additional names. For that reason, we need to ensure we record aliases here1187 } else {
755 // too so that we can tie them with their matching unwind records and vice versa.1188 assert(self.hasSymbolStabs());
756 const aliases = self.getSymbolAliases(reloc_target.sym_index);1189
757 var i: u32 = 0;1190 for (self.stab_files.items) |sf| {
758 while (i < aliases.len) : (i += 1) {1191 self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
759 const actual_target = SymbolWithLoc{1192 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getCompDir(self).len + 1)); // comp_dir
760 .sym_index = i + aliases.start,1193 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getTuName(self).len + 1)); // tu_name
761 .file = reloc_target.file,1194 self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getOsoPath(self).len + 1)); // path
762 };1195
763 log.debug("FDE at offset {x} tracks {s}", .{1196 for (sf.stabs.items) |stab| {
764 offset,1197 const sym = stab.getSymbol(macho_file) orelse continue;
765 macho_file.getSymbolName(actual_target),1198 const file = sym.getFile(macho_file).?;
766 });1199 if (file.getIndex() != self.index) continue;
767 try self.eh_frame_records_lookup.putNoClobber(gpa, actual_target, offset);1200 if (!sym.flags.output_symtab) continue;
768 }1201 const nstabs: u32 = switch (stab.tag) {
1202 .func => 4, // N_BNSYM, N_FUN, N_FUN, N_ENSYM
1203 .global => 1, // N_GSYM
1204 .static => 1, // N_STSYM
1205 };
1206 self.output_symtab_ctx.nstabs += nstabs;
769 }1207 }
770 }1208 }
771 }1209 }
772}1210}
7731211
774fn parseUnwindInfo(self: *Object, macho_file: *MachO, object_id: u32) !void {1212pub fn writeSymtab(self: Object, macho_file: *MachO) void {
775 const comp = macho_file.base.comp;1213 const tracy = trace(@src());
776 const gpa = comp.gpa;1214 defer tracy.end();
777 const target = macho_file.base.comp.root_mod.resolved_target.result;1215
778 const cpu_arch = target.cpu.arch;1216 for (self.symbols.items) |sym_index| {
779 const sect_id = self.unwind_info_sect_id orelse {1217 const sym = macho_file.getSymbol(sym_index);
780 // If it so happens that the object had `__eh_frame` section defined but no `__compact_unwind`,1218 const file = sym.getFile(macho_file) orelse continue;
781 // we will try fully synthesising unwind info records to somewhat match Apple ld's1219 if (file.getIndex() != self.index) continue;
782 // approach. However, we will only synthesise DWARF records and nothing more. For this reason,1220 const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
783 // we still create the output `__TEXT,__unwind_info` section.1221 const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
784 if (self.hasEhFrameRecords()) {1222 macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
785 if (macho_file.unwind_info_section_index == null) {1223 macho_file.strtab.appendAssumeCapacity(0);
786 macho_file.unwind_info_section_index = try macho_file.initSection(1224 const out_sym = &macho_file.symtab.items[idx];
787 "__TEXT",1225 out_sym.n_strx = n_strx;
788 "__unwind_info",1226 sym.setOutputSym(macho_file, out_sym);
789 .{},1227 }
790 );1228
1229 if (!macho_file.options.strip and self.hasDebugInfo()) self.writeStabs(macho_file);
1230}
1231
1232pub fn writeStabs(self: *const Object, macho_file: *MachO) void {
1233 const writeFuncStab = struct {
1234 inline fn writeFuncStab(
1235 n_strx: u32,
1236 n_sect: u8,
1237 n_value: u64,
1238 size: u64,
1239 index: u32,
1240 ctx: *MachO,
1241 ) void {
1242 ctx.symtab.items[index] = .{
1243 .n_strx = 0,
1244 .n_type = macho.N_BNSYM,
1245 .n_sect = n_sect,
1246 .n_desc = 0,
1247 .n_value = n_value,
1248 };
1249 ctx.symtab.items[index + 1] = .{
1250 .n_strx = n_strx,
1251 .n_type = macho.N_FUN,
1252 .n_sect = n_sect,
1253 .n_desc = 0,
1254 .n_value = n_value,
1255 };
1256 ctx.symtab.items[index + 2] = .{
1257 .n_strx = 0,
1258 .n_type = macho.N_FUN,
1259 .n_sect = 0,
1260 .n_desc = 0,
1261 .n_value = size,
1262 };
1263 ctx.symtab.items[index + 3] = .{
1264 .n_strx = 0,
1265 .n_type = macho.N_ENSYM,
1266 .n_sect = n_sect,
1267 .n_desc = 0,
1268 .n_value = size,
1269 };
1270 }
1271 }.writeFuncStab;
1272
1273 var index = self.output_symtab_ctx.istab;
1274
1275 if (self.dwarf_info) |dw| {
1276 // TODO handle multiple CUs
1277 const cu = dw.compile_units.items[0];
1278 const comp_dir = cu.getCompileDir(dw) orelse return;
1279 const tu_name = cu.getSourceFile(dw) orelse return;
1280
1281 // Open scope
1282 // N_SO comp_dir
1283 var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1284 macho_file.strtab.appendSliceAssumeCapacity(comp_dir);
1285 macho_file.strtab.appendAssumeCapacity(0);
1286 macho_file.symtab.items[index] = .{
1287 .n_strx = n_strx,
1288 .n_type = macho.N_SO,
1289 .n_sect = 0,
1290 .n_desc = 0,
1291 .n_value = 0,
1292 };
1293 index += 1;
1294 // N_SO tu_name
1295 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1296 macho_file.strtab.appendSliceAssumeCapacity(tu_name);
1297 macho_file.strtab.appendAssumeCapacity(0);
1298 macho_file.symtab.items[index] = .{
1299 .n_strx = n_strx,
1300 .n_type = macho.N_SO,
1301 .n_sect = 0,
1302 .n_desc = 0,
1303 .n_value = 0,
1304 };
1305 index += 1;
1306 // N_OSO path
1307 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1308 if (self.archive) |path| {
1309 macho_file.strtab.appendSliceAssumeCapacity(path);
1310 macho_file.strtab.appendAssumeCapacity('(');
1311 macho_file.strtab.appendSliceAssumeCapacity(self.path);
1312 macho_file.strtab.appendAssumeCapacity(')');
1313 macho_file.strtab.appendAssumeCapacity(0);
1314 } else {
1315 macho_file.strtab.appendSliceAssumeCapacity(self.path);
1316 macho_file.strtab.appendAssumeCapacity(0);
1317 }
1318 macho_file.symtab.items[index] = .{
1319 .n_strx = n_strx,
1320 .n_type = macho.N_OSO,
1321 .n_sect = 0,
1322 .n_desc = 1,
1323 .n_value = self.mtime,
1324 };
1325 index += 1;
1326
1327 for (self.symbols.items) |sym_index| {
1328 const sym = macho_file.getSymbol(sym_index);
1329 const file = sym.getFile(macho_file) orelse continue;
1330 if (file.getIndex() != self.index) continue;
1331 if (!sym.flags.output_symtab) continue;
1332 if (macho_file.options.relocatable) {
1333 const name = sym.getName(macho_file);
1334 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
1335 }
1336 const sect = macho_file.sections.items(.header)[sym.out_n_sect];
1337 const sym_n_strx = n_strx: {
1338 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
1339 const osym = macho_file.symtab.items[symtab_index];
1340 break :n_strx osym.n_strx;
1341 };
1342 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
1343 const sym_n_value = sym.getAddress(.{}, macho_file);
1344 const sym_size = sym.getSize(macho_file);
1345 if (sect.isCode()) {
1346 writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
1347 index += 4;
1348 } else if (sym.visibility == .global) {
1349 macho_file.symtab.items[index] = .{
1350 .n_strx = sym_n_strx,
1351 .n_type = macho.N_GSYM,
1352 .n_sect = sym_n_sect,
1353 .n_desc = 0,
1354 .n_value = 0,
1355 };
1356 index += 1;
1357 } else {
1358 macho_file.symtab.items[index] = .{
1359 .n_strx = sym_n_strx,
1360 .n_type = macho.N_STSYM,
1361 .n_sect = sym_n_sect,
1362 .n_desc = 0,
1363 .n_value = sym_n_value,
1364 };
1365 index += 1;
791 }1366 }
792 }1367 }
793 return;
794 };
7951368
796 log.debug("parsing unwind info in {s}", .{self.name});1369 // Close scope
1370 // N_SO
1371 macho_file.symtab.items[index] = .{
1372 .n_strx = 0,
1373 .n_type = macho.N_SO,
1374 .n_sect = 0,
1375 .n_desc = 0,
1376 .n_value = 0,
1377 };
1378 } else {
1379 assert(self.hasSymbolStabs());
1380
1381 for (self.stab_files.items) |sf| {
1382 // Open scope
1383 // N_SO comp_dir
1384 var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1385 macho_file.strtab.appendSliceAssumeCapacity(sf.getCompDir(self));
1386 macho_file.strtab.appendAssumeCapacity(0);
1387 macho_file.symtab.items[index] = .{
1388 .n_strx = n_strx,
1389 .n_type = macho.N_SO,
1390 .n_sect = 0,
1391 .n_desc = 0,
1392 .n_value = 0,
1393 };
1394 index += 1;
1395 // N_SO tu_name
1396 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1397 macho_file.strtab.appendSliceAssumeCapacity(sf.getTuName(self));
1398 macho_file.strtab.appendAssumeCapacity(0);
1399 macho_file.symtab.items[index] = .{
1400 .n_strx = n_strx,
1401 .n_type = macho.N_SO,
1402 .n_sect = 0,
1403 .n_desc = 0,
1404 .n_value = 0,
1405 };
1406 index += 1;
1407 // N_OSO path
1408 n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
1409 macho_file.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self));
1410 macho_file.strtab.appendAssumeCapacity(0);
1411 macho_file.symtab.items[index] = .{
1412 .n_strx = n_strx,
1413 .n_type = macho.N_OSO,
1414 .n_sect = 0,
1415 .n_desc = 1,
1416 .n_value = sf.getOsoModTime(self),
1417 };
1418 index += 1;
1419
1420 for (sf.stabs.items) |stab| {
1421 const sym = stab.getSymbol(macho_file) orelse continue;
1422 const file = sym.getFile(macho_file).?;
1423 if (file.getIndex() != self.index) continue;
1424 if (!sym.flags.output_symtab) continue;
1425 const sym_n_strx = n_strx: {
1426 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
1427 const osym = macho_file.symtab.items[symtab_index];
1428 break :n_strx osym.n_strx;
1429 };
1430 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
1431 const sym_n_value = sym.getAddress(.{}, macho_file);
1432 const sym_size = sym.getSize(macho_file);
1433 switch (stab.tag) {
1434 .func => {
1435 writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
1436 index += 4;
1437 },
1438 .global => {
1439 macho_file.symtab.items[index] = .{
1440 .n_strx = sym_n_strx,
1441 .n_type = macho.N_GSYM,
1442 .n_sect = sym_n_sect,
1443 .n_desc = 0,
1444 .n_value = 0,
1445 };
1446 index += 1;
1447 },
1448 .static => {
1449 macho_file.symtab.items[index] = .{
1450 .n_strx = sym_n_strx,
1451 .n_type = macho.N_STSYM,
1452 .n_sect = sym_n_sect,
1453 .n_desc = 0,
1454 .n_value = sym_n_value,
1455 };
1456 index += 1;
1457 },
1458 }
1459 }
7971460
798 if (macho_file.unwind_info_section_index == null) {1461 // Close scope
799 macho_file.unwind_info_section_index = try macho_file.initSection("__TEXT", "__unwind_info", .{});1462 // N_SO
1463 macho_file.symtab.items[index] = .{
1464 .n_strx = 0,
1465 .n_type = macho.N_SO,
1466 .n_sect = 0,
1467 .n_desc = 0,
1468 .n_value = 0,
1469 };
1470 index += 1;
1471 }
800 }1472 }
1473}
8011474
802 const unwind_records = self.getUnwindRecords();1475fn getLoadCommand(self: Object, lc: macho.LC) ?LoadCommandIterator.LoadCommand {
8031476 var it = LoadCommandIterator{
804 try self.unwind_records_lookup.ensureUnusedCapacity(gpa, @as(u32, @intCast(unwind_records.len)));1477 .ncmds = self.header.?.ncmds,
1478 .buffer = self.data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds],
1479 };
1480 while (it.next()) |cmd| {
1481 if (cmd.cmd() == lc) return cmd;
1482 } else return null;
1483}
8051484
806 const needs_eh_frame = for (unwind_records) |record| {1485pub fn getSectionData(self: *const Object, index: u32) []const u8 {
807 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;1486 const slice = self.sections.slice();
808 } else false;1487 assert(index < slice.items(.header).len);
1488 const sect = slice.items(.header)[index];
1489 return self.data[sect.offset..][0..sect.size];
1490}
8091491
810 if (needs_eh_frame and !self.hasEhFrameRecords()) return error.MissingEhFrameSection;1492fn getString(self: Object, off: u32) [:0]const u8 {
1493 assert(off < self.strtab.len);
1494 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
1495}
8111496
812 try self.parseRelocs(gpa, sect_id);1497/// TODO handle multiple CUs
813 const relocs = self.getRelocs(sect_id);1498pub fn hasDebugInfo(self: Object) bool {
1499 if (self.dwarf_info) |dw| {
1500 return dw.compile_units.items.len > 0;
1501 }
1502 return self.hasSymbolStabs();
1503}
8141504
815 for (unwind_records, 0..) |record, record_id| {1505fn hasSymbolStabs(self: Object) bool {
816 const offset = record_id * @sizeOf(macho.compact_unwind_entry);1506 return self.stab_files.items.len > 0;
817 const rel_pos = filterRelocs(1507}
818 relocs,
819 offset,
820 offset + @sizeOf(macho.compact_unwind_entry),
821 );
822 assert(rel_pos.len > 0); // TODO convert to an error as the unwind info is malformed
823 self.unwind_relocs_lookup[record_id] = .{
824 .dead = false,
825 .reloc = rel_pos,
826 };
8271508
828 // Find function symbol that this record describes1509pub fn hasObjc(self: Object) bool {
829 const rel = relocs[rel_pos.start..][rel_pos.len - 1];1510 for (self.symtab.items(.nlist)) |nlist| {
830 const reloc_target = Atom.parseRelocTarget(macho_file, .{1511 const name = self.getString(nlist.n_strx);
831 .object_id = object_id,1512 if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true;
832 .rel = rel,1513 }
833 .code = mem.asBytes(&record),1514 for (self.sections.items(.header)) |sect| {
834 .base_offset = @as(i32, @intCast(offset)),1515 if (mem.eql(u8, sect.segName(), "__DATA") and mem.eql(u8, sect.sectName(), "__objc_catlist")) return true;
835 });1516 if (mem.eql(u8, sect.segName(), "__TEXT") and mem.eql(u8, sect.sectName(), "__swift")) return true;
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 }
858 }
859 }1517 }
1518 return false;
860}1519}
8611520
862pub fn getSourceSymbol(self: Object, index: u32) ?macho.nlist_64 {1521pub fn getDataInCode(self: Object) []align(1) const macho.data_in_code_entry {
863 const symtab = self.in_symtab.?;1522 const lc = self.getLoadCommand(.DATA_IN_CODE) orelse return &[0]macho.data_in_code_entry{};
864 if (index >= symtab.len) return null;1523 const cmd = lc.cast(macho.linkedit_data_command).?;
865 const mapped_index = self.source_symtab_lookup[index];1524 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
866 return symtab[mapped_index];1525 const dice = @as(
1526 [*]align(1) const macho.data_in_code_entry,
1527 @ptrCast(self.data.ptr + cmd.dataoff),
1528 )[0..ndice];
1529 return dice;
867}1530}
8681531
869pub fn getSourceSection(self: Object, index: u8) macho.section_64 {1532pub inline fn hasSubsections(self: Object) bool {
870 const sections = self.getSourceSections();1533 return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
871 assert(index < sections.len);
872 return sections[index];
873}1534}
8741535
875pub fn getSourceSectionByName(self: Object, segname: []const u8, sectname: []const u8) ?macho.section_64 {1536pub fn asFile(self: *Object) File {
876 const index = self.getSourceSectionIndexByName(segname, sectname) orelse return null;1537 return .{ .object = self };
877 const sections = self.getSourceSections();
878 return sections[index];
879}1538}
8801539
881pub fn getSourceSectionIndexByName(self: Object, segname: []const u8, sectname: []const u8) ?u8 {1540pub fn format(
882 const sections = self.getSourceSections();1541 self: *Object,
883 for (sections, 0..) |sect, i| {1542 comptime unused_fmt_string: []const u8,
884 if (mem.eql(u8, segname, sect.segName()) and mem.eql(u8, sectname, sect.sectName()))1543 options: std.fmt.FormatOptions,
885 return @as(u8, @intCast(i));1544 writer: anytype,
886 } else return null;1545) !void {
1546 _ = self;
1547 _ = unused_fmt_string;
1548 _ = options;
1549 _ = writer;
1550 @compileError("do not format objects directly");
887}1551}
8881552
889pub fn getSourceSections(self: Object) []align(1) const macho.section_64 {1553const FormatContext = struct {
890 var it = LoadCommandIterator{1554 object: *Object,
891 .ncmds = self.header.ncmds,1555 macho_file: *MachO,
892 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],1556};
893 };
894 while (it.next()) |cmd| switch (cmd.cmd()) {
895 .SEGMENT_64 => {
896 return cmd.getSections();
897 },
898 else => {},
899 } else unreachable;
900}
9011557
902pub fn parseDataInCode(self: *Object, gpa: Allocator) !void {1558pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatAtoms) {
903 var it = LoadCommandIterator{1559 return .{ .data = .{
904 .ncmds = self.header.ncmds,1560 .object = self,
905 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],1561 .macho_file = macho_file,
906 };1562 } };
907 const cmd = while (it.next()) |cmd| {
908 switch (cmd.cmd()) {
909 .DATA_IN_CODE => break cmd.cast(macho.linkedit_data_command).?,
910 else => {},
911 }
912 } else return;
913 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
914 const dice = @as([*]align(1) const macho.data_in_code_entry, @ptrCast(self.contents.ptr + cmd.dataoff))[0..ndice];
915 try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len);
916 self.data_in_code.appendUnalignedSliceAssumeCapacity(dice);
917 mem.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan);
918}1563}
9191564
920fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool {1565fn formatAtoms(
921 _ = ctx;1566 ctx: FormatContext,
922 return lhs.offset < rhs.offset;1567 comptime unused_fmt_string: []const u8,
1568 options: std.fmt.FormatOptions,
1569 writer: anytype,
1570) !void {
1571 _ = unused_fmt_string;
1572 _ = options;
1573 const object = ctx.object;
1574 try writer.writeAll(" atoms\n");
1575 for (object.atoms.items) |atom_index| {
1576 const atom = ctx.macho_file.getAtom(atom_index).?;
1577 try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
1578 }
923}1579}
9241580
925fn getDysymtab(self: Object) ?macho.dysymtab_command {1581pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatCies) {
926 var it = LoadCommandIterator{1582 return .{ .data = .{
927 .ncmds = self.header.ncmds,1583 .object = self,
928 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],1584 .macho_file = macho_file,
929 };1585 } };
930 while (it.next()) |cmd| {
931 switch (cmd.cmd()) {
932 .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?,
933 else => {},
934 }
935 } else return null;
936}1586}
9371587
938pub fn parseDwarfInfo(self: Object) DwarfInfo {1588fn formatCies(
939 var di = DwarfInfo{1589 ctx: FormatContext,
940 .debug_info = &[0]u8{},1590 comptime unused_fmt_string: []const u8,
941 .debug_abbrev = &[0]u8{},1591 options: std.fmt.FormatOptions,
942 .debug_str = &[0]u8{},1592 writer: anytype,
943 };1593) !void {
944 for (self.getSourceSections()) |sect| {1594 _ = unused_fmt_string;
945 if (!sect.isDebug()) continue;1595 _ = options;
946 const sectname = sect.sectName();1596 const object = ctx.object;
947 if (mem.eql(u8, sectname, "__debug_info")) {1597 try writer.writeAll(" cies\n");
948 di.debug_info = self.getSectionContents(sect);1598 for (object.cies.items, 0..) |cie, i| {
949 } else if (mem.eql(u8, sectname, "__debug_abbrev")) {1599 try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.macho_file) });
950 di.debug_abbrev = self.getSectionContents(sect);
951 } else if (mem.eql(u8, sectname, "__debug_str")) {
952 di.debug_str = self.getSectionContents(sect);
953 }
954 }1600 }
955 return di;
956}1601}
9571602
958/// Returns Platform composed from the first encountered build version type load command:1603pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatFdes) {
959/// either LC_BUILD_VERSION or LC_VERSION_MIN_*.1604 return .{ .data = .{
960pub fn getPlatform(self: Object) ?Platform {1605 .object = self,
961 var it = LoadCommandIterator{1606 .macho_file = macho_file,
962 .ncmds = self.header.ncmds,1607 } };
963 .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds],
964 };
965 while (it.next()) |cmd| {
966 switch (cmd.cmd()) {
967 .BUILD_VERSION,
968 .VERSION_MIN_MACOSX,
969 .VERSION_MIN_IPHONEOS,
970 .VERSION_MIN_TVOS,
971 .VERSION_MIN_WATCHOS,
972 => return Platform.fromLoadCommand(cmd),
973 else => {},
974 }
975 } else return null;
976}1608}
9771609
978pub fn getSectionContents(self: Object, sect: macho.section_64) []const u8 {1610fn formatFdes(
979 const size = @as(usize, @intCast(sect.size));1611 ctx: FormatContext,
980 return self.contents[sect.offset..][0..size];1612 comptime unused_fmt_string: []const u8,
1613 options: std.fmt.FormatOptions,
1614 writer: anytype,
1615) !void {
1616 _ = unused_fmt_string;
1617 _ = options;
1618 const object = ctx.object;
1619 try writer.writeAll(" fdes\n");
1620 for (object.fdes.items, 0..) |fde, i| {
1621 try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.macho_file) });
1622 }
981}1623}
9821624
983pub fn getSectionAliasSymbolIndex(self: Object, sect_id: u8) u32 {1625pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatUnwindRecords) {
984 const start = @as(u32, @intCast(self.in_symtab.?.len));1626 return .{ .data = .{
985 return start + sect_id;1627 .object = self,
1628 .macho_file = macho_file,
1629 } };
986}1630}
9871631
988pub fn getSectionAliasSymbol(self: *Object, sect_id: u8) macho.nlist_64 {1632fn formatUnwindRecords(
989 return self.symtab[self.getSectionAliasSymbolIndex(sect_id)];1633 ctx: FormatContext,
1634 comptime unused_fmt_string: []const u8,
1635 options: std.fmt.FormatOptions,
1636 writer: anytype,
1637) !void {
1638 _ = unused_fmt_string;
1639 _ = options;
1640 const object = ctx.object;
1641 const macho_file = ctx.macho_file;
1642 try writer.writeAll(" unwind records\n");
1643 for (object.unwind_records.items) |rec| {
1644 try writer.print(" rec({d}) : {}\n", .{ rec, macho_file.getUnwindRecord(rec).fmt(macho_file) });
1645 }
990}1646}
9911647
992pub fn getSectionAliasSymbolPtr(self: *Object, sect_id: u8) *macho.nlist_64 {1648pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Formatter(formatSymtab) {
993 return &self.symtab[self.getSectionAliasSymbolIndex(sect_id)];1649 return .{ .data = .{
1650 .object = self,
1651 .macho_file = macho_file,
1652 } };
994}1653}
9951654
996fn getSourceRelocs(self: Object, sect: macho.section_64) ?[]align(1) const macho.relocation_info {1655fn formatSymtab(
997 if (sect.nreloc == 0) return null;1656 ctx: FormatContext,
998 return @as([*]align(1) const macho.relocation_info, @ptrCast(self.contents.ptr + sect.reloff))[0..sect.nreloc];1657 comptime unused_fmt_string: []const u8,
1658 options: std.fmt.FormatOptions,
1659 writer: anytype,
1660) !void {
1661 _ = unused_fmt_string;
1662 _ = options;
1663 const object = ctx.object;
1664 try writer.writeAll(" symbols\n");
1665 for (object.symbols.items) |index| {
1666 const sym = ctx.macho_file.getSymbol(index);
1667 try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)});
1668 }
999}1669}
10001670
1001pub fn getRelocs(self: Object, sect_id: u8) []const macho.relocation_info {1671pub fn fmtPath(self: Object) std.fmt.Formatter(formatPath) {
1002 const sect = self.getSourceSection(sect_id);1672 return .{ .data = self };
1003 const start = self.section_relocs_lookup.items[sect_id];
1004 const len = sect.nreloc;
1005 return self.relocations.items[start..][0..len];
1006}1673}
10071674
1008pub fn getSymbolName(self: Object, index: u32) []const u8 {1675fn formatPath(
1009 const strtab = self.in_strtab.?;1676 object: Object,
1010 const sym = self.symtab[index];1677 comptime unused_fmt_string: []const u8,
1678 options: std.fmt.FormatOptions,
1679 writer: anytype,
1680) !void {
1681 _ = unused_fmt_string;
1682 _ = options;
1683 if (object.archive) |path| {
1684 try writer.writeAll(path);
1685 try writer.writeByte('(');
1686 try writer.writeAll(object.path);
1687 try writer.writeByte(')');
1688 } else try writer.writeAll(object.path);
1689}
1690
1691const Section = struct {
1692 header: macho.section_64,
1693 subsections: std.ArrayListUnmanaged(Subsection) = .{},
1694 relocs: std.ArrayListUnmanaged(Relocation) = .{},
1695};
1696
1697const Subsection = struct {
1698 atom: Atom.Index,
1699 off: u64,
1700};
1701
1702const Nlist = struct {
1703 nlist: macho.nlist_64,
1704 size: u64,
1705 atom: Atom.Index,
1706};
1707
1708const StabFile = struct {
1709 comp_dir: u32,
1710 stabs: std.ArrayListUnmanaged(Stab) = .{},
10111711
1012 if (self.getSourceSymbol(index) == null) {1712 fn getCompDir(sf: StabFile, object: *const Object) [:0]const u8 {
1013 assert(sym.n_strx == 0);1713 const nlist = object.symtab.items(.nlist)[sf.comp_dir];
1014 return "";1714 return object.getString(nlist.n_strx);
1015 }1715 }
10161716
1017 const start = sym.n_strx;1717 fn getTuName(sf: StabFile, object: *const Object) [:0]const u8 {
1018 const len = self.strtab_lookup[index];1718 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1];
1719 return object.getString(nlist.n_strx);
1720 }
10191721
1020 return strtab[start..][0 .. len - 1 :0];1722 fn getOsoPath(sf: StabFile, object: *const Object) [:0]const u8 {
1021}1723 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
1724 return object.getString(nlist.n_strx);
1725 }
10221726
1023fn getSymbolAliases(self: Object, index: u32) Entry {1727 fn getOsoModTime(sf: StabFile, object: *const Object) u64 {
1024 const addr = self.source_address_lookup[index];1728 const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
1025 var start = index;1729 return nlist.n_value;
1026 while (start > 0 and1730 }
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}
10341731
1035pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {1732 const Stab = struct {
1036 // Find containing atom1733 tag: enum { func, global, static } = .func,
1037 const Predicate = struct {1734 symbol: ?Symbol.Index = null,
1038 addr: i64,
10391735
1040 pub fn predicate(pred: @This(), other: i64) bool {1736 fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol {
1041 return if (other == -1) true else other > pred.addr;1737 return if (stab.symbol) |s| macho_file.getSymbol(s) else null;
1042 }1738 }
1043 };1739 };
1740};
10441741
1045 if (sect_hint) |sect_id| {1742const x86_64 = struct {
1046 if (self.source_section_index_lookup[sect_id].len > 0) {1743 fn parseRelocs(
1047 const lookup = self.source_section_index_lookup[sect_id];1744 self: *const Object,
1048 const target_sym_index = MachO.lsearch(1745 n_sect: u8,
1049 i64,1746 sect: macho.section_64,
1050 self.source_address_lookup[lookup.start..][0..lookup.len],1747 out: *std.ArrayListUnmanaged(Relocation),
1051 Predicate{ .addr = @as(i64, @intCast(addr)) },1748 macho_file: *MachO,
1052 );1749 ) !void {
1053 if (target_sym_index > 0) {1750 const gpa = macho_file.base.allocator;
1054 // Hone in on the most senior alias of the target symbol.1751
1055 // See SymbolAtIndex.lessThan for more context.1752 const relocs = @as(
1056 const aliases = self.getSymbolAliases(@intCast(lookup.start + target_sym_index - 1));1753 [*]align(1) const macho.relocation_info,
1057 return aliases.start;1754 @ptrCast(self.data.ptr + sect.reloff),
1058 }1755 )[0..sect.nreloc];
1059 }1756 const code = self.getSectionData(@intCast(n_sect));
1060 return self.getSectionAliasSymbolIndex(sect_id);1757
1061 }1758 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
1759
1760 var i: usize = 0;
1761 while (i < relocs.len) : (i += 1) {
1762 const rel = relocs[i];
1763 const rel_type: macho.reloc_type_x86_64 = @enumFromInt(rel.r_type);
1764 const rel_offset = @as(u32, @intCast(rel.r_address));
1765
1766 var addend = switch (rel.r_length) {
1767 0 => code[rel_offset],
1768 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
1769 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
1770 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
1771 };
1772 addend += switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
1773 .X86_64_RELOC_SIGNED_1 => 1,
1774 .X86_64_RELOC_SIGNED_2 => 2,
1775 .X86_64_RELOC_SIGNED_4 => 4,
1776 else => 0,
1777 };
10621778
1063 const target_sym_index = MachO.lsearch(i64, self.source_address_lookup, Predicate{1779 const target = if (rel.r_extern == 0) blk: {
1064 .addr = @as(i64, @intCast(addr)),1780 const nsect = rel.r_symbolnum - 1;
1065 });1781 const taddr: i64 = if (rel.r_pcrel == 1)
1066 assert(target_sym_index > 0);1782 @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4
1067 return @as(u32, @intCast(target_sym_index - 1));1783 else
1068}1784 addend;
1785 const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
1786 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
1787 self.fmtPath(), sect.segName(), sect.sectName(), rel.r_address,
1788 });
1789 return error.ParseFailed;
1790 };
1791 addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
1792 break :blk target;
1793 } else self.symbols.items[rel.r_symbolnum];
1794
1795 const has_subtractor = if (i > 0 and
1796 @as(macho.reloc_type_x86_64, @enumFromInt(relocs[i - 1].r_type)) == .X86_64_RELOC_SUBTRACTOR)
1797 blk: {
1798 if (rel_type != .X86_64_RELOC_UNSIGNED) {
1799 macho_file.base.fatal("{}: {s},{s}: 0x{x}: X86_64_RELOC_SUBTRACTOR followed by {s}", .{
1800 self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
1801 });
1802 return error.ParseFailed;
1803 }
1804 break :blk true;
1805 } else false;
1806
1807 const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
1808 switch (err) {
1809 error.Pcrel => macho_file.base.fatal(
1810 "{}: {s},{s}: 0x{x}: PC-relative {s} relocation",
1811 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1812 ),
1813 error.NonPcrel => macho_file.base.fatal(
1814 "{}: {s},{s}: 0x{x}: non-PC-relative {s} relocation",
1815 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1816 ),
1817 error.InvalidLength => macho_file.base.fatal(
1818 "{}: {s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
1819 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
1820 ),
1821 error.NonExtern => macho_file.base.fatal(
1822 "{}: {s},{s}: 0x{x}: non-extern target in {s} relocation",
1823 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1824 ),
1825 }
1826 return error.ParseFailed;
1827 };
10691828
1070pub fn getGlobal(self: Object, sym_index: u32) ?u32 {1829 out.appendAssumeCapacity(.{
1071 if (self.globals_lookup[sym_index] == -1) return null;1830 .tag = if (rel.r_extern == 1) .@"extern" else .local,
1072 return @as(u32, @intCast(self.globals_lookup[sym_index]));1831 .offset = @as(u32, @intCast(rel.r_address)),
1073}1832 .target = target,
1833 .addend = addend,
1834 .type = @"type",
1835 .meta = .{
1836 .pcrel = rel.r_pcrel == 1,
1837 .has_subtractor = has_subtractor,
1838 .length = rel.r_length,
1839 .symbolnum = rel.r_symbolnum,
1840 },
1841 });
1842 }
1843 }
10741844
1075pub fn getAtomIndexForSymbol(self: Object, sym_index: u32) ?Atom.Index {1845 fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_x86_64) !Relocation.Type {
1076 return self.atom_by_index_table[sym_index];1846 switch (rel_type) {
1077}1847 .X86_64_RELOC_UNSIGNED => {
1848 if (rel.r_pcrel == 1) return error.Pcrel;
1849 if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
1850 return .unsigned;
1851 },
1852
1853 .X86_64_RELOC_SUBTRACTOR => {
1854 if (rel.r_pcrel == 1) return error.Pcrel;
1855 return .subtractor;
1856 },
1857
1858 .X86_64_RELOC_BRANCH,
1859 .X86_64_RELOC_GOT_LOAD,
1860 .X86_64_RELOC_GOT,
1861 .X86_64_RELOC_TLV,
1862 => {
1863 if (rel.r_pcrel == 0) return error.NonPcrel;
1864 if (rel.r_length != 2) return error.InvalidLength;
1865 if (rel.r_extern == 0) return error.NonExtern;
1866 return switch (rel_type) {
1867 .X86_64_RELOC_BRANCH => .branch,
1868 .X86_64_RELOC_GOT_LOAD => .got_load,
1869 .X86_64_RELOC_GOT => .got,
1870 .X86_64_RELOC_TLV => .tlv,
1871 else => unreachable,
1872 };
1873 },
1874
1875 .X86_64_RELOC_SIGNED,
1876 .X86_64_RELOC_SIGNED_1,
1877 .X86_64_RELOC_SIGNED_2,
1878 .X86_64_RELOC_SIGNED_4,
1879 => {
1880 if (rel.r_pcrel == 0) return error.NonPcrel;
1881 if (rel.r_length != 2) return error.InvalidLength;
1882 return switch (rel_type) {
1883 .X86_64_RELOC_SIGNED => .signed,
1884 .X86_64_RELOC_SIGNED_1 => .signed1,
1885 .X86_64_RELOC_SIGNED_2 => .signed2,
1886 .X86_64_RELOC_SIGNED_4 => .signed4,
1887 else => unreachable,
1888 };
1889 },
1890 }
1891 }
1892};
10781893
1079pub fn hasUnwindRecords(self: Object) bool {1894const aarch64 = struct {
1080 return self.unwind_info_sect_id != null;1895 fn parseRelocs(
1081}1896 self: *const Object,
1897 n_sect: u8,
1898 sect: macho.section_64,
1899 out: *std.ArrayListUnmanaged(Relocation),
1900 macho_file: *MachO,
1901 ) !void {
1902 const gpa = macho_file.base.allocator;
1903
1904 const relocs = @as(
1905 [*]align(1) const macho.relocation_info,
1906 @ptrCast(self.data.ptr + sect.reloff),
1907 )[0..sect.nreloc];
1908 const code = self.getSectionData(@intCast(n_sect));
1909
1910 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
1911
1912 var i: usize = 0;
1913 while (i < relocs.len) : (i += 1) {
1914 var rel = relocs[i];
1915 const rel_offset = @as(u32, @intCast(rel.r_address));
1916
1917 var addend: i64 = 0;
1918
1919 switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
1920 .ARM64_RELOC_ADDEND => {
1921 addend = rel.r_symbolnum;
1922 i += 1;
1923 if (i >= relocs.len) {
1924 macho_file.base.fatal("{}: {s},{s}: 0x{x}: unterminated ARM64_RELOC_ADDEND", .{
1925 self.fmtPath(), sect.segName(), sect.sectName(), rel_offset,
1926 });
1927 return error.ParseFailed;
1928 }
1929 rel = relocs[i];
1930 switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
1931 .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
1932 else => |x| {
1933 macho_file.base.fatal(
1934 "{}: {s},{s}: 0x{x}: ARM64_RELOC_ADDEND followed by {s}",
1935 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(x) },
1936 );
1937 return error.ParseFailed;
1938 },
1939 }
1940 },
1941 .ARM64_RELOC_UNSIGNED => {
1942 addend = switch (rel.r_length) {
1943 0 => code[rel_offset],
1944 1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
1945 2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
1946 3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
1947 };
1948 },
1949 else => {},
1950 }
10821951
1083pub fn getUnwindRecords(self: Object) []align(1) const macho.compact_unwind_entry {1952 const rel_type: macho.reloc_type_arm64 = @enumFromInt(rel.r_type);
1084 const sect_id = self.unwind_info_sect_id orelse return &[0]macho.compact_unwind_entry{};
1085 const sect = self.getSourceSection(sect_id);
1086 const data = self.getSectionContents(sect);
1087 const num_entries = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
1088 return @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data))[0..num_entries];
1089}
10901953
1091pub fn hasEhFrameRecords(self: Object) bool {1954 const target = if (rel.r_extern == 0) blk: {
1092 return self.eh_frame_sect_id != null;1955 const nsect = rel.r_symbolnum - 1;
1093}1956 const taddr: i64 = if (rel.r_pcrel == 1)
1957 @as(i64, @intCast(sect.addr)) + rel.r_address + addend
1958 else
1959 addend;
1960 const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
1961 macho_file.base.fatal("{}: {s},{s}: 0x{x}: bad relocation", .{
1962 self.fmtPath(), sect.segName(), sect.sectName(), rel.r_address,
1963 });
1964 return error.ParseFailed;
1965 };
1966 addend = taddr - @as(i64, @intCast(macho_file.getAtom(target).?.getInputAddress(macho_file)));
1967 break :blk target;
1968 } else self.symbols.items[rel.r_symbolnum];
1969
1970 const has_subtractor = if (i > 0 and
1971 @as(macho.reloc_type_arm64, @enumFromInt(relocs[i - 1].r_type)) == .ARM64_RELOC_SUBTRACTOR)
1972 blk: {
1973 if (rel_type != .ARM64_RELOC_UNSIGNED) {
1974 macho_file.base.fatal("{}: {s},{s}: 0x{x}: ARM64_RELOC_SUBTRACTOR followed by {s}", .{
1975 self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
1976 });
1977 return error.ParseFailed;
1978 }
1979 break :blk true;
1980 } else false;
1981
1982 const @"type": Relocation.Type = validateRelocType(rel, rel_type) catch |err| {
1983 switch (err) {
1984 error.Pcrel => macho_file.base.fatal(
1985 "{}: {s},{s}: 0x{x}: PC-relative {s} relocation",
1986 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1987 ),
1988 error.NonPcrel => macho_file.base.fatal(
1989 "{}: {s},{s}: 0x{x}: non-PC-relative {s} relocation",
1990 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1991 ),
1992 error.InvalidLength => macho_file.base.fatal(
1993 "{}: {s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
1994 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
1995 ),
1996 error.NonExtern => macho_file.base.fatal(
1997 "{}: {s},{s}: 0x{x}: non-extern target in {s} relocation",
1998 .{ self.fmtPath(), sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
1999 ),
2000 }
2001 return error.ParseFailed;
2002 };
10942003
1095pub fn getEhFrameRecordsIterator(self: Object) eh_frame.Iterator {2004 out.appendAssumeCapacity(.{
1096 const sect_id = self.eh_frame_sect_id orelse return .{ .data = &[0]u8{} };2005 .tag = if (rel.r_extern == 1) .@"extern" else .local,
1097 const sect = self.getSourceSection(sect_id);2006 .offset = @as(u32, @intCast(rel.r_address)),
1098 const data = self.getSectionContents(sect);2007 .target = target,
1099 return .{ .data = data };2008 .addend = addend,
1100}2009 .type = @"type",
2010 .meta = .{
2011 .pcrel = rel.r_pcrel == 1,
2012 .has_subtractor = has_subtractor,
2013 .length = rel.r_length,
2014 .symbolnum = rel.r_symbolnum,
2015 },
2016 });
2017 }
2018 }
11012019
1102pub fn hasDataInCode(self: Object) bool {2020 fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_arm64) !Relocation.Type {
1103 return self.data_in_code.items.len > 0;2021 switch (rel_type) {
1104}2022 .ARM64_RELOC_UNSIGNED => {
2023 if (rel.r_pcrel == 1) return error.Pcrel;
2024 if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
2025 return .unsigned;
2026 },
2027
2028 .ARM64_RELOC_SUBTRACTOR => {
2029 if (rel.r_pcrel == 1) return error.Pcrel;
2030 return .subtractor;
2031 },
2032
2033 .ARM64_RELOC_BRANCH26,
2034 .ARM64_RELOC_PAGE21,
2035 .ARM64_RELOC_GOT_LOAD_PAGE21,
2036 .ARM64_RELOC_TLVP_LOAD_PAGE21,
2037 .ARM64_RELOC_POINTER_TO_GOT,
2038 => {
2039 if (rel.r_pcrel == 0) return error.NonPcrel;
2040 if (rel.r_length != 2) return error.InvalidLength;
2041 if (rel.r_extern == 0) return error.NonExtern;
2042 return switch (rel_type) {
2043 .ARM64_RELOC_BRANCH26 => .branch,
2044 .ARM64_RELOC_PAGE21 => .page,
2045 .ARM64_RELOC_GOT_LOAD_PAGE21 => .got_load_page,
2046 .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp_page,
2047 .ARM64_RELOC_POINTER_TO_GOT => .got,
2048 else => unreachable,
2049 };
2050 },
2051
2052 .ARM64_RELOC_PAGEOFF12,
2053 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
2054 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
2055 => {
2056 if (rel.r_pcrel == 1) return error.Pcrel;
2057 if (rel.r_length != 2) return error.InvalidLength;
2058 if (rel.r_extern == 0) return error.NonExtern;
2059 return switch (rel_type) {
2060 .ARM64_RELOC_PAGEOFF12 => .pageoff,
2061 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got_load_pageoff,
2062 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp_pageoff,
2063 else => unreachable,
2064 };
2065 },
11052066
1106const Object = @This();2067 .ARM64_RELOC_ADDEND => unreachable, // We make it part of the addend field
2068 }
2069 }
2070};
11072071
1108const std = @import("std");
1109const build_options = @import("build_options");
1110const assert = std.debug.assert;2072const assert = std.debug.assert;
1111const dwarf = std.dwarf;
1112const eh_frame = @import("eh_frame.zig");2073const eh_frame = @import("eh_frame.zig");
1113const fs = std.fs;
1114const io = std.io;
1115const log = std.log.scoped(.link);2074const log = std.log.scoped(.link);
1116const macho = std.macho;2075const macho = std.macho;
1117const math = std.math;2076const math = std.math;
1118const mem = std.mem;2077const mem = std.mem;
1119const sort = std.sort;2078const trace = @import("../tracy.zig").trace;
1120const trace = @import("../../tracy.zig").trace;2079const std = @import("std");
11212080
1122const Allocator = mem.Allocator;2081const Allocator = mem.Allocator;
1123const Atom = @import("Atom.zig");2082const Atom = @import("Atom.zig");
2083const Cie = eh_frame.Cie;
1124const DwarfInfo = @import("DwarfInfo.zig");2084const DwarfInfo = @import("DwarfInfo.zig");
2085const Fde = eh_frame.Fde;
2086const File = @import("file.zig").File;
1125const LoadCommandIterator = macho.LoadCommandIterator;2087const LoadCommandIterator = macho.LoadCommandIterator;
1126const MachO = @import("../MachO.zig");2088const MachO = @import("../MachO.zig");
1127const Platform = @import("load_commands.zig").Platform;2089const Object = @This();
1128const SymbolWithLoc = MachO.SymbolWithLoc;2090const Relocation = @import("Relocation.zig");
2091const StringTable = @import("../strtab.zig").StringTable;
2092const Symbol = @import("Symbol.zig");
1129const UnwindInfo = @import("UnwindInfo.zig");2093const UnwindInfo = @import("UnwindInfo.zig");
1130const Alignment = Atom.Alignment;
src/link/MachO/Relocation.zig+92-225
...@@ -1,235 +1,62 @@...@@ -1,235 +1,62 @@
1//! Relocation used by the self-hosted backends to instruct the linker where and how to1tag: enum { @"extern", local },
2//! fixup the values when flushing the contents to file and/or memory.
3
4type: Type,
5target: SymbolWithLoc,
6offset: u32,2offset: u32,
3target: u32,
7addend: i64,4addend: i64,
8pcrel: bool,5type: Type,
9length: u2,6meta: packed struct {
10dirty: bool = true,7 pcrel: bool,
118 has_subtractor: bool,
12pub const Type = enum {9 length: u2,
13 // x86, x86_6410 symbolnum: u24,
14 /// RIP-relative displacement to a GOT pointer11},
15 got,12
16 /// RIP-relative displacement13pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol {
17 signed,14 assert(rel.tag == .@"extern");
18 /// RIP-relative displacement to a TLV thunk15 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.
41pub fn isResolvable(self: Relocation, macho_file: *MachO) bool {
42 _ = self.getTargetBaseAddress(macho_file) orelse return false;
43 return true;
44}16}
4517
46pub fn isGotIndirection(self: Relocation) bool {18pub 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}
5222
53pub fn isStubTrampoline(self: Relocation, macho_file: *MachO) bool {23pub 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}
5929
60pub fn getTargetBaseAddress(self: Relocation, macho_file: *MachO) ?u64 {30pub 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
87pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) void {
88 const target = macho_file.base.comp.root_mod.resolved_target.result;
89 const arch = target.cpu.arch;
90 const atom = macho_file.getAtom(atom_index);
91 const source_sym = atom.getSymbol(macho_file);
92 const source_addr = source_sym.n_value + self.offset;
93
94 const target_base_addr = self.getTargetBaseAddress(macho_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
95 const target_addr: i64 = switch (self.type) {
96 .tlv_initializer => blk: {
97 assert(self.addend == 0); // Addend here makes no sense.
98 const header = macho_file.sections.items(.header)[macho_file.thread_data_section_index.?];
99 break :blk @as(i64, @intCast(target_base_addr - header.addr));
100 },
101 else => @as(i64, @intCast(target_base_addr)) + self.addend,
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
118fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {
119 var buffer = code[self.offset..];
120 switch (self.type) {
121 .branch => {
122 const displacement = math.cast(
123 i28,
124 @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)),
125 ) orelse unreachable; // TODO codegen should never allow for jump larger than i28 displacement
126 var inst = aarch64.Instruction{
127 .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
128 aarch64.Instruction,
129 aarch64.Instruction.unconditional_branch_immediate,
130 ), buffer[0..4]),
131 };
132 inst.unconditional_branch_immediate.imm26 = @as(u26, @truncate(@as(u28, @bitCast(displacement >> 2))));
133 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
134 },
135 .page, .got_page => {
136 const source_page = @as(i32, @intCast(source_addr >> 12));
137 const target_page = @as(i32, @intCast(target_addr >> 12));
138 const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page))));
139 var inst = aarch64.Instruction{
140 .pc_relative_address = mem.bytesToValue(meta.TagPayload(
141 aarch64.Instruction,
142 aarch64.Instruction.pc_relative_address,
143 ), buffer[0..4]),
144 };
145 inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
146 inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
147 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
148 },
149 .pageoff, .got_pageoff => {
150 const narrowed = @as(u12, @truncate(@as(u64, @intCast(target_addr))));
151 if (isArithmeticOp(buffer[0..4])) {
152 var inst = aarch64.Instruction{
153 .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
154 aarch64.Instruction,
155 aarch64.Instruction.add_subtract_immediate,
156 ), buffer[0..4]),
157 };
158 inst.add_subtract_immediate.imm12 = narrowed;
159 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
160 } else {
161 var inst = aarch64.Instruction{
162 .load_store_register = mem.bytesToValue(meta.TagPayload(
163 aarch64.Instruction,
164 aarch64.Instruction.load_store_register,
165 ), buffer[0..4]),
166 };
167 const offset: u12 = blk: {
168 if (inst.load_store_register.size == 0) {
169 if (inst.load_store_register.v == 1) {
170 // 128-bit SIMD is scaled by 16.
171 break :blk @divExact(narrowed, 16);
172 }
173 // Otherwise, 8-bit SIMD or ldrb.
174 break :blk narrowed;
175 } else {
176 const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable;
177 break :blk @divExact(narrowed, denom);
178 }
179 };
180 inst.load_store_register.offset = offset;
181 mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
182 }
183 },
184 .tlv_initializer, .unsigned => switch (self.length) {
185 2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little),
186 3 => mem.writeInt(u64, buffer[0..8], @as(u64, @bitCast(target_addr)), .little),
187 else => unreachable,
188 },
189 .got, .signed, .tlv => unreachable, // Invalid target architecture.
190 }
191}35}
19236
193fn resolveX8664(self: Relocation, source_addr: u64, target_addr: i64, code: []u8) void {37pub fn getRelocAddend(rel: Relocation, cpu_arch: std.Target.Cpu.Arch) i64 {
194 switch (self.type) {38 const addend: i64 = switch (rel.type) {
195 .branch, .got, .tlv, .signed => {39 .signed => 0,
196 const displacement = @as(i32, @intCast(@as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr)) - 4));40 .signed1 => -1,
197 mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @bitCast(displacement)), .little);41 .signed2 => -2,
198 },42 .signed4 => -4,
199 .tlv_initializer, .unsigned => {43 else => 0,
200 switch (self.length) {44 };
201 2 => {45 return switch (cpu_arch) {
202 mem.writeInt(u32, code[self.offset..][0..4], @as(u32, @truncate(@as(u64, @bitCast(target_addr)))), .little);46 .x86_64 => if (rel.meta.pcrel) addend - 4 else addend,
203 },47 else => addend,
204 3 => {48 };
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}
213
214pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
215 const group_decode = @as(u5, @truncate(inst[3]));
216 return ((group_decode >> 2) == 4);
217}
218
219pub fn calcPcRelativeDisplacementX86(source_addr: u64, target_addr: u64, correction: u3) error{Overflow}!i32 {
220 const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 4 + correction));
221 return math.cast(i32, disp) orelse error.Overflow;
222}49}
22350
224pub fn calcPcRelativeDisplacementArm64(source_addr: u64, target_addr: u64) error{Overflow}!i28 {51pub fn lessThan(ctx: void, lhs: Relocation, rhs: Relocation) bool {
225 const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr));52 _ = ctx;
226 return math.cast(i28, disp) orelse error.Overflow;53 return lhs.offset < rhs.offset;
227}54}
22855
229pub fn calcNumberOfPages(source_addr: u64, target_addr: u64) i21 {56pub fn calcNumberOfPages(saddr: u64, taddr: u64) error{Overflow}!i21 {
230 const source_page = @as(i32, @intCast(source_addr >> 12));57 const spage = math.cast(i32, saddr >> 12) orelse return error.Overflow;
231 const target_page = @as(i32, @intCast(target_addr >> 12));58 const tpage = math.cast(i32, taddr >> 12) orelse return error.Overflow;
232 const pages = @as(i21, @intCast(target_page - source_page));59 const pages = math.cast(i21, tpage - spage) orelse return error.Overflow;
233 return pages;60 return pages;
234}61}
23562
...@@ -242,8 +69,8 @@ pub const PageOffsetInstKind = enum {...@@ -242,8 +69,8 @@ pub const PageOffsetInstKind = enum {
242 load_store_128,69 load_store_128,
243};70};
24471
245pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {72pub fn calcPageOffset(taddr: u64, kind: PageOffsetInstKind) !u12 {
246 const narrowed = @as(u12, @truncate(target_addr));73 const narrowed = @as(u12, @truncate(taddr));
247 return switch (kind) {74 return switch (kind) {
248 .arithmetic, .load_store_8 => narrowed,75 .arithmetic, .load_store_8 => narrowed,
249 .load_store_16 => try math.divExact(u12, narrowed, 2),76 .load_store_16 => try math.divExact(u12, narrowed, 2),
...@@ -253,17 +80,57 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {...@@ -253,17 +80,57 @@ pub fn calcPageOffset(target_addr: u64, kind: PageOffsetInstKind) !u12 {
253 };80 };
254}81}
25582
256const Relocation = @This();83pub inline fn isArithmeticOp(inst: *const [4]u8) bool {
84 const group_decode = @as(u5, @truncate(inst[3]));
85 return ((group_decode >> 2) == 4);
86}
87
88pub const Type = enum {
89 // x86_64
90 /// RIP-relative displacement (X86_64_RELOC_SIGNED)
91 signed,
92 /// RIP-relative displacement (X86_64_RELOC_SIGNED_1)
93 signed1,
94 /// RIP-relative displacement (X86_64_RELOC_SIGNED_2)
95 signed2,
96 /// RIP-relative displacement (X86_64_RELOC_SIGNED_4)
97 signed4,
98 /// RIP-relative GOT load (X86_64_RELOC_GOT_LOAD)
99 got_load,
100 /// RIP-relative TLV load (X86_64_RELOC_TLV)
101 tlv,
102
103 // arm64
104 /// PC-relative load (distance to page, ARM64_RELOC_PAGE21)
105 page,
106 /// Non-PC-relative offset to symbol (ARM64_RELOC_PAGEOFF12)
107 pageoff,
108 /// PC-relative GOT load (distance to page, ARM64_RELOC_GOT_LOAD_PAGE21)
109 got_load_page,
110 /// Non-PC-relative offset to GOT slot (ARM64_RELOC_GOT_LOAD_PAGEOFF12)
111 got_load_pageoff,
112 /// PC-relative TLV load (distance to page, ARM64_RELOC_TLVP_LOAD_PAGE21)
113 tlvp_page,
114 /// Non-PC-relative offset to TLV slot (ARM64_RELOC_TLVP_LOAD_PAGEOFF12)
115 tlvp_pageoff,
116
117 // common
118 /// PC-relative call/bl/b (X86_64_RELOC_BRANCH or ARM64_RELOC_BRANCH26)
119 branch,
120 /// PC-relative displacement to GOT pointer (X86_64_RELOC_GOT or ARM64_RELOC_POINTER_TO_GOT)
121 got,
122 /// Absolute subtractor value (X86_64_RELOC_SUBTRACTOR or ARM64_RELOC_SUBTRACTOR)
123 subtractor,
124 /// Absolute relocation (X86_64_RELOC_UNSIGNED or ARM64_RELOC_UNSIGNED)
125 unsigned,
126};
257127
258const std = @import("std");
259const aarch64 = @import("../../arch/aarch64/bits.zig");
260const assert = std.debug.assert;128const assert = std.debug.assert;
261const relocs_log = std.log.scoped(.link_relocs);
262const macho = std.macho;129const macho = std.macho;
263const math = std.math;130const math = std.math;
264const mem = std.mem;131const std = @import("std");
265const meta = std.meta;
266132
267const Atom = @import("Atom.zig");133const Atom = @import("Atom.zig");
268const MachO = @import("../MachO.zig");134const MachO = @import("../MachO.zig");
269const SymbolWithLoc = MachO.SymbolWithLoc;135const Relocation = @This();
136const Symbol = @import("Symbol.zig");
src/link/MachO/Symbol.zig created+383
...@@ -0,0 +1,383 @@
1//! Represents a defined symbol.
2
3/// Allocated address value of this symbol.
4value: u64 = 0,
5
6/// Offset into the linker's intern table.
7name: u32 = 0,
8
9/// File where this symbol is defined.
10file: File.Index = 0,
11
12/// Atom containing this symbol if any.
13/// Index of 0 means there is no associated atom with this symbol.
14/// Use `getAtom` to get the pointer to the atom.
15atom: Atom.Index = 0,
16
17/// Assigned output section index for this atom.
18out_n_sect: u16 = 0,
19
20/// Index of the source nlist this symbol references.
21/// Use `getNlist` to pull the nlist from the relevant file.
22nlist_idx: u32 = 0,
23
24/// Misc flags for the symbol packaged as packed struct for compression.
25flags: Flags = .{},
26
27visibility: Visibility = .local,
28
29extra: u32 = 0,
30
31pub fn isLocal(symbol: Symbol) bool {
32 return !(symbol.flags.import or symbol.flags.@"export");
33}
34
35pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
36 const file = symbol.getFile(macho_file) orelse return false;
37 return switch (file) {
38 .object => symbol.getNlist(macho_file).stab(),
39 else => false,
40 };
41}
42
43pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool {
44 const name = symbol.getName(macho_file);
45 return std.mem.indexOf(u8, name, "$tlv$init") != null;
46}
47
48pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
49 const file = symbol.getFile(macho_file).?;
50 const is_dylib_weak = switch (file) {
51 .dylib => |x| x.weak,
52 else => false,
53 };
54 return is_dylib_weak or symbol.flags.weak_ref;
55}
56
57pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
58 return macho_file.string_intern.getAssumeExists(symbol.name);
59}
60
61pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
62 return macho_file.getAtom(symbol.atom);
63}
64
65pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
66 return macho_file.getFile(symbol.file);
67}
68
69/// Asserts file is an object.
70pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
71 const file = symbol.getFile(macho_file).?;
72 return switch (file) {
73 .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
74 else => unreachable,
75 };
76}
77
78pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 {
79 const file = symbol.getFile(macho_file).?;
80 assert(file == .object);
81 return file.object.symtab.items(.size)[symbol.nlist_idx];
82}
83
84pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 {
85 assert(symbol.flags.import);
86 const file = symbol.getFile(macho_file) orelse return null;
87 return switch (file) {
88 .dylib => |x| x.ordinal,
89 else => null,
90 };
91}
92
93pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 {
94 const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32);
95 const in_archive = switch (file) {
96 .object => |x| !x.alive,
97 else => false,
98 };
99 return file.getSymbolRank(.{
100 .archive = in_archive,
101 .weak = symbol.flags.weak,
102 .tentative = symbol.flags.tentative,
103 });
104}
105
106pub fn getAddress(symbol: Symbol, opts: struct {
107 stubs: bool = true,
108}, macho_file: *MachO) u64 {
109 if (opts.stubs) {
110 if (symbol.flags.stubs) {
111 return symbol.getStubsAddress(macho_file);
112 } else if (symbol.flags.objc_stubs) {
113 return symbol.getObjcStubsAddress(macho_file);
114 }
115 }
116 if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value;
117 return symbol.value;
118}
119
120pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
121 if (!symbol.flags.got) return 0;
122 const extra = symbol.getExtra(macho_file).?;
123 return macho_file.got.getAddress(extra.got, macho_file);
124}
125
126pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
127 if (!symbol.flags.stubs) return 0;
128 const extra = symbol.getExtra(macho_file).?;
129 return macho_file.stubs.getAddress(extra.stubs, macho_file);
130}
131
132pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
133 if (!symbol.flags.objc_stubs) return 0;
134 const extra = symbol.getExtra(macho_file).?;
135 return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
136}
137
138pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
139 if (!symbol.flags.objc_stubs) return 0;
140 const extra = symbol.getExtra(macho_file).?;
141 const atom = macho_file.getAtom(extra.objc_selrefs).?;
142 assert(atom.flags.alive);
143 return atom.value;
144}
145
146pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
147 if (!symbol.flags.tlv_ptr) return 0;
148 const extra = symbol.getExtra(macho_file).?;
149 return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
150}
151
152pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
153 if (!symbol.flags.output_symtab) return null;
154 assert(!symbol.isSymbolStab(macho_file));
155 const file = symbol.getFile(macho_file).?;
156 const symtab_ctx = switch (file) {
157 inline else => |x| x.output_symtab_ctx,
158 };
159 var idx = symbol.getExtra(macho_file).?.symtab;
160 if (symbol.isLocal()) {
161 idx += symtab_ctx.ilocal;
162 } else if (symbol.flags.@"export") {
163 idx += symtab_ctx.iexport;
164 } else {
165 assert(symbol.flags.import);
166 idx += symtab_ctx.iimport;
167 }
168 return idx;
169}
170
171const AddExtraOpts = struct {
172 got: ?u32 = null,
173 stubs: ?u32 = null,
174 objc_stubs: ?u32 = null,
175 objc_selrefs: ?u32 = null,
176 tlv_ptr: ?u32 = null,
177 symtab: ?u32 = null,
178};
179
180pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
181 if (symbol.getExtra(macho_file) == null) {
182 symbol.extra = try macho_file.addSymbolExtra(.{});
183 }
184 var extra = symbol.getExtra(macho_file).?;
185 inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
186 if (@field(opts, field.name)) |x| {
187 @field(extra, field.name) = x;
188 }
189 }
190 symbol.setExtra(extra, macho_file);
191}
192
193pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
194 return macho_file.getSymbolExtra(symbol.extra);
195}
196
197pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
198 macho_file.setSymbolExtra(symbol.extra, extra);
199}
200
201pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
202 if (symbol.isLocal()) {
203 out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
204 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
205 out.n_desc = 0;
206 out.n_value = symbol.getAddress(.{}, macho_file);
207
208 switch (symbol.visibility) {
209 .hidden => out.n_type |= macho.N_PEXT,
210 else => {},
211 }
212 } else if (symbol.flags.@"export") {
213 assert(symbol.visibility == .global);
214 out.n_type = macho.N_EXT;
215 out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
216 out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
217 out.n_value = symbol.getAddress(.{}, macho_file);
218 out.n_desc = 0;
219
220 if (symbol.flags.weak) {
221 out.n_desc |= macho.N_WEAK_DEF;
222 }
223 if (symbol.flags.dyn_ref) {
224 out.n_desc |= macho.REFERENCED_DYNAMICALLY;
225 }
226 } else {
227 assert(symbol.visibility == .global);
228 out.n_type = macho.N_EXT;
229 out.n_sect = 0;
230 out.n_value = 0;
231 out.n_desc = 0;
232
233 const ord: u16 = if (macho_file.options.namespace == .flat)
234 @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP))
235 else if (symbol.getDylibOrdinal(macho_file)) |ord|
236 ord
237 else
238 macho.BIND_SPECIAL_DYLIB_SELF;
239 out.n_desc = macho.N_SYMBOL_RESOLVER * ord;
240
241 if (symbol.flags.weak) {
242 out.n_desc |= macho.N_WEAK_DEF;
243 }
244
245 if (symbol.weakRef(macho_file)) {
246 out.n_desc |= macho.N_WEAK_REF;
247 }
248 }
249}
250
251pub fn format(
252 symbol: Symbol,
253 comptime unused_fmt_string: []const u8,
254 options: std.fmt.FormatOptions,
255 writer: anytype,
256) !void {
257 _ = symbol;
258 _ = unused_fmt_string;
259 _ = options;
260 _ = writer;
261 @compileError("do not format symbols directly");
262}
263
264const FormatContext = struct {
265 symbol: Symbol,
266 macho_file: *MachO,
267};
268
269pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) {
270 return .{ .data = .{
271 .symbol = symbol,
272 .macho_file = macho_file,
273 } };
274}
275
276fn format2(
277 ctx: FormatContext,
278 comptime unused_fmt_string: []const u8,
279 options: std.fmt.FormatOptions,
280 writer: anytype,
281) !void {
282 _ = options;
283 _ = unused_fmt_string;
284 const symbol = ctx.symbol;
285 try writer.print("%{d} : {s} : @{x}", .{
286 symbol.nlist_idx,
287 symbol.getName(ctx.macho_file),
288 symbol.getAddress(.{}, ctx.macho_file),
289 });
290 if (symbol.getFile(ctx.macho_file)) |file| {
291 if (symbol.out_n_sect != 0) {
292 try writer.print(" : sect({d})", .{symbol.out_n_sect});
293 }
294 if (symbol.getAtom(ctx.macho_file)) |atom| {
295 try writer.print(" : atom({d})", .{atom.atom_index});
296 }
297 var buf: [2]u8 = .{'_'} ** 2;
298 if (symbol.flags.@"export") buf[0] = 'E';
299 if (symbol.flags.import) buf[1] = 'I';
300 try writer.print(" : {s}", .{&buf});
301 if (symbol.flags.weak) try writer.writeAll(" : weak");
302 if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab");
303 switch (file) {
304 .internal => |x| try writer.print(" : internal({d})", .{x.index}),
305 .object => |x| try writer.print(" : object({d})", .{x.index}),
306 .dylib => |x| try writer.print(" : dylib({d})", .{x.index}),
307 }
308 } else try writer.writeAll(" : unresolved");
309}
310
311pub const Flags = packed struct {
312 /// Whether the symbol is imported at runtime.
313 import: bool = false,
314
315 /// Whether the symbol is exported at runtime.
316 @"export": bool = false,
317
318 /// Whether this symbol is weak.
319 weak: bool = false,
320
321 /// Whether this symbol is weakly referenced.
322 weak_ref: bool = false,
323
324 /// Whether this symbol is dynamically referenced.
325 dyn_ref: bool = false,
326
327 /// Whether this symbol was marked as N_NO_DEAD_STRIP.
328 no_dead_strip: bool = false,
329
330 /// Whether this symbol can be interposed at runtime.
331 interposable: bool = false,
332
333 /// Whether this symbol is absolute.
334 abs: bool = false,
335
336 /// Whether this symbol is a tentative definition.
337 tentative: bool = false,
338
339 /// Whether this symbol is a thread-local variable.
340 tlv: bool = false,
341
342 /// Whether the symbol makes into the output symtab or not.
343 output_symtab: bool = false,
344
345 /// Whether the symbol contains __got indirection.
346 got: bool = false,
347
348 /// Whether the symbols contains __stubs indirection.
349 stubs: bool = false,
350
351 /// Whether the symbol has a TLV pointer.
352 tlv_ptr: bool = false,
353
354 /// Whether the symbol contains __objc_stubs indirection.
355 objc_stubs: bool = false,
356};
357
358pub const Visibility = enum {
359 global,
360 hidden,
361 local,
362};
363
364pub const Extra = struct {
365 got: u32 = 0,
366 stubs: u32 = 0,
367 objc_stubs: u32 = 0,
368 objc_selrefs: u32 = 0,
369 tlv_ptr: u32 = 0,
370 symtab: u32 = 0,
371};
372
373pub const Index = u32;
374
375const assert = std.debug.assert;
376const macho = std.macho;
377const std = @import("std");
378
379const Atom = @import("Atom.zig");
380const File = @import("file.zig").File;
381const MachO = @import("../MachO.zig");
382const Object = @import("Object.zig");
383const Symbol = @This();
src/link/MachO/Trie.zig deleted-613
...@@ -1,613 +0,0 @@
1//! Represents export trie used in MachO executables and dynamic libraries.
2//! The purpose of an export trie is to encode as compactly as possible all
3//! export symbols for the loader `dyld`.
4//! The export trie encodes offset and other information using ULEB128
5//! encoding, and is part of the __LINKEDIT segment.
6//!
7//! Description from loader.h:
8//!
9//! The symbols exported by a dylib are encoded in a trie. This is a compact
10//! representation that factors out common prefixes. It also reduces LINKEDIT pages
11//! in RAM because it encodes all information (name, address, flags) in one small,
12//! contiguous range. The export area is a stream of nodes. The first node sequentially
13//! is the start node for the trie.
14//!
15//! Nodes for a symbol start with a uleb128 that is the length of the exported symbol
16//! information for the string so far. If there is no exported symbol, the node starts
17//! with a zero byte. If there is exported info, it follows the length.
18//!
19//! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded
20//! offset which is location of the content named by the symbol from the mach_header
21//! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags
22//! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string
23//! is zero length, then the symbol is re-export from the specified dylib with the same name.
24//! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two
25//! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers.
26//! The resolver is used by lazy pointers and must be called to get the actual address to use.
27//!
28//! After the optional exported symbol information is a byte of how many edges (0-255) that
29//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
30//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
31/// The root node of the trie.
32root: ?*Node = null,
33
34/// If you want to access nodes ordered in DFS fashion,
35/// you should call `finalize` first since the nodes
36/// in this container are not guaranteed to not be stale
37/// if more insertions took place after the last `finalize`
38/// call.
39ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
40
41/// The size of the trie in bytes.
42/// This value may be outdated if there were additional
43/// insertions performed after `finalize` was called.
44/// Call `finalize` before accessing this value to ensure
45/// it is up-to-date.
46size: u64 = 0,
47
48/// Number of nodes currently in the trie.
49node_count: usize = 0,
50
51trie_dirty: bool = true,
52
53/// Export symbol that is to be placed in the trie.
54pub const ExportSymbol = struct {
55 /// Name of the symbol.
56 name: []const u8,
57
58 /// Offset of this symbol's virtual memory address from the beginning
59 /// of the __TEXT segment.
60 vmaddr_offset: u64,
61
62 /// Export flags of this exported symbol.
63 export_flags: u64,
64};
65
66/// Insert a symbol into the trie, updating the prefixes in the process.
67/// This operation may change the layout of the trie by splicing edges in
68/// certain circumstances.
69pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
70 const node = try self.root.?.put(allocator, symbol.name);
71 node.terminal_info = .{
72 .vmaddr_offset = symbol.vmaddr_offset,
73 .export_flags = symbol.export_flags,
74 };
75 self.trie_dirty = true;
76}
77
78/// Finalizes this trie for writing to a byte stream.
79/// This step performs multiple passes through the trie ensuring
80/// there are no gaps after every `Node` is ULEB128 encoded.
81/// Call this method before trying to `write` the trie to a byte stream.
82pub fn finalize(self: *Trie, allocator: Allocator) !void {
83 if (!self.trie_dirty) return;
84
85 self.ordered_nodes.shrinkRetainingCapacity(0);
86 try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
87
88 var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
89 defer fifo.deinit();
90
91 try fifo.writeItem(self.root.?);
92
93 while (fifo.readItem()) |next| {
94 for (next.edges.items) |*edge| {
95 try fifo.writeItem(edge.to);
96 }
97 self.ordered_nodes.appendAssumeCapacity(next);
98 }
99
100 var more: bool = true;
101 while (more) {
102 self.size = 0;
103 more = false;
104 for (self.ordered_nodes.items) |node| {
105 const res = try node.finalize(self.size);
106 self.size += res.node_size;
107 if (res.updated) more = true;
108 }
109 }
110
111 self.trie_dirty = false;
112}
113
114const ReadError = error{
115 OutOfMemory,
116 EndOfStream,
117 Overflow,
118};
119
120/// Parse the trie from a byte stream.
121pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
122 return self.root.?.read(allocator, reader);
123}
124
125/// Write the trie to a byte stream.
126/// Panics if the trie was not finalized using `finalize` before calling this method.
127pub fn write(self: Trie, writer: anytype) !u64 {
128 assert(!self.trie_dirty);
129 var counting_writer = std.io.countingWriter(writer);
130 for (self.ordered_nodes.items) |node| {
131 try node.write(counting_writer.writer());
132 }
133 return counting_writer.bytes_written;
134}
135
136pub fn init(self: *Trie, allocator: Allocator) !void {
137 assert(self.root == null);
138 const root = try allocator.create(Node);
139 root.* = .{ .base = self };
140 self.root = root;
141 self.node_count += 1;
142}
143
144pub fn deinit(self: *Trie, allocator: Allocator) void {
145 if (self.root) |root| {
146 root.deinit(allocator);
147 allocator.destroy(root);
148 }
149 self.ordered_nodes.deinit(allocator);
150}
151
152test "Trie node count" {
153 const gpa = testing.allocator;
154 var trie: Trie = .{};
155 defer trie.deinit(gpa);
156 try trie.init(gpa);
157
158 try testing.expectEqual(trie.node_count, 0);
159 try testing.expect(trie.root == null);
160
161 try trie.put(gpa, .{
162 .name = "_main",
163 .vmaddr_offset = 0,
164 .export_flags = 0,
165 });
166 try testing.expectEqual(trie.node_count, 2);
167
168 // Inserting the same node shouldn't update the trie.
169 try trie.put(gpa, .{
170 .name = "_main",
171 .vmaddr_offset = 0,
172 .export_flags = 0,
173 });
174 try testing.expectEqual(trie.node_count, 2);
175
176 try trie.put(gpa, .{
177 .name = "__mh_execute_header",
178 .vmaddr_offset = 0x1000,
179 .export_flags = 0,
180 });
181 try testing.expectEqual(trie.node_count, 4);
182
183 // Inserting the same node shouldn't update the trie.
184 try trie.put(gpa, .{
185 .name = "__mh_execute_header",
186 .vmaddr_offset = 0x1000,
187 .export_flags = 0,
188 });
189 try testing.expectEqual(trie.node_count, 4);
190 try trie.put(gpa, .{
191 .name = "_main",
192 .vmaddr_offset = 0,
193 .export_flags = 0,
194 });
195 try testing.expectEqual(trie.node_count, 4);
196}
197
198test "Trie basic" {
199 const gpa = testing.allocator;
200 var trie: Trie = .{};
201 defer trie.deinit(gpa);
202 try trie.init(gpa);
203
204 // root --- _st ---> node
205 try trie.put(gpa, .{
206 .name = "_st",
207 .vmaddr_offset = 0,
208 .export_flags = 0,
209 });
210 try testing.expect(trie.root.?.edges.items.len == 1);
211 try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
212
213 {
214 // root --- _st ---> node --- art ---> node
215 try trie.put(gpa, .{
216 .name = "_start",
217 .vmaddr_offset = 0,
218 .export_flags = 0,
219 });
220 try testing.expect(trie.root.?.edges.items.len == 1);
221
222 const nextEdge = &trie.root.?.edges.items[0];
223 try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
224 try testing.expect(nextEdge.to.edges.items.len == 1);
225 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
226 }
227 {
228 // root --- _ ---> node --- st ---> node --- art ---> node
229 // |
230 // | --- main ---> node
231 try trie.put(gpa, .{
232 .name = "_main",
233 .vmaddr_offset = 0,
234 .export_flags = 0,
235 });
236 try testing.expect(trie.root.?.edges.items.len == 1);
237
238 const nextEdge = &trie.root.?.edges.items[0];
239 try testing.expect(mem.eql(u8, nextEdge.label, "_"));
240 try testing.expect(nextEdge.to.edges.items.len == 2);
241 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
242 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
243
244 const nextNextEdge = &nextEdge.to.edges.items[0];
245 try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
246 }
247}
248
249fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
250 assert(expected.len > 0);
251 if (mem.eql(u8, expected, given)) return;
252 const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
253 defer testing.allocator.free(expected_fmt);
254 const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
255 defer testing.allocator.free(given_fmt);
256 const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
257 const padding = try testing.allocator.alloc(u8, idx + 5);
258 defer testing.allocator.free(padding);
259 @memset(padding, ' ');
260 std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
261 return error.TestFailed;
262}
263
264test "write Trie to a byte stream" {
265 var gpa = testing.allocator;
266 var trie: Trie = .{};
267 defer trie.deinit(gpa);
268 try trie.init(gpa);
269
270 try trie.put(gpa, .{
271 .name = "__mh_execute_header",
272 .vmaddr_offset = 0,
273 .export_flags = 0,
274 });
275 try trie.put(gpa, .{
276 .name = "_main",
277 .vmaddr_offset = 0x1000,
278 .export_flags = 0,
279 });
280
281 try trie.finalize(gpa);
282 try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes.
283
284 const exp_buffer = [_]u8{
285 0x0, 0x1, // node root
286 0x5f, 0x0, 0x5, // edge '_'
287 0x0, 0x2, // non-terminal node
288 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
289 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
290 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
291 0x2, 0x0, 0x0, 0x0, // terminal node
292 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
293 };
294
295 const buffer = try gpa.alloc(u8, trie.size);
296 defer gpa.free(buffer);
297 var stream = std.io.fixedBufferStream(buffer);
298 {
299 _ = try trie.write(stream.writer());
300 try expectEqualHexStrings(&exp_buffer, buffer);
301 }
302 {
303 // Writing finalized trie again should yield the same result.
304 try stream.seekTo(0);
305 _ = try trie.write(stream.writer());
306 try expectEqualHexStrings(&exp_buffer, buffer);
307 }
308}
309
310test "parse Trie from byte stream" {
311 var gpa = testing.allocator;
312
313 const in_buffer = [_]u8{
314 0x0, 0x1, // node root
315 0x5f, 0x0, 0x5, // edge '_'
316 0x0, 0x2, // non-terminal node
317 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
318 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
319 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
320 0x2, 0x0, 0x0, 0x0, // terminal node
321 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
322 };
323
324 var in_stream = std.io.fixedBufferStream(&in_buffer);
325 var trie: Trie = .{};
326 defer trie.deinit(gpa);
327 try trie.init(gpa);
328 const nread = try trie.read(gpa, in_stream.reader());
329
330 try testing.expect(nread == in_buffer.len);
331
332 try trie.finalize(gpa);
333
334 const out_buffer = try gpa.alloc(u8, trie.size);
335 defer gpa.free(out_buffer);
336 var out_stream = std.io.fixedBufferStream(out_buffer);
337 _ = try trie.write(out_stream.writer());
338 try expectEqualHexStrings(&in_buffer, out_buffer);
339}
340
341test "ordering bug" {
342 var gpa = testing.allocator;
343 var trie: Trie = .{};
344 defer trie.deinit(gpa);
345 try trie.init(gpa);
346
347 try trie.put(gpa, .{
348 .name = "_asStr",
349 .vmaddr_offset = 0x558,
350 .export_flags = 0,
351 });
352 try trie.put(gpa, .{
353 .name = "_a",
354 .vmaddr_offset = 0x8008,
355 .export_flags = 0,
356 });
357 try trie.finalize(gpa);
358
359 const exp_buffer = [_]u8{
360 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
361 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
362 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
363 };
364
365 const buffer = try gpa.alloc(u8, trie.size);
366 defer gpa.free(buffer);
367 var stream = std.io.fixedBufferStream(buffer);
368 // Writing finalized trie again should yield the same result.
369 _ = try trie.write(stream.writer());
370 try expectEqualHexStrings(&exp_buffer, buffer);
371}
372
373pub const Node = struct {
374 base: *Trie,
375
376 /// Terminal info associated with this node.
377 /// If this node is not a terminal node, info is null.
378 terminal_info: ?struct {
379 /// Export flags associated with this exported symbol.
380 export_flags: u64,
381 /// VM address offset wrt to the section this symbol is defined against.
382 vmaddr_offset: u64,
383 } = null,
384
385 /// Offset of this node in the trie output byte stream.
386 trie_offset: ?u64 = null,
387
388 /// List of all edges originating from this node.
389 edges: std.ArrayListUnmanaged(Edge) = .{},
390
391 node_dirty: bool = true,
392
393 /// Edge connecting to nodes in the trie.
394 pub const Edge = struct {
395 from: *Node,
396 to: *Node,
397 label: []u8,
398
399 fn deinit(self: *Edge, allocator: Allocator) void {
400 self.to.deinit(allocator);
401 allocator.destroy(self.to);
402 allocator.free(self.label);
403 self.from = undefined;
404 self.to = undefined;
405 self.label = undefined;
406 }
407 };
408
409 fn deinit(self: *Node, allocator: Allocator) void {
410 for (self.edges.items) |*edge| {
411 edge.deinit(allocator);
412 }
413 self.edges.deinit(allocator);
414 }
415
416 /// Inserts a new node starting from `self`.
417 fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
418 // Check for match with edges from this node.
419 for (self.edges.items) |*edge| {
420 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
421 if (match == 0) continue;
422 if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
423
424 // Found a match, need to splice up nodes.
425 // From: A -> B
426 // To: A -> C -> B
427 const mid = try allocator.create(Node);
428 mid.* = .{ .base = self.base };
429 const to_label = try allocator.dupe(u8, edge.label[match..]);
430 allocator.free(edge.label);
431 const to_node = edge.to;
432 edge.to = mid;
433 edge.label = try allocator.dupe(u8, label[0..match]);
434 self.base.node_count += 1;
435
436 try mid.edges.append(allocator, .{
437 .from = mid,
438 .to = to_node,
439 .label = to_label,
440 });
441
442 return if (match == label.len) mid else mid.put(allocator, label[match..]);
443 }
444
445 // Add a new node.
446 const node = try allocator.create(Node);
447 node.* = .{ .base = self.base };
448 self.base.node_count += 1;
449
450 try self.edges.append(allocator, .{
451 .from = self,
452 .to = node,
453 .label = try allocator.dupe(u8, label),
454 });
455
456 return node;
457 }
458
459 /// Recursively parses the node from the input byte stream.
460 fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
461 self.node_dirty = true;
462 const trie_offset = try reader.context.getPos();
463 self.trie_offset = trie_offset;
464
465 var nread: usize = 0;
466
467 const node_size = try leb.readULEB128(u64, reader);
468 if (node_size > 0) {
469 const export_flags = try leb.readULEB128(u64, reader);
470 // TODO Parse special flags.
471 assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
472 export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
473
474 const vmaddr_offset = try leb.readULEB128(u64, reader);
475
476 self.terminal_info = .{
477 .export_flags = export_flags,
478 .vmaddr_offset = vmaddr_offset,
479 };
480 }
481
482 const nedges = try reader.readByte();
483 self.base.node_count += nedges;
484
485 nread += (try reader.context.getPos()) - trie_offset;
486
487 var i: usize = 0;
488 while (i < nedges) : (i += 1) {
489 const edge_start_pos = try reader.context.getPos();
490
491 const label = blk: {
492 var label_buf = std.ArrayList(u8).init(allocator);
493 while (true) {
494 const next = try reader.readByte();
495 if (next == @as(u8, 0))
496 break;
497 try label_buf.append(next);
498 }
499 break :blk try label_buf.toOwnedSlice();
500 };
501
502 const seek_to = try leb.readULEB128(u64, reader);
503 const return_pos = try reader.context.getPos();
504
505 nread += return_pos - edge_start_pos;
506 try reader.context.seekTo(seek_to);
507
508 const node = try allocator.create(Node);
509 node.* = .{ .base = self.base };
510
511 nread += try node.read(allocator, reader);
512 try self.edges.append(allocator, .{
513 .from = self,
514 .to = node,
515 .label = label,
516 });
517 try reader.context.seekTo(return_pos);
518 }
519
520 return nread;
521 }
522
523 /// Writes this node to a byte stream.
524 /// The children of this node *are* not written to the byte stream
525 /// recursively. To write all nodes to a byte stream in sequence,
526 /// iterate over `Trie.ordered_nodes` and call this method on each node.
527 /// This is one of the requirements of the MachO.
528 /// Panics if `finalize` was not called before calling this method.
529 fn write(self: Node, writer: anytype) !void {
530 assert(!self.node_dirty);
531 if (self.terminal_info) |info| {
532 // Terminal node info: encode export flags and vmaddr offset of this symbol.
533 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
534 var info_stream = std.io.fixedBufferStream(&info_buf);
535 // TODO Implement for special flags.
536 assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
537 info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
538 try leb.writeULEB128(info_stream.writer(), info.export_flags);
539 try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
540
541 // Encode the size of the terminal node info.
542 var size_buf: [@sizeOf(u64)]u8 = undefined;
543 var size_stream = std.io.fixedBufferStream(&size_buf);
544 try leb.writeULEB128(size_stream.writer(), info_stream.pos);
545
546 // Now, write them to the output stream.
547 try writer.writeAll(size_buf[0..size_stream.pos]);
548 try writer.writeAll(info_buf[0..info_stream.pos]);
549 } else {
550 // Non-terminal node is delimited by 0 byte.
551 try writer.writeByte(0);
552 }
553 // Write number of edges (max legal number of edges is 256).
554 try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
555
556 for (self.edges.items) |edge| {
557 // Write edge label and offset to next node in trie.
558 try writer.writeAll(edge.label);
559 try writer.writeByte(0);
560 try leb.writeULEB128(writer, edge.to.trie_offset.?);
561 }
562 }
563
564 const FinalizeResult = struct {
565 /// Current size of this node in bytes.
566 node_size: u64,
567
568 /// True if the trie offset of this node in the output byte stream
569 /// would need updating; false otherwise.
570 updated: bool,
571 };
572
573 /// Updates offset of this node in the output byte stream.
574 fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
575 var stream = std.io.countingWriter(std.io.null_writer);
576 const writer = stream.writer();
577
578 var node_size: u64 = 0;
579 if (self.terminal_info) |info| {
580 try leb.writeULEB128(writer, info.export_flags);
581 try leb.writeULEB128(writer, info.vmaddr_offset);
582 try leb.writeULEB128(writer, stream.bytes_written);
583 } else {
584 node_size += 1; // 0x0 for non-terminal nodes
585 }
586 node_size += 1; // 1 byte for edge count
587
588 for (self.edges.items) |edge| {
589 const next_node_offset = edge.to.trie_offset orelse 0;
590 node_size += edge.label.len + 1;
591 try leb.writeULEB128(writer, next_node_offset);
592 }
593
594 const trie_offset = self.trie_offset orelse 0;
595 const updated = offset_in_trie != trie_offset;
596 self.trie_offset = offset_in_trie;
597 self.node_dirty = false;
598 node_size += stream.bytes_written;
599
600 return FinalizeResult{ .node_size = node_size, .updated = updated };
601 }
602};
603
604const Trie = @This();
605
606const std = @import("std");
607const mem = std.mem;
608const leb = std.leb;
609const log = std.log.scoped(.link);
610const macho = std.macho;
611const testing = std.testing;
612const assert = std.debug.assert;
613const Allocator = mem.Allocator;
src/link/MachO/UnwindInfo.zig+429-578
...@@ -1,376 +1,122 @@...@@ -1,376 +1,122 @@
1gpa: Allocator,
2
3/// List of all unwind records gathered from all objects and sorted1/// 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.
5records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{},3records: std.ArrayListUnmanaged(Record.Index) = .{},
6records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{},
74
8/// List of all personalities referenced by either unwind info entries5/// List of all personalities referenced by either unwind info entries
9/// or __eh_frame entries.6/// or __eh_frame entries.
10personalities: [max_personalities]SymbolWithLoc = undefined,7personalities: [max_personalities]Symbol.Index = undefined,
11personalities_count: u2 = 0,8personalities_count: u2 = 0,
129
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.
14common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined,11common_encodings: [max_common_encodings]Encoding = undefined,
15common_encodings_count: u7 = 0,12common_encodings_count: u7 = 0,
1613
17/// List of record indexes containing an LSDA pointer.14/// List of record indexes containing an LSDA pointer.
18lsdas: std.ArrayListUnmanaged(RecordIndex) = .{},15lsdas: std.ArrayListUnmanaged(u32) = .{},
19lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{},16lsdas_lookup: std.ArrayListUnmanaged(u32) = .{},
2017
21/// List of second level pages.18/// List of second level pages.
22pages: std.ArrayListUnmanaged(Page) = .{},19pages: std.ArrayListUnmanaged(Page) = .{},
2320
24/// Upper bound (exclusive) of all the record ranges21pub fn deinit(info: *UnwindInfo, allocator: Allocator) void {
25end_boundary: u64 = 0,22 info.records.deinit(allocator);
2623 info.pages.deinit(allocator);
27const RecordIndex = u32;24 info.lsdas.deinit(allocator);
2825 info.lsdas_lookup.deinit(allocator);
29const max_personalities = 3;26}
30const max_common_encodings = 127;
31const max_compact_encodings = 256;
32
33const second_level_page_bytes = 0x1000;
34const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
35
36const max_regular_second_level_entries =
37 (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
38 @sizeOf(macho.unwind_info_regular_second_level_entry);
39
40const max_compressed_second_level_entries =
41 (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
42 @sizeOf(u32);
43
44const compressed_entry_func_offset_mask = ~@as(u24, 0);
45
46const Page = struct {
47 kind: enum { regular, compressed },
48 start: RecordIndex,
49 count: u16,
50 page_encodings: [max_compact_encodings]RecordIndex = undefined,
51 page_encodings_count: u9 = 0,
52
53 fn appendPageEncoding(page: *Page, record_id: RecordIndex) void {
54 assert(page.page_encodings_count <= max_compact_encodings);
55 page.page_encodings[page.page_encodings_count] = record_id;
56 page.page_encodings_count += 1;
57 }
58
59 fn getPageEncoding(
60 page: *const Page,
61 info: *const UnwindInfo,
62 enc: macho.compact_unwind_encoding_t,
63 ) ?u8 {
64 comptime var index: u9 = 0;
65 inline while (index < max_compact_encodings) : (index += 1) {
66 if (index >= page.page_encodings_count) return null;
67 const record_id = page.page_encodings[index];
68 const record = info.records.items[record_id];
69 if (record.compactUnwindEncoding == enc) {
70 return @as(u8, @intCast(index));
71 }
72 }
73 return null;
74 }
75
76 fn format(
77 page: *const Page,
78 comptime unused_format_string: []const u8,
79 options: std.fmt.FormatOptions,
80 writer: anytype,
81 ) !void {
82 _ = page;
83 _ = unused_format_string;
84 _ = options;
85 _ = writer;
86 @compileError("do not format Page directly; use page.fmtDebug()");
87 }
88
89 const DumpCtx = struct {
90 page: *const Page,
91 info: *const UnwindInfo,
92 };
93
94 fn dump(
95 ctx: DumpCtx,
96 comptime unused_format_string: []const u8,
97 options: std.fmt.FormatOptions,
98 writer: anytype,
99 ) @TypeOf(writer).Error!void {
100 _ = options;
101 comptime assert(unused_format_string.len == 0);
102 try writer.writeAll("Page:\n");
103 try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
104 try writer.print(" entries: {d} - {d}\n", .{
105 ctx.page.start,
106 ctx.page.start + ctx.page.count,
107 });
108 try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
109 for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |record_id, i| {
110 const record = ctx.info.records.items[record_id];
111 const enc = record.compactUnwindEncoding;
112 try writer.print(" {d}: 0x{x:0>8}\n", .{ ctx.info.common_encodings_count + i, enc });
113 }
114 }
115
116 fn fmtDebug(page: *const Page, info: *const UnwindInfo) std.fmt.Formatter(dump) {
117 return .{ .data = .{
118 .page = page,
119 .info = info,
120 } };
121 }
122
123 fn write(page: *const Page, info: *const UnwindInfo, writer: anytype) !void {
124 switch (page.kind) {
125 .regular => {
126 try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
127 .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
128 .entryCount = page.count,
129 });
130
131 for (info.records.items[page.start..][0..page.count]) |record| {
132 try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
133 .functionOffset = @as(u32, @intCast(record.rangeStart)),
134 .encoding = record.compactUnwindEncoding,
135 });
136 }
137 },
138 .compressed => {
139 const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
140 @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
141 try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
142 .entryPageOffset = entry_offset,
143 .entryCount = page.count,
144 .encodingsPageOffset = @sizeOf(
145 macho.unwind_info_compressed_second_level_page_header,
146 ),
147 .encodingsCount = page.page_encodings_count,
148 });
149
150 for (page.page_encodings[0..page.page_encodings_count]) |record_id| {
151 const enc = info.records.items[record_id].compactUnwindEncoding;
152 try writer.writeInt(u32, enc, .little);
153 }
154
155 assert(page.count > 0);
156 const first_entry = info.records.items[page.start];
157 for (info.records.items[page.start..][0..page.count]) |record| {
158 const enc_index = blk: {
159 if (info.getCommonEncoding(record.compactUnwindEncoding)) |id| {
160 break :blk id;
161 }
162 const ncommon = info.common_encodings_count;
163 break :blk ncommon + page.getPageEncoding(info, record.compactUnwindEncoding).?;
164 };
165 const compressed = macho.UnwindInfoCompressedEntry{
166 .funcOffset = @as(u24, @intCast(record.rangeStart - first_entry.rangeStart)),
167 .encodingIndex = @as(u8, @intCast(enc_index)),
168 };
169 try writer.writeStruct(compressed);
170 }
171 },
172 }
173 }
174};
17527
176pub fn deinit(info: *UnwindInfo) void {28fn 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.options.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}
18343
184pub fn scanRelocs(macho_file: *MachO) !void {44pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
185 if (macho_file.unwind_info_section_index == null) return;45 const gpa = macho_file.base.allocator;
18646
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
214pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
215 if (macho_file.unwind_info_section_index == null) return;
216
217 const target = macho_file.base.comp.root_mod.resolved_target.result;
218 const cpu_arch = target.cpu.arch;
219
220 var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
221 defer records.deinit();
222
223 var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa);
224 defer sym_indexes.deinit();
225
226 // TODO handle dead stripping
227 for (macho_file.objects.items, 0..) |*object, object_id| {
228 log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id });
229 const unwind_records = object.getUnwindRecords();
230
231 // Contents of unwind records does not have to cover all symbol in executable section
232 // so we need insert them ourselves.
233 try records.ensureUnusedCapacity(object.exec_atoms.items.len);
234 try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
235
236 for (object.exec_atoms.items) |atom_index| {
237 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
238 var prev_symbol: ?SymbolWithLoc = null;
239 while (inner_syms_it.next()) |symbol| {
240 var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
241 if (object.unwind_relocs_lookup[record_id].dead) continue;
242 var record = unwind_records[record_id];
243
244 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
245 info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
246 } else {
247 if (getPersonalityFunctionReloc(
248 macho_file,
249 @as(u32, @intCast(object_id)),
250 record_id,
251 )) |rel| {
252 const reloc_target = Atom.parseRelocTarget(macho_file, .{
253 .object_id = @as(u32, @intCast(object_id)),
254 .rel = rel,
255 .code = mem.asBytes(&record),
256 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
257 });
258 const personality_index = info.getPersonalityFunction(reloc_target) orelse inner: {
259 const personality_index = info.personalities_count;
260 info.personalities[personality_index] = reloc_target;
261 info.personalities_count += 1;
262 break :inner personality_index;
263 };
264
265 record.personalityFunction = personality_index + 1;
266 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
267 }
268
269 if (getLsdaReloc(macho_file, @as(u32, @intCast(object_id)), record_id)) |rel| {
270 const reloc_target = Atom.parseRelocTarget(macho_file, .{
271 .object_id = @as(u32, @intCast(object_id)),
272 .rel = rel,
273 .code = mem.asBytes(&record),
274 .base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
275 });
276 record.lsda = @as(u64, @bitCast(reloc_target));
277 }
278 }
279 break :blk record;
280 } else blk: {
281 const sym = macho_file.getSymbol(symbol);
282 if (sym.n_desc == MachO.N_DEAD) continue;
283 if (prev_symbol) |prev_sym| {
284 const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
285 const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
286 if (prev_addr == curr_addr) continue;
287 }
288
289 if (!object.hasUnwindRecords()) {
290 if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
291 if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
292 var record = nullRecord();
293 info.collectPersonalityFromDwarf(macho_file, @as(u32, @intCast(object_id)), symbol, &record);
294 switch (cpu_arch) {
295 .aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
296 .x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
297 else => unreachable,
298 }
299 break :blk record;
300 }
301 }
302
303 break :blk nullRecord();
304 };
30562
306 const atom = macho_file.getAtom(atom_index);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 Atom68 } else if (rec.getPersonality(macho_file)) |_| {
312 // is the way to go, kinda like we do for ELF. We might also want to perhaps tag69 const personality_index = try info.getOrPutPersonalityFunction(rec.personality.?); // TODO handle error
313 // symbol aliases somehow so that they are excluded from everything except relocation70 rec.enc.setPersonalityIndex(personality_index + 1);
314 // resolution.
315 defer inner_syms_it.pos -= 1;
316 const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
317 const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
318 if (next_addr > curr_addr) break :blk next_addr - curr_addr;
319 break :blk macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
320 } else macho_file.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
321 record.rangeStart = sym.n_value;
322 record.rangeLength = @as(u32, @intCast(size));
323
324 try records.append(record);
325 try sym_indexes.append(symbol);
326
327 prev_symbol = symbol;
328 }
329 }71 }
330 }72 }
33173
332 // Record the ending boundary before folding.74 // Sort by assigned relative address within each output section
333 assert(records.items.len > 0);75 const sortFn = struct {
334 info.end_boundary = blk: {76 fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool {
335 const last_record = records.items[records.items.len - 1];77 const lhs = ctx.getUnwindRecord(lhs_index);
336 break :blk last_record.rangeStart + last_record.rangeLength;78 const rhs = ctx.getUnwindRecord(rhs_index);
337 };79 const lhsa = lhs.getAtom(ctx);
80 const rhsa = rhs.getAtom(ctx);
81 if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx);
82 return lhsa.out_n_sect < rhsa.out_n_sect;
83 }
84 }.sortFn;
85 mem.sort(Record.Index, info.records.items, macho_file, sortFn);
33886
339 // Fold records87 // Fold the records
340 try info.records.ensureTotalCapacity(info.gpa, records.items.len);88 // 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)));89 {
34290 var i: usize = 0;
343 var maybe_prev: ?macho.compact_unwind_entry = null;91 var j: usize = 1;
344 for (records.items, 0..) |record, i| {92 while (j < info.records.items.len) : (j += 1) {
345 const record_id = blk: {93 if (canFold(macho_file, info.records.items[i], info.records.items[j])) {
346 if (maybe_prev) |prev| {94 const rec = macho_file.getUnwindRecord(info.records.items[i]);
347 const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);95 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 {96 } else {
361 const record_id = @as(RecordIndex, @intCast(info.records.items.len));97 i += 1;
362 info.records.appendAssumeCapacity(record);98 info.records.items[i] = info.records.items[j];
363 maybe_prev = record;
364 break :blk record_id;
365 }99 }
366 };100 }
367 info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id);101 info.records.shrinkAndFree(gpa, i + 1);
102 }
103
104 for (info.records.items) |rec_index| {
105 const rec = macho_file.getUnwindRecord(rec_index);
106 const atom = rec.getAtom(macho_file);
107 log.debug("@{x}-{x} : {s} : rec({d}) : {}", .{
108 rec.getAtomAddress(macho_file),
109 rec.getAtomAddress(macho_file) + rec.length,
110 atom.getName(macho_file),
111 rec_index,
112 rec.enc,
113 });
368 }114 }
369115
370 // Calculate common encodings116 // Calculate common encodings
371 {117 {
372 const CommonEncWithCount = struct {118 const CommonEncWithCount = struct {
373 enc: macho.compact_unwind_encoding_t,119 enc: Encoding,
374 count: u32,120 count: u32,
375121
376 fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool {122 fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool {
...@@ -380,39 +126,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -380,39 +126,38 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
380 };126 };
381127
382 const Context = struct {128 const Context = struct {
383 pub fn hash(ctx: @This(), key: macho.compact_unwind_encoding_t) u32 {129 pub fn hash(ctx: @This(), key: Encoding) u32 {
384 _ = ctx;130 _ = ctx;
385 return key;131 return key.enc;
386 }132 }
387133
388 pub fn eql(134 pub fn eql(
389 ctx: @This(),135 ctx: @This(),
390 key1: macho.compact_unwind_encoding_t,136 key1: Encoding,
391 key2: macho.compact_unwind_encoding_t,137 key2: Encoding,
392 b_index: usize,138 b_index: usize,
393 ) bool {139 ) bool {
394 _ = ctx;140 _ = ctx;
395 _ = b_index;141 _ = b_index;
396 return key1 == key2;142 return key1.eql(key2);
397 }143 }
398 };144 };
399145
400 var common_encodings_counts = std.ArrayHashMap(146 var common_encodings_counts = std.ArrayHashMap(
401 macho.compact_unwind_encoding_t,147 Encoding,
402 CommonEncWithCount,148 CommonEncWithCount,
403 Context,149 Context,
404 false,150 false,
405 ).init(info.gpa);151 ).init(gpa);
406 defer common_encodings_counts.deinit();152 defer common_encodings_counts.deinit();
407153
408 for (info.records.items) |record| {154 for (info.records.items) |rec_index| {
409 assert(!isNull(record));155 const rec = macho_file.getUnwindRecord(rec_index);
410 if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue;156 if (rec.enc.isDwarf(macho_file)) continue;
411 const enc = record.compactUnwindEncoding;157 const gop = try common_encodings_counts.getOrPut(rec.enc);
412 const gop = try common_encodings_counts.getOrPut(enc);
413 if (!gop.found_existing) {158 if (!gop.found_existing) {
414 gop.value_ptr.* = .{159 gop.value_ptr.* = .{
415 .enc = enc,160 .enc = rec.enc,
416 .count = 0,161 .count = 0,
417 };162 };
418 }163 }
...@@ -427,7 +172,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -427,7 +172,7 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
427 if (i >= max_common_encodings) break;172 if (i >= max_common_encodings) break;
428 if (slice[i].count < 2) continue;173 if (slice[i].count < 2) continue;
429 info.appendCommonEncoding(slice[i].enc);174 info.appendCommonEncoding(slice[i].enc);
430 log.debug("adding common encoding: {d} => 0x{x:0>8}", .{ i, slice[i].enc });175 log.debug("adding common encoding: {d} => {}", .{ i, slice[i].enc });
431 }176 }
432 }177 }
433178
...@@ -435,8 +180,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -435,8 +180,8 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
435 {180 {
436 var i: u32 = 0;181 var i: u32 = 0;
437 while (i < info.records.items.len) {182 while (i < info.records.items.len) {
438 const range_start_max: u64 =183 const rec = macho_file.getUnwindRecord(info.records.items[i]);
439 info.records.items[i].rangeStart + compressed_entry_func_offset_mask;184 const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask;
440 var encoding_count: u9 = info.common_encodings_count;185 var encoding_count: u9 = info.common_encodings_count;
441 var space_left: u32 = second_level_page_words -186 var space_left: u32 = second_level_page_words -
442 @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32);187 @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32);
...@@ -447,19 +192,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -447,19 +192,18 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
447 };192 };
448193
449 while (space_left >= 1 and i < info.records.items.len) {194 while (space_left >= 1 and i < info.records.items.len) {
450 const record = info.records.items[i];195 const next = macho_file.getUnwindRecord(info.records.items[i]);
451 const enc = record.compactUnwindEncoding;196 const is_dwarf = next.enc.isDwarf(macho_file);
452 const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
453197
454 if (record.rangeStart >= range_start_max) {198 if (next.getAtomAddress(macho_file) >= range_start_max) {
455 break;199 break;
456 } else if (info.getCommonEncoding(enc) != null or200 } else if (info.getCommonEncoding(next.enc) != null or
457 page.getPageEncoding(info, enc) != null and !is_dwarf)201 page.getPageEncoding(next.enc) != null and !is_dwarf)
458 {202 {
459 i += 1;203 i += 1;
460 space_left -= 1;204 space_left -= 1;
461 } else if (space_left >= 2 and encoding_count < max_compact_encodings) {205 } else if (space_left >= 2 and encoding_count < max_compact_encodings) {
462 page.appendPageEncoding(i);206 page.appendPageEncoding(next.enc);
463 i += 1;207 i += 1;
464 space_left -= 2;208 space_left -= 2;
465 encoding_count += 1;209 encoding_count += 1;
...@@ -481,63 +225,24 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -481,63 +225,24 @@ pub fn collect(info: *UnwindInfo, macho_file: *MachO) !void {
481 page.kind = .compressed;225 page.kind = .compressed;
482 }226 }
483227
484 log.debug("{}", .{page.fmtDebug(info)});228 log.debug("{}", .{page.fmt(info.*)});
485229
486 try info.pages.append(info.gpa, page);230 try info.pages.append(gpa, page);
487 }231 }
488 }232 }
489233
490 // Save indices of records requiring LSDA relocation234 // Save records having an LSDA pointer
491 try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(info.records.items.len)));235 try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len);
492 for (info.records.items, 0..) |rec, i| {236 for (info.records.items, 0..) |index, i| {
493 info.lsdas_lookup.putAssumeCapacityNoClobber(@as(RecordIndex, @intCast(i)), @as(u32, @intCast(info.lsdas.items.len)));237 const rec = macho_file.getUnwindRecord(index);
494 if (rec.lsda == 0) continue;238 info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len));
495 try info.lsdas.append(info.gpa, @as(RecordIndex, @intCast(i)));239 if (rec.getLsdaAtom(macho_file)) |_| {
496 }240 try info.lsdas.append(gpa, @intCast(i));
497}241 }
498
499fn collectPersonalityFromDwarf(
500 info: *UnwindInfo,
501 macho_file: *MachO,
502 object_id: u32,
503 sym_loc: SymbolWithLoc,
504 record: *macho.compact_unwind_entry,
505) void {
506 const object = &macho_file.objects.items[object_id];
507 var it = object.getEhFrameRecordsIterator();
508 const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
509 it.seekTo(fde_offset);
510 const fde = (it.next() catch return).?; // We don't care about the error since we already handled it
511 const cie_ptr = fde.getCiePointerSource(object_id, macho_file, fde_offset);
512 const cie_offset = fde_offset + 4 - cie_ptr;
513 it.seekTo(cie_offset);
514 const cie = (it.next() catch return).?; // We don't care about the error since we already handled it
515
516 if (cie.getPersonalityPointerReloc(
517 macho_file,
518 @as(u32, @intCast(object_id)),
519 cie_offset,
520 )) |target| {
521 const personality_index = info.getPersonalityFunction(target) orelse inner: {
522 const personality_index = info.personalities_count;
523 info.personalities[personality_index] = target;
524 info.personalities_count += 1;
525 break :inner personality_index;
526 };
527
528 record.personalityFunction = personality_index + 1;
529 UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
530 }242 }
531}243}
532244
533pub fn calcSectionSize(info: UnwindInfo, macho_file: *MachO) void {245pub 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
540fn calcRequiredSize(info: UnwindInfo) usize {
541 var total_size: usize = 0;246 var total_size: usize = 0;
542 total_size += @sizeOf(macho.unwind_info_section_header);247 total_size += @sizeOf(macho.unwind_info_section_header);
543 total_size +=248 total_size +=
...@@ -549,59 +254,12 @@ fn calcRequiredSize(info: UnwindInfo) usize {...@@ -549,59 +254,12 @@ fn calcRequiredSize(info: UnwindInfo) usize {
549 return total_size;254 return total_size;
550}255}
551256
552pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {257pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
553 const sect_id = macho_file.unwind_info_section_index orelse return;258 const seg = macho_file.getTextSegment();
554 const sect = &macho_file.sections.items(.header)[sect_id];259 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];
560260
561 var personalities: [max_personalities]u32 = undefined;261 var stream = std.io.fixedBufferStream(buffer);
562 const target = macho_file.base.comp.root_mod.resolved_target.result;262 var cwriter = std.io.countingWriter(stream.writer());
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
598 var buffer = std.ArrayList(u8).init(info.gpa);
599 defer buffer.deinit();
600
601 const size = info.calcRequiredSize();
602 try buffer.ensureTotalCapacityPrecise(size);
603
604 var cwriter = std.io.countingWriter(buffer.writer());
605 const writer = cwriter.writer();263 const writer = cwriter.writer();
606264
607 const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header);265 const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header);
...@@ -621,211 +279,404 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {...@@ -621,211 +279,404 @@ pub fn write(info: *UnwindInfo, macho_file: *MachO) !void {
621 });279 });
622280
623 try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count]));281 try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count]));
624 try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count]));
625282
626 const pages_base_offset = @as(u32, @intCast(size - (info.pages.items.len * second_level_page_bytes)));283 for (info.personalities[0..info.personalities_count]) |sym_index| {
284 const sym = macho_file.getSymbol(sym_index);
285 try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little);
286 }
287
288 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 -289 const lsda_base_offset = @as(u32, @intCast(pages_base_offset -
628 (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));290 (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));
629 for (info.pages.items, 0..) |page, i| {291 for (info.pages.items, 0..) |page, i| {
630 assert(page.count > 0);292 assert(page.count > 0);
631 const first_entry = info.records.items[page.start];293 const rec = macho_file.getUnwindRecord(info.records.items[page.start]);
632 try writer.writeStruct(macho.unwind_info_section_header_index_entry{294 try writer.writeStruct(macho.unwind_info_section_header_index_entry{
633 .functionOffset = @as(u32, @intCast(first_entry.rangeStart)),295 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
634 .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)),296 .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)),
635 .lsdaIndexArraySectionOffset = lsda_base_offset +297 .lsdaIndexArraySectionOffset = lsda_base_offset +
636 info.lsdas_lookup.get(page.start).? * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),298 info.lsdas_lookup.items[page.start] * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
637 });299 });
638 }300 }
639301
640 // Relocate end boundary address302 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));303 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{304 try writer.writeStruct(macho.unwind_info_section_header_index_entry{
643 .functionOffset = end_boundary,305 .functionOffset = sentinel_address,
644 .secondLevelPagesSectionOffset = 0,306 .secondLevelPagesSectionOffset = 0,
645 .lsdaIndexArraySectionOffset = lsda_base_offset +307 .lsdaIndexArraySectionOffset = lsda_base_offset +
646 @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),308 @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
647 });309 });
648310
649 for (info.lsdas.items) |record_id| {311 for (info.lsdas.items) |index| {
650 const record = info.records.items[record_id];312 const rec = macho_file.getUnwindRecord(info.records.items[index]);
651 try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{313 try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{
652 .functionOffset = @as(u32, @intCast(record.rangeStart)),314 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
653 .lsdaOffset = @as(u32, @intCast(record.lsda)),315 .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)),
654 });316 });
655 }317 }
656318
657 for (info.pages.items) |page| {319 for (info.pages.items) |page| {
658 const start = cwriter.bytes_written;320 const start = cwriter.bytes_written;
659 try page.write(info, writer);321 try page.write(info, macho_file, writer);
660 const nwritten = cwriter.bytes_written - start;322 const nwritten = cwriter.bytes_written - start;
661 if (nwritten < second_level_page_bytes) {323 if (nwritten < second_level_page_bytes) {
662 const offset = math.cast(usize, second_level_page_bytes - nwritten) orelse return error.Overflow;324 try writer.writeByteNTimes(0, second_level_page_bytes - nwritten);
663 try writer.writeByteNTimes(0, offset);
664 }325 }
665 }326 }
666327
667 const padding = buffer.items.len - cwriter.bytes_written;328 const padding = buffer.len - cwriter.bytes_written;
668 if (padding > 0) {329 if (padding > 0) {
669 const offset = math.cast(usize, cwriter.bytes_written) orelse return error.Overflow;330 @memset(buffer[cwriter.bytes_written..], 0);
670 @memset(buffer.items[offset..], 0);
671 }
672
673 try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);
674}
675
676fn getRelocs(macho_file: *MachO, object_id: u32, record_id: usize) []const macho.relocation_info {
677 const object = &macho_file.objects.items[object_id];
678 assert(object.hasUnwindRecords());
679 const rel_pos = object.unwind_relocs_lookup[record_id].reloc;
680 const relocs = object.getRelocs(object.unwind_info_sect_id.?);
681 return relocs[rel_pos.start..][0..rel_pos.len];
682}
683
684fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool {
685 const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
686 const rel_offset = rel.r_address - base_offset;
687 return rel_offset == 16;
688}
689
690pub fn getPersonalityFunctionReloc(
691 macho_file: *MachO,
692 object_id: u32,
693 record_id: usize,
694) ?macho.relocation_info {
695 const relocs = getRelocs(macho_file, object_id, record_id);
696 for (relocs) |rel| {
697 if (isPersonalityFunction(record_id, rel)) return rel;
698 }331 }
699 return null;
700}332}
701333
702fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 {334fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 {
703 comptime var index: u2 = 0;335 comptime var index: u2 = 0;
704 inline while (index < max_personalities) : (index += 1) {336 inline while (index < max_personalities) : (index += 1) {
705 if (index >= info.personalities_count) return null;337 if (info.personalities[index] == sym_index) {
706 if (info.personalities[index].eql(global_index)) {338 return index;
339 } else if (index == info.personalities_count) {
340 info.personalities[index] = sym_index;
341 info.personalities_count += 1;
707 return index;342 return index;
708 }343 }
709 }344 }
710 return null;345 return error.TooManyPersonalities;
711}
712
713fn isLsda(record_id: usize, rel: macho.relocation_info) bool {
714 const base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry)));
715 const rel_offset = rel.r_address - base_offset;
716 return rel_offset == 24;
717}
718
719pub fn getLsdaReloc(macho_file: *MachO, object_id: u32, record_id: usize) ?macho.relocation_info {
720 const relocs = getRelocs(macho_file, object_id, record_id);
721 for (relocs) |rel| {
722 if (isLsda(record_id, rel)) return rel;
723 }
724 return null;
725}346}
726347
727pub fn isNull(rec: macho.compact_unwind_entry) bool {348fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void {
728 return rec.rangeStart == 0 and
729 rec.rangeLength == 0 and
730 rec.compactUnwindEncoding == 0 and
731 rec.lsda == 0 and
732 rec.personalityFunction == 0;
733}
734
735inline fn nullRecord() macho.compact_unwind_entry {
736 return .{
737 .rangeStart = 0,
738 .rangeLength = 0,
739 .compactUnwindEncoding = 0,
740 .personalityFunction = 0,
741 .lsda = 0,
742 };
743}
744
745fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void {
746 assert(info.common_encodings_count <= max_common_encodings);349 assert(info.common_encodings_count <= max_common_encodings);
747 info.common_encodings[info.common_encodings_count] = enc;350 info.common_encodings[info.common_encodings_count] = enc;
748 info.common_encodings_count += 1;351 info.common_encodings_count += 1;
749}352}
750353
751fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 {354fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 {
752 comptime var index: u7 = 0;355 comptime var index: u7 = 0;
753 inline while (index < max_common_encodings) : (index += 1) {356 inline while (index < max_common_encodings) : (index += 1) {
754 if (index >= info.common_encodings_count) return null;357 if (index >= info.common_encodings_count) return null;
755 if (info.common_encodings[index] == enc) {358 if (info.common_encodings[index].eql(enc)) {
756 return index;359 return index;
757 }360 }
758 }361 }
759 return null;362 return null;
760}363}
761364
762pub const UnwindEncoding = struct {365pub const Encoding = extern struct {
763 pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 {366 enc: macho.compact_unwind_encoding_t,
367
368 pub fn getMode(enc: Encoding) u4 {
764 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);369 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));370 return @as(u4, @truncate((enc.enc & macho.UNWIND_ARM64_MODE_MASK) >> 24));
766 }371 }
767372
768 pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool {373 pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool {
769 const mode = getMode(enc);374 const mode = enc.getMode();
770 return switch (cpu_arch) {375 return switch (macho_file.options.cpu_arch.?) {
771 .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF,376 .aarch64 => @as(macho.UNWIND_ARM64_MODE, @enumFromInt(mode)) == .DWARF,
772 .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF,377 .x86_64 => @as(macho.UNWIND_X86_64_MODE, @enumFromInt(mode)) == .DWARF,
773 else => unreachable,378 else => unreachable,
774 };379 };
775 }380 }
776381
777 pub fn setMode(enc: *macho.compact_unwind_encoding_t, mode: anytype) void {382 pub fn setMode(enc: *Encoding, mode: anytype) void {
778 enc.* |= @as(u32, @intCast(@intFromEnum(mode))) << 24;383 enc.enc |= @as(u32, @intCast(@intFromEnum(mode))) << 24;
779 }384 }
780385
781 pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool {386 pub fn hasLsda(enc: Encoding) bool {
782 const has_lsda = @as(u1, @truncate((enc & macho.UNWIND_HAS_LSDA) >> 31));387 const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> 31));
783 return has_lsda == 1;388 return has_lsda == 1;
784 }389 }
785390
786 pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void {391 pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void {
787 const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31;392 const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << 31;
788 enc.* |= mask;393 enc.enc |= mask;
789 }394 }
790395
791 pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 {396 pub fn getPersonalityIndex(enc: Encoding) u2 {
792 const index = @as(u2, @truncate((enc & macho.UNWIND_PERSONALITY_MASK) >> 28));397 const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> 28));
793 return index;398 return index;
794 }399 }
795400
796 pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void {401 pub fn setPersonalityIndex(enc: *Encoding, index: u2) void {
797 const mask = @as(u32, @intCast(index)) << 28;402 const mask = @as(u32, @intCast(index)) << 28;
798 enc.* |= mask;403 enc.enc |= mask;
799 }404 }
800405
801 pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 {406 pub fn getDwarfSectionOffset(enc: Encoding) u24 {
802 assert(isDwarf(enc, cpu_arch));407 const offset = @as(u24, @truncate(enc.enc));
803 const offset = @as(u24, @truncate(enc));
804 return offset;408 return offset;
805 }409 }
806410
807 pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void {411 pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void {
808 assert(isDwarf(enc.*, cpu_arch));412 enc.enc |= offset;
809 enc.* |= offset;413 }
414
415 pub fn eql(enc: Encoding, other: Encoding) bool {
416 return enc.enc == other.enc;
417 }
418
419 pub fn format(
420 enc: Encoding,
421 comptime unused_fmt_string: []const u8,
422 options: std.fmt.FormatOptions,
423 writer: anytype,
424 ) !void {
425 _ = unused_fmt_string;
426 _ = options;
427 try writer.print("0x{x:0>8}", .{enc.enc});
810 }428 }
811};429};
812430
813const UnwindInfo = @This();431pub const Record = struct {
432 length: u32 = 0,
433 enc: Encoding = .{ .enc = 0 },
434 atom: Atom.Index = 0,
435 atom_offset: u32 = 0,
436 lsda: Atom.Index = 0,
437 lsda_offset: u32 = 0,
438 personality: ?Symbol.Index = null, // TODO make this zero-is-null
439 fde: Fde.Index = 0, // TODO actually make FDE at 0 an invalid FDE
440 file: File.Index = 0,
441 alive: bool = true,
442
443 pub fn getObject(rec: Record, macho_file: *MachO) *Object {
444 return macho_file.getFile(rec.file).?.object;
445 }
446
447 pub fn getAtom(rec: Record, macho_file: *MachO) *Atom {
448 return macho_file.getAtom(rec.atom).?;
449 }
450
451 pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom {
452 return macho_file.getAtom(rec.lsda);
453 }
454
455 pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol {
456 const personality = rec.personality orelse return null;
457 return macho_file.getSymbol(personality);
458 }
459
460 pub fn getFde(rec: Record, macho_file: *MachO) ?Fde {
461 if (!rec.enc.isDwarf(macho_file)) return null;
462 return rec.getObject(macho_file).fdes.items[rec.fde];
463 }
464
465 pub fn getFdePtr(rec: Record, macho_file: *MachO) ?*Fde {
466 if (!rec.enc.isDwarf(macho_file)) return null;
467 return &rec.getObject(macho_file).fdes.items[rec.fde];
468 }
469
470 pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 {
471 const atom = rec.getAtom(macho_file);
472 return atom.value + rec.atom_offset;
473 }
474
475 pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 {
476 const lsda = rec.getLsdaAtom(macho_file) orelse return 0;
477 return lsda.value + rec.lsda_offset;
478 }
479
480 pub fn format(
481 rec: Record,
482 comptime unused_fmt_string: []const u8,
483 options: std.fmt.FormatOptions,
484 writer: anytype,
485 ) !void {
486 _ = rec;
487 _ = unused_fmt_string;
488 _ = options;
489 _ = writer;
490 @compileError("do not format UnwindInfo.Records directly");
491 }
492
493 pub fn fmt(rec: Record, macho_file: *MachO) std.fmt.Formatter(format2) {
494 return .{ .data = .{
495 .rec = rec,
496 .macho_file = macho_file,
497 } };
498 }
499
500 const FormatContext = struct {
501 rec: Record,
502 macho_file: *MachO,
503 };
504
505 fn format2(
506 ctx: FormatContext,
507 comptime unused_fmt_string: []const u8,
508 options: std.fmt.FormatOptions,
509 writer: anytype,
510 ) !void {
511 _ = unused_fmt_string;
512 _ = options;
513 const rec = ctx.rec;
514 const macho_file = ctx.macho_file;
515 try writer.print("{x} : len({x})", .{
516 rec.enc.enc, rec.length,
517 });
518 if (rec.enc.isDwarf(macho_file)) try writer.print(" : fde({d})", .{rec.fde});
519 try writer.print(" : {s}", .{rec.getAtom(macho_file).getName(macho_file)});
520 if (!rec.alive) try writer.writeAll(" : [*]");
521 }
522
523 pub const Index = u32;
524};
525
526const max_personalities = 3;
527const max_common_encodings = 127;
528const max_compact_encodings = 256;
529
530const second_level_page_bytes = 0x1000;
531const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
532
533const max_regular_second_level_entries =
534 (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
535 @sizeOf(macho.unwind_info_regular_second_level_entry);
536
537const max_compressed_second_level_entries =
538 (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
539 @sizeOf(u32);
540
541const compressed_entry_func_offset_mask = ~@as(u24, 0);
542
543const Page = struct {
544 kind: enum { regular, compressed },
545 start: u32,
546 count: u16,
547 page_encodings: [max_compact_encodings]Encoding = undefined,
548 page_encodings_count: u9 = 0,
549
550 fn appendPageEncoding(page: *Page, enc: Encoding) void {
551 assert(page.page_encodings_count <= max_compact_encodings);
552 page.page_encodings[page.page_encodings_count] = enc;
553 page.page_encodings_count += 1;
554 }
555
556 fn getPageEncoding(page: Page, enc: Encoding) ?u8 {
557 comptime var index: u9 = 0;
558 inline while (index < max_compact_encodings) : (index += 1) {
559 if (index >= page.page_encodings_count) return null;
560 if (page.page_encodings[index].eql(enc)) {
561 return @as(u8, @intCast(index));
562 }
563 }
564 return null;
565 }
566
567 fn format(
568 page: *const Page,
569 comptime unused_format_string: []const u8,
570 options: std.fmt.FormatOptions,
571 writer: anytype,
572 ) !void {
573 _ = page;
574 _ = unused_format_string;
575 _ = options;
576 _ = writer;
577 @compileError("do not format Page directly; use page.fmt()");
578 }
579
580 const FormatPageContext = struct {
581 page: Page,
582 info: UnwindInfo,
583 };
584
585 fn format2(
586 ctx: FormatPageContext,
587 comptime unused_format_string: []const u8,
588 options: std.fmt.FormatOptions,
589 writer: anytype,
590 ) @TypeOf(writer).Error!void {
591 _ = options;
592 _ = unused_format_string;
593 try writer.writeAll("Page:\n");
594 try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)});
595 try writer.print(" entries: {d} - {d}\n", .{
596 ctx.page.start,
597 ctx.page.start + ctx.page.count,
598 });
599 try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count});
600 for (ctx.page.page_encodings[0..ctx.page.page_encodings_count], 0..) |enc, i| {
601 try writer.print(" {d}: {}\n", .{ ctx.info.common_encodings_count + i, enc });
602 }
603 }
604
605 fn fmt(page: Page, info: UnwindInfo) std.fmt.Formatter(format2) {
606 return .{ .data = .{
607 .page = page,
608 .info = info,
609 } };
610 }
611
612 fn write(page: Page, info: UnwindInfo, macho_file: *MachO, writer: anytype) !void {
613 const seg = macho_file.getTextSegment();
614
615 switch (page.kind) {
616 .regular => {
617 try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{
618 .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
619 .entryCount = page.count,
620 });
621
622 for (info.records.items[page.start..][0..page.count]) |index| {
623 const rec = macho_file.getUnwindRecord(index);
624 try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
625 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
626 .encoding = rec.enc.enc,
627 });
628 }
629 },
630 .compressed => {
631 const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
632 @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
633 try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{
634 .entryPageOffset = entry_offset,
635 .entryCount = page.count,
636 .encodingsPageOffset = @sizeOf(macho.unwind_info_compressed_second_level_page_header),
637 .encodingsCount = page.page_encodings_count,
638 });
639
640 for (page.page_encodings[0..page.page_encodings_count]) |enc| {
641 try writer.writeInt(u32, enc.enc, .little);
642 }
643
644 assert(page.count > 0);
645 const first_rec = macho_file.getUnwindRecord(info.records.items[page.start]);
646 for (info.records.items[page.start..][0..page.count]) |index| {
647 const rec = macho_file.getUnwindRecord(index);
648 const enc_index = blk: {
649 if (info.getCommonEncoding(rec.enc)) |id| break :blk id;
650 const ncommon = info.common_encodings_count;
651 break :blk ncommon + page.getPageEncoding(rec.enc).?;
652 };
653 const compressed = macho.UnwindInfoCompressedEntry{
654 .funcOffset = @as(u24, @intCast(rec.getAtomAddress(macho_file) - first_rec.getAtomAddress(macho_file))),
655 .encodingIndex = @as(u8, @intCast(enc_index)),
656 };
657 try writer.writeStruct(compressed);
658 }
659 },
660 }
661 }
662};
814663
815const std = @import("std");664const std = @import("std");
816const assert = std.debug.assert;665const assert = std.debug.assert;
817const eh_frame = @import("eh_frame.zig");666const eh_frame = @import("eh_frame.zig");
818const fs = std.fs;667const fs = std.fs;
819const leb = std.leb;668const leb = std.leb;
820const log = std.log.scoped(.unwind_info);669const log = std.log.scoped(.link);
821const macho = std.macho;670const macho = std.macho;
822const math = std.math;671const math = std.math;
823const mem = std.mem;672const mem = std.mem;
824const trace = @import("../../tracy.zig").trace;673const trace = @import("../tracy.zig").trace;
825674
826const Allocator = mem.Allocator;675const Allocator = mem.Allocator;
827const Atom = @import("Atom.zig");676const Atom = @import("Atom.zig");
828const EhFrameRecord = eh_frame.EhFrameRecord;677const Fde = eh_frame.Fde;
678const File = @import("file.zig").File;
829const MachO = @import("../MachO.zig");679const MachO = @import("../MachO.zig");
830const Object = @import("Object.zig");680const Object = @import("Object.zig");
831const SymbolWithLoc = MachO.SymbolWithLoc;681const Symbol = @import("Symbol.zig");
682const UnwindInfo = @This();
src/link/MachO/dead_strip.zig+140-431
...@@ -1,495 +1,204 @@...@@ -1,495 +1,204 @@
1//! An algorithm for dead stripping of unreferenced Atoms.
2
3pub fn gcAtoms(macho_file: *MachO) !void {1pub fn gcAtoms(macho_file: *MachO) !void {
4 const comp = macho_file.base.comp;2 const gpa = macho_file.base.allocator;
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)));
123
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) |index| objects.appendAssumeCapacity(index);
158
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}
2011
21fn 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}
3316
34fn collectRoots(macho_file: *MachO, roots: *AtomTable) !void {17fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
35 log.debug("collecting roots", .{});18 for (objects) |index| {
3619 const object = macho_file.getFile(index).?;
37 const comp = macho_file.base.comp;20 for (object.getSymbols()) |sym_index| {
3821 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 root24 if (sym.flags.no_dead_strip or (macho_file.options.dylib 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;
5727
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 }
7342
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| {
7645 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_SYMBOLS48 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
114fn markLive(macho_file: *MachO, atom_index: Atom.Index, alive: *AtomTable) void {
115 if (alive.contains(atom_index)) return;
116
117 const atom = macho_file.getAtom(atom_index);
118 const sym_loc = atom.getSymbolWithLoc();
11952
120 log.debug("mark(ATOM({d}, %{d}, {?d}))", .{ atom_index, sym_loc.sym_index, sym_loc.getFile() });53 for (macho_file.undefined_symbols.items) |sym_index| {
12154 const sym = macho_file.getSymbol(sym_index);
122 alive.putAssumeCapacityNoClobber(atom_index, {});55 try markSymbol(sym, roots, macho_file);
12356 }
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;
16357
164 unreachable; // referenced symbol not found58 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}
17869
179fn refersLive(macho_file: *MachO, atom_index: Atom.Index, alive: AtomTable) bool {70fn 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);
18273}
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 }
23274
233 return false;75fn markAtom(atom: *Atom) bool {
76 const already_visited = atom.flags.visited;
77 atom.flags.visited = true;
78 return atom.flags.alive and !already_visited;
234}79}
23580
236fn mark(macho_file: *MachO, roots: AtomTable, alive: *AtomTable) void {81fn 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 }
24185
242 var loop: bool = true;86 var loop: bool = true;
243 while (loop) {87 while (loop) {
244 loop = false;88 loop = false;
24589
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).?;
24993 const isec = atom.getInputSection(macho_file);
250 const atom = macho_file.getAtom(atom_index);94 if (isec.isDontDeadStripIfReferencesLive() and !atom.flags.alive and refersLive(atom, macho_file)) {
251 const sect_id = if (object.getSourceSymbol(atom.sym_index)) |source_sym|95 markLive(atom, macho_file);
252 source_sym.n_sect - 196 loop = true;
253 else blk: {
254 const nbase = @as(u32, @intCast(object.in_symtab.?.len));
255 const sect_id = @as(u8, @intCast(atom.sym_index - nbase));
256 break :blk sect_id;
257 };
258 const source_sect = object.getSourceSection(sect_id);
259
260 if (source_sect.isDontDeadStripIfReferencesLive()) {
261 if (refersLive(macho_file, atom_index, alive.*)) {
262 markLive(macho_file, atom_index, alive);
263 loop = true;
264 }
265 }97 }
266 }98 }
267 }99 }
268 }100 }
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}101}
276102
277fn markUnwindRecords(macho_file: *MachO, object_id: u32, alive: *AtomTable) void {103fn markLive(atom: *Atom, macho_file: *MachO) void {
278 const object = &macho_file.objects.items[object_id];104 assert(atom.flags.visited);
279 const target = macho_file.base.comp.root_mod.resolved_target.result;105 atom.flags.alive = true;
280 const cpu_arch = target.cpu.arch;106 track_live_log.debug("{}marking live atom({d},{s})", .{
281107 track_live_level,
282 const unwind_records = object.getUnwindRecords();108 atom.atom_index,
109 atom.getName(macho_file),
110 });
283111
284 for (object.exec_atoms.items) |atom_index| {112 if (build_options.enable_logging)
285 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);113 track_live_level.incr();
286114
287 if (!object.hasUnwindRecords()) {115 for (atom.getRelocs(macho_file)) |rel| {
288 if (alive.contains(atom_index)) {116 const target_atom = switch (rel.tag) {
289 // Mark references live and continue.117 .local => rel.getTargetAtom(macho_file),
290 markEhFrameRecords(macho_file, object_id, atom_index, alive);118 .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
291 } else {119 };
292 while (inner_syms_it.next()) |sym| {120 if (target_atom) |ta| {
293 if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {121 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 }122 }
123 }
301124
302 while (inner_syms_it.next()) |sym| {125 for (atom.getUnwindRecords(macho_file)) |cu_index| {
303 const record_id = object.unwind_records_lookup.get(sym) orelse continue;126 const cu = macho_file.getUnwindRecord(cu_index);
304 if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do127 const cu_atom = cu.getAtom(macho_file);
305 if (!alive.contains(atom_index)) {128 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 }
313129
314 const record = unwind_records[record_id];130 if (cu.getLsdaAtom(macho_file)) |lsda| {
315 if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {131 if (markAtom(lsda)) markLive(lsda, macho_file);
316 markEhFrameRecords(macho_file, object_id, atom_index, alive);132 }
317 } else {133 if (cu.getFde(macho_file)) |fde| {
318 if (UnwindInfo.getPersonalityFunctionReloc(macho_file, object_id, record_id)) |rel| {134 const fde_atom = fde.getAtom(macho_file);
319 const reloc_target = Atom.parseRelocTarget(macho_file, .{135 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 }
332136
333 if (UnwindInfo.getLsdaReloc(macho_file, object_id, record_id)) |rel| {137 if (fde.getLsdaAtom(macho_file)) |lsda| {
334 const reloc_target = Atom.parseRelocTarget(macho_file, .{138 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 }139 }
345 }140 }
346 }141 }
347}142}
348143
349fn markEhFrameRecords(macho_file: *MachO, object_id: u32, atom_index: Atom.Index, alive: *AtomTable) void {144fn refersLive(atom: *Atom, macho_file: *MachO) bool {
350 const target = macho_file.base.comp.root_mod.resolved_target.result;145 for (atom.getRelocs(macho_file)) |rel| {
351 const cpu_arch = target.cpu.arch;146 const target_atom = switch (rel.tag) {
352 const object = &macho_file.objects.items[object_id];147 .local => rel.getTargetAtom(macho_file),
353 var it = object.getEhFrameRecordsIterator();148 .@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file),
354 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);149 };
355150 if (target_atom) |ta| {
356 while (inner_syms_it.next()) |sym| {151 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 }152 }
153 }
154 return false;
155}
401156
402 // Mark CIE references which should include any referenced personalities157fn prune(objects: []const File.Index, macho_file: *MachO) void {
403 // that are defined locally.158 for (objects) |index| {
404 if (cie.getPersonalityPointerReloc(macho_file, object_id, cie_offset)) |reloc_target| {159 for (macho_file.getFile(index).?.getAtoms()) |atom_index| {
405 const target_sym = macho_file.getSymbol(reloc_target);160 const atom = macho_file.getAtom(atom_index).?;
406 if (!target_sym.undf()) {161 if (atom.flags.alive and !atom.flags.visited) {
407 const target_object = macho_file.objects.items[reloc_target.getFile().?];162 atom.flags.alive = false;
408 const target_atom_index = target_object.getAtomIndexForSymbol(reloc_target.sym_index).?;163 atom.markUnwindRecordsDead(macho_file);
409 markLive(macho_file, target_atom_index, alive);
410 }164 }
411 }165 }
412 }166 }
413}167}
414168
415fn prune(macho_file: *MachO, alive: AtomTable) void {169const Level = struct {
416 log.debug("pruning dead atoms", .{});170 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;
466171
467 var inner_sym_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);172 fn incr(self: *@This()) void {
468 while (inner_sym_it.next()) |inner| {173 self.value += 1;
469 const inner_sym = macho_file.getSymbolPtr(inner);174 }
470 inner_sym.n_desc = MachO.N_DEAD;
471 }
472175
473 if (Atom.getSectionAlias(macho_file, atom_index)) |alias| {176 pub fn format(
474 const alias_sym = macho_file.getSymbolPtr(alias);177 self: *const @This(),
475 alias_sym.n_desc = MachO.N_DEAD;178 comptime unused_fmt_string: []const u8,
476 }179 options: std.fmt.FormatOptions,
477 }180 writer: anytype,
181 ) !void {
182 _ = unused_fmt_string;
183 _ = options;
184 try writer.writeByteNTimes(' ', self.value);
478 }185 }
479}186};
187
188var track_live_level: Level = .{};
480189
481const std = @import("std");
482const assert = std.debug.assert;190const assert = std.debug.assert;
483const eh_frame = @import("eh_frame.zig");191const build_options = @import("build_options");
484const log = std.log.scoped(.dead_strip);192const log = std.log.scoped(.dead_strip);
485const macho = std.macho;193const macho = std.macho;
486const math = std.math;194const math = std.math;
487const mem = std.mem;195const mem = std.mem;
196const trace = @import("../tracy.zig").trace;
197const track_live_log = std.log.scoped(.dead_strip_track_live);
198const std = @import("std");
488199
489const Allocator = mem.Allocator;200const Allocator = mem.Allocator;
490const Atom = @import("Atom.zig");201const Atom = @import("Atom.zig");
202const File = @import("file.zig").File;
491const MachO = @import("../MachO.zig");203const MachO = @import("../MachO.zig");
492const SymbolWithLoc = MachO.SymbolWithLoc;204const Symbol = @import("Symbol.zig");
493const UnwindInfo = @import("UnwindInfo.zig");
494
495const AtomTable = std.AutoHashMap(Atom.Index, void);
src/link/MachO/dyld_info/Rebase.zig+12-12
...@@ -1,3 +1,14 @@...@@ -1,3 +1,14 @@
1const Rebase = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const leb = std.leb;
6const log = std.log.scoped(.dyld_info);
7const macho = std.macho;
8const testing = std.testing;
9
10const Allocator = std.mem.Allocator;
11
1entries: std.ArrayListUnmanaged(Entry) = .{},12entries: std.ArrayListUnmanaged(Entry) = .{},
2buffer: std.ArrayListUnmanaged(u8) = .{},13buffer: std.ArrayListUnmanaged(u8) = .{},
314
...@@ -168,7 +179,7 @@ fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void {...@@ -168,7 +179,7 @@ fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void {
168179
169fn addAddr(addr: u64, writer: anytype) !void {180fn addAddr(addr: u64, writer: anytype) !void {
170 log.debug(">>> add: {x}", .{addr});181 log.debug(">>> add: {x}", .{addr});
171 if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) {182 if (std.mem.isAligned(addr, @sizeOf(u64))) {
172 const imm = @divExact(addr, @sizeOf(u64));183 const imm = @divExact(addr, @sizeOf(u64));
173 if (imm <= 0xf) {184 if (imm <= 0xf) {
174 try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm)));185 try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm)));
...@@ -561,14 +572,3 @@ test "rebase - composite" {...@@ -561,14 +572,3 @@ test "rebase - composite" {
561 macho.REBASE_OPCODE_DONE,572 macho.REBASE_OPCODE_DONE,
562 }, rebase.buffer.items);573 }, rebase.buffer.items);
563}574}
564
565const Rebase = @This();
566
567const std = @import("std");
568const assert = std.debug.assert;
569const leb = std.leb;
570const log = std.log.scoped(.dyld_info);
571const macho = std.macho;
572const testing = std.testing;
573
574const Allocator = std.mem.Allocator;
src/link/MachO/dyld_info/Trie.zig created+612
...@@ -0,0 +1,612 @@
1//! Represents export trie used in MachO executables and dynamic libraries.
2//! The purpose of an export trie is to encode as compactly as possible all
3//! export symbols for the loader `dyld`.
4//! The export trie encodes offset and other information using ULEB128
5//! encoding, and is part of the __LINKEDIT segment.
6//!
7//! Description from loader.h:
8//!
9//! The symbols exported by a dylib are encoded in a trie. This is a compact
10//! representation that factors out common prefixes. It also reduces LINKEDIT pages
11//! in RAM because it encodes all information (name, address, flags) in one small,
12//! contiguous range. The export area is a stream of nodes. The first node sequentially
13//! is the start node for the trie.
14//!
15//! Nodes for a symbol start with a uleb128 that is the length of the exported symbol
16//! information for the string so far. If there is no exported symbol, the node starts
17//! with a zero byte. If there is exported info, it follows the length.
18//!
19//! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded
20//! offset which is location of the content named by the symbol from the mach_header
21//! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags
22//! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string
23//! is zero length, then the symbol is re-export from the specified dylib with the same name.
24//! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two
25//! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers.
26//! The resolver is used by lazy pointers and must be called to get the actual address to use.
27//!
28//! After the optional exported symbol information is a byte of how many edges (0-255) that
29//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
30//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
31const Trie = @This();
32
33const std = @import("std");
34const mem = std.mem;
35const leb = std.leb;
36const log = std.log.scoped(.macho);
37const macho = std.macho;
38const testing = std.testing;
39const assert = std.debug.assert;
40const Allocator = mem.Allocator;
41
42pub const Node = struct {
43 base: *Trie,
44
45 /// Terminal info associated with this node.
46 /// If this node is not a terminal node, info is null.
47 terminal_info: ?struct {
48 /// Export flags associated with this exported symbol.
49 export_flags: u64,
50 /// VM address offset wrt to the section this symbol is defined against.
51 vmaddr_offset: u64,
52 } = null,
53
54 /// Offset of this node in the trie output byte stream.
55 trie_offset: ?u64 = null,
56
57 /// List of all edges originating from this node.
58 edges: std.ArrayListUnmanaged(Edge) = .{},
59
60 node_dirty: bool = true,
61
62 /// Edge connecting to nodes in the trie.
63 pub const Edge = struct {
64 from: *Node,
65 to: *Node,
66 label: []u8,
67
68 fn deinit(self: *Edge, allocator: Allocator) void {
69 self.to.deinit(allocator);
70 allocator.destroy(self.to);
71 allocator.free(self.label);
72 self.from = undefined;
73 self.to = undefined;
74 self.label = undefined;
75 }
76 };
77
78 fn deinit(self: *Node, allocator: Allocator) void {
79 for (self.edges.items) |*edge| {
80 edge.deinit(allocator);
81 }
82 self.edges.deinit(allocator);
83 }
84
85 /// Inserts a new node starting from `self`.
86 fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node {
87 // Check for match with edges from this node.
88 for (self.edges.items) |*edge| {
89 const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to;
90 if (match == 0) continue;
91 if (match == edge.label.len) return edge.to.put(allocator, label[match..]);
92
93 // Found a match, need to splice up nodes.
94 // From: A -> B
95 // To: A -> C -> B
96 const mid = try allocator.create(Node);
97 mid.* = .{ .base = self.base };
98 const to_label = try allocator.dupe(u8, edge.label[match..]);
99 allocator.free(edge.label);
100 const to_node = edge.to;
101 edge.to = mid;
102 edge.label = try allocator.dupe(u8, label[0..match]);
103 self.base.node_count += 1;
104
105 try mid.edges.append(allocator, .{
106 .from = mid,
107 .to = to_node,
108 .label = to_label,
109 });
110
111 return if (match == label.len) mid else mid.put(allocator, label[match..]);
112 }
113
114 // Add a new node.
115 const node = try allocator.create(Node);
116 node.* = .{ .base = self.base };
117 self.base.node_count += 1;
118
119 try self.edges.append(allocator, .{
120 .from = self,
121 .to = node,
122 .label = try allocator.dupe(u8, label),
123 });
124
125 return node;
126 }
127
128 /// Recursively parses the node from the input byte stream.
129 fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
130 self.node_dirty = true;
131 const trie_offset = try reader.context.getPos();
132 self.trie_offset = trie_offset;
133
134 var nread: usize = 0;
135
136 const node_size = try leb.readULEB128(u64, reader);
137 if (node_size > 0) {
138 const export_flags = try leb.readULEB128(u64, reader);
139 // TODO Parse special flags.
140 assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
141 export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
142
143 const vmaddr_offset = try leb.readULEB128(u64, reader);
144
145 self.terminal_info = .{
146 .export_flags = export_flags,
147 .vmaddr_offset = vmaddr_offset,
148 };
149 }
150
151 const nedges = try reader.readByte();
152 self.base.node_count += nedges;
153
154 nread += (try reader.context.getPos()) - trie_offset;
155
156 var i: usize = 0;
157 while (i < nedges) : (i += 1) {
158 const edge_start_pos = try reader.context.getPos();
159
160 const label = blk: {
161 var label_buf = std.ArrayList(u8).init(allocator);
162 while (true) {
163 const next = try reader.readByte();
164 if (next == @as(u8, 0))
165 break;
166 try label_buf.append(next);
167 }
168 break :blk try label_buf.toOwnedSlice();
169 };
170
171 const seek_to = try leb.readULEB128(u64, reader);
172 const return_pos = try reader.context.getPos();
173
174 nread += return_pos - edge_start_pos;
175 try reader.context.seekTo(seek_to);
176
177 const node = try allocator.create(Node);
178 node.* = .{ .base = self.base };
179
180 nread += try node.read(allocator, reader);
181 try self.edges.append(allocator, .{
182 .from = self,
183 .to = node,
184 .label = label,
185 });
186 try reader.context.seekTo(return_pos);
187 }
188
189 return nread;
190 }
191
192 /// Writes this node to a byte stream.
193 /// The children of this node *are* not written to the byte stream
194 /// recursively. To write all nodes to a byte stream in sequence,
195 /// iterate over `Trie.ordered_nodes` and call this method on each node.
196 /// This is one of the requirements of the MachO.
197 /// Panics if `finalize` was not called before calling this method.
198 fn write(self: Node, writer: anytype) !void {
199 assert(!self.node_dirty);
200 if (self.terminal_info) |info| {
201 // Terminal node info: encode export flags and vmaddr offset of this symbol.
202 var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
203 var info_stream = std.io.fixedBufferStream(&info_buf);
204 // TODO Implement for special flags.
205 assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
206 info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
207 try leb.writeULEB128(info_stream.writer(), info.export_flags);
208 try leb.writeULEB128(info_stream.writer(), info.vmaddr_offset);
209
210 // Encode the size of the terminal node info.
211 var size_buf: [@sizeOf(u64)]u8 = undefined;
212 var size_stream = std.io.fixedBufferStream(&size_buf);
213 try leb.writeULEB128(size_stream.writer(), info_stream.pos);
214
215 // Now, write them to the output stream.
216 try writer.writeAll(size_buf[0..size_stream.pos]);
217 try writer.writeAll(info_buf[0..info_stream.pos]);
218 } else {
219 // Non-terminal node is delimited by 0 byte.
220 try writer.writeByte(0);
221 }
222 // Write number of edges (max legal number of edges is 256).
223 try writer.writeByte(@as(u8, @intCast(self.edges.items.len)));
224
225 for (self.edges.items) |edge| {
226 // Write edge label and offset to next node in trie.
227 try writer.writeAll(edge.label);
228 try writer.writeByte(0);
229 try leb.writeULEB128(writer, edge.to.trie_offset.?);
230 }
231 }
232
233 const FinalizeResult = struct {
234 /// Current size of this node in bytes.
235 node_size: u64,
236
237 /// True if the trie offset of this node in the output byte stream
238 /// would need updating; false otherwise.
239 updated: bool,
240 };
241
242 /// Updates offset of this node in the output byte stream.
243 fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
244 var stream = std.io.countingWriter(std.io.null_writer);
245 const writer = stream.writer();
246
247 var node_size: u64 = 0;
248 if (self.terminal_info) |info| {
249 try leb.writeULEB128(writer, info.export_flags);
250 try leb.writeULEB128(writer, info.vmaddr_offset);
251 try leb.writeULEB128(writer, stream.bytes_written);
252 } else {
253 node_size += 1; // 0x0 for non-terminal nodes
254 }
255 node_size += 1; // 1 byte for edge count
256
257 for (self.edges.items) |edge| {
258 const next_node_offset = edge.to.trie_offset orelse 0;
259 node_size += edge.label.len + 1;
260 try leb.writeULEB128(writer, next_node_offset);
261 }
262
263 const trie_offset = self.trie_offset orelse 0;
264 const updated = offset_in_trie != trie_offset;
265 self.trie_offset = offset_in_trie;
266 self.node_dirty = false;
267 node_size += stream.bytes_written;
268
269 return FinalizeResult{ .node_size = node_size, .updated = updated };
270 }
271};
272
273/// The root node of the trie.
274root: ?*Node = null,
275
276/// If you want to access nodes ordered in DFS fashion,
277/// you should call `finalize` first since the nodes
278/// in this container are not guaranteed to not be stale
279/// if more insertions took place after the last `finalize`
280/// call.
281ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
282
283/// The size of the trie in bytes.
284/// This value may be outdated if there were additional
285/// insertions performed after `finalize` was called.
286/// Call `finalize` before accessing this value to ensure
287/// it is up-to-date.
288size: u64 = 0,
289
290/// Number of nodes currently in the trie.
291node_count: usize = 0,
292
293trie_dirty: bool = true,
294
295/// Export symbol that is to be placed in the trie.
296pub const ExportSymbol = struct {
297 /// Name of the symbol.
298 name: []const u8,
299
300 /// Offset of this symbol's virtual memory address from the beginning
301 /// of the __TEXT segment.
302 vmaddr_offset: u64,
303
304 /// Export flags of this exported symbol.
305 export_flags: u64,
306};
307
308/// Insert a symbol into the trie, updating the prefixes in the process.
309/// This operation may change the layout of the trie by splicing edges in
310/// certain circumstances.
311pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
312 const node = try self.root.?.put(allocator, symbol.name);
313 node.terminal_info = .{
314 .vmaddr_offset = symbol.vmaddr_offset,
315 .export_flags = symbol.export_flags,
316 };
317 self.trie_dirty = true;
318}
319
320/// Finalizes this trie for writing to a byte stream.
321/// This step performs multiple passes through the trie ensuring
322/// there are no gaps after every `Node` is ULEB128 encoded.
323/// Call this method before trying to `write` the trie to a byte stream.
324pub fn finalize(self: *Trie, allocator: Allocator) !void {
325 if (!self.trie_dirty) return;
326
327 self.ordered_nodes.shrinkRetainingCapacity(0);
328 try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count);
329
330 var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator);
331 defer fifo.deinit();
332
333 try fifo.writeItem(self.root.?);
334
335 while (fifo.readItem()) |next| {
336 for (next.edges.items) |*edge| {
337 try fifo.writeItem(edge.to);
338 }
339 self.ordered_nodes.appendAssumeCapacity(next);
340 }
341
342 var more: bool = true;
343 while (more) {
344 self.size = 0;
345 more = false;
346 for (self.ordered_nodes.items) |node| {
347 const res = try node.finalize(self.size);
348 self.size += res.node_size;
349 if (res.updated) more = true;
350 }
351 }
352
353 self.trie_dirty = false;
354}
355
356const ReadError = error{
357 OutOfMemory,
358 EndOfStream,
359 Overflow,
360};
361
362/// Parse the trie from a byte stream.
363pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize {
364 return self.root.?.read(allocator, reader);
365}
366
367/// Write the trie to a byte stream.
368/// Panics if the trie was not finalized using `finalize` before calling this method.
369pub fn write(self: Trie, writer: anytype) !void {
370 assert(!self.trie_dirty);
371 for (self.ordered_nodes.items) |node| {
372 try node.write(writer);
373 }
374}
375
376pub fn init(self: *Trie, allocator: Allocator) !void {
377 assert(self.root == null);
378 const root = try allocator.create(Node);
379 root.* = .{ .base = self };
380 self.root = root;
381 self.node_count += 1;
382}
383
384pub fn deinit(self: *Trie, allocator: Allocator) void {
385 if (self.root) |root| {
386 root.deinit(allocator);
387 allocator.destroy(root);
388 }
389 self.ordered_nodes.deinit(allocator);
390}
391
392test "Trie node count" {
393 const gpa = testing.allocator;
394 var trie: Trie = .{};
395 defer trie.deinit(gpa);
396 try trie.init(gpa);
397
398 try testing.expectEqual(@as(usize, 1), trie.node_count);
399 try testing.expect(trie.root != null);
400
401 try trie.put(gpa, .{
402 .name = "_main",
403 .vmaddr_offset = 0,
404 .export_flags = 0,
405 });
406 try testing.expectEqual(@as(usize, 2), trie.node_count);
407
408 // Inserting the same node shouldn't update the trie.
409 try trie.put(gpa, .{
410 .name = "_main",
411 .vmaddr_offset = 0,
412 .export_flags = 0,
413 });
414 try testing.expectEqual(@as(usize, 2), trie.node_count);
415
416 try trie.put(gpa, .{
417 .name = "__mh_execute_header",
418 .vmaddr_offset = 0x1000,
419 .export_flags = 0,
420 });
421 try testing.expectEqual(@as(usize, 4), trie.node_count);
422
423 // Inserting the same node shouldn't update the trie.
424 try trie.put(gpa, .{
425 .name = "__mh_execute_header",
426 .vmaddr_offset = 0x1000,
427 .export_flags = 0,
428 });
429 try testing.expectEqual(@as(usize, 4), trie.node_count);
430 try trie.put(gpa, .{
431 .name = "_main",
432 .vmaddr_offset = 0,
433 .export_flags = 0,
434 });
435 try testing.expectEqual(@as(usize, 4), trie.node_count);
436}
437
438test "Trie basic" {
439 const gpa = testing.allocator;
440 var trie: Trie = .{};
441 defer trie.deinit(gpa);
442 try trie.init(gpa);
443
444 // root --- _st ---> node
445 try trie.put(gpa, .{
446 .name = "_st",
447 .vmaddr_offset = 0,
448 .export_flags = 0,
449 });
450 try testing.expect(trie.root.?.edges.items.len == 1);
451 try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
452
453 {
454 // root --- _st ---> node --- art ---> node
455 try trie.put(gpa, .{
456 .name = "_start",
457 .vmaddr_offset = 0,
458 .export_flags = 0,
459 });
460 try testing.expect(trie.root.?.edges.items.len == 1);
461
462 const nextEdge = &trie.root.?.edges.items[0];
463 try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
464 try testing.expect(nextEdge.to.edges.items.len == 1);
465 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
466 }
467 {
468 // root --- _ ---> node --- st ---> node --- art ---> node
469 // |
470 // | --- main ---> node
471 try trie.put(gpa, .{
472 .name = "_main",
473 .vmaddr_offset = 0,
474 .export_flags = 0,
475 });
476 try testing.expect(trie.root.?.edges.items.len == 1);
477
478 const nextEdge = &trie.root.?.edges.items[0];
479 try testing.expect(mem.eql(u8, nextEdge.label, "_"));
480 try testing.expect(nextEdge.to.edges.items.len == 2);
481 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
482 try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
483
484 const nextNextEdge = &nextEdge.to.edges.items[0];
485 try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
486 }
487}
488
489fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
490 assert(expected.len > 0);
491 if (mem.eql(u8, expected, given)) return;
492 const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
493 defer testing.allocator.free(expected_fmt);
494 const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
495 defer testing.allocator.free(given_fmt);
496 const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
497 const padding = try testing.allocator.alloc(u8, idx + 5);
498 defer testing.allocator.free(padding);
499 @memset(padding, ' ');
500 std.debug.print("\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ expected_fmt, given_fmt, padding });
501 return error.TestFailed;
502}
503
504test "write Trie to a byte stream" {
505 var gpa = testing.allocator;
506 var trie: Trie = .{};
507 defer trie.deinit(gpa);
508 try trie.init(gpa);
509
510 try trie.put(gpa, .{
511 .name = "__mh_execute_header",
512 .vmaddr_offset = 0,
513 .export_flags = 0,
514 });
515 try trie.put(gpa, .{
516 .name = "_main",
517 .vmaddr_offset = 0x1000,
518 .export_flags = 0,
519 });
520
521 try trie.finalize(gpa);
522 try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
523
524 const exp_buffer = [_]u8{
525 0x0, 0x1, // node root
526 0x5f, 0x0, 0x5, // edge '_'
527 0x0, 0x2, // non-terminal node
528 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
529 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
530 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
531 0x2, 0x0, 0x0, 0x0, // terminal node
532 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
533 };
534
535 const buffer = try gpa.alloc(u8, trie.size);
536 defer gpa.free(buffer);
537 var stream = std.io.fixedBufferStream(buffer);
538 {
539 _ = try trie.write(stream.writer());
540 try expectEqualHexStrings(&exp_buffer, buffer);
541 }
542 {
543 // Writing finalized trie again should yield the same result.
544 try stream.seekTo(0);
545 _ = try trie.write(stream.writer());
546 try expectEqualHexStrings(&exp_buffer, buffer);
547 }
548}
549
550test "parse Trie from byte stream" {
551 const gpa = testing.allocator;
552
553 const in_buffer = [_]u8{
554 0x0, 0x1, // node root
555 0x5f, 0x0, 0x5, // edge '_'
556 0x0, 0x2, // non-terminal node
557 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
558 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
559 0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
560 0x2, 0x0, 0x0, 0x0, // terminal node
561 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
562 };
563
564 var in_stream = std.io.fixedBufferStream(&in_buffer);
565 var trie: Trie = .{};
566 defer trie.deinit(gpa);
567 try trie.init(gpa);
568 const nread = try trie.read(gpa, in_stream.reader());
569
570 try testing.expect(nread == in_buffer.len);
571
572 try trie.finalize(gpa);
573
574 const out_buffer = try gpa.alloc(u8, trie.size);
575 defer gpa.free(out_buffer);
576 var out_stream = std.io.fixedBufferStream(out_buffer);
577 _ = try trie.write(out_stream.writer());
578 try expectEqualHexStrings(&in_buffer, out_buffer);
579}
580
581test "ordering bug" {
582 const gpa = testing.allocator;
583 var trie: Trie = .{};
584 defer trie.deinit(gpa);
585 try trie.init(gpa);
586
587 try trie.put(gpa, .{
588 .name = "_asStr",
589 .vmaddr_offset = 0x558,
590 .export_flags = 0,
591 });
592 try trie.put(gpa, .{
593 .name = "_a",
594 .vmaddr_offset = 0x8008,
595 .export_flags = 0,
596 });
597
598 try trie.finalize(gpa);
599
600 const exp_buffer = [_]u8{
601 0x00, 0x01, 0x5F, 0x61, 0x00, 0x06, 0x04, 0x00,
602 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
603 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
604 };
605
606 const buffer = try gpa.alloc(u8, trie.size);
607 defer gpa.free(buffer);
608 var stream = std.io.fixedBufferStream(buffer);
609 // Writing finalized trie again should yield the same result.
610 _ = try trie.write(stream.writer());
611 try expectEqualHexStrings(&exp_buffer, buffer);
612}
src/link/MachO/dyld_info/bind.zig+348-614
...@@ -1,231 +1,391 @@...@@ -1,231 +1,391 @@
1pub fn Bind(comptime Ctx: type, comptime Target: type) type {1const std = @import("std");
2 return struct {2const assert = std.debug.assert;
3 entries: std.ArrayListUnmanaged(Entry) = .{},3const leb = std.leb;
4 buffer: std.ArrayListUnmanaged(u8) = .{},4const log = std.log.scoped(.dyld_info);
55const macho = std.macho;
6 const Self = @This();6const 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 };
267
27 pub fn deinit(self: *Self, gpa: Allocator) void {8const Allocator = std.mem.Allocator;
28 self.entries.deinit(gpa);9const MachO = @import("../../MachO.zig");
29 self.buffer.deinit(gpa);10const Symbol = @import("../Symbol.zig");
11
12pub const Entry = struct {
13 target: Symbol.Index,
14 offset: u64,
15 segment_id: u8,
16 addend: i64,
17
18 pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool {
19 if (entry.segment_id == other.segment_id) {
20 if (entry.target == other.target) {
21 return entry.offset < other.offset;
22 }
23 const entry_name = ctx.getSymbol(entry.target).getName(ctx);
24 const other_name = ctx.getSymbol(other.target).getName(ctx);
25 return std.mem.lessThan(u8, entry_name, other_name);
30 }26 }
27 return entry.segment_id < other.segment_id;
28 }
29};
3130
32 pub fn size(self: Self) u64 {31pub const Bind = struct {
33 return @as(u64, @intCast(self.buffer.items.len));32 entries: std.ArrayListUnmanaged(Entry) = .{},
34 }33 buffer: std.ArrayListUnmanaged(u8) = .{},
3534
36 pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {35 const Self = @This();
37 if (self.entries.items.len == 0) return;
3836
39 const writer = self.buffer.writer(gpa);37 pub fn deinit(self: *Self, gpa: Allocator) void {
38 self.entries.deinit(gpa);
39 self.buffer.deinit(gpa);
40 }
4041
41 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);42 pub fn size(self: Self) u64 {
43 return @as(u64, @intCast(self.buffer.items.len));
44 }
4245
43 var start: usize = 0;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 }
5148
52 try finalizeSegment(self.entries.items[start..], ctx, writer);49 const writer = self.buffer.writer(gpa);
53 try done(writer);50
51 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
52
53 var start: usize = 0;
54 var seg_id: ?u8 = null;
55 for (self.entries.items, 0..) |entry, i| {
56 if (seg_id != null and seg_id.? == entry.segment_id) continue;
57 try finalizeSegment(self.entries.items[start..i], ctx, writer);
58 seg_id = entry.segment_id;
59 start = i;
54 }60 }
5561
56 fn finalizeSegment(entries: []const Entry, ctx: Ctx, writer: anytype) !void {62 try finalizeSegment(self.entries.items[start..], ctx, writer);
57 if (entries.len == 0) return;63 try done(writer);
5864 }
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;
8565
86 const sym = ctx.getSymbol(current.target);66 fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
87 const name = ctx.getSymbolName(current.target);67 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);
9068
91 try setSymbol(name, flags, writer);69 const seg_id = entries[0].segment_id;
92 try setTypePointer(writer);70 try setSegmentOffset(seg_id, 0, writer);
93 try setDylibOrdinal(ordinal, writer);
9471
95 if (current.addend != addend) {72 var offset: u64 = 0;
96 addend = current.addend;73 var addend: i64 = 0;
97 try setAddend(addend, writer);74 var count: usize = 0;
98 }75 var skip: u64 = 0;
99 }76 var target: ?Symbol.Index = null;
10077
101 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });78 var state: enum {
102 log.debug(" => {x}", .{current.offset});79 start,
80 bind_single,
81 bind_times_skip,
82 } = .start;
83
84 var i: usize = 0;
85 while (i < entries.len) : (i += 1) {
86 const current = entries[i];
87 if (target == null or target.? != current.target) {
103 switch (state) {88 switch (state) {
104 .start => {89 .start => {},
105 if (current.offset < offset) {90 .bind_single => try doBind(writer),
106 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);91 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
107 offset = offset - (offset - current.offset);92 }
108 } else if (current.offset > offset) {93 state = .start;
109 const delta = current.offset - offset;94 target = current.target;
110 try addAddr(delta, writer);95
111 offset += delta;96 const sym = ctx.getSymbol(current.target);
112 }97 const name = sym.getName(ctx);
113 state = .bind_single;98 const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
114 offset += @sizeOf(u64);99 const ordinal: i16 = ord: {
115 count = 1;100 if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
116 },101 if (sym.flags.import) {
117 .bind_single => {102 if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
118 if (current.offset == offset) {103 if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
119 try doBind(writer);104 }
120 state = .start;105 if (ctx.options.undefined_treatment == .dynamic_lookup)
121 } else if (current.offset > offset) {106 break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
122 const delta = current.offset - offset;107 break :ord macho.BIND_SPECIAL_DYLIB_SELF;
123 state = .bind_times_skip;108 };
124 skip = @as(u64, @intCast(delta));109
125 offset += skip;110 try setSymbol(name, flags, writer);
126 } else unreachable;111 try setTypePointer(writer);
127 i -= 1;112 try setDylibOrdinal(ordinal, writer);
128 },113
129 .bind_times_skip => {114 if (current.addend != addend) {
130 if (current.offset < offset) {115 addend = current.addend;
131 count -= 1;116 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 }117 }
150 }118 }
151119
120 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
121 log.debug(" => {x}", .{current.offset});
152 switch (state) {122 switch (state) {
153 .start => unreachable,123 .start => {
154 .bind_single => try doBind(writer),124 if (current.offset < offset) {
155 .bind_times_skip => try doBindTimesSkip(count, skip, writer),125 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
126 offset = offset - (offset - current.offset);
127 } else if (current.offset > offset) {
128 const delta = current.offset - offset;
129 try addAddr(delta, writer);
130 offset += delta;
131 }
132 state = .bind_single;
133 offset += @sizeOf(u64);
134 count = 1;
135 },
136 .bind_single => {
137 if (current.offset == offset) {
138 try doBind(writer);
139 state = .start;
140 } else if (current.offset > offset) {
141 const delta = current.offset - offset;
142 state = .bind_times_skip;
143 skip = @as(u64, @intCast(delta));
144 offset += skip;
145 } else unreachable;
146 i -= 1;
147 },
148 .bind_times_skip => {
149 if (current.offset < offset) {
150 count -= 1;
151 if (count == 1) {
152 try doBindAddAddr(skip, writer);
153 } else {
154 try doBindTimesSkip(count, skip, writer);
155 }
156 state = .start;
157 offset = offset - (@sizeOf(u64) + skip);
158 i -= 2;
159 } else if (current.offset == offset) {
160 count += 1;
161 offset += @sizeOf(u64) + skip;
162 } else {
163 try doBindTimesSkip(count, skip, writer);
164 state = .start;
165 i -= 1;
166 }
167 },
156 }168 }
157 }169 }
158170
159 pub fn write(self: Self, writer: anytype) !void {171 switch (state) {
160 if (self.size() == 0) return;172 .start => unreachable,
161 try writer.writeAll(self.buffer.items);173 .bind_single => try doBind(writer),
174 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
162 }175 }
163 };176 }
164}
165177
166pub fn LazyBind(comptime Ctx: type, comptime Target: type) type {178 pub fn write(self: Self, writer: anytype) !void {
167 return struct {179 if (self.size() == 0) return;
168 entries: std.ArrayListUnmanaged(Entry) = .{},180 try writer.writeAll(self.buffer.items);
169 buffer: std.ArrayListUnmanaged(u8) = .{},181 }
170 offsets: std.ArrayListUnmanaged(u32) = .{},182};
171183
172 const Self = @This();184pub const WeakBind = struct {
173185 entries: std.ArrayListUnmanaged(Entry) = .{},
174 const Entry = struct {186 buffer: std.ArrayListUnmanaged(u8) = .{},
175 target: Target,187
176 offset: u64,188 const Self = @This();
177 segment_id: u8,189
178 addend: i64,190 pub fn deinit(self: *Self, gpa: Allocator) void {
179 };191 self.entries.deinit(gpa);
180192 self.buffer.deinit(gpa);
181 pub fn deinit(self: *Self, gpa: Allocator) void {193 }
182 self.entries.deinit(gpa);
183 self.buffer.deinit(gpa);
184 self.offsets.deinit(gpa);
185 }
186194
187 pub fn size(self: Self) u64 {195 pub fn size(self: Self) u64 {
188 return @as(u64, @intCast(self.buffer.items.len));196 return @as(u64, @intCast(self.buffer.items.len));
197 }
198
199 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
200 if (self.entries.items.len == 0) return;
201
202 const writer = self.buffer.writer(gpa);
203
204 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
205
206 var start: usize = 0;
207 var seg_id: ?u8 = null;
208 for (self.entries.items, 0..) |entry, i| {
209 if (seg_id != null and seg_id.? == entry.segment_id) continue;
210 try finalizeSegment(self.entries.items[start..i], ctx, writer);
211 seg_id = entry.segment_id;
212 start = i;
189 }213 }
190214
191 pub fn finalize(self: *Self, gpa: Allocator, ctx: Ctx) !void {215 try finalizeSegment(self.entries.items[start..], ctx, writer);
192 if (self.entries.items.len == 0) return;216 try done(writer);
217 }
218
219 fn finalizeSegment(entries: []const Entry, ctx: *MachO, writer: anytype) !void {
220 if (entries.len == 0) return;
193221
194 try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);222 const seg_id = entries[0].segment_id;
223 try setSegmentOffset(seg_id, 0, writer);
195224
196 var cwriter = std.io.countingWriter(self.buffer.writer(gpa));225 var offset: u64 = 0;
197 const writer = cwriter.writer();226 var addend: i64 = 0;
227 var count: usize = 0;
228 var skip: u64 = 0;
229 var target: ?Symbol.Index = null;
198230
199 var addend: i64 = 0;231 var state: enum {
232 start,
233 bind_single,
234 bind_times_skip,
235 } = .start;
200236
201 for (self.entries.items) |entry| {237 var i: usize = 0;
202 self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));238 while (i < entries.len) : (i += 1) {
239 const current = entries[i];
240 if (target == null or target.? != current.target) {
241 switch (state) {
242 .start => {},
243 .bind_single => try doBind(writer),
244 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
245 }
246 state = .start;
247 target = current.target;
203248
204 const sym = ctx.getSymbol(entry.target);249 const sym = ctx.getSymbol(current.target);
205 const name = ctx.getSymbolName(entry.target);250 const name = sym.getName(ctx);
206 const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;251 const flags: u8 = 0; // TODO NON_WEAK_DEFINITION
207 const ordinal = @divTrunc(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
208252
209 try setSegmentOffset(entry.segment_id, entry.offset, writer);
210 try setSymbol(name, flags, writer);253 try setSymbol(name, flags, writer);
211 try setDylibOrdinal(ordinal, writer);254 try setTypePointer(writer);
212255
213 if (entry.addend != addend) {256 if (current.addend != addend) {
214 try setAddend(entry.addend, writer);257 addend = current.addend;
215 addend = entry.addend;258 try setAddend(addend, writer);
216 }259 }
260 }
217261
218 try doBind(writer);262 log.debug("{x}, {d}, {x}, {?x}, {s}", .{ offset, count, skip, addend, @tagName(state) });
219 try done(writer);263 log.debug(" => {x}", .{current.offset});
264 switch (state) {
265 .start => {
266 if (current.offset < offset) {
267 try addAddr(@as(u64, @bitCast(@as(i64, @intCast(current.offset)) - @as(i64, @intCast(offset)))), writer);
268 offset = offset - (offset - current.offset);
269 } else if (current.offset > offset) {
270 const delta = current.offset - offset;
271 try addAddr(delta, writer);
272 offset += delta;
273 }
274 state = .bind_single;
275 offset += @sizeOf(u64);
276 count = 1;
277 },
278 .bind_single => {
279 if (current.offset == offset) {
280 try doBind(writer);
281 state = .start;
282 } else if (current.offset > offset) {
283 const delta = current.offset - offset;
284 state = .bind_times_skip;
285 skip = @as(u64, @intCast(delta));
286 offset += skip;
287 } else unreachable;
288 i -= 1;
289 },
290 .bind_times_skip => {
291 if (current.offset < offset) {
292 count -= 1;
293 if (count == 1) {
294 try doBindAddAddr(skip, writer);
295 } else {
296 try doBindTimesSkip(count, skip, writer);
297 }
298 state = .start;
299 offset = offset - (@sizeOf(u64) + skip);
300 i -= 2;
301 } else if (current.offset == offset) {
302 count += 1;
303 offset += @sizeOf(u64) + skip;
304 } else {
305 try doBindTimesSkip(count, skip, writer);
306 state = .start;
307 i -= 1;
308 }
309 },
220 }310 }
221 }311 }
222312
223 pub fn write(self: Self, writer: anytype) !void {313 switch (state) {
224 if (self.size() == 0) return;314 .start => unreachable,
225 try writer.writeAll(self.buffer.items);315 .bind_single => try doBind(writer),
316 .bind_times_skip => try doBindTimesSkip(count, skip, writer),
226 }317 }
227 };318 }
228}319
320 pub fn write(self: Self, writer: anytype) !void {
321 if (self.size() == 0) return;
322 try writer.writeAll(self.buffer.items);
323 }
324};
325
326pub const LazyBind = struct {
327 entries: std.ArrayListUnmanaged(Entry) = .{},
328 buffer: std.ArrayListUnmanaged(u8) = .{},
329 offsets: std.ArrayListUnmanaged(u32) = .{},
330
331 const Self = @This();
332
333 pub fn deinit(self: *Self, gpa: Allocator) void {
334 self.entries.deinit(gpa);
335 self.buffer.deinit(gpa);
336 self.offsets.deinit(gpa);
337 }
338
339 pub fn size(self: Self) u64 {
340 return @as(u64, @intCast(self.buffer.items.len));
341 }
342
343 pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
344 if (self.entries.items.len == 0) return;
345
346 try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
347
348 var cwriter = std.io.countingWriter(self.buffer.writer(gpa));
349 const writer = cwriter.writer();
350
351 var addend: i64 = 0;
352
353 for (self.entries.items) |entry| {
354 self.offsets.appendAssumeCapacity(@as(u32, @intCast(cwriter.bytes_written)));
355
356 const sym = ctx.getSymbol(entry.target);
357 const name = sym.getName(ctx);
358 const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
359 const ordinal: i16 = ord: {
360 if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
361 if (sym.flags.import) {
362 if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
363 if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord);
364 }
365 if (ctx.options.undefined_treatment == .dynamic_lookup)
366 break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
367 break :ord macho.BIND_SPECIAL_DYLIB_SELF;
368 };
369
370 try setSegmentOffset(entry.segment_id, entry.offset, writer);
371 try setSymbol(name, flags, writer);
372 try setDylibOrdinal(ordinal, writer);
373
374 if (entry.addend != addend) {
375 try setAddend(entry.addend, writer);
376 addend = entry.addend;
377 }
378
379 try doBind(writer);
380 try done(writer);
381 }
382 }
383
384 pub fn write(self: Self, writer: anytype) !void {
385 if (self.size() == 0) return;
386 try writer.writeAll(self.buffer.items);
387 }
388};
229389
230fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {390fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
231 log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });391 log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
...@@ -282,7 +442,7 @@ fn doBind(writer: anytype) !void {...@@ -282,7 +442,7 @@ fn doBind(writer: anytype) !void {
282442
283fn doBindAddAddr(addr: u64, writer: anytype) !void {443fn doBindAddAddr(addr: u64, writer: anytype) !void {
284 log.debug(">>> bind with add: {x}", .{addr});444 log.debug(">>> bind with add: {x}", .{addr});
285 if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) {445 if (std.mem.isAligned(addr, @sizeOf(u64))) {
286 const imm = @divExact(addr, @sizeOf(u64));446 const imm = @divExact(addr, @sizeOf(u64));
287 if (imm <= 0xf) {447 if (imm <= 0xf) {
288 try writer.writeByte(448 try writer.writeByte(
...@@ -312,429 +472,3 @@ fn done(writer: anytype) !void {...@@ -312,429 +472,3 @@ fn done(writer: anytype) !void {
312 log.debug(">>> done", .{});472 log.debug(">>> done", .{});
313 try writer.writeByte(macho.BIND_OPCODE_DONE);473 try writer.writeByte(macho.BIND_OPCODE_DONE);
314}474}
315
316const TestContext = struct {
317 symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{},
318 strtab: std.ArrayListUnmanaged(u8) = .{},
319
320 const Target = struct {
321 index: u32,
322
323 fn eql(this: Target, other: Target) bool {
324 return this.index == other.index;
325 }
326 };
327
328 fn deinit(ctx: *TestContext, gpa: Allocator) void {
329 ctx.symbols.deinit(gpa);
330 ctx.strtab.deinit(gpa);
331 }
332
333 fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void {
334 const n_strx = try ctx.addString(gpa, name);
335 var n_desc = @as(u16, @bitCast(ordinal * macho.N_SYMBOL_RESOLVER));
336 n_desc |= flags;
337 try ctx.symbols.append(gpa, .{
338 .n_value = 0,
339 .n_strx = n_strx,
340 .n_desc = n_desc,
341 .n_type = macho.N_EXT,
342 .n_sect = 0,
343 });
344 }
345
346 fn addString(ctx: *TestContext, gpa: Allocator, name: []const u8) !u32 {
347 const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
348 try ctx.strtab.appendSlice(gpa, name);
349 try ctx.strtab.append(gpa, 0);
350 return n_strx;
351 }
352
353 fn getSymbol(ctx: TestContext, target: Target) macho.nlist_64 {
354 return ctx.symbols.items[target.index];
355 }
356
357 fn getSymbolName(ctx: TestContext, target: Target) []const u8 {
358 const sym = ctx.getSymbol(target);
359 assert(sym.n_strx < ctx.strtab.items.len);
360 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + sym.n_strx)), 0);
361 }
362};
363
364fn generateTestContext() !TestContext {
365 const gpa = testing.allocator;
366 var ctx = TestContext{};
367 try ctx.addSymbol(gpa, "_import_1", 1, 0);
368 try ctx.addSymbol(gpa, "_import_2", 1, 0);
369 try ctx.addSymbol(gpa, "_import_3", 1, 0);
370 try ctx.addSymbol(gpa, "_import_4", 2, 0);
371 try ctx.addSymbol(gpa, "_import_5_weak", 2, macho.N_WEAK_REF);
372 try ctx.addSymbol(gpa, "_import_6", 2, 0);
373 return ctx;
374}
375
376test "bind - no entries" {
377 const gpa = testing.allocator;
378
379 var test_context = try generateTestContext();
380 defer test_context.deinit(gpa);
381
382 var bind = Bind(TestContext, TestContext.Target){};
383 defer bind.deinit(gpa);
384
385 try bind.finalize(gpa, test_context);
386 try testing.expectEqual(@as(u64, 0), bind.size());
387}
388
389test "bind - single entry" {
390 const gpa = testing.allocator;
391
392 var test_context = try generateTestContext();
393 defer test_context.deinit(gpa);
394
395 var bind = Bind(TestContext, TestContext.Target){};
396 defer bind.deinit(gpa);
397
398 try bind.entries.append(gpa, .{
399 .offset = 0x10,
400 .segment_id = 1,
401 .target = TestContext.Target{ .index = 0 },
402 .addend = 0,
403 });
404 try bind.finalize(gpa, test_context);
405 try testing.expectEqualSlices(u8, &[_]u8{
406 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
407 0x0,
408 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
409 0x5f,
410 0x69,
411 0x6d,
412 0x70,
413 0x6f,
414 0x72,
415 0x74,
416 0x5f,
417 0x31,
418 0x0,
419 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
420 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
421 macho.BIND_OPCODE_ADD_ADDR_ULEB,
422 0x10,
423 macho.BIND_OPCODE_DO_BIND,
424 macho.BIND_OPCODE_DONE,
425 }, bind.buffer.items);
426}
427
428test "bind - multiple occurrences within the same segment" {
429 const gpa = testing.allocator;
430
431 var test_context = try generateTestContext();
432 defer test_context.deinit(gpa);
433
434 var bind = Bind(TestContext, TestContext.Target){};
435 defer bind.deinit(gpa);
436
437 try bind.entries.append(gpa, .{
438 .offset = 0x10,
439 .segment_id = 1,
440 .target = TestContext.Target{ .index = 0 },
441 .addend = 0,
442 });
443 try bind.entries.append(gpa, .{
444 .offset = 0x18,
445 .segment_id = 1,
446 .target = TestContext.Target{ .index = 0 },
447 .addend = 0,
448 });
449 try bind.entries.append(gpa, .{
450 .offset = 0x20,
451 .segment_id = 1,
452 .target = TestContext.Target{ .index = 0 },
453 .addend = 0,
454 });
455 try bind.entries.append(gpa, .{
456 .offset = 0x28,
457 .segment_id = 1,
458 .target = TestContext.Target{ .index = 0 },
459 .addend = 0,
460 });
461
462 try bind.finalize(gpa, test_context);
463 try testing.expectEqualSlices(u8, &[_]u8{
464 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
465 0x0,
466 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
467 0x5f,
468 0x69,
469 0x6d,
470 0x70,
471 0x6f,
472 0x72,
473 0x74,
474 0x5f,
475 0x31,
476 0x0,
477 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
478 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
479 macho.BIND_OPCODE_ADD_ADDR_ULEB,
480 0x10,
481 macho.BIND_OPCODE_DO_BIND,
482 macho.BIND_OPCODE_DO_BIND,
483 macho.BIND_OPCODE_DO_BIND,
484 macho.BIND_OPCODE_DO_BIND,
485 macho.BIND_OPCODE_DONE,
486 }, bind.buffer.items);
487}
488
489test "bind - multiple occurrences with skip and addend" {
490 const gpa = testing.allocator;
491
492 var test_context = try generateTestContext();
493 defer test_context.deinit(gpa);
494
495 var bind = Bind(TestContext, TestContext.Target){};
496 defer bind.deinit(gpa);
497
498 try bind.entries.append(gpa, .{
499 .offset = 0x0,
500 .segment_id = 1,
501 .target = TestContext.Target{ .index = 0 },
502 .addend = 0x10,
503 });
504 try bind.entries.append(gpa, .{
505 .offset = 0x10,
506 .segment_id = 1,
507 .target = TestContext.Target{ .index = 0 },
508 .addend = 0x10,
509 });
510 try bind.entries.append(gpa, .{
511 .offset = 0x20,
512 .segment_id = 1,
513 .target = TestContext.Target{ .index = 0 },
514 .addend = 0x10,
515 });
516 try bind.entries.append(gpa, .{
517 .offset = 0x30,
518 .segment_id = 1,
519 .target = TestContext.Target{ .index = 0 },
520 .addend = 0x10,
521 });
522
523 try bind.finalize(gpa, test_context);
524 try testing.expectEqualSlices(u8, &[_]u8{
525 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
526 0x0,
527 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
528 0x5f,
529 0x69,
530 0x6d,
531 0x70,
532 0x6f,
533 0x72,
534 0x74,
535 0x5f,
536 0x31,
537 0x0,
538 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
539 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
540 macho.BIND_OPCODE_SET_ADDEND_SLEB,
541 0x10,
542 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
543 0x4,
544 0x8,
545 macho.BIND_OPCODE_DONE,
546 }, bind.buffer.items);
547}
548
549test "bind - complex" {
550 const gpa = testing.allocator;
551
552 var test_context = try generateTestContext();
553 defer test_context.deinit(gpa);
554
555 var bind = Bind(TestContext, TestContext.Target){};
556 defer bind.deinit(gpa);
557
558 try bind.entries.append(gpa, .{
559 .offset = 0x58,
560 .segment_id = 1,
561 .target = TestContext.Target{ .index = 0 },
562 .addend = 0,
563 });
564 try bind.entries.append(gpa, .{
565 .offset = 0x100,
566 .segment_id = 1,
567 .target = TestContext.Target{ .index = 1 },
568 .addend = 0x10,
569 });
570 try bind.entries.append(gpa, .{
571 .offset = 0x110,
572 .segment_id = 1,
573 .target = TestContext.Target{ .index = 1 },
574 .addend = 0x10,
575 });
576 try bind.entries.append(gpa, .{
577 .offset = 0x130,
578 .segment_id = 1,
579 .target = TestContext.Target{ .index = 1 },
580 .addend = 0x10,
581 });
582 try bind.entries.append(gpa, .{
583 .offset = 0x140,
584 .segment_id = 1,
585 .target = TestContext.Target{ .index = 1 },
586 .addend = 0x10,
587 });
588 try bind.entries.append(gpa, .{
589 .offset = 0x148,
590 .segment_id = 1,
591 .target = TestContext.Target{ .index = 2 },
592 .addend = 0,
593 });
594
595 try bind.finalize(gpa, test_context);
596 try testing.expectEqualSlices(u8, &[_]u8{
597 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
598 0x0,
599 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
600 0x5f,
601 0x69,
602 0x6d,
603 0x70,
604 0x6f,
605 0x72,
606 0x74,
607 0x5f,
608 0x31,
609 0x0,
610 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
611 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
612 macho.BIND_OPCODE_ADD_ADDR_ULEB,
613 0x58,
614 macho.BIND_OPCODE_DO_BIND,
615 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
616 0x5f,
617 0x69,
618 0x6d,
619 0x70,
620 0x6f,
621 0x72,
622 0x74,
623 0x5f,
624 0x32,
625 0x0,
626 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
627 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
628 macho.BIND_OPCODE_SET_ADDEND_SLEB,
629 0x10,
630 macho.BIND_OPCODE_ADD_ADDR_ULEB,
631 0xa0,
632 0x1,
633 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
634 0x2,
635 0x8,
636 macho.BIND_OPCODE_ADD_ADDR_ULEB,
637 0x10,
638 macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
639 0x2,
640 0x8,
641 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
642 0x5f,
643 0x69,
644 0x6d,
645 0x70,
646 0x6f,
647 0x72,
648 0x74,
649 0x5f,
650 0x33,
651 0x0,
652 macho.BIND_OPCODE_SET_TYPE_IMM | 1,
653 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
654 macho.BIND_OPCODE_SET_ADDEND_SLEB,
655 0x0,
656 macho.BIND_OPCODE_ADD_ADDR_ULEB,
657 0xf8,
658 0xff,
659 0xff,
660 0xff,
661 0xff,
662 0xff,
663 0xff,
664 0xff,
665 0xff,
666 0x1,
667 macho.BIND_OPCODE_DO_BIND,
668 macho.BIND_OPCODE_DONE,
669 }, bind.buffer.items);
670}
671
672test "lazy bind" {
673 const gpa = testing.allocator;
674
675 var test_context = try generateTestContext();
676 defer test_context.deinit(gpa);
677
678 var bind = LazyBind(TestContext, TestContext.Target){};
679 defer bind.deinit(gpa);
680
681 try bind.entries.append(gpa, .{
682 .offset = 0x10,
683 .segment_id = 1,
684 .target = TestContext.Target{ .index = 0 },
685 .addend = 0,
686 });
687 try bind.entries.append(gpa, .{
688 .offset = 0x20,
689 .segment_id = 2,
690 .target = TestContext.Target{ .index = 1 },
691 .addend = 0x10,
692 });
693
694 try bind.finalize(gpa, test_context);
695 try testing.expectEqualSlices(u8, &[_]u8{
696 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
697 0x10,
698 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
699 0x5f,
700 0x69,
701 0x6d,
702 0x70,
703 0x6f,
704 0x72,
705 0x74,
706 0x5f,
707 0x31,
708 0x0,
709 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
710 macho.BIND_OPCODE_DO_BIND,
711 macho.BIND_OPCODE_DONE,
712 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
713 0x20,
714 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | 0,
715 0x5f,
716 0x69,
717 0x6d,
718 0x70,
719 0x6f,
720 0x72,
721 0x74,
722 0x5f,
723 0x32,
724 0x0,
725 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
726 macho.BIND_OPCODE_SET_ADDEND_SLEB,
727 0x10,
728 macho.BIND_OPCODE_DO_BIND,
729 macho.BIND_OPCODE_DONE,
730 }, bind.buffer.items);
731}
732
733const std = @import("std");
734const assert = std.debug.assert;
735const leb = std.leb;
736const log = std.log.scoped(.dyld_info);
737const macho = std.macho;
738const testing = std.testing;
739
740const Allocator = std.mem.Allocator;
src/link/MachO/eh_frame.zig+468-558
...@@ -1,628 +1,538 @@...@@ -1,628 +1,538 @@
1pub fn scanRelocs(macho_file: *MachO) !void {1pub const Cie = struct {
2 const comp = macho_file.base.comp;2 /// Includes 4byte size cell.
3 const gpa = comp.gpa;3 offset: u32,
44 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,
88 file: File.Index = 0,
9 var it = object.getEhFrameRecordsIterator();9 alive: bool = false,
1010
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 it17
1818 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 ..]);
2121 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 it25 _ = 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) {
3239 @panic("unexpected personality pointer encoding"); // TODO error
33pub fn calcSectionSize(macho_file: *MachO, unwind_info: *const UnwindInfo) error{OutOfMemory}!void {
34 const sect_id = macho_file.eh_frame_section_index orelse return;
35 const sect = &macho_file.sections.items(.header)[sect_id];
36 sect.@"align" = 3;
37 sect.size = 0;
38
39 const target = macho_file.base.comp.root_mod.resolved_target.result;
40 const cpu_arch = target.cpu.arch;
41 const comp = macho_file.base.comp;
42 const gpa = comp.gpa;
43 var size: u32 = 0;
44
45 for (macho_file.objects.items, 0..) |*object, object_id| {
46 var cies = std.AutoHashMap(u32, u32).init(gpa);
47 defer cies.deinit();
48
49 var eh_it = object.getEhFrameRecordsIterator();
50
51 for (object.exec_atoms.items) |atom_index| {
52 var inner_syms_it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
53 while (inner_syms_it.next()) |sym| {
54 const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
55 if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
56
57 const record_id = unwind_info.records_lookup.get(sym) orelse continue;
58 const record = unwind_info.records.items[record_id];
59
60 // TODO skip this check if no __compact_unwind is present
61 const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
62 if (!is_dwarf) continue;
63
64 eh_it.seekTo(fde_record_offset);
65 const source_fde_record = (eh_it.next() catch continue).?; // We already handled this error
66
67 const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);
68 const cie_offset = fde_record_offset + 4 - cie_ptr;
69
70 const gop = try cies.getOrPut(cie_offset);
71 if (!gop.found_existing) {
72 eh_it.seekTo(cie_offset);
73 const source_cie_record = (eh_it.next() catch continue).?; // We already handled this error
74 gop.value_ptr.* = size;
75 size += source_cie_record.getSize();
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 }
7754
78 size += source_fde_record.getSize();55 pub inline fn getSize(cie: Cie) u32 {
79 }56 return cie.size + 4;
80 }57 }
8158
82 sect.size = size;59 pub fn getObject(cie: Cie, macho_file: *MachO) *Object {
60 const file = macho_file.getFile(cie.file).?;
61 return file.object;
83 }62 }
84}
8563
86pub 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 }
10468
105 var eh_frame_offset: u32 = 0;69 pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
70 const personality = cie.personality orelse return null;
71 return macho_file.getSymbol(personality.index);
72 }
10673
107 for (macho_file.objects.items, 0..) |*object, object_id| {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 }
10982
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 }
11295
113 var eh_it = object.getEhFrameRecordsIterator();96 pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Formatter(format2) {
97 return .{ .data = .{
98 .cie = cie,
99 .macho_file = macho_file,
100 } };
101 }
114102
115 for (object.exec_atoms.items) |atom_index| {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;
120107
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 }
123123
124 // TODO skip this check if no __compact_unwind is present124 pub const Index = u32;
125 const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
126 if (!is_dwarf) continue;
127125
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 error127 index: Symbol.Index = 0,
128 offset: u32 = 0,
129 };
130};
130131
131 const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), macho_file, fde_record_offset);132pub 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 macho_file.base.fatal("{}: {s},{s}: 0x{x}: invalid function reference in FDE", .{
159 object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + 8,
160 });
161 return error.ParseFailed;
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 macho_file.base.fatal("{}: no matching CIE found for FDE at offset {x}", .{
176 object.fmtPath(),
177 fde.offset,
178 });
179 return error.ParseFailed;
180 }
133181
134 const gop = try cies.getOrPut(cie_offset);182 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 }
148183
149 var fde_record = try source_fde_record.toOwned(gpa);184 // Parse LSDA atom index if any
150 try fde_record.relocate(macho_file, @as(u32, @intCast(object_id)), .{185 if (cie.lsda_size) |lsda_size| {
151 .source_offset = fde_record_offset,186 var stream = std.io.fixedBufferStream(data[24..]);
152 .out_offset = eh_frame_offset,187 var creader = std.io.countingReader(stream.reader());
153 .sect_addr = sect.addr,188 const reader = creader.reader();
189 _ = try leb.readULEB128(u64, reader); // augmentation length
190 fde.lsda_ptr_offset = @intCast(creader.bytes_read + 24);
191 const lsda_ptr = switch (lsda_size) {
192 .p32 => try reader.readInt(i32, .little),
193 .p64 => try reader.readInt(i64, .little),
194 };
195 const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr);
196 fde.lsda = object.findAtom(lsda_addr) orelse {
197 macho_file.base.fatal("{}: {s},{s}: 0x{x}: invalid LSDA reference in FDE", .{
198 object.fmtPath(), sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset,
154 });199 });
155 fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);200 return error.ParseFailed;
156201 };
157 switch (cpu_arch) {202 const lsda_atom = fde.getLsdaAtom(macho_file).?;
158 .aarch64 => {}, // relocs take care of LSDA pointers203 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 }204 }
210 }205 }
211206
212 var buffer = std.ArrayList(u8).init(gpa);207 pub inline fn getSize(fde: Fde) u32 {
213 defer buffer.deinit();208 return fde.size + 4;
214 const writer = buffer.writer();209 }
215210
216 for (eh_records.values()) |record| {211 pub fn getObject(fde: Fde, macho_file: *MachO) *Object {
217 try writer.writeInt(u32, record.size, .little);212 const file = macho_file.getFile(fde.file).?;
218 try buffer.appendSlice(record.data);213 return file.object;
219 }214 }
220215
221 try macho_file.base.file.?.pwriteAll(buffer.items, sect.offset);216 pub fn getData(fde: Fde, macho_file: *MachO) []const u8 {
222}217 const object = fde.getObject(macho_file);
223const EhFrameRecordTag = enum { cie, fde };218 return object.eh_frame_data.items[fde.offset..][0..fde.getSize()];
219 }
224220
225pub fn EhFrameRecord(comptime is_mutable: bool) type {221 pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie {
226 return struct {222 const object = fde.getObject(macho_file);
227 tag: EhFrameRecordTag,223 return &object.cies.items[fde.cie];
228 size: u32,224 }
229 data: if (is_mutable) []u8 else []const u8,
230225
231 const Record = @This();226 pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
227 return macho_file.getAtom(fde.atom).?;
228 }
232229
233 pub fn deinit(rec: *Record, gpa: Allocator) void {230 pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
234 comptime assert(is_mutable);231 return macho_file.getAtom(fde.lsda);
235 gpa.free(rec.data);232 }
236 }
237233
238 pub fn toOwned(rec: Record, gpa: Allocator) Allocator.Error!EhFrameRecord(true) {234 pub fn format(
239 const data = try gpa.dupe(u8, rec.data);235 fde: Fde,
240 return EhFrameRecord(true){236 comptime unused_fmt_string: []const u8,
241 .tag = rec.tag,237 options: std.fmt.FormatOptions,
242 .size = rec.size,238 writer: anytype,
243 .data = data,239 ) !void {
244 };240 _ = fde;
245 }241 _ = unused_fmt_string;
242 _ = options;
243 _ = writer;
244 @compileError("do not format FDEs directly");
245 }
246246
247 pub inline fn getSize(rec: Record) u32 {247 pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Formatter(format2) {
248 return 4 + rec.size;248 return .{ .data = .{
249 }249 .fde = fde,
250 .macho_file = macho_file,
251 } };
252 }
250253
251 pub fn scanRelocs(254 const FormatContext = struct {
252 rec: Record,255 fde: Fde,
253 macho_file: *MachO,256 macho_file: *MachO,
254 object_id: u32,257 };
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 }
261258
262 pub fn getTargetSymbolAddress(rec: Record, ctx: struct {259 fn format2(
263 base_addr: u64,260 ctx: FormatContext,
264 base_offset: u64,261 comptime unused_fmt_string: []const u8,
265 }) u64 {262 options: std.fmt.FormatOptions,
266 assert(rec.tag == .fde);263 writer: anytype,
267 const addend = mem.readInt(i64, rec.data[4..][0..8], .little);264 ) !void {
268 return @as(u64, @intCast(@as(i64, @intCast(ctx.base_addr + ctx.base_offset + 8)) + addend));265 _ = unused_fmt_string;
269 }266 _ = options;
267 const fde = ctx.fde;
268 const macho_file = ctx.macho_file;
269 try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
270 fde.offset,
271 fde.getSize(),
272 fde.cie,
273 fde.getAtom(macho_file).getName(macho_file),
274 });
275 if (!fde.alive) try writer.writeAll(" : [*]");
276 }
270277
271 pub fn setTargetSymbolAddress(rec: *Record, value: u64, ctx: struct {278 pub const Index = u32;
272 base_addr: u64,279};
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 }
279280
280 pub fn getPersonalityPointerReloc(281pub const Iterator = struct {
281 rec: Record,282 data: []const u8,
282 macho_file: *MachO,283 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 }
320284
321 pub fn relocate(rec: *Record, macho_file: *MachO, object_id: u32, ctx: struct {285 pub const Record = struct {
322 source_offset: u32,286 tag: enum { fde, cie },
323 out_offset: u32,287 offset: u32,
324 sect_addr: u64,288 size: u32,
325 }) !void {289 };
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 }
381290
382 pub fn getCiePointerSource(rec: Record, object_id: u32, macho_file: *MachO, offset: u32) u32 {291 pub fn next(it: *Iterator) !?Record {
383 assert(rec.tag == .fde);292 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 }
405293
406 pub fn getCiePointer(rec: Record) u32 {294 var stream = std.io.fixedBufferStream(it.data[it.pos..]);
407 assert(rec.tag == .fde);295 const reader = stream.reader();
408 return mem.readInt(u32, rec.data[0..4], .little);
409 }
410296
411 pub fn setCiePointer(rec: *Record, ptr: u32) void {297 const size = try reader.readInt(u32, .little);
412 assert(rec.tag == .fde);298 if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS");
413 mem.writeInt(u32, rec.data[0..4], ptr, .little);
414 }
415299
416 pub fn getAugmentationString(rec: Record) []const u8 {300 const id = try reader.readInt(u32, .little);
417 assert(rec.tag == .cie);301 const record = Record{
418 return mem.sliceTo(@as([*:0]const u8, @ptrCast(rec.data.ptr + 5)), 0);302 .tag = if (id == 0) .cie else .fde,
419 }303 .offset = it.pos,
304 .size = size,
305 };
306 it.pos += size + 4;
420307
421 pub fn getPersonalityPointer(rec: Record, ctx: struct {308 return record;
422 base_addr: u64,309 }
423 base_offset: u64,310};
424 }) !?u64 {
425 assert(rec.tag == .cie);
426 const aug_str = rec.getAugmentationString();
427311
428 var stream = std.io.fixedBufferStream(rec.data[9 + aug_str.len ..]);312pub fn calcSize(macho_file: *MachO) !u32 {
429 var creader = std.io.countingReader(stream.reader());313 const tracy = trace(@src());
430 const reader = creader.reader();314 defer tracy.end();
431315
432 for (aug_str, 0..) |ch, i| switch (ch) {316 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 };
453317
454 return null;318 var cies = std.ArrayList(Cie).init(macho_file.base.allocator);
455 }319 defer cies.deinit();
320
321 for (macho_file.objects.items) |index| {
322 const object = macho_file.getFile(index).?.object;
456323
457 pub fn getLsdaPointer(rec: Record, cie: Record, ctx: struct {324 outer: for (object.cies.items) |*cie| {
458 base_addr: u64,325 for (cies.items) |other| {
459 base_offset: u64,326 if (other.eql(cie.*, macho_file)) {
460 }) !?u64 {327 // We already have a CIE record that has the exact same contents, so instead of
461 assert(rec.tag == .fde);328 // duplicating them, we mark this one dead and set its output offset to be
462 const enc = (try cie.getLsdaEncoding()) orelse return null;329 // equal to that of the alive record. This way, we won't have to rewrite
463 var stream = std.io.fixedBufferStream(rec.data[20..]);330 // Fde.cie_index field when committing the records to file.
464 const reader = stream.reader();331 cie.out_offset = other.out_offset;
465 _ = try reader.readByte();332 continue :outer;
466 const offset = ctx.base_offset + 25;333 }
467 const ptr = try getEncodedPointer(enc, @as(i64, @intCast(ctx.base_addr + offset)), reader);334 }
468 return ptr;335 cie.alive = true;
336 cie.out_offset = offset;
337 offset += cie.getSize();
338 try cies.append(cie.*);
469 }339 }
340 }
470341
471 pub fn setLsdaPointer(rec: *Record, cie: Record, value: u64, ctx: struct {342 for (macho_file.objects.items) |index| {
472 base_addr: u64,343 const object = macho_file.getFile(index).?.object;
473 base_offset: u64,344 for (object.fdes.items) |*fde| {
474 }) !void {345 if (!fde.alive) continue;
475 assert(rec.tag == .fde);346 fde.out_offset = offset;
476 const enc = (try cie.getLsdaEncoding()) orelse unreachable;347 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 }348 }
349 }
482350
483 fn getLsdaEncoding(rec: Record) !?u8 {351 return offset;
484 assert(rec.tag == .cie);352}
485 const aug_str = rec.getAugmentationString();
486353
487 const base_offset = 9 + aug_str.len;354pub fn calcNumRelocs(macho_file: *MachO) u32 {
488 var stream = std.io.fixedBufferStream(rec.data[base_offset..]);355 const tracy = trace(@src());
489 var creader = std.io.countingReader(stream.reader());356 defer tracy.end();
490 const reader = creader.reader();
491357
492 for (aug_str, 0..) |ch, i| switch (ch) {358 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 };
512359
513 return null;360 for (macho_file.objects.items) |index| {
361 const object = macho_file.getFile(index).?.object;
362 for (object.cies.items) |cie| {
363 if (!cie.alive) continue;
364 if (cie.getPersonality(macho_file)) |_| {
365 nreloc += 1; // personality
366 }
514 }367 }
368 }
515369
516 fn getEncodedPointer(enc: u8, pcrel_offset: i64, reader: anytype) !?u64 {370 return nreloc;
517 if (enc == EH_PE.omit) return null;371}
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 };
531372
532 switch (enc & 0x70) {373pub fn write(macho_file: *MachO, buffer: []u8) void {
533 EH_PE.absptr => {},374 const tracy = trace(@src());
534 EH_PE.pcrel => ptr += pcrel_offset,375 defer tracy.end();
535 EH_PE.datarel,376
536 EH_PE.textrel,377 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
537 EH_PE.funcrel,378 const addend: i64 = switch (macho_file.options.cpu_arch.?) {
538 EH_PE.aligned,379 .x86_64 => 4,
539 => return null,380 else => 0,
540 else => return null,381 };
541 }
542382
543 return @as(u64, @bitCast(ptr));383 for (macho_file.objects.items) |index| {
384 const object = macho_file.getFile(index).?.object;
385 for (object.cies.items) |cie| {
386 if (!cie.alive) continue;
387
388 @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
389
390 if (cie.getPersonality(macho_file)) |sym| {
391 const offset = cie.out_offset + cie.personality.?.offset;
392 const saddr = sect.addr + offset;
393 const taddr = sym.getGotAddress(macho_file);
394 std.mem.writeInt(
395 i32,
396 buffer[offset..][0..4],
397 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
398 .little,
399 );
400 }
544 }401 }
402 }
403
404 for (macho_file.objects.items) |index| {
405 const object = macho_file.getFile(index).?.object;
406 for (object.fdes.items) |fde| {
407 if (!fde.alive) continue;
545408
546 fn setEncodedPointer(enc: u8, pcrel_offset: i64, value: u64, writer: anytype) !void {409 @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
547 if (enc == EH_PE.omit) return;
548410
549 var actual = @as(i64, @intCast(value));411 {
412 const offset = fde.out_offset + 4;
413 const value = offset - fde.getCie(macho_file).out_offset;
414 std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);
415 }
550416
551 switch (enc & 0x70) {417 {
552 EH_PE.absptr => {},418 const offset = fde.out_offset + 8;
553 EH_PE.pcrel => actual -= pcrel_offset,419 const saddr = sect.addr + offset;
554 EH_PE.datarel,420 const taddr = fde.getAtom(macho_file).value;
555 EH_PE.textrel,421 std.mem.writeInt(
556 EH_PE.funcrel,422 i64,
557 EH_PE.aligned,423 buffer[offset..][0..8],
558 => unreachable,424 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
559 else => unreachable,425 .little,
426 );
560 }427 }
561428
562 switch (enc & 0x0F) {429 if (fde.getLsdaAtom(macho_file)) |atom| {
563 EH_PE.absptr => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),430 const offset = fde.out_offset + fde.lsda_offset;
564 EH_PE.udata2 => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(actual)))), .little),431 const saddr = sect.addr + offset;
565 EH_PE.udata4 => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(actual)))), .little),432 const taddr = atom.value;
566 EH_PE.udata8 => try writer.writeInt(u64, @as(u64, @bitCast(actual)), .little),433 switch (fde.getCie(macho_file).lsda_size.?) {
567 EH_PE.uleb128 => try leb.writeULEB128(writer, @as(u64, @bitCast(actual))),434 .p32 => std.mem.writeInt(
568 EH_PE.sdata2 => try writer.writeInt(i16, @as(i16, @intCast(actual)), .little),435 i32,
569 EH_PE.sdata4 => try writer.writeInt(i32, @as(i32, @intCast(actual)), .little),436 buffer[offset..][0..4],
570 EH_PE.sdata8 => try writer.writeInt(i64, actual, .little),437 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
571 EH_PE.sleb128 => try leb.writeILEB128(writer, actual),438 .little,
572 else => unreachable,439 ),
440 .p64 => std.mem.writeInt(
441 i64,
442 buffer[offset..][0..8],
443 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
444 .little,
445 ),
446 }
573 }447 }
574 }448 }
575 };449 }
576}
577
578pub fn getRelocs(macho_file: *MachO, object_id: u32, source_offset: u32) []const macho.relocation_info {
579 const object = &macho_file.objects.items[object_id];
580 assert(object.hasEhFrameRecords());
581 const urel = object.eh_frame_relocs_lookup.get(source_offset) orelse
582 return &[0]macho.relocation_info{};
583 const all_relocs = object.getRelocs(object.eh_frame_sect_id.?);
584 return all_relocs[urel.reloc.start..][0..urel.reloc.len];
585}450}
586451
587pub const Iterator = struct {452pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void {
588 data: []const u8,453 const tracy = trace(@src());
589 pos: u32 = 0,454 defer tracy.end();
590455
591 pub fn next(it: *Iterator) !?EhFrameRecord(false) {456 const cpu_arch = macho_file.options.cpu_arch.?;
592 if (it.pos >= it.data.len) return null;457 const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
593458 const addend: i64 = switch (cpu_arch) {
594 var stream = std.io.fixedBufferStream(it.data[it.pos..]);459 .x86_64 => 4,
595 const reader = stream.reader();460 else => 0,
461 };
596462
597 const size = try reader.readInt(u32, .little);463 for (macho_file.objects.items) |index| {
598 if (size == 0xFFFFFFFF) {464 const object = macho_file.getFile(index).?.object;
599 log.debug("MachO doesn't support 64bit DWARF CFI __eh_frame records", .{});465 for (object.cies.items) |cie| {
600 return error.BadDwarfCfi;466 if (!cie.alive) continue;
467
468 @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
469
470 if (cie.getPersonality(macho_file)) |sym| {
471 const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow;
472 const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
473 relocs.appendAssumeCapacity(.{
474 .r_address = r_address,
475 .r_symbolnum = r_symbolnum,
476 .r_length = 2,
477 .r_extern = 1,
478 .r_pcrel = 1,
479 .r_type = switch (cpu_arch) {
480 .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT),
481 .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT),
482 else => unreachable,
483 },
484 });
485 }
601 }486 }
487 }
602488
603 const id = try reader.readInt(u32, .little);489 for (macho_file.objects.items) |index| {
604 const tag: EhFrameRecordTag = if (id == 0) .cie else .fde;490 const object = macho_file.getFile(index).?.object;
605 const offset: u32 = 4;491 for (object.fdes.items) |fde| {
606 const record = EhFrameRecord(false){492 if (!fde.alive) continue;
607 .tag = tag,
608 .size = size,
609 .data = it.data[it.pos + offset ..][0..size],
610 };
611493
612 it.pos += size + offset;494 @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
613495
614 return record;496 {
615 }497 const offset = fde.out_offset + 4;
498 const value = offset - fde.getCie(macho_file).out_offset;
499 std.mem.writeInt(u32, code[offset..][0..4], value, .little);
500 }
616501
617 pub fn reset(it: *Iterator) void {502 {
618 it.pos = 0;503 const offset = fde.out_offset + 8;
619 }504 const saddr = sect.addr + offset;
505 const taddr = fde.getAtom(macho_file).value;
506 std.mem.writeInt(
507 i64,
508 code[offset..][0..8],
509 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
510 .little,
511 );
512 }
620513
621 pub fn seekTo(it: *Iterator, pos: u32) void {514 if (fde.getLsdaAtom(macho_file)) |atom| {
622 assert(pos >= 0 and pos < it.data.len);515 const offset = fde.out_offset + fde.lsda_ptr_offset;
623 it.pos = pos;516 const saddr = sect.addr + offset;
517 const taddr = atom.value + fde.lsda_offset;
518 switch (fde.getCie(macho_file).lsda_size.?) {
519 .p32 => std.mem.writeInt(
520 i32,
521 code[offset..][0..4],
522 @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
523 .little,
524 ),
525 .p64 => std.mem.writeInt(
526 i64,
527 code[offset..][0..8],
528 @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
529 .little,
530 ),
531 }
532 }
533 }
624 }534 }
625};535}
626536
627pub const EH_PE = struct {537pub const EH_PE = struct {
628 pub const absptr = 0x00;538 pub const absptr = 0x00;
...@@ -643,17 +553,17 @@ pub const EH_PE = struct {...@@ -643,17 +553,17 @@ pub const EH_PE = struct {
643 pub const omit = 0xFF;553 pub const omit = 0xFF;
644};554};
645555
646const std = @import("std");
647const assert = std.debug.assert;556const assert = std.debug.assert;
557const leb = std.leb;
648const macho = std.macho;558const macho = std.macho;
649const math = std.math;559const math = std.math;
650const mem = std.mem;560const mem = std.mem;
651const leb = std.leb;561const std = @import("std");
652const log = std.log.scoped(.eh_frame);562const trace = @import("../tracy.zig").trace;
653563
654const Allocator = mem.Allocator;564const Allocator = std.mem.Allocator;
655const Atom = @import("Atom.zig");565const Atom = @import("Atom.zig");
566const File = @import("file.zig").File;
656const MachO = @import("../MachO.zig");567const MachO = @import("../MachO.zig");
657const Relocation = @import("Relocation.zig");568const Object = @import("Object.zig");
658const SymbolWithLoc = MachO.SymbolWithLoc;569const Symbol = @import("Symbol.zig");
659const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/fat.zig+17-16
...@@ -1,3 +1,13 @@...@@ -1,3 +1,13 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const builtin = @import("builtin");
4const log = std.log.scoped(.macho);
5const macho = std.macho;
6const mem = std.mem;
7const native_endian = builtin.target.cpu.arch.endian();
8
9const MachO = @import("../MachO.zig");
10
1pub fn isFatLibrary(file: std.fs.File) bool {11pub fn isFatLibrary(file: std.fs.File) bool {
2 const reader = file.reader();12 const reader = file.reader();
3 const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false;13 const hdr = reader.readStructEndian(macho.fat_header, .big) catch return false;
...@@ -7,18 +17,16 @@ pub fn isFatLibrary(file: std.fs.File) bool {...@@ -7,18 +17,16 @@ pub fn isFatLibrary(file: std.fs.File) bool {
717
8pub const Arch = struct {18pub 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};
1223
13/// Caller owns the memory.24pub fn parseArchs(file: std.fs.File, buffer: *[2]Arch) ![]const Arch {
14pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {
15 const reader = file.reader();25 const reader = file.reader();
16 const fat_header = try reader.readStructEndian(macho.fat_header, .big);26 const fat_header = try reader.readStructEndian(macho.fat_header, .big);
17 assert(fat_header.magic == macho.FAT_MAGIC);27 assert(fat_header.magic == macho.FAT_MAGIC);
1828
19 var archs = try std.ArrayList(Arch).initCapacity(gpa, fat_header.nfat_arch);29 var count: usize = 0;
20 defer archs.deinit();
21
22 var fat_arch_index: u32 = 0;30 var fat_arch_index: u32 = 0;
23 while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {31 while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
24 const fat_arch = try reader.readStructEndian(macho.fat_arch, .big);32 const fat_arch = try reader.readStructEndian(macho.fat_arch, .big);
...@@ -29,16 +37,9 @@ pub fn parseArchs(gpa: Allocator, file: std.fs.File) ![]const Arch {...@@ -29,16 +37,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,37 macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue,
30 else => continue,38 else => continue,
31 };39 };
3240 buffer[count] = .{ .tag = arch, .offset = fat_arch.offset, .size = fat_arch.size };
33 archs.appendAssumeCapacity(.{ .tag = arch, .offset = fat_arch.offset });41 count += 1;
34 }42 }
3543
36 return archs.toOwnedSlice();44 return buffer[0..count];
37}45}
38
39const std = @import("std");
40const assert = std.debug.assert;
41const log = std.log.scoped(.archive);
42const macho = std.macho;
43const mem = std.mem;
44const Allocator = mem.Allocator;
src/link/MachO/file.zig created+116
...@@ -0,0 +1,116 @@
1pub const File = union(enum) {
2 internal: *InternalObject,
3 object: *Object,
4 dylib: *Dylib,
5
6 pub fn getIndex(file: File) Index {
7 return switch (file) {
8 inline else => |x| x.index,
9 };
10 }
11
12 pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) {
13 return .{ .data = file };
14 }
15
16 fn formatPath(
17 file: File,
18 comptime unused_fmt_string: []const u8,
19 options: std.fmt.FormatOptions,
20 writer: anytype,
21 ) !void {
22 _ = unused_fmt_string;
23 _ = options;
24 switch (file) {
25 .internal => try writer.writeAll(""),
26 .object => |x| try writer.print("{}", .{x.fmtPath()}),
27 .dylib => |x| try writer.writeAll(x.path),
28 }
29 }
30
31 pub fn resolveSymbols(file: File, macho_file: *MachO) void {
32 switch (file) {
33 .internal => unreachable,
34 inline else => |x| x.resolveSymbols(macho_file),
35 }
36 }
37
38 pub fn resetGlobals(file: File, macho_file: *MachO) void {
39 switch (file) {
40 .internal => unreachable,
41 inline else => |x| x.resetGlobals(macho_file),
42 }
43 }
44
45 /// Encodes symbol rank so that the following ordering applies:
46 /// * strong in object
47 /// * weak in object
48 /// * tentative in object
49 /// * strong in archive/dylib
50 /// * weak in archive/dylib
51 /// * tentative in archive
52 /// * unclaimed
53 pub fn getSymbolRank(file: File, args: struct {
54 archive: bool = false,
55 weak: bool = false,
56 tentative: bool = false,
57 }) u32 {
58 if (file == .object and !args.archive) {
59 const base: u32 = blk: {
60 if (args.tentative) break :blk 3;
61 break :blk if (args.weak) 2 else 1;
62 };
63 return (base << 16) + file.getIndex();
64 }
65 const base: u32 = blk: {
66 if (args.tentative) break :blk 3;
67 break :blk if (args.weak) 2 else 1;
68 };
69 return base + (file.getIndex() << 24);
70 }
71
72 pub fn getSymbols(file: File) []const Symbol.Index {
73 return switch (file) {
74 inline else => |x| x.symbols.items,
75 };
76 }
77
78 pub fn getAtoms(file: File) []const Atom.Index {
79 return switch (file) {
80 .dylib => unreachable,
81 inline else => |x| x.atoms.items,
82 };
83 }
84
85 pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
86 return switch (file) {
87 inline else => |x| x.calcSymtabSize(macho_file),
88 };
89 }
90
91 pub fn writeSymtab(file: File, macho_file: *MachO) void {
92 return switch (file) {
93 inline else => |x| x.writeSymtab(macho_file),
94 };
95 }
96
97 pub const Index = u32;
98
99 pub const Entry = union(enum) {
100 null: void,
101 internal: InternalObject,
102 object: Object,
103 dylib: Dylib,
104 };
105};
106
107const macho = std.macho;
108const std = @import("std");
109
110const Allocator = std.mem.Allocator;
111const Atom = @import("Atom.zig");
112const InternalObject = @import("InternalObject.zig");
113const MachO = @import("../MachO.zig");
114const Object = @import("Object.zig");
115const Dylib = @import("Dylib.zig");
116const Symbol = @import("Symbol.zig");
src/link/MachO/hasher.zig+12-9
...@@ -9,15 +9,14 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -9,15 +9,14 @@ 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 = .{};
1316
14 const file_size = blk: {17 const file_size = opts.max_file_size orelse try file.getEndPos();
15 const file_size = opts.max_file_size orelse try file.getEndPos();
16 break :blk std.math.cast(usize, file_size) orelse return error.Overflow;
17 };
18 const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow;
1918
20 const buffer = try self.allocator.alloc(u8, chunk_size * out.len);19 const buffer = try self.allocator.alloc(u8, opts.chunk_size * out.len);
21 defer self.allocator.free(buffer);20 defer self.allocator.free(buffer);
2221
23 const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);22 const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);
...@@ -28,8 +27,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -28,8 +27,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
28 defer wg.wait();27 defer wg.wait();
2928
30 for (out, results, 0..) |*out_buf, *result, i| {29 for (out, results, 0..) |*out_buf, *result, i| {
31 const fstart = i * chunk_size;30 const fstart = i * opts.chunk_size;
32 const fsize = if (fstart + chunk_size > file_size) file_size - fstart else chunk_size;31 const fsize = if (fstart + opts.chunk_size > file_size)
32 file_size - fstart
33 else
34 opts.chunk_size;
33 wg.start();35 wg.start();
34 try self.thread_pool.spawn(worker, .{36 try self.thread_pool.spawn(worker, .{
35 file,37 file,
...@@ -61,10 +63,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -61,10 +63,11 @@ pub fn ParallelHasher(comptime Hasher: type) type {
61 };63 };
62}64}
6365
64const std = @import("std");
65const assert = std.debug.assert;66const assert = std.debug.assert;
66const fs = std.fs;67const fs = std.fs;
67const mem = std.mem;68const mem = std.mem;
69const std = @import("std");
70const trace = @import("../tracy.zig").trace;
6871
69const Allocator = mem.Allocator;72const Allocator = mem.Allocator;
70const ThreadPool = std.Thread.Pool;73const ThreadPool = std.Thread.Pool;
src/link/MachO/load_commands.zig+110-412
...@@ -1,4 +1,14 @@...@@ -1,4 +1,14 @@
1/// Default path to dyld.1const std = @import("std");
2const assert = std.debug.assert;
3const log = std.log.scoped(.link);
4const macho = std.macho;
5const mem = std.mem;
6
7const Allocator = mem.Allocator;
8const Dylib = @import("Dylib.zig");
9const MachO = @import("../MachO.zig");
10const Options = @import("../MachO.zig").Options;
11
2pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";12pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
313
4fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {14fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
...@@ -7,31 +17,20 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool...@@ -7,31 +17,20 @@ fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool
7 return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));17 return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));
8}18}
919
10const CalcLCsSizeCtx = struct {20pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 {
11 segments: []const macho.segment_command_64,21 const options = &macho_file.options;
12 dylibs: []const Dylib,
13 referenced_dylibs: []u16,
14 wants_function_starts: bool = true,
15};
16
17fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
18 const comp = m.base.comp;
19 const gpa = comp.gpa;
20 var has_text_segment: bool = false;
21 var sizeofcmds: u64 = 0;22 var sizeofcmds: u64 = 0;
22 for (ctx.segments) |seg| {23
23 sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64);24 // LC_SEGMENT_64
24 if (mem.eql(u8, seg.segName(), "__TEXT")) {25 sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len;
25 has_text_segment = true;26 for (macho_file.segments.items) |seg| {
26 }27 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
27 }28 }
2829
29 // LC_DYLD_INFO_ONLY30 // LC_DYLD_INFO_ONLY
30 sizeofcmds += @sizeOf(macho.dyld_info_command);31 sizeofcmds += @sizeOf(macho.dyld_info_command);
31 // LC_FUNCTION_STARTS32 // LC_FUNCTION_STARTS
32 if (has_text_segment and ctx.wants_function_starts) {33 sizeofcmds += @sizeOf(macho.linkedit_data_command);
33 sizeofcmds += @sizeOf(macho.linkedit_data_command);
34 }
35 // LC_DATA_IN_CODE34 // LC_DATA_IN_CODE
36 sizeofcmds += @sizeOf(macho.linkedit_data_command);35 sizeofcmds += @sizeOf(macho.linkedit_data_command);
37 // LC_SYMTAB36 // LC_SYMTAB
...@@ -45,15 +44,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {...@@ -45,15 +44,14 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
45 false,44 false,
46 );45 );
47 // LC_MAIN46 // LC_MAIN
48 if (comp.config.output_mode == .Exe) {47 if (!options.dylib) {
49 sizeofcmds += @sizeOf(macho.entry_point_command);48 sizeofcmds += @sizeOf(macho.entry_point_command);
50 }49 }
51 // LC_ID_DYLIB50 // LC_ID_DYLIB
52 if (comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic) {51 if (options.dylib) {
53 sizeofcmds += blk: {52 sizeofcmds += blk: {
54 const emit = m.base.emit;53 const emit = options.emit;
55 const install_name = m.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path});54 const install_name = options.install_name orelse emit.sub_path;
56 defer if (m.install_name == null) gpa.free(install_name);
57 break :blk calcInstallNameLen(55 break :blk calcInstallNameLen(
58 @sizeOf(macho.dylib_command),56 @sizeOf(macho.dylib_command),
59 install_name,57 install_name,
...@@ -63,9 +61,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {...@@ -63,9 +61,7 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
63 }61 }
64 // LC_RPATH62 // LC_RPATH
65 {63 {
66 var it = RpathIterator.init(gpa, m.base.rpath_list);64 for (options.rpath_list) |rpath| {
67 defer it.deinit();
68 while (try it.next()) |rpath| {
69 sizeofcmds += calcInstallNameLen(65 sizeofcmds += calcInstallNameLen(
70 @sizeOf(macho.rpath_command),66 @sizeOf(macho.rpath_command),
71 rpath,67 rpath,
...@@ -75,24 +71,22 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {...@@ -75,24 +71,22 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
75 }71 }
76 // LC_SOURCE_VERSION72 // LC_SOURCE_VERSION
77 sizeofcmds += @sizeOf(macho.source_version_command);73 sizeofcmds += @sizeOf(macho.source_version_command);
78 // LC_BUILD_VERSION or LC_VERSION_MIN_ or nothing74 if (options.platform) |platform| {
79 {
80 const target = comp.root_mod.resolved_target.result;
81 const platform = Platform.fromTarget(target);
82 if (platform.isBuildVersionCompatible()) {75 if (platform.isBuildVersionCompatible()) {
83 // LC_BUILD_VERSION76 // LC_BUILD_VERSION
84 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);77 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
85 } else if (platform.isVersionMinCompatible()) {78 } else {
86 // LC_VERSION_MIN_79 // LC_VERSION_MIN_*
87 sizeofcmds += @sizeOf(macho.version_min_command);80 sizeofcmds += @sizeOf(macho.version_min_command);
88 }81 }
89 }82 }
90 // LC_UUID83 // LC_UUID
91 sizeofcmds += @sizeOf(macho.uuid_command);84 sizeofcmds += @sizeOf(macho.uuid_command);
92 // LC_LOAD_DYLIB85 // LC_LOAD_DYLIB
93 for (ctx.referenced_dylibs) |id| {86 for (macho_file.dylibs.items) |index| {
94 const dylib = ctx.dylibs[id];87 const dylib = macho_file.getFile(index).?.dylib;
95 const dylib_id = dylib.id orelse unreachable;88 assert(dylib.isAlive(macho_file));
89 const dylib_id = dylib.id.?;
96 sizeofcmds += calcInstallNameLen(90 sizeofcmds += calcInstallNameLen(
97 @sizeOf(macho.dylib_command),91 @sizeOf(macho.dylib_command),
98 dylib_id.name,92 dylib_id.name,
...@@ -100,19 +94,52 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {...@@ -100,19 +94,52 @@ fn calcLCsSize(m: *MachO, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 {
100 );94 );
101 }95 }
102 // LC_CODE_SIGNATURE96 // LC_CODE_SIGNATURE
103 if (m.requiresCodeSignature()) {97 if (macho_file.requiresCodeSig()) {
104 sizeofcmds += @sizeOf(macho.linkedit_data_command);98 sizeofcmds += @sizeOf(macho.linkedit_data_command);
105 }99 }
106100
107 return @intCast(sizeofcmds);101 return @as(u32, @intCast(sizeofcmds));
102}
103
104pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
105 const options = &macho_file.options;
106 var sizeofcmds: u64 = 0;
107
108 // LC_SEGMENT_64
109 {
110 assert(macho_file.segments.items.len == 1);
111 sizeofcmds += @sizeOf(macho.segment_command_64);
112 const seg = macho_file.segments.items[0];
113 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
114 }
115
116 // LC_DATA_IN_CODE
117 sizeofcmds += @sizeOf(macho.linkedit_data_command);
118 // LC_SYMTAB
119 sizeofcmds += @sizeOf(macho.symtab_command);
120 // LC_DYSYMTAB
121 sizeofcmds += @sizeOf(macho.dysymtab_command);
122
123 if (options.platform) |platform| {
124 if (platform.isBuildVersionCompatible()) {
125 // LC_BUILD_VERSION
126 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
127 } else {
128 // LC_VERSION_MIN_*
129 sizeofcmds += @sizeOf(macho.version_min_command);
130 }
131 }
132
133 return @as(u32, @intCast(sizeofcmds));
108}134}
109135
110pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {136pub fn calcMinHeaderPadSize(macho_file: *MachO) u32 {
111 var padding: u32 = (try calcLCsSize(m, ctx, false)) + m.headerpad_size;137 const options = &macho_file.options;
138 var padding: u32 = calcLoadCommandsSize(macho_file, false) + (options.headerpad orelse 0);
112 log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});139 log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
113140
114 if (m.headerpad_max_install_names) {141 if (options.headerpad_max_install_names) {
115 const min_headerpad_size: u32 = try calcLCsSize(m, ctx, true);142 const min_headerpad_size: u32 = calcLoadCommandsSize(macho_file, true);
116 log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{143 log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
117 min_headerpad_size + @sizeOf(macho.mach_header_64),144 min_headerpad_size + @sizeOf(macho.mach_header_64),
118 });145 });
...@@ -125,34 +152,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {...@@ -125,34 +152,22 @@ pub fn calcMinHeaderPad(m: *MachO, ctx: CalcLCsSizeCtx) !u64 {
125 return offset;152 return offset;
126}153}
127154
128pub fn calcNumOfLCs(lc_buffer: []const u8) u32 {155pub 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
140pub fn writeDylinkerLC(lc_writer: anytype) !void {
141 const name_len = mem.sliceTo(default_dyld_path, 0).len;156 const name_len = mem.sliceTo(default_dyld_path, 0).len;
142 const cmdsize = @as(u32, @intCast(mem.alignForward(157 const cmdsize = @as(u32, @intCast(mem.alignForward(
143 u64,158 u64,
144 @sizeOf(macho.dylinker_command) + name_len,159 @sizeOf(macho.dylinker_command) + name_len,
145 @sizeOf(u64),160 @sizeOf(u64),
146 )));161 )));
147 try lc_writer.writeStruct(macho.dylinker_command{162 try writer.writeStruct(macho.dylinker_command{
148 .cmd = .LOAD_DYLINKER,163 .cmd = .LOAD_DYLINKER,
149 .cmdsize = cmdsize,164 .cmdsize = cmdsize,
150 .name = @sizeOf(macho.dylinker_command),165 .name = @sizeOf(macho.dylinker_command),
151 });166 });
152 try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0));167 try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
153 const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;168 const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
154 if (padding > 0) {169 if (padding > 0) {
155 try lc_writer.writeByteNTimes(0, padding);170 try writer.writeByteNTimes(0, padding);
156 }171 }
157}172}
158173
...@@ -164,14 +179,14 @@ const WriteDylibLCCtx = struct {...@@ -164,14 +179,14 @@ const WriteDylibLCCtx = struct {
164 compatibility_version: u32 = 0x10000,179 compatibility_version: u32 = 0x10000,
165};180};
166181
167fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {182pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void {
168 const name_len = ctx.name.len + 1;183 const name_len = ctx.name.len + 1;
169 const cmdsize = @as(u32, @intCast(mem.alignForward(184 const cmdsize = @as(u32, @intCast(mem.alignForward(
170 u64,185 u64,
171 @sizeOf(macho.dylib_command) + name_len,186 @sizeOf(macho.dylib_command) + name_len,
172 @sizeOf(u64),187 @sizeOf(u64),
173 )));188 )));
174 try lc_writer.writeStruct(macho.dylib_command{189 try writer.writeStruct(macho.dylib_command{
175 .cmd = ctx.cmd,190 .cmd = ctx.cmd,
176 .cmdsize = cmdsize,191 .cmdsize = cmdsize,
177 .dylib = .{192 .dylib = .{
...@@ -181,392 +196,75 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {...@@ -181,392 +196,75 @@ fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void {
181 .compatibility_version = ctx.compatibility_version,196 .compatibility_version = ctx.compatibility_version,
182 },197 },
183 });198 });
184 try lc_writer.writeAll(ctx.name);199 try writer.writeAll(ctx.name);
185 try lc_writer.writeByte(0);200 try writer.writeByte(0);
186 const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;201 const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
187 if (padding > 0) {202 if (padding > 0) {
188 try lc_writer.writeByteNTimes(0, padding);203 try writer.writeByteNTimes(0, padding);
189 }204 }
190}205}
191206
192pub fn writeDylibIdLC(macho_file: *MachO, lc_writer: anytype) !void {207pub fn writeDylibIdLC(options: *const Options, writer: anytype) !void {
193 const comp = macho_file.base.comp;208 assert(options.dylib);
194 const gpa = comp.gpa;209 const emit = options.emit;
195 assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic);210 const install_name = options.install_name orelse emit.sub_path;
196 const emit = macho_file.base.emit;211 const curr = options.current_version orelse Options.Version.new(1, 0, 0);
197 const install_name = macho_file.install_name orelse212 const compat = options.compatibility_version orelse Options.Version.new(1, 0, 0);
198 try emit.directory.join(gpa, &.{emit.sub_path});
199 defer if (macho_file.install_name == null) gpa.free(install_name);
200 const curr = comp.version orelse std.SemanticVersion{
201 .major = 1,
202 .minor = 0,
203 .patch = 0,
204 };
205 const compat = macho_file.compatibility_version orelse std.SemanticVersion{
206 .major = 1,
207 .minor = 0,
208 .patch = 0,
209 };
210 try writeDylibLC(.{213 try writeDylibLC(.{
211 .cmd = .ID_DYLIB,214 .cmd = .ID_DYLIB,
212 .name = install_name,215 .name = install_name,
213 .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)),216 .current_version = curr.value,
214 .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)),217 .compatibility_version = compat.value,
215 }, lc_writer);218 }, writer);
216}219}
217220
218const RpathIterator = struct {221pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void {
219 buffer: []const []const u8,222 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
243pub fn writeRpathLCs(macho_file: *MachO, lc_writer: anytype) !void {
244 const comp = macho_file.base.comp;
245 const gpa = comp.gpa;
246
247 var it = RpathIterator.init(gpa, macho_file.base.rpath_list);
248 defer it.deinit();
249
250 while (try it.next()) |rpath| {
251 const rpath_len = rpath.len + 1;223 const rpath_len = rpath.len + 1;
252 const cmdsize = @as(u32, @intCast(mem.alignForward(224 const cmdsize = @as(u32, @intCast(mem.alignForward(
253 u64,225 u64,
254 @sizeOf(macho.rpath_command) + rpath_len,226 @sizeOf(macho.rpath_command) + rpath_len,
255 @sizeOf(u64),227 @sizeOf(u64),
256 )));228 )));
257 try lc_writer.writeStruct(macho.rpath_command{229 try writer.writeStruct(macho.rpath_command{
258 .cmdsize = cmdsize,230 .cmdsize = cmdsize,
259 .path = @sizeOf(macho.rpath_command),231 .path = @sizeOf(macho.rpath_command),
260 });232 });
261 try lc_writer.writeAll(rpath);233 try writer.writeAll(rpath);
262 try lc_writer.writeByte(0);234 try writer.writeByte(0);
263 const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;235 const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
264 if (padding > 0) {236 if (padding > 0) {
265 try lc_writer.writeByteNTimes(0, padding);237 try writer.writeByteNTimes(0, padding);
266 }238 }
267 }239 }
268}240}
269241
270pub fn writeVersionMinLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {242pub fn writeVersionMinLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void {
271 const cmd: macho.LC = switch (platform.os_tag) {243 const cmd: macho.LC = switch (platform.platform) {
272 .macos => .VERSION_MIN_MACOSX,244 .MACOS => .VERSION_MIN_MACOSX,
273 .ios => .VERSION_MIN_IPHONEOS,245 .IOS, .IOSSIMULATOR => .VERSION_MIN_IPHONEOS,
274 .tvos => .VERSION_MIN_TVOS,246 .TVOS, .TVOSSIMULATOR => .VERSION_MIN_TVOS,
275 .watchos => .VERSION_MIN_WATCHOS,247 .WATCHOS, .WATCHOSSIMULATOR => .VERSION_MIN_WATCHOS,
276 else => unreachable,248 else => unreachable,
277 };249 };
278 try lc_writer.writeAll(mem.asBytes(&macho.version_min_command{250 try writer.writeAll(mem.asBytes(&macho.version_min_command{
279 .cmd = cmd,251 .cmd = cmd,
280 .version = platform.toAppleVersion(),252 .version = platform.version.value,
281 .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),253 .sdk = if (sdk_version) |ver| ver.value else platform.version.value,
282 }));254 }));
283}255}
284256
285pub fn writeBuildVersionLC(platform: Platform, sdk_version: ?std.SemanticVersion, lc_writer: anytype) !void {257pub fn writeBuildVersionLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void {
286 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);258 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
287 try lc_writer.writeStruct(macho.build_version_command{259 try writer.writeStruct(macho.build_version_command{
288 .cmdsize = cmdsize,260 .cmdsize = cmdsize,
289 .platform = platform.toApplePlatform(),261 .platform = platform.platform,
290 .minos = platform.toAppleVersion(),262 .minos = platform.version.value,
291 .sdk = if (sdk_version) |ver| semanticVersionToAppleVersion(ver) else platform.toAppleVersion(),263 .sdk = if (sdk_version) |ver| ver.value else platform.version.value,
292 .ntools = 1,264 .ntools = 1,
293 });265 });
294 try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{266 try writer.writeAll(mem.asBytes(&macho.build_tool_version{
295 .tool = .ZIG,267 .tool = @as(macho.TOOL, @enumFromInt(0x6)),
296 .version = 0x0,268 .version = 0x0,
297 }));269 }));
298}270}
299
300pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void {
301 for (referenced) |index| {
302 const dylib = dylibs[index];
303 const dylib_id = dylib.id orelse unreachable;
304 try writeDylibLC(.{
305 .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB,
306 .name = dylib_id.name,
307 .timestamp = dylib_id.timestamp,
308 .current_version = dylib_id.current_version,
309 .compatibility_version = dylib_id.compatibility_version,
310 }, lc_writer);
311 }
312}
313
314pub const Platform = struct {
315 os_tag: std.Target.Os.Tag,
316 abi: std.Target.Abi,
317 version: std.SemanticVersion,
318
319 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
320 /// the extracted minimum platform version.
321 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
322 switch (lc.cmd()) {
323 .BUILD_VERSION => {
324 const cmd = lc.cast(macho.build_version_command).?;
325 return .{
326 .os_tag = switch (cmd.platform) {
327 .MACOS => .macos,
328 .IOS, .IOSSIMULATOR => .ios,
329 .TVOS, .TVOSSIMULATOR => .tvos,
330 .WATCHOS, .WATCHOSSIMULATOR => .watchos,
331 else => @panic("TODO"),
332 },
333 .abi = switch (cmd.platform) {
334 .IOSSIMULATOR,
335 .TVOSSIMULATOR,
336 .WATCHOSSIMULATOR,
337 => .simulator,
338 else => .none,
339 },
340 .version = appleVersionToSemanticVersion(cmd.minos),
341 };
342 },
343 .VERSION_MIN_MACOSX,
344 .VERSION_MIN_IPHONEOS,
345 .VERSION_MIN_TVOS,
346 .VERSION_MIN_WATCHOS,
347 => {
348 const cmd = lc.cast(macho.version_min_command).?;
349 return .{
350 .os_tag = switch (lc.cmd()) {
351 .VERSION_MIN_MACOSX => .macos,
352 .VERSION_MIN_IPHONEOS => .ios,
353 .VERSION_MIN_TVOS => .tvos,
354 .VERSION_MIN_WATCHOS => .watchos,
355 else => unreachable,
356 },
357 .abi = .none,
358 .version = appleVersionToSemanticVersion(cmd.version),
359 };
360 },
361 else => unreachable,
362 }
363 }
364
365 pub fn fromTarget(target: std.Target) Platform {
366 return .{
367 .os_tag = target.os.tag,
368 .abi = target.abi,
369 .version = target.os.version_range.semver.min,
370 };
371 }
372
373 pub fn toAppleVersion(plat: Platform) u32 {
374 return semanticVersionToAppleVersion(plat.version);
375 }
376
377 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
378 return switch (plat.os_tag) {
379 .macos => .MACOS,
380 .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
381 .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
382 .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
383 else => unreachable,
384 };
385 }
386
387 pub fn isBuildVersionCompatible(plat: Platform) bool {
388 inline for (supported_platforms) |sup_plat| {
389 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
390 return sup_plat[2] <= plat.toAppleVersion();
391 }
392 }
393 return false;
394 }
395
396 pub fn isVersionMinCompatible(plat: Platform) bool {
397 inline for (supported_platforms) |sup_plat| {
398 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
399 return sup_plat[3] <= plat.toAppleVersion();
400 }
401 }
402 return false;
403 }
404
405 pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(formatTarget) {
406 return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
407 }
408
409 const FmtCtx = struct {
410 platform: Platform,
411 cpu_arch: std.Target.Cpu.Arch,
412 };
413
414 pub fn formatTarget(
415 ctx: FmtCtx,
416 comptime unused_fmt_string: []const u8,
417 options: std.fmt.FormatOptions,
418 writer: anytype,
419 ) !void {
420 _ = unused_fmt_string;
421 _ = options;
422 try writer.print("{s}-{s}", .{ @tagName(ctx.cpu_arch), @tagName(ctx.platform.os_tag) });
423 if (ctx.platform.abi != .none) {
424 try writer.print("-{s}", .{@tagName(ctx.platform.abi)});
425 }
426 }
427
428 /// Caller owns the memory.
429 pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
430 var buffer = std.ArrayList(u8).init(gpa);
431 defer buffer.deinit();
432 try buffer.writer().print("{}", .{plat.fmtTarget(cpu_arch)});
433 return buffer.toOwnedSlice();
434 }
435
436 pub fn eqlTarget(plat: Platform, other: Platform) bool {
437 return plat.os_tag == other.os_tag and plat.abi == other.abi;
438 }
439};
440
441const SupportedPlatforms = struct {
442 std.Target.Os.Tag,
443 std.Target.Abi,
444 u32, // Min platform version for which to emit LC_BUILD_VERSION
445 u32, // Min supported platform version
446};
447
448// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
449// zig fmt: off
450const supported_platforms = [_]SupportedPlatforms{
451 .{ .macos, .none, 0xA0E00, 0xA0800 },
452 .{ .ios, .none, 0xC0000, 0x70000 },
453 .{ .tvos, .none, 0xC0000, 0x70000 },
454 .{ .watchos, .none, 0x50000, 0x20000 },
455 .{ .ios, .simulator, 0xD0000, 0x80000 },
456 .{ .tvos, .simulator, 0xD0000, 0x80000 },
457 .{ .watchos, .simulator, 0x60000, 0x20000 },
458};
459// zig fmt: on
460
461inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
462 const major = version.major;
463 const minor = version.minor;
464 const patch = version.patch;
465 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
466}
467
468pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
469 return .{
470 .major = @as(u16, @truncate(version >> 16)),
471 .minor = @as(u8, @truncate(version >> 8)),
472 .patch = @as(u8, @truncate(version)),
473 };
474}
475
476pub fn inferSdkVersion(macho_file: *MachO) ?std.SemanticVersion {
477 const comp = macho_file.base.comp;
478 const gpa = comp.gpa;
479
480 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
481 defer arena_allocator.deinit();
482 const arena = arena_allocator.allocator();
483
484 const sdk_layout = macho_file.sdk_layout orelse return null;
485 const sdk_dir = switch (sdk_layout) {
486 .sdk => comp.sysroot.?,
487 .vendored => std.fs.path.join(arena, &.{ comp.zig_lib_directory.path.?, "libc", "darwin" }) catch return null,
488 };
489 if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
490 return parseSdkVersion(ver);
491 } else |_| {
492 // Read from settings should always succeed when vendored.
493 if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
494 }
495
496 // infer from pathname
497 const stem = std.fs.path.stem(sdk_dir);
498 const start = for (stem, 0..) |c, i| {
499 if (std.ascii.isDigit(c)) break i;
500 } else stem.len;
501 const end = for (stem[start..], start..) |c, i| {
502 if (std.ascii.isDigit(c) or c == '.') continue;
503 break i;
504 } else stem.len;
505 return parseSdkVersion(stem[start..end]);
506}
507
508// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
509// Use property `MinimalDisplayName` to determine version.
510// The file/property is also available with vendored libc.
511fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
512 const sdk_path = try std.fs.path.join(arena, &.{ dir, "SDKSettings.json" });
513 const contents = try std.fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16));
514 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
515 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
516 return error.SdkVersionFailure;
517}
518
519// Versions reported by Apple aren't exactly semantically valid as they usually omit
520// the patch component, so we parse SDK value by hand.
521fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
522 var parsed: std.SemanticVersion = .{
523 .major = 0,
524 .minor = 0,
525 .patch = 0,
526 };
527
528 const parseNext = struct {
529 fn parseNext(it: anytype) ?u16 {
530 const nn = it.next() orelse return null;
531 return std.fmt.parseInt(u16, nn, 10) catch null;
532 }
533 }.parseNext;
534
535 var it = std.mem.splitAny(u8, raw, ".");
536 parsed.major = parseNext(&it) orelse return null;
537 parsed.minor = parseNext(&it) orelse return null;
538 parsed.patch = parseNext(&it) orelse 0;
539 return parsed;
540}
541
542const expect = std.testing.expect;
543const expectEqual = std.testing.expectEqual;
544
545fn testParseSdkVersionSuccess(exp: std.SemanticVersion, raw: []const u8) !void {
546 const maybe_ver = parseSdkVersion(raw);
547 try expect(maybe_ver != null);
548 const ver = maybe_ver.?;
549 try expectEqual(exp.major, ver.major);
550 try expectEqual(exp.minor, ver.minor);
551 try expectEqual(exp.patch, ver.patch);
552}
553
554test "parseSdkVersion" {
555 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 0 }, "13.4");
556 try testParseSdkVersionSuccess(.{ .major = 13, .minor = 4, .patch = 1 }, "13.4.1");
557 try testParseSdkVersionSuccess(.{ .major = 11, .minor = 15, .patch = 0 }, "11.15");
558
559 try expect(parseSdkVersion("11") == null);
560}
561
562const std = @import("std");
563const assert = std.debug.assert;
564const link = @import("../../link.zig");
565const log = std.log.scoped(.link);
566const macho = std.macho;
567const mem = std.mem;
568
569const Allocator = mem.Allocator;
570const Dylib = @import("Dylib.zig");
571const MachO = @import("../MachO.zig");
572const Compilation = @import("../../Compilation.zig");
src/link/MachO/relocatable.zig created+452
...@@ -0,0 +1,452 @@
1pub fn flush(macho_file: *MachO) !void {
2 markExports(macho_file);
3 claimUnresolved(macho_file);
4 try initOutputSections(macho_file);
5 try macho_file.sortSections();
6 try macho_file.addAtomsToSections();
7 try calcSectionSizes(macho_file);
8
9 {
10 // For relocatable, we only ever need a single segment so create it now.
11 const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC;
12 try macho_file.segments.append(macho_file.base.allocator, .{
13 .cmdsize = @sizeOf(macho.segment_command_64),
14 .segname = MachO.makeStaticString(""),
15 .maxprot = prot,
16 .initprot = prot,
17 });
18 const seg = &macho_file.segments.items[0];
19 seg.nsects = @intCast(macho_file.sections.items(.header).len);
20 seg.cmdsize += seg.nsects * @sizeOf(macho.section_64);
21 }
22
23 var off = try allocateSections(macho_file);
24
25 {
26 // Allocate the single segment.
27 assert(macho_file.segments.items.len == 1);
28 const seg = &macho_file.segments.items[0];
29 var vmaddr: u64 = 0;
30 var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
31 seg.vmaddr = vmaddr;
32 seg.fileoff = fileoff;
33
34 for (macho_file.sections.items(.header)) |header| {
35 vmaddr = header.addr + header.size;
36 if (!header.isZerofill()) {
37 fileoff = header.offset + header.size;
38 }
39 }
40
41 seg.vmsize = vmaddr - seg.vmaddr;
42 seg.filesize = fileoff - seg.fileoff;
43 }
44
45 macho_file.allocateAtoms();
46
47 state_log.debug("{}", .{macho_file.dumpState()});
48
49 try macho_file.calcSymtabSize();
50 try writeAtoms(macho_file);
51 try writeCompactUnwind(macho_file);
52 try writeEhFrame(macho_file);
53
54 off = mem.alignForward(u32, off, @alignOf(u64));
55 off = try macho_file.writeDataInCode(0, off);
56 off = mem.alignForward(u32, off, @alignOf(u64));
57 off = try macho_file.writeSymtab(off);
58 off = mem.alignForward(u32, off, @alignOf(u64));
59 off = try macho_file.writeStrtab(off);
60
61 const ncmds, const sizeofcmds = try writeLoadCommands(macho_file);
62 try writeHeader(macho_file, ncmds, sizeofcmds);
63}
64
65fn markExports(macho_file: *MachO) void {
66 for (macho_file.objects.items) |index| {
67 for (macho_file.getFile(index).?.getSymbols()) |sym_index| {
68 const sym = macho_file.getSymbol(sym_index);
69 const file = sym.getFile(macho_file) orelse continue;
70 if (sym.visibility != .global) continue;
71 if (file.getIndex() == index) {
72 sym.flags.@"export" = true;
73 }
74 }
75 }
76}
77
78fn claimUnresolved(macho_file: *MachO) void {
79 for (macho_file.objects.items) |index| {
80 const object = macho_file.getFile(index).?.object;
81
82 for (object.symbols.items, 0..) |sym_index, i| {
83 const nlist_idx = @as(Symbol.Index, @intCast(i));
84 const nlist = object.symtab.items(.nlist)[nlist_idx];
85 if (!nlist.ext()) continue;
86 if (!nlist.undf()) continue;
87
88 const sym = macho_file.getSymbol(sym_index);
89 if (sym.getFile(macho_file) != null) continue;
90
91 sym.value = 0;
92 sym.atom = 0;
93 sym.nlist_idx = nlist_idx;
94 sym.file = index;
95 sym.flags.weak_ref = nlist.weakRef();
96 sym.flags.import = true;
97 sym.visibility = .global;
98 }
99 }
100}
101
102fn initOutputSections(macho_file: *MachO) !void {
103 for (macho_file.objects.items) |index| {
104 const object = macho_file.getFile(index).?.object;
105 for (object.atoms.items) |atom_index| {
106 const atom = macho_file.getAtom(atom_index) orelse continue;
107 if (!atom.flags.alive) continue;
108 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
109 }
110 }
111
112 const needs_unwind_info = for (macho_file.objects.items) |index| {
113 if (macho_file.getFile(index).?.object.compact_unwind_sect_index != null) break true;
114 } else false;
115 if (needs_unwind_info) {
116 macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{
117 .flags = macho.S_ATTR_DEBUG,
118 });
119 }
120
121 const needs_eh_frame = for (macho_file.objects.items) |index| {
122 if (macho_file.getFile(index).?.object.eh_frame_sect_index != null) break true;
123 } else false;
124 if (needs_eh_frame) {
125 assert(needs_unwind_info);
126 macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{});
127 }
128}
129
130fn calcSectionSizes(macho_file: *MachO) !void {
131 const slice = macho_file.sections.slice();
132 for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
133 if (atoms.items.len == 0) continue;
134 for (atoms.items) |atom_index| {
135 const atom = macho_file.getAtom(atom_index).?;
136 const atom_alignment = try math.powi(u32, 2, atom.alignment);
137 const offset = mem.alignForward(u64, header.size, atom_alignment);
138 const padding = offset - header.size;
139 atom.value = offset;
140 header.size += padding + atom.size;
141 header.@"align" = @max(header.@"align", atom.alignment);
142 header.nreloc += atom.calcNumRelocs(macho_file);
143 }
144 }
145
146 if (macho_file.unwind_info_sect_index) |index| {
147 calcCompactUnwindSize(macho_file, index);
148 }
149
150 if (macho_file.eh_frame_sect_index) |index| {
151 const sect = &macho_file.sections.items(.header)[index];
152 sect.size = try eh_frame.calcSize(macho_file);
153 sect.@"align" = 3;
154 sect.nreloc = eh_frame.calcNumRelocs(macho_file);
155 }
156}
157
158fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void {
159 var size: u32 = 0;
160 var nreloc: u32 = 0;
161
162 for (macho_file.objects.items) |index| {
163 const object = macho_file.getFile(index).?.object;
164 for (object.unwind_records.items) |irec| {
165 const rec = macho_file.getUnwindRecord(irec);
166 if (!rec.alive) continue;
167 size += @sizeOf(macho.compact_unwind_entry);
168 nreloc += 1;
169 if (rec.getPersonality(macho_file)) |_| {
170 nreloc += 1;
171 }
172 if (rec.getLsdaAtom(macho_file)) |_| {
173 nreloc += 1;
174 }
175 }
176 }
177
178 const sect = &macho_file.sections.items(.header)[sect_index];
179 sect.size = size;
180 sect.nreloc = nreloc;
181 sect.@"align" = 3;
182}
183
184fn allocateSections(macho_file: *MachO) !u32 {
185 var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
186 var vmaddr: u64 = 0;
187 const slice = macho_file.sections.slice();
188
189 for (slice.items(.header)) |*header| {
190 const alignment = try math.powi(u32, 2, header.@"align");
191 vmaddr = mem.alignForward(u64, vmaddr, alignment);
192 header.addr = vmaddr;
193 vmaddr += header.size;
194
195 if (!header.isZerofill()) {
196 fileoff = mem.alignForward(u32, fileoff, alignment);
197 header.offset = fileoff;
198 fileoff += @intCast(header.size);
199 }
200 }
201
202 for (slice.items(.header)) |*header| {
203 if (header.nreloc == 0) continue;
204 header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
205 fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
206 }
207
208 return fileoff;
209}
210
211// We need to sort relocations in descending order to be compatible with Apple's linker.
212fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
213 _ = ctx;
214 return lhs.r_address > rhs.r_address;
215}
216
217fn writeAtoms(macho_file: *MachO) !void {
218 const tracy = trace(@src());
219 defer tracy.end();
220
221 const gpa = macho_file.base.allocator;
222 const cpu_arch = macho_file.options.cpu_arch.?;
223 const slice = macho_file.sections.slice();
224
225 for (slice.items(.header), slice.items(.atoms)) |header, atoms| {
226 if (atoms.items.len == 0) continue;
227 if (header.isZerofill()) continue;
228
229 const code = try gpa.alloc(u8, header.size);
230 defer gpa.free(code);
231 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
232 @memset(code, padding_byte);
233
234 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
235 defer relocs.deinit();
236
237 for (atoms.items) |atom_index| {
238 const atom = macho_file.getAtom(atom_index).?;
239 assert(atom.flags.alive);
240 const off = atom.value - header.addr;
241 @memcpy(code[off..][0..atom.size], atom.getCode(macho_file));
242 try atom.writeRelocs(macho_file, code[off..][0..atom.size], &relocs);
243 }
244
245 assert(relocs.items.len == header.nreloc);
246
247 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
248
249 // TODO scattered writes?
250 try macho_file.base.file.pwriteAll(code, header.offset);
251 try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
252 }
253}
254
255fn writeCompactUnwind(macho_file: *MachO) !void {
256 const sect_index = macho_file.unwind_info_sect_index orelse return;
257 const gpa = macho_file.base.allocator;
258 const header = macho_file.sections.items(.header)[sect_index];
259
260 const nrecs = @divExact(header.size, @sizeOf(macho.compact_unwind_entry));
261 var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs);
262 defer entries.deinit();
263
264 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
265 defer relocs.deinit();
266
267 const addReloc = struct {
268 fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info {
269 return .{
270 .r_address = offset,
271 .r_symbolnum = 0,
272 .r_pcrel = 0,
273 .r_length = 3,
274 .r_extern = 0,
275 .r_type = switch (cpu_arch) {
276 .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
277 .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
278 else => unreachable,
279 },
280 };
281 }
282 }.addReloc;
283
284 var offset: i32 = 0;
285 for (macho_file.objects.items) |index| {
286 const object = macho_file.getFile(index).?.object;
287 for (object.unwind_records.items) |irec| {
288 const rec = macho_file.getUnwindRecord(irec);
289 if (!rec.alive) continue;
290
291 var out: macho.compact_unwind_entry = .{
292 .rangeStart = 0,
293 .rangeLength = rec.length,
294 .compactUnwindEncoding = rec.enc.enc,
295 .personalityFunction = 0,
296 .lsda = 0,
297 };
298
299 {
300 // Function address
301 const atom = rec.getAtom(macho_file);
302 const addr = rec.getAtomAddress(macho_file);
303 out.rangeStart = addr;
304 var reloc = addReloc(offset, macho_file.options.cpu_arch.?);
305 reloc.r_symbolnum = atom.out_n_sect + 1;
306 relocs.appendAssumeCapacity(reloc);
307 }
308
309 // Personality function
310 if (rec.getPersonality(macho_file)) |sym| {
311 const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
312 var reloc = addReloc(offset + 16, macho_file.options.cpu_arch.?);
313 reloc.r_symbolnum = r_symbolnum;
314 reloc.r_extern = 1;
315 relocs.appendAssumeCapacity(reloc);
316 }
317
318 // LSDA address
319 if (rec.getLsdaAtom(macho_file)) |atom| {
320 const addr = rec.getLsdaAddress(macho_file);
321 out.lsda = addr;
322 var reloc = addReloc(offset + 24, macho_file.options.cpu_arch.?);
323 reloc.r_symbolnum = atom.out_n_sect + 1;
324 relocs.appendAssumeCapacity(reloc);
325 }
326
327 entries.appendAssumeCapacity(out);
328 offset += @sizeOf(macho.compact_unwind_entry);
329 }
330 }
331
332 assert(entries.items.len == nrecs);
333 assert(relocs.items.len == header.nreloc);
334
335 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
336
337 // TODO scattered writes?
338 try macho_file.base.file.pwriteAll(mem.sliceAsBytes(entries.items), header.offset);
339 try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
340}
341
342fn writeEhFrame(macho_file: *MachO) !void {
343 const sect_index = macho_file.eh_frame_sect_index orelse return;
344 const gpa = macho_file.base.allocator;
345 const header = macho_file.sections.items(.header)[sect_index];
346
347 const code = try gpa.alloc(u8, header.size);
348 defer gpa.free(code);
349
350 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
351 defer relocs.deinit();
352
353 try eh_frame.writeRelocs(macho_file, code, &relocs);
354 assert(relocs.items.len == header.nreloc);
355
356 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
357
358 // TODO scattered writes?
359 try macho_file.base.file.pwriteAll(code, header.offset);
360 try macho_file.base.file.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
361}
362
363fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {
364 const gpa = macho_file.base.allocator;
365 const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file);
366 const buffer = try gpa.alloc(u8, needed_size);
367 defer gpa.free(buffer);
368
369 var stream = std.io.fixedBufferStream(buffer);
370 var cwriter = std.io.countingWriter(stream.writer());
371 const writer = cwriter.writer();
372
373 var ncmds: usize = 0;
374
375 // Segment and section load commands
376 {
377 assert(macho_file.segments.items.len == 1);
378 const seg = macho_file.segments.items[0];
379 try writer.writeStruct(seg);
380 for (macho_file.sections.items(.header)) |header| {
381 try writer.writeStruct(header);
382 }
383 ncmds += 1;
384 }
385
386 try writer.writeStruct(macho_file.data_in_code_cmd);
387 ncmds += 1;
388 try writer.writeStruct(macho_file.symtab_cmd);
389 ncmds += 1;
390 try writer.writeStruct(macho_file.dysymtab_cmd);
391 ncmds += 1;
392
393 if (macho_file.options.platform) |platform| {
394 if (platform.isBuildVersionCompatible()) {
395 try load_commands.writeBuildVersionLC(platform, macho_file.options.sdk_version, writer);
396 ncmds += 1;
397 } else {
398 try load_commands.writeVersionMinLC(platform, macho_file.options.sdk_version, writer);
399 ncmds += 1;
400 }
401 }
402
403 assert(cwriter.bytes_written == needed_size);
404
405 try macho_file.base.file.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
406
407 return .{ ncmds, buffer.len };
408}
409
410fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
411 var header: macho.mach_header_64 = .{};
412 header.filetype = macho.MH_OBJECT;
413
414 const subsections_via_symbols = for (macho_file.objects.items) |index| {
415 const object = macho_file.getFile(index).?.object;
416 if (object.hasSubsections()) break true;
417 } else false;
418 if (subsections_via_symbols) {
419 header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS;
420 }
421
422 switch (macho_file.options.cpu_arch.?) {
423 .aarch64 => {
424 header.cputype = macho.CPU_TYPE_ARM64;
425 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
426 },
427 .x86_64 => {
428 header.cputype = macho.CPU_TYPE_X86_64;
429 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
430 },
431 else => {},
432 }
433
434 header.ncmds = @intCast(ncmds);
435 header.sizeofcmds = @intCast(sizeofcmds);
436
437 try macho_file.base.file.pwriteAll(mem.asBytes(&header), 0);
438}
439
440const assert = std.debug.assert;
441const eh_frame = @import("eh_frame.zig");
442const load_commands = @import("load_commands.zig");
443const macho = std.macho;
444const math = std.math;
445const mem = std.mem;
446const state_log = std.log.scoped(.state);
447const std = @import("std");
448const trace = @import("../tracy.zig").trace;
449
450const Atom = @import("Atom.zig");
451const MachO = @import("../MachO.zig");
452const Symbol = @import("Symbol.zig");
src/link/MachO/stubs.zig deleted-169
...@@ -1,169 +0,0 @@
1pub inline fn stubHelperPreambleSize(cpu_arch: std.Target.Cpu.Arch) u8 {
2 return switch (cpu_arch) {
3 .x86_64 => 15,
4 .aarch64 => 6 * @sizeOf(u32),
5 else => unreachable, // unhandled architecture type
6 };
7}
8
9pub inline fn stubHelperSize(cpu_arch: std.Target.Cpu.Arch) u8 {
10 return switch (cpu_arch) {
11 .x86_64 => 10,
12 .aarch64 => 3 * @sizeOf(u32),
13 else => unreachable, // unhandled architecture type
14 };
15}
16
17pub inline fn stubSize(cpu_arch: std.Target.Cpu.Arch) u8 {
18 return switch (cpu_arch) {
19 .x86_64 => 6,
20 .aarch64 => 3 * @sizeOf(u32),
21 else => unreachable, // unhandled architecture type
22 };
23}
24
25pub inline fn stubAlignment(cpu_arch: std.Target.Cpu.Arch) u8 {
26 return switch (cpu_arch) {
27 .x86_64 => 1,
28 .aarch64 => 4,
29 else => unreachable, // unhandled architecture type
30 };
31}
32
33pub inline fn stubOffsetInStubHelper(cpu_arch: std.Target.Cpu.Arch) u8 {
34 return switch (cpu_arch) {
35 .x86_64 => 1,
36 .aarch64 => 2 * @sizeOf(u32),
37 else => unreachable,
38 };
39}
40
41pub fn writeStubHelperPreambleCode(args: struct {
42 cpu_arch: std.Target.Cpu.Arch,
43 source_addr: u64,
44 dyld_private_addr: u64,
45 dyld_stub_binder_got_addr: u64,
46}, writer: anytype) !void {
47 switch (args.cpu_arch) {
48 .x86_64 => {
49 try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
50 {
51 const disp = try Relocation.calcPcRelativeDisplacementX86(
52 args.source_addr + 3,
53 args.dyld_private_addr,
54 0,
55 );
56 try writer.writeInt(i32, disp, .little);
57 }
58 try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
59 {
60 const disp = try Relocation.calcPcRelativeDisplacementX86(
61 args.source_addr + 11,
62 args.dyld_stub_binder_got_addr,
63 0,
64 );
65 try writer.writeInt(i32, disp, .little);
66 }
67 },
68 .aarch64 => {
69 {
70 const pages = Relocation.calcNumberOfPages(args.source_addr, args.dyld_private_addr);
71 try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
72 }
73 {
74 const off = try Relocation.calcPageOffset(args.dyld_private_addr, .arithmetic);
75 try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
76 }
77 try writer.writeInt(u32, aarch64.Instruction.stp(
78 .x16,
79 .x17,
80 aarch64.Register.sp,
81 aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
82 ).toU32(), .little);
83 {
84 const pages = Relocation.calcNumberOfPages(args.source_addr + 12, args.dyld_stub_binder_got_addr);
85 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
86 }
87 {
88 const off = try Relocation.calcPageOffset(args.dyld_stub_binder_got_addr, .load_store_64);
89 try writer.writeInt(u32, aarch64.Instruction.ldr(
90 .x16,
91 .x16,
92 aarch64.Instruction.LoadStoreOffset.imm(off),
93 ).toU32(), .little);
94 }
95 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
96 },
97 else => unreachable,
98 }
99}
100
101pub fn writeStubHelperCode(args: struct {
102 cpu_arch: std.Target.Cpu.Arch,
103 source_addr: u64,
104 target_addr: u64,
105}, writer: anytype) !void {
106 switch (args.cpu_arch) {
107 .x86_64 => {
108 try writer.writeAll(&.{ 0x68, 0x0, 0x0, 0x0, 0x0, 0xe9 });
109 {
110 const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 6, args.target_addr, 0);
111 try writer.writeInt(i32, disp, .little);
112 }
113 },
114 .aarch64 => {
115 const stub_size: u4 = 3 * @sizeOf(u32);
116 const literal = blk: {
117 const div_res = try std.math.divExact(u64, stub_size - @sizeOf(u32), 4);
118 break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
119 };
120 try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
121 .w16,
122 literal,
123 ).toU32(), .little);
124 {
125 const disp = try Relocation.calcPcRelativeDisplacementArm64(args.source_addr + 4, args.target_addr);
126 try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
127 }
128 try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
129 },
130 else => unreachable,
131 }
132}
133
134pub fn writeStubCode(args: struct {
135 cpu_arch: std.Target.Cpu.Arch,
136 source_addr: u64,
137 target_addr: u64,
138}, writer: anytype) !void {
139 switch (args.cpu_arch) {
140 .x86_64 => {
141 try writer.writeAll(&.{ 0xff, 0x25 });
142 {
143 const disp = try Relocation.calcPcRelativeDisplacementX86(args.source_addr + 2, args.target_addr, 0);
144 try writer.writeInt(i32, disp, .little);
145 }
146 },
147 .aarch64 => {
148 {
149 const pages = Relocation.calcNumberOfPages(args.source_addr, args.target_addr);
150 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
151 }
152 {
153 const off = try Relocation.calcPageOffset(args.target_addr, .load_store_64);
154 try writer.writeInt(u32, aarch64.Instruction.ldr(
155 .x16,
156 .x16,
157 aarch64.Instruction.LoadStoreOffset.imm(off),
158 ).toU32(), .little);
159 }
160 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
161 },
162 else => unreachable,
163 }
164}
165
166const std = @import("std");
167const aarch64 = @import("../../arch/aarch64/bits.zig");
168
169const Relocation = @import("Relocation.zig");
src/link/MachO/synthetic.zig created+669
...@@ -0,0 +1,669 @@
1pub const GotSection = struct {
2 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
3
4 pub const Index = u32;
5
6 pub fn deinit(got: *GotSection, allocator: Allocator) void {
7 got.symbols.deinit(allocator);
8 }
9
10 pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
11 const gpa = macho_file.base.allocator;
12 const index = @as(Index, @intCast(got.symbols.items.len));
13 const entry = try got.symbols.addOne(gpa);
14 entry.* = sym_index;
15 const symbol = macho_file.getSymbol(sym_index);
16 try symbol.addExtra(.{ .got = index }, macho_file);
17 }
18
19 pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 {
20 assert(index < got.symbols.items.len);
21 const header = macho_file.sections.items(.header)[macho_file.got_sect_index.?];
22 return header.addr + index * @sizeOf(u64);
23 }
24
25 pub fn size(got: GotSection) usize {
26 return got.symbols.items.len * @sizeOf(u64);
27 }
28
29 pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
30 const tracy = trace(@src());
31 defer tracy.end();
32 const gpa = macho_file.base.allocator;
33 const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
34 const seg = macho_file.segments.items[seg_id];
35
36 for (got.symbols.items, 0..) |sym_index, idx| {
37 const sym = macho_file.getSymbol(sym_index);
38 const addr = got.getAddress(@intCast(idx), macho_file);
39 const entry = bind.Entry{
40 .target = sym_index,
41 .offset = addr - seg.vmaddr,
42 .segment_id = seg_id,
43 .addend = 0,
44 };
45 if (sym.flags.import) {
46 try macho_file.bind.entries.append(gpa, entry);
47 if (sym.flags.weak) {
48 try macho_file.weak_bind.entries.append(gpa, entry);
49 }
50 } else {
51 try macho_file.rebase.entries.append(gpa, .{
52 .offset = addr - seg.vmaddr,
53 .segment_id = seg_id,
54 });
55 if (sym.flags.weak) {
56 try macho_file.weak_bind.entries.append(gpa, entry);
57 } else if (sym.flags.interposable) {
58 try macho_file.bind.entries.append(gpa, entry);
59 }
60 }
61 }
62 }
63
64 pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
65 const tracy = trace(@src());
66 defer tracy.end();
67 for (got.symbols.items) |sym_index| {
68 const sym = macho_file.getSymbol(sym_index);
69 const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file);
70 try writer.writeInt(u64, value, .little);
71 }
72 }
73
74 const FormatCtx = struct {
75 got: GotSection,
76 macho_file: *MachO,
77 };
78
79 pub fn fmt(got: GotSection, macho_file: *MachO) std.fmt.Formatter(format2) {
80 return .{ .data = .{ .got = got, .macho_file = macho_file } };
81 }
82
83 pub fn format2(
84 ctx: FormatCtx,
85 comptime unused_fmt_string: []const u8,
86 options: std.fmt.FormatOptions,
87 writer: anytype,
88 ) !void {
89 _ = options;
90 _ = unused_fmt_string;
91 for (ctx.got.symbols.items, 0..) |entry, i| {
92 const symbol = ctx.macho_file.getSymbol(entry);
93 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
94 i,
95 symbol.getGotAddress(ctx.macho_file),
96 entry,
97 symbol.getAddress(.{}, ctx.macho_file),
98 symbol.getName(ctx.macho_file),
99 });
100 }
101 }
102};
103
104pub const StubsSection = struct {
105 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
106
107 pub const Index = u32;
108
109 pub fn deinit(stubs: *StubsSection, allocator: Allocator) void {
110 stubs.symbols.deinit(allocator);
111 }
112
113 pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
114 const gpa = macho_file.base.allocator;
115 const index = @as(Index, @intCast(stubs.symbols.items.len));
116 const entry = try stubs.symbols.addOne(gpa);
117 entry.* = sym_index;
118 const symbol = macho_file.getSymbol(sym_index);
119 try symbol.addExtra(.{ .stubs = index }, macho_file);
120 }
121
122 pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 {
123 assert(index < stubs.symbols.items.len);
124 const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
125 return header.addr + index * header.reserved2;
126 }
127
128 pub fn size(stubs: StubsSection, macho_file: *MachO) usize {
129 const header = macho_file.sections.items(.header)[macho_file.stubs_sect_index.?];
130 return stubs.symbols.items.len * header.reserved2;
131 }
132
133 pub fn write(stubs: StubsSection, macho_file: *MachO, writer: anytype) !void {
134 const tracy = trace(@src());
135 defer tracy.end();
136 const cpu_arch = macho_file.options.cpu_arch.?;
137 const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
138
139 for (stubs.symbols.items, 0..) |sym_index, idx| {
140 const sym = macho_file.getSymbol(sym_index);
141 const source = sym.getAddress(.{ .stubs = true }, macho_file);
142 const target = laptr_sect.addr + idx * @sizeOf(u64);
143 switch (cpu_arch) {
144 .x86_64 => {
145 try writer.writeAll(&.{ 0xff, 0x25 });
146 try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
147 },
148 .aarch64 => {
149 // TODO relax if possible
150 const pages = try Relocation.calcNumberOfPages(source, target);
151 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
152 const off = try Relocation.calcPageOffset(target, .load_store_64);
153 try writer.writeInt(
154 u32,
155 aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
156 .little,
157 );
158 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
159 },
160 else => unreachable,
161 }
162 }
163 }
164
165 const FormatCtx = struct {
166 stubs: StubsSection,
167 macho_file: *MachO,
168 };
169
170 pub fn fmt(stubs: StubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
171 return .{ .data = .{ .stubs = stubs, .macho_file = macho_file } };
172 }
173
174 pub fn format2(
175 ctx: FormatCtx,
176 comptime unused_fmt_string: []const u8,
177 options: std.fmt.FormatOptions,
178 writer: anytype,
179 ) !void {
180 _ = options;
181 _ = unused_fmt_string;
182 for (ctx.stubs.symbols.items, 0..) |entry, i| {
183 const symbol = ctx.macho_file.getSymbol(entry);
184 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
185 i,
186 symbol.getStubsAddress(ctx.macho_file),
187 entry,
188 symbol.getAddress(.{}, ctx.macho_file),
189 symbol.getName(ctx.macho_file),
190 });
191 }
192 }
193};
194
195pub const StubsHelperSection = struct {
196 pub inline fn preambleSize(cpu_arch: std.Target.Cpu.Arch) usize {
197 return switch (cpu_arch) {
198 .x86_64 => 15,
199 .aarch64 => 6 * @sizeOf(u32),
200 else => 0,
201 };
202 }
203
204 pub inline fn entrySize(cpu_arch: std.Target.Cpu.Arch) usize {
205 return switch (cpu_arch) {
206 .x86_64 => 10,
207 .aarch64 => 3 * @sizeOf(u32),
208 else => 0,
209 };
210 }
211
212 pub fn size(stubs_helper: StubsHelperSection, macho_file: *MachO) usize {
213 const tracy = trace(@src());
214 defer tracy.end();
215 _ = stubs_helper;
216 const cpu_arch = macho_file.options.cpu_arch.?;
217 var s: usize = preambleSize(cpu_arch);
218 for (macho_file.stubs.symbols.items) |sym_index| {
219 const sym = macho_file.getSymbol(sym_index);
220 if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) {
221 s += entrySize(cpu_arch);
222 }
223 }
224 return s;
225 }
226
227 pub fn write(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
228 const tracy = trace(@src());
229 defer tracy.end();
230
231 try stubs_helper.writePreamble(macho_file, writer);
232
233 const cpu_arch = macho_file.options.cpu_arch.?;
234 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
235 const preamble_size = preambleSize(cpu_arch);
236 const entry_size = entrySize(cpu_arch);
237
238 var idx: usize = 0;
239 for (macho_file.stubs.symbols.items) |sym_index| {
240 const sym = macho_file.getSymbol(sym_index);
241 if ((sym.flags.import and !sym.flags.weak) or (!sym.flags.weak and sym.flags.interposable)) {
242 const offset = macho_file.lazy_bind.offsets.items[idx];
243 const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx);
244 const target: i64 = @intCast(sect.addr);
245 switch (cpu_arch) {
246 .x86_64 => {
247 try writer.writeByte(0x68);
248 try writer.writeInt(u32, offset, .little);
249 try writer.writeByte(0xe9);
250 try writer.writeInt(i32, @intCast(target - source - 6 - 4), .little);
251 },
252 .aarch64 => {
253 const literal = blk: {
254 const div_res = try std.math.divExact(u64, entry_size - @sizeOf(u32), 4);
255 break :blk std.math.cast(u18, div_res) orelse return error.Overflow;
256 };
257 try writer.writeInt(u32, aarch64.Instruction.ldrLiteral(
258 .w16,
259 literal,
260 ).toU32(), .little);
261 const disp = math.cast(i28, @as(i64, @intCast(target)) - @as(i64, @intCast(source + 4))) orelse
262 return error.Overflow;
263 try writer.writeInt(u32, aarch64.Instruction.b(disp).toU32(), .little);
264 try writer.writeAll(&.{ 0x0, 0x0, 0x0, 0x0 });
265 },
266 else => unreachable,
267 }
268 idx += 1;
269 }
270 }
271 }
272
273 fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
274 _ = stubs_helper;
275 const cpu_arch = macho_file.options.cpu_arch.?;
276 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
277 const dyld_private_addr = target: {
278 const sym = macho_file.getSymbol(macho_file.dyld_private_index.?);
279 break :target sym.getAddress(.{}, macho_file);
280 };
281 const dyld_stub_binder_addr = target: {
282 const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?);
283 break :target sym.getGotAddress(macho_file);
284 };
285 switch (cpu_arch) {
286 .x86_64 => {
287 try writer.writeAll(&.{ 0x4c, 0x8d, 0x1d });
288 try writer.writeInt(i32, @intCast(dyld_private_addr - sect.addr - 3 - 4), .little);
289 try writer.writeAll(&.{ 0x41, 0x53, 0xff, 0x25 });
290 try writer.writeInt(i32, @intCast(dyld_stub_binder_addr - sect.addr - 11 - 4), .little);
291 },
292 .aarch64 => {
293 {
294 // TODO relax if possible
295 const pages = try Relocation.calcNumberOfPages(sect.addr, dyld_private_addr);
296 try writer.writeInt(u32, aarch64.Instruction.adrp(.x17, pages).toU32(), .little);
297 const off = try Relocation.calcPageOffset(dyld_private_addr, .arithmetic);
298 try writer.writeInt(u32, aarch64.Instruction.add(.x17, .x17, off, false).toU32(), .little);
299 }
300 try writer.writeInt(u32, aarch64.Instruction.stp(
301 .x16,
302 .x17,
303 aarch64.Register.sp,
304 aarch64.Instruction.LoadStorePairOffset.pre_index(-16),
305 ).toU32(), .little);
306 {
307 // TODO relax if possible
308 const pages = try Relocation.calcNumberOfPages(sect.addr + 12, dyld_stub_binder_addr);
309 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
310 const off = try Relocation.calcPageOffset(dyld_stub_binder_addr, .load_store_64);
311 try writer.writeInt(u32, aarch64.Instruction.ldr(
312 .x16,
313 .x16,
314 aarch64.Instruction.LoadStoreOffset.imm(off),
315 ).toU32(), .little);
316 }
317 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
318 },
319 else => unreachable,
320 }
321 }
322};
323
324pub const LaSymbolPtrSection = struct {
325 pub fn size(laptr: LaSymbolPtrSection, macho_file: *MachO) usize {
326 _ = laptr;
327 return macho_file.stubs.symbols.items.len * @sizeOf(u64);
328 }
329
330 pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
331 const tracy = trace(@src());
332 defer tracy.end();
333 _ = laptr;
334 const gpa = macho_file.base.allocator;
335
336 const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
337 const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
338 const seg = macho_file.segments.items[seg_id];
339
340 for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
341 const sym = macho_file.getSymbol(sym_index);
342 const addr = sect.addr + idx * @sizeOf(u64);
343 const entry = bind.Entry{
344 .target = sym_index,
345 .offset = addr - seg.vmaddr,
346 .segment_id = seg_id,
347 .addend = 0,
348 };
349 if (sym.flags.import) {
350 if (sym.flags.weak) {
351 try macho_file.bind.entries.append(gpa, entry);
352 try macho_file.weak_bind.entries.append(gpa, entry);
353 } else {
354 try macho_file.lazy_bind.entries.append(gpa, entry);
355 }
356 } else {
357 if (sym.flags.weak) {
358 try macho_file.rebase.entries.append(gpa, .{
359 .offset = addr - seg.vmaddr,
360 .segment_id = seg_id,
361 });
362 try macho_file.weak_bind.entries.append(gpa, entry);
363 } else if (sym.flags.interposable) {
364 try macho_file.lazy_bind.entries.append(gpa, entry);
365 }
366 }
367 }
368 }
369
370 pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
371 const tracy = trace(@src());
372 defer tracy.end();
373 _ = laptr;
374 const cpu_arch = macho_file.options.cpu_arch.?;
375 const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
376 for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
377 const sym = macho_file.getSymbol(sym_index);
378 const value: u64 = if (sym.flags.@"export")
379 sym.getAddress(.{ .stubs = false }, macho_file)
380 else if (sym.flags.weak)
381 @as(u64, 0)
382 else
383 sect.addr + StubsHelperSection.preambleSize(cpu_arch) +
384 StubsHelperSection.entrySize(cpu_arch) * idx;
385 try writer.writeInt(u64, @intCast(value), .little);
386 }
387 }
388};
389
390pub const TlvPtrSection = struct {
391 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
392
393 pub const Index = u32;
394
395 pub fn deinit(tlv: *TlvPtrSection, allocator: Allocator) void {
396 tlv.symbols.deinit(allocator);
397 }
398
399 pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
400 const gpa = macho_file.base.allocator;
401 const index = @as(Index, @intCast(tlv.symbols.items.len));
402 const entry = try tlv.symbols.addOne(gpa);
403 entry.* = sym_index;
404 const symbol = macho_file.getSymbol(sym_index);
405 try symbol.addExtra(.{ .tlv_ptr = index }, macho_file);
406 }
407
408 pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 {
409 assert(index < tlv.symbols.items.len);
410 const header = macho_file.sections.items(.header)[macho_file.tlv_ptr_sect_index.?];
411 return header.addr + index * @sizeOf(u64) * 3;
412 }
413
414 pub fn size(tlv: TlvPtrSection) usize {
415 return tlv.symbols.items.len * @sizeOf(u64);
416 }
417
418 pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
419 const tracy = trace(@src());
420 defer tracy.end();
421 const gpa = macho_file.base.allocator;
422 const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
423 const seg = macho_file.segments.items[seg_id];
424
425 for (tlv.symbols.items, 0..) |sym_index, idx| {
426 const sym = macho_file.getSymbol(sym_index);
427 const addr = tlv.getAddress(@intCast(idx), macho_file);
428 const entry = bind.Entry{
429 .target = sym_index,
430 .offset = addr - seg.vmaddr,
431 .segment_id = seg_id,
432 .addend = 0,
433 };
434 if (sym.flags.import) {
435 try macho_file.bind.entries.append(gpa, entry);
436 if (sym.flags.weak) {
437 try macho_file.weak_bind.entries.append(gpa, entry);
438 }
439 } else {
440 try macho_file.rebase.entries.append(gpa, .{
441 .offset = addr - seg.vmaddr,
442 .segment_id = seg_id,
443 });
444 if (sym.flags.weak) {
445 try macho_file.weak_bind.entries.append(gpa, entry);
446 } else if (sym.flags.interposable) {
447 try macho_file.bind.entries.append(gpa, entry);
448 }
449 }
450 }
451 }
452
453 pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
454 const tracy = trace(@src());
455 defer tracy.end();
456
457 for (tlv.symbols.items) |sym_index| {
458 const sym = macho_file.getSymbol(sym_index);
459 if (sym.flags.import) {
460 try writer.writeInt(u64, 0, .little);
461 } else {
462 try writer.writeInt(u64, sym.getAddress(.{}, macho_file), .little);
463 }
464 }
465 }
466
467 const FormatCtx = struct {
468 tlv: TlvPtrSection,
469 macho_file: *MachO,
470 };
471
472 pub fn fmt(tlv: TlvPtrSection, macho_file: *MachO) std.fmt.Formatter(format2) {
473 return .{ .data = .{ .tlv = tlv, .macho_file = macho_file } };
474 }
475
476 pub fn format2(
477 ctx: FormatCtx,
478 comptime unused_fmt_string: []const u8,
479 options: std.fmt.FormatOptions,
480 writer: anytype,
481 ) !void {
482 _ = options;
483 _ = unused_fmt_string;
484 for (ctx.tlv.symbols.items, 0..) |entry, i| {
485 const symbol = ctx.macho_file.getSymbol(entry);
486 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
487 i,
488 symbol.getTlvPtrAddress(ctx.macho_file),
489 entry,
490 symbol.getAddress(.{}, ctx.macho_file),
491 symbol.getName(ctx.macho_file),
492 });
493 }
494 }
495};
496
497pub const ObjcStubsSection = struct {
498 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
499
500 pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void {
501 objc.symbols.deinit(allocator);
502 }
503
504 pub fn entrySize(cpu_arch: std.Target.Cpu.Arch) u8 {
505 return switch (cpu_arch) {
506 .x86_64 => 13,
507 .aarch64 => 8 * @sizeOf(u32),
508 else => unreachable,
509 };
510 }
511
512 pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void {
513 const gpa = macho_file.base.allocator;
514 const index = @as(Index, @intCast(objc.symbols.items.len));
515 const entry = try objc.symbols.addOne(gpa);
516 entry.* = sym_index;
517 const symbol = macho_file.getSymbol(sym_index);
518 try symbol.addExtra(.{ .objc_stubs = index }, macho_file);
519 }
520
521 pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 {
522 assert(index < objc.symbols.items.len);
523 const header = macho_file.sections.items(.header)[macho_file.objc_stubs_sect_index.?];
524 return header.addr + index * entrySize(macho_file.options.cpu_arch.?);
525 }
526
527 pub fn size(objc: ObjcStubsSection, macho_file: *MachO) usize {
528 return objc.symbols.items.len * entrySize(macho_file.options.cpu_arch.?);
529 }
530
531 pub fn write(objc: ObjcStubsSection, macho_file: *MachO, writer: anytype) !void {
532 const tracy = trace(@src());
533 defer tracy.end();
534
535 for (objc.symbols.items, 0..) |sym_index, idx| {
536 const sym = macho_file.getSymbol(sym_index);
537 const addr = objc.getAddress(@intCast(idx), macho_file);
538 switch (macho_file.options.cpu_arch.?) {
539 .x86_64 => {
540 try writer.writeAll(&.{ 0x48, 0x8b, 0x35 });
541 {
542 const target = sym.getObjcSelrefsAddress(macho_file);
543 const source = addr;
544 try writer.writeInt(i32, @intCast(target - source - 3 - 4), .little);
545 }
546 try writer.writeAll(&.{ 0xff, 0x25 });
547 {
548 const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
549 const target = target_sym.getGotAddress(macho_file);
550 const source = addr + 7;
551 try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
552 }
553 },
554 .aarch64 => {
555 {
556 const target = sym.getObjcSelrefsAddress(macho_file);
557 const source = addr;
558 const pages = try Relocation.calcNumberOfPages(source, target);
559 try writer.writeInt(u32, aarch64.Instruction.adrp(.x1, pages).toU32(), .little);
560 const off = try Relocation.calcPageOffset(target, .load_store_64);
561 try writer.writeInt(
562 u32,
563 aarch64.Instruction.ldr(.x1, .x1, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
564 .little,
565 );
566 }
567 {
568 const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?);
569 const target = target_sym.getGotAddress(macho_file);
570 const source = addr + 2 * @sizeOf(u32);
571 const pages = try Relocation.calcNumberOfPages(source, target);
572 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
573 const off = try Relocation.calcPageOffset(target, .load_store_64);
574 try writer.writeInt(
575 u32,
576 aarch64.Instruction.ldr(.x16, .x16, aarch64.Instruction.LoadStoreOffset.imm(off)).toU32(),
577 .little,
578 );
579 }
580 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
581 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
582 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
583 try writer.writeInt(u32, aarch64.Instruction.brk(1).toU32(), .little);
584 },
585 else => unreachable,
586 }
587 }
588 }
589
590 const FormatCtx = struct {
591 objc: ObjcStubsSection,
592 macho_file: *MachO,
593 };
594
595 pub fn fmt(objc: ObjcStubsSection, macho_file: *MachO) std.fmt.Formatter(format2) {
596 return .{ .data = .{ .objc = objc, .macho_file = macho_file } };
597 }
598
599 pub fn format2(
600 ctx: FormatCtx,
601 comptime unused_fmt_string: []const u8,
602 options: std.fmt.FormatOptions,
603 writer: anytype,
604 ) !void {
605 _ = options;
606 _ = unused_fmt_string;
607 for (ctx.objc.symbols.items, 0..) |entry, i| {
608 const symbol = ctx.macho_file.getSymbol(entry);
609 try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
610 i,
611 symbol.getObjcStubsAddress(ctx.macho_file),
612 entry,
613 symbol.getAddress(.{}, ctx.macho_file),
614 symbol.getName(ctx.macho_file),
615 });
616 }
617 }
618
619 pub const Index = u32;
620};
621
622pub const Indsymtab = struct {
623 pub inline fn nsyms(ind: Indsymtab, macho_file: *MachO) u32 {
624 _ = ind;
625 return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len);
626 }
627
628 pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void {
629 const tracy = trace(@src());
630 defer tracy.end();
631
632 _ = ind;
633
634 for (macho_file.stubs.symbols.items) |sym_index| {
635 const sym = macho_file.getSymbol(sym_index);
636 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
637 }
638
639 for (macho_file.got.symbols.items) |sym_index| {
640 const sym = macho_file.getSymbol(sym_index);
641 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
642 }
643
644 for (macho_file.stubs.symbols.items) |sym_index| {
645 const sym = macho_file.getSymbol(sym_index);
646 try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
647 }
648 }
649};
650
651pub const RebaseSection = Rebase;
652pub const BindSection = bind.Bind;
653pub const WeakBindSection = bind.WeakBind;
654pub const LazyBindSection = bind.LazyBind;
655pub const ExportTrieSection = Trie;
656
657const aarch64 = @import("../aarch64.zig");
658const assert = std.debug.assert;
659const bind = @import("dyld_info/bind.zig");
660const math = std.math;
661const std = @import("std");
662const trace = @import("../tracy.zig").trace;
663
664const Allocator = std.mem.Allocator;
665const MachO = @import("../MachO.zig");
666const Rebase = @import("dyld_info/Rebase.zig");
667const Relocation = @import("Relocation.zig");
668const Symbol = @import("Symbol.zig");
669const Trie = @import("dyld_info/Trie.zig");
src/link/MachO/thunks.zig+137-335
...@@ -1,374 +1,176 @@...@@ -1,374 +1,176 @@
1//! An algorithm for allocating output machine code section (aka `__TEXT,__text`),1pub fn createThunks(sect_id: u8, macho_file: *MachO) !void {
2//! and insertion of range extending thunks. As such, this algorithm is only run2 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 in5 const gpa = macho_file.base.allocator;
6//! another output section is out of range.6 const slice = macho_file.sections.slice();
77 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;
9const jump_bits = @bitSizeOf(i28);9 assert(atoms.len > 0);
1010
11const max_distance = (1 << (jump_bits - 1));11 for (atoms) |atom_index| {
1212 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.
17const max_allowed_distance = max_distance - 0x500_000;
18
19pub const Thunk = struct {
20 start_index: Atom.Index,
21 len: u32,
22
23 targets: std.MultiArrayList(Target) = .{},
24 lookup: std.AutoHashMapUnmanaged(Target, u32) = .{},
25
26 pub const Tag = enum {
27 stub,
28 atom,
29 };
30
31 pub const Target = struct {
32 tag: Tag,
33 target: SymbolWithLoc,
34 };
35
36 pub const Index = u32;
37
38 pub fn deinit(self: *Thunk, gpa: Allocator) void {
39 self.targets.deinit(gpa);
40 self.lookup.deinit(gpa);
41 }
42
43 pub fn getStartAtomIndex(self: Thunk) Atom.Index {
44 assert(self.len != 0);
45 return self.start_index;
46 }
47
48 pub fn getEndAtomIndex(self: Thunk) Atom.Index {
49 assert(self.len != 0);
50 return self.start_index + self.len - 1;
51 }
52
53 pub fn getSize(self: Thunk) u64 {
54 return 12 * self.len;
55 }13 }
5614
57 pub fn getAlignment() u32 {15 var i: usize = 0;
58 return @alignOf(u32);16 while (i < atoms.len) {
59 }17 const start = i;
6018 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
6624 header.size - start_atom.value < max_allowed_distance) : (i += 1)
67pub 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).?;
7028 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 }30 }
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 }
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
133 // Scan relocs in the group and create trampolines for any unreachable callsite.
134 var atom_index = group_start;
135 while (true) {
136 const atom = macho_file.getAtom(atom_index);
137 try scanRelocs(
138 macho_file,
139 atom_index,
140 allocated,
141 thunk_index,
142 group_end,
143 );
144
145 if (atom_index == group_end) break;
14631
147 if (atom.next_index) |next_index| {32 // Insert a thunk at the group end
148 atom_index = next_index;33 const thunk_index = try macho_file.addThunk();
149 } else break;34 const thunk = macho_file.getThunk(thunk_index);
35 thunk.out_n_sect = sect_id;
36
37 // Scan relocs in the group and create trampolines for any unreachable callsite
38 for (atoms[start..i]) |atom_index| {
39 const atom = macho_file.getAtom(atom_index).?;
40 log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) });
41 for (atom.getRelocs(macho_file)) |rel| {
42 if (rel.type != .branch) continue;
43 if (isReachable(atom, rel, macho_file)) continue;
44 try thunk.symbols.put(gpa, rel.target, {});
45 }
46 atom.thunk_index = thunk_index;
150 }47 }
15148
152 offset = mem.alignForward(u64, offset, Thunk.getAlignment());49 thunk.value = try advance(header, thunk.size(), 2);
153 allocateThunk(macho_file, thunk_index, offset, header);
154 offset += macho_file.thunks.items[thunk_index].getSize();
15550
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}
17554
176fn allocateThunk(55fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 {
177 macho_file: *MachO,56 const alignment = try math.powi(u32, 2, pow2_align);
178 thunk_index: Thunk.Index,57 const offset = mem.alignForward(u64, sect.size, alignment);
179 base_offset: u64,58 const padding = offset - sect.size;
180 header: *macho.section_64,59 sect.size += padding + size;
181) void {60 sect.@"align" = @max(sect.@"align", pow2_align);
182 const thunk = macho_file.thunks.items[thunk_index];61 return offset;
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}62}
20963
210fn scanRelocs(64fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
211 macho_file: *MachO,65 const target = rel.getTargetSymbol(macho_file);
212 atom_index: Atom.Index,66 if (target.flags.stubs or target.flags.objc_stubs) return false;
213 allocated: std.AutoHashMap(Atom.Index, void),67 if (atom.out_n_sect != target.out_n_sect) return false;
214 thunk_index: Thunk.Index,68 const target_atom = target.getAtom(macho_file).?;
215 group_end: Atom.Index,69 if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
216) !void {70 const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off));
217 const atom = macho_file.getAtom(atom_index);71 const taddr: i64 = @intCast(rel.getTargetAddress(macho_file));
218 const object = macho_file.objects.items[atom.getFile().?];72 _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
21973 return true;
220 const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: {74}
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);
22875
229 for (relocs) |rel| {76pub const Thunk = struct {
230 if (!relocNeedsThunk(rel)) continue;77 value: u64 = 0,
78 out_n_sect: u8 = 0,
79 symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{},
23180
232 const target = Atom.parseRelocTarget(macho_file, .{81 pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
233 .object_id = atom.getFile().?,82 thunk.symbols.deinit(allocator);
234 .rel = rel,83 }
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;
24084
241 log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{85 pub fn size(thunk: Thunk) usize {
242 rel.r_address - base_offset,86 return thunk.symbols.keys().len * trampoline_size;
243 macho_file.getSymbolName(atom.getSymbolWithLoc()),87 }
244 macho_file.getSymbol(atom.getSymbolWithLoc()).n_value,
245 macho_file.getSymbolName(target),
246 macho_file.getSymbol(target).n_value,
247 });
24888
249 const comp = macho_file.base.comp;89 pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 {
250 const gpa = comp.gpa;90 return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size;
251 const target_sym = macho_file.getSymbol(target);91 }
252 const thunk = &macho_file.thunks.items[thunk_index];
25392
254 const tag: Thunk.Tag = if (target_sym.undf()) .stub else .atom;93 pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void {
255 const thunk_target: Thunk.Target = .{ .tag = tag, .target = target };94 for (thunk.symbols.keys(), 0..) |sym_index, i| {
256 const gop = try thunk.lookup.getOrPut(gpa, thunk_target);95 const sym = macho_file.getSymbol(sym_index);
257 if (!gop.found_existing) {96 const saddr = thunk.value + i * trampoline_size;
258 gop.value_ptr.* = try pushThunkAtom(macho_file, thunk, group_end);97 const taddr = sym.getAddress(.{}, macho_file);
259 try thunk.targets.append(gpa, thunk_target);98 const pages = try Relocation.calcNumberOfPages(saddr, taddr);
99 try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little);
100 const off = try Relocation.calcPageOffset(taddr, .arithmetic);
101 try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little);
102 try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little);
260 }103 }
261
262 try macho_file.thunk_table.put(gpa, atom_index, thunk_index);
263 }104 }
264}
265105
266fn pushThunkAtom(macho_file: *MachO, thunk: *Thunk, group_end: Atom.Index) !Atom.Index {106 pub fn format(
267 const thunk_atom_index = try createThunkAtom(macho_file);107 thunk: Thunk,
268108 comptime unused_fmt_string: []const u8,
269 const thunk_atom = macho_file.getAtomPtr(thunk_atom_index);109 options: std.fmt.FormatOptions,
270 const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex();110 writer: anytype,
271 const end_atom = macho_file.getAtomPtr(end_atom_index);111 ) !void {
272112 _ = thunk;
273 if (end_atom.next_index) |first_after_index| {113 _ = unused_fmt_string;
274 const first_after_atom = macho_file.getAtomPtr(first_after_index);114 _ = options;
275 first_after_atom.prev_index = thunk_atom_index;115 _ = writer;
276 thunk_atom.next_index = first_after_index;116 @compileError("do not format Thunk directly");
277 }117 }
278118
279 end_atom.next_index = thunk_atom_index;119 pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) {
280 thunk_atom.prev_index = end_atom_index;120 return .{ .data = .{
281121 .thunk = thunk,
282 if (thunk.len == 0) {122 .macho_file = macho_file,
283 thunk.start_index = thunk_atom_index;123 } };
284 }124 }
285125
286 thunk.len += 1;126 const FormatContext = struct {
287127 thunk: Thunk,
288 return thunk_atom_index;128 macho_file: *MachO,
289}129 };
290
291inline fn relocNeedsThunk(rel: macho.relocation_info) bool {
292 const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type));
293 return rel_type == .ARM64_RELOC_BRANCH26;
294}
295
296fn isReachable(
297 macho_file: *MachO,
298 atom_index: Atom.Index,
299 rel: macho.relocation_info,
300 base_offset: i32,
301 target: SymbolWithLoc,
302 allocated: std.AutoHashMap(Atom.Index, void),
303) bool {
304 if (macho_file.stub_table.lookup.contains(target)) return false;
305
306 const source_atom = macho_file.getAtom(atom_index);
307 const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc());
308
309 const target_object = macho_file.objects.items[target.getFile().?];
310 const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
311 const target_atom = macho_file.getAtom(target_atom_index);
312 const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc());
313
314 if (source_sym.n_sect != target_sym.n_sect) return false;
315130
316 if (!allocated.contains(target_atom_index)) return false;131 fn format2(
132 ctx: FormatContext,
133 comptime unused_fmt_string: []const u8,
134 options: std.fmt.FormatOptions,
135 writer: anytype,
136 ) !void {
137 _ = options;
138 _ = unused_fmt_string;
139 const thunk = ctx.thunk;
140 const macho_file = ctx.macho_file;
141 try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() });
142 for (thunk.symbols.keys()) |index| {
143 const sym = macho_file.getSymbol(index);
144 try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value });
145 }
146 }
317147
318 const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset));148 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;
325149
326 return true;150 pub const Index = u32;
327}151};
328152
329fn createThunkAtom(macho_file: *MachO) !Atom.Index {153/// Branch instruction has 26 bits immediate but is 4 byte aligned.
330 const sym_index = try macho_file.allocateSymbol();154const jump_bits = @bitSizeOf(i28);
331 const atom_index = try macho_file.createAtom(sym_index, .{155const 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}
340156
341pub fn writeThunkCode(macho_file: *MachO, thunk: *const Thunk, writer: anytype) !void {157/// A branch will need an extender if its target is larger than
342 const slice = thunk.targets.slice();158/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
343 for (thunk.getStartAtomIndex()..thunk.getEndAtomIndex(), 0..) |atom_index, target_index| {159/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
344 const atom = macho_file.getAtom(@intCast(atom_index));160/// and assume margin to be 5MiB.
345 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());161const 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}
360162
361const std = @import("std");163const aarch64 = @import("../aarch64.zig");
362const assert = std.debug.assert;164const assert = std.debug.assert;
363const log = std.log.scoped(.thunks);165const log = std.log.scoped(.link);
364const macho = std.macho;166const macho = std.macho;
365const math = std.math;167const math = std.math;
366const mem = std.mem;168const mem = std.mem;
367169const std = @import("std");
368const aarch64 = @import("../../arch/aarch64/bits.zig");170const trace = @import("../tracy.zig").trace;
369171
370const Allocator = mem.Allocator;172const Allocator = mem.Allocator;
371const Atom = @import("Atom.zig");173const Atom = @import("Atom.zig");
372const MachO = @import("../MachO.zig");174const MachO = @import("../MachO.zig");
373const Relocation = @import("Relocation.zig");175const Relocation = @import("Relocation.zig");
374const SymbolWithLoc = MachO.SymbolWithLoc;176const Symbol = @import("Symbol.zig");
src/link/MachO/uuid.zig+18-8
...@@ -4,22 +4,31 @@...@@ -4,22 +4,31 @@
4/// and we will use it too as it seems accepted by Apple OSes.4/// and we will use it too as it seems accepted by Apple OSes.
5/// TODO LLD also hashes the output filename to disambiguate between same builds with different5/// 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?
7pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void {7pub fn calcUuid(
8 allocator: Allocator,
9 thread_pool: *ThreadPool,
10 file: fs.File,
11 file_size: u64,
12 out: *[Md5.digest_length]u8,
13) !void {
14 const tracy = trace(@src());
15 defer tracy.end();
16
8 const chunk_size: usize = 1024 * 1024;17 const chunk_size: usize = 1024 * 1024;
9 const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;18 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;19 const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks;
1120
12 const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks);21 const hashes = try allocator.alloc([Md5.digest_length]u8, actual_num_chunks);
13 defer comp.gpa.free(hashes);22 defer allocator.free(hashes);
1423
15 var hasher = Hasher(Md5){ .allocator = comp.gpa, .thread_pool = comp.thread_pool };24 var hasher = Hasher(Md5){ .allocator = allocator, .thread_pool = thread_pool };
16 try hasher.hash(file, hashes, .{25 try hasher.hash(file, hashes, .{
17 .chunk_size = chunk_size,26 .chunk_size = chunk_size,
18 .max_file_size = file_size,27 .max_file_size = file_size,
19 });28 });
2029
21 const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length);30 const final_buffer = try allocator.alloc(u8, actual_num_chunks * Md5.digest_length);
22 defer comp.gpa.free(final_buffer);31 defer allocator.free(final_buffer);
2332
24 for (hashes, 0..) |hash, i| {33 for (hashes, 0..) |hash, i| {
25 @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);34 @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);
...@@ -35,11 +44,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {...@@ -35,11 +44,12 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
35 out[8] = (out[8] & 0x3F) | 0x80;44 out[8] = (out[8] & 0x3F) | 0x80;
36}45}
3746
38const std = @import("std");
39const fs = std.fs;47const fs = std.fs;
40const mem = std.mem;48const mem = std.mem;
49const std = @import("std");
50const trace = @import("../tracy.zig").trace;
4151
42const Allocator = mem.Allocator;52const Allocator = mem.Allocator;
43const Compilation = @import("../../Compilation.zig");
44const Md5 = std.crypto.hash.Md5;53const Md5 = std.crypto.hash.Md5;
45const Hasher = @import("hasher.zig").ParallelHasher;54const Hasher = @import("hasher.zig").ParallelHasher;
55const ThreadPool = std.Thread.Pool;
src/link/MachO/zld.zig deleted-1230
...@@ -1,1230 +0,0 @@
1pub fn linkWithZld(
2 macho_file: *MachO,
3 arena: Allocator,
4 prog_node: *std.Progress.Node,
5) link.File.FlushError!void {
6 const tracy = trace(@src());
7 defer tracy.end();
8
9 const comp = macho_file.base.comp;
10 const gpa = comp.gpa;
11 const target = comp.root_mod.resolved_target.result;
12 const emit = macho_file.base.emit;
13
14 const directory = emit.directory; // Just an alias to make it shorter to type.
15 const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path});
16 const opt_zcu = comp.module;
17
18 // If there is no Zig code to compile, then we should skip flushing the output file because it
19 // will not be part of the linker line anyway.
20 const module_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
21 try macho_file.flushModule(arena, prog_node);
22
23 if (fs.path.dirname(full_out_path)) |dirname| {
24 break :blk try fs.path.join(arena, &.{ dirname, macho_file.base.zcu_object_sub_path.? });
25 } else {
26 break :blk macho_file.base.zcu_object_sub_path.?;
27 }
28 } else null;
29
30 var sub_prog_node = prog_node.start("MachO Flush", 0);
31 sub_prog_node.activate();
32 sub_prog_node.context.refresh();
33 defer sub_prog_node.end();
34
35 const output_mode = comp.config.output_mode;
36 const link_mode = comp.config.link_mode;
37 const cpu_arch = target.cpu.arch;
38 const is_lib = output_mode == .Lib;
39 const is_dyn_lib = link_mode == .Dynamic and is_lib;
40 const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
41 const stack_size = macho_file.base.stack_size;
42
43 const id_symlink_basename = "zld.id";
44
45 var man: Cache.Manifest = undefined;
46 defer if (!macho_file.base.disable_lld_caching) man.deinit();
47
48 var digest: [Cache.hex_digest_len]u8 = undefined;
49
50 const objects = comp.objects;
51
52 if (!macho_file.base.disable_lld_caching) {
53 man = comp.cache_parent.obtain();
54
55 // We are about to obtain this lock, so here we give other processes a chance first.
56 macho_file.base.releaseLock();
57
58 comptime assert(Compilation.link_hash_implementation_version == 11);
59
60 for (objects) |obj| {
61 _ = try man.addFile(obj.path, null);
62 man.hash.add(obj.must_link);
63 }
64 for (comp.c_object_table.keys()) |key| {
65 _ = try man.addFile(key.status.success.object_path, null);
66 }
67 try man.addOptionalFile(module_obj_path);
68 // We can skip hashing libc and libc++ components that we are in charge of building from Zig
69 // installation sources because they are always a product of the compiler version + target information.
70 man.hash.add(stack_size);
71 man.hash.add(macho_file.pagezero_vmsize);
72 man.hash.add(macho_file.headerpad_size);
73 man.hash.add(macho_file.headerpad_max_install_names);
74 man.hash.add(macho_file.base.gc_sections);
75 man.hash.add(macho_file.dead_strip_dylibs);
76 man.hash.add(comp.root_mod.strip);
77 try MachO.hashAddFrameworks(&man, macho_file.frameworks);
78 man.hash.addListOfBytes(macho_file.base.rpath_list);
79 if (is_dyn_lib) {
80 man.hash.addOptionalBytes(macho_file.install_name);
81 man.hash.addOptional(comp.version);
82 }
83 try link.hashAddSystemLibs(&man, comp.system_libs);
84 man.hash.addOptionalBytes(comp.sysroot);
85 man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
86 try man.addOptionalFile(macho_file.entitlements);
87
88 // We don't actually care whether it's a cache hit or miss; we just
89 // need the digest and the lock.
90 _ = try man.hit();
91 digest = man.final();
92
93 var prev_digest_buf: [digest.len]u8 = undefined;
94 const prev_digest: []u8 = Cache.readSmallFile(
95 directory.handle,
96 id_symlink_basename,
97 &prev_digest_buf,
98 ) catch |err| blk: {
99 log.debug("MachO Zld new_digest={s} error: {s}", .{
100 std.fmt.fmtSliceHexLower(&digest),
101 @errorName(err),
102 });
103 // Handle this as a cache miss.
104 break :blk prev_digest_buf[0..0];
105 };
106 if (mem.eql(u8, prev_digest, &digest)) {
107 // Hot diggity dog! The output binary is already there.
108 log.debug("MachO Zld digest={s} match - skipping invocation", .{
109 std.fmt.fmtSliceHexLower(&digest),
110 });
111 macho_file.base.lock = man.toOwnedLock();
112 return;
113 }
114 log.debug("MachO Zld prev_digest={s} new_digest={s}", .{
115 std.fmt.fmtSliceHexLower(prev_digest),
116 std.fmt.fmtSliceHexLower(&digest),
117 });
118
119 // We are about to change the output file to be different, so we invalidate the build hash now.
120 directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
121 error.FileNotFound => {},
122 else => |e| return e,
123 };
124 }
125
126 if (output_mode == .Obj) {
127 // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy
128 // here. TODO: think carefully about how we can avoid this redundant operation when doing
129 // build-obj. See also the corresponding TODO in linkAsArchive.
130 const the_object_path = blk: {
131 if (objects.len != 0) {
132 break :blk objects[0].path;
133 }
134
135 if (comp.c_object_table.count() != 0)
136 break :blk comp.c_object_table.keys()[0].status.success.object_path;
137
138 if (module_obj_path) |p|
139 break :blk p;
140
141 // TODO I think this is unreachable. Audit this situation when solving the above TODO
142 // regarding eliding redundant object -> object transformations.
143 return error.NoObjectsToLink;
144 };
145 // This can happen when using --enable-cache and using the stage1 backend. In this case
146 // we can skip the file copy.
147 if (!mem.eql(u8, the_object_path, full_out_path)) {
148 try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
149 }
150 } else {
151 const sub_path = emit.sub_path;
152
153 const old_file = macho_file.base.file; // TODO is this needed at all?
154 defer macho_file.base.file = old_file;
155
156 const file = try directory.handle.createFile(sub_path, .{
157 .truncate = true,
158 .read = true,
159 .mode = link.File.determineMode(false, output_mode, link_mode),
160 });
161 defer file.close();
162 macho_file.base.file = file;
163
164 // Index 0 is always a null symbol.
165 try macho_file.locals.append(gpa, .{
166 .n_strx = 0,
167 .n_type = 0,
168 .n_sect = 0,
169 .n_desc = 0,
170 .n_value = 0,
171 });
172 try macho_file.strtab.buffer.append(gpa, 0);
173
174 // Positional arguments to the linker such as object files and static archives.
175 var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
176 try positionals.ensureUnusedCapacity(objects.len);
177 positionals.appendSliceAssumeCapacity(objects);
178
179 for (comp.c_object_table.keys()) |key| {
180 try positionals.append(.{ .path = key.status.success.object_path });
181 }
182
183 if (module_obj_path) |p| {
184 try positionals.append(.{ .path = p });
185 }
186
187 if (comp.compiler_rt_lib) |lib| try positionals.append(.{ .path = lib.full_object_path });
188 if (comp.compiler_rt_obj) |obj| try positionals.append(.{ .path = obj.full_object_path });
189
190 // libc++ dep
191 if (comp.config.link_libcpp) {
192 try positionals.ensureUnusedCapacity(2);
193 positionals.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
194 positionals.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
195 }
196
197 var libs = std.StringArrayHashMap(link.SystemLib).init(arena);
198
199 {
200 const vals = comp.system_libs.values();
201 try libs.ensureUnusedCapacity(vals.len);
202 for (vals) |v| libs.putAssumeCapacity(v.path.?, v);
203 }
204
205 {
206 try libs.ensureUnusedCapacity(macho_file.frameworks.len);
207 for (macho_file.frameworks) |v| libs.putAssumeCapacity(v.path, .{
208 .needed = v.needed,
209 .weak = v.weak,
210 .path = v.path,
211 });
212 }
213
214 try macho_file.resolveLibSystem(arena, comp, &libs);
215
216 if (comp.verbose_link) {
217 var argv = std.ArrayList([]const u8).init(arena);
218
219 try argv.append("zig");
220 try argv.append("ld");
221
222 if (is_exe_or_dyn_lib) {
223 try argv.append("-dynamic");
224 }
225
226 if (is_dyn_lib) {
227 try argv.append("-dylib");
228
229 if (macho_file.install_name) |install_name| {
230 try argv.append("-install_name");
231 try argv.append(install_name);
232 }
233 }
234
235 {
236 const platform = Platform.fromTarget(target);
237 try argv.append("-platform_version");
238 try argv.append(@tagName(platform.os_tag));
239 try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
240
241 const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
242 if (sdk_version) |ver| {
243 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
244 } else {
245 try argv.append(try std.fmt.allocPrint(arena, "{}", .{platform.version}));
246 }
247 }
248
249 if (comp.sysroot) |syslibroot| {
250 try argv.append("-syslibroot");
251 try argv.append(syslibroot);
252 }
253
254 for (macho_file.base.rpath_list) |rpath| {
255 try argv.append("-rpath");
256 try argv.append(rpath);
257 }
258
259 try argv.appendSlice(&.{
260 "-pagezero_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.pagezero_vmsize}),
261 "-headerpad_size", try std.fmt.allocPrint(arena, "0x{x}", .{macho_file.headerpad_size}),
262 });
263
264 if (macho_file.headerpad_max_install_names) {
265 try argv.append("-headerpad_max_install_names");
266 }
267
268 if (macho_file.base.gc_sections) {
269 try argv.append("-dead_strip");
270 }
271
272 if (macho_file.dead_strip_dylibs) {
273 try argv.append("-dead_strip_dylibs");
274 }
275
276 if (macho_file.entry_name) |entry_name| {
277 try argv.appendSlice(&.{ "-e", entry_name });
278 }
279
280 for (objects) |obj| {
281 if (obj.must_link) {
282 try argv.append("-force_load");
283 }
284 try argv.append(obj.path);
285 }
286
287 for (comp.c_object_table.keys()) |key| {
288 try argv.append(key.status.success.object_path);
289 }
290
291 if (module_obj_path) |p| {
292 try argv.append(p);
293 }
294
295 if (comp.compiler_rt_lib) |lib| try argv.append(lib.full_object_path);
296 if (comp.compiler_rt_obj) |obj| try argv.append(obj.full_object_path);
297
298 if (comp.config.link_libcpp) {
299 try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
300 try argv.append(comp.libcxx_static_lib.?.full_object_path);
301 }
302
303 try argv.append("-o");
304 try argv.append(full_out_path);
305
306 try argv.append("-lSystem");
307
308 for (comp.system_libs.keys()) |l_name| {
309 const info = comp.system_libs.get(l_name).?;
310 const arg = if (info.needed)
311 try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name})
312 else if (info.weak)
313 try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name})
314 else
315 try std.fmt.allocPrint(arena, "-l{s}", .{l_name});
316 try argv.append(arg);
317 }
318
319 for (macho_file.frameworks) |framework| {
320 const name = std.fs.path.stem(framework.path);
321 const arg = if (framework.needed)
322 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
323 else if (framework.weak)
324 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
325 else
326 try std.fmt.allocPrint(arena, "-framework {s}", .{name});
327 try argv.append(arg);
328 }
329
330 if (is_dyn_lib and macho_file.base.allow_shlib_undefined) {
331 try argv.append("-undefined");
332 try argv.append("dynamic_lookup");
333 }
334
335 Compilation.dump_argv(argv.items);
336 }
337
338 var dependent_libs = std.fifo.LinearFifo(MachO.DylibReExportInfo, .Dynamic).init(arena);
339
340 for (positionals.items) |obj| {
341 const in_file = try std.fs.cwd().openFile(obj.path, .{});
342 defer in_file.close();
343
344 var parse_ctx = MachO.ParseErrorCtx.init(gpa);
345 defer parse_ctx.deinit();
346
347 macho_file.parsePositional(
348 in_file,
349 obj.path,
350 obj.must_link,
351 &dependent_libs,
352 &parse_ctx,
353 ) catch |err| try macho_file.handleAndReportParseError(obj.path, err, &parse_ctx);
354 }
355
356 for (libs.keys(), libs.values()) |path, lib| {
357 const in_file = try std.fs.cwd().openFile(path, .{});
358 defer in_file.close();
359
360 var parse_ctx = MachO.ParseErrorCtx.init(gpa);
361 defer parse_ctx.deinit();
362
363 macho_file.parseLibrary(
364 in_file,
365 path,
366 lib,
367 false,
368 false,
369 null,
370 &dependent_libs,
371 &parse_ctx,
372 ) catch |err| try macho_file.handleAndReportParseError(path, err, &parse_ctx);
373 }
374
375 try macho_file.parseDependentLibs(&dependent_libs);
376
377 try macho_file.resolveSymbols();
378 if (macho_file.unresolved.count() > 0) {
379 try macho_file.reportUndefined();
380 return error.FlushFailure;
381 }
382
383 for (macho_file.objects.items, 0..) |*object, object_id| {
384 object.splitIntoAtoms(macho_file, @as(u32, @intCast(object_id))) catch |err| switch (err) {
385 error.MissingEhFrameSection => try macho_file.reportParseError(
386 object.name,
387 "missing section: '__TEXT,__eh_frame' is required but could not be found",
388 .{},
389 ),
390 error.BadDwarfCfi => try macho_file.reportParseError(
391 object.name,
392 "invalid DWARF: failed to parse '__TEXT,__eh_frame' section",
393 .{},
394 ),
395 else => |e| return e,
396 };
397 }
398
399 if (macho_file.base.gc_sections) {
400 try dead_strip.gcAtoms(macho_file);
401 }
402
403 try macho_file.createDyldPrivateAtom();
404 try macho_file.createTentativeDefAtoms();
405
406 if (comp.config.output_mode == .Exe) {
407 const global = macho_file.getEntryPoint().?;
408 if (macho_file.getSymbol(global).undf()) {
409 // We do one additional check here in case the entry point was found in one of the dylibs.
410 // (I actually have no idea what this would imply but it is a possible outcome and so we
411 // support it.)
412 try macho_file.addStubEntry(global);
413 }
414 }
415
416 for (macho_file.objects.items) |object| {
417 for (object.atoms.items) |atom_index| {
418 const atom = macho_file.getAtom(atom_index);
419 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
420 const header = macho_file.sections.items(.header)[sym.n_sect - 1];
421 if (header.isZerofill()) continue;
422
423 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
424 try Atom.scanAtomRelocs(macho_file, atom_index, relocs);
425 }
426 }
427
428 try eh_frame.scanRelocs(macho_file);
429 try UnwindInfo.scanRelocs(macho_file);
430
431 if (macho_file.dyld_stub_binder_index) |index|
432 try macho_file.addGotEntry(macho_file.globals.items[index]);
433
434 try calcSectionSizes(macho_file);
435
436 var unwind_info = UnwindInfo{ .gpa = gpa };
437 defer unwind_info.deinit();
438 try unwind_info.collect(macho_file);
439
440 try eh_frame.calcSectionSize(macho_file, &unwind_info);
441 unwind_info.calcSectionSize(macho_file);
442
443 try pruneAndSortSections(macho_file);
444 try createSegments(macho_file);
445 try allocateSegments(macho_file);
446
447 try macho_file.allocateSpecialSymbols();
448
449 if (build_options.enable_logging) {
450 macho_file.logSymtab();
451 macho_file.logSegments();
452 macho_file.logSections();
453 macho_file.logAtoms();
454 }
455
456 try writeAtoms(macho_file);
457 if (target.cpu.arch == .aarch64) try writeThunks(macho_file);
458 try writeDyldPrivateAtom(macho_file);
459
460 if (macho_file.stubs_section_index) |_| {
461 try writeStubs(macho_file);
462 try writeStubHelpers(macho_file);
463 try writeLaSymbolPtrs(macho_file);
464 }
465 if (macho_file.got_section_index) |sect_id|
466 try writePointerEntries(macho_file, sect_id, &macho_file.got_table);
467 if (macho_file.tlv_ptr_section_index) |sect_id|
468 try writePointerEntries(macho_file, sect_id, &macho_file.tlv_ptr_table);
469
470 try eh_frame.write(macho_file, &unwind_info);
471 try unwind_info.write(macho_file);
472 try macho_file.writeLinkeditSegmentData();
473
474 // If the last section of __DATA segment is zerofill section, we need to ensure
475 // that the free space between the end of the last non-zerofill section of __DATA
476 // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will
477 // copy-paste this space into memory for quicker zerofill operation.
478 if (macho_file.data_segment_cmd_index) |data_seg_id| blk: {
479 var physical_zerofill_start: ?u64 = null;
480 const section_indexes = macho_file.getSectionIndexes(data_seg_id);
481 for (macho_file.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
482 if (header.isZerofill() and header.size > 0) break;
483 physical_zerofill_start = header.offset + header.size;
484 } else break :blk;
485 const start = physical_zerofill_start orelse break :blk;
486 const linkedit = macho_file.getLinkeditSegmentPtr();
487 const size = math.cast(usize, linkedit.fileoff - start) orelse return error.Overflow;
488 if (size > 0) {
489 log.debug("zeroing out zerofill area of length {x} at {x}", .{ size, start });
490 const padding = try gpa.alloc(u8, size);
491 defer gpa.free(padding);
492 @memset(padding, 0);
493 try macho_file.base.file.?.pwriteAll(padding, start);
494 }
495 }
496
497 // Write code signature padding if required
498 var codesig: ?CodeSignature = if (macho_file.requiresCodeSignature()) blk: {
499 // Preallocate space for the code signature.
500 // We need to do this at this stage so that we have the load commands with proper values
501 // written out to the file.
502 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
503 // where the code signature goes into.
504 var codesig = CodeSignature.init(MachO.getPageSize(cpu_arch));
505 codesig.code_directory.ident = fs.path.basename(full_out_path);
506 if (macho_file.entitlements) |path| {
507 try codesig.addEntitlements(gpa, path);
508 }
509 try macho_file.writeCodeSignaturePadding(&codesig);
510 break :blk codesig;
511 } else null;
512 defer if (codesig) |*csig| csig.deinit(gpa);
513
514 // Write load commands
515 var lc_buffer = std.ArrayList(u8).init(arena);
516 const lc_writer = lc_buffer.writer();
517
518 try macho_file.writeSegmentHeaders(lc_writer);
519 try lc_writer.writeStruct(macho_file.dyld_info_cmd);
520 try lc_writer.writeStruct(macho_file.function_starts_cmd);
521 try lc_writer.writeStruct(macho_file.data_in_code_cmd);
522 try lc_writer.writeStruct(macho_file.symtab_cmd);
523 try lc_writer.writeStruct(macho_file.dysymtab_cmd);
524 try load_commands.writeDylinkerLC(lc_writer);
525
526 switch (output_mode) {
527 .Exe => blk: {
528 const seg_id = macho_file.header_segment_cmd_index.?;
529 const seg = macho_file.segments.items[seg_id];
530 const global = macho_file.getEntryPoint() orelse break :blk;
531 const sym = macho_file.getSymbol(global);
532
533 const addr: u64 = if (sym.undf())
534 // In this case, the symbol has been resolved in one of dylibs and so we point
535 // to the stub as its vmaddr value.
536 macho_file.getStubsEntryAddress(global).?
537 else
538 sym.n_value;
539
540 try lc_writer.writeStruct(macho.entry_point_command{
541 .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
542 .stacksize = macho_file.base.stack_size,
543 });
544 },
545 .Lib => if (link_mode == .Dynamic) {
546 try load_commands.writeDylibIdLC(macho_file, lc_writer);
547 },
548 else => {},
549 }
550
551 try load_commands.writeRpathLCs(macho_file, lc_writer);
552 try lc_writer.writeStruct(macho.source_version_command{
553 .version = 0,
554 });
555 {
556 const platform = Platform.fromTarget(target);
557 const sdk_version: ?std.SemanticVersion = load_commands.inferSdkVersion(macho_file);
558 if (platform.isBuildVersionCompatible()) {
559 try load_commands.writeBuildVersionLC(platform, sdk_version, lc_writer);
560 } else {
561 try load_commands.writeVersionMinLC(platform, sdk_version, lc_writer);
562 }
563 }
564
565 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @as(u32, @intCast(lc_buffer.items.len));
566 try lc_writer.writeStruct(macho_file.uuid_cmd);
567
568 try load_commands.writeLoadDylibLCs(
569 macho_file.dylibs.items,
570 macho_file.referenced_dylibs.keys(),
571 lc_writer,
572 );
573
574 if (codesig != null) {
575 try lc_writer.writeStruct(macho_file.codesig_cmd);
576 }
577
578 const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
579 try macho_file.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
580 try macho_file.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len)));
581 try macho_file.writeUuid(comp, uuid_cmd_offset, codesig != null);
582
583 if (codesig) |*csig| {
584 try macho_file.writeCodeSignature(comp, csig); // code signing always comes last
585 try MachO.invalidateKernelCache(directory.handle, macho_file.base.emit.sub_path);
586 }
587 }
588
589 if (!macho_file.base.disable_lld_caching) {
590 // Update the file with the digest. If it fails we can continue; it only
591 // means that the next invocation will have an unnecessary cache miss.
592 Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
593 log.debug("failed to save linking hash digest file: {s}", .{@errorName(err)});
594 };
595 // Again failure here only means an unnecessary cache miss.
596 if (man.have_exclusive_lock) {
597 man.writeManifest() catch |err| {
598 log.debug("failed to write cache manifest when linking: {s}", .{@errorName(err)});
599 };
600 }
601 // We hang on to this lock so that the output file path can be used without
602 // other processes clobbering it.
603 macho_file.base.lock = man.toOwnedLock();
604 }
605}
606
607fn createSegments(macho_file: *MachO) !void {
608 const comp = macho_file.base.comp;
609 const gpa = comp.gpa;
610 const target = macho_file.base.comp.root_mod.resolved_target.result;
611 const page_size = MachO.getPageSize(target.cpu.arch);
612 const aligned_pagezero_vmsize = mem.alignBackward(u64, macho_file.pagezero_vmsize, page_size);
613 if (macho_file.base.comp.config.output_mode != .Lib and aligned_pagezero_vmsize > 0) {
614 if (aligned_pagezero_vmsize != macho_file.pagezero_vmsize) {
615 log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{macho_file.pagezero_vmsize});
616 log.warn(" rounding down to 0x{x}", .{aligned_pagezero_vmsize});
617 }
618 macho_file.pagezero_segment_cmd_index = @intCast(macho_file.segments.items.len);
619 try macho_file.segments.append(gpa, .{
620 .cmdsize = @sizeOf(macho.segment_command_64),
621 .segname = MachO.makeStaticString("__PAGEZERO"),
622 .vmsize = aligned_pagezero_vmsize,
623 });
624 }
625
626 // __TEXT segment is non-optional
627 {
628 const protection = MachO.getSegmentMemoryProtection("__TEXT");
629 macho_file.text_segment_cmd_index = @intCast(macho_file.segments.items.len);
630 macho_file.header_segment_cmd_index = macho_file.text_segment_cmd_index.?;
631 try macho_file.segments.append(gpa, .{
632 .cmdsize = @sizeOf(macho.segment_command_64),
633 .segname = MachO.makeStaticString("__TEXT"),
634 .maxprot = protection,
635 .initprot = protection,
636 });
637 }
638
639 for (macho_file.sections.items(.header), 0..) |header, sect_id| {
640 if (header.size == 0) continue; // empty section
641
642 const segname = header.segName();
643 const segment_id = macho_file.getSegmentByName(segname) orelse blk: {
644 log.debug("creating segment '{s}'", .{segname});
645 const segment_id = @as(u8, @intCast(macho_file.segments.items.len));
646 const protection = MachO.getSegmentMemoryProtection(segname);
647 try macho_file.segments.append(gpa, .{
648 .cmdsize = @sizeOf(macho.segment_command_64),
649 .segname = MachO.makeStaticString(segname),
650 .maxprot = protection,
651 .initprot = protection,
652 });
653 break :blk segment_id;
654 };
655 const segment = &macho_file.segments.items[segment_id];
656 segment.cmdsize += @sizeOf(macho.section_64);
657 segment.nsects += 1;
658 macho_file.sections.items(.segment_index)[sect_id] = segment_id;
659 }
660
661 if (macho_file.getSegmentByName("__DATA_CONST")) |index| {
662 macho_file.data_const_segment_cmd_index = index;
663 }
664
665 if (macho_file.getSegmentByName("__DATA")) |index| {
666 macho_file.data_segment_cmd_index = index;
667 }
668
669 // __LINKEDIT always comes last
670 {
671 const protection = MachO.getSegmentMemoryProtection("__LINKEDIT");
672 macho_file.linkedit_segment_cmd_index = @intCast(macho_file.segments.items.len);
673 try macho_file.segments.append(gpa, .{
674 .cmdsize = @sizeOf(macho.segment_command_64),
675 .segname = MachO.makeStaticString("__LINKEDIT"),
676 .maxprot = protection,
677 .initprot = protection,
678 });
679 }
680}
681
682fn writeAtoms(macho_file: *MachO) !void {
683 const comp = macho_file.base.comp;
684 const gpa = comp.gpa;
685 const slice = macho_file.sections.slice();
686
687 for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| {
688 const header = slice.items(.header)[sect_id];
689 if (header.isZerofill()) continue;
690
691 var atom_index = first_atom_index orelse continue;
692
693 var buffer = try gpa.alloc(u8, math.cast(usize, header.size) orelse return error.Overflow);
694 defer gpa.free(buffer);
695 @memset(buffer, 0); // TODO with NOPs
696
697 log.debug("writing atoms in {s},{s}", .{ header.segName(), header.sectName() });
698
699 while (true) {
700 const atom = macho_file.getAtom(atom_index);
701 if (atom.getFile()) |file| {
702 const this_sym = macho_file.getSymbol(atom.getSymbolWithLoc());
703 const padding_size: usize = if (atom.next_index) |next_index| blk: {
704 const next_sym = macho_file.getSymbol(macho_file.getAtom(next_index).getSymbolWithLoc());
705 const size = next_sym.n_value - (this_sym.n_value + atom.size);
706 break :blk math.cast(usize, size) orelse return error.Overflow;
707 } else 0;
708
709 log.debug(" (adding ATOM(%{d}, '{s}') from object({d}) to buffer)", .{
710 atom.sym_index,
711 macho_file.getSymbolName(atom.getSymbolWithLoc()),
712 file,
713 });
714 if (padding_size > 0) {
715 log.debug(" (with padding {x})", .{padding_size});
716 }
717
718 const offset = math.cast(usize, this_sym.n_value - header.addr) orelse
719 return error.Overflow;
720 log.debug(" (at offset 0x{x})", .{offset});
721
722 const code = Atom.getAtomCode(macho_file, atom_index);
723 const relocs = Atom.getAtomRelocs(macho_file, atom_index);
724 const size = math.cast(usize, atom.size) orelse return error.Overflow;
725 @memcpy(buffer[offset .. offset + size], code);
726 try Atom.resolveRelocs(
727 macho_file,
728 atom_index,
729 buffer[offset..][0..size],
730 relocs,
731 );
732 }
733
734 if (atom.next_index) |next_index| {
735 atom_index = next_index;
736 } else break;
737 }
738
739 log.debug(" (writing at file offset 0x{x})", .{header.offset});
740 try macho_file.base.file.?.pwriteAll(buffer, header.offset);
741 }
742}
743
744fn writeDyldPrivateAtom(macho_file: *MachO) !void {
745 const atom_index = macho_file.dyld_private_atom_index orelse return;
746 const atom = macho_file.getAtom(atom_index);
747 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
748 const sect_id = macho_file.data_section_index.?;
749 const header = macho_file.sections.items(.header)[sect_id];
750 const offset = sym.n_value - header.addr + header.offset;
751 log.debug("writing __dyld_private at offset 0x{x}", .{offset});
752 const buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
753 try macho_file.base.file.?.pwriteAll(&buffer, offset);
754}
755
756fn writeThunks(macho_file: *MachO) !void {
757 const target = macho_file.base.comp.root_mod.resolved_target.result;
758 assert(target.cpu.arch == .aarch64);
759 const comp = macho_file.base.comp;
760 const gpa = comp.gpa;
761
762 const sect_id = macho_file.text_section_index orelse return;
763 const header = macho_file.sections.items(.header)[sect_id];
764
765 for (macho_file.thunks.items, 0..) |*thunk, i| {
766 if (thunk.getSize() == 0) continue;
767 const thunk_size = math.cast(usize, thunk.getSize()) orelse return error.Overflow;
768 var buffer = try std.ArrayList(u8).initCapacity(gpa, thunk_size);
769 defer buffer.deinit();
770 try thunks.writeThunkCode(macho_file, thunk, buffer.writer());
771 const thunk_atom = macho_file.getAtom(thunk.getStartAtomIndex());
772 const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc());
773 const offset = thunk_sym.n_value - header.addr + header.offset;
774 log.debug("writing thunk({d}) at offset 0x{x}", .{ i, offset });
775 try macho_file.base.file.?.pwriteAll(buffer.items, offset);
776 }
777}
778
779fn writePointerEntries(macho_file: *MachO, sect_id: u8, table: anytype) !void {
780 const comp = macho_file.base.comp;
781 const gpa = comp.gpa;
782 const header = macho_file.sections.items(.header)[sect_id];
783 const capacity = math.cast(usize, header.size) orelse return error.Overflow;
784 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
785 defer buffer.deinit();
786 for (table.entries.items) |entry| {
787 const sym = macho_file.getSymbol(entry);
788 buffer.writer().writeInt(u64, sym.n_value, .little) catch unreachable;
789 }
790 log.debug("writing __DATA_CONST,__got contents at file offset 0x{x}", .{header.offset});
791 try macho_file.base.file.?.pwriteAll(buffer.items, header.offset);
792}
793
794fn writeStubs(macho_file: *MachO) !void {
795 const comp = macho_file.base.comp;
796 const gpa = comp.gpa;
797 const target = macho_file.base.comp.root_mod.resolved_target.result;
798 const cpu_arch = target.cpu.arch;
799 const stubs_header = macho_file.sections.items(.header)[macho_file.stubs_section_index.?];
800 const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
801
802 const capacity = math.cast(usize, stubs_header.size) orelse return error.Overflow;
803 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
804 defer buffer.deinit();
805
806 for (0..macho_file.stub_table.count()) |index| {
807 try stubs.writeStubCode(.{
808 .cpu_arch = cpu_arch,
809 .source_addr = stubs_header.addr + stubs.stubSize(cpu_arch) * index,
810 .target_addr = la_symbol_ptr_header.addr + index * @sizeOf(u64),
811 }, buffer.writer());
812 }
813
814 log.debug("writing __TEXT,__stubs contents at file offset 0x{x}", .{stubs_header.offset});
815 try macho_file.base.file.?.pwriteAll(buffer.items, stubs_header.offset);
816}
817
818fn writeStubHelpers(macho_file: *MachO) !void {
819 const comp = macho_file.base.comp;
820 const gpa = comp.gpa;
821 const target = macho_file.base.comp.root_mod.resolved_target.result;
822 const cpu_arch = target.cpu.arch;
823 const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
824
825 const capacity = math.cast(usize, stub_helper_header.size) orelse return error.Overflow;
826 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
827 defer buffer.deinit();
828
829 {
830 const dyld_private_addr = blk: {
831 const atom = macho_file.getAtom(macho_file.dyld_private_atom_index.?);
832 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
833 break :blk sym.n_value;
834 };
835 const dyld_stub_binder_got_addr = blk: {
836 const sym_loc = macho_file.globals.items[macho_file.dyld_stub_binder_index.?];
837 break :blk macho_file.getGotEntryAddress(sym_loc).?;
838 };
839 try stubs.writeStubHelperPreambleCode(.{
840 .cpu_arch = cpu_arch,
841 .source_addr = stub_helper_header.addr,
842 .dyld_private_addr = dyld_private_addr,
843 .dyld_stub_binder_got_addr = dyld_stub_binder_got_addr,
844 }, buffer.writer());
845 }
846
847 for (0..macho_file.stub_table.count()) |index| {
848 const source_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
849 stubs.stubHelperSize(cpu_arch) * index;
850 try stubs.writeStubHelperCode(.{
851 .cpu_arch = cpu_arch,
852 .source_addr = source_addr,
853 .target_addr = stub_helper_header.addr,
854 }, buffer.writer());
855 }
856
857 log.debug("writing __TEXT,__stub_helper contents at file offset 0x{x}", .{
858 stub_helper_header.offset,
859 });
860 try macho_file.base.file.?.pwriteAll(buffer.items, stub_helper_header.offset);
861}
862
863fn writeLaSymbolPtrs(macho_file: *MachO) !void {
864 const comp = macho_file.base.comp;
865 const gpa = comp.gpa;
866 const target = macho_file.base.comp.root_mod.resolved_target.result;
867 const cpu_arch = target.cpu.arch;
868 const la_symbol_ptr_header = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_section_index.?];
869 const stub_helper_header = macho_file.sections.items(.header)[macho_file.stub_helper_section_index.?];
870
871 const capacity = math.cast(usize, la_symbol_ptr_header.size) orelse return error.Overflow;
872 var buffer = try std.ArrayList(u8).initCapacity(gpa, capacity);
873 defer buffer.deinit();
874
875 for (0..macho_file.stub_table.count()) |index| {
876 const target_addr = stub_helper_header.addr + stubs.stubHelperPreambleSize(cpu_arch) +
877 stubs.stubHelperSize(cpu_arch) * index;
878 buffer.writer().writeInt(u64, target_addr, .little) catch unreachable;
879 }
880
881 log.debug("writing __DATA,__la_symbol_ptr contents at file offset 0x{x}", .{
882 la_symbol_ptr_header.offset,
883 });
884 try macho_file.base.file.?.pwriteAll(buffer.items, la_symbol_ptr_header.offset);
885}
886
887fn pruneAndSortSections(macho_file: *MachO) !void {
888 const Entry = struct {
889 index: u8,
890
891 pub fn lessThan(ctx: *MachO, lhs: @This(), rhs: @This()) bool {
892 const lhs_header = ctx.sections.items(.header)[lhs.index];
893 const rhs_header = ctx.sections.items(.header)[rhs.index];
894 return MachO.getSectionPrecedence(lhs_header) < MachO.getSectionPrecedence(rhs_header);
895 }
896 };
897
898 const comp = macho_file.base.comp;
899 const gpa = comp.gpa;
900
901 var entries = try std.ArrayList(Entry).initCapacity(gpa, macho_file.sections.slice().len);
902 defer entries.deinit();
903
904 for (0..macho_file.sections.slice().len) |index| {
905 const section = macho_file.sections.get(index);
906 if (section.header.size == 0) {
907 log.debug("pruning section {s},{s} {?d}", .{
908 section.header.segName(),
909 section.header.sectName(),
910 section.first_atom_index,
911 });
912 for (&[_]*?u8{
913 &macho_file.text_section_index,
914 &macho_file.data_const_section_index,
915 &macho_file.data_section_index,
916 &macho_file.bss_section_index,
917 &macho_file.thread_vars_section_index,
918 &macho_file.thread_data_section_index,
919 &macho_file.thread_bss_section_index,
920 &macho_file.eh_frame_section_index,
921 &macho_file.unwind_info_section_index,
922 &macho_file.got_section_index,
923 &macho_file.tlv_ptr_section_index,
924 &macho_file.stubs_section_index,
925 &macho_file.stub_helper_section_index,
926 &macho_file.la_symbol_ptr_section_index,
927 }) |maybe_index| {
928 if (maybe_index.* != null and maybe_index.*.? == index) {
929 maybe_index.* = null;
930 }
931 }
932 continue;
933 }
934 entries.appendAssumeCapacity(.{ .index = @intCast(index) });
935 }
936
937 mem.sort(Entry, entries.items, macho_file, Entry.lessThan);
938
939 var slice = macho_file.sections.toOwnedSlice();
940 defer slice.deinit(gpa);
941
942 const backlinks = try gpa.alloc(u8, slice.len);
943 defer gpa.free(backlinks);
944 for (entries.items, 0..) |entry, i| {
945 backlinks[entry.index] = @as(u8, @intCast(i));
946 }
947
948 try macho_file.sections.ensureTotalCapacity(gpa, entries.items.len);
949 for (entries.items) |entry| {
950 macho_file.sections.appendAssumeCapacity(slice.get(entry.index));
951 }
952
953 for (&[_]*?u8{
954 &macho_file.text_section_index,
955 &macho_file.data_const_section_index,
956 &macho_file.data_section_index,
957 &macho_file.bss_section_index,
958 &macho_file.thread_vars_section_index,
959 &macho_file.thread_data_section_index,
960 &macho_file.thread_bss_section_index,
961 &macho_file.eh_frame_section_index,
962 &macho_file.unwind_info_section_index,
963 &macho_file.got_section_index,
964 &macho_file.tlv_ptr_section_index,
965 &macho_file.stubs_section_index,
966 &macho_file.stub_helper_section_index,
967 &macho_file.la_symbol_ptr_section_index,
968 }) |maybe_index| {
969 if (maybe_index.*) |*index| {
970 index.* = backlinks[index.*];
971 }
972 }
973}
974
975fn calcSectionSizes(macho_file: *MachO) !void {
976 const target = macho_file.base.comp.root_mod.resolved_target.result;
977 const slice = macho_file.sections.slice();
978 for (slice.items(.header), 0..) |*header, sect_id| {
979 if (header.size == 0) continue;
980 if (macho_file.text_section_index) |txt| {
981 if (txt == sect_id and target.cpu.arch == .aarch64) continue;
982 }
983
984 var atom_index = slice.items(.first_atom_index)[sect_id] orelse continue;
985
986 header.size = 0;
987 header.@"align" = 0;
988
989 while (true) {
990 const atom = macho_file.getAtom(atom_index);
991 const atom_offset = atom.alignment.forward(header.size);
992 const padding = atom_offset - header.size;
993
994 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
995 sym.n_value = atom_offset;
996
997 header.size += padding + atom.size;
998 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
999
1000 atom_index = atom.next_index orelse break;
1001 }
1002 }
1003
1004 if (macho_file.text_section_index != null and target.cpu.arch == .aarch64) {
1005 // Create jump/branch range extenders if needed.
1006 try thunks.createThunks(macho_file, macho_file.text_section_index.?);
1007 }
1008
1009 // Update offsets of all symbols contained within each Atom.
1010 // We need to do this since our unwind info synthesiser relies on
1011 // traversing the symbols when synthesising unwind info and DWARF CFI records.
1012 for (slice.items(.first_atom_index)) |first_atom_index| {
1013 var atom_index = first_atom_index orelse continue;
1014
1015 while (true) {
1016 const atom = macho_file.getAtom(atom_index);
1017 const sym = macho_file.getSymbol(atom.getSymbolWithLoc());
1018
1019 if (atom.getFile() != null) {
1020 // Update each symbol contained within the atom
1021 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
1022 while (it.next()) |sym_loc| {
1023 const inner_sym = macho_file.getSymbolPtr(sym_loc);
1024 inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
1025 macho_file,
1026 atom_index,
1027 sym_loc.sym_index,
1028 );
1029 }
1030
1031 // If there is a section alias, update it now too
1032 if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
1033 const alias = macho_file.getSymbolPtr(sym_loc);
1034 alias.n_value = sym.n_value;
1035 }
1036 }
1037
1038 if (atom.next_index) |next_index| {
1039 atom_index = next_index;
1040 } else break;
1041 }
1042 }
1043
1044 if (macho_file.got_section_index) |sect_id| {
1045 const header = &macho_file.sections.items(.header)[sect_id];
1046 header.size = macho_file.got_table.count() * @sizeOf(u64);
1047 header.@"align" = 3;
1048 }
1049
1050 if (macho_file.tlv_ptr_section_index) |sect_id| {
1051 const header = &macho_file.sections.items(.header)[sect_id];
1052 header.size = macho_file.tlv_ptr_table.count() * @sizeOf(u64);
1053 header.@"align" = 3;
1054 }
1055
1056 const cpu_arch = target.cpu.arch;
1057
1058 if (macho_file.stubs_section_index) |sect_id| {
1059 const header = &macho_file.sections.items(.header)[sect_id];
1060 header.size = macho_file.stub_table.count() * stubs.stubSize(cpu_arch);
1061 header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
1062 }
1063
1064 if (macho_file.stub_helper_section_index) |sect_id| {
1065 const header = &macho_file.sections.items(.header)[sect_id];
1066 header.size = macho_file.stub_table.count() * stubs.stubHelperSize(cpu_arch) +
1067 stubs.stubHelperPreambleSize(cpu_arch);
1068 header.@"align" = math.log2(stubs.stubAlignment(cpu_arch));
1069 }
1070
1071 if (macho_file.la_symbol_ptr_section_index) |sect_id| {
1072 const header = &macho_file.sections.items(.header)[sect_id];
1073 header.size = macho_file.stub_table.count() * @sizeOf(u64);
1074 header.@"align" = 3;
1075 }
1076}
1077
1078fn allocateSegments(macho_file: *MachO) !void {
1079 for (macho_file.segments.items, 0..) |*segment, segment_index| {
1080 const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT");
1081 const base_size = if (is_text_segment)
1082 try load_commands.calcMinHeaderPad(macho_file, .{
1083 .segments = macho_file.segments.items,
1084 .dylibs = macho_file.dylibs.items,
1085 .referenced_dylibs = macho_file.referenced_dylibs.keys(),
1086 })
1087 else
1088 0;
1089 try allocateSegment(macho_file, @as(u8, @intCast(segment_index)), base_size);
1090 }
1091}
1092
1093fn getSegmentAllocBase(macho_file: *MachO, segment_index: u8) struct { vmaddr: u64, fileoff: u64 } {
1094 if (segment_index > 0) {
1095 const prev_segment = macho_file.segments.items[segment_index - 1];
1096 return .{
1097 .vmaddr = prev_segment.vmaddr + prev_segment.vmsize,
1098 .fileoff = prev_segment.fileoff + prev_segment.filesize,
1099 };
1100 }
1101 return .{ .vmaddr = 0, .fileoff = 0 };
1102}
1103
1104fn allocateSegment(macho_file: *MachO, segment_index: u8, init_size: u64) !void {
1105 const target = macho_file.base.comp.root_mod.resolved_target.result;
1106 const segment = &macho_file.segments.items[segment_index];
1107
1108 if (mem.eql(u8, segment.segName(), "__PAGEZERO")) return; // allocated upon creation
1109
1110 const base = getSegmentAllocBase(macho_file, segment_index);
1111 segment.vmaddr = base.vmaddr;
1112 segment.fileoff = base.fileoff;
1113 segment.filesize = init_size;
1114 segment.vmsize = init_size;
1115
1116 // Allocate the sections according to their alignment at the beginning of the segment.
1117 const indexes = macho_file.getSectionIndexes(segment_index);
1118 var start = init_size;
1119
1120 const slice = macho_file.sections.slice();
1121 for (slice.items(.header)[indexes.start..indexes.end], 0..) |*header, sect_id| {
1122 const alignment = try math.powi(u32, 2, header.@"align");
1123 const start_aligned = mem.alignForward(u64, start, alignment);
1124 const n_sect = @as(u8, @intCast(indexes.start + sect_id + 1));
1125
1126 header.offset = if (header.isZerofill())
1127 0
1128 else
1129 @as(u32, @intCast(segment.fileoff + start_aligned));
1130 header.addr = segment.vmaddr + start_aligned;
1131
1132 if (slice.items(.first_atom_index)[indexes.start + sect_id]) |first_atom_index| {
1133 var atom_index = first_atom_index;
1134
1135 log.debug("allocating local symbols in sect({d}, '{s},{s}')", .{
1136 n_sect,
1137 header.segName(),
1138 header.sectName(),
1139 });
1140
1141 while (true) {
1142 const atom = macho_file.getAtom(atom_index);
1143 const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc());
1144 sym.n_value += header.addr;
1145 sym.n_sect = n_sect;
1146
1147 log.debug(" ATOM(%{d}, '{s}') @{x}", .{
1148 atom.sym_index,
1149 macho_file.getSymbolName(atom.getSymbolWithLoc()),
1150 sym.n_value,
1151 });
1152
1153 if (atom.getFile() != null) {
1154 // Update each symbol contained within the atom
1155 var it = Atom.getInnerSymbolsIterator(macho_file, atom_index);
1156 while (it.next()) |sym_loc| {
1157 const inner_sym = macho_file.getSymbolPtr(sym_loc);
1158 inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
1159 macho_file,
1160 atom_index,
1161 sym_loc.sym_index,
1162 );
1163 inner_sym.n_sect = n_sect;
1164 }
1165
1166 // If there is a section alias, update it now too
1167 if (Atom.getSectionAlias(macho_file, atom_index)) |sym_loc| {
1168 const alias = macho_file.getSymbolPtr(sym_loc);
1169 alias.n_value = sym.n_value;
1170 alias.n_sect = n_sect;
1171 }
1172 }
1173
1174 if (atom.next_index) |next_index| {
1175 atom_index = next_index;
1176 } else break;
1177 }
1178 }
1179
1180 start = start_aligned + header.size;
1181
1182 if (!header.isZerofill()) {
1183 segment.filesize = start;
1184 }
1185 segment.vmsize = start;
1186 }
1187
1188 const page_size = MachO.getPageSize(target.cpu.arch);
1189 segment.filesize = mem.alignForward(u64, segment.filesize, page_size);
1190 segment.vmsize = mem.alignForward(u64, segment.vmsize, page_size);
1191}
1192
1193const std = @import("std");
1194const build_options = @import("build_options");
1195const assert = std.debug.assert;
1196const dwarf = std.dwarf;
1197const fs = std.fs;
1198const log = std.log.scoped(.link);
1199const macho = std.macho;
1200const math = std.math;
1201const mem = std.mem;
1202
1203const aarch64 = @import("../../arch/aarch64/bits.zig");
1204const calcUuid = @import("uuid.zig").calcUuid;
1205const dead_strip = @import("dead_strip.zig");
1206const eh_frame = @import("eh_frame.zig");
1207const fat = @import("fat.zig");
1208const link = @import("../../link.zig");
1209const load_commands = @import("load_commands.zig");
1210const stubs = @import("stubs.zig");
1211const thunks = @import("thunks.zig");
1212const trace = @import("../../tracy.zig").trace;
1213
1214const Allocator = mem.Allocator;
1215const Archive = @import("Archive.zig");
1216const Atom = @import("Atom.zig");
1217const Cache = std.Build.Cache;
1218const CodeSignature = @import("CodeSignature.zig");
1219const Compilation = @import("../../Compilation.zig");
1220const Dylib = @import("Dylib.zig");
1221const MachO = @import("../MachO.zig");
1222const Md5 = std.crypto.hash.Md5;
1223const LibStub = @import("../tapi.zig").LibStub;
1224const Object = @import("Object.zig");
1225const Platform = load_commands.Platform;
1226const Section = MachO.Section;
1227const SymbolWithLoc = MachO.SymbolWithLoc;
1228const TableSection = @import("../table_section.zig").TableSection;
1229const Trie = @import("Trie.zig");
1230const UnwindInfo = @import("UnwindInfo.zig");