MuGuardianAngel¶
MuGuardianAngel ("MuGA") is the MuTools memory debugger shipped in the NDK 3.2 MuTools/ drawer (version 40.51 (29.06.2018)). It extends MuForce. MuForce traps accesses to address space that does not exist. MuGuardianAngel also uses mmu.library to make free memory pages unavailable, so that a program touching memory it never allocated, or has already released, causes a hit at the faulting instruction.
MuGuardianAngel also replaces exec's allocation functions with checking versions that put a "mung wall" around every allocation, fill new and freed memory with patterns, and validate every release. In the guide's words, it replaces four tools in one: the original Commodore GuardianAngel, MungWall, MungList and MemSniff.
It is a development tool, not a protector. It slows the system down and uses extra memory, and the guide says it "shouldn't be run permanently". Software that causes MuGuardianAngel hits will not run stably without it either.
How it works¶
| Mechanism | What it detects |
|---|---|
| Free memory pages marked invalid through mmu.library (lower-level page properties, via a page access handler in the public context) | Any user-mode read or write of free memory. Released memory is made unavailable as soon as possible, so use-after-free faults on the spot. The fault itself is reported by MuForce. |
Mung walls: PRESIZE bytes before and POSTSIZE bytes after each block, filled with a pattern |
Buffer under- and overruns, checked when the block is freed. |
| A "cookie" in free memory | Writes to free memory that slipped past page granularity, detected at the next allocation. |
Non-zero fill of blocks allocated without MEMF_CLEAR |
Code that assumes fresh memory is zeroed. |
| Bookkeeping of each allocation's size, pool and caller | Wrong-size frees, wrong-pool frees, double frees, NULL frees and misaligned frees. |
| Stack checks on entry to each patched function, plus a periodic scan | Tasks that are nearly out of stack. MuGuardianAngel switches to an emergency stack and warns. |
Optional memory list checks (CONSISTENCY) |
Corrupted exec MemHeader / MemChunk lists. |
To expose code that ignores results, the patched functions deliberately trash the scratch registers (D1/A0–A1, or D0–D1/A0–A1) and set the Z flag. Code that tests the condition codes after AllocMem() instead of D0 will break, as will code that expects a return value from FreeMem().
Replaced functions¶
MuGuardianAngel replaces these exec functions completely. Patches installed earlier are no longer called.
| Function | Replacement adds |
|---|---|
AllocMem 1.3+ |
Snooping, munging, consistency check, marks the block valid. |
FreeMem 1.3+ |
Wall and consistency checks; makes the memory unavailable and fills it with a cookie. |
AllocAbs 1.3+ |
Makes the block available and fills it with a dummy pattern (not snooped). |
AvailMem 1.3+ |
Accounts for mung wall sizes. |
AllocPooled / FreePooled |
Munging for pooled allocations. |
CreatePool / DeletePool |
Argument checks; adjusts puddle and threshold sizes to make room for the walls. |
AllocVec / FreeVec |
Trashes registers; marks the block as a vector allocation. |
Allocate / Deallocate |
No cookie for private MemHeaders; Deallocate clears released memory and trashes registers. |
The replacements never call Alert(). Every failure is printed as text.
Do not install any patch on top of MuGuardianAngel. It needs the true caller's return PC, and stacked patches break that. On V39 and older they also break the layers.library workaround and crash the system. The one exception the guide names is ShowMem 2.06 or newer. MuGuardianAngel reports
MuGuardianAngel patches have been overwrittenwhen it notices this.SaferPatches REMEMBERcan help find the culprit.
Requirements and installation¶
- Install mmu.library (and 68040/68060.library on those CPUs).
- Start MuForce first. MuGuardianAngel needs it, and MuForce refuses to quit while MuGuardianAngel runs (enforced since mmu.library release 42.11, MuGA 40.48). The MuManual ReadMe (release 43.5) notes that current MuGuardianAngel releases need MuForce 40.30 or newer.
- Remove Enforcer, CyberGuard, MungWall, MungList and MemSniff.
- Start MuGuardianAngel after MuForce and, if you use it, after SegTracker:
run >NIL: <NIL: MuForce DATESTAMP STACKLINES=4
run >NIL: <NIL: MuGuardianAngel WAITFORMUFORCE TOMUFORCE SHOWSTACK SHOWPC SHOWHUNK NAMETAG DISPC DUMPWALL
By default hits go to the serial port. MuGuardianAngel does not call RawIOInit() (since 40.29), so the serial port must already be set up. TOMUFORCE merges the output into MuForce's stream, wherever that goes. A redirector such as Sushi or Sashimi also works. From Workbench, options are read from tooltypes, and WINDOW=CON:... sets the output window (default NIL:). Stop MuGuardianAngel with QUIT/OFF or CTRL-C. The MuGuardianAngel_Off icon is provided for Workbench.
Options¶
Template (from the 40.51 binary):
WINDOW/K,QUIT=OFF/S,MUNGLIST/S,PRI=PRIORITY/K/N,CLIENT=TASK/K,WAIT/S,SNOOP/S,
MIN/K,MAX/K,DEBUG/S,SHOWFAIL/S,SHOWSTACK/S,SHOWPC/S,SHOWHUNK/S,DISRANGE/K/N,
DISPC/S,STACKLINES/K/N,NAMETAG/S,DATESTAMP=TIMESTAMP/S,STACKSNOOP/S,
CON=CONSISTENCY/S,PRESIZE/K,POSTSIZE/K,FILLCHAR/K,LED/K/N,INTRO/K,DISABLEBELL/S,
TINY/S,DREGCHECK/S,AREGCHECK/S,STACKCHECK/S,TOMUFORCE/S,NOMMU/S,ALLOWREUSE/S,
DUMPWALL/S,NOSTACKWARN/S,MINSTACK/N,WAITFORMUFORCE/S,PROTECTPOST/S
Detection¶
| Option | Effect |
|---|---|
PRESIZE=n / POSTSIZE=n |
Front and rear wall sizes. Default 32 bytes each, rounded up to a multiple of 8. Accepts 0x or $ hex. |
FILLCHAR=n |
Wall fill byte. The default −1 selects a "rolling wall" that changes with every allocation. |
PROTECTPOST |
Aligns each block so its end falls on an MMU page boundary and the next page can be protected, so overruns fault immediately. Combine with POSTSIZE=0. Fragments memory heavily: allocations are rounded to at least 8K on 040/060, and the guide expects 32–64 MB to be needed. Can be limited to one task with TASK. |
CONSISTENCY (= CON) |
Checks the memory lists every vertical blank: free byte count, sort order, range and alignment of chunks. Costs a lot of CPU time. |
STACKSNOOP |
Enables stack overflow/underflow reports from the periodic scan. The guide recommends leaving it off: some tasks (the SetENV command, the FastFilingSystem) switch stacks without informing the system. Since 40.28 periodic "nearly out of stack" checks run regardless. |
MINSTACK=n |
Raises the "nearly out of stack" threshold. It cannot go below the built-in limit. |
NOSTACKWARN |
Suppresses "nearly out of stack" warnings. The emergency stack is still used. The guide warns that this hides real bugs. |
SHOWFAIL |
Reports failed allocations as hits. Memory flushes such as AllocMem(0x7ffffff0,...) are not counted as failures. |
ALLOWREUSE |
Delays unmapping of freed memory to the next task switch. Recommended together with PRI=-1. A workaround for broken software that weakens detection. |
NOMMU |
Disables the MMU part entirely, which reduces MuGA to a MungWall equivalent. Last resort only. |
Report content¶
| Option | Effect |
|---|---|
TINY |
Prints the first line of each hit only. |
SHOWSTACK |
Dumps STACKLINES lines of the caller's stack. |
STACKLINES=n |
Number of stack lines of 16 bytes each. Default 2. |
SHOWPC |
Dumps memory around the return PC. |
SHOWHUNK |
Prints SegTracker hunk and offset for the PCs. Needs SegTracker. |
DISPC / DISRANGE=n |
Disassembles around the return PC through disassembler.library. Default range 32 bytes. |
STACKCHECK / AREGCHECK / DREGCHECK |
Checks stack longwords and registers against SegTracker. |
NAMETAG |
Records the allocating task's name, at a memory cost. Needed for MUNGLIST TASK= and to name the allocator in free errors. |
DUMPWALL |
Hex and ASCII dump of a damaged wall (W:) or cookie (C:). |
DATESTAMP (= TIMESTAMP) |
Date and time on each hit. |
INTRO=string |
Header printed before every hit. |
LED=n |
LED flash length. Default 0, meaning off. This differs from MuForce. |
DISABLEBELL |
Do not send BEL. |
TOMUFORCE |
Send output to the MuForce stream instead of the serial port. |
Control and queries¶
| Option | Effect |
|---|---|
WAIT |
Suspends the offending task in Wait(SIGF_SINGLE) so a debugger can attach. |
DEBUG |
Calls exec Debug() on a hit with A0 = return PC and A1 = message string. The guide recommends installing a system monitor first. |
SNOOP |
Logs every allocation and free. Restrict it with MIN, MAX and TASK, or the system becomes very slow. |
MIN=n / MAX=n |
Size range for SNOOP and MUNGLIST. Defaults 0 and 0xffffffff; hex accepted. |
TASK=name (= CLIENT) |
Task filter for SNOOP, MUNGLIST and PROTECTPOST. With MUNGLIST it needs NAMETAG in the running copy. |
MUNGLIST |
Run a second time while MuGA is active: lists live allocations. |
PRI=n |
Priority of the MuGA process. Default 80. The process updates the timestamp and unmaps memory that was freed under Forbid(). |
WAITFORMUFORCE |
Waits up to one minute for MuForce to appear. |
Reading a report¶
Bad program!
03-Jul-99 14:26:14
Mung-wall at 01083bc8 is damaged.
PC : 0105bd6a USP: 010d6434 ( )( ) Name: memorydevil
----> 0105bd6a - "memorydevil" Hunk 0 Offset 00000032
PCa : 0105bd5a Name: memorydevil
----> 0105bd5a - "memorydevil" Hunk 0 Offset 00000022
By : AllocMem(0000002a,00000001)
Data: 0000002a 00000000 00001000 0101b744 00000001 0041ac8b 0040f69f 0105bd34
Addr: 010d6430 01083bc8 0101a62c 0105bd34 010d6438 00f9feaa SysBase --------
Stck: 00fa06d6 00001000 0101b2ac a3a3a3a3 a3a3a3a3 a3a3a3a3 a3a3a3a3 a3a3a3a3
0105bd60 : 4229 ffff clr.b -$1(a1)
0105bd64 : 702a moveq.l #$2a,d0
0105bd66 : 4eae ff2e jsr -$d2(a6)
0105bd6a : *4e75 rts
W: 013e2838 b2<>b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 b2 ................
| Line | Meaning |
|---|---|
| intro, date | INTRO and DATESTAMP. |
| message | The hit type (see the table below) and the address involved. |
PC : |
The return PC of the OS call, not the call instruction. Then the USP, the Forbid and Disable indicators ((F), (D)) and the task name. The return PC is correct even through stubs, but the allocation may be a side effect of an OS function. |
PCa : |
For release errors: the return PC of the matching allocation and the allocator's name. The name needs NAMETAG; otherwise it shows ????. |
----> |
SegTracker lines (SHOWHUNK). |
By : |
The allocating call: AllocMem, AllocVec or AllocPooled, with its arguments. |
Data: / Addr: |
Registers. A6 is shown as SysBase; A7 is shown as USP. |
Stck: |
SHOWSTACK. |
| disassembly | DISPC. The * marks the return PC, not the faulty instruction, which is usually unknown. |
W: / C: |
DUMPWALL: the expected byte before <>, then the actual contents. |
Accesses to protected free memory are not reported in this format. They are MMU faults, so MuForce prints them as ordinary hits (see MuForce).
Snoop format¶
A: # ssssssss @ aaaaaaaa attr: tttttttt PC: pppppppp - MyTask
F: # ssssssss @ aaaaaaaa PC: pppppppp - MyTask
A marks an allocation and F a free; # is the size, @ the address and PC the return PC. To find a leak:
- Run with
NAMETAG TASK=yourtask SNOOP. avail flush, then run and quit the program.avail flushagain.- Every
Aline needs exactly oneFline. AnAline without one is a leak.
Hit catalogue¶
| Message | Cause | What to do |
|---|---|---|
Clear memory cookie at .. was found defective |
Something wrote into free memory that page protection could not cover. Found at the next allocation; culprit unknown. | Increase PRESIZE/POSTSIZE, use DUMPWALL and look at the C: data. |
Release of ..: alloc. length .. <> released length .. |
FreeMem/FreePooled size differs from the allocation size. The block is not freed. |
Fix the size. A PC inside layers.library means someone patched on top of MuGA. |
Release of .. :allocation pool .. <> release pool .. |
Freed to the wrong pool, or AllocPooled memory freed with FreeMem (pool 00000000 = non-pooled). The block is not freed. |
Use pools consistently. |
Front/Rear Mung-wall at .. is damaged |
Underrun (front) or overrun (rear), for example an off-by-one or a missing string terminator. | DUMPWALL; check buffer sizes and strcpy(). |
MemHeader of chunk .. is not available |
The freed address is in no exec memory list. | Invalid pointer, or corrupted memory lists (try CONSISTENCY). |
Memory at .. released twice |
Double free. | NULL pointers after freeing. |
Invalid FreeMem of memory at .. |
The block is partly free already: a stale pointer, after part of it was reallocated. | Same. |
Mem header ..: free counter .. <> .. in chunks |
The header's free byte count disagrees with its chunk list, typically because a write to freed memory broke the chunk link (MC_NEXT). |
CONSISTENCY; the reporting task is usually innocent. |
Null-sized allocation, ignored |
AllocMem(0, ...); MuGA returns NULL. |
Handle the zero case yourself. |
FreeMem of NULL pointer attempted, ignored |
FreeMem(NULL, ...). |
Check for NULL. |
Misaligned release of .., size .. performed |
The pointer is not 8-byte aligned. | Bad pointer, unless it is a correct AllocAbs release (see below). |
Allocation of .. bytes failed |
SHOWFAIL only: out of memory, or that memory type is not available. |
May indicate a leak; use SNOOP. |
Task ..: nearly out of stack |
Low stack on entry to a MuGA function (with full details and an emergency stack) or found by the periodic scan (task name only). | Increase the stack. |
Task ..: stack overflow / stack underflow |
STACKSNOOP reports. |
Increase the stack; an underflow means a runaway program. Bogus reports are possible. |
Invalid memory list / Mem header ..: node type is not NT_MEMORY / lower is larger than upper / chunk .. <lower or >upper / chunk .. order broken / chunk after .. misaligned |
CONSISTENCY found exec's free lists corrupted. No PC or task is available. |
Reboot soon; reproduce under a debugger. Larger walls may help. |
MuGuardianAngel patches have been overwritten |
Something patched on top of MuGA. | Remove it (SaferPatches REMEMBER). |
.. called from interrupt or supervisor mode |
AllocMem, FreeMem, AvailMem, AllocAbs, AllocPooled, FreePooled, CreatePool or DeletePool was called from an interrupt. |
Pre-allocate memory from a task; Signal() it for more. |
CreatePool puddleSize .., thresSize .. invalid |
A size is 0, or threshSize > puddleSize. |
Fix the arguments (see Memory Pools). |
CreatePool failed |
SHOWFAIL only. |
As for failed allocations. |
DeletePool of NULL pointer attempted, ignored |
DeletePool(NULL). |
Check CreatePool()'s result. |
Alloc/FreePooled from NULL pool attempted |
NULL pool handle. | Same. |
Memory pool .. garbled |
The chunk's puddle cannot be found: a bad pointer, or the pool was corrupted. | Check pointers. |
Memory rules MuGuardianAngel enforces¶
The guide's chapter "Correct memory handling" restates the RKRM rules. The important ones:
- Free exactly what you allocated. Use the same address and size. Never free part of a block and never round the size.
- Never touch freed memory. There is one tolerated exception: inside a single
Forbid(), a just-freed block (except its first 8 bytes) stays intact until something breaks the Forbid, for example aWait(). MuGA honours this and unmaps the memory when the Forbid ends. It still checks such blocks twice. - Check every allocation for failure by testing D0. The Z flag is undocumented after
AllocMem(), and MuGA sets it on purpose. Processes getERROR_NO_FREE_STORE(103) fromIoErr(). - Do not assume contents. Without
MEMF_CLEARthe block is garbage; MuGA fills it with a non-zero pattern. - Use
MEMF_PUBLICfor anything shared with other tasks, interrupts or the OS. MuGA does not check this flag. - Alignment is 8 bytes, no more. The guide gives an
AllocAbs()-basedAllocAligned()workaround. FreeAllocAbs()memory with the requested address and size, not the returned ones:AllocAbs(7, 0x300007)returns0x300000with 16 bytes, and onlyFreeMem(0x300007, 7)releases all of it. MuGA flags that release as misaligned; ignore that particular hit. - No allocation from interrupts or supervisor mode. Input handlers run on the input.device task, so they may allocate.
- Wait for the blitter (
WaitBlit()) before freeing a blitter buffer. The MMU cannot see blitter DMA, so MuGA cannot catch these accesses. - Use
CachePreDMA()/CachePostDMA()around your own DMA. - Debuggers that must read free memory should do so in supervisor mode. MuGA only traps user-mode accesses.
Known problems and workarounds¶
| Component | Issue | Workaround named in the guide |
|---|---|---|
| cybscsi.device | Reads unallocated chip memory as a delay loop, causing hits on every disk access. | Run FixCybAccess first. |
| RAM-Handler | Buggy under simultaneous read and write; stack too small. | PatchRAM early in the startup-sequence. |
| console.device | ROM bug on window resize. | ConsoleFix. |
| FFS | Some installations set the stack too small, causing stack warnings. | Raise the stack in the RDB. |
| narrator.device 37.7 | Stack too small. | Patch in the MMULib "Fixes" directory. |
| FFS before 43.20 | Expects FreeMem() to return −1. |
MuGA causes odd Close() results with it. |
| V37 layers.library | Frees large blocks in pieces. | Built-in workaround, disabled on V40+. |
| PatchWork | — | Start PatchWork first. |
| Executive | Incompatible. | — |
| FastExec | — | Replace with MuMove4K + MuFastZero FastExec. |
The guide also lists these limitations:
- MuGA cannot work if exec's free list contains remapped memory.
- It cannot detect accesses to memory owned by another task.
- Page granularity leaves small gaps.
If nothing else helps, remove other system patches, then try ALLOWREUSE PRI=-1, and finally NOMMU.
Recommended tool set¶
The guide's own list:
| Tool | Role |
|---|---|
| MuForce | Accesses to the vector base and to unmapped regions. |
| MuGuardianAngel | Free-memory access, walls, allocation misuse. |
| SegTracker | Maps addresses to program, hunk and offset. |
| PatchWork | Invalid parameters to OS calls. |
| Sashimi | Redirects serial debug output to a console. |
| SaferPatches | Detects illegal function patching. |
Even a program that runs cleanly under all of these is "not necessarily bug free".
See Also¶
Sources: NDK 3.2 R4 MuTools/MuGuardianAngel.guide, MuTools/MuGuardianAngel (40.51 template string), MuManual/ReadMe Updated: 2026-11-13