Map SGI Indy IP24 PROM 5.3 Rev B10
Fetch and verify the four MAME indy_4610 PROM dumps, wrap the newest dump in an ELF32 BE MIPS image for Ghidra/REA, and record the resulting structural map: image layout, exception vector table, boot flow, function anchors and firmware feature clusters.
This commit is contained in:
commit
1dc99443b7
11 files changed
+2562
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,89 @@
|
||||
# REA setup used for this project
|
||||
|
||||
[REA](https://github.com/morluto/rea) is the reverse-engineering MCP server and
|
||||
CLI used to analyze the Indy PROM. This note records the exact environment so
|
||||
the analysis can be reproduced.
|
||||
|
||||
## Installed components
|
||||
|
||||
| Component | Version | Location |
|
||||
| --- | --- | --- |
|
||||
| Node.js | 22.23.4 | `/opt/node-v22.23.4` (`node`, `npm`, `npx` symlinked into `/usr/local/bin`) |
|
||||
| REA | 6.8.0 | global npm install; `rea` in `/usr/local/bin` |
|
||||
| Ghidra | 12.1.2 | `/opt/ghidra` |
|
||||
| JDK | OpenJDK 21.0.13 | `/usr/lib/jvm/java-21-openjdk-amd64` |
|
||||
|
||||
REA requires Node `^22.23.4 || ^24.22.1 || >=26.12.1`. Debian 13 ships
|
||||
Node 20.19, so the official Node 22 tarball was installed under `/opt` instead
|
||||
of the distribution package.
|
||||
|
||||
## Registration
|
||||
|
||||
The setup was applied for OpenCode only:
|
||||
|
||||
```bash
|
||||
export GHIDRA_INSTALL_DIR=/opt/ghidra
|
||||
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
|
||||
|
||||
rea setup --client opencode --dry-run --json # reviewed plan
|
||||
rea setup --client opencode --yes
|
||||
```
|
||||
|
||||
The plan had two actions and no provider installation:
|
||||
|
||||
- update `/home/arti/.config/opencode/opencode.json` with an MCP server entry
|
||||
whose launcher is `/usr/local/bin/rea mcp`;
|
||||
- install the bundled `reverse-engineer-anything` skill under
|
||||
`~/.agents/skills/reverse-engineer-anything`.
|
||||
|
||||
Setup preserved the existing `kagi` MCP entry and wrote
|
||||
`opencode.json.rea.backup`. OpenCode must be restarted before the MCP server is
|
||||
available in a session; the CLI (`rea ...`) works immediately.
|
||||
|
||||
`rea doctor` after setup reports the Ghidra provider as available with
|
||||
`GHIDRA_INSTALL_DIR=/opt/ghidra`.
|
||||
|
||||
The machine's SSH key is not authorized on the Gitea host, so the git remote
|
||||
uses HTTPS with the configured `tea` credential helper:
|
||||
|
||||
```
|
||||
https://git.wut.ee/opencode.energia.arti.ee/sgi-indy.git
|
||||
```
|
||||
|
||||
## The MIPS problem
|
||||
|
||||
REA 6.8.0's `--target-format raw-binary` lane accepts `x86`, `x86_64`, `arm`,
|
||||
`arm64`, `6502` and `m68k` only. There is no raw MIPS architecture. MIPS is
|
||||
supported through the Ghidra ELF lane (`docs/ghidra-mips.md` upstream).
|
||||
|
||||
The Indy PROM is a headerless 512 KiB dump, so the analysis uses
|
||||
`tools/make-elf-wrapper.py` to build a load-image-only ELF32 big-endian MIPS
|
||||
file:
|
||||
|
||||
- `e_type = ET_EXEC`, `e_machine = EM_MIPS`;
|
||||
- `e_flags = 0x50000000` (`EF_MIPS_ARCH_32`, no ABI bits);
|
||||
- no `PT_MIPS_ABIFLAGS` record;
|
||||
- one `PT_LOAD` mapping file offset `0x1000` to virtual address `0x1FC00000`;
|
||||
- entry point `0x1FC00000`.
|
||||
|
||||
That combination hits REA's "recordless legacy" admission. REA records that o32
|
||||
is an analysis assumption, not a declared ABI fact, and pins Ghidra to
|
||||
`MIPS:BE:32:default`. The import log confirms:
|
||||
|
||||
```
|
||||
Using Language/Compiler: MIPS:BE:32:default:default
|
||||
```
|
||||
|
||||
## What was collected
|
||||
|
||||
The MCP session performed:
|
||||
|
||||
- `binary_overview` (segments, counts);
|
||||
- `list_procedures` (1,074 functions);
|
||||
- `list_strings` (1,418 defined strings);
|
||||
- `batch_decompile` of the reset path, main entry and exception handlers;
|
||||
- `read_function_instructions` for the reset handler and a cache/HPC3 stub;
|
||||
- `export_evidence_bundle` to `evidence/rea-bundle.json`.
|
||||
|
||||
The bundle is a local, git-ignored artifact. `tools/rom-map.py` reads it to
|
||||
produce the checked-in JSON map and CSV function index.
|
||||
@@ -0,0 +1,494 @@
|
||||
{
|
||||
"source": {
|
||||
"bytes": 524288,
|
||||
"sha1": "ac663a4db68528e400487e98cbf901f620fb30ce",
|
||||
"sha256": "333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae",
|
||||
"version_string": "SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996",
|
||||
"last_non_ff_offset": 522751,
|
||||
"last_non_ff_address": "0x1fc7f9ff"
|
||||
},
|
||||
"image": {
|
||||
"base": "0x1fc00000",
|
||||
"end": "0x1fc80000",
|
||||
"page_size": 4096
|
||||
},
|
||||
"code": {
|
||||
"start": "0x1fc00000",
|
||||
"last_function": "0x1fc4a7e0",
|
||||
"count": 1074
|
||||
},
|
||||
"strings": {
|
||||
"start": "0x1fc01b7c",
|
||||
"end": "0x1fc7c16b",
|
||||
"count": 1418
|
||||
},
|
||||
"page_kinds": {
|
||||
"exception-vectors": 1,
|
||||
"code": 74,
|
||||
"strings": 17,
|
||||
"binary-data": 31,
|
||||
"zero-data": 5
|
||||
},
|
||||
"regions": [
|
||||
{
|
||||
"kind": "exception-vectors",
|
||||
"start": "0x1fc00000",
|
||||
"end": "0x1fc01000",
|
||||
"procedures": 10,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "code",
|
||||
"start": "0x1fc01000",
|
||||
"end": "0x1fc4b000",
|
||||
"procedures": 1064,
|
||||
"strings": 103
|
||||
},
|
||||
{
|
||||
"kind": "strings",
|
||||
"start": "0x1fc4b000",
|
||||
"end": "0x1fc56000",
|
||||
"procedures": 0,
|
||||
"strings": 1272
|
||||
},
|
||||
{
|
||||
"kind": "binary-data",
|
||||
"start": "0x1fc56000",
|
||||
"end": "0x1fc6d000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "strings",
|
||||
"start": "0x1fc6d000",
|
||||
"end": "0x1fc6f000",
|
||||
"procedures": 0,
|
||||
"strings": 30
|
||||
},
|
||||
{
|
||||
"kind": "zero-data",
|
||||
"start": "0x1fc6f000",
|
||||
"end": "0x1fc71000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "binary-data",
|
||||
"start": "0x1fc71000",
|
||||
"end": "0x1fc72000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "strings",
|
||||
"start": "0x1fc72000",
|
||||
"end": "0x1fc74000",
|
||||
"procedures": 0,
|
||||
"strings": 2
|
||||
},
|
||||
{
|
||||
"kind": "zero-data",
|
||||
"start": "0x1fc74000",
|
||||
"end": "0x1fc76000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "binary-data",
|
||||
"start": "0x1fc76000",
|
||||
"end": "0x1fc78000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "zero-data",
|
||||
"start": "0x1fc78000",
|
||||
"end": "0x1fc79000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "binary-data",
|
||||
"start": "0x1fc79000",
|
||||
"end": "0x1fc7b000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
},
|
||||
{
|
||||
"kind": "strings",
|
||||
"start": "0x1fc7b000",
|
||||
"end": "0x1fc7d000",
|
||||
"procedures": 0,
|
||||
"strings": 11
|
||||
},
|
||||
{
|
||||
"kind": "binary-data",
|
||||
"start": "0x1fc7d000",
|
||||
"end": "0x1fc80000",
|
||||
"procedures": 0,
|
||||
"strings": 0
|
||||
}
|
||||
],
|
||||
"exception_vectors": [
|
||||
{
|
||||
"offset": 0,
|
||||
"address": "0x1fc00000",
|
||||
"word": "0x0bf000f0",
|
||||
"target": "0x1fc003c0"
|
||||
},
|
||||
{
|
||||
"offset": 16,
|
||||
"address": "0x1fc00010",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 32,
|
||||
"address": "0x1fc00020",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 48,
|
||||
"address": "0x1fc00030",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 64,
|
||||
"address": "0x1fc00040",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 80,
|
||||
"address": "0x1fc00050",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 96,
|
||||
"address": "0x1fc00060",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 112,
|
||||
"address": "0x1fc00070",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 128,
|
||||
"address": "0x1fc00080",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 144,
|
||||
"address": "0x1fc00090",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 160,
|
||||
"address": "0x1fc000a0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 176,
|
||||
"address": "0x1fc000b0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 192,
|
||||
"address": "0x1fc000c0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 208,
|
||||
"address": "0x1fc000d0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 224,
|
||||
"address": "0x1fc000e0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 240,
|
||||
"address": "0x1fc000f0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 256,
|
||||
"address": "0x1fc00100",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 272,
|
||||
"address": "0x1fc00110",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 288,
|
||||
"address": "0x1fc00120",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 304,
|
||||
"address": "0x1fc00130",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 320,
|
||||
"address": "0x1fc00140",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 336,
|
||||
"address": "0x1fc00150",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 352,
|
||||
"address": "0x1fc00160",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 368,
|
||||
"address": "0x1fc00170",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 384,
|
||||
"address": "0x1fc00180",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 400,
|
||||
"address": "0x1fc00190",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 416,
|
||||
"address": "0x1fc001a0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 432,
|
||||
"address": "0x1fc001b0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 448,
|
||||
"address": "0x1fc001c0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 464,
|
||||
"address": "0x1fc001d0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 480,
|
||||
"address": "0x1fc001e0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 496,
|
||||
"address": "0x1fc001f0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 512,
|
||||
"address": "0x1fc00200",
|
||||
"word": "0x0bf003c8",
|
||||
"target": "0x1fc00f20"
|
||||
},
|
||||
{
|
||||
"offset": 528,
|
||||
"address": "0x1fc00210",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 544,
|
||||
"address": "0x1fc00220",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 560,
|
||||
"address": "0x1fc00230",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 576,
|
||||
"address": "0x1fc00240",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 592,
|
||||
"address": "0x1fc00250",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 608,
|
||||
"address": "0x1fc00260",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 624,
|
||||
"address": "0x1fc00270",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 640,
|
||||
"address": "0x1fc00280",
|
||||
"word": "0x0bf003ca",
|
||||
"target": "0x1fc00f28"
|
||||
},
|
||||
{
|
||||
"offset": 656,
|
||||
"address": "0x1fc00290",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 672,
|
||||
"address": "0x1fc002a0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 688,
|
||||
"address": "0x1fc002b0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 704,
|
||||
"address": "0x1fc002c0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 720,
|
||||
"address": "0x1fc002d0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 736,
|
||||
"address": "0x1fc002e0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 752,
|
||||
"address": "0x1fc002f0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 768,
|
||||
"address": "0x1fc00300",
|
||||
"word": "0x0bf003c6",
|
||||
"target": "0x1fc00f18"
|
||||
},
|
||||
{
|
||||
"offset": 784,
|
||||
"address": "0x1fc00310",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 800,
|
||||
"address": "0x1fc00320",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 816,
|
||||
"address": "0x1fc00330",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 832,
|
||||
"address": "0x1fc00340",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 848,
|
||||
"address": "0x1fc00350",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 864,
|
||||
"address": "0x1fc00360",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 880,
|
||||
"address": "0x1fc00370",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 896,
|
||||
"address": "0x1fc00380",
|
||||
"word": "0x0bf00390",
|
||||
"target": "0x1fc00e40"
|
||||
},
|
||||
{
|
||||
"offset": 912,
|
||||
"address": "0x1fc00390",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 928,
|
||||
"address": "0x1fc003a0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
},
|
||||
{
|
||||
"offset": 944,
|
||||
"address": "0x1fc003b0",
|
||||
"word": "0x0bf001f1",
|
||||
"target": "0x1fc007c4"
|
||||
}
|
||||
]
|
||||
}
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
# Indy IP24 PROM 5.3 Rev B10: structural map
|
||||
|
||||
This document maps the newest known SGI Indy boot PROM,
|
||||
`ip24prom.070-9101-011.bin`. All addresses are PROM addresses as mapped by the
|
||||
hardware; file offsets are `address - 0x1FC00000`.
|
||||
|
||||
Facts in this document come from three places:
|
||||
|
||||
1. byte-level inspection of the dump (padding, strings, jump vectors);
|
||||
2. Ghidra 12.1.2 auto-analysis driven through REA 6.8.0;
|
||||
3. MAME's IP22/IP24 machine definitions for the surrounding address map.
|
||||
|
||||
Function names such as `FUN_1fc02000` are Ghidra auto-generated. The
|
||||
interpretations attached to them are inferences from their code, call sites and
|
||||
the strings they reference; they are labelled as such.
|
||||
|
||||
## Identity
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| File | `ip24prom.070-9101-011.bin` |
|
||||
| Size | 524,288 bytes (512 KiB) |
|
||||
| Part number | 070-9101-011 |
|
||||
| Version | `SGI Version 5.3 Rev B10 R4X00/R5000 IP24 Feb 12, 1996` |
|
||||
| SHA-1 | `ac663a4db68528e400487e98cbf901f620fb30ce` |
|
||||
| SHA-256 | `333a0b2e39bb828a28a758b6f633ae7290030129dc747297a1be68dc317b86ae` |
|
||||
| MAME set | `indy_4610` |
|
||||
| Load address | `0x1FC00000` |
|
||||
| Analyzed functions | 1,074 |
|
||||
| Analyzed strings | 1,418 |
|
||||
|
||||
The SHA-1 matches MAME's `src/mame/sgi/indy_indigo2.cpp` ROM entry exactly.
|
||||
|
||||
### The other dumps in the set
|
||||
|
||||
The `indy_4610` set contains four PROM revisions. SHA-1 values below are the
|
||||
ones MAME records.
|
||||
|
||||
| File | Version | SHA-1 |
|
||||
| --- | --- | --- |
|
||||
| `ip24prom.070-9101-005.bin` | 5.1.2 Rev B4, 9 Dec 1993 | `28b769b28218a1fcd0400dceef9a284dcfbdda5b` |
|
||||
| `ip24prom.070-9101-007.bin` | 5.0 Rev B6, 28 Sep 1994 | `ade54cd2ecb7064957f8602894f05685e2f4e8fb` |
|
||||
| `ip24prom.070-9101-008.bin` | 5.3 Rev B7, 16 Feb 1995 | `a752a4aef7e2c6086b8b0244e9f064861a11870f` |
|
||||
| `ip24prom.070-9101-011.bin` | 5.3 Rev B10, 12 Feb 1996 | `ac663a4db68528e400487e98cbf901f620fb30ce` |
|
||||
|
||||
`011` is the newest, and the only one whose version string claims
|
||||
`R4X00/R5000` support. MAME selects it as the default BIOS for both
|
||||
`indy_4610` (R4600) and `indy_5015` (R5000).
|
||||
|
||||
## How the image was opened
|
||||
|
||||
The dump is headerless. REA's raw-binary lane has no MIPS architecture, so a
|
||||
minimal ELF wrapper is produced by `tools/make-elf-wrapper.py`:
|
||||
|
||||
- ELF32, big-endian, `ET_EXEC`, `EM_MIPS`;
|
||||
- `e_flags = 0x50000000` (`EF_MIPS_ARCH_32`, no ABI field);
|
||||
- no `PT_MIPS_ABIFLAGS` record;
|
||||
- one `PT_LOAD` at file offset `0x1000`, virtual address `0x1FC00000`;
|
||||
- entry `0x1FC00000`.
|
||||
|
||||
REA admits this through its recordless legacy MIPS lane and pins Ghidra to
|
||||
`MIPS:BE:32:default`. The import log confirms that language. The ROM bytes are
|
||||
not modified.
|
||||
|
||||
The declared ABI is therefore absent by construction: o32 calling conventions
|
||||
are an analysis assumption of the lane, not a property recovered from the ROM.
|
||||
This affects parameter recovery in decompiled output, not instruction decoding
|
||||
or control flow.
|
||||
|
||||
## Physical placement
|
||||
|
||||
MAME's IP24 map places the 512 KiB BIOS window at `0x1FC00000`. Through the MIPS
|
||||
segments the same bytes are also visible at:
|
||||
|
||||
| View | Address |
|
||||
| --- | --- |
|
||||
| Physical / KUSEG | `0x1FC00000` |
|
||||
| KSEG0 (cached) | `0x9FC00000` |
|
||||
| KSEG1 (uncached) | `0xBFC00000` |
|
||||
|
||||
Ghidra analyzed the physical mapping. The reset code itself uses KSEG0/KSEG1
|
||||
addresses such as `0x9FC7DD18`, `0x9FC4AAC8` and `0xBFA0xxxx`; those appear in
|
||||
decompiled output as their unmapped KSEG forms.
|
||||
|
||||
Relevant neighbours from MAME (`src/mame/sgi/indy_indigo2.cpp`):
|
||||
|
||||
| Range | Device |
|
||||
| --- | --- |
|
||||
| `0x00000000` - `0x0007FFFF` | Alias for first 512 KiB of RAM |
|
||||
| `0x08000000` - `0x17FFFFFF` | Low system memory |
|
||||
| `0x1F000000` - `0x1F3FFFFF` | GIO64 graphics |
|
||||
| `0x1F400000` - `0x1F5FFFFF` | GIO64 EXP0 |
|
||||
| `0x1FA00000` - `0x1FA1FFFF` | Memory controller |
|
||||
| `0x1FB00000` - `0x1FB7FFFF` | HPC3 CHIP1 |
|
||||
| `0x1FB80000` - `0x1FBFFFFF` | HPC3 CHIP0 |
|
||||
| `0x1FC00000` - `0x1FFFFFFF` | BIOS (this ROM) |
|
||||
| `0x20000000` - `0x2FFFFFFF` | High system memory |
|
||||
| `0x80000000` - `0xFFFFFFFF` | EISA memory |
|
||||
|
||||
The reset code touches `0xBFA00xxx`/`0xBFA01xxx` (KSEG1 alias of the memory
|
||||
controller) and `0xBFBD9xxx`/`0xBFBBxxxx` (KSEG1 alias inside HPC3 CHIP0).
|
||||
|
||||
## Image layout
|
||||
|
||||
Page-level detail is in [rom-map.json](rom-map.json). The compressed view:
|
||||
|
||||
| File offset | PROM address | Size | Content |
|
||||
| --- | --- | --- | --- |
|
||||
| `0x00000` - `0x003BF` | `0x1FC00000` - `0x1FC003BF` | 960 B | 60-entry, 16-byte exception jump table |
|
||||
| `0x003C0` - `0x4A89F` | `0x1FC003C0` - `0x1FC4A89F` | ~298 KiB | Reset handler, code and inline diagnostic strings; 1,074 functions, last one starting at `0x1FC4A7E0` |
|
||||
| `0x4A8A0` - `0x5568F` | `0x1FC4A8A0` - `0x1FC5568F` | ~44 KiB | Core message blob: boot sequence, diagnostics, monitor help, environment and ARCS strings |
|
||||
| `0x56000` - `0x6CFFF` | `0x1FC56000` - `0x1FC6CFFF` | 92 KiB | Binary tables; no Ghidra-defined strings |
|
||||
| `0x6D000` - `0x6EA4C` | `0x1FC6D000` - `0x1FC6EA4C` | ~6.5 KiB | Path-test strings and the environment variable/default table |
|
||||
| `0x6EA4D` - `0x7BF8F` | `0x1FC6EA4D` - `0x1FC7BF8F` | ~54 KiB | Mostly zero/binary pages plus two isolated strings |
|
||||
| `0x7BF90` - `0x7C16B` | `0x1FC7BF90` - `0x1FC7C16B` | ~480 B | SCSI tape drive name table |
|
||||
| `0x7C16C` - `0x7F9FF` | `0x1FC7C16C` - `0x1FC7F9FF` | ~14 KiB | Remaining data tables |
|
||||
| `0x7FA00` - `0x7FFFF` | `0x1FC7FA00` - `0x1FC7FFFF` | 1536 B | `0xFF` padding |
|
||||
|
||||
Only four `0xFF` runs of 16 bytes or more exist; three are tiny alignment holes
|
||||
inside the string area, and the real padding is the final 1,536 bytes. The last
|
||||
non-`0xFF` byte is at file offset `0x7F9FF`.
|
||||
|
||||
The code and message regions overlap at the page level: page
|
||||
`0x1FC4A000`-`0x1FC4AFFF` holds the last nine functions and 64 strings, so the
|
||||
"code ends" boundary above is the end of executable content, not the start of
|
||||
all strings.
|
||||
|
||||
## Exception vector table
|
||||
|
||||
The first 960 bytes are 60 cells of 16 bytes:
|
||||
|
||||
```
|
||||
0x1FC00000 + (index * 0x10)
|
||||
```
|
||||
|
||||
Each cell begins with an absolute MIPS `J` instruction followed by three
|
||||
`NOP`s. The reset handler lives at `0x1FC003C0`, immediately after the table, so
|
||||
the final four 16-byte slots are not table entries.
|
||||
|
||||
Observed targets:
|
||||
|
||||
| Cell address | Index | Target | Count |
|
||||
| --- | --- | --- | --- |
|
||||
| `0x1FC00000` | 0 | `0x1FC003C0` (reset handler) | 1 |
|
||||
| `0x1FC00200` | 32 | `0x1FC00F20` | 1 |
|
||||
| `0x1FC00280` | 40 | `0x1FC00F28` | 1 |
|
||||
| `0x1FC00300` | 48 | `0x1FC00F18` | 1 |
|
||||
| `0x1FC00380` | 56 | `0x1FC00E40` | 1 |
|
||||
| all other cells | - | `0x1FC007C4` (shared handler) | 55 |
|
||||
|
||||
The architectural MIPS R4000 vector offsets (`0x080` TLB refill, `0x100` general
|
||||
exception, `0x180` XTLB refill, `0x200` cache error) fall inside this table. Of
|
||||
those, only `0x200` is special; the rest use the shared handler. The meaning of
|
||||
indices 32, 40, 48 and 56 is not established by this analysis. One plausible
|
||||
reading is an SGI dispatch table indexed by a cause-derived value rather than by
|
||||
the architectural vector offset, but that is an inference, not an observation.
|
||||
|
||||
The four special targets all land inside the block at `0x1FC00E40`-`0x1FC00F28`.
|
||||
Ghidra did not split that block into separate functions; auto-analysis treats
|
||||
`0x1FC00DCC` as the nearest function start. The bytes there are worth a focused
|
||||
follow-up with manual function creation.
|
||||
|
||||
## Reset and boot flow
|
||||
|
||||
### Reset handler, `0x1FC003C0`
|
||||
|
||||
Observed instruction sequence (REA/Ghidra):
|
||||
|
||||
1. `lui v0, 0x3041; mtc0 v0, Status` and clear `Cause`, `PageMask`,
|
||||
`WatchLo`, `WatchHi`.
|
||||
2. `Config = 2`, then test `Config` bit 15. If clear, call `FUN_1fc0a810`,
|
||||
which reads and rewrites a CP0 configuration value and writes
|
||||
`0xBFA00000 = 0x10000`.
|
||||
3. Write memory-controller registers at `0xBFA000EC`, `0xBFA000FC`,
|
||||
`0xBFA00004`, `0xBFA0000C`, `0xBFA00084`, `0xBFA000D4`,
|
||||
`0xBFA000DC` and `0xBFA0002C`. The value stored at `0xBFA00084` is loaded
|
||||
from `0x9FC7DD18`.
|
||||
4. Poll `0xBFA01004` until the counter advances by at least `0x271`.
|
||||
5. Call `0x1FC00A3C`, `0x1FC00BD0`, `0x1FC00DCC`, `0x1FC00D58`.
|
||||
6. Set `s8 = 0x9FC03660`, then call `0x1FC039E0`, `0x1FC03DE0`,
|
||||
`0x1FC03EB0`, `0x1FC01070`.
|
||||
7. Sample `0xBFA000C4`, test bits `0x2200`, optionally set low bits of
|
||||
`0xBFA00004`.
|
||||
8. Call `0x1FC009D4`, then `j 0x1FC02000`.
|
||||
|
||||
The Ghidra decompiler renders the end of this function as an infinite loop
|
||||
because it does not model the final `j` as a return. The instruction listing is
|
||||
the authority here.
|
||||
|
||||
### Cold-start main, `0x1FC02000`
|
||||
|
||||
The function reached by the reset jump initializes HPC3/IOC registers around
|
||||
`0xBFBD9xxx`, calls a long sequence of hardware setup routines, and then reads
|
||||
the `diagmode` environment variable at `0x9FC4AAC8`.
|
||||
|
||||
Two paths follow:
|
||||
|
||||
- diagnostics/menu path (`FUN_1fc02a50`) when the diagnostic state is set;
|
||||
- autoboot path (`FUN_1fc0fb10`) otherwise.
|
||||
|
||||
`FUN_1fc0fb10` reads `AutoLoad` (`0x1FC4D090`) and `NoAutoLoad`
|
||||
(`0x1FC4D09C`). If autoload is enabled it prints the maintenance prompt
|
||||
`To perform system maintenance instead, press <Esc>` and attempts a boot; a
|
||||
failure prints `Unable to boot; press any key to continue:` and falls back
|
||||
through `FUN_1fc00624`.
|
||||
|
||||
`FUN_1fc02a50` is the menu/boot loop. It reads `VERBOSE` and `diskless`,
|
||||
touches IOC interrupt-enable bits, and loops through the maintenance menu whose
|
||||
strings sit at `0x1FC4D99C`:
|
||||
|
||||
- `Install System Software`
|
||||
- `Recover System`
|
||||
- `Enter Command Monitor`
|
||||
- `Select Keyboard Layout`
|
||||
|
||||
The same area contains `Autoboot failed`, `Diagnostics` and
|
||||
`Command Monitor. Type "exit" %sto return to the menu.`
|
||||
|
||||
### Boot-sequence messages
|
||||
|
||||
The ordered diagnostic strings beginning at `0x1FC4A8A0` describe the intended
|
||||
power-on sequence:
|
||||
|
||||
1. `Power switch released.`
|
||||
2. `Initializing primary/secondary caches.`
|
||||
3. `Secondary cache size is 0x%x (%d Kbytes)`
|
||||
4. `Initializing exception handlers.`
|
||||
5. `Initializing Status Register.`
|
||||
6. `Enabling PLP, ISDN B/A DMA channels.`
|
||||
7. `Selecting Gen Control register bits.`
|
||||
8. `Initialize Map Polarity.`
|
||||
9. `Deasserting VIDEO Reset.`
|
||||
10. `Playing startup tune.`
|
||||
11. `Initializing environment.`
|
||||
12. `Executing remainder of diagnostic.`
|
||||
13. `Initializing software and devices.`
|
||||
14. `All initialization and diagnostic completed.`
|
||||
|
||||
## Function anchors
|
||||
|
||||
| Address | Auto name | Interpretation | Basis |
|
||||
| --- | --- | --- | --- |
|
||||
| `0x1FC00000` | `entry` | Table slot 0 thunk to the reset handler | Ghidra symbol, thunk target |
|
||||
| `0x1FC003C0` | `FUN_1fc003c0` | Reset handler | CP0 init, memory-controller setup, jump to main |
|
||||
| `0x1FC00624` | `FUN_1fc00624` | Shared exception/fallback path | Vector-table default target; cache and memory scrub code |
|
||||
| `0x1FC00DCC` | `FUN_1fc00dcc` | Short HPC3/cache stub | Writes `0xBFB91004`, `0xBFBC0003`, `0xBFBC0007`; nearest function for special vectors |
|
||||
| `0x1FC02000` | `FUN_1fc02000` | Cold-start main | Reached by reset jump; HPC3/IOC init; branches to diagnostics or autoboot |
|
||||
| `0x1FC02A50` | `FUN_1fc02a50` | Menu/boot loop | Reads `VERBOSE`/`diskless`; loops over maintenance menu |
|
||||
| `0x1FC0FB10` | `FUN_1fc0fb10` | Autoboot policy | Reads `AutoLoad`/`NoAutoLoad`; maintenance prompt and boot failure text |
|
||||
|
||||
The full list of 1,074 function starts is in
|
||||
[procedure-index.csv](procedure-index.csv). The CSV has three columns:
|
||||
`address,name,thunk_target`.
|
||||
|
||||
## Firmware feature map
|
||||
|
||||
### Command monitor
|
||||
|
||||
Help strings at `0x1FC4ACE8`-`0x1FC4B054` enumerate the monitor commands:
|
||||
|
||||
`autoboot` (`auto`), `boot`, `checksum`, `date`, `dump`, `exit`, `fill`, `get`,
|
||||
`go`, `help`/`?`, `initialize` (`init`), `inventory`, `passwd`, `printenv`,
|
||||
`put`, `reboot`, `resetenv`, `resetpw`, `setenv`, `single`, `unsetenv`,
|
||||
`version`.
|
||||
|
||||
The banner is `PROM Monitor %s (BE)` at `0x1FC4B090`, followed by the version
|
||||
string.
|
||||
|
||||
### Boot targets
|
||||
|
||||
Strings at `0x1FC4AAF0`-`0x1FC4AB44` name the default load paths:
|
||||
|
||||
- `bootp()ide.IP24`
|
||||
- `sashIP24`
|
||||
- `bootp()sashIP24`
|
||||
- `bootp()unix.IP24`
|
||||
|
||||
Elsewhere the image references `sashARCS`, `part(8)sashARCS`, `/stand/ide`,
|
||||
IDE distribution CD boot, and ARCS-style boot.
|
||||
|
||||
### Diagnostics
|
||||
|
||||
Diagnostic result strings name the tests the PROM can run:
|
||||
|
||||
- memory probe, memory data test, memory address test, walking-bit test;
|
||||
- cache diagnostic;
|
||||
- graphics diagnostic;
|
||||
- interrupt controller diagnostic;
|
||||
- SCSI controller, SCSI device/cable, WD SCSI0 path test;
|
||||
- RTC path test, INT path test, data path test;
|
||||
- PC keyboard/mouse controller, keyboard, mouse;
|
||||
- CPU base board, graphics board, CPU module, SIMM replacement prompts.
|
||||
|
||||
The display-side strings also expose the VDMA and DMA error registers
|
||||
(`DMA_CAUSE`, `DMA_MEMADR`, `CPU_MEMACC`, `GIO_MEMACC`, `CPU_ERR_ADDR`,
|
||||
`CPU_ERR_STAT`, `GIO_ERR_ADDR`, `GIO_ERR_STAT`) and the MIPS exception dump
|
||||
fields (`ErrEPC`, `BadVaddr`, `Cause`, `Status`, `CacheErr`, `CpuParityErr`,
|
||||
`GioParityErr`, `LIOstatus0..2`, `CpuErrorAddr`, `GioErrorAddr`).
|
||||
|
||||
### Environment variables
|
||||
|
||||
The environment table around `0x1FC6E4F0`-`0x1FC6EA4C` and the help strings
|
||||
around `0x1FC4CD00`-`0x1FC4E000` cover:
|
||||
|
||||
`AutoLoad`, `NoAutoLoad`, `diagmode`, `console`, `diskless`, `VERBOSE`,
|
||||
`eaddr`, `netaddr`, `srvaddr`, `gateaddr`, `netinsthost`, `netinstfile`,
|
||||
`tapedevice`, `scsihostid`, `scsiretries`, `pagecolor`, `TimeZone`,
|
||||
`autopower`, `SystemPartition`, `OSLoader`, `OSLoadPartition`,
|
||||
`OSLoadFilename`/`OSLoadFileName`, `OSLoadOptions`, `ConsoleIn`,
|
||||
`ConsoleOut`, `ConsoleWarning`, `passwd_key`, `netpasswd_key`.
|
||||
|
||||
The NVRAM path has its own messages: `NVRAM checksum is incorrect:
|
||||
reinitializing.`, `nvchecksum:`, `Checksum mismatch: is 0x%x, s/b 0x%x`,
|
||||
`Enter new password:`, `Confirm new password:`, `Password cleared.` and the
|
||||
password-jumper warnings.
|
||||
|
||||
### ARCS and SCSI tables
|
||||
|
||||
The string blob around `0x1FC5549C`-`0x1FC55524` contains the ARCS component
|
||||
names `ExceptionBlock`, `SystemParameterBlock`, `FreeContiguous`, `FreeMemory`,
|
||||
`BadMemory`, `LoadedProgram`, `FirmwareTemporary` and `FirmwarePermanent`.
|
||||
|
||||
The cluster at `0x1FC7BF90`-`0x1FC7C16B` is a SCSI tape-drive name table:
|
||||
`CIPHER`, `TANDBERG TDC 3660`, `ARCHIVE VIPER 150`, `VIPER 60`, `Python`,
|
||||
`EXABYTE` and `KENNEDY`.
|
||||
|
||||
The sentinel value `cafebabedeadbeef` appears at `0x1FC6EA4C`; it is likely an
|
||||
uninitialised-memory pattern used by the environment/NVRAM code, but that is an
|
||||
inference.
|
||||
|
||||
## Confidence and limitations
|
||||
|
||||
- Function boundaries and names come from Ghidra auto-analysis. There are no
|
||||
symbols in the image beyond the imported `entry` label.
|
||||
- The ELF wrapper declares no ABI. o32 is an analysis assumption, so recovered
|
||||
parameter counts and types are weaker than the control flow.
|
||||
- The vector table's 60 x 16-byte structure and observed targets are facts.
|
||||
What each index means is not established.
|
||||
- The decompiler output is heuristic. Where it conflicts with the instruction
|
||||
listing (for example the reset handler's final jump), the instruction listing
|
||||
is authoritative.
|
||||
- String inventory covers Ghidra-defined string data only (1,418 entries).
|
||||
Undefined or packed text may be missing, and an absent string is not proof
|
||||
that the text is absent from the artifact.
|
||||
- No code was executed. Nothing here establishes runtime behaviour.
|
||||
- Ghidra 12.1.2 was used; REA's bridge is verified against 12.1.4 and accepts
|
||||
other 12.1.x builds.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```bash
|
||||
./tools/fetch-roms.sh
|
||||
./tools/make-elf-wrapper.py \
|
||||
roms/ip24prom.070-9101-011.bin -o roms/indy-ip24-5.3-b10.elf
|
||||
|
||||
export GHIDRA_INSTALL_DIR=/opt/ghidra
|
||||
rea analyze roms/indy-ip24-5.3-b10.elf --provider ghidra --json
|
||||
```
|
||||
|
||||
The checked-in artifacts were produced with the REA MCP session described in
|
||||
[rea-setup.md](rea-setup.md), then:
|
||||
|
||||
```bash
|
||||
./tools/rom-map.py roms/ip24prom.070-9101-011.bin \
|
||||
--evidence evidence/rea-bundle.json \
|
||||
--output docs/rom-map.json \
|
||||
--procedures-csv docs/procedure-index.csv
|
||||
```
|
||||
Reference in new issue
Block a user