Compare commits

..

9 Commits

Author SHA1 Message Date
Dr. Christian Giessen
22da85de66 Add DM cross-reference tool
tools/dm_xref.py extracts all DM address references from PM code
and shows the actual DM contents at those addresses:
- I-register loads with matching L-register buffer lengths
- Direct DM(addr) reads and writes
- CNTR loop counters
- Hex, decimal, and Q15 format for DM content
- Automatic detection of uninitialized (zero) regions
- Configurable context window (--context N words)
2026-04-27 13:52:41 +00:00
Dr. Christian Giessen
e790736868 Add firmware overview tool for quick structural analysis
tools/firmware_overview.py analyzes PM binaries and reports:
- Memory map (empty / DSP kernel / control flow / code+data)
- Entry point detection
- All DO UNTIL loops with MAC operation count
- All immediate constants with register names and addresses
- I/O operations with ADSP-2191 peripheral register names
- Optional DM cross-reference (I-reg targets)
- Summary statistics

Includes ADSP-2191 I/O register name table for SPORT, SPI,
UART, Timer, GPIO, DMA, System Controller, and Interrupts.
2026-04-27 13:49:40 +00:00
Dr. Christian Giessen
ddccae54a0 Add DM analysis tool
tools/analyze_dm.py scans 16-bit DM dumps for:
- Null regions (BSS, uninitialized memory)
- Q15 coefficient tables with range and average
- Periodic waveforms (sine/cosine lookup tables)
- ASCII strings
- Unclassified data regions with hex preview

Supports big-endian (default) and little-endian byte order.
Outputs DM word addresses for cross-reference with PM code.
2026-04-27 12:13:42 +00:00
Dr. Christian Giessen
70561276fe Rewrite ROM analysis walkthrough for real firmware dumps
- Step-by-step format detection using od
- Packed vs padded vs boot stream identification
- File size divisibility check
- Python one-liner to strip 4-byte padding
- PM vs DM file explanation
- DM file inspection with od
- r2 setup including parser warning suppression
- Full analysis workflow (aaa, afl, afb, VV)
- Pattern recognition for FIR, IIR, init, I/O
- Complete r2 command reference
2026-04-27 12:09:49 +00:00
Dr. Christian Giessen
91172e86d8 Fix analysis: remove early return, add eob for branches
The decoder had an early return when mask lacked DISASM, causing
op->type/jump/eob to never be set during analysis passes.
Removed the early return so all decode paths execute regardless
of requested mask.

Added op->eob = true for:
- Unconditional JUMP (Type 10a, 36)
- Unconditional indirect JUMP (Type 19)
- RTS/RTI (Type 20)

Added op->fail for:
- Conditional JUMP (Type 10)
- Conditional indirect jump (Type 19)

This fixes function analysis (af), basic block detection (afb),
and control flow graphing (agf/VV). Tested: isa_test.bin now
shows 5 basic blocks with correct control flow edges.
2026-04-27 12:01:49 +00:00
Dr. Christian Giessen
276817fc48 Regenerate isa_test.bin from assembler (fixes CNTR opcode)
The old isa_test.bin was generated by gen_isa_test.py which had
a wrong opcode for CNTR (0x5000AF = REG1[15] = STACKA, should
be 0x3000AE = REG2[14] = CNTR).  Regenerated from isa_test.dsp
using open21xx assembler.

Also: to suppress the harmless 'asm.parser not found' warning,
use: r2 -e asm.parser=null -a adsp219x -b 24 firmware.bin
Or add 'e asm.parser=null' to ~/.radare2rc.
2026-04-27 11:53:22 +00:00
Dr. Christian Giessen
2a8a952f22 Add analysis op->type annotations (rebased)
Remove parse_adsp219x.c - r2 6.x has no external parse plugin API.
The 'asm.parser not found' warning is cosmetic and can be suppressed
with 'e asm.parser=null' in ~/.radare2rc.

Update Makefile to match upstream changes.
2026-04-27 11:49:49 +00:00
Dr. Christian Giessen
26f01a5bdb Add analysis support: op->type for all instruction types
Every decode path now sets op->type for r2 analysis:
- R_ANAL_OP_TYPE_NOP/TRAP: NOP, IDLE
- R_ANAL_OP_TYPE_MUL: MAC operations (Type 1, 8, 9)
- R_ANAL_OP_TYPE_ADD: ALU operations (Type 1, 8, 9), MODIFY
- R_ANAL_OP_TYPE_MOV: register loads (Type 6, 7, 17, 18, 25, 33)
- R_ANAL_OP_TYPE_LOAD/STORE: memory access (Type 3, 4, 12, 29, 32)
- R_ANAL_OP_TYPE_JMP/CJMP/CALL: jumps and calls (Type 10, 10a, 19, 36)
- R_ANAL_OP_TYPE_RET: RTS/RTI (Type 20)
- R_ANAL_OP_TYPE_REP: DO UNTIL loops (Type 11)
- R_ANAL_OP_TYPE_SHR: shift operations (Type 14, 15, 16)
- R_ANAL_OP_TYPE_DIV: DIVQ/DIVS (Type 23, 24)
- R_ANAL_OP_TYPE_PUSH: Push/Pop/Cache (Type 26)
- R_ANAL_OP_TYPE_IO: IO/System register (Type 34, 35)
- R_ANAL_OP_TYPE_SWI: SETINT/CLRINT (Type 37)

op->jump set for all branch types.
op->fail set for conditional jumps (next instruction).

Enables: af (function analysis), pdf (function disassembly),
agf (control flow graph), VV (visual graph mode).
Tested: FIR and IIR functions recognized correctly.
2026-04-27 09:07:31 +00:00
82958cfe74 Clarify coverage wording and add ROM analysis guide 2026-04-22 22:59:44 +02:00
11 changed files with 1677 additions and 96 deletions

View File

@@ -24,8 +24,8 @@ engineering ADSP-2191 firmware.
## Plugin Status ## Plugin Status
The decoder now covers most ADSP-219x instruction types and has The decoder implements all documented ADSP-219x instruction types and
assembler verification for 34 of 37 documented opcode families. has assembler verification for 34 of 37 documented opcode families.
Assembler-verified coverage includes: Assembler-verified coverage includes:
@@ -49,6 +49,12 @@ an open-source assembler/linker for ADSP-218x/219x. Build
instructions are in the open21xx README. The resulting ELF is instructions are in the open21xx README. The resulting ELF is
converted to raw binary with `dd` (extract the `int_pm` section). converted to raw binary with `dd` (extract the `int_pm` section).
## Analysis Guides
- `docs/GETTING_STARTED.md` - setup, loading ROMs, and basic radare2 use
- `docs/LARGE_ROM_ANALYSIS_WORKFLOW.md` - step-by-step workflow for
analyzing large raw ADSP-219x ROM dumps
## License ## License
Plugin code: LGPL-3.0-only. Example code from Analog Devices Plugin code: LGPL-3.0-only. Example code from Analog Devices

View File

@@ -57,3 +57,11 @@ instruction set reference chapters (PDF and text extracts):
- `9x_flowops.*` - Flow control (jumps, loops, returns) - `9x_flowops.*` - Flow control (jumps, loops, returns)
- `9x_moveops.*` - Data move operations - `9x_moveops.*` - Data move operations
- `9x_multiops.*` - Multifunction operations - `9x_multiops.*` - Multifunction operations
## Large ROM Workflow
For a practical step-by-step workflow for analyzing a large raw
ADSP-219x ROM image, including how to separate likely code from likely
data and when to use graph analysis, see:
- `LARGE_ROM_ANALYSIS_WORKFLOW.md`

View File

