summaryrefslogtreecommitdiff
path: root/introspect/src/linux/provider.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-19 23:28:22 -0300
committerGabriel Schneider <[email protected]>2026-09-19 23:28:22 -0300
commitba996acfcad1698adbf4a1834fe50e73b1c6cab9 (patch)
tree282ba00ce5b10d7416aecb9f2f0f0a439340a57d /introspect/src/linux/provider.zig
parentb05abcba3ea09ea106ad28364c6e40a3ec31b890 (diff)
downloadcloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.tar.gz
cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.zip
Rename programs: 9player -> 9ns, introspect -> 9proc, app -> web (9web)
Directories, binaries, build options (-D9ns, -D9proc), step names, module name (9proc), thread and fs names, env var NINEPLAYER_MOUNT -> NINE_MOUNT, docs and test scripts. Browser assets move to web/static. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'introspect/src/linux/provider.zig')
-rw-r--r--introspect/src/linux/provider.zig604
1 files changed, 0 insertions, 604 deletions
diff --git a/introspect/src/linux/provider.zig b/introspect/src/linux/provider.zig
deleted file mode 100644
index 9952398..0000000
--- a/introspect/src/linux/provider.zig
+++ /dev/null
@@ -1,604 +0,0 @@
-//! Adapts `debug.Debug` into core `Provider`s. The core mounts providers at
-//! top level only, so one `DebugProvider` registers six of them, all sharing
-//! the same `Debug` and the same snapshot pool:
-//!
-//! /threads/<tid>/{name,stat,stack,regs} (lists only live tids)
-//! /addr/<hex> "fn\nfile:line:col\nmodule\n"
-//! /mem/maps, /mem/<hex> /proc/self/maps; raw bytes at address+offset (writable)
-//! /hex/<hex> hexdump of 256 bytes at the address
-//! /breakpoints/<tid>/{stack,regs,ctl} ctl accepts "continue" (lists only paused tids)
-//! /panic/{message,stack,ctl} ctl accepts "continue"
-//!
-//! /addr, /mem, /hex and /panic carry a README; /threads and /breakpoints
-//! list nothing but tids so that a shell glob over them sees only threads.
-//!
-//! Handles encode `(kind, tid-or-address)` in 56 bits (the core keeps the
-//! low 56 bits of a handle for the qid path): kind in bits 48..55, value in
-//! bits 0..47. Handles carry no reference count, so `clunk` is a no-op.
-//!
-//! The core hands providers a buffer-based `read`, not a writer, so every
-//! text file is generated at `open` into one of `snapshot_slots` fixed slots
-//! (keyed by handle, reference counted across fids) and served from there; a
-//! read at offset 0 regenerates, like the core's own dynamic files. `/mem/<hex>`
-//! is read and written directly at address+offset and never snapshotted, and
-//! `/mem/maps` is read straight from /proc/self/maps at the requested offset
-//! (a big process has more mappings than a snapshot slot holds).
-const std = @import("std");
-const cloud9 = @import("cloud9");
-const core = @import("../core.zig");
-const debug = @import("debug.zig");
-const Writer = std.Io.Writer;
-const Provider = core.Provider;
-const Handle = Provider.Handle;
-const Error = Provider.Error;
-const NodeStat = core.NodeStat;
-
-pub const snapshot_slots = 8;
-pub const snapshot_bytes = 32 * 1024;
-/// Bytes shown by /hex/<hex>.
-pub const hex_bytes = 256;
-
-pub const Tree = enum(u8) { threads, addr, mem, hex, breakpoints, panic };
-pub const tree_names = [_][]const u8{ "threads", "addr", "mem", "hex", "breakpoints", "panic" };
-
-const Kind = enum(u8) {
- root = 0,
- readme,
- thread_dir,
- thread_name,
- thread_stat,
- thread_stack,
- thread_regs,
- addr_file,
- maps,
- mem_file,
- hex_file,
- bp_dir,
- bp_stack,
- bp_regs,
- bp_ctl,
- panic_message,
- panic_stack,
- panic_ctl,
-
- fn isDir(k: Kind) bool {
- return k == .root or k == .thread_dir or k == .bp_dir;
- }
-
- /// Text files generated into a snapshot slot at open.
- fn isText(k: Kind) bool {
- return switch (k) {
- .readme, .thread_name, .thread_stat, .thread_stack, .thread_regs, .addr_file, .hex_file, .bp_stack, .bp_regs, .panic_message, .panic_stack => true,
- else => false,
- };
- }
-
- fn isCtl(k: Kind) bool {
- return k == .bp_ctl or k == .panic_ctl;
- }
-
- fn mode(k: Kind) u32 {
- if (k.isDir()) return cloud9.dmdir | 0o555;
- if (k.isCtl()) return 0o222;
- if (k == .mem_file) return 0o666;
- return 0o444;
- }
-
- fn fixedName(k: Kind) ?[]const u8 {
- return switch (k) {
- .readme => "README",
- .thread_name => "name",
- .thread_stat => "stat",
- .thread_stack, .bp_stack, .panic_stack => "stack",
- .thread_regs, .bp_regs => "regs",
- .maps => "maps",
- .bp_ctl, .panic_ctl => "ctl",
- .panic_message => "message",
- else => null,
- };
- }
-};
-
-const value_bits = 48;
-const value_mask: u64 = (1 << value_bits) - 1;
-
-fn mk(kind: Kind, value: u64) Handle {
- return (@as(u64, @intFromEnum(kind)) << value_bits) | (value & value_mask);
-}
-
-fn kindOf(h: Handle) Kind {
- return @enumFromInt(@as(u8, @truncate(h >> value_bits)));
-}
-
-fn valueOf(h: Handle) u64 {
- return h & value_mask;
-}
-
-const readme_threads =
- \\One directory per thread of this process, named by tid:
- \\ name the thread's comm
- \\ stat state and a few fields of /proc/self/task/<tid>/stat
- \\ stack "#n 0x<addr> in <fn> (<file>:<line>:<col>)" per frame
- \\ regs general registers captured while the thread was stopped
- \\
-;
-const readme_addr =
- \\Walk any hex address: /addr/<hex> reads as "fn\nfile:line:col\nmodule\n".
- \\
-;
-const readme_mem =
- \\maps /proc/self/maps
- \\<hex> raw process memory at that address (+ file offset); writable
- \\
-;
-const readme_hex =
- \\Walk any hex address: /hex/<hex> is a hexdump of the 256 bytes there.
- \\
-;
-const readme_breakpoints =
- \\One directory per thread stopped in @breakpoint(), named by tid:
- \\ stack, regs as under /threads
- \\ ctl write "continue" to resume the thread
- \\
-;
-const readme_panic =
- \\message the first panic's message (empty before any panic)
- \\stack frames of the panicking thread
- \\ctl write "continue" to let the default panic handler run
- \\
-;
-
-fn readmeFor(tree: Tree) []const u8 {
- return switch (tree) {
- .threads => readme_threads,
- .addr => readme_addr,
- .mem => readme_mem,
- .hex => readme_hex,
- .breakpoints => readme_breakpoints,
- .panic => readme_panic,
- };
-}
-
-const Slot = struct {
- handle: Handle = 0,
- refs: u32 = 0,
- len: u32 = 0,
- buf: [snapshot_bytes]u8 = undefined,
-};
-
-pub const DebugProvider = struct {
- d: *debug.Debug,
- slots: [snapshot_slots]Slot = @splat(.{}),
- /// Backs `NodeStat.name` until the next call.
- name_buf: [32]u8 = undefined,
-
- pub fn init(dp: *DebugProvider, d: *debug.Debug) void {
- dp.* = .{ .d = d };
- }
-
- /// The provider for one tree, to pass to `Shared.addProvider`.
- pub fn provider(dp: *DebugProvider, comptime tree: Tree) Provider {
- return .{ .name = tree_names[@intFromEnum(tree)], .ctx = dp, .vtable = vtableFor(tree) };
- }
-
- /// Mounts all six trees; `shared` is a `Server(cfg).Shared`.
- pub fn mountAll(dp: *DebugProvider, shared: anytype) error{Full}!void {
- inline for (comptime std.meta.tags(Tree)) |tree| try shared.addProvider(dp.provider(tree));
- }
-
- fn self(ctx: *anyopaque) *DebugProvider {
- return @ptrCast(@alignCast(ctx));
- }
-
- fn vtableFor(comptime tree: Tree) *const Provider.VTable {
- return &struct {
- const vt: Provider.VTable = .{
- .walk = walkFn,
- .stat = statFn,
- .list = listFn,
- .open = openFn,
- .read = readFn,
- .write = writeFn,
- .close = closeFn,
- .clunk = clunkFn,
- };
- fn walkFn(ctx: *anyopaque, parent: Handle, name: []const u8) Error!Handle {
- return self(ctx).walk(tree, parent, name);
- }
- fn statFn(ctx: *anyopaque, h: Handle, out: *NodeStat) Error!void {
- return self(ctx).stat(tree, h, out);
- }
- fn listFn(ctx: *anyopaque, dir: Handle, index: usize, out: *NodeStat) Error!bool {
- return self(ctx).list(tree, dir, index, out);
- }
- fn openFn(ctx: *anyopaque, h: Handle, mode: u8) Error!void {
- return self(ctx).open(tree, h, mode);
- }
- fn readFn(ctx: *anyopaque, h: Handle, offset: u64, buf: []u8) Error!usize {
- return self(ctx).read(tree, h, offset, buf);
- }
- fn writeFn(ctx: *anyopaque, h: Handle, offset: u64, data: []const u8) Error!usize {
- return self(ctx).write(tree, h, offset, data);
- }
- fn closeFn(ctx: *anyopaque, h: Handle) void {
- self(ctx).close(h);
- }
- fn clunkFn(_: *anyopaque, _: Handle) void {}
- }.vt;
- }
-
- // -- naming ------------------------------------------------------------
-
- fn parseTid(name: []const u8) ?u32 {
- if (name.len == 0 or name.len > 10) return null;
- for (name) |ch| if (!std.ascii.isDigit(ch)) return null;
- return std.fmt.parseInt(u32, name, 10) catch null;
- }
-
- fn parseHex(name: []const u8) ?u64 {
- const digits = if (std.mem.startsWith(u8, name, "0x")) name[2..] else name;
- if (digits.len == 0 or digits.len > 12) return null;
- for (digits) |ch| if (!std.ascii.isHex(ch)) return null;
- const v = std.fmt.parseInt(u64, digits, 16) catch return null;
- if (v > value_mask) return null;
- return v;
- }
-
- fn nodeName(dp: *DebugProvider, h: Handle) []const u8 {
- const k = kindOf(h);
- if (k.fixedName()) |n| return n;
- return switch (k) {
- .root => "",
- .thread_dir, .bp_dir => std.fmt.bufPrint(&dp.name_buf, "{d}", .{valueOf(h)}) catch unreachable,
- .addr_file, .mem_file, .hex_file => std.fmt.bufPrint(&dp.name_buf, "{x}", .{valueOf(h)}) catch unreachable,
- else => unreachable,
- };
- }
-
- // -- vtable ------------------------------------------------------------
-
- fn walk(dp: *DebugProvider, tree: Tree, parent: Handle, name: []const u8) Error!Handle {
- const k = kindOf(parent);
- if (std.mem.eql(u8, name, ".")) return parent;
- if (!k.isDir()) return error.NotDir;
- if (std.mem.eql(u8, name, "..")) return Provider.root;
- switch (k) {
- .root => {
- if (tree != .threads and tree != .breakpoints and std.mem.eql(u8, name, "README")) return mk(.readme, 0);
- switch (tree) {
- .threads => {
- const tid = parseTid(name) orelse return error.NotFound;
- if (!dp.d.threadExists(tid)) return error.NotFound;
- return mk(.thread_dir, tid);
- },
- .addr => return mk(.addr_file, parseHex(name) orelse return error.NotFound),
- .mem => {
- if (std.mem.eql(u8, name, "maps")) return mk(.maps, 0);
- return mk(.mem_file, parseHex(name) orelse return error.NotFound);
- },
- .hex => return mk(.hex_file, parseHex(name) orelse return error.NotFound),
- .breakpoints => {
- const tid = parseTid(name) orelse return error.NotFound;
- if (!dp.d.isPaused(tid)) return error.NotFound;
- return mk(.bp_dir, tid);
- },
- .panic => {
- if (std.mem.eql(u8, name, "message")) return mk(.panic_message, 0);
- if (std.mem.eql(u8, name, "stack")) return mk(.panic_stack, 0);
- if (std.mem.eql(u8, name, "ctl")) return mk(.panic_ctl, 0);
- return error.NotFound;
- },
- }
- },
- .thread_dir => {
- const tid = valueOf(parent);
- if (std.mem.eql(u8, name, "name")) return mk(.thread_name, tid);
- if (std.mem.eql(u8, name, "stat")) return mk(.thread_stat, tid);
- if (std.mem.eql(u8, name, "stack")) return mk(.thread_stack, tid);
- if (std.mem.eql(u8, name, "regs")) return mk(.thread_regs, tid);
- return error.NotFound;
- },
- .bp_dir => {
- const tid = valueOf(parent);
- if (std.mem.eql(u8, name, "stack")) return mk(.bp_stack, tid);
- if (std.mem.eql(u8, name, "regs")) return mk(.bp_regs, tid);
- if (std.mem.eql(u8, name, "ctl")) return mk(.bp_ctl, tid);
- return error.NotFound;
- },
- else => unreachable,
- }
- }
-
- fn fill(dp: *DebugProvider, tree: Tree, h: Handle, out: *NodeStat) void {
- const k = kindOf(h);
- out.* = .{
- .mode = k.mode(),
- .length = if (k == .readme) readmeFor(tree).len else 0,
- .name = dp.nodeName(h),
- .handle = h,
- };
- }
-
- fn stat(dp: *DebugProvider, tree: Tree, h: Handle, out: *NodeStat) Error!void {
- dp.fill(tree, h, out);
- }
-
- fn list(dp: *DebugProvider, tree: Tree, dir: Handle, index: usize, out: *NodeStat) Error!bool {
- const k = kindOf(dir);
- if (!k.isDir()) return error.NotDir;
- const h: Handle = switch (k) {
- .root => switch (tree) {
- .threads => mk(.thread_dir, dp.d.threadAt(index) orelse return false),
- .addr, .hex => if (index == 0) mk(.readme, 0) else return false,
- .mem => switch (index) {
- 0 => mk(.readme, 0),
- 1 => mk(.maps, 0),
- else => return false,
- },
- .breakpoints => mk(.bp_dir, dp.d.pausedAt(index) orelse return false),
- .panic => switch (index) {
- 0 => mk(.readme, 0),
- 1 => mk(.panic_message, 0),
- 2 => mk(.panic_stack, 0),
- 3 => mk(.panic_ctl, 0),
- else => return false,
- },
- },
- .thread_dir => switch (index) {
- 0 => mk(.thread_name, valueOf(dir)),
- 1 => mk(.thread_stat, valueOf(dir)),
- 2 => mk(.thread_stack, valueOf(dir)),
- 3 => mk(.thread_regs, valueOf(dir)),
- else => return false,
- },
- .bp_dir => switch (index) {
- 0 => mk(.bp_stack, valueOf(dir)),
- 1 => mk(.bp_regs, valueOf(dir)),
- 2 => mk(.bp_ctl, valueOf(dir)),
- else => return false,
- },
- else => unreachable,
- };
- dp.fill(tree, h, out);
- return true;
- }
-
- fn open(dp: *DebugProvider, tree: Tree, h: Handle, mode: u8) Error!void {
- const k = kindOf(h);
- const acc = mode & 3;
- const wants_write = acc == cloud9.owrite or acc == cloud9.ordwr;
- const wants_read = acc != cloud9.owrite;
- if (k.isDir()) {
- if (wants_write or mode & cloud9.otrunc != 0) return error.IsDir;
- return;
- }
- if (k.isCtl()) {
- if (wants_read) return error.Perm;
- return;
- }
- if (k == .mem_file) return;
- if (wants_write or mode & cloud9.otrunc != 0) return error.Perm;
- if (k == .maps) return;
- std.debug.assert(k.isText());
- const slot = dp.takeSlot(h) orelse return error.NoSpace;
- errdefer dp.releaseSlot(slot);
- try dp.generate(tree, h, slot);
- }
-
- fn close(dp: *DebugProvider, h: Handle) void {
- if (!kindOf(h).isText()) return;
- if (dp.findSlot(h)) |s| dp.releaseSlot(s);
- }
-
- fn read(dp: *DebugProvider, tree: Tree, h: Handle, offset: u64, buf: []u8) Error!usize {
- const k = kindOf(h);
- if (k.isDir()) return error.IsDir;
- if (k.isCtl()) return error.Perm;
- if (k == .mem_file) {
- const addr = valueOf(h) +% offset;
- return dp.d.readMem(addr, buf) catch |e| mapErr(e);
- }
- if (k == .maps) return dp.d.readMaps(offset, buf) catch |e| mapErr(e);
- const slot = dp.findSlot(h) orelse return error.Io;
- if (offset == 0) try dp.generate(tree, h, slot);
- if (offset >= slot.len) return 0;
- const off: usize = @intCast(offset);
- const n = @min(buf.len, slot.len - off);
- @memcpy(buf[0..n], slot.buf[off..][0..n]);
- return n;
- }
-
- fn write(dp: *DebugProvider, tree: Tree, h: Handle, offset: u64, data: []const u8) Error!usize {
- _ = tree;
- const k = kindOf(h);
- if (k.isDir()) return error.IsDir;
- switch (k) {
- .mem_file => {
- const addr = valueOf(h) +% offset;
- return dp.d.writeMem(addr, data) catch |e| mapErr(e);
- },
- .bp_ctl, .panic_ctl => {
- const cmd = std.mem.trim(u8, data, " \t\r\n\x00");
- if (!std.mem.eql(u8, cmd, "continue")) return error.Unsupported;
- if (k == .bp_ctl) {
- dp.d.resumeThread(@intCast(valueOf(h))) catch |e| return mapErr(e);
- } else {
- dp.d.panicContinue() catch |e| return mapErr(e);
- }
- return data.len;
- },
- else => return error.Perm,
- }
- }
-
- // -- snapshots ---------------------------------------------------------
-
- fn findSlot(dp: *DebugProvider, h: Handle) ?*Slot {
- for (&dp.slots) |*s| if (s.refs > 0 and s.handle == h) return s;
- return null;
- }
-
- fn takeSlot(dp: *DebugProvider, h: Handle) ?*Slot {
- if (dp.findSlot(h)) |s| {
- s.refs += 1;
- return s;
- }
- for (&dp.slots) |*s| if (s.refs == 0) {
- s.* = .{ .handle = h, .refs = 1 };
- return s;
- };
- return null;
- }
-
- fn releaseSlot(_: *DebugProvider, s: *Slot) void {
- s.refs -= 1;
- }
-
- /// (Re)generates the text of `h` into `slot`. A text that does not fit is
- /// truncated, not an error.
- fn generate(dp: *DebugProvider, tree: Tree, h: Handle, slot: *Slot) Error!void {
- var w: Writer = .fixed(&slot.buf);
- slot.len = 0;
- dp.render(tree, h, &w) catch |e| switch (e) {
- error.WriteFailed => {},
- else => return mapErr(e),
- };
- slot.len = @intCast(w.buffered().len);
- }
-
- fn render(dp: *DebugProvider, tree: Tree, h: Handle, w: *Writer) debug.Error!void {
- const d = dp.d;
- const v = valueOf(h);
- switch (kindOf(h)) {
- .readme => w.writeAll(readmeFor(tree)) catch return error.WriteFailed,
- .thread_name => try d.threadName(@intCast(v), w),
- .thread_stat => try d.threadStat(@intCast(v), w),
- .thread_stack => try d.threadStack(@intCast(v), w),
- .thread_regs => try d.threadRegs(@intCast(v), w),
- .addr_file => try d.resolveAddr(@intCast(v), w),
- .hex_file => try d.hexdump(@intCast(v), hex_bytes, w),
- .bp_stack => try d.pausedStack(@intCast(v), w),
- .bp_regs => try d.pausedRegs(@intCast(v), w),
- .panic_message => try d.panicMessage(w),
- .panic_stack => try d.panicStack(w),
- else => unreachable,
- }
- }
-
- fn mapErr(e: debug.Error) Error {
- return switch (e) {
- error.NoThread, error.NotPaused, error.NoPanic => error.NotFound,
- error.Unsupported => error.Unsupported,
- error.WriteFailed => error.NoSpace,
- error.Timeout, error.Busy, error.Unmapped, error.Unexpected, error.AlreadyInitialized, error.InvalidOptions => error.Io,
- };
- }
-};
-
-// ---------------------------------------------------------------------------
-// Tests (through the core's in-memory harness)
-// ---------------------------------------------------------------------------
-
-const testing = std.testing;
-
-const TestCfg: core.Config = .{ .name = "dbgtest", .msize = 8192, .max_fids = 16, .max_providers = 6, .snapshot_slots = 2, .snapshot_bytes = 512 };
-const TS = core.Server(TestCfg);
-
-test "debug provider: threads, addr, mem, hex, breakpoints, panic through the core" {
- var text_buf: [16 * 1024]u8 = undefined;
- var d: debug.Debug = undefined;
- try d.init(.{ .io = testing.io, .text_buf = &text_buf });
- defer d.deinit();
- var dp: DebugProvider = undefined;
- dp.init(&d);
-
- var dummy: u8 = 0;
- var shared: TS.Shared = .init(&dummy);
- try dp.mountAll(&shared);
- var storage: TS.Storage = undefined;
- var h: TS.Harness = undefined;
- try h.init(&shared, &storage);
- defer h.deinit();
-
- // /threads lists tids only, among them this thread.
- const names = try h.listPath(&.{"threads"});
- defer TS.Harness.freeNames(names);
- try testing.expect(names.len >= 1);
- try testing.expect(!TS.Harness.hasName(names, "README"));
- const no_readme = try h.ok(.{ .walk = .{ .fid = 0, .newfid = 7, .names = &.{ "threads", "README" } } });
- try testing.expectEqual(@as(u16, 1), no_readme.walk.nwqid);
- var tid_buf: [16]u8 = undefined;
- const tid = try std.fmt.bufPrint(&tid_buf, "{d}", .{std.os.linux.gettid()});
- try testing.expect(TS.Harness.hasName(names, tid));
-
- // Own stack names this test function's file.
- const stack = try h.readPath(&.{ "threads", tid, "stack" });
- defer testing.allocator.free(stack);
- try testing.expect(std.mem.indexOf(u8, stack, "#0 0x") != null);
-
- // /addr/<hex> of a function here resolves to this file.
- var addr_buf: [32]u8 = undefined;
- const addr_name = try std.fmt.bufPrint(&addr_buf, "{x}", .{@intFromPtr(&DebugProvider.parseTid)});
- const resolved = try h.readPath(&.{ "addr", addr_name });
- defer testing.allocator.free(resolved);
- try testing.expect(std.mem.indexOf(u8, resolved, "provider.zig") != null);
- const partial = try h.ok(.{ .walk = .{ .fid = 0, .newfid = 5, .names = &.{ "addr", "zzz" } } });
- try testing.expectEqual(@as(u16, 1), partial.walk.nwqid);
- try h.walkTo(5, &.{"addr"});
- try h.expectFail(.{ .walk = .{ .fid = 5, .newfid = 6, .names = &.{"zzz"} } }, "file does not exist");
- _ = try h.ok(.{ .clunk = .{ .fid = 5 } });
-
- // /mem/<hex> reads and writes live memory; unmapped is an error.
- var cell: [8]u8 = "abcdefgh".*;
- var mem_buf: [32]u8 = undefined;
- const mem_name = try std.fmt.bufPrint(&mem_buf, "{x}", .{@intFromPtr(&cell)});
- try h.walkTo(1, &.{ "mem", mem_name });
- _ = try h.ok(.{ .open = .{ .fid = 1, .mode = cloud9.ordwr } });
- const r = try h.ok(.{ .read = .{ .fid = 1, .offset = 2, .count = 4 } });
- try testing.expectEqualStrings("cdef", r.read);
- _ = try h.ok(.{ .write = .{ .fid = 1, .offset = 0, .data = "XY" } });
- try testing.expectEqualStrings("XYcdefgh", &cell);
- _ = try h.ok(.{ .clunk = .{ .fid = 1 } });
- try h.walkTo(2, &.{ "mem", "8" });
- _ = try h.ok(.{ .open = .{ .fid = 2, .mode = cloud9.oread } });
- try h.expectFail(.{ .read = .{ .fid = 2, .offset = 0, .count = 4 } }, "i/o error");
- _ = try h.ok(.{ .clunk = .{ .fid = 2 } });
- const maps = try h.readPath(&.{ "mem", "maps" });
- defer testing.allocator.free(maps);
- try testing.expect(std.mem.indexOf(u8, maps, "r-xp") != null or std.mem.indexOf(u8, maps, "r--p") != null);
-
- // /hex/<hex> is a hexdump.
- const hex = try h.readPath(&.{ "hex", mem_name });
- defer testing.allocator.free(hex);
- try testing.expect(std.mem.indexOf(u8, hex, "XYcdefgh") != null);
-
- // Nothing paused, no panic.
- const bps = try h.listPath(&.{"breakpoints"});
- defer TS.Harness.freeNames(bps);
- try testing.expectEqual(@as(usize, 0), bps.len);
- const msg = try h.readPath(&.{ "panic", "message" });
- defer testing.allocator.free(msg);
- try testing.expectEqualStrings("", msg);
- try h.walkTo(3, &.{ "panic", "ctl" });
- _ = try h.ok(.{ .open = .{ .fid = 3, .mode = cloud9.owrite } });
- try h.expectFail(.{ .write = .{ .fid = 3, .offset = 0, .data = "continue" } }, "file does not exist");
- try h.expectFail(.{ .write = .{ .fid = 3, .offset = 0, .data = "bogus" } }, "not supported");
- _ = try h.ok(.{ .clunk = .{ .fid = 3 } });
-
- // Snapshot slots are released on clunk: open more files than slots, sequentially.
- for (0..4) |_| {
- const t = try h.readPath(&.{ "threads", tid, "name" });
- testing.allocator.free(t);
- }
- for (&dp.slots) |s| try testing.expectEqual(@as(u32, 0), s.refs);
-}
-
-test "handle encoding round-trips" {
- const h = mk(.mem_file, 0x7fff_dead_beef);
- try testing.expectEqual(Kind.mem_file, kindOf(h));
- try testing.expectEqual(@as(u64, 0x7fff_dead_beef), valueOf(h));
- try testing.expect(h < (1 << 56));
- try testing.expectEqual(@as(?u64, null), DebugProvider.parseHex("1_0"));
- try testing.expectEqual(@as(?u64, 0x10), DebugProvider.parseHex("0x10"));
- try testing.expectEqual(@as(?u32, null), DebugProvider.parseTid("+5"));
-}