Turn the VM lane into a real engineering loop:
- stage a FreeBSD 15 guest image without polluting this repo;
- install the custom
TWQDEBUGkernel safely; - boot the guest under
bhyve; - run the raw
twq_kernreturnprobe inside the guest automatically; - capture the serial log for regression testing.
This milestone is now proven end-to-end.
The guest boots the custom kernel, runs the probe from rc, and powers off
cleanly. The observed transition is the exact one the scaffold should produce:
- stock host kernel: syscall
468traps withSIGSYS; - guest
TWQDEBUGkernel: knownTWQ_OP_INITreturnsENOTSUP; - guest
TWQDEBUGkernel: invalid op returnsEINVAL.
That confirms the VM path is executing the custom kernel and not silently falling back to the stock host behavior.
The important pieces now in this repo are:
scripts/bhyve/stage-guest.shscripts/bhyve/run-guest.shzig/src/twq_probe_stub.zigelixir/lib/twq_test/vm.exelixir/test/twq_test/vm_integration_test.exs
The Elixir harness can now drive the real guest path through
TwqTest.VM.probe_guest/1.
The initial installkernel path failed because it tried to install a full
module set from an objdir that only had the linked kernel.
The correct FreeBSD-native fix was:
- use
INSTKERNNAME=TWQDEBUG; - use
NO_MODULES=yes; - boot with
kernel="TWQDEBUG"; - preserve module loading with
module_path="/boot/kernel;/boot/modules;/boot/TWQDEBUG".
This keeps the stock image recoverable and avoids unnecessary module churn.
The staging cleanup initially leaked mounted md devices because the script
compared the literal TWQ_GUEST_ROOT path, which could contain ../, against
the canonical mount path reported by mount(8).
The fix was to normalize guest_root after creation so unmount and
mdconfig -d target the real mounted path.
run-guest.sh now supports a real TWQ_SERIAL_LOG capture path instead of
only printing to the terminal. That makes the VM lane usable from ExUnit.
The important guest-side probe lines are:
{"kind":"zig-probe","status":"syscall_error","data":{"syscall":468,"op":1,"arg3":0,"arg4":0,"rc":-1,"errno":45,"errno_name":"ENOTSUP"},"meta":{"component":"zig","binary":"twq-probe-stub"}}
{"kind":"zig-probe","status":"syscall_error","data":{"syscall":468,"op":9999,"arg3":0,"arg4":0,"rc":-1,"errno":22,"errno_name":"EINVAL"},"meta":{"component":"zig","binary":"twq-probe-stub"}}The boot log also confirms the guest is running:
FreeBSD 15.0-STABLE TWQDEBUG amd64
- manual guest staging and serial boot;
- normal Elixir suite:
make test - gated VM integration test:
TWQ_RUN_VM_INTEGRATION=1 mix test test/twq_test/vm_integration_test.exs
The gated integration test passed locally and completed the full stage, boot, probe, capture, and shutdown cycle.
With M02 proven, later kernel milestones can be tested without host rebooting:
- syscall ABI extensions;
- real per-process and per-thread state;
- scheduler feedback hooks;
- guest-side regression checks from Elixir.
The next highest-value work is M05 into M06:
- replace no-op lifecycle anchors with real
twq_procandtwq_threadallocation and teardown; - define the first real kernel-private
TWQ_OP_*state layout; - keep using the VM probe lane to validate each step before touching
libthr.