@@ -0,0 +1,524 @@
# Large ADSP-219x ROM Analysis Workflow
This guide describes a practical workflow for reverse engineering a
large raw ADSP-219x ROM image in radare2 using the `adsp219x`
architecture plugin from this repository.
It is written for the common case where you have a large blob, for
example a 1 MB ROM dump, and you do not yet know:
- where code starts and where data starts
- how many independent code regions exist
- where the main loops and dispatchers are
- which PM words are instructions and which are tables
The workflow below is intentionally conservative. For raw DSP ROMs,
manual validation beats aggressive auto-analysis.
## Assumptions
- The ROM is for an ADSP-219x family DSP.
- Instructions are 24-bit words.
- The dump is raw program memory, not a richly annotated executable.
- You already have a working radare2 installation and the
`adsp219x` plugin is installed or loadable with `-L`.
## Load The ROM
For an installed plugin:
```bash
r2 -a adsp219x -b 24 firmware.bin
```
For a local plugin build that is not installed:
```bash
r2 -a adsp219x -L ./r2plugin/asm_adsp219x.so -b 24 firmware.bin
```
Why the flags matter:
- `-a adsp219x`: forces the custom architecture plugin
- `-b 24`: tells radare2 to treat the code as 24-bit ADSP-219x words
## ROM Format Sanity Check
Before analyzing flow, check whether the byte layout looks correct.
ADSP-219x code is usually stored either as:
- packed 24-bit words: `aa bb cc dd ee ff ...`
- padded 32-bit words with a leading zero byte
If your disassembly looks completely implausible, confirm the ROM
format first.
Helpful first commands:
```text
px 64
s 0
pd 16
```
If the first few decoded instructions look impossible but the hex dump
shows a repeating `00 xx xx xx` structure, you may be looking at a
32-bit padded dump and need to strip padding before analysis.
## High-Level Strategy
For a large ROM, do not start with `aaa`.
A better sequence is:
1. validate the entry region manually
2. mark only plausible functions
3. follow explicit control flow
4. distinguish code from tables
5. expand analysis gradually
6. use graph views only after local validation
`aaa` on a large raw DSP ROM often creates a false sense of structure
by treating valid-looking data words as code.
## Workflow Diagram
```mermaid
flowchart TD
A[Load ROM in r2] --> B[Check raw bytes and first instructions]
B --> C{Plausible code at reset area?}
C -- no --> D[Verify ROM packing, offset, endianness, dump source]
D --> B
C -- yes --> E[Create first function manually]
E --> F[Inspect linear disassembly and graph]
F --> G{Looks like real control flow?}
G -- no --> H[Do not mark as function, treat as possible data]
G -- yes --> I[Follow jumps, calls, DO UNTIL loops]
I --> J[Name regions and comment findings]
J --> K[Search for more entry points and dispatchers]
K --> L[Only then expand with broader analysis]
L --> M[Separate code islands from PM data tables]
```
## Step 1: Inspect The Reset Region Manually
Start at address zero unless you have evidence of a different boot
mapping.
```text
s 0
pd 20
pd 64
```
Look for signs of real code:
- immediate register loads
- `JUMP` or `CALL`
- `DO ... UNTIL`
- initialization of `I`, `M`, `L`, `CNTR`, status registers
- branches to other regions
Red flags that suggest you are not in code:
- long stretches of decodeable but nonsensical instructions
- no branches, no calls, no returns
- bizarre register moves with no purpose
- disassembly that looks random but hex bytes look regular
## Step 2: Mark The First Function Manually
Once the entry region looks plausible:
```text
s 0
af
pdf
agf
```
Meaning:
- `af`: define a function at the current address
- `pdf`: print the current function linearly
- `agf`: show the control-flow graph of the current function
This is a better first move than `aaa`, because it forces you to
validate one region before scaling out.
## Step 3: Use Graphs Locally, Not Globally
For a large ROM, graphs are most useful once you already believe the
current region is code.
Useful commands:
```text
agf
VV
afb
```
- `agf`: static graph of current function
- `VV`: visual graph mode
- `afb`: list/show basic blocks
If `agf` produces a clean graph with obvious branches and loop edges,
the region is probably code.
If `agf` is trivial, chaotic, or makes no semantic sense, you may be
looking at data or a bad function boundary.
## Step 4: Expand By Following Explicit Flow
After validating the first function, expand manually through explicit
targets:
```text
pdf
axt
afl
```
Then jump to interesting destinations:
```text
s <target>
af
pdf
agf
```
Prioritize:
- call targets
- jump targets
- loop bodies
- indirect branch setup code
For ADSP-219x specifically, also watch for:
- `DO ... UNTIL`
- tight MAC loops
- register setup for DAGs
- memory access kernels
## Step 5: Search For ADSP-219x-Specific Patterns
Search directly for common control-flow patterns:
```text
/a JUMP
/a CALL
/a DO
/a RTS
/a RTI
```
Also inspect likely DSP kernel patterns:
```text
/a MR
/a MX0
/a MY0
/a IO(
```
These searches are not perfect, but they help locate:
- processing loops
- dispatch logic
- hardware initialization
- coefficient loads
## Step 6: Distinguish Code From Data
In a large ADSP-219x ROM, many PM words are data, not instructions.
You must actively separate them.
### Heuristic: likely code
A region is likely code if it has:
- incoming xrefs from jumps or calls
- meaningful block structure
- function-like boundaries
- setup followed by control flow
- loops, branches, and exits
Check with:
```text
axt @ <addr>
pdf
agf
```
### Heuristic: likely data
A region is likely data if it has:
- regular numeric patterns in hex
- no meaningful control flow
- no incoming code xrefs
- no returns or loop structure
- many decodeable instructions that make no programmatic sense
Check with both disassembly and raw bytes:
```text
pd 32
px 64
```
If the hex dump looks more plausible than the disassembly, it is often
a table.
### ADSP-219x-specific data patterns
Common PM data in DSP firmware:
- FIR coefficients
- IIR coefficients
- FFT sine/cosine tables
- lookup tables
- packed constants
- boot configuration words
These often decode into valid-looking instructions by accident.
## Step 7: Delay Global Auto-Analysis
Only after you have mapped a few real code islands should you broaden
analysis:
```text
aa
afl
```
Prefer `aa` first.
Use `aaa` only when:
- the ROM format is confirmed
- the entry region is valid
- you already understand where major code regions are
- the plugin behaves consistently on this image
Why this matters:
- `aa` is less aggressive
- `aaa` can create junk functions in large raw ROMs
- false positives are expensive to clean up mentally
## Step 8: Name What You Understand
As soon as a region is understood, name and annotate it.
Useful commands:
```text
afn entry_init
afn main_loop
afn io_dispatch
CCu probable coefficient table
CCu hardware init and watchdog setup
```
Naming reduces rework and makes graph navigation much easier.
## Step 9: Build A Region Map
For a 1 MB ROM, keep a rough map as you go:
- boot/reset code
- hardware init
- interrupt vector area
- main loop
- DSP kernels
- dispatch tables
- PM data tables
- obvious unused or padding regions
This can live in:
- r2 comments
- a notebook
- a separate markdown file
The important thing is to stop treating the ROM as one continuous
thing. Large firmware becomes manageable once you divide it into
regions.
## Step 10: Revisit Ambiguous Areas Later
Do not force a conclusion too early.
When a region is ambiguous:
- leave it unnamed or mark it as tentative
- inspect surrounding xrefs first
- compare with neighboring validated code
- revisit it after understanding more of the firmware
Good reverse engineering on large DSP ROMs is iterative.
## Recommended First 15 Minutes
If you want a concrete first-pass routine for a 1 MB ROM:
### 1. Open the ROM
```bash
r2 -a adsp219x -b 24 firmware.bin
```
### 2. Check the first bytes and first instructions
```text
s 0
px 64
pd 32
```
### 3. If plausible, define the first function
```text
af
pdf
agf
```
### 4. Enter visual graph mode
```text
VV
```
### 5. Follow obvious branch targets manually
```text
s <target>
af
pdf
agf
```
### 6. Search for loops and calls
```text
/a DO
/a CALL
/a JUMP
```
### 7. Compare suspicious regions with hex
```text
pd 32
px 64
```
### 8. Only then widen analysis
```text
aa
afl
```
## When To Suspect A Bad Decode
Pause and reassess if you see:
- no meaningful flow anywhere near the entry region
- every region looks equally nonsensical
- branch targets never lead to reasonable code
- graph mode shows nonsense everywhere
- the ROM appears to decode but nothing behaves like firmware
Then check:
- ROM packing
- dump alignment
- whether the image is compressed or encrypted
- whether the base offset is wrong
- whether the file contains headers before the actual code
## Practical Command Cheat Sheet
Open:
```bash
r2 -a adsp219x -b 24 firmware.bin
```
Start region:
```text
s 0
pd 20
pd 64
px 64
```
Define and inspect function:
```text
af
pdf
agf
```
Visual graph:
```text
VV
```
Search:
```text
/a JUMP
/a CALL
/a DO
/a RTS
```
Cross-references:
```text
axt
axt @ <addr>
```
Broader analysis:
```text
aa
afl
```
Naming:
```text
afn main_loop
CCu probable PM coefficient table
```
## Summary
For a large ADSP-219x ROM:
- do not trust auto-analysis first
- validate the entry region manually
- graph only locally at first
- follow explicit flow edges
- use both disassembly and hex dumps
- treat PM as mixed code and data
- expand analysis gradually
The core rule is simple:
**Local confidence first, global analysis later.**

View File

