summaryrefslogtreecommitdiff
path: root/introspect/README.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-19 21:26:05 -0300
committerGabriel Schneider <[email protected]>2026-09-19 21:26:05 -0300
commitb05abcba3ea09ea106ad28364c6e40a3ec31b890 (patch)
tree9170fac5e7e5d8bde108de34a182aaa9d6844117 /introspect/README.md
parentae310a207534b33b7321dd2b9f423a73b1969159 (diff)
downloadcloud9-b05abcba3ea09ea106ad28364c6e40a3ec31b890.tar.gz
cloud9-b05abcba3ea09ea106ad28364c6e40a3ec31b890.zip
Add 9player and introspect as programs beside the library
9player/: FUSE mount CLI that mounts a 9P2000 tree into a fresh user+mount namespace and runs a program in it (no root, no libfuse, no libc). introspect/: the 9P debug/introspection library (freestanding core, value renderers, Linux probe with threads/stacks/memory/breakpoints/panics) and its demo server. Each has its own build fragment; the root build.zig wires them behind -D9player/-Dintrospect with namespaced steps (9player-itest, introspect-check-freestanding, programs-test, ...) and exports the introspect module for dependents. This is the layout for related programs. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'introspect/README.md')
-rw-r--r--introspect/README.md130
1 files changed, 130 insertions, 0 deletions
diff --git a/introspect/README.md b/introspect/README.md
new file mode 100644
index 0000000..ba7120b
--- /dev/null
+++ b/introspect/README.md
@@ -0,0 +1,130 @@
+# introspect
+
+A 9P2000 debug/introspection server as a Zig 0.16 library, built on
+[cloud9](../): a debugger-shaped interface where the protocol is just files.
+Anything that can read a filesystem (a shell, an agent, an editor, `9p`,
+[9player](../9player)) can inspect a running program: build facts, comptime
+type layouts, live values, threads and their stacks, memory, breakpoints,
+panics.
+
+The core (`core`, `vars`) is freestanding: no allocator, no OS, no threads,
+caller-owned static `Storage`, fixed-capacity tables sized at comptime. It
+compiles for `riscv32-freestanding-none`. `scratch` (an in-memory read/write
+tree) takes an allocator; `linux` is the platform layer (listeners, a poll
+loop on one background thread, threads/stacks/registers, memory, breakpoints
+and panics via `std.debug`). [docs/LIBRARY.md](docs/LIBRARY.md) has the full
+contract.
+
+```
+introspect/
+ build.zig fragment imported by cloud9's root build.zig (steps below)
+ src/root.zig pub const core, vars, scratch, linux; Config, Server(cfg), Provider
+ src/core.zig Tree/Server engine on cloud9.Server: fids, walks, dir reads, providers
+ src/vars.zig comptime value renderers (@typeInfo) for /vars
+ src/scratch.zig in-memory read/write tree provider (takes an Allocator)
+ src/freestanding_check.zig root for the riscv32-freestanding-none compile check
+ src/linux/probe.zig background thread + poll loop + unix/tcp/fd listeners
+ src/linux/debug.zig threads, stacks, registers, addr→source, memory, breakpoints, panic
+ src/linux/provider.zig the debug provider (/threads, /addr, /mem, /hex, /breakpoints, /panic)
+ src/linux/runtime.zig /runtime generators (pid, uptime, argv, cwd, env, clients)
+ demo/main.zig the `introspect` binary (below)
+ test/ debug.sh, adv_introspect_hostile, adv_core_hostile, adv_linux_probe, adversarial.sh
+ docs/LIBRARY.md design rules and the module contracts
+```
+
+## Using the library
+
+cloud9's `build.zig` exports two modules: `cloud9` (the protocol) and
+`introspect` (this library, which imports `cloud9` itself). A package that
+depends on cloud9 takes both from the one dependency:
+
+```zig
+const cloud9_dep = b.dependency("cloud9", .{ .target = target, .optimize = optimize });
+exe.root_module.addImport("cloud9", cloud9_dep.module("cloud9"));
+exe.root_module.addImport("introspect", cloud9_dep.module("introspect"));
+```
+
+Embedding the core is three static objects and a push/step/output loop, the
+same shape as cloud9's `Server`:
+
+```zig
+const introspect = @import("introspect");
+
+const State = struct { ticks: u32, phase: enum { idle, busy } };
+const cfg: introspect.Config = .{ .name = "fw", .types = &.{State}, .msize = 2048, .max_fids = 16 };
+const S = introspect.Server(cfg);
+
+var state: State = .{ .ticks = 0, .phase = .idle };
+var storage: S.Storage = undefined; // per connection: in/out frames + snapshot slots
+var shared: S.Shared = undefined; // once: providers and exposed variables
+
+pub fn main() void {
+ shared = .init(&state);
+ shared.expose("state", &state) catch unreachable; // /vars/state/{value,type,size,addr,raw,f/...}
+ var conn: S.Conn = .init(&shared, &storage, cfg.msize);
+ // transport loop: conn.push(bytes) ... while (try conn.step()) {} ... send conn.output(), conn.wrote(n)
+}
+```
+
+On Linux, `introspect.linux.Probe` runs that loop for you on one background
+thread over a Unix, TCP or inherited listener, and adds the debug provider;
+`pub const panic = std.debug.FullPanic(introspect.linux.debug.panicHook);`
+in the root module publishes panics under `/panic`. `demo/main.zig` shows
+every piece together.
+
+## The demo (`zig build introspect`, binary `introspect`)
+
+A single-binary 9P2000 server whose file tree is the binary itself: build-time
+facts, `comptime` reflection, live runtime state, a worker thread whose state
+is exposed under `/vars`, and the Linux debug layer.
+
+```
+/README
+/build/{zig_version,target,optimize,time,change} captured by introspect/build.zig (jj change id, UTC time)
+/comptime/types/<T>/{name,size,align,fields} @sizeOf/@alignOf/@typeInfo, generated at comptime
+/comptime/decls pub declarations of the server module
+/runtime/{pid,ppid,uptime,argv,cwd,env,clients}
+/runtime/fn/<name> reading calls a Zig function (hostname, now, random, uname, fib30)
+/runtime/ctl write "fib N" | "add A B" | "echo TEXT" | "sleep-ms N" | "trap" | "panic"
+/scratch/ in-memory read/write tree
+/vars/state/... the worker's State (readable and writable leaves)
+/threads/<tid>/{name,stat,stack,regs} /addr/<hex> /mem/{maps,<hex>} /hex/<hex>
+/breakpoints/<tid>/{stack,regs,ctl} /panic/{message,stack,ctl}
+```
+
+`/runtime/env` exposes the server's whole environment, so serve it on a Unix
+socket or loopback only.
+
+```sh
+zig-out/bin/introspect --unix /tmp/intro.sock & # or --tcp IP:PORT, --stdio, --no-hold
+zig-out/bin/9player --unix /tmp/intro.sock -- sh -c '
+ cat $NINEPLAYER_MOUNT/build/zig_version; echo
+ cat $NINEPLAYER_MOUNT/comptime/types/Qid/fields
+ echo "fib 20" > $NINEPLAYER_MOUNT/runtime/ctl; cat $NINEPLAYER_MOUNT/runtime/ctl
+ cat $NINEPLAYER_MOUNT/threads/*/stack'
+```
+
+## Building and testing
+
+introspect lives in the cloud9 repository as `cloud9/introspect/` and is
+wired into cloud9's `build.zig` through the fragment `introspect/build.zig`.
+Everything is run from the cloud9 root:
+
+```sh
+zig build # installs zig-out/bin/introspect with the other binaries
+zig build introspect # build and install only the demo
+zig build introspect-test # library unit tests (core, vars, scratch, linux) and the demo's
+zig build introspect-check-freestanding # compile the core for riscv32-freestanding-none
+zig build introspect-debug-test # src/linux/debug.zig unit tests
+zig build introspect-debug-itest # test/debug.sh: threads, stacks, breakpoints, panic through 9player
+zig build introspect-adv # hostile raw-9P clients against the demo and the core,
+ # the Linux layer through a 9player mount (several minutes)
+zig build -Dintrospect=false # leave introspect out
+zig build introspect-check-freestanding -Dtarget=riscv32-freestanding-none -Dintrospect=true
+```
+
+`-Dintrospect` (default: on for Linux targets) enables the module and the
+freestanding check on any target; the demo and the Linux suites are added
+only when the target OS is Linux. The end-to-end suites also need 9player
+(`-D9player=true`, the Linux default), unprivileged user namespaces,
+`/dev/fuse` and Python 3, and skip themselves otherwise.