authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:35-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:26:55-04:00
loga98a4f465797a7205f9b1cefedfcb4957b65d593
tree29c122cfe37573c168ed0937bf6c6eecc27b4c42
parentdef87a6efb03197d2387dbbe91f02d1676427034

Coff: special symbols

- Add a step after exports are known that looks for specific required symbols (entry points, _tls_used) and set up relocs as they may move after this point - Remove the special case handling of entry point symbols - Change the linker crash reporter to require --dump-link-snapshot to dump the snapshot

4 files changed, 180 insertions(+), 96 deletions(-)

src/crash_report.zig+6-3
......@@ -133,10 +133,13 @@ fn dumpCrashContext() Io.Writer.Error!void {
133133 } else if (LinkerOp.current) |linker_op| {
134134 try w.writeAll("Linker snapshot:\n\n");
135135 if (build_options.enable_link_snapshots) {
136 try linker_op.lf.dump(w, linker_op.tid);
137 try w.writeAll("\n\n");
136 switch (try linker_op.lf.dump(w, linker_op.tid)) {
137 .unsupported => try w.writeAll("(backend does not support link snapshots))"),
138 .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"),
139 .enabled => try w.writeAll("\n\n"),
140 }
138141 } else {
139 try w.print("(build with -Dlink-snapshot to dump linker state)", .{});
142 try w.writeAll("(build with -Dlink-snapshot to dump linker state)");
140143 }
141144 } else {
142145 try w.writeAll("(no context)\n\n");
src/link.zig+8-2
......@@ -1090,7 +1090,13 @@ pub const File = struct {
10901090 }
10911091 }
10921092
1093 pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !void {
1093 pub const DumpResult = enum {
1094 unsupported,
1095 disabled,
1096 enabled,
1097 };
1098
1099 pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult {
10941100 if (!build_options.enable_link_snapshots) unreachable;
10951101 switch (base.tag) {
10961102 .elf,
......@@ -1100,7 +1106,7 @@ pub const File = struct {
11001106 .spirv,
11011107 .plan9,
11021108 .lld,
1103 => {},
1109 => return .unsupported,
11041110 inline else => |tag| {
11051111 dev.check(tag.devFeature());
11061112 return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid);
src/link/Coff.zig+158-89
......@@ -49,6 +49,7 @@ input_sections: std.ArrayList(Node.InputSection),
4949input_section_pending_index: u32,
5050inputs_complete: bool,
5151exports_complete: bool,
52special_symbols_complete: bool,
5253strings: std.HashMapUnmanaged(
5354 u32,
5455 void,
......@@ -74,7 +75,6 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct {
7475 alignment: InternPool.Alignment,
7576}),
7677relocs: std.ArrayList(Reloc),
77entry: Node.GlobalMapIndex,
7878const_prog_node: std.Progress.Node,
7979synth_prog_node: std.Progress.Node,
8080symbol_prog_node: std.Progress.Node,
......@@ -896,8 +896,7 @@ pub const Symbol = struct {
896896 dll_storage_class: DllStorageClass,
897897 // Only defined for .alias_si and .alias_name
898898 weak_external_strat: WeakExternalStrat,
899 is_entry: bool,
900 _: u7 = 0,
899 _: u8 = 0,
901900 },
902901 /// Relocations contained within this symbol
903902 loc_relocs: Reloc.Index,
......@@ -1017,6 +1016,11 @@ pub const Symbol = struct {
10171016 return &coff.symbols.items[@intFromEnum(si)];
10181017 }
10191018
1019 pub fn unwrap(si: Symbol.Index) ?Symbol.Index {
1020 if (si == .null) return null;
1021 return si;
1022 }
1023
10201024 pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index {
10211025 const ni = si.get(coff).ni;
10221026 assert(ni != .none);
......@@ -1074,12 +1078,6 @@ pub const Symbol = struct {
10741078 pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) void {
10751079 const sym = si.get(coff);
10761080
1077 // TODO: Would this be better modeled using an actual reloc? Would need a si for the header
1078 if (sym.flags.is_entry) {
1079 log.debug("updateEntryRVA({d}, 0x{x})", .{ si, sym.rva });
1080 coff.optionalHeaderStandardPtr().address_of_entry_point = sym.rva;
1081 }
1082
10831081 var ri = sym.target_relocs;
10841082 while (ri != end) {
10851083 const reloc = ri.get(coff);
......@@ -1549,6 +1547,7 @@ fn create(
15491547 .input_section_pending_index = 0,
15501548 .inputs_complete = false,
15511549 .exports_complete = false,
1550 .special_symbols_complete = false,
15521551 .strings = .empty,
15531552 .string_bytes = .empty,
15541553 .section_table = .empty,
......@@ -1567,7 +1566,6 @@ fn create(
15671566 }),
15681567 .pending_uavs = .empty,
15691568 .relocs = .empty,
1570 .entry = .none,
15711569 .const_prog_node = .none,
15721570 .synth_prog_node = .none,
15731571 .symbol_prog_node = .none,
......@@ -2525,7 +2523,6 @@ fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
25252523 .type = .unknown,
25262524 .dll_storage_class = .default,
25272525 .weak_external_strat = undefined,
2528 .is_entry = false,
25292526 },
25302527 .loc_relocs = .none,
25312528 .target_relocs = .none,
......@@ -2642,6 +2639,14 @@ fn getOrPutGlobalSymbol(
26422639 return sym_gop;
26432640}
26442641
2642fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index {
2643 if (coff.globals.get(.{
2644 .name = coff.getString(name).unwrap() orelse return .null,
2645 .lib_name = .none,
2646 })) |si| if (si.get(coff).ni != .none) return si;
2647 return .null;
2648}
2649
26452650pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
26462651 const gop = try coff.getOrPutGlobalSymbol(opts);
26472652 if (gop.found_existing) {
......@@ -3423,8 +3428,6 @@ pub fn addReloc(
34233428) !void {
34243429 const gpa = coff.base.comp.gpa;
34253430 const target = target_si.get(coff);
3426 // TODO: Could duplicate the uninit flag on Symbol.flags?
3427 assert(!coff.targetLoad(loc_si.get(coff).section_number.header(coff).flags).CNT_UNINITIALIZED_DATA);
34283431
34293432 const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len);
34303433 log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{
......@@ -4386,11 +4389,10 @@ fn loadObject(
43864389 // Resolve this once we see alias
43874390 alias.weak_external_psi = .wrap(@intCast(i));
43884391 } else {
4389 sym.setValue(if (alias.si == .null) .{
4390 // See .external branch above
4391 .alias_name = alias.name,
4392 sym.setValue(if (alias.si.unwrap()) |alias_si| .{
4393 .alias_si = alias_si,
43924394 } else .{
4393 .alias_si = alias.si,
4395 .alias_name = alias.name,
43944396 });
43954397 sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux;
43964398 }
......@@ -5027,35 +5029,9 @@ pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
50275029 }
50285030 }
50295031
5030 if (coff.isImage() and comp.config.link_libc) {
5031 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
5032 if (comp.zcu == null) switch (coff.optionalHeaderField(.subsystem)) {
5033 .WINDOWS_CUI => &.{
5034 .{ "main", "mainCRTStartup" },
5035 .{ "wmain", "wmainCRTStartup" },
5036 },
5037 .WINDOWS_GUI => &.{
5038 .{ "WinMain", "WinMainCRTStartup" },
5039 .{ "wWinMain", "wWinMainCRTStartup" },
5040 },
5041 else => unreachable,
5042 } else &.{}
5043 else
5044 &.{.{ null, "_DllMainCRTStartup" }};
5045
5046 for (entries) |entry| {
5047 if (entry[0]) |required_name| {
5048 const str = coff.getString(required_name).unwrap() orelse continue;
5049 const si = coff.globals.get(.{ .name = str, .lib_name = .none }) orelse continue;
5050 if (si.get(coff).ni == .none) continue;
5051 }
5052
5053 const si = try coff.globalSymbol(.{ .name = entry[1], .type = .code });
5054 coff.updateEntry(si.get(coff).gmi);
5055 }
5056 }
5057
50585032 coff.inputs_complete = true;
5033 if (comp.zcu == null)
5034 coff.exports_complete = true;
50595035}
50605036
50615037pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
......@@ -5347,17 +5323,6 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
53475323 const gpa = comp.gpa;
53485324 const max_notes = 4;
53495325
5350 if (coff.isImage()) {
5351 if (coff.entry == .none)
5352 comp.link_diags.addError("no entry point defined", .{})
5353 else if (coff.entry.symbol(coff).get(coff).ni == .none) {
5354 comp.link_diags.addError(
5355 "no definition for entry point '{s}' found",
5356 .{coff.entry.globalName(coff).name.toSlice(coff)},
5357 );
5358 }
5359 }
5360
53615326 var undef_indices: std.ArrayListUnmanaged(u32) = .empty;
53625327 for (coff.relocs.items, 0..) |reloc, reloc_i| {
53635328 const target_sym = reloc.target.get(coff);
......@@ -5406,12 +5371,20 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
54065371 for (undef_indices.items[start_i .. i + 1]) |reference_i| {
54075372 if (err.note_slot == num_full_notes) break;
54085373
5409 const loc_si = coff.relocs.items[reference_i].loc;
5374 const reloc = &coff.relocs.items[reference_i];
5375 const loc_si = reloc.loc;
54105376 if (loc_si == prev_loc_si) continue;
54115377 defer prev_loc_si = loc_si;
54125378
54135379 const loc_sym = loc_si.get(coff);
54145380 switch (coff.getNode(loc_sym.ni)) {
5381 .data_directories => {
5382 const dir_align = std.mem.Alignment.of(std.coff.ImageDataDirectory);
5383 const dir: std.coff.IMAGE.DIRECTORY_ENTRY =
5384 @enumFromInt(dir_align.backward(reloc.offset) / @sizeOf(std.coff.IMAGE.DIRECTORY_ENTRY));
5385 err.addNote("referenced by data directory entry: {t}", .{dir});
5386 },
5387 .optional_header => err.addNote("referenced by optional header field", .{}),
54155388 .input_section => |isi| {
54165389 const other_ioi = isi.input(coff);
54175390 if (loc_sym.gmi == .none) {
......@@ -5577,6 +5550,17 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
55775550 }) coff.late_globals_pending_index += 1;
55785551 break :task;
55795552 }
5553 if (coff.exports_complete and !coff.special_symbols_complete) {
5554 coff.special_symbols_complete = true;
5555 coff.flushSpecialSymbols() catch |err| switch (err) {
5556 error.OutOfMemory => |e| return e,
5557 else => |e| return comp.link_diags.fail(
5558 "linker failed to flush special symbols: {t}",
5559 .{e},
5560 ),
5561 };
5562 break :task;
5563 }
55805564 var lazy_it = coff.lazy.iterator();
55815565 while (lazy_it.next()) |lazy| if (lazy.value.pending_index < lazy.value.map.count()) {
55825566 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid };
......@@ -5713,7 +5697,9 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
57135697 if (coff.pending_uavs.count() > 0) return true;
57145698 if (coff.pending_input != null) return true;
57155699 if (coff.inputs_complete and coff.globals.count() > coff.global_pending_index) return true;
5700 assert(!coff.exports_complete or coff.inputs_complete);
57165701 if (coff.exports_complete and coff.late_globals.items.len > coff.late_globals_pending_index) return true;
5702 if (coff.exports_complete and !coff.special_symbols_complete) return true;
57175703 for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
57185704 if (coff.symbol_table.pending.count() > 0) return true;
57195705 if (coff.input_sections.items.len > coff.input_section_pending_index) return true;
......@@ -6258,6 +6244,105 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
62586244 return true;
62596245}
62606246
6247fn flushSpecialSymbols(coff: *Coff) !void {
6248 const comp = coff.base.comp;
6249 const gpa = comp.gpa;
6250 const machine = coff.targetLoad(&coff.headerPtr().machine);
6251
6252 if (coff.isImage()) {
6253 // TODO: Use explicitly specified entry if set, add err if not found
6254 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
6255 if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
6256 .WINDOWS_CUI => &.{
6257 .{ "main", "mainCRTStartup" },
6258 .{ "wmain", "wmainCRTStartup" },
6259 },
6260 .WINDOWS_GUI => &.{
6261 .{ "WinMain", "WinMainCRTStartup" },
6262 .{ "wWinMain", "wWinMainCRTStartup" },
6263 },
6264 else => unreachable,
6265 } else &.{
6266 .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
6267 }
6268 else
6269 &.{.{ null, "_DllMainCRTStartup" }};
6270
6271 const entry_si = for (entries) |entry| {
6272 if (entry[0]) |required_name|
6273 if (coff.getDefinedGlobal(required_name) == .null) continue;
6274
6275 break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
6276 } else .null;
6277
6278 if (entry_si != .null) {
6279 log.debug(
6280 "entry({s}, {d})",
6281 .{ entry_si.get(coff).gmi.globalName(coff).name.toSlice(coff), entry_si },
6282 );
6283
6284 try coff.symbols.ensureTotalCapacity(gpa, 1);
6285 const optional_hdr_si = coff.addSymbolAssumeCapacity();
6286 const optional_hdr_sym = optional_hdr_si.get(coff);
6287 optional_hdr_sym.ni = Node.known.optional_header;
6288 assert(optional_hdr_sym.loc_relocs == .none);
6289 optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6290
6291 const optional_hdr = coff.optionalHeaderStandardPtr();
6292 optional_hdr.address_of_entry_point = std.mem.nativeTo(
6293 u32,
6294 entry_si.get(coff).rva,
6295 coff.targetEndian(),
6296 );
6297
6298 try coff.addReloc(
6299 optional_hdr_si,
6300 @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
6301 entry_si,
6302 .{ .known = 0 },
6303 switch (machine) {
6304 else => |tag| @panic(@tagName(tag)),
6305 .AMD64 => .{ .AMD64 = .ADDR32NB },
6306 .I386 => .{ .I386 = .DIR32NB },
6307 },
6308 );
6309 }
6310 }
6311
6312 if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
6313 const tls_directory = coff.dataDirectoryPtr(.TLS);
6314 tls_directory.* = .{
6315 .virtual_address = tls_used_si.get(coff).rva,
6316 .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
6317 _ => unreachable,
6318 .PE32 => 24,
6319 .@"PE32+" => 40,
6320 },
6321 };
6322 if (coff.targetEndian() != native_endian)
6323 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6324
6325 try coff.symbols.ensureTotalCapacity(gpa, 1);
6326 const data_dir_si = coff.addSymbolAssumeCapacity();
6327 const data_dir_sym = data_dir_si.get(coff);
6328 data_dir_sym.ni = Node.known.data_directories;
6329 assert(data_dir_sym.loc_relocs == .none);
6330 data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6331
6332 try coff.addReloc(
6333 data_dir_si,
6334 @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
6335 tls_used_si,
6336 .{ .known = 0 },
6337 switch (machine) {
6338 else => |tag| @panic(@tagName(tag)),
6339 .AMD64 => .{ .AMD64 = .ADDR32NB },
6340 .I386 => .{ .I386 = .DIR32NB },
6341 },
6342 );
6343 }
6344}
6345
62616346fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
62626347 const zcu = pt.zcu;
62636348 const gpa = zcu.gpa;
......@@ -6312,7 +6397,7 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
63126397}
63136398
63146399fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
6315 log.debug("flushMoved({s})", .{@tagName(coff.getNode(ni))});
6400 log.debug("flushMoved({s}, n{d})", .{ @tagName(coff.getNode(ni)), ni });
63166401 switch (coff.getNode(ni)) {
63176402 .file,
63186403 .header,
......@@ -6500,7 +6585,7 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
65006585
65016586fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
65026587 const offset, const size = ni.location(&coff.mf).resolve(&coff.mf);
6503 log.debug("flushResized({s}, 0x{x})", .{ @tagName(coff.getNode(ni)), size });
6588 log.debug("flushResized({s}, n{d}, 0x{x})", .{ @tagName(coff.getNode(ni)), ni, size });
65046589
65056590 switch (coff.getNode(ni)) {
65066591 .file => {
......@@ -6779,6 +6864,7 @@ fn updateExportsInner(
67796864 };
67806865 while (try coff.idle(pt.tid)) {}
67816866
6867 const machine = coff.targetLoad(&coff.headerPtr().machine);
67826868 const exported_ni = exported_si.node(coff);
67836869 const exported_sym = exported_si.get(coff);
67846870 for (export_indices) |export_index| {
......@@ -6795,18 +6881,7 @@ fn updateExportsInner(
67956881 export_sym.section_number = exported_sym.section_number;
67966882 defer export_si.applyTargetRelocs(coff, .none);
67976883
6798 if (coff.isImage()) {
6799 if (@"export".opts.name.eqlSlice("_tls_used", ip)) {
6800 const tls_directory = coff.dataDirectoryPtr(.TLS);
6801 tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.value.size };
6802 if (coff.targetEndian() != native_endian)
6803 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6804 } else if ((coff.isExe() and @"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) or
6805 (!coff.isExe() and @"export".opts.name.eqlSlice("_DllMainCRTStartup", ip)))
6806 {
6807 coff.updateEntry(export_sym.gmi);
6808 }
6809 } else continue;
6884 if (!coff.isImage()) continue;
68106885
68116886 const entries_ctx = ExportTable.Adapter{ .coff = coff };
68126887 const gop = try coff.export_table.entries.getOrPutAdapted(
......@@ -6888,7 +6963,11 @@ fn updateExportsInner(
68886963 @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index),
68896964 export_si,
68906965 .{ .known = 0 },
6891 .{ .AMD64 = .ADDR32NB },
6966 switch (machine) {
6967 else => |tag| @panic(@tagName(tag)),
6968 .AMD64 => .{ .AMD64 = .ADDR32NB },
6969 .I386 => .{ .I386 = .DIR32NB },
6970 },
68926971 );
68936972 } else {
68946973 gop.value_ptr.si = export_si;
......@@ -6898,20 +6977,6 @@ fn updateExportsInner(
68986977 }
68996978}
69006979
6901/// Caller ensures that `applyTargetRelocs` will be called on `si` eventually
6902fn updateEntry(coff: *Coff, gmi: Node.GlobalMapIndex) void {
6903 const si = gmi.symbol(coff);
6904 log.debug("updateEntry({s}, {d})", .{ gmi.globalName(coff).name.toSlice(coff), si });
6905
6906 if (coff.entry != .none)
6907 coff.entry.symbol(coff).get(coff).flags.is_entry = false;
6908
6909 // TODO: Should we detect the subsystem like link.exe does (if not explicitly set) based on entry name?
6910
6911 coff.entry = gmi;
6912 si.get(coff).flags.is_entry = true;
6913}
6914
69156980pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void {
69166981 _ = coff;
69176982 _ = exported;
......@@ -6928,11 +6993,15 @@ fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void {
69286993 const stderr = try io.lockStderr(&buffer, null);
69296994 defer io.unlockStderr();
69306995 const w = &stderr.file_writer.interface;
6931 try coff.dump(w, tid);
6996 _ = try coff.dump(w, tid);
69326997}
69336998
6934pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !void {
6935 try coff.printNode(tid, w, .root, 0);
6999pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
7000 if (coff.dump_snapshot) {
7001 try coff.printNode(tid, w, .root, 0);
7002 return .enabled;
7003 }
7004 return .disabled;
69367005}
69377006
69387007pub fn printNode(
src/link/Elf2.zig+8-2
......@@ -161,6 +161,7 @@ textrel_count: u32,
161161const_prog_node: std.Progress.Node,
162162synth_prog_node: std.Progress.Node,
163163input_prog_node: std.Progress.Node,
164dump_snapshot: bool,
164165
165166const Error = link.Error || error{MappedFileIo};
166167
......@@ -2624,6 +2625,7 @@ fn create(
26242625 .synth_prog_node = .none,
26252626 .input_prog_node = .none,
26262627 .textrel_count = 0,
2628 .dump_snapshot = options.enable_link_snapshots,
26272629 };
26282630 errdefer elf.deinit();
26292631
......@@ -6711,8 +6713,12 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm
67116713 _ = name;
67126714}
67136715
6714pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !void {
6715 return elf.printNode(tid, w, .root, 0);
6716pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
6717 if (elf.dump_snapshot) {
6718 try elf.printNode(tid, w, .root, 0);
6719 return .enabled;
6720 }
6721 return .disabled;
67166722}
67176723
67186724pub fn printNode(