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 }