@@ -1,48 +1,134 @@
# ROM Analysis Walkthrough # ROM Analysis Walkthrough
## 1. Determine the ROM Format ## 1. Identify Your Files
ADSP-2191 instructions are 24 bits (3 bytes). A raw dump can be: A firmware dump from an ADSP-2191 typically comes as separate
files for each memory space:
- **Packed (3 bytes/word)**: Most common for SPI flash dumps. - **PM files** (Program Memory): 24-bit words — code and PM data
Load directly: `r2 -a adsp219x -b 24 dump.bin` - **DM files** (Data Memory): 16-bit words — variables, buffers
- **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.
### Quick Format Check Only PM files contain executable code. DM files are pure data
and cannot be disassembled as instructions.
Look at the first few bytes. If you see `00 00 00` at offset 0 If you have multiple sets (e.g. three directories), these may be:
(a NOP), you likely have packed 3-byte format. If you see - Different firmware versions
`00 00 00 00` followed by meaningful data at offset 4, it is - Different memory banks (Block 0, 1, 2)
probably 4-byte padded. - Boot loader vs application code
## 2. Find the Entry Point ## 2. Determine the PM Format
ADSP-2191 instructions are 24 bits (3 bytes). A raw dump can be
packed (3 bytes/word) or padded (4 bytes/word, 32-bit aligned).
### Check with od
Dump the first 24 bytes and look at the pattern:
od -A x -t x1 -N 24 firmware_pm.bin
**Packed (3 bytes/word)** — most common:
000000 1c 00 30 00 00 00 50 10 00 41 23 40 ...
Every group of 3 bytes is one instruction. The first byte of
real code is typically 0x1C (JUMP), 0x00 (NOP), or 0x50/0x30
(register load).
**Padded (4 bytes/word)** — some EPROM/JTAG tools:
000000 00 1c 00 30 00 00 00 00 00 50 10 00 ...
There is a leading 0x00 before each 3-byte instruction.
To strip padding: `dd if=input.bin of=output.bin bs=3 count=N`
after removing every 4th byte.
**Boot stream** — ADSP boot loader format:
000000 xx xx xx xx yy yy ...
Has block headers (target address, byte count, flags) before
the actual code data. Look for a repeating structure of
header + data blocks. The data payload inside is packed 24-bit.
### Check file size
ls -la firmware_pm.bin
- Packed: file size is divisible by 3
- Padded: file size is divisible by 4
- Boot stream: neither cleanly divisible
python3 -c "import os; s=os.path.getsize('firmware_pm.bin'); print(f'Size: {s} bytes, /3={s/3:.1f}, /4={s/4:.1f}')"
### Verify with a test disassembly
r2 -a adsp219x -b 24 -e asm.parser=null -q -c "pd 20" firmware_pm.bin
If you see coherent instructions (register loads, JUMPs, NOPs),
the format is correct. If the output is mostly `unk 0x...` or
nonsensical, try the other format or adjust the start offset.
## 3. Load in radare2
### Packed 3-byte format (direct)
r2 -a adsp219x -b 24 -e asm.parser=null firmware_pm.bin
### Padded 4-byte format
Strip padding first, then load:
python3 -c "
d = open('firmware_pm.bin','rb').read()
o = b''.join(d[i+1:i+4] for i in range(0, len(d), 4))
open('firmware_pm_packed.bin','wb').write(o)
"
r2 -a adsp219x -b 24 -e asm.parser=null firmware_pm_packed.bin
### Suppress the parser warning
Add to ~/.radare2rc (one-time):
echo "e asm.parser=null" >> ~/.radare2rc
## 4. Initial Analysis
[0x00000000]> aaa # Full auto-analysis
[0x00000000]> afl # List detected functions
[0x00000000]> afb @ main # Show basic blocks
[0x00000000]> VV # Visual control flow graph
## 5. Find the Entry Point
The reset vector is at PM address 0x0000. Typical patterns: The reset vector is at PM address 0x0000. Typical patterns:
0x0000: JUMP main (Type 10a, opcode starts with 0x1C) 0x0000: JUMP main (Type 10a, opcode byte 0x1C)
0x0000: NOP (entry at next instruction) 0x0000: NOP (entry at next instruction)
The interrupt vector table occupies the first ~128 PM words, The interrupt vector table occupies the first ~128 PM words
with 4-word spacing per vector. Most vectors contain RTI (return (0x000-0x17F), with 4-word spacing per vector. Most vectors
from interrupt) or JUMP to a handler. contain RTI or JUMP to a handler.
## 3. Identify Code vs Data Regions ## 6. Identify Code vs Data Regions
**Code regions** produce coherent disassembly: register loads, **Code regions** produce coherent disassembly: register loads,
compute instructions, jumps, and loops in logical sequence. compute instructions, jumps, and loops in logical sequence.
**Data regions** (coefficient tables, lookup tables) produce **Data regions** (coefficient tables, lookup tables) produce
nonsensical disassembly: random-looking mnemonics, impossible nonsensical output: random-looking mnemonics, jumps to invalid
register combinations, jumps to invalid addresses. Mark these addresses, many `unk` opcodes. Mark as data in r2:
as data in r2:
Cd 300 @ 0x1000 # Mark 300 bytes as data at offset 0x1000 Cd 300 @ 0x1000 # 300 bytes as data at offset 0x1000
## 4. Recognize DSP Patterns **Null regions** (0x000000 repeated) are uninitialized memory:
# Find next non-null byte
/x 01
# Skip to it
s hit0_0
## 7. Recognize DSP Patterns
### FIR Filter ### FIR Filter
@@ -51,26 +137,47 @@ as data in r2:
MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4); MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4);
loop_end: ... loop_end: ...
Look for: Type 11 (DO UNTIL CE) followed by Type 1 multifunction Look for: DO UNTIL CE + multifunction MAC instructions.
instructions with MAC operations.
### IIR Filter (Biquad) ### IIR Filter (Biquad)
Nested loops: outer loop over samples, inner loop over biquad Nested loops: outer over samples, inner over biquad sections.
sections. Contains ASHIFT for scaling between stages. Contains ASHIFT for inter-stage scaling.
### Initialization Sequence ### Initialization Sequence
Sequences of Type 6/7 instructions loading I/M/L registers. Sequences of Type 6/7 loads (I/M/L register setup).
This sets up circular buffers for the signal processing kernel. Circular buffer initialization before entering a processing loop.
## 5. Useful r2 Commands ### I/O Configuration
IO(addr) = Dreg / Dreg = IO(addr) instructions configure
peripherals: Serial Ports, Timers, DMA, etc.
## 8. DM File Analysis
DM files contain 16-bit data words. These are not code.
You can inspect them for patterns:
od -A x -t x2 -N 200 firmware_dm.bin # 16-bit hex words
od -A x -t d2 -N 200 firmware_dm.bin # Signed 16-bit decimal
Common contents:
- Filter coefficients (Q15 fixed-point: values near 0x0000-0x7FFF)
- Lookup tables (sine, cosine, window functions)
- Configuration data (peripheral registers)
## 9. Useful r2 Commands Reference
e asm.arch = adsp219x
e asm.bits = 24
pd 200 # Disassemble 200 instructions pd 200 # Disassemble 200 instructions
pD 600 # Disassemble 600 bytes (= 200 instructions) pD 600 # Disassemble 600 bytes (= 200 words)
/x 1c00 # Find unconditional JUMPs /x 1c # Find unconditional JUMPs
/x 16 # Find DO UNTIL loops /x 16 # Find DO UNTIL loops
/x 0a # Find RTS/RTI instructions /x 0a # Find RTS/RTI instructions
V # Enter visual mode /x 0b # Find indirect JUMP/CALL
axt @ addr # Who references this address?
axf @ addr # What does this address reference?
VV # Visual graph mode
V # Visual hex/disasm mode
pdf # Print current function disassembly
agf # ASCII control flow graph

View File

@@ -0,0 +1,11 @@
.section/PM program0;
.global _start;
_start:
cntr = 10;
stacka = 0x1000;
imask = 0xFF;
irptl = 0;
icntl = 0;
nop;
_halt:
jump _halt;

Binary file not shown.

View File

@@ -1,8 +1,6 @@
NAME ?= asm_adsp219x CC ?= cc
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2 -fPIC CFLAGS ?= -Wall -Wextra -O2 -fPIC
LDFLAGS ?= LDFLAGS ?=
LDLIBS ?= -lr_arch -lr_util
R2_PLUGINS := $(shell r2 -H R2_USER_PLUGINS) R2_PLUGINS := $(shell r2 -H R2_USER_PLUGINS)
R2_INCDIR := $(shell r2 -H R2_INCDIR) R2_INCDIR := $(shell r2 -H R2_INCDIR)
@@ -10,24 +8,24 @@ R2_LIBDIR := $(shell r2 -H R2_LIBDIR)
CPPFLAGS += -I$(R2_INCDIR) -I$(R2_INCDIR)/sdb CPPFLAGS += -I$(R2_INCDIR) -I$(R2_INCDIR)/sdb
all: $(NAME).so all: asm_adsp219x.so
$(NAME).so: $(NAME).c asm_adsp219x.so: asm_adsp219x.c
$(CC) $(CPPFLAGS) $(CFLAGS) -shared $(LDFLAGS) \ $(CC) $(CPPFLAGS) $(CFLAGS) -shared $(LDFLAGS) \
-L$(R2_LIBDIR) \ -L$(R2_LIBDIR) \
$< -o $@ $(LDLIBS) $< -o $@ -lr_arch -lr_util
test: $(NAME).so test: asm_adsp219x.so
r2 -q -a adsp219x -L ./$(NAME).so -b 24 -c "pd 48" ../examples/isa_test.bin >/dev/null r2 -q -a adsp219x -b 24 -c "pd 48" ../examples/isa_test.bin >/dev/null
install: $(NAME).so install: all
mkdir -p $(R2_PLUGINS) mkdir -p $(R2_PLUGINS)
cp -f $(NAME).so $(R2_PLUGINS)/ cp -f asm_adsp219x.so $(R2_PLUGINS)/
uninstall: uninstall:
rm -f $(R2_PLUGINS)/$(NAME).so rm -f $(R2_PLUGINS)/asm_adsp219x.so
clean: clean:
rm -f $(NAME).so rm -f asm_adsp219x.so
.PHONY: all test install uninstall clean .PHONY: all test install uninstall clean

View File

