Compare commits
9 Commits
14694fcbc4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22da85de66 | ||
|
|
e790736868 | ||
|
|
ddccae54a0 | ||
|
|
70561276fe | ||
|
|
91172e86d8 | ||
|
|
276817fc48 | ||
|
|
2a8a952f22 | ||
|
|
26f01a5bdb | ||
| 82958cfe74 |
10
README.md
10
README.md
@@ -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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
524
docs/LARGE_ROM_ANALYSIS_WORKFLOW.md
Normal file
524
docs/LARGE_ROM_ANALYSIS_WORKFLOW.md
Normal 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.**
|
||||||
@@ -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
|
||||||
|
|
||||||
The reset vector is at PM address 0x0000. Typical patterns:
|
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).
|
||||||
|
|
||||||
0x0000: JUMP main (Type 10a, opcode starts with 0x1C)
|
### 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:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
e asm.arch = adsp219x
|
IO(addr) = Dreg / Dreg = IO(addr) instructions configure
|
||||||
e asm.bits = 24
|
peripherals: Serial Ports, Timers, DMA, etc.
|
||||||
pd 200 # Disassemble 200 instructions
|
|
||||||
pD 600 # Disassemble 600 bytes (= 200 instructions)
|
## 8. DM File Analysis
|
||||||
/x 1c00 # Find unconditional JUMPs
|
|
||||||
/x 16 # Find DO UNTIL loops
|
DM files contain 16-bit data words. These are not code.
|
||||||
/x 0a # Find RTS/RTI instructions
|
You can inspect them for patterns:
|
||||||
V # Enter visual mode
|
|
||||||
|
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
|
||||||
|
|
||||||
|
pd 200 # Disassemble 200 instructions
|
||||||
|
pD 600 # Disassemble 600 bytes (= 200 words)
|
||||||
|
/x 1c # Find unconditional JUMPs
|
||||||
|
/x 16 # Find DO UNTIL loops
|
||||||
|
/x 0a # Find RTS/RTI instructions
|
||||||
|
/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
|
||||||
|
|||||||
11
examples/build/regmap_test.dsp
Normal file
11
examples/build/regmap_test.dsp
Normal 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.
@@ -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
|
||||||
|
|||||||
@@ -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",
|
{
|
||||||
addr, rname);
|
op->mnemonic = r_str_newf ("DM(0x%04X) = %s",
|
||||||
|
addr, rname);
|
||||||
|
op->type = R_ANAL_OP_TYPE_STORE;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s = DM(0x%04X)",
|
{
|
||||||
rname, addr);
|
op->mnemonic = r_str_newf ("%s = DM(0x%04X)",
|
||||||
|
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",
|
{
|
||||||
f, mem, ireg, mreg,
|
op->mnemonic = r_str_newf ("%s, %cM(I%d += M%d) = %s",
|
||||||
reg0[dreg]);
|
f, mem, ireg, mreg,
|
||||||
|
reg0[dreg]);
|
||||||
|
op->type = R_ANAL_OP_TYPE_STORE;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s, %s = %cM(I%d += M%d)",
|
{
|
||||||
f, reg0[dreg], mem,
|
op->mnemonic = r_str_newf ("%s, %s = %cM(I%d += M%d)",
|
||||||
ireg, mreg);
|
f, reg0[dreg], mem,
|
||||||
|
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",
|
{
|
||||||
ireg + base, op_str, smod,
|
op->mnemonic = r_str_newf ("DM(I%d %s %d) = %s",
|
||||||
reg0[dreg]);
|
ireg + base, op_str, smod,
|
||||||
|
reg0[dreg]);
|
||||||
|
op->type = R_ANAL_OP_TYPE_STORE;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s = DM(I%d %s %d)",
|
{
|
||||||
reg0[dreg], ireg + base, op_str,
|
op->mnemonic = r_str_newf ("%s = DM(I%d %s %d)",
|
||||||
smod);
|
reg0[dreg], ireg + base, op_str,
|
||||||
|
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",
|
{
|
||||||
mem, ireg + base, mod, mreg + base,
|
op->mnemonic = r_str_newf ("%s(I%d %s M%d) = %s",
|
||||||
rname);
|
mem, ireg + base, mod, mreg + base,
|
||||||
|
rname);
|
||||||
|
op->type = R_ANAL_OP_TYPE_STORE;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s = %s(I%d %s M%d)",
|
{
|
||||||
rname, mem, ireg + base, mod,
|
op->mnemonic = r_str_newf ("%s = %s(I%d %s M%d)",
|
||||||
mreg + base);
|
rname, mem, ireg + base, mod,
|
||||||
|
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",
|
{
|
||||||
addr, reg0[dreg]);
|
op->mnemonic = r_str_newf ("IO(0x%03X) = %s",
|
||||||
|
addr, reg0[dreg]);
|
||||||
|
op->type = R_ANAL_OP_TYPE_IO;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s = IO(0x%03X)",
|
{
|
||||||
reg0[dreg], addr);
|
op->mnemonic = r_str_newf ("%s = IO(0x%03X)",
|
||||||
|
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",
|
{
|
||||||
addr, reg0[dreg]);
|
op->mnemonic = r_str_newf ("REG(0x%02X) = %s",
|
||||||
|
addr, reg0[dreg]);
|
||||||
|
op->type = R_ANAL_OP_TYPE_IO;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
op->mnemonic = r_str_newf ("%s = REG(0x%02X)",
|
{
|
||||||
reg0[dreg], addr);
|
op->mnemonic = r_str_newf ("%s = REG(0x%02X)",
|
||||||
|
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
251
tools/analyze_dm.py
Executable 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
270
tools/dm_xref.py
Executable 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
334
tools/firmware_overview.py
Executable 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()
|
||||||
Reference in New Issue
Block a user