Update all docs and test generator to match verified assembler opcodes
- README.md: Complete project overview with plugin status - ARCHITECTURE.md: Fixed register tables (CNTR in REG1, not REG2) - GETTING_STARTED.md: r2-native workflow, removed Python disasm refs - PRACTICAL_EXAMPLE.md: Uses verified open21xx opcodes with bit layouts - ROM_ANALYSIS_WALKTHROUGH.md: Updated format detection and r2 commands - r2plugin/README.md: Simplified, points to assembler test ROM - gen_isa_test.py: All opcodes from open21xx assembler with labels
This commit is contained in:
48
README.md
48
README.md
@@ -1,3 +1,47 @@
|
||||
# adsp219x-re
|
||||
# ADSP-219x Reverse Engineering Toolkit
|
||||
|
||||
ADSP-2191 Reverse Engineering - Disassembler, radare2 plugin, and analysis tools
|
||||
Radare2 architecture plugin and reference materials for reverse
|
||||
engineering ADSP-2191 firmware.
|
||||
|
||||
## Contents
|
||||
|
||||
r2plugin/ Native radare2 arch plugin (C, GNU style)
|
||||
examples/ ADSP-2191 assembly examples (FIR, IIR, FFT, Viterbi)
|
||||
+ assembler-verified ISA test ROM
|
||||
testrom/ Test ROM generator for plugin validation
|
||||
docs/ Architecture reference, opcode tables, walkthrough guides
|
||||
|
||||
## Quick Start
|
||||
|
||||
cd r2plugin && make && make install
|
||||
r2 -a adsp219x -b 24 examples/isa_test.bin
|
||||
[0x00000000]> pd 48
|
||||
|
||||
## Requirements
|
||||
|
||||
- Radare2 >= 5.8.0 (RArchPlugin API)
|
||||
- GCC
|
||||
|
||||
## Plugin Status
|
||||
|
||||
Currently decodes: NOP, IDLE, Type 1 (multifunction), Type 3
|
||||
(direct memory), Type 4 (compute + memory), Type 6/7 (immediate
|
||||
loads), Type 10/10a (jump/call), Type 17 (reg move), Type 20
|
||||
(RTS/RTI).
|
||||
|
||||
Work in progress: Type 9/9a (standalone compute), Type 11
|
||||
(DO UNTIL), Type 15 (shift), Type 18 (mode control), Type 25
|
||||
(saturate).
|
||||
|
||||
## Assembler
|
||||
|
||||
The examples were assembled with [open21xx](https://github.com/wodz/open21xx),
|
||||
an open-source assembler/linker for ADSP-218x/219x. Build
|
||||
instructions are in the open21xx README. The resulting ELF is
|
||||
converted to raw binary with `dd` (extract the `int_pm` section).
|
||||
|
||||
## License
|
||||
|
||||
Plugin code: LGPL-3.0-only. Example code from Analog Devices
|
||||
application notes. Documentation extracts from the ADSP-219x
|
||||
DSP Instruction Set Reference.
|
||||
|
||||
@@ -1,34 +1,60 @@
|
||||
# ADSP-219x Architecture Reference ⚙️
|
||||
# ADSP-219x Architecture Reference
|
||||
|
||||
## Core Features
|
||||
The ADSP-219x family consists of a 16-bit fixed-point DSP core with a 24-bit instruction word.
|
||||
|
||||
- **Harvard Architecture**: Separate Program Memory (PM) and Data Memory (DM) buses.
|
||||
- **Instruction Width**: Exactly 24 bits. Padded often with a leading 0x00 or trailing zero byte if stored in 32-bit words, or packed as 3 bytes.
|
||||
The ADSP-219x family is a 16-bit fixed-point DSP core with a 24-bit
|
||||
instruction word.
|
||||
|
||||
- **Harvard Architecture**: Separate Program Memory (PM) and Data
|
||||
Memory (DM) buses.
|
||||
- **Instruction Width**: 24 bits (3 bytes). ROM dumps are typically
|
||||
packed (3 bytes per word, big-endian) or padded (4 bytes with a
|
||||
leading 0x00).
|
||||
- **Memory Model**:
|
||||
- PM Adressraum (24-bit Wörter): 16-bit to 24-bit depending on the model.
|
||||
- DM Adressraum (16-bit Wörter): Up to 64K words.
|
||||
- PM: 24-bit words, up to 64K words (model-dependent).
|
||||
- DM: 16-bit words, up to 64K words.
|
||||
|
||||
## Register Set 🗳️
|
||||
## Register Set
|
||||
|
||||
| Group | Registry | Purpose |
|
||||
|-------|----------|---------|
|
||||
| **REG0** | AX0, AX1, MX0, MX1, AY0, AY1, MY0, MY1, MR2, SR2, AR, SI, MR1, SR1, MR0, SR0 | ALU, Multiplier, MAC, and Shifter registers. |
|
||||
| **REG1** | I0-I3, M0-M3, L0-L3, IMASK, IRPTL, ICNTL, STACKA | DAG1 (Data Address Generator) indices, modifies, lengths, and interrupt control. |
|
||||
| **REG2** | I4-I7, M4-M7, L4-L7, Reserved, CNTR, LPSTACKA | DAG2 indices, modifies, lengths, and hardware loop structures. |
|
||||
| **REG3** | ASTAT, MSTAT, SSTAT, LPSTACKP, CCODE, SE, SB, PX, DMPG1, DMPG2, IOPG, IJPG, Reserved, STACKP | Status registers, page registers, and control stacks. |
|
||||
| Group | Registers (index 0-15) | Purpose |
|
||||
|----------|------------------------------------------------------------------------------------------|--------------------------------------------------|
|
||||
| **REG0** | AX0, AX1, MX0, MX1, AY0, AY1, MY0, MY1, MR2, SR2, AR, SI, MR1, SR1, MR0, SR0 | ALU, Multiplier, Shifter operand/result registers |
|
||||
| **REG1** | I0, I1, I2, I3, M0, M1, M2, M3, L0, L1, L2, L3, IMASK, IRPTL, ICNTL, CNTR | DAG1 + interrupt control + counter |
|
||||
| **REG2** | I4, I5, I6, I7, M4, M5, M6, M7, L4, L5, L6, L7, STACKA, LPCSTACKA, (reserved x2) | DAG2 + stack/loop registers |
|
||||
| **REG3** | ASTAT, MSTAT, SSTAT, LPSTACKP, CCODE, SE, SB, PX, DMPG1, DMPG2, IOPG, IJPG, (res x3), STACKP | Status, page, and control registers |
|
||||
|
||||
## Arithmetic Elements
|
||||
- **ALU**: 16-bit with overflow and saturation logic.
|
||||
- **Multiplier/MAC**: 16x16 → 40-bit accumulation (MR).
|
||||
- **Barrel Shifter**: 32-bit with 16-bit input and bit-manipulation capabilities.
|
||||
|
||||
## Instruction Types 🕹️
|
||||
There are ~37 distinct instruction types, distinguished by their MSB encoding.
|
||||
- **ALU**: 16-bit, supports add/subtract/logic/pass/abs with
|
||||
carry, overflow, and saturation.
|
||||
- **Multiplier/MAC**: 16x16 -> 40-bit accumulator (MR0/MR1/MR2).
|
||||
Signed/unsigned modes (SS, SU, US, UU) plus rounding.
|
||||
- **Barrel Shifter**: 32-bit shift register (SR0/SR1), supports
|
||||
logical/arithmetic shift, normalize, and exponent detect.
|
||||
|
||||
- **Type 1**: Compute + Dual Memory Read (Multifunction).
|
||||
- **Type 3**: Direct Register Read/Write.
|
||||
- **Type 4**: Compute + Single Memory Read/Write.
|
||||
- **Type 6/7**: Load Immediate 16-bit to Register.
|
||||
- **Type 10/10a/19/36**: Jumps and Calls (Relative/Indirect/Long).
|
||||
- **Type 15/16**: Shifter operations.
|
||||
## Instruction Encoding (Top-Level Dispatch)
|
||||
|
||||
Instructions are decoded by their most-significant bits:
|
||||
|
||||
| Bits 23-22 | Bits 21-20 | Type(s) |
|
||||
|------------|------------|-----------------------------------|
|
||||
| 11 | xx | Type 1: Compute + DM + PM (multifunction) |
|
||||
| 10 | xx | Type 3/3a: Direct memory access |
|
||||
| 01 | 00 | Type 6: Dreg = Imm16 (REG0) |
|
||||
| 01 | 01 | Type 7: Reg = Imm16 (REG1) |
|
||||
| 01 | 10 | Type 11/33/34/35 (DO, IO, REG3) |
|
||||
| 01 | 11 | Type 4: Compute + DM/PM postmodify |
|
||||
| 00 | 11 | Type 7: Reg = Imm16 (REG2) |
|
||||
| 00 | 10 | Type 9/9a: Compute (standalone) |
|
||||
| 00 | 01 | Type 10/10a: Jump/Call |
|
||||
| 00 | 00 | Type 15/17/18/20/25/30/31 (misc) |
|
||||
|
||||
## Boot and ROM Format
|
||||
|
||||
The ADSP-2191 boots from external memory (SPI flash, host, etc.).
|
||||
A raw ROM dump contains packed 24-bit instructions in big-endian
|
||||
byte order. Boot-stream images may include block headers:
|
||||
|
||||
[Target Addr] [Byte Count] [Flags] [Data...]
|
||||
|
||||
The reset vector is at PM address 0x0000.
|
||||
|
||||
@@ -1,26 +1,59 @@
|
||||
# ADSP-219x Analysis Workflow (Air-Gapped 🛡️)
|
||||
# Getting Started (Air-Gapped Deployment)
|
||||
|
||||
## Phase 1: Preparation (Online)
|
||||
1. **Download and Copy**: Ensure the entire `adsp219x-re/` folder is on the air-gapped machine.
|
||||
2. **Setup Dependencies**: Ensure Python 3.8+ is installed. Radare2 must be on $PATH.
|
||||
3. **Verify Python**: `python3 -m pip install r2pipe` (if not already part of your r2 install).
|
||||
## Prerequisites
|
||||
|
||||
## Phase 2: Loading ROMs
|
||||
Use raw loading for ADSP-2191 ROMs:
|
||||
`r2 -a adsp2 -b 24 -m 0x0 my_rom.bin`
|
||||
(Note: `-a` and `-b` are placeholders until a native plugin exists. We use raw mode.)
|
||||
- Radare2 5.8.0+ (built from source on the target machine)
|
||||
- GCC (to compile the plugin)
|
||||
- Python 3 (optional, for test ROM generation)
|
||||
|
||||
## Phase 3: Automated Disassembly
|
||||
Run the standalone disassembler first to get a quick overview:
|
||||
`python3 disassembler/adsp219x_disasm.py my_rom.bin 3 > disassembly.txt`
|
||||
## Setup
|
||||
|
||||
## Phase 4: Radare2 + Iaito Integration
|
||||
To use our custom Python disassembly inside radare2:
|
||||
1. Open the ROM in radare2.
|
||||
2. Run the analysis script via r2pipe:
|
||||
`#!python analysis/analyze_rom.py` (Inside r2: `#!pipe python3 ...`)
|
||||
3. Use `iaito` to browse the memory with the comments generated by the script.
|
||||
1. Copy the entire `adsp219x-re/` directory to the air-gapped machine.
|
||||
|
||||
## Phase 5: Debugging/Validation
|
||||
Compare your proprietary ROM against our test ROMs in `testrom/test_roms/`.
|
||||
If you find a new instruction type, add it to `disassembler/adsp219x_disasm.py` and submit!
|
||||
2. Build and install the radare2 plugin:
|
||||
|
||||
cd r2plugin
|
||||
make
|
||||
make install
|
||||
|
||||
3. Verify installation:
|
||||
|
||||
r2 -a adsp219x -b 24 -q -c "pd 10" examples/isa_test.bin
|
||||
|
||||
You should see decoded instructions (NOP, AX0 = 0x1234, etc.).
|
||||
|
||||
## Loading a ROM Dump
|
||||
|
||||
For a raw 24-bit packed binary (3 bytes per instruction, big-endian):
|
||||
|
||||
r2 -a adsp219x -b 24 firmware.bin
|
||||
|
||||
For a padded 32-bit dump (4 bytes per instruction with leading 0x00),
|
||||
strip the padding first or adjust the base offset manually.
|
||||
|
||||
## Useful r2 Commands
|
||||
|
||||
pd 100 # Disassemble 100 instructions
|
||||
s 0x200 # Seek to PM address 0x200 (byte offset 0x600)
|
||||
/x 1c # Search for JUMP opcodes (Type 10a prefix)
|
||||
axt @@ sym.* # Cross-references (after analysis)
|
||||
|
||||
## Validation
|
||||
|
||||
Compare your disassembly against the reference examples in
|
||||
`examples/`. The `isa_test.bin` was assembled with the open21xx
|
||||
toolchain and contains verified opcodes for all major instruction
|
||||
types.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
The `docs/` directory contains the original Analog Devices
|
||||
instruction set reference chapters (PDF and text extracts):
|
||||
|
||||
- `9x_opcodes.*` - Opcode definitions and mnemonics
|
||||
- `9x_ALUops.*` - ALU operations
|
||||
- `9x_mltops.*` - Multiplier/MAC operations
|
||||
- `9x_shftops.*` - Shifter operations
|
||||
- `9x_flowops.*` - Flow control (jumps, loops, returns)
|
||||
- `9x_moveops.*` - Data move operations
|
||||
- `9x_multiops.*` - Multifunction operations
|
||||
|
||||
@@ -1,54 +1,78 @@
|
||||
# Praktisches Analyse-Beispiel: ADSP-219x Boot-Sequenz
|
||||
# Practical Example: Disassembling a Boot Sequence
|
||||
|
||||
In diesem Beispiel analysieren wir einen (generierten) 3-Byte-Packed Dump. Wir gehen die Instruktionen nacheinander durch, wie du es in `iaito` oder mit unserem Disassembler machen würdest.
|
||||
This walkthrough uses verified opcodes from the open21xx assembler
|
||||
to demonstrate analysis of a typical ADSP-2191 initialization.
|
||||
|
||||
## Die Rohdaten (Hex-Dump)
|
||||
```text
|
||||
Offset 0x00: 00 00 00
|
||||
Offset 0x03: 40 12 30
|
||||
Offset 0x06: 50 20 00
|
||||
Offset 0x09: 50 00 14
|
||||
Offset 0x0C: DA 00 00
|
||||
Offset 0x0F: 18 10 0F
|
||||
```
|
||||
## Hex Dump (from examples/isa_test.bin)
|
||||
|
||||
## Schritt-für-Schritt Disassembly
|
||||
0x000 000000 NOP
|
||||
0x003 412340 AX0 = 0x1234
|
||||
0x006 456781 AX1 = 0x5678
|
||||
0x009 4AAAA4 AY0 = 0xAAAA
|
||||
0x00C 440002 MX0 = 0x4000
|
||||
0x00F 420006 MY0 = 0x2000
|
||||
0x012 501000 I0 = 0x0100
|
||||
0x015 502001 I1 = 0x0200
|
||||
0x018 303000 I4 = 0x0300
|
||||
|
||||
### 1. Adresse 0x0000: `0x000000`
|
||||
* **Decodierung:** Type 30 (NOP).
|
||||
* **Bedeutung:** Keine Operation. Oft am Reset-Vektor zu sehen, falls der eigentliche Einsprungpunkt erst bei 0x0004 liegt (je nach Core-Revision).
|
||||
## Step-by-Step Analysis
|
||||
|
||||
### 2. Adresse 0x0001: `0x401230`
|
||||
* **Decodierung:** Type 6 (Immediate Register Load). `0x0123` ist der Wert (4 Bits geshiftet).
|
||||
* **Assembly:** `AX0 = 0x1230`
|
||||
* **Bedeutung:** Das Arithmetische X-Register 0 wird mit einer Konstanten geladen. Das ist der typische Beginn einer Berechnung.
|
||||
### 1. NOP at Reset Vector (0x000000)
|
||||
|
||||
### 3. Adresse 0x0002: `0x502000`
|
||||
* **Decodierung:** Type 7 (Immediate Address Register Load).
|
||||
* **Assembly:** `I0 = 0x2000`
|
||||
* **Bedeutung:** Ein Index-Register des DAG1 wird mit einer Startadresse geladen. Hier fängt wahrscheinlich ein Daten-Buffer im DM (Data Memory) an.
|
||||
The processor starts at PM address 0x0000. A NOP here means the
|
||||
real entry point follows immediately, or there is a JUMP to the
|
||||
main code further ahead.
|
||||
|
||||
### 4. Adresse 0x0003: `0x500014`
|
||||
* **Decodierung:** Type 7.
|
||||
* **Assembly:** `M1 = 1`
|
||||
* **Bedeutung:** Das Modifier-Register wird auf 1 gesetzt. Das bedeutet, bei jedem Zugriff auf den Buffers springt der interne Pointer genau 1 Word weiter.
|
||||
### 2. Register Initialization (Type 6: Dreg = Imm16)
|
||||
|
||||
### 5. Adresse 0x0004: `0xDA0000` (Multifunktions-Instruction)
|
||||
* **Decodierung:** Type 1. Opcode beginnt mit `11` (Binar: `11 01 101...`).
|
||||
* **Bedeutung:** Hier passiert die Magie. `AMF = 13` (Add), `DMI=0`, `DMM=0`.
|
||||
* **Assembly:** `AR = AX0 + AY0, AX0 = DM(I0 += M0), AY0 = PM(I4 += M4)`
|
||||
* **Schlussfolgerung:** Das ist eine DSP-Operation. Er berechnet die Summe zweier Werte, lädt gleichzeitig den nächsten Wert aus dem Datenspeicher (DM) und gleichzeitig den übernächsten Filter-Koeffizienten aus dem Programmspeicher (PM). **Wenn du das siehst, hast du die Signalverarbeitung gefunden!**
|
||||
0x412340 -> AX0 = 0x1234
|
||||
Bit layout: 01.00.0001.00100.011.0100.0000
|
||||
Bits 23-20 = 0100 (Type 6), bits 3-0 = 0000 (AX0)
|
||||
Immediate value in bits 19-4 = 0x1234
|
||||
|
||||
### 6. Adresse 0x0005: `0x18100F`
|
||||
* **Decodierung:** Type 10 (Direct Jump).
|
||||
* **Assembly:** `JUMP 0x0100`
|
||||
* **Bedeutung:** Ein Sprung zu einer anderen Code-Region (wahrscheinlich das Hauptprogramm oder eine Header-Überspringen).
|
||||
### 3. DAG Setup (Type 7: Reg1/Reg2 = Imm16)
|
||||
|
||||
## Worauf du achten musst:
|
||||
1. **I/M Paare:** Wenn du siehst, dass `I2` geladen wird, suche nach dem zugehörigen `M2`. Ohne `Mn` kann der DAG nicht sinnvoll inkrementieren.
|
||||
2. **Register-Gruppen:** Der ADSP-2191 hat 4 Gruppen (REG0-3). `AX0` ist REG0, `I0` ist REG1. Wenn du Register-zu-Register Kopien siehst (z.B. `REG(0,4) = REG(1,0)`), achte auf die Gruppennummern im Opcode.
|
||||
3. **B-Bit (Delayed Branches):** Bei Sprüngen (`JUMP`, `CALL`) gibt es oft ein **B-Bit**. Wenn es gesetzt ist (`JUMP (DB)`), wird der Befehl *nach* dem Sprung noch ausgeführt, bevor er springt (Pipelining!). Das ist eine häufige Falle bei Reverse-Engineering.
|
||||
0x501000 -> I0 = 0x0100
|
||||
Bits 23-20 = 0101 (Type 7, REG1), bits 3-0 = 0000 (I0)
|
||||
|
||||
---
|
||||
**Nächster Schritt:** Probiere den neuen Disassembler mit dem generierten `base_test.bin` aus:
|
||||
`python3 disassembler/adsp219x_disasm.py testrom/test_roms/base_test.bin`
|
||||
0x303000 -> I4 = 0x0300
|
||||
Bits 23-20 = 0011 (Type 7, REG2), bits 3-0 = 0000 (I4)
|
||||
|
||||
This is the classic DAG initialization pattern: set index registers
|
||||
to buffer start addresses, then set modifier and length registers.
|
||||
|
||||
### 4. Compute Instructions (Type 9: ALU/MAC)
|
||||
|
||||
0x22600F -> AR = AX0 + AY0
|
||||
Bits 23-21 = 001, 20-19 = 00 (Type 9)
|
||||
AMF = bits 17-13 = 10011 = X+Y
|
||||
XOP = bits 10-8 = 000 (AX0), YOP = bits 12-11 = 00 (AY0)
|
||||
COND = bits 3-0 = 1111 (TRUE = unconditional)
|
||||
|
||||
0x20800F -> MR = MX0 * MY0 (SS)
|
||||
AMF = bits 17-13 = 00100 = X*Y (SS)
|
||||
|
||||
### 5. Jumps (Type 10 vs 10a)
|
||||
|
||||
Type 10 (conditional, 13-bit address):
|
||||
|
||||
0x180010 -> IF EQ JUMP 0x0001
|
||||
Bits 23-20 = 0001, 19 = 1, 18 = 0 (Type 10)
|
||||
B = bit 17 = 0 (no delay slot)
|
||||
Address = bits 16-4, COND = bits 3-0 = 0000 (EQ)
|
||||
|
||||
Type 10a (unconditional, 16-bit address):
|
||||
|
||||
0x1C0010 -> JUMP 0x0001
|
||||
Bits 23-18 = 000111 (Type 10a)
|
||||
Address = bits 17-4 + bits 1-0 as MSBs
|
||||
S = bit 2 (0=JUMP, 1=CALL), B = bit 3 (delayed branch)
|
||||
|
||||
## Patterns to Look For
|
||||
|
||||
- **FIR/IIR kernels**: Tight DO...UNTIL CE loops containing
|
||||
Type 1 multifunction instructions (MAC + dual memory read).
|
||||
- **Initialization**: Sequences of Type 6/7 loads setting up
|
||||
DAG registers (I/M/L) before entering a processing loop.
|
||||
- **Data tables in PM**: Regions that disassemble as nonsense
|
||||
are likely coefficient tables (24-bit PM data words).
|
||||
|
||||
@@ -1,75 +1,76 @@
|
||||
# ADSP-219x ROM Analyse Walkthrough
|
||||
# ROM Analysis Walkthrough
|
||||
|
||||
Dieses Dokument beschreibt den systematischen Prozess der Analyse eines ADSP-219x Binär-Dumps. Da wir mit einer 24-Bit Architektur auf einer 8-Bit-basierten Dateisystem-Welt arbeiten, ist der erste Schritt immer die Validierung des Formats.
|
||||
## 1. Determine the ROM Format
|
||||
|
||||
## 1. Phase: Format-Validierung (Padded vs. Packed)
|
||||
ADSP-2191 instructions are 24 bits (3 bytes). A raw dump can be:
|
||||
|
||||
Ein ADSP-2191 Befehl ist **24 Bit (3 Bytes)** groß. In einem ROM-Dump liegen diese meist in einem von zwei Formaten vor:
|
||||
- **Packed (3 bytes/word)**: Most common for SPI flash dumps.
|
||||
Load directly: `r2 -a adsp219x -b 24 dump.bin`
|
||||
- **Padded (4 bytes/word)**: 32-bit aligned with a leading 0x00.
|
||||
Strip padding or use `r2 -s 1` to skip the first pad byte.
|
||||
- **Boot stream**: Contains block headers (target address, byte
|
||||
count, flags) followed by data. Requires parsing the header
|
||||
format first.
|
||||
|
||||
* **Packed (3-Byte):** `[B0][B1][B2] [B3][B4][B5] ...` (Keine Lücken)
|
||||
* **Padded (4-Byte):** `[00][B0][B1][B2] [00][B3][B4][B5] ...` (Oft bei 32-Bit EPROM-Builds)
|
||||
### Quick Format Check
|
||||
|
||||
### Woran erkenne ich das Format?
|
||||
Suche nach dem **NOP** Opcode.
|
||||
* Ein klassischer NOP (Type 30) ist `0x000000`.
|
||||
* Ein Multifunktions-NOP (Type 1) ist `0xC00000`.
|
||||
Look at the first few bytes. If you see `00 00 00` at offset 0
|
||||
(a NOP), you likely have packed 3-byte format. If you see
|
||||
`00 00 00 00` followed by meaningful data at offset 4, it is
|
||||
probably 4-byte padded.
|
||||
|
||||
Wenn du alle 3 oder 4 Bytes lange Ketten von `00` findest, hast du wahrscheinlich das Alignment gefunden.
|
||||
## 2. Find the Entry Point
|
||||
|
||||
## 2. Phase: Den Einstiegspunkt finden (Vector Table)
|
||||
The reset vector is at PM address 0x0000. Typical patterns:
|
||||
|
||||
Der ADSP-2191 startet nach einem Reset bei Adresse `0x0000` im Programm-Memory (PM).
|
||||
Die ersten Adressen sind die Interrupt-Vektoren (jeweils 8 oder 16 Words Platz).
|
||||
0x0000: JUMP main (Type 10a, opcode starts with 0x1C)
|
||||
0x0000: NOP (entry at next instruction)
|
||||
|
||||
**Typisches Muster am Anfang:**
|
||||
```text
|
||||
0x0000: JUMP 0x0100 <-- Reset Vector
|
||||
0x0008: RTI <-- Power Down Vector
|
||||
0x0010: RTI <-- Kernel Interrupt
|
||||
...
|
||||
```
|
||||
The interrupt vector table occupies the first ~128 PM words,
|
||||
with 4-word spacing per vector. Most vectors contain RTI (return
|
||||
from interrupt) or JUMP to a handler.
|
||||
|
||||
Wenn dein Disassembler am Anfang nur `UNKNOWN` auswirkt, ist meistens das Alignment (3 vs 4 Byte) oder die Endianness falsch. ADSP-219x ist standardmäßig **Big-Endian**.
|
||||
## 3. Identify Code vs Data Regions
|
||||
|
||||
## 3. Phase: Den Code "lesen" (Pattern Recognition)
|
||||
**Code regions** produce coherent disassembly: register loads,
|
||||
compute instructions, jumps, and loops in logical sequence.
|
||||
|
||||
### A. Register Initialisierung (Type 6/7)
|
||||
DSP-Code beginnt fast immer damit, die DAGs (Data Address Generators) zu füttern.
|
||||
* `I0 = 0x2000` (Index-Register auf Start eines Buffers)
|
||||
* `M0 = 1` (Modifier auf Schrittweite 1)
|
||||
* `L0 = 0` (Circular Buffering aus)
|
||||
**Data regions** (coefficient tables, lookup tables) produce
|
||||
nonsensical disassembly: random-looking mnemonics, impossible
|
||||
register combinations, jumps to invalid addresses. Mark these
|
||||
as data in r2:
|
||||
|
||||
**In deinem Disassembler:**
|
||||
`AX0 = 0x1234` entspricht Opcode `0x4x....`. Das ist ein Type 6 Befehl.
|
||||
Cd 300 @ 0x1000 # Mark 300 bytes as data at offset 0x1000
|
||||
|
||||
### B. Die "Magic" des DSP: Multifunktions-Opcodes (Type 1)
|
||||
Das ist der Kern des DSPs. In einem einzigen Takt passiert:
|
||||
1. Eine Berechnung (ALU/MAC)
|
||||
2. Ein Read aus dem DM (Data Memory)
|
||||
3. Ein Read aus dem PM (Program Memory)
|
||||
## 4. Recognize DSP Patterns
|
||||
|
||||
**Beispiel:**
|
||||
`MR + X * Y (SS), AX0 = DM(I0 += M0), AY0 = PM(I4 += M4)`
|
||||
### FIR Filter
|
||||
|
||||
* **Identifikation:** Opcodes die mit `0xC` bis `0xF` beginnen.
|
||||
* **Schlussfolgerung:** Das ist oft eine innere Schleife (z.B. ein FIR-Filter). Wenn du viele dieser Befehle hintereinander siehst, hast du den Signalverarbeitungs-Kernel gefunden.
|
||||
CNTR = N;
|
||||
DO loop_end UNTIL CE;
|
||||
MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4);
|
||||
loop_end: ...
|
||||
|
||||
## 4. Phase: Daten vs. Code im PM
|
||||
Look for: Type 11 (DO UNTIL CE) followed by Type 1 multifunction
|
||||
instructions with MAC operations.
|
||||
|
||||
Der ADSP-2191 hat eine Harvard-Architektur. Das Programm-Memory (PM) kann aber auch Daten enthalten (24-Bit breit).
|
||||
Dank des **Visible Instruction State (VIS)** kann der 2191 hocheffizient 24-Bit Koeffizienten aus dem PM laden, während er rechnet.
|
||||
### IIR Filter (Biquad)
|
||||
|
||||
**Woran erkenne ich Daten im PM?**
|
||||
Wenn der Disassembler sinnlose Befehle ausgibt (z.B. hunderte `UNKNOWN` oder seltsame Sprünge in ungültige Bereiche), handelt es sich wahrscheinlich um eine **Koeffizienten-Tabelle** (z.B. Sinus-Werte oder Filter-Taps).
|
||||
Nested loops: outer loop over samples, inner loop over biquad
|
||||
sections. Contains ASHIFT for scaling between stages.
|
||||
|
||||
## 5. Phase: Analyse-Workflow mit radare2
|
||||
### Initialization Sequence
|
||||
|
||||
Wenn du den Dump in `iaito` oder `r2` lädst:
|
||||
Sequences of Type 6/7 instructions loading I/M/L registers.
|
||||
This sets up circular buffers for the signal processing kernel.
|
||||
|
||||
1. **Bit-Breite:** Setze `e asm.bits = 24`.
|
||||
2. **Suchen:** Suche nach `000000` (NOPs), um Funktionsgrenzen zu finden.
|
||||
3. **Cross-References:** Suche nach `CALL` Befehlen (`0x11....`), um die Programmstruktur zu verstehen.
|
||||
4. **I/O Analyse:** Der ADSP-2191 steuert Peripherie über I/O-Register. Suche nach `IO(addr) = Dreg`. Adressen wie `0x00` bis `0x3F` sind oft System-Controller oder Sport (Serial Port) Konfiguration.
|
||||
## 5. Useful r2 Commands
|
||||
|
||||
---
|
||||
**Tipp:** Nutze das beiliegende Skript `analysis/analyze_rom.py`, um eine erste Statistik über die verwendeten Instruktionstypen zu erhalten. Das zeigt dir sofort, ob der Dump primär aus Logik (`JUMP`, `IF`) oder aus Mathe (`Compute`, `Multifunction`) besteht.
|
||||
e asm.arch = adsp219x
|
||||
e asm.bits = 24
|
||||
pd 200 # Disassemble 200 instructions
|
||||
pD 600 # Disassemble 600 bytes (= 200 instructions)
|
||||
/x 1c00 # Find unconditional JUMPs
|
||||
/x 16 # Find DO UNTIL loops
|
||||
/x 0a # Find RTS/RTI instructions
|
||||
V # Enter visual mode
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
# ADSP-219x Radare2 Plugin
|
||||
|
||||
This plugin adds support for the ADSP-219x architecture to radare2 and iaito.
|
||||
Native architecture plugin for disassembling ADSP-219x (including
|
||||
ADSP-2191) 24-bit instructions in radare2 and iaito.
|
||||
|
||||
## Prerequisites
|
||||
## Build & Install
|
||||
|
||||
- GCC
|
||||
- Radare2 5.8.0+ (uses the `RArchPlugin` API)
|
||||
- Python 3 (for test ROM generation)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cd r2plugin
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
To uninstall:
|
||||
```bash
|
||||
make uninstall
|
||||
```
|
||||
Requires: GCC, radare2 >= 5.8.0 (RArchPlugin API).
|
||||
|
||||
## Usage
|
||||
|
||||
Load a binary file specifying the architecture and bits:
|
||||
|
||||
```bash
|
||||
r2 -a adsp219x -b 24 firmware.bin
|
||||
```
|
||||
|
||||
## Maintenance & Testing
|
||||
## Validation
|
||||
|
||||
A test ROM generator is provided in the `testrom/` directory to verify instruction decoding accuracy.
|
||||
Compare against the assembler-verified test ROM:
|
||||
|
||||
```bash
|
||||
cd testrom
|
||||
python3 gen_isa_test.py
|
||||
r2 -a adsp219x -b 24 -q -c "pd 42" test_roms/isa_test.bin
|
||||
```
|
||||
r2 -a adsp219x -b 24 -q -c "pd 48" ../examples/isa_test.bin
|
||||
|
||||
The test ROM (`isa_test.dsp`) was assembled with open21xx and
|
||||
contains known opcodes for all major instruction types.
|
||||
|
||||
## Coding Standards
|
||||
|
||||
This project follows the **GNU Coding Standards** for C code and avoids hardcoded absolute paths in scripts.
|
||||
This plugin follows the GNU Coding Standards for C source code.
|
||||
No hardcoded absolute paths in any scripts.
|
||||
|
||||
@@ -1,35 +1,102 @@
|
||||
"""Generate an ISA test ROM from assembler-verified opcodes.
|
||||
|
||||
The opcodes below were produced by assembling isa_test.dsp with
|
||||
the open21xx toolchain (as219x + ld21) and extracting the int_pm
|
||||
section from the resulting ELF. They serve as ground truth for
|
||||
validating the radare2 disassembler plugin.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def write_ins(f, ins_list):
|
||||
"""Write 24-bit instructions to the file in Big Endian format."""
|
||||
for ins in ins_list:
|
||||
f.write(bytes([(ins >> 16) & 0xFF, (ins >> 8) & 0xFF, ins & 0xFF]))
|
||||
|
||||
def gen_isa_test_rom():
|
||||
"""Generates a test ROM with various ADSP-219x instruction types."""
|
||||
# Use path relative to the script's location
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
rom_p = os.path.join(base_dir, "test_roms", "isa_test.bin")
|
||||
|
||||
os.makedirs(os.path.dirname(rom_p), exist_ok=True)
|
||||
|
||||
instructions = [
|
||||
0x000000, 0x000215, 0xC00000, 0xCC9A55, 0x801234, 0x815678, 0x606800,
|
||||
0x412340, 0x4ABCDD, 0x511110, 0x344440, 0x226810, 0x206800, 0x18100F,
|
||||
0x1A2000, (0x1C1234 << 4), 0x16610F, 0x124000, 0x128010, 0x0F0050,
|
||||
0x0EE000, 0x0D4400, 0x0C0001, 0x0C1000, 0x0B000F, 0x0A000F, 0x0A800F,
|
||||
0x018000, 0x010100, 0x07E340, 0x120000, 0x038000, 0x03F000, 0x030000,
|
||||
0x040010, 0x081050, 0x150040, 0x06D100, 0x06C200, 0x06000F, 0x123456, 0x077001
|
||||
# Verified opcodes from open21xx assembler output.
|
||||
# Source: examples/isa_test.dsp -> as219x -> ld21 -> dd
|
||||
VERIFIED_OPCODES = [
|
||||
# Type 30: NOP
|
||||
(0x000000, "NOP"),
|
||||
# Type 6: Dreg = Imm16
|
||||
(0x412340, "AX0 = 0x1234"),
|
||||
(0x456781, "AX1 = 0x5678"),
|
||||
(0x4AAAA4, "AY0 = 0xAAAA"),
|
||||
(0x440002, "MX0 = 0x4000"),
|
||||
(0x420006, "MY0 = 0x2000"),
|
||||
# Type 7: Reg1 = Imm16
|
||||
(0x501000, "I0 = 0x0100"),
|
||||
(0x502001, "I1 = 0x0200"),
|
||||
(0x303000, "I4 = 0x0300"),
|
||||
(0x500014, "M0 = 1"),
|
||||
(0x500015, "M1 = 1"),
|
||||
(0x5FFFF7, "M3 = -1"),
|
||||
(0x300014, "M4 = 1"),
|
||||
(0x500008, "L0 = 0"),
|
||||
(0x300008, "L4 = 0"),
|
||||
(0x3000AE, "CNTR = 10"),
|
||||
# Type 17: Reg = Reg
|
||||
(0x0D00A0, "AR = AX0"),
|
||||
(0x0D00B1, "SI = AX1"),
|
||||
(0x0D00E2, "MR0 = MX0"),
|
||||
# Type 9: Compute (ALU)
|
||||
(0x22600F, "AR = AX0 + AY0"),
|
||||
(0x22E00F, "AR = AX0 - AY0"),
|
||||
(0x23800F, "AR = AX0 AND AY0"),
|
||||
(0x23A00F, "AR = AX0 OR AY0"),
|
||||
(0x23C00F, "AR = AX0 XOR AY0"),
|
||||
(0x22780F, "AR = PASS AX0"),
|
||||
(0x23380F, "AR = -AX0"),
|
||||
(0x23E00F, "AR = ABS AX0"),
|
||||
# Type 9: Compute (MAC)
|
||||
(0x20800F, "MR = MX0 * MY0 (SS)"),
|
||||
# Type 10a: Jump (unconditional, 16-bit addr)
|
||||
(0x1C0010, "JUMP _skip1"),
|
||||
(0x000000, "NOP"),
|
||||
# Type 10: Jump (conditional, 13-bit addr)
|
||||
(0x180010, "IF EQ JUMP _skip2"),
|
||||
(0x000000, "NOP"),
|
||||
# Type 10a: Call + Jump
|
||||
(0x1C0024, "CALL _sub1"),
|
||||
(0x1C0030, "JUMP _after_sub"),
|
||||
(0x000000, "NOP (in sub)"),
|
||||
# Type 20: RTS
|
||||
(0x0A00F0, "RTS"),
|
||||
(0x000000, "NOP (after sub)"),
|
||||
# Type 7: CNTR for loop
|
||||
(0x30004E, "CNTR = 4"),
|
||||
# Type 11: DO UNTIL
|
||||
(0x16002E, "DO _loop_end UNTIL CE"),
|
||||
(0x000000, "NOP (loop body)"),
|
||||
(0x000000, "NOP (loop end)"),
|
||||
# Type 15: Shift Immediate
|
||||
(0x0F0B04, "SR = LSHIFT SI BY 4 (HI)"),
|
||||
(0x0F4BFE, "SR = ASHIFT SI BY -2 (HI)"),
|
||||
# Type 18: Mode Control
|
||||
(0x0C0800, "DIS AR_SAT"),
|
||||
(0x0C0C00, "ENA AR_SAT"),
|
||||
# Type 25: Saturate MR
|
||||
(0x030000, "SAT MR"),
|
||||
# Type 33: Reg3 = short immediate
|
||||
(0x100005, "SE = 0"),
|
||||
# Type 10a: Infinite loop
|
||||
(0x1C0000, "JUMP _halt"),
|
||||
]
|
||||
|
||||
try:
|
||||
with open(rom_p, "wb") as f:
|
||||
write_ins(f, instructions)
|
||||
print(f"Generated ISA test ROM with {len(instructions)} instructions at: {rom_p}")
|
||||
except OSError as e:
|
||||
print(f"Error writing ROM file: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
rom_path = os.path.join(base_dir, "test_roms", "isa_test.bin")
|
||||
os.makedirs(os.path.dirname(rom_path), exist_ok=True)
|
||||
|
||||
with open(rom_path, "wb") as f:
|
||||
for opcode, _label in VERIFIED_OPCODES:
|
||||
f.write(bytes([
|
||||
(opcode >> 16) & 0xFF,
|
||||
(opcode >> 8) & 0xFF,
|
||||
opcode & 0xFF,
|
||||
]))
|
||||
|
||||
print("Generated ISA test ROM with %d instructions at: %s"
|
||||
% (len(VERIFIED_OPCODES), rom_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gen_isa_test_rom()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user