mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
docs(amiga): complete AmigaOS 3.1/3.2 developer reference — 172 files across 17 sections
Comprehensive technical documentation covering: - Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives - Boot sequence: cold boot through startup-sequence - Binary format: HUNK executable spec, relocation, debug info - Linking & ABI: .fd files, LVO tables, register calling conventions - Exec kernel: tasks, interrupts, memory, signals, semaphores - AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting - Graphics: planar bitmaps, Copper programming, HAM/EHB modes - Intuition: screens, windows, IDCMP, BOOPSI - Devices: trackdisk, SCSI, serial, timer, audio, keyboard - Libraries: utility, expansion, IFFParse, locale, ARexx - Networking: bsdsocket API, SANA-II, TCP/IP stack comparison - Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging - Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies - CPU & MMU: 68040/060 emulation libs, PMMU, cache management - Driver development: SANA-II, Picasso96/RTG, AHI audio All files include breadcrumb navigation. No local paths or proprietary content.
This commit is contained in:
parent
f07a368bf1
commit
21751c0025
172 changed files with 19701 additions and 0 deletions
110
05_reversing/dynamic/enforcer_mungwall.md
Normal file
110
05_reversing/dynamic/enforcer_mungwall.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Enforcer and MungWall — Memory Violation Tracing
|
||||
|
||||
## Overview
|
||||
|
||||
**Enforcer** (by Michael Sinz) and **MungWall** are the two canonical Amiga memory debugging tools. They catch illegal memory accesses and heap corruption at runtime, providing the equivalent of AddressSanitizer for AmigaOS.
|
||||
|
||||
---
|
||||
|
||||
## Enforcer
|
||||
|
||||
Enforcer uses the 68020+ MMU (or software patching on 68000) to trap accesses to:
|
||||
- Address `$0000–$07FF` (lower 2 KB — reserved vectors and exec structures)
|
||||
- Odd-addressed word/longword reads
|
||||
- Accesses above the installed RAM
|
||||
- Writes to ROM addresses
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
; AmigaOS Shell:
|
||||
run enforcer
|
||||
; or for logging:
|
||||
run enforcer QUIET LOG enforcer.log
|
||||
```
|
||||
|
||||
Enforcer patches the `BusError` exception vector (`$8`). Any illegal access causes a bus error, which Enforcer catches, logs, and (usually) continues.
|
||||
|
||||
### Output Format
|
||||
|
||||
```
|
||||
ENFORCER HIT: by Unknown (Task: "DPaint" at $001234AB)
|
||||
Program Counter: $0023AB12
|
||||
Address Accessed: $0000012C (read longword)
|
||||
Stack Dump: $001234C0 $0001A2B4 ...
|
||||
```
|
||||
|
||||
- **Program Counter** — instruction that caused the hit
|
||||
- **Address Accessed** — illegal address
|
||||
- Cross-reference PC against `HUNK_SYMBOL` names or IDA disassembly
|
||||
|
||||
### Common Causes
|
||||
|
||||
| Hit Pattern | Likely Cause |
|
||||
|---|---|
|
||||
| Access to `$0–$3FF` | NULL pointer dereference |
|
||||
| Access to `$4` (SysBase) without read | Null exec base |
|
||||
| Odd address read (word/long) | Misaligned pointer |
|
||||
| Access to `$B80000–$BFFFFF` | CIA access without correct alignment |
|
||||
| Write to ROM `$F80000+` | Write to Kickstart ROM |
|
||||
|
||||
---
|
||||
|
||||
## MungWall
|
||||
|
||||
MungWall fills `AllocMem()` allocations with a known pattern (`$ABADCAFE`) and adds guard longwords before and after each block (`$DEADBEEF`). On `FreeMem()`, it verifies the guards.
|
||||
|
||||
### What It Catches
|
||||
|
||||
- **Heap underrun** — write before the allocated block (guard before = corrupted)
|
||||
- **Heap overrun** — write past the end of block (guard after = corrupted)
|
||||
- **Use after free** — block is filled with `$DEADBEEF` on free; reads from it will fail if Enforcer is also running
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
run mungwall
|
||||
```
|
||||
|
||||
### Output on Corruption
|
||||
|
||||
```
|
||||
MUNGWALL: Block $001A2000 (size 128) has been overwritten!
|
||||
Header guard: OK
|
||||
Trailer guard: CORRUPTED at +132
|
||||
Caller: $0023BC44 (FreeMem called from here)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Combined Workflow
|
||||
|
||||
1. `run mungwall` first — patches AllocMem/FreeMem
|
||||
2. `run enforcer` — adds MMU-level illegal access detection
|
||||
3. Launch the suspect program
|
||||
4. Any crash produces Enforcer + MungWall output on the serial port / `enforcer.log`
|
||||
5. Cross-reference the PC value with `HUNK_SYMBOL` or IDA to find the exact line
|
||||
|
||||
---
|
||||
|
||||
## Serial Port Logging
|
||||
|
||||
Both tools output via `kprintf` to serial port (115200 8N1). Capture on host:
|
||||
|
||||
```bash
|
||||
# macOS / Linux:
|
||||
screen /dev/cu.usbserial-XXXX 115200
|
||||
# or
|
||||
minicom -D /dev/cu.usbserial-XXXX -b 115200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Enforcer: Michael Sinz — available on Aminet (`util/misc/Enforcer.lha`)
|
||||
- MungWall: original CBM debug tool, available on Aminet
|
||||
- `dynamic/serial_debug.md` — serial output setup
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — exec memory management
|
||||
132
05_reversing/dynamic/live_memory_probing.md
Normal file
132
05_reversing/dynamic/live_memory_probing.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Live Memory Probing
|
||||
|
||||
## Overview
|
||||
|
||||
Live memory probing on a running Amiga means directly reading exec structures — `SysBase`, `LibList`, `TaskReady`, `MemList` — to observe system state without a traditional debugger.
|
||||
|
||||
---
|
||||
|
||||
## SysBase: The Root of Everything
|
||||
|
||||
`SysBase` is always at absolute address `$4` (a pointer to the `ExecBase` structure):
|
||||
|
||||
```c
|
||||
struct ExecBase *SysBase = *((struct ExecBase **)4);
|
||||
printf("exec version: %d.%d\n",
|
||||
SysBase->LibNode.lib_Version,
|
||||
SysBase->LibNode.lib_Revision);
|
||||
```
|
||||
|
||||
In assembly:
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; A6 = SysBase (exec.library base)
|
||||
MOVE.W ($16,A6), D0 ; lib_Version
|
||||
MOVE.W ($18,A6), D1 ; lib_Revision
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Walking the Library List
|
||||
|
||||
```c
|
||||
struct Node *n = SysBase->LibList.lh_Head;
|
||||
while (n->ln_Succ != NULL) {
|
||||
struct Library *lib = (struct Library *)n;
|
||||
printf("%-30s v%d.%d opens=%d\n",
|
||||
lib->lib_Node.ln_Name,
|
||||
lib->lib_Version, lib->lib_Revision,
|
||||
lib->lib_OpenCnt);
|
||||
n = n->ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
This enumerates all currently loaded libraries. Useful for:
|
||||
- Finding if a target library is loaded
|
||||
- Reading `lib_OpenCnt` to detect if your hook is installed
|
||||
- Checking `lib_Flags & LIBF_DELEXP` (expunge pending)
|
||||
|
||||
---
|
||||
|
||||
## Reading `lib_OpenCnt` Live
|
||||
|
||||
```c
|
||||
/* Check if bsdsocket.library is loaded and its open count */
|
||||
struct Library *base = FindName(&SysBase->LibList, "bsdsocket.library");
|
||||
if (base) {
|
||||
printf("bsdsocket: OpenCnt=%d, Version=%d\n",
|
||||
base->lib_OpenCnt, base->lib_Version);
|
||||
}
|
||||
```
|
||||
|
||||
`FindName` scans `ln_Name` in a linked list — it is an exec function at LVO −276.
|
||||
|
||||
---
|
||||
|
||||
## Memory Region Map
|
||||
|
||||
`SysBase->MemList` lists all memory regions:
|
||||
|
||||
```c
|
||||
struct MemHeader *mh = (struct MemHeader *)SysBase->MemList.lh_Head;
|
||||
while (mh->mh_Node.ln_Succ) {
|
||||
printf("Region: %s %08lx–%08lx free=%ld\n",
|
||||
mh->mh_Node.ln_Name,
|
||||
(ULONG)mh->mh_Lower,
|
||||
(ULONG)mh->mh_Upper,
|
||||
mh->mh_Free);
|
||||
mh = (struct MemHeader *)mh->mh_Node.ln_Succ;
|
||||
}
|
||||
```
|
||||
|
||||
Output example:
|
||||
```
|
||||
Region: chip memory $000000–$1FFFFF free=524288
|
||||
Region: fast memory $200000–$9FFFFF free=6291456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task List Inspection
|
||||
|
||||
```c
|
||||
/* Running tasks: */
|
||||
Forbid();
|
||||
struct Task *t = (struct Task *)SysBase->TaskReady.lh_Head;
|
||||
while (t->tc_Node.ln_Succ) {
|
||||
printf("Task: %-20s pri=%d state=%d\n",
|
||||
t->tc_Node.ln_Name,
|
||||
t->tc_Node.ln_Pri,
|
||||
t->tc_State);
|
||||
t = (struct Task *)t->tc_Node.ln_Succ;
|
||||
}
|
||||
Permit();
|
||||
```
|
||||
|
||||
`Forbid()` / `Permit()` are mandatory — the task list must not change while walking it.
|
||||
|
||||
---
|
||||
|
||||
## Patching Memory Live (Surgical Writes)
|
||||
|
||||
For RE/patching: direct longword write to an OS structure:
|
||||
|
||||
```c
|
||||
/* Example: force a library's version to 99 */
|
||||
Forbid();
|
||||
target_lib->lib_Version = 99;
|
||||
Permit();
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Direct memory writes to OS structures bypass all synchronization. Always use `Forbid()` at minimum; use `Disable()` if modifying interrupt-visible data.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`, `exec/memory.h`, `exec/tasks.h`
|
||||
- `06_exec_os/exec_base.md` — full ExecBase offset table
|
||||
- `06_exec_os/memory_management.md` — MemHeader structure
|
||||
- `05_reversing/dynamic/setfunction_patching.md` — Forbid/Permit patterns
|
||||
118
05_reversing/dynamic/serial_debug.md
Normal file
118
05_reversing/dynamic/serial_debug.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# Serial Debugging — kprintf and Serial Output
|
||||
|
||||
## Overview
|
||||
|
||||
The Amiga's built-in serial port is the primary low-level debugging channel. `kprintf()` (kernel printf) and `RawPutChar()` write directly to the serial hardware, bypassing `dos.library` and working even from interrupt context or before OS initialization.
|
||||
|
||||
---
|
||||
|
||||
## `kprintf()` — Kernel Printf
|
||||
|
||||
`kprintf()` is a ROM debug function present in Kickstart 1.3 and later debug ROMs. It formats a string and outputs each character via `RawPutChar`.
|
||||
|
||||
```c
|
||||
/* Prototype (exec internal, not in NDK — declare manually): */
|
||||
void kprintf(const char *fmt, ...);
|
||||
/* Arguments in: D1=fmt, stack args (unlike standard AmigaOS register ABI) */
|
||||
```
|
||||
|
||||
### Calling `kprintf` from Assembly
|
||||
|
||||
```asm
|
||||
MOVEA.L 4.W, A6 ; SysBase
|
||||
LEA _fmt_str(PC), A0 ; format string
|
||||
MOVE.L A0, -(SP) ; push as stack argument
|
||||
MOVE.L A0, D1 ; some implementations use D1
|
||||
JSR (-$F0,A6) ; RawDoFmt or debug rom entry
|
||||
; OR for ROM debug builds:
|
||||
JSR _kprintf
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `kprintf` is **not available** in standard Kickstart 3.1 release ROMs. Use `debug.lib` stubs (`dprintf`) or `RawDoFmt + RawPutChar` instead.
|
||||
|
||||
---
|
||||
|
||||
## `RawDoFmt` + `RawPutChar` — Universal Approach
|
||||
|
||||
This works on **all** Kickstart versions (1.2+):
|
||||
|
||||
```c
|
||||
/* Format into a buffer and output via RawPutChar */
|
||||
static void serial_putchar(UBYTE c, APTR dummy) {
|
||||
/* write directly to serial data register */
|
||||
volatile UWORD *SERDATR = (UWORD *)0xDFF018;
|
||||
volatile UWORD *SERDATW = (UWORD *)0xDFF030;
|
||||
volatile UWORD *SERDATSTAT;
|
||||
/* Wait for TBE (transmit buffer empty) */
|
||||
while (!(*SERDATR & 0x2000));
|
||||
*SERDATW = 0x0100 | c; /* 8 bits + start bit */
|
||||
}
|
||||
|
||||
void dbg_printf(const char *fmt, ...) {
|
||||
UBYTE buf[256];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
/* RawDoFmt(fmt, args, putChar, buf) */
|
||||
RawDoFmt((STRPTR)fmt, &args,
|
||||
(VOID (*)())serial_putchar, buf);
|
||||
va_end(args);
|
||||
}
|
||||
```
|
||||
|
||||
Or simpler — write to the serial hardware directly:
|
||||
|
||||
```c
|
||||
static void SerPutChar(UBYTE c) {
|
||||
while (!(*((volatile UWORD *)0xDFF018) & 0x2000)); /* wait TBE */
|
||||
*((volatile UWORD *)0xDFF030) = 0x0100 | c;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `debug.lib` (SAS/C)
|
||||
|
||||
SAS/C ships `debug.lib` providing `dprintf`:
|
||||
|
||||
```c
|
||||
#include <debug.h>
|
||||
dprintf("mylib: Open called, name=%s\n", name);
|
||||
```
|
||||
|
||||
Output goes to the serial port at the rate set by SERPER (default 9600 baud on startup, 115200 if set).
|
||||
|
||||
---
|
||||
|
||||
## Setting Baud Rate
|
||||
|
||||
```c
|
||||
/* Set serial to 115200 baud (PAL, 3.546895 MHz clock): */
|
||||
/* SERPER = (clock / (16 * baud)) - 1 */
|
||||
/* = (3546895 / (16 * 115200)) - 1 = 0 */
|
||||
volatile UWORD *SERPER = (UWORD *)0xDFF032;
|
||||
*SERPER = 0x0000; /* 115200 on PAL */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Host-Side Capture
|
||||
|
||||
```bash
|
||||
# macOS (USB-serial adapter):
|
||||
screen /dev/cu.usbserial-XXXX 115200
|
||||
# or:
|
||||
stty -f /dev/cu.usbserial-XXXX 115200 raw && cat /dev/cu.usbserial-XXXX
|
||||
```
|
||||
|
||||
MiSTer FPGA: the UART bridge is exposed on the MiSTer IO board or via the DE10-Nano UART.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h` — `RawDoFmt`, `RawPutChar` LVOs
|
||||
- `01_hardware/ocs_a500/paula_serial.md` — SERPER, SERDATR, SERDATW register details
|
||||
- Aminet: `debug/misc/dprintf.lha`
|
||||
130
05_reversing/dynamic/setfunction_patching.md
Normal file
130
05_reversing/dynamic/setfunction_patching.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
[← Home](../../README.md) · [Reverse Engineering](../README.md)
|
||||
|
||||
# SetFunction — Hooking Library Vectors at Runtime
|
||||
|
||||
## Overview
|
||||
|
||||
`SetFunction()` is the official AmigaOS mechanism for **patching a library's JMP table** at runtime. It installs a custom function at a given LVO, replacing the original, and returns the old function pointer so a trampoline can be constructed.
|
||||
|
||||
---
|
||||
|
||||
## `SetFunction()` API
|
||||
|
||||
```c
|
||||
/* exec/execbase.h */
|
||||
APTR SetFunction(struct Library *library, LONG funcOffset, APTR newFunction);
|
||||
/* Returns: pointer to OLD function */
|
||||
```
|
||||
|
||||
- `library` — target library base (e.g., `DOSBase`)
|
||||
- `funcOffset` — negative LVO offset (e.g., `-30` for `dos.library Open`)
|
||||
- `newFunction` — your replacement function
|
||||
|
||||
---
|
||||
|
||||
## Installing a Hook
|
||||
|
||||
```asm
|
||||
; Example: hook dos.library Write() at LVO -48
|
||||
|
||||
MOVEA.L _SysBase, A6
|
||||
JSR (-120,A6) ; Forbid() — prevent preemption during patch
|
||||
|
||||
MOVEA.L _DOSBase, A1
|
||||
MOVE.L #-48, A0 ; LVO for Write
|
||||
LEA _my_write(PC), A2
|
||||
JSR (-420,A6) ; SetFunction(DOSBase, -48, &my_write)
|
||||
MOVE.L D0, _orig_write ; save original function pointer
|
||||
|
||||
JSR (-126,A6) ; Permit()
|
||||
```
|
||||
|
||||
### C equivalent:
|
||||
|
||||
```c
|
||||
static APTR orig_write;
|
||||
|
||||
void install_hook(void) {
|
||||
Forbid();
|
||||
orig_write = SetFunction((struct Library *)DOSBase, -48,
|
||||
(APTR)my_write_hook);
|
||||
Permit();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writing a Trampoline
|
||||
|
||||
The hook function must:
|
||||
1. Perform its instrumentation
|
||||
2. Call the original via the saved pointer
|
||||
3. Return with the original return value in D0
|
||||
|
||||
```asm
|
||||
_my_write:
|
||||
; D1 = file handle, D2 = buffer, D3 = length (Write args)
|
||||
MOVEM.L D0-D7/A0-A6, -(SP) ; save all (we may corrupt anything)
|
||||
|
||||
; ... instrumentation: log args, patch buffer, etc. ...
|
||||
|
||||
MOVEM.L (SP)+, D0-D7/A0-A6
|
||||
MOVEA.L _orig_write, A0
|
||||
JMP (A0) ; jump to original — not JSR; let original RTS
|
||||
```
|
||||
|
||||
In C (with `__asm` constraints):
|
||||
```c
|
||||
LONG __asm my_write_hook(register __d1 BPTR fh,
|
||||
register __d2 APTR buf,
|
||||
register __d3 LONG len) {
|
||||
/* instrumentation */
|
||||
return ((LONG (*)(BPTR,APTR,LONG))orig_write)(fh, buf, len);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Restoring on Exit
|
||||
|
||||
**Critical:** Always restore the original function before the program exits. Failure leaves a dangling pointer in the library JMP table, causing crashes for any subsequent users of the library.
|
||||
|
||||
```c
|
||||
void remove_hook(void) {
|
||||
Forbid();
|
||||
SetFunction((struct Library *)DOSBase, -48, orig_write);
|
||||
Permit();
|
||||
}
|
||||
|
||||
/* Register with atexit: */
|
||||
atexit(remove_hook);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety Considerations
|
||||
|
||||
- `Forbid()` / `Permit()` disable task switching — keep the window minimal
|
||||
- If the hook itself calls OS functions, use `Disable()` / `Enable()` instead only when interrupts must be excluded
|
||||
- Hooks are system-global — all tasks using the library will go through your hook
|
||||
|
||||
---
|
||||
|
||||
## Common Use Cases in RE
|
||||
|
||||
| Use | Hook | LVO |
|
||||
|---|---|---|
|
||||
| Trace file access | `dos.library Open` | −30 |
|
||||
| Intercept writes | `dos.library Write` | −48 |
|
||||
| Monitor memory allocation | `exec.library AllocMem` | −198 |
|
||||
| Log task creation | `exec.library AddTask` | −282 |
|
||||
| Spy on library opens | `exec.library OpenLibrary` | −552 |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/execbase.h`
|
||||
- ADCD 2.1: `SetFunction` autodoc
|
||||
- `05_reversing/dynamic/live_memory_probing.md` — SysBase structure access
|
||||
- *Amiga ROM Kernel Reference Manual: Libraries* — SetFunction chapter
|
||||
Loading…
Add table
Add a link
Reference in a new issue