authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-22 22:49:29+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-26 18:21:21+01:00
logc744875fd59cbe1fd4b1d89f51218a2e4e778b7b
treed6c1970a6dacd5c19416f1e0145756778181ce88
parentcdfeda8f0d7734dc3930caf3363a5f227773c6a1
signature Commit is signed but in an unrecognized format.

MachO2: emit a valid executable

We're still not doing anything useful, but we now emit a valid Mach-O file, with a few basic load commands. This is enough that the following command now emits a binary which will run on a macOS system configured to allow unsigned executables: ``` zig build-exe -target aarch64-macos -fno-compiler-rt -fno-sanitize-c -fnew-linker -femit-bin=empty ``` Of course, the binary crashes immediately since it does not contain any instructions, but just getting something to run without being killed by the kernel or dyld is progress.

2 files changed, 1122 insertions(+), 44 deletions(-)

src/link/Elf2.zig+1-1
...@@ -12359,7 +12359,7 @@ pub fn printNode(...@@ -12359,7 +12359,7 @@ pub fn printNode(
12359 mf_node.flags.alignment.toByteUnits(),12359 mf_node.flags.alignment.toByteUnits(),
12360 mf_node.flags.position,12360 mf_node.flags.position,
12361 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",12361 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",
12362 if (mf_node.flags.resized) " moved" else "",12362 if (mf_node.flags.moved) " moved" else "",
12363 if (mf_node.flags.resized) " resized" else "",12363 if (mf_node.flags.resized) " resized" else "",
12364 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",12364 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",
12365 if (mf_node.flags.next_moved) " next_moved" else "",12365 if (mf_node.flags.next_moved) " next_moved" else "",
src/link/MachO2.zig+1121-43
...@@ -2,22 +2,223 @@ const MachO = @This();...@@ -2,22 +2,223 @@ const MachO = @This();
22
3base: link.File,3base: link.File,
4options: link.File.OpenOptions,4options: link.File.OpenOptions,
5mf: MappedFile,
6nodes: std.MultiArrayList(Node),
75
8const_prog_node: std.Progress.Node,6const_prog_node: std.Progress.Node,
9synth_prog_node: std.Progress.Node,7synth_prog_node: std.Progress.Node,
10input_prog_node: std.Progress.Node,8input_prog_node: std.Progress.Node,
119
10mf: MappedFile,
11nodes: std.MultiArrayList(Node),
12
13node: struct {
14 macho: MappedFile.Node.Index,
15 header: MappedFile.Node.Index,
16 symtab: MappedFile.Node.Index,
17 strtab: MappedFile.Node.Index,
18},
19
20lc: struct {
21 offsets: std.ArrayList(u32),
22 pagezero: LoadCommand,
23 symtab: LoadCommand,
24 entry_point: ?LoadCommand,
25},
26/// Does not contain an entry for the `__PAGEZERO` segment.
27segments: std.MultiArrayList(struct {
28 lc: LoadCommand,
29 node: MappedFile.Node.Index,
30}),
31sections: std.MultiArrayList(struct {
32 lc: LoadCommand,
33 node: MappedFile.Node.Index,
34 first_symbol: Symbol.Index.Optional,
35 index: u8,
36}),
37/// Accessed with `Segment.NameAdapter` using `[]const u8` keys.
38segments_by_name: std.array_hash_map.Custom(void, void, void, true),
39/// Accessed with `Section.NameAdapter` using `Section.NameAdapter.SegmentAndSection`.
40sections_by_name: std.array_hash_map.Custom(void, void, void, true),
41
42/// Accessed with `String.Adapter` using `[]const u8` keys.
43strtab: std.array_hash_map.Custom(String, void, void, true),
44
45symtab: std.ArrayList(Symbol),
46
47sections_changed_index: std.array_hash_map.Auto(Section, void),
48
49const Error = link.Error || error{MappedFileIo};
50
51/// Stable reference to a particular load command (or `std.macho.section_64`, despite those not
52/// technically being load commands). Underlying value is an index into `MachO.lc.offsets`.
53const LoadCommand = enum(u32) {
54 _,
55 fn offset(lc: LoadCommand, macho: *const MachO) u32 {
56 return macho.lc.offsets.items[@backingInt(lc)];
57 }
58 fn ptrConst(lc: LoadCommand, macho: *const MachO, comptime Cmd: type) *align(8) const Cmd {
59 return @ptrCast(@alignCast(
60 macho.node.header.sliceConst(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)],
61 ));
62 }
63 fn ptr(lc: LoadCommand, macho: *MachO, comptime Cmd: type) *align(8) Cmd {
64 return @ptrCast(@alignCast(
65 macho.node.header.slice(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)],
66 ));
67 }
68};
69
70/// Stable reference to a segment defined by a `std.macho.segment_command_64` load command.
71///
72/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to
73/// refer to sections, so the limitation exists regardless.
74const Segment = enum(u8) {
75 _,
76
77 fn lc(seg: Segment, macho: *const MachO) LoadCommand {
78 return macho.segments.items(.lc)[@backingInt(seg)];
79 }
80 fn lcPtrConst(seg: Segment, macho: *const MachO) *const std.macho.segment_command_64 {
81 return seg.lc(macho).ptrConst(macho, std.macho.segment_command_64);
82 }
83 fn lcPtr(seg: Segment, macho: *MachO) *std.macho.segment_command_64 {
84 return seg.lc(macho).ptr(macho, std.macho.segment_command_64);
85 }
86
87 fn name(seg: Segment, macho: *const MachO) []const u8 {
88 return std.mem.sliceTo(&seg.lcPtrConst(macho).segname, 0);
89 }
90
91 fn vaddr(seg: Segment, macho: *const MachO) u64 {
92 return macho.targetLoad(&seg.lcPtrConst(macho).vmaddr);
93 }
94
95 fn node(seg: Segment, macho: *const MachO) MappedFile.Node.Index {
96 return macho.segments.items(.node)[@backingInt(seg)];
97 }
98
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 };
110};
111
112/// Stable reference to a section defined by a `std.macho.section_64` in the load commands.
113///
114/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to
115/// refer to sections, so the limitation exists regardless.
116///
117/// However, the backing integer of a `Section` is *not* the same as the section index, because
118/// section indices can change. For the index of a section, see `Section.index`.
119const Section = enum(u8) {
120 _,
121
122 fn lc(sect: Section, macho: *const MachO) LoadCommand {
123 return macho.sections.items(.lc)[@backingInt(sect)];
124 }
125 fn lcPtrConst(sect: Section, macho: *const MachO) *const std.macho.section_64 {
126 return sect.lc(macho).ptrConst(macho, std.macho.section_64);
127 }
128 fn lcPtr(sect: Section, macho: *MachO) *std.macho.section_64 {
129 return sect.lc(macho).ptr(macho, std.macho.section_64);
130 }
131
132 fn name(sect: Section, macho: *const MachO) []const u8 {
133 return std.mem.sliceTo(&sect.lcPtrConst(macho).sectname, 0);
134 }
135
136 fn segmentName(sect: Section, macho: *const MachO) []const u8 {
137 return std.mem.sliceTo(&sect.lcPtrConst(macho).segname, 0);
138 }
139
140 fn node(sect: Section, macho: *const MachO) MappedFile.Node.Index {
141 return macho.sections.items(.node)[@backingInt(sect)];
142 }
143
144 /// Returns this section's index in the Mach-O file. Section indices are implicitly assigned
145 /// based on the order of load commands, so this value may change in response to segments or
146 /// sections being added. When this value changes for a given `Section`, it is added to
147 /// `MachO.sections_changed_index`, so that references to the section index can be updated.
148 fn index(sect: Section, macho: *const MachO) u8 {
149 return macho.sections.items(.index)[@backingInt(sect)];
150 }
151
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
157 const NameAdapter = struct {
158 macho: *const MachO,
159 const SegmentAndSection = struct {
160 segment: []const u8,
161 section: []const u8,
162 };
163 pub fn eql(ctx: NameAdapter, a_name: SegmentAndSection, _: void, b_sect_raw: usize) bool {
164 const b_sect: Section = @fromBackingInt(@intCast(b_sect_raw));
165 return std.mem.eql(u8, a_name.segment, b_sect.segmentName(ctx.macho)) and
166 std.mem.eql(u8, a_name.section, b_sect.name(ctx.macho));
167 }
168 pub fn hash(ctx: NameAdapter, a_name: SegmentAndSection) u32 {
169 _ = ctx;
170 var h: std.hash.Wyhash = .init(a_name.section.len);
171 h.update(a_name.section);
172 h.update(a_name.segment);
173 return @truncate(h.final());
174 }
175 };
176};
177
178const Symbol = struct {
179 next_in_section: Symbol.Index.Optional,
180
181 const Index = enum(u32) {
182 _,
183 fn ptr(i: Index, macho: *MachO) *Symbol {
184 return &macho.symtab.items[@backingInt(i)];
185 }
186 fn nlist(i: Index, macho: *MachO) *std.macho.nlist_64 {
187 return &macho.nlistSlice()[@backingInt(i)];
188 }
189 const Optional = enum(u32) {
190 none = std.math.maxInt(u32),
191 _,
192 fn unwrap(o: Optional) ?Index {
193 return switch (o) {
194 .none => null,
195 _ => @fromBackingInt(@backingInt(o)),
196 };
197 }
198 fn wrap(i: Index) Optional {
199 return @bitCast(i);
200 }
201 };
202 };
203};
204
12const Node = union(enum) {205const Node = union(enum) {
13 macho,206 macho,
207 /// Contains the `mach_header_64` structure and all load commands. Inside of the `.segment` node for the `__TEXT` segment.
208 macho_header,
209
210 symtab,
211 strtab,
212
213 segment: Segment,
214 section: Section,
14};215};
15fn getNode(macho: *MachO, ni: MappedFile.Node.Index) Node {216fn getNode(macho: *const MachO, ni: MappedFile.Node.Index) Node {
16 return macho.nodes.get(@backingInt(ni));217 return macho.nodes.get(@backingInt(ni));
17}218}
18219
19pub fn open(220pub fn open(
20 arena: std.mem.Allocator,221 arena: Allocator,
21 comp: *Compilation,222 comp: *Compilation,
22 path: std.Build.Cache.Path,223 path: std.Build.Cache.Path,
23 options: link.File.OpenOptions,224 options: link.File.OpenOptions,
...@@ -25,7 +226,7 @@ pub fn open(...@@ -25,7 +226,7 @@ pub fn open(
25 return create(arena, comp, path, options);226 return create(arena, comp, path, options);
26}227}
27pub fn createEmpty(228pub fn createEmpty(
28 arena: std.mem.Allocator,229 arena: Allocator,
29 comp: *Compilation,230 comp: *Compilation,
30 path: std.Build.Cache.Path,231 path: std.Build.Cache.Path,
31 options: link.File.OpenOptions,232 options: link.File.OpenOptions,
...@@ -33,7 +234,7 @@ pub fn createEmpty(...@@ -33,7 +234,7 @@ pub fn createEmpty(
33 return create(arena, comp, path, options);234 return create(arena, comp, path, options);
34}235}
35fn create(236fn create(
36 arena: std.mem.Allocator,237 arena: Allocator,
37 comp: *Compilation,238 comp: *Compilation,
38 path: std.Build.Cache.Path,239 path: std.Build.Cache.Path,
39 options: link.File.OpenOptions,240 options: link.File.OpenOptions,
...@@ -61,11 +262,30 @@ fn create(...@@ -61,11 +262,30 @@ fn create(
61 .stack_size = 0,262 .stack_size = 0,
62 },263 },
63 .options = options,264 .options = options,
64 .mf = try .init(file, comp.gpa, io),265
65 .nodes = .empty,
66 .const_prog_node = .none,266 .const_prog_node = .none,
67 .synth_prog_node = .none,267 .synth_prog_node = .none,
68 .input_prog_node = .none,268 .input_prog_node = .none,
269
270 .mf = try .init(file, comp.gpa, io),
271 .nodes = .empty,
272
273 .node = undefined,
274 .lc = .{
275 .offsets = .empty,
276 .pagezero = undefined,
277 .symtab = undefined,
278 .entry_point = null,
279 },
280 .segments = .empty,
281 .sections = .empty,
282 .segments_by_name = .empty,
283 .sections_by_name = .empty,
284
285 .strtab = .empty,
286 .symtab = .empty,
287
288 .sections_changed_index = .empty,
69 };289 };
70 errdefer macho.deinit();290 errdefer macho.deinit();
71291
...@@ -77,15 +297,679 @@ fn initInner(macho: *MachO) !void {...@@ -77,15 +297,679 @@ fn initInner(macho: *MachO) !void {
77 const comp = macho.base.comp;297 const comp = macho.base.comp;
78 const gpa = comp.gpa;298 const gpa = comp.gpa;
79299
80 try macho.nodes.append(gpa, .macho); // file root node300 macho.node.macho = .root;
301 try macho.nodes.append(gpa, .macho);
81302
82 @panic("TODO");303 const arch: enum { aarch64, x86_64 } = switch (comp.getTarget().cpu.arch) {
304 .aarch64 => .aarch64,
305 .x86_64 => .x86_64,
306 else => |arch| std.debug.panic("TODO: unsupported target arch {t}", .{arch}),
307 };
308
309 const mach_header_64 = std.macho.mach_header_64;
310
311 // The `__TEXT` segment is special because it contains the mach header and load commands, so we
312 // must create the text segment node before we can actually call `addSegment`.
313 const text_seg_node = try macho.node.macho.addOnlyHeaderChild(gpa, &macho.mf, .{
314 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
315 .moved = true, // required by `addSegment`
316 .bubbles_moved = false,
317 });
318 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
319
320 macho.node.header = try text_seg_node.addOnlyHeaderChild(gpa, &macho.mf, .{
321 .size = @sizeOf(mach_header_64),
322 .alignment = .@"8",
323 });
324 try macho.nodes.append(gpa, .macho_header);
325
326 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
327 macho.node.header.slice(&macho.mf)[0..@sizeOf(mach_header_64)],
328 ));
329 mach_header.* = .{
330 .cputype = switch (arch) {
331 .aarch64 => std.macho.CPU_TYPE_ARM64,
332 .x86_64 => std.macho.CPU_TYPE_X86_64,
333 },
334 .cpusubtype = switch (arch) {
335 .aarch64 => std.macho.CPU_SUBTYPE_ARM_ALL,
336 .x86_64 => std.macho.CPU_SUBTYPE_X86_64_ALL,
337 },
338 .filetype = switch (comp.config.output_mode) {
339 .Exe => std.macho.MH_EXECUTE,
340 .Lib => switch (comp.config.link_mode) {
341 .static => std.macho.MH_OBJECT,
342 .dynamic => std.macho.MH_DYLIB,
343 },
344 .Obj => std.macho.MH_OBJECT,
345 },
346 .ncmds = 0,
347 .sizeofcmds = 0,
348 .flags = std.macho.MH_DYLDLINK | std.macho.MH_PIE,
349 };
350 if (macho.targetEndian() != std.lang.Endian.native) {
351 std.mem.byteSwapAllFields(mach_header_64, mach_header);
352 }
353
354 // The "__LINKEDIT" segment is special in that it needs to be at the end of the Mach-O file, so
355 // like "__TEXT", we'll construct its node here instead of in `addSegment`.
356 const linkedit_seg_node = try macho.node.macho.addFooterChildBefore(gpa, &macho.mf, .none, .{
357 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
358 .moved = true, // required by `addSegment`
359 .bubbles_moved = false,
360 });
361 try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment`
362
363 // The "__PAGEZERO" segment must come before any other segment load commands. It is inaccessible
364 // and occupies 4 GiB (apparently to catch invalid 32-bit pointer accesses).
365 macho.lc.pagezero = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
366 .cmdsize = @sizeOf(std.macho.segment_command_64),
367 .segname = "__PAGEZERO\x00\x00\x00\x00\x00\x00".*,
368 .vmaddr = 0,
369 .vmsize = 0x1_0000_0000,
370 .fileoff = 0,
371 .filesize = 0,
372 .maxprot = .{},
373 .initprot = .{},
374 .nsects = 0,
375 .flags = 0,
376 });
377 const text_seg = try macho.addSegment(.{
378 .name = "__TEXT",
379 .prot = .{ .READ = true, .EXEC = true },
380 .existing_node = text_seg_node,
381 });
382 const data_seg = try macho.addSegment(.{
383 .name = "__DATA",
384 .prot = .{ .READ = true, .WRITE = true },
385 });
386 const linkedit_seg = try macho.addSegment(.{
387 .name = "__LINKEDIT",
388 .prot = .{ .READ = true },
389 .existing_node = linkedit_seg_node,
390 });
391
392 _ = try macho.addSection(.{
393 .segment = text_seg,
394 .name = "__text",
395 .@"align" = .@"4",
396 .flags = .{ .some_instructions = true },
397 });
398 _ = try macho.addSection(.{
399 .segment = text_seg,
400 .name = "__const",
401 .@"align" = .@"2",
402 .flags = .{},
403 });
404 _ = try macho.addSection(.{
405 .segment = data_seg,
406 .name = "__data",
407 .@"align" = .@"2",
408 .flags = .{},
409 });
410
411 macho.node.symtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
412 .alignment = macho.mf.flags.block_size.max(.of(std.macho.nlist_64)),
413 .moved = true, // so that `symoff` in the symtab load command will be set
414 });
415 try macho.nodes.append(gpa, .symtab);
416
417 macho.node.strtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
418 .alignment = macho.mf.flags.block_size,
419 .moved = true, // so that `stroff` in the symtab load command will be set
420 });
421 try macho.nodes.append(gpa, .strtab);
422
423 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, 1);
424 macho.node.strtab.slice(&macho.mf)[0] = 0;
425
426 _ = try macho.appendSimpleLoadCommand(std.macho.dyld_info_command, .{});
427
428 macho.lc.symtab = try macho.appendSimpleLoadCommand(std.macho.symtab_command, .{
429 .symoff = 0,
430 .nsyms = 0,
431 .stroff = 0,
432 .strsize = 1, // empty string (null terminator)
433 });
434
435 const want_dyld_path: bool = switch (comp.config.output_mode) {
436 .Obj => false,
437 .Lib => switch (comp.config.link_mode) {
438 .static => false,
439 .dynamic => comp.root_mod.resolved_target.is_explicit_dynamic_linker,
440 },
441 .Exe => true,
442 };
443 if (want_dyld_path) {
444 if (comp.getTarget().dynamic_linker.get()) |dyld_path| {
445 const cmd = try macho.appendLoadCommand(std.macho.dylinker_command, .{
446 .cmd = .LOAD_DYLINKER,
447 .cmdsize = std.mem.alignForward(u32, @intCast(
448 @sizeOf(std.macho.dylinker_command) + dyld_path.len + 1,
449 ), 8),
450 .name = @sizeOf(std.macho.dylinker_command),
451 });
452 @memcpy(cmd.trailing[0..dyld_path.len], dyld_path);
453 @memset(cmd.trailing[dyld_path.len..], 0);
454 }
455 }
456
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 }),
463 }
464
465 _ = try macho.appendSimpleLoadCommand(std.macho.uuid_command, .{
466 .uuid = @splat(0),
467 });
468
469 try macho.addSimpleSymbol("some_function", 0x1234, "__TEXT", "__text");
470 try macho.addSimpleSymbol("my_cool_data", 0x5678, "__DATA", "__data");
471
472 try macho.updateSegmentLoadAddresses();
473}
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,
478 name: []const u8,
479 value: u64,
480 segment_name: []const u8,
481 section_name: []const u8,
482) Error!void {
483 const gpa = macho.base.comp.gpa;
484 try macho.symtab.ensureUnusedCapacity(gpa, 1);
485 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
486
487 const strx = try macho.string(name);
488 const section = macho.sectionByName(segment_name, section_name).?;
489
490 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
491 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
492 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
493
494 const sym_index: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
495 macho.symtab.appendAssumeCapacity(.{
496 .next_in_section = section.firstSymbol(macho).*,
497 });
498 section.firstSymbol(macho).* = .wrap(sym_index);
499
500 sym_index.nlist(macho).* = .{
501 .n_strx = @backingInt(strx),
502 .n_type = .{ .bits = .{
503 .ext = true,
504 .type = .sect,
505 .pext = false,
506 .is_stab = 0,
507 } },
508 .n_sect = section.index(macho),
509 .n_desc = .{
510 .arm_thumb_def = false,
511 .referenced_dynamically = false,
512 .discarded_or_no_dead_strip = false,
513 .weak_ref = false,
514 .weak_def_or_ref_to_weak = false,
515 .symbol_resolver = false,
516 .alt_entry = false,
517 },
518 .n_value = value,
519 };
520 if (macho.targetEndian() != std.lang.Endian.native) {
521 std.mem.byteSwapAllFields(std.macho.nlist_64, sym_index.nlist(macho));
522 }
523}
524
525fn addSegment(macho: *MachO, opts: struct {
526 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 {
533 const gpa = macho.base.comp.gpa;
534
535 const segment_command_64 = std.macho.segment_command_64;
536
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", .{});
541 }
542
543 try macho.nodes.ensureUnusedCapacity(gpa, 1);
544 try macho.segments.ensureUnusedCapacity(gpa, 1);
545 try macho.segments_by_name.ensureUnusedCapacity(gpa, 1);
546
547 const root_ni: MappedFile.Node.Index = .root;
548
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 });
566
567 const segment: Segment = @fromBackingInt(@intCast(macho.segments.len));
568 const segment_node: MappedFile.Node.Index = node: {
569 if (opts.existing_node) |node| {
570 assert(node.hasMoved(&macho.mf)); // so that `fileoff` will be set
571 assert(macho.nodes.items(.tags)[@backingInt(node)] == .segment);
572 macho.nodes.set(@backingInt(node), .{ .segment = segment });
573 break :node node;
574 }
575 const node = try root_ni.addFloatingChild(gpa, &macho.mf, .{
576 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
577 .moved = true, // so that `fileoff` will be set
578 .bubbles_moved = false,
579 });
580 macho.nodes.appendAssumeCapacity(.{ .segment = segment });
581 break :node node;
582 };
583 macho.segments.appendAssumeCapacity(.{
584 .lc = lc,
585 .node = segment_node,
586 });
587
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
597 return segment;
598}
599
600const String = enum(u32) {
601 empty = 0,
602 _,
603 fn slice(s: String, macho: *const MachO) [:0]const u8 {
604 const overlong = macho.node.strtab.slice(&macho.mf)[@backingInt(s)..];
605 return overlong[0..std.mem.findScalar(u8, overlong, 0).? :0];
606 }
607
608 const Adapter = struct {
609 macho: *const MachO,
610 pub fn eql(ctx: Adapter, lhs_slice: []const u8, rhs: String, _: usize) bool {
611 const macho = ctx.macho;
612 return std.mem.eql(u8, lhs_slice, rhs.slice(macho));
613 }
614 pub fn hash(ctx: Adapter, s: []const u8) u32 {
615 _ = ctx;
616 return std.array_hash_map.hashString(s);
617 }
618 };
619};
620
621fn string(macho: *MachO, slice: []const u8) Error!String {
622 const gpa = macho.base.comp.gpa;
623
624 const old_strsize = macho.targetLoad(
625 &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize,
626 );
627 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, old_strsize + slice.len + 1);
628
629 try macho.strtab.ensureUnusedCapacity(gpa, 1);
630
631 errdefer comptime unreachable;
632
633 const adapter: String.Adapter = .{ .macho = macho };
634 const gop = macho.strtab.getOrPutAssumeCapacityAdapted(slice, adapter);
635 if (!gop.found_existing) {
636 macho.targetStore(
637 &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize,
638 @intCast(old_strsize + slice.len + 1),
639 );
640 gop.key_ptr.* = @fromBackingInt(old_strsize);
641 const dest_slice = macho.node.strtab.slice(&macho.mf)[old_strsize..];
642 @memcpy(dest_slice[0..slice.len], slice);
643 dest_slice[slice.len] = 0;
644 }
645 return gop.key_ptr.*;
646}
647
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}
656
657fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void {
658 // TODO: this segment load address allocation logic is bad because it moves segments quite
659 // frequently. We should avoid that by introducing padding between segments so that they have
660 // space to grow in-place. We could also consider allowing a segment which can't fit to jump
661 // over another segment, like in `Elf2.allocateSegmentLoadAddress`.
662 var cur_addr: u64 = start_addr: {
663 const pagezero_ptr = macho.lc.pagezero.ptr(macho, std.macho.segment_command_64);
664 assert(macho.targetLoad(&pagezero_ptr.vmaddr) == 0);
665 break :start_addr macho.targetLoad(&pagezero_ptr.vmsize);
666 };
667 for (0..macho.segments.len) |segment_raw| {
668 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
669
670 _, const size: u64 = segment.node(macho).location(&macho.mf).resolve(&macho.mf);
671 const cmd_ptr = segment.lcPtr(macho);
672
673 macho.targetStore(&cmd_ptr.vmsize, size);
674
675 const old_addr = macho.targetLoad(&cmd_ptr.vmaddr);
676 if (old_addr >= cur_addr) {
677 cur_addr = old_addr;
678 } else {
679 macho.targetStore(&cmd_ptr.vmaddr, cur_addr);
680 try segment.node(macho).childrenMoved(macho.base.comp.gpa, &macho.mf);
681 }
682
683 cur_addr = macho.targetPageAlign().forward(cur_addr + size);
684 }
685}
686
687/// Like `appendLoadCommand`, but asserts that `cmd.cmdsize` exactly equals `@sizeOf(Cmd)` and does
688/// not return the (empty) trailing slice.
689fn appendSimpleLoadCommand(
690 macho: *MachO,
691 comptime Cmd: type,
692 cmd: Cmd,
693) Error!LoadCommand {
694 comptime assert(@sizeOf(Cmd) % 8 == 0);
695 assert(cmd.cmdsize == @sizeOf(Cmd));
696 return (try macho.appendLoadCommand(Cmd, cmd)).lc;
697}
698
699/// Asserts that the first two fields of `Cmd` are `cmd: LC` and `cmdsize: u32`, and that the value
700/// of `lc.cmdsize` is 8-byte aligned.
701fn appendLoadCommand(
702 macho: *MachO,
703 comptime Cmd: type,
704 cmd: Cmd,
705) Error!struct {
706 lc: LoadCommand,
707 trailing: []u8,
708} {
709 const gpa = macho.base.comp.gpa;
710
711 try macho.lc.offsets.ensureUnusedCapacity(gpa, 1);
712
713 comptime {
714 const s = @typeInfo(Cmd).@"struct";
715 assert(s.layout == .@"extern");
716 // First field of load command should be `cmd: LC`
717 assert(std.mem.eql(u8, s.field_names[0], "cmd"));
718 assert(s.field_types[0] == std.macho.LC);
719 // Second field of load command should be `cmdsize: u32`
720 assert(std.mem.eql(u8, s.field_names[1], "cmdsize"));
721 assert(s.field_types[1] == u32);
722 }
723
724 const cmd_size = @as(*const std.macho.load_command, @ptrCast(&cmd)).cmdsize;
725 assert(cmd_size % 8 == 0);
726
727 const mach_header_64 = std.macho.mach_header_64;
728 const old_cmds_size: u32 = size: {
729 const mach_header: *const mach_header_64 = @ptrCast(@alignCast(
730 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)],
731 ));
732 break :size macho.targetLoad(&mach_header.sizeofcmds);
733 };
734 const new_cmds_size = old_cmds_size + cmd_size;
735 try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size);
736
737 const header_slice = macho.node.header.slice(&macho.mf);
738 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
739 header_slice[0..@sizeOf(mach_header_64)],
740 ));
741 macho.targetStore(&mach_header.sizeofcmds, new_cmds_size);
742 macho.targetStore(&mach_header.ncmds, macho.targetLoad(&mach_header.ncmds) + 1);
743
744 const cmd_slice: []align(8) u8 = @alignCast(
745 header_slice[@sizeOf(mach_header_64) + old_cmds_size ..][0..cmd_size],
746 );
747
748 const cmd_ptr: *Cmd = @ptrCast(cmd_slice[0..@sizeOf(Cmd)]);
749 cmd_ptr.* = cmd;
750 if (macho.targetEndian() != std.lang.Endian.native) {
751 std.mem.byteSwapAllFields(Cmd, cmd_ptr);
752 }
753
754 const lc: LoadCommand = @fromBackingInt(@intCast(macho.lc.offsets.items.len));
755 macho.lc.offsets.appendAssumeCapacity(@sizeOf(mach_header_64) + old_cmds_size);
756
757 return .{ .lc = lc, .trailing = cmd_slice[@sizeOf(Cmd)..] };
758}
759
760fn addSection(macho: *MachO, opts: struct {
761 name: []const u8,
762 segment: Segment,
763 @"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 },
770}) Error!Section {
771 const gpa = macho.base.comp.gpa;
772
773 const mach_header_64 = std.macho.mach_header_64;
774 const segment_command_64 = std.macho.segment_command_64;
775 const section_64 = std.macho.section_64;
776
777 // Check if we can actually fit another section. The limit is 255 because Mach-O implicitly
778 // assigns 8-bit indices to sections starting from 1 (0 is reserved).
779 if (macho.sections.len == 255) {
780 return macho.base.comp.link_diags.fail("maximum section count exceeded", .{});
781 }
782
783 const old_cmds_size: u32 = size: {
784 const mach_header: *const mach_header_64 = @ptrCast(@alignCast(
785 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)],
786 ));
787 break :size macho.targetLoad(&mach_header.sizeofcmds);
788 };
789 const new_cmds_size = old_cmds_size + @sizeOf(section_64);
790 try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size);
791
792 try macho.nodes.ensureUnusedCapacity(gpa, 1);
793 try macho.lc.offsets.ensureUnusedCapacity(gpa, 1);
794 try macho.sections.ensureUnusedCapacity(gpa, 1);
795 try macho.sections_by_name.ensureUnusedCapacity(gpa, 1);
796
797 const section: Section = @fromBackingInt(@intCast(macho.sections.len));
798 const section_node = try opts.segment.node(macho).addFloatingChild(gpa, &macho.mf, .{
799 .alignment = opts.@"align",
800 .moved = true, // so that `offset` will be set
801 });
802 macho.nodes.appendAssumeCapacity(.{ .section = section });
803 macho.sections.appendAssumeCapacity(.{
804 .lc = @fromBackingInt(@intCast(macho.lc.offsets.items.len)),
805 .node = section_node,
806 .first_symbol = .none,
807 .index = undefined, // populated later
808 });
809
810 {
811 const adapter: Section.NameAdapter = .{ .macho = macho };
812 const key: Section.NameAdapter.SegmentAndSection = .{
813 .segment = opts.segment.name(macho),
814 .section = opts.name,
815 };
816 const gop = macho.sections_by_name.getOrPutAssumeCapacityAdapted(key, adapter);
817 assert(!gop.found_existing);
818 assert(gop.index == @backingInt(section));
819 }
820
821 const header_slice = macho.node.header.slice(&macho.mf);
822
823 {
824 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
825 header_slice[0..@sizeOf(mach_header_64)],
826 ));
827 macho.targetStore(&mach_header.sizeofcmds, new_cmds_size);
828 }
829
830 const seg_cmd = opts.segment.lcPtr(macho);
831 const old_nsects = macho.targetLoad(&seg_cmd.nsects);
832 const old_cmdsize = macho.targetLoad(&seg_cmd.cmdsize);
833 assert(old_cmdsize == @sizeOf(segment_command_64) + old_nsects * @sizeOf(section_64));
834 const new_cmdsize = old_cmdsize + @sizeOf(section_64);
835 macho.targetStore(&seg_cmd.cmdsize, new_cmdsize);
836 macho.targetStore(&seg_cmd.nsects, old_nsects + 1);
837
838 const seg_cmd_off = opts.segment.lc(macho).offset(macho);
839 const offset = seg_cmd_off + old_cmdsize;
840
841 // Shift forward every load command which comes after this segment command, to make space
842 // for the new `section_64` (and update the offsets in `macho.lc.offsets` accordingly).
843 @memmove(
844 header_slice[seg_cmd_off + new_cmdsize .. @sizeOf(mach_header_64) + new_cmds_size],
845 header_slice[seg_cmd_off + old_cmdsize .. @sizeOf(mach_header_64) + old_cmds_size],
846 );
847 for (macho.lc.offsets.items) |*other_off| {
848 if (other_off.* >= offset) {
849 other_off.* += @sizeOf(section_64);
850 }
851 }
852 // Only now do we append our *own* offset (otherwise the above loop would have changed it!).
853 macho.lc.offsets.appendAssumeCapacity(offset);
854
855 // Increment the index of every section after where we've inserted ourselves.
856 {
857 var new_section_index: u8 = 1;
858 try macho.sections_changed_index.ensureUnusedCapacity(gpa, macho.sections.len);
859 for (0..macho.sections.len) |other_section_raw| {
860 const other_section: Section = @fromBackingInt(@intCast(other_section_raw));
861 if (other_section == section) continue;
862 if (other_section.lc(macho).offset(macho) > offset) {
863 macho.sections.items(.index)[@backingInt(other_section)] += 1;
864 if (other_section.firstSymbol(macho).* != .none) {
865 macho.sections_changed_index.putAssumeCapacity(other_section, {});
866 }
867 } else {
868 new_section_index += 1;
869 }
870 }
871 macho.sections.items(.index)[@backingInt(section)] = new_section_index;
872 }
873
874 // Write the new `section_64`.
875 const section_cmd: *section_64 = @ptrCast(@alignCast(
876 header_slice[offset..][0..@sizeOf(section_64)],
877 ));
878 section_cmd.* = .{
879 .sectname = name: {
880 var name: [16]u8 = undefined;
881 @memcpy(name[0..opts.name.len], opts.name);
882 @memset(name[opts.name.len..], 0);
883 break :name name;
884 },
885 .segname = seg_cmd.segname,
886 .addr = 0,
887 .size = 0,
888 .offset = 0,
889 .@"align" = opts.@"align".toLog2Units(),
890 .reloff = 0,
891 .nreloc = 0,
892 .flags = @bitCast(opts.flags),
893 };
894 if (macho.targetEndian() != std.lang.Endian.native) {
895 std.mem.byteSwapAllFields(section_64, section_cmd);
896 }
897
898 return section;
899}
900
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
910fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []const u8) ?Section {
911 const adapter: Section.NameAdapter = .{ .macho = macho };
912 const key: Section.NameAdapter.SegmentAndSection = .{
913 .segment = segment_name,
914 .section = section_name,
915 };
916 if (macho.sections_by_name.getIndexAdapted(key, adapter)) |section_raw| {
917 return @fromBackingInt(@intCast(section_raw));
918 } else {
919 return null;
920 }
921}
922
923fn targetEndian(macho: *const MachO) std.lang.Endian {
924 _ = macho;
925 return .little;
926}
927fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
928 _ = macho;
929 return .fromByteUnits(0x4000);
930}
931fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
932 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
933 const Child = pointer_ty.child;
934 const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child);
935 return switch (@typeInfo(Child)) {
936 else => @compileError(@typeName(Child)),
937 .int => std.mem.toNative(Child, ptr.*, macho.targetEndian()),
938 .@"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))),
941 ),
942 };
943}
944fn targetStore(macho: *const MachO, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).pointer.child) void {
945 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
946 const Child = pointer_ty.child;
947 const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child);
948 return switch (@typeInfo(Child)) {
949 else => @compileError(@typeName(Child)),
950 .int => ptr.* = std.mem.nativeTo(Child, val, macho.targetEndian()),
951 .@"enum" => |@"enum"| macho.targetStore(
952 @as(*align(alignment) @"enum".tag_type, @ptrCast(ptr)),
953 @backingInt(val),
954 ),
955 .@"struct" => |@"struct"| macho.targetStore(
956 @as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr)),
957 @bitCast(val),
958 ),
959 };
83}960}
84961
85pub fn deinit(macho: *MachO) void {962pub fn deinit(macho: *MachO) void {
86 const gpa = macho.base.comp.gpa;963 const gpa = macho.base.comp.gpa;
87 macho.mf.deinit(gpa);964 macho.mf.deinit(gpa);
88 macho.nodes.deinit(gpa);965 macho.nodes.deinit(gpa);
966 macho.segments.deinit(gpa);
967 macho.sections.deinit(gpa);
968 macho.segments_by_name.deinit(gpa);
969 macho.sections_by_name.deinit(gpa);
970 macho.strtab.deinit(gpa);
971 macho.symtab.deinit(gpa);
972 macho.sections_changed_index.deinit(gpa);
89 macho.* = undefined;973 macho.* = undefined;
90}974}
91975
...@@ -134,30 +1018,110 @@ pub fn relocSymAddr(...@@ -134,30 +1018,110 @@ pub fn relocSymAddr(
134}1018}
1351019
136pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {1020pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
137 _ = macho;1021 const diags = &macho.base.comp.link_diags;
138 _ = input;1022 macho.loadInputInner(input) catch |err| switch (err) {
139 @panic("TODO");1023 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
1024 else => |e| return e,
1025 };
140}1026}
141pub fn setDarwinSdkVersion(macho: *MachO, version: link.DarwinSdkVersion) link.Error!void {1027fn loadInputInner(macho: *MachO, input: link.Input) Error!void {
142 _ = macho;1028 switch (input) {
143 std.log.err("MLUGG TODO: sdk version is '{d}.{d}.{d}'", .{ version.major, version.minor, version.patch });1029 .res => unreachable,
1030 .object => @panic("TODO(MachO2): load object"),
1031 .archive => @panic("TODO(MachO2): load archive"),
1032 .dso => @panic("TODO(MachO2): load dso"),
1033 .tbd => |tbd| {
1034 log.debug("load tbd {qf}", .{tbd.path.fmtEscapeString()});
1035
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";
1041
1042 const cmd = try macho.appendLoadCommand(std.macho.dylib_command, .{
1043 .cmd = .LOAD_DYLIB,
1044 .cmdsize = std.mem.alignForward(u32, @intCast(
1045 @sizeOf(std.macho.dylib_command) + prefix.len + name.len + suffix.len + 1,
1046 ), 8),
1047 .dylib = .{
1048 .name = @sizeOf(std.macho.dylib_command),
1049 .timestamp = 0,
1050 .current_version = 0,
1051 .compatibility_version = 0,
1052 },
1053 });
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);
1058 },
1059 }
1060}
1061pub fn setDarwinSdkVersion(macho: *MachO, sdk_version: link.DarwinSdkVersion) link.Error!void {
1062 const comp = macho.base.comp;
1063 const diags = &comp.link_diags;
1064 const target = comp.getTarget();
1065
1066 const os_min_version: link.DarwinSdkVersion = .{
1067 .major = @intCast(target.os.version_range.semver.min.major),
1068 .minor = @intCast(target.os.version_range.semver.min.minor),
1069 .patch = @intCast(target.os.version_range.semver.min.patch),
1070 };
1071
1072 _ = macho.appendSimpleLoadCommand(std.macho.version_min_command, .{
1073 .cmd = switch (target.os.tag) {
1074 .macos => .VERSION_MIN_MACOSX,
1075 .ios => .VERSION_MIN_IPHONEOS,
1076 .tvos => .VERSION_MIN_TVOS,
1077 .watchos => .VERSION_MIN_WATCHOS,
1078 .maccatalyst, .driverkit, .visionos => |os_tag| std.debug.panic(
1079 "TODO(MachO2): sdk version for '{t}'",
1080 .{os_tag},
1081 ),
1082 else => unreachable,
1083 },
1084 .version = @backingInt(os_min_version),
1085 .sdk = @backingInt(sdk_version),
1086 }) catch |err| switch (err) {
1087 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
1088 else => |e| return e,
1089 };
144}1090}
145pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {1091pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {
146 _ = macho;1092 _ = macho;
147 _ = prog_node;1093 _ = prog_node;
148 @panic("TODO");
149}1094}
150pub fn flush(1095pub fn flush(
151 macho: *MachO,1096 macho: *MachO,
152 arena: std.mem.Allocator,1097 arena: Allocator,
153 tid: Zcu.PerThread.Id,1098 tid: Zcu.PerThread.Id,
154 prog_node: std.Progress.Node,1099 prog_node: std.Progress.Node,
155) link.Error!void {1100) link.Error!void {
156 _ = macho;1101 const diags = &macho.base.comp.link_diags;
157 _ = arena;1102 _ = arena;
158 _ = tid;
159 _ = prog_node;1103 _ = prog_node;
160 @panic("TODO");1104
1105 try macho.updateSegmentLoadAddresses();
1106 while (try macho.idle(tid)) {}
1107
1108 if (macho.lc.entry_point) |entry_point_lc| {
1109 // TODO: actually find the entry point.
1110 // For now, we just set the entry point to the start of the "__TEXT,__text" section.
1111 const entry_node = macho.sectionByName("__TEXT", "__text").?.node(macho);
1112 const entry_file_off: u64, _ = entry_node.location(&macho.mf).resolve(&macho.mf);
1113 const entry_point_ptr = entry_point_lc.ptr(macho, std.macho.entry_point_command);
1114 macho.targetStore(&entry_point_ptr.entryoff, @intCast(entry_file_off));
1115 }
1116
1117 macho.mf.flush() catch |err| switch (err) {
1118 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
1119 else => |e| return e,
1120 };
1121
1122 if (macho.options.enable_link_snapshots)
1123 macho.dumpStderr(tid) catch |err|
1124 return diags.fail("dumping link snapshot failed: {t}", .{err});
161}1125}
162pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {1126pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {
163 _ = macho;1127 _ = macho;
...@@ -165,9 +1129,99 @@ pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {...@@ -165,9 +1129,99 @@ pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {
165 @panic("TODO");1129 @panic("TODO");
166}1130}
167pub fn idle(macho: *MachO, tid: Zcu.PerThread.Id) link.Error!bool {1131pub fn idle(macho: *MachO, tid: Zcu.PerThread.Id) link.Error!bool {
168 _ = macho;
169 _ = tid;1132 _ = tid;
170 @panic("TODO");1133
1134 macho.mf.nodes_lock.lock();
1135 defer macho.mf.nodes_lock.unlock();
1136
1137 task: {
1138 if (macho.sections_changed_index.pop()) |kv| {
1139 const section = kv.key;
1140 var sym_it = section.firstSymbol(macho).*;
1141 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_section) {
1142 macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho));
1143 }
1144 break :task;
1145 }
1146
1147 while (macho.mf.updates.pop()) |ni| {
1148 const clean_moved = ni.cleanMoved(&macho.mf);
1149 const clean_resized = ni.cleanResized(&macho.mf);
1150 const clean_next_moved = ni.cleanNextMoved(&macho.mf);
1151 if (!clean_moved and !clean_resized and !clean_next_moved) continue;
1152
1153 const sub_prog_node = macho.mf.update_prog_node.start(@tagName(macho.getNode(ni)), 0);
1154 defer sub_prog_node.end();
1155
1156 if (clean_moved) try macho.flushMoved(ni);
1157 if (clean_resized) try macho.flushResized(ni);
1158
1159 break :task;
1160 }
1161 }
1162
1163 return macho.sections_changed_index.count() > 0 or
1164 macho.mf.updates.items.len > 0;
1165}
1166fn flushMoved(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
1167 macho.flushMachOFileOffset(ni);
1168 switch (macho.getNode(ni)) {
1169 .macho => unreachable,
1170 .macho_header => {},
1171 .segment => return, // segments don't bubble moved
1172 .section => |section| {
1173 const segment_vaddr: u64 = macho.getNode(ni.parent(&macho.mf).unwrap().?).segment.vaddr(macho);
1174 const section_offset = ni.location(&macho.mf).resolve(&macho.mf)[0];
1175 macho.targetStore(&section.lcPtr(macho).addr, segment_vaddr + section_offset);
1176 },
1177 .symtab => {},
1178 .strtab => {},
1179 }
1180 try ni.childrenMoved(macho.base.comp.gpa, &macho.mf);
1181}
1182fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
1183 const offset = ni.fileLocation(&macho.mf, false).offset;
1184 switch (macho.getNode(ni)) {
1185 .macho => unreachable,
1186 .macho_header => {
1187 assert(offset == 0);
1188 },
1189 .segment => |segment| {
1190 macho.targetStore(&segment.lcPtr(macho).fileoff, @intCast(offset));
1191 var child_oni = ni.first(&macho.mf);
1192 while (child_oni.unwrap()) |child_ni| : (child_oni = child_ni.next(&macho.mf)) {
1193 macho.flushMachOFileOffset(child_ni);
1194 }
1195 },
1196 .section => |section| {
1197 macho.targetStore(&section.lcPtr(macho).offset, @intCast(offset));
1198 },
1199 .symtab => {
1200 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1201 macho.targetStore(&symtab_cmd.symoff, @intCast(offset));
1202 },
1203 .strtab => {
1204 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1205 macho.targetStore(&symtab_cmd.stroff, @intCast(offset));
1206 },
1207 }
1208}
1209fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
1210 const size = ni.location(&macho.mf).resolve(&macho.mf)[1];
1211 switch (macho.getNode(ni)) {
1212 .macho => {},
1213 .macho_header => {},
1214 .segment => |segment| {
1215 macho.targetStore(&segment.lcPtr(macho).filesize, size);
1216 },
1217 .section => |section| {
1218 macho.targetStore(&section.lcPtr(macho).size, size);
1219 },
1220 .symtab, .strtab => {
1221 // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do
1222 // that when we actually add strings or symbols.
1223 },
1224 }
171}1225}
1721226
173pub fn updateFunc(1227pub fn updateFunc(
...@@ -199,6 +1253,18 @@ pub fn updateExports(...@@ -199,6 +1253,18 @@ pub fn updateExports(
199 @panic("TODO");1253 @panic("TODO");
200}1254}
2011255
1256fn dumpStderr(macho: *MachO, tid: Zcu.PerThread.Id) Io.File.Writer.Error!void {
1257 const comp = macho.base.comp;
1258 const io = comp.io;
1259 var buffer: [512]u8 = undefined;
1260 const stderr = try io.lockStderr(&buffer, null);
1261 defer io.unlockStderr();
1262 const w = &stderr.file_writer.interface;
1263 _ = macho.dump(w, tid) catch |err| switch (err) {
1264 error.WriteFailed => return stderr.file_writer.err.?,
1265 };
1266}
1267
202pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {1268pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
203 if (macho.options.enable_link_snapshots) {1269 if (macho.options.enable_link_snapshots) {
204 try macho.printNode(tid, w, .root, 0);1270 try macho.printNode(tid, w, .root, 0);
...@@ -207,7 +1273,7 @@ pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.Dump...@@ -207,7 +1273,7 @@ pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.Dump
207 return .disabled;1273 return .disabled;
208}1274}
209fn printNode(1275fn printNode(
210 macho: *MachO,1276 macho: *const MachO,
211 tid: Zcu.PerThread.Id,1277 tid: Zcu.PerThread.Id,
212 w: *Io.Writer,1278 w: *Io.Writer,
213 ni: MappedFile.Node.Index,1279 ni: MappedFile.Node.Index,
...@@ -217,6 +1283,11 @@ fn printNode(...@@ -217,6 +1283,11 @@ fn printNode(
217 try w.splatByteAll(' ', indent);1283 try w.splatByteAll(' ', indent);
218 switch (node) {1284 switch (node) {
219 .macho => try w.writeAll("macho"),1285 .macho => try w.writeAll("macho"),
1286 .macho_header => try w.writeAll("macho_header"),
1287 .section => |section| try w.print("section({q})", .{section.name(macho)}),
1288 .segment => |segment| try w.print("segment({q})", .{segment.name(macho)}),
1289 .symtab => try w.writeAll("symtab"),
1290 .strtab => try w.writeAll("strtab"),
220 }1291 }
221 {1292 {
222 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];1293 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];
...@@ -228,7 +1299,7 @@ fn printNode(...@@ -228,7 +1299,7 @@ fn printNode(
228 mf_node.flags.alignment.toByteUnits(),1299 mf_node.flags.alignment.toByteUnits(),
229 mf_node.flags.position,1300 mf_node.flags.position,
230 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",1301 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",
231 if (mf_node.flags.resized) " moved" else "",1302 if (mf_node.flags.moved) " moved" else "",
232 if (mf_node.flags.resized) " resized" else "",1303 if (mf_node.flags.resized) " resized" else "",
233 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",1304 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",
234 if (mf_node.flags.next_moved) " next_moved" else "",1305 if (mf_node.flags.next_moved) " next_moved" else "",
...@@ -244,31 +1315,38 @@ fn printNode(...@@ -244,31 +1315,38 @@ fn printNode(
244 }1315 }
245 return;1316 return;
246 }1317 }
247 const file_loc = ni.fileLocation(&macho.mf, false);1318 const start_address: usize, const end_address: usize = file_loc: {
248 var address = file_loc.offset;1319 const file_loc = ni.fileLocation(&macho.mf, false);
249 if (file_loc.size == 0) {1320 break :file_loc .{ @intCast(file_loc.offset), @intCast(file_loc.offset + file_loc.size) };
250 try w.splatByteAll(' ', indent + 1);1321 };
251 try w.print("{x:0>8}\n", .{address});1322 var address = start_address;
252 return;
253 }
254 const line_len = 0x10;1323 const line_len = 0x10;
255 var line_it = std.mem.window(1324 while (true) : (address = @min(std.mem.alignForward(usize, address + 1, line_len), end_address)) {
256 u8,
257 macho.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
258 line_len,
259 line_len,
260 );
261 while (line_it.next()) |line_bytes| : (address += line_len) {
262 try w.splatByteAll(' ', indent + 1);1325 try w.splatByteAll(' ', indent + 1);
263 try w.print("{x:0>8} ", .{address});1326 try w.print("{x:0>8}", .{address});
264 for (line_bytes) |byte| try w.print("{x:0>2} ", .{byte});1327 if (address == end_address) break try w.writeByte('\n');
265 try w.splatByteAll(' ', 3 * (line_len - line_bytes.len) + 1);1328 try w.splatByteAll(' ', 2);
266 for (line_bytes) |byte| try w.writeByte(if (std.ascii.isPrint(byte)) byte else '.');1329 const start_byte_address = std.mem.alignBackward(usize, address, line_len);
1330 const end_byte_address = start_byte_address + line_len;
1331 for (start_byte_address..end_byte_address) |byte_address|
1332 if (byte_address < start_address or byte_address >= end_address)
1333 try w.splatByteAll(' ', 3)
1334 else
1335 try w.print("{x:0>2} ", .{macho.mf.memory_map.memory[byte_address]});
1336 try w.writeByte(' ');
1337 for (start_byte_address..@min(end_address, end_byte_address)) |byte_address|
1338 try w.writeByte(if (byte_address < start_address or byte_address >= end_address) ' ' else char: {
1339 const byte = macho.mf.memory_map.memory[byte_address];
1340 break :char if (std.ascii.isPrint(byte)) byte else '.';
1341 });
267 try w.writeByte('\n');1342 try w.writeByte('\n');
268 }1343 }
269}1344}
2701345
271const std = @import("std");1346const std = @import("std");
1347const assert = std.debug.assert;
1348const log = std.log.scoped(.link);
1349const Allocator = std.mem.Allocator;
272const Io = std.Io;1350const Io = std.Io;
2731351
274const link = @import("../link.zig");1352const link = @import("../link.zig");