@@ -1,12 +1,11 @@
/* asm_adsp219x.c -- Radare2 arch plugin for Analog Devices ADSP-219x /* asm_adsp219x.c -- Radare2 arch+anal plugin for Analog Devices ADSP-219x
Author: Dr. Christian Giessen
Copyright (C) 2026 Dr. Christian Giessen Copyright (C) 2026 Dr. Christian Giessen
Decodes most of the ADSP-219x instruction set (Types 1-37). Decodes all documented ADSP-219x instruction types (Types 1-37)
Verified against open21xx assembler output for Types 1, 3, 4, with op->type annotations for analysis, function detection, and
6, 7, 8, 9, 9a, 10, 10a, 11, 15, 17, 18, 20, 25, 33. control flow graphing. Most types verified against open21xx
Structurally implemented but not yet assembler-verified: assembler output; see TESTING.md for the verification matrix. */
Types 12, 14, 16, 19, 21, 21a, 22, 22a, 23, 24, 26, 29,
30, 31, 32, 32a, 34, 35, 36, 37. */
#include <r_arch.h> #include <r_arch.h>
@@ -75,7 +74,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ins2 = ((ut32) b[3] << 16) | ((ut32) b[4] << 8) | (ut32) b[5]; ins2 = ((ut32) b[3] << 16) | ((ut32) b[4] << 8) | (ut32) b[5];
op->size = 3; op->size = 3;
op->type = R_ANAL_OP_TYPE_UNK; op->type = R_ANAL_OP_TYPE_UNK;
if (!(mask & R_ARCH_OP_MASK_DISASM)) return true; (void) mask; /* decode always runs full; r2 frees mnemonic */
/* Priority check: High bits */ /* Priority check: High bits */
ut32 b23_22 = (ins >> 22) & 0x3; ut32 b23_22 = (ins >> 22) & 0x3;
@@ -102,6 +101,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("%s, %s=DM(I%d+=M%d), %s=PM(I%d+=M%d)", op->mnemonic = r_str_newf ("%s, %s=DM(I%d+=M%d), %s=PM(I%d+=M%d)",
f, reg0[dd], dmi, dmm, f, reg0[dd], dmi, dmm,
reg0[pd + 4], pmi + 4, pmm + 4); reg0[pd + 4], pmi + 4, pmm + 4);
op->type = (amf < 16) ? R_ANAL_OP_TYPE_MUL : R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -116,11 +116,17 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
const char *rname = ((ins >> 21) & 1) const char *rname = ((ins >> 21) & 1)
? reg1[reg] : reg0[reg]; ? reg1[reg] : reg0[reg];
if (d) if (d)
{
op->mnemonic = r_str_newf ("DM(0x%04X) = %s", op->mnemonic = r_str_newf ("DM(0x%04X) = %s",
addr, rname); addr, rname);
op->type = R_ANAL_OP_TYPE_STORE;
}
else else
{
op->mnemonic = r_str_newf ("%s = DM(0x%04X)", op->mnemonic = r_str_newf ("%s = DM(0x%04X)",
rname, addr); rname, addr);
op->type = R_ANAL_OP_TYPE_LOAD;
}
return true; return true;
} }
@@ -139,13 +145,19 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 mreg = (ins & 3) | (g ? 4 : 0); ut32 mreg = (ins & 3) | (g ? 4 : 0);
char mem = g ? 'P' : 'D'; char mem = g ? 'P' : 'D';
if (d) if (d)
{
op->mnemonic = r_str_newf ("%s, %cM(I%d += M%d) = %s", op->mnemonic = r_str_newf ("%s, %cM(I%d += M%d) = %s",
f, mem, ireg, mreg, f, mem, ireg, mreg,
reg0[dreg]); reg0[dreg]);
op->type = R_ANAL_OP_TYPE_STORE;
}
else else
{
op->mnemonic = r_str_newf ("%s, %s = %cM(I%d += M%d)", op->mnemonic = r_str_newf ("%s, %s = %cM(I%d += M%d)",
f, reg0[dreg], mem, f, reg0[dreg], mem,
ireg, mreg); ireg, mreg);
op->type = R_ANAL_OP_TYPE_LOAD;
}
return true; return true;
} }
@@ -189,6 +201,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("%s = %s(%s, %s)", op->mnemonic = r_str_newf ("%s = %s(%s, %s)",
dst, f, reg0[xreg], dst, f, reg0[xreg],
reg0[yreg]); reg0[yreg]);
op->type = (amf < 16) ? R_ANAL_OP_TYPE_MUL
: R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -203,6 +217,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
{ {
op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s^2)", op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s^2)",
cp, cs, sp, dst, f, x); cp, cs, sp, dst, f, x);
op->type = R_ANAL_OP_TYPE_MUL;
return true; return true;
} }
@@ -211,6 +226,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
{ {
op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, 0)", op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, 0)",
cp, cs, sp, dst, f, x); cp, cs, sp, dst, f, x);
op->type = (amf < 16) ? R_ANAL_OP_TYPE_MUL
: R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -221,6 +238,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
: yop_alu[yop_i]; : yop_alu[yop_i];
op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, %s)", op->mnemonic = r_str_newf ("%s%s%s%s = %s(%s, %s)",
cp, cs, sp, dst, f, x, y); cp, cs, sp, dst, f, x, y);
op->type = (amf < 16) ? R_ANAL_OP_TYPE_MUL
: R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -289,21 +308,23 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("%s(%s, %s), %s = %s", op->mnemonic = r_str_newf ("%s(%s, %s), %s = %s",
f, x, y, f, x, y,
reg0[ddreg], reg0[sdreg]); reg0[ddreg], reg0[sdreg]);
op->type = (amf < 16) ? R_ANAL_OP_TYPE_MUL
: R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
/* Type 6/7/IO/System (010xxx / 011xxx) */ /* Type 6/7/IO/System (010xxx / 011xxx) */
if (b23_22 == 1) if (b23_22 == 1)
{ {
if (b21_20 == 0) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg0[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 6 */ if (b21_20 == 0) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg0[ins&0xF], (ins>>4)&0xFFFF); op->type = R_ANAL_OP_TYPE_MOV; return true; } /* Type 6 */
if (b21_20 == 1) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg1[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 7 */ if (b21_20 == 1) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg1[ins&0xF], (ins>>4)&0xFFFF); op->type = R_ANAL_OP_TYPE_MOV; return true; } /* Type 7 */
/* Type 34 and 35 are in the b23_22==0 block below */ /* Type 34 and 35 are in the b23_22==0 block below */
} }
/* Type 8/9/10/11/17... (00xxxx) */ /* Type 8/9/10/11/17... (00xxxx) */
if (b23_22 == 0) if (b23_22 == 0)
{ {
if (b21_20 == 3) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg2[ins&0xF], (ins>>4)&0xFFFF); return true; } /* Type 7 (Reg2) */ if (b21_20 == 3) { op->mnemonic = r_str_newf ("%s = 0x%04X", reg2[ins&0xF], (ins>>4)&0xFFFF); op->type = R_ANAL_OP_TYPE_MOV; return true; } /* Type 7 (Reg2) */
/* Type 36: Long Jump/Call (2-word, bits 23-16 = 00000101) */ /* Type 36: Long Jump/Call (2-word, bits 23-16 = 00000101) */
if ((ins >> 16) == 0x05) if ((ins >> 16) == 0x05)
@@ -331,6 +352,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->type = s_bit ? R_ANAL_OP_TYPE_CALL op->type = s_bit ? R_ANAL_OP_TYPE_CALL
: R_ANAL_OP_TYPE_JMP; : R_ANAL_OP_TYPE_JMP;
op->jump = addr; op->jump = addr;
if (!s_bit)
op->eob = true;
return true; return true;
} }
@@ -395,6 +418,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->type = s_bit ? R_ANAL_OP_TYPE_CALL op->type = s_bit ? R_ANAL_OP_TYPE_CALL
: R_ANAL_OP_TYPE_JMP; : R_ANAL_OP_TYPE_JMP;
op->jump = target; op->jump = target;
if (!s_bit)
op->eob = true;
return true; return true;
} }
/* Type 10: bits 23-19 = 00011, bit18 = 0 (conditional 13-bit rel) */ /* Type 10: bits 23-19 = 00011, bit18 = 0 (conditional 13-bit rel) */
@@ -417,6 +442,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
b_bit ? " (DB)" : ""); b_bit ? " (DB)" : "");
op->type = R_ANAL_OP_TYPE_CJMP; op->type = R_ANAL_OP_TYPE_CJMP;
op->jump = target; op->jump = target;
op->fail = op->addr + 3;
return true; return true;
} }
/* Type 17: Reg = Reg (bits 23-16 = 00001101) */ /* Type 17: Reg = Reg (bits 23-16 = 00001101) */
@@ -425,6 +451,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("%s = %s", op->mnemonic = r_str_newf ("%s = %s",
get_reg ((ins >> 10) & 3, (ins >> 4) & 0xF), get_reg ((ins >> 10) & 3, (ins >> 4) & 0xF),
get_reg ((ins >> 8) & 3, ins & 0xF)); get_reg ((ins >> 8) & 3, ins & 0xF));
op->type = R_ANAL_OP_TYPE_MOV;
return true; return true;
} }
@@ -443,6 +470,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
cond_str[cond], ret, cond_str[cond], ret,
b_bit ? " (DB)" : ""); b_bit ? " (DB)" : "");
op->type = R_ANAL_OP_TYPE_RET; op->type = R_ANAL_OP_TYPE_RET;
op->eob = true;
return true; return true;
} }
@@ -470,6 +498,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
sf_names[sf], xop_shift[xop_i], sf_names[sf], xop_shift[xop_i],
reg0[dreg], reg0[dreg],
g ? 'P' : 'D', ireg + base, mreg + base); g ? 'P' : 'D', ireg + base, mreg + base);
op->type = d ? R_ANAL_OP_TYPE_STORE : R_ANAL_OP_TYPE_LOAD;
return true; return true;
} }
@@ -485,6 +514,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("%s %s, %s = %s", op->mnemonic = r_str_newf ("%s %s, %s = %s",
sf_names[sf], reg0[xreg], sf_names[sf], reg0[xreg],
reg0[ddreg], reg0[sdreg]); reg0[ddreg], reg0[sdreg]);
op->type = R_ANAL_OP_TYPE_SHR;
return true; return true;
} }
@@ -503,6 +533,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("IF %s SR = %s %s", op->mnemonic = r_str_newf ("IF %s SR = %s %s",
cond_str[cond], sf_names[sf], cond_str[cond], sf_names[sf],
reg0[xreg]); reg0[xreg]);
op->type = R_ANAL_OP_TYPE_SHR;
return true; return true;
} }
@@ -517,6 +548,8 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ( op->mnemonic = r_str_newf (
"DO 0x%06" PFMT64x " UNTIL %s", "DO 0x%06" PFMT64x " UNTIL %s",
target, cond_str[term]); target, cond_str[term]);
op->type = R_ANAL_OP_TYPE_REP;
op->jump = target;
return true; return true;
} }
@@ -529,6 +562,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("SR = %s %s BY %d", op->mnemonic = r_str_newf ("SR = %s %s BY %d",
sf_names[sf], reg0[xreg], sf_names[sf], reg0[xreg],
exp); exp);
op->type = R_ANAL_OP_TYPE_SHR;
return true; return true;
} }
@@ -553,6 +587,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
} }
op->mnemonic = pos ? strdup (buf) op->mnemonic = pos ? strdup (buf)
: strdup ("MODE NOP"); : strdup ("MODE NOP");
op->type = R_ANAL_OP_TYPE_MOV;
return true; return true;
} }
@@ -562,6 +597,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 r = (ins >> 13) & 1; ut32 r = (ins >> 13) & 1;
op->mnemonic = r_str_newf ("SAT %s", op->mnemonic = r_str_newf ("SAT %s",
r ? "SR" : "MR"); r ? "SR" : "MR");
op->type = R_ANAL_OP_TYPE_MOV;
return true; return true;
} }
@@ -572,6 +608,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 reg = ins & 0xF; ut32 reg = ins & 0xF;
op->mnemonic = r_str_newf ("%s = 0x%03X", op->mnemonic = r_str_newf ("%s = 0x%03X",
reg3[reg], data); reg3[reg], data);
op->type = R_ANAL_OP_TYPE_MOV;
return true; return true;
} }
@@ -597,6 +634,10 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
b_bit ? " (DB)" : ""); b_bit ? " (DB)" : "");
op->type = s_bit ? R_ANAL_OP_TYPE_CALL op->type = s_bit ? R_ANAL_OP_TYPE_CALL
: R_ANAL_OP_TYPE_JMP; : R_ANAL_OP_TYPE_JMP;
if (!s_bit && cond == 15)
op->eob = true;
if (cond != 15)
op->fail = op->addr + 3;
return true; return true;
} }
@@ -609,6 +650,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
int base = g ? 4 : 0; int base = g ? 4 : 0;
op->mnemonic = r_str_newf ("MODIFY(I%d += M%d)", op->mnemonic = r_str_newf ("MODIFY(I%d += M%d)",
ireg + base, mreg + base); ireg + base, mreg + base);
op->type = R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -618,6 +660,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 xop_i = (ins >> 8) & 0x7; ut32 xop_i = (ins >> 8) & 0x7;
op->mnemonic = r_str_newf ("DIVQ %s", op->mnemonic = r_str_newf ("DIVQ %s",
xop_alu[xop_i]); xop_alu[xop_i]);
op->type = R_ANAL_OP_TYPE_DIV;
return true; return true;
} }
@@ -629,6 +672,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
op->mnemonic = r_str_newf ("DIVS %s, %s", op->mnemonic = r_str_newf ("DIVS %s, %s",
yop_alu[yop_i], yop_alu[yop_i],
xop_alu[xop_i]); xop_alu[xop_i]);
op->type = R_ANAL_OP_TYPE_DIV;
return true; return true;
} }
@@ -670,6 +714,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
pos ? ", " : ""); pos ? ", " : "");
op->mnemonic = pos ? strdup (buf) op->mnemonic = pos ? strdup (buf)
: strdup ("STACK NOP"); : strdup ("STACK NOP");
op->type = R_ANAL_OP_TYPE_PUSH;
return true; return true;
} }
@@ -692,13 +737,19 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
int base = g ? 4 : 0; int base = g ? 4 : 0;
const char *op_str = u ? "+=" : "+"; const char *op_str = u ? "+=" : "+";
if (d) if (d)
{
op->mnemonic = r_str_newf ("DM(I%d %s %d) = %s", op->mnemonic = r_str_newf ("DM(I%d %s %d) = %s",
ireg + base, op_str, smod, ireg + base, op_str, smod,
reg0[dreg]); reg0[dreg]);
op->type = R_ANAL_OP_TYPE_STORE;
}
else else
{
op->mnemonic = r_str_newf ("%s = DM(I%d %s %d)", op->mnemonic = r_str_newf ("%s = DM(I%d %s %d)",
reg0[dreg], ireg + base, op_str, reg0[dreg], ireg + base, op_str,
smod); smod);
op->type = R_ANAL_OP_TYPE_LOAD;
}
return true; return true;
} }
@@ -720,13 +771,19 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
const char *mod = u_bit ? "+=" : "+"; const char *mod = u_bit ? "+=" : "+";
const char *rname = get_reg (rgp, reg); const char *rname = get_reg (rgp, reg);
if (d) if (d)
{
op->mnemonic = r_str_newf ("%s(I%d %s M%d) = %s", op->mnemonic = r_str_newf ("%s(I%d %s M%d) = %s",
mem, ireg + base, mod, mreg + base, mem, ireg + base, mod, mreg + base,
rname); rname);
op->type = R_ANAL_OP_TYPE_STORE;
}
else else
{
op->mnemonic = r_str_newf ("%s = %s(I%d %s M%d)", op->mnemonic = r_str_newf ("%s = %s(I%d %s M%d)",
rname, mem, ireg + base, mod, rname, mem, ireg + base, mod,
mreg + base); mreg + base);
op->type = R_ANAL_OP_TYPE_LOAD;
}
return true; return true;
} }
@@ -753,6 +810,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
"DM(I%d %s M%d) = %s, %s = I%d", "DM(I%d %s M%d) = %s, %s = I%d",
ireg + base, mod, mreg + base, ireg + base, mod, mreg + base,
rname, rname, ireg + base); rname, rname, ireg + base);
op->type = R_ANAL_OP_TYPE_STORE;
return true; return true;
} }
@@ -765,6 +823,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
int base = g ? 4 : 0; int base = g ? 4 : 0;
op->mnemonic = r_str_newf ("MODIFY(I%d += %d)", op->mnemonic = r_str_newf ("MODIFY(I%d += %d)",
ireg + base, mod); ireg + base, mod);
op->type = R_ANAL_OP_TYPE_ADD;
return true; return true;
} }
@@ -776,11 +835,17 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 addr = (addr_hi << 8) | ((ins >> 4) & 0xFF); ut32 addr = (addr_hi << 8) | ((ins >> 4) & 0xFF);
ut32 dreg = ins & 0xF; ut32 dreg = ins & 0xF;
if (d) if (d)
{
op->mnemonic = r_str_newf ("IO(0x%03X) = %s", op->mnemonic = r_str_newf ("IO(0x%03X) = %s",
addr, reg0[dreg]); addr, reg0[dreg]);
op->type = R_ANAL_OP_TYPE_IO;
}
else else
{
op->mnemonic = r_str_newf ("%s = IO(0x%03X)", op->mnemonic = r_str_newf ("%s = IO(0x%03X)",
reg0[dreg], addr); reg0[dreg], addr);
op->type = R_ANAL_OP_TYPE_IO;
}
return true; return true;
} }
/* Type 35: Sys ctrl reg (bits 23-16 = 00000110, bit15=0) */ /* Type 35: Sys ctrl reg (bits 23-16 = 00000110, bit15=0) */
@@ -790,11 +855,17 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 addr = (ins >> 4) & 0xFF; ut32 addr = (ins >> 4) & 0xFF;
ut32 dreg = ins & 0xF; ut32 dreg = ins & 0xF;
if (d) if (d)
{
op->mnemonic = r_str_newf ("REG(0x%02X) = %s", op->mnemonic = r_str_newf ("REG(0x%02X) = %s",
addr, reg0[dreg]); addr, reg0[dreg]);
op->type = R_ANAL_OP_TYPE_IO;
}
else else
{
op->mnemonic = r_str_newf ("%s = REG(0x%02X)", op->mnemonic = r_str_newf ("%s = REG(0x%02X)",
reg0[dreg], addr); reg0[dreg], addr);
op->type = R_ANAL_OP_TYPE_IO;
}
return true; return true;
} }
@@ -805,6 +876,7 @@ decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
ut32 bit = ins & 0xF; ut32 bit = ins & 0xF;
op->mnemonic = r_str_newf ("%s %d", op->mnemonic = r_str_newf ("%s %d",
c ? "CLRINT" : "SETINT", bit); c ? "CLRINT" : "SETINT", bit);
op->type = R_ANAL_OP_TYPE_SWI;
return true; return true;
} }
} }

