authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-15 23:09:16+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-16 09:56:43+01:00
log5e4275346c522ced19f0baacf8a375177bc684a8
tree642075981aa9b8511ea5fa9edc4fb12eafd22661
parent56eb5323106cccfea7a5212dd6ab2e66d35f3f64
signature Commit is signed but in an unrecognized format.

MachO2: get some stuff working

This linker is now capable of building basic Zig executables with `-fsingle-threaded -fstrip -fno-unwind-tables`. So far I think I've managed to architect this reasonably well thanks to the experience I have gained working on `Elf2`. Unfortunately I have not managed to turn on any CI tests in this branch because `MachO2` does not yet support code-signing. However, by manually signing executables on the CLI (`codesign -s - path/to/exe`), I have confirmed that the std tests and behavior tests both compile and run successfully using the new linker, for both aarch64-macos and x86_64-macos. (These tests are all using the LLVM backend---this linker is not yet integrated with the self-hosted codegen pipeline, although that shouldn't be too tricky to do!) Here are some important next steps: * Ad-hoc code-signing * Thread-local variable support * Symbol exports via `LC_DYLD_EXPORTS_TRIE` * Atom splitting in `__TEXT,__cstring` section * Atom deduplication in `__TEXT,__literal4`, `__TEXT,__literal8`, and `__TEXT,__literal16` sections * dylib input handling (and more complete tbd handling) * Support for bind fixup addends >255 * Support for "subtractor" relocations (although I doubt these are at all common in practice) * Range extension thunks on aarch64 A few of those are fairly straightforward, while others are a little more complex and will require some design work.

