Commit f8678c48ff
Changed files (4)
src
link
src/link/MachO/Object.zig
@@ -389,7 +389,7 @@ const TextBlockParser = struct {
return switch (rreg.linkage) {
.global => true,
.linkage_unit => lreg.linkage == .translation_unit,
- else => lsym.isTemp(),
+ else => lsym.isTemp(context.zld),
};
}
@@ -417,7 +417,7 @@ const TextBlockParser = struct {
const sym = self.object.symbols.items[nlist_with_index.index];
if (sym.payload != .regular) {
log.err("expected a regular symbol, found {s}", .{sym.payload});
- log.err(" when remapping {s}", .{sym.name});
+ log.err(" when remapping {s}", .{self.zld.getString(sym.strx)});
return error.SymbolIsNotRegular;
}
assert(sym.payload.regular.local_sym_index != 0); // This means the symbol has not been properly resolved.
@@ -463,7 +463,7 @@ const TextBlockParser = struct {
}
}
}
- if (self.zld.globals.contains(senior_sym.name)) break :blk .global;
+ if (self.zld.globals.contains(self.zld.getString(senior_sym.strx))) break :blk .global;
break :blk .static;
} else null;
@@ -598,7 +598,11 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
sectionName(sect),
});
defer self.allocator.free(name);
- const symbol = try Symbol.new(self.allocator, name);
+ const symbol = try zld.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try zld.makeString(name),
+ .payload = .{ .undef = .{} },
+ };
try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
break :symbol symbol;
};
@@ -684,7 +688,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
const reg = &sym.payload.regular;
if (reg.file) |file| {
if (file != self) {
- log.debug("deduping definition of {s} in {s}", .{ sym.name, self.name.? });
+ log.debug("deduping definition of {s} in {s}", .{ zld.getString(sym.strx), self.name.? });
block.deinit();
self.allocator.destroy(block);
continue;
@@ -739,7 +743,11 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
sectionName(sect),
});
defer self.allocator.free(name);
- const symbol = try Symbol.new(self.allocator, name);
+ const symbol = try zld.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try zld.makeString(name),
+ .payload = .{ .undef = .{} },
+ };
try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
break :symbol symbol;
};
@@ -812,7 +820,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void {
}
}
}
- if (zld.globals.contains(sym.name)) break :blk .global;
+ if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global;
break :blk .static;
} else null;
@@ -870,7 +878,7 @@ fn parseRelocs(
try parser.parse();
}
-pub fn symbolFromReloc(self: *Object, rel: macho.relocation_info) !*Symbol {
+pub fn symbolFromReloc(self: *Object, zld: *Zld, rel: macho.relocation_info) !*Symbol {
const symbol = blk: {
if (rel.r_extern == 1) {
break :blk self.symbols.items[rel.r_symbolnum];
@@ -888,12 +896,15 @@ pub fn symbolFromReloc(self: *Object, rel: macho.relocation_info) !*Symbol {
sectionName(sect),
});
defer self.allocator.free(name);
- const symbol = try Symbol.new(self.allocator, name);
- symbol.payload = .{
- .regular = .{
- .linkage = .translation_unit,
- .address = sect.addr,
- .file = self,
+ const symbol = try zld.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try zld.makeString(name),
+ .payload = .{
+ .regular = .{
+ .linkage = .translation_unit,
+ .address = sect.addr,
+ .file = self,
+ },
},
};
try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, symbol);
src/link/MachO/reloc.zig
@@ -407,7 +407,7 @@ pub const Relocation = struct {
const dc_seg = zld.load_commands.items[zld.data_const_segment_cmd_index.?].Segment;
const got = dc_seg.sections.items[zld.got_section_index.?];
const got_index = self.target.got_index orelse {
- log.err("expected GOT entry for symbol '{s}'", .{self.target.name});
+ log.err("expected GOT entry for symbol '{s}'", .{zld.getString(self.target.strx)});
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
};
@@ -446,8 +446,8 @@ pub const Relocation = struct {
break :blk reg.address;
},
- .proxy => |proxy| {
- if (mem.eql(u8, self.target.name, "__tlv_bootstrap")) {
+ .proxy => {
+ if (mem.eql(u8, zld.getString(self.target.strx), "__tlv_bootstrap")) {
break :blk 0; // Dynamically bound by dyld.
}
@@ -460,7 +460,9 @@ pub const Relocation = struct {
break :blk stubs.addr + stubs_index * stubs.reserved2;
},
else => {
- log.err("failed to resolve symbol '{s}' as a relocation target", .{self.target.name});
+ log.err("failed to resolve symbol '{s}' as a relocation target", .{
+ zld.getString(self.target.strx),
+ });
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
},
@@ -634,7 +636,10 @@ pub const Parser = struct {
out_rel.target.got_index = index;
try self.zld.got_entries.append(self.zld.allocator, out_rel.target);
- log.debug("adding GOT entry for symbol {s} at index {}", .{ out_rel.target.name, index });
+ log.debug("adding GOT entry for symbol {s} at index {}", .{
+ self.zld.getString(out_rel.target.strx),
+ index,
+ });
} else if (out_rel.payload == .unsigned) {
const sym = out_rel.target;
switch (sym.payload) {
@@ -697,14 +702,14 @@ pub const Parser = struct {
sym.stubs_index = index;
try self.zld.stubs.append(self.zld.allocator, sym);
- log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index });
+ log.debug("adding stub entry for symbol {s} at index {}", .{ self.zld.getString(sym.strx), index });
}
}
}
fn parseBaseRelInfo(self: *Parser, rel: macho.relocation_info) !Relocation {
const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr);
- const target = try self.object.symbolFromReloc(rel);
+ const target = try self.object.symbolFromReloc(self.zld, rel);
return Relocation{
.offset = offset,
.target = target,
@@ -888,7 +893,7 @@ pub const Parser = struct {
assert(rel.r_pcrel == 0);
assert(self.subtractor == null);
- self.subtractor = try self.object.symbolFromReloc(rel);
+ self.subtractor = try self.object.symbolFromReloc(self.zld, rel);
}
fn parseLoad(self: *Parser, rel: macho.relocation_info) !Relocation {
src/link/MachO/Symbol.zig
@@ -11,8 +11,8 @@ const Dylib = @import("Dylib.zig");
const Object = @import("Object.zig");
const Zld = @import("Zld.zig");
-/// Symbol name. Owned slice.
-name: []const u8,
+/// Offset into the string table.
+strx: u32,
/// Index in GOT table for indirection.
got_index: ?u32 = null,
@@ -160,26 +160,11 @@ pub const Undefined = struct {
}
};
-/// Create new undefined symbol.
-pub fn new(allocator: *Allocator, name: []const u8) !*Symbol {
- const new_sym = try allocator.create(Symbol);
- errdefer allocator.destroy(new_sym);
-
- new_sym.* = .{
- .name = try allocator.dupe(u8, name),
- .payload = .{
- .undef = .{},
- },
- };
-
- return new_sym;
-}
-
pub fn format(self: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
try std.fmt.format(writer, "Symbol {{", .{});
- try std.fmt.format(writer, ".name = {s}, ", .{self.name});
+ try std.fmt.format(writer, ".strx = {d}, ", .{self.strx});
if (self.got_index) |got_index| {
try std.fmt.format(writer, ".got_index = {}, ", .{got_index});
}
@@ -190,11 +175,12 @@ pub fn format(self: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOpt
try std.fmt.format(writer, "}}", .{});
}
-pub fn isTemp(symbol: Symbol) bool {
+pub fn isTemp(symbol: Symbol, zld: *Zld) bool {
+ const sym_name = zld.getString(symbol.strx);
switch (symbol.payload) {
.regular => |regular| {
if (regular.linkage == .translation_unit) {
- return mem.startsWith(u8, symbol.name, "l") or mem.startsWith(u8, symbol.name, "L");
+ return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
}
},
else => {},
@@ -202,24 +188,12 @@ pub fn isTemp(symbol: Symbol) bool {
return false;
}
-pub fn needsTlvOffset(self: Symbol, zld: *Zld) bool {
- if (self.payload != .regular) return false;
-
- const reg = self.payload.regular;
- const seg = zld.load_command.items[reg.segment_id].Segment;
- const sect = seg.sections.items[reg.section_id];
- const sect_type = commands.sectionType(sect);
-
- return sect_type == macho.S_THREAD_LOCAL_VARIABLES;
-}
-
pub fn asNlist(symbol: *Symbol, zld: *Zld) !macho.nlist_64 {
- const n_strx = try zld.makeString(symbol.name);
const nlist = nlist: {
switch (symbol.payload) {
.regular => |regular| {
var nlist = macho.nlist_64{
- .n_strx = n_strx,
+ .n_strx = symbol.strx,
.n_type = macho.N_SECT,
.n_sect = regular.sectionId(zld),
.n_desc = 0,
@@ -239,7 +213,7 @@ pub fn asNlist(symbol: *Symbol, zld: *Zld) !macho.nlist_64 {
.tentative => {
// TODO
break :nlist macho.nlist_64{
- .n_strx = n_strx,
+ .n_strx = symbol.strx,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
@@ -248,7 +222,7 @@ pub fn asNlist(symbol: *Symbol, zld: *Zld) !macho.nlist_64 {
},
.proxy => |proxy| {
break :nlist macho.nlist_64{
- .n_strx = n_strx,
+ .n_strx = symbol.strx,
.n_type = macho.N_UNDF | macho.N_EXT,
.n_sect = 0,
.n_desc = (proxy.dylibOrdinal() * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
@@ -258,7 +232,7 @@ pub fn asNlist(symbol: *Symbol, zld: *Zld) !macho.nlist_64 {
.undef => {
// TODO
break :nlist macho.nlist_64{
- .n_strx = n_strx,
+ .n_strx = symbol.strx,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
@@ -270,10 +244,6 @@ pub fn asNlist(symbol: *Symbol, zld: *Zld) !macho.nlist_64 {
return nlist;
}
-pub fn deinit(symbol: *Symbol, allocator: *Allocator) void {
- allocator.free(symbol.name);
-}
-
pub fn isStab(sym: macho.nlist_64) bool {
return (macho.N_STAB & sym.n_type) != 0;
}
src/link/MachO/Zld.zig
@@ -113,7 +113,6 @@ stub_helper_stubs_start_off: ?u64 = null,
blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
-strtab_cache: std.StringHashMapUnmanaged(u32) = .{},
has_dices: bool = false,
has_stabs: bool = false,
@@ -171,7 +170,7 @@ pub const TextBlock = struct {
.n_value = reg.address,
});
nlists.appendAssumeCapacity(.{
- .n_strx = try zld.makeString(sym.name),
+ .n_strx = sym.strx,
.n_type = macho.N_FUN,
.n_sect = section_id,
.n_desc = 0,
@@ -194,7 +193,7 @@ pub const TextBlock = struct {
},
.global => {
try nlists.append(.{
- .n_strx = try zld.makeString(sym.name),
+ .n_strx = sym.strx,
.n_type = macho.N_GSYM,
.n_sect = 0,
.n_desc = 0,
@@ -203,7 +202,7 @@ pub const TextBlock = struct {
},
.static => {
try nlists.append(.{
- .n_strx = try zld.makeString(sym.name),
+ .n_strx = sym.strx,
.n_type = macho.N_STSYM,
.n_sect = reg.sectionId(zld),
.n_desc = 0,
@@ -349,26 +348,20 @@ pub fn deinit(self: *Zld) void {
self.dylibs.deinit(self.allocator);
for (self.imports.items) |sym| {
- sym.deinit(self.allocator);
self.allocator.destroy(sym);
}
self.imports.deinit(self.allocator);
for (self.locals.items) |sym| {
- sym.deinit(self.allocator);
self.allocator.destroy(sym);
}
self.locals.deinit(self.allocator);
+ for (self.globals.keys()) |key| {
+ self.allocator.free(key);
+ }
self.globals.deinit(self.allocator);
- {
- var it = self.strtab_cache.keyIterator();
- while (it.next()) |key| {
- self.allocator.free(key.*);
- }
- }
- self.strtab_cache.deinit(self.allocator);
self.strtab.deinit(self.allocator);
// TODO dealloc all blocks
@@ -1168,7 +1161,7 @@ fn allocateTextBlocks(self: *Zld) !void {
sym.payload.regular.address = base_addr;
log.debug(" {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
- sym.name,
+ self.getString(sym.strx),
base_addr,
base_addr + block.size,
block.size,
@@ -1231,7 +1224,7 @@ fn writeTextBlocks(self: *Zld) !void {
const sym = self.locals.items[block.local_sym_index];
log.debug(" {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{
- sym.name,
+ self.getString(sym.strx),
aligned_base_off,
aligned_base_off + block.size,
block.size,
@@ -1552,14 +1545,17 @@ fn resolveSymbolsInObject(self: *Zld, object: *Object) !void {
if (Symbol.isSect(sym) and !Symbol.isExt(sym)) {
// Regular symbol local to translation unit
- const symbol = try Symbol.new(self.allocator, sym_name);
- symbol.payload = .{
- .regular = .{
- .linkage = .translation_unit,
- .address = sym.n_value,
- .weak_ref = Symbol.isWeakRef(sym),
- .file = object,
- .local_sym_index = @intCast(u32, self.locals.items.len),
+ const symbol = try self.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try self.makeString(sym_name),
+ .payload = .{
+ .regular = .{
+ .linkage = .translation_unit,
+ .address = sym.n_value,
+ .weak_ref = Symbol.isWeakRef(sym),
+ .file = object,
+ .local_sym_index = @intCast(u32, self.locals.items.len),
+ },
},
};
try self.locals.append(self.allocator, symbol);
@@ -1569,9 +1565,13 @@ fn resolveSymbolsInObject(self: *Zld, object: *Object) !void {
const symbol = self.globals.get(sym_name) orelse symbol: {
// Insert new global symbol.
- const symbol = try Symbol.new(self.allocator, sym_name);
- symbol.payload.undef.file = object;
- try self.globals.putNoClobber(self.allocator, symbol.name, symbol);
+ const symbol = try self.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try self.makeString(sym_name),
+ .payload = .{ .undef = .{ .file = object } },
+ };
+ const alloc_name = try self.allocator.dupe(u8, sym_name);
+ try self.globals.putNoClobber(self.allocator, alloc_name, symbol);
break :symbol symbol;
};
@@ -1628,7 +1628,8 @@ fn resolveSymbolsInObject(self: *Zld, object: *Object) !void {
fn resolveSymbols(self: *Zld) !void {
// TODO mimicking insertion of null symbol from incremental linker.
// This will need to moved.
- const null_sym = try Symbol.new(self.allocator, "");
+ const null_sym = try self.allocator.create(Symbol);
+ null_sym.* = .{ .strx = 0, .payload = .{ .undef = .{} } };
try self.locals.append(self.allocator, null_sym);
// First pass, resolve symbols in provided objects.
@@ -1639,12 +1640,13 @@ fn resolveSymbols(self: *Zld) !void {
// Second pass, resolve symbols in static libraries.
var sym_it = self.globals.iterator();
while (sym_it.next()) |entry| {
+ const sym_name = entry.key_ptr.*;
const symbol = entry.value_ptr.*;
if (symbol.payload != .undef) continue;
for (self.archives.items) |archive| {
// Check if the entry exists in a static archive.
- const offsets = archive.toc.get(symbol.name) orelse {
+ const offsets = archive.toc.get(sym_name) orelse {
// No hit.
continue;
};
@@ -1734,21 +1736,27 @@ fn resolveSymbols(self: *Zld) !void {
// Third pass, resolve symbols in dynamic libraries.
{
// Put dyld_stub_binder as an undefined special symbol.
- const symbol = try Symbol.new(self.allocator, "dyld_stub_binder");
+ const symbol = try self.allocator.create(Symbol);
+ symbol.* = .{
+ .strx = try self.makeString("dyld_stub_binder"),
+ .payload = .{ .undef = .{} },
+ };
const index = @intCast(u32, self.got_entries.items.len);
symbol.got_index = index;
try self.got_entries.append(self.allocator, symbol);
- try self.globals.putNoClobber(self.allocator, symbol.name, symbol);
+ const alloc_name = try self.allocator.dupe(u8, "dyld_stub_binder");
+ try self.globals.putNoClobber(self.allocator, alloc_name, symbol);
}
var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
defer referenced.deinit();
- loop: for (self.globals.values()) |symbol| {
+ loop: for (self.globals.keys()) |sym_name| {
+ const symbol = self.globals.get(sym_name).?;
if (symbol.payload != .undef) continue;
for (self.dylibs.items) |dylib| {
- if (!dylib.symbols.contains(symbol.name)) continue;
+ if (!dylib.symbols.contains(sym_name)) continue;
try referenced.put(dylib, {});
const index = @intCast(u32, self.imports.items.len);
@@ -1798,10 +1806,11 @@ fn resolveSymbols(self: *Zld) !void {
}
var has_undefined = false;
- for (self.globals.values()) |symbol| {
+ for (self.globals.keys()) |sym_name| {
+ const symbol = self.globals.get(sym_name).?;
if (symbol.payload != .undef) continue;
- log.err("undefined reference to symbol '{s}'", .{symbol.name});
+ log.err("undefined reference to symbol '{s}'", .{sym_name});
if (symbol.payload.undef.file) |file| {
log.err(" | referenced in {s}", .{file.name.?});
}
@@ -2344,7 +2353,7 @@ fn writeBindInfoTable(self: *Zld) !void {
.offset = base_offset + sym.got_index.? * @sizeOf(u64),
.segment_id = segment_id,
.dylib_ordinal = proxy.dylibOrdinal(),
- .name = sym.name,
+ .name = self.getString(sym.strx),
});
}
}
@@ -2372,7 +2381,7 @@ fn writeBindInfoTable(self: *Zld) !void {
.offset = binding.offset + base_offset,
.segment_id = match.seg,
.dylib_ordinal = proxy.dylibOrdinal(),
- .name = bind_sym.name,
+ .name = self.getString(bind_sym.strx),
});
}
@@ -2395,7 +2404,7 @@ fn writeBindInfoTable(self: *Zld) !void {
.offset = base_offset,
.segment_id = segment_id,
.dylib_ordinal = proxy.dylibOrdinal(),
- .name = sym.name,
+ .name = self.getString(sym.strx),
});
}
@@ -2435,7 +2444,7 @@ fn writeLazyBindInfoTable(self: *Zld) !void {
.offset = base_offset + sym.stubs_index.? * @sizeOf(u64),
.segment_id = segment_id,
.dylib_ordinal = proxy.dylibOrdinal(),
- .name = sym.name,
+ .name = self.getString(sym.strx),
});
}
}
@@ -2547,7 +2556,7 @@ fn writeExportInfo(self: *Zld) !void {
if (sym.payload != .regular) continue;
const reg = sym.payload.regular;
if (reg.linkage != .global) continue;
- try sorted_globals.append(sym.name);
+ try sorted_globals.append(self.getString(sym.strx));
}
std.sort.sort([]const u8, sorted_globals.items, {}, Sorter.lessThan);
@@ -2556,10 +2565,10 @@ fn writeExportInfo(self: *Zld) !void {
const sym = self.globals.get(sym_name) orelse unreachable;
const reg = sym.payload.regular;
- log.debug(" | putting '{s}' defined at 0x{x}", .{ sym.name, reg.address });
+ log.debug(" | putting '{s}' defined at 0x{x}", .{ sym_name, reg.address });
try trie.put(.{
- .name = sym.name,
+ .name = sym_name,
.vmaddr_offset = reg.address - base_address,
.export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
});
@@ -2597,7 +2606,7 @@ fn writeSymbolTable(self: *Zld) !void {
for (self.locals.items) |symbol, i| {
if (i == 0) continue; // skip null symbol
- if (symbol.isTemp()) continue; // TODO when merging codepaths, this should go into freelist
+ if (symbol.isTemp(self)) continue; // TODO when merging codepaths, this should go into freelist
const reg = symbol.payload.regular;
const nlist = try symbol.asNlist(self);
@@ -2673,7 +2682,7 @@ fn writeSymbolTable(self: *Zld) !void {
const nlist = try sym.asNlist(self);
const id = @intCast(u32, undefs.items.len);
try undefs.append(nlist);
- try undef_dir.putNoClobber(sym.name, id);
+ try undef_dir.putNoClobber(self.getString(sym.strx), id);
}
const nlocals = locals.items.len;
@@ -2735,7 +2744,8 @@ fn writeSymbolTable(self: *Zld) !void {
stubs.reserved1 = 0;
for (self.stubs.items) |sym| {
- const id = undef_dir.get(sym.name) orelse unreachable;
+ const sym_name = self.getString(sym.strx);
+ const id = undef_dir.get(sym_name) orelse unreachable;
try writer.writeIntLittle(u32, dysymtab.iundefsym + id);
}
@@ -2743,7 +2753,8 @@ fn writeSymbolTable(self: *Zld) !void {
for (self.got_entries.items) |sym| {
switch (sym.payload) {
.proxy => {
- const id = undef_dir.get(sym.name) orelse unreachable;
+ const sym_name = self.getString(sym.strx);
+ const id = undef_dir.get(sym_name) orelse unreachable;
try writer.writeIntLittle(u32, dysymtab.iundefsym + id);
},
else => {
@@ -2754,7 +2765,8 @@ fn writeSymbolTable(self: *Zld) !void {
la_symbol_ptr.reserved1 = got.reserved1 + ngot_entries;
for (self.stubs.items) |sym| {
- const id = undef_dir.get(sym.name) orelse unreachable;
+ const sym_name = self.getString(sym.strx);
+ const id = undef_dir.get(sym_name) orelse unreachable;
try writer.writeIntLittle(u32, dysymtab.iundefsym + id);
}
@@ -2940,11 +2952,6 @@ fn writeHeader(self: *Zld) !void {
}
pub fn makeString(self: *Zld, string: []const u8) !u32 {
- if (self.strtab_cache.get(string)) |off| {
- log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off });
- return off;
- }
-
try self.strtab.ensureUnusedCapacity(self.allocator, string.len + 1);
const new_off = @intCast(u32, self.strtab.items.len);
@@ -2953,12 +2960,10 @@ pub fn makeString(self: *Zld, string: []const u8) !u32 {
self.strtab.appendSliceAssumeCapacity(string);
self.strtab.appendAssumeCapacity(0);
- try self.strtab_cache.putNoClobber(self.allocator, try self.allocator.dupe(u8, string), new_off);
-
return new_off;
}
-pub fn getString(self: *Zld, off: u32) ?[]const u8 {
+pub fn getString(self: *Zld, off: u32) []const u8 {
assert(off < self.strtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
}