authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-05-21 08:28:12+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-05-26 06:48:53+01:00
log9ce9ee9ae2e2c74c782de416ed735a77e94d3c07
treeba5e508c40e3be2e7621d9d5cb68725671fae69e
parentbdc2b2846d3f8f3cd81b50cddb56a3a6f3f29485
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: basic support for custom sections

We maintain a map of all section names and, when an input section doesn't have a well-known name like `.text`, we will add a corresponding output section deduplicated with that map. The input section's flags are consulted to determine how to map the output section to a segment. For instance, input sections with SHF_ALLOC and SHF_WRITE are placed in a corresponding output section in the "data" segment, i.e. the segment which contains the `.data` section. This very loosely mimics the way that LLD and GNU ld handle these unknown sections (what they call "orphan sections"). Sections without SHF_ALLOC are placed outside of all segments. If input sections of the same name have conflicting flags an error is emitted, unless the section is not SHF_ALLOC, in which case I have opted to silently drop the input section for now on the basis that it probably isn't necessary to produce a functioning executable. An unintended (but nice!) consequence of this commit is that DWARF debug information from input objects now appears to work correctly in the output binary. I suspect that this won't *always* work correctly (I'm concerned that we might try to pad the end of the `.debug_info` section with zeroes, which I don't believe is valid?), but it's still neat that this works at all! Just to be clear, there is still no debug information emitted for Zig code compiled using this linker. That requires the linker to actually be aware of DWARF sections and to coordinate with the codegen logic to emit data to them. That is of course planned, but is not yet implemetned.

1 files changed, 249 insertions(+), 60 deletions(-)