4 files changed, 3257 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+3095-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,456 @@ 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, macho: *const MachO, 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: *align(1) const aarch64.encoding.Instruction = @ptrCast(dest_overlong[0..4]);
436 return switch (macho.targetLoad(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: *align(1) const aarch64.encoding.Instruction = @ptrCast(dest_overlong[0..4]);
446 return switch (macho.targetLoad(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: *align(1) const aarch64.encoding.Instruction = @ptrCast(dest_overlong[0..4]);
459 return switch (macho.targetLoad(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 const inst: *align(1) aarch64.encoding.Instruction = @ptrCast(dest_slice[0..4]);
606 var x = macho.targetLoad(inst);
607 x.branch_exception_generating_system.unconditional_branch_immediate.group.imm26 = casted;
608 macho.targetStore(inst, x);
609 },
610 .arm64_pagerel21 => {
611 const cur_page: u53 = @intCast(dest_vaddr >> 12);
612 const target_page: u53 = @intCast(target_val >> 12);
613 const page_off: i53 = @bitCast(target_page -% cur_page);
614 const split: packed struct(i21) { lo: u2, hi: i19 } = @bitCast(
615 std.math.cast(i21, page_off) orelse return error.RelocationOverflow,
616 );
617
618 const inst: *align(1) aarch64.encoding.Instruction = @ptrCast(dest_slice[0..4]);
619 var x = macho.targetLoad(inst);
620 x.data_processing_immediate.pc_relative_addressing.adrp.immhi = split.hi;
621 x.data_processing_immediate.pc_relative_addressing.adrp.immlo = split.lo;
622 macho.targetStore(inst, x);
623 },
624 .arm64_pageoff12 => {
625 const page_off: u12 = @truncate(target_val);
626
627 // This could be one of several instructions: an `ADD`, an `LDR`, or an `STR`. The
628 // `LDR` and `STR` instructions have scale factors which we need to compensate for
629 // by dividing the value we store. Therefore, we need to figure out which case we're
630 // in by looking at the instruction encoding.
631 const inst: *align(1) aarch64.encoding.Instruction = @ptrCast(dest_slice[0..4]);
632 switch (macho.targetLoad(inst).decode()) {
633 .data_processing_immediate => {
634 var x = macho.targetLoad(inst);
635 x.data_processing_immediate.add_subtract_immediate.group.imm12 = page_off;
636 macho.targetStore(inst, x);
637 },
638 .load_store => |ls| {
639 const scale: u3 = switch (ls.register_unsigned_immediate.decode()) {
640 .integer => |i| @backingInt(i.group.size), // 1/2/4/8-byte load/store
641 .vector => |v| @backingInt(v.group.opc1.decode(v.group.size)), // 1/2/4/8/16-byte vector load/store
642 };
643 const shifted = page_off >> scale;
644 if (shifted << scale != page_off) {
645 return error.RelocationMisaligned;
646 }
647 var x = macho.targetLoad(inst);
648 x.load_store.register_unsigned_immediate.group.imm12 = shifted;
649 macho.targetStore(inst, x);
650 },
651 else => unreachable,
652 }
653 },
188654 }
655 }
656
657 const Index = enum(u32) {
658 _,
659
189660 const Optional = enum(u32) {
190661 none = std.math.maxInt(u32),
191662 _,
663
192664 fn unwrap(o: Optional) ?Index {
193665 return switch (o) {
194666 .none => null,
......@@ -199,6 +671,10 @@ const Symbol = struct {
199671 return @bitCast(i);
200672 }
201673 };
674
675 fn ptr(ri: Reloc.Index, macho: *MachO) *Reloc {
676 return &macho.relocs.items[@backingInt(ri)];
677 }
202678 };
203679};
204680
......@@ -210,8 +686,25 @@ const Node = union(enum) {
210686 symtab,
211687 strtab,
212688
689 indirect_syms,
690 section_indirect_syms: Section,
691
692 linkedit_data: LoadCommand,
693
694 /// Header of the `LC_DYLD_CHAINED_FIXUPS` load command's `.linkedit_data` node, containing the
695 /// `std.macho.dyld_chained_fixups_header` struct.
696 fixup_chains_header,
697 fixup_chains_imports,
698 fixup_chains_symbol_names,
699 fixup_chains_starts,
700 fixup_chains_starts_in_image,
701 fixup_chains_starts_in_segment: Segment,
702
213703 segment: Segment,
214704 section: Section,
705 section_manual_size: Section,
706
707 input_atom: InputAtom.Index,
215708};
216709fn getNode(macho: *const MachO, ni: MappedFile.Node.Index) Node {
217710 return macho.nodes.get(@backingInt(ni));
......@@ -275,17 +768,34 @@ fn create(
275768 .offsets = .empty,
276769 .pagezero = undefined,
277770 .symtab = undefined,
771 .dysymtab = undefined,
278772 .entry_point = null,
279773 },
774 .known_sections = undefined,
280775 .segments = .empty,
281776 .sections = .empty,
282 .segments_by_name = .empty,
283777 .sections_by_name = .empty,
284778
779 .dirty_fixup_chain_pages = .empty,
780
781 .input_atoms = .empty,
782
285783 .strtab = .empty,
286784 .symtab = .empty,
785 .global_symbols = .empty,
786
787 .relocs = .empty,
788 .overflowed_reloc_count = 0,
789 .misaligned_reloc_count = 0,
790
791 .fixup_chain_symbol_names_len = 0,
792 .imports = .empty,
793 .got = .empty,
794 .stubs = .empty,
287795
288796 .sections_changed_index = .empty,
797
798 .prev_got_offset = 0,
289799 };
290800 errdefer macho.deinit();
291801
......@@ -315,7 +825,7 @@ fn initInner(macho: *MachO) !void {
315825 .moved = true, // required by `addSegment`
316826 .bubbles_moved = false,
317827 });
318 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
828 try macho.nodes.append(gpa, .{ .segment = undefined });
319829
320830 macho.node.header = try text_seg_node.addOnlyHeaderChild(gpa, &macho.mf, .{
321831 .size = @sizeOf(mach_header_64),
......@@ -358,7 +868,7 @@ fn initInner(macho: *MachO) !void {
358868 .moved = true, // required by `addSegment`
359869 .bubbles_moved = false,
360870 });
361 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
871 try macho.nodes.append(gpa, .{ .segment = undefined });
362872
363873 // The "__PAGEZERO" segment must come before any other segment load commands. It is inaccessible
364874 // and occupies 4 GiB (apparently to catch invalid 32-bit pointer accesses).
......@@ -374,38 +884,83 @@ fn initInner(macho: *MachO) !void {
374884 .nsects = 0,
375885 .flags = 0,
376886 });
377 const text_seg = try macho.addSegment(.{
887 assert(try macho.addSegment(.{
378888 .name = "__TEXT",
379889 .prot = .{ .READ = true, .EXEC = true },
380890 .existing_node = text_seg_node,
381 });
891 .flags = .{},
892 }) == .text);
382893 const data_seg = try macho.addSegment(.{
383894 .name = "__DATA",
384895 .prot = .{ .READ = true, .WRITE = true },
896 .flags = .{},
385897 });
898 // TODO: we should also create a `__DATA_CONST` segment with `.{ .flags = .read_only = true }`,
899 // and when we do, `loadObject` should use it for `InputSection.Usage.data_const_fixups`.
900 // Unfortunately, doing that now introduces a second floating segment node, which reveals that
901 // dyld has an annoying ordering requirement:
902 //
903 // dyld[77415]: segment '__DATA' file offset out of order
904 //
905 // We need to decide the best way to resolve this. Reordering the segments in the load commands
906 // and in virtual memory is probably too expensive to risk doing often, so we need to preserve
907 // the segment order somehow, e.g. by having all but one segment be a header or footer.
386908 const linkedit_seg = try macho.addSegment(.{
387909 .name = "__LINKEDIT",
388910 .prot = .{ .READ = true },
389911 .existing_node = linkedit_seg_node,
912 .flags = .{},
390913 });
391914
392915 _ = try macho.addSection(.{
393 .segment = text_seg,
916 .segment = .text,
394917 .name = "__text",
395918 .@"align" = .@"4",
396 .flags = .{ .some_instructions = true },
919 .flags = .{ .type = .regular, .some_instructions = true },
397920 });
398921 _ = try macho.addSection(.{
399 .segment = text_seg,
922 .segment = .text,
400923 .name = "__const",
401 .@"align" = .@"2",
402 .flags = .{},
924 .@"align" = .@"1",
925 .flags = .{
926 .type = .regular,
927 },
403928 });
404929 _ = try macho.addSection(.{
405930 .segment = data_seg,
406931 .name = "__data",
407 .@"align" = .@"2",
408 .flags = .{},
932 .@"align" = .@"1",
933 .flags = .{
934 .type = .regular,
935 },
936 });
937
938 macho.known_sections.got = try macho.addSection(.{
939 .segment = data_seg,
940 .name = "__got",
941 .@"align" = .@"8",
942 .manual_size = true,
943 .flags = .{ .type = .non_lazy_symbol_pointers },
944 // This field holds an offset into the "indirect symbol table" at which this section's
945 // indirect symbol references begin. It will be populated by `flushMoved` of a
946 // `.section_indirect_syms` node we'll create shortly.
947 .reserved1 = 0,
948 });
949
950 macho.known_sections.stubs = try macho.addSection(.{
951 .segment = .text,
952 .name = "__stubs",
953 .@"align" = .@"4",
954 .manual_size = true,
955 .flags = .{
956 .type = .symbol_stubs,
957 .some_instructions = true,
958 .pure_instructions = true,
959 },
960 // As with `__got`, will be populated by `flushMoved` of a `.section_indirect_syms` node.
961 .reserved1 = 0,
962 // `reserved2` holds the size of each stub entry.
963 .reserved2 = macho.targetStubSize(),
409964 });
410965
411966 macho.node.symtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
......@@ -423,7 +978,129 @@ fn initInner(macho: *MachO) !void {
423978 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, 1);
424979 macho.node.strtab.slice(&macho.mf)[0] = 0;
425980
426 _ = try macho.appendSimpleLoadCommand(std.macho.dyld_info_command, .{});
981 const indirect_syms_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
982 .alignment = .@"4",
983 .moved = true, // so that `indirectsymoff` in the dysymtab load command will be set
984 });
985 try macho.nodes.append(gpa, .indirect_syms);
986
987 macho.node.got_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.got });
992
993 macho.node.stubs_indirect_syms = try indirect_syms_node.addFloatingChild(gpa, &macho.mf, .{
994 .alignment = .@"4",
995 .moved = true, // so that `reserved1` in the section will be set
996 });
997 try macho.nodes.append(gpa, .{ .section_indirect_syms = macho.known_sections.stubs });
998
999 const chained_fixups_lc = try macho.appendSimpleLoadCommand(std.macho.linkedit_data_command, .{
1000 .cmd = .DYLD_CHAINED_FIXUPS,
1001 .dataoff = 0,
1002 .datasize = 0,
1003 });
1004 const chained_fixups_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
1005 .alignment = macho.mf.flags.block_size.max(.@"8"),
1006 .moved = true, // so that `dataoff` will be set
1007 .bubbles_moved = false,
1008 });
1009 try macho.nodes.append(gpa, .{ .linkedit_data = chained_fixups_lc });
1010
1011 macho.node.fixup_chains_header = try chained_fixups_node.addOnlyHeaderChild(gpa, &macho.mf, .{
1012 .size = @sizeOf(std.macho.dyld_chained_fixups_header),
1013 .alignment = .@"4",
1014 });
1015 try macho.nodes.append(gpa, .fixup_chains_header);
1016
1017 {
1018 const chained_fixups_hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
1019 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
1020 ));
1021 chained_fixups_hdr.* = .{
1022 .fixups_version = 0,
1023 .starts_offset = 0,
1024 .imports_offset = 0,
1025 .symbols_offset = 0,
1026 .imports_count = 0,
1027 .imports_format = .addend_64,
1028 .symbols_format = .uncompressed,
1029 };
1030 if (macho.targetEndian() != std.lang.Endian.native) {
1031 std.mem.byteSwapAllFields(std.macho.dyld_chained_fixups_header, chained_fixups_hdr);
1032 }
1033 }
1034
1035 // We are now going to create the `std.macho.dyld_chained_starts_in_segment` nodes for every
1036 // segment, which means we are assuming that no more segments will be created. Therefore, all
1037 // `addSegment` calls must come *before* this point.
1038
1039 const fixup_chains_starts = try chained_fixups_node.addFloatingChild(gpa, &macho.mf, .{
1040 .size = 0,
1041 .alignment = .@"8",
1042 .moved = true, // so that `starts_offset` is populated
1043 });
1044 try macho.nodes.append(gpa, .fixup_chains_starts);
1045
1046 macho.node.fixup_chains_starts_in_image = try fixup_chains_starts.addOnlyHeaderChild(gpa, &macho.mf, .{
1047 .size = 8 + 4 * macho.segments.len,
1048 .alignment = .@"4",
1049 });
1050 try macho.nodes.append(gpa, .fixup_chains_starts_in_image);
1051
1052 {
1053 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
1054 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
1055 ));
1056 chained_starts_in_image[0] = @intCast(macho.segments.len + 1); // +1 for `__PAGEZERO`
1057 chained_starts_in_image[1] = 0; // `__PAGEZERO`
1058 chained_starts_in_image[macho.segments.len + 1] = 0; // `__LINKEDIT`
1059 for (
1060 macho.segments.items(.fixup_chain_starts)[0 .. macho.segments.len - 1],
1061 0..,
1062 ) |*starts_in_segment, segment_usize| {
1063 const segment: Segment = @fromBackingInt(@intCast(segment_usize));
1064 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1065 starts_in_segment.* = .wrap(try fixup_chains_starts.addFloatingChild(gpa, &macho.mf, .{
1066 .alignment = .@"8",
1067 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1068 .moved = true,
1069 }));
1070 try macho.nodes.append(gpa, .{ .fixup_chains_starts_in_segment = segment });
1071 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1072 starts_in_segment.unwrap().?.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1073 ));
1074 starts_hdr.* = .{
1075 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1076 .page_size = @intCast(macho.targetPageAlign().toByteUnits()),
1077 .pointer_format = .@"64",
1078 .segment_offset = 0, // populated by `flushMoved` of the corresponding segment
1079 .max_valid_pointer = 0,
1080 .page_count = 0,
1081 };
1082 if (macho.targetEndian() != std.lang.Endian.native) {
1083 std.mem.byteSwapAllFields(std.macho.dyld_chained_starts_in_segment, starts_hdr);
1084 }
1085 }
1086 }
1087
1088 // This is a footer because dyld has an undocumented requirement that the imports must be
1089 // ordered after the starts.
1090 macho.node.fixup_chains_imports = try chained_fixups_node.addOnlyFooterChild(gpa, &macho.mf, .{
1091 .alignment = .@"8",
1092 .moved = true, // so that `imports_offset` is populated
1093 });
1094 try macho.nodes.append(gpa, .fixup_chains_imports);
1095
1096 // This is a footer because `dyld_info` complains if the imports appear after the symbol name
1097 // string table. Since there's no length associated with that string table, `dyld_info` can only
1098 // be sure they don't overlap if the string table is the last thing in the chained fixups data.
1099 macho.node.fixup_chains_symbol_names = try chained_fixups_node.addFooterChildBefore(gpa, &macho.mf, .none, .{
1100 .alignment = .@"8", // so that the alignment of `macho.node.fixup_chains_imports` is not violated
1101 .moved = true, // so that `symbols_offset` is populated
1102 });
1103 try macho.nodes.append(gpa, .fixup_chains_symbol_names);
4271104
4281105 macho.lc.symtab = try macho.appendSimpleLoadCommand(std.macho.symtab_command, .{
4291106 .symoff = 0,
......@@ -432,6 +1109,48 @@ fn initInner(macho: *MachO) !void {
4321109 .strsize = 1, // empty string (null terminator)
4331110 });
4341111
1112 // Nowadays, `LC_DYSYMTAB` is redundant with chained fixups and the symbol export trie. However,
1113 // if we omit it, some macOS tooling does get annoyed at us. Therefore, we try to emit a minimum
1114 // viable `LC_DYSYMTAB` command, just to keep the tooling happy.
1115 macho.lc.dysymtab = try macho.appendSimpleLoadCommand(std.macho.dysymtab_command, .{
1116 // Some tooling will check that the number of symbols according to the `LC_DYSYMTAB` command
1117 // matches the actual length of the symbol table in `LC_SYMTAB`. However, we don't want to
1118 // need to partition our symbol table into local/extdef/undef symbols, because that makes
1119 // incremental linking less efficient. Luckily, since this information is basically useless
1120 // nowadays, nothing seems to mind if we just say that every symbol is local. So, whenever
1121 // we add any symbol to the symtab, we will increase `ilocalsym` in lockstep with the symbol
1122 // count. We also need to increment `iextdefsym` and `iundefsym`, because some tooling may
1123 // check that local symbols come before extdef and undef symbols here. I don't even know why
1124 // they *have* the `iextdefsym` and `iundefsym` fields if they're only going to allow them
1125 // to have specific values...
1126 .ilocalsym = 0, // always 0
1127 .nlocalsym = 0, // equal to symbol count
1128 .iextdefsym = 0, // equal to symbol count
1129 .nextdefsym = 0, // always 0
1130 .iundefsym = 0, // equal to symbol count
1131 .nundefsym = 0, // always 0
1132
1133 // We *do* need to emit a valid indirect symbol table, which is an array of symbol indices
1134 // indicating which symbols are being targeted by entries in `__got` and `__stubs`. Although
1135 // not required for fixups, some debug tooling will still try to read this table without any
1136 // validation, so will just crash if it's absent or invalid. Luckily, the indirection here
1137 // actually makes this pretty straightforward for us to emit.
1138 .indirectsymoff = 0, // populated by `flushMachOFileOffset` of the `.indirect_syms` node
1139 .nindirectsyms = 0, // populated by `flushResized` of the `.indirect_syms` node
1140
1141 // These fields are *required* to be zeroed in modern Mach-O binaries.
1142 .tocoff = 0,
1143 .ntoc = 0,
1144 .modtaboff = 0,
1145 .nmodtab = 0,
1146 .extrefsymoff = 0,
1147 .nextrefsyms = 0,
1148 .extreloff = 0,
1149 .nextrel = 0,
1150 .locreloff = 0,
1151 .nlocrel = 0,
1152 });
1153
4351154 const want_dyld_path: bool = switch (comp.config.output_mode) {
4361155 .Obj => false,
4371156 .Lib => switch (comp.config.link_mode) {
......@@ -454,58 +1173,84 @@ fn initInner(macho: *MachO) !void {
4541173 }
4551174 }
4561175
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 }),
1176 entry: switch (macho.options.entry) {
1177 .default => switch (comp.config.output_mode) {
1178 .Exe => continue :entry .enabled,
1179 .Lib, .Obj => continue :entry .disabled,
1180 },
1181 .disabled => {},
1182 .enabled, .named => {
1183 macho.lc.entry_point = try macho.appendSimpleLoadCommand(
1184 std.macho.entry_point_command,
1185 .{ .entryoff = 0, .stacksize = macho.options.stack_size orelse 0 },
1186 );
1187 },
4631188 }
4641189
4651190 _ = try macho.appendSimpleLoadCommand(std.macho.uuid_command, .{
4661191 .uuid = @splat(0),
4671192 });
4681193
469 try macho.addSimpleSymbol("some_function", 0x1234, "__TEXT", "__text");
470 try macho.addSimpleSymbol("my_cool_data", 0x5678, "__DATA", "__data");
471
4721194 try macho.updateSegmentLoadAddresses();
4731195}
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,
1196fn addInternalSymbol(macho: *MachO, opts: struct {
4781197 name: []const u8,
479 value: u64,
480 segment_name: []const u8,
481 section_name: []const u8,
482) Error!void {
1198 value: Symbol.InitValue,
1199}) Error!Symbol.Index {
4831200 const gpa = macho.base.comp.gpa;
1201
4841202 try macho.symtab.ensureUnusedCapacity(gpa, 1);
4851203 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
4861204
487 const strx = try macho.string(name);
488 const section = macho.sectionByName(segment_name, section_name).?;
1205 const strx = try macho.string(opts.name);
4891206
4901207 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
4911208 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
4921209 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
4931210
1211 {
1212 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1213 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1214 // on why we're doing this.
1215 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1216 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1217 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1218 }
1219
4941220 const sym_index: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
4951221 macho.symtab.appendAssumeCapacity(.{
496 .next_in_section = section.firstSymbol(macho).*,
1222 .node = switch (opts.value) {
1223 .abs => .none,
1224 .addr_in_node => |addr| .wrap(addr.node),
1225 },
1226 .prev_in_node = .none,
1227 .next_in_node = .none,
1228 .first_target_reloc = .none,
4971229 });
498 section.firstSymbol(macho).* = .wrap(sym_index);
1230 if (sym_index.ptr(macho).node.unwrap()) |node| {
1231 const head_ptr = macho.nodeFirstSymbolPtr(node);
1232 sym_index.ptr(macho).next_in_node = head_ptr.*;
1233 if (head_ptr.*.unwrap()) |next_symbol| {
1234 next_symbol.ptr(macho).prev_in_node = .wrap(sym_index);
1235 }
1236 head_ptr.* = .wrap(sym_index);
1237 }
4991238
5001239 sym_index.nlist(macho).* = .{
5011240 .n_strx = @backingInt(strx),
5021241 .n_type = .{ .bits = .{
503 .ext = true,
504 .type = .sect,
1242 .ext = false,
1243 .type = switch (opts.value) {
1244 .abs => .abs,
1245 .addr_in_node => .sect,
1246 },
5051247 .pext = false,
5061248 .is_stab = 0,
5071249 } },
508 .n_sect = section.index(macho),
1250 .n_sect = switch (opts.value) {
1251 .abs => 0,
1252 .addr_in_node => |addr| macho.getNodeSection(addr.node).index(macho),
1253 },
5091254 .n_desc = .{
5101255 .arm_thumb_def = false,
5111256 .referenced_dynamically = false,
......@@ -515,54 +1260,311 @@ fn addSimpleSymbol(
5151260 .symbol_resolver = false,
5161261 .alt_entry = false,
5171262 },
518 .n_value = value,
1263 .n_value = switch (opts.value) {
1264 .abs => |x| x,
1265 .addr_in_node => |addr| macho.getNodeVAddr(addr.node) + addr.offset,
1266 },
5191267 };
5201268 if (macho.targetEndian() != std.lang.Endian.native) {
5211269 std.mem.byteSwapAllFields(std.macho.nlist_64, sym_index.nlist(macho));
5221270 }
523}
5241271
525fn addSegment(macho: *MachO, opts: struct {
1272 return sym_index;
1273}
1274fn addGlobalSymbol(macho: *MachO, opts: struct {
5261275 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 {
1276 linkage: std.lang.GlobalLinkage,
1277 visibility: enum { default, hidden },
1278 /// `null` means the symbol is undefined.
1279 value: ?Symbol.InitValue,
1280}) (Error || error{MultipleDefinitions})!Symbol.Index {
5331281 const gpa = macho.base.comp.gpa;
5341282
535 const segment_command_64 = std.macho.segment_command_64;
1283 try macho.symtab.ensureUnusedCapacity(gpa, 1);
1284 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
5361285
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", .{});
1286 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1287 const gop = try macho.global_symbols.getOrPutAdapted(gpa, opts.name, adapter);
1288 if (gop.found_existing) {
1289 const symbol = gop.key_ptr.*;
1290
1291 switch (opts.visibility) {
1292 .default => {},
1293 .hidden => {
1294 var n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1295 n_type.bits.pext = true;
1296 switch (macho.machFiletype()) {
1297 .OBJECT => {},
1298 .EXECUTE, .DYLIB => n_type.bits.ext = false,
1299 }
1300 macho.targetStore(&symbol.nlist(macho).n_type, n_type);
1301 // TODO: once the exports trie is implemented, remove `symbol` from it here
1302 },
1303 }
1304
1305 const existing_defined: bool, const existing_linkage: std.lang.GlobalLinkage = existing: {
1306 const n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1307 const n_desc = macho.targetLoad(&symbol.nlist(macho).n_desc);
1308 break :existing switch (n_type.bits.type) {
1309 .undf => .{ false, if (n_desc.weak_ref) .weak else .strong },
1310 else => .{ true, if (n_desc.weak_def_or_ref_to_weak) .weak else .strong },
1311 };
1312 };
1313
1314 const override: bool = if (opts.value != null) override: {
1315 if (!existing_defined) break :override true;
1316 // Two definitions of the same name!
1317 break :override switch (opts.linkage) {
1318 .strong => switch (existing_linkage) {
1319 .strong => return error.MultipleDefinitions,
1320 .weak => true,
1321 },
1322 .weak => false,
1323 };
1324 } else override: {
1325 if (existing_defined) break :override false;
1326 break :override switch (opts.linkage) {
1327 .strong => switch (existing_linkage) {
1328 .strong => false,
1329 .weak => true,
1330 },
1331 .weak => false,
1332 };
1333 };
1334
1335 if (override) {
1336 try macho.setGlobalSymbolValue(symbol, .{
1337 .linkage = opts.linkage,
1338 .value = opts.value,
1339 });
1340 }
1341
1342 return symbol;
5411343 }
1344 errdefer _ = macho.global_symbols.pop().?;
5421345
543 try macho.nodes.ensureUnusedCapacity(gpa, 1);
544 try macho.segments.ensureUnusedCapacity(gpa, 1);
545 try macho.segments_by_name.ensureUnusedCapacity(gpa, 1);
1346 const strx = try macho.string(opts.name);
5461347
547 const root_ni: MappedFile.Node.Index = .root;
1348 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1349 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
1350 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
5481351
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 });
1352 {
1353 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1354 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1355 // on why we're doing this.
1356 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1357 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1358 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1359 }
1360
1361 const symbol: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
1362 gop.key_ptr.* = symbol;
1363 macho.symtab.appendAssumeCapacity(.{
1364 .node = .none,
1365 .prev_in_node = .none,
1366 .next_in_node = .none,
1367 .first_target_reloc = .none,
1368 });
1369 symbol.nlist(macho).* = .{
1370 .n_strx = @backingInt(strx),
1371 .n_type = .{ .bits = .{
1372 .ext = switch (opts.visibility) {
1373 .default => true,
1374 .hidden => false,
1375 },
1376 .pext = switch (opts.visibility) {
1377 .default => false,
1378 .hidden => true,
1379 },
1380 .type = .undf,
1381 .is_stab = 0,
1382 } },
1383 .n_sect = 0,
1384 .n_desc = .{
1385 .arm_thumb_def = false,
1386 .referenced_dynamically = false,
1387 .discarded_or_no_dead_strip = false,
1388 .weak_ref = false,
1389 .weak_def_or_ref_to_weak = false,
1390 .symbol_resolver = false,
1391 .alt_entry = false,
1392 },
1393 .n_value = 0,
1394 };
1395 if (macho.targetEndian() != std.lang.Endian.native) {
1396 std.mem.byteSwapAllFields(std.macho.nlist_64, symbol.nlist(macho));
1397 }
1398
1399 try macho.setGlobalSymbolValue(symbol, .{
1400 .linkage = opts.linkage,
1401 .value = opts.value,
1402 });
1403
1404 return symbol;
1405}
1406fn setGlobalSymbolValue(macho: *MachO, symbol: Symbol.Index, opts: struct {
1407 linkage: std.lang.GlobalLinkage,
1408 value: ?Symbol.InitValue,
1409}) Allocator.Error!void {
1410 const gpa = macho.base.comp.gpa;
1411
1412 log.debug("setGlobalSymbolValue({q}, {?any})", .{ symbol.name(macho), opts.value });
1413
1414 const nlist = symbol.nlist(macho);
1415
1416 {
1417 const weak: bool = switch (opts.linkage) {
1418 .strong => false,
1419 .weak => true,
1420 };
1421 var n_desc = macho.targetLoad(&nlist.n_desc);
1422 n_desc.weak_ref = opts.value == null and weak;
1423 n_desc.weak_def_or_ref_to_weak = opts.value != null and weak;
1424 macho.targetStore(&nlist.n_desc, n_desc);
1425 }
1426
1427 // Remove this symbol from its old node.
1428 if (symbol.ptr(macho).node.unwrap()) |old_node| {
1429 if (symbol.ptr(macho).next_in_node.unwrap()) |next_symbol| {
1430 assert(next_symbol.ptr(macho).prev_in_node == Symbol.Index.Optional.wrap(symbol));
1431 next_symbol.ptr(macho).prev_in_node = symbol.ptr(macho).prev_in_node;
1432 }
1433 if (symbol.ptr(macho).prev_in_node.unwrap()) |prev_symbol| {
1434 assert(prev_symbol.ptr(macho).next_in_node == Symbol.Index.Optional.wrap(symbol));
1435 prev_symbol.ptr(macho).next_in_node = symbol.ptr(macho).next_in_node;
1436 } else {
1437 // We're the head of the list.
1438 macho.nodeFirstSymbolPtr(old_node).* = symbol.ptr(macho).next_in_node;
1439 }
1440 }
1441
1442 if (opts.value) |value| switch (value) {
1443 .abs => |val| {
1444 var n_type = macho.targetLoad(&nlist.n_type);
1445 n_type.bits.type = .abs;
1446 macho.targetStore(&nlist.n_type, n_type);
1447
1448 macho.targetStore(&nlist.n_sect, 0);
1449 macho.targetStore(&nlist.n_value, val);
1450
1451 symbol.ptr(macho).node = .none;
1452 symbol.ptr(macho).prev_in_node = .none;
1453 symbol.ptr(macho).next_in_node = .none;
1454 },
1455 .addr_in_node => |addr| {
1456 var n_type = macho.targetLoad(&nlist.n_type);
1457 n_type.bits.type = .sect;
1458 macho.targetStore(&nlist.n_type, n_type);
1459
1460 macho.targetStore(&nlist.n_sect, macho.getNodeSection(addr.node).index(macho));
1461 macho.targetStore(&nlist.n_value, macho.getNodeVAddr(addr.node) + addr.offset);
1462
1463 const head_ptr = macho.nodeFirstSymbolPtr(addr.node);
1464 symbol.ptr(macho).node = .wrap(addr.node);
1465 symbol.ptr(macho).prev_in_node = .none;
1466 symbol.ptr(macho).next_in_node = head_ptr.*;
1467 if (head_ptr.*.unwrap()) |next_symbol| {
1468 next_symbol.ptr(macho).prev_in_node = .wrap(symbol);
1469 }
1470 head_ptr.* = .wrap(symbol);
1471 },
1472 } else {
1473 var n_type = macho.targetLoad(&nlist.n_type);
1474 n_type.bits.type = .undf;
1475 macho.targetStore(&nlist.n_type, n_type);
1476
1477 macho.targetStore(&nlist.n_sect, 0);
1478 macho.targetStore(&nlist.n_value, 0);
1479
1480 symbol.ptr(macho).node = .none;
1481 symbol.ptr(macho).prev_in_node = .none;
1482 symbol.ptr(macho).next_in_node = .none;
1483 }
1484
1485 // If `symbol` has a GOT entry, it needs regenerating.
1486 if (macho.got.getIndex(symbol)) |got_index| {
1487 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
1488 const segment = macho.known_sections.got.segment(macho);
1489 const page_index: u16 = @intCast(@divFloor(
1490 macho.prev_got_offset + got_index * macho.targetPtrSize(),
1491 macho.targetPageAlign().toByteUnits(),
1492 ));
1493 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
1494 }
1495 }
1496
1497 // If `symbol` has an `imports` entry, it should not exist.
1498 if (macho.imports.getIndex(symbol)) |import_index| {
1499 // TODO: delete the import. For now we just mark it as weak so that dyld doesn't complain.
1500 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
1501 macho.node.fixup_chains_imports.slice(&macho.mf)[0 .. macho.imports.count() * @sizeOf(std.macho.dyld_chained_import_addend64)],
1502 ));
1503 macho.targetStore(&imports[import_index].bits, .{
1504 .lib_ordinal = macho.targetLoad(&imports[import_index].bits).lib_ordinal,
1505 .name_offset = macho.targetLoad(&imports[import_index].bits).name_offset,
1506 .weak_import = true,
1507 });
1508 }
1509}
1510fn globalByName(macho: *MachO, name: []const u8) Symbol.Index.Optional {
1511 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1512 if (macho.global_symbols.getKeyAdapted(name, adapter)) |symbol| {
1513 return .wrap(symbol);
1514 } else {
1515 return .none;
1516 }
1517}
1518
1519/// All calls to this function must occur in `initInner`, and must be before we initialize fixup
1520/// chains, as fixup chains requires some per-segment tracking and the logic to initialize them
1521/// assumes that no more segments will be created.
1522fn addSegment(macho: *MachO, opts: struct {
1523 name: []const u8,
1524 prot: std.macho.vm_prot_t,
1525 /// The caller is allowed to create the section's node themselves, in which case it is passed in
1526 /// here. The caller must initialize the `MachO.nodes` entry to `.{ .segment = undefined }`, and
1527 /// the node must be created with the option `.moved = true`.
1528 existing_node: ?MappedFile.Node.Index = null,
1529 /// This matches the layout of `std.macho.segment_command_64.flags`.
1530 flags: packed struct(u32) {
1531 _unused0: u4 = 0,
1532 read_only: bool = false,
1533 _unused1: u27 = 0,
1534 },
1535}) Error!Segment {
1536 const gpa = macho.base.comp.gpa;
1537
1538 const segment_command_64 = std.macho.segment_command_64;
1539
1540 // We limit the number of segments to 255, because there can only be 255 *sections*, so there's
1541 // no need to allow more. See corresponding check in `addSection`.
1542 if (macho.segments.len == 255) {
1543 return macho.base.comp.link_diags.fail("maximum segment count exceeded", .{});
1544 }
1545
1546 try macho.nodes.ensureUnusedCapacity(gpa, 1);
1547 try macho.segments.ensureUnusedCapacity(gpa, 1);
1548
1549 const root_ni: MappedFile.Node.Index = .root;
1550
1551 const lc = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
1552 .cmdsize = @sizeOf(segment_command_64),
1553 .segname = name: {
1554 var name: [16]u8 = undefined;
1555 @memcpy(name[0..opts.name.len], opts.name);
1556 @memset(name[opts.name.len..], 0);
1557 break :name name;
1558 },
1559 .vmaddr = 0,
1560 .vmsize = 0,
1561 .fileoff = 0, // to be set by `flushMoved`
1562 .filesize = 0,
1563 .maxprot = opts.prot,
1564 .initprot = opts.prot,
1565 .nsects = 0,
1566 .flags = @bitCast(opts.flags),
1567 });
5661568
5671569 const segment: Segment = @fromBackingInt(@intCast(macho.segments.len));
5681570 const segment_node: MappedFile.Node.Index = node: {
......@@ -583,17 +1585,9 @@ fn addSegment(macho: *MachO, opts: struct {
5831585 macho.segments.appendAssumeCapacity(.{
5841586 .lc = lc,
5851587 .node = segment_node,
1588 .fixup_chain_starts = .none,
5861589 });
5871590
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
5971591 return segment;
5981592}
5991593
......@@ -645,16 +1639,37 @@ fn string(macho: *MachO, slice: []const u8) Error!String {
6451639 return gop.key_ptr.*;
6461640}
6471641
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}
1642fn updateSegmentLoadAddresses(macho: *MachO) Error!void {
1643 const gpa = macho.base.comp.gpa;
1644
1645 // Grow all `.fixup_chains_starts_in_segment` node to the necessary length. We need to do this
1646 // before updating the load addresses because this could cause `__LINKEDIT` to grow.
1647 for (0..macho.segments.len) |segment_raw| {
1648 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
1649
1650 const starts_node = segment.fixupChainStartsNode(macho).unwrap() orelse {
1651 assert(std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1652 continue;
1653 };
1654
1655 const size = segment.node(macho).location(&macho.mf).resolve(&macho.mf)[1];
1656
1657 const page_count: u16 = @intCast(@divExact(size, macho.targetPageAlign().toByteUnits()));
1658 try starts_node.ensureMinimumSize(gpa, &macho.mf, MappedFile.Alignment.@"8".forward(
1659 @sizeOf(std.macho.dyld_chained_starts_in_segment) + page_count * 2,
1660 ));
1661 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1662 starts_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1663 ));
1664 const old_page_count = macho.targetLoad(&starts_hdr.page_count);
1665 macho.targetStore(&starts_hdr.page_count, page_count);
1666 macho.targetStore(&starts_hdr.size, @sizeOf(std.macho.dyld_chained_starts_in_segment) + 2 * page_count);
1667 const page_starts: []u16 = @ptrCast(@alignCast(
1668 starts_node.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
1669 ));
1670 @memset(page_starts[old_page_count..page_count], 0xFFFF);
1671 }
6561672
657fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6581673 // TODO: this segment load address allocation logic is bad because it moves segments quite
6591674 // frequently. We should avoid that by introducing padding between segments so that they have
6601675 // space to grow in-place. We could also consider allowing a segment which can't fit to jump
......@@ -664,6 +1679,7 @@ fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6641679 assert(macho.targetLoad(&pagezero_ptr.vmaddr) == 0);
6651680 break :start_addr macho.targetLoad(&pagezero_ptr.vmsize);
6661681 };
1682
6671683 for (0..macho.segments.len) |segment_raw| {
6681684 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
6691685
......@@ -677,7 +1693,7 @@ fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
6771693 cur_addr = old_addr;
6781694 } else {
6791695 macho.targetStore(&cmd_ptr.vmaddr, cur_addr);
680 try segment.node(macho).childrenMoved(macho.base.comp.gpa, &macho.mf);
1696 try segment.node(macho).childrenMoved(gpa, &macho.mf);
6811697 }
6821698
6831699 cur_addr = macho.targetPageAlign().forward(cur_addr + size);
......@@ -761,12 +1777,10 @@ fn addSection(macho: *MachO, opts: struct {
7611777 name: []const u8,
7621778 segment: Segment,
7631779 @"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 },
1780 manual_size: bool = false,
1781 flags: Section.Flags,
1782 reserved1: u32 = 0,
1783 reserved2: u32 = 0,
7701784}) Error!Section {
7711785 const gpa = macho.base.comp.gpa;
7721786
......@@ -796,14 +1810,18 @@ fn addSection(macho: *MachO, opts: struct {
7961810
7971811 const section: Section = @fromBackingInt(@intCast(macho.sections.len));
7981812 const section_node = try opts.segment.node(macho).addFloatingChild(gpa, &macho.mf, .{
799 .alignment = opts.@"align",
1813 .alignment = macho.mf.flags.block_size.max(opts.@"align"),
8001814 .moved = true, // so that `offset` will be set
8011815 });
802 macho.nodes.appendAssumeCapacity(.{ .section = section });
1816 if (opts.manual_size) {
1817 macho.nodes.appendAssumeCapacity(.{ .section_manual_size = section });
1818 } else {
1819 macho.nodes.appendAssumeCapacity(.{ .section = section });
1820 }
8031821 macho.sections.appendAssumeCapacity(.{
8041822 .lc = @fromBackingInt(@intCast(macho.lc.offsets.items.len)),
8051823 .node = section_node,
806 .first_symbol = .none,
1824 .segment = opts.segment,
8071825 .index = undefined, // populated later
8081826 });
8091827
......@@ -861,9 +1879,7 @@ fn addSection(macho: *MachO, opts: struct {
8611879 if (other_section == section) continue;
8621880 if (other_section.lc(macho).offset(macho) > offset) {
8631881 macho.sections.items(.index)[@backingInt(other_section)] += 1;
864 if (other_section.firstSymbol(macho).* != .none) {
865 macho.sections_changed_index.putAssumeCapacity(other_section, {});
866 }
1882 macho.sections_changed_index.putAssumeCapacity(other_section, {});
8671883 } else {
8681884 new_section_index += 1;
8691885 }
......@@ -890,6 +1906,8 @@ fn addSection(macho: *MachO, opts: struct {
8901906 .reloff = 0,
8911907 .nreloc = 0,
8921908 .flags = @bitCast(opts.flags),
1909 .reserved1 = opts.reserved1,
1910 .reserved2 = opts.reserved2,
8931911 };
8941912 if (macho.targetEndian() != std.lang.Endian.native) {
8951913 std.mem.byteSwapAllFields(section_64, section_cmd);
......@@ -898,15 +1916,6 @@ fn addSection(macho: *MachO, opts: struct {
8981916 return section;
8991917}
9001918
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
9101919fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []const u8) ?Section {
9111920 const adapter: Section.NameAdapter = .{ .macho = macho };
9121921 const key: Section.NameAdapter.SegmentAndSection = .{
......@@ -920,13 +1929,31 @@ fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []
9201929 }
9211930}
9221931
1932fn machFiletype(macho: *const MachO) enum(u32) {
1933 OBJECT = std.macho.MH_OBJECT,
1934 DYLIB = std.macho.MH_DYLIB,
1935 EXECUTE = std.macho.MH_EXECUTE,
1936} {
1937 const hdr: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
1938 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
1939 ));
1940 return @fromBackingInt(macho.targetLoad(&hdr.filetype));
1941}
1942
9231943fn targetEndian(macho: *const MachO) std.lang.Endian {
9241944 _ = macho;
9251945 return .little;
9261946}
927fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
1947fn targetPtrSize(macho: *const MachO) u8 {
9281948 _ = macho;
929 return .fromByteUnits(0x4000);
1949 return 8;
1950}
1951fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
1952 return switch (macho.base.comp.getTarget().cpu.arch) {
1953 else => unreachable,
1954 .aarch64 => .fromByteUnits(0x4000),
1955 .x86_64 => .fromByteUnits(0x1000),
1956 };
9301957}
9311958fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
9321959 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
......@@ -936,8 +1963,8 @@ fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer
9361963 else => @compileError(@typeName(Child)),
9371964 .int => std.mem.toNative(Child, ptr.*, macho.targetEndian()),
9381965 .@"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))),
1966 inline .@"struct", .@"union" => |bitpack| @bitCast(
1967 macho.targetLoad(@as(*align(alignment) const bitpack.backing_integer.?, @ptrCast(ptr))),
9411968 ),
9421969 };
9431970}
......@@ -952,8 +1979,8 @@ fn targetStore(macho: *const MachO, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).p
9521979 @as(*align(alignment) @"enum".tag_type, @ptrCast(ptr)),
9531980 @backingInt(val),
9541981 ),
955 .@"struct" => |@"struct"| macho.targetStore(
956 @as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr)),
1982 inline .@"struct", .@"union" => |bitpack| macho.targetStore(
1983 @as(*align(alignment) bitpack.backing_integer.?, @ptrCast(ptr)),
9571984 @bitCast(val),
9581985 ),
9591986 };
......@@ -965,10 +1992,16 @@ pub fn deinit(macho: *MachO) void {
9651992 macho.nodes.deinit(gpa);
9661993 macho.segments.deinit(gpa);
9671994 macho.sections.deinit(gpa);
968 macho.segments_by_name.deinit(gpa);
9691995 macho.sections_by_name.deinit(gpa);
1996 macho.dirty_fixup_chain_pages.deinit(gpa);
1997 macho.input_atoms.deinit(gpa);
9701998 macho.strtab.deinit(gpa);
9711999 macho.symtab.deinit(gpa);
2000 macho.global_symbols.deinit(gpa);
2001 macho.relocs.deinit(gpa);
2002 macho.imports.deinit(gpa);
2003 macho.got.deinit(gpa);
2004 macho.stubs.deinit(gpa);
9722005 macho.sections_changed_index.deinit(gpa);
9732006 macho.* = undefined;
9742007}
......@@ -1023,41 +2056,1153 @@ pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
10232056 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
10242057 else => |e| return e,
10252058 };
2059 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
2060 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
2061 else => |e| return e,
2062 };
10262063}
10272064fn loadInputInner(macho: *MachO, input: link.Input) Error!void {
2065 const comp = macho.base.comp;
2066 const diags = &comp.link_diags;
2067 const gpa = comp.gpa;
2068 const io = comp.io;
10282069 switch (input) {
10292070 .res => unreachable,
1030 .object => @panic("TODO(MachO2): load object"),
1031 .archive => @panic("TODO(MachO2): load archive"),
2071 .object => |object| {
2072 var buf: [4096]u8 = undefined;
2073 var fr = object.file.reader(io, &buf);
2074 macho.loadObject(object.path, null, &fr, .{
2075 .offset = fr.logicalPos(),
2076 .size = fr.getSize() catch |err| switch (err) {
2077 error.Canceled => |e| return e,
2078 else => |e| return diags.fail(
2079 "failed to stat \"{f}\": {t}",
2080 .{ object.path.fmtEscapeString(), e },
2081 ),
2082 },
2083 }) catch |err| switch (err) {
2084 else => |e| return e,
2085 error.EndOfStream => return diags.failParse(
2086 object.path,
2087 "unexpected eof",
2088 .{},
2089 ),
2090 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2091 "failed to read \"{f}\": {t}",
2092 .{ object.path.fmtEscapeString(), e },
2093 ),
2094 error.ReadFailed => switch (fr.err.?) {
2095 error.Canceled => |e| return e,
2096 else => |e| return diags.fail(
2097 "failed to read \"{f}\": {t}",
2098 .{ object.path.fmtEscapeString(), e },
2099 ),
2100 },
2101 };
2102 },
2103 .archive => |archive| {
2104 var buf: [4096]u8 = undefined;
2105 var fr = archive.file.reader(io, &buf);
2106 macho.loadArchive(archive.path, &fr) catch |err| switch (err) {
2107 else => |e| return e,
2108 error.EndOfStream => return diags.failParse(
2109 archive.path,
2110 "unexpected eof",
2111 .{},
2112 ),
2113 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2114 "failed to read \"{f}\": {t}",
2115 .{ archive.path.fmtEscapeString(), e },
2116 ),
2117 error.ReadFailed => switch (fr.err.?) {
2118 error.Canceled => |e| return e,
2119 else => |e| return diags.fail(
2120 "failed to read \"{f}\": {t}",
2121 .{ archive.path.fmtEscapeString(), e },
2122 ),
2123 },
2124 };
2125 },
10322126 .dso => @panic("TODO(MachO2): load dso"),
10332127 .tbd => |tbd| {
10342128 log.debug("load tbd {qf}", .{tbd.path.fmtEscapeString()});
10352129
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";
2130 var lib_stub = tapi.LibStub.loadFromFile(gpa, io, tbd.file) catch |err| switch (err) {
2131 error.Canceled => |e| return e,
2132 else => |e| return diags.failParse(tbd.path, "failed to parse tbd: {t}", .{e}),
2133 };
2134 defer lib_stub.deinit();
2135
2136 const umbrella_lib = lib_stub.inner[0];
10412137
2138 const dylib_name = umbrella_lib.installName();
10422139 const cmd = try macho.appendLoadCommand(std.macho.dylib_command, .{
10432140 .cmd = .LOAD_DYLIB,
10442141 .cmdsize = std.mem.alignForward(u32, @intCast(
1045 @sizeOf(std.macho.dylib_command) + prefix.len + name.len + suffix.len + 1,
2142 @sizeOf(std.macho.dylib_command) + dylib_name.len + 1,
10462143 ), 8),
10472144 .dylib = .{
10482145 .name = @sizeOf(std.macho.dylib_command),
10492146 .timestamp = 0,
1050 .current_version = 0,
1051 .compatibility_version = 0,
2147 .current_version = 0, // TODO
2148 .compatibility_version = 0, // TODO
10522149 },
10532150 });
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);
2151 @memcpy(cmd.trailing[0..dylib_name.len], dylib_name);
2152 @memset(cmd.trailing[dylib_name.len..], 0);
2153 },
2154 }
2155}
2156
2157/// Internal error set used by input parsing functions `loadObject`, `loadArchive`, `loadDso`.
2158const LoadParseInputError = Error || Io.File.SeekError || Io.Reader.Error;
2159
2160fn loadArchive(macho: *MachO, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadParseInputError!void {
2161 const comp = macho.base.comp;
2162 const gpa = comp.gpa;
2163 const diags = &comp.link_diags;
2164 const r = &fr.interface;
2165
2166 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
2167
2168 {
2169 const magic = r.take(std.macho.ARMAG.len) catch |err| switch (err) {
2170 error.ReadFailed => |e| return e,
2171 error.EndOfStream => return diags.failParse(path, "invalid archive file", .{}),
2172 };
2173 if (!std.mem.eql(u8, magic, std.macho.ARMAG)) {
2174 return diags.failParse(path, "invalid archive file", .{});
2175 }
2176 }
2177
2178 var long_name_buf: std.ArrayList(u8) = .empty;
2179 defer long_name_buf.deinit(gpa);
2180
2181 while (r.takeStruct(std.macho.ar_hdr, .native)) |header| {
2182 if (!std.mem.eql(u8, &header.ar_fmag, std.macho.ARFMAG))
2183 return diags.failParse(path, "bad file magic", .{});
2184 const offset = fr.logicalPos();
2185 const size = header.size() catch
2186 return diags.failParse(path, "bad member size", .{});
2187 const member_name: []const u8, const member_offset: u64, const member_size: u64 = member: {
2188 const name_len = header.nameLength() orelse {
2189 break :member .{ header.name(), offset, size };
2190 };
2191 try long_name_buf.resize(gpa, name_len);
2192 try fr.interface.readSliceAll(long_name_buf.items);
2193 break :member .{
2194 std.mem.sliceTo(long_name_buf.items, 0),
2195 offset + name_len,
2196 size - name_len,
2197 };
2198 };
2199 if (std.mem.eql(u8, member_name, std.macho.SYMDEF) or
2200 std.mem.eql(u8, member_name, std.macho.SYMDEF64) or
2201 std.mem.eql(u8, member_name, std.macho.SYMDEF_SORTED) or
2202 std.mem.eql(u8, member_name, std.macho.SYMDEF64_SORTED))
2203 {
2204 // Ignore these special members.
2205 } else {
2206 try macho.loadObject(path, member_name, fr, .{
2207 .offset = member_offset,
2208 .size = member_size,
2209 });
2210 }
2211 try fr.seekTo(std.mem.alignForward(u64, offset + size, 2));
2212 } else |err| switch (err) {
2213 else => |e| return e,
2214 error.EndOfStream => if (!fr.atEnd()) return error.EndOfStream,
2215 }
2216}
2217fn loadObject(
2218 macho: *MachO,
2219 path: std.Build.Cache.Path,
2220 member: ?[]const u8,
2221 fr: *Io.File.Reader,
2222 fl: MappedFile.Node.FileLocation,
2223) LoadParseInputError!void {
2224 const comp = macho.base.comp;
2225 const diags = &comp.link_diags;
2226 const gpa = comp.gpa;
2227
2228 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) });
2229
2230 const target_endian = macho.targetEndian();
2231
2232 switch (try fr.interface.peekInt(u32, target_endian)) {
2233 std.macho.MH_MAGIC_64 => {},
2234 std.macho.MH_MAGIC => {
2235 return diags.failParse(path, "incorrect pointer bit width (expected 64)", .{});
2236 },
2237 std.macho.MH_CIGAM_64, std.macho.MH_CIGAM => {
2238 return diags.failParse(path, "incorrect endian (expected {t})", .{target_endian});
2239 },
2240 else => {
2241 return diags.failParse(path, "invalid Mach-O object file", .{});
10582242 },
10592243 }
2244
2245 const header = try fr.interface.takeStruct(std.macho.mach_header_64, target_endian);
2246 assert(header.magic == std.macho.MH_MAGIC_64); // checked above
2247
2248 if (header.filetype != std.macho.MH_OBJECT) return diags.failParse(
2249 path,
2250 "not a relocatable",
2251 .{},
2252 );
2253
2254 {
2255 const output_header: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
2256 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
2257 ));
2258 const output_cputype = macho.targetLoad(&output_header.cputype);
2259 const output_cpusubtype = macho.targetLoad(&output_header.cpusubtype);
2260 if (header.cputype != output_cputype) return diags.failParse(
2261 path,
2262 "cputype '{d}' does not match target cputype '{d}'",
2263 .{ header.cputype, output_cputype },
2264 );
2265 if (header.cpusubtype != output_cpusubtype) return diags.failParse(
2266 path,
2267 "cpusubtype '{d}' does not match target cpusubtype '{d}'",
2268 .{ header.cpusubtype, output_cpusubtype },
2269 );
2270 }
2271
2272 if (@sizeOf(std.macho.mach_header_64) + header.sizeofcmds > fl.size) {
2273 return diags.failParse(path, "load commands exceed file size", .{});
2274 }
2275
2276 var maybe_symtab_lc: ?std.macho.symtab_command = null;
2277
2278 const InputSection = struct {
2279 segname: [16]u8,
2280 sectname: [16]u8,
2281
2282 reloff: u32,
2283 nreloc: u32,
2284
2285 addr: u64,
2286 size: u64,
2287 @"align": MappedFile.Alignment,
2288
2289 usage: Usage,
2290 file_offset: u32,
2291
2292 atoms_start: usize,
2293 atoms_end: usize,
2294
2295 const Usage = enum {
2296 text,
2297 data_const,
2298 data_const_split_4,
2299 data_const_split_8,
2300 data_const_split_16,
2301 data_const_split_c_strings,
2302 data_const_fixups,
2303 data_mut,
2304 bss,
2305 fn parentSection(usage: Usage, m: *const MachO) Section {
2306 return switch (usage) {
2307 .text => m.sectionByName("__TEXT", "__text").?,
2308
2309 .data_const,
2310 .data_const_split_4,
2311 .data_const_split_8,
2312 .data_const_split_16,
2313 .data_const_split_c_strings,
2314 => m.sectionByName("__TEXT", "__const").?,
2315
2316 // TODO: this should really be "__DATA_CONST,__data". See comment in `initInner`
2317 .data_const_fixups => m.sectionByName("__DATA", "__data").?,
2318
2319 .data_mut => m.sectionByName("__DATA", "__data").?,
2320 .bss => m.sectionByName("__DATA", "__data").?,
2321 };
2322 }
2323 };
2324 };
2325 var input_sections: std.ArrayList(InputSection) = .empty;
2326 defer input_sections.deinit(gpa);
2327
2328 var cmd_offset: u64 = 0;
2329 for (0..header.ncmds) |_| {
2330 const lc = try fr.interface.peekStruct(std.macho.load_command, target_endian);
2331 if (lc.cmdsize < @sizeOf(std.macho.load_command)) return diags.failParse(
2332 path,
2333 "load command at offset 0x{X} has invalid size",
2334 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2335 );
2336 if (cmd_offset + lc.cmdsize > header.sizeofcmds) return diags.failParse(
2337 path,
2338 "load command at offset 0x{X} exceeds 'sizeofcmds'",
2339 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2340 );
2341 cmd_offset += lc.cmdsize;
2342 switch (lc.cmd) {
2343 else => if (std.enums.tagName(std.macho.LC, lc.cmd)) |cmd_name| {
2344 return diags.failParse(path, "unhandled load command 'LC_{s}'", .{cmd_name});
2345 } else {
2346 return diags.failParse(path, "unknown load command 0x{X}", .{lc.cmd});
2347 },
2348
2349 .NONE,
2350 .DYSYMTAB,
2351 .NOTE,
2352 .BUILD_VERSION,
2353 .DATA_IN_CODE,
2354 .LINKER_OPTIMIZATION_HINT,
2355 => try fr.interface.discardAll(lc.cmdsize), // ignore
2356
2357 .SEGMENT_64 => {
2358 if (lc.cmdsize < @sizeOf(std.macho.segment_command_64)) {
2359 return diags.failParse(path, "bad cmdsize in 'LC_SEGMENT_64' load command", .{});
2360 }
2361 const segment_lc = try fr.interface.takeStruct(std.macho.segment_command_64, target_endian);
2362 if (lc.cmdsize - @sizeOf(std.macho.segment_command_64) < segment_lc.nsects * @sizeOf(std.macho.section_64)) {
2363 return diags.failParse(path, "nsects in 'LC_SEGMENT_64' load command exceeds cmdsize", .{});
2364 }
2365 try input_sections.ensureUnusedCapacity(gpa, segment_lc.nsects);
2366 for (0..segment_lc.nsects) |_| {
2367 const section = try fr.interface.takeStruct(std.macho.section_64, target_endian);
2368
2369 const flags: Section.Flags = @bitCast(section.flags);
2370
2371 const segname = std.mem.sliceTo(&section.segname, 0);
2372 const sectname = std.mem.sliceTo(&section.sectname, 0);
2373
2374 const usage: InputSection.Usage = switch (flags.type) {
2375 _ => return diags.failParse(
2376 path,
2377 "section '{s},{s}' has unknown type '{d}'",
2378 .{ segname, sectname, flags.type },
2379 ),
2380
2381 else => return diags.failParse(
2382 path,
2383 "section '{s},{s}' has unsupported type '{t}'",
2384 .{ segname, sectname, flags.type },
2385 ),
2386
2387 .zerofill => .bss,
2388
2389 .@"4byte_literals" => .data_const_split_4,
2390 .@"8byte_literals" => .data_const_split_8,
2391 .@"16byte_literals" => .data_const_split_16,
2392 .cstring_literals => .data_const_split_c_strings,
2393
2394 .regular => usage: {
2395 if (flags.some_instructions or flags.pure_instructions) {
2396 break :usage .text;
2397 }
2398
2399 // "__DATA,__const" is constant data with fixups.
2400 if (std.mem.eql(u8, segname, "__DATA") and
2401 std.mem.eql(u8, sectname, "__const"))
2402 {
2403 break :usage .data_const_fixups;
2404 }
2405
2406 // Otherwise, the decision is based solely on segment name.
2407 if (std.mem.eql(u8, segname, "__TEXT")) {
2408 break :usage .data_const;
2409 }
2410 if (std.mem.eql(u8, segname, "__DATA_CONST")) {
2411 break :usage .data_const_fixups;
2412 }
2413 if (std.mem.eql(u8, segname, "__DATA")) {
2414 break :usage .data_mut;
2415 }
2416
2417 return diags.failParse(path, "unknown segment name '{s}'", .{segname});
2418 },
2419 };
2420
2421 if (usage != .bss and section.offset + section.size > fl.size) {
2422 return diags.failParse(path, "section '{s},{s}' exceeds file size", .{
2423 segname, sectname,
2424 });
2425 }
2426
2427 const @"align": MappedFile.Alignment = .fromLog2Units(std.math.cast(u6, section.@"align") orelse {
2428 return diags.failParse(path, "section '{s},{s}' has unsupported alignment '1 << {d}'", .{
2429 segname, sectname, section.@"align",
2430 });
2431 });
2432
2433 // Before creating the input section nodes, make sure the parent section is
2434 // suitably aligned. Unlike ELF, Mach-O does not allow customizing segment
2435 // alignment, so section alignments greater than the page size cannot be
2436 // fulfilled and must trigger an error.
2437 if (@"align".compare(.gt, macho.targetPageAlign())) {
2438 return diags.failParse(path, "section '{s},{s}' has alignment '{d}' which exceeds page alignment '{d}'", .{
2439 segname, sectname, @"align".toByteUnits(), macho.targetPageAlign().toByteUnits(),
2440 });
2441 }
2442 const parent_section = usage.parentSection(macho);
2443 if (@"align".compare(.gt, parent_section.alignment(macho))) {
2444 macho.targetStore(&parent_section.lcPtr(macho).@"align", @"align".toLog2Units());
2445 if (@"align".compare(.gt, parent_section.node(macho).alignment(&macho.mf))) {
2446 try parent_section.node(macho).realign(gpa, &macho.mf, @"align");
2447 }
2448 }
2449
2450 input_sections.appendAssumeCapacity(.{
2451 .segname = section.segname,
2452 .sectname = section.sectname,
2453 .reloff = section.reloff,
2454 .nreloc = section.nreloc,
2455 .addr = section.addr,
2456 .size = section.size,
2457 .@"align" = @"align",
2458 .usage = usage,
2459 .file_offset = section.offset,
2460 .atoms_start = undefined,
2461 .atoms_end = undefined,
2462 });
2463 }
2464 try fr.interface.discardAll(lc.cmdsize -
2465 @sizeOf(std.macho.segment_command_64) -
2466 segment_lc.nsects * @sizeOf(std.macho.section_64));
2467 },
2468
2469 .SYMTAB => {
2470 if (lc.cmdsize < @sizeOf(std.macho.symtab_command)) {
2471 return diags.failParse(path, "bad cmdsize in 'LC_SYMTAB' load command", .{});
2472 }
2473 if (maybe_symtab_lc != null) {
2474 return diags.failParse(path, "duplicate 'LC_SYMTAB' load command", .{});
2475 }
2476 maybe_symtab_lc = try fr.interface.peekStruct(std.macho.symtab_command, target_endian);
2477 try fr.interface.discardAll(lc.cmdsize);
2478 },
2479 }
2480 }
2481
2482 const symtab_lc = maybe_symtab_lc orelse return diags.failParse(
2483 path,
2484 "object file does not contain any 'LC_SYMTAB' load command",
2485 .{},
2486 );
2487 if (symtab_lc.symoff + symtab_lc.nsyms * @sizeOf(std.macho.nlist_64) > fl.size) {
2488 return diags.failParse(path, "symbol table in 'LC_SYMTAB' load command exceeds file size", .{});
2489 }
2490 if (symtab_lc.stroff + symtab_lc.strsize > fl.size) {
2491 return diags.failParse(path, "string table in 'LC_SYMTAB' load command exceeds file size", .{});
2492 }
2493
2494 const symbols = try gpa.alloc(Symbol.Index, symtab_lc.nsyms);
2495 defer gpa.free(symbols);
2496
2497 const Atom = struct {
2498 offset_in_section: u64,
2499 index: InputAtom.Index,
2500 };
2501 var atoms: std.ArrayList(Atom) = .empty;
2502 defer atoms.deinit(gpa);
2503
2504 // This block processes symbols and atoms (populating `symbols` and `atoms` in the process).
2505 // After the block exits, the only work remining is relocation lowering.
2506 {
2507 try fr.seekTo(fl.offset + symtab_lc.stroff);
2508 const strtab = try fr.interface.readAllocAll(gpa, symtab_lc.strsize);
2509 defer gpa.free(strtab);
2510
2511 try fr.seekTo(fl.offset + symtab_lc.symoff);
2512 const symtab = try fr.interface.readSliceEndianAlloc(gpa, std.macho.nlist_64, symtab_lc.nsyms, target_endian);
2513 defer gpa.free(symtab);
2514
2515 // Sort the symbols so that we can do subsection splitting efficiently. This is okay because
2516 // there is no significance to the order of the symbol table in an object file. We'll put
2517 // any symbols not in a section (undef, absolute, etc) first, and then we'll sort the
2518 // remaining symbols by section index and address.
2519 //
2520 // Small caveat: when we lower symbols into the `symbols` array we allocated earlier, we do
2521 // need to use the *original* indices rather than our sorted indices, so that we can look up
2522 // relocation target symbols by index later. To help us do that, we'll also build a mapping
2523 // from our sorted indices to the original indices.
2524 const orig_sym_indices = try gpa.alloc(u32, symtab.len);
2525 defer gpa.free(orig_sym_indices);
2526 for (orig_sym_indices, 0..) |*orig_index, i| {
2527 orig_index.* = @intCast(i);
2528 }
2529 const SymtabSortContext = struct {
2530 symtab: []std.macho.nlist_64,
2531 map: []u32,
2532 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
2533 const lhs = ctx.symtab[lhs_index];
2534 const rhs = ctx.symtab[rhs_index];
2535 // Don't need to check `n_type.bits.is_stab` here, because valid object files do not
2536 // contain stabs symbols, so if any are present we'll error out later anyway.
2537 const lhs_sect: u8, const lhs_addr: u64 = switch (lhs.n_type.bits.type) {
2538 .sect => .{ lhs.n_sect, lhs.n_value },
2539 else => .{ 0, 0 },
2540 };
2541 const rhs_sect: u8, const rhs_addr: u64 = switch (rhs.n_type.bits.type) {
2542 .sect => .{ rhs.n_sect, rhs.n_value },
2543 else => .{ 0, 0 },
2544 };
2545 const order = std.math.order(lhs_sect, rhs_sect).differ() orelse std.math.order(lhs_addr, rhs_addr);
2546 return order.compare(.lt);
2547 }
2548 pub fn swap(ctx: @This(), lhs_index: usize, rhs_index: usize) void {
2549 std.mem.swap(std.macho.nlist_64, &ctx.symtab[lhs_index], &ctx.symtab[rhs_index]);
2550 std.mem.swap(u32, &ctx.map[lhs_index], &ctx.map[rhs_index]);
2551 }
2552 };
2553 std.mem.sortUnstableContext(0, symtab.len, @as(SymtabSortContext, .{
2554 .symtab = symtab,
2555 .map = orig_sym_indices,
2556 }));
2557
2558 var sym_index: u32 = 0;
2559
2560 // This initial loop processes all of the non-section symbols (undef, absolute, etc). When
2561 // it exits, `sym_index` points to the first symbol with `n_type.bits.type == .sect`, which
2562 // is guaranteed to have `n_sect > 0`.
2563 while (sym_index < symtab.len) : (sym_index += 1) {
2564 const nlist = &symtab[sym_index];
2565
2566 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2567 path,
2568 "invalid symtab entry at index {d}: object file cannot contain stabs",
2569 .{sym_index},
2570 );
2571 if (nlist.n_strx > strtab.len) return diags.failParse(
2572 path,
2573 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2574 .{ sym_index, nlist.n_strx },
2575 );
2576 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2577
2578 symbols[orig_sym_indices[sym_index]] = switch (nlist.n_type.bits.type) {
2579 .sect => if (nlist.n_sect == 0) return diags.failParse(
2580 path,
2581 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2582 .{ sym_name, nlist.n_sect },
2583 ) else {
2584 // All done with the non-section symbols!
2585 break;
2586 },
2587
2588 .undf => if (!nlist.n_type.bits.ext) return diags.failParse(
2589 path,
2590 "internal symbol '{s}' cannot be undefined",
2591 .{sym_name},
2592 ) else macho.addGlobalSymbol(.{
2593 .name = sym_name,
2594 .linkage = switch (nlist.n_desc.weak_ref) {
2595 false => .strong,
2596 true => .weak,
2597 },
2598 .visibility = switch (nlist.n_type.bits.pext) {
2599 false => .default,
2600 true => .hidden,
2601 },
2602 .value = null,
2603 }) catch |err| switch (err) {
2604 error.MultipleDefinitions => unreachable, // not a definition
2605 else => |e| return e,
2606 },
2607
2608 .abs => if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2609 .name = sym_name,
2610 .value = .{ .abs = nlist.n_value },
2611 }) else macho.addGlobalSymbol(.{
2612 .name = sym_name,
2613 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2614 false => .strong,
2615 true => .weak,
2616 },
2617 .visibility = switch (nlist.n_type.bits.pext) {
2618 false => .default,
2619 true => .hidden,
2620 },
2621 .value = null,
2622 }) catch |err| switch (err) {
2623 error.MultipleDefinitions => return diags.failParse(
2624 path,
2625 "multiple definitions of '{s}'",
2626 .{sym_name},
2627 ),
2628 else => |e| return e,
2629 },
2630
2631 .pbud, .indr => |sym_type| return diags.failParse(
2632 path,
2633 "symbol '{s}' has unsupported symbol type '{t}",
2634 .{ sym_name, sym_type },
2635 ),
2636
2637 _ => |sym_type| return diags.failParse(
2638 path,
2639 "symbol '{s}' has unknown symbol type '0x{X}'",
2640 .{ sym_name, sym_type },
2641 ),
2642 };
2643 }
2644
2645 // Now we need to process the symbols defined in sections. We're going to do this by looping
2646 // over *sections*, just because subsection splitting turns out to be much easier this way.
2647 for (input_sections.items, 0..) |*section, section_index| {
2648 if (section.usage != .bss) {
2649 try fr.seekTo(fl.offset + section.file_offset);
2650 }
2651
2652 const parent_section = section.usage.parentSection(macho);
2653
2654 section.atoms_start = atoms.items.len;
2655 var cur_atom_end: u64 = 0;
2656 while (true) {
2657 const sym_in_section: bool = sym_in_section: {
2658 if (sym_index == symtab.len) break :sym_in_section false;
2659
2660 const nlist = &symtab[sym_index];
2661
2662 assert(nlist.n_sect - 1 >= section_index); // we sorted the symtab by section
2663 if (nlist.n_sect - 1 != section_index) break :sym_in_section false;
2664
2665 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2666 path,
2667 "invalid symtab entry at index {d}: object file cannot contain stabs",
2668 .{sym_index},
2669 );
2670 if (nlist.n_strx > strtab.len) return diags.failParse(
2671 path,
2672 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2673 .{ sym_index, nlist.n_strx },
2674 );
2675
2676 if (nlist.n_value < section.addr or
2677 // This comparison intentionally uses '>=' to disallow defining a symbol at
2678 // the exclusive end of a section. This is because of an annoying quirk of
2679 // Mach-O: when using `MH_SUBSECTIONS_VIA_SYMBOLS`, it is impossible to
2680 // define a symbol at the exclusive end of any subsection, because such a
2681 // symbol would be considered a part of the *next* subsection.
2682 nlist.n_value >= section.addr + section.size)
2683 {
2684 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2685 const segname = std.mem.sliceTo(&section.segname, 0);
2686 const sectname = std.mem.sliceTo(&section.sectname, 0);
2687 return diags.failParse(
2688 path,
2689 "symbol '{s}' address '0x{X}' exceeds range of containing section '{s},{s}'",
2690 .{ sym_name, nlist.n_value, segname, sectname },
2691 );
2692 }
2693
2694 break :sym_in_section true;
2695 };
2696
2697 const target_atom_offset: u64 = target_atom_offset: {
2698 if (sym_in_section) {
2699 break :target_atom_offset symtab[sym_index].n_value - section.addr;
2700 }
2701 if (cur_atom_end != section.size) {
2702 // There were no more symbols in this section, but we still need to create
2703 // the atoms for the rest of the section! So just process everything up to
2704 // and including the last byte of the section.
2705 break :target_atom_offset section.size - 1;
2706 }
2707 // There are no symbols or atoms left to process, so we're done.
2708 break;
2709 };
2710 assert(target_atom_offset < section.size);
2711
2712 while (cur_atom_end <= target_atom_offset) {
2713 // We need to start a new atom at `cur_atom_end`.
2714 const atom_offset: u64 = cur_atom_end;
2715 const atom_size: u64 = usage: switch (section.usage) {
2716 // TODO: we should also deduplicate these
2717 .data_const_split_4 => 4,
2718 .data_const_split_8 => 8,
2719 .data_const_split_16 => 16,
2720
2721 // TODO: split C string sections (although `MH_SUBSECTIONS_VIA_SYMBOLS` is
2722 // usually sufficient anyway); and deduplicate those too
2723 .data_const_split_c_strings => continue :usage .data_const,
2724
2725 .text,
2726 .data_const,
2727 .data_const_fixups,
2728 .data_mut,
2729 .bss,
2730 => atom_size: {
2731 if (header.flags & std.macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) {
2732 // We aren't splitting the section into multiple atoms, so this must
2733 // mean that we haven't created *any* atom for this section yet.
2734 assert(atom_offset == 0);
2735 break :atom_size section.size;
2736 }
2737
2738 // Find the next symbol which splits this section. Usually this is just
2739 // the next symbol, but there could be some alt entry or alias symbols.
2740 var other_sym_index = sym_index + 1;
2741 const new_atom_end: u64 = while (other_sym_index < symtab.len and
2742 symtab[other_sym_index].n_sect - 1 == section_index) : (other_sym_index += 1)
2743 {
2744 if (symtab[other_sym_index].n_value == section.addr + atom_offset) continue;
2745 if (symtab[other_sym_index].n_desc.alt_entry) continue;
2746 // This symbol splits up the subsections. We haven't validated the
2747 // value of the symbol, so it might exceed the section bounds. We
2748 // will check for that error case later when the outer loop reaches
2749 // that symbol, so we don't need to check now, but we should limit
2750 // the size of the atom so that we don't allocate a potentially huge
2751 // node just before failing.
2752 break @min(
2753 symtab[other_sym_index].n_value - section.addr,
2754 section.size,
2755 );
2756 } else new_atom_end: {
2757 // There is no other symbol which splits this section, so the new
2758 // atom must cover the entire remainder of the section.
2759 break :new_atom_end section.size;
2760 };
2761
2762 break :atom_size new_atom_end - atom_offset;
2763 },
2764 };
2765 assert(atom_size > 0);
2766 assert(atom_offset + atom_size <= section.size);
2767
2768 const atom_align = section.@"align".offset(atom_offset);
2769
2770 try macho.nodes.ensureUnusedCapacity(gpa, 1);
2771 try macho.input_atoms.ensureUnusedCapacity(gpa, 1);
2772 const atom_node = try parent_section.node(macho).addFloatingChild(gpa, &macho.mf, .{
2773 .size = atom_align.forward(atom_size),
2774 .alignment = atom_align,
2775 .moved = true, // `flushMoved` will set up vaddrs and apply relocs
2776 });
2777 const iai: InputAtom.Index = @fromBackingInt(@intCast(macho.input_atoms.items.len));
2778 macho.input_atoms.appendAssumeCapacity(.{
2779 .node = atom_node,
2780 .first_symbol = .none,
2781 .first_reloc = @fromBackingInt(0), // populated for real later
2782 .vaddr = 0,
2783 .any_fixups = false,
2784 });
2785 macho.nodes.appendAssumeCapacity(.{ .input_atom = iai });
2786
2787 if (section.usage != .bss) {
2788 const slice = atom_node.slice(&macho.mf);
2789 try fr.interface.readSliceAll(slice[0..@intCast(atom_size)]);
2790 }
2791
2792 try atoms.append(gpa, .{
2793 .offset_in_section = atom_offset,
2794 .index = iai,
2795 });
2796 cur_atom_end = atom_offset + atom_size;
2797 }
2798
2799 if (sym_in_section) {
2800 // We just advanced to the atom containing this symbol, so now we can add it.
2801 const atom = atoms.lastPtr().?;
2802 const nlist = &symtab[sym_index];
2803 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2804 const symbol_offset_in_section = symtab[sym_index].n_value - section.addr;
2805 symbols[orig_sym_indices[sym_index]] = if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2806 .name = sym_name,
2807 .value = .{ .addr_in_node = .{
2808 .node = atom.index.node(macho),
2809 .offset = symbol_offset_in_section - atom.offset_in_section,
2810 } },
2811 }) else macho.addGlobalSymbol(.{
2812 .name = sym_name,
2813 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2814 false => .strong,
2815 true => .weak,
2816 },
2817 .visibility = switch (nlist.n_type.bits.pext) {
2818 false => .default,
2819 true => .hidden,
2820 },
2821 .value = .{ .addr_in_node = .{
2822 .node = atom.index.node(macho),
2823 .offset = symbol_offset_in_section - atom.offset_in_section,
2824 } },
2825 }) catch |err| switch (err) {
2826 error.MultipleDefinitions => return diags.failParse(
2827 path,
2828 "multiple definitions of '{s}'",
2829 .{sym_name},
2830 ),
2831 else => |e| return e,
2832 };
2833
2834 sym_index += 1;
2835 }
2836 }
2837 section.atoms_end = atoms.items.len;
2838 }
2839
2840 // If, after handling all of the input sections, there is still a symbol remaining, then
2841 // that symbol must have an out-of-range `n_sect` value.
2842 if (sym_index != symtab.len) {
2843 const nlist = &symtab[sym_index];
2844 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2845 path,
2846 "invalid symtab entry at index {d}: object file cannot contain stabs",
2847 .{sym_index},
2848 );
2849 if (nlist.n_strx > strtab.len) return diags.failParse(
2850 path,
2851 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2852 .{ sym_index, nlist.n_strx },
2853 );
2854 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2855 assert(nlist.n_type.bits.type == .sect);
2856 assert(nlist.n_sect - 1 > input_sections.items.len);
2857 return diags.failParse(
2858 path,
2859 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2860 .{ sym_name, nlist.n_sect },
2861 );
2862 }
2863 }
2864
2865 // Now that the symbols are done, we can process relocations.
2866
2867 var relocs_buf: std.ArrayList(std.macho.relocation_info) = .empty;
2868 defer relocs_buf.deinit(gpa);
2869
2870 for (input_sections.items) |*section| {
2871 if (section.reloff + section.nreloc > fl.size) {
2872 return diags.failParse(path, "relocations for '{s},{s}' exceed file size", .{
2873 std.mem.sliceTo(&section.segname, 0),
2874 std.mem.sliceTo(&section.sectname, 0),
2875 });
2876 }
2877
2878 try relocs_buf.resize(gpa, section.nreloc);
2879 try fr.seekTo(fl.offset + section.reloff);
2880 try fr.interface.readSliceEndian(std.macho.relocation_info, relocs_buf.items, target_endian);
2881
2882 // We need to sort the section's relocations by address for two reasons:
2883 //
2884 // * We need the relocations in each atom to be contiguous. This allows us to use sub-arrays
2885 // of `macho.relocs` for the relocations in an atom, rather than e.g. linked lists.
2886 //
2887 // * We need the relocations *within* each atom to be sorted, so that we can efficiently
2888 // generate runtime fixup chains if necessary.
2889 //
2890 // This sort must be stable so that `*_RELOC_ADDEND` and `*_RELOC_SUBTRACTOR` relocations
2891 // remain correctly paired.
2892 const SortRelocsContext = struct {
2893 fn lessThan(_: @This(), lhs: std.macho.relocation_info, rhs: std.macho.relocation_info) bool {
2894 return lhs.r_address < rhs.r_address;
2895 }
2896 };
2897 std.mem.sort(
2898 std.macho.relocation_info,
2899 relocs_buf.items,
2900 @as(SortRelocsContext, .{}),
2901 SortRelocsContext.lessThan,
2902 );
2903
2904 try macho.relocs.ensureUnusedCapacity(gpa, relocs_buf.items.len);
2905
2906 assert(atoms.items[section.atoms_start].offset_in_section == 0);
2907
2908 var atom_index = section.atoms_start;
2909 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2910 macho.relocs.items.len,
2911 ));
2912
2913 var paired_addend: ?u64 = null;
2914 for (relocs_buf.items) |reloc| {
2915 if (reloc.r_address < 0) {
2916 return diags.failParse(path, "scattered relocations are not supported", .{});
2917 }
2918 const offset_in_section: u32 = @intCast(reloc.r_address);
2919
2920 // Find the atom containing `reloc`. (This is why we sorted the relocations!)
2921 while (atom_index + 1 < section.atoms_end and
2922 offset_in_section >= atoms.items[atom_index + 1].offset_in_section)
2923 {
2924 atom_index += 1;
2925 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2926 macho.relocs.items.len,
2927 ));
2928 }
2929
2930 {
2931 const atom_end: u64 = atom_end: {
2932 if (atom_index == section.atoms_end - 1) {
2933 break :atom_end section.size;
2934 }
2935 const next_atom = &atoms.items[atom_index + 1];
2936 break :atom_end next_atom.offset_in_section;
2937 };
2938 if (offset_in_section + (@as(u8, 1) << reloc.r_length) > atom_end) {
2939 const segname = std.mem.sliceTo(&section.segname, 0);
2940 const sectname = std.mem.sliceTo(&section.sectname, 0);
2941 return diags.failParse(
2942 path,
2943 "relocation at offset '{d}' in '{s},{s}' spans multiple atoms",
2944 .{ offset_in_section, segname, sectname },
2945 );
2946 }
2947 }
2948
2949 const offset_in_atom: u32 = @intCast(offset_in_section - atoms.items[atom_index].offset_in_section);
2950
2951 const length_types: [4]?Reloc.Type, const target: Reloc.Target, const pcrel: bool, const implicit_negative_addend: u64 = switch (header.cputype) {
2952 else => unreachable,
2953
2954 std.macho.CPU_TYPE_X86_64 => reloc_type: switch (std.enums.fromInt(
2955 std.macho.reloc_type_x86_64,
2956 reloc.r_type,
2957 ) orelse {
2958 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2959 }) {
2960 .X86_64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2961
2962 .X86_64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2963 .X86_64_RELOC_SIGNED => .{ .{ null, null, .rel32, null }, .symbol, true, 4 },
2964 .X86_64_RELOC_SIGNED_1 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 5 else 4 },
2965 .X86_64_RELOC_SIGNED_2 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 6 else 4 },
2966 .X86_64_RELOC_SIGNED_4 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 8 else 4 },
2967
2968 .X86_64_RELOC_BRANCH => .{ .{ null, null, .rel32, null }, .branch, true, 4 },
2969
2970 .X86_64_RELOC_TLV => .{ .{ null, null, .rel32, null }, .tlv, true, 4 },
2971
2972 .X86_64_RELOC_GOT_LOAD => continue :reloc_type .X86_64_RELOC_GOT, // relaxable
2973 .X86_64_RELOC_GOT => .{ .{ null, null, .rel32, null }, .got, true, 4 },
2974 },
2975
2976 std.macho.CPU_TYPE_ARM64 => switch (std.enums.fromInt(
2977 std.macho.reloc_type_arm64,
2978 reloc.r_type,
2979 ) orelse {
2980 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2981 }) {
2982 .ARM64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2983
2984 .ARM64_RELOC_ADDEND => {
2985 const addend: i24 = @bitCast(reloc.r_symbolnum);
2986 const extended: i64 = addend;
2987 paired_addend = @bitCast(extended);
2988 continue;
2989 },
2990
2991 .ARM64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2992 .ARM64_RELOC_BRANCH26 => .{ .{ null, null, .arm64_branchrel26, null }, .branch, true, 0 },
2993 .ARM64_RELOC_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .symbol, true, 0 },
2994 .ARM64_RELOC_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .symbol, false, 0 },
2995
2996 .ARM64_RELOC_TLVP_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .tlv, true, 0 },
2997 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .tlv, false, 0 },
2998
2999 .ARM64_RELOC_GOT_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .got, true, 0 },
3000 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .got, false, 0 },
3001
3002 // Apple's ld_classic supported an 8-byte non-pcrel version of this relocation,
3003 // but it had very weird semantics which didn't actually involve the GOT at all.
3004 // My guess is that they intended it to do the obvious thing but shipped with a
3005 // bug. In any case, it is not supported in Apple's new linker, and LLD does not
3006 // support it either, so therefore we should follow suit and only support the
3007 // usual form of this relocation (4-byte pcrel).
3008 .ARM64_RELOC_POINTER_TO_GOT => .{ .{ null, null, .rel32, null }, .got, true, 0 },
3009 },
3010 };
3011 const reloc_type_str: []const u8 = switch (header.cputype) {
3012 else => unreachable,
3013 std.macho.CPU_TYPE_ARM64 => @tagName(@as(
3014 std.macho.reloc_type_arm64,
3015 @fromBackingInt(reloc.r_type),
3016 )),
3017 std.macho.CPU_TYPE_X86_64 => @tagName(@as(
3018 std.macho.reloc_type_x86_64,
3019 @fromBackingInt(reloc.r_type),
3020 )),
3021 };
3022 if (@intFromBool(pcrel) != reloc.r_pcrel) {
3023 return diags.failParse(path, "relocation '{s}' {s} be pc-relative", .{
3024 reloc_type_str,
3025 if (pcrel) "must" else "cannot",
3026 });
3027 }
3028 const internal_type: Reloc.Type = length_types[reloc.r_length] orelse {
3029 return diags.failParse(path, "relocation '{s}' does not support length '{d}'", .{
3030 reloc_type_str,
3031 @as(u8, 1) << reloc.r_length,
3032 });
3033 };
3034
3035 const dest_slice = atoms.items[atom_index].index.node(macho).sliceConst(&macho.mf)[offset_in_atom..];
3036 if (!internal_type.validateInst(macho, dest_slice)) {
3037 return diags.failParse(path, "relocation '{s}' against invalid instruction", .{
3038 reloc_type_str,
3039 });
3040 }
3041 const stored_addend: u64 = switch (internal_type) {
3042 .abs8 => std.mem.readInt(u8, dest_slice[0..1], target_endian),
3043 .abs16 => std.mem.readInt(u16, dest_slice[0..2], target_endian),
3044 .abs32 => std.mem.readInt(u32, dest_slice[0..4], target_endian),
3045 .abs64 => std.mem.readInt(u64, dest_slice[0..8], target_endian),
3046 .rel32 => a: {
3047 const extended: i64 = std.mem.readInt(i32, dest_slice[0..4], target_endian);
3048 break :a @bitCast(extended);
3049 },
3050
3051 .arm64_branchrel26,
3052 .arm64_pagerel21,
3053 .arm64_pageoff12,
3054 => a: {
3055 defer paired_addend = null;
3056 break :a paired_addend orelse 0;
3057 },
3058 };
3059 if (paired_addend != null) {
3060 return diags.failParse(path, "relocation '{s}' does not support paired addend", .{reloc_type_str});
3061 }
3062 const target_symbol: Symbol.Index, const final_addend: u64 = switch (reloc.r_extern) {
3063 1 => target: {
3064 // The relocation targets a symbol. This is the nice and reasonable case.
3065 if (reloc.r_symbolnum >= symbols.len) {
3066 return diags.failParse(path, "relocation target symbol '{d}' is invalid", .{reloc.r_symbolnum});
3067 }
3068 const target_sym = symbols[reloc.r_symbolnum];
3069 break :target .{ target_sym, stored_addend -% implicit_negative_addend };
3070 },
3071 0 => target: {
3072 // In this case, the relocation targets an offset into a *section*. I think the
3073 // best way to interpret this is to say that the "stored addend" value contains
3074 // what the relocated value *would* be if the linked image's section addresses
3075 // matched those of the relocatable itself (unlike ELF, Mach-O uses a flat
3076 // virtual address space even in relocatables). So, the best way to handle this
3077 // in the pcrel case is to reverse the computation to figure out what the actual
3078 // target address is.
3079 //
3080 // Unfortunately, this is further complicated by the existence of subsections.
3081 // Subsections split up the target section's virtual address space, and the
3082 // subsection being targeted by a relocation is determined implicitly based on
3083 // the target address. Therefore, to ensure we target the correct subsection, it
3084 // is necessary to compute the *exact* target address (no off-by-N). This Mach-O
3085 // quirk is why we have `implicit_negative_addend` on x86_64: it indicates that
3086 // the encoded pc-relative value is relative not to the exact address of the
3087 // relocation, but rather to an address a few bytes later, allowing us to
3088 // compute the exact target address instead of being 4--8 bytes short.
3089 //
3090 // Mach-O is a tremendously well-designed object format with no flaws whatsoever.
3091 if (reloc.r_symbolnum > input_sections.items.len) {
3092 return diags.failParse(path, "relocation target section '{d}' is invalid", .{reloc.r_symbolnum});
3093 }
3094 const target_addr_in_object: u64 = switch (pcrel) {
3095 false => stored_addend +% implicit_negative_addend,
3096 true => target_addr: {
3097 const pcrel_base: u64 = section.addr + @as(u32, @intCast(reloc.r_address));
3098 break :target_addr pcrel_base +% stored_addend +% implicit_negative_addend;
3099 },
3100 };
3101 const target_section = &input_sections.items[reloc.r_symbolnum - 1];
3102 const target_offset_in_section = target_addr_in_object -% target_section.addr;
3103 if (target_offset_in_section >= target_section.size) {
3104 const segname = std.mem.sliceTo(&target_section.segname, 0);
3105 const sectname = std.mem.sliceTo(&target_section.sectname, 0);
3106 return diags.failParse(
3107 path,
3108 "relocation target offset '{d}' exceeds range of target section '{s},{s}'",
3109 .{ target_offset_in_section, segname, sectname },
3110 );
3111 }
3112 // We need to know which atom we're targeting.
3113 const FindAtomContext = struct {
3114 offset_in_section: u64,
3115 fn order(lhs: @This(), rhs: Atom) std.math.Order {
3116 return std.math.order(lhs.offset_in_section, rhs.offset_in_section);
3117 }
3118 };
3119 assert(target_section.atoms_start < target_section.atoms_end);
3120 assert(atoms.items[target_section.atoms_start].offset_in_section == 0);
3121 const target_atom_index = target_section.atoms_start + std.sort.upperBound(
3122 Atom,
3123 atoms.items[target_section.atoms_start..target_section.atoms_end],
3124 @as(FindAtomContext, .{ .offset_in_section = target_offset_in_section }),
3125 FindAtomContext.order,
3126 ) - 1; // safe because atom 0 is at offset 0 so never >rhs
3127 assert(target_offset_in_section >= atoms.items[target_atom_index].offset_in_section);
3128 if (target_atom_index != target_section.atoms_end - 1) {
3129 assert(target_offset_in_section < atoms.items[target_atom_index + 1].offset_in_section);
3130 }
3131 // To preserve my own sanity, I'm just creating symbols to represent these
3132 // relocation targets. It's fine, I'm sure nobody will notice.
3133 var sym_name_buf: [64]u8 = undefined; // sectname is capped at 16 bytes
3134 const sym_name = std.mem.print(
3135 &sym_name_buf,
3136 "l_{s}_{s}_{x}",
3137 .{
3138 std.mem.trim(u8, std.mem.sliceTo(&target_section.segname, 0), "_"),
3139 std.mem.trim(u8, std.mem.sliceTo(&target_section.sectname, 0), "_"),
3140 target_offset_in_section,
3141 },
3142 ) catch unreachable;
3143 const target_sym = try macho.addInternalSymbol(.{
3144 .name = sym_name,
3145 .value = .{ .addr_in_node = .{
3146 .node = atoms.items[target_atom_index].index.node(macho),
3147 .offset = target_offset_in_section - atoms.items[target_atom_index].offset_in_section,
3148 } },
3149 });
3150 // Lovely, we have an actual symbol at the correct address which we can target.
3151 // Just remember to add the implicit addend back in so that the relocated
3152 // PC-relative address is relative to the correct base address!
3153 break :target .{ target_sym, -%implicit_negative_addend };
3154 },
3155 };
3156 const target_is_undef = macho.targetLoad(&target_symbol.nlist(macho).n_type).bits.type == .undf;
3157 if (target_is_undef) {
3158 const imports_gop = try macho.imports.getOrPut(gpa, target_symbol);
3159 if (!imports_gop.found_existing) {
3160 assert(imports_gop.index == macho.imports.count() - 1);
3161 try macho.appendImportEntry(macho.imports.count() - 1, target_symbol);
3162 }
3163 }
3164 if (target == .got or (target_is_undef and target == .branch)) {
3165 const got_gop = try macho.got.getOrPut(gpa, target_symbol);
3166 if (!got_gop.found_existing) {
3167 assert(got_gop.index == macho.got.count() - 1);
3168 try macho.appendGotEntry(macho.got.count() - 1, target_symbol);
3169 }
3170 }
3171 if (target_is_undef and target == .branch) {
3172 const stubs_gop = try macho.stubs.getOrPut(gpa, target_symbol);
3173 if (!stubs_gop.found_existing) {
3174 assert(stubs_gop.index == macho.stubs.count() - 1);
3175 const got_index = macho.got.getIndex(target_symbol).?; // added above
3176 try macho.appendStubEntry(macho.stubs.count() - 1, got_index, target_symbol);
3177 }
3178 }
3179 const ri: Reloc.Index = @fromBackingInt(@intCast(macho.relocs.items.len));
3180 macho.relocs.appendAssumeCapacity(.{
3181 .node = atoms.items[atom_index].index.node(macho),
3182 .offset = offset_in_atom,
3183 .target_symbol = target_symbol,
3184 .addend = final_addend,
3185 .bits = .{ .target = target, .type = internal_type, .result = .ok },
3186 .next_with_same_target_sym = target_symbol.ptr(macho).first_target_reloc,
3187 });
3188 target_symbol.ptr(macho).first_target_reloc = .wrap(ri);
3189
3190 if (target == .symbol and internal_type == .abs64) {
3191 // In this case the relocation needs a runtime fixup. This is the case even if the
3192 // target symbol is internal, because we still need to rebase the address.
3193 ri.ptr(macho).bits.result = .fixup;
3194 atoms.items[atom_index].index.ptr(macho).any_fixups = true;
3195 }
3196 }
3197 }
10603198}
3199fn fmtMemberString(member: ?[]const u8) std.fmt.Alt(?[]const u8, memberStringEscape) {
3200 return .{ .data = member };
3201}
3202fn memberStringEscape(member: ?[]const u8, w: *Io.Writer) Io.Writer.Error!void {
3203 try w.print("({f})", .{std.zig.fmtString(member orelse return)});
3204}
3205
10613206pub fn setDarwinSdkVersion(macho: *MachO, sdk_version: link.DarwinSdkVersion) link.Error!void {
10623207 const comp = macho.base.comp;
10633208 const diags = &comp.link_diags;
......@@ -1098,21 +3243,54 @@ pub fn flush(
10983243 tid: Zcu.PerThread.Id,
10993244 prog_node: std.Progress.Node,
11003245) link.Error!void {
1101 const diags = &macho.base.comp.link_diags;
3246 const comp = macho.base.comp;
3247 const diags = &comp.link_diags;
11023248 _ = arena;
11033249 _ = tid;
11043250 _ = prog_node;
11053251
1106 try macho.updateSegmentLoadAddresses();
3252 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
3253 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
3254 else => |e| return e,
3255 };
3256
11073257 while (try macho.idle()) {}
11083258
3259 if (macho.overflowed_reloc_count > 0) {
3260 diags.addError("failed to apply {d} relocations: overflow", .{macho.overflowed_reloc_count});
3261 }
3262 if (macho.misaligned_reloc_count > 0) {
3263 diags.addError("failed to apply {d} relocations: misaligned value", .{macho.misaligned_reloc_count});
3264 }
3265
11093266 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);
3267 const sym_name: []const u8 = name: switch (macho.options.entry) {
3268 .default => switch (comp.config.output_mode) {
3269 .Exe => continue :name .enabled,
3270 .Lib, .Obj => continue :name .disabled,
3271 },
3272 .disabled => unreachable, // `macho.lc.entry_point` is only set if entry is enabled
3273 .enabled => "_main",
3274 .named => |named| named,
3275 };
3276 const symbol = macho.globalByName(sym_name).unwrap() orelse {
3277 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3278 };
3279 if (macho.targetLoad(&symbol.nlist(macho).n_type).bits.type == .undf) {
3280 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3281 }
3282
3283 // Documentation says that this field should be the offset of the entry point in the file,
3284 // but that's a blatant lie; if you read the dyld source code, it's actually the vaddr minus
3285 // the text base vaddr. Because, like, obviously---what the fuck would they do with the
3286 // offset in the Mach-O file?
11143287 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));
3288 macho.targetStore(&entry_point_ptr.entryoff, std.math.cast(
3289 u32,
3290 symbol.value(macho) - Segment.text.vaddr(macho),
3291 ) orelse {
3292 return diags.fail("entry point address too big", .{});
3293 });
11163294 }
11173295
11183296 macho.mf.flush() catch |err| switch (err) {
......@@ -1136,9 +3314,32 @@ pub fn idle(macho: *MachO) link.Error!bool {
11363314 task: {
11373315 if (macho.sections_changed_index.pop()) |kv| {
11383316 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));
3317 var child_it = section.node(macho).first(&macho.mf);
3318 while (child_it.unwrap()) |child| : (child_it = child.next(&macho.mf)) {
3319 switch (macho.getNode(child)) {
3320 .macho => unreachable,
3321 .macho_header => unreachable,
3322 .symtab => unreachable,
3323 .strtab => unreachable,
3324 .indirect_syms => unreachable,
3325 .section_indirect_syms => unreachable,
3326 .linkedit_data => unreachable,
3327 .fixup_chains_header => unreachable,
3328 .fixup_chains_imports => unreachable,
3329 .fixup_chains_symbol_names => unreachable,
3330 .fixup_chains_starts => unreachable,
3331 .fixup_chains_starts_in_image => unreachable,
3332 .fixup_chains_starts_in_segment => unreachable,
3333 .segment => unreachable,
3334 .section => unreachable,
3335 .section_manual_size => unreachable,
3336 .input_atom => |iai| {
3337 var sym_it = iai.ptrConst(macho).first_symbol;
3338 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3339 macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho));
3340 }
3341 },
3342 }
11423343 }
11433344 break :task;
11443345 }
......@@ -1157,24 +3358,209 @@ pub fn idle(macho: *MachO) link.Error!bool {
11573358
11583359 break :task;
11593360 }
3361
3362 if (macho.dirty_fixup_chain_pages.pop()) |kv| {
3363 const sub_prog_node = macho.mf.update_prog_node.start("Fixup Chains", 0);
3364 defer sub_prog_node.end();
3365 macho.flushFixupChains(kv.key.seg, kv.key.page);
3366 break :task;
3367 }
11603368 }
11613369
11623370 return macho.sections_changed_index.count() > 0 or
1163 macho.mf.updates.items.len > 0;
3371 macho.mf.updates.items.len > 0 or
3372 macho.dirty_fixup_chain_pages.count() > 0;
11643373}
11653374fn flushMoved(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
11663375 macho.flushMachOFileOffset(ni);
11673376 switch (macho.getNode(ni)) {
11683377 .macho => unreachable,
11693378 .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);
3379 .indirect_syms => {},
3380 .section_indirect_syms => |section| {
3381 const offset, _ = ni.location(&macho.mf).resolve(&macho.mf);
3382 macho.targetStore(&section.lcPtr(macho).reserved1, @intCast(
3383 @divExact(offset, 4), // valid due to alignment
3384 ));
3385 },
3386 .segment => |segment| {
3387 if (segment.fixupChainStartsNode(macho).unwrap()) |starts_in_segment_node| {
3388 const starts_in_segment: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
3389 starts_in_segment_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
3390 ));
3391 macho.targetStore(
3392 &starts_in_segment.segment_offset,
3393 segment.vaddr(macho) - Segment.text.vaddr(macho),
3394 );
3395 }
3396 return; // segments don't bubble moved
3397 },
3398 .section, .section_manual_size => |section| {
3399 const segment_vaddr: u64 = section.segment(macho).vaddr(macho);
11733400 const section_offset = ni.location(&macho.mf).resolve(&macho.mf)[0];
11743401 macho.targetStore(&section.lcPtr(macho).addr, segment_vaddr + section_offset);
3402
3403 if (section == macho.known_sections.stubs) {
3404 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3405 const got_index = macho.got.getIndex(symbol).?;
3406 macho.writeStubEntry(stub_index, got_index);
3407 }
3408 for (macho.got.keys()) |symbol| {
3409 try symbol.applyTargetRelocs(macho, .init(.{
3410 .symbol = false,
3411 .branch = true,
3412 .got = false,
3413 .tlv = false,
3414 }));
3415 }
3416 }
3417
3418 if (section == macho.known_sections.got) {
3419 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3420 const got_index = macho.got.getIndex(symbol).?;
3421 macho.writeStubEntry(stub_index, got_index);
3422 }
3423 for (macho.got.keys()) |symbol| {
3424 try symbol.applyTargetRelocs(macho, .init(.{
3425 .symbol = false,
3426 .branch = false,
3427 .got = true,
3428 .tlv = false,
3429 }));
3430 }
3431 }
3432
3433 // If it is the GOT which has moved, its runtime fixup need to be flushed.
3434 if (section == macho.known_sections.got and macho.got.count() > 0) {
3435 const segment = section.segment(macho);
3436
3437 const old_start = macho.prev_got_offset;
3438 const new_start: u32 = off: {
3439 const got_file_off = ni.fileLocation(&macho.mf, false).offset;
3440 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3441 break :off @intCast(got_file_off - seg_file_off);
3442 };
3443
3444 const page_align = macho.targetPageAlign();
3445 const got_size = @as(u64, macho.got.count()) * macho.targetPtrSize();
3446
3447 const old_page_start: u32 = @intCast(@divFloor(old_start, page_align.toByteUnits()));
3448 const old_page_end: u32 = @intCast(@divCeil(old_start + got_size, page_align.toByteUnits()));
3449 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3450 macho.base.comp.gpa,
3451 old_page_end - old_page_start,
3452 );
3453 for (old_page_start..old_page_end) |page_index| {
3454 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3455 .seg = segment,
3456 .page = @intCast(page_index),
3457 }, {});
3458 }
3459
3460 const new_page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3461 const new_page_end: u32 = @intCast(@divCeil(new_start + got_size, page_align.toByteUnits()));
3462 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3463 macho.base.comp.gpa,
3464 new_page_end - new_page_start,
3465 );
3466 for (new_page_start..new_page_end) |page_index| {
3467 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3468 .seg = segment,
3469 .page = @intCast(page_index),
3470 }, {});
3471 }
3472
3473 macho.prev_got_offset = new_start;
3474 }
11753475 },
11763476 .symtab => {},
11773477 .strtab => {},
3478 .linkedit_data => {
3479 // No need to bubble moved.
3480 return;
3481 },
3482 .fixup_chains_header => {},
3483 .fixup_chains_imports => {
3484 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3485 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3486 macho.node.fixup_chains_header.slice(&macho.mf),
3487 ));
3488 macho.targetStore(&hdr.imports_offset, @intCast(offset));
3489 },
3490 .fixup_chains_symbol_names => {
3491 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3492 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3493 macho.node.fixup_chains_header.slice(&macho.mf),
3494 ));
3495 macho.targetStore(&hdr.symbols_offset, @intCast(offset));
3496 },
3497 .fixup_chains_starts => {
3498 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3499 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3500 macho.node.fixup_chains_header.slice(&macho.mf),
3501 ));
3502 macho.targetStore(&hdr.starts_offset, @intCast(offset));
3503 },
3504 .fixup_chains_starts_in_image => {},
3505 .fixup_chains_starts_in_segment => |segment| {
3506 const starts_in_image_off = macho.node.fixup_chains_starts_in_image.location(&macho.mf).resolve(&macho.mf)[0];
3507 const starts_in_segment_off = ni.location(&macho.mf).resolve(&macho.mf)[0];
3508
3509 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
3510 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
3511 ));
3512 macho.targetStore(
3513 &chained_starts_in_image[@backingInt(segment) + 2],
3514 @intCast(starts_in_segment_off - starts_in_image_off),
3515 );
3516 },
3517 .input_atom => |iai| {
3518 const input_atom_offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3519 const section = macho.getNode(iai.node(macho).parent(&macho.mf).unwrap().?).section;
3520
3521 const old_atom_addr = iai.ptrConst(macho).vaddr;
3522 const new_atom_addr = macho.targetLoad(&section.lcPtrConst(macho).addr) + input_atom_offset;
3523 iai.ptr(macho).vaddr = new_atom_addr;
3524
3525 const first_reloc = iai.ptr(macho).first_reloc;
3526 for (macho.relocs.items[@backingInt(first_reloc)..]) |*reloc| {
3527 if (reloc.node != ni) break;
3528 try reloc.apply(macho);
3529 }
3530
3531 var sym_it = iai.ptrConst(macho).first_symbol;
3532 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3533 const new_addr = sym.value(macho) - old_atom_addr + new_atom_addr;
3534 try sym.flushMoved(macho, new_addr);
3535 }
3536
3537 // If the atom which has moved contains runtime fixups, they need to be flushed.
3538 if (iai.ptr(macho).any_fixups) {
3539 const segment = section.segment(macho);
3540
3541 const page_align = macho.targetPageAlign();
3542
3543 const new_start: u32 = off: {
3544 const ni_file_off = ni.fileLocation(&macho.mf, false).offset;
3545 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3546 break :off @intCast(ni_file_off - seg_file_off);
3547 };
3548 const new_end = new_start + ni.location(&macho.mf).resolve(&macho.mf)[1];
3549
3550 const page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3551 const page_end: u32 = @intCast(@divCeil(new_end, page_align.toByteUnits()));
3552 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3553 macho.base.comp.gpa,
3554 page_end - page_start,
3555 );
3556 for (page_start..page_end) |page_index| {
3557 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3558 .seg = segment,
3559 .page = @intCast(page_index),
3560 }, {});
3561 }
3562 }
3563 },
11783564 }
11793565 try ni.childrenMoved(macho.base.comp.gpa, &macho.mf);
11803566}
......@@ -1185,6 +3571,29 @@ fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
11853571 .macho_header => {
11863572 assert(offset == 0);
11873573 },
3574 .symtab => {
3575 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3576 macho.targetStore(&symtab_cmd.symoff, @intCast(offset));
3577 },
3578 .strtab => {
3579 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3580 macho.targetStore(&symtab_cmd.stroff, @intCast(offset));
3581 },
3582 .indirect_syms => {
3583 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3584 macho.targetStore(&dysymtab_cmd.indirectsymoff, @intCast(offset));
3585 },
3586 .section_indirect_syms => {},
3587 .linkedit_data => |lc| {
3588 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3589 macho.targetStore(&linkedit_data_cmd.dataoff, @intCast(offset));
3590 },
3591 .fixup_chains_header => {},
3592 .fixup_chains_imports => {},
3593 .fixup_chains_symbol_names => {},
3594 .fixup_chains_starts => {},
3595 .fixup_chains_starts_in_image => {},
3596 .fixup_chains_starts_in_segment => {},
11883597 .segment => |segment| {
11893598 macho.targetStore(&segment.lcPtr(macho).fileoff, @intCast(offset));
11903599 var child_oni = ni.first(&macho.mf);
......@@ -1192,17 +3601,10 @@ fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
11923601 macho.flushMachOFileOffset(child_ni);
11933602 }
11943603 },
1195 .section => |section| {
3604 .section, .section_manual_size => |section| {
11963605 macho.targetStore(&section.lcPtr(macho).offset, @intCast(offset));
11973606 },
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 },
3607 .input_atom => {},
12063608 }
12073609}
12083610fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
......@@ -1210,19 +3612,249 @@ fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
12103612 switch (macho.getNode(ni)) {
12113613 .macho => {},
12123614 .macho_header => {},
3615 .symtab, .strtab => {
3616 // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do
3617 // that when we actually add strings or symbols.
3618 },
3619 .indirect_syms => {
3620 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3621 macho.targetStore(&dysymtab_cmd.nindirectsyms, @intCast(@divExact(size, 4)));
3622 },
3623 .section_indirect_syms => {},
3624 .linkedit_data => |lc| {
3625 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3626 macho.targetStore(&linkedit_data_cmd.datasize, @intCast(size));
3627 },
3628 .fixup_chains_header => {},
3629 .fixup_chains_imports => {},
3630 .fixup_chains_symbol_names => {},
3631 .fixup_chains_starts => {},
3632 .fixup_chains_starts_in_image => {},
3633 .fixup_chains_starts_in_segment => {},
12133634 .segment => |segment| {
12143635 macho.targetStore(&segment.lcPtr(macho).filesize, size);
12153636 },
12163637 .section => |section| {
12173638 macho.targetStore(&section.lcPtr(macho).size, size);
12183639 },
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.
3640 .section_manual_size => {},
3641 .input_atom => {},
3642 }
3643}
3644
3645fn getNodeVAddr(macho: *MachO, ni: MappedFile.Node.Index) u64 {
3646 return switch (macho.getNode(ni)) {
3647 .macho => unreachable,
3648 .macho_header => unreachable,
3649 .symtab => unreachable,
3650 .strtab => unreachable,
3651 .indirect_syms => unreachable,
3652 .section_indirect_syms => unreachable,
3653 .linkedit_data => unreachable,
3654 .fixup_chains_header => unreachable,
3655 .fixup_chains_imports => unreachable,
3656 .fixup_chains_symbol_names => unreachable,
3657 .fixup_chains_starts => unreachable,
3658 .fixup_chains_starts_in_image => unreachable,
3659 .fixup_chains_starts_in_segment => unreachable,
3660 .segment => |segment| segment.vaddr(macho),
3661 .section, .section_manual_size => |section| section.vaddr(macho),
3662 .input_atom => |iai| iai.ptr(macho).vaddr,
3663 };
3664}
3665fn getNodeSection(macho: *MachO, ni: MappedFile.Node.Index) Section {
3666 var cur_ni = ni;
3667 while (true) {
3668 switch (macho.getNode(cur_ni)) {
3669 .section, .section_manual_size => |section| return section,
3670 else => cur_ni = cur_ni.parent(&macho.mf).unwrap().?,
3671 }
3672 }
3673}
3674fn nodeFirstSymbolPtr(macho: *MachO, ni: MappedFile.Node.Index) *Symbol.Index.Optional {
3675 return switch (macho.getNode(ni)) {
3676 .input_atom => |iai| &iai.ptr(macho).first_symbol,
3677 else => unreachable,
3678 };
3679}
3680
3681fn targetStubSize(macho: *const MachO) u8 {
3682 return switch (macho.base.comp.getTarget().cpu.arch) {
3683 else => unreachable,
3684 .aarch64 => 12,
3685 .x86_64 => 6,
3686 };
3687}
3688fn appendStubEntry(macho: *MachO, old_len: u64, got_index: u64, target_symbol: Symbol.Index) Error!void {
3689 const gpa = macho.base.comp.gpa;
3690
3691 const entry_size = macho.targetStubSize();
3692 const stub_offset = entry_size * old_len;
3693 assert(macho.targetLoad(&macho.known_sections.stubs.lcPtrConst(macho).size) == stub_offset);
3694
3695 const new_size = stub_offset + entry_size;
3696 try macho.known_sections.stubs.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3697 macho.targetStore(&macho.known_sections.stubs.lcPtr(macho).size, new_size);
3698
3699 macho.writeStubEntry(old_len, got_index);
3700
3701 try macho.node.stubs_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3702 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3703 macho.node.stubs_indirect_syms.slice(&macho.mf),
3704 ));
3705 indirect_syms[@intCast(old_len)] = target_symbol;
3706}
3707fn writeStubEntry(macho: *MachO, stub_index: u64, got_index: u64) void {
3708 if (macho.known_sections.stubs.node(macho).hasMoved(&macho.mf)) return;
3709 if (macho.known_sections.got.node(macho).hasMoved(&macho.mf)) return;
3710
3711 const entry_size = macho.targetStubSize();
3712 const stub_offset = entry_size * stub_index;
3713
3714 const got_entry_vaddr = macho.known_sections.got.vaddr(macho) + got_index * macho.targetPtrSize();
3715 const new_stub_vaddr = macho.known_sections.stubs.vaddr(macho) + stub_offset;
3716
3717 switch (macho.base.comp.getTarget().cpu.arch) {
3718 else => unreachable,
3719 .aarch64 => {
3720 // The first instruction of the stub will be an ADRP for which we need to compute the
3721 // offset from that instruction's 4k page to the GOT entry's 4k page.
3722 const Imm21 = packed union {
3723 raw: i21,
3724 split: packed struct(u21) { lo: u2, hi: u19 },
3725 };
3726 const page_off: Imm21 = page_off: {
3727 const cur_page: u53 = @intCast((new_stub_vaddr + 4) >> 12); // first instruction, so pc = stub addr + 4
3728 const target_page: u53 = @intCast(got_entry_vaddr >> 12);
3729 const off: i53 = @bitCast(target_page -% cur_page);
3730 break :page_off .{ .raw = @intCast(off) };
3731 };
3732
3733 const Inst = packed union(u32) {
3734 raw: u32,
3735 adrp: packed struct(u32) {
3736 reg: u5,
3737 immhi: u19,
3738 other0: u5,
3739 immlo: u2,
3740 other1: u1,
3741 },
3742 ldr: packed struct(u32) {
3743 reg_t: u5,
3744 reg_n: u5,
3745 imm12: u12,
3746 other0: u10,
3747 },
3748 };
3749 const insts: []align(4) Inst = @ptrCast(@alignCast(
3750 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3751 ));
3752 @memcpy(insts, @as([]const Inst, &.{
3753 // adrp x16, got_entry@page
3754 .{ .adrp = .{
3755 .reg = 0b10000,
3756 .immhi = page_off.split.hi,
3757 .other0 = 0b100_00,
3758 .immlo = page_off.split.lo,
3759 .other1 = 0b1,
3760 } },
3761 // ldr x16, [x16, got_entry@pageoff]
3762 .{ .ldr = .{
3763 .reg_t = 0b10000,
3764 .reg_n = 0b10000,
3765 .imm12 = @divExact(@as(u12, @truncate(got_entry_vaddr)), 8),
3766 .other0 = 0b11_111_0_01_01,
3767 } },
3768 // br x16
3769 .{ .raw = 0xd61f0200 },
3770 }));
3771 if (macho.targetEndian() != std.lang.Endian.native) {
3772 std.mem.byteSwapAllElements(Inst, insts);
3773 }
3774 },
3775 .x86_64 => {
3776 const new_stub_slice: []align(2) u8 = @ptrCast(@alignCast(
3777 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3778 ));
3779 // jmpq *got_entry(%rip)
3780 @memcpy(new_stub_slice[0..2], @as([]const u8, &.{ 0xff, 0x25 }));
3781 const offset_ptr: *align(2) i32 = @ptrCast(new_stub_slice[2..]);
3782 const offset: i64 = @bitCast(got_entry_vaddr -% (new_stub_vaddr + 6));
3783 macho.targetStore(offset_ptr, @intCast(offset));
12223784 },
12233785 }
12243786}
12253787
3788fn appendGotEntry(macho: *MachO, old_len: u64, target_symbol: Symbol.Index) Error!void {
3789 const gpa = macho.base.comp.gpa;
3790
3791 const ptr_size = macho.targetPtrSize();
3792 const entry_offset = ptr_size * old_len;
3793 assert(macho.targetLoad(&macho.known_sections.got.lcPtrConst(macho).size) == entry_offset);
3794
3795 const new_size = entry_offset + ptr_size;
3796 try macho.known_sections.got.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3797 macho.targetStore(&macho.known_sections.got.lcPtr(macho).size, new_size);
3798
3799 const new_entry = macho.known_sections.got.node(macho).slice(&macho.mf)[@intCast(entry_offset)..][0..ptr_size];
3800 @memset(new_entry, 0);
3801
3802 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
3803 const segment = macho.known_sections.got.segment(macho);
3804 const page_index: u16 = @intCast(@divFloor(
3805 macho.prev_got_offset + entry_offset,
3806 macho.targetPageAlign().toByteUnits(),
3807 ));
3808 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
3809 }
3810
3811 try macho.node.got_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3812 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3813 macho.node.got_indirect_syms.slice(&macho.mf),
3814 ));
3815 indirect_syms[@intCast(old_len)] = target_symbol;
3816}
3817
3818fn appendImportEntry(macho: *MachO, old_len: u64, symbol_index: Symbol.Index) Error!void {
3819 const gpa = macho.base.comp.gpa;
3820
3821 const new_size = @sizeOf(std.macho.dyld_chained_import_addend64) * (old_len + 1);
3822 try macho.node.fixup_chains_imports.ensureMinimumSize(gpa, &macho.mf, new_size);
3823
3824 const symbol_name_off = macho.fixup_chain_symbol_names_len;
3825 macho.fixup_chain_symbol_names_len += @intCast(symbol_index.name(macho).len + 1);
3826 try macho.node.fixup_chains_symbol_names.ensureMinimumSize(gpa, &macho.mf, macho.fixup_chain_symbol_names_len);
3827 {
3828 const sym_name = symbol_index.name(macho);
3829 const name_slice = macho.node.fixup_chains_symbol_names.slice(&macho.mf)[symbol_name_off..];
3830 @memcpy(name_slice[0..sym_name.len], sym_name);
3831 name_slice[sym_name.len] = 0;
3832 }
3833
3834 {
3835 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3836 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
3837 ));
3838 assert(macho.targetLoad(&hdr.imports_count) == old_len);
3839 macho.targetStore(&hdr.imports_count, @intCast(old_len + 1));
3840 }
3841
3842 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
3843 macho.node.fixup_chains_imports.slice(&macho.mf)[0..@intCast(new_size)],
3844 ));
3845 imports[@intCast(old_len)] = .{
3846 .bits = .{
3847 .lib_ordinal = 0xFFFE,
3848 .weak_import = false,
3849 .name_offset = symbol_name_off,
3850 },
3851 .addend = 0,
3852 };
3853 if (macho.targetEndian() != std.lang.Endian.native) {
3854 std.mem.byteSwapAllFields(std.macho.dyld_chained_import_addend64, &imports[@intCast(old_len)]);
3855 }
3856}
3857
12263858pub fn updateFunc(
12273859 macho: *MachO,
12283860 pt: Zcu.PerThread,
......@@ -1277,15 +3909,68 @@ fn printNode(
12773909 ni: MappedFile.Node.Index,
12783910 indent: usize,
12793911) Io.Writer.Error!void {
1280 const node = macho.getNode(ni);
12813912 try w.splatByteAll(' ', indent);
1282 switch (node) {
3913 switch (macho.getNode(ni)) {
12833914 .macho => try w.writeAll("macho"),
12843915 .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)}),
12873916 .symtab => try w.writeAll("symtab"),
12883917 .strtab => try w.writeAll("strtab"),
3918 .indirect_syms => try w.writeAll("indirect_syms"),
3919 .section_indirect_syms => |section| try w.print("section_indirect_syms({s},{s})", .{
3920 section.segmentName(macho),
3921 section.name(macho),
3922 }),
3923 .linkedit_data => |lc| try w.print("linkedit_data(LC_{t})", .{
3924 macho.targetLoad(&lc.ptrConst(macho, std.macho.load_command).cmd),
3925 }),
3926 .fixup_chains_header => try w.writeAll("fixup_chains_header"),
3927 .fixup_chains_imports => try w.writeAll("fixup_chains_imports"),
3928 .fixup_chains_symbol_names => try w.writeAll("fixup_chains_symbol_names"),
3929 .fixup_chains_starts => try w.writeAll("fixup_chains_starts"),
3930 .fixup_chains_starts_in_image => try w.writeAll("fixup_chains_starts_in_image"),
3931 .fixup_chains_starts_in_segment => |seg| try w.print("fixup_chains_starts_in_segment({s})", .{seg.name(macho)}),
3932 .section => |section| try w.print("section({s},{s})", .{
3933 section.segmentName(macho),
3934 section.name(macho),
3935 }),
3936 .section_manual_size => |section| try w.print("section_manual_size({s},{s})", .{
3937 section.segmentName(macho),
3938 section.name(macho),
3939 }),
3940 .segment => |segment| try w.print("segment({s})", .{segment.name(macho)}),
3941 .input_atom => |iai| {
3942 // It's useful to try and give a name to the input atom, but input atoms don't actually
3943 // have names. However, a decent approach if the input atom only contains one symbol is
3944 // to print that symbol's name. We also accept the input atom containing many global
3945 // symbols but only one internal symbol (since that's how Zig exports work).
3946 if (iai.ptrConst(macho).first_symbol.unwrap()) |first_symbol| {
3947 if (first_symbol.ptrConst(macho).next_in_node == .none) {
3948 // There is only one symbol in the section, so it's a good identifier.
3949 try w.print("input_atom({q})", .{first_symbol.name(macho)});
3950 } else {
3951 // Also, if it has N global symbols but only one internal symbol, go with the
3952 // internal name---this works well for how Zig handles exports.
3953 var prev_local_sym: Symbol.Index.Optional = .none;
3954 var sym_it: Symbol.Index.Optional = .wrap(first_symbol);
3955 const only_local_sym: Symbol.Index.Optional = while (sym_it.unwrap()) |sym| : (sym_it = sym.ptrConst(macho).next_in_node) {
3956 const n_type = macho.targetLoad(&sym.nlistConst(macho).n_type);
3957 if (!n_type.bits.ext and !n_type.bits.pext) {
3958 // this is an internal symbol
3959 if (prev_local_sym != .none) break .none;
3960 prev_local_sym = .wrap(sym);
3961 }
3962 } else prev_local_sym;
3963
3964 if (only_local_sym.unwrap()) |local_sym| {
3965 try w.print("input_atom({q})", .{local_sym.name(macho)});
3966 } else {
3967 try w.writeAll("input_atom([multiple symbols])");
3968 }
3969 }
3970 } else {
3971 try w.writeAll("input_atom([no symbols])");
3972 }
3973 },
12893974 }
12903975 {
12913976 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];
......@@ -1341,13 +4026,239 @@ fn printNode(
13414026 }
13424027}
13434028
4029fn flushFixupChains(
4030 macho: *MachO,
4031 segment: Segment,
4032 page: u16,
4033) void {
4034 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
4035
4036 const page_size = macho.targetPageAlign().toByteUnits();
4037 const start_vaddr = segment.vaddr(macho) + @as(u64, page) * page_size;
4038 const end_vaddr = start_vaddr + page_size;
4039
4040 var next_vaddr: u64 = 0;
4041 var node_it = segment.node(macho).last(&macho.mf);
4042 while (node_it.unwrap()) |node| : (node_it = node.prev(&macho.mf)) {
4043 // Other than `__LINKEDIT`, segments can only contain sections.
4044 const sect: Section = switch (macho.getNode(node)) {
4045 .section, .section_manual_size => |sect| sect,
4046 .macho => unreachable,
4047 .macho_header => continue, // the Mach header and load commands are in the __TEXT section
4048 .symtab => unreachable,
4049 .strtab => unreachable,
4050 .indirect_syms => unreachable,
4051 .section_indirect_syms => unreachable,
4052 .linkedit_data => unreachable,
4053 .fixup_chains_header => unreachable,
4054 .fixup_chains_imports => unreachable,
4055 .fixup_chains_symbol_names => unreachable,
4056 .fixup_chains_starts => unreachable,
4057 .fixup_chains_starts_in_image => unreachable,
4058 .fixup_chains_starts_in_segment => unreachable,
4059 .segment => unreachable,
4060 .input_atom => unreachable,
4061 };
4062 if (sect.vaddr(macho) >= end_vaddr) {
4063 // This section is after the page we're interested in. We're iterating backwards, so
4064 // that means we've not yet found the page.
4065 continue;
4066 }
4067 if (sect.vaddr(macho) + macho.targetLoad(&sect.lcPtrConst(macho).size) < start_vaddr) {
4068 // This section is before the page we're interested in. We're iterating backwards, so
4069 // that means we've finished scanning the page.
4070 break;
4071 }
4072 if (sect == macho.known_sections.got) {
4073 macho.flushFixupChainsGot(
4074 &next_vaddr,
4075 start_vaddr,
4076 start_vaddr + page_size,
4077 );
4078 } else {
4079 var sub_node_it = node.last(&macho.mf);
4080 while (sub_node_it.unwrap()) |sub_node| : (sub_node_it = sub_node.prev(&macho.mf)) {
4081 macho.flushFixupChainsNonSpecial(
4082 sub_node,
4083 &next_vaddr,
4084 start_vaddr,
4085 start_vaddr + page_size,
4086 );
4087 }
4088 }
4089 }
4090
4091 const page_starts: []u16 = @ptrCast(@alignCast(
4092 segment.fixupChainStartsNode(macho).unwrap().?.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
4093 ));
4094 if (next_vaddr == 0) {
4095 macho.targetStore(&page_starts[page], 0xFFFF);
4096 } else {
4097 macho.targetStore(&page_starts[page], @intCast(next_vaddr - start_vaddr));
4098 }
4099}
4100fn flushFixupChainsGot(
4101 macho: *MachO,
4102 next_vaddr: *u64,
4103 start_vaddr: u64,
4104 end_vaddr: u64,
4105) void {
4106 const ptr_size = macho.targetPtrSize();
4107 const got_start_vaddr = macho.known_sections.got.vaddr(macho);
4108 const got_end_vaddr = got_start_vaddr + @as(u64, macho.got.count()) * ptr_size;
4109 if (got_start_vaddr >= end_vaddr) return;
4110 if (got_end_vaddr <= start_vaddr) return;
4111 // There is some overlap between the GOT and the page range.
4112 const start_index = @divExact(start_vaddr -| got_start_vaddr, ptr_size);
4113 const end_index = @min(@divExact(end_vaddr -| got_start_vaddr, ptr_size), macho.got.count());
4114
4115 const got_node = macho.known_sections.got.node(macho);
4116 const got_syms = macho.got.keys();
4117
4118 var got_index = end_index;
4119 while (got_index > start_index) {
4120 got_index -= 1;
4121 macho.writeOneFixup(
4122 got_syms[got_index],
4123 0,
4124 got_node,
4125 got_index * ptr_size,
4126 start_vaddr,
4127 end_vaddr,
4128 next_vaddr,
4129 );
4130 }
4131}
4132fn flushFixupChainsNonSpecial(
4133 macho: *MachO,
4134 node: MappedFile.Node.Index,
4135 next_vaddr: *u64,
4136 start_vaddr: u64,
4137 end_vaddr: u64,
4138) void {
4139 switch (macho.getNode(node)) {
4140 .macho => unreachable,
4141 .macho_header => unreachable,
4142 .symtab => unreachable,
4143 .strtab => unreachable,
4144 .indirect_syms => unreachable,
4145 .section_indirect_syms => unreachable,
4146 .linkedit_data => unreachable,
4147 .fixup_chains_header => unreachable,
4148 .fixup_chains_imports => unreachable,
4149 .fixup_chains_symbol_names => unreachable,
4150 .fixup_chains_starts => unreachable,
4151 .fixup_chains_starts_in_image => unreachable,
4152 .fixup_chains_starts_in_segment => unreachable,
4153 .segment => unreachable,
4154 .section => unreachable,
4155 .section_manual_size => unreachable,
4156 .input_atom => |iai| {
4157 const first_reloc = iai.ptr(macho).first_reloc;
4158
4159 var reloc = first_reloc;
4160 var prev_reloc_off: ?u32 = null;
4161 while (@backingInt(reloc) < macho.relocs.items.len and
4162 reloc.ptr(macho).node == node)
4163 {
4164 if (prev_reloc_off) |prev_off| {
4165 assert(reloc.ptr(macho).offset > prev_off);
4166 }
4167 prev_reloc_off = reloc.ptr(macho).offset;
4168 reloc = @fromBackingInt(@backingInt(reloc) + 1);
4169 }
4170
4171 while (reloc != first_reloc) {
4172 reloc = @fromBackingInt(@backingInt(reloc) - 1);
4173 if (reloc.ptr(macho).bits.result == .fixup) {
4174 macho.writeOneFixup(
4175 reloc.ptr(macho).target_symbol,
4176 reloc.ptr(macho).addend,
4177 reloc.ptr(macho).node,
4178 reloc.ptr(macho).offset,
4179 start_vaddr,
4180 end_vaddr,
4181 next_vaddr,
4182 );
4183 }
4184 }
4185 },
4186 }
4187}
4188fn writeOneFixup(
4189 macho: *MachO,
4190 target: Symbol.Index,
4191 addend: u64,
4192 dest_node: MappedFile.Node.Index,
4193 dest_offset: u64,
4194 start_vaddr: u64,
4195 end_vaddr: u64,
4196 next_vaddr: *u64,
4197) void {
4198 assert(macho.targetPtrSize() == 8);
4199 const dest_vaddr = macho.getNodeVAddr(dest_node) + dest_offset;
4200 if (dest_vaddr < start_vaddr or dest_vaddr >= end_vaddr) return;
4201 const dest_ptr: *align(4) std.macho.dyld_chained_ptr_64 = @ptrCast(@alignCast(
4202 dest_node.slice(&macho.mf)[@intCast(dest_offset)..][0..8],
4203 ));
4204 switch (macho.targetLoad(&target.nlist(macho).n_type).bits.type) {
4205 .undf => {
4206 const import_index = macho.imports.getIndex(target).?;
4207 macho.targetStore(dest_ptr, .{
4208 .kind = .bind,
4209 .next = if (next_vaddr.* != 0) @intCast(
4210 @divExact(next_vaddr.* - dest_vaddr, 4),
4211 ) else 0,
4212 .per_kind = .{ .bind = .{
4213 .ordinal = @intCast(import_index),
4214 .addend = std.math.cast(u8, addend) orelse {
4215 std.debug.panic("TODO(MachO2): large fixup addend (target={q}, addend={d})", .{
4216 target.name(macho), addend,
4217 });
4218 },
4219 } },
4220 });
4221 next_vaddr.* = dest_vaddr;
4222 },
4223 .sect => {
4224 const target_val: packed struct(u64) {
4225 low36: u36,
4226 mid: u20,
4227 high8: u8,
4228 } = @bitCast(target.value(macho) +% addend);
4229 if (target_val.mid != 0) {
4230 @panic("TODO(MachO2): report non-zero mid bits in rebase fixup (vmaddrs too large?)");
4231 }
4232 macho.targetStore(dest_ptr, .{
4233 .kind = .rebase,
4234 .next = if (next_vaddr.* != 0) @intCast(
4235 @divExact(next_vaddr.* - dest_vaddr, 4),
4236 ) else 0,
4237 .per_kind = .{ .rebase = .{
4238 .target = target_val.low36,
4239 .high8 = target_val.high8,
4240 } },
4241 });
4242 next_vaddr.* = dest_vaddr;
4243 },
4244 .abs => {
4245 // We actually *don't* need a fixup---this is just a static relocation with extra steps!
4246 const target_val = target.value(macho) +% addend;
4247 macho.targetStore(dest_ptr, @bitCast(target_val));
4248 },
4249 else => unreachable,
4250 }
4251}
4252
13444253const std = @import("std");
13454254const assert = std.debug.assert;
13464255const log = std.log.scoped(.link);
13474256const Allocator = std.mem.Allocator;
13484257const Io = std.Io;
13494258
4259const aarch64 = @import("../codegen/aarch64.zig");
13504260const link = @import("../link.zig");
4261const tapi = @import("tapi.zig");
13514262const codegen = @import("../codegen.zig");
13524263const Compilation = @import("../Compilation.zig");
13534264const InternPool = @import("../InternPool.zig");