5.5 KiB
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
Residentstructure. BeginIOdispatch 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):
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.
- Memory Allocation: When first initialized, it allocates a large block for disk data.
- Validation: It writes a "magic cookie" and a checksum at the start of this block.
- Resident List: It adds its own
ROMTagto theExecBase->ResModuleslist. - Warm Reboot: On a reset, the Exec loader scans memory for
RTC_MATCHWORD($4AFC). When it finds theramdrive.devicetag, it checks the block's checksum. - Re-binding: If valid, the device re-binds the existing data block instead of allocating a new one.
Locating ramdrive.device in ROM
# 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):
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:
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
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:
; 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
- Search for string
"ramdrive.device"→ finds theROMTag. RT_INITpointer → points to the initialization function.RT_INITlogic → callsMakeLibrarythenAddDevice.- Library Base → Follow the
rd_Devicebase to find theBeginIOentry point at offset -30. - Switch Table →
BeginIOtypically uses a jump table (JMP) or a series ofCMPI / BEQto dispatch commands.
References
- NDK39:
exec/devices.h,exec/io.h,devices/trackdisk.h,exec/resident.h - io_requests.md — IORequest structure and dispatch
10_devices/trackdisk_device.md— TD_* command codes- IRA Disassembly of ramdrive.device — Reference for instruction patterns.