// debugger.zig - Low-level debugger (Factor Error Protocol / factorbug) // Extracted from vm.zig. Provides interactive debugging, stack printing, // heap walking, and object inspection. const std = @import("std"); const code_blocks = @import("code_blocks.zig"); const layouts = @import("layouts.zig"); const vm_mod = @import("vm.zig"); const Cell = layouts.Cell; const FactorVM = vm_mod.FactorVM; // --- Free functions (no VM needed) --- fn factorbugUsage(advanced: bool) void { std.debug.print("Basic commands:\n", .{}); std.debug.print(" q ^D -- quit Factor\n", .{}); std.debug.print(" c -- continue executing Factor - NOT SAFE\n", .{}); std.debug.print(" t -- throw exception in Factor - NOT SAFE\n", .{}); std.debug.print(" .s .r .c -- print data, retain, call stacks\n", .{}); if (advanced) { std.debug.print(" help -- reprint this message\n", .{}); std.debug.print("Advanced commands:\n", .{}); std.debug.print(" e -- dump environment\n", .{}); std.debug.print(" d -- dump memory\n", .{}); std.debug.print(" u -- dump object at tagged \n", .{}); std.debug.print(" . -- print object at tagged \n", .{}); std.debug.print(" g -- dump memory layout\n", .{}); std.debug.print(" ds dr -- dump data, retain stacks\n", .{}); std.debug.print(" trim -- toggle output trimming\n", .{}); std.debug.print(" data -- data heap dump\n", .{}); std.debug.print(" words -- words dump\n", .{}); std.debug.print(" tuples -- tuples dump\n", .{}); std.debug.print(" edges -- print all object-to-object references\n", .{}); std.debug.print(" refs -- find data heap references to object\n", .{}); std.debug.print(" push -- push object on data stack - NOT SAFE\n", .{}); std.debug.print(" gc -- trigger full GC - NOT SAFE\n", .{}); std.debug.print(" compact-gc -- trigger compacting GC - NOT SAFE\n", .{}); std.debug.print(" code -- code heap dump\n", .{}); std.debug.print(" abort -- call abort()\n", .{}); std.debug.print(" breakpoint -- trigger system breakpoint\n", .{}); } else { std.debug.print(" help -- full help, including advanced commands\n", .{}); } std.debug.print("\n", .{}); } fn printWord(word_ptr: *const layouts.Word) void { if (word_ptr.vocabulary != layouts.false_object and layouts.hasTag(word_ptr.vocabulary, .string)) { const vocab: *const layouts.String = @ptrFromInt(layouts.UNTAG(word_ptr.vocabulary)); const vocab_len = layouts.untagFixnumUnsigned(vocab.length); const vocab_data = vocab.data(); for (0..@min(vocab_len, 50)) |i| { const ch = vocab_data[i]; if (ch >= 32 and ch < 127) { std.debug.print("{c}", .{@as(u8, @truncate(ch))}); } } std.debug.print(":", .{}); } if (word_ptr.name != layouts.false_object and layouts.hasTag(word_ptr.name, .string)) { const name: *const layouts.String = @ptrFromInt(layouts.UNTAG(word_ptr.name)); const name_len = layouts.untagFixnumUnsigned(name.length); const name_data = name.data(); for (0..@min(name_len, 50)) |i| { const ch = name_data[i]; if (ch >= 32 and ch < 127) { std.debug.print("{c}", .{@as(u8, @truncate(ch))}); } } } else { std.debug.print("#", .{}); } } fn dumpCell(addr: Cell) void { const val = @as(*const Cell, @ptrFromInt(addr)).*; std.debug.print("{x:016}: {x:016} tag {}\n", .{ addr, val, layouts.TAG(val) }); } fn objectSizeForDebug(obj: *const layouts.Object, obj_type: layouts.TypeTag) Cell { const aligned = layouts.alignCell; const cell_size = @sizeOf(Cell); return switch (obj_type) { .array => blk: { const arr: *const layouts.Array = @ptrCast(obj); if (!layouts.hasTag(arr.capacity, .fixnum)) { break :blk layouts.data_alignment; } const cap = layouts.untagFixnumUnsigned(arr.capacity); break :blk aligned(@sizeOf(layouts.Array) + cap * cell_size, layouts.data_alignment); }, .byte_array => blk: { const ba: *const layouts.ByteArray = @ptrCast(obj); if (!layouts.hasTag(ba.capacity, .fixnum)) { break :blk layouts.data_alignment; } const cap = layouts.untagFixnumUnsigned(ba.capacity); break :blk aligned(@sizeOf(layouts.ByteArray) + cap, layouts.data_alignment); }, .string => blk: { const str: *const layouts.String = @ptrCast(obj); if (!layouts.hasTag(str.length, .fixnum)) { break :blk layouts.data_alignment; } const len = layouts.untagFixnumUnsigned(str.length); break :blk aligned(@sizeOf(layouts.String) + len, layouts.data_alignment); }, .word => aligned(@sizeOf(layouts.Word), layouts.data_alignment), .quotation => aligned(@sizeOf(layouts.Quotation), layouts.data_alignment), .wrapper => aligned(@sizeOf(layouts.Wrapper), layouts.data_alignment), .float => aligned(@sizeOf(layouts.BoxedFloat), layouts.data_alignment), .tuple => blk: { const tuple: *const layouts.Tuple = @ptrCast(obj); if (layouts.hasTag(tuple.layout, .array)) { const layout_addr = layouts.UNTAG(tuple.layout); if (layout_addr == 0) break :blk aligned(@sizeOf(layouts.Tuple), layouts.data_alignment); const layout: *const layouts.TupleLayout = @ptrFromInt(layout_addr); if (!layouts.hasTag(layout.size, .fixnum)) { break :blk aligned(@sizeOf(layouts.Tuple), layouts.data_alignment); } const slot_count = layouts.untagFixnumUnsigned(layout.size); break :blk aligned(@sizeOf(layouts.Tuple) + slot_count * cell_size, layouts.data_alignment); } break :blk aligned(@sizeOf(layouts.Tuple), layouts.data_alignment); }, .alien => aligned(@sizeOf(layouts.Alien), layouts.data_alignment), .dll => aligned(@sizeOf(layouts.Dll), layouts.data_alignment), .callstack => blk: { const cs: *const layouts.Callstack = @ptrCast(obj); if (!layouts.hasTag(cs.length, .fixnum)) { break :blk layouts.data_alignment; } const len = layouts.untagFixnumUnsigned(cs.length); break :blk aligned(@sizeOf(layouts.Callstack) + len, layouts.data_alignment); }, .bignum => blk: { const bn: *const layouts.Bignum = @ptrCast(obj); if (!layouts.hasTag(bn.capacity, .fixnum)) { break :blk layouts.data_alignment; } const cap = layouts.untagFixnumUnsigned(bn.capacity); break :blk aligned(@sizeOf(layouts.Bignum) + cap * cell_size, layouts.data_alignment); }, else => layouts.data_alignment, }; } fn printCodeBlockOwner(block: *const code_blocks.CodeBlock) void { const owner = block.owner; if (layouts.hasTag(owner, .word)) { const word: *const layouts.Word = @ptrFromInt(layouts.UNTAG(owner)); if (word.name != layouts.false_object and layouts.hasTag(word.name, .string)) { const str: *const layouts.String = @ptrFromInt(layouts.UNTAG(word.name)); const len = layouts.untagFixnumUnsigned(str.length); const str_data = str.data(); std.debug.print(" word: ", .{}); for (0..@min(len, 60)) |j| { const ch = str_data[j]; if (ch >= 32 and ch < 127) { std.debug.print("{c}", .{@as(u8, @truncate(ch))}); } } } else { std.debug.print(" word: (unnamed 0x{x})", .{owner}); } } else if (layouts.hasTag(owner, .quotation)) { std.debug.print(" quotation 0x{x}", .{owner}); } else { std.debug.print(" owner=0x{x} (tag={})", .{ owner, layouts.TAG(owner) }); } } fn parseHexAddress(addr_str: []const u8) ?Cell { const trimmed = std.mem.trim(u8, addr_str, " \t"); const hex_str = if (std.mem.startsWith(u8, trimmed, "0x") or std.mem.startsWith(u8, trimmed, "0X")) trimmed[2..] else trimmed; return std.fmt.parseInt(Cell, hex_str, 16) catch null; } // --- Functions that take a VM pointer --- fn printFactorString(vm: *FactorVM, str: *const layouts.String) void { const len = layouts.untagFixnumUnsigned(str.length); const data = str.data(); std.debug.print("\"", .{}); const print_len = if (len > 100 and !vm.full_output) @as(usize, 100) else len; for (0..print_len) |i| { const ch = data[i]; if (ch >= 32 and ch < 127) { std.debug.print("{c}", .{@as(u8, @truncate(ch))}); } else { std.debug.print("\\x{x:02}", .{@as(u8, @truncate(ch))}); } } if (print_len < len) std.debug.print("...", .{}); std.debug.print("\"", .{}); } fn printNestedObj(vm: *FactorVM, obj: Cell, nesting: i32) void { if (nesting <= 0 and !vm.full_output) { std.debug.print(" ... ", .{}); return; } const tag = layouts.TAG(obj); switch (@as(layouts.TypeTag, @enumFromInt(@as(u4, @truncate(tag))))) { .fixnum => { std.debug.print("{}", .{layouts.untagFixnum(obj)}); }, .float => { const float_ptr: *const layouts.BoxedFloat = @ptrFromInt(layouts.UNTAG(obj)); std.debug.print("{d}", .{float_ptr.n}); }, .word => { const word_ptr: *const layouts.Word = @ptrFromInt(layouts.UNTAG(obj)); printWord(word_ptr); }, .string => { const str: *const layouts.String = @ptrFromInt(layouts.UNTAG(obj)); printFactorString(vm, str); }, .f => { std.debug.print("f", .{}); }, .array => { std.debug.print("{{", .{}); const arr: *const layouts.Array = @ptrFromInt(layouts.UNTAG(obj)); const arr_len = layouts.untagFixnumUnsigned(arr.capacity); const data = arr.data(); const print_len = if (arr_len > 10 and !vm.full_output) @as(usize, 10) else arr_len; for (0..print_len) |i| { std.debug.print(" ", .{}); printNestedObj(vm, data[i], nesting - 1); } if (print_len < arr_len) std.debug.print("...", .{}); std.debug.print(" }}", .{}); }, .quotation => { std.debug.print("[", .{}); const quot: *const layouts.Quotation = @ptrFromInt(layouts.UNTAG(obj)); if (quot.array != layouts.false_object and layouts.hasTag(quot.array, .array)) { const arr: *const layouts.Array = @ptrFromInt(layouts.UNTAG(quot.array)); const arr_len = layouts.untagFixnumUnsigned(arr.capacity); const data = arr.data(); const print_len = if (arr_len > 10 and !vm.full_output) @as(usize, 10) else arr_len; for (0..print_len) |i| { std.debug.print(" ", .{}); printNestedObj(vm, data[i], nesting - 1); } if (print_len < arr_len) std.debug.print("...", .{}); } std.debug.print(" ]", .{}); }, .wrapper => { std.debug.print("W{{ ", .{}); const wrapper: *const layouts.Wrapper = @ptrFromInt(layouts.UNTAG(obj)); printNestedObj(vm, wrapper.object, nesting - 1); std.debug.print(" }}", .{}); }, .byte_array => { std.debug.print("B{{", .{}); const ba: *const layouts.ByteArray = @ptrFromInt(layouts.UNTAG(obj)); const ba_len = layouts.untagFixnumUnsigned(ba.capacity); const data = ba.data(); const print_len = if (ba_len > 16 and !vm.full_output) @as(usize, 16) else ba_len; for (0..print_len) |i| { std.debug.print(" {}", .{data[i]}); } if (print_len < ba_len) std.debug.print("...", .{}); std.debug.print(" }}", .{}); }, .tuple => { std.debug.print("T{{", .{}); const tuple: *const layouts.Tuple = @ptrFromInt(layouts.UNTAG(obj)); if (layouts.hasTag(tuple.layout, .array)) { const layout: *const layouts.TupleLayout = @ptrFromInt(layouts.UNTAG(tuple.layout)); std.debug.print(" ", .{}); printNestedObj(vm, layout.klass, nesting - 1); if (layouts.hasTag(layout.size, .fixnum)) { const slot_count = layouts.untagFixnumUnsigned(layout.size); const print_count = if (slot_count > 10 and !vm.full_output) @as(usize, 10) else slot_count; const slots = tuple.data(); for (0..print_count) |i| { std.debug.print(" ", .{}); printNestedObj(vm, slots[i], nesting - 1); } if (print_count < slot_count) std.debug.print("...", .{}); } } else { std.debug.print(" ...", .{}); } std.debug.print(" }}", .{}); }, .alien => { const alien: *const layouts.Alien = @ptrFromInt(layouts.UNTAG(obj)); if (alien.expired != layouts.false_object) { std.debug.print("#", .{}); } else if (alien.base != layouts.false_object) { std.debug.print("#", .{}); } else { std.debug.print("#", .{alien.address}); } }, else => { std.debug.print("#<{s} @ 0x{x}>", .{ if (tag < layouts.type_count) @tagName(@as(layouts.TypeTag, @enumFromInt(@as(u4, @truncate(tag))))) else "unknown", obj, }); }, } } fn printObj(vm: *FactorVM, obj: Cell) void { printNestedObj(vm, obj, 10); } fn dumpMemory(from: Cell, to: Cell) void { const start = layouts.UNTAG(from); var addr = start; while (addr <= to) : (addr += @sizeOf(Cell)) { dumpCell(addr); } } fn printDatastack(vm: *FactorVM) void { const ctx = vm.vm_asm.ctx; const seg = ctx.datastack_seg orelse { std.debug.print("*** Datastack segment not initialized\n", .{}); return; }; std.debug.print("==== DATA STACK:\n", .{}); var addr = seg.start; while (addr <= ctx.datastack) : (addr += @sizeOf(Cell)) { const val = @as(*const Cell, @ptrFromInt(addr)).*; printObj(vm, val); std.debug.print("\n", .{}); } } fn printRetainstack(vm: *FactorVM) void { const ctx = vm.vm_asm.ctx; const seg = ctx.retainstack_seg orelse { std.debug.print("*** Retainstack segment not initialized\n", .{}); return; }; std.debug.print("==== RETAIN STACK:\n", .{}); var addr = seg.start; while (addr <= ctx.retainstack) : (addr += @sizeOf(Cell)) { const val = @as(*const Cell, @ptrFromInt(addr)).*; printObj(vm, val); std.debug.print("\n", .{}); } } fn dumpDatastackRaw(vm: *FactorVM) void { const ctx = vm.vm_asm.ctx; const seg = ctx.datastack_seg orelse { std.debug.print("*** Datastack segment not initialized\n", .{}); return; }; dumpMemory(seg.start, ctx.datastack); } fn dumpRetainstackRaw(vm: *FactorVM) void { const ctx = vm.vm_asm.ctx; const seg = ctx.retainstack_seg orelse { std.debug.print("*** Retainstack segment not initialized\n", .{}); return; }; dumpMemory(seg.start, ctx.retainstack); } fn printCallstack(vm: *FactorVM) void { const ctx = vm.vm_asm.ctx; std.debug.print("==== CALL STACK:\n", .{}); std.debug.print(" callstack_top=0x{x}, callstack_bottom=0x{x}\n", .{ ctx.callstack_top, ctx.callstack_bottom }); if (ctx.callstack_top < ctx.callstack_bottom) { const size = ctx.callstack_bottom - ctx.callstack_top; std.debug.print(" size={} bytes\n", .{size}); var frame_addr = ctx.callstack_top; var i: usize = 0; while (frame_addr < ctx.callstack_bottom and i < 30) : (i += 1) { const ret_addr = @as(*const Cell, @ptrFromInt(frame_addr)).*; std.debug.print(" [{:2}] 0x{x}", .{ i, ret_addr }); if (vm.code) |code_heap| { if (code_heap.codeBlockForAddress(ret_addr)) |block| { printCodeBlockOwner(block); const frame_size = block.stackFrameSizeForAddress(ret_addr); if (frame_size == 0) { frame_addr += @sizeOf(Cell); } else { frame_addr += frame_size; } } else { std.debug.print(" (no code block)", .{}); frame_addr += @sizeOf(Cell); } } else { frame_addr += @sizeOf(Cell); } std.debug.print("\n", .{}); } } } fn dumpEnvironment(vm: *FactorVM) void { std.debug.print("--- Special objects:\n", .{}); for (vm.vm_asm.special_objects, 0..) |obj, i| { if (obj != layouts.false_object) { std.debug.print(" [{:3}] 0x{x:016}\n", .{ i, obj }); } } } fn dumpMemoryLayout(vm: *FactorVM) void { std.debug.print("--- Memory layout:\n", .{}); if (vm.data) |data_ptr| { std.debug.print(" Tenured: 0x{x} - 0x{x}\n", .{ data_ptr.tenured.start, data_ptr.tenured.start + data_ptr.tenured.size }); std.debug.print(" Aging: 0x{x} - 0x{x}\n", .{ data_ptr.aging.start, data_ptr.aging.end }); } if (vm.code) |code_ptr| { if (code_ptr.seg) |seg| { std.debug.print(" Code heap: 0x{x} - 0x{x}\n", .{ seg.start, seg.end }); } } std.debug.print(" Nursery: 0x{x} - 0x{x} (here=0x{x})\n", .{ vm.vm_asm.nursery.start, vm.vm_asm.nursery.end, vm.vm_asm.nursery.here, }); } fn parseAndDumpMemory(args: []const u8) void { const trimmed = std.mem.trim(u8, args, " \t"); var iter = std.mem.splitScalar(u8, trimmed, ' '); const addr_str = iter.next() orelse { std.debug.print("Usage: d \n", .{}); return; }; const count_str = iter.next() orelse { std.debug.print("Usage: d \n", .{}); return; }; const addr = parseHexAddress(addr_str) orelse { std.debug.print("Invalid address: {s}\n", .{addr_str}); return; }; const count = std.fmt.parseInt(usize, count_str, 10) catch { std.debug.print("Invalid count: {s}\n", .{count_str}); return; }; const end_addr = addr + count * @sizeOf(Cell); dumpMemory(addr, end_addr); } fn parseAndDumpObject(vm: *FactorVM, args: []const u8) void { const addr = parseHexAddress(args) orelse { std.debug.print("Invalid address: {s}\n", .{args}); return; }; dumpObject(vm, addr); } fn parseAndPrintObject(vm: *FactorVM, args: []const u8) void { const addr = parseHexAddress(args) orelse { std.debug.print("Invalid address: {s}\n", .{args}); return; }; printObj(vm, addr); std.debug.print("\n", .{}); } fn parseAndFindRefs(vm: *FactorVM, args: []const u8) void { const addr = parseHexAddress(args) orelse { std.debug.print("Invalid address: {s}\n", .{args}); return; }; findRefs(vm, addr); } fn parseAndPush(vm: *FactorVM, args: []const u8) void { const addr = parseHexAddress(args) orelse { std.debug.print("Invalid address: {s}\n", .{args}); return; }; const ctx = vm.vm_asm.ctx; ctx.datastack += @sizeOf(Cell); @as(*Cell, @ptrFromInt(ctx.datastack)).* = addr; std.debug.print("Pushed 0x{x}\n", .{addr}); } fn dumpObject(vm: *FactorVM, tagged_addr: Cell) void { const tag = layouts.TAG(tagged_addr); const untagged = layouts.UNTAG(tagged_addr); std.debug.print("Object at 0x{x} (tag={}, {s}):\n", .{ tagged_addr, tag, if (tag < layouts.type_count) @tagName(@as(layouts.TypeTag, @enumFromInt(@as(u4, @truncate(tag))))) else "unknown", }); switch (@as(layouts.TypeTag, @enumFromInt(@as(u4, @truncate(tag))))) { .fixnum => { std.debug.print(" fixnum: {}\n", .{layouts.untagFixnum(tagged_addr)}); }, .array => { const arr: *const layouts.Array = @ptrFromInt(untagged); const len = layouts.untagFixnumUnsigned(arr.capacity); std.debug.print(" capacity: {}\n", .{len}); const data = arr.data(); for (0..@min(len, 20)) |i| { std.debug.print(" [{:3}] ", .{i}); printObj(vm, data[i]); std.debug.print("\n", .{}); } if (len > 20) std.debug.print(" ... ({} more elements)\n", .{len - 20}); }, .string => { const str: *const layouts.String = @ptrFromInt(untagged); const len = layouts.untagFixnumUnsigned(str.length); std.debug.print(" length: {}\n", .{len}); std.debug.print(" value: ", .{}); printFactorString(vm, str); std.debug.print("\n", .{}); }, .word => { const word: *const layouts.Word = @ptrFromInt(untagged); std.debug.print(" name: ", .{}); printWord(word); std.debug.print("\n", .{}); std.debug.print(" hashcode: 0x{x}\n", .{word.hashcode_field}); std.debug.print(" def: 0x{x}\n", .{word.def}); std.debug.print(" props: 0x{x}\n", .{word.props}); std.debug.print(" pic_def: 0x{x}\n", .{word.pic_def}); std.debug.print(" pic_tail_def: 0x{x}\n", .{word.pic_tail_def}); std.debug.print(" subprimitive: 0x{x}\n", .{word.subprimitive}); std.debug.print(" entry_point: 0x{x}\n", .{word.entry_point}); }, .quotation => { const quot: *const layouts.Quotation = @ptrFromInt(untagged); std.debug.print(" array: 0x{x}\n", .{quot.array}); std.debug.print(" cached_effect: 0x{x}\n", .{quot.cached_effect}); std.debug.print(" cache_counter: 0x{x}\n", .{quot.cache_counter}); std.debug.print(" entry_point: 0x{x}\n", .{quot.entry_point}); if (quot.array != layouts.false_object and layouts.hasTag(quot.array, .array)) { std.debug.print(" contents: ", .{}); printObj(vm, tagged_addr); std.debug.print("\n", .{}); } }, .byte_array => { const ba: *const layouts.ByteArray = @ptrFromInt(untagged); const len = layouts.untagFixnumUnsigned(ba.capacity); std.debug.print(" capacity: {}\n", .{len}); const data = ba.data(); std.debug.print(" first 32 bytes: ", .{}); for (0..@min(len, 32)) |i| { std.debug.print("{x:02} ", .{data[i]}); } std.debug.print("\n", .{}); }, .tuple => { const tuple: *const layouts.Tuple = @ptrFromInt(untagged); std.debug.print(" layout: 0x{x}\n", .{tuple.layout}); if (layouts.hasTag(tuple.layout, .tuple)) { const layout: *const layouts.TupleLayout = @ptrFromInt(layouts.UNTAG(tuple.layout)); const size = layouts.untagFixnumUnsigned(layout.size); std.debug.print(" size: {}\n", .{size}); const slots = tuple.data(); for (0..@min(size, 20)) |i| { std.debug.print(" [{:2}] ", .{i}); printObj(vm, slots[i]); std.debug.print("\n", .{}); } } else { std.debug.print(" (layout tag: {})\n", .{layouts.TAG(tuple.layout)}); } }, .wrapper => { const wrapper: *const layouts.Wrapper = @ptrFromInt(untagged); std.debug.print(" wrapped: 0x{x}\n", .{wrapper.object}); std.debug.print(" value: ", .{}); printObj(vm, wrapper.object); std.debug.print("\n", .{}); }, .f => { std.debug.print(" (false singleton)\n", .{}); }, else => { std.debug.print(" raw cells:\n", .{}); for (0..8) |i| { const ptr = untagged + i * @sizeOf(Cell); const val = @as(*const Cell, @ptrFromInt(ptr)).*; std.debug.print(" [{:2}] 0x{x}\n", .{ i, val }); } }, } } fn dumpDataHeap(vm: *FactorVM) void { std.debug.print("--- Data heap dump:\n", .{}); const data_ptr = vm.data orelse { std.debug.print(" (data heap not initialized)\n", .{}); return; }; const tenured_start = data_ptr.tenured.start; const tenured_end = data_ptr.tenured.end; var addr = tenured_start; var obj_count: usize = 0; while (addr < tenured_end) { const obj: *const layouts.Object = @ptrFromInt(addr); if ((obj.header & 3) != 0) break; const obj_type = obj.getType(); obj_count += 1; std.debug.print("{x:0>16} ", .{addr}); printObj(vm, addr | @intFromEnum(obj_type)); std.debug.print("\n", .{}); const size = objectSizeForDebug(obj, obj_type); if (size == 0) { addr += layouts.data_alignment; continue; } addr += size; } std.debug.print(" (dumped {} objects)\n", .{obj_count}); } fn dumpWords(vm: *FactorVM) void { std.debug.print("--- Words dump:\n", .{}); const data_ptr = vm.data orelse { std.debug.print(" (data heap not initialized)\n", .{}); return; }; const tenured_start = data_ptr.tenured.start; const tenured_end = data_ptr.tenured.end; std.debug.print(" Scanning tenured: 0x{x} - 0x{x}\n", .{ tenured_start, tenured_end }); var addr = tenured_start; var word_count: usize = 0; var obj_count: usize = 0; while (addr < tenured_end) { const obj: *const layouts.Object = @ptrFromInt(addr); if ((obj.header & 3) != 0) { std.debug.print(" ERROR: Invalid header at 0x{x}: 0x{x} (bits 0-1 not zero)\n", .{ addr, obj.header }); std.debug.print(" Stopping iteration - likely misaligned due to previous size error\n", .{}); break; } obj_count += 1; const obj_type = obj.getType(); if (obj_count <= 50) { const raw_size = objectSizeForDebug(obj, obj_type); switch (obj_type) { .array => { const arr: *const layouts.Array = @ptrFromInt(addr); std.debug.print(" [obj#{} at 0x{x}: type=array, header=0x{x}, cap_raw=0x{x}, size={}]\n", .{ obj_count, addr, obj.header, arr.capacity, raw_size, }); }, .string => { const str: *const layouts.String = @ptrFromInt(addr); std.debug.print(" [obj#{} at 0x{x}: type=string, header=0x{x}, len_raw=0x{x}, size={}]\n", .{ obj_count, addr, obj.header, str.length, raw_size, }); }, .byte_array => { const ba: *const layouts.ByteArray = @ptrFromInt(addr); std.debug.print(" [obj#{} at 0x{x}: type=byte_array, header=0x{x}, cap_raw=0x{x}, size={}]\n", .{ obj_count, addr, obj.header, ba.capacity, raw_size, }); }, .bignum => { const bn: *const layouts.Bignum = @ptrFromInt(addr); std.debug.print(" [obj#{} at 0x{x}: type=bignum, header=0x{x}, cap_raw=0x{x}, size={}]\n", .{ obj_count, addr, obj.header, bn.capacity, raw_size, }); }, .word => {}, else => { std.debug.print(" [obj#{} at 0x{x}: type={t}, header=0x{x}, size={}]\n", .{ obj_count, addr, obj_type, obj.header, raw_size, }); }, } } if (obj_type == .word) { word_count += 1; const word: *const layouts.Word = @ptrFromInt(addr); std.debug.print("{x:0>16} ", .{addr}); printWord(word); std.debug.print("\n", .{}); } const raw_size = objectSizeForDebug(obj, obj_type); const size = layouts.alignCell(raw_size, layouts.data_alignment); if (size == 0) { addr += layouts.data_alignment; continue; } addr += size; } std.debug.print(" (scanned {} objects, found {} words)\n", .{ obj_count, word_count }); } fn dumpTuples(vm: *FactorVM) void { std.debug.print("--- Tuples dump:\n", .{}); const data_ptr = vm.data orelse { std.debug.print(" (data heap not initialized)\n", .{}); return; }; const tenured_start = data_ptr.tenured.start; const tenured_end = data_ptr.tenured.end; var addr = tenured_start; var tuple_count: usize = 0; while (addr < tenured_end) { const obj: *const layouts.Object = @ptrFromInt(addr); if ((obj.header & 3) != 0) break; const obj_type = obj.getType(); if (obj_type == .tuple) { tuple_count += 1; std.debug.print("{x:0>16} ", .{addr}); printObj(vm, addr | @intFromEnum(layouts.TypeTag.tuple)); std.debug.print("\n", .{}); } const size = objectSizeForDebug(obj, obj_type); if (size == 0) { addr += layouts.data_alignment; continue; } addr += size; } std.debug.print(" (found {} tuples)\n", .{tuple_count}); } fn findRefs(vm: *FactorVM, target: Cell) void { std.debug.print("--- Finding references to 0x{x}:\n", .{target}); const data_ptr = vm.data orelse { std.debug.print(" (data heap not initialized)\n", .{}); return; }; var ref_count: usize = 0; const tenured_start = data_ptr.tenured.start; const tenured_end = data_ptr.tenured.end; var addr = tenured_start; while (addr < tenured_end and ref_count < 50) { const val = @as(*const Cell, @ptrFromInt(addr)).*; if (val == target) { std.debug.print(" Found at 0x{x} (tenured)\n", .{addr}); ref_count += 1; } addr += @sizeOf(Cell); } addr = data_ptr.nursery.start; while (addr < data_ptr.nursery.here and ref_count < 50) { const val = @as(*const Cell, @ptrFromInt(addr)).*; if (val == target) { std.debug.print(" Found at 0x{x} (nursery)\n", .{addr}); ref_count += 1; } addr += @sizeOf(Cell); } for (vm.vm_asm.special_objects, 0..) |obj, i| { if (obj == target) { std.debug.print(" Found in special_objects[{}]\n", .{i}); ref_count += 1; } } std.debug.print(" (found {} references)\n", .{ref_count}); } fn objectSizeTagged(tagged: Cell) Cell { const tag = layouts.typeTag(tagged); if (tag == .fixnum or tag == .f) { return 0; } const addr = layouts.UNTAG(tagged); const obj: *const layouts.Object = @ptrFromInt(addr); const obj_type = obj.getType(); return objectSizeForDebug(obj, obj_type); } fn dumpCodeHeap(vm: *FactorVM) void { const code_ptr = vm.code orelse { std.debug.print("(code heap not initialized)\n", .{}); return; }; const start = code_ptr.code_start; const end = start + code_ptr.code_size; if (start == 0 or code_ptr.code_size == 0) { std.debug.print("0 bytes used by relocation tables\n", .{}); std.debug.print("0 bytes used by parameter tables\n", .{}); return; } var reloc_size: Cell = 0; var parameter_size: Cell = 0; var scan = start; while (scan < end) { const block: *const code_blocks.CodeBlock = @ptrFromInt(scan); const block_size = block.size(); if (block_size == 0) { std.debug.print("ERROR: zero-size block at 0x{x}\n", .{scan}); break; } if (!block.isFree()) { if (block.relocation != layouts.false_object) { reloc_size += objectSizeTagged(block.relocation); } if (block.parameters != layouts.false_object) { parameter_size += objectSizeTagged(block.parameters); } const status: []const u8 = "allocated"; std.debug.print("{x} {x} {s} stack frame {}\n", .{ scan, block_size, status, block.stackFrameSize(), }); } scan += block_size; } std.debug.print("{} bytes used by relocation tables\n", .{reloc_size}); std.debug.print("{} bytes used by parameter tables\n", .{parameter_size}); } // --- Public entry points (called from FactorVM methods) --- pub fn factorbug(vm: *FactorVM) void { if (vm.fep_disabled) { std.debug.print("Low level debugger disabled\n", .{}); std.process.exit(1); } vm.fep_p = true; std.debug.print("Starting low level debugger...\n", .{}); const c_api = @import("c_api.zig"); c_api.lock_console(); const signals = @import("signals.zig"); signals.ignoreCtrlC(); const stdin_fd = 0; if (std.c.isatty(stdin_fd) == 0) { std.debug.print("(stdin is not a terminal - printing stacks and exiting)\n", .{}); printDatastack(vm); printRetainstack(vm); printCallstack(vm); std.process.exit(1); } if (!vm.fep_help_was_shown) { factorbugUsage(false); vm.fep_help_was_shown = true; } var seen_command = false; while (true) { std.debug.print("> ", .{}); var buf: [1024]u8 = undefined; const read_result = std.c.read(stdin_fd, &buf, buf.len); if (read_result < 0) { if (!seen_command) { vm.fep_disabled = true; printDatastack(vm); printRetainstack(vm); printCallstack(vm); } std.process.exit(1); } const bytes_read: usize = @intCast(read_result); if (bytes_read == 0) { if (!seen_command) { vm.fep_disabled = true; printDatastack(vm); printRetainstack(vm); printCallstack(vm); } std.process.exit(1); } const line = buf[0..bytes_read]; const cmd = std.mem.trim(u8, line, " \t\r\n"); if (cmd.len == 0) continue; seen_command = true; if (std.mem.eql(u8, cmd, "q")) { std.process.exit(1); } else if (std.mem.eql(u8, cmd, "c")) { c_api.unlock_console(); signals.handleCtrlC(); vm.fep_p = false; return; } else if (std.mem.eql(u8, cmd, "t")) { c_api.unlock_console(); signals.handleCtrlC(); vm.fep_p = false; vm.generalError(.interrupt, layouts.false_object, layouts.false_object); } else if (std.mem.eql(u8, cmd, ".s")) { printDatastack(vm); } else if (std.mem.eql(u8, cmd, ".r")) { printRetainstack(vm); } else if (std.mem.eql(u8, cmd, ".c")) { printCallstack(vm); } else if (std.mem.eql(u8, cmd, "ds")) { dumpDatastackRaw(vm); } else if (std.mem.eql(u8, cmd, "dr")) { dumpRetainstackRaw(vm); } else if (std.mem.eql(u8, cmd, "e")) { dumpEnvironment(vm); } else if (std.mem.eql(u8, cmd, "g")) { dumpMemoryLayout(vm); } else if (std.mem.eql(u8, cmd, "help")) { factorbugUsage(true); } else if (std.mem.eql(u8, cmd, "trim")) { vm.full_output = !vm.full_output; std.debug.print("Output trimming: {s}\n", .{if (vm.full_output) "disabled" else "enabled"}); } else if (std.mem.eql(u8, cmd, "data")) { dumpDataHeap(vm); } else if (std.mem.eql(u8, cmd, "words")) { dumpWords(vm); } else if (std.mem.eql(u8, cmd, "tuples")) { dumpTuples(vm); } else if (std.mem.eql(u8, cmd, "code")) { dumpCodeHeap(vm); } else if (std.mem.eql(u8, cmd, "gc")) { if (vm.gc) |gc_inst| { std.debug.print("Running full GC...\n", .{}); gc_inst.collectFull(false); std.debug.print("GC complete.\n", .{}); } else { std.debug.print("GC not available.\n", .{}); } } else if (std.mem.eql(u8, cmd, "compact-gc")) { if (vm.gc) |gc_inst| { std.debug.print("Running compacting GC...\n", .{}); gc_inst.collectFull(true); std.debug.print("Compacting GC complete.\n", .{}); } else { std.debug.print("GC not available.\n", .{}); } } else if (std.mem.eql(u8, cmd, "abort")) { std.debug.panic("User requested abort", .{}); } else if (std.mem.eql(u8, cmd, "breakpoint")) { const arch = @import("builtin").cpu.arch; if (arch == .x86_64 or arch == .x86) { asm volatile ("ud2"); } else if (arch == .aarch64) { asm volatile ("udf #0"); } else { const ptr: *volatile u8 = @ptrFromInt(0); _ = ptr.*; } } else if (std.mem.startsWith(u8, cmd, "d ")) { parseAndDumpMemory(cmd[2..]); } else if (std.mem.startsWith(u8, cmd, "u ")) { parseAndDumpObject(vm, cmd[2..]); } else if (std.mem.startsWith(u8, cmd, ". ")) { parseAndPrintObject(vm, cmd[2..]); } else if (std.mem.startsWith(u8, cmd, "refs ")) { parseAndFindRefs(vm, cmd[5..]); } else if (std.mem.startsWith(u8, cmd, "push ")) { parseAndPush(vm, cmd[5..]); } else { std.debug.print("unknown command: '{s}'\n", .{cmd}); } } } pub fn criticalError(vm: *FactorVM, msg: []const u8, tagged: Cell) void { std.debug.print("You have triggered a bug in Factor. Please report.\n", .{}); std.debug.print("critical_error: {s}: 0x{x}\n", .{ msg, tagged }); factorbug(vm); }