Third stage of the preemptive context-switching plan. The real save/restore switch mechanism now exists -- the first time anything has ever executed on a VM's own native stack (Stage 1 allocated them, unused). New sk_vm_switch_to() (switch.S, one per arch) is an ordinary function call, not an interrupt -- so unlike Stage 0's trap frame, the ABI already covers every caller-saved register; only the callee-saved set needs explicit save/restore (amd64: rbx/rbp/r12-r15, no FP at all since SysV has no callee-saved XMM; aarch64: x19-x28/x29/x30 + d8-d15; riscv64: s0-s11/ra + fs0-fs11, FS-gated like Stage 0 but read once and reused for both halves within one call, since FS is genuine global CPU state, not part of what's switched). A sibling sk_vm_switch_prime() in the same file builds the synthetic first-entry frame, kept in assembly so the layout can never drift out of sync with sk_vm_switch_to() itself. New switch.c/switch.h: sk_vm_context_switch(from, to) handles first-entry priming vs. resuming a parked context, and updates registry state (new VM_STATE_SWITCHED_OUT, distinct from VM_STATE_STOPPED -- STOPPED means no live frame, this means the opposite). sk_vm_switch_entry() is the minimal permanent trampoline every freshly-entered VM lands in: no production behavior defined yet, so it just yields straight back to whoever switched to it, forever. Closes the confirmed unguarded-KILL UAF found during planning: capsule_vm_kill(), mama_word_kill(), and capsule_vm_kill_all_nonmama() all now refuse (or silently leak rather than free, on the cold-restart path where arch_cold_reset() wipes everything immediately after anyway) tearing down a switched-out VM. Side effect found, not built on purpose: the existing MSG-TICK idle-pump already filters on VM_STATE_LIVE, so it automatically stopped dispatching into a switched-out VM with zero changes needed there. Verified via a temporary SWITCH-TEST probe (boot-triggered, since nothing can type interactively into a foreground-only QEMU session) that round-tripped a sentinel through 5 real Hera<->Hermes switches on all 3 architectures: 5/5 rounds, 0 failures, clean continuation to ok>. Probe fully reverted after capture; kernel_main.c shows zero diff. Also: Makefile.starkernel's LOADER_EXTRA_SRCS/LOADER_ASM needed the new files added explicitly (this project's "loader" PE binary is the full running kernel, not a thin bootstrap stage), and aarch64's switch.S needed the same #ifndef _WIN32 guard around .hidden that isr.S already carries (aarch64's loader assembles via clang targeting a PE/COFF target with no .hidden equivalent) -- caught by a build failure, fixed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
247 lines
8.6 KiB
C
247 lines
8.6 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.h - DoE Run Logging (M7.1)
|
||
*
|
||
* Structures for logging capsule execution runs and VM births.
|
||
* Supports provenance tracking and experiment reproducibility.
|
||
*/
|
||
|
||
#ifndef STARKERNEL_CAPSULE_RUN_H
|
||
#define STARKERNEL_CAPSULE_RUN_H
|
||
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include "starkernel/vm_uuid.h" /* VMUuid -- FABRIC-0.md item 3.8 */
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/*===========================================================================
|
||
* Run Log Configuration
|
||
*===========================================================================*/
|
||
|
||
/** Phase A: Fixed ring buffer size */
|
||
#define CAPSULE_MAX_RUN_RECORDS 1024
|
||
|
||
/*===========================================================================
|
||
* Result Codes
|
||
*===========================================================================*/
|
||
|
||
typedef enum {
|
||
CAPSULE_RUN_OK = 0,
|
||
CAPSULE_RUN_ERR_INVALID, /* Invalid capsule */
|
||
CAPSULE_RUN_ERR_NOT_ELIGIBLE, /* Capsule not eligible for operation */
|
||
CAPSULE_RUN_ERR_EXEC_FAIL, /* Execution failed */
|
||
CAPSULE_RUN_ERR_HASH_MISMATCH, /* Post-run hash mismatch */
|
||
CAPSULE_RUN_ERR_STILLBORN, /* VM birth failed */
|
||
CAPSULE_RUN_ERR_FLEET_FULL, /* Outer Stadium at stadium_max_vm_count() (FABRIC-0.md item 1.5/2.2) */
|
||
} CapsuleRunResult;
|
||
|
||
/*===========================================================================
|
||
* CapsuleRunRecord - DoE Execution Log Entry
|
||
*===========================================================================*/
|
||
|
||
typedef struct {
|
||
uint64_t run_id; /* Sequential run identifier */
|
||
VMUuid vm_id; /* Which VM executed this (item 3.8) */
|
||
uint32_t reserved; /* Padding */
|
||
uint64_t capsule_id; /* Which capsule was run */
|
||
uint64_t capsule_hash; /* Hash at time of execution */
|
||
uint64_t pre_dict_hash; /* Dictionary state before run */
|
||
uint64_t post_dict_hash; /* Dictionary state after run */
|
||
uint64_t started_ns; /* Monotonic start time */
|
||
uint64_t ended_ns; /* Monotonic end time */
|
||
uint32_t result_code; /* CapsuleRunResult */
|
||
uint32_t flags; /* Run flags (mode, etc.) */
|
||
} CapsuleRunRecord;
|
||
|
||
/*===========================================================================
|
||
* VM Registry Entry
|
||
*===========================================================================*/
|
||
|
||
/** Maximum length of a VM symbolic name, including null terminator */
|
||
#define VM_NAME_MAX 64
|
||
|
||
typedef enum {
|
||
VM_STATE_EMBRYO = 0, /* Allocated but not yet born */
|
||
VM_STATE_LIVE, /* Successfully born, operational */
|
||
VM_STATE_STOPPED, /* Suspended — execution state saved. Set by
|
||
* the START word after STOP cleanly unwinds
|
||
* its C stack back to START's own frame: no
|
||
* live native frame remains, safe to free. */
|
||
VM_STATE_STILLBORN, /* Birth failed */
|
||
VM_STATE_DEAD, /* Terminated */
|
||
VM_STATE_SWITCHED_OUT, /* FABRIC-3.md §XXVIII, Stage 2 (2026-09-13):
|
||
* a live saved register/stack context is
|
||
* parked on this VM's own native stack
|
||
* (SWITCH-TO switched control away mid-
|
||
* execution). Deliberately distinct from
|
||
* VM_STATE_STOPPED -- that state means "no
|
||
* live native frame," this one means the
|
||
* opposite. KILL must refuse/defer here: the
|
||
* parked frame still points into vm->memory
|
||
* and the native stack, both of which a free
|
||
* would invalidate out from under it. */
|
||
} VMState;
|
||
|
||
typedef struct {
|
||
VMUuid vm_id; /* Assigned at birth, immutable (item 3.8) */
|
||
uint32_t state; /* VMState */
|
||
uint64_t birth_capsule_id; /* Which capsule birthed this VM */
|
||
uint64_t birth_timestamp_ns; /* When VM was born */
|
||
uint64_t birth_dict_hash; /* Dictionary hash after birth */
|
||
uint32_t flags; /* VM flags */
|
||
VMUuid parent_vm_id; /* Who birthed this VM. Set once at birth,
|
||
* never rewritten. Hera's own entry is
|
||
* self-referential (parent_vm_id == vm_id
|
||
* == vm_uuid_hera(), all-zero) -- the
|
||
* sentinel a heat-fanout walk up the
|
||
* parent chain stops at. */
|
||
void *vm_ptr; /* Pointer to live VM object; NULL when dead */
|
||
char name[VM_NAME_MAX]; /* Symbolic name, e.g. "Hera", "Hermes" */
|
||
size_t stadium_patron_cell; /* FABRIC-2.md SS B, VM-COOL: this VM's own
|
||
* Stadium cell index (STADIUM_CELL_NONE,
|
||
* i.e. (size_t)-1, if never admitted or
|
||
* already reaped) -- admitted into the VM's
|
||
* own quota at birth, explicitly evicted at
|
||
* KILL. Not Hera's; she is pinned and never
|
||
* reaches this field's purpose. */
|
||
} VMRegistryEntry;
|
||
|
||
/*===========================================================================
|
||
* Run Log Functions
|
||
*===========================================================================*/
|
||
|
||
/**
|
||
* capsule_run_log_init - Initialize run log
|
||
*/
|
||
void capsule_run_log_init(void);
|
||
|
||
/**
|
||
* capsule_run_log_record - Log a run record
|
||
*
|
||
* @param record Record to log
|
||
* @return Run ID assigned, or 0 on failure
|
||
*/
|
||
uint64_t capsule_run_log_record(const CapsuleRunRecord *record);
|
||
|
||
/**
|
||
* capsule_run_log_get - Get a run record by ID
|
||
*
|
||
* @param run_id Run ID to retrieve
|
||
* @param out Output record
|
||
* @return 0 on success, -1 if not found
|
||
*/
|
||
int capsule_run_log_get(uint64_t run_id, CapsuleRunRecord *out);
|
||
|
||
/**
|
||
* capsule_run_log_count - Get number of logged runs
|
||
*/
|
||
uint32_t capsule_run_log_count(void);
|
||
|
||
/*===========================================================================
|
||
* Parity Logging
|
||
*===========================================================================*/
|
||
|
||
/**
|
||
* capsule_parity_log_birth - Log VM birth parity record
|
||
*
|
||
* Output format:
|
||
* PARITY:BIRTH vm_id=N capsule_id=X mode=p capsule_hash=H dict_hash=D
|
||
*/
|
||
void capsule_parity_log_birth(
|
||
VMUuid vm_id,
|
||
uint64_t capsule_id,
|
||
uint64_t capsule_hash,
|
||
uint64_t dict_hash
|
||
);
|
||
|
||
/**
|
||
* capsule_parity_log_birth_failed - Log failed birth
|
||
*
|
||
* Output format:
|
||
* PARITY:BIRTH_FAILED vm_id=N capsule_id=X error=E partial_dict_hash=H
|
||
*/
|
||
void capsule_parity_log_birth_failed(
|
||
VMUuid vm_id,
|
||
uint64_t capsule_id,
|
||
CapsuleRunResult error,
|
||
uint64_t partial_dict_hash
|
||
);
|
||
|
||
/**
|
||
* capsule_parity_log_run - Log DoE run parity record
|
||
*
|
||
* Output format:
|
||
* PARITY:RUN vm_id=N run_id=R capsule_id=X mode=e pre_dict=P post_dict=Q
|
||
*/
|
||
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
|
||
);
|
||
|
||
/**
|
||
* capsule_parity_log_mama_init - Log Mama init parity record
|
||
*
|
||
* Output format:
|
||
* PARITY:MAMA_INIT capsule_id=X mode=m capsule_hash=H dict_hash=D
|
||
*/
|
||
void capsule_parity_log_mama_init(
|
||
uint64_t capsule_id,
|
||
uint64_t capsule_hash,
|
||
uint64_t dict_hash
|
||
);
|
||
|
||
/**
|
||
* capsule_parity_log_kill - Log VM kill parity record
|
||
*
|
||
* Output format:
|
||
* PARITY:KILL vm_id=N name=X
|
||
*/
|
||
void capsule_parity_log_kill(
|
||
VMUuid vm_id,
|
||
const char *name
|
||
);
|
||
|
||
/**
|
||
* capsule_parity_set_output - Set output hooks for parity logging
|
||
*
|
||
* @param putc_fn Function to output a single character
|
||
* @param puts_fn Function to output a string
|
||
*/
|
||
void capsule_parity_set_output(
|
||
void (*putc_fn)(char),
|
||
void (*puts_fn)(const char *)
|
||
);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* STARKERNEL_CAPSULE_RUN_H */
|