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 overwritten when it notices this. SaferPatches REMEMBER can help find the culprit.

Requirements and installation

  1. Install mmu.library (and 68040/68060.library on those CPUs).
  2. 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.
  3. Remove Enforcer, CyberGuard, MungWall, MungList and MemSniff.
  4. 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:

  1. Run with NAMETAG TASK=yourtask SNOOP.
  2. avail flush, then run and quit the program.
  3. avail flush again.
  4. Every A line needs exactly one F line. An A line 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 a Wait(). 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 get ERROR_NO_FREE_STORE (103) from IoErr().
  • Do not assume contents. Without MEMF_CLEAR the block is garbage; MuGA fills it with a non-zero pattern.
  • Use MEMF_PUBLIC for 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()-based AllocAligned() workaround. Free AllocAbs() memory with the requested address and size, not the returned ones: AllocAbs(7, 0x300007) returns 0x300000 with 16 bytes, and only FreeMem(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.

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