amiga-bootcamp/05_reversing/case_studies/ramdrive_device.md

167 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../../README.md) · [Reverse Engineering](../README.md)
# Case Study — ramdrive.device Structure Analysis
## Overview
`ramdrive.device` is the Amiga's built-in RAM disk device. It provides a RAM-based disk drive (`RAD:`) that can survive a warm reboot (Ctrl-Amiga-Amiga). This makes it an excellent target for reverse engineering to understand **Resident Modules**, **Exec Device Architecture**, and **Memory Survival** techniques.
Analysing it teaches:
- Exec device initialization and the `Resident` structure.
- `BeginIO` dispatch logic.
- Persistence mechanisms across system resets.
---
## Resident Structure (`ROMTag`)
Like all Amiga libraries and devices, `ramdrive.device` starts with a `struct Resident` (defined in `exec/resident.h`):
```c
struct Resident {
UWORD rt_MatchWord; /* $4AFC (RTC_MATCHWORD) */
struct Resident *rt_MatchTag; /* Pointer to self */
APTR rt_EndSkip; /* Pointer to end of module */
UBYTE rt_Flags; /* RTF_AFTERDOS | RTF_COLDBOOT */
UBYTE rt_Version; /* Version of module */
UBYTE rt_Type; /* NT_DEVICE */
BYTE rt_Pri; /* Priority */
char *rt_Name; /* "ramdrive.device" */
char *rt_IdString; /* ID string */
APTR rt_Init; /* Pointer to Init routine */
};
```
### Reset Survival Mechanism
The primary challenge for `ramdrive.device` is ensuring its memory is not reclaimed by the system after a reset.
1. **Memory Allocation**: When first initialized, it allocates a large block for disk data.
2. **Validation**: It writes a "magic cookie" and a checksum at the start of this block.
3. **Resident List**: It adds its own `ROMTag` to the `ExecBase->ResModules` list.
4. **Warm Reboot**: On a reset, the Exec loader scans memory for `RTC_MATCHWORD` ($4AFC). When it finds the `ramdrive.device` tag, it checks the block's checksum.
5. **Re-binding**: If valid, the device re-binds the existing data block instead of allocating a new one.
---
## Locating ramdrive.device in ROM
```bash
# Find the resident tag in Kickstart ROM dump:
python3 - <<'EOF'
import struct, sys
rom = open("kick31.rom", "rb").read()
for i in range(0, len(rom)-4, 2):
tag = struct.unpack_from(">H", rom, i)[0]
if tag == 0x4AFC: # RomTag magic
rt_matchword = struct.unpack_from(">H", rom, i)[0]
rt_matchtag = struct.unpack_from(">I", rom, i+2)[0]
rt_name = struct.unpack_from(">I", rom, i+14)[0]
# Offset lookup for "ramdrive.device" string
print(f"RomTag @ ROM+{i:#x}")
EOF
```
---
## Device Structure Layout
`ramdrive.device` extends `struct Device` (which extends `struct Library`):
```c
struct RAMDriveBase {
struct Device rd_Device; /* standard device base */
/* private fields follow */
APTR rd_RAMStart; /* pointer to allocated RAM block */
ULONG rd_RAMSize; /* total size */
ULONG rd_BlockSize; /* always 512 */
ULONG rd_NumBlocks; /* RAMSize / BlockSize */
struct MinList rd_Units; /* list of open units */
};
```
---
## Standard Device Vectors (LVO)
| Offset | Vector | Description |
|---|---|---|
| 6 | `Open` | Open a unit (unit number in io_Unit) |
| 12 | `Close` | Close unit, decrement open count |
| 18 | `Expunge` | Unload if no users |
| 24 | `Reserved` | NULL |
| 30 | `BeginIO` | Queue or execute an IORequest |
| 36 | `AbortIO` | Cancel pending IORequest |
---
## IORequest Command Handling
`BeginIO` is the heart of the driver. It dispatches on `io_Command`:
```c
void BeginIO(struct IORequest *ior) {
struct IOStdReq *io = (struct IOStdReq *)ior;
switch (io->io_Command) {
case CMD_READ: rd_Read(io); break;
case CMD_WRITE: rd_Write(io); break;
case CMD_CLEAR: rd_Clear(io); break;
case TD_FORMAT: rd_Format(io); break;
case TD_GETGEOMETRY: rd_Geometry(io); break;
default:
io->io_Error = IOERR_NOCMD;
ReplyMsg(&io->io_Message);
}
}
```
### `CMD_READ` Implementation
```c
void rd_Read(struct IOStdReq *io) {
UBYTE *src = rdbase->rd_RAMStart + io->io_Offset;
CopyMem(src, io->io_Data, io->io_Length);
io->io_Actual = io->io_Length;
io->io_Error = 0;
ReplyMsg(&io->io_Message);
}
```
---
## Deep Analysis: Checksum Verification
When disassembling the initialization routine, look for the verification pattern that identifies a valid "surviving" RAM disk:
```asm
; Typical checksum verification pattern
CheckSum:
move.l (a0)+, d1 ; Get magic cookie
cmpi.l #$ABCDEF01, d1 ; Verify magic
bne.s Invalid
move.l #Length, d0
Loop:
add.l (a0)+, d2 ; Sum up the block
dbf d0, Loop
cmp.l Expected, d2
```
---
## Disassembly Landmarks in IDA
1. **Search for string `"ramdrive.device"`** → finds the `ROMTag`.
2. **`RT_INIT` pointer** → points to the initialization function.
3. **`RT_INIT` logic** → calls `MakeLibrary` then `AddDevice`.
4. **Library Base** → Follow the `rd_Device` base to find the `BeginIO` entry point at offset -30.
5. **Switch Table**`BeginIO` typically uses a jump table (JMP) or a series of `CMPI / BEQ` to dispatch commands.
---
## References
- NDK39: `exec/devices.h`, `exec/io.h`, `devices/trackdisk.h`, `exec/resident.h`
- [io_requests.md](../../06_exec_os/io_requests.md) — IORequest structure and dispatch
- `10_devices/trackdisk_device.md` — TD_* command codes
- [IRA Disassembly of ramdrive.device](http://aminet.net/package/dev/asm/ramdrive_src) — Reference for instruction patterns.