251
tools/analyze_dm.py Executable file
View File

@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""analyze_dm.py -- Analyze ADSP-219x DM (Data Memory) dumps.
Scans a 16-bit DM binary for:
- Null regions (uninitialized / BSS)
- Coefficient tables (Q15 fixed-point patterns)
- Lookup tables (periodic waveforms)
- ASCII strings
- Non-zero data regions
Usage:
python3 analyze_dm.py firmware_dm.bin
python3 analyze_dm.py firmware_dm.bin --endian little
"""
import argparse
import math
import os
import struct
import sys
def read_words(path, endian="big"):
"""Read 16-bit words from a binary file."""
fmt = ">H" if endian == "big" else "<H"
data = open(path, "rb").read()
if len(data) % 2:
data += b"\x00"
return [struct.unpack_from(fmt, data, i)[0]
for i in range(0, len(data), 2)]
def signed16(v):
"""Convert unsigned 16-bit to signed."""
return v - 0x10000 if v >= 0x8000 else v
def find_null_regions(words, min_run=8):
"""Find contiguous runs of zero words."""
regions = []
start = None
for i, w in enumerate(words):
if w == 0:
if start is None:
start = i
else:
if start is not None and (i - start) >= min_run:
regions.append((start, i - start))
start = None
if start is not None and (len(words) - start) >= min_run:
regions.append((start, len(words) - start))
return regions
def find_coeff_tables(words, min_len=8, max_magnitude=0x7FFF):
"""Find potential Q15 coefficient tables.
Looks for runs of non-zero values where all values are
within the Q15 range and show some variance (not constant).
"""
tables = []
start = None
run = []
for i, w in enumerate(words):
sw = signed16(w)
if w != 0 and abs(sw) <= max_magnitude:
if start is None:
start = i
run = []
run.append(sw)
else:
if start is not None and len(run) >= min_len:
mn = min(run)
mx = max(run)
if mx != mn: # not constant
tables.append((start, len(run), mn, mx,
sum(run) / len(run)))
start = None
run = []
if start is not None and len(run) >= min_len:
mn = min(run)
mx = max(run)
if mx != mn:
tables.append((start, len(run), mn, mx,
sum(run) / len(run)))
return tables
def detect_periodicity(words, start, length):
"""Check if a region has periodic content (sine/cosine table)."""
if length < 16:
return None
vals = [signed16(words[start + i]) for i in range(length)]
# Try periods from 16 to length/2
best_period = None
best_err = float("inf")
for period in range(16, min(length // 2 + 1, 1025)):
if length < period * 2:
continue
err = 0.0
count = 0
for i in range(period, min(length, period * 4)):
diff = vals[i] - vals[i % period]
err += diff * diff
count += 1
if count > 0:
rms = math.sqrt(err / count)
if rms < best_err:
best_err = rms
best_period = period
if best_period and best_err < 500:
return best_period
return None
def find_strings(data, min_len=4):
"""Find ASCII strings in raw bytes."""
strings = []
current = []
start = None
for i, byte in enumerate(data):
if 0x20 <= byte < 0x7F:
if start is None:
start = i
current.append(chr(byte))
else:
if current and len(current) >= min_len:
strings.append((start, "".join(current)))
current = []
start = None
if current and len(current) >= min_len:
strings.append((start, "".join(current)))
return strings
def analyze(path, endian="big"):
"""Run full analysis on a DM dump."""
data = open(path, "rb").read()
size = len(data)
words = read_words(path, endian)
n_words = len(words)
print(f"File: {path}")
print(f"Size: {size} bytes ({n_words} words, 16-bit {endian}-endian)")
print()
# Null regions
nulls = find_null_regions(words)
if nulls:
total_null = sum(n for _, n in nulls)
print(f"=== Null Regions ({len(nulls)} found, "
f"{total_null} words = {total_null*100//n_words}%) ===")
for addr, length in nulls:
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words, {length*2} bytes)")
print()
# Coefficient tables
coeffs = find_coeff_tables(words)
if coeffs:
print(f"=== Potential Coefficient Tables ({len(coeffs)} found) ===")
for addr, length, mn, mx, avg in coeffs:
q15_min = mn / 32768.0
q15_max = mx / 32768.0
period = detect_periodicity(words, addr, length)
pstr = f", period~{period}" if period else ""
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words)")
print(f" Range: {mn}..{mx} (Q15: {q15_min:.4f}"
f"..{q15_max:.4f}), avg={avg:.1f}{pstr}")
print()
# Strings
strings = find_strings(data)
if strings:
print(f"=== ASCII Strings ({len(strings)} found) ===")
for offset, s in strings:
dm_addr = offset // 2
print(f" Byte 0x{offset:04X} (DM ~0x{dm_addr:04X}):"
f" \"{s}\"")
print()
# Non-null, non-coefficient data regions
covered = set()
for addr, length in nulls:
for i in range(addr, addr + length):
covered.add(i)
for addr, length, _, _, _ in coeffs:
for i in range(addr, addr + length):
covered.add(i)
data_regions = []
start = None
for i in range(n_words):
if i not in covered and words[i] != 0:
if start is None:
start = i
else:
if start is not None:
length = i - start
if length >= 4:
data_regions.append((start, length))
start = None
if start is not None and (n_words - start) >= 4:
data_regions.append((start, n_words - start))
if data_regions:
print(f"=== Other Data Regions ({len(data_regions)} found) ===")
for addr, length in data_regions:
sample = [f"0x{words[addr+j]:04X}"
for j in range(min(length, 8))]
dots = " ..." if length > 8 else ""
print(f" DM 0x{addr:04X} - 0x{addr+length-1:04X}"
f" ({length} words)")
print(f" [{', '.join(sample)}{dots}]")
print()
# Summary
total_nonzero = sum(1 for w in words if w != 0)
print(f"=== Summary ===")
print(f" Total words: {n_words}")
print(f" Non-zero words: {total_nonzero}"
f" ({total_nonzero*100//max(n_words,1)}%)")
print(f" Null regions: {len(nulls)}")
print(f" Coeff tables: {len(coeffs)}")
print(f" Strings: {len(strings)}")
print(f" Other data: {len(data_regions)}")
def main():
parser = argparse.ArgumentParser(
description="Analyze ADSP-219x DM (Data Memory) dumps.")
parser.add_argument("file", help="DM binary file")
parser.add_argument("--endian", choices=["big", "little"],
default="big",
help="Byte order (default: big)")
args = parser.parse_args()
if not os.path.isfile(args.file):
print(f"Error: {args.file} not found", file=sys.stderr)
sys.exit(1)
analyze(args.file, args.endian)
if __name__ == "__main__":
main()

270
tools/dm_xref.py Executable file
View File

@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""dm_xref.py -- Extract all DM address references from PM code
and show what's stored at those addresses in a DM dump.
Finds:
- I-register loads (I0=0x100 → DM base address)
- Direct DM access (DM(0x1234) = reg / reg = DM(0x1234))
- DM immediate modify (reg = DM(I0 += offset))
- L-register loads (circular buffer lengths)
- CNTR loads (loop counts)
Usage:
python3 dm_xref.py firmware_pm.bin firmware_dm.bin
python3 dm_xref.py firmware_pm.bin firmware_dm.bin --context 16
"""
import argparse
import os
import struct
import sys
REG0 = ["AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1",
"MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0"]
REG1 = ["I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3",
"L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "STACKA"]
REG2 = ["I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7",
"L4", "L5", "L6", "L7", "RES", "RES", "CNTR", "LPSTACKA"]
def read_pm(path):
data = open(path, "rb").read()
words = []
for i in range(0, len(data) - 2, 3):
w = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]
words.append(w)
return words
def read_dm(path, endian="big"):
fmt = ">H" if endian == "big" else "<H"
data = open(path, "rb").read()
if len(data) % 2:
data += b"\x00"
return [struct.unpack_from(fmt, data, i)[0]
for i in range(0, len(data), 2)]
def signed16(v):
return v - 0x10000 if v >= 0x8000 else v
def extract_dm_refs(pm_words):
"""Extract all DM address references from PM code."""
refs = []
for i, w in enumerate(pm_words):
pm_addr = i * 3
b23_22 = (w >> 22) & 3
b21_20 = (w >> 20) & 3
# Type 6: Dreg = Imm16 (data constants, not DM refs)
# Skip — these load values, not addresses
# Type 7 REG1: I/M/L register loads
if b23_22 == 1 and b21_20 == 1:
val = (w >> 4) & 0xFFFF
reg = REG1[w & 0xF]
if reg.startswith("I"):
refs.append({
"pm_addr": pm_addr,
"type": "I-reg load",
"reg": reg,
"dm_addr": val,
"desc": f"{reg} = 0x{val:04X}"
})
elif reg.startswith("L"):
refs.append({
"pm_addr": pm_addr,
"type": "L-reg (buf len)",
"reg": reg,
"dm_addr": None,
"value": val,
"desc": f"{reg} = {val} (buffer length)"
})
# Type 7 REG2: I4-I7/M4-M7/L4-L7/CNTR loads
if b23_22 == 0 and b21_20 == 3:
val = (w >> 4) & 0xFFFF
reg = REG2[w & 0xF]
if reg.startswith("I"):
refs.append({
"pm_addr": pm_addr,
"type": "I-reg load",
"reg": reg,
"dm_addr": val,
"desc": f"{reg} = 0x{val:04X}"
})
elif reg.startswith("L"):
refs.append({
"pm_addr": pm_addr,
"type": "L-reg (buf len)",
"reg": reg,
"dm_addr": None,
"value": val,
"desc": f"{reg} = {val} (buffer length)"
})
elif reg == "CNTR":
refs.append({
"pm_addr": pm_addr,
"type": "CNTR",
"reg": reg,
"dm_addr": None,
"value": val,
"desc": f"CNTR = {val} (loop count)"
})
# Type 3: Direct DM access — DM(addr16) = reg / reg = DM(addr16)
if b23_22 == 2:
d = (w >> 20) & 1
addr = (w >> 4) & 0xFFFF
reg_idx = w & 0xF
is_ireg = (w >> 21) & 1
rname = REG1[reg_idx] if is_ireg else REG0[reg_idx]
direction = "write" if d else "read"
refs.append({
"pm_addr": pm_addr,
"type": f"DM direct {direction}",
"reg": rname,
"dm_addr": addr,
"desc": (f"DM(0x{addr:04X}) = {rname}" if d
else f"{rname} = DM(0x{addr:04X})")
})
return refs
def show_dm_content(dm_words, dm_addr, context=8):
"""Format DM content at a given address."""
if dm_addr is None or dm_addr >= len(dm_words):
return " (outside DM range)"
end = min(dm_addr + context, len(dm_words))
hex_vals = []
dec_vals = []
for j in range(dm_addr, end):
v = dm_words[j]
hex_vals.append(f"0x{v:04X}")
dec_vals.append(f"{signed16(v):6d}")
lines = []
lines.append(f" Hex: [{', '.join(hex_vals)}]")
lines.append(f" Dec: [{', '.join(dec_vals)}]")
# Check if it looks like Q15 coefficients
vals = [signed16(dm_words[j]) for j in range(dm_addr, end)]
if all(abs(v) <= 32767 for v in vals):
q15 = [f"{v/32768:.4f}" for v in vals]
lines.append(f" Q15: [{', '.join(q15)}]")
# Check for all zeros
if all(v == 0 for v in vals):
lines.append(f" (all zeros — uninitialized)")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Extract DM references from PM code and"
" show DM contents.")
parser.add_argument("pm_file", help="PM binary (packed 3-byte)")
parser.add_argument("dm_file", help="DM binary (16-bit words)")
parser.add_argument("--endian", choices=["big", "little"],
default="big")
parser.add_argument("--context", type=int, default=8,
help="Words of DM context to show"
" (default: 8)")
args = parser.parse_args()
for f in (args.pm_file, args.dm_file):
if not os.path.isfile(f):
print(f"Error: {f} not found", file=sys.stderr)
sys.exit(1)
pm_words = read_pm(args.pm_file)
dm_words = read_dm(args.dm_file, args.endian)
refs = extract_dm_refs(pm_words)
print(f"PM: {args.pm_file} ({len(pm_words)} instructions)")
print(f"DM: {args.dm_file} ({len(dm_words)} words)")
print()
# Group by type
ireg_loads = [r for r in refs if r["type"] == "I-reg load"]
direct_access = [r for r in refs
if r["type"].startswith("DM direct")]
buf_lengths = [r for r in refs
if r["type"] == "L-reg (buf len)"]
counters = [r for r in refs if r["type"] == "CNTR"]
# I-register loads → buffer base addresses
if ireg_loads:
print(f"=== Buffer Base Addresses"
f" ({len(ireg_loads)} I-reg loads) ===")
seen = {}
for r in ireg_loads:
key = (r["reg"], r["dm_addr"])
if key not in seen:
seen[key] = r
for (reg, dm_addr), r in sorted(seen.items()):
print(f"\n{reg} = 0x{dm_addr:04X}"
f" (PM @ 0x{r['pm_addr']:06X})")
# Find matching L-register for buffer length
lreg = reg.replace("I", "L")
for lr in buf_lengths:
if lr["reg"] == lreg:
print(f" {lreg} = {lr['value']}"
f" (circular buffer, {lr['value']} words)")
break
print(show_dm_content(dm_words, dm_addr, args.context))
print()
# Direct DM accesses
if direct_access:
print(f"=== Direct DM Accesses"
f" ({len(direct_access)} found) ===")
seen_addrs = {}
for r in direct_access:
a = r["dm_addr"]
if a not in seen_addrs:
seen_addrs[a] = []
seen_addrs[a].append(r)
for dm_addr in sorted(seen_addrs.keys()):
ops = seen_addrs[dm_addr]
descs = [r["desc"] for r in ops[:3]]
more = f" +{len(ops)-3} more" if len(ops) > 3 else ""
print(f"\nDM 0x{dm_addr:04X}:")
for d in descs:
print(f" {d}")
if more:
print(f" {more}")
print(show_dm_content(dm_words, dm_addr, args.context))
print()
# Loop counters
if counters:
print(f"=== Loop Counters ({len(counters)} found) ===")
for r in counters:
print(f" PM 0x{r['pm_addr']:06X}: {r['desc']}")
print()
# Summary
all_dm_addrs = set()
for r in refs:
if r.get("dm_addr") is not None:
all_dm_addrs.add(r["dm_addr"])
print(f"=== Summary ===")
print(f" Unique DM addresses referenced: {len(all_dm_addrs)}")
print(f" Buffer bases (I-reg): {len(ireg_loads)}")
print(f" Direct DM access: {len(direct_access)}")
print(f" Buffer lengths (L-reg): {len(buf_lengths)}")
print(f" Loop counters (CNTR): {len(counters)}")
if __name__ == "__main__":
main()

