# 9proc 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`, [9ns](../9ns)) 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`. The tree is a backend of `cloud9.fs.Server`, the file-server engine every 9P server built on cloud9 shares: the engine owns fids, walks, permissions and directory cursors; the core answers its requests from static nodes, exposed variables and providers, and a provider may answer a read later (`error.Again` parks it until a later step). `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. ``` 9proc/ 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 the tree (static nodes, vars, providers) as a backend of cloud9.fs.Server 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 `9proc-demo` binary (below) test/ debug.sh, adv_9proc_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 `9proc` (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("9proc", cloud9_dep.module("9proc")); ``` Embedding the core is three static objects and a push/step/output loop, the same shape as cloud9's `Server`: ```zig const proc9 = @import("9proc"); // the module is `9proc`; identifiers cannot start with a digit const State = struct { ticks: u32, phase: enum { idle, busy } }; const cfg: proc9.Config = .{ .name = "fw", .types = &.{State}, .msize = 2048, .max_fids = 16 }; const S = proc9.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, `proc9.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(proc9.linux.debug.panicHook);` in the root module publishes panics under `/panic`. `demo/main.zig` shows every piece together. ## The demo (`zig build 9proc`, binary `9proc-demo`) 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 9proc/build.zig (jj change id, UTC time) /comptime/types//{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/ 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//{name,stat,stack,regs} /addr/ /mem/{maps,} /hex/ /breakpoints//{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/9proc-demo --unix /tmp/intro.sock & # or --tcp IP:PORT, --stdio, --no-hold zig-out/bin/9ns --unix /tmp/intro.sock -- sh -c ' cat $NINE_MOUNT/build/zig_version; echo cat $NINE_MOUNT/comptime/types/Qid/fields echo "fib 20" > $NINE_MOUNT/runtime/ctl; cat $NINE_MOUNT/runtime/ctl cat $NINE_MOUNT/threads/*/stack' ``` ## Building and testing 9proc lives in the cloud9 repository as `cloud9/9proc/` and is wired into cloud9's `build.zig` through the fragment `9proc/build.zig`. Everything is run from the cloud9 root: ```sh zig build # installs zig-out/bin/9proc-demo with the other binaries zig build 9proc # build and install only the demo zig build 9proc-test # library unit tests (core, vars, scratch, linux) and the demo's zig build 9proc-check-freestanding # compile the core for riscv32-freestanding-none zig build 9proc-debug-test # src/linux/debug.zig unit tests zig build 9proc-debug-itest # test/debug.sh: threads, stacks, breakpoints, panic through 9ns zig build 9proc-adv # hostile raw-9P clients against the demo and the core, # the Linux layer through a 9ns mount (several minutes) zig build -D9proc=false # leave 9proc out zig build 9proc-check-freestanding -Dtarget=riscv32-freestanding-none -D9proc=true ``` `-D9proc` (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 9ns (`-D9ns=true`, the Linux default), unprivileged user namespaces, `/dev/fuse` and Python 3, and skip themselves otherwise.