--sanitize (#65) catches what Enforcer and MuForce structurally cannot — byte-granular heap redzones, use-after-free, return-address corruption. This issue covers the converse: the bug class Enforcer exists to catch and volamos currently absorbs in silence, NULL and low-memory access.
The evidence
LawBreaker (the deliberate-illegal-access test program from the Enforcer package, see #70) commits four violations on purpose. Under volamos with the sanitizer fully enabled, the entire run reports nothing:
$ volamos --sanitize LawBreaker
volamos: Alert(0x35000000): AT_Recovery -- SubSysId=0x35 GeneralError=0x00 SpecificError=0x0000
exit=0
Its disassembled violations (full listing in #70):
31c0 0000 move.w d0,($0000).w ; write to address 0
11c1 0101 move.b d1,($0101).w ; write to $0101
21c0 0102 move.l d0,($0102).w ; write to $0102
All three land inside volamos's own reserved region — TRAP_TABLE_BASE = 0x0000, TRAP_TABLE_SIZE = 0x2A00 (backend.rs) — which the shadow map treats as ordinary valid memory. So rather than being reported, these writes silently corrupt volamos's own fake library jump tables, ExecBase, and the library bases that live there. A guest bug that would produce an instant, obvious Guru on real hardware instead produces either nothing or an inexplicable failure much later, somewhere else entirely.
Worth stating plainly: this is the single most common Amiga crash class. A NULL pointer dereference writes to low memory, and that is precisely why Enforcer was written.
Proposal
Poison the reserved low-memory region in the shadow map so guest accesses to it are reported as violations:
- A new
PoisonReason (e.g. LowMemory / ReservedRegion) so the report distinguishes "you dereferenced NULL" from "you overran a heap block".
- Address
0 specifically is worth calling out in the message as a NULL dereference, since that is the diagnosis the user actually wants.
The design problem to solve first, and the reason this needs thought rather than a quick patch: volamos's own runtime legitimately reads and writes this whole region constantly. It builds the trap table there, maintains a real struct ExecBase, keeps library bases with their jump tables, and the guest legitimately reads AbsExecBase from address 4 and jumps through library vectors in that range on every single library call. So the checking cannot be a blanket poison; it has to distinguish:
- Guest data accesses to low memory → violation.
- The guest reading
AbsExecBase at address 4 → legitimate, and every real Amiga program does it.
- The guest's
JSR through a library base into the jump table → legitimate; this is the entire library-call mechanism.
- volamos's own host-side construction and maintenance of those structures → legitimate.
The existing FlatMemory::peek_* methods (added in #67 for exactly this class of problem — the sanitizer inspecting memory on its own behalf) are the precedent for keeping runtime accesses out of the checked path. Instruction fetch and vector-jump reads will need similar care, or the mechanism reports every library call ever made.
Because of that, this is not a "poison [0, 0x2A00) and done" change, and it should be developed against a false-positive sweep (PhxAss, pLhA, SAS/C sc) the same way #66/#67 were — every false-positive class in those PRs was found by running real software, never by unit tests.
Scope
Related: #65 (the sanitizer), #70 (LawBreaker's provenance and disassembly), #72 (wild-pointer/out-of-range accesses), #73 (register snapshots in reports).
--sanitize(#65) catches what Enforcer and MuForce structurally cannot — byte-granular heap redzones, use-after-free, return-address corruption. This issue covers the converse: the bug class Enforcer exists to catch and volamos currently absorbs in silence, NULL and low-memory access.The evidence
LawBreaker (the deliberate-illegal-access test program from the Enforcer package, see #70) commits four violations on purpose. Under volamos with the sanitizer fully enabled, the entire run reports nothing:
Its disassembled violations (full listing in #70):
All three land inside volamos's own reserved region —
TRAP_TABLE_BASE = 0x0000,TRAP_TABLE_SIZE = 0x2A00(backend.rs) — which the shadow map treats as ordinary valid memory. So rather than being reported, these writes silently corrupt volamos's own fake library jump tables,ExecBase, and the library bases that live there. A guest bug that would produce an instant, obvious Guru on real hardware instead produces either nothing or an inexplicable failure much later, somewhere else entirely.Worth stating plainly: this is the single most common Amiga crash class. A NULL pointer dereference writes to low memory, and that is precisely why Enforcer was written.
Proposal
Poison the reserved low-memory region in the shadow map so guest accesses to it are reported as violations:
PoisonReason(e.g.LowMemory/ReservedRegion) so the report distinguishes "you dereferenced NULL" from "you overran a heap block".0specifically is worth calling out in the message as a NULL dereference, since that is the diagnosis the user actually wants.The design problem to solve first, and the reason this needs thought rather than a quick patch: volamos's own runtime legitimately reads and writes this whole region constantly. It builds the trap table there, maintains a real
struct ExecBase, keeps library bases with their jump tables, and the guest legitimately readsAbsExecBasefrom address 4 and jumps through library vectors in that range on every single library call. So the checking cannot be a blanket poison; it has to distinguish:AbsExecBaseat address 4 → legitimate, and every real Amiga program does it.JSRthrough a library base into the jump table → legitimate; this is the entire library-call mechanism.The existing
FlatMemory::peek_*methods (added in #67 for exactly this class of problem — the sanitizer inspecting memory on its own behalf) are the precedent for keeping runtime accesses out of the checked path. Instruction fetch and vector-jump reads will need similar care, or the mechanism reports every library call ever made.Because of that, this is not a "poison
[0, 0x2A00)and done" change, and it should be developed against a false-positive sweep (PhxAss, pLhA, SAS/Csc) the same way #66/#67 were — every false-positive class in those PRs was found by running real software, never by unit tests.Scope
PoisonReason; special-case address 0 in the message as a NULL dereference.--sanitize— it may be noisier than the heap/stack detectors, in which case--sanitize-lowmemfollows the--sanitize-uninitprecedent set in Optional --sanitize-uninit: uninitialized-read detection as an opt-in extra (issue #65 increment 3) #68.sc; all must stay clean.Related: #65 (the sanitizer), #70 (LawBreaker's provenance and disassembly), #72 (wild-pointer/out-of-range accesses), #73 (register snapshots in reports).