Files
adsp219x-re/docs/ROM_ANALYSIS_WALKTHROUGH.md
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

5.5 KiB

ROM Analysis Walkthrough

1. Identify Your Files

A firmware dump from an ADSP-2191 typically comes as separate files for each memory space:

  • PM files (Program Memory): 24-bit words — code and PM data
  • DM files (Data Memory): 16-bit words — variables, buffers

Only PM files contain executable code. DM files are pure data and cannot be disassembled as instructions.

If you have multiple sets (e.g. three directories), these may be:

  • Different firmware versions
  • Different memory banks (Block 0, 1, 2)
  • Boot loader vs application code

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:

0x0000: JUMP main       (Type 10a, opcode byte 0x1C)
0x0000: NOP             (entry at next instruction)

The interrupt vector table occupies the first ~128 PM words (0x000-0x17F), with 4-word spacing per vector. Most vectors contain RTI or JUMP to a handler.

6. Identify Code vs Data Regions

Code regions produce coherent disassembly: register loads, compute instructions, jumps, and loops in logical sequence.

Data regions (coefficient tables, lookup tables) produce nonsensical output: random-looking mnemonics, jumps to invalid addresses, many unk opcodes. Mark as data in r2:

Cd 300 @ 0x1000       # 300 bytes as data at offset 0x1000

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

CNTR = N;
DO loop_end UNTIL CE;
    MR = MR + MX0*MY0 (SS), MX0 = DM(I0,M0), MY0 = PM(I4,M4);
loop_end: ...

Look for: DO UNTIL CE + multifunction MAC instructions.

IIR Filter (Biquad)

Nested loops: outer over samples, inner over biquad sections. Contains ASHIFT for inter-stage scaling.

Initialization Sequence

Sequences of Type 6/7 loads (I/M/L register setup). Circular buffer initialization before entering a processing loop.

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

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