src/link/Elf2.zig+249-60
......@@ -92,6 +92,8 @@ lazy: std.EnumArray(link.File.LazySymbol.Kind, struct {
9292}),
9393pending_uavs: std.ArrayList(Node.UavMapIndex),
9494relocs: std.ArrayList(Reloc),
95/// Index matches the index into `shdrs`.
96section_by_name: std.array_hash_map.Auto(String(.shstrtab), void),
9597
9698/// Key is the name of a global symbol which has been moved to a new symtab index. Any relocation
9799/// entries which target that symbol must be updated to reference the correct symbol index.
......@@ -361,11 +363,10 @@ const Section = struct {
361363 return &elf.shdrs.items[@intFromEnum(s)];
362364 }
363365
364 fn name(s: Index, elf: *Elf) [:0]const u8 {
365 const str: String(.shstrtab) = switch (elf.shdrPtr(s)) {
366 fn name(s: Index, elf: *Elf) String(.shstrtab) {
367 return switch (elf.shdrPtr(s)) {
366368 inline else => |shdr| @enumFromInt(elf.targetLoad(&shdr.name)),
367369 };
368 return str.slice(elf);
369370 }
370371
371372 fn vaddr(s: Index, elf: *Elf) u64 {
......@@ -1372,7 +1373,7 @@ pub fn navSymbol(elf: *Elf, nav_index: InternPool.Nav.Index) !link.File.SymbolId
13721373 return elf.externSymbol(.{
13731374 .name = @"extern".name.toSlice(ip),
13741375 .lib_name = @"extern".lib_name.toSlice(ip),
1375 .type = navType(ip, nav.resolved.?, elf.base.comp.config.any_non_single_threaded),
1376 .type = elf.navType(nav.resolved.?),
13761377 .linkage = @"extern".linkage,
13771378 .visibility = @"extern".visibility,
13781379 });
......@@ -1956,6 +1957,7 @@ fn create(
19561957 }),
19571958 .pending_uavs = .empty,
19581959 .relocs = .empty,
1960 .section_by_name = .empty,
19591961 .changed_symtab_index = .empty,
19601962 .const_prog_node = .none,
19611963 .synth_prog_node = .none,
......@@ -1992,6 +1994,7 @@ pub fn deinit(elf: *Elf) void {
19921994 for (&elf.lazy.values) |*lazy| lazy.map.deinit(gpa);
19931995 elf.pending_uavs.deinit(gpa);
19941996 elf.relocs.deinit(gpa);
1997 elf.section_by_name.deinit(gpa);
19951998 elf.changed_symtab_index.deinit(gpa);
19961999 elf.* = undefined;
19972000}
......@@ -2561,6 +2564,12 @@ fn initHeaders(
25612564 .addralign = elf.mf.flags.block_size,
25622565 });
25632566 assert(elf.nodes.len == expected_nodes_len);
2567
2568 try elf.section_by_name.ensureUnusedCapacity(gpa, elf.shdrs.items.len);
2569 for (0..elf.shdrs.items.len) |shndx_raw| {
2570 const shndx: Section.Index = @enumFromInt(shndx_raw);
2571 elf.section_by_name.putAssumeCapacityNoClobber(shndx.name(elf), {});
2572 }
25642573}
25652574
25662575pub fn startProgress(elf: *Elf, prog_node: std.Progress.Node) void {
......@@ -2810,28 +2819,129 @@ fn dynsymPtr(elf: *Elf, index: u32) SymPtr {
28102819 }
28112820}
28122821
2813fn navType(
2814 ip: *const InternPool,
2815 nav_resolved: @typeInfo(@FieldType(InternPool.Nav, "resolved")).optional.child,
2816 any_non_single_threaded: bool,
2817) std.elf.STT {
2822fn navType(elf: *const Elf, nav_resolved: InternPool.Nav.Resolved) std.elf.STT {
2823 const any_non_single_threaded = elf.base.comp.config.any_non_single_threaded;
28182824 return if (any_non_single_threaded and nav_resolved.@"threadlocal")
28192825 .TLS
2820 else if (ip.isFunctionType(nav_resolved.type))
2826 else if (elf.base.comp.zcu.?.intern_pool.isFunctionType(nav_resolved.type))
28212827 .FUNC
28222828 else
28232829 .OBJECT;
28242830}
2825fn namedSection(elf: *const Elf, name: []const u8) ?Section.Index {
2826 if (std.mem.eql(u8, name, ".rodata") or
2827 std.mem.startsWith(u8, name, ".rodata.")) return .rodata;
2828 if (std.mem.eql(u8, name, ".text") or
2829 std.mem.startsWith(u8, name, ".text.")) return .text;
2830 if (std.mem.eql(u8, name, ".data") or
2831 std.mem.startsWith(u8, name, ".data.")) return .data;
2832 if (std.mem.eql(u8, name, ".tdata") or
2833 std.mem.startsWith(u8, name, ".tdata.")) return elf.shndx.tdata;
2834 return null;
2831fn mapInputSection(elf: *Elf, opts: struct {
2832 name: []const u8,
2833 flags: std.elf.SHF,
2834 addralign: std.elf.Xword,
2835 entsize: std.elf.Xword,
2836}) !Section.Index {
2837 const gpa = elf.base.comp.gpa;
2838 if (opts.flags.INFO_LINK or
2839 opts.flags.LINK_ORDER or
2840 opts.flags.OS_NONCONFORMING or
2841 (opts.flags.EXECINSTR and opts.flags.WRITE) or
2842 (opts.flags.EXECINSTR and opts.flags.TLS))
2843 {
2844 return error.UnsupportedSectionFlags;
2845 }
2846 if (opts.flags.TLS and elf.ni.tls == .none) {
2847 assert(!elf.base.comp.config.any_non_single_threaded);
2848 return error.TlsSectionUnavailable;
2849 }
2850 const name: []const u8 = switch (elf.ehdrField(.type)) {
2851 .NONE, .CORE, _ => unreachable,
2852 .REL => opts.name,
2853 .EXEC, .DYN => name: {
2854 if (std.mem.startsWith(u8, opts.name, ".text.")) break :name ".text";
2855 if (std.mem.startsWith(u8, opts.name, ".rodata.")) break :name ".rodata";
2856 if (std.mem.startsWith(u8, opts.name, ".data.")) break :name ".data";
2857 if (std.mem.startsWith(u8, opts.name, ".data.rel.ro.")) break :name ".data.rel.ro";
2858 if (std.mem.startsWith(u8, opts.name, ".tdata.")) break :name ".tdata";
2859 if (std.mem.startsWith(u8, opts.name, ".gcc_except_table.")) break :name ".gcc_except_table";
2860 // TODO: actually generate a bss section!
2861 if (std.mem.eql(u8, opts.name, ".bss")) break :name ".data";
2862 if (std.mem.startsWith(u8, opts.name, ".bss.")) break :name ".data";
2863 // TODO: actually generate a tbss section!
2864 if (std.mem.eql(u8, opts.name, ".tbss")) break :name ".tdata";
2865 if (std.mem.startsWith(u8, opts.name, ".tbss.")) break :name ".tdata";
2866 break :name opts.name;
2867 },
2868 };
2869 const existing_shndx: Section.Index = existing: {
2870 const name_shstrtab = try elf.string(.shstrtab, name);
2871 const gop = try elf.section_by_name.getOrPut(gpa, name_shstrtab);
2872 if (gop.found_existing) {
2873 break :existing @enumFromInt(gop.index);
2874 }
2875 errdefer assert(elf.section_by_name.pop().?.key == name_shstrtab);
2876 const parent_node: MappedFile.Node.Index = parent: {
2877 if (!opts.flags.ALLOC) break :parent elf.ni.file;
2878 if (opts.flags.EXECINSTR) break :parent elf.ni.text;
2879 if (opts.flags.TLS) break :parent elf.ni.tls;
2880 if (opts.flags.WRITE) break :parent elf.ni.data;
2881 break :parent elf.ni.rodata;
2882 };
2883 assert(gop.index == elf.shdrs.items.len);
2884 return elf.addSection(parent_node, .{
2885 .name = name,
2886 .type = .NULL, // because initial size is 0
2887 .flags = flags: {
2888 // We need to decompress the section for linking.
2889 var flags = opts.flags;
2890 flags.COMPRESSED = false;
2891 break :flags flags;
2892 },
2893 .node_align = .fromByteUnits(std.math.ceilPowerOfTwoAssert(
2894 usize,
2895 @intCast(@max(opts.addralign, 1)),
2896 )),
2897 .entsize = std.math.lossyCast(u32, opts.entsize),
2898 });
2899 };
2900 // Validate that the input is compatible with this section...
2901 switch (elf.shdrPtr(existing_shndx)) {
2902 inline else => |shdr| {
2903 const cur_flags = elf.targetLoad(&shdr.flags).shf;
2904 if (cur_flags.EXECINSTR != opts.flags.EXECINSTR or
2905 cur_flags.WRITE != opts.flags.WRITE or
2906 cur_flags.TLS != opts.flags.TLS)
2907 {
2908 return error.SectionFlagsConflict;
2909 }
2910
2911 switch (elf.targetLoad(&shdr.type)) {
2912 .NULL, .PROGBITS => {},
2913 else => return error.SectionTypeConflict,
2914 }
2915 },
2916 }
2917 // ...then realign the section's node if necessary...
2918 if (opts.addralign > existing_shndx.get(elf).ni.alignment(&elf.mf).toByteUnits()) {
2919 const new_alignment: std.mem.Alignment = .fromByteUnits(
2920 std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)),
2921 );
2922 try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment);
2923 }
2924 // ...and update the shdr as needed.
2925 switch (elf.shdrPtr(existing_shndx)) {
2926 inline else => |shdr| {
2927 // Combine the section flags.
2928 const cur_flags = elf.targetLoad(&shdr.flags).shf;
2929 elf.targetStore(&shdr.flags, .{ .shf = .{
2930 .EXECINSTR = cur_flags.EXECINSTR,
2931 .WRITE = cur_flags.WRITE,
2932 .TLS = cur_flags.TLS,
2933 .ALLOC = cur_flags.ALLOC or opts.flags.ALLOC,
2934 .STRINGS = cur_flags.STRINGS and opts.flags.STRINGS,
2935 .MERGE = cur_flags.MERGE and opts.flags.MERGE,
2936 } });
2937 // Increase addralign to the maximum of the current value and the new value---the node
2938 // alignment was already increased above.
2939 if (opts.addralign > elf.targetLoad(&shdr.addralign)) {
2940 elf.targetStore(&shdr.addralign, @intCast(opts.addralign));
2941 }
2942 },
2943 }
2944 return existing_shndx;
28352945}
28362946fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex {
28372947 const gpa = zcu.gpa;
......@@ -2845,17 +2955,40 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavM
28452955 const nav_gop = elf.navs.getOrPutAssumeCapacity(nav_index);
28462956 const nmi: Node.NavMapIndex = @enumFromInt(nav_gop.index);
28472957 if (!nav_gop.found_existing) {
2848 const sym_type = navType(ip, nav.resolved.?, elf.base.comp.config.any_non_single_threaded);
28492958 const shndx: Section.Index = section: {
28502959 if (nav.resolved.?.@"linksection".toSlice(ip)) |@"linksection"| {
2851 if (elf.namedSection(@"linksection")) |shndx| break :section shndx;
2960 if (elf.mapInputSection(.{
2961 .name = @"linksection",
2962 .flags = .{
2963 .ALLOC = true,
2964 .EXECINSTR = ip.isFunctionType(nav.resolved.?.type),
2965 .WRITE = !nav.resolved.?.@"const",
2966 .TLS = elf.base.comp.config.any_non_single_threaded and
2967 nav.resolved.?.@"threadlocal",
2968 },
2969 .addralign = 1,
2970 .entsize = 0,
2971 })) |shndx| {
2972 break :section shndx;
2973 } else |err| switch (err) {
2974 error.TlsSectionUnavailable,
2975 error.UnsupportedSectionFlags,
2976 error.SectionTypeConflict,
2977 error.SectionFlagsConflict,
2978 => {}, // fall back to default behavior below
2979
2980 else => |e| return e,
2981 }
2982 }
2983 if (elf.base.comp.config.any_non_single_threaded and nav.resolved.?.@"threadlocal") {
2984 break :section elf.shndx.tdata;
2985 } else if (!nav.resolved.?.@"const") {
2986 break :section .data;
2987 } else if (ip.isFunctionType(nav.resolved.?.type)) {
2988 break :section .text;
2989 } else {
2990 break :section .rodata;
28522991 }
2853 break :section switch (sym_type) {
2854 else => unreachable,
2855 .FUNC => .text,
2856 .OBJECT => .data,
2857 .TLS => elf.shndx.tdata,
2858 };
28592992 };
28602993 const alignment: InternPool.Alignment = switch (Type.fromInterned(nav.resolved.?.type).zigTypeTag(zcu)) {
28612994 .@"fn" => a: {
......@@ -2887,7 +3020,7 @@ fn navMapIndex(elf: *Elf, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavM
28873020 .name = try elf.string(.strtab, nav.fqn.toSlice(ip)),
28883021 .value = 0,
28893022 .size = 0,
2890 .type = sym_type,
3023 .type = elf.navType(nav.resolved.?),
28913024 .shndx = shndx,
28923025 }),
28933026 .first_reloc = .none,
......@@ -3131,26 +3264,69 @@ fn loadObject(
31313264 node_fixed: bool,
31323265 } = switch (section.shdr.type) {
31333266 else => continue,
3134 .PROGBITS => .{
3135 .shndx = elf.namedSection(name) orelse continue,
3136 .has_file_bits = true,
3137 .node_fixed = false,
3138 },
3139 .NOBITS => .{
3140 .shndx = shndx: {
3141 // TODO: actually generate a .bss section. For now, just throw it into `.data`.
3142 if (std.mem.eql(u8, name, ".bss") or std.mem.startsWith(u8, name, ".bss.")) {
3143 break :shndx .data;
3144 }
3145 if (elf.shndx.tdata != .UNDEF and
3146 (std.mem.eql(u8, name, ".tbss") or std.mem.startsWith(u8, name, ".tbss.")))
3147 {
3148 break :shndx elf.shndx.tdata;
3149 }
3267 .PROGBITS, .NOBITS => opts: {
3268 const shndx = elf.mapInputSection(.{
3269 .name = name,
3270 .flags = section.shdr.flags.shf,
3271 .addralign = section.shdr.addralign,
3272 .entsize = section.shdr.entsize,
3273 }) catch |err| switch (err) {
3274 error.TlsSectionUnavailable => return diags.failParse(
3275 path,
3276 "thread-local storage section '{s}' is incompatible with '-fsingle-threaded'",
3277 .{name},
3278 ),
3279 error.UnsupportedSectionFlags => if (!section.shdr.flags.shf.ALLOC) {
3280 // It probably doesn't matter, just skip this section.
3281 continue;
3282 } else return diags.failParse(
3283 path,
3284 "unsupported flags for section '{s}'",
3285 .{name},
3286 ),
3287 error.SectionTypeConflict => if (!section.shdr.flags.shf.ALLOC) {
3288 // It probably doesn't matter, just skip this section.
3289 continue;
3290 } else return diags.failParse(
3291 path,
3292 "type of section '{s}' conflicts with other inputs",
3293 .{name},
3294 ),
3295 error.SectionFlagsConflict => if (!section.shdr.flags.shf.ALLOC) {
3296 // It probably doesn't matter, just skip this section.
3297 continue;
3298 } else return diags.failParse(
3299 path,
3300 "flags of section '{s}' conflict with other inputs",
3301 .{name},
3302 ),
3303 else => |e| return e,
3304 };
3305 if (section.shdr.flags.shf.COMPRESSED) {
3306 // SHF_COMPRESSED is only allowed on non-alloc sections.
3307 if (section.shdr.flags.shf.ALLOC) return diags.failParse(
3308 path,
3309 "section '{s}' has conflicting flags SHF_ALLOC and SHF_COMPRESSED",
3310 .{name},
3311 );
3312 // TODO: handle compressed input sections. We'll need to set a flag to
3313 // indicate that `flushInputSection` needs to decompress the section.
3314 // But because this section isn't SHF_ALLOC, it's probably okay to just
3315 // skip it for now.
31503316 continue;
3151 },
3152 .has_file_bits = false,
3153 .node_fixed = false,
3317 }
3318 break :opts .{
3319 .shndx = shndx,
3320 .has_file_bits = section.shdr.type == .PROGBITS,
3321 // For well-known sections, we know that it's fine to have e.g. random
3322 // padding, so there's no need to make the sections fixed. For custom
3323 // sections, however, we do want fixed nodes to avoid padding.
3324 .node_fixed = shndx != .text and
3325 shndx != .rodata and
3326 shndx != .data and
3327 shndx != .data_rel_ro and
3328 shndx != elf.shndx.tdata,
3329 };
31543330 },
31553331 inline .INIT_ARRAY, .FINI_ARRAY, .PREINIT_ARRAY => |@"type"| .{
31563332 .shndx = shndx: {
......@@ -3372,11 +3548,17 @@ fn loadObject(
33723548 .{rel.info.sym},
33733549 );
33743550 const target = symmap.items[rel.info.sym - 1];
3375 if (target == Symbol.Id.null) return diags.failParse(
3376 path,
3377 "unsupported symbol at index {d} required for relocation",
3378 .{rel.info.sym},
3379 );
3551 if (target == Symbol.Id.null) {
3552 // If this is not an SHF_ALLOC section, then let's let this
3553 // slide for now, because it probably doesn't affect the final
3554 // binary's functionality for this section to be a bit broken.
3555 if (!loc_sec.shdr.flags.shf.ALLOC) continue;
3556 return diags.failParse(
3557 path,
3558 "unsupported symbol at index {d} required for relocation",
3559 .{rel.info.sym},
3560 );
3561 }
33803562 elf.addRelocAssumeCapacity(
33813563 loc_node,
33823564 rel.offset - loc_sec.shdr.addr,
......@@ -3531,17 +3713,21 @@ fn createInitFiniArraySection(
35313713 @"type": std.elf.SHT,
35323714) !void {
35333715 assert(shndx.* == .UNDEF);
3716 const gpa = elf.base.comp.gpa;
35343717 const addr_align: std.mem.Alignment = switch (elf.identClass()) {
35353718 .NONE, _ => unreachable,
35363719 .@"32" => .@"4",
35373720 .@"64" => .@"8",
35383721 };
3722 assert(elf.section_by_name.count() == elf.shdrs.items.len);
3723 try elf.section_by_name.ensureUnusedCapacity(gpa, 1);
35393724 shndx.* = try elf.addSection(elf.ni.data_rel_ro, .{
35403725 .name = "." ++ name,
35413726 .type = @"type",
35423727 .flags = .{ .WRITE = true, .ALLOC = true },
35433728 .node_align = addr_align,
35443729 });
3730 elf.section_by_name.putAssumeCapacityNoClobber(shndx.name(elf), {});
35453731 try elf.ensureUnusedSymbolCapacity(2, .maybe_global);
35463732 _ = elf.addGlobalSymbolAssumeCapacity(.{
35473733 .node = shndx.get(elf).ni,
......@@ -3879,9 +4065,11 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize)
38794065 var bfa: std.heap.BufferFirstAllocator = .init(&bfa_buf, gpa);
38804066 const allocator = bfa.allocator();
38814067
3882 const rela_name = try std.fmt.allocPrint(allocator, ".rela{s}", .{shndx.name(elf)});
4068 const rela_name = try std.fmt.allocPrint(allocator, ".rela{s}", .{shndx.name(elf).slice(elf)});
38834069 defer allocator.free(rela_name);
38844070
4071 assert(elf.section_by_name.count() == elf.shdrs.items.len);
4072 try elf.section_by_name.ensureUnusedCapacity(gpa, 1);
38854073 const rela_shndx = try elf.addSection(.none, .{
38864074 .name = rela_name,
38874075 .type = .RELA,
......@@ -3898,6 +4086,7 @@ fn ensureUnusedRelocCapacity(elf: *Elf, node: MappedFile.Node.Index, len: usize)
38984086 },
38994087 .node_align = elf.mf.flags.block_size,
39004088 });
4089 elf.section_by_name.putAssumeCapacityNoClobber(rela_shndx.name(elf), {});
39014090 shndx.get(elf).rela_shndx = rela_shndx;
39024091 }
39034092 break :rela .{ shndx.get(elf).rela_shndx, len };
......@@ -4272,7 +4461,7 @@ pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool {
42724461 return comp.link_diags.fail(
42734462 "linker failed to read input section '{s}' from \"{f}{f}\": {t}",
42744463 .{
4275 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf),
4464 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
42764465 ii.path(elf).fmtEscapeString(),
42774466 fmtMemberString(ii.member(elf)),
42784467 e,
......@@ -4325,13 +4514,13 @@ fn idleProgNode(
43254514 var name: [std.Progress.Node.max_name_len]u8 = undefined;
43264515 return prog_node.start(name: switch (node) {
43274516 else => |tag| @tagName(tag),
4328 .section => |shndx| shndx.name(elf),
4517 .section => |shndx| shndx.name(elf).slice(elf),
43294518 .input_section => |isi| {
43304519 const ii = isi.input(elf);
43314520 break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{
43324521 ii.path(elf).fmtEscapeString(),
43334522 fmtMemberString(ii.member(elf)),
4334 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf),
4523 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
43354524 }) catch &name;
43364525 },
43374526 .nav => |nmi| {
......@@ -4885,7 +5074,7 @@ fn updateExportsInner(
48855074 const exported_lsi: Symbol.LocalIndex, const @"type": std.elf.STT = switch (exported) {
48865075 .nav => |nav| .{
48875076 (try elf.navMapIndex(zcu, nav)).symbol(elf),
4888 navType(ip, ip.getNav(nav).resolved.?, elf.base.comp.config.any_non_single_threaded),
5077 elf.navType(ip.getNav(nav).resolved.?),
48895078 },
48905079 .uav => |uav| .{ (try elf.uavMapIndex(uav, .none)).symbol(elf), .OBJECT },
48915080 };
......@@ -4987,13 +5176,13 @@ pub fn printNode(
49875176 try w.writeByte(')');
49885177 },
49895178 },
4990 .section => |shndx| try w.print("({s})", .{shndx.name(elf)}),
5179 .section => |shndx| try w.print("({s})", .{shndx.name(elf).slice(elf)}),
49915180 .input_section => |isi| {
49925181 const ii = isi.input(elf);
49935182 try w.print("({f}{f}, {s})", .{
49945183 ii.path(elf).fmtEscapeString(),
49955184 fmtMemberString(ii.member(elf)),
4996 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf),
5185 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
49975186 });
49985187 },
49995188 .nav => |nmi| {