authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-17 01:03:57+02:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-17 01:03:57+02:00
logf72a1d4e161e290f2791b5d6f7b746d66e181e59
treed714ff99c31b013aeefbf09b82f3ab520080c5fc
parent274fd8a5697ed40fb8cd3956707dbf4db3964e22
parent6882d5022d1eae5659f634de44dcb2a352e18712

Merge pull request 'MachO2: get some stuff working' (#37266) from mlugg/macho2 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37266

4 files changed, 3220 insertions(+), 257 deletions(-)

lib/std/macho.zig+131-1
......@@ -948,7 +948,7 @@ pub const nlist_64 = extern struct {
948948/// This modification is required to support symbols in an arbitrary number of
949949/// sections not just the three sections (text, data and bss) in a 4.3BSD file.
950950/// Also the last 4 bits have had the r_type tag added to them.
951pub const relocation_info = packed struct {
951pub const relocation_info = packed struct(u64) {
952952 /// offset in the section to what is being relocated
953953 r_address: i32,
954954
......@@ -2220,3 +2220,133 @@ pub const CompactUnwindEncoding = packed struct(u32) {
22202220pub const ARMAG = "!<arch>\n";
22212221/// String in ar_fmag at the end of each header.
22222222pub const ARFMAG = "`\n";
2223
2224pub const SYMDEF = "__.SYMDEF";
2225pub const SYMDEF64 = "__.SYMDEF_64";
2226pub const SYMDEF_SORTED = "__.SYMDEF SORTED";
2227pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED";
2228
2229pub const ar_hdr = extern struct {
2230 /// Member file name, sometimes / terminated.
2231 ar_name: [16]u8,
2232 /// File date, decimal seconds since Epoch.
2233 ar_date: [12]u8,
2234 /// User ID, in ASCII format.
2235 ar_uid: [6]u8,
2236 /// Group ID, in ASCII format.
2237 ar_gid: [6]u8,
2238 /// File mode, in ASCII octal.
2239 ar_mode: [8]u8,
2240 /// File size, in ASCII decimal.
2241 ar_size: [10]u8,
2242 /// Always contains ARFMAG.
2243 ar_fmag: [2]u8,
2244
2245 pub fn date(hdr: *const ar_hdr) std.fmt.ParseIntError!u64 {
2246 const value = mem.trimEnd(u8, &hdr.ar_date, " ");
2247 return std.fmt.parseInt(u64, value, 10);
2248 }
2249
2250 pub fn size(hdr: *const ar_hdr) std.fmt.ParseIntError!u32 {
2251 const value = mem.trimEnd(u8, &hdr.ar_size, " ");
2252 return std.fmt.parseInt(u32, value, 10);
2253 }
2254
2255 /// Asserts that `nameLength(hdr)` is `null` and returns the short name of the archive member.
2256 pub fn name(hdr: *const ar_hdr) []const u8 {
2257 assert(hdr.nameLength() == null);
2258 return mem.sliceTo(&hdr.ar_name, '/');
2259 }
2260
2261 pub fn nameLength(hdr: *const ar_hdr) ?u32 {
2262 if (!mem.startsWith(u8, &hdr.ar_name, "#1/")) return null;
2263 const trimmed = mem.trimEnd(u8, hdr.ar_name["#1/".len..], " ");
2264 return std.fmt.parseInt(u32, trimmed, 10) catch return null;
2265 }
2266};
2267
2268pub const dyld_chained_fixups_header = extern struct {
2269 fixups_version: u32,
2270 starts_offset: u32,
2271 imports_offset: u32,
2272 symbols_offset: u32,
2273 imports_count: u32,
2274 imports_format: enum(u32) {
2275 no_addend = 1,
2276 addend_32 = 2,
2277 addend_64 = 3,
2278 _,
2279 },
2280 symbols_format: enum(u32) {
2281 uncompressed = 0,
2282 zlib = 1,
2283 _,
2284 },
2285};
2286/// This struct has two bytes of trailing padding containing the first `page_starts` entry.
2287pub const dyld_chained_starts_in_segment = extern struct {
2288 size: u32,
2289 page_size: u16,
2290 pointer_format: enum(u16) {
2291 arm64e = 1,
2292 @"64" = 2,
2293 @"32" = 3,
2294 @"32_cache" = 4,
2295 @"32_firmware" = 5,
2296 @"64_offset" = 6,
2297 arm64e_offset = 7,
2298 @"64_kernel_cache" = 8,
2299 arm64e_userland = 9,
2300 arm64e_firmware = 10,
2301 x86_64_kernel_cache = 11,
2302 arm64e_userland24 = 12,
2303 arm64e_shared_cache = 13,
2304 arm64e_segmented = 14,
2305 _,
2306 pub const arm64e_kernel: @This() = .arm64e_offset;
2307 },
2308 segment_offset: u64,
2309 max_valid_pointer: u32,
2310 page_count: u16,
2311 // trailing: `page_starts` and `chain_starts`
2312};
2313pub const dyld_chained_ptr_64 = packed struct(u64) {
2314 per_kind: packed union {
2315 rebase: packed struct {
2316 target: u36,
2317 high8: u8,
2318 reserved: u7 = 0,
2319 },
2320 bind: packed struct {
2321 ordinal: u24,
2322 addend: u8,
2323 reserved: u19 = 0,
2324 },
2325 },
2326 next: u12, // in 4-byte units
2327 kind: enum(u1) { rebase = 0, bind = 1 },
2328};
2329pub const dyld_chained_import = extern struct {
2330 bits: packed struct(u32) {
2331 lib_ordinal: u8,
2332 weak_import: bool,
2333 name_offset: u23,
2334 },
2335};
2336pub const dyld_chained_import_addend = extern struct {
2337 bits: packed struct(u32) {
2338 lib_ordinal: u8,
2339 weak_import: bool,
2340 name_offset: u23,
2341 },
2342 addend: i32,
2343};
2344pub const dyld_chained_import_addend64 = extern struct {
2345 bits: packed struct(u64) {
2346 lib_ordinal: u16,
2347 weak_import: bool,
2348 reserved: u15 = 0,
2349 name_offset: u32,
2350 },
2351 addend: u64,
2352};
src/link/MachO/Archive.zig+27-68
......@@ -22,13 +22,13 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
2222 if (pos >= end_pos) break;
2323 if (!mem.isAligned(pos, 2)) pos += 1;
2424
25 var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;
25 var hdr_buffer: [@sizeOf(macho.ar_hdr)]u8 = undefined;
2626 {
2727 const amt = try handle.readPositionalAll(io, &hdr_buffer, pos);
28 if (amt != @sizeOf(ar_hdr)) return error.InputOutput;
28 if (amt != @sizeOf(macho.ar_hdr)) return error.InputOutput;
2929 }
30 const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*;
31 pos += @sizeOf(ar_hdr);
30 const hdr = @as(*align(1) const macho.ar_hdr, @ptrCast(&hdr_buffer)).*;
31 pos += @sizeOf(macho.ar_hdr);
3232
3333 if (!mem.eql(u8, &hdr.ar_fmag, macho.ARFMAG)) {
3434 return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{
......@@ -38,24 +38,21 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
3838
3939 var hdr_size = try hdr.size();
4040 const name = name: {
41 if (hdr.name()) |n| break :name n;
42 if (try hdr.nameLength()) |len| {
43 hdr_size -= len;
44 const buf = try arena.allocator().alloc(u8, len);
45 const amt = try handle.readPositionalAll(io, buf, pos);
46 if (amt != len) return error.InputOutput;
47 pos += len;
48 const actual_len = mem.findScalar(u8, buf, @as(u8, 0)) orelse len;
49 break :name buf[0..actual_len];
50 }
51 unreachable;
41 const len = hdr.nameLength() orelse break :name hdr.name();
42 hdr_size -= len;
43 const buf = try arena.allocator().alloc(u8, len);
44 const amt = try handle.readPositionalAll(io, buf, pos);
45 if (amt != len) return error.InputOutput;
46 pos += len;
47 const actual_len = mem.findScalar(u8, buf, @as(u8, 0)) orelse len;
48 break :name buf[0..actual_len];
5249 };
5350 defer pos += hdr_size;
5451
55 if (mem.eql(u8, name, SYMDEF) or
56 mem.eql(u8, name, SYMDEF64) or
57 mem.eql(u8, name, SYMDEF_SORTED) or
58 mem.eql(u8, name, SYMDEF64_SORTED)) continue;
52 if (mem.eql(u8, name, macho.SYMDEF) or
53 mem.eql(u8, name, macho.SYMDEF64) or
54 mem.eql(u8, name, macho.SYMDEF_SORTED) or
55 mem.eql(u8, name, macho.SYMDEF64_SORTED)) continue;
5956
6057 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
6158 comp.dirs.cwd,
......@@ -91,9 +88,17 @@ pub fn writeHeader(
9188 object_size: usize,
9289 writer: *Writer,
9390) !void {
94 var hdr: ar_hdr = .{};
91 var hdr: macho.ar_hdr = .{
92 .ar_name = @splat(' '),
93 .ar_date = "0 ".*,
94 .ar_uid = "0 ".*,
95 .ar_gid = "0 ".*,
96 .ar_mode = "0 ".*,
97 .ar_size = @splat(' '),
98 .ar_fmag = macho.ARFMAG.*,
99 };
95100
96 const object_name_start = writer.end + @sizeOf(ar_hdr);
101 const object_name_start = writer.end + @sizeOf(macho.ar_hdr);
97102 const object_start = mem.alignForward(usize, object_name_start + object_name.len + 1, 8);
98103 const object_name_len = object_start - object_name_start;
99104 const total_object_size = object_size + object_name_len;
......@@ -116,52 +121,6 @@ pub fn writeHeader(
116121 }
117122}
118123
119pub const SYMDEF = "__.SYMDEF";
120pub const SYMDEF64 = "__.SYMDEF_64";
121pub const SYMDEF_SORTED = "__.SYMDEF SORTED";
122pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED";
123
124pub const ar_hdr = extern struct {
125 /// Member file name, sometimes / terminated.
126 ar_name: [16]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
127 /// File date, decimal seconds since Epoch.
128 ar_date: [12]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
129 /// User ID, in ASCII format.
130 ar_uid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
131 /// Group ID, in ASCII format.
132 ar_gid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
133 /// File mode, in ASCII octal.
134 ar_mode: [8]u8 = "0\x20\x20\x20\x20\x20\x20\x20".*,
135 /// File size, in ASCII decimal.
136 ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
137 /// Always contains ARFMAG.
138 ar_fmag: [2]u8 = macho.ARFMAG.*,
139
140 fn date(self: ar_hdr) !u64 {
141 const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
142 return std.fmt.parseInt(u64, value, 10);
143 }
144
145 fn size(self: ar_hdr) !u32 {
146 const value = mem.trimEnd(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
147 return std.fmt.parseInt(u32, value, 10);
148 }
149
150 fn name(self: *const ar_hdr) ?[]const u8 {
151 const value = &self.ar_name;
152 if (mem.startsWith(u8, value, "#1/")) return null;
153 const sentinel = mem.findScalar(u8, value, '/') orelse value.len;
154 return value[0..sentinel];
155 }
156
157 fn nameLength(self: ar_hdr) !?u32 {
158 const value = &self.ar_name;
159 if (!mem.startsWith(u8, value, "#1/")) return null;
160 const trimmed = mem.trimEnd(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
161 return try std.fmt.parseInt(u32, trimmed, 10);
162 }
163};
164
165124pub const ArSymtab = struct {
166125 entries: std.ArrayList(Entry) = .empty,
167126 strtab: StringTable = .{},
......@@ -183,7 +142,7 @@ pub const ArSymtab = struct {
183142 pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: *Writer) !void {
184143 const ptr_width = ptrWidth(format);
185144 // Header
186 try writeHeader(SYMDEF, ar.size(format), writer);
145 try writeHeader(macho.SYMDEF, ar.size(format), writer);
187146 // Symtab size
188147 try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer);
189148 // Symtab entries
src/link/MachO/relocatable.zig+4-4
......@@ -152,8 +152,8 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void {
152152 // Update file offsets of contributing objects
153153 const total_size: usize = blk: {
154154 var pos: usize = macho.ARMAG.len;
155 pos += @sizeOf(Archive.ar_hdr);
156 pos += Archive.SYMDEF.len + 1;
155 pos += @sizeOf(macho.ar_hdr);
156 pos += macho.SYMDEF.len + 1;
157157 pos = mem.alignForward(usize, pos, 8);
158158 pos += ar_symtab.size(format);
159159
......@@ -164,7 +164,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void {
164164 const state = &zo.output_ar_state;
165165 pos = mem.alignForward(usize, pos, 2);
166166 state.file_off = pos;
167 pos += @sizeOf(Archive.ar_hdr);
167 pos += @sizeOf(macho.ar_hdr);
168168 pos += zo.basename.len + 1;
169169 pos = mem.alignForward(usize, pos, 8);
170170 pos += try macho_file.cast(usize, state.size);
......@@ -173,7 +173,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void {
173173 const state = &o.output_ar_state;
174174 pos = mem.alignForward(usize, pos, 2);
175175 state.file_off = pos;
176 pos += @sizeOf(Archive.ar_hdr);
176 pos += @sizeOf(macho.ar_hdr);
177177 pos += std.fs.path.basename(o.path).len + 1;
178178 pos = mem.alignForward(usize, pos, 8);
179179 pos += try macho_file.cast(usize, state.size);
src/link/MachO2.zig+3058-184
......@@ -15,37 +15,72 @@ node: struct {
1515 header: MappedFile.Node.Index,
1616 symtab: MappedFile.Node.Index,
1717 strtab: MappedFile.Node.Index,
18 got_indirect_syms: MappedFile.Node.Index,
19 stubs_indirect_syms: MappedFile.Node.Index,
20 fixup_chains_header: MappedFile.Node.Index,
21 fixup_chains_imports: MappedFile.Node.Index,
22 fixup_chains_starts_in_image: MappedFile.Node.Index,
23 fixup_chains_symbol_names: MappedFile.Node.Index,
1824},
1925
2026lc: struct {
2127 offsets: std.ArrayList(u32),
2228 pagezero: LoadCommand,
2329 symtab: LoadCommand,
30 dysymtab: LoadCommand,
2431 entry_point: ?LoadCommand,
2532},
33known_sections: struct {
34 got: Section,
35 stubs: Section,
36},
2637/// Does not contain an entry for the `__PAGEZERO` segment.
2738segments: std.MultiArrayList(struct {
2839 lc: LoadCommand,
2940 node: MappedFile.Node.Index,
41 fixup_chain_starts: MappedFile.Node.Index.Optional,
3042}),
3143sections: std.MultiArrayList(struct {
3244 lc: LoadCommand,
3345 node: MappedFile.Node.Index,
34 first_symbol: Symbol.Index.Optional,
46 segment: Segment,
3547 index: u8,
3648}),
37/// Accessed with `Segment.NameAdapter` using `[]const u8` keys.
38segments_by_name: std.array_hash_map.Custom(void, void, void, true),
3949/// Accessed with `Section.NameAdapter` using `Section.NameAdapter.SegmentAndSection`.
4050sections_by_name: std.array_hash_map.Custom(void, void, void, true),
4151
52dirty_fixup_chain_pages: std.array_hash_map.Auto(struct { seg: Segment, page: u16 }, void),
53
54input_atoms: std.ArrayList(InputAtom),
55
4256/// Accessed with `String.Adapter` using `[]const u8` keys.
4357strtab: std.array_hash_map.Custom(String, void, void, true),
4458
4559symtab: std.ArrayList(Symbol),
4660
61/// Contains all symbols which can be referenced by name. Mach-O generally calls these "external"
62/// symbols, but I'm avoiding that name in favour of ELF's term "global symbol", because the Mach-O
63/// term is needlessly confusing (they may be defined locally!).
64///
65/// Accessed with `Symbol.GlobalAdapter` using `[]const u8` keys.
66global_symbols: std.array_hash_map.Custom(Symbol.Index, void, void, true),
67
68relocs: std.ArrayList(Reloc),
69overflowed_reloc_count: u32,
70misaligned_reloc_count: u32,
71
72imports: std.array_hash_map.Auto(Symbol.Index, void),
73fixup_chain_symbol_names_len: u32,
74got: std.array_hash_map.Auto(Symbol.Index, void),
75stubs: std.array_hash_map.Auto(Symbol.Index, void),
76
4777sections_changed_index: std.array_hash_map.Auto(Section, void),
4878
79/// The file offset of the `__got` section relative to its parent segment at the time that fixups
80/// for the GOT were last marked dirty. We track this so that when the GOT moves, we know its
81/// previous location and can invalidate those fixup pages too (to clear the old fixup entries).
82prev_got_offset: u32,
83
4984const Error = link.Error || error{MappedFileIo};
5085
5186/// Stable reference to a particular load command (or `std.macho.section_64`, despite those not
......@@ -72,6 +107,10 @@ const LoadCommand = enum(u32) {
72107/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to
73108/// refer to sections, so the limitation exists regardless.
74109const Segment = enum(u8) {
110 /// Aside from `__PAGEZERO` (which we don't assign a `Segment`), the `__TEXT` segment must come
111 /// first in memory. This segment is special: it must contain the Mach header, and some offsets
112 /// are relative to its vmaddr. So, as it's special and always exists, we give it a named tag!
113 text,
75114 _,
76115
77116 fn lc(seg: Segment, macho: *const MachO) LoadCommand {
......@@ -96,17 +135,9 @@ const Segment = enum(u8) {
96135 return macho.segments.items(.node)[@backingInt(seg)];
97136 }
98137
99 const NameAdapter = struct {
100 macho: *const MachO,
101 pub fn eql(ctx: NameAdapter, a_seg_name: []const u8, _: void, b_seg_raw: usize) bool {
102 const b_seg: Segment = @fromBackingInt(@intCast(b_seg_raw));
103 return std.mem.eql(u8, a_seg_name, b_seg.name(ctx.macho));
104 }
105 pub fn hash(ctx: NameAdapter, a_seg_name: []const u8) u32 {
106 _ = ctx;
107 return std.array_hash_map.hashString(a_seg_name);
108 }
109 };
138 fn fixupChainStartsNode(seg: Segment, macho: *const MachO) MappedFile.Node.Index.Optional {
139 return macho.segments.items(.fixup_chain_starts)[@backingInt(seg)];
140 }
110141};
111142
112143/// Stable reference to a section defined by a `std.macho.section_64` in the load commands.
......@@ -133,14 +164,26 @@ const Section = enum(u8) {
133164 return std.mem.sliceTo(&sect.lcPtrConst(macho).sectname, 0);
134165 }
135166
167 fn vaddr(sect: Section, macho: *const MachO) u64 {
168 return macho.targetLoad(&sect.lcPtrConst(macho).addr);
169 }
170
136171 fn segmentName(sect: Section, macho: *const MachO) []const u8 {
137172 return std.mem.sliceTo(&sect.lcPtrConst(macho).segname, 0);
138173 }
139174
175 fn segment(sect: Section, macho: *const MachO) Segment {
176 return macho.sections.items(.segment)[@backingInt(sect)];
177 }
178
140179 fn node(sect: Section, macho: *const MachO) MappedFile.Node.Index {
141180 return macho.sections.items(.node)[@backingInt(sect)];
142181 }
143182
183 fn alignment(sect: Section, macho: *const MachO) MappedFile.Alignment {
184 return .fromLog2Units(@intCast(macho.targetLoad(&sect.lcPtrConst(macho).@"align")));
185 }
186
144187 /// Returns this section's index in the Mach-O file. Section indices are implicitly assigned
145188 /// based on the order of load commands, so this value may change in response to segments or
146189 /// sections being added. When this value changes for a given `Section`, it is added to
......@@ -149,11 +192,6 @@ const Section = enum(u8) {
149192 return macho.sections.items(.index)[@backingInt(sect)];
150193 }
151194
152 /// Returns the head of a linked list of symbols defined relative to this section.
153 fn firstSymbol(sect: Section, macho: *MachO) *Symbol.Index.Optional {
154 return &macho.sections.items(.first_symbol)[@backingInt(sect)];
155 }
156
157195 const NameAdapter = struct {
158196 macho: *const MachO,
159197 const SegmentAndSection = struct {
......@@ -173,22 +211,450 @@ const Section = enum(u8) {
173211 return @truncate(h.final());
174212 }
175213 };
214
215 /// Matches the layout of `std.macho.section_64.flags`.
216 ///
217 /// TODO: move to `std.macho` and use as the type of `std.macho.section_64.flags`.
218 const Flags = packed struct(u32) {
219 type: enum(u8) {
220 regular = 0x00,
221 zerofill = 0x01,
222 cstring_literals = 0x02,
223 @"4byte_literals" = 0x03,
224 @"8byte_literals" = 0x04,
225 literal_pointers = 0x05,
226 non_lazy_symbol_pointers = 0x06,
227 lazy_symbol_pointers = 0x07,
228 symbol_stubs = 0x08,
229 mod_init_func_pointers = 0x09,
230 mod_term_func_pointers = 0x0a,
231 coalesced = 0x0b,
232 gb_zerofill = 0x0c,
233 interposing = 0x0d,
234 @"16byte_literals" = 0x0e,
235 dtrace_dof = 0x0f,
236 lazy_dylib_symbol_pointers = 0x10,
237 thread_local_regular = 0x11,
238 thread_local_zerofill = 0x12,
239 thread_local_variables = 0x13,
240 thread_local_variable_pointers = 0x14,
241 thread_local_init_function_pointers = 0x15,
242 init_func_offsets = 0x16,
243 _,
244 },
245 loc_reloc: bool = false,
246 ext_reloc: bool = false,
247 some_instructions: bool = false,
248 _unused: u15 = 0,
249 self_modifying_code: bool = false,
250 live_support: bool = false,
251 no_dead_strip: bool = false,
252 strip_static_syms: bool = false,
253 no_toc: bool = false,
254 pure_instructions: bool = false,
255 };
256};
257
258const InputAtom = struct {
259 node: MappedFile.Node.Index,
260 first_symbol: Symbol.Index.Optional,
261 first_reloc: Reloc.Index,
262 vaddr: u64,
263 /// `true` if there are any relocations *in* this atom which are runtime fixups.
264 ///
265 /// TODO: storing a bool here is bad for memory utilization, but there's no point in me finding
266 /// somewhere else to put this state before we finish figuring out this linker's design. It
267 /// should definitely be moved at some point though!
268 any_fixups: bool,
269
270 const Index = enum(u32) {
271 _,
272 fn ptr(iai: InputAtom.Index, macho: *MachO) *InputAtom {
273 return &macho.input_atoms.items[@backingInt(iai)];
274 }
275 fn ptrConst(iai: InputAtom.Index, macho: *const MachO) *const InputAtom {
276 return &macho.input_atoms.items[@backingInt(iai)];
277 }
278 fn node(iai: InputAtom.Index, macho: *const MachO) MappedFile.Node.Index {
279 return iai.ptrConst(macho).node;
280 }
281 };
176282};
177283
178284const Symbol = struct {
179 next_in_section: Symbol.Index.Optional,
285 node: MappedFile.Node.Index.Optional,
286 first_target_reloc: Reloc.Index.Optional,
287 prev_in_node: Symbol.Index.Optional,
288 next_in_node: Symbol.Index.Optional,
180289
181290 const Index = enum(u32) {
182291 _,
183292 fn ptr(i: Index, macho: *MachO) *Symbol {
184293 return &macho.symtab.items[@backingInt(i)];
185294 }
295 fn ptrConst(i: Index, macho: *const MachO) *const Symbol {
296 return &macho.symtab.items[@backingInt(i)];
297 }
186298 fn nlist(i: Index, macho: *MachO) *std.macho.nlist_64 {
187 return &macho.nlistSlice()[@backingInt(i)];
299 const nsyms = macho.targetLoad(
300 &macho.lc.symtab.ptrConst(macho, std.macho.symtab_command).nsyms,
301 );
302 const slice: []std.macho.nlist_64 = @ptrCast(@alignCast(
303 macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)],
304 ));
305 return &slice[@backingInt(i)];
306 }
307 fn nlistConst(i: Index, macho: *const MachO) *const std.macho.nlist_64 {
308 const nsyms = macho.targetLoad(
309 &macho.lc.symtab.ptrConst(macho, std.macho.symtab_command).nsyms,
310 );
311 const slice: []const std.macho.nlist_64 = @ptrCast(@alignCast(
312 macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)],
313 ));
314 return &slice[@backingInt(i)];
315 }
316 fn name(i: Index, macho: *const MachO) []const u8 {
317 const n_strx = macho.targetLoad(&i.nlistConst(macho).n_strx);
318 return std.mem.sliceTo(macho.node.strtab.sliceConst(&macho.mf)[n_strx..], 0);
319 }
320 fn value(i: Index, macho: *const MachO) u64 {
321 return macho.targetLoad(&i.nlistConst(macho).n_value);
322 }
323 fn flushMoved(symbol: Index, macho: *MachO, new_value: u64) Allocator.Error!void {
324 macho.targetStore(&symbol.nlist(macho).n_value, new_value);
325 try symbol.applyTargetRelocs(macho, .init(.{
326 .symbol = true,
327 .branch = true,
328 .got = false,
329 .tlv = false,
330 }));
331 }
332 fn applyTargetRelocs(
333 i: Index,
334 macho: *MachO,
335 targets: std.enums.EnumSet(Reloc.Target),
336 ) Allocator.Error!void {
337 var reloc_it = i.ptr(macho).first_target_reloc;
338 while (reloc_it.unwrap()) |ri| : (reloc_it = ri.ptr(macho).next_with_same_target_sym) {
339 if (targets.contains(ri.ptr(macho).bits.target)) {
340 try ri.ptr(macho).apply(macho);
341 }
342 }
343 }
344 const Optional = enum(u32) {
345 none = std.math.maxInt(u32),
346 _,
347 fn unwrap(o: Optional) ?Index {
348 return switch (o) {
349 .none => null,
350 _ => @fromBackingInt(@backingInt(o)),
351 };
352 }
353 fn wrap(i: Index) Optional {
354 return @bitCast(i);
355 }
356 };
357 };
358
359 const InitValue = union(enum) {
360 abs: u64,
361 addr_in_node: struct {
362 /// Must support `getNodeVAddr`, `getNodeSection`, and `nodeFirstSymbolPtr`.
363 node: MappedFile.Node.Index,
364 offset: u64,
365 },
366 };
367
368 const GlobalAdapter = struct {
369 macho: *MachO,
370 pub fn eql(ctx: GlobalAdapter, lhs_slice: []const u8, rhs_symbol: Symbol.Index, _: usize) bool {
371 const macho = ctx.macho;
372 return std.mem.eql(u8, lhs_slice, rhs_symbol.name(macho));
373 }
374 pub fn hash(ctx: GlobalAdapter, s: []const u8) u32 {
375 _ = ctx;
376 return std.array_hash_map.hashString(s);
377 }
378 };
379};
380
381const Reloc = struct {
382 node: MappedFile.Node.Index,
383 offset: u32,
384 target_symbol: Symbol.Index,
385 addend: u64,
386 bits: Bits,
387 next_with_same_target_sym: Reloc.Index.Optional,
388
389 const Bits = packed struct(u8) {
390 target: Target,
391 type: Type,
392 result: enum(u2) {
393 ok,
394 overflowed,
395 misaligned,
396 /// This is a special value indicating that this relocation is lowered to a runtime
397 /// fixup rather than being resolved statically. TODO: I'm not entirely sure if this is
398 /// a good idea, particularly since the question of "is this a runtime fixup" can, I
399 /// believe, be fully recovered based on the `Bits.target` and `Bits.type`...?
400 fixup,
401 },
402 };
403 const Target = enum(u2) {
404 symbol,
405 branch,
406 got,
407 tlv,
408 };
409 const Type = enum(u4) {
410 abs8,
411 abs16,
412 abs32,
413 abs64,
414 rel32,
415 arm64_branchrel26,
416 arm64_pagerel21,
417 arm64_pageoff12,
418
419 /// If the given relocation requires a particular machine instruction to already exist in
420 /// the destination bytes, this function checks if that requirement is upheld and returns
421 /// `false` if not.
422 ///
423 /// This function does not perform length checks on `dest_overlong`; the caller guarantees
424 /// that it will be at least long enough to support this relocation type.
425 fn validateInst(t: Type, dest_overlong: []const u8) bool {
426 switch (t) {
427 .abs8,
428 .abs16,
429 .abs32,
430 .abs64,
431 .rel32,
432 => return true,
433
434 .arm64_branchrel26 => {
435 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
436 return switch (inst.decode()) {
437 .branch_exception_generating_system => |begs| switch (begs.decode()) {
438 .unconditional_branch_immediate => true,
439 else => false,
440 },
441 else => false,
442 };
443 },
444 .arm64_pagerel21 => {
445 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
446 return switch (inst.decode()) {
447 .data_processing_immediate => |dpi| switch (dpi.decode()) {
448 .pc_relative_addressing => |pra| switch (pra.decode()) {
449 .adrp => true,
450 else => false,
451 },
452 else => false,
453 },
454 else => false,
455 };
456 },
457 .arm64_pageoff12 => {
458 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
459 return switch (inst.decode()) {
460 .data_processing_immediate => |dpi| switch (dpi.decode()) {
461 .add_subtract_immediate => true,
462 else => false,
463 },
464 .load_store => |ls| switch (ls.decode()) {
465 .register_unsigned_immediate => true,
466 else => false,
467 },
468 else => false,
469 };
470 },
471 }
472 }
473 };
474
475 fn apply(reloc: *Reloc, macho: *MachO) Allocator.Error!void {
476 // Don't bother applying the reloc if the bytes we're writing to have a pending move event:
477 if (reloc.node.hasMoved(&macho.mf)) {
478 return;
479 }
480
481 // Also don't bother if a pending move will change the target value:
482 {
483 const target_node: MappedFile.Node.Index.Optional = switch (reloc.bits.target) {
484 .symbol => reloc.target_symbol.ptr(macho).node,
485 .branch => switch (macho.stubs.contains(reloc.target_symbol)) {
486 false => reloc.target_symbol.ptr(macho).node,
487 true => .wrap(macho.known_sections.stubs.node(macho)),
488 },
489 .got => .wrap(macho.known_sections.got.node(macho)),
490 .tlv => @panic("TODO(MachO2): apply TLV relocation"),
491 };
492 if (target_node.unwrap()) |node| {
493 if (node.hasMoved(&macho.mf)) {
494 return;
495 }
496 }
497 }
498
499 switch (reloc.bits.result) {
500 .fixup => {
501 const segment = macho.getNodeSection(reloc.node).segment(macho);
502 const offset_in_segment: u64 = reloc.node.fileLocation(&macho.mf, false).offset -
503 segment.node(macho).fileLocation(&macho.mf, false).offset +
504 reloc.offset;
505 const page_align = macho.targetPageAlign();
506 const page_start: u32 = @intCast(@divFloor(offset_in_segment, page_align.toByteUnits()));
507 const page_end: u32 = @intCast(@divCeil(offset_in_segment + 8, page_align.toByteUnits()));
508 for (page_start..page_end) |page_index| {
509 try macho.dirty_fixup_chain_pages.put(macho.base.comp.gpa, .{
510 .seg = segment,
511 .page = @intCast(page_index),
512 }, {});
513 }
514 return;
515 },
516 .ok => {},
517 .overflowed => macho.overflowed_reloc_count -= 1,
518 .misaligned => macho.misaligned_reloc_count -= 1,
519 }
520
521 if (reloc.applyInner(macho)) {
522 @branchHint(.likely);
523 reloc.bits.result = .ok;
524 } else |err| switch (err) {
525 error.RelocationOverflow => {
526 log.debug("relocation failed (overflow)", .{});
527 reloc.bits.result = .overflowed;
528 macho.overflowed_reloc_count += 1;
529 },
530 error.RelocationMisaligned => {
531 log.debug("relocation failed (misaligned)", .{});
532 reloc.bits.result = .misaligned;
533 macho.misaligned_reloc_count += 1;
534 },
535 }
536 }
537 fn applyInner(r: Reloc, macho: *MachO) error{ RelocationMisaligned, RelocationOverflow }!void {
538 log.debug("applying relocation: nodes[{d}][{d}..] = {t}({t}({q}) + {d})", .{
539 r.node, r.offset, r.bits.type, r.bits.target, r.target_symbol.name(macho), r.addend,
540 });
541
542 const target_endian = macho.targetEndian();
543
544 const dest_slice = r.node.slice(&macho.mf)[r.offset..];
545 const dest_vaddr: u64 = macho.getNodeVAddr(r.node) + r.offset;
546
547 const target_val: u64 = switch (r.bits.target) {
548 .symbol => r.target_symbol.value(macho) +% r.addend,
549 .branch => if (macho.stubs.getIndex(r.target_symbol)) |stub_index| val: {
550 const stub_offset = @as(u64, stub_index) * macho.targetStubSize();
551 const stub_vaddr = macho.known_sections.stubs.vaddr(macho) + stub_offset;
552 break :val stub_vaddr +% r.addend;
553 } else r.target_symbol.value(macho) +% r.addend,
554 .got => val: {
555 const got_entry_index = macho.got.getIndex(r.target_symbol).?;
556 const got_entry_offset = got_entry_index * macho.targetPtrSize();
557 const got_entry_vaddr = macho.known_sections.got.vaddr(macho) + got_entry_offset;
558 break :val got_entry_vaddr +% r.addend;
559 },
560 .tlv => @panic("TODO(MachO2): apply TLV relocation"),
561 };
562
563 switch (r.bits.type) {
564 .abs8 => std.mem.writeInt(
565 u8,
566 dest_slice[0..1],
567 std.math.cast(u8, target_val) orelse return error.RelocationOverflow,
568 target_endian,
569 ),
570 .abs16 => std.mem.writeInt(
571 u16,
572 dest_slice[0..2],
573 std.math.cast(u16, target_val) orelse return error.RelocationOverflow,
574 target_endian,
575 ),
576 .abs32 => std.mem.writeInt(
577 u32,
578 dest_slice[0..4],
579 std.math.cast(u32, target_val) orelse return error.RelocationOverflow,
580 target_endian,
581 ),
582 .abs64 => std.mem.writeInt(
583 u64,
584 dest_slice[0..8],
585 std.math.cast(u64, target_val) orelse return error.RelocationOverflow,
586 target_endian,
587 ),
588 .rel32 => std.mem.writeInt(
589 i32,
590 dest_slice[0..4],
591 std.math.cast(
592 i32,
593 @as(i64, @bitCast(target_val -% dest_vaddr)),
594 ) orelse return error.RelocationOverflow,
595 target_endian,
596 ),
597 .arm64_branchrel26 => {
598 const off: i64 = @bitCast(target_val -% dest_vaddr);
599 const shifted: i64 = off >> 2;
600 if (shifted << 2 != off) {
601 return error.RelocationMisaligned;
602 }
603 const casted = std.math.cast(i26, shifted) orelse return error.RelocationOverflow;
604
605 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
606 inst.branch_exception_generating_system.unconditional_branch_immediate.group.imm26 = casted;
607 inst.write(dest_slice[0..4]);
608 },
609 .arm64_pagerel21 => {
610 const cur_page: u53 = @intCast(dest_vaddr >> 12);
611 const target_page: u53 = @intCast(target_val >> 12);
612 const page_off: i53 = @bitCast(target_page -% cur_page);
613 const split: packed struct(i21) { lo: u2, hi: i19 } = @bitCast(
614 std.math.cast(i21, page_off) orelse return error.RelocationOverflow,
615 );
616
617 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
618 inst.data_processing_immediate.pc_relative_addressing.adrp.immhi = split.hi;
619 inst.data_processing_immediate.pc_relative_addressing.adrp.immlo = split.lo;
620 inst.write(dest_slice[0..4]);
621 },
622 .arm64_pageoff12 => {
623 const page_off: u12 = @truncate(target_val);
624
625 // This could be one of several instructions: an `ADD`, an `LDR`, or an `STR`. The
626 // `LDR` and `STR` instructions have scale factors which we need to compensate for,
627 // so we need to decode the instruction to figure out which case we're in.
628 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
629 switch (inst.decode()) {
630 .data_processing_immediate => {
631 inst.data_processing_immediate.add_subtract_immediate.group.imm12 = page_off;
632 },
633 .load_store => |ls| {
634 const scale: u3 = switch (ls.register_unsigned_immediate.decode()) {
635 .integer => |i| @backingInt(i.group.size), // 1/2/4/8-byte load/store
636 .vector => |v| @backingInt(v.group.opc1.decode(v.group.size)), // 1/2/4/8/16-byte vector load/store
637 };
638 const shifted = page_off >> scale;
639 if (shifted << scale != page_off) {
640 return error.RelocationMisaligned;
641 }
642 inst.load_store.register_unsigned_immediate.group.imm12 = shifted;
643 },
644 else => unreachable,
645 }
646 inst.write(dest_slice[0..4]);
647 },
188648 }
649 }
650
651 const Index = enum(u32) {
652 _,
653
189654 const Optional = enum(u32) {
190655 none = std.math.maxInt(u32),
191656 _,
657
192658 fn unwrap(o: Optional) ?Index {
193659 return switch (o) {
194660 .none => null,
......@@ -199,6 +665,10 @@ const Symbol = struct {
199665 return @bitCast(i);
200666 }
201667 };
668
669 fn ptr(ri: Reloc.Index, macho: *MachO) *Reloc {
670 return &macho.relocs.items[@backingInt(ri)];
671 }
202672 };
203673};
204674
......@@ -210,8 +680,25 @@ const Node = union(enum) {
210680 symtab,
211681 strtab,
212682
683 indirect_syms,
684 section_indirect_syms: Section,
685
686 linkedit_data: LoadCommand,
687
688 /// Header of the `LC_DYLD_CHAINED_FIXUPS` load command's `.linkedit_data` node, containing the
689 /// `std.macho.dyld_chained_fixups_header` struct.
690 fixup_chains_header,
691 fixup_chains_imports,
692 fixup_chains_symbol_names,
693 fixup_chains_starts,
694 fixup_chains_starts_in_image,
695 fixup_chains_starts_in_segment: Segment,
696
213697 segment: Segment,
214698 section: Section,
699 section_manual_size: Section,
700
701 input_atom: InputAtom.Index,
215702};
216703fn getNode(macho: *const MachO, ni: MappedFile.Node.Index) Node {
217704 return macho.nodes.get(@backingInt(ni));
......@@ -275,17 +762,34 @@ fn create(
275762 .offsets = .empty,
276763 .pagezero = undefined,
277764 .symtab = undefined,
765 .dysymtab = undefined,
278766 .entry_point = null,
279767 },
768 .known_sections = undefined,
280769 .segments = .empty,
281770 .sections = .empty,
282 .segments_by_name = .empty,
283771 .sections_by_name = .empty,
284772
773 .dirty_fixup_chain_pages = .empty,
774
775 .input_atoms = .empty,
776
285777 .strtab = .empty,
286778 .symtab = .empty,
779 .global_symbols = .empty,
780
781 .relocs = .empty,
782 .overflowed_reloc_count = 0,
783 .misaligned_reloc_count = 0,
784
785 .fixup_chain_symbol_names_len = 0,
786 .imports = .empty,
787 .got = .empty,
788 .stubs = .empty,
287789
288790 .sections_changed_index = .empty,
791
792 .prev_got_offset = 0,
289793 };
290794 errdefer macho.deinit();
291795
......@@ -315,7 +819,7 @@ fn initInner(macho: *MachO) !void {
315819 .moved = true, // required by `addSegment`
316820 .bubbles_moved = false,
317821 });
318 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
822 try macho.nodes.append(gpa, .{ .segment = undefined });
319823
320824 macho.node.header = try text_seg_node.addOnlyHeaderChild(gpa, &macho.mf, .{
321825 .size = @sizeOf(mach_header_64),
......@@ -358,7 +862,7 @@ fn initInner(macho: *MachO) !void {
358862 .moved = true, // required by `addSegment`
359863 .bubbles_moved = false,
360864 });
361 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
865 try macho.nodes.append(gpa, .{ .segment = undefined });
362866
363867 // The "__PAGEZERO" segment must come before any other segment load commands. It is inaccessible
364868 // and occupies 4 GiB (apparently to catch invalid 32-bit pointer accesses).
......@@ -374,38 +878,83 @@ fn initInner(macho: *MachO) !void {
374878 .nsects = 0,
375879 .flags = 0,
376880 });
377 const text_seg = try macho.addSegment(.{
881 assert(try macho.addSegment(.{
378882 .name = "__TEXT",
379883 .prot = .{ .READ = true, .EXEC = true },
380884 .existing_node = text_seg_node,
381 });
885 .flags = .{},
886 }) == .text);
382887 const data_seg = try macho.addSegment(.{
383888 .name = "__DATA",
384889 .prot = .{ .READ = true, .WRITE = true },
890 .flags = .{},
385891 });
892 // TODO: we should also create a `__DATA_CONST` segment with `.{ .flags = .read_only = true }`,
893 // and when we do, `loadObject` should use it for `InputSection.Usage.data_const_fixups`.
894 // Unfortunately, doing that now introduces a second floating segment node, which reveals that
895 // dyld has an annoying ordering requirement:
896 //
897 // dyld[77415]: segment '__DATA' file offset out of order
898 //
899 // We need to decide the best way to resolve this. Reordering the segments in the load commands
900 // and in virtual memory is probably too expensive to risk doing often, so we need to preserve
901 // the segment order somehow, e.g. by having all but one segment be a header or footer.
386902 const linkedit_seg = try macho.addSegment(.{
387903 .name = "__LINKEDIT",
388904 .prot = .{ .READ = true },
389905 .existing_node = linkedit_seg_node,
906 .flags = .{},
390907 });
391908
392909 _ = try macho.addSection(.{
393 .segment = text_seg,
910 .segment = .text,
394911 .name = "__text",
395912 .@"align" = .@"4",
396 .flags = .{ .some_instructions = true },
913 .flags = .{ .type = .regular, .some_instructions = true },
397914 });
398915 _ = try macho.addSection(.{
399 .segment = text_seg,
916 .segment = .text,
400917 .name = "__const",
401 .@"align" = .@"2",
402 .flags = .{},
918 .@"align" = .@"1",
919 .flags = .{
920 .type = .regular,
921 },
403922 });
404923 _ = try macho.addSection(.{
405924 .segment = data_seg,
406925 .name = "__data",
407 .@"align" = .@"2",
408 .flags = .{},
926 .@"align" = .@"1",
927 .flags = .{
928 .type = .regular,
929 },
930 });
931
932 macho.known_sections.got = try macho.addSection(.{
933 .segment = data_seg,
934 .name = "__got",
935 .@"align" = .@"8",
936 .manual_size = true,
937 .flags = .{ .type = .non_lazy_symbol_pointers },
938 // This field holds an offset into the "indirect symbol table" at which this section's
939 // indirect symbol references begin. It will be populated by `flushMoved` of a
940 // `.section_indirect_syms` node we'll create shortly.
941 .reserved1 = 0,
942 });
943
944 macho.known_sections.stubs = try macho.addSection(.{
945 .segment = .text,
946 .name = "__stubs",
947 .@"align" = .@"4",
948 .manual_size = true,
949 .flags = .{
950 .type = .symbol_stubs,
951 .some_instructions = true,
952 .pure_instructions = true,
953 },
954 // As with `__got`, will be populated by `flushMoved` of a `.section_indirect_syms` node.
955 .reserved1 = 0,
956 // `reserved2` holds the size of each stub entry.
957 .reserved2 = macho.targetStubSize(),
409958 });
410959
411960 macho.node.symtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
......@@ -423,7 +972,129 @@ fn initInner(macho: *MachO) !void {
423972 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, 1);
424973 macho.node.strtab.slice(&macho.mf)[0] = 0;
425974
426 _ = try macho.appendSimpleLoadCommand(std.macho.dyld_info_command, .{});
975 const indirect_syms_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
976 .alignment = .@"4",
977 .moved = true, // so that `indirectsymoff` in the dysymtab load command will be set
978 });
979 try macho.nodes.append(gpa, .indirect_syms);
980
981 macho.node.got_indirect_syms = try indirect_syms_node.addFloatingChild(gpa, &macho.mf, .{
982 .alignment = .@"4",
983 .moved = true, // so that `reserved1` in the section will be set
984 });
985 try macho.nodes.append(gpa, .{ .section_indirect_syms = macho.known_sections.got });
986
987 macho.node.stubs_indirect_syms = try indirect_syms_node.addFloatingChild(gpa, &macho.mf, .{
988 .alignment = .@"4",
989 .moved = true, // so that `reserved1` in the section will be set
990 });
991 try macho.nodes.append(gpa, .{ .section_indirect_syms = macho.known_sections.stubs });
992
993 const chained_fixups_lc = try macho.appendSimpleLoadCommand(std.macho.linkedit_data_command, .{
994 .cmd = .DYLD_CHAINED_FIXUPS,
995 .dataoff = 0,
996 .datasize = 0,
997 });
998 const chained_fixups_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
999 .alignment = macho.mf.flags.block_size.max(.@"8"),
1000 .moved = true, // so that `dataoff` will be set
1001 .bubbles_moved = false,
1002 });
1003 try macho.nodes.append(gpa, .{ .linkedit_data = chained_fixups_lc });
1004
1005 macho.node.fixup_chains_header = try chained_fixups_node.addOnlyHeaderChild(gpa, &macho.mf, .{
1006 .size = @sizeOf(std.macho.dyld_chained_fixups_header),
1007 .alignment = .@"4",
1008 });
1009 try macho.nodes.append(gpa, .fixup_chains_header);
1010
1011 {
1012 const chained_fixups_hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
1013 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
1014 ));
1015 chained_fixups_hdr.* = .{
1016 .fixups_version = 0,
1017 .starts_offset = 0,
1018 .imports_offset = 0,
1019 .symbols_offset = 0,
1020 .imports_count = 0,
1021 .imports_format = .addend_64,
1022 .symbols_format = .uncompressed,
1023 };
1024 if (macho.targetEndian() != std.lang.Endian.native) {
1025 std.mem.byteSwapAllFields(std.macho.dyld_chained_fixups_header, chained_fixups_hdr);
1026 }
1027 }
1028
1029 // We are now going to create the `std.macho.dyld_chained_starts_in_segment` nodes for every
1030 // segment, which means we are assuming that no more segments will be created. Therefore, all
1031 // `addSegment` calls must come *before* this point.
1032
1033 const fixup_chains_starts = try chained_fixups_node.addFloatingChild(gpa, &macho.mf, .{
1034 .size = 0,
1035 .alignment = .@"8",
1036 .moved = true, // so that `starts_offset` is populated
1037 });
1038 try macho.nodes.append(gpa, .fixup_chains_starts);
1039
1040 macho.node.fixup_chains_starts_in_image = try fixup_chains_starts.addOnlyHeaderChild(gpa, &macho.mf, .{
1041 .size = 8 + 4 * macho.segments.len,
1042 .alignment = .@"4",
1043 });
1044 try macho.nodes.append(gpa, .fixup_chains_starts_in_image);
1045
1046 {
1047 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
1048 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
1049 ));
1050 chained_starts_in_image[0] = @intCast(macho.segments.len + 1); // +1 for `__PAGEZERO`
1051 chained_starts_in_image[1] = 0; // `__PAGEZERO`
1052 chained_starts_in_image[macho.segments.len + 1] = 0; // `__LINKEDIT`
1053 for (
1054 macho.segments.items(.fixup_chain_starts)[0 .. macho.segments.len - 1],
1055 0..,
1056 ) |*starts_in_segment, segment_usize| {
1057 const segment: Segment = @fromBackingInt(@intCast(segment_usize));
1058 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1059 starts_in_segment.* = .wrap(try fixup_chains_starts.addFloatingChild(gpa, &macho.mf, .{
1060 .alignment = .@"8",
1061 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1062 .moved = true,
1063 }));
1064 try macho.nodes.append(gpa, .{ .fixup_chains_starts_in_segment = segment });
1065 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1066 starts_in_segment.unwrap().?.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1067 ));
1068 starts_hdr.* = .{
1069 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1070 .page_size = @intCast(macho.targetPageAlign().toByteUnits()),
1071 .pointer_format = .@"64",
1072 .segment_offset = 0, // populated by `flushMoved` of the corresponding segment
1073 .max_valid_pointer = 0,
1074 .page_count = 0,
1075 };
1076 if (macho.targetEndian() != std.lang.Endian.native) {
1077 std.mem.byteSwapAllFields(std.macho.dyld_chained_starts_in_segment, starts_hdr);
1078 }
1079 }
1080 }
1081
1082 // This is a footer because dyld has an undocumented requirement that the imports must be
1083 // ordered after the starts.
1084 macho.node.fixup_chains_imports = try chained_fixups_node.addOnlyFooterChild(gpa, &macho.mf, .{
1085 .alignment = .@"8",
1086 .moved = true, // so that `imports_offset` is populated
1087 });
1088 try macho.nodes.append(gpa, .fixup_chains_imports);
1089
1090 // This is a footer because `dyld_info` complains if the imports appear after the symbol name
1091 // string table. Since there's no length associated with that string table, `dyld_info` can only
1092 // be sure they don't overlap if the string table is the last thing in the chained fixups data.
1093 macho.node.fixup_chains_symbol_names = try chained_fixups_node.addFooterChildBefore(gpa, &macho.mf, .none, .{
1094 .alignment = .@"8", // so that the alignment of `macho.node.fixup_chains_imports` is not violated
1095 .moved = true, // so that `symbols_offset` is populated
1096 });
1097 try macho.nodes.append(gpa, .fixup_chains_symbol_names);
4271098
4281099 macho.lc.symtab = try macho.appendSimpleLoadCommand(std.macho.symtab_command, .{
4291100 .symoff = 0,
......@@ -432,6 +1103,48 @@ fn initInner(macho: *MachO) !void {
4321103 .strsize = 1, // empty string (null terminator)
4331104 });
4341105
1106 // Nowadays, `LC_DYSYMTAB` is redundant with chained fixups and the symbol export trie. However,
1107 // if we omit it, some macOS tooling does get annoyed at us. Therefore, we try to emit a minimum
1108 // viable `LC_DYSYMTAB` command, just to keep the tooling happy.
1109 macho.lc.dysymtab = try macho.appendSimpleLoadCommand(std.macho.dysymtab_command, .{
1110 // Some tooling will check that the number of symbols according to the `LC_DYSYMTAB` command
1111 // matches the actual length of the symbol table in `LC_SYMTAB`. However, we don't want to
1112 // need to partition our symbol table into local/extdef/undef symbols, because that makes
1113 // incremental linking less efficient. Luckily, since this information is basically useless
1114 // nowadays, nothing seems to mind if we just say that every symbol is local. So, whenever
1115 // we add any symbol to the symtab, we will increase `ilocalsym` in lockstep with the symbol
1116 // count. We also need to increment `iextdefsym` and `iundefsym`, because some tooling may
1117 // check that local symbols come before extdef and undef symbols here. I don't even know why
1118 // they *have* the `iextdefsym` and `iundefsym` fields if they're only going to allow them
1119 // to have specific values...
1120 .ilocalsym = 0, // always 0
1121 .nlocalsym = 0, // equal to symbol count
1122 .iextdefsym = 0, // equal to symbol count
1123 .nextdefsym = 0, // always 0
1124 .iundefsym = 0, // equal to symbol count
1125 .nundefsym = 0, // always 0
1126
1127 // We *do* need to emit a valid indirect symbol table, which is an array of symbol indices
1128 // indicating which symbols are being targeted by entries in `__got` and `__stubs`. Although
1129 // not required for fixups, some debug tooling will still try to read this table without any
1130 // validation, so will just crash if it's absent or invalid. Luckily, the indirection here
1131 // actually makes this pretty straightforward for us to emit.
1132 .indirectsymoff = 0, // populated by `flushMachOFileOffset` of the `.indirect_syms` node
1133 .nindirectsyms = 0, // populated by `flushResized` of the `.indirect_syms` node
1134
1135 // These fields are *required* to be zeroed in modern Mach-O binaries.
1136 .tocoff = 0,
1137 .ntoc = 0,
1138 .modtaboff = 0,
1139 .nmodtab = 0,
1140 .extrefsymoff = 0,
1141 .nextrefsyms = 0,
1142 .extreloff = 0,
1143 .nextrel = 0,
1144 .locreloff = 0,
1145 .nlocrel = 0,
1146 });
1147
4351148 const want_dyld_path: bool = switch (comp.config.output_mode) {
4361149 .Obj => false,
4371150 .Lib => switch (comp.config.link_mode) {
......@@ -454,58 +1167,84 @@ fn initInner(macho: *MachO) !void {
4541167 }
4551168 }
4561169
457 switch (comp.config.output_mode) {
458 .Obj, .Lib => {},
459 .Exe => macho.lc.entry_point = try macho.appendSimpleLoadCommand(std.macho.entry_point_command, .{
460 .entryoff = 0,
461 .stacksize = macho.options.stack_size orelse 0,
462 }),
1170 entry: switch (macho.options.entry) {
1171 .default => switch (comp.config.output_mode) {
1172 .Exe => continue :entry .enabled,
1173 .Lib, .Obj => continue :entry .disabled,
1174 },
1175 .disabled => {},
1176 .enabled, .named => {
1177 macho.lc.entry_point = try macho.appendSimpleLoadCommand(
1178 std.macho.entry_point_command,
1179 .{ .entryoff = 0, .stacksize = macho.options.stack_size orelse 0 },
1180 );
1181 },
4631182 }
4641183
4651184 _ = try macho.appendSimpleLoadCommand(std.macho.uuid_command, .{
4661185 .uuid = @splat(0),
4671186 });
4681187
469 try macho.addSimpleSymbol("some_function", 0x1234, "__TEXT", "__text");
470 try macho.addSimpleSymbol("my_cool_data", 0x5678, "__DATA", "__data");
471
4721188 try macho.updateSegmentLoadAddresses();
4731189}
474/// TODO: this function doesn't actually do anything particularly useful, it's just a placeholder
475/// for actual symbol creation logic so that we can test adding stuff to the symtab.
476fn addSimpleSymbol(
477 macho: *MachO,
1190fn addInternalSymbol(macho: *MachO, opts: struct {
4781191 name: []const u8,
479 value: u64,
480 segment_name: []const u8,
481 section_name: []const u8,
482) Error!void {
1192 value: Symbol.InitValue,
1193}) Error!Symbol.Index {
4831194 const gpa = macho.base.comp.gpa;
1195
4841196 try macho.symtab.ensureUnusedCapacity(gpa, 1);
4851197 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
4861198
487 const strx = try macho.string(name);
488 const section = macho.sectionByName(segment_name, section_name).?;
1199 const strx = try macho.string(opts.name);
4891200
4901201 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
4911202 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
4921203 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
4931204
1205 {
1206 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1207 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1208 // on why we're doing this.
1209 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1210 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1211 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1212 }
1213
4941214 const sym_index: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
4951215 macho.symtab.appendAssumeCapacity(.{
496 .next_in_section = section.firstSymbol(macho).*,
1216 .node = switch (opts.value) {
1217 .abs => .none,
1218 .addr_in_node => |addr| .wrap(addr.node),
1219 },
1220 .prev_in_node = .none,
1221 .next_in_node = .none,
1222 .first_target_reloc = .none,
4971223 });
498 section.firstSymbol(macho).* = .wrap(sym_index);
1224 if (sym_index.ptr(macho).node.unwrap()) |node| {
1225 const head_ptr = macho.nodeFirstSymbolPtr(node);
1226 sym_index.ptr(macho).next_in_node = head_ptr.*;
1227 if (head_ptr.*.unwrap()) |next_symbol| {
1228 next_symbol.ptr(macho).prev_in_node = .wrap(sym_index);
1229 }
1230 head_ptr.* = .wrap(sym_index);
1231 }
4991232
5001233 sym_index.nlist(macho).* = .{
5011234 .n_strx = @backingInt(strx),
5021235 .n_type = .{ .bits = .{
503 .ext = true,
504 .type = .sect,
1236 .ext = false,
1237 .type = switch (opts.value) {
1238 .abs => .abs,
1239 .addr_in_node => .sect,
1240 },
5051241 .pext = false,
5061242 .is_stab = 0,
5071243 } },
508 .n_sect = section.index(macho),
1244 .n_sect = switch (opts.value) {
1245 .abs => 0,
1246 .addr_in_node => |addr| macho.getNodeSection(addr.node).index(macho),
1247 },
5091248 .n_desc = .{
5101249 .arm_thumb_def = false,
5111250 .referenced_dynamically = false,
......@@ -515,54 +1254,314 @@ fn addSimpleSymbol(
5151254 .symbol_resolver = false,
5161255 .alt_entry = false,
5171256 },
518 .n_value = value,
1257 .n_value = switch (opts.value) {
1258 .abs => |x| x,
1259 .addr_in_node => |addr| macho.getNodeVAddr(addr.node) + addr.offset,
1260 },
5191261 };
5201262 if (macho.targetEndian() != std.lang.Endian.native) {
5211263 std.mem.byteSwapAllFields(std.macho.nlist_64, sym_index.nlist(macho));
5221264 }
523}
5241265
525fn addSegment(macho: *MachO, opts: struct {
1266 return sym_index;
1267}
1268fn addGlobalSymbol(macho: *MachO, opts: struct {
5261269 name: []const u8,
527 prot: std.macho.vm_prot_t,
528 /// The caller is allowed to create the section's node themselves, in which case it is passed in
529 /// here. The caller must initialize the `MachO.nodes` entry to `.{ .segment = undefined }`, and
530 /// the node must be created with the option `.moved = true`.
531 existing_node: ?MappedFile.Node.Index = null,
532}) Error!Segment {
1270 linkage: std.lang.GlobalLinkage,
1271 visibility: enum { default, hidden },
1272 /// `null` means the symbol is undefined.
1273 value: ?Symbol.InitValue,
1274}) (Error || error{MultipleDefinitions})!Symbol.Index {
5331275 const gpa = macho.base.comp.gpa;
5341276
535 const segment_command_64 = std.macho.segment_command_64;
1277 try macho.symtab.ensureUnusedCapacity(gpa, 1);
1278 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
5361279
537 // We limit the number of segments to 255, because there can only be 255 *sections*, so there's
538 // no need to allow more. See corresponding check in `addSection`.
539 if (macho.segments.len == 255) {
540 return macho.base.comp.link_diags.fail("maximum segment count exceeded", .{});
1280 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1281 const gop = try macho.global_symbols.getOrPutAdapted(gpa, opts.name, adapter);
1282 if (gop.found_existing) {
1283 const symbol = gop.key_ptr.*;
1284
1285 switch (opts.visibility) {
1286 .default => {},
1287 .hidden => {
1288 var n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1289 n_type.bits.pext = true;
1290 switch (macho.machFiletype()) {
1291 .OBJECT => {},
1292 .EXECUTE, .DYLIB => n_type.bits.ext = false,
1293 }
1294 macho.targetStore(&symbol.nlist(macho).n_type, n_type);
1295 // TODO: once the exports trie is implemented, remove `symbol` from it here
1296 },
1297 }
1298
1299 const existing_defined: bool, const existing_linkage: std.lang.GlobalLinkage = existing: {
1300 const n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1301 const n_desc = macho.targetLoad(&symbol.nlist(macho).n_desc);
1302 break :existing switch (n_type.bits.type) {
1303 .undf => .{ false, if (n_desc.weak_ref) .weak else .strong },
1304 else => .{ true, if (n_desc.weak_def_or_ref_to_weak) .weak else .strong },
1305 };
1306 };
1307
1308 const override: bool = if (opts.value != null) override: {
1309 if (!existing_defined) break :override true;
1310 // Two definitions of the same name!
1311 break :override switch (opts.linkage) {
1312 .strong => switch (existing_linkage) {
1313 .strong => return error.MultipleDefinitions,
1314 .weak => true,
1315 },
1316 .weak => false,
1317 };
1318 } else override: {
1319 if (existing_defined) break :override false;
1320 break :override switch (opts.linkage) {
1321 .strong => switch (existing_linkage) {
1322 .strong => false,
1323 .weak => true,
1324 },
1325 .weak => false,
1326 };
1327 };
1328
1329 if (override) {
1330 try macho.setGlobalSymbolValue(symbol, .{
1331 .linkage = opts.linkage,
1332 .value = opts.value,
1333 });
1334 }
1335
1336 return symbol;
5411337 }
1338 errdefer _ = macho.global_symbols.pop().?;
5421339
543 try macho.nodes.ensureUnusedCapacity(gpa, 1);
544 try macho.segments.ensureUnusedCapacity(gpa, 1);
545 try macho.segments_by_name.ensureUnusedCapacity(gpa, 1);
1340 const strx = try macho.string(opts.name);
5461341
547 const root_ni: MappedFile.Node.Index = .root;
1342 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1343 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
1344 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
5481345
549 const lc = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
550 .cmdsize = @sizeOf(segment_command_64),
551 .segname = name: {
552 var name: [16]u8 = undefined;
553 @memcpy(name[0..opts.name.len], opts.name);
554 @memset(name[opts.name.len..], 0);
555 break :name name;
556 },
557 .vmaddr = 0,
558 .vmsize = 0,
559 .fileoff = 0, // to be set by `flushMoved`
560 .filesize = 0,
561 .maxprot = opts.prot,
562 .initprot = opts.prot,
563 .nsects = 0,
564 .flags = 0,
565 });
1346 {
1347 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1348 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1349 // on why we're doing this.
1350 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1351 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1352 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1353 }
1354
1355 const symbol: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
1356 gop.key_ptr.* = symbol;
1357 macho.symtab.appendAssumeCapacity(.{
1358 .node = .none,
1359 .prev_in_node = .none,
1360 .next_in_node = .none,
1361 .first_target_reloc = .none,
1362 });
1363 symbol.nlist(macho).* = .{
1364 .n_strx = @backingInt(strx),
1365 .n_type = .{ .bits = .{
1366 .ext = switch (macho.machFiletype()) {
1367 .OBJECT => true,
1368 .EXECUTE, .DYLIB => switch (opts.visibility) {
1369 .default => true,
1370 .hidden => false,
1371 },
1372 },
1373 .pext = switch (opts.visibility) {
1374 .default => false,
1375 .hidden => true,
1376 },
1377 .type = .undf,
1378 .is_stab = 0,
1379 } },
1380 .n_sect = 0,
1381 .n_desc = .{
1382 .arm_thumb_def = false,
1383 .referenced_dynamically = false,
1384 .discarded_or_no_dead_strip = false,
1385 .weak_ref = false,
1386 .weak_def_or_ref_to_weak = false,
1387 .symbol_resolver = false,
1388 .alt_entry = false,
1389 },
1390 .n_value = 0,
1391 };
1392 if (macho.targetEndian() != std.lang.Endian.native) {
1393 std.mem.byteSwapAllFields(std.macho.nlist_64, symbol.nlist(macho));
1394 }
1395
1396 try macho.setGlobalSymbolValue(symbol, .{
1397 .linkage = opts.linkage,
1398 .value = opts.value,
1399 });
1400
1401 return symbol;
1402}
1403fn setGlobalSymbolValue(macho: *MachO, symbol: Symbol.Index, opts: struct {
1404 linkage: std.lang.GlobalLinkage,
1405 value: ?Symbol.InitValue,
1406}) Allocator.Error!void {
1407 const gpa = macho.base.comp.gpa;
1408
1409 log.debug("setGlobalSymbolValue({q}, {?any})", .{ symbol.name(macho), opts.value });
1410
1411 const nlist = symbol.nlist(macho);
1412
1413 {
1414 const weak: bool = switch (opts.linkage) {
1415 .strong => false,
1416 .weak => true,
1417 };
1418 var n_desc = macho.targetLoad(&nlist.n_desc);
1419 n_desc.weak_ref = opts.value == null and weak;
1420 n_desc.weak_def_or_ref_to_weak = opts.value != null and weak;
1421 macho.targetStore(&nlist.n_desc, n_desc);
1422 }
1423
1424 // Remove this symbol from its old node.
1425 if (symbol.ptr(macho).node.unwrap()) |old_node| {
1426 if (symbol.ptr(macho).next_in_node.unwrap()) |next_symbol| {
1427 assert(next_symbol.ptr(macho).prev_in_node == Symbol.Index.Optional.wrap(symbol));
1428 next_symbol.ptr(macho).prev_in_node = symbol.ptr(macho).prev_in_node;
1429 }
1430 if (symbol.ptr(macho).prev_in_node.unwrap()) |prev_symbol| {
1431 assert(prev_symbol.ptr(macho).next_in_node == Symbol.Index.Optional.wrap(symbol));
1432 prev_symbol.ptr(macho).next_in_node = symbol.ptr(macho).next_in_node;
1433 } else {
1434 // We're the head of the list.
1435 macho.nodeFirstSymbolPtr(old_node).* = symbol.ptr(macho).next_in_node;
1436 }
1437 }
1438
1439 if (opts.value) |value| switch (value) {
1440 .abs => |val| {
1441 var n_type = macho.targetLoad(&nlist.n_type);
1442 n_type.bits.type = .abs;
1443 macho.targetStore(&nlist.n_type, n_type);
1444
1445 macho.targetStore(&nlist.n_sect, 0);
1446 macho.targetStore(&nlist.n_value, val);
1447
1448 symbol.ptr(macho).node = .none;
1449 symbol.ptr(macho).prev_in_node = .none;
1450 symbol.ptr(macho).next_in_node = .none;
1451 },
1452 .addr_in_node => |addr| {
1453 var n_type = macho.targetLoad(&nlist.n_type);
1454 n_type.bits.type = .sect;
1455 macho.targetStore(&nlist.n_type, n_type);
1456
1457 macho.targetStore(&nlist.n_sect, macho.getNodeSection(addr.node).index(macho));
1458 macho.targetStore(&nlist.n_value, macho.getNodeVAddr(addr.node) + addr.offset);
1459
1460 const head_ptr = macho.nodeFirstSymbolPtr(addr.node);
1461 symbol.ptr(macho).node = .wrap(addr.node);
1462 symbol.ptr(macho).prev_in_node = .none;
1463 symbol.ptr(macho).next_in_node = head_ptr.*;
1464 if (head_ptr.*.unwrap()) |next_symbol| {
1465 next_symbol.ptr(macho).prev_in_node = .wrap(symbol);
1466 }
1467 head_ptr.* = .wrap(symbol);
1468 },
1469 } else {
1470 var n_type = macho.targetLoad(&nlist.n_type);
1471 n_type.bits.type = .undf;
1472 macho.targetStore(&nlist.n_type, n_type);
1473
1474 macho.targetStore(&nlist.n_sect, 0);
1475 macho.targetStore(&nlist.n_value, 0);
1476
1477 symbol.ptr(macho).node = .none;
1478 symbol.ptr(macho).prev_in_node = .none;
1479 symbol.ptr(macho).next_in_node = .none;
1480 }
1481
1482 // If `symbol` has a GOT entry, it needs regenerating.
1483 if (macho.got.getIndex(symbol)) |got_index| {
1484 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
1485 const segment = macho.known_sections.got.segment(macho);
1486 const page_index: u16 = @intCast(@divFloor(
1487 macho.prev_got_offset + got_index * macho.targetPtrSize(),
1488 macho.targetPageAlign().toByteUnits(),
1489 ));
1490 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
1491 }
1492 }
1493
1494 // If `symbol` has an `imports` entry, it should not exist.
1495 if (macho.imports.getIndex(symbol)) |import_index| {
1496 // TODO: delete the import. For now we just mark it as weak so that dyld doesn't complain.
1497 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
1498 macho.node.fixup_chains_imports.slice(&macho.mf)[0 .. macho.imports.count() * @sizeOf(std.macho.dyld_chained_import_addend64)],
1499 ));
1500 macho.targetStore(&imports[import_index].bits, .{
1501 .lib_ordinal = macho.targetLoad(&imports[import_index].bits).lib_ordinal,
1502 .name_offset = macho.targetLoad(&imports[import_index].bits).name_offset,
1503 .weak_import = true,
1504 });
1505 }
1506}
1507fn globalByName(macho: *MachO, name: []const u8) Symbol.Index.Optional {
1508 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1509 if (macho.global_symbols.getKeyAdapted(name, adapter)) |symbol| {
1510 return .wrap(symbol);
1511 } else {
1512 return .none;
1513 }
1514}
1515
1516/// All calls to this function must occur in `initInner`, and must be before we initialize fixup
1517/// chains, as fixup chains requires some per-segment tracking and the logic to initialize them
1518/// assumes that no more segments will be created.
1519fn addSegment(macho: *MachO, opts: struct {
1520 name: []const u8,
1521 prot: std.macho.vm_prot_t,
1522 /// The caller is allowed to create the section's node themselves, in which case it is passed in
1523 /// here. The caller must initialize the `MachO.nodes` entry to `.{ .segment = undefined }`, and
1524 /// the node must be created with the option `.moved = true`.
1525 existing_node: ?MappedFile.Node.Index = null,
1526 /// This matches the layout of `std.macho.segment_command_64.flags`.
1527 flags: packed struct(u32) {
1528 _unused0: u4 = 0,
1529 read_only: bool = false,
1530 _unused1: u27 = 0,
1531 },
1532}) Error!Segment {
1533 const gpa = macho.base.comp.gpa;
1534
1535 const segment_command_64 = std.macho.segment_command_64;
1536
1537 // We limit the number of segments to 255, because there can only be 255 *sections*, so there's
1538 // no need to allow more. See corresponding check in `addSection`.
1539 if (macho.segments.len == 255) {
1540 return macho.base.comp.link_diags.fail("maximum segment count exceeded", .{});
1541 }
1542
1543 try macho.nodes.ensureUnusedCapacity(gpa, 1);
1544 try macho.segments.ensureUnusedCapacity(gpa, 1);
1545
1546 const root_ni: MappedFile.Node.Index = .root;
1547
1548 const lc = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
1549 .cmdsize = @sizeOf(segment_command_64),
1550 .segname = name: {
1551 var name: [16]u8 = undefined;
1552 @memcpy(name[0..opts.name.len], opts.name);
1553 @memset(name[opts.name.len..], 0);
1554 break :name name;
1555 },
1556 .vmaddr = 0,
1557 .vmsize = 0,
1558 .fileoff = 0, // to be set by `flushMoved`
1559 .filesize = 0,
1560 .maxprot = opts.prot,
1561 .initprot = opts.prot,
1562 .nsects = 0,
1563 .flags = @bitCast(opts.flags),
1564 });
5661565
5671566 const segment: Segment = @fromBackingInt(@intCast(macho.segments.len));
5681567 const segment_node: MappedFile.Node.Index = node: {
......@@ -583,17 +1582,9 @@ fn addSegment(macho: *MachO, opts: struct {
5831582 macho.segments.appendAssumeCapacity(.{
5841583 .lc = lc,
5851584 .node = segment_node,
1585 .fixup_chain_starts = .none,
5861586 });
5871587
588 {
589 const gop = macho.segments_by_name.getOrPutAssumeCapacityAdapted(
590 opts.name,
591 @as(Segment.NameAdapter, .{ .macho = macho }),
592 );
593 assert(!gop.found_existing);
594 assert(gop.index == @backingInt(segment));
595 }
596
5971588 return segment;
5981589}
5991590
......@@ -645,16 +1636,37 @@ fn string(macho: *MachO, slice: []const u8) Error!String {
6451636 return gop.key_ptr.*;
6461637}
6471638
648fn nlistSlice(macho: *MachO) []std.macho.nlist_64 {
649 const nsyms = macho.targetLoad(
650 &macho.lc.symtab.ptr(macho, std.macho.symtab_command).nsyms,
651 );
652 return @ptrCast(@alignCast(
653 macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)],
654 ));
655}
1639fn updateSegmentLoadAddresses(macho: *MachO) Error!void {
1640 const gpa = macho.base.comp.gpa;
1641
1642 // Grow all `.fixup_chains_starts_in_segment` node to the necessary length. We need to do this
1643 // before updating the load addresses because this could cause `__LINKEDIT` to grow.
1644 for (0..macho.segments.len) |segment_raw| {
1645 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
1646
1647 const starts_node = segment.fixupChainStartsNode(macho).unwrap() orelse {
1648 assert(std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1649 continue;
1650 };
1651
1652 const size = segment.node(macho).location(&macho.mf).resolve(&macho.mf)[1];
1653
1654 const page_count: u16 = @intCast(@divExact(size, macho.targetPageAlign().toByteUnits()));
1655 try starts_node.ensureMinimumSize(gpa, &macho.mf, MappedFile.Alignment.@"8".forward(
1656 @sizeOf(std.macho.dyld_chained_starts_in_segment) + page_count * 2,
1657 ));
1658 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1659 starts_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1660 ));
1661 const old_page_count = macho.targetLoad(&starts_hdr.page_count);
1662 macho.targetStore(&starts_hdr.page_count, page_count);
1663 macho.targetStore(&starts_hdr.size, @sizeOf(std.macho.dyld_chained_starts_in_segment) + 2 * page_count);
1664 const page_starts: []u16 = @ptrCast(@alignCast(
1665 starts_node.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
1666 ));
1667 @memset(page_starts[old_page_count..page_count], 0xFFFF);
1668 }
6561669
657fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6581670 // TODO: this segment load address allocation logic is bad because it moves segments quite
6591671 // frequently. We should avoid that by introducing padding between segments so that they have
6601672 // space to grow in-place. We could also consider allowing a segment which can't fit to jump
......@@ -664,6 +1676,7 @@ fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6641676 assert(macho.targetLoad(&pagezero_ptr.vmaddr) == 0);
6651677 break :start_addr macho.targetLoad(&pagezero_ptr.vmsize);
6661678 };
1679
6671680 for (0..macho.segments.len) |segment_raw| {
6681681 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
6691682
......@@ -677,7 +1690,7 @@ fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6771690 cur_addr = old_addr;
6781691 } else {
6791692 macho.targetStore(&cmd_ptr.vmaddr, cur_addr);
680 try segment.node(macho).childrenMoved(macho.base.comp.gpa, &macho.mf);
1693 try segment.node(macho).childrenMoved(gpa, &macho.mf);
6811694 }
6821695
6831696 cur_addr = macho.targetPageAlign().forward(cur_addr + size);
......@@ -761,12 +1774,10 @@ fn addSection(macho: *MachO, opts: struct {
7611774 name: []const u8,
7621775 segment: Segment,
7631776 @"align": MappedFile.Alignment,
764 /// This matches the layout of `std.macho.section_64.flags`.
765 flags: packed struct(u32) {
766 _unused0: u10 = 0,
767 some_instructions: bool = false,
768 _unused1: u21 = 0,
769 },
1777 manual_size: bool = false,
1778 flags: Section.Flags,
1779 reserved1: u32 = 0,
1780 reserved2: u32 = 0,
7701781}) Error!Section {
7711782 const gpa = macho.base.comp.gpa;
7721783
......@@ -796,14 +1807,18 @@ fn addSection(macho: *MachO, opts: struct {
7961807
7971808 const section: Section = @fromBackingInt(@intCast(macho.sections.len));
7981809 const section_node = try opts.segment.node(macho).addFloatingChild(gpa, &macho.mf, .{
799 .alignment = opts.@"align",
1810 .alignment = macho.mf.flags.block_size.max(opts.@"align"),
8001811 .moved = true, // so that `offset` will be set
8011812 });
802 macho.nodes.appendAssumeCapacity(.{ .section = section });
1813 if (opts.manual_size) {
1814 macho.nodes.appendAssumeCapacity(.{ .section_manual_size = section });
1815 } else {
1816 macho.nodes.appendAssumeCapacity(.{ .section = section });
1817 }
8031818 macho.sections.appendAssumeCapacity(.{
8041819 .lc = @fromBackingInt(@intCast(macho.lc.offsets.items.len)),
8051820 .node = section_node,
806 .first_symbol = .none,
1821 .segment = opts.segment,
8071822 .index = undefined, // populated later
8081823 });
8091824
......@@ -861,9 +1876,7 @@ fn addSection(macho: *MachO, opts: struct {
8611876 if (other_section == section) continue;
8621877 if (other_section.lc(macho).offset(macho) > offset) {
8631878 macho.sections.items(.index)[@backingInt(other_section)] += 1;
864 if (other_section.firstSymbol(macho).* != .none) {
865 macho.sections_changed_index.putAssumeCapacity(other_section, {});
866 }
1879 macho.sections_changed_index.putAssumeCapacity(other_section, {});
8671880 } else {
8681881 new_section_index += 1;
8691882 }
......@@ -890,6 +1903,8 @@ fn addSection(macho: *MachO, opts: struct {
8901903 .reloff = 0,
8911904 .nreloc = 0,
8921905 .flags = @bitCast(opts.flags),
1906 .reserved1 = opts.reserved1,
1907 .reserved2 = opts.reserved2,
8931908 };
8941909 if (macho.targetEndian() != std.lang.Endian.native) {
8951910 std.mem.byteSwapAllFields(section_64, section_cmd);
......@@ -898,15 +1913,6 @@ fn addSection(macho: *MachO, opts: struct {
8981913 return section;
8991914}
9001915
901fn segmentByName(macho: *const MachO, name: []const u8) ?Segment {
902 const adapter: Segment.NameAdapter = .{ .macho = macho };
903 if (macho.segments_by_name.getIndexAdapted(name, adapter)) |segment_raw| {
904 return @fromBackingInt(@intCast(segment_raw));
905 } else {
906 return null;
907 }
908}
909
9101916fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []const u8) ?Section {
9111917 const adapter: Section.NameAdapter = .{ .macho = macho };
9121918 const key: Section.NameAdapter.SegmentAndSection = .{
......@@ -920,13 +1926,31 @@ fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []
9201926 }
9211927}
9221928
1929fn machFiletype(macho: *const MachO) enum(u32) {
1930 OBJECT = std.macho.MH_OBJECT,
1931 DYLIB = std.macho.MH_DYLIB,
1932 EXECUTE = std.macho.MH_EXECUTE,
1933} {
1934 const hdr: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
1935 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
1936 ));
1937 return @fromBackingInt(macho.targetLoad(&hdr.filetype));
1938}
1939
9231940fn targetEndian(macho: *const MachO) std.lang.Endian {
9241941 _ = macho;
9251942 return .little;
9261943}
927fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
1944fn targetPtrSize(macho: *const MachO) u8 {
9281945 _ = macho;
929 return .fromByteUnits(0x4000);
1946 return 8;
1947}
1948fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
1949 return switch (macho.base.comp.getTarget().cpu.arch) {
1950 else => unreachable,
1951 .aarch64 => .fromByteUnits(0x4000),
1952 .x86_64 => .fromByteUnits(0x1000),
1953 };
9301954}
9311955fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
9321956 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
......@@ -936,8 +1960,8 @@ fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer
9361960 else => @compileError(@typeName(Child)),
9371961 .int => std.mem.toNative(Child, ptr.*, macho.targetEndian()),
9381962 .@"enum" => |@"enum"| @fromBackingInt(macho.targetLoad(@as(*align(alignment) const @"enum".tag_type, @ptrCast(ptr)))),
939 .@"struct" => |@"struct"| @bitCast(
940 macho.targetLoad(@as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr))),
1963 inline .@"struct", .@"union" => |bitpack| @bitCast(
1964 macho.targetLoad(@as(*align(alignment) const bitpack.backing_integer.?, @ptrCast(ptr))),
9411965 ),
9421966 };
9431967}
......@@ -952,8 +1976,8 @@ fn targetStore(macho: *const MachO, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).p
9521976 @as(*align(alignment) @"enum".tag_type, @ptrCast(ptr)),
9531977 @backingInt(val),
9541978 ),
955 .@"struct" => |@"struct"| macho.targetStore(
956 @as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr)),
1979 inline .@"struct", .@"union" => |bitpack| macho.targetStore(
1980 @as(*align(alignment) bitpack.backing_integer.?, @ptrCast(ptr)),
9571981 @bitCast(val),
9581982 ),
9591983 };
......@@ -965,10 +1989,16 @@ pub fn deinit(macho: *MachO) void {
9651989 macho.nodes.deinit(gpa);
9661990 macho.segments.deinit(gpa);
9671991 macho.sections.deinit(gpa);
968 macho.segments_by_name.deinit(gpa);
9691992 macho.sections_by_name.deinit(gpa);
1993 macho.dirty_fixup_chain_pages.deinit(gpa);
1994 macho.input_atoms.deinit(gpa);
9701995 macho.strtab.deinit(gpa);
9711996 macho.symtab.deinit(gpa);
1997 macho.global_symbols.deinit(gpa);
1998 macho.relocs.deinit(gpa);
1999 macho.imports.deinit(gpa);
2000 macho.got.deinit(gpa);
2001 macho.stubs.deinit(gpa);
9722002 macho.sections_changed_index.deinit(gpa);
9732003 macho.* = undefined;
9742004}
......@@ -1023,41 +2053,1153 @@ pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
10232053 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
10242054 else => |e| return e,
10252055 };
2056 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
2057 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
2058 else => |e| return e,
2059 };
10262060}
10272061fn loadInputInner(macho: *MachO, input: link.Input) Error!void {
2062 const comp = macho.base.comp;
2063 const diags = &comp.link_diags;
2064 const gpa = comp.gpa;
2065 const io = comp.io;
10282066 switch (input) {
10292067 .res => unreachable,
1030 .object => @panic("TODO(MachO2): load object"),
1031 .archive => @panic("TODO(MachO2): load archive"),
2068 .object => |object| {
2069 var buf: [4096]u8 = undefined;
2070 var fr = object.file.reader(io, &buf);
2071 macho.loadObject(object.path, null, &fr, .{
2072 .offset = fr.logicalPos(),
2073 .size = fr.getSize() catch |err| switch (err) {
2074 error.Canceled => |e| return e,
2075 else => |e| return diags.fail(
2076 "failed to stat \"{f}\": {t}",
2077 .{ object.path.fmtEscapeString(), e },
2078 ),
2079 },
2080 }) catch |err| switch (err) {
2081 else => |e| return e,
2082 error.EndOfStream => return diags.failParse(
2083 object.path,
2084 "unexpected eof",
2085 .{},
2086 ),
2087 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2088 "failed to read \"{f}\": {t}",
2089 .{ object.path.fmtEscapeString(), e },
2090 ),
2091 error.ReadFailed => switch (fr.err.?) {
2092 error.Canceled => |e| return e,
2093 else => |e| return diags.fail(
2094 "failed to read \"{f}\": {t}",
2095 .{ object.path.fmtEscapeString(), e },
2096 ),
2097 },
2098 };
2099 },
2100 .archive => |archive| {
2101 var buf: [4096]u8 = undefined;
2102 var fr = archive.file.reader(io, &buf);
2103 macho.loadArchive(archive.path, &fr) catch |err| switch (err) {
2104 else => |e| return e,
2105 error.EndOfStream => return diags.failParse(
2106 archive.path,
2107 "unexpected eof",
2108 .{},
2109 ),
2110 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2111 "failed to read \"{f}\": {t}",
2112 .{ archive.path.fmtEscapeString(), e },
2113 ),
2114 error.ReadFailed => switch (fr.err.?) {
2115 error.Canceled => |e| return e,
2116 else => |e| return diags.fail(
2117 "failed to read \"{f}\": {t}",
2118 .{ archive.path.fmtEscapeString(), e },
2119 ),
2120 },
2121 };
2122 },
10322123 .dso => @panic("TODO(MachO2): load dso"),
10332124 .tbd => |tbd| {
10342125 log.debug("load tbd {qf}", .{tbd.path.fmtEscapeString()});
10352126
1036 // TODO: obviously this is completely wrong, we need to parse the tbd to determine the
1037 // dylib install name; but for now this lets us link libSystem
1038 const prefix = "/usr/lib/";
1039 const name = std.fs.path.stem(tbd.path.sub_path);
1040 const suffix = ".dylib";
2127 var lib_stub = tapi.LibStub.loadFromFile(gpa, io, tbd.file) catch |err| switch (err) {
2128 error.Canceled => |e| return e,
2129 else => |e| return diags.failParse(tbd.path, "failed to parse tbd: {t}", .{e}),
2130 };
2131 defer lib_stub.deinit();
2132
2133 const umbrella_lib = lib_stub.inner[0];
10412134
2135 const dylib_name = umbrella_lib.installName();
10422136 const cmd = try macho.appendLoadCommand(std.macho.dylib_command, .{
10432137 .cmd = .LOAD_DYLIB,
10442138 .cmdsize = std.mem.alignForward(u32, @intCast(
1045 @sizeOf(std.macho.dylib_command) + prefix.len + name.len + suffix.len + 1,
2139 @sizeOf(std.macho.dylib_command) + dylib_name.len + 1,
10462140 ), 8),
10472141 .dylib = .{
10482142 .name = @sizeOf(std.macho.dylib_command),
10492143 .timestamp = 0,
1050 .current_version = 0,
1051 .compatibility_version = 0,
2144 .current_version = 0, // TODO
2145 .compatibility_version = 0, // TODO
10522146 },
10532147 });
1054 @memcpy(cmd.trailing[0..prefix.len], prefix);
1055 @memcpy(cmd.trailing[prefix.len..][0..name.len], name);
1056 @memcpy(cmd.trailing[prefix.len + name.len ..][0..suffix.len], suffix);
1057 @memset(cmd.trailing[prefix.len + name.len + suffix.len ..], 0);
2148 @memcpy(cmd.trailing[0..dylib_name.len], dylib_name);
2149 @memset(cmd.trailing[dylib_name.len..], 0);
10582150 },
10592151 }
10602152}
2153
2154/// Internal error set used by input parsing functions `loadObject`, `loadArchive`, `loadDso`.
2155const LoadParseInputError = Error || Io.File.SeekError || Io.Reader.Error;
2156
2157fn loadArchive(macho: *MachO, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadParseInputError!void {
2158 const comp = macho.base.comp;
2159 const gpa = comp.gpa;
2160 const diags = &comp.link_diags;
2161 const r = &fr.interface;
2162
2163 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
2164
2165 {
2166 const magic = r.take(std.macho.ARMAG.len) catch |err| switch (err) {
2167 error.ReadFailed => |e| return e,
2168 error.EndOfStream => return diags.failParse(path, "invalid archive file", .{}),
2169 };
2170 if (!std.mem.eql(u8, magic, std.macho.ARMAG)) {
2171 return diags.failParse(path, "invalid archive file", .{});
2172 }
2173 }
2174
2175 var long_name_buf: std.ArrayList(u8) = .empty;
2176 defer long_name_buf.deinit(gpa);
2177
2178 while (r.takeStruct(std.macho.ar_hdr, .native)) |header| {
2179 if (!std.mem.eql(u8, &header.ar_fmag, std.macho.ARFMAG))
2180 return diags.failParse(path, "bad file magic", .{});
2181 const offset = fr.logicalPos();
2182 const size = header.size() catch
2183 return diags.failParse(path, "bad member size", .{});
2184 const member_name: []const u8, const member_offset: u64, const member_size: u64 = member: {
2185 const name_len = header.nameLength() orelse {
2186 break :member .{ header.name(), offset, size };
2187 };
2188 try long_name_buf.resize(gpa, name_len);
2189 try fr.interface.readSliceAll(long_name_buf.items);
2190 break :member .{
2191 std.mem.sliceTo(long_name_buf.items, 0),
2192 offset + name_len,
2193 size - name_len,
2194 };
2195 };
2196 if (std.mem.eql(u8, member_name, std.macho.SYMDEF) or
2197 std.mem.eql(u8, member_name, std.macho.SYMDEF64) or
2198 std.mem.eql(u8, member_name, std.macho.SYMDEF_SORTED) or
2199 std.mem.eql(u8, member_name, std.macho.SYMDEF64_SORTED))
2200 {
2201 // Ignore these special members.
2202 } else {
2203 try macho.loadObject(path, member_name, fr, .{
2204 .offset = member_offset,
2205 .size = member_size,
2206 });
2207 }
2208 try fr.seekTo(std.mem.alignForward(u64, offset + size, 2));
2209 } else |err| switch (err) {
2210 else => |e| return e,
2211 error.EndOfStream => if (!fr.atEnd()) return error.EndOfStream,
2212 }
2213}
2214fn loadObject(
2215 macho: *MachO,
2216 path: std.Build.Cache.Path,
2217 member: ?[]const u8,
2218 fr: *Io.File.Reader,
2219 fl: MappedFile.Node.FileLocation,
2220) LoadParseInputError!void {
2221 const comp = macho.base.comp;
2222 const diags = &comp.link_diags;
2223 const gpa = comp.gpa;
2224
2225 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) });
2226
2227 const target_endian = macho.targetEndian();
2228
2229 switch (try fr.interface.peekInt(u32, target_endian)) {
2230 std.macho.MH_MAGIC_64 => {},
2231 std.macho.MH_MAGIC => {
2232 return diags.failParse(path, "incorrect pointer bit width (expected 64)", .{});
2233 },
2234 std.macho.MH_CIGAM_64, std.macho.MH_CIGAM => {
2235 return diags.failParse(path, "incorrect endian (expected {t})", .{target_endian});
2236 },
2237 else => {
2238 return diags.failParse(path, "invalid Mach-O object file", .{});
2239 },
2240 }
2241
2242 const header = try fr.interface.takeStruct(std.macho.mach_header_64, target_endian);
2243 assert(header.magic == std.macho.MH_MAGIC_64); // checked above
2244
2245 if (header.filetype != std.macho.MH_OBJECT) return diags.failParse(
2246 path,
2247 "not a relocatable",
2248 .{},
2249 );
2250
2251 {
2252 const output_header: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
2253 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
2254 ));
2255 const output_cputype = macho.targetLoad(&output_header.cputype);
2256 const output_cpusubtype = macho.targetLoad(&output_header.cpusubtype);
2257 if (header.cputype != output_cputype) return diags.failParse(
2258 path,
2259 "cputype '{d}' does not match target cputype '{d}'",
2260 .{ header.cputype, output_cputype },
2261 );
2262 if (header.cpusubtype != output_cpusubtype) return diags.failParse(
2263 path,
2264 "cpusubtype '{d}' does not match target cpusubtype '{d}'",
2265 .{ header.cpusubtype, output_cpusubtype },
2266 );
2267 }
2268
2269 if (@sizeOf(std.macho.mach_header_64) + header.sizeofcmds > fl.size) {
2270 return diags.failParse(path, "load commands exceed file size", .{});
2271 }
2272
2273 var maybe_symtab_lc: ?std.macho.symtab_command = null;
2274
2275 const InputSection = struct {
2276 segname: [16]u8,
2277 sectname: [16]u8,
2278
2279 reloff: u32,
2280 nreloc: u32,
2281
2282 addr: u64,
2283 size: u64,
2284 @"align": MappedFile.Alignment,
2285
2286 usage: Usage,
2287 file_offset: u32,
2288
2289 atoms_start: usize,
2290 atoms_end: usize,
2291
2292 const Usage = enum {
2293 text,
2294 data_const,
2295 data_const_split_4,
2296 data_const_split_8,
2297 data_const_split_16,
2298 data_const_split_c_strings,
2299 data_const_fixups,
2300 data_mut,
2301 bss,
2302 fn parentSection(usage: Usage, m: *const MachO) Section {
2303 return switch (usage) {
2304 .text => m.sectionByName("__TEXT", "__text").?,
2305
2306 .data_const,
2307 .data_const_split_4,
2308 .data_const_split_8,
2309 .data_const_split_16,
2310 .data_const_split_c_strings,
2311 => m.sectionByName("__TEXT", "__const").?,
2312
2313 // TODO: this should really be "__DATA_CONST,__data". See comment in `initInner`
2314 .data_const_fixups => m.sectionByName("__DATA", "__data").?,
2315
2316 .data_mut => m.sectionByName("__DATA", "__data").?,
2317 .bss => m.sectionByName("__DATA", "__data").?,
2318 };
2319 }
2320 };
2321 };
2322 var input_sections: std.ArrayList(InputSection) = .empty;
2323 defer input_sections.deinit(gpa);
2324
2325 var cmd_offset: u64 = 0;
2326 for (0..header.ncmds) |_| {
2327 const lc = try fr.interface.peekStruct(std.macho.load_command, target_endian);
2328 if (lc.cmdsize < @sizeOf(std.macho.load_command)) return diags.failParse(
2329 path,
2330 "load command at offset 0x{X} has invalid size",
2331 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2332 );
2333 if (cmd_offset + lc.cmdsize > header.sizeofcmds) return diags.failParse(
2334 path,
2335 "load command at offset 0x{X} exceeds 'sizeofcmds'",
2336 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2337 );
2338 cmd_offset += lc.cmdsize;
2339 switch (lc.cmd) {
2340 else => if (std.enums.tagName(std.macho.LC, lc.cmd)) |cmd_name| {
2341 return diags.failParse(path, "unhandled load command 'LC_{s}'", .{cmd_name});
2342 } else {
2343 return diags.failParse(path, "unknown load command 0x{X}", .{lc.cmd});
2344 },
2345
2346 .NONE,
2347 .DYSYMTAB,
2348 .NOTE,
2349 .BUILD_VERSION,
2350 .DATA_IN_CODE,
2351 .LINKER_OPTIMIZATION_HINT,
2352 => try fr.interface.discardAll(lc.cmdsize), // ignore
2353
2354 .SEGMENT_64 => {
2355 if (lc.cmdsize < @sizeOf(std.macho.segment_command_64)) {
2356 return diags.failParse(path, "bad cmdsize in 'LC_SEGMENT_64' load command", .{});
2357 }
2358 const segment_lc = try fr.interface.takeStruct(std.macho.segment_command_64, target_endian);
2359 if (lc.cmdsize - @sizeOf(std.macho.segment_command_64) < segment_lc.nsects * @sizeOf(std.macho.section_64)) {
2360 return diags.failParse(path, "nsects in 'LC_SEGMENT_64' load command exceeds cmdsize", .{});
2361 }
2362 try input_sections.ensureUnusedCapacity(gpa, segment_lc.nsects);
2363 for (0..segment_lc.nsects) |_| {
2364 const section = try fr.interface.takeStruct(std.macho.section_64, target_endian);
2365
2366 const flags: Section.Flags = @bitCast(section.flags);
2367
2368 const segname = std.mem.sliceTo(&section.segname, 0);
2369 const sectname = std.mem.sliceTo(&section.sectname, 0);
2370
2371 const usage: InputSection.Usage = switch (flags.type) {
2372 _ => return diags.failParse(
2373 path,
2374 "section '{s},{s}' has unknown type '{d}'",
2375 .{ segname, sectname, flags.type },
2376 ),
2377
2378 else => return diags.failParse(
2379 path,
2380 "section '{s},{s}' has unsupported type '{t}'",
2381 .{ segname, sectname, flags.type },
2382 ),
2383
2384 .zerofill => .bss,
2385
2386 .@"4byte_literals" => .data_const_split_4,
2387 .@"8byte_literals" => .data_const_split_8,
2388 .@"16byte_literals" => .data_const_split_16,
2389 .cstring_literals => .data_const_split_c_strings,
2390
2391 .regular => usage: {
2392 if (flags.some_instructions or flags.pure_instructions) {
2393 break :usage .text;
2394 }
2395
2396 // "__DATA,__const" is constant data with fixups.
2397 if (std.mem.eql(u8, segname, "__DATA") and
2398 std.mem.eql(u8, sectname, "__const"))
2399 {
2400 break :usage .data_const_fixups;
2401 }
2402
2403 // Otherwise, the decision is based solely on segment name.
2404 if (std.mem.eql(u8, segname, "__TEXT")) {
2405 break :usage .data_const;
2406 }
2407 if (std.mem.eql(u8, segname, "__DATA_CONST")) {
2408 break :usage .data_const_fixups;
2409 }
2410 if (std.mem.eql(u8, segname, "__DATA")) {
2411 break :usage .data_mut;
2412 }
2413
2414 return diags.failParse(path, "unknown segment name '{s}'", .{segname});
2415 },
2416 };
2417
2418 if (usage != .bss and section.offset + section.size > fl.size) {
2419 return diags.failParse(path, "section '{s},{s}' exceeds file size", .{
2420 segname, sectname,
2421 });
2422 }
2423
2424 const @"align": MappedFile.Alignment = .fromLog2Units(std.math.cast(u6, section.@"align") orelse {
2425 return diags.failParse(path, "section '{s},{s}' has unsupported alignment '1 << {d}'", .{
2426 segname, sectname, section.@"align",
2427 });
2428 });
2429
2430 // Before creating the input section nodes, make sure the parent section is
2431 // suitably aligned. Unlike ELF, Mach-O does not allow customizing segment
2432 // alignment, so section alignments greater than the page size cannot be
2433 // fulfilled and must trigger an error.
2434 if (@"align".compare(.gt, macho.targetPageAlign())) {
2435 return diags.failParse(path, "section '{s},{s}' has alignment '{d}' which exceeds page alignment '{d}'", .{
2436 segname, sectname, @"align".toByteUnits(), macho.targetPageAlign().toByteUnits(),
2437 });
2438 }
2439 const parent_section = usage.parentSection(macho);
2440 if (@"align".compare(.gt, parent_section.alignment(macho))) {
2441 macho.targetStore(&parent_section.lcPtr(macho).@"align", @"align".toLog2Units());
2442 if (@"align".compare(.gt, parent_section.node(macho).alignment(&macho.mf))) {
2443 try parent_section.node(macho).realign(gpa, &macho.mf, @"align");
2444 }
2445 }
2446
2447 input_sections.appendAssumeCapacity(.{
2448 .segname = section.segname,
2449 .sectname = section.sectname,
2450 .reloff = section.reloff,
2451 .nreloc = section.nreloc,
2452 .addr = section.addr,
2453 .size = section.size,
2454 .@"align" = @"align",
2455 .usage = usage,
2456 .file_offset = section.offset,
2457 .atoms_start = undefined,
2458 .atoms_end = undefined,
2459 });
2460 }
2461 try fr.interface.discardAll(lc.cmdsize -
2462 @sizeOf(std.macho.segment_command_64) -
2463 segment_lc.nsects * @sizeOf(std.macho.section_64));
2464 },
2465
2466 .SYMTAB => {
2467 if (lc.cmdsize < @sizeOf(std.macho.symtab_command)) {
2468 return diags.failParse(path, "bad cmdsize in 'LC_SYMTAB' load command", .{});
2469 }
2470 if (maybe_symtab_lc != null) {
2471 return diags.failParse(path, "duplicate 'LC_SYMTAB' load command", .{});
2472 }
2473 maybe_symtab_lc = try fr.interface.peekStruct(std.macho.symtab_command, target_endian);
2474 try fr.interface.discardAll(lc.cmdsize);
2475 },
2476 }
2477 }
2478
2479 const symtab_lc = maybe_symtab_lc orelse return diags.failParse(
2480 path,
2481 "object file does not contain any 'LC_SYMTAB' load command",
2482 .{},
2483 );
2484 if (symtab_lc.symoff + symtab_lc.nsyms * @sizeOf(std.macho.nlist_64) > fl.size) {
2485 return diags.failParse(path, "symbol table in 'LC_SYMTAB' load command exceeds file size", .{});
2486 }
2487 if (symtab_lc.stroff + symtab_lc.strsize > fl.size) {
2488 return diags.failParse(path, "string table in 'LC_SYMTAB' load command exceeds file size", .{});
2489 }
2490
2491 const symbols = try gpa.alloc(Symbol.Index, symtab_lc.nsyms);
2492 defer gpa.free(symbols);
2493
2494 const Atom = struct {
2495 offset_in_section: u64,
2496 index: InputAtom.Index,
2497 };
2498 var atoms: std.ArrayList(Atom) = .empty;
2499 defer atoms.deinit(gpa);
2500
2501 // This block processes symbols and atoms (populating `symbols` and `atoms` in the process).
2502 // After the block exits, the only work remining is relocation lowering.
2503 {
2504 try fr.seekTo(fl.offset + symtab_lc.stroff);
2505 const strtab = try fr.interface.readAllocAll(gpa, symtab_lc.strsize);
2506 defer gpa.free(strtab);
2507
2508 try fr.seekTo(fl.offset + symtab_lc.symoff);
2509 const symtab = try fr.interface.readSliceEndianAlloc(gpa, std.macho.nlist_64, symtab_lc.nsyms, target_endian);
2510 defer gpa.free(symtab);
2511
2512 // Sort the symbols so that we can do subsection splitting efficiently. This is okay because
2513 // there is no significance to the order of the symbol table in an object file. We'll put
2514 // any symbols not in a section (undef, absolute, etc) first, and then we'll sort the
2515 // remaining symbols by section index and address.
2516 //
2517 // Small caveat: when we lower symbols into the `symbols` array we allocated earlier, we do
2518 // need to use the *original* indices rather than our sorted indices, so that we can look up
2519 // relocation target symbols by index later. To help us do that, we'll also build a mapping
2520 // from our sorted indices to the original indices.
2521 const orig_sym_indices = try gpa.alloc(u32, symtab.len);
2522 defer gpa.free(orig_sym_indices);
2523 for (orig_sym_indices, 0..) |*orig_index, i| {
2524 orig_index.* = @intCast(i);
2525 }
2526 const SymtabSortContext = struct {
2527 symtab: []std.macho.nlist_64,
2528 map: []u32,
2529 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
2530 const lhs = ctx.symtab[lhs_index];
2531 const rhs = ctx.symtab[rhs_index];
2532 // Don't need to check `n_type.bits.is_stab` here, because valid object files do not
2533 // contain stabs symbols, so if any are present we'll error out later anyway.
2534 const lhs_sect: u8, const lhs_addr: u64 = switch (lhs.n_type.bits.type) {
2535 .sect => .{ lhs.n_sect, lhs.n_value },
2536 else => .{ 0, 0 },
2537 };
2538 const rhs_sect: u8, const rhs_addr: u64 = switch (rhs.n_type.bits.type) {
2539 .sect => .{ rhs.n_sect, rhs.n_value },
2540 else => .{ 0, 0 },
2541 };
2542 const order = std.math.order(lhs_sect, rhs_sect).differ() orelse std.math.order(lhs_addr, rhs_addr);
2543 return order.compare(.lt);
2544 }
2545 pub fn swap(ctx: @This(), lhs_index: usize, rhs_index: usize) void {
2546 std.mem.swap(std.macho.nlist_64, &ctx.symtab[lhs_index], &ctx.symtab[rhs_index]);
2547 std.mem.swap(u32, &ctx.map[lhs_index], &ctx.map[rhs_index]);
2548 }
2549 };
2550 std.mem.sortUnstableContext(0, symtab.len, @as(SymtabSortContext, .{
2551 .symtab = symtab,
2552 .map = orig_sym_indices,
2553 }));
2554
2555 var sym_index: u32 = 0;
2556
2557 // This initial loop processes all of the non-section symbols (undef, absolute, etc). When
2558 // it exits, `sym_index` points to the first symbol with `n_type.bits.type == .sect`, which
2559 // is guaranteed to have `n_sect > 0`.
2560 while (sym_index < symtab.len) : (sym_index += 1) {
2561 const nlist = &symtab[sym_index];
2562
2563 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2564 path,
2565 "invalid symtab entry at index {d}: object file cannot contain stabs",
2566 .{sym_index},
2567 );
2568 if (nlist.n_strx > strtab.len) return diags.failParse(
2569 path,
2570 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2571 .{ sym_index, nlist.n_strx },
2572 );
2573 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2574
2575 symbols[orig_sym_indices[sym_index]] = switch (nlist.n_type.bits.type) {
2576 .sect => if (nlist.n_sect == 0) return diags.failParse(
2577 path,
2578 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2579 .{ sym_name, nlist.n_sect },
2580 ) else {
2581 // All done with the non-section symbols!
2582 break;
2583 },
2584
2585 .undf => if (!nlist.n_type.bits.ext) return diags.failParse(
2586 path,
2587 "internal symbol '{s}' cannot be undefined",
2588 .{sym_name},
2589 ) else macho.addGlobalSymbol(.{
2590 .name = sym_name,
2591 .linkage = switch (nlist.n_desc.weak_ref) {
2592 false => .strong,
2593 true => .weak,
2594 },
2595 .visibility = switch (nlist.n_type.bits.pext) {
2596 false => .default,
2597 true => .hidden,
2598 },
2599 .value = null,
2600 }) catch |err| switch (err) {
2601 error.MultipleDefinitions => unreachable, // not a definition
2602 else => |e| return e,
2603 },
2604
2605 .abs => if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2606 .name = sym_name,
2607 .value = .{ .abs = nlist.n_value },
2608 }) else macho.addGlobalSymbol(.{
2609 .name = sym_name,
2610 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2611 false => .strong,
2612 true => .weak,
2613 },
2614 .visibility = switch (nlist.n_type.bits.pext) {
2615 false => .default,
2616 true => .hidden,
2617 },
2618 .value = null,
2619 }) catch |err| switch (err) {
2620 error.MultipleDefinitions => return diags.failParse(
2621 path,
2622 "multiple definitions of '{s}'",
2623 .{sym_name},
2624 ),
2625 else => |e| return e,
2626 },
2627
2628 .pbud, .indr => |sym_type| return diags.failParse(
2629 path,
2630 "symbol '{s}' has unsupported symbol type '{t}",
2631 .{ sym_name, sym_type },
2632 ),
2633
2634 _ => |sym_type| return diags.failParse(
2635 path,
2636 "symbol '{s}' has unknown symbol type '0x{X}'",
2637 .{ sym_name, sym_type },
2638 ),
2639 };
2640 }
2641
2642 // Now we need to process the symbols defined in sections. We're going to do this by looping
2643 // over *sections*, just because subsection splitting turns out to be much easier this way.
2644 for (input_sections.items, 0..) |*section, section_index| {
2645 if (section.usage != .bss) {
2646 try fr.seekTo(fl.offset + section.file_offset);
2647 }
2648
2649 const parent_section = section.usage.parentSection(macho);
2650
2651 section.atoms_start = atoms.items.len;
2652 var cur_atom_end: u64 = 0;
2653 while (true) {
2654 const sym_in_section: bool = sym_in_section: {
2655 if (sym_index == symtab.len) break :sym_in_section false;
2656
2657 const nlist = &symtab[sym_index];
2658
2659 assert(nlist.n_sect - 1 >= section_index); // we sorted the symtab by section
2660 if (nlist.n_sect - 1 != section_index) break :sym_in_section false;
2661
2662 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2663 path,
2664 "invalid symtab entry at index {d}: object file cannot contain stabs",
2665 .{sym_index},
2666 );
2667 if (nlist.n_strx > strtab.len) return diags.failParse(
2668 path,
2669 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2670 .{ sym_index, nlist.n_strx },
2671 );
2672
2673 if (nlist.n_value < section.addr or
2674 // This comparison intentionally uses '>=' to disallow defining a symbol at
2675 // the exclusive end of a section. This is because of an annoying quirk of
2676 // Mach-O: when using `MH_SUBSECTIONS_VIA_SYMBOLS`, it is impossible to
2677 // define a symbol at the exclusive end of any subsection, because such a
2678 // symbol would be considered a part of the *next* subsection.
2679 nlist.n_value >= section.addr + section.size)
2680 {
2681 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2682 const segname = std.mem.sliceTo(&section.segname, 0);
2683 const sectname = std.mem.sliceTo(&section.sectname, 0);
2684 return diags.failParse(
2685 path,
2686 "symbol '{s}' address '0x{X}' exceeds range of containing section '{s},{s}'",
2687 .{ sym_name, nlist.n_value, segname, sectname },
2688 );
2689 }
2690
2691 break :sym_in_section true;
2692 };
2693
2694 const target_atom_offset: u64 = target_atom_offset: {
2695 if (sym_in_section) {
2696 break :target_atom_offset symtab[sym_index].n_value - section.addr;
2697 }
2698 if (cur_atom_end != section.size) {
2699 // There were no more symbols in this section, but we still need to create
2700 // the atoms for the rest of the section! So just process everything up to
2701 // and including the last byte of the section.
2702 break :target_atom_offset section.size - 1;
2703 }
2704 // There are no symbols or atoms left to process, so we're done.
2705 break;
2706 };
2707 assert(target_atom_offset < section.size);
2708
2709 while (cur_atom_end <= target_atom_offset) {
2710 // We need to start a new atom at `cur_atom_end`.
2711 const atom_offset: u64 = cur_atom_end;
2712 const atom_size: u64 = usage: switch (section.usage) {
2713 // TODO: we should also deduplicate these
2714 .data_const_split_4 => 4,
2715 .data_const_split_8 => 8,
2716 .data_const_split_16 => 16,
2717
2718 // TODO: split C string sections (although `MH_SUBSECTIONS_VIA_SYMBOLS` is
2719 // usually sufficient anyway); and deduplicate those too
2720 .data_const_split_c_strings => continue :usage .data_const,
2721
2722 .text,
2723 .data_const,
2724 .data_const_fixups,
2725 .data_mut,
2726 .bss,
2727 => atom_size: {
2728 if (header.flags & std.macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) {
2729 // We aren't splitting the section into multiple atoms, so this must
2730 // mean that we haven't created *any* atom for this section yet.
2731 assert(atom_offset == 0);
2732 break :atom_size section.size;
2733 }
2734
2735 // Find the next symbol which splits this section. Usually this is just
2736 // the next symbol, but there could be some alt entry or alias symbols.
2737 var other_sym_index = sym_index + 1;
2738 const new_atom_end: u64 = while (other_sym_index < symtab.len and
2739 symtab[other_sym_index].n_sect - 1 == section_index) : (other_sym_index += 1)
2740 {
2741 if (symtab[other_sym_index].n_value == section.addr + atom_offset) continue;
2742 if (symtab[other_sym_index].n_desc.alt_entry) continue;
2743 // This symbol splits up the subsections. We haven't validated the
2744 // value of the symbol, so it might exceed the section bounds. We
2745 // will check for that error case later when the outer loop reaches
2746 // that symbol, so we don't need to check now, but we should limit
2747 // the size of the atom so that we don't allocate a potentially huge
2748 // node just before failing.
2749 break @min(
2750 symtab[other_sym_index].n_value - section.addr,
2751 section.size,
2752 );
2753 } else new_atom_end: {
2754 // There is no other symbol which splits this section, so the new
2755 // atom must cover the entire remainder of the section.
2756 break :new_atom_end section.size;
2757 };
2758
2759 break :atom_size new_atom_end - atom_offset;
2760 },
2761 };
2762 assert(atom_size > 0);
2763 assert(atom_offset + atom_size <= section.size);
2764
2765 const atom_align = section.@"align".offset(atom_offset);
2766
2767 try macho.nodes.ensureUnusedCapacity(gpa, 1);
2768 try macho.input_atoms.ensureUnusedCapacity(gpa, 1);
2769 const atom_node = try parent_section.node(macho).addFloatingChild(gpa, &macho.mf, .{
2770 .size = atom_align.forward(atom_size),
2771 .alignment = atom_align,
2772 .moved = true, // `flushMoved` will set up vaddrs and apply relocs
2773 });
2774 const iai: InputAtom.Index = @fromBackingInt(@intCast(macho.input_atoms.items.len));
2775 macho.input_atoms.appendAssumeCapacity(.{
2776 .node = atom_node,
2777 .first_symbol = .none,
2778 .first_reloc = @fromBackingInt(0), // populated for real later
2779 .vaddr = 0,
2780 .any_fixups = false,
2781 });
2782 macho.nodes.appendAssumeCapacity(.{ .input_atom = iai });
2783
2784 if (section.usage != .bss) {
2785 const slice = atom_node.slice(&macho.mf);
2786 try fr.interface.readSliceAll(slice[0..@intCast(atom_size)]);
2787 }
2788
2789 try atoms.append(gpa, .{
2790 .offset_in_section = atom_offset,
2791 .index = iai,
2792 });
2793 cur_atom_end = atom_offset + atom_size;
2794 }
2795
2796 if (sym_in_section) {
2797 // We just advanced to the atom containing this symbol, so now we can add it.
2798 const atom = atoms.lastPtr().?;
2799 const nlist = &symtab[sym_index];
2800 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2801 const symbol_offset_in_section = symtab[sym_index].n_value - section.addr;
2802 symbols[orig_sym_indices[sym_index]] = if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2803 .name = sym_name,
2804 .value = .{ .addr_in_node = .{
2805 .node = atom.index.node(macho),
2806 .offset = symbol_offset_in_section - atom.offset_in_section,
2807 } },
2808 }) else macho.addGlobalSymbol(.{
2809 .name = sym_name,
2810 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2811 false => .strong,
2812 true => .weak,
2813 },
2814 .visibility = switch (nlist.n_type.bits.pext) {
2815 false => .default,
2816 true => .hidden,
2817 },
2818 .value = .{ .addr_in_node = .{
2819 .node = atom.index.node(macho),
2820 .offset = symbol_offset_in_section - atom.offset_in_section,
2821 } },
2822 }) catch |err| switch (err) {
2823 error.MultipleDefinitions => return diags.failParse(
2824 path,
2825 "multiple definitions of '{s}'",
2826 .{sym_name},
2827 ),
2828 else => |e| return e,
2829 };
2830
2831 sym_index += 1;
2832 }
2833 }
2834 section.atoms_end = atoms.items.len;
2835 }
2836
2837 // If, after handling all of the input sections, there is still a symbol remaining, then
2838 // that symbol must have an out-of-range `n_sect` value.
2839 if (sym_index != symtab.len) {
2840 const nlist = &symtab[sym_index];
2841 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2842 path,
2843 "invalid symtab entry at index {d}: object file cannot contain stabs",
2844 .{sym_index},
2845 );
2846 if (nlist.n_strx > strtab.len) return diags.failParse(
2847 path,
2848 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2849 .{ sym_index, nlist.n_strx },
2850 );
2851 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2852 assert(nlist.n_type.bits.type == .sect);
2853 assert(nlist.n_sect - 1 > input_sections.items.len);
2854 return diags.failParse(
2855 path,
2856 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2857 .{ sym_name, nlist.n_sect },
2858 );
2859 }
2860 }
2861
2862 // Now that the symbols are done, we can process relocations.
2863
2864 var relocs_buf: std.ArrayList(std.macho.relocation_info) = .empty;
2865 defer relocs_buf.deinit(gpa);
2866
2867 for (input_sections.items) |*section| {
2868 if (section.reloff + section.nreloc > fl.size) {
2869 return diags.failParse(path, "relocations for '{s},{s}' exceed file size", .{
2870 std.mem.sliceTo(&section.segname, 0),
2871 std.mem.sliceTo(&section.sectname, 0),
2872 });
2873 }
2874
2875 try relocs_buf.resize(gpa, section.nreloc);
2876 try fr.seekTo(fl.offset + section.reloff);
2877 try fr.interface.readSliceEndian(std.macho.relocation_info, relocs_buf.items, target_endian);
2878
2879 // We need to sort the section's relocations by address for two reasons:
2880 //
2881 // * We need the relocations in each atom to be contiguous. This allows us to use sub-arrays
2882 // of `macho.relocs` for the relocations in an atom, rather than e.g. linked lists.
2883 //
2884 // * We need the relocations *within* each atom to be sorted, so that we can efficiently
2885 // generate runtime fixup chains if necessary.
2886 //
2887 // This sort must be stable so that `*_RELOC_ADDEND` and `*_RELOC_SUBTRACTOR` relocations
2888 // remain correctly paired.
2889 const SortRelocsContext = struct {
2890 fn lessThan(_: @This(), lhs: std.macho.relocation_info, rhs: std.macho.relocation_info) bool {
2891 return lhs.r_address < rhs.r_address;
2892 }
2893 };
2894 std.mem.sort(
2895 std.macho.relocation_info,
2896 relocs_buf.items,
2897 @as(SortRelocsContext, .{}),
2898 SortRelocsContext.lessThan,
2899 );
2900
2901 try macho.relocs.ensureUnusedCapacity(gpa, relocs_buf.items.len);
2902
2903 assert(atoms.items[section.atoms_start].offset_in_section == 0);
2904
2905 var atom_index = section.atoms_start;
2906 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2907 macho.relocs.items.len,
2908 ));
2909
2910 var paired_addend: ?u64 = null;
2911 for (relocs_buf.items) |reloc| {
2912 if (reloc.r_address < 0) {
2913 return diags.failParse(path, "scattered relocations are not supported", .{});
2914 }
2915 const offset_in_section: u32 = @intCast(reloc.r_address);
2916
2917 // Find the atom containing `reloc`. (This is why we sorted the relocations!)
2918 while (atom_index + 1 < section.atoms_end and
2919 offset_in_section >= atoms.items[atom_index + 1].offset_in_section)
2920 {
2921 atom_index += 1;
2922 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2923 macho.relocs.items.len,
2924 ));
2925 }
2926
2927 {
2928 const atom_end: u64 = atom_end: {
2929 if (atom_index == section.atoms_end - 1) {
2930 break :atom_end section.size;
2931 }
2932 const next_atom = &atoms.items[atom_index + 1];
2933 break :atom_end next_atom.offset_in_section;
2934 };
2935 if (offset_in_section + (@as(u8, 1) << reloc.r_length) > atom_end) {
2936 const segname = std.mem.sliceTo(&section.segname, 0);
2937 const sectname = std.mem.sliceTo(&section.sectname, 0);
2938 return diags.failParse(
2939 path,
2940 "relocation at offset '{d}' in '{s},{s}' spans multiple atoms",
2941 .{ offset_in_section, segname, sectname },
2942 );
2943 }
2944 }
2945
2946 const offset_in_atom: u32 = @intCast(offset_in_section - atoms.items[atom_index].offset_in_section);
2947
2948 const length_types: [4]?Reloc.Type, const target: Reloc.Target, const pcrel: bool, const implicit_negative_addend: u64 = switch (header.cputype) {
2949 else => unreachable,
2950
2951 std.macho.CPU_TYPE_X86_64 => reloc_type: switch (std.enums.fromInt(
2952 std.macho.reloc_type_x86_64,
2953 reloc.r_type,
2954 ) orelse {
2955 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2956 }) {
2957 .X86_64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2958
2959 .X86_64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2960 .X86_64_RELOC_SIGNED => .{ .{ null, null, .rel32, null }, .symbol, true, 4 },
2961 .X86_64_RELOC_SIGNED_1 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 5 else 4 },
2962 .X86_64_RELOC_SIGNED_2 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 6 else 4 },
2963 .X86_64_RELOC_SIGNED_4 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 8 else 4 },
2964
2965 .X86_64_RELOC_BRANCH => .{ .{ null, null, .rel32, null }, .branch, true, 4 },
2966
2967 .X86_64_RELOC_TLV => .{ .{ null, null, .rel32, null }, .tlv, true, 4 },
2968
2969 .X86_64_RELOC_GOT_LOAD => continue :reloc_type .X86_64_RELOC_GOT, // relaxable
2970 .X86_64_RELOC_GOT => .{ .{ null, null, .rel32, null }, .got, true, 4 },
2971 },
2972
2973 std.macho.CPU_TYPE_ARM64 => switch (std.enums.fromInt(
2974 std.macho.reloc_type_arm64,
2975 reloc.r_type,
2976 ) orelse {
2977 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2978 }) {
2979 .ARM64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2980
2981 .ARM64_RELOC_ADDEND => {
2982 const addend: i24 = @bitCast(reloc.r_symbolnum);
2983 const extended: i64 = addend;
2984 paired_addend = @bitCast(extended);
2985 continue;
2986 },
2987
2988 .ARM64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2989 .ARM64_RELOC_BRANCH26 => .{ .{ null, null, .arm64_branchrel26, null }, .branch, true, 0 },
2990 .ARM64_RELOC_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .symbol, true, 0 },
2991 .ARM64_RELOC_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .symbol, false, 0 },
2992
2993 .ARM64_RELOC_TLVP_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .tlv, true, 0 },
2994 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .tlv, false, 0 },
2995
2996 .ARM64_RELOC_GOT_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .got, true, 0 },
2997 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .got, false, 0 },
2998
2999 // Apple's ld_classic supported an 8-byte non-pcrel version of this relocation,
3000 // but it had very weird semantics which didn't actually involve the GOT at all.
3001 // My guess is that they intended it to do the obvious thing but shipped with a
3002 // bug. In any case, it is not supported in Apple's new linker, and LLD does not
3003 // support it either, so therefore we should follow suit and only support the
3004 // usual form of this relocation (4-byte pcrel).
3005 .ARM64_RELOC_POINTER_TO_GOT => .{ .{ null, null, .rel32, null }, .got, true, 0 },
3006 },
3007 };
3008 const reloc_type_str: []const u8 = switch (header.cputype) {
3009 else => unreachable,
3010 std.macho.CPU_TYPE_ARM64 => @tagName(@as(
3011 std.macho.reloc_type_arm64,
3012 @fromBackingInt(reloc.r_type),
3013 )),
3014 std.macho.CPU_TYPE_X86_64 => @tagName(@as(
3015 std.macho.reloc_type_x86_64,
3016 @fromBackingInt(reloc.r_type),
3017 )),
3018 };
3019 if (@intFromBool(pcrel) != reloc.r_pcrel) {
3020 return diags.failParse(path, "relocation '{s}' {s} be pc-relative", .{
3021 reloc_type_str,
3022 if (pcrel) "must" else "cannot",
3023 });
3024 }
3025 const internal_type: Reloc.Type = length_types[reloc.r_length] orelse {
3026 return diags.failParse(path, "relocation '{s}' does not support length '{d}'", .{
3027 reloc_type_str,
3028 @as(u8, 1) << reloc.r_length,
3029 });
3030 };
3031
3032 const dest_slice = atoms.items[atom_index].index.node(macho).sliceConst(&macho.mf)[offset_in_atom..];
3033 if (!internal_type.validateInst(dest_slice)) {
3034 return diags.failParse(path, "relocation '{s}' against invalid instruction", .{
3035 reloc_type_str,
3036 });
3037 }
3038 const stored_addend: u64 = switch (internal_type) {
3039 .abs8 => std.mem.readInt(u8, dest_slice[0..1], target_endian),
3040 .abs16 => std.mem.readInt(u16, dest_slice[0..2], target_endian),
3041 .abs32 => std.mem.readInt(u32, dest_slice[0..4], target_endian),
3042 .abs64 => std.mem.readInt(u64, dest_slice[0..8], target_endian),
3043 .rel32 => a: {
3044 const extended: i64 = std.mem.readInt(i32, dest_slice[0..4], target_endian);
3045 break :a @bitCast(extended);
3046 },
3047
3048 .arm64_branchrel26,
3049 .arm64_pagerel21,
3050 .arm64_pageoff12,
3051 => a: {
3052 defer paired_addend = null;
3053 break :a paired_addend orelse 0;
3054 },
3055 };
3056 if (paired_addend != null) {
3057 return diags.failParse(path, "relocation '{s}' does not support paired addend", .{reloc_type_str});
3058 }
3059 const target_symbol: Symbol.Index, const final_addend: u64 = switch (reloc.r_extern) {
3060 1 => target: {
3061 // The relocation targets a symbol. This is the nice and reasonable case.
3062 if (reloc.r_symbolnum >= symbols.len) {
3063 return diags.failParse(path, "relocation target symbol '{d}' is invalid", .{reloc.r_symbolnum});
3064 }
3065 const target_sym = symbols[reloc.r_symbolnum];
3066 break :target .{ target_sym, stored_addend -% implicit_negative_addend };
3067 },
3068 0 => target: {
3069 // In this case, the relocation targets an offset into a *section*. I think the
3070 // best way to interpret this is to say that the "stored addend" value contains
3071 // what the relocated value *would* be if the linked image's section addresses
3072 // matched those of the relocatable itself (unlike ELF, Mach-O uses a flat
3073 // virtual address space even in relocatables). So, the best way to handle this
3074 // in the pcrel case is to reverse the computation to figure out what the actual
3075 // target address is.
3076 //
3077 // Unfortunately, this is further complicated by the existence of subsections.
3078 // Subsections split up the target section's virtual address space, and the
3079 // subsection being targeted by a relocation is determined implicitly based on
3080 // the target address. Therefore, to ensure we target the correct subsection, it
3081 // is necessary to compute the *exact* target address (no off-by-N). This Mach-O
3082 // quirk is why we have `implicit_negative_addend` on x86_64: it indicates that
3083 // the encoded pc-relative value is relative not to the exact address of the
3084 // relocation, but rather to an address a few bytes later, allowing us to
3085 // compute the exact target address instead of being 4--8 bytes short.
3086 //
3087 // Mach-O is a tremendously well-designed object format with no flaws whatsoever.
3088 if (reloc.r_symbolnum > input_sections.items.len) {
3089 return diags.failParse(path, "relocation target section '{d}' is invalid", .{reloc.r_symbolnum});
3090 }
3091 const target_addr_in_object: u64 = switch (pcrel) {
3092 false => stored_addend +% implicit_negative_addend,
3093 true => target_addr: {
3094 const pcrel_base: u64 = section.addr + @as(u32, @intCast(reloc.r_address));
3095 break :target_addr pcrel_base +% stored_addend +% implicit_negative_addend;
3096 },
3097 };
3098 const target_section = &input_sections.items[reloc.r_symbolnum - 1];
3099 const target_offset_in_section = target_addr_in_object -% target_section.addr;
3100 if (target_offset_in_section >= target_section.size) {
3101 const segname = std.mem.sliceTo(&target_section.segname, 0);
3102 const sectname = std.mem.sliceTo(&target_section.sectname, 0);
3103 return diags.failParse(
3104 path,
3105 "relocation target offset '{d}' exceeds range of target section '{s},{s}'",
3106 .{ target_offset_in_section, segname, sectname },
3107 );
3108 }
3109 // We need to know which atom we're targeting.
3110 const FindAtomContext = struct {
3111 offset_in_section: u64,
3112 fn order(lhs: @This(), rhs: Atom) std.math.Order {
3113 return std.math.order(lhs.offset_in_section, rhs.offset_in_section);
3114 }
3115 };
3116 assert(target_section.atoms_start < target_section.atoms_end);
3117 assert(atoms.items[target_section.atoms_start].offset_in_section == 0);
3118 const target_atom_index = target_section.atoms_start + std.sort.upperBound(
3119 Atom,
3120 atoms.items[target_section.atoms_start..target_section.atoms_end],
3121 @as(FindAtomContext, .{ .offset_in_section = target_offset_in_section }),
3122 FindAtomContext.order,
3123 ) - 1; // safe because atom 0 is at offset 0 so never >rhs
3124 assert(target_offset_in_section >= atoms.items[target_atom_index].offset_in_section);
3125 if (target_atom_index != target_section.atoms_end - 1) {
3126 assert(target_offset_in_section < atoms.items[target_atom_index + 1].offset_in_section);
3127 }
3128 // To preserve my own sanity, I'm just creating symbols to represent these
3129 // relocation targets. It's fine, I'm sure nobody will notice.
3130 var sym_name_buf: [64]u8 = undefined; // sectname is capped at 16 bytes
3131 const sym_name = std.mem.print(
3132 &sym_name_buf,
3133 "l_{s}_{s}_{x}",
3134 .{
3135 std.mem.trim(u8, std.mem.sliceTo(&target_section.segname, 0), "_"),
3136 std.mem.trim(u8, std.mem.sliceTo(&target_section.sectname, 0), "_"),
3137 target_offset_in_section,
3138 },
3139 ) catch unreachable;
3140 const target_sym = try macho.addInternalSymbol(.{
3141 .name = sym_name,
3142 .value = .{ .addr_in_node = .{
3143 .node = atoms.items[target_atom_index].index.node(macho),
3144 .offset = target_offset_in_section - atoms.items[target_atom_index].offset_in_section,
3145 } },
3146 });
3147 // Lovely, we have an actual symbol at the correct address which we can target.
3148 // Just remember to add the implicit addend back in so that the relocated
3149 // PC-relative address is relative to the correct base address!
3150 break :target .{ target_sym, -%implicit_negative_addend };
3151 },
3152 };
3153 const target_is_undef = macho.targetLoad(&target_symbol.nlist(macho).n_type).bits.type == .undf;
3154 if (target_is_undef) {
3155 const imports_gop = try macho.imports.getOrPut(gpa, target_symbol);
3156 if (!imports_gop.found_existing) {
3157 assert(imports_gop.index == macho.imports.count() - 1);
3158 try macho.appendImportEntry(macho.imports.count() - 1, target_symbol);
3159 }
3160 }
3161 if (target == .got or (target_is_undef and target == .branch)) {
3162 const got_gop = try macho.got.getOrPut(gpa, target_symbol);
3163 if (!got_gop.found_existing) {
3164 assert(got_gop.index == macho.got.count() - 1);
3165 try macho.appendGotEntry(macho.got.count() - 1, target_symbol);
3166 }
3167 }
3168 if (target_is_undef and target == .branch) {
3169 const stubs_gop = try macho.stubs.getOrPut(gpa, target_symbol);
3170 if (!stubs_gop.found_existing) {
3171 assert(stubs_gop.index == macho.stubs.count() - 1);
3172 const got_index = macho.got.getIndex(target_symbol).?; // added above
3173 try macho.appendStubEntry(macho.stubs.count() - 1, got_index, target_symbol);
3174 }
3175 }
3176 const ri: Reloc.Index = @fromBackingInt(@intCast(macho.relocs.items.len));
3177 macho.relocs.appendAssumeCapacity(.{
3178 .node = atoms.items[atom_index].index.node(macho),
3179 .offset = offset_in_atom,
3180 .target_symbol = target_symbol,
3181 .addend = final_addend,
3182 .bits = .{ .target = target, .type = internal_type, .result = .ok },
3183 .next_with_same_target_sym = target_symbol.ptr(macho).first_target_reloc,
3184 });
3185 target_symbol.ptr(macho).first_target_reloc = .wrap(ri);
3186
3187 if (target == .symbol and internal_type == .abs64) {
3188 // In this case the relocation needs a runtime fixup. This is the case even if the
3189 // target symbol is internal, because we still need to rebase the address.
3190 ri.ptr(macho).bits.result = .fixup;
3191 atoms.items[atom_index].index.ptr(macho).any_fixups = true;
3192 }
3193 }
3194 }
3195}
3196fn fmtMemberString(member: ?[]const u8) std.fmt.Alt(?[]const u8, memberStringEscape) {
3197 return .{ .data = member };
3198}
3199fn memberStringEscape(member: ?[]const u8, w: *Io.Writer) Io.Writer.Error!void {
3200 try w.print("({f})", .{std.zig.fmtString(member orelse return)});
3201}
3202
10613203pub fn setDarwinSdkVersion(macho: *MachO, sdk_version: link.DarwinSdkVersion) link.Error!void {
10623204 const comp = macho.base.comp;
10633205 const diags = &comp.link_diags;
......@@ -1098,21 +3240,54 @@ pub fn flush(
10983240 tid: Zcu.PerThread.Id,
10993241 prog_node: std.Progress.Node,
11003242) link.Error!void {
1101 const diags = &macho.base.comp.link_diags;
3243 const comp = macho.base.comp;
3244 const diags = &comp.link_diags;
11023245 _ = arena;
11033246 _ = tid;
11043247 _ = prog_node;
11053248
1106 try macho.updateSegmentLoadAddresses();
3249 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
3250 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
3251 else => |e| return e,
3252 };
3253
11073254 while (try macho.idle()) {}
11083255
3256 if (macho.overflowed_reloc_count > 0) {
3257 diags.addError("failed to apply {d} relocations: overflow", .{macho.overflowed_reloc_count});
3258 }
3259 if (macho.misaligned_reloc_count > 0) {
3260 diags.addError("failed to apply {d} relocations: misaligned value", .{macho.misaligned_reloc_count});
3261 }
3262
11093263 if (macho.lc.entry_point) |entry_point_lc| {
1110 // TODO: actually find the entry point.
1111 // For now, we just set the entry point to the start of the "__TEXT,__text" section.
1112 const entry_node = macho.sectionByName("__TEXT", "__text").?.node(macho);
1113 const entry_file_off: u64, _ = entry_node.location(&macho.mf).resolve(&macho.mf);
3264 const sym_name: []const u8 = name: switch (macho.options.entry) {
3265 .default => switch (comp.config.output_mode) {
3266 .Exe => continue :name .enabled,
3267 .Lib, .Obj => continue :name .disabled,
3268 },
3269 .disabled => unreachable, // `macho.lc.entry_point` is only set if entry is enabled
3270 .enabled => "_main",
3271 .named => |named| named,
3272 };
3273 const symbol = macho.globalByName(sym_name).unwrap() orelse {
3274 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3275 };
3276 if (macho.targetLoad(&symbol.nlist(macho).n_type).bits.type == .undf) {
3277 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3278 }
3279
3280 // Documentation says that this field should be the offset of the entry point in the file,
3281 // but that's a blatant lie; if you read the dyld source code, it's actually the vaddr minus
3282 // the text base vaddr. Because, like, obviously---what the fuck would they do with the
3283 // offset in the Mach-O file?
11143284 const entry_point_ptr = entry_point_lc.ptr(macho, std.macho.entry_point_command);
1115 macho.targetStore(&entry_point_ptr.entryoff, @intCast(entry_file_off));
3285 macho.targetStore(&entry_point_ptr.entryoff, std.math.cast(
3286 u32,
3287 symbol.value(macho) - Segment.text.vaddr(macho),
3288 ) orelse {
3289 return diags.fail("entry point address too big", .{});
3290 });
11163291 }
11173292
11183293 macho.mf.flush() catch |err| switch (err) {
......@@ -1136,9 +3311,32 @@ pub fn idle(macho: *MachO) link.Error!bool {
11363311 task: {
11373312 if (macho.sections_changed_index.pop()) |kv| {
11383313 const section = kv.key;
1139 var sym_it = section.firstSymbol(macho).*;
1140 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_section) {
1141 macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho));
3314 var child_it = section.node(macho).first(&macho.mf);
3315 while (child_it.unwrap()) |child| : (child_it = child.next(&macho.mf)) {
3316 switch (macho.getNode(child)) {
3317 .macho => unreachable,
3318 .macho_header => unreachable,
3319 .symtab => unreachable,
3320 .strtab => unreachable,
3321 .indirect_syms => unreachable,
3322 .section_indirect_syms => unreachable,
3323 .linkedit_data => unreachable,
3324 .fixup_chains_header => unreachable,
3325 .fixup_chains_imports => unreachable,
3326 .fixup_chains_symbol_names => unreachable,
3327 .fixup_chains_starts => unreachable,
3328 .fixup_chains_starts_in_image => unreachable,
3329 .fixup_chains_starts_in_segment => unreachable,
3330 .segment => unreachable,
3331 .section => unreachable,
3332 .section_manual_size => unreachable,
3333 .input_atom => |iai| {
3334 var sym_it = iai.ptrConst(macho).first_symbol;
3335 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3336 macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho));
3337 }
3338 },
3339 }
11423340 }
11433341 break :task;
11443342 }
......@@ -1157,24 +3355,209 @@ pub fn idle(macho: *MachO) link.Error!bool {
11573355
11583356 break :task;
11593357 }
3358
3359 if (macho.dirty_fixup_chain_pages.pop()) |kv| {
3360 const sub_prog_node = macho.mf.update_prog_node.start("Fixup Chains", 0);
3361 defer sub_prog_node.end();
3362 macho.flushFixupChains(kv.key.seg, kv.key.page);
3363 break :task;
3364 }
11603365 }
11613366
11623367 return macho.sections_changed_index.count() > 0 or
1163 macho.mf.updates.items.len > 0;
3368 macho.mf.updates.items.len > 0 or
3369 macho.dirty_fixup_chain_pages.count() > 0;
11643370}
11653371fn flushMoved(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
11663372 macho.flushMachOFileOffset(ni);
11673373 switch (macho.getNode(ni)) {
11683374 .macho => unreachable,
11693375 .macho_header => {},
1170 .segment => return, // segments don't bubble moved
1171 .section => |section| {
1172 const segment_vaddr: u64 = macho.getNode(ni.parent(&macho.mf).unwrap().?).segment.vaddr(macho);
3376 .indirect_syms => {},
3377 .section_indirect_syms => |section| {
3378 const offset, _ = ni.location(&macho.mf).resolve(&macho.mf);
3379 macho.targetStore(&section.lcPtr(macho).reserved1, @intCast(
3380 @divExact(offset, 4), // valid due to alignment
3381 ));
3382 },
3383 .segment => |segment| {
3384 if (segment.fixupChainStartsNode(macho).unwrap()) |starts_in_segment_node| {
3385 const starts_in_segment: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
3386 starts_in_segment_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
3387 ));
3388 macho.targetStore(
3389 &starts_in_segment.segment_offset,
3390 segment.vaddr(macho) - Segment.text.vaddr(macho),
3391 );
3392 }
3393 return; // segments don't bubble moved
3394 },
3395 .section, .section_manual_size => |section| {
3396 const segment_vaddr: u64 = section.segment(macho).vaddr(macho);
11733397 const section_offset = ni.location(&macho.mf).resolve(&macho.mf)[0];
11743398 macho.targetStore(&section.lcPtr(macho).addr, segment_vaddr + section_offset);
3399
3400 if (section == macho.known_sections.stubs) {
3401 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3402 const got_index = macho.got.getIndex(symbol).?;
3403 macho.writeStubEntry(stub_index, got_index);
3404 }
3405 for (macho.got.keys()) |symbol| {
3406 try symbol.applyTargetRelocs(macho, .init(.{
3407 .symbol = false,
3408 .branch = true,
3409 .got = false,
3410 .tlv = false,
3411 }));
3412 }
3413 }
3414
3415 if (section == macho.known_sections.got) {
3416 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3417 const got_index = macho.got.getIndex(symbol).?;
3418 macho.writeStubEntry(stub_index, got_index);
3419 }
3420 for (macho.got.keys()) |symbol| {
3421 try symbol.applyTargetRelocs(macho, .init(.{
3422 .symbol = false,
3423 .branch = false,
3424 .got = true,
3425 .tlv = false,
3426 }));
3427 }
3428 }
3429
3430 // If it is the GOT which has moved, its runtime fixup need to be flushed.
3431 if (section == macho.known_sections.got and macho.got.count() > 0) {
3432 const segment = section.segment(macho);
3433
3434 const old_start = macho.prev_got_offset;
3435 const new_start: u32 = off: {
3436 const got_file_off = ni.fileLocation(&macho.mf, false).offset;
3437 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3438 break :off @intCast(got_file_off - seg_file_off);
3439 };
3440
3441 const page_align = macho.targetPageAlign();
3442 const got_size = @as(u64, macho.got.count()) * macho.targetPtrSize();
3443
3444 const old_page_start: u32 = @intCast(@divFloor(old_start, page_align.toByteUnits()));
3445 const old_page_end: u32 = @intCast(@divCeil(old_start + got_size, page_align.toByteUnits()));
3446 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3447 macho.base.comp.gpa,
3448 old_page_end - old_page_start,
3449 );
3450 for (old_page_start..old_page_end) |page_index| {
3451 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3452 .seg = segment,
3453 .page = @intCast(page_index),
3454 }, {});
3455 }
3456
3457 const new_page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3458 const new_page_end: u32 = @intCast(@divCeil(new_start + got_size, page_align.toByteUnits()));
3459 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3460 macho.base.comp.gpa,
3461 new_page_end - new_page_start,
3462 );
3463 for (new_page_start..new_page_end) |page_index| {
3464 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3465 .seg = segment,
3466 .page = @intCast(page_index),
3467 }, {});
3468 }
3469
3470 macho.prev_got_offset = new_start;
3471 }
11753472 },
11763473 .symtab => {},
11773474 .strtab => {},
3475 .linkedit_data => {
3476 // No need to bubble moved.
3477 return;
3478 },
3479 .fixup_chains_header => {},
3480 .fixup_chains_imports => {
3481 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3482 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3483 macho.node.fixup_chains_header.slice(&macho.mf),
3484 ));
3485 macho.targetStore(&hdr.imports_offset, @intCast(offset));
3486 },
3487 .fixup_chains_symbol_names => {
3488 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3489 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3490 macho.node.fixup_chains_header.slice(&macho.mf),
3491 ));
3492 macho.targetStore(&hdr.symbols_offset, @intCast(offset));
3493 },
3494 .fixup_chains_starts => {
3495 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3496 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3497 macho.node.fixup_chains_header.slice(&macho.mf),
3498 ));
3499 macho.targetStore(&hdr.starts_offset, @intCast(offset));
3500 },
3501 .fixup_chains_starts_in_image => {},
3502 .fixup_chains_starts_in_segment => |segment| {
3503 const starts_in_image_off = macho.node.fixup_chains_starts_in_image.location(&macho.mf).resolve(&macho.mf)[0];
3504 const starts_in_segment_off = ni.location(&macho.mf).resolve(&macho.mf)[0];
3505
3506 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
3507 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
3508 ));
3509 macho.targetStore(
3510 &chained_starts_in_image[@backingInt(segment) + 2],
3511 @intCast(starts_in_segment_off - starts_in_image_off),
3512 );
3513 },
3514 .input_atom => |iai| {
3515 const input_atom_offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3516 const section = macho.getNode(iai.node(macho).parent(&macho.mf).unwrap().?).section;
3517
3518 const old_atom_addr = iai.ptrConst(macho).vaddr;
3519 const new_atom_addr = macho.targetLoad(&section.lcPtrConst(macho).addr) + input_atom_offset;
3520 iai.ptr(macho).vaddr = new_atom_addr;
3521
3522 const first_reloc = iai.ptr(macho).first_reloc;
3523 for (macho.relocs.items[@backingInt(first_reloc)..]) |*reloc| {
3524 if (reloc.node != ni) break;
3525 try reloc.apply(macho);
3526 }
3527
3528 var sym_it = iai.ptrConst(macho).first_symbol;
3529 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3530 const new_addr = sym.value(macho) - old_atom_addr + new_atom_addr;
3531 try sym.flushMoved(macho, new_addr);
3532 }
3533
3534 // If the atom which has moved contains runtime fixups, they need to be flushed.
3535 if (iai.ptr(macho).any_fixups) {
3536 const segment = section.segment(macho);
3537
3538 const page_align = macho.targetPageAlign();
3539
3540 const new_start: u32 = off: {
3541 const ni_file_off = ni.fileLocation(&macho.mf, false).offset;
3542 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3543 break :off @intCast(ni_file_off - seg_file_off);
3544 };
3545 const new_end = new_start + ni.location(&macho.mf).resolve(&macho.mf)[1];
3546
3547 const page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3548 const page_end: u32 = @intCast(@divCeil(new_end, page_align.toByteUnits()));
3549 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3550 macho.base.comp.gpa,
3551 page_end - page_start,
3552 );
3553 for (page_start..page_end) |page_index| {
3554 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3555 .seg = segment,
3556 .page = @intCast(page_index),
3557 }, {});
3558 }
3559 }
3560 },
11783561 }
11793562 try ni.childrenMoved(macho.base.comp.gpa, &macho.mf);
11803563}
......@@ -1185,6 +3568,29 @@ fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
11853568 .macho_header => {
11863569 assert(offset == 0);
11873570 },
3571 .symtab => {
3572 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3573 macho.targetStore(&symtab_cmd.symoff, @intCast(offset));
3574 },
3575 .strtab => {
3576 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3577 macho.targetStore(&symtab_cmd.stroff, @intCast(offset));
3578 },
3579 .indirect_syms => {
3580 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3581 macho.targetStore(&dysymtab_cmd.indirectsymoff, @intCast(offset));
3582 },
3583 .section_indirect_syms => {},
3584 .linkedit_data => |lc| {
3585 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3586 macho.targetStore(&linkedit_data_cmd.dataoff, @intCast(offset));
3587 },
3588 .fixup_chains_header => {},
3589 .fixup_chains_imports => {},
3590 .fixup_chains_symbol_names => {},
3591 .fixup_chains_starts => {},
3592 .fixup_chains_starts_in_image => {},
3593 .fixup_chains_starts_in_segment => {},
11883594 .segment => |segment| {
11893595 macho.targetStore(&segment.lcPtr(macho).fileoff, @intCast(offset));
11903596 var child_oni = ni.first(&macho.mf);
......@@ -1192,17 +3598,10 @@ fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
11923598 macho.flushMachOFileOffset(child_ni);
11933599 }
11943600 },
1195 .section => |section| {
3601 .section, .section_manual_size => |section| {
11963602 macho.targetStore(&section.lcPtr(macho).offset, @intCast(offset));
11973603 },
1198 .symtab => {
1199 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1200 macho.targetStore(&symtab_cmd.symoff, @intCast(offset));
1201 },
1202 .strtab => {
1203 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1204 macho.targetStore(&symtab_cmd.stroff, @intCast(offset));
1205 },
3604 .input_atom => {},
12063605 }
12073606}
12083607fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
......@@ -1210,19 +3609,215 @@ fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
12103609 switch (macho.getNode(ni)) {
12113610 .macho => {},
12123611 .macho_header => {},
3612 .symtab, .strtab => {
3613 // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do
3614 // that when we actually add strings or symbols.
3615 },
3616 .indirect_syms => {
3617 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3618 macho.targetStore(&dysymtab_cmd.nindirectsyms, @intCast(@divExact(size, 4)));
3619 },
3620 .section_indirect_syms => {},
3621 .linkedit_data => |lc| {
3622 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3623 macho.targetStore(&linkedit_data_cmd.datasize, @intCast(size));
3624 },
3625 .fixup_chains_header => {},
3626 .fixup_chains_imports => {},
3627 .fixup_chains_symbol_names => {},
3628 .fixup_chains_starts => {},
3629 .fixup_chains_starts_in_image => {},
3630 .fixup_chains_starts_in_segment => {},
12133631 .segment => |segment| {
12143632 macho.targetStore(&segment.lcPtr(macho).filesize, size);
12153633 },
12163634 .section => |section| {
12173635 macho.targetStore(&section.lcPtr(macho).size, size);
12183636 },
1219 .symtab, .strtab => {
1220 // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do
1221 // that when we actually add strings or symbols.
3637 .section_manual_size => {},
3638 .input_atom => {},
3639 }
3640}
3641
3642fn getNodeVAddr(macho: *MachO, ni: MappedFile.Node.Index) u64 {
3643 return switch (macho.getNode(ni)) {
3644 .macho => unreachable,
3645 .macho_header => unreachable,
3646 .symtab => unreachable,
3647 .strtab => unreachable,
3648 .indirect_syms => unreachable,
3649 .section_indirect_syms => unreachable,
3650 .linkedit_data => unreachable,
3651 .fixup_chains_header => unreachable,
3652 .fixup_chains_imports => unreachable,
3653 .fixup_chains_symbol_names => unreachable,
3654 .fixup_chains_starts => unreachable,
3655 .fixup_chains_starts_in_image => unreachable,
3656 .fixup_chains_starts_in_segment => unreachable,
3657 .segment => |segment| segment.vaddr(macho),
3658 .section, .section_manual_size => |section| section.vaddr(macho),
3659 .input_atom => |iai| iai.ptr(macho).vaddr,
3660 };
3661}
3662fn getNodeSection(macho: *MachO, ni: MappedFile.Node.Index) Section {
3663 var cur_ni = ni;
3664 while (true) {
3665 switch (macho.getNode(cur_ni)) {
3666 .section, .section_manual_size => |section| return section,
3667 else => cur_ni = cur_ni.parent(&macho.mf).unwrap().?,
3668 }
3669 }
3670}
3671fn nodeFirstSymbolPtr(macho: *MachO, ni: MappedFile.Node.Index) *Symbol.Index.Optional {
3672 return switch (macho.getNode(ni)) {
3673 .input_atom => |iai| &iai.ptr(macho).first_symbol,
3674 else => unreachable,
3675 };
3676}
3677
3678fn targetStubSize(macho: *const MachO) u8 {
3679 return switch (macho.base.comp.getTarget().cpu.arch) {
3680 else => unreachable,
3681 .aarch64 => 12,
3682 .x86_64 => 6,
3683 };
3684}
3685fn appendStubEntry(macho: *MachO, old_len: u64, got_index: u64, target_symbol: Symbol.Index) Error!void {
3686 const gpa = macho.base.comp.gpa;
3687
3688 const entry_size = macho.targetStubSize();
3689 const stub_offset = entry_size * old_len;
3690 assert(macho.targetLoad(&macho.known_sections.stubs.lcPtrConst(macho).size) == stub_offset);
3691
3692 const new_size = stub_offset + entry_size;
3693 try macho.known_sections.stubs.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3694 macho.targetStore(&macho.known_sections.stubs.lcPtr(macho).size, new_size);
3695
3696 macho.writeStubEntry(old_len, got_index);
3697
3698 try macho.node.stubs_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3699 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3700 macho.node.stubs_indirect_syms.slice(&macho.mf),
3701 ));
3702 indirect_syms[@intCast(old_len)] = target_symbol;
3703}
3704fn writeStubEntry(macho: *MachO, stub_index: u64, got_index: u64) void {
3705 if (macho.known_sections.stubs.node(macho).hasMoved(&macho.mf)) return;
3706 if (macho.known_sections.got.node(macho).hasMoved(&macho.mf)) return;
3707
3708 const entry_size = macho.targetStubSize();
3709 const stub_offset = entry_size * stub_index;
3710
3711 const got_entry_vaddr = macho.known_sections.got.vaddr(macho) + got_index * macho.targetPtrSize();
3712 const new_stub_vaddr = macho.known_sections.stubs.vaddr(macho) + stub_offset;
3713
3714 switch (macho.base.comp.getTarget().cpu.arch) {
3715 else => unreachable,
3716 .aarch64 => {
3717 // The first instruction of the stub will be an ADRP for which we need to compute the
3718 // offset from that instruction's 4k page to the GOT entry's 4k page.
3719 const cur_page_base = (new_stub_vaddr >> 12) << 12;
3720 const target_page_base = (got_entry_vaddr >> 12) << 12;
3721 const cur_page_to_target_page: i64 = @bitCast(target_page_base - cur_page_base);
3722
3723 // Then we will use an LDR to reference the GOT entry on that page.
3724 const target_page_off: u12 = @truncate(got_entry_vaddr);
3725
3726 const insts: []align(4) aarch64.encoding.Instruction = @ptrCast(@alignCast(
3727 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3728 ));
3729 @memcpy(insts, @as([]const aarch64.encoding.Instruction, &.{
3730 .adrp(.x16, @intCast(cur_page_to_target_page)),
3731 .ldr(.x16, .{ .unsigned_offset = .{ .base = .x16, .offset = target_page_off } }),
3732 .br(.x16),
3733 }));
3734 if (.little != std.lang.Endian.native) { // aarch64 instructions are little-endian
3735 std.mem.byteSwapAllElements(aarch64.encoding.Instruction, insts);
3736 }
3737 },
3738 .x86_64 => {
3739 const new_stub_slice: []align(2) u8 = @ptrCast(@alignCast(
3740 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3741 ));
3742 // jmpq *got_entry(%rip)
3743 @memcpy(new_stub_slice[0..2], @as([]const u8, &.{ 0xff, 0x25 }));
3744 const offset_ptr: *align(2) i32 = @ptrCast(new_stub_slice[2..]);
3745 const offset: i64 = @bitCast(got_entry_vaddr -% (new_stub_vaddr + 6));
3746 macho.targetStore(offset_ptr, @intCast(offset));
12223747 },
12233748 }
12243749}
12253750
3751fn appendGotEntry(macho: *MachO, old_len: u64, target_symbol: Symbol.Index) Error!void {
3752 const gpa = macho.base.comp.gpa;
3753
3754 const ptr_size = macho.targetPtrSize();
3755 const entry_offset = ptr_size * old_len;
3756 assert(macho.targetLoad(&macho.known_sections.got.lcPtrConst(macho).size) == entry_offset);
3757
3758 const new_size = entry_offset + ptr_size;
3759 try macho.known_sections.got.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3760 macho.targetStore(&macho.known_sections.got.lcPtr(macho).size, new_size);
3761
3762 const new_entry = macho.known_sections.got.node(macho).slice(&macho.mf)[@intCast(entry_offset)..][0..ptr_size];
3763 @memset(new_entry, 0);
3764
3765 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
3766 const segment = macho.known_sections.got.segment(macho);
3767 const page_index: u16 = @intCast(@divFloor(
3768 macho.prev_got_offset + entry_offset,
3769 macho.targetPageAlign().toByteUnits(),
3770 ));
3771 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
3772 }
3773
3774 try macho.node.got_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3775 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3776 macho.node.got_indirect_syms.slice(&macho.mf),
3777 ));
3778 indirect_syms[@intCast(old_len)] = target_symbol;
3779}
3780
3781fn appendImportEntry(macho: *MachO, old_len: u64, symbol_index: Symbol.Index) Error!void {
3782 const gpa = macho.base.comp.gpa;
3783
3784 const new_size = @sizeOf(std.macho.dyld_chained_import_addend64) * (old_len + 1);
3785 try macho.node.fixup_chains_imports.ensureMinimumSize(gpa, &macho.mf, new_size);
3786
3787 const symbol_name_off = macho.fixup_chain_symbol_names_len;
3788 macho.fixup_chain_symbol_names_len += @intCast(symbol_index.name(macho).len + 1);
3789 try macho.node.fixup_chains_symbol_names.ensureMinimumSize(gpa, &macho.mf, macho.fixup_chain_symbol_names_len);
3790 {
3791 const sym_name = symbol_index.name(macho);
3792 const name_slice = macho.node.fixup_chains_symbol_names.slice(&macho.mf)[symbol_name_off..];
3793 @memcpy(name_slice[0..sym_name.len], sym_name);
3794 name_slice[sym_name.len] = 0;
3795 }
3796
3797 {
3798 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3799 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
3800 ));
3801 assert(macho.targetLoad(&hdr.imports_count) == old_len);
3802 macho.targetStore(&hdr.imports_count, @intCast(old_len + 1));
3803 }
3804
3805 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
3806 macho.node.fixup_chains_imports.slice(&macho.mf)[0..@intCast(new_size)],
3807 ));
3808 imports[@intCast(old_len)] = .{
3809 .bits = .{
3810 .lib_ordinal = 0xFFFE,
3811 .weak_import = false,
3812 .name_offset = symbol_name_off,
3813 },
3814 .addend = 0,
3815 };
3816 if (macho.targetEndian() != std.lang.Endian.native) {
3817 std.mem.byteSwapAllFields(std.macho.dyld_chained_import_addend64, &imports[@intCast(old_len)]);
3818 }
3819}
3820
12263821pub fn updateFunc(
12273822 macho: *MachO,
12283823 pt: Zcu.PerThread,
......@@ -1277,15 +3872,68 @@ fn printNode(
12773872 ni: MappedFile.Node.Index,
12783873 indent: usize,
12793874) Io.Writer.Error!void {
1280 const node = macho.getNode(ni);
12813875 try w.splatByteAll(' ', indent);
1282 switch (node) {
3876 switch (macho.getNode(ni)) {
12833877 .macho => try w.writeAll("macho"),
12843878 .macho_header => try w.writeAll("macho_header"),
1285 .section => |section| try w.print("section({q})", .{section.name(macho)}),
1286 .segment => |segment| try w.print("segment({q})", .{segment.name(macho)}),
12873879 .symtab => try w.writeAll("symtab"),
12883880 .strtab => try w.writeAll("strtab"),
3881 .indirect_syms => try w.writeAll("indirect_syms"),
3882 .section_indirect_syms => |section| try w.print("section_indirect_syms({s},{s})", .{
3883 section.segmentName(macho),
3884 section.name(macho),
3885 }),
3886 .linkedit_data => |lc| try w.print("linkedit_data(LC_{t})", .{
3887 macho.targetLoad(&lc.ptrConst(macho, std.macho.load_command).cmd),
3888 }),
3889 .fixup_chains_header => try w.writeAll("fixup_chains_header"),
3890 .fixup_chains_imports => try w.writeAll("fixup_chains_imports"),
3891 .fixup_chains_symbol_names => try w.writeAll("fixup_chains_symbol_names"),
3892 .fixup_chains_starts => try w.writeAll("fixup_chains_starts"),
3893 .fixup_chains_starts_in_image => try w.writeAll("fixup_chains_starts_in_image"),
3894 .fixup_chains_starts_in_segment => |seg| try w.print("fixup_chains_starts_in_segment({s})", .{seg.name(macho)}),
3895 .section => |section| try w.print("section({s},{s})", .{
3896 section.segmentName(macho),
3897 section.name(macho),
3898 }),
3899 .section_manual_size => |section| try w.print("section_manual_size({s},{s})", .{
3900 section.segmentName(macho),
3901 section.name(macho),
3902 }),
3903 .segment => |segment| try w.print("segment({s})", .{segment.name(macho)}),
3904 .input_atom => |iai| {
3905 // It's useful to try and give a name to the input atom, but input atoms don't actually
3906 // have names. However, a decent approach if the input atom only contains one symbol is
3907 // to print that symbol's name. We also accept the input atom containing many global
3908 // symbols but only one internal symbol (since that's how Zig exports work).
3909 if (iai.ptrConst(macho).first_symbol.unwrap()) |first_symbol| {
3910 if (first_symbol.ptrConst(macho).next_in_node == .none) {
3911 // There is only one symbol in the section, so it's a good identifier.
3912 try w.print("input_atom({q})", .{first_symbol.name(macho)});
3913 } else {
3914 // Also, if it has N global symbols but only one internal symbol, go with the
3915 // internal name---this works well for how Zig handles exports.
3916 var prev_local_sym: Symbol.Index.Optional = .none;
3917 var sym_it: Symbol.Index.Optional = .wrap(first_symbol);
3918 const only_local_sym: Symbol.Index.Optional = while (sym_it.unwrap()) |sym| : (sym_it = sym.ptrConst(macho).next_in_node) {
3919 const n_type = macho.targetLoad(&sym.nlistConst(macho).n_type);
3920 if (!n_type.bits.ext and !n_type.bits.pext) {
3921 // this is an internal symbol
3922 if (prev_local_sym != .none) break .none;
3923 prev_local_sym = .wrap(sym);
3924 }
3925 } else prev_local_sym;
3926
3927 if (only_local_sym.unwrap()) |local_sym| {
3928 try w.print("input_atom({q})", .{local_sym.name(macho)});
3929 } else {
3930 try w.writeAll("input_atom([multiple symbols])");
3931 }
3932 }
3933 } else {
3934 try w.writeAll("input_atom([no symbols])");
3935 }
3936 },
12893937 }
12903938 {
12913939 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];
......@@ -1341,13 +3989,239 @@ fn printNode(
13413989 }
13423990}
13433991
3992fn flushFixupChains(
3993 macho: *MachO,
3994 segment: Segment,
3995 page: u16,
3996) void {
3997 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
3998
3999 const page_size = macho.targetPageAlign().toByteUnits();
4000 const start_vaddr = segment.vaddr(macho) + @as(u64, page) * page_size;
4001 const end_vaddr = start_vaddr + page_size;
4002
4003 var next_vaddr: u64 = 0;
4004 var node_it = segment.node(macho).last(&macho.mf);
4005 while (node_it.unwrap()) |node| : (node_it = node.prev(&macho.mf)) {
4006 // Other than `__LINKEDIT`, segments can only contain sections.
4007 const sect: Section = switch (macho.getNode(node)) {
4008 .section, .section_manual_size => |sect| sect,
4009 .macho => unreachable,
4010 .macho_header => continue, // the Mach header and load commands are in the __TEXT section
4011 .symtab => unreachable,
4012 .strtab => unreachable,
4013 .indirect_syms => unreachable,
4014 .section_indirect_syms => unreachable,
4015 .linkedit_data => unreachable,
4016 .fixup_chains_header => unreachable,
4017 .fixup_chains_imports => unreachable,
4018 .fixup_chains_symbol_names => unreachable,
4019 .fixup_chains_starts => unreachable,
4020 .fixup_chains_starts_in_image => unreachable,
4021 .fixup_chains_starts_in_segment => unreachable,
4022 .segment => unreachable,
4023 .input_atom => unreachable,
4024 };
4025 if (sect.vaddr(macho) >= end_vaddr) {
4026 // This section is after the page we're interested in. We're iterating backwards, so
4027 // that means we've not yet found the page.
4028 continue;
4029 }
4030 if (sect.vaddr(macho) + macho.targetLoad(&sect.lcPtrConst(macho).size) < start_vaddr) {
4031 // This section is before the page we're interested in. We're iterating backwards, so
4032 // that means we've finished scanning the page.
4033 break;
4034 }
4035 if (sect == macho.known_sections.got) {
4036 macho.flushFixupChainsGot(
4037 &next_vaddr,
4038 start_vaddr,
4039 start_vaddr + page_size,
4040 );
4041 } else {
4042 var sub_node_it = node.last(&macho.mf);
4043 while (sub_node_it.unwrap()) |sub_node| : (sub_node_it = sub_node.prev(&macho.mf)) {
4044 macho.flushFixupChainsNonSpecial(
4045 sub_node,
4046 &next_vaddr,
4047 start_vaddr,
4048 start_vaddr + page_size,
4049 );
4050 }
4051 }
4052 }
4053
4054 const page_starts: []u16 = @ptrCast(@alignCast(
4055 segment.fixupChainStartsNode(macho).unwrap().?.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
4056 ));
4057 if (next_vaddr == 0) {
4058 macho.targetStore(&page_starts[page], 0xFFFF);
4059 } else {
4060 macho.targetStore(&page_starts[page], @intCast(next_vaddr - start_vaddr));
4061 }
4062}
4063fn flushFixupChainsGot(
4064 macho: *MachO,
4065 next_vaddr: *u64,
4066 start_vaddr: u64,
4067 end_vaddr: u64,
4068) void {
4069 const ptr_size = macho.targetPtrSize();
4070 const got_start_vaddr = macho.known_sections.got.vaddr(macho);
4071 const got_end_vaddr = got_start_vaddr + @as(u64, macho.got.count()) * ptr_size;
4072 if (got_start_vaddr >= end_vaddr) return;
4073 if (got_end_vaddr <= start_vaddr) return;
4074 // There is some overlap between the GOT and the page range.
4075 const start_index = @divExact(start_vaddr -| got_start_vaddr, ptr_size);
4076 const end_index = @min(@divExact(end_vaddr -| got_start_vaddr, ptr_size), macho.got.count());
4077
4078 const got_node = macho.known_sections.got.node(macho);
4079 const got_syms = macho.got.keys();
4080
4081 var got_index = end_index;
4082 while (got_index > start_index) {
4083 got_index -= 1;
4084 macho.writeOneFixup(
4085 got_syms[got_index],
4086 0,
4087 got_node,
4088 got_index * ptr_size,
4089 start_vaddr,
4090 end_vaddr,
4091 next_vaddr,
4092 );
4093 }
4094}
4095fn flushFixupChainsNonSpecial(
4096 macho: *MachO,
4097 node: MappedFile.Node.Index,
4098 next_vaddr: *u64,
4099 start_vaddr: u64,
4100 end_vaddr: u64,
4101) void {
4102 switch (macho.getNode(node)) {
4103 .macho => unreachable,
4104 .macho_header => unreachable,
4105 .symtab => unreachable,
4106 .strtab => unreachable,
4107 .indirect_syms => unreachable,
4108 .section_indirect_syms => unreachable,
4109 .linkedit_data => unreachable,
4110 .fixup_chains_header => unreachable,
4111 .fixup_chains_imports => unreachable,
4112 .fixup_chains_symbol_names => unreachable,
4113 .fixup_chains_starts => unreachable,
4114 .fixup_chains_starts_in_image => unreachable,
4115 .fixup_chains_starts_in_segment => unreachable,
4116 .segment => unreachable,
4117 .section => unreachable,
4118 .section_manual_size => unreachable,
4119 .input_atom => |iai| {
4120 const first_reloc = iai.ptr(macho).first_reloc;
4121
4122 var reloc = first_reloc;
4123 var prev_reloc_off: ?u32 = null;
4124 while (@backingInt(reloc) < macho.relocs.items.len and
4125 reloc.ptr(macho).node == node)
4126 {
4127 if (prev_reloc_off) |prev_off| {
4128 assert(reloc.ptr(macho).offset > prev_off);
4129 }
4130 prev_reloc_off = reloc.ptr(macho).offset;
4131 reloc = @fromBackingInt(@backingInt(reloc) + 1);
4132 }
4133
4134 while (reloc != first_reloc) {
4135 reloc = @fromBackingInt(@backingInt(reloc) - 1);
4136 if (reloc.ptr(macho).bits.result == .fixup) {
4137 macho.writeOneFixup(
4138 reloc.ptr(macho).target_symbol,
4139 reloc.ptr(macho).addend,
4140 reloc.ptr(macho).node,
4141 reloc.ptr(macho).offset,
4142 start_vaddr,
4143 end_vaddr,
4144 next_vaddr,
4145 );
4146 }
4147 }
4148 },
4149 }
4150}
4151fn writeOneFixup(
4152 macho: *MachO,
4153 target: Symbol.Index,
4154 addend: u64,
4155 dest_node: MappedFile.Node.Index,
4156 dest_offset: u64,
4157 start_vaddr: u64,
4158 end_vaddr: u64,
4159 next_vaddr: *u64,
4160) void {
4161 assert(macho.targetPtrSize() == 8);
4162 const dest_vaddr = macho.getNodeVAddr(dest_node) + dest_offset;
4163 if (dest_vaddr < start_vaddr or dest_vaddr >= end_vaddr) return;
4164 const dest_ptr: *align(4) std.macho.dyld_chained_ptr_64 = @ptrCast(@alignCast(
4165 dest_node.slice(&macho.mf)[@intCast(dest_offset)..][0..8],
4166 ));
4167 switch (macho.targetLoad(&target.nlist(macho).n_type).bits.type) {
4168 .undf => {
4169 const import_index = macho.imports.getIndex(target).?;
4170 macho.targetStore(dest_ptr, .{
4171 .kind = .bind,
4172 .next = if (next_vaddr.* != 0) @intCast(
4173 @divExact(next_vaddr.* - dest_vaddr, 4),
4174 ) else 0,
4175 .per_kind = .{ .bind = .{
4176 .ordinal = @intCast(import_index),
4177 .addend = std.math.cast(u8, addend) orelse {
4178 std.debug.panic("TODO(MachO2): large fixup addend (target={q}, addend={d})", .{
4179 target.name(macho), addend,
4180 });
4181 },
4182 } },
4183 });
4184 next_vaddr.* = dest_vaddr;
4185 },
4186 .sect => {
4187 const target_val: packed struct(u64) {
4188 low36: u36,
4189 mid: u20,
4190 high8: u8,
4191 } = @bitCast(target.value(macho) +% addend);
4192 if (target_val.mid != 0) {
4193 @panic("TODO(MachO2): report non-zero mid bits in rebase fixup (vmaddrs too large?)");
4194 }
4195 macho.targetStore(dest_ptr, .{
4196 .kind = .rebase,
4197 .next = if (next_vaddr.* != 0) @intCast(
4198 @divExact(next_vaddr.* - dest_vaddr, 4),
4199 ) else 0,
4200 .per_kind = .{ .rebase = .{
4201 .target = target_val.low36,
4202 .high8 = target_val.high8,
4203 } },
4204 });
4205 next_vaddr.* = dest_vaddr;
4206 },
4207 .abs => {
4208 // We actually *don't* need a fixup---this is just a static relocation with extra steps!
4209 const target_val = target.value(macho) +% addend;
4210 macho.targetStore(dest_ptr, @bitCast(target_val));
4211 },
4212 else => unreachable,
4213 }
4214}
4215
13444216const std = @import("std");
13454217const assert = std.debug.assert;
13464218const log = std.log.scoped(.link);
13474219const Allocator = std.mem.Allocator;
13484220const Io = std.Io;
13494221
4222const aarch64 = @import("../codegen/aarch64.zig");
13504223const link = @import("../link.zig");
4224const tapi = @import("tapi.zig");
13514225const codegen = @import("../codegen.zig");
13524226const Compilation = @import("../Compilation.zig");
13534227const InternPool = @import("../InternPool.zig");