Punch list §25 item 3.8 complete. Added after starting item 4.1
surfaced the need to thread a vm_id into stadium_admit()'s new quota
parameter; Captain Bob ruled UUID/GUID rather than keeping the
narrower uint32_t.
New VMUuid type (vm_uuid.h/vm_uuid.c): two uint64_t halves, RFC-4122-
shaped for logging. Not real randomness -- checked directly against
QEMU 10.2.1's actual CPU feature set: amd64 RDRAND and riscv64 Zkr are
both real, available features here; aarch64 has no RNG property on any
CPU model including "max" (verified exhaustively via QMP
query-cpu-model-expansion). Captain Bob ruled a uniform fallback
across all three ISAs rather than a per-architecture split.
Fallback is a deterministic PRNG (splitmix64) seeded from the Mama
capsule's content hash, pre-filling a 16-entry FIFO pool at boot and
refilling with another batch of the same stream when exhausted --
exactly the shape requested. Same capsule booted twice produces the
same id sequence, preserving the dict_hash reproducibility this
session has relied on throughout.
Hera keeps a fixed, reserved all-zero id, not drawn from the pool --
capsule_birth.c uses vm_id == 0 as a load-bearing sentinel in three
places (KILL protection x2, fleet heat-fanout parent-chain
terminator), found by reading before writing any code.
Two real sentinel-collision bugs caught before shipping, same class as
STADIUM_CONTAINS_NONE: vm_uuid_none() (all-ones, not all-zero) for
"not yet assigned"/"no VM" placeholders; confirmed item 3.7's quota
table already used an in_use boolean rather than a vm_id sentinel, so
no second collision was actually possible there -- the dead,
never-referenced STADIUM_QUOTA_SLOT_EMPTY macro was removed.
Blast radius larger than first scoped, flagged mid-work rather than
silently absorbed: capsule_vm_physics.c/.h (the fleet heat-transfer
layer item 2.1 modified earlier this session) has its own vm_id-keyed
node table and walks parent_vm_id chains through the same identity
space, so it needed the same change, plus its callers in
mama_forth_words.c and sk_vm_bootstrap.c.
One live FORTH word contract changed, by explicit ruling: CAPSULE-BIRTH
was ( capsule-id -- vm-id ), a single cell -- can't hold 128 bits.
Captain Bob picked pushing two cells ("there is doubles support in the
FORTH std word set anyway"): ( capsule-id -- vm-id-hi vm-id-lo ).
MAMA-VM-ID changed the same way: ( -- 0 0 ).
Verified: full (not standalone-file) kernel rebuild to catch cross-file
breakage given the size of this change -- it surfaced the
capsule_vm_physics.c blast radius a narrower check would have missed.
Three-architecture boot (amd64, aarch64, riscv64), all reaching ok>
with identical dict_hash=0x3d4e1daf289da94f matching the item-3.7
baseline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
289 lines
8.2 KiB
C
289 lines
8.2 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
This file is part of the StarForth project.
|
||
|
||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
|
||
You may obtain a copy of the License at:
|
||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
|
||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
express or implied, including but not limited to the warranties of
|
||
merchantability, fitness for a particular purpose, and noninfringement.
|
||
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
*/
|
||
|
||
/**
|
||
* capsule_run.c - DoE Run Logging and Parity (M7.1)
|
||
*
|
||
* Ring buffer for capsule execution logging and parity record emission.
|
||
* Freestanding - no libc dependency except for console output hooks.
|
||
*/
|
||
|
||
#include "starkernel/capsule_run.h"
|
||
#include "starkernel/capsule.h"
|
||
#ifdef __STARKERNEL__
|
||
#include "starkernel/console.h" /* parity output goes straight to the console */
|
||
#endif
|
||
|
||
/* Parity output sink.
|
||
*
|
||
* In the monolithic kernel/PE build, taking the address of a cross-translation-
|
||
* unit function (e.g. &console_puts) is resolved through an absolute
|
||
* R_X86_64_32S relocation that the PE base-relocator cannot fix up, so a stored
|
||
* console function pointer holds a garbage address and faults #GP when called.
|
||
* Direct calls to console_puts/console_putc are PC-relative and always valid, so
|
||
* the kernel parity path calls the console directly rather than via a pointer.
|
||
* The hosted build keeps the redirectable function-pointer hooks. */
|
||
#ifdef __STARKERNEL__
|
||
#define PARITY_HAVE_SINK 1
|
||
#define PARITY_EMIT(s) console_puts(s)
|
||
#else
|
||
#define PARITY_HAVE_SINK (parity_puts != 0)
|
||
#define PARITY_EMIT(s) do { if (parity_puts) parity_puts(s); } while (0)
|
||
#endif
|
||
|
||
/*===========================================================================
|
||
* Run Log Ring Buffer
|
||
*===========================================================================*/
|
||
|
||
static CapsuleRunRecord run_log[CAPSULE_MAX_RUN_RECORDS];
|
||
static uint32_t run_log_head = 0; /* Next write position */
|
||
static uint32_t run_log_count = 0; /* Total records logged */
|
||
static uint64_t run_id_counter = 0; /* Sequential run ID */
|
||
|
||
void capsule_run_log_init(void) {
|
||
run_log_head = 0;
|
||
run_log_count = 0;
|
||
run_id_counter = 0;
|
||
|
||
/* Zero the ring buffer */
|
||
for (uint32_t i = 0; i < CAPSULE_MAX_RUN_RECORDS; i++) {
|
||
run_log[i].run_id = 0;
|
||
run_log[i].vm_id = vm_uuid_none(); /* not {0,0} -- that's Hera's reserved id (item 3.8) */
|
||
run_log[i].reserved = 0;
|
||
run_log[i].capsule_id = 0;
|
||
run_log[i].capsule_hash = 0;
|
||
run_log[i].pre_dict_hash = 0;
|
||
run_log[i].post_dict_hash = 0;
|
||
run_log[i].started_ns = 0;
|
||
run_log[i].ended_ns = 0;
|
||
run_log[i].result_code = 0;
|
||
run_log[i].flags = 0;
|
||
}
|
||
}
|
||
|
||
uint64_t capsule_run_log_record(const CapsuleRunRecord *record) {
|
||
if (!record) {
|
||
return 0;
|
||
}
|
||
|
||
/* Assign run ID */
|
||
uint64_t id = ++run_id_counter;
|
||
|
||
/* Copy record to ring buffer */
|
||
CapsuleRunRecord *slot = &run_log[run_log_head];
|
||
*slot = *record;
|
||
slot->run_id = id;
|
||
|
||
/* Advance head (circular) */
|
||
run_log_head = (run_log_head + 1) % CAPSULE_MAX_RUN_RECORDS;
|
||
|
||
/* Track total count (saturates at buffer size for wrap detection) */
|
||
if (run_log_count < CAPSULE_MAX_RUN_RECORDS) {
|
||
run_log_count++;
|
||
}
|
||
|
||
return id;
|
||
}
|
||
|
||
int capsule_run_log_get(uint64_t run_id, CapsuleRunRecord *out) {
|
||
if (!out || run_id == 0 || run_id > run_id_counter) {
|
||
return -1;
|
||
}
|
||
|
||
/* Search ring buffer for matching run_id */
|
||
for (uint32_t i = 0; i < CAPSULE_MAX_RUN_RECORDS; i++) {
|
||
if (run_log[i].run_id == run_id) {
|
||
*out = run_log[i];
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
return -1; /* Not found (may have been overwritten) */
|
||
}
|
||
|
||
uint32_t capsule_run_log_count(void) {
|
||
return run_log_count;
|
||
}
|
||
|
||
/*===========================================================================
|
||
* Parity Logging
|
||
*
|
||
* These functions emit structured parity records to the console/serial.
|
||
* Format is designed for deterministic verification and debugging.
|
||
*
|
||
* Output goes through a console hook that can be redirected to:
|
||
* - Serial port (kernel mode)
|
||
* - stdout (hosted mode)
|
||
* - Debug port 0x402 (QEMU)
|
||
*===========================================================================*/
|
||
|
||
/* Console output hook - to be set by platform layer */
|
||
static void (*parity_putc)(char c) = 0;
|
||
static void (*parity_puts)(const char *s) = 0;
|
||
|
||
void capsule_parity_set_output(void (*putc_fn)(char), void (*puts_fn)(const char *)) {
|
||
parity_putc = putc_fn;
|
||
parity_puts = puts_fn;
|
||
}
|
||
|
||
/* Helper: output hex value */
|
||
static void parity_put_hex64(uint64_t val) {
|
||
static const char hex[] = "0123456789abcdef";
|
||
char buf[19]; /* "0x" + 16 hex + null */
|
||
buf[0] = '0';
|
||
buf[1] = 'x';
|
||
for (int i = 15; i >= 0; i--) {
|
||
buf[2 + (15 - i)] = hex[(val >> (i * 4)) & 0xF];
|
||
}
|
||
buf[18] = '\0';
|
||
PARITY_EMIT(buf);
|
||
}
|
||
|
||
/* item 3.8: VMUuid printed as its two hex64 halves, hyphen-separated --
|
||
* reuses parity_put_hex64 rather than a new hex-formatting routine. */
|
||
static void parity_put_uuid(VMUuid id) {
|
||
parity_put_hex64(id.hi);
|
||
PARITY_EMIT("-");
|
||
parity_put_hex64(id.lo);
|
||
}
|
||
|
||
static void parity_put_u32(uint32_t val) {
|
||
char buf[12];
|
||
int i = 11;
|
||
buf[i--] = '\0';
|
||
if (val == 0) {
|
||
buf[i--] = '0';
|
||
} else {
|
||
while (val > 0 && i >= 0) {
|
||
buf[i--] = '0' + (val % 10);
|
||
val /= 10;
|
||
}
|
||
}
|
||
PARITY_EMIT(&buf[i + 1]);
|
||
}
|
||
|
||
static void parity_put_u64(uint64_t val) {
|
||
char buf[21];
|
||
int i = 20;
|
||
buf[i--] = '\0';
|
||
if (val == 0) {
|
||
buf[i--] = '0';
|
||
} else {
|
||
while (val > 0 && i >= 0) {
|
||
buf[i--] = '0' + (val % 10);
|
||
val /= 10;
|
||
}
|
||
}
|
||
PARITY_EMIT(&buf[i + 1]);
|
||
}
|
||
|
||
void capsule_parity_log_birth(
|
||
VMUuid vm_id,
|
||
uint64_t capsule_id,
|
||
uint64_t capsule_hash,
|
||
uint64_t dict_hash)
|
||
{
|
||
if (!PARITY_HAVE_SINK) return;
|
||
|
||
PARITY_EMIT("PARITY:BIRTH vm_id=");
|
||
parity_put_uuid(vm_id);
|
||
PARITY_EMIT(" capsule_id=");
|
||
parity_put_hex64(capsule_id);
|
||
PARITY_EMIT(" mode=p capsule_hash=");
|
||
parity_put_hex64(capsule_hash);
|
||
PARITY_EMIT(" dict_hash=");
|
||
parity_put_hex64(dict_hash);
|
||
PARITY_EMIT("\n");
|
||
}
|
||
|
||
void capsule_parity_log_birth_failed(
|
||
VMUuid vm_id,
|
||
uint64_t capsule_id,
|
||
CapsuleRunResult error,
|
||
uint64_t partial_dict_hash)
|
||
{
|
||
if (!PARITY_HAVE_SINK) return;
|
||
|
||
PARITY_EMIT("PARITY:BIRTH_FAILED vm_id=");
|
||
parity_put_uuid(vm_id);
|
||
PARITY_EMIT(" capsule_id=");
|
||
parity_put_hex64(capsule_id);
|
||
PARITY_EMIT(" error=");
|
||
parity_put_u32((uint32_t)error);
|
||
PARITY_EMIT(" partial_dict_hash=");
|
||
parity_put_hex64(partial_dict_hash);
|
||
PARITY_EMIT("\n");
|
||
}
|
||
|
||
void capsule_parity_log_run(
|
||
VMUuid vm_id,
|
||
uint64_t run_id,
|
||
uint64_t capsule_id,
|
||
uint64_t pre_dict_hash,
|
||
uint64_t post_dict_hash)
|
||
{
|
||
if (!PARITY_HAVE_SINK) return;
|
||
|
||
PARITY_EMIT("PARITY:RUN vm_id=");
|
||
parity_put_uuid(vm_id);
|
||
PARITY_EMIT(" run_id=");
|
||
parity_put_u64(run_id);
|
||
PARITY_EMIT(" capsule_id=");
|
||
parity_put_hex64(capsule_id);
|
||
PARITY_EMIT(" mode=e pre_dict=");
|
||
parity_put_hex64(pre_dict_hash);
|
||
PARITY_EMIT(" post_dict=");
|
||
parity_put_hex64(post_dict_hash);
|
||
PARITY_EMIT("\n");
|
||
}
|
||
|
||
/*===========================================================================
|
||
* Mama Init Parity
|
||
*===========================================================================*/
|
||
|
||
void capsule_parity_log_mama_init(
|
||
uint64_t capsule_id,
|
||
uint64_t capsule_hash,
|
||
uint64_t dict_hash)
|
||
{
|
||
if (!PARITY_HAVE_SINK) return;
|
||
|
||
PARITY_EMIT("PARITY:MAMA_INIT capsule_id=");
|
||
parity_put_hex64(capsule_id);
|
||
PARITY_EMIT(" mode=m capsule_hash=");
|
||
parity_put_hex64(capsule_hash);
|
||
PARITY_EMIT(" dict_hash=");
|
||
parity_put_hex64(dict_hash);
|
||
PARITY_EMIT("\n");
|
||
}
|
||
|
||
void capsule_parity_log_kill(VMUuid vm_id, const char *name)
|
||
{
|
||
if (!PARITY_HAVE_SINK) return;
|
||
|
||
PARITY_EMIT("PARITY:KILL vm_id=");
|
||
parity_put_uuid(vm_id);
|
||
PARITY_EMIT(" name=");
|
||
if (name) PARITY_EMIT(name);
|
||
PARITY_EMIT("\n");
|
||
}
|