lib/tldr/src/formats/macho.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const root = @import("../root.zig");
3 const model = root.model;
4 const trace = root.trace;
5
6 const Allocator = std.mem.Allocator;
7
8 const header_size = @sizeOf(std.macho.mach_header_64);
9 const load_command_size = @sizeOf(std.macho.load_command);
10 const segment_command_64_size = @sizeOf(std.macho.segment_command_64);
11 const section_64_size = @sizeOf(std.macho.section_64);
12 const symtab_command_size = @sizeOf(std.macho.symtab_command);
13 const entry_point_command_size = @sizeOf(std.macho.entry_point_command);
14 const nlist_64_size = @sizeOf(std.macho.nlist_64);
15 const relocation_info_size = @sizeOf(std.macho.relocation_info);
16 const macho_text_protection = 0x5;
17
18 pub const Object = struct {
19 target: model.Target,
20 sections: []Section,
21 relocations: []Relocation,
22 symbols: []Symbol,
23 string_table: []const u8,
24
25 pub fn deinit(self: *Object, allocator: Allocator) void {
26 if (self.sections.len != 0) allocator.free(self.sections);
27 if (self.relocations.len != 0) allocator.free(self.relocations);
28 if (self.symbols.len != 0) allocator.free(self.symbols);
29 self.* = undefined;
30 }
31 };
32
33 pub const Section = struct {
34 segment_name: []const u8,
35 name: []const u8,
36 address: u64,
37 size: u64,
38 offset: u32,
39 alignment_shift: u32,
40 relocation_offset: u32,
41 relocation_count: u32,
42 flags: u32,
43 };
44
45 pub const Relocation = struct {
46 section_index: usize,
47 address: i32,
48 symbol_number: u32,
49 pc_relative: bool,
50 length: u8,
51 external: bool,
52 kind: u8,
53 };
54
55 pub const Symbol = struct {
56 name: []const u8,
57 section_index: u8,
58 value: u64,
59 kind: u8,
60 external: bool,
61 };
62
63 const SymtabCommand = struct {
64 symbol_offset: u32,
65 symbol_count: u32,
66 string_offset: u32,
67 string_size: u32,
68 };
69
70 const TextContribution = struct {
71 input_name: []const u8,
72 input_index: usize,
73 section_index: usize,
74 section_name: []const u8,
75 bytes: []const u8,
76 alignment: u64,
77 output_offset: u64 = 0,
78 };
79
80 const LinkObject = struct {
81 input_name: []const u8,
82 input_index: usize,
83 object: Object,
84 };
85
86 const SymbolDefinition = struct {
87 input_index: usize,
88 section_index: usize,
89 value: u64,
90 };
91
92 pub fn linkExecutable(
93 allocator: Allocator,
94 inputs: []const model.Input,
95 options: model.LinkOptions,
96 ) model.Error!root.LinkedImage {
97 const link_phase = trace.scope("link.macho");
98 defer link_phase.end();
99 if (options.diagnostics) |diagnostics| diagnostics.clear();
100 if (options.output_kind != .executable) return error.UnsupportedOutputKind;
101 if (options.target.architecture != .x86_64 or options.target.endianness != .little) {
102 return error.UnsupportedArchitecture;
103 }
104 if (options.gc_sections or options.icf != .off) return error.UnsupportedFormat;
105 if (inputs.len == 0) return error.NoAllocSections;
106
107 var scratch_state = std.heap.ArenaAllocator.init(allocator);
108 defer scratch_state.deinit();
109 const scratch = scratch_state.allocator();
110
111 var manifest_builder = build_manifest: {
112 const manifest_phase = trace.product(.manifest_recording);
113 defer manifest_phase.end();
114 var builder = try root.incremental.Builder.init(allocator, options);
115 errdefer builder.deinit();
116 for (inputs) |input| try builder.addInput(input);
117 break :build_manifest builder;
118 };
119 errdefer manifest_builder.deinit();
120
121 var contributions = std.ArrayListUnmanaged(TextContribution).empty;
122 defer contributions.deinit(scratch);
123
124 var definitions: std.StringHashMapUnmanaged(SymbolDefinition) = .{};
125 defer definitions.deinit(scratch);
126
127 var objects = std.ArrayListUnmanaged(LinkObject).empty;
128 defer objects.deinit(scratch);
129
130 for (inputs, 0..) |input, input_index| {
131 if (root.archive.isArchive(input.bytes)) return error.UnsupportedFormat;
132 const object = try parseObject(scratch, input.bytes);
133 if (object.target.object_format != .macho) return error.UnsupportedFormat;
134 if (object.target.architecture != options.target.architecture) return error.UnsupportedArchitecture;
135
136 try objects.append(scratch, .{
137 .input_name = input.name,
138 .input_index = input_index,
139 .object = object,
140 });
141 }
142
143 {
144 const layout_phase = trace.product(.section_contribution_graph);
145 defer layout_phase.end();
146 for (objects.items) |link_object| {
147 const input = inputs[link_object.input_index];
148 try collectTextContributions(scratch, &contributions, input, link_object.input_index, link_object.object.sections);
149 }
150 }
151 {
152 const symbol_phase = trace.product(.symbol_database);
153 defer symbol_phase.end();
154 for (objects.items) |link_object| {
155 try indexExternalDefinitions(scratch, &definitions, link_object.input_index, link_object.object);
156 }
157 }
158
159 const text_size = layout_text: {
160 const layout_phase = trace.product(.address_assignment);
161 defer layout_phase.end();
162 break :layout_text try layoutTextContributions(contributions.items);
163 };
164 if (text_size == 0) return error.NoAllocSections;
165
166 const entry = resolve_entry: {
167 const symbol_phase = trace.product(.symbol_database);
168 defer symbol_phase.end();
169 const entry_definition = definitions.get(options.entry_symbol) orelse return error.MissingEntrySymbol;
170 const entry_contribution = contributionForDefinition(contributions.items, entry_definition) orelse return error.MissingEntrySymbol;
171 if (entry_definition.value > entry_contribution.bytes.len) return error.InvalidRange;
172 break :resolve_entry .{
173 .definition = entry_definition,
174 .contribution = entry_contribution,
175 };
176 };
177
178 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
179 const text_file_offset = try alignForward(header_size + load_size, 16);
180 const entry_file_offset = try checkedAddU64(text_file_offset, try checkedAddU64(entry.contribution.output_offset, entry.definition.value));
181 const total_size = try checkedAddU64(text_file_offset, text_size);
182 const text_vmaddr = try checkedAddU64(options.image_base, text_file_offset);
183 const text_vmsize = try alignForward(total_size, options.page_size);
184
185 const image = try allocator.alloc(u8, try checkedUsize(total_size));
186 errdefer allocator.free(image);
187 {
188 const write_phase = trace.product(.output_writing);
189 defer write_phase.end();
190 @memset(image, 0);
191
192 try writeExecutableHeaders(
193 image,
194 options,
195 load_size,
196 text_file_offset,
197 entry_file_offset,
198 total_size,
199 text_vmsize,
200 text_size,
201 );
202
203 for (contributions.items) |contribution| {
204 const start = try checkedUsize(try checkedAddU64(text_file_offset, contribution.output_offset));
205 @memcpy(image[start..][0..contribution.bytes.len], contribution.bytes);
206 }
207 }
208 {
209 const manifest_phase = trace.product(.manifest_recording);
210 defer manifest_phase.end();
211 for (contributions.items) |contribution| {
212 try manifest_builder.addContribution(
213 contribution.input_name,
214 contribution.input_index,
215 .section,
216 contribution.section_name,
217 @intCast(contribution.section_index),
218 "__TEXT,__text",
219 try checkedAddU64(text_vmaddr, contribution.output_offset),
220 try checkedAddU64(text_file_offset, contribution.output_offset),
221 contribution.bytes.len,
222 contribution.bytes.len,
223 contribution.alignment,
224 );
225 }
226 }
227 try applyRelocations(
228 image,
229 objects.items,
230 contributions.items,
231 &definitions,
232 options,
233 text_file_offset,
234 text_vmaddr,
235 );
236
237 {
238 const manifest_phase = trace.product(.manifest_recording);
239 defer manifest_phase.end();
240 try manifest_builder.addSection(
241 "__TEXT,__text",
242 text_vmaddr,
243 text_file_offset,
244 text_size,
245 text_size,
246 16,
247 );
248 }
249
250 const manifest = finish_manifest: {
251 const manifest_phase = trace.product(.manifest_recording);
252 defer manifest_phase.end();
253 break :finish_manifest try manifest_builder.finish();
254 };
255
256 return .{
257 .bytes = image,
258 .manifest = manifest,
259 };
260 }
261
262 pub fn parseObject(allocator: Allocator, bytes: []const u8) model.Error!Object {
263 const phase = trace.product(.input_discovery);
264 defer phase.end();
265 const magic = try readU32(bytes, 0);
266 if (magic != std.macho.MH_MAGIC_64) return error.UnsupportedFormat;
267
268 const filetype = try readU32(bytes, 12);
269 if (filetype != std.macho.MH_OBJECT) return error.UnsupportedFormat;
270
271 const target = try targetFromCpuType(try readU32(bytes, 4));
272 const command_count = try readU32(bytes, 16);
273 const command_bytes_len = try readU32(bytes, 20);
274 const command_start: usize = header_size;
275 const command_end = command_start + try checkedUsize(command_bytes_len);
276 _ = try range(bytes, command_start, try checkedUsize(command_bytes_len));
277
278 var section_count: usize = 0;
279 var symtab: ?SymtabCommand = null;
280 var command_offset = command_start;
281 var command_index: u32 = 0;
282 while (command_index < command_count) : (command_index += 1) {
283 const command = try readU32(bytes, command_offset);
284 const command_size = try readU32(bytes, command_offset + 4);
285 const command_len = try checkedUsize(command_size);
286 if (command_len < load_command_size) return error.InvalidObject;
287 _ = try range(bytes, command_offset, command_len);
288 if (command_offset + command_len > command_end) return error.InvalidRange;
289
290 if (command == @backingInt(std.macho.LC.SEGMENT_64)) {
291 if (command_len < segment_command_64_size) return error.InvalidObject;
292 const sections_in_segment = try readU32(bytes, command_offset + 64);
293 const required_size = segment_command_64_size + try checkedMul(try checkedUsize(sections_in_segment), section_64_size);
294 if (command_len < required_size) return error.InvalidObject;
295 section_count += try checkedUsize(sections_in_segment);
296 } else if (command == @backingInt(std.macho.LC.SYMTAB)) {
297 if (command_len < symtab_command_size) return error.InvalidObject;
298 if (symtab != null) return error.InvalidObject;
299 symtab = .{
300 .symbol_offset = try readU32(bytes, command_offset + 8),
301 .symbol_count = try readU32(bytes, command_offset + 12),
302 .string_offset = try readU32(bytes, command_offset + 16),
303 .string_size = try readU32(bytes, command_offset + 20),
304 };
305 }
306
307 command_offset += command_len;
308 }
309 if (command_offset != command_end) return error.InvalidRange;
310
311 const sections = try allocator.alloc(Section, section_count);
312 errdefer allocator.free(sections);
313 var section_cursor: usize = 0;
314
315 command_offset = command_start;
316 command_index = 0;
317 while (command_index < command_count) : (command_index += 1) {
318 const command = try readU32(bytes, command_offset);
319 const command_len = try checkedUsize(try readU32(bytes, command_offset + 4));
320 if (command == @backingInt(std.macho.LC.SEGMENT_64)) {
321 const sections_in_segment = try checkedUsize(try readU32(bytes, command_offset + 64));
322 var section_offset = command_offset + segment_command_64_size;
323 var index: usize = 0;
324 while (index < sections_in_segment) : (index += 1) {
325 const size = try readU64(bytes, section_offset + 40);
326 const data_offset = try readU32(bytes, section_offset + 48);
327 const relocation_offset = try readU32(bytes, section_offset + 56);
328 const relocation_count = try readU32(bytes, section_offset + 60);
329 if (size != 0 and data_offset != 0) {
330 _ = try range(bytes, try checkedUsize(data_offset), try checkedUsize(size));
331 }
332 if (relocation_count != 0) {
333 _ = try range(
334 bytes,
335 try checkedUsize(relocation_offset),
336 try checkedMul(try checkedUsize(relocation_count), relocation_info_size),
337 );
338 }
339 sections[section_cursor] = .{
340 .segment_name = paddedName((try range(bytes, section_offset + 16, 16))[0..16]),
341 .name = paddedName((try range(bytes, section_offset, 16))[0..16]),
342 .address = try readU64(bytes, section_offset + 32),
343 .size = size,
344 .offset = data_offset,
345 .alignment_shift = try readU32(bytes, section_offset + 52),
346 .relocation_offset = relocation_offset,
347 .relocation_count = relocation_count,
348 .flags = try readU32(bytes, section_offset + 64),
349 };
350 section_cursor += 1;
351 section_offset += section_64_size;
352 }
353 }
354 command_offset += command_len;
355 }
356
357 const relocations = try parseRelocations(
358 allocator,
359 bytes,
360 sections,
361 if (symtab) |table| table.symbol_count else 0,
362 );
363 errdefer if (relocations.len != 0) allocator.free(relocations);
364
365 const symbols: []Symbol = if (symtab) |table| blk: {
366 const string_table = try range(bytes, try checkedUsize(table.string_offset), try checkedUsize(table.string_size));
367 const symbol_bytes_len = try checkedMul(try checkedUsize(table.symbol_count), nlist_64_size);
368 _ = try range(bytes, try checkedUsize(table.symbol_offset), symbol_bytes_len);
369
370 const parsed_symbols = try allocator.alloc(Symbol, try checkedUsize(table.symbol_count));
371 errdefer allocator.free(parsed_symbols);
372 var index: usize = 0;
373 while (index < parsed_symbols.len) : (index += 1) {
374 const symbol_offset = try checkedUsize(table.symbol_offset) + index * nlist_64_size;
375 const symbol_kind = (try range(bytes, symbol_offset + 4, 1))[0];
376 const section_index = (try range(bytes, symbol_offset + 5, 1))[0];
377 try validateSymbolSectionIndex(symbol_kind, section_index, sections.len);
378 parsed_symbols[index] = .{
379 .name = try stringFromTable(string_table, try readU32(bytes, symbol_offset)),
380 .section_index = section_index,
381 .value = try readU64(bytes, symbol_offset + 8),
382 .kind = symbol_kind,
383 .external = symbol_kind & 0x1 != 0,
384 };
385 }
386 break :blk parsed_symbols;
387 } else try allocator.alloc(Symbol, 0);
388 errdefer if (symbols.len != 0) allocator.free(symbols);
389
390 return .{
391 .target = target,
392 .sections = sections,
393 .relocations = relocations,
394 .symbols = symbols,
395 .string_table = if (symtab) |table| try range(bytes, try checkedUsize(table.string_offset), try checkedUsize(table.string_size)) else &.{},
396 };
397 }
398
399 pub fn parseObjectMetadata(allocator: Allocator, bytes: []const u8) model.Error!root.Object {
400 var object = try parseObject(allocator, bytes);
401 defer object.deinit(allocator);
402
403 const sections = try allocator.alloc(root.ObjectSection, object.sections.len);
404 errdefer allocator.free(sections);
405 for (sections, object.sections) |*section, source| {
406 section.* = .{
407 .segment_name = source.segment_name,
408 .name = source.name,
409 .address = source.address,
410 .size = source.size,
411 .offset = source.offset,
412 .alignment = try alignmentFromShift(source.alignment_shift),
413 .flags = source.flags,
414 .relocation_count = source.relocation_count,
415 };
416 }
417
418 const symbols = try allocator.alloc(root.ObjectSymbol, object.symbols.len);
419 errdefer allocator.free(symbols);
420 for (symbols, object.symbols) |*symbol, source| {
421 symbol.* = .{
422 .name = source.name,
423 .section_index = source.section_index,
424 .value = source.value,
425 .kind = source.kind,
426 .binding = @intFromBool(source.external),
427 .external = source.external,
428 .undefined = source.section_index == 0,
429 };
430 }
431
432 return .{
433 .target = object.target,
434 .sections = sections,
435 .symbols = symbols,
436 };
437 }
438
439 fn collectTextContributions(
440 allocator: Allocator,
441 contributions: *std.ArrayListUnmanaged(TextContribution),
442 input: model.Input,
443 input_index: usize,
444 sections: []const Section,
445 ) model.Error!void {
446 for (sections, 0..) |section, section_index| {
447 if (!sectionHasImageData(section)) continue;
448 try contributions.append(allocator, .{
449 .input_name = input.name,
450 .input_index = input_index,
451 .section_index = section_index,
452 .section_name = section.name,
453 .bytes = try sectionData(input.bytes, section),
454 .alignment = try alignmentFromShift(section.alignment_shift),
455 });
456 }
457 }
458
459 fn applyRelocations(
460 image: []u8,
461 objects: []const LinkObject,
462 contributions: []const TextContribution,
463 definitions: *const std.StringHashMapUnmanaged(SymbolDefinition),
464 options: model.LinkOptions,
465 text_file_offset: u64,
466 text_vmaddr: u64,
467 ) model.Error!void {
468 const phase = trace.product(.relocation_application);
469 defer phase.end();
470 const branch_kind: u8 = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH));
471 const unsigned_kind: u8 = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED));
472 for (objects) |link_object| {
473 for (link_object.object.relocations) |relocation| {
474 if (relocation.section_index >= link_object.object.sections.len) return error.InvalidObject;
475 const section = link_object.object.sections[relocation.section_index];
476 if (!sectionHasImageData(section)) continue;
477 const pcrel_offset = signedRelocationOffset(relocation.kind);
478 const supported_branch_pcrel32 = relocation.kind == branch_kind and relocation.pc_relative and relocation.length == 2;
479 const supported_signed_pcrel32 = pcrel_offset != null and relocation.pc_relative and relocation.length == 2;
480 const supported_unsigned = relocation.kind == unsigned_kind and !relocation.pc_relative and (relocation.length == 2 or relocation.length == 3);
481 const supported_external = relocation.external and (supported_branch_pcrel32 or supported_signed_pcrel32 or supported_unsigned);
482 const supported_local = !relocation.external and (supported_signed_pcrel32 or supported_unsigned);
483 if (!supported_external and !supported_local) {
484 if (options.diagnostics) |diagnostics| {
485 diagnostics.recordUnsupportedRelocation(
486 link_object.input_name,
487 section.name,
488 relocationSymbolName(link_object.object, relocation),
489 relocation.kind,
490 );
491 }
492 return error.UnsupportedRelocation;
493 }
494
495 const source = contributionForSection(
496 contributions,
497 link_object.input_index,
498 relocation.section_index,
499 ) orelse return error.InvalidObject;
500 if (supported_local) {
501 const target_section_index = try relocationTargetSectionIndex(relocation, link_object.object.sections.len);
502 const target_section = link_object.object.sections[target_section_index];
503 if (!sectionHasImageData(target_section)) {
504 if (options.diagnostics) |diagnostics| {
505 diagnostics.recordUnsupportedRelocation(
506 link_object.input_name,
507 section.name,
508 relocationSymbolName(link_object.object, relocation),
509 relocation.kind,
510 );
511 }
512 return error.UnsupportedRelocation;
513 }
514 const target_contribution = contributionForSection(
515 contributions,
516 link_object.input_index,
517 target_section_index,
518 ) orelse return error.InvalidObject;
519 const target_vmaddr = try checkedAddU64(text_vmaddr, target_contribution.output_offset);
520 if (supported_unsigned) {
521 try applyUnsignedRelocation(image, text_file_offset, source, relocation, target_vmaddr, target_section.address);
522 } else {
523 try applyLocalPcrel32Relocation(
524 image,
525 text_file_offset,
526 text_vmaddr,
527 source,
528 section,
529 relocation,
530 target_contribution,
531 target_section,
532 pcrel_offset orelse 0,
533 );
534 }
535 continue;
536 }
537 const symbol = symbolByIndex(link_object.object.symbols, relocation.symbol_number) orelse return error.InvalidObject;
538 const target = try definitionForRelocationSymbol(
539 definitions,
540 link_object.input_name,
541 link_object.input_index,
542 link_object.object.sections,
543 symbol,
544 options,
545 );
546 const target_contribution = contributionForDefinition(contributions, target) orelse return error.InvalidObject;
547 if (target.value > target_contribution.bytes.len) return error.InvalidRange;
548 const target_vmaddr = try checkedAddU64(
549 text_vmaddr,
550 try checkedAddU64(target_contribution.output_offset, target.value),
551 );
552 if (supported_unsigned) {
553 try applyUnsignedRelocation(image, text_file_offset, source, relocation, target_vmaddr, 0);
554 } else {
555 try applyPcrel32Relocation(
556 image,
557 text_file_offset,
558 text_vmaddr,
559 source,
560 relocation,
561 target_vmaddr,
562 pcrel_offset orelse 0,
563 );
564 }
565 }
566 }
567 }
568
569 fn signedRelocationOffset(kind: u8) ?u64 {
570 if (kind == @backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED)) return 0;
571 if (kind == @backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED_1)) return 1;
572 if (kind == @backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED_2)) return 2;
573 if (kind == @backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED_4)) return 4;
574 return null;
575 }
576
577 fn applyPcrel32Relocation(
578 image: []u8,
579 text_file_offset: u64,
580 text_vmaddr: u64,
581 source: TextContribution,
582 relocation: Relocation,
583 target_vmaddr: u64,
584 pcrel_offset: u64,
585 ) model.Error!void {
586 const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 4);
587 const relocation_offset: u64 = @intCast(relocation.address);
588 const place_vmaddr = try checkedAddU64(
589 text_vmaddr,
590 try checkedAddU64(source.output_offset, relocation_offset),
591 );
592 const addend = try checkedAddI64(
593 std.mem.readInt(i32, image[patch_index..][0..4], .little),
594 try checkedI64FromU64(pcrel_offset),
595 );
596 const pc = try checkedAddU64(try checkedAddU64(place_vmaddr, 4), pcrel_offset);
597 const value = try checkedSubI64(
598 try checkedAddI64(try checkedI64FromU64(target_vmaddr), addend),
599 try checkedI64FromU64(pc),
600 );
601 std.mem.writeInt(i32, image[patch_index..][0..4], try checkedI32FromI64(value), .little);
602 }
603
604 fn applyLocalPcrel32Relocation(
605 image: []u8,
606 text_file_offset: u64,
607 text_vmaddr: u64,
608 source: TextContribution,
609 source_section: Section,
610 relocation: Relocation,
611 target: TextContribution,
612 target_section: Section,
613 pcrel_offset: u64,
614 ) model.Error!void {
615 const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 4);
616 const relocation_offset: u64 = @intCast(relocation.address);
617 const embedded_addend = try checkedAddI64(
618 std.mem.readInt(i32, image[patch_index..][0..4], .little),
619 try checkedI64FromU64(pcrel_offset),
620 );
621 const input_reloc_base = try checkedAddU64(
622 source_section.address,
623 try checkedAddU64(relocation_offset, 4),
624 );
625 const input_referent = try checkedAddI64(try checkedI64FromU64(input_reloc_base), embedded_addend);
626 const target_offset_i64 = try checkedSubI64(input_referent, try checkedI64FromU64(target_section.address));
627 if (target_offset_i64 < 0) return error.InvalidRange;
628 const target_offset: u64 = @intCast(target_offset_i64);
629 if (target_offset > target.bytes.len) return error.InvalidRange;
630
631 const target_vmaddr = try checkedAddU64(
632 text_vmaddr,
633 try checkedAddU64(target.output_offset, target_offset),
634 );
635 const place_vmaddr = try checkedAddU64(
636 text_vmaddr,
637 try checkedAddU64(source.output_offset, relocation_offset),
638 );
639 const pc = try checkedAddU64(try checkedAddU64(place_vmaddr, 4), pcrel_offset);
640 const value = try checkedSubI64(try checkedI64FromU64(target_vmaddr), try checkedI64FromU64(pc));
641 std.mem.writeInt(i32, image[patch_index..][0..4], try checkedI32FromI64(value), .little);
642 }
643
644 fn applyUnsignedRelocation(
645 image: []u8,
646 text_file_offset: u64,
647 source: TextContribution,
648 relocation: Relocation,
649 target_vmaddr: u64,
650 target_section_address: u64,
651 ) model.Error!void {
652 if (relocation.length == 2) {
653 const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 4);
654 const addend = offsetFromSectionAddress(std.mem.readInt(u32, image[patch_index..][0..4], .little), target_section_address);
655 const value = try checkedRelocationAddU64(target_vmaddr, addend);
656 std.mem.writeInt(u32, image[patch_index..][0..4], try checkedU32FromRelocation(value), .little);
657 } else if (relocation.length == 3) {
658 const patch_index = try relocationPatchIndex(image, text_file_offset, source, relocation, 8);
659 const addend = offsetFromSectionAddress(std.mem.readInt(u64, image[patch_index..][0..8], .little), target_section_address);
660 std.mem.writeInt(u64, image[patch_index..][0..8], try checkedRelocationAddU64(target_vmaddr, addend), .little);
661 } else {
662 return error.UnsupportedRelocation;
663 }
664 }
665
666 fn relocationPatchIndex(
667 image: []const u8,
668 text_file_offset: u64,
669 source: TextContribution,
670 relocation: Relocation,
671 width: usize,
672 ) model.Error!usize {
673 if (relocation.address < 0) return error.InvalidObject;
674 const relocation_offset: u64 = @intCast(relocation.address);
675 const patch_file_offset = try checkedAddU64(
676 text_file_offset,
677 try checkedAddU64(source.output_offset, relocation_offset),
678 );
679 const patch_index = try checkedUsize(patch_file_offset);
680 if (patch_index > image.len or width > image.len - patch_index) return error.InvalidRange;
681 return patch_index;
682 }
683
684 fn symbolByIndex(symbols: []const Symbol, raw_index: u32) ?Symbol {
685 const index = std.math.cast(usize, raw_index) orelse return null;
686 if (index >= symbols.len) return null;
687 return symbols[index];
688 }
689
690 fn relocationTargetSectionIndex(relocation: Relocation, section_count: usize) model.Error!usize {
691 if (relocation.symbol_number == 0) return error.InvalidObject;
692 const index = try checkedUsize(relocation.symbol_number - 1);
693 if (index >= section_count) return error.InvalidObject;
694 return index;
695 }
696
697 fn definitionForRelocationSymbol(
698 definitions: *const std.StringHashMapUnmanaged(SymbolDefinition),
699 input_name: []const u8,
700 input_index: usize,
701 sections: []const Section,
702 symbol: Symbol,
703 options: model.LinkOptions,
704 ) model.Error!SymbolDefinition {
705 if ((symbol.kind & std.macho.N_TYPE) == std.macho.N_SECT) {
706 const section_index: usize = @intCast(symbol.section_index - 1);
707 if (section_index >= sections.len) return error.InvalidObject;
708 return .{
709 .input_index = input_index,
710 .section_index = section_index,
711 .value = try symbolOffsetInSection(sections[section_index], symbol),
712 };
713 }
714 if (definitions.get(symbol.name)) |definition| return definition;
715 if (options.diagnostics) |diagnostics| diagnostics.recordUndefinedSymbol(input_name, symbol.name);
716 return error.UndefinedSymbol;
717 }
718
719 fn contributionForSection(
720 contributions: []const TextContribution,
721 input_index: usize,
722 section_index: usize,
723 ) ?TextContribution {
724 for (contributions) |contribution| {
725 if (contribution.input_index != input_index) continue;
726 if (contribution.section_index != section_index) continue;
727 return contribution;
728 }
729 return null;
730 }
731
732 fn relocationSymbolName(object: Object, relocation: Relocation) []const u8 {
733 if (relocation.external) {
734 const index = std.math.cast(usize, relocation.symbol_number) orelse return "";
735 if (index < object.symbols.len) return object.symbols[index].name;
736 return "";
737 }
738 if (relocation.symbol_number == 0) return "";
739 const section_index: usize = @intCast(relocation.symbol_number - 1);
740 if (section_index < object.sections.len) return object.sections[section_index].name;
741 return "";
742 }
743
744 fn indexExternalDefinitions(
745 allocator: Allocator,
746 definitions: *std.StringHashMapUnmanaged(SymbolDefinition),
747 input_index: usize,
748 object: Object,
749 ) model.Error!void {
750 for (object.symbols) |symbol| {
751 if (!symbol.external) continue;
752 if ((symbol.kind & std.macho.N_TYPE) != std.macho.N_SECT) continue;
753 if (symbol.section_index == 0) continue;
754 const section_index: usize = @intCast(symbol.section_index - 1);
755 if (section_index >= object.sections.len) return error.InvalidObject;
756 const section = object.sections[section_index];
757 if (!sectionHasImageData(section)) continue;
758 const gop = try definitions.getOrPut(allocator, symbol.name);
759 if (gop.found_existing) return error.DuplicateSymbol;
760 gop.value_ptr.* = .{
761 .input_index = input_index,
762 .section_index = section_index,
763 .value = try symbolOffsetInSection(section, symbol),
764 };
765 }
766 }
767
768 fn symbolOffsetInSection(section: Section, symbol: Symbol) model.Error!u64 {
769 return offsetFromSectionAddress(symbol.value, section.address);
770 }
771
772 fn offsetFromSectionAddress(value: u64, section_address: u64) u64 {
773 if (value >= section_address) return value - section_address;
774 return value;
775 }
776
777 fn layoutTextContributions(contributions: []TextContribution) model.Error!u64 {
778 var text_size: u64 = 0;
779 for (contributions) |*contribution| {
780 text_size = try alignForward(text_size, contribution.alignment);
781 contribution.output_offset = text_size;
782 text_size = try checkedAddU64(text_size, contribution.bytes.len);
783 }
784 return text_size;
785 }
786
787 fn contributionForDefinition(
788 contributions: []const TextContribution,
789 definition: SymbolDefinition,
790 ) ?TextContribution {
791 for (contributions) |contribution| {
792 if (contribution.input_index != definition.input_index) continue;
793 if (contribution.section_index != definition.section_index) continue;
794 return contribution;
795 }
796 return null;
797 }
798
799 fn sectionHasImageData(section: Section) bool {
800 if (section.size == 0) return false;
801 if (!std.mem.eql(u8, section.segment_name, "__TEXT")) return false;
802 if (std.mem.eql(u8, section.name, "__text")) return true;
803 return section.flags & (std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS) != 0;
804 }
805
806 fn sectionData(bytes: []const u8, section: Section) model.Error![]const u8 {
807 return try range(bytes, try checkedUsize(section.offset), try checkedUsize(section.size));
808 }
809
810 fn writeExecutableHeaders(
811 image: []u8,
812 options: model.LinkOptions,
813 load_size: usize,
814 text_file_offset: u64,
815 entry_file_offset: u64,
816 total_size: u64,
817 text_vmsize: u64,
818 text_size: u64,
819 ) model.Error!void {
820 writeU32(image, 0, std.macho.MH_MAGIC_64);
821 writeU32(image, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
822 writeU32(image, 8, @bitCast(std.macho.CPU_SUBTYPE_X86_64_ALL));
823 writeU32(image, 12, std.macho.MH_EXECUTE);
824 writeU32(image, 16, 2);
825 writeU32(image, 20, @intCast(load_size));
826 writeU32(image, 24, std.macho.MH_NOUNDEFS);
827
828 const segment_offset = header_size;
829 writeU32(image, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
830 writeU32(image, segment_offset + 4, segment_command_64_size + section_64_size);
831 writeName(image, segment_offset + 8, 16, "__TEXT");
832 writeU64(image, segment_offset + 24, options.image_base);
833 writeU64(image, segment_offset + 32, text_vmsize);
834 writeU64(image, segment_offset + 40, 0);
835 writeU64(image, segment_offset + 48, total_size);
836 writeU32(image, segment_offset + 56, macho_text_protection);
837 writeU32(image, segment_offset + 60, macho_text_protection);
838 writeU32(image, segment_offset + 64, 1);
839
840 const section_offset = segment_offset + segment_command_64_size;
841 writeName(image, section_offset, 16, "__text");
842 writeName(image, section_offset + 16, 16, "__TEXT");
843 writeU64(image, section_offset + 32, try checkedAddU64(options.image_base, text_file_offset));
844 writeU64(image, section_offset + 40, text_size);
845 writeU32(image, section_offset + 48, try checkedU32FromU64(text_file_offset));
846 writeU32(image, section_offset + 52, 4);
847 writeU32(image, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
848
849 const entry_offset = section_offset + section_64_size;
850 writeU32(image, entry_offset, @backingInt(std.macho.LC.MAIN));
851 writeU32(image, entry_offset + 4, entry_point_command_size);
852 writeU64(image, entry_offset + 8, entry_file_offset);
853 }
854
855 fn targetFromCpuType(cpu_type: u32) model.Error!model.Target {
856 return switch (@as(std.macho.cpu_type_t, @bitCast(cpu_type))) {
857 std.macho.CPU_TYPE_X86_64 => .{
858 .object_format = .macho,
859 .architecture = .x86_64,
860 .endianness = .little,
861 .pointer_width_bits = 64,
862 },
863 std.macho.CPU_TYPE_ARM64 => .{
864 .object_format = .macho,
865 .architecture = .aarch64,
866 .endianness = .little,
867 .pointer_width_bits = 64,
868 },
869 else => error.UnsupportedArchitecture,
870 };
871 }
872
873 fn alignmentFromShift(shift: u32) model.Error!u64 {
874 if (shift >= 63) return error.InvalidAlignment;
875 return @as(u64, 1) << @intCast(shift);
876 }
877
878 fn paddedName(bytes: *const [16]u8) []const u8 {
879 const end = std.mem.indexOfScalar(u8, bytes, 0) orelse bytes.len;
880 return bytes[0..end];
881 }
882
883 fn stringFromTable(table: []const u8, offset: u32) model.Error![]const u8 {
884 if (offset == 0) return "";
885 const start = try checkedUsize(offset);
886 if (start >= table.len) return error.InvalidStringTable;
887 const end = std.mem.indexOfScalarPos(u8, table, start, 0) orelse return error.InvalidStringTable;
888 return table[start..end];
889 }
890
891 fn validateSymbolSectionIndex(kind: u8, section_index: u8, section_count: usize) model.Error!void {
892 if ((kind & std.macho.N_TYPE) == std.macho.N_SECT) {
893 if (section_index == 0 or section_index > section_count) return error.InvalidObject;
894 } else if (section_index != 0) {
895 return error.InvalidObject;
896 }
897 }
898
899 fn parseRelocations(
900 allocator: Allocator,
901 bytes: []const u8,
902 sections: []const Section,
903 symbol_count: u32,
904 ) model.Error![]Relocation {
905 var relocation_count: usize = 0;
906 for (sections) |section| {
907 relocation_count = try checkedAdd(relocation_count, try checkedUsize(section.relocation_count));
908 }
909
910 const relocations = try allocator.alloc(Relocation, relocation_count);
911 errdefer if (relocations.len != 0) allocator.free(relocations);
912
913 var cursor: usize = 0;
914 for (sections, 0..) |section, section_index| {
915 const count = try checkedUsize(section.relocation_count);
916 if (count == 0) continue;
917 const relocation_range = try range(
918 bytes,
919 try checkedUsize(section.relocation_offset),
920 try checkedMul(count, relocation_info_size),
921 );
922 var index: usize = 0;
923 while (index < count) : (index += 1) {
924 const offset = index * relocation_info_size;
925 const encoded = std.mem.readInt(u32, relocation_range[offset + 4 ..][0..4], .little);
926 const symbol_number = encoded & 0x00ff_ffff;
927 const external = ((encoded >> 27) & 0x1) != 0;
928 const address = std.mem.readInt(i32, relocation_range[offset..][0..4], .little);
929 const length: u8 = @intCast((encoded >> 25) & 0x3);
930 try validateRelocationAddress(address, length, section.size);
931 if (external) {
932 if (symbol_number >= symbol_count) return error.InvalidObject;
933 } else if (symbol_number == 0 or symbol_number > sections.len) {
934 return error.InvalidObject;
935 }
936 relocations[cursor] = .{
937 .section_index = section_index,
938 .address = address,
939 .symbol_number = symbol_number,
940 .pc_relative = ((encoded >> 24) & 0x1) != 0,
941 .length = length,
942 .external = external,
943 .kind = @intCast((encoded >> 28) & 0xf),
944 };
945 cursor += 1;
946 }
947 }
948
949 return relocations;
950 }
951
952 fn validateRelocationAddress(address: i32, length: u8, section_size: u64) model.Error!void {
953 if (address < 0) return error.InvalidObject;
954 const offset: u64 = @intCast(address);
955 const width = relocationWidth(length);
956 if (offset > section_size) return error.InvalidObject;
957 if (width > section_size - offset) return error.InvalidObject;
958 }
959
960 fn relocationWidth(length: u8) u64 {
961 return @as(u64, 1) << @intCast(length);
962 }
963
964 fn maxRelocationLength(section_size: u64) usize {
965 if (section_size >= 8) return 3;
966 if (section_size >= 4) return 2;
967 if (section_size >= 2) return 1;
968 return 0;
969 }
970
971 fn range(bytes: []const u8, offset: usize, len: usize) model.Error![]const u8 {
972 if (offset > bytes.len) return error.InvalidRange;
973 if (len > bytes.len - offset) return error.InvalidRange;
974 return bytes[offset..][0..len];
975 }
976
977 fn readU32(bytes: []const u8, offset: usize) model.Error!u32 {
978 return std.mem.readInt(u32, (try range(bytes, offset, 4))[0..4], .little);
979 }
980
981 fn readU64(bytes: []const u8, offset: usize) model.Error!u64 {
982 return std.mem.readInt(u64, (try range(bytes, offset, 8))[0..8], .little);
983 }
984
985 fn checkedUsize(value: anytype) model.Error!usize {
986 return std.math.cast(usize, value) orelse error.InvalidRange;
987 }
988
989 fn checkedMul(a: usize, b: usize) model.Error!usize {
990 return std.math.mul(usize, a, b) catch error.InvalidRange;
991 }
992
993 fn checkedAdd(a: usize, b: usize) model.Error!usize {
994 return std.math.add(usize, a, b) catch error.InvalidRange;
995 }
996
997 fn checkedAddU64(a: u64, b: u64) model.Error!u64 {
998 return std.math.add(u64, a, b) catch error.InvalidRange;
999 }
1000
1001 fn checkedAddI64(a: i64, b: i64) model.Error!i64 {
1002 return std.math.add(i64, a, b) catch error.InvalidRange;
1003 }
1004
1005 fn checkedSubI64(a: i64, b: i64) model.Error!i64 {
1006 return std.math.sub(i64, a, b) catch error.InvalidRange;
1007 }
1008
1009 fn checkedU32FromU64(value: u64) model.Error!u32 {
1010 return std.math.cast(u32, value) orelse error.InvalidRange;
1011 }
1012
1013 fn checkedRelocationAddU64(a: u64, b: u64) model.Error!u64 {
1014 return std.math.add(u64, a, b) catch error.RelocationOverflow;
1015 }
1016
1017 fn checkedU32FromRelocation(value: u64) model.Error!u32 {
1018 return std.math.cast(u32, value) orelse error.RelocationOverflow;
1019 }
1020
1021 fn checkedI64FromU64(value: u64) model.Error!i64 {
1022 return std.math.cast(i64, value) orelse error.InvalidRange;
1023 }
1024
1025 fn checkedI32FromI64(value: i64) model.Error!i32 {
1026 return std.math.cast(i32, value) orelse error.RelocationOverflow;
1027 }
1028
1029 fn alignForward(value: u64, alignment: u64) model.Error!u64 {
1030 if (alignment == 0) return error.InvalidAlignment;
1031 if (!std.math.isPowerOfTwo(alignment)) return error.InvalidAlignment;
1032 const mask = alignment - 1;
1033 return (try checkedAddU64(value, mask)) & ~mask;
1034 }
1035
1036 fn writeU32(bytes: []u8, offset: usize, value: u32) void {
1037 std.mem.writeInt(u32, bytes[offset..][0..4], value, .little);
1038 }
1039
1040 fn writeI32(bytes: []u8, offset: usize, value: i32) void {
1041 std.mem.writeInt(i32, bytes[offset..][0..4], value, .little);
1042 }
1043
1044 fn writeU64(bytes: []u8, offset: usize, value: u64) void {
1045 std.mem.writeInt(u64, bytes[offset..][0..8], value, .little);
1046 }
1047
1048 fn writeName(bytes: []u8, offset: usize, comptime size: usize, value: []const u8) void {
1049 @memset(bytes[offset..][0..size], 0);
1050 @memcpy(bytes[offset..][0..value.len], value);
1051 }
1052
1053 const FixtureRelocation = struct {
1054 section_index: usize,
1055 address: i32,
1056 symbol_number: u32,
1057 pc_relative: bool,
1058 length: u8,
1059 external: bool,
1060 kind: u8,
1061 };
1062
1063 fn writeRelocation(bytes: []u8, offset: usize, relocation: FixtureRelocation) void {
1064 writeI32(bytes, offset, relocation.address);
1065 const encoded = (relocation.symbol_number & 0x00ff_ffff) |
1066 (@as(u32, @intFromBool(relocation.pc_relative)) << 24) |
1067 (@as(u32, relocation.length) << 25) |
1068 (@as(u32, @intFromBool(relocation.external)) << 27) |
1069 (@as(u32, relocation.kind) << 28);
1070 writeU32(bytes, offset + 4, encoded);
1071 }
1072
1073 fn fixtureObject(allocator: Allocator) ![]u8 {
1074 const segment_load_size = segment_command_64_size + section_64_size;
1075 const load_size = segment_load_size + symtab_command_size;
1076 const text_offset = header_size + load_size;
1077 const text = "\xe8\x00\x00\x00\x00\x90\x90\xc3";
1078 const relocation_offset = text_offset + text.len;
1079 const symbol_offset = relocation_offset + relocation_info_size;
1080 const symbol_count = 2;
1081 const string_table = "\x00_start\x00local\x00";
1082 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1083 const total_size = string_offset + string_table.len;
1084
1085 const bytes = try allocator.alloc(u8, total_size);
1086 @memset(bytes, 0);
1087
1088 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1089 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1090 writeU32(bytes, 12, std.macho.MH_OBJECT);
1091 writeU32(bytes, 16, 2);
1092 writeU32(bytes, 20, @intCast(load_size));
1093
1094 const segment_offset = header_size;
1095 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1096 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1097 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1098 writeU64(bytes, segment_offset + 32, text.len);
1099 writeU32(bytes, segment_offset + 64, 1);
1100
1101 const section_offset = segment_offset + segment_command_64_size;
1102 writeName(bytes, section_offset, 16, "__text");
1103 writeName(bytes, section_offset + 16, 16, "__TEXT");
1104 writeU64(bytes, section_offset + 40, text.len);
1105 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1106 writeU32(bytes, section_offset + 52, 4);
1107 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1108 writeU32(bytes, section_offset + 60, 1);
1109
1110 const symtab_offset = segment_offset + segment_load_size;
1111 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1112 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1113 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1114 writeU32(bytes, symtab_offset + 12, symbol_count);
1115 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1116 writeU32(bytes, symtab_offset + 20, string_table.len);
1117
1118 @memcpy(bytes[text_offset..][0..text.len], text);
1119 writeRelocation(bytes, relocation_offset, .{
1120 .section_index = 0,
1121 .address = 1,
1122 .symbol_number = 0,
1123 .pc_relative = true,
1124 .length = 2,
1125 .external = true,
1126 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH)),
1127 });
1128
1129 writeU32(bytes, symbol_offset, 1);
1130 bytes[symbol_offset + 4] = 0x0f;
1131 bytes[symbol_offset + 5] = 1;
1132 writeU64(bytes, symbol_offset + 8, 0);
1133 const local_symbol_offset = symbol_offset + nlist_64_size;
1134 writeU32(bytes, local_symbol_offset, 8);
1135 bytes[local_symbol_offset + 4] = 0x0e;
1136 bytes[local_symbol_offset + 5] = 1;
1137 writeU64(bytes, local_symbol_offset + 8, 1);
1138
1139 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1140 return bytes;
1141 }
1142
1143 fn fixtureBranchCallerObject(allocator: Allocator) ![]u8 {
1144 const segment_load_size = segment_command_64_size + section_64_size;
1145 const load_size = segment_load_size + symtab_command_size;
1146 const text_offset = header_size + load_size;
1147 const text = "\xe8\x00\x00\x00\x00\xc3";
1148 const relocation_offset = text_offset + text.len;
1149 const symbol_offset = relocation_offset + relocation_info_size;
1150 const symbol_count = 2;
1151 const string_table = "\x00_start\x00callee\x00";
1152 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1153 const total_size = string_offset + string_table.len;
1154
1155 const bytes = try allocator.alloc(u8, total_size);
1156 @memset(bytes, 0);
1157
1158 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1159 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1160 writeU32(bytes, 12, std.macho.MH_OBJECT);
1161 writeU32(bytes, 16, 2);
1162 writeU32(bytes, 20, @intCast(load_size));
1163
1164 const segment_offset = header_size;
1165 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1166 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1167 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1168 writeU64(bytes, segment_offset + 32, text.len);
1169 writeU32(bytes, segment_offset + 64, 1);
1170
1171 const section_offset = segment_offset + segment_command_64_size;
1172 writeName(bytes, section_offset, 16, "__text");
1173 writeName(bytes, section_offset + 16, 16, "__TEXT");
1174 writeU64(bytes, section_offset + 40, text.len);
1175 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1176 writeU32(bytes, section_offset + 52, 4);
1177 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1178 writeU32(bytes, section_offset + 60, 1);
1179 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1180
1181 const symtab_offset = segment_offset + segment_load_size;
1182 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1183 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1184 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1185 writeU32(bytes, symtab_offset + 12, symbol_count);
1186 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1187 writeU32(bytes, symtab_offset + 20, string_table.len);
1188
1189 @memcpy(bytes[text_offset..][0..text.len], text);
1190 writeRelocation(bytes, relocation_offset, .{
1191 .section_index = 0,
1192 .address = 1,
1193 .symbol_number = 1,
1194 .pc_relative = true,
1195 .length = 2,
1196 .external = true,
1197 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH)),
1198 });
1199
1200 writeU32(bytes, symbol_offset, 1);
1201 bytes[symbol_offset + 4] = 0x0f;
1202 bytes[symbol_offset + 5] = 1;
1203 writeU64(bytes, symbol_offset + 8, 0);
1204 const callee_symbol_offset = symbol_offset + nlist_64_size;
1205 writeU32(bytes, callee_symbol_offset, 8);
1206 bytes[callee_symbol_offset + 4] = 0x01;
1207
1208 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1209 return bytes;
1210 }
1211
1212 fn fixtureSignedCallerObject(allocator: Allocator) ![]u8 {
1213 const segment_load_size = segment_command_64_size + section_64_size;
1214 const load_size = segment_load_size + symtab_command_size;
1215 const text_offset = header_size + load_size;
1216 const text = "\x48\x8d\x05\x00\x00\x00\x00\xc3";
1217 const relocation_offset = text_offset + text.len;
1218 const symbol_offset = relocation_offset + relocation_info_size;
1219 const symbol_count = 2;
1220 const string_table = "\x00_start\x00callee\x00";
1221 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1222 const total_size = string_offset + string_table.len;
1223
1224 const bytes = try allocator.alloc(u8, total_size);
1225 @memset(bytes, 0);
1226
1227 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1228 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1229 writeU32(bytes, 12, std.macho.MH_OBJECT);
1230 writeU32(bytes, 16, 2);
1231 writeU32(bytes, 20, @intCast(load_size));
1232
1233 const segment_offset = header_size;
1234 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1235 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1236 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1237 writeU64(bytes, segment_offset + 32, text.len);
1238 writeU32(bytes, segment_offset + 64, 1);
1239
1240 const section_offset = segment_offset + segment_command_64_size;
1241 writeName(bytes, section_offset, 16, "__text");
1242 writeName(bytes, section_offset + 16, 16, "__TEXT");
1243 writeU64(bytes, section_offset + 40, text.len);
1244 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1245 writeU32(bytes, section_offset + 52, 4);
1246 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1247 writeU32(bytes, section_offset + 60, 1);
1248 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1249
1250 const symtab_offset = segment_offset + segment_load_size;
1251 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1252 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1253 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1254 writeU32(bytes, symtab_offset + 12, symbol_count);
1255 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1256 writeU32(bytes, symtab_offset + 20, string_table.len);
1257
1258 @memcpy(bytes[text_offset..][0..text.len], text);
1259 writeRelocation(bytes, relocation_offset, .{
1260 .section_index = 0,
1261 .address = 3,
1262 .symbol_number = 1,
1263 .pc_relative = true,
1264 .length = 2,
1265 .external = true,
1266 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED)),
1267 });
1268
1269 writeU32(bytes, symbol_offset, 1);
1270 bytes[symbol_offset + 4] = 0x0f;
1271 bytes[symbol_offset + 5] = 1;
1272 writeU64(bytes, symbol_offset + 8, 0);
1273 const callee_symbol_offset = symbol_offset + nlist_64_size;
1274 writeU32(bytes, callee_symbol_offset, 8);
1275 bytes[callee_symbol_offset + 4] = 0x01;
1276
1277 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1278 return bytes;
1279 }
1280
1281 fn fixtureUnsignedCallerObject(allocator: Allocator) ![]u8 {
1282 const segment_load_size = segment_command_64_size + section_64_size;
1283 const load_size = segment_load_size + symtab_command_size;
1284 const text_offset = header_size + load_size;
1285 const text = "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xc3";
1286 const relocation_offset = text_offset + text.len;
1287 const symbol_offset = relocation_offset + relocation_info_size;
1288 const symbol_count = 2;
1289 const string_table = "\x00_start\x00callee\x00";
1290 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1291 const total_size = string_offset + string_table.len;
1292
1293 const bytes = try allocator.alloc(u8, total_size);
1294 @memset(bytes, 0);
1295
1296 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1297 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1298 writeU32(bytes, 12, std.macho.MH_OBJECT);
1299 writeU32(bytes, 16, 2);
1300 writeU32(bytes, 20, @intCast(load_size));
1301
1302 const segment_offset = header_size;
1303 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1304 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1305 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1306 writeU64(bytes, segment_offset + 32, text.len);
1307 writeU32(bytes, segment_offset + 64, 1);
1308
1309 const section_offset = segment_offset + segment_command_64_size;
1310 writeName(bytes, section_offset, 16, "__text");
1311 writeName(bytes, section_offset + 16, 16, "__TEXT");
1312 writeU64(bytes, section_offset + 40, text.len);
1313 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1314 writeU32(bytes, section_offset + 52, 4);
1315 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1316 writeU32(bytes, section_offset + 60, 1);
1317 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1318
1319 const symtab_offset = segment_offset + segment_load_size;
1320 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1321 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1322 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1323 writeU32(bytes, symtab_offset + 12, symbol_count);
1324 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1325 writeU32(bytes, symtab_offset + 20, string_table.len);
1326
1327 @memcpy(bytes[text_offset..][0..text.len], text);
1328 writeRelocation(bytes, relocation_offset, .{
1329 .section_index = 0,
1330 .address = 2,
1331 .symbol_number = 1,
1332 .pc_relative = false,
1333 .length = 3,
1334 .external = true,
1335 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED)),
1336 });
1337
1338 writeU32(bytes, symbol_offset, 1);
1339 bytes[symbol_offset + 4] = 0x0f;
1340 bytes[symbol_offset + 5] = 1;
1341 writeU64(bytes, symbol_offset + 8, 0);
1342 const callee_symbol_offset = symbol_offset + nlist_64_size;
1343 writeU32(bytes, callee_symbol_offset, 8);
1344 bytes[callee_symbol_offset + 4] = 0x01;
1345
1346 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1347 return bytes;
1348 }
1349
1350 fn fixtureLocalUnsignedCallerObject(allocator: Allocator) ![]u8 {
1351 const segment_load_size = segment_command_64_size + section_64_size;
1352 const load_size = segment_load_size + symtab_command_size;
1353 const text_offset = header_size + load_size;
1354 const text_address = 0x1000;
1355 const target_address = text_address + 16;
1356 const text = "\x48\xb8\x10\x10\x00\x00\x00\x00\x00\x00\xc3\x90\x90\x90\x90\x90\xc3";
1357 const relocation_offset = text_offset + text.len;
1358 const symbol_offset = relocation_offset + relocation_info_size;
1359 const symbol_count = 1;
1360 const string_table = "\x00_start\x00";
1361 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1362 const total_size = string_offset + string_table.len;
1363
1364 const bytes = try allocator.alloc(u8, total_size);
1365 @memset(bytes, 0);
1366
1367 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1368 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1369 writeU32(bytes, 12, std.macho.MH_OBJECT);
1370 writeU32(bytes, 16, 2);
1371 writeU32(bytes, 20, @intCast(load_size));
1372
1373 const segment_offset = header_size;
1374 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1375 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1376 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1377 writeU64(bytes, segment_offset + 24, text_address);
1378 writeU64(bytes, segment_offset + 32, text.len);
1379 writeU32(bytes, segment_offset + 64, 1);
1380
1381 const section_offset = segment_offset + segment_command_64_size;
1382 writeName(bytes, section_offset, 16, "__text");
1383 writeName(bytes, section_offset + 16, 16, "__TEXT");
1384 writeU64(bytes, section_offset + 32, text_address);
1385 writeU64(bytes, section_offset + 40, text.len);
1386 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1387 writeU32(bytes, section_offset + 52, 4);
1388 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1389 writeU32(bytes, section_offset + 60, 1);
1390 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1391
1392 const symtab_offset = segment_offset + segment_load_size;
1393 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1394 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1395 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1396 writeU32(bytes, symtab_offset + 12, symbol_count);
1397 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1398 writeU32(bytes, symtab_offset + 20, string_table.len);
1399
1400 @memcpy(bytes[text_offset..][0..text.len], text);
1401 writeU64(bytes, text_offset + 2, target_address);
1402 writeRelocation(bytes, relocation_offset, .{
1403 .section_index = 0,
1404 .address = 2,
1405 .symbol_number = 1,
1406 .pc_relative = false,
1407 .length = 3,
1408 .external = false,
1409 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED)),
1410 });
1411
1412 writeU32(bytes, symbol_offset, 1);
1413 bytes[symbol_offset + 4] = 0x0f;
1414 bytes[symbol_offset + 5] = 1;
1415 writeU64(bytes, symbol_offset + 8, text_address);
1416
1417 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1418 return bytes;
1419 }
1420
1421 fn fixtureLocalSignedCallerObject(allocator: Allocator) ![]u8 {
1422 const segment_load_size = segment_command_64_size + 2 * section_64_size;
1423 const load_size = segment_load_size + symtab_command_size;
1424 const caller_text = "\x48\x8d\x05\x00\x00\x00\x00\xc3";
1425 const target_text = "\xc3";
1426 const caller_address = 0x1000;
1427 const target_address = 0x2000;
1428 const text_offset = header_size + load_size;
1429 const target_offset = text_offset + caller_text.len;
1430 const relocation_offset = target_offset + target_text.len;
1431 const symbol_offset = relocation_offset + relocation_info_size;
1432 const symbol_count = 1;
1433 const string_table = "\x00_start\x00";
1434 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1435 const total_size = string_offset + string_table.len;
1436
1437 const bytes = try allocator.alloc(u8, total_size);
1438 @memset(bytes, 0);
1439
1440 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1441 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1442 writeU32(bytes, 12, std.macho.MH_OBJECT);
1443 writeU32(bytes, 16, 2);
1444 writeU32(bytes, 20, @intCast(load_size));
1445
1446 const segment_offset = header_size;
1447 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1448 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1449 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1450 writeU64(bytes, segment_offset + 24, caller_address);
1451 writeU64(bytes, segment_offset + 32, target_address + target_text.len - caller_address);
1452 writeU32(bytes, segment_offset + 64, 2);
1453
1454 const caller_section_offset = segment_offset + segment_command_64_size;
1455 writeName(bytes, caller_section_offset, 16, "__text");
1456 writeName(bytes, caller_section_offset + 16, 16, "__TEXT");
1457 writeU64(bytes, caller_section_offset + 32, caller_address);
1458 writeU64(bytes, caller_section_offset + 40, caller_text.len);
1459 writeU32(bytes, caller_section_offset + 48, @intCast(text_offset));
1460 writeU32(bytes, caller_section_offset + 52, 4);
1461 writeU32(bytes, caller_section_offset + 56, @intCast(relocation_offset));
1462 writeU32(bytes, caller_section_offset + 60, 1);
1463 writeU32(bytes, caller_section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1464
1465 const target_section_offset = caller_section_offset + section_64_size;
1466 writeName(bytes, target_section_offset, 16, "__text2");
1467 writeName(bytes, target_section_offset + 16, 16, "__TEXT");
1468 writeU64(bytes, target_section_offset + 32, target_address);
1469 writeU64(bytes, target_section_offset + 40, target_text.len);
1470 writeU32(bytes, target_section_offset + 48, @intCast(target_offset));
1471 writeU32(bytes, target_section_offset + 52, 4);
1472 writeU32(bytes, target_section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1473
1474 const symtab_offset = segment_offset + segment_load_size;
1475 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1476 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1477 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1478 writeU32(bytes, symtab_offset + 12, symbol_count);
1479 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1480 writeU32(bytes, symtab_offset + 20, string_table.len);
1481
1482 @memcpy(bytes[text_offset..][0..caller_text.len], caller_text);
1483 @memcpy(bytes[target_offset..][0..target_text.len], target_text);
1484 writeI32(bytes, text_offset + 3, target_address - caller_address - 7);
1485 writeRelocation(bytes, relocation_offset, .{
1486 .section_index = 0,
1487 .address = 3,
1488 .symbol_number = 2,
1489 .pc_relative = true,
1490 .length = 2,
1491 .external = false,
1492 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED)),
1493 });
1494
1495 writeU32(bytes, symbol_offset, 1);
1496 bytes[symbol_offset + 4] = 0x0f;
1497 bytes[symbol_offset + 5] = 1;
1498 writeU64(bytes, symbol_offset + 8, caller_address);
1499
1500 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1501 return bytes;
1502 }
1503
1504 fn fixtureBranchCalleeObject(allocator: Allocator) ![]u8 {
1505 const segment_load_size = segment_command_64_size + section_64_size;
1506 const load_size = segment_load_size + symtab_command_size;
1507 const text_offset = header_size + load_size;
1508 const text = "\xc3";
1509 const symbol_offset = text_offset + text.len;
1510 const symbol_count = 1;
1511 const string_table = "\x00callee\x00";
1512 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1513 const total_size = string_offset + string_table.len;
1514
1515 const bytes = try allocator.alloc(u8, total_size);
1516 @memset(bytes, 0);
1517
1518 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1519 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1520 writeU32(bytes, 12, std.macho.MH_OBJECT);
1521 writeU32(bytes, 16, 2);
1522 writeU32(bytes, 20, @intCast(load_size));
1523
1524 const segment_offset = header_size;
1525 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1526 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1527 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1528 writeU64(bytes, segment_offset + 32, text.len);
1529 writeU32(bytes, segment_offset + 64, 1);
1530
1531 const section_offset = segment_offset + segment_command_64_size;
1532 writeName(bytes, section_offset, 16, "__text");
1533 writeName(bytes, section_offset + 16, 16, "__TEXT");
1534 writeU64(bytes, section_offset + 40, text.len);
1535 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1536 writeU32(bytes, section_offset + 52, 4);
1537 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1538 @memcpy(bytes[text_offset..][0..text.len], text);
1539
1540 const symtab_offset = segment_offset + segment_load_size;
1541 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1542 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1543 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1544 writeU32(bytes, symtab_offset + 12, symbol_count);
1545 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1546 writeU32(bytes, symtab_offset + 20, string_table.len);
1547
1548 writeU32(bytes, symbol_offset, 1);
1549 bytes[symbol_offset + 4] = 0x0f;
1550 bytes[symbol_offset + 5] = 1;
1551
1552 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1553 return bytes;
1554 }
1555
1556 fn fixtureRelocationOnlyObject(allocator: Allocator) ![]u8 {
1557 const segment_load_size = segment_command_64_size + section_64_size;
1558 const load_size = segment_load_size;
1559 const text_offset = header_size + load_size;
1560 const text = "\x00\x00\x00\x00\x00\x00\x00\x00";
1561 const relocation_offset = text_offset + text.len;
1562 const total_size = relocation_offset + relocation_info_size;
1563
1564 const bytes = try allocator.alloc(u8, total_size);
1565 @memset(bytes, 0);
1566
1567 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1568 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1569 writeU32(bytes, 12, std.macho.MH_OBJECT);
1570 writeU32(bytes, 16, 1);
1571 writeU32(bytes, 20, @intCast(load_size));
1572
1573 const segment_offset = header_size;
1574 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1575 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1576 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1577 writeU32(bytes, segment_offset + 64, 1);
1578
1579 const section_offset = segment_offset + segment_command_64_size;
1580 writeName(bytes, section_offset, 16, "__text");
1581 writeName(bytes, section_offset + 16, 16, "__TEXT");
1582 writeU64(bytes, section_offset + 40, text.len);
1583 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1584 writeU32(bytes, section_offset + 56, @intCast(relocation_offset));
1585 writeU32(bytes, section_offset + 60, 1);
1586 @memcpy(bytes[text_offset..][0..text.len], text);
1587
1588 writeRelocation(bytes, relocation_offset, .{
1589 .section_index = 0,
1590 .address = 0,
1591 .symbol_number = 1,
1592 .pc_relative = false,
1593 .length = 3,
1594 .external = false,
1595 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED)),
1596 });
1597
1598 return bytes;
1599 }
1600
1601 fn fixtureExecutableObject(allocator: Allocator) ![]u8 {
1602 const segment_load_size = segment_command_64_size + section_64_size;
1603 const load_size = segment_load_size + symtab_command_size;
1604 const text_offset = header_size + load_size;
1605 const text = "\xc3";
1606 const symbol_offset = text_offset + text.len;
1607 const symbol_count = 1;
1608 const string_table = "\x00_start\x00";
1609 const string_offset = symbol_offset + symbol_count * nlist_64_size;
1610 const total_size = string_offset + string_table.len;
1611
1612 const bytes = try allocator.alloc(u8, total_size);
1613 @memset(bytes, 0);
1614
1615 writeU32(bytes, 0, std.macho.MH_MAGIC_64);
1616 writeU32(bytes, 4, @bitCast(std.macho.CPU_TYPE_X86_64));
1617 writeU32(bytes, 12, std.macho.MH_OBJECT);
1618 writeU32(bytes, 16, 2);
1619 writeU32(bytes, 20, @intCast(load_size));
1620
1621 const segment_offset = header_size;
1622 writeU32(bytes, segment_offset, @backingInt(std.macho.LC.SEGMENT_64));
1623 writeU32(bytes, segment_offset + 4, @intCast(segment_load_size));
1624 writeName(bytes, segment_offset + 8, 16, "__TEXT");
1625 writeU64(bytes, segment_offset + 32, text.len);
1626 writeU32(bytes, segment_offset + 64, 1);
1627
1628 const section_offset = segment_offset + segment_command_64_size;
1629 writeName(bytes, section_offset, 16, "__text");
1630 writeName(bytes, section_offset + 16, 16, "__TEXT");
1631 writeU64(bytes, section_offset + 40, text.len);
1632 writeU32(bytes, section_offset + 48, @intCast(text_offset));
1633 writeU32(bytes, section_offset + 52, 4);
1634 writeU32(bytes, section_offset + 64, std.macho.S_REGULAR | std.macho.S_ATTR_PURE_INSTRUCTIONS | std.macho.S_ATTR_SOME_INSTRUCTIONS);
1635 @memcpy(bytes[text_offset..][0..text.len], text);
1636
1637 const symtab_offset = segment_offset + segment_load_size;
1638 writeU32(bytes, symtab_offset, @backingInt(std.macho.LC.SYMTAB));
1639 writeU32(bytes, symtab_offset + 4, symtab_command_size);
1640 writeU32(bytes, symtab_offset + 8, @intCast(symbol_offset));
1641 writeU32(bytes, symtab_offset + 12, symbol_count);
1642 writeU32(bytes, symtab_offset + 16, @intCast(string_offset));
1643 writeU32(bytes, symtab_offset + 20, string_table.len);
1644
1645 writeU32(bytes, symbol_offset, 1);
1646 bytes[symbol_offset + 4] = 0x0f;
1647 bytes[symbol_offset + 5] = 1;
1648
1649 @memcpy(bytes[string_offset..][0..string_table.len], string_table);
1650 return bytes;
1651 }
1652
1653 test "Mach-O parser reads 64-bit object sections and symbols" {
1654 const allocator = std.testing.allocator;
1655 const bytes = try fixtureObject(allocator);
1656 defer allocator.free(bytes);
1657
1658 var object = try parseObject(allocator, bytes);
1659 defer object.deinit(allocator);
1660
1661 try std.testing.expectEqual(model.ObjectFormat.macho, object.target.object_format);
1662 try std.testing.expectEqual(model.Architecture.x86_64, object.target.architecture);
1663 try std.testing.expectEqual(@as(usize, 1), object.sections.len);
1664 try std.testing.expectEqualStrings("__TEXT", object.sections[0].segment_name);
1665 try std.testing.expectEqualStrings("__text", object.sections[0].name);
1666 try std.testing.expectEqual(@as(u64, 8), object.sections[0].size);
1667 try std.testing.expectEqual(@as(u32, 4), object.sections[0].alignment_shift);
1668 try std.testing.expectEqual(@as(usize, 1), object.relocations.len);
1669 try std.testing.expectEqual(@as(usize, 0), object.relocations[0].section_index);
1670 try std.testing.expectEqual(@as(i32, 1), object.relocations[0].address);
1671 try std.testing.expectEqual(@as(u32, 0), object.relocations[0].symbol_number);
1672 try std.testing.expect(object.relocations[0].pc_relative);
1673 try std.testing.expectEqual(@as(u8, 2), object.relocations[0].length);
1674 try std.testing.expect(object.relocations[0].external);
1675 try std.testing.expectEqual(@as(u8, @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH))), object.relocations[0].kind);
1676 try std.testing.expectEqual(@as(usize, 2), object.symbols.len);
1677 try std.testing.expectEqualStrings("_start", object.symbols[0].name);
1678 try std.testing.expect(object.symbols[0].external);
1679 try std.testing.expectEqualStrings("local", object.symbols[1].name);
1680 try std.testing.expect(!object.symbols[1].external);
1681 }
1682
1683 test "Mach-O metadata parser projects sections and symbols" {
1684 const allocator = std.testing.allocator;
1685 const bytes = try fixtureObject(allocator);
1686 defer allocator.free(bytes);
1687
1688 var object = try parseObjectMetadata(allocator, bytes);
1689 defer object.deinit(allocator);
1690
1691 try std.testing.expectEqual(model.ObjectFormat.macho, object.target.object_format);
1692 try std.testing.expectEqual(@as(usize, 1), object.sections.len);
1693 try std.testing.expectEqualStrings("__TEXT", object.sections[0].segment_name);
1694 try std.testing.expectEqualStrings("__text", object.sections[0].name);
1695 try std.testing.expectEqual(@as(u64, 16), object.sections[0].alignment);
1696 try std.testing.expectEqual(@as(usize, 2), object.symbols.len);
1697 try std.testing.expectEqualStrings("_start", object.symbols[0].name);
1698 try std.testing.expect(object.symbols[0].external);
1699 }
1700
1701 test "Mach-O linker emits a minimal x86_64 executable" {
1702 const allocator = std.testing.allocator;
1703 const bytes = try fixtureExecutableObject(allocator);
1704 defer allocator.free(bytes);
1705
1706 var linked = try root.link(
1707 allocator,
1708 &.{.{ .name = "start.o", .bytes = bytes }},
1709 .{
1710 .target = .macos_x86_64_macho,
1711 .image_base = 0x100000000,
1712 },
1713 );
1714 defer linked.deinit(allocator);
1715
1716 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1717 const text_file_offset = try alignForward(header_size + load_size, 16);
1718 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 1)), linked.bytes.len);
1719 try std.testing.expectEqual(std.macho.MH_MAGIC_64, try readU32(linked.bytes, 0));
1720 try std.testing.expectEqual(@as(u32, @bitCast(std.macho.CPU_TYPE_X86_64)), try readU32(linked.bytes, 4));
1721 try std.testing.expectEqual(std.macho.MH_EXECUTE, try readU32(linked.bytes, 12));
1722 try std.testing.expectEqual(@as(u32, 2), try readU32(linked.bytes, 16));
1723 try std.testing.expectEqual(@as(u32, @intCast(load_size)), try readU32(linked.bytes, 20));
1724 try std.testing.expectEqual(std.macho.MH_NOUNDEFS, try readU32(linked.bytes, 24));
1725
1726 const segment_offset = header_size;
1727 try std.testing.expectEqual(@as(u32, @backingInt(std.macho.LC.SEGMENT_64)), try readU32(linked.bytes, segment_offset));
1728 try std.testing.expectEqual(@as(u32, segment_command_64_size + section_64_size), try readU32(linked.bytes, segment_offset + 4));
1729 try std.testing.expectEqualSlices(u8, "__TEXT", linked.bytes[segment_offset + 8 ..][0..6]);
1730 try std.testing.expectEqual(@as(u64, 0x100000000), try readU64(linked.bytes, segment_offset + 24));
1731 try std.testing.expectEqual(@as(u64, 0), try readU64(linked.bytes, segment_offset + 40));
1732 try std.testing.expectEqual(@as(u64, linked.bytes.len), try readU64(linked.bytes, segment_offset + 48));
1733 try std.testing.expectEqual(@as(u32, macho_text_protection), try readU32(linked.bytes, segment_offset + 56));
1734 try std.testing.expectEqual(@as(u32, 1), try readU32(linked.bytes, segment_offset + 64));
1735
1736 const section_offset = segment_offset + segment_command_64_size;
1737 try std.testing.expectEqualSlices(u8, "__text", linked.bytes[section_offset..][0..6]);
1738 try std.testing.expectEqualSlices(u8, "__TEXT", linked.bytes[section_offset + 16 ..][0..6]);
1739 try std.testing.expectEqual(@as(u64, 0x100000000 + text_file_offset), try readU64(linked.bytes, section_offset + 32));
1740 try std.testing.expectEqual(@as(u64, 1), try readU64(linked.bytes, section_offset + 40));
1741 try std.testing.expectEqual(@as(u32, @intCast(text_file_offset)), try readU32(linked.bytes, section_offset + 48));
1742 try std.testing.expectEqual(@as(u32, 4), try readU32(linked.bytes, section_offset + 52));
1743 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset)]);
1744
1745 const entry_offset = section_offset + section_64_size;
1746 try std.testing.expectEqual(@as(u32, @backingInt(std.macho.LC.MAIN)), try readU32(linked.bytes, entry_offset));
1747 try std.testing.expectEqual(@as(u32, entry_point_command_size), try readU32(linked.bytes, entry_offset + 4));
1748 try std.testing.expectEqual(text_file_offset, try readU64(linked.bytes, entry_offset + 8));
1749
1750 try std.testing.expectEqual(model.ObjectFormat.macho, linked.manifest.target.object_format);
1751 try std.testing.expectEqual(@as(usize, 1), linked.manifest.sections.len);
1752 try std.testing.expectEqualStrings("__TEXT,__text", linked.manifest.string(linked.manifest.sections[0].name_id));
1753 try std.testing.expectEqual(@as(u64, 0x100000000 + text_file_offset), linked.manifest.sections[0].address);
1754 try std.testing.expectEqual(@as(usize, 1), linked.manifest.contributions.len);
1755 try std.testing.expectEqualStrings("start.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1756 }
1757
1758 test "Mach-O linker applies x86_64 branch relocations" {
1759 const allocator = std.testing.allocator;
1760 const caller = try fixtureBranchCallerObject(allocator);
1761 defer allocator.free(caller);
1762 const callee = try fixtureBranchCalleeObject(allocator);
1763 defer allocator.free(callee);
1764
1765 var linked = try root.link(
1766 allocator,
1767 &.{
1768 .{ .name = "caller.o", .bytes = caller },
1769 .{ .name = "callee.o", .bytes = callee },
1770 },
1771 .{
1772 .target = .macos_x86_64_macho,
1773 .image_base = 0x100000000,
1774 },
1775 );
1776 defer linked.deinit(allocator);
1777
1778 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1779 const text_file_offset = try alignForward(header_size + load_size, 16);
1780 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 17)), linked.bytes.len);
1781 try std.testing.expectEqual(@as(u64, 17), linked.manifest.sections[0].size);
1782
1783 const displacement_offset = try checkedUsize(text_file_offset + 1);
1784 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[displacement_offset..][0..4], .little));
1785 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 5)]);
1786 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 16)]);
1787 try std.testing.expectEqual(@as(usize, 2), linked.manifest.contributions.len);
1788 try std.testing.expectEqualStrings("caller.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1789 try std.testing.expectEqualStrings("callee.o", linked.manifest.string(linked.manifest.contributions[1].input_name_id));
1790 }
1791
1792 test "Mach-O linker applies x86_64 signed PC-relative relocations" {
1793 const allocator = std.testing.allocator;
1794 const caller = try fixtureSignedCallerObject(allocator);
1795 defer allocator.free(caller);
1796 const callee = try fixtureBranchCalleeObject(allocator);
1797 defer allocator.free(callee);
1798
1799 var linked = try root.link(
1800 allocator,
1801 &.{
1802 .{ .name = "signed.o", .bytes = caller },
1803 .{ .name = "callee.o", .bytes = callee },
1804 },
1805 .{
1806 .target = .macos_x86_64_macho,
1807 .image_base = 0x100000000,
1808 },
1809 );
1810 defer linked.deinit(allocator);
1811
1812 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1813 const text_file_offset = try alignForward(header_size + load_size, 16);
1814 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 17)), linked.bytes.len);
1815 try std.testing.expectEqual(@as(u64, 17), linked.manifest.sections[0].size);
1816
1817 const displacement_offset = try checkedUsize(text_file_offset + 3);
1818 try std.testing.expectEqual(@as(i32, 9), std.mem.readInt(i32, linked.bytes[displacement_offset..][0..4], .little));
1819 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 7)]);
1820 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 16)]);
1821 try std.testing.expectEqual(@as(usize, 2), linked.manifest.contributions.len);
1822 try std.testing.expectEqualStrings("signed.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1823 try std.testing.expectEqualStrings("callee.o", linked.manifest.string(linked.manifest.contributions[1].input_name_id));
1824 }
1825
1826 test "Mach-O linker applies x86_64 unsigned absolute relocations" {
1827 const allocator = std.testing.allocator;
1828 const caller = try fixtureUnsignedCallerObject(allocator);
1829 defer allocator.free(caller);
1830 const callee = try fixtureBranchCalleeObject(allocator);
1831 defer allocator.free(callee);
1832
1833 var linked = try root.link(
1834 allocator,
1835 &.{
1836 .{ .name = "absolute.o", .bytes = caller },
1837 .{ .name = "callee.o", .bytes = callee },
1838 },
1839 .{
1840 .target = .macos_x86_64_macho,
1841 .image_base = 0x100000000,
1842 },
1843 );
1844 defer linked.deinit(allocator);
1845
1846 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1847 const text_file_offset = try alignForward(header_size + load_size, 16);
1848 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 17)), linked.bytes.len);
1849 try std.testing.expectEqual(@as(u64, 17), linked.manifest.sections[0].size);
1850
1851 const immediate_offset = try checkedUsize(text_file_offset + 2);
1852 try std.testing.expectEqual(@as(u64, 0x100000000 + text_file_offset + 16), std.mem.readInt(u64, linked.bytes[immediate_offset..][0..8], .little));
1853 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 10)]);
1854 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 16)]);
1855 try std.testing.expectEqual(@as(usize, 2), linked.manifest.contributions.len);
1856 try std.testing.expectEqualStrings("absolute.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1857 try std.testing.expectEqualStrings("callee.o", linked.manifest.string(linked.manifest.contributions[1].input_name_id));
1858 }
1859
1860 test "Mach-O linker applies x86_64 local unsigned absolute relocations" {
1861 const allocator = std.testing.allocator;
1862 const bytes = try fixtureLocalUnsignedCallerObject(allocator);
1863 defer allocator.free(bytes);
1864
1865 var linked = try root.link(
1866 allocator,
1867 &.{.{ .name = "local-absolute.o", .bytes = bytes }},
1868 .{
1869 .target = .macos_x86_64_macho,
1870 .image_base = 0x100000000,
1871 },
1872 );
1873 defer linked.deinit(allocator);
1874
1875 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1876 const text_file_offset = try alignForward(header_size + load_size, 16);
1877 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 17)), linked.bytes.len);
1878 try std.testing.expectEqual(@as(u64, 17), linked.manifest.sections[0].size);
1879
1880 const immediate_offset = try checkedUsize(text_file_offset + 2);
1881 try std.testing.expectEqual(@as(u64, 0x100000000 + text_file_offset + 16), std.mem.readInt(u64, linked.bytes[immediate_offset..][0..8], .little));
1882 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 10)]);
1883 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 16)]);
1884 try std.testing.expectEqual(@as(usize, 1), linked.manifest.contributions.len);
1885 try std.testing.expectEqualStrings("local-absolute.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1886 }
1887
1888 test "Mach-O linker applies x86_64 local signed PC-relative relocations" {
1889 const allocator = std.testing.allocator;
1890 const bytes = try fixtureLocalSignedCallerObject(allocator);
1891 defer allocator.free(bytes);
1892
1893 var linked = try root.link(
1894 allocator,
1895 &.{.{ .name = "local-signed.o", .bytes = bytes }},
1896 .{
1897 .target = .macos_x86_64_macho,
1898 .image_base = 0x100000000,
1899 },
1900 );
1901 defer linked.deinit(allocator);
1902
1903 const load_size = segment_command_64_size + section_64_size + entry_point_command_size;
1904 const text_file_offset = try alignForward(header_size + load_size, 16);
1905 try std.testing.expectEqual(@as(usize, try checkedUsize(text_file_offset + 17)), linked.bytes.len);
1906 try std.testing.expectEqual(@as(u64, 17), linked.manifest.sections[0].size);
1907
1908 const displacement_offset = try checkedUsize(text_file_offset + 3);
1909 try std.testing.expectEqual(@as(i32, 9), std.mem.readInt(i32, linked.bytes[displacement_offset..][0..4], .little));
1910 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 7)]);
1911 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[try checkedUsize(text_file_offset + 16)]);
1912 try std.testing.expectEqual(@as(usize, 2), linked.manifest.contributions.len);
1913 try std.testing.expectEqualStrings("local-signed.o", linked.manifest.string(linked.manifest.contributions[0].input_name_id));
1914 try std.testing.expectEqualStrings("local-signed.o", linked.manifest.string(linked.manifest.contributions[1].input_name_id));
1915 }
1916
1917 test "Mach-O linker records unsupported relocation diagnostics" {
1918 const allocator = std.testing.allocator;
1919 const bytes = try fixtureObject(allocator);
1920 defer allocator.free(bytes);
1921
1922 const relocation_offset = header_size + segment_command_64_size + section_64_size + symtab_command_size + 8;
1923 writeRelocation(bytes, relocation_offset, .{
1924 .section_index = 0,
1925 .address = 1,
1926 .symbol_number = 0,
1927 .pc_relative = true,
1928 .length = 2,
1929 .external = true,
1930 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT_LOAD)),
1931 });
1932
1933 var diagnostics: model.Diagnostics = .{};
1934 try std.testing.expectError(
1935 error.UnsupportedRelocation,
1936 root.link(
1937 allocator,
1938 &.{.{ .name = "call.o", .bytes = bytes }},
1939 .{
1940 .target = .macos_x86_64_macho,
1941 .image_base = 0x100000000,
1942 .diagnostics = &diagnostics,
1943 },
1944 ),
1945 );
1946
1947 const failure = diagnostics.linkFailure(error.UnsupportedRelocation);
1948 const relocation = switch (failure) {
1949 .unsupported_relocation => |relocation| relocation,
1950 else => return error.ExpectedUnsupportedRelocationDiagnostic,
1951 };
1952 try std.testing.expectEqual(@as(u32, 3), relocation.relocation_type);
1953 try std.testing.expectEqualStrings("call.o", relocation.input_name);
1954 try std.testing.expectEqualStrings("__text", relocation.section_name);
1955 try std.testing.expectEqualStrings("_start", relocation.symbol_name);
1956 }
1957
1958 test "Mach-O parser rejects truncated load commands" {
1959 const allocator = std.testing.allocator;
1960 const bytes = try fixtureObject(allocator);
1961 defer allocator.free(bytes);
1962
1963 try std.testing.expectError(error.InvalidRange, parseObject(allocator, bytes[0 .. header_size + 8]));
1964 }
1965
1966 test "Mach-O parser rejects truncated relocation tables" {
1967 const allocator = std.testing.allocator;
1968 const bytes = try fixtureObject(allocator);
1969 defer allocator.free(bytes);
1970
1971 const section_offset = header_size + segment_command_64_size;
1972 writeU32(bytes, section_offset + 56, @intCast(bytes.len - relocation_info_size + 1));
1973 writeU32(bytes, section_offset + 60, 1);
1974
1975 try std.testing.expectError(error.InvalidRange, parseObject(allocator, bytes));
1976 }
1977
1978 test "Mach-O parser reads relocation entries without a symbol table" {
1979 const allocator = std.testing.allocator;
1980 const bytes = try fixtureRelocationOnlyObject(allocator);
1981 defer allocator.free(bytes);
1982
1983 var object = try parseObject(allocator, bytes);
1984 defer object.deinit(allocator);
1985
1986 try std.testing.expectEqual(@as(usize, 1), object.relocations.len);
1987 try std.testing.expectEqual(@as(i32, 0), object.relocations[0].address);
1988 try std.testing.expectEqual(@as(u32, 1), object.relocations[0].symbol_number);
1989 try std.testing.expect(!object.relocations[0].external);
1990 try std.testing.expectEqual(@as(u8, 3), object.relocations[0].length);
1991 }
1992
1993 test "Mach-O parser rejects invalid external relocation symbol references" {
1994 const allocator = std.testing.allocator;
1995 const bytes = try fixtureObject(allocator);
1996 defer allocator.free(bytes);
1997
1998 const relocation_offset = header_size + segment_command_64_size + section_64_size + symtab_command_size + 8;
1999 writeRelocation(bytes, relocation_offset, .{
2000 .section_index = 0,
2001 .address = 1,
2002 .symbol_number = 2,
2003 .pc_relative = true,
2004 .length = 2,
2005 .external = true,
2006 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH)),
2007 });
2008
2009 try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
2010 }
2011
2012 test "Mach-O parser rejects invalid local relocation section references" {
2013 const allocator = std.testing.allocator;
2014 const bytes = try fixtureRelocationOnlyObject(allocator);
2015 defer allocator.free(bytes);
2016
2017 const relocation_offset = header_size + segment_command_64_size + section_64_size + 8;
2018 writeRelocation(bytes, relocation_offset, .{
2019 .section_index = 0,
2020 .address = 0,
2021 .symbol_number = 2,
2022 .pc_relative = false,
2023 .length = 3,
2024 .external = false,
2025 .kind = @intCast(@backingInt(std.macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED)),
2026 });
2027
2028 try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
2029 }
2030
2031 test "Mach-O parser rejects scattered relocation entries" {
2032 const allocator = std.testing.allocator;
2033 const bytes = try fixtureObject(allocator);
2034 defer allocator.free(bytes);
2035
2036 const relocation_offset = header_size + segment_command_64_size + section_64_size + symtab_command_size + 8;
2037 writeU32(bytes, relocation_offset, 0x8000_0000);
2038
2039 try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
2040 }
2041
2042 test "Mach-O parser rejects relocations outside section data" {
2043 const allocator = std.testing.allocator;
2044 const bytes = try fixtureObject(allocator);
2045 defer allocator.free(bytes);
2046
2047 const relocation_offset = header_size + segment_command_64_size + section_64_size + symtab_command_size + 8;
2048 writeI32(bytes, relocation_offset, 5);
2049
2050 try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
2051 }
2052
2053 test "Mach-O parser rejects invalid symbol section references" {
2054 const allocator = std.testing.allocator;
2055 const bytes = try fixtureObject(allocator);
2056 defer allocator.free(bytes);
2057
2058 const symbol_offset = header_size + segment_command_64_size + section_64_size + symtab_command_size + 8 + relocation_info_size;
2059 bytes[symbol_offset + 5] = 2;
2060
2061 try std.testing.expectError(error.InvalidObject, parseObject(allocator, bytes));
2062 }