lib/choir/src/backends/debug.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const ir = @import("../core/root.zig");
   3 const sys = @import("sys");
   4 
   5 const Allocator = std.mem.Allocator;
   6 
   7 pub const LineInfo = struct {
   8     file: ?[]const u8,
   9     line: u32,
  10     column: u32,
  11     name: ?[]const u8,
  12     op_name: []const u8,
  13 };
  14 
  15 pub const LineEntry = struct {
  16     offset: u32,
  17     info: LineInfo,
  18 };
  19 
  20 const LocationView = struct {
  21     file: ?[]const u8 = null,
  22     line: u32 = 0,
  23     column: u32 = 0,
  24     name: ?[]const u8 = null,
  25 };
  26 
  27 const StringTable = struct {
  28     allocator: Allocator,
  29     items: std.ArrayListUnmanaged([]const u8),
  30     index: std.StringHashMapUnmanaged(usize),
  31 
  32     fn init(allocator: Allocator) StringTable {
  33         return .{
  34             .allocator = allocator,
  35             .items = .empty,
  36             .index = .{},
  37         };
  38     }
  39 
  40     fn deinit(self: *StringTable) void {
  41         for (self.items.items) |item| {
  42             self.allocator.free(item);
  43         }
  44         self.items.deinit(self.allocator);
  45         self.index.deinit(self.allocator);
  46     }
  47 
  48     fn reset(self: *StringTable) void {
  49         for (self.items.items) |item| {
  50             self.allocator.free(item);
  51         }
  52         self.items.clearRetainingCapacity();
  53         self.index.clearRetainingCapacity();
  54     }
  55 
  56     fn take(self: *StringTable) StringTable {
  57         const out = self.*;
  58         self.* = StringTable.init(self.allocator);
  59         return out;
  60     }
  61 
  62     fn intern(self: *StringTable, s: []const u8) Allocator.Error![]const u8 {
  63         if (s.len == 0) return s;
  64         if (self.index.get(s)) |idx| {
  65             return self.items.items[idx];
  66         }
  67         const owned = try self.allocator.dupe(u8, s);
  68         errdefer self.allocator.free(owned);
  69         try self.items.append(self.allocator, owned);
  70         errdefer _ = self.items.pop();
  71         try self.index.put(self.allocator, owned, self.items.items.len - 1);
  72         return owned;
  73     }
  74 };
  75 
  76 pub const LineTable = struct {
  77     allocator: Allocator,
  78     entries: []LineEntry,
  79     strings: StringTable,
  80 
  81     pub fn deinit(self: *LineTable) void {
  82         self.strings.deinit();
  83         if (self.entries.len != 0) {
  84             self.allocator.free(self.entries);
  85         }
  86         self.* = undefined;
  87     }
  88 
  89     pub fn isEmpty(self: *const LineTable) bool {
  90         return self.entries.len == 0;
  91     }
  92 
  93     pub fn lookup(self: *const LineTable, offset: u32) ?LineInfo {
  94         if (self.entries.len == 0) return null;
  95 
  96         var lo: usize = 0;
  97         var hi: usize = self.entries.len;
  98         while (lo < hi) {
  99             const mid = lo + (hi - lo) / 2;
 100             if (self.entries[mid].offset > offset) {
 101                 hi = mid;
 102             } else {
 103                 lo = mid + 1;
 104             }
 105         }
 106 
 107         if (lo == 0) return null;
 108         return self.entries[lo - 1].info;
 109     }
 110 };
 111 
 112 pub const LineTableBuilder = struct {
 113     allocator: Allocator,
 114     entries: std.ArrayListUnmanaged(LineEntry),
 115     strings: StringTable,
 116 
 117     pub fn init(allocator: Allocator) LineTableBuilder {
 118         return .{
 119             .allocator = allocator,
 120             .entries = .empty,
 121             .strings = StringTable.init(allocator),
 122         };
 123     }
 124 
 125     pub fn deinit(self: *LineTableBuilder) void {
 126         self.entries.deinit(self.allocator);
 127         self.strings.deinit();
 128     }
 129 
 130     pub fn reset(self: *LineTableBuilder) void {
 131         self.entries.clearRetainingCapacity();
 132         self.strings.reset();
 133     }
 134 
 135     pub fn record(self: *LineTableBuilder, offset: u32, loc: ir.Location, op_name: []const u8) Allocator.Error!void {
 136         const view = pickLocation(loc);
 137 
 138         const info = LineInfo{
 139             .file = if (view.file) |file| try self.strings.intern(file) else null,
 140             .line = view.line,
 141             .column = view.column,
 142             .name = if (view.name) |name| try self.strings.intern(name) else null,
 143             .op_name = try self.strings.intern(op_name),
 144         };
 145 
 146         if (self.entries.items.len > 0) {
 147             const last = &self.entries.items[self.entries.items.len - 1];
 148             if (last.offset == offset) {
 149                 last.* = .{ .offset = offset, .info = info };
 150                 return;
 151             }
 152             if (lineInfoEql(last.info, info)) return;
 153         }
 154 
 155         try self.entries.append(self.allocator, .{ .offset = offset, .info = info });
 156     }
 157 
 158     pub fn finish(self: *LineTableBuilder) Allocator.Error!LineTable {
 159         var entries: []LineEntry = &.{};
 160         if (self.entries.items.len != 0) {
 161             entries = try self.entries.toOwnedSlice(self.allocator);
 162         } else if (self.entries.capacity != 0) {
 163             self.entries.deinit(self.allocator);
 164         } else {
 165             self.entries.clearRetainingCapacity();
 166         }
 167         const strings = self.strings.take();
 168         self.entries = .empty;
 169         return .{
 170             .allocator = self.allocator,
 171             .entries = entries,
 172             .strings = strings,
 173         };
 174     }
 175 };
 176 
 177 fn lineInfoEql(a: LineInfo, b: LineInfo) bool {
 178     return optionalStrEql(a.file, b.file) and
 179         a.line == b.line and
 180         a.column == b.column and
 181         optionalStrEql(a.name, b.name) and
 182         std.mem.eql(u8, a.op_name, b.op_name);
 183 }
 184 
 185 fn optionalStrEql(a: ?[]const u8, b: ?[]const u8) bool {
 186     if (a == null and b == null) return true;
 187     if (a == null or b == null) return false;
 188     return std.mem.eql(u8, a.?, b.?);
 189 }
 190 
 191 fn pickLocation(loc: ir.Location) LocationView {
 192     return switch (loc) {
 193         .unknown => .{},
 194         .file => |f| .{
 195             .file = f.filename,
 196             .line = f.line,
 197             .column = f.column,
 198         },
 199         .file_range => |range| .{
 200             .file = range.filename,
 201             .line = range.start.line,
 202             .column = range.start.column,
 203         },
 204         .name => |n| blk: {
 205             var view = if (n.child) |child| pickLocation(child.*) else LocationView{};
 206             view.name = n.name;
 207             break :blk view;
 208         },
 209         .fused => |f| blk: {
 210             var fallback: LocationView = .{};
 211             for (f.locations) |item| {
 212                 const view = pickLocation(item);
 213                 if (view.file != null) break :blk view;
 214                 if (fallback.name == null and view.name != null) {
 215                     fallback = view;
 216                 }
 217             }
 218             break :blk fallback;
 219         },
 220         .call_site => |c| blk: {
 221             const caller = pickLocation(c.caller.*);
 222             if (caller.file != null or caller.name != null) break :blk caller;
 223             break :blk pickLocation(c.callee.*);
 224         },
 225     };
 226 }
 227 
 228 const dwarf = struct {
 229     const version: u16 = 4;
 230     const addr_size: u8 = 8;
 231 
 232     const TAG_compile_unit: u64 = 0x11;
 233     const TAG_subprogram: u64 = 0x2e;
 234 
 235     const CHILDREN_no: u8 = 0;
 236     const CHILDREN_yes: u8 = 1;
 237 
 238     const AT_name: u64 = 0x03;
 239     const AT_stmt_list: u64 = 0x10;
 240     const AT_low_pc: u64 = 0x11;
 241     const AT_high_pc: u64 = 0x12;
 242     const AT_comp_dir: u64 = 0x1b;
 243     const AT_decl_file: u64 = 0x3a;
 244     const AT_decl_line: u64 = 0x3b;
 245 
 246     const FORM_addr: u64 = 0x01;
 247     const FORM_data1: u64 = 0x0b;
 248     const FORM_data4: u64 = 0x06;
 249     const FORM_sec_offset: u64 = 0x17;
 250     const FORM_string: u64 = 0x08;
 251 
 252     const LNS_copy: u8 = 1;
 253     const LNS_advance_pc: u8 = 2;
 254     const LNS_advance_line: u8 = 3;
 255     const LNS_set_file: u8 = 4;
 256     const LNS_set_column: u8 = 5;
 257     const LNS_const_add_pc: u8 = 8;
 258     const LNS_fixed_advance_pc: u8 = 9;
 259 
 260     const LNE_end_sequence: u8 = 1;
 261     const LNE_set_address: u8 = 2;
 262 };
 263 
 264 const ElfStringTable = struct {
 265     data: std.ArrayListUnmanaged(u8) = .empty,
 266 
 267     fn init(allocator: Allocator) !ElfStringTable {
 268         var table = ElfStringTable{};
 269         try table.data.append(allocator, 0);
 270         return table;
 271     }
 272 
 273     fn deinit(self: *ElfStringTable, allocator: Allocator) void {
 274         self.data.deinit(allocator);
 275     }
 276 
 277     fn add(self: *ElfStringTable, allocator: Allocator, name: []const u8) !u32 {
 278         const offset: u32 = @intCast(self.data.items.len);
 279         try self.data.appendSlice(allocator, name);
 280         try self.data.append(allocator, 0);
 281         return offset;
 282     }
 283 };
 284 
 285 pub const JitDebugHandle = struct {
 286     entry: ?*JitCodeEntry,
 287     object: []u8,
 288 
 289     pub fn deinit(self: *JitDebugHandle, allocator: Allocator) void {
 290         if (self.entry) |entry| {
 291             unregisterGdbEntry(entry);
 292             allocator.destroy(entry);
 293         }
 294         allocator.free(self.object);
 295         self.* = undefined;
 296     }
 297 };
 298 
 299 pub const JitDebugSupport = struct {
 300     supported: bool,
 301     reason: ?[]const u8,
 302     note: ?[]const u8,
 303 };
 304 
 305 pub fn jitDebugSupportFor(os_tag: std.Target.Os.Tag, arch: std.Target.Cpu.Arch) JitDebugSupport {
 306     const support = (sys.capabilities.Capabilities{ .os = os_tag, .arch = arch }).jitDebugSupport();
 307     return .{ .supported = support.supported, .reason = support.reason, .note = support.note };
 308 }
 309 
 310 pub fn jitDebugSupport(arch: std.Target.Cpu.Arch) JitDebugSupport {
 311     return jitDebugSupportFor(sys.capabilities.current.os, arch);
 312 }
 313 
 314 pub fn supportsJitDebugInfo(arch: std.Target.Cpu.Arch) bool {
 315     return jitDebugSupport(arch).supported;
 316 }
 317 
 318 pub fn registerJitDebugInfo(
 319     allocator: Allocator,
 320     arch: std.Target.Cpu.Arch,
 321     code_addr: usize,
 322     code: []const u8,
 323     table: *const LineTable,
 324     func_name: []const u8,
 325 ) Allocator.Error!?JitDebugHandle {
 326     if (!supportsJitDebugInfo(arch) or table.isEmpty()) return null;
 327 
 328     const object = try buildElfDebugObject(allocator, arch, code_addr, code, table, func_name);
 329     errdefer allocator.free(object);
 330 
 331     const entry = try allocator.create(JitCodeEntry);
 332     errdefer allocator.destroy(entry);
 333 
 334     registerGdbEntry(entry, object);
 335 
 336     return .{
 337         .entry = entry,
 338         .object = object,
 339     };
 340 }
 341 
 342 const JitCodeEntry = extern struct {
 343     next: ?*JitCodeEntry,
 344     prev: ?*JitCodeEntry,
 345     symfile_addr: [*]const u8,
 346     symfile_size: u64,
 347 };
 348 
 349 const JitDescriptor = extern struct {
 350     version: u32,
 351     action_flag: u32,
 352     relevant_entry: ?*JitCodeEntry,
 353     first_entry: ?*JitCodeEntry,
 354 };
 355 
 356 const JitAction = enum(u32) {
 357     no_action = 0,
 358     register_fn = 1,
 359     unregister_fn = 2,
 360 };
 361 
 362 /// The GDB JIT interface descriptor. A debugger finds it by this exact symbol name and reads it
 363 /// when it stops at `__jit_debug_register_code`. Every runtime in this process shares it, and
 364 /// `registration_mutex` guards every change.
 365 export var __jit_debug_descriptor: JitDescriptor = .{
 366     .version = 1,
 367     .action_flag = @backingInt(JitAction.no_action),
 368     .relevant_entry = null,
 369     .first_entry = null,
 370 };
 371 
 372 /// The GDB JIT interface breakpoint. A debugger finds it by this exact symbol name and stops here
 373 /// to read `__jit_debug_descriptor`. The body keeps a call to it from being dropped, and a caller
 374 /// reaches it through `@call(.never_inline, ...)`, so that an optimized build keeps the call.
 375 export fn __jit_debug_register_code() callconv(.c) void {
 376     std.mem.doNotOptimizeAway(&__jit_debug_descriptor);
 377 }
 378 
 379 /// Serializes every change to the entry list and the notification that follows it. Every runtime
 380 /// in this process registers into that one list.
 381 var registration_mutex: sys.thread.Mutex = .{};
 382 
 383 fn registerGdbEntry(entry: *JitCodeEntry, object: []const u8) void {
 384     std.debug.assert(object.len != 0);
 385     registration_mutex.lock();
 386     defer registration_mutex.unlock();
 387     std.debug.assert(__jit_debug_descriptor.action_flag == @backingInt(JitAction.no_action));
 388 
 389     entry.* = .{
 390         .next = __jit_debug_descriptor.first_entry,
 391         .prev = null,
 392         .symfile_addr = object.ptr,
 393         .symfile_size = object.len,
 394     };
 395     if (__jit_debug_descriptor.first_entry) |first| {
 396         first.prev = entry;
 397     }
 398     __jit_debug_descriptor.first_entry = entry;
 399     notifyDebugger(entry, .register_fn);
 400 }
 401 
 402 fn unregisterGdbEntry(entry: *JitCodeEntry) void {
 403     registration_mutex.lock();
 404     defer registration_mutex.unlock();
 405     std.debug.assert(__jit_debug_descriptor.action_flag == @backingInt(JitAction.no_action));
 406 
 407     if (entry.prev) |prev| {
 408         prev.next = entry.next;
 409     } else {
 410         __jit_debug_descriptor.first_entry = entry.next;
 411     }
 412     if (entry.next) |next| {
 413         next.prev = entry.prev;
 414     }
 415     notifyDebugger(entry, .unregister_fn);
 416 }
 417 
 418 /// Notifies a debugger of `action` on `entry` by calling `__jit_debug_register_code`, and leaves
 419 /// `action_flag` at `no_action`. The caller must hold `registration_mutex`.
 420 fn notifyDebugger(entry: *JitCodeEntry, action: JitAction) void {
 421     std.debug.assert(action != .no_action);
 422     __jit_debug_descriptor.relevant_entry = entry;
 423     __jit_debug_descriptor.action_flag = @backingInt(action);
 424     @call(.never_inline, __jit_debug_register_code, .{});
 425     __jit_debug_descriptor.action_flag = @backingInt(JitAction.no_action);
 426 }
 427 
 428 const DebugLineSection = struct {
 429     bytes: []u8,
 430     primary_name: []const u8,
 431     primary_dir: []const u8,
 432     primary_file_index: u32,
 433     primary_line: u32,
 434 };
 435 
 436 const FileEntry = struct {
 437     name: []const u8,
 438     dir_index: u32,
 439 };
 440 
 441 pub fn buildElfDebugObject(
 442     allocator: Allocator,
 443     arch: std.Target.Cpu.Arch,
 444     code_addr: usize,
 445     code: []const u8,
 446     table: *const LineTable,
 447     func_name: []const u8,
 448 ) Allocator.Error![]u8 {
 449     const machine = elfMachineForArch(arch) orelse return error.OutOfMemory;
 450 
 451     var line_section = try buildDebugLineSection(allocator, code_addr, table);
 452     defer allocator.free(line_section.bytes);
 453 
 454     const abbrev = try buildDebugAbbrev(allocator);
 455     defer allocator.free(abbrev);
 456 
 457     const debug_info = try buildDebugInfo(
 458         allocator,
 459         code_addr,
 460         code.len,
 461         &line_section,
 462         func_name,
 463     );
 464     defer allocator.free(debug_info);
 465 
 466     var shstrtab = try ElfStringTable.init(allocator);
 467     defer shstrtab.deinit(allocator);
 468 
 469     const sh_name_text = try shstrtab.add(allocator, ".text");
 470     const sh_name_debug_info = try shstrtab.add(allocator, ".debug_info");
 471     const sh_name_debug_abbrev = try shstrtab.add(allocator, ".debug_abbrev");
 472     const sh_name_debug_line = try shstrtab.add(allocator, ".debug_line");
 473     const sh_name_symtab = try shstrtab.add(allocator, ".symtab");
 474     const sh_name_strtab = try shstrtab.add(allocator, ".strtab");
 475     const sh_name_shstrtab = try shstrtab.add(allocator, ".shstrtab");
 476 
 477     var strtab = try ElfStringTable.init(allocator);
 478     defer strtab.deinit(allocator);
 479     const func_name_offset = try strtab.add(allocator, func_name);
 480 
 481     var symbols = std.ArrayListUnmanaged(std.elf.Elf64_Sym).empty;
 482     defer symbols.deinit(allocator);
 483     try symbols.append(allocator, .{
 484         .st_name = 0,
 485         .st_info = 0,
 486         .st_other = 0,
 487         .st_shndx = 0,
 488         .st_value = 0,
 489         .st_size = 0,
 490     });
 491     try symbols.append(allocator, .{
 492         .st_name = 0,
 493         .st_info = (@as(u8, std.elf.STB_LOCAL) << 4) | @as(u8, std.elf.STT_SECTION),
 494         .st_other = 0,
 495         .st_shndx = 1,
 496         .st_value = 0,
 497         .st_size = 0,
 498     });
 499     try symbols.append(allocator, .{
 500         .st_name = func_name_offset,
 501         .st_info = (@as(u8, std.elf.STB_GLOBAL) << 4) | @as(u8, std.elf.STT_FUNC),
 502         .st_other = 0,
 503         .st_shndx = 1,
 504         .st_value = @intCast(code_addr),
 505         .st_size = @intCast(code.len),
 506     });
 507 
 508     const symtab_bytes = std.mem.sliceAsBytes(symbols.items);
 509     const strtab_bytes = strtab.data.items;
 510     const shstrtab_bytes = shstrtab.data.items;
 511 
 512     const header_size = @sizeOf(std.elf.Elf64_Ehdr);
 513     const shdr_size = @sizeOf(std.elf.Elf64_Shdr);
 514 
 515     var offset: usize = alignForward(header_size, 16);
 516     const text_offset = offset;
 517     offset += code.len;
 518     offset = alignForward(offset, 8);
 519 
 520     const debug_info_offset = offset;
 521     offset += debug_info.len;
 522     offset = alignForward(offset, 8);
 523 
 524     const debug_abbrev_offset = offset;
 525     offset += abbrev.len;
 526     offset = alignForward(offset, 8);
 527 
 528     const debug_line_offset = offset;
 529     offset += line_section.bytes.len;
 530     offset = alignForward(offset, 8);
 531 
 532     const symtab_offset = offset;
 533     offset += symtab_bytes.len;
 534     offset = alignForward(offset, 8);
 535 
 536     const strtab_offset = offset;
 537     offset += strtab_bytes.len;
 538     offset = alignForward(offset, 8);
 539 
 540     const shstrtab_offset = offset;
 541     offset += shstrtab_bytes.len;
 542     offset = alignForward(offset, 8);
 543 
 544     const shoff = offset;
 545     const shnum: u16 = 8;
 546     const total_size = shoff + shdr_size * shnum;
 547 
 548     var buffer = try allocator.alloc(u8, total_size);
 549     @memset(buffer, 0);
 550 
 551     std.mem.copyForwards(u8, buffer[text_offset .. text_offset + code.len], code);
 552     std.mem.copyForwards(u8, buffer[debug_info_offset .. debug_info_offset + debug_info.len], debug_info);
 553     std.mem.copyForwards(u8, buffer[debug_abbrev_offset .. debug_abbrev_offset + abbrev.len], abbrev);
 554     std.mem.copyForwards(u8, buffer[debug_line_offset .. debug_line_offset + line_section.bytes.len], line_section.bytes);
 555     std.mem.copyForwards(u8, buffer[symtab_offset .. symtab_offset + symtab_bytes.len], symtab_bytes);
 556     std.mem.copyForwards(u8, buffer[strtab_offset .. strtab_offset + strtab_bytes.len], strtab_bytes);
 557     std.mem.copyForwards(u8, buffer[shstrtab_offset .. shstrtab_offset + shstrtab_bytes.len], shstrtab_bytes);
 558 
 559     var ident: [std.elf.EI_NIDENT]u8 = @splat(0);
 560     ident[0] = 0x7f;
 561     ident[1] = 'E';
 562     ident[2] = 'L';
 563     ident[3] = 'F';
 564     ident[std.elf.EI_CLASS] = std.elf.ELFCLASS64;
 565     ident[std.elf.EI_DATA] = std.elf.ELFDATA2LSB;
 566     ident[std.elf.EI_VERSION] = 1;
 567 
 568     const header = std.elf.Elf64_Ehdr{
 569         .e_ident = ident,
 570         .e_type = std.elf.ET.DYN,
 571         .e_machine = machine,
 572         .e_version = 1,
 573         .e_entry = 0,
 574         .e_phoff = 0,
 575         .e_shoff = @intCast(shoff),
 576         .e_flags = 0,
 577         .e_ehsize = @intCast(header_size),
 578         .e_phentsize = 0,
 579         .e_phnum = 0,
 580         .e_shentsize = @intCast(shdr_size),
 581         .e_shnum = shnum,
 582         .e_shstrndx = 7,
 583     };
 584 
 585     std.mem.copyForwards(u8, buffer[0..header_size], std.mem.asBytes(&header));
 586 
 587     var shdrs: [8]std.elf.Elf64_Shdr = undefined;
 588     shdrs[0] = .{
 589         .sh_name = 0,
 590         .sh_type = std.elf.SHT_NULL,
 591         .sh_flags = 0,
 592         .sh_addr = 0,
 593         .sh_offset = 0,
 594         .sh_size = 0,
 595         .sh_link = 0,
 596         .sh_info = 0,
 597         .sh_addralign = 0,
 598         .sh_entsize = 0,
 599     };
 600     shdrs[1] = .{
 601         .sh_name = sh_name_text,
 602         .sh_type = std.elf.SHT_PROGBITS,
 603         .sh_flags = std.elf.SHF_ALLOC | std.elf.SHF_EXECINSTR,
 604         .sh_addr = @intCast(code_addr),
 605         .sh_offset = @intCast(text_offset),
 606         .sh_size = @intCast(code.len),
 607         .sh_link = 0,
 608         .sh_info = 0,
 609         .sh_addralign = 16,
 610         .sh_entsize = 0,
 611     };
 612     shdrs[2] = .{
 613         .sh_name = sh_name_debug_info,
 614         .sh_type = std.elf.SHT_PROGBITS,
 615         .sh_flags = 0,
 616         .sh_addr = 0,
 617         .sh_offset = @intCast(debug_info_offset),
 618         .sh_size = @intCast(debug_info.len),
 619         .sh_link = 0,
 620         .sh_info = 0,
 621         .sh_addralign = 1,
 622         .sh_entsize = 0,
 623     };
 624     shdrs[3] = .{
 625         .sh_name = sh_name_debug_abbrev,
 626         .sh_type = std.elf.SHT_PROGBITS,
 627         .sh_flags = 0,
 628         .sh_addr = 0,
 629         .sh_offset = @intCast(debug_abbrev_offset),
 630         .sh_size = @intCast(abbrev.len),
 631         .sh_link = 0,
 632         .sh_info = 0,
 633         .sh_addralign = 1,
 634         .sh_entsize = 0,
 635     };
 636     shdrs[4] = .{
 637         .sh_name = sh_name_debug_line,
 638         .sh_type = std.elf.SHT_PROGBITS,
 639         .sh_flags = 0,
 640         .sh_addr = 0,
 641         .sh_offset = @intCast(debug_line_offset),
 642         .sh_size = @intCast(line_section.bytes.len),
 643         .sh_link = 0,
 644         .sh_info = 0,
 645         .sh_addralign = 1,
 646         .sh_entsize = 0,
 647     };
 648     shdrs[5] = .{
 649         .sh_name = sh_name_symtab,
 650         .sh_type = std.elf.SHT_SYMTAB,
 651         .sh_flags = 0,
 652         .sh_addr = 0,
 653         .sh_offset = @intCast(symtab_offset),
 654         .sh_size = @intCast(symtab_bytes.len),
 655         .sh_link = 6,
 656         .sh_info = 2,
 657         .sh_addralign = 8,
 658         .sh_entsize = @sizeOf(std.elf.Elf64_Sym),
 659     };
 660     shdrs[6] = .{
 661         .sh_name = sh_name_strtab,
 662         .sh_type = std.elf.SHT_STRTAB,
 663         .sh_flags = 0,
 664         .sh_addr = 0,
 665         .sh_offset = @intCast(strtab_offset),
 666         .sh_size = @intCast(strtab_bytes.len),
 667         .sh_link = 0,
 668         .sh_info = 0,
 669         .sh_addralign = 1,
 670         .sh_entsize = 0,
 671     };
 672     shdrs[7] = .{
 673         .sh_name = sh_name_shstrtab,
 674         .sh_type = std.elf.SHT_STRTAB,
 675         .sh_flags = 0,
 676         .sh_addr = 0,
 677         .sh_offset = @intCast(shstrtab_offset),
 678         .sh_size = @intCast(shstrtab_bytes.len),
 679         .sh_link = 0,
 680         .sh_info = 0,
 681         .sh_addralign = 1,
 682         .sh_entsize = 0,
 683     };
 684 
 685     var shdr_offset = shoff;
 686     for (shdrs) |shdr| {
 687         std.mem.copyForwards(u8, buffer[shdr_offset .. shdr_offset + shdr_size], std.mem.asBytes(&shdr));
 688         shdr_offset += shdr_size;
 689     }
 690 
 691     return buffer;
 692 }
 693 
 694 fn buildDebugLineSection(
 695     allocator: Allocator,
 696     code_addr: usize,
 697     table: *const LineTable,
 698 ) Allocator.Error!DebugLineSection {
 699     var dirs = std.ArrayListUnmanaged([]const u8).empty;
 700     defer dirs.deinit(allocator);
 701     var files = std.ArrayListUnmanaged(FileEntry).empty;
 702     defer files.deinit(allocator);
 703 
 704     var dir_index = std.StringHashMapUnmanaged(u32){};
 705     defer dir_index.deinit(allocator);
 706     var file_index = std.StringHashMapUnmanaged(u32){};
 707     defer file_index.deinit(allocator);
 708 
 709     for (table.entries) |entry| {
 710         const full_path = entry.info.file orelse "<unknown>";
 711         if (file_index.contains(full_path)) continue;
 712 
 713         const dir = std.fs.path.dirname(full_path) orelse "";
 714         const base = std.fs.path.basename(full_path);
 715 
 716         var dir_idx: u32 = 0;
 717         if (dir.len != 0) {
 718             if (dir_index.get(dir)) |idx| {
 719                 dir_idx = idx;
 720             } else {
 721                 try dirs.append(allocator, dir);
 722                 dir_idx = @intCast(dirs.items.len);
 723                 try dir_index.put(allocator, dir, dir_idx);
 724             }
 725         }
 726 
 727         try files.append(allocator, .{ .name = base, .dir_index = dir_idx });
 728         try file_index.put(allocator, full_path, @intCast(files.items.len));
 729     }
 730 
 731     if (files.items.len == 0) {
 732         try files.append(allocator, .{ .name = "<unknown>", .dir_index = 0 });
 733         try file_index.put(allocator, "<unknown>", 1);
 734     }
 735 
 736     const primary_file = files.items[0];
 737     const primary_name = primary_file.name;
 738     const primary_dir = if (primary_file.dir_index > 0) dirs.items[primary_file.dir_index - 1] else ".";
 739 
 740     var out = std.ArrayListUnmanaged(u8).empty;
 741     errdefer out.deinit(allocator);
 742 
 743     const unit_length_offset = out.items.len;
 744     try appendInt(&out, allocator, u32, 0);
 745     try appendInt(&out, allocator, u16, dwarf.version);
 746     const header_length_offset = out.items.len;
 747     try appendInt(&out, allocator, u32, 0);
 748 
 749     const header_start = out.items.len;
 750     try appendInt(&out, allocator, u8, 1);
 751     try appendInt(&out, allocator, u8, 1);
 752     try appendInt(&out, allocator, u8, 1);
 753     try appendInt(&out, allocator, u8, @bitCast(@as(i8, -5)));
 754     try appendInt(&out, allocator, u8, 14);
 755     try appendInt(&out, allocator, u8, 13);
 756     const std_op_lengths = [_]u8{ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 };
 757     try out.appendSlice(allocator, &std_op_lengths);
 758 
 759     for (dirs.items) |dir| {
 760         try appendString(&out, allocator, dir);
 761     }
 762     try out.append(allocator, 0);
 763 
 764     for (files.items) |file| {
 765         try appendString(&out, allocator, file.name);
 766         try appendUleb128(&out, allocator, file.dir_index);
 767         try appendUleb128(&out, allocator, 0);
 768         try appendUleb128(&out, allocator, 0);
 769     }
 770     try out.append(allocator, 0);
 771 
 772     const header_end = out.items.len;
 773     const header_length: u32 = @intCast(header_end - header_start);
 774     const header_bytes = out.items[header_length_offset .. header_length_offset + 4];
 775     var header_buf: [4]u8 = undefined;
 776     std.mem.writeInt(u32, &header_buf, header_length, .little);
 777     @memcpy(header_bytes, &header_buf);
 778 
 779     var current_line: i64 = 1;
 780     var current_file: u32 = 1;
 781     var current_column: u32 = 0;
 782 
 783     for (table.entries) |entry| {
 784         const info = entry.info;
 785         const full_path = info.file orelse "<unknown>";
 786         const file_idx = file_index.get(full_path) orelse 1;
 787         if (file_idx != current_file) {
 788             try out.append(allocator, dwarf.LNS_set_file);
 789             try appendUleb128(&out, allocator, file_idx);
 790             current_file = file_idx;
 791         }
 792 
 793         if (info.column != current_column) {
 794             try out.append(allocator, dwarf.LNS_set_column);
 795             try appendUleb128(&out, allocator, info.column);
 796             current_column = info.column;
 797         }
 798 
 799         const addr = code_addr + entry.offset;
 800         try appendExtendedOpcode(&out, allocator, dwarf.LNE_set_address, dwarf.addr_size, addr);
 801 
 802         const next_line: i64 = if (info.line == 0) 1 else @intCast(info.line);
 803         const line_delta = next_line - current_line;
 804         if (line_delta != 0) {
 805             try out.append(allocator, dwarf.LNS_advance_line);
 806             try appendSleb128(&out, allocator, line_delta);
 807             current_line = next_line;
 808         }
 809 
 810         try out.append(allocator, dwarf.LNS_copy);
 811     }
 812 
 813     try appendExtendedOpcode(&out, allocator, dwarf.LNE_end_sequence, dwarf.addr_size, 0);
 814 
 815     const unit_length: u32 = @intCast(out.items.len - unit_length_offset - 4);
 816     const unit_bytes = out.items[unit_length_offset .. unit_length_offset + 4];
 817     var unit_buf: [4]u8 = undefined;
 818     std.mem.writeInt(u32, &unit_buf, unit_length, .little);
 819     @memcpy(unit_bytes, &unit_buf);
 820 
 821     return .{
 822         .bytes = try out.toOwnedSlice(allocator),
 823         .primary_name = primary_name,
 824         .primary_dir = primary_dir,
 825         .primary_file_index = if (files.items.len > 0) 1 else 0,
 826         .primary_line = if (table.entries.len > 0) @max(@as(u32, 1), table.entries[0].info.line) else 1,
 827     };
 828 }
 829 
 830 fn buildDebugAbbrev(allocator: Allocator) Allocator.Error![]u8 {
 831     var out = std.ArrayListUnmanaged(u8).empty;
 832     errdefer out.deinit(allocator);
 833 
 834     try appendUleb128(&out, allocator, 1);
 835     try appendUleb128(&out, allocator, dwarf.TAG_compile_unit);
 836     try out.append(allocator, dwarf.CHILDREN_yes);
 837     try appendUleb128(&out, allocator, dwarf.AT_name);
 838     try appendUleb128(&out, allocator, dwarf.FORM_string);
 839     try appendUleb128(&out, allocator, dwarf.AT_comp_dir);
 840     try appendUleb128(&out, allocator, dwarf.FORM_string);
 841     try appendUleb128(&out, allocator, dwarf.AT_low_pc);
 842     try appendUleb128(&out, allocator, dwarf.FORM_addr);
 843     try appendUleb128(&out, allocator, dwarf.AT_high_pc);
 844     try appendUleb128(&out, allocator, dwarf.FORM_addr);
 845     try appendUleb128(&out, allocator, dwarf.AT_stmt_list);
 846     try appendUleb128(&out, allocator, dwarf.FORM_sec_offset);
 847     try appendUleb128(&out, allocator, 0);
 848     try appendUleb128(&out, allocator, 0);
 849 
 850     try appendUleb128(&out, allocator, 2);
 851     try appendUleb128(&out, allocator, dwarf.TAG_subprogram);
 852     try out.append(allocator, dwarf.CHILDREN_no);
 853     try appendUleb128(&out, allocator, dwarf.AT_name);
 854     try appendUleb128(&out, allocator, dwarf.FORM_string);
 855     try appendUleb128(&out, allocator, dwarf.AT_low_pc);
 856     try appendUleb128(&out, allocator, dwarf.FORM_addr);
 857     try appendUleb128(&out, allocator, dwarf.AT_high_pc);
 858     try appendUleb128(&out, allocator, dwarf.FORM_addr);
 859     try appendUleb128(&out, allocator, dwarf.AT_decl_file);
 860     try appendUleb128(&out, allocator, dwarf.FORM_data1);
 861     try appendUleb128(&out, allocator, dwarf.AT_decl_line);
 862     try appendUleb128(&out, allocator, dwarf.FORM_data4);
 863     try appendUleb128(&out, allocator, 0);
 864     try appendUleb128(&out, allocator, 0);
 865 
 866     try appendUleb128(&out, allocator, 0);
 867 
 868     return out.toOwnedSlice(allocator);
 869 }
 870 
 871 fn buildDebugInfo(
 872     allocator: Allocator,
 873     code_addr: usize,
 874     code_size: usize,
 875     line_section: *const DebugLineSection,
 876     func_name: []const u8,
 877 ) Allocator.Error![]u8 {
 878     var out = std.ArrayListUnmanaged(u8).empty;
 879     errdefer out.deinit(allocator);
 880 
 881     const unit_length_offset = out.items.len;
 882     try appendInt(&out, allocator, u32, 0);
 883     try appendInt(&out, allocator, u16, dwarf.version);
 884     try appendInt(&out, allocator, u32, 0);
 885     try appendInt(&out, allocator, u8, dwarf.addr_size);
 886 
 887     try appendUleb128(&out, allocator, 1);
 888     const cu_name = if (line_section.primary_name.len == 0) func_name else line_section.primary_name;
 889     try appendString(&out, allocator, cu_name);
 890     try appendString(&out, allocator, line_section.primary_dir);
 891     try appendAddress(&out, allocator, code_addr);
 892     try appendAddress(&out, allocator, code_addr + code_size);
 893     try appendInt(&out, allocator, u32, 0);
 894 
 895     try appendUleb128(&out, allocator, 2);
 896     try appendString(&out, allocator, func_name);
 897     try appendAddress(&out, allocator, code_addr);
 898     try appendAddress(&out, allocator, code_addr + code_size);
 899     const decl_file: u8 = if (line_section.primary_file_index <= std.math.maxInt(u8))
 900         @intCast(line_section.primary_file_index)
 901     else
 902         0;
 903     try appendInt(&out, allocator, u8, decl_file);
 904     try appendInt(&out, allocator, u32, line_section.primary_line);
 905 
 906     try out.append(allocator, 0);
 907 
 908     const unit_length: u32 = @intCast(out.items.len - unit_length_offset - 4);
 909     const unit_bytes = out.items[unit_length_offset .. unit_length_offset + 4];
 910     var unit_buf: [4]u8 = undefined;
 911     std.mem.writeInt(u32, &unit_buf, unit_length, .little);
 912     @memcpy(unit_bytes, &unit_buf);
 913 
 914     return out.toOwnedSlice(allocator);
 915 }
 916 
 917 fn elfMachineForArch(arch: std.Target.Cpu.Arch) ?std.elf.EM {
 918     return switch (arch) {
 919         .x86_64 => std.elf.EM.X86_64,
 920         .aarch64 => std.elf.EM.AARCH64,
 921         else => null,
 922     };
 923 }
 924 
 925 fn appendInt(list: *std.ArrayListUnmanaged(u8), allocator: Allocator, comptime T: type, value: T) Allocator.Error!void {
 926     var buf: [@sizeOf(T)]u8 = undefined;
 927     std.mem.writeInt(T, &buf, value, .little);
 928     try list.appendSlice(allocator, &buf);
 929 }
 930 
 931 fn appendString(list: *std.ArrayListUnmanaged(u8), allocator: Allocator, value: []const u8) Allocator.Error!void {
 932     try list.appendSlice(allocator, value);
 933     try list.append(allocator, 0);
 934 }
 935 
 936 fn appendAddress(list: *std.ArrayListUnmanaged(u8), allocator: Allocator, value: usize) Allocator.Error!void {
 937     var buf: [dwarf.addr_size]u8 = undefined;
 938     std.mem.writeInt(u64, &buf, @intCast(value), .little);
 939     try list.appendSlice(allocator, &buf);
 940 }
 941 
 942 fn appendUleb128(list: *std.ArrayListUnmanaged(u8), allocator: Allocator, value: u64) Allocator.Error!void {
 943     var val = value;
 944     while (true) {
 945         var byte: u8 = @intCast(val & 0x7f);
 946         val >>= 7;
 947         if (val != 0) byte |= 0x80;
 948         try list.append(allocator, byte);
 949         if (val == 0) break;
 950     }
 951 }
 952 
 953 fn appendSleb128(list: *std.ArrayListUnmanaged(u8), allocator: Allocator, value: i64) Allocator.Error!void {
 954     var val = value;
 955     while (true) {
 956         var byte: u8 = @intCast(val & 0x7f);
 957         const sign_bit = (byte & 0x40) != 0;
 958         val >>= 7;
 959         const done = (val == 0 and !sign_bit) or (val == -1 and sign_bit);
 960         if (!done) byte |= 0x80;
 961         try list.append(allocator, byte);
 962         if (done) break;
 963     }
 964 }
 965 
 966 fn appendExtendedOpcode(
 967     list: *std.ArrayListUnmanaged(u8),
 968     allocator: Allocator,
 969     opcode: u8,
 970     addr_size: u8,
 971     addr: usize,
 972 ) Allocator.Error!void {
 973     try list.append(allocator, 0);
 974     const payload_len: u64 = if (opcode == dwarf.LNE_set_address) 1 + addr_size else 1;
 975     try appendUleb128(list, allocator, payload_len);
 976     try list.append(allocator, opcode);
 977     if (opcode == dwarf.LNE_set_address) {
 978         var buf: [dwarf.addr_size]u8 = undefined;
 979         std.mem.writeInt(u64, &buf, @intCast(addr), .little);
 980         try list.appendSlice(allocator, &buf);
 981     }
 982 }
 983 
 984 fn alignForward(value: usize, alignment: usize) usize {
 985     const mask = alignment - 1;
 986     return (value + mask) & ~mask;
 987 }
 988 
 989 fn sliceSectionByName(data: []const u8, name: []const u8) ?[]const u8 {
 990     if (data.len < @sizeOf(std.elf.Elf64_Ehdr)) return null;
 991     const header = std.mem.bytesToValue(std.elf.Elf64_Ehdr, data[0..@sizeOf(std.elf.Elf64_Ehdr)]);
 992     const shoff: usize = @intCast(header.e_shoff);
 993     const shnum = header.e_shnum;
 994     const shstrndx = header.e_shstrndx;
 995     if (shnum == 0 or shstrndx >= shnum) return null;
 996 
 997     const shdr_size = @sizeOf(std.elf.Elf64_Shdr);
 998     const shstr_off = shoff + shdr_size * shstrndx;
 999     if (shstr_off + shdr_size > data.len) return null;
1000     const shstr_hdr = std.mem.bytesToValue(std.elf.Elf64_Shdr, data[shstr_off .. shstr_off + shdr_size]);
1001     const shstr_start: usize = @intCast(shstr_hdr.sh_offset);
1002     const shstr_size: usize = @intCast(shstr_hdr.sh_size);
1003     const shstr_end: usize = shstr_start + shstr_size;
1004     if (shstr_end > data.len) return null;
1005     const shstrtab = data[shstr_start..shstr_end];
1006 
1007     var idx: usize = 0;
1008     while (idx < shnum) : (idx += 1) {
1009         const shdr_off = shoff + shdr_size * idx;
1010         if (shdr_off + shdr_size > data.len) return null;
1011         const shdr = std.mem.bytesToValue(std.elf.Elf64_Shdr, data[shdr_off .. shdr_off + shdr_size]);
1012         const name_off: usize = @intCast(shdr.sh_name);
1013         if (name_off >= shstrtab.len) continue;
1014         const section_name = std.mem.sliceTo(shstrtab[name_off..], 0);
1015         if (!std.mem.eql(u8, section_name, name)) continue;
1016         const start: usize = @intCast(shdr.sh_offset);
1017         const size: usize = @intCast(shdr.sh_size);
1018         const end = start + size;
1019         if (end > data.len) return null;
1020         return data[start..end];
1021     }
1022     return null;
1023 }
1024 
1025 const ValidationError = error{
1026     OutOfMemory,
1027     InvalidElfHeader,
1028     InvalidElfSection,
1029     OverlappingSections,
1030     MissingSection,
1031     InvalidSymtab,
1032     InvalidDebugLine,
1033     InvalidDebugInfo,
1034 };
1035 
1036 const ElfSection = struct {
1037     index: usize,
1038     header: std.elf.Elf64_Shdr,
1039     name: []const u8,
1040 };
1041 
1042 const ElfView = struct {
1043     allocator: Allocator,
1044     header: std.elf.Elf64_Ehdr,
1045     sections: []ElfSection,
1046     shstrtab: []const u8,
1047 
1048     fn deinit(self: *ElfView) void {
1049         self.allocator.free(self.sections);
1050         self.* = undefined;
1051     }
1052 
1053     fn findSection(self: *const ElfView, name: []const u8) ?ElfSection {
1054         for (self.sections) |section| {
1055             if (std.mem.eql(u8, section.name, name)) return section;
1056         }
1057         return null;
1058     }
1059 };
1060 
1061 const ByteReader = struct {
1062     data: []const u8,
1063     offset: usize = 0,
1064 
1065     const ReadError = error{Truncated};
1066 
1067     fn init(data: []const u8) ByteReader {
1068         return .{ .data = data, .offset = 0 };
1069     }
1070 
1071     fn readBytes(self: *ByteReader, len: usize) ReadError![]const u8 {
1072         if (self.offset + len > self.data.len) return error.Truncated;
1073         const out = self.data[self.offset .. self.offset + len];
1074         self.offset += len;
1075         return out;
1076     }
1077 
1078     fn readInt(self: *ByteReader, comptime T: type) ReadError!T {
1079         const bytes = try self.readBytes(@sizeOf(T));
1080         return std.mem.readInt(T, bytes[0..@sizeOf(T)], .little);
1081     }
1082 
1083     fn readByte(self: *ByteReader) ReadError!u8 {
1084         return self.readInt(u8);
1085     }
1086 
1087     fn readCString(self: *ByteReader) ReadError![]const u8 {
1088         if (self.offset >= self.data.len) return error.Truncated;
1089         const start = self.offset;
1090         const rel = std.mem.indexOfScalar(u8, self.data[start..], 0) orelse return error.Truncated;
1091         self.offset = start + rel + 1;
1092         return self.data[start .. start + rel];
1093     }
1094 
1095     fn readUleb(self: *ByteReader) ReadError!u64 {
1096         var result: u64 = 0;
1097         var shift: u6 = 0;
1098         while (true) {
1099             const byte = try self.readByte();
1100             result |= (@as(u64, byte & 0x7f) << shift);
1101             if ((byte & 0x80) == 0) break;
1102             shift += 7;
1103             if (shift >= 63) return error.Truncated;
1104         }
1105         return result;
1106     }
1107 
1108     fn readSleb(self: *ByteReader) ReadError!i64 {
1109         var result: i64 = 0;
1110         var shift: u6 = 0;
1111         var byte: u8 = 0;
1112         while (true) {
1113             byte = try self.readByte();
1114             result |= (@as(i64, byte & 0x7f) << shift);
1115             shift += 7;
1116             if ((byte & 0x80) == 0) break;
1117             if (shift >= 63) return error.Truncated;
1118         }
1119         if ((shift < 64) and ((byte & 0x40) != 0)) {
1120             result |= -(@as(i64, 1) << shift);
1121         }
1122         return result;
1123     }
1124 };
1125 
1126 fn parseElfView(allocator: Allocator, data: []const u8) ValidationError!ElfView {
1127     if (data.len < @sizeOf(std.elf.Elf64_Ehdr)) return ValidationError.InvalidElfHeader;
1128     const header = std.mem.bytesToValue(std.elf.Elf64_Ehdr, data[0..@sizeOf(std.elf.Elf64_Ehdr)]);
1129 
1130     const shoff: usize = @intCast(header.e_shoff);
1131     const shnum: usize = header.e_shnum;
1132     const shentsize: usize = header.e_shentsize;
1133     if (shnum == 0 or shentsize != @sizeOf(std.elf.Elf64_Shdr)) return ValidationError.InvalidElfHeader;
1134     if (header.e_shstrndx >= header.e_shnum) return ValidationError.InvalidElfHeader;
1135     if (shoff + shentsize * shnum > data.len) return ValidationError.InvalidElfSection;
1136 
1137     const shstr_off = shoff + shentsize * header.e_shstrndx;
1138     if (shstr_off + shentsize > data.len) return ValidationError.InvalidElfSection;
1139     const shstr_hdr = std.mem.bytesToValue(std.elf.Elf64_Shdr, data[shstr_off .. shstr_off + shentsize]);
1140     const shstr_start: usize = @intCast(shstr_hdr.sh_offset);
1141     const shstr_size: usize = @intCast(shstr_hdr.sh_size);
1142     const shstr_end = shstr_start + shstr_size;
1143     if (shstr_end > data.len) return ValidationError.InvalidElfSection;
1144     const shstrtab = data[shstr_start..shstr_end];
1145 
1146     var sections = try allocator.alloc(ElfSection, shnum);
1147     errdefer allocator.free(sections);
1148 
1149     var idx: usize = 0;
1150     while (idx < shnum) : (idx += 1) {
1151         const shdr_off = shoff + shentsize * idx;
1152         if (shdr_off + shentsize > data.len) return ValidationError.InvalidElfSection;
1153         const shdr = std.mem.bytesToValue(std.elf.Elf64_Shdr, data[shdr_off .. shdr_off + shentsize]);
1154         const name_off: usize = @intCast(shdr.sh_name);
1155         if (name_off >= shstrtab.len) return ValidationError.InvalidElfSection;
1156         const name = std.mem.sliceTo(shstrtab[name_off..], 0);
1157         sections[idx] = .{ .index = idx, .header = shdr, .name = name };
1158     }
1159 
1160     return .{
1161         .allocator = allocator,
1162         .header = header,
1163         .sections = sections,
1164         .shstrtab = shstrtab,
1165     };
1166 }
1167 
1168 fn validateElfHeader(header: std.elf.Elf64_Ehdr, arch: std.Target.Cpu.Arch) ValidationError!void {
1169     if (header.e_ident[0] != 0x7f or header.e_ident[1] != 'E' or header.e_ident[2] != 'L' or header.e_ident[3] != 'F') {
1170         return ValidationError.InvalidElfHeader;
1171     }
1172     if (header.e_ident[std.elf.EI_CLASS] != std.elf.ELFCLASS64) return ValidationError.InvalidElfHeader;
1173     if (header.e_ident[std.elf.EI_DATA] != std.elf.ELFDATA2LSB) return ValidationError.InvalidElfHeader;
1174     if (header.e_ident[std.elf.EI_VERSION] != 1) return ValidationError.InvalidElfHeader;
1175     if (header.e_ehsize != @sizeOf(std.elf.Elf64_Ehdr)) return ValidationError.InvalidElfHeader;
1176     if (header.e_shentsize != @sizeOf(std.elf.Elf64_Shdr)) return ValidationError.InvalidElfHeader;
1177     const machine = elfMachineForArch(arch) orelse return ValidationError.InvalidElfHeader;
1178     if (header.e_machine != machine) return ValidationError.InvalidElfHeader;
1179     if (header.e_shstrndx >= header.e_shnum) return ValidationError.InvalidElfHeader;
1180 }
1181 
1182 fn validateSectionRanges(view: *const ElfView, data_len: usize) ValidationError!void {
1183     for (view.sections) |section| {
1184         const size: usize = @intCast(section.header.sh_size);
1185         const start: usize = @intCast(section.header.sh_offset);
1186         if (size == 0) continue;
1187         const end = start + size;
1188         if (end > data_len) return ValidationError.InvalidElfSection;
1189     }
1190 
1191     var i: usize = 0;
1192     while (i < view.sections.len) : (i += 1) {
1193         const a = view.sections[i];
1194         const a_size: usize = @intCast(a.header.sh_size);
1195         if (a_size == 0) continue;
1196         const a_start: usize = @intCast(a.header.sh_offset);
1197         const a_end = a_start + a_size;
1198 
1199         var j: usize = i + 1;
1200         while (j < view.sections.len) : (j += 1) {
1201             const b = view.sections[j];
1202             const b_size: usize = @intCast(b.header.sh_size);
1203             if (b_size == 0) continue;
1204             const b_start: usize = @intCast(b.header.sh_offset);
1205             const b_end = b_start + b_size;
1206             if (a_start < b_end and b_start < a_end) {
1207                 return ValidationError.OverlappingSections;
1208             }
1209         }
1210     }
1211 }
1212 
1213 fn validateSymtab(
1214     view: *const ElfView,
1215     data: []const u8,
1216     func_name: []const u8,
1217     code_addr: usize,
1218     code_len: usize,
1219 ) ValidationError!void {
1220     const symtab = view.findSection(".symtab") orelse return ValidationError.MissingSection;
1221     const strtab = view.findSection(".strtab") orelse return ValidationError.MissingSection;
1222 
1223     if (symtab.header.sh_link != strtab.index) return ValidationError.InvalidSymtab;
1224     if (symtab.header.sh_entsize != @sizeOf(std.elf.Elf64_Sym)) return ValidationError.InvalidSymtab;
1225     if ((symtab.header.sh_size % @sizeOf(std.elf.Elf64_Sym)) != 0) return ValidationError.InvalidSymtab;
1226 
1227     const sym_offset: usize = @intCast(symtab.header.sh_offset);
1228     const sym_size: usize = @intCast(symtab.header.sh_size);
1229     if (sym_offset + sym_size > data.len) return ValidationError.InvalidSymtab;
1230     const sym_count = sym_size / @sizeOf(std.elf.Elf64_Sym);
1231     if (sym_count == 0) return ValidationError.InvalidSymtab;
1232 
1233     const str_offset: usize = @intCast(strtab.header.sh_offset);
1234     const str_size: usize = @intCast(strtab.header.sh_size);
1235     if (str_offset + str_size > data.len) return ValidationError.InvalidSymtab;
1236     const strtab_bytes = data[str_offset .. str_offset + str_size];
1237 
1238     const first_global = symtab.header.sh_info;
1239     if (first_global == 0 or first_global > sym_count) return ValidationError.InvalidSymtab;
1240 
1241     var found_func = false;
1242     var idx: usize = 0;
1243     while (idx < sym_count) : (idx += 1) {
1244         const start = sym_offset + idx * @sizeOf(std.elf.Elf64_Sym);
1245         const sym = std.mem.bytesToValue(std.elf.Elf64_Sym, data[start .. start + @sizeOf(std.elf.Elf64_Sym)]);
1246         const bind = sym.st_info >> 4;
1247         if (idx < first_global and bind != std.elf.STB_LOCAL) return ValidationError.InvalidSymtab;
1248         if (idx >= first_global and bind == std.elf.STB_LOCAL) return ValidationError.InvalidSymtab;
1249 
1250         if (sym.st_name != 0) {
1251             const name_off: usize = @intCast(sym.st_name);
1252             if (name_off >= strtab_bytes.len) return ValidationError.InvalidSymtab;
1253             const name = std.mem.sliceTo(strtab_bytes[name_off..], 0);
1254             if (std.mem.eql(u8, name, func_name)) {
1255                 const typ = sym.st_info & 0x0f;
1256                 if (typ != std.elf.STT_FUNC) return ValidationError.InvalidSymtab;
1257                 if (sym.st_value != code_addr) return ValidationError.InvalidSymtab;
1258                 if (sym.st_size != code_len) return ValidationError.InvalidSymtab;
1259                 found_func = true;
1260             }
1261         }
1262     }
1263 
1264     if (!found_func) return ValidationError.InvalidSymtab;
1265 }
1266 
1267 fn validateDebugLine(section: []const u8) ValidationError!void {
1268     var reader = ByteReader.init(section);
1269     const unit_length = reader.readInt(u32) catch return ValidationError.InvalidDebugLine;
1270     if (unit_length + 4 != section.len) return ValidationError.InvalidDebugLine;
1271     const version = reader.readInt(u16) catch return ValidationError.InvalidDebugLine;
1272     if (version != dwarf.version) return ValidationError.InvalidDebugLine;
1273     const header_length = reader.readInt(u32) catch return ValidationError.InvalidDebugLine;
1274     const header_end = reader.offset + header_length;
1275     if (header_end > section.len) return ValidationError.InvalidDebugLine;
1276 
1277     _ = reader.readByte() catch return ValidationError.InvalidDebugLine;
1278     _ = reader.readByte() catch return ValidationError.InvalidDebugLine;
1279     _ = reader.readByte() catch return ValidationError.InvalidDebugLine;
1280     _ = reader.readByte() catch return ValidationError.InvalidDebugLine;
1281     _ = reader.readByte() catch return ValidationError.InvalidDebugLine;
1282     const opcode_base = reader.readByte() catch return ValidationError.InvalidDebugLine;
1283     if (opcode_base == 0) return ValidationError.InvalidDebugLine;
1284     const std_op_lengths = reader.readBytes(opcode_base - 1) catch return ValidationError.InvalidDebugLine;
1285 
1286     if (header_end < reader.offset) return ValidationError.InvalidDebugLine;
1287     reader.offset = header_end;
1288 
1289     var last_was_end_sequence = false;
1290     while (reader.offset < section.len) {
1291         const opcode = reader.readByte() catch return ValidationError.InvalidDebugLine;
1292         if (opcode == 0) {
1293             const payload_len = reader.readUleb() catch return ValidationError.InvalidDebugLine;
1294             if (payload_len == 0) return ValidationError.InvalidDebugLine;
1295             if (reader.offset + payload_len > section.len) return ValidationError.InvalidDebugLine;
1296             const subopcode = reader.readByte() catch return ValidationError.InvalidDebugLine;
1297             const remaining = payload_len - 1;
1298             if (subopcode == dwarf.LNE_end_sequence) {
1299                 if (remaining != 0) return ValidationError.InvalidDebugLine;
1300                 last_was_end_sequence = true;
1301             } else {
1302                 last_was_end_sequence = false;
1303             }
1304             reader.offset += remaining;
1305             continue;
1306         }
1307 
1308         last_was_end_sequence = false;
1309         if (opcode < opcode_base) {
1310             switch (opcode) {
1311                 dwarf.LNS_copy => {},
1312                 dwarf.LNS_advance_pc => {
1313                     _ = reader.readUleb() catch return ValidationError.InvalidDebugLine;
1314                 },
1315                 dwarf.LNS_advance_line => {
1316                     _ = reader.readSleb() catch return ValidationError.InvalidDebugLine;
1317                 },
1318                 dwarf.LNS_set_file => {
1319                     _ = reader.readUleb() catch return ValidationError.InvalidDebugLine;
1320                 },
1321                 dwarf.LNS_set_column => {
1322                     _ = reader.readUleb() catch return ValidationError.InvalidDebugLine;
1323                 },
1324                 dwarf.LNS_const_add_pc => {},
1325                 dwarf.LNS_fixed_advance_pc => {
1326                     _ = reader.readInt(u16) catch return ValidationError.InvalidDebugLine;
1327                 },
1328                 else => {
1329                     const op_index: usize = opcode - 1;
1330                     if (op_index >= std_op_lengths.len) return ValidationError.InvalidDebugLine;
1331                     var count = std_op_lengths[op_index];
1332                     while (count > 0) : (count -= 1) {
1333                         _ = reader.readUleb() catch return ValidationError.InvalidDebugLine;
1334                     }
1335                 },
1336             }
1337             continue;
1338         }
1339     }
1340 
1341     if (!last_was_end_sequence) return ValidationError.InvalidDebugLine;
1342     if (reader.offset != section.len) return ValidationError.InvalidDebugLine;
1343 }
1344 
1345 fn validateDebugInfo(
1346     section: []const u8,
1347     code_addr: usize,
1348     code_len: usize,
1349     func_name: []const u8,
1350     expected_file: []const u8,
1351     expected_dir: []const u8,
1352     expected_line: u32,
1353 ) ValidationError!void {
1354     var reader = ByteReader.init(section);
1355     const unit_length = reader.readInt(u32) catch return ValidationError.InvalidDebugInfo;
1356     if (unit_length + 4 != section.len) return ValidationError.InvalidDebugInfo;
1357     const version = reader.readInt(u16) catch return ValidationError.InvalidDebugInfo;
1358     if (version != dwarf.version) return ValidationError.InvalidDebugInfo;
1359     const abbrev_offset = reader.readInt(u32) catch return ValidationError.InvalidDebugInfo;
1360     if (abbrev_offset != 0) return ValidationError.InvalidDebugInfo;
1361     const addr_size = reader.readByte() catch return ValidationError.InvalidDebugInfo;
1362     if (addr_size != dwarf.addr_size) return ValidationError.InvalidDebugInfo;
1363 
1364     const cu_abbrev = reader.readUleb() catch return ValidationError.InvalidDebugInfo;
1365     if (cu_abbrev != 1) return ValidationError.InvalidDebugInfo;
1366 
1367     const cu_name = reader.readCString() catch return ValidationError.InvalidDebugInfo;
1368     const comp_dir = reader.readCString() catch return ValidationError.InvalidDebugInfo;
1369     const low_pc = reader.readInt(u64) catch return ValidationError.InvalidDebugInfo;
1370     const high_pc = reader.readInt(u64) catch return ValidationError.InvalidDebugInfo;
1371     const stmt_list = reader.readInt(u32) catch return ValidationError.InvalidDebugInfo;
1372     if (!std.mem.eql(u8, cu_name, expected_file)) return ValidationError.InvalidDebugInfo;
1373     if (!std.mem.eql(u8, comp_dir, expected_dir)) return ValidationError.InvalidDebugInfo;
1374     if (low_pc != code_addr or high_pc != code_addr + code_len) return ValidationError.InvalidDebugInfo;
1375     if (stmt_list != 0) return ValidationError.InvalidDebugInfo;
1376 
1377     const sub_abbrev = reader.readUleb() catch return ValidationError.InvalidDebugInfo;
1378     if (sub_abbrev != 2) return ValidationError.InvalidDebugInfo;
1379     const sub_name = reader.readCString() catch return ValidationError.InvalidDebugInfo;
1380     if (!std.mem.eql(u8, sub_name, func_name)) return ValidationError.InvalidDebugInfo;
1381     const sub_low = reader.readInt(u64) catch return ValidationError.InvalidDebugInfo;
1382     const sub_high = reader.readInt(u64) catch return ValidationError.InvalidDebugInfo;
1383     const decl_file = reader.readInt(u8) catch return ValidationError.InvalidDebugInfo;
1384     const decl_line = reader.readInt(u32) catch return ValidationError.InvalidDebugInfo;
1385     if (sub_low != code_addr or sub_high != code_addr + code_len) return ValidationError.InvalidDebugInfo;
1386     if (decl_file == 0) return ValidationError.InvalidDebugInfo;
1387     if (decl_line != expected_line) return ValidationError.InvalidDebugInfo;
1388 
1389     const terminator = reader.readUleb() catch return ValidationError.InvalidDebugInfo;
1390     if (terminator != 0) return ValidationError.InvalidDebugInfo;
1391     if (reader.offset != section.len) return ValidationError.InvalidDebugInfo;
1392 }
1393 
1394 fn validateElfDebugObject(
1395     allocator: Allocator,
1396     data: []const u8,
1397     arch: std.Target.Cpu.Arch,
1398     code_addr: usize,
1399     code_len: usize,
1400     func_name: []const u8,
1401     expected_file: []const u8,
1402     expected_dir: []const u8,
1403     expected_line: u32,
1404 ) ValidationError!void {
1405     var view = try parseElfView(allocator, data);
1406     defer view.deinit();
1407 
1408     try validateElfHeader(view.header, arch);
1409     try validateSectionRanges(&view, data.len);
1410     try validateSymtab(&view, data, func_name, code_addr, code_len);
1411 
1412     const debug_line = view.findSection(".debug_line") orelse return ValidationError.MissingSection;
1413     const debug_info = view.findSection(".debug_info") orelse return ValidationError.MissingSection;
1414     const debug_abbrev = view.findSection(".debug_abbrev") orelse return ValidationError.MissingSection;
1415 
1416     if (debug_abbrev.header.sh_size == 0) return ValidationError.InvalidDebugInfo;
1417 
1418     const line_offset: usize = @intCast(debug_line.header.sh_offset);
1419     const line_size: usize = @intCast(debug_line.header.sh_size);
1420     const info_offset: usize = @intCast(debug_info.header.sh_offset);
1421     const info_size: usize = @intCast(debug_info.header.sh_size);
1422     if (line_offset + line_size > data.len) return ValidationError.InvalidDebugLine;
1423     if (info_offset + info_size > data.len) return ValidationError.InvalidDebugInfo;
1424 
1425     try validateDebugLine(data[line_offset .. line_offset + line_size]);
1426     try validateDebugInfo(
1427         data[info_offset .. info_offset + info_size],
1428         code_addr,
1429         code_len,
1430         func_name,
1431         expected_file,
1432         expected_dir,
1433         expected_line,
1434     );
1435 }
1436 
1437 test "LineTableBuilder lookup returns nearest entry" {
1438     var builder = LineTableBuilder.init(std.testing.allocator);
1439     defer builder.deinit();
1440 
1441     try builder.record(0, ir.Location.getFile("test.choir", 1, 1), "arith.add");
1442     try builder.record(8, ir.Location.getFile("test.choir", 2, 5), "arith.mul");
1443 
1444     var table = try builder.finish();
1445     defer table.deinit();
1446 
1447     const at0_opt = table.lookup(0);
1448     try std.testing.expect(at0_opt != null);
1449     const at0 = at0_opt.?;
1450     try std.testing.expectEqualStrings("test.choir", at0.file.?);
1451     try std.testing.expectEqual(@as(u32, 1), at0.line);
1452 
1453     const at4_opt = table.lookup(4);
1454     try std.testing.expect(at4_opt != null);
1455     const at4 = at4_opt.?;
1456     try std.testing.expectEqual(@as(u32, 1), at4.line);
1457 
1458     const at8_opt = table.lookup(8);
1459     try std.testing.expect(at8_opt != null);
1460     const at8 = at8_opt.?;
1461     try std.testing.expectEqual(@as(u32, 2), at8.line);
1462 }
1463 
1464 test "LineTableBuilder preserves name location" {
1465     var builder = LineTableBuilder.init(std.testing.allocator);
1466     defer builder.deinit();
1467 
1468     const loc = ir.Location.getName("generated", null);
1469     try builder.record(0, loc, "arith.add");
1470 
1471     var table = try builder.finish();
1472     defer table.deinit();
1473 
1474     const info_opt = table.lookup(0);
1475     try std.testing.expect(info_opt != null);
1476     const info = info_opt.?;
1477     try std.testing.expectEqualStrings("generated", info.name.?);
1478     try std.testing.expectEqualStrings("arith.add", info.op_name);
1479 }
1480 
1481 test "LineTableBuilder finish handles empty table" {
1482     var builder = LineTableBuilder.init(std.testing.allocator);
1483     defer builder.deinit();
1484 
1485     var table = try builder.finish();
1486     defer table.deinit();
1487 
1488     try std.testing.expect(table.isEmpty());
1489 }
1490 
1491 test "buildElfDebugObject emits debug sections" {
1492     var builder = LineTableBuilder.init(std.testing.allocator);
1493     defer builder.deinit();
1494 
1495     try builder.record(0, ir.Location.getFile("test.choir", 3, 1), "arith.add");
1496 
1497     var table = try builder.finish();
1498     defer table.deinit();
1499 
1500     const obj = try buildElfDebugObject(
1501         std.testing.allocator,
1502         .x86_64,
1503         0x1000,
1504         &[_]u8{ 0x90, 0x90 },
1505         &table,
1506         "jit_fn",
1507     );
1508     defer std.testing.allocator.free(obj);
1509 
1510     const debug_line = sliceSectionByName(obj, ".debug_line");
1511     try std.testing.expect(debug_line != null);
1512     try std.testing.expect(std.mem.containsAtLeast(u8, debug_line.?, 1, "test.choir"));
1513 
1514     const debug_info = sliceSectionByName(obj, ".debug_info");
1515     try std.testing.expect(debug_info != null);
1516     try std.testing.expect(debug_info.?.len > 0);
1517 
1518     const symtab = sliceSectionByName(obj, ".symtab");
1519     try std.testing.expect(symtab != null);
1520     try std.testing.expect(symtab.?.len > 0);
1521 }
1522 
1523 test "buildElfDebugObject emits valid ELF + DWARF structure" {
1524     var builder = LineTableBuilder.init(std.testing.allocator);
1525     defer builder.deinit();
1526 
1527     try builder.record(0, ir.Location.getFile("test.choir", 3, 1), "arith.add");
1528 
1529     var table = try builder.finish();
1530     defer table.deinit();
1531 
1532     const code_addr = 0x1000;
1533     const code = [_]u8{ 0x90, 0x90 };
1534 
1535     const obj = try buildElfDebugObject(
1536         std.testing.allocator,
1537         .x86_64,
1538         code_addr,
1539         &code,
1540         &table,
1541         "jit_fn",
1542     );
1543     defer std.testing.allocator.free(obj);
1544 
1545     try validateElfDebugObject(
1546         std.testing.allocator,
1547         obj,
1548         .x86_64,
1549         code_addr,
1550         code.len,
1551         "jit_fn",
1552         "test.choir",
1553         ".",
1554         3,
1555     );
1556 }
1557 
1558 test "validateElfDebugObject rejects malformed debug_line" {
1559     var builder = LineTableBuilder.init(std.testing.allocator);
1560     defer builder.deinit();
1561 
1562     try builder.record(0, ir.Location.getFile("test.choir", 3, 1), "arith.add");
1563 
1564     var table = try builder.finish();
1565     defer table.deinit();
1566 
1567     const code_addr = 0x1000;
1568     const code = [_]u8{ 0x90, 0x90 };
1569 
1570     const obj = try buildElfDebugObject(
1571         std.testing.allocator,
1572         .x86_64,
1573         code_addr,
1574         &code,
1575         &table,
1576         "jit_fn",
1577     );
1578     defer std.testing.allocator.free(obj);
1579 
1580     var corrupted = try std.testing.allocator.dupe(u8, obj);
1581     defer std.testing.allocator.free(corrupted);
1582 
1583     var view = try parseElfView(std.testing.allocator, corrupted);
1584     const debug_line = view.findSection(".debug_line") orelse {
1585         view.deinit();
1586         return error.TestFailure;
1587     };
1588     const line_offset: usize = @intCast(debug_line.header.sh_offset);
1589     const line_size: usize = @intCast(debug_line.header.sh_size);
1590     view.deinit();
1591 
1592     if (line_size == 0 or line_offset + line_size > corrupted.len) {
1593         return error.TestFailure;
1594     }
1595 
1596     corrupted[line_offset + line_size - 1] = 0;
1597 
1598     try std.testing.expectError(
1599         ValidationError.InvalidDebugLine,
1600         validateElfDebugObject(
1601             std.testing.allocator,
1602             corrupted,
1603             .x86_64,
1604             code_addr,
1605             code.len,
1606             "jit_fn",
1607             "test.choir",
1608             ".",
1609             3,
1610         ),
1611     );
1612 }
1613 
1614 test "validateElfDebugObject rejects malformed debug_info" {
1615     var builder = LineTableBuilder.init(std.testing.allocator);
1616     defer builder.deinit();
1617 
1618     try builder.record(0, ir.Location.getFile("test.choir", 3, 1), "arith.add");
1619 
1620     var table = try builder.finish();
1621     defer table.deinit();
1622 
1623     const code_addr = 0x1000;
1624     const code = [_]u8{ 0x90, 0x90 };
1625 
1626     const obj = try buildElfDebugObject(
1627         std.testing.allocator,
1628         .x86_64,
1629         code_addr,
1630         &code,
1631         &table,
1632         "jit_fn",
1633     );
1634     defer std.testing.allocator.free(obj);
1635 
1636     var corrupted = try std.testing.allocator.dupe(u8, obj);
1637     defer std.testing.allocator.free(corrupted);
1638 
1639     var view = try parseElfView(std.testing.allocator, corrupted);
1640     const debug_info = view.findSection(".debug_info") orelse {
1641         view.deinit();
1642         return error.TestFailure;
1643     };
1644     const info_offset: usize = @intCast(debug_info.header.sh_offset);
1645     const info_size: usize = @intCast(debug_info.header.sh_size);
1646     view.deinit();
1647 
1648     if (info_size <= 4 or info_offset + info_size > corrupted.len) {
1649         return error.TestFailure;
1650     }
1651 
1652     std.mem.writeInt(u32, corrupted[info_offset..][0..4], 0, .little);
1653 
1654     try std.testing.expectError(
1655         ValidationError.InvalidDebugInfo,
1656         validateElfDebugObject(
1657             std.testing.allocator,
1658             corrupted,
1659             .x86_64,
1660             code_addr,
1661             code.len,
1662             "jit_fn",
1663             "test.choir",
1664             ".",
1665             3,
1666         ),
1667     );
1668 }
1669 
1670 test "jitDebugSupportFor reports macOS note" {
1671     const linux_support = jitDebugSupportFor(.linux, .x86_64);
1672     try std.testing.expect(linux_support.supported);
1673     try std.testing.expect(linux_support.note == null);
1674 
1675     const mac_support = jitDebugSupportFor(.macos, .aarch64);
1676     try std.testing.expect(mac_support.supported);
1677     try std.testing.expect(mac_support.note != null);
1678     try std.testing.expect(std.mem.containsAtLeast(u8, mac_support.note.?, 1, "lldb"));
1679 
1680     const windows_support = jitDebugSupportFor(.windows, .x86_64);
1681     try std.testing.expect(!windows_support.supported);
1682     try std.testing.expect(windows_support.reason != null);
1683 }
1684 
1685 test "registerJitDebugInfo registers and unregisters entries when supported" {
1686     if (!supportsJitDebugInfo(sys.capabilities.current.arch)) return;
1687 
1688     var builder = LineTableBuilder.init(std.testing.allocator);
1689     defer builder.deinit();
1690 
1691     try builder.record(0, ir.Location.getFile("jit_test.choir", 1, 1), "arith.add");
1692 
1693     var table = try builder.finish();
1694     defer table.deinit();
1695 
1696     const prev_first = __jit_debug_descriptor.first_entry;
1697 
1698     const code_addr = 0x1000;
1699     const code = [_]u8{ 0x90, 0x90 };
1700     const handle_opt = try registerJitDebugInfo(
1701         std.testing.allocator,
1702         sys.capabilities.current.arch,
1703         code_addr,
1704         &code,
1705         &table,
1706         "jit_fn",
1707     );
1708     try std.testing.expect(handle_opt != null);
1709     var handle = handle_opt.?;
1710     try std.testing.expect(handle.entry != null);
1711     try std.testing.expect(__jit_debug_descriptor.first_entry == handle.entry);
1712 
1713     handle.deinit(std.testing.allocator);
1714 
1715     try std.testing.expect(__jit_debug_descriptor.first_entry == prev_first);
1716     try std.testing.expectEqual(@backingInt(JitAction.no_action), __jit_debug_descriptor.action_flag);
1717 }
1718 
1719 test "GDB JIT registration exports its descriptor and its breakpoint by name" {
1720     const descriptor = @extern(*JitDescriptor, .{ .name = "__jit_debug_descriptor" });
1721     try std.testing.expectEqual(&__jit_debug_descriptor, descriptor);
1722     try std.testing.expectEqual(@as(u32, 1), descriptor.version);
1723 
1724     const register = @extern(
1725         *const fn () callconv(.c) void,
1726         .{ .name = "__jit_debug_register_code" },
1727     );
1728     try std.testing.expectEqual(&__jit_debug_register_code, register);
1729 }
1730 
1731 const registration_worker_entries = 8;
1732 
1733 /// One thread of the registration race test. It registers or unregisters its own entries once
1734 /// every worker of its party has arrived.
1735 const RegistrationWorker = struct {
1736     object: []const u8,
1737     started: *std.atomic.Value(usize),
1738     party: usize,
1739     unregistering: bool = false,
1740     entries: [registration_worker_entries]JitCodeEntry = undefined,
1741 
1742     fn run(self: *RegistrationWorker) void {
1743         _ = self.started.fetchAdd(1, .acq_rel);
1744         while (self.started.load(.acquire) < self.party) std.atomic.spinLoopHint();
1745         if (!self.unregistering) {
1746             for (&self.entries) |*entry| registerGdbEntry(entry, self.object);
1747             return;
1748         }
1749         var remaining = self.entries.len;
1750         while (remaining != 0) : (remaining -= 1) {
1751             unregisterGdbEntry(&self.entries[remaining - 1]);
1752         }
1753     }
1754 };
1755 
1756 /// Runs one register or unregister phase across `workers`, and joins every thread it spawns.
1757 fn runRegistrationPhase(workers: []RegistrationWorker, unregistering: bool) !void {
1758     var started = std.atomic.Value(usize).init(0);
1759     var threads: [8]sys.thread.JoinHandle = undefined;
1760     std.debug.assert(workers.len <= threads.len);
1761     for (workers) |*worker| {
1762         worker.started = &started;
1763         worker.party = workers.len;
1764         worker.unregistering = unregistering;
1765     }
1766 
1767     var spawned: usize = 0;
1768     errdefer {
1769         _ = started.fetchAdd(workers.len - spawned, .acq_rel);
1770         for (threads[0..spawned]) |thread| thread.join();
1771     }
1772     while (spawned < workers.len) : (spawned += 1) {
1773         threads[spawned] = try sys.thread.spawn(RegistrationWorker.run, .{&workers[spawned]});
1774     }
1775     for (threads[0..workers.len]) |thread| thread.join();
1776 }
1777 
1778 /// Returns the length of the entry list. Fails when a link disagrees with its neighbor or when the
1779 /// list holds more than `limit` entries.
1780 fn registeredEntryCount(limit: usize) error{CorruptEntryList}!usize {
1781     var length: usize = 0;
1782     var previous: ?*JitCodeEntry = null;
1783     var node = __jit_debug_descriptor.first_entry;
1784     while (node) |entry| : (node = entry.next) {
1785         if (entry.prev != previous) return error.CorruptEntryList;
1786         length += 1;
1787         if (length > limit) return error.CorruptEntryList;
1788         previous = entry;
1789     }
1790     return length;
1791 }
1792 
1793 test "GDB JIT registration serializes concurrent register and unregister" {
1794     if (!sys.thread.threadsSupported()) return error.SkipZigTest;
1795 
1796     const rounds = 64;
1797     const object = [_]u8{0x7f};
1798     var workers: [4]RegistrationWorker = undefined;
1799     for (&workers) |*worker| {
1800         worker.* = .{ .object = &object, .started = undefined, .party = workers.len };
1801     }
1802 
1803     const registered = workers.len * registration_worker_entries;
1804     const limit = registered + 64;
1805     const initial = try registeredEntryCount(limit);
1806     for (0..rounds) |_| {
1807         try runRegistrationPhase(&workers, false);
1808         try std.testing.expectEqual(initial + registered, try registeredEntryCount(limit));
1809         try runRegistrationPhase(&workers, true);
1810         try std.testing.expectEqual(initial, try registeredEntryCount(limit));
1811     }
1812 
1813     const action_flag = __jit_debug_descriptor.action_flag;
1814     try std.testing.expectEqual(@backingInt(JitAction.no_action), action_flag);
1815 }