Changelog¶
This page tracks major milestones during development, following the
version scheme in Cargo.toml.
0.7¶
- The heap detectors now cover four more allocators (issue #83,
tier 0):
utility.library'sAllocateTagItems,dos.library'sAllocDosObject, andexec.library'sCreateIORequestandCreateMsgPort, plus their matching free calls. All four carve from the same guest heap asAllocMem, so redzone space was already being reserved for them whenever--sanitizewas on — only the shadow marking was missing, which meant a guest overrunning aFileInfoBlock, anRDArgs, aMsgPortor aTagItemarray that volamos handed it went unreported.
It found a real bug on the first sweep: the PhxAss assembler asks
CreateIORequest for 40 bytes (sizeof(struct timerequest)) and then
reads two bytes one past the end. Harmless on real hardware, where
that read lands in whatever follows on the heap, which is exactly the
class of latent bug this exists to surface. So PhxAss is no longer
silent under --sanitize — that one report is expected;
--sanitize-ignore-pc silences it if you want a clean baseline.
Still not covered: exec.library/Allocate, where the guest owns the
memory pool, and which is where a C runtime's malloc actually
sub-allocates.
- Diagnostics now name the source location (issue #74). Sanitizer
violations are annotated with
file:linewhen the program carries aHUNK_DEBUGLINEblock (SAS/C'sDEBUG=LINE, PhxAss'sLINEDEBUG), falling back tosymbol+offsetfromHUNK_SYMBOLotherwise, and to the bare address when a binary carries neither. The raw PC is always kept alongside, since that is what a disassembly needs.
Worth knowing what this does and doesn't reach: m68k-amigaos-gcc
emits stabs debug info, which isn't decoded, so -g alone doesn't
give file:line — but gcc binaries do carry symbols, so a real
gcc-built stack-smash now reports
expected 0x00002b40, found 0x41414141 ... (at ___main+0x3c).
static functions never appear in a symbol table, so attribution
inside one falls to the nearest exported symbol.
Also new: fixtures/linetest, a repo-owned binary carrying real
LINE data (PhxAss-built, since amiga_asm.py can't emit debug
hunks), so the parser's tests don't depend on artefacts that vanish.
- Added
--dirty-heap(issue #80): fills every allocation made withoutMEMF_CLEARwith0xA5instead of leaving it zeroed, so a guest that relies on uncleared memory being zero fails here the way it can on real hardware — whereAllocMemreturns whatever debris was there.MEMF_CLEARallocations are untouched. Deliberately independent of--sanitize, because this one changes what the guest sees rather than only observing it, and--sanitize's never-perturb-the-program property is worth protecting (re-verified: the real SAS/C compiler's output object file is still byte-identical under--sanitize).
This also settled a verdict --sanitize-uninit had to leave open: the
real PhxAss assembler reads an uninitialized field from a ~568-slot
table, and with the fill on its output is byte-identical (checked on a
60-symbol source as well as a trivial one), so it does not act on
those values. Benign, not a latent bug.
- Added
--sanitize-uninit(issue #68): opt-in uninitialized-read detection on top of--sanitize, plus--sanitize-ignore-pcfor silencing a site you have already triaged. Byte-granular, so a partially-initialized structure is caught rather than being treated as initialized because something in it was written;MEMF_CLEARallocations never report.
Getting the false-positive rate usable was the bulk of the work, and
it turned on two latent bugs that were harmless only because uninit
reporting was off: a below-stack-pointer write forgiven by the grace
band returned without healing the byte, so the value a JSR had just
pushed read back as never-written; and stack growth blanket-marked the
newly-exposed range uninitialized, clobbering the bytes the very
instruction that moved the stack pointer had just written (a push
writes as it decrements). Together those took real pLhA listing a
102-file archive from 109,204 reports to zero, and every
fixture to zero.
Violation reports now also group by PC: a site with many violations collapses to one line with a count and address range, while a site with few prints each violation in full — so a corrupted return address never loses its expected/actual pair, which is its entire diagnostic value. Real PhxAss goes from a wall of 574 lines to 6 legible sites, one of which accounts for 568 of them.
0.6¶
-
Added
--sanitize, a valgrind/ASan-style memory sanitizer (issue #65).m68k-amigaos-gcchas no-fsanitize=address, and MMU-based tools like MuForce are page-granular, so single-byte heap overruns and use-after-free went undetected. volamos can do better because it is the allocator and every guest access already funnels through one trait: a shadow byte per guest byte now backs poisoned redzones either side of everyAllocMem/AllocVec/AllocPooledblock (including the alignment padding), plus a free quarantine that keeps a freed address out of circulation so use-after-free can't hide behind an address nothing happened to reuse. Host-side library handlers write guest memory through the same checked path, so bad buffers passed todos.librarycalls are caught with no per-function instrumentation. Violations are reported to stderr, deduplicated with hit counts, and never abort the guest -- a detector, not an enforcer. Two things worth knowing:--sanitizeforces the JIT off, because its raw-pointer fast path bypasses the checks entirely (a sanitized JIT run would look clean no matter what the program did); and overflows within a single stack frame remain invisible, exactly as they are to valgrind, since catching those needs compiler instrumentation. Newfixtures/memtestexercises all of it, and is the first fixture whose.sis assembled by the real PhxAss running under volamos itself. -
Extended
--sanitizeto stack bugs (issue #65, increment 2): accesses below the stack pointer, and return-address corruption via a shadow call stack that records what eachJSR/BSRpushed and verifies it at the matching return — stack-smash detection valgrind itself doesn't offer. Getting this to zero false positives on real software was most of the work: a push writes below the stack pointer by definition, so a 64-byte grace band (sized toMOVEM's worst case, the same window valgrind forgives) is needed or every subroutine call reports; volamos performs library-call returns itself rather than executing anRTS, sodispatchhas to retire shadow frames explicitly or a stale frame sits exactly where the next push lands; andStackSwapabandons a whole stack, so both the stale poison and the pending call frames have to be cleared or the replacement stack's reused addresses produce bogus reports. Real PhxAss, real pLhA and the real SAS/C 6.58 compiler now all run clean, withsc's object file byte-identical to an unsanitized run's. Newfixtures/stacktestcovers both detectors plus three false-positive guards.
0.5¶
- Fixed the guest command-line buffer's missing trailing space
(issue #63): whenever a launched program has at least one argument,
real AmigaOS's own command-line buffer carries a trailing space
before the final
'\n'("foo bar baz \n", not"foo bar baz\n") -- confirmed directly against real Kickstart 2.0/3.0/3.1 hardware via a new local, real-Kickstart comparison harness (tools/compare_kickstart_versions.py, using Copperline'scopperhf.device). Kickstart 3.2 alone doesn't add it -- a real, intentionally-untouched AmigaOS version difference, not a bug (this project targets 3.1 first). Also fixed a real bug found along the way infixtures/echoargs.s/libcall.s:A0is a scratch register across any library call (real Kickstart'sOpenLibraryclobbers it; volamos's own doesn't), so reading the command-line pointer back fromA0after anOpenLibrarycall is real-hardware-unsafe even though it happened to work under volamos. - Implemented
mathieeesingbas.library/mathieeesingtrans.library: the single-precision IEEE math libraries, previously only a fakeOpenLibrarystand-in with no real function support. Mirrorsmathieeedoubbas.library/mathieeedoubtrans.libraryfunction-for- function on plainf32instead off64. Fixed a real bug found via amitools' ownmath_single_transground truth along the way:IEEESPPow's result isyraised to thexpower (IEEESPPow(3.0, 4.0)->64.0=4**3), notxraised toyas a naive reading of its.conf-derived argument names would suggest -- tracing this down also revealedmathieeedoubtrans.library's existingIEEEDPPowalready had the equivalent (correct) behavior, just a misleading doc comment, now corrected.
0.4¶
- Fixed
mathffp.library/mathtrans.library's FFP encoding (issue #53): the sign bit and exponent field were in swapped bit positions (an old bug hidden by a unit test that re-derived the same wrong layout from this module's own -- also wrong -- doc comment instead of checking against real hardware), and overflow/underflow/ domain-error (NaN) results weren't saturating correctly. Found via a new local comparison harness against amitools' own test corpus (tools/compare_amitools_suite.py); took the affected tests' divergence fromvamosfrom the large majority of their output lines down to a handful of residual, believed-benign ones (a single overflow-boundary edge case and ordinary cross-implementation transcendental-function rounding variance). Both since confirmed against real Kickstart 3.1 hardware: the overflow-boundary case (SPMulsaturating exactly at FFP's maximum exponent field) was correct as fixed, and two more real bugs turned up in the same pass --RawDoFmt/VPrintf's%x/%lxprinted lowercase hex where real hardware prints uppercase (issue #48), andIEEEDPCeil()returned-0.0for a ceil-to-zero result where real hardware (matchingvamos) returns+0.0(issue #51, originally misdiagnosed as correct IEEE-754 behavior on volamos's side andvamos's divergence -- backwards). Also confirmed thatmathieeedoubbas/mathieeedoubtrans's positive-signedNaNconvention for domain-error results (0/0, out-of-domainacos/asin/log/etc.) matches real hardware andvamos's negative-signed convention isvamos's own divergence (issue #52), and canonicalized an internal inconsistency where Rust's ownf64::asin/acosdidn't agree with themselves onNaNsign for symmetric out-of-domain inputs. - Fixed
RawDoFmt's%b(BSTR) format (issue #45): the data-list entry for%bis aBPTR, not a raw byte address, so it needs the same<< 2conversiondos.library's ownBPTR-taking calls already apply -- confirmed against real Kickstart 3.1 hardware and amitools' ownexec_rawdofmttest (BStr: 'Hoi!', previously garbled). - Fixed
Seek()not rejecting an out-of-range target position (issue #47): a host file's ownseek()happily allows seeking arbitrarily far past end-of-file (standard POSIX behavior), but realSeek()'s own NDK autodoc says "you cannot Seek() beyond the end of a file." The target position is now computed and validated against the file's actual length (and against a negative result) before touching the host file at all, matching-1/ERROR_SEEK_ERROR, the modern (post-V39) contract -- not the old, documented-as-fixed pre-V39 behavior of returning the current position instead, which amitools' owndos_seektest still (incorrectly, for a V40 target) expects since it's a literalvamos-captured assertion, not real hardware. - Implemented
dos.library/FindArg(issue #55): finds the zero-based slot index a keyword names in aReadArgs-style template (or-1), reusing the same template parser andNAME=ABBREValias handlingReadArgsitself already had. Previously an unhandled library call. - Fixed
AnchorPath'sap_Bufqualification (issue #46): confirmed via real Kickstart 3.1 hardware (through a genuine in-memory FFS/OFS volume synthesized from a host directory, not just a convenience boot mount) thatap_Buftracks whatever device/path qualification the caller's ownMatchFirst/MatchNextpattern text had -- a device-qualified pattern ("sys:") reports device-qualified entries ("sys:c"); a bare, current-directory-relative pattern with no prefix at all reports entries with no qualification either, matchingfib_FileNameexactly. volamos previously always strippedap_Bufdown to a bare relative name regardless (issue #14's own fix, which turned out to be treating a symptom rather than the actual mechanism -- see #46's closing comment for the full story). - Fixed
AnchorPath's volume-rootfib_FileName(issue #58): aMatchFirst/MatchNextreport for a bare volume root now has a blankfib_FileName, matching real Kickstart 3.1 hardware -- a distinct convention from plainLock()/Examine()on the same volume root, which correctly keeps reporting the volume name. - Built-in standard-volume defaults (issue #43):
SYS:,RAM:, and the standardC:/S:/LIBS:/DEVS:/ENVARC:/T:/ENV:assigns onto them now resolve out of the box, with zero-V/-aconfiguration needed — backed by empty host directories created only on first actual use (SYS:persists across runs under--volumes-dir/VOLUMES_DIR, default~/.volamos.d/volumes;RAM:/T:/ENV:are a fresh, unique-per-process temp directory, removed automatically once the run ends — never shared between, or surviving past, a singlevolamosinvocation). An explicit-V/-afor the same name always overrides the matching default, so-V SYS:~/amiga/wb31brings that volume's own realC:/Libs:/etc. along with it rather than the synthetic skeleton reappearing underneath it.--no-defaults/DEFAULTS=falserestores the original "nothing configured means no filesystem at all" behavior. Only these specific real-AmigaOS names are covered — unlikevamos's broader auto-assign machinery, a genuinely unknown/typo'd volume name still fails loudly with anIoErr(). See Volumes and Assigns. - Program-directory config file (issue #16): a
.volamosnext to the launched binary (in<program>'s own containing directory) is now consulted between./.volamosand~/.volamos, so a toolchain installation can carry its own volume/CPU settings and be invoked from anywhere. Behavior change: relativeVOLUME/AUTO_ASSIGNpaths in any config file now resolve against that file's own directory instead of volamos's process working directory — an existing~/.volamosor./.volamosusing relative paths resolves differently if its directory isn't where you invoke volamos from (CLI-supplied relative paths are unchanged). See Configuration. - Parent-step (
a//b) fidelity: a parent step that climbs above the volume root now fails like a missing object instead of being clamped at the root — the root has no parent, matching real FFS behavior as verified in amitools PR #7's writeup (vamos made the same change there). Lock names reported back to the guest (NameFromLock()) now also carry the volume/assign name in its configured spelling (Lock("sys:foo")names itselfSYS:foo), completing the canonical-path treatment their components already got (parent steps collapsed, on-disk case). ExAll/ExAllEnd: the batched directory scanner libnix'sreaddir()(and so most gcc-built programs that scan a directory) is built on, including continuation across calls viaeac_LastKey,eac_MatchStringpattern filtering, allED_NAME..ED_OWNERentry levels, andAllocDosObject/FreeDosObjectsupport forDOS_EXALLCONTROL. Before this,ExAllwas an unhandled call, so every directory looked empty toreaddir()-based programs. Mirrors amitools PR #8, which added the same to vamos.
0.3¶
- Interpreter and release-build performance:
FlatMemory's multi-byte reads/writes now do a single bounds check plus a native big-endian load/store instead of decomposing into repeated byte-level calls, and the non---jitexecution path now runs through them68kcrate'srun_batch(batch size 1) instead of looping its plainstep, picking uprun_batch's non-cycle-accurate bus mode and raw-pointer fast-memory access — same per-instruction granularity, no observable behavior change. Release builds also gainlto = true/codegen-units = 1. On a real CoreMark 1.0 run, this took the interpreter from 105.9 to 198.2 iterations/sec (~1.9x) and--jitfrom 445.1 to 537.1 (~1.2x) — see the CLI Reference's--jitnote for the full comparison table, includingvamos's 270.6 on the same binary.
Unreleased (0.1, in development)¶
- Core runtime: CPU + A-line trap dispatch plumbing over the
m68kcrate, real guest heap and stack regions (with overflow detection), a configurable total guest address space (--ram, default 16 MiB) with a clean upfront error if--stackdoesn't leave it room, a host-backed volume/assign filesystem (-V/-a/--auto-assign, multi-assign search order, real Amiga path semantics including/-as-parent-dir), and config files (~/.volamos/.volamos) supplying default flag values for repeated-use projects. dos.library: file I/O, locks and directory traversal, pattern matching (ParsePattern/MatchFirst/MatchNext),ReadArgs/FreeArgs, a realENV:volume for environment variables,LoadSeg/UnLoadSeg/RunCommand, andSystem()/Execute()for nested guest programs.exec.library: memory allocation (flatAllocMem/AllocVec/ memory pools, and a real coalescingMemHeader/MemChunkfree list forAllocate/Deallocate), guest-visible lists/nodes/message ports, task/signal basics with hostSIGINT/SIGTERMdelivery, the fullSignalSemaphoreAPI, and CPU-detection plumbing (AttnFlags/CacheControl/Supervisor) —--cpu/--fpuselect the emulated model.utility.library/locale.library: tag lists, case-insensitive compare/conversion (classic Amiga charset), Amiga date conversions.intuition.library: a thin headless stub (DisplayAlert/AutoRequest/EasyRequestArgs/CurrentTime), matchingvamos's own scope for this library.- Math libraries:
mathffp,mathtrans,mathieeedoubbas,mathieeedoubtrans— real arithmetic, including a faithfully reproduced historicalSPSub/SPDivargument-order quirk. - Empirical hardening: extensive testing against a real Workbench
3.1.4
C:command corpus and real third-party binaries (the PhxAss assembler, a real backup-tool project), plus a full audit againstvamos's own library/device coverage to close the gaps it flagged.
What's not done yet¶
- A formal three-oracle parity pass (volamos vs.
vamosvs. real Kickstart, on a shared corpus). - A tagged release / packaged binaries for direct download.
exec.library'sMakeLibrary/SetFunction(would need a real architectural extension — see Supported Libraries).