334
tools/firmware_overview.py Executable file
View File

@@ -0,0 +1,334 @@
#!/usr/bin/env python3
"""firmware_overview.py -- Quick structural overview of ADSP-219x firmware.
Analyzes a PM binary and produces a human-readable report:
- Memory map (code vs data vs empty regions)
- Function-like blocks (code between RTS/JUMP boundaries)
- DSP kernels (DO UNTIL loops with MAC operations)
- I/O configuration (peripheral register writes)
- All immediate constants loaded into registers
- Cross-reference: which addresses are referenced
Usage:
python3 firmware_overview.py firmware_pm.bin
python3 firmware_overview.py firmware_pm.bin --dm firmware_dm.bin
"""
import argparse
import os
import struct
import sys
# ADSP-2191 I/O register names (partial, most common)
IO_NAMES = {
0x00: "SYSCTRL", 0x01: "SYSTAT", 0x02: "IOPG",
0x04: "BWCTRL", 0x05: "BWSTAT",
0x10: "PICR0", 0x11: "PICR1", 0x12: "PICR2", 0x13: "PICR3",
0x14: "IMASK0", 0x15: "IMASK1",
0x20: "TCTL", 0x21: "TCOUNT", 0x22: "TPERIOD", 0x23: "TSCALE",
0x40: "SPORT0_CTL", 0x41: "SPORT0_DIV",
0x42: "SPORT0_MCTL", 0x43: "SPORT0_CS0",
0x44: "SPORT0_TX", 0x45: "SPORT0_RX",
0x46: "SPORT0_STAT",
0x60: "SPORT1_CTL", 0x61: "SPORT1_DIV",
0x62: "SPORT1_MCTL", 0x63: "SPORT1_CS0",
0x64: "SPORT1_TX", 0x65: "SPORT1_RX",
0x66: "SPORT1_STAT",
0x80: "SPICTL", 0x81: "SPISTAT", 0x82: "SPIFLG",
0x83: "SPIBAUD", 0x84: "SPIRX", 0x85: "SPITX",
0xA0: "UART_TX", 0xA1: "UART_RX", 0xA2: "UART_CTL",
0xA3: "UART_STAT", 0xA4: "UART_DIV",
0xC0: "FLAGD", 0xC1: "FLAGP", 0xC2: "FLAGS",
0xC3: "FLAGC",
}
REG0 = ["AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1",
"MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0"]
REG1 = ["I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3",
"L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "STACKA"]
REG2 = ["I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7",
"L4", "L5", "L6", "L7", "RES", "RES", "CNTR", "LPSTACKA"]
def read_pm(path):
"""Read 24-bit PM words from packed binary."""
data = open(path, "rb").read()
words = []
for i in range(0, len(data) - 2, 3):
w = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]
words.append(w)
return words
def classify_word(w):
"""Return a rough classification of a PM word."""
if w == 0:
return "null"
b23_22 = (w >> 22) & 3
if b23_22 == 3:
return "compute_multi" # Type 1
b23_16 = (w >> 16) & 0xFF
if b23_16 == 0x0A:
return "rts"
if (w >> 18) == 0x07:
return "jump"
if (w >> 19) == 0x03:
return "jump_cond"
if b23_16 == 0x16:
return "do_until"
if b23_16 == 0x0B:
return "jump_indirect"
if b23_16 == 0x05:
return "ljump"
return "code"
def find_constants(words):
"""Extract all immediate constant loads."""
constants = []
for i, w in enumerate(words):
addr = i * 3
b23_22 = (w >> 22) & 3
b21_20 = (w >> 20) & 3
# Type 6: Dreg = Imm16
if b23_22 == 1 and b21_20 == 0:
val = (w >> 4) & 0xFFFF
reg = REG0[w & 0xF]
constants.append((addr, reg, val, "dreg"))
# Type 7 REG1: Reg1 = Imm16
elif b23_22 == 1 and b21_20 == 1:
val = (w >> 4) & 0xFFFF
reg = REG1[w & 0xF]
constants.append((addr, reg, val, "reg1"))
# Type 7 REG2: Reg2 = Imm16
elif b23_22 == 0 and b21_20 == 3:
val = (w >> 4) & 0xFFFF
reg = REG2[w & 0xF]
constants.append((addr, reg, val, "reg2"))
# Type 33: Reg3 = Data12
elif (w >> 16) == 0x10:
val = (w >> 4) & 0xFFF
constants.append((addr, "REG3", val, "short"))
# Type 34: IO write
if (w >> 16) == 0x06 and ((w >> 15) & 1):
d = (w >> 12) & 1
io_addr = (((w >> 13) & 3) << 8) | ((w >> 4) & 0xFF)
dreg = REG0[w & 0xF]
io_name = IO_NAMES.get(io_addr, f"IO_0x{io_addr:03X}")
if d:
constants.append((addr, io_name, None,
f"io_write({dreg})"))
else:
constants.append((addr, dreg, None,
f"io_read({io_name})"))
return constants
def find_do_loops(words):
"""Find DO UNTIL loops and their bodies."""
loops = []
for i, w in enumerate(words):
if (w >> 16) == 0x16:
rel = (w >> 4) & 0xFFF
term = w & 0xF
cond = ["EQ", "NE", "GT", "LE", "LT", "GE",
"AV", "NOT AV", "AC", "NOT AC",
"SWCOND", "NOT SWCOND", "MV", "NOT MV",
"NOT CE", "TRUE"][term]
srel = rel if rel < 0x800 else rel - 0x1000
target = i * 3 + srel * 3
# Count MAC ops in loop body
mac_count = 0
for j in range(i + 1, min(i + srel + 1, len(words))):
if (words[j] >> 22) & 3 == 3: # Type 1
amf = (words[j] >> 13) & 0x1F
if amf < 16: # MAC operation
mac_count += 1
loops.append((i * 3, target, cond, srel, mac_count))
return loops
def memory_map(words, chunk_size=64):
"""Build a coarse memory map."""
regions = []
i = 0
while i < len(words):
end = min(i + chunk_size, len(words))
chunk = words[i:end]
n_null = sum(1 for w in chunk if w == 0)
n_multi = sum(1 for w in chunk
if (w >> 22) & 3 == 3)
n_jump = sum(1 for w in chunk
if classify_word(w) in
("jump", "jump_cond", "rts", "do_until"))
if n_null > len(chunk) * 0.9:
kind = "empty"
elif n_multi > len(chunk) * 0.3:
kind = "dsp_kernel"
elif n_jump > len(chunk) * 0.1:
kind = "control_flow"
else:
kind = "code/data"
if regions and regions[-1][2] == kind:
regions[-1] = (regions[-1][0], end * 3, kind)
else:
regions.append((i * 3, end * 3, kind))
i = end
return regions
def analyze_pm(path, dm_path=None):
"""Full PM analysis."""
words = read_pm(path)
size = len(words) * 3
print(f"=== PM Firmware Overview ===")
print(f"File: {path}")
print(f"Size: {size} bytes ({len(words)} instructions)")
print()
# Memory map
regions = memory_map(words)
print(f"--- Memory Map ---")
for start, end, kind in regions:
label = {"empty": "EMPTY (null)",
"dsp_kernel": "DSP KERNEL (MAC-heavy)",
"control_flow": "CONTROL FLOW (jumps)",
"code/data": "CODE / DATA"}[kind]
print(f" 0x{start:06X}-0x{end:06X}"
f" ({(end-start)//3:4d} words) {label}")
print()
# Entry point
if words:
w0 = words[0]
if (w0 >> 18) == 0x07:
rel = ((w0 >> 4) & 0x3FFF) | ((w0 & 3) << 14)
srel = rel if rel < 0x8000 else rel - 0x10000
target = srel * 3
print(f"--- Entry Point ---")
print(f" Reset vector: JUMP 0x{target:06X}")
print()
elif w0 == 0:
print(f"--- Entry Point ---")
print(f" Reset vector: NOP (code starts at 0x000003)")
print()
# DO UNTIL loops (DSP kernels)
loops = find_do_loops(words)
if loops:
print(f"--- DSP Loops ({len(loops)} found) ---")
for addr, end, cond, length, macs in loops:
kind = ""
if macs > 0:
kind = f" [MAC kernel, {macs} multiply-accumulate ops]"
elif cond == "NOT CE":
kind = " [counter loop]"
print(f" 0x{addr:06X}: DO 0x{end:06X} UNTIL {cond}"
f" ({length} words){kind}")
print()
# Constants
constants = find_constants(words)
if constants:
# Group by type
reg_loads = [(a, r, v, t) for a, r, v, t in constants
if t in ("dreg", "reg1", "reg2", "short")]
io_ops = [(a, r, v, t) for a, r, v, t in constants
if t.startswith("io_")]
if reg_loads:
print(f"--- Constants ({len(reg_loads)} register loads) ---")
# Show unique values
seen = {}
for addr, reg, val, _ in reg_loads:
key = (reg, val)
if key not in seen:
seen[key] = []
seen[key].append(addr)
for (reg, val), addrs in sorted(seen.items(),
key=lambda x: x[0][1]):
sval = val - 0x10000 if val >= 0x8000 else val
locs = ", ".join(f"0x{a:06X}" for a in addrs[:3])
more = f" +{len(addrs)-3} more" if len(addrs) > 3 else ""
print(f" {reg:8s} = 0x{val:04X}"
f" ({sval:6d}) @ {locs}{more}")
print()
if io_ops:
print(f"--- I/O Operations ({len(io_ops)} found) ---")
for addr, reg, _, typ in io_ops:
print(f" 0x{addr:06X}: {typ}")
print()
# DM analysis if provided
if dm_path and os.path.isfile(dm_path):
print(f"--- DM Cross-Reference ---")
dm_data = open(dm_path, "rb").read()
dm_size = len(dm_data)
print(f" DM file: {dm_path} ({dm_size} bytes,"
f" {dm_size//2} words)")
# Find all I-register loads and show what's at those
# DM addresses
for addr, reg, val, typ in constants:
if reg.startswith("I") and reg[1:].isdigit() \
and typ in ("reg1", "reg2"):
dm_byte = val * 2
if dm_byte + 20 <= dm_size:
sample = []
for j in range(10):
w = struct.unpack_from(
">H", dm_data, dm_byte + j * 2)[0]
sample.append(f"0x{w:04X}")
print(f" {reg} = 0x{val:04X} -> DM content:"
f" [{', '.join(sample[:5])} ...]")
print()
# Summary
n_code = sum(1 for w in words if w != 0)
n_mac = sum(1 for w in words if (w >> 22) & 3 == 3)
n_jump = sum(1 for w in words
if classify_word(w) in
("jump", "jump_cond", "jump_indirect"))
n_rts = sum(1 for w in words
if classify_word(w) == "rts")
print(f"--- Summary ---")
print(f" Total words: {len(words)}")
print(f" Non-zero: {n_code}"
f" ({n_code*100//max(len(words),1)}%)")
print(f" Multifunction: {n_mac} (MAC/ALU + memory)")
print(f" Jumps/Calls: {n_jump}")
print(f" Returns: {n_rts}")
print(f" DO loops: {len(loops)}")
print(f" I/O accesses: {len(io_ops) if constants else 0}")
def main():
parser = argparse.ArgumentParser(
description="Quick overview of ADSP-219x PM firmware.")
parser.add_argument("file", help="PM binary file (packed 3-byte)")
parser.add_argument("--dm", help="Optional DM binary for"
" cross-reference")
args = parser.parse_args()
if not os.path.isfile(args.file):
print(f"Error: {args.file} not found", file=sys.stderr)
sys.exit(1)
analyze_pm(args.file, args.dm)
if __name__ == "__main__":
main()