Compare commits

...
80 Commits
Author SHA1 Message Date
pancake ee031a7021 vc4: upstream cgen tables, assembler, ESIL emulation and analysis
- Update the cgen generated sources to puppeh/binutils-vc4 (branch vc4),
  which decodes the vector unit and fixes sized ld/st, scaled immediates
  and the signed 12 bit offsets
- Decode 80 bit vector insns (16 byte buffer, 10 byte max op size)
- Decode what the tables miss or get wrong: tbb/tbh, 48 bit b/bl pc+o32,
  the missing 48 bit alu ops, and the shift of 32 bit addscale immediates
- Treat the 16 bit sp offset of lea/add sp as signed, print float6
  immediates as values so they assemble back
- Add an assembler on top of the cgen one, pc relative operands, relaxed
  wider forms, 48 bit fallbacks, and a check that rejects encodings cgen
  silently truncates
- Replace the analysis with a text driven one that emits ESIL for all
  scalar insns: flags in sr, conditions, compare and branch, ldm/stm,
  table branches, floats and conversions; vector insns are typed only
- Rewrite the pseudo plugin for the upstream syntax
- Drop libvc4 from the builds, the plugin never used it
2026-11-04 20:19:45 +01:00
pancake 0a0327aa45 vc4: pseudo compare-and-branch forms and meson include path fix
- Translate addcmpb<cond> and 3 operand b<cond> into compare and goto
- Render the never-taken f condition as false and fix 'x -N' spacing
- Resolve ../../include with realpath since p/vc4 is usually a symlink
- Document the separate arch.vc4 and asm.vc4 XPS plugins
2026-11-04 18:41:02 +01:00
pancake 9870775224 Add r2plugin xps interface and pseudo for vc4 2026-11-04 18:31:28 +01:00
pancake d5e7a28186 Ignore vc4 plugin build artifacts and editor caches 2026-11-04 11:33:25 +01:00
pancake c9571534b9 Add standalone Makefile to build the vc4 asm/anal plugin
Allows building arch_vc4 from libr/asm/arch/vc4 directly instead of
going through the whole r2 build. Mirrors libr/asm/p/vc4.mk but
resolves the sources locally, links libr/libvc4 objects and the
libr/asm/p/arch_vc4.c glue, and adds an install target.
2026-11-04 11:33:09 +01:00
pancake 5fff0016a3 Support bits:// packed and be16 unpacked roms for r2tama 2026-11-03 18:30:00 +01:00
pancake f2199032a7 Add Tamagotchi P1/P2 ESIL emulator 2026-11-03 18:10:09 +01:00
n0p 5d28dc6de4 md32: tests and docs for the scq16 table update and the public test vectors
test/test_scq16.py (267 checks) and test/test_md32.c cover the new forms, the open marker, the broadcast ports, the confidence levels and ctz(0). test/scq16_vectors.json carries 195 public bytes-only vectors with the expected result in the big-endian regime of the harness and in the little-endian regime of the core data memory; test/test_scq16_vectors.py runs both and the Makefile adds it to make test. doc/scq16.md, README and CHANGELOG describe the hypothesis file, the confidence levels and the open question on ctz(0).
2026-11-03 10:17:01 +01:00
n0p 645f4154ff md32: scq16 table update (229 forms), the open-reading marker, the broadcast ports and the confidence levels
jr rn (6f ec) reads a 5-bit register field (r0-r31); btst rd, rd, #pos (67 f8-fb) has a 5-bit position; ctz is its own form without the hypothetical flag and sqr? belongs to the op41 placeholder; and rd, rs, #0x1|#0x3|#0xf (2 bytes), add rd, rs, #-1 (2 bytes) and the 4-byte sb are high, the 4-byte btst rd, rs, #pos is medium (settled by execution results).

The exporter marks an open reading (a family placeholder or a provisional name) with SCQ16_FL_OPEN and gives an open alternative without a template the uniform ESIL marker REF,ALT,open; export --hyp applies the declared plugin hypotheses of isa/scq16_hyp.json, which is empty: the packing ESIL of op41.fld rd, rs, rt, #pos is carried by the ISA source, still flagged hypothetical.

The scalar ports v0-v31 are the broadcast ports of the vector register file: a write by vbcast / vbcast.i reaches the 64 lanes of vr_k; the custom op open has no effect; aoj reports open, reading, hypothetical and confidence per unit. ctz of a zero operand gives -1 under the table ESIL; whether the hardware returns 32 is an open question. asm: the pseudo rule of op41.fld.
2026-11-03 10:17:01 +01:00
n0p 899e3344ff md32: document the three encodings the plugin serves and its scope
The README introduction states what MS15E30, uSIP and SCQ16 are and how they relate: two MD32 encodings and one distinct byte-granular encoding that shares the MD32 scalar programming model, which is why it is a cpu mode of this plugin; the remaining DSP cores of the same modem use an unrelated SIMD ISA and are out of scope. doc/scq16.md carries the same note.
2026-11-03 08:31:29 +01:00
n0p 6493987c72 md32: tests and docs for the scq16 table update and the vector unit model
test/test_scq16.py: bytes-only checks of ctz (established) next to the
provisional sqr?, sra? rd, rd, #pos, vrd? rd, vb1[x], op41.fld with three
registers, the provisional and? masks, lw.post of 2 bytes with a 4-bit
destination, the signed post-increment of the 6e page, vector ops with
non-zero fields, a vector head and B slots with the new forms; analysis
(ctz named, sqr? / sra? / vrd? provisional, op41.fld with rt, the lw.post
destination r11); the ESIL of ctz / sqr? / sra? / vrd?, the .post
templates writing rd last (rd == rb), the signed post-increments and the
vector unit model (vset.a / vmem.set, v.mem.ld with a count and with the
whole register, v.mem.ld + v.mem.st through the VU, v.move.mv back and
forth, F2 >= 4 without effect, a bundle with a vector load, unmodelled
vector ops stepped over).  test/test_md32.c: ctz, lw.post r11, sra?,
v.mem.ld with its fields.  doc/scq16.md: the new forms, the .post
convention, the VU model and its limits, the register profile with vr*,
the calling convention (r16-r31 callee-saved, r0-r15 caller-saved, lr
saved by the functions that call); README "SCQ16 mode": the VU model,
the provisional sqr? / sra?, the calling convention; CHANGELOG entry.
2026-11-03 08:31:29 +01:00
n0p ba096f3546 md32: scq16 table update (402 bit alternatives), the .post convention and the vector unit model
isa/scq16_isa.json is re-exported by the `export` command of
tools/gen_scq16_tables.py: 228 forms / 402 bit alternatives.  ctz rd, rs
(41 f3 0x22) is an established operation with the ESIL rd = 31 - clz(rs
& -rs); new provisional readings: sqr? rd, rs (41 f3 0x23, rd = rs * rs),
sra? rd, rd, #pos (67 fc-fd), vrd? rd, vb1[x] (1a xy, a 32-bit read of
the VU scalar bank with rd = w[3:0] + 1), and? #0xf000? (67 98-9b and the
2-byte 00/01 with k = 6) and and? #0xc000? (67 b4-b7); op41.fld rd, rs,
rt, #pos reads its third field as a register; lw.post rd, [rb] += 4 (21)
has a 4-bit destination (r1-r16); the 3-bit post-increment of the 6e
.post family (sh.post, lw.post, lhu.post?, lbu.post) is signed (x = 4..7
is -4..-1 units); sra rd, rd, #imm (09) is established; the load
templates with a post-increment write rd last, so with rd == rb the
loaded value wins (a declared convention: the public MD32 documents rd
written first).

scq16_vu.inc.c (new, included by scq16_arch.inc.c): the vector unit
model of the ESIL.  The register profile gains the vector register file
vr0-vr31 (128 bytes each, exposed as the four 256-bit quarters
vrK_0..vrK_3; lane 16q is the low 16 bits of quarter q); vp0-vp7 (written
by vmem.set and the hypothetical vcfg4c? #0xc) and va0-va7 (written by
vset.a) are the existing scalar ports.  The custom ESIL op vop models the
three established vector operations and nothing else: v.mem.ld copies n
halves from DM[p] into vr[F1 >> 2] and v.mem.st copies n halves from vr0
to DM[p], with p = vp[2 * F2 + F3[4]] when F2 < 4 and n = va[A] when
1..64 (otherwise the whole register), post-incrementing the pointer by
the bytes accessed; v.move.mv copies vr[F3] into vr[F1].  A memory op
with F2 >= 4, the pair mode B = 2 of v.move.mv and every other op9 pop
their operands and have no effect.  The data memory is read and written
through the ESIL memory callbacks; the vector registers are kept in the
register arena.

asm_md32.c: pseudo rule of sqr (rd = (rs * rs)).  Makefile: the new
include file among the dependencies of the arch plugin.
2026-11-03 08:31:29 +01:00
n0p bfab794507 md32: tests and docs for the scq16 table update
test/test_scq16.py: bytes-only checks of the page 42 (cmov.ge, cmov.eq
with a register, the provisional cmov.*?, the remaining sel? codes), the
67 page (mv, bset/btst rd, rd, #pos, and #0x3fff), lbu.post with 5-bit
registers and the 23 page with a signed post-increment, the 6-byte and
#imm16, ctz?/clb?, vcfg4c? #0xc, ld46?, the established vector names
(v.move.mv, v.mem.ld, v.mem.st) next to the provisional ones, addsp 14 8x,
halt, sys41, lbu? off(sp), the 4-byte beq of 5c 40-6f, j.d1 decided by its
target (also in a B slot), the ESIL of the new forms, negative frame
offsets, halt/sys41 without effect and ret.d2 / jr.d2 across two aes
steps (a packet in the first slot).  test/test_md32.c: cmov.ge, mv 67 c8,
j.d1 by target, lbu.post of the 23 page, v.mem.ld.  doc/scq16.md: the
j.d1 length by target, the vector names (established / provisional), the
page 42, 46, 4c, 41 f3, 22/23, 67, 6e and 74 families, the ESIL
conventions (.d2 across two aes steps, 32-bit addresses, no-effect units,
the memory order inside a packet), the hypothetical alternatives of the
table, the limits (rd == rb post-increment, the vector unit), the
data-space bases; README "SCQ16 mode" updates; CHANGELOG entry;
tools/scq16_flags.r2 (DM, I/O window and DM2 bases as flags).
2026-11-02 16:26:48 +01:00
n0p 619f60a7df md32: scq16 table update (228 forms), decoder rules and generator features
isa/scq16_isa.json is re-exported by the `export` command of
tools/gen_scq16_tables.py: 228 forms / 400 bit alternatives.  The ESIL
templates, the delayed transfers (SETJT/SETD) and the hypothetical flag
come from the form-description module itself (its HYP table), so the
exporter applies no overlay of its own.  The vector names carry the ? of
a provisional name in the table string and none when the operation is
established (v.move.mv, v.mem.ld, v.mem.st: vector.firm_op9).  New or
renamed forms: cmov.ge rd, rs, #imm, ra; cmov.eq rd, rs, rx, ra (register
form); the provisional cmov.leu? / cmov.ltu? / cmov.lt? / cmov.le?; mv rd,
rs (67 c8); bset / btst rd, rd, #pos (3 bytes); lbu.post rd, [rb] += inc
with 5-bit registers (6e q2) and the 23 page as lbu.post rd, [rb] += simm3
(-4..-1); and rd, rs, #0x3fff (67 a4-a7); the 6-byte and rd, rs, #imm16
(74 sub 1); the provisional ctz? / clb? (41 f3); vcfg4c? #0xc with the
hypothetical p_k <- rs; ld46? as a hypothetical byte load with a
post-increment; lbu? rd, off(sp) (ex lb?); addsp sp, #imm (14 8x-fx); fxts
without the ?; halt and sys41 with the empty ESIL; the length leaves make
5c 40-6f a 4-byte beq rA, rB.

scq16_decode.c: a first byte 56 with the top two bits of the second byte
clear is the 4-byte j.d1 when its 22-bit target (from the packet start in
a B slot) is a plausible code address, else a formless 2-byte unit
(without a function table the plugin cannot decide by membership).  Every
ESIL memory access cuts its address to 32 bits: the ESIL machine computes
in 64 bits and aes does not mask addresses, so a negative frame offset
over sp wrapped above 4 GB.  The ? of a vector name now comes with the
table string (scq16_vname_firm).  scq16_arch.inc.c: a unit whose ESIL is
the empty string (nop, halt, ieon/ieoff, loopend, vwait?, sys41) no
longer blocks the ESIL of its packet; the override hook counts as the
unit ESIL; opex named / provisional of a vector op follow the table name.

asm_md32.c: pseudo rules of cmov.ge / cmov.leu / cmov.le (the ? of a
provisional name is stripped before the lookup), sys41, ctz / clb.
2026-11-02 16:26:48 +01:00
n0p f4f11b90c3 md32: tests and docs for the uSIP table update 2026-11-02 06:43:08 +01:00
n0p f58de93399 md32: uSIP table update (760 forms), decoder rules and generator features 2026-11-02 06:43:08 +01:00
n0p 58a5ac18e6 md32: English output text for the scq16 loop, jump and trap forms 2026-11-01 20:21:45 +01:00
n0p 9b0adabca8 md32: tests and docs for the scq16 mode
test/test_scq16.py exercises cpu=scq16 inside r2 with loose bytes only
(disassembly of units, packets, vector operations and bundles; analysis;
ESIL; the pseudo parser) and runs from make test; test/test_md32.c gets
three decode checks.  README section "SCQ16 mode", doc/scq16.md and the
CHANGELOG entry.
2026-11-01 20:21:45 +01:00
n0p 219b1f7c07 md32: scq16 cpu mode (table, decoder, arch and asm)
Add cpu=scq16, the SCQ16 variant of the MD32 family with a byte-granular
packet encoding, as a third cpu of the plugin.

isa/scq16_isa.json is the ISA table (218 forms, 389 bit alternatives, the
length-rule leaves of stand-alone units and packet heads, the vector op9
classes and provisional names, the register names) and
tools/gen_scq16_tables.py generates scq16_opcodes.h from it (the VU scalar
ports as ESIL registers, vector operations as the custom op vop, delayed
transfers as SETJT/SETD, the no-effect system forms and the hypothetical
readings flagged).

scq16_decode.c implements the length and packet rule by first-byte leaves
(stand-alone units, packet heads read shifted by one bit with the borrowed
bit of the B slot, masked orphans, 4-byte vector operations, vector heads,
8-byte bundles), the form match over the transformed unit, the operand
text and the ESIL expansion with the parallel-packet register remap and
the VU scalar-port registers.

scq16_arch.inc.c carries the analysis of the SCQ16 decode (types,
jump/fail with the delay slots counted in instructions, do as a loop
reference, loads/stores, stack ops, opex with the unit/packet/bundle
structure), the ESIL (the scalar forms; a packet as A then B with the
registers B reads and A writes copied to temporaries first; delayed
transfers through SETJT/SETD; the VU ports as registers; vector operations
as the custom op vop, a no-op) and the register profile (sp/lr as
csr14/csr15, a0, the loop registers, the VU ports).  arch_md32.c
dispatches decode, info and regs on cpu=scq16 and registers vop through
esilcb; asm_md32.c gets the SCQ16 pseudo-syntax (packets as
parallel { a; b; }, memory, stack, bit-field and transfer rules).
2026-11-01 20:21:45 +01:00
n0p a1b8861f9d md32: tests and docs for the uSIP table update
Plugin vectors for the loop count, the F' = 0 / h = 1 / m = 1 loops
(validated and unverified), the 4- and 3-unit bz.s (validated and unknown),
beq9sp, the bgt rA, #-1 cell, the W6 h = 1 cells and the loop guard, op2z?,
the h = 1 shifts, the 16-bit lea, the splits (R-slot4n, R-op4imm in 2 and
more units, S0-slot, S3), op72?, saturate?, sat40 H H T T, R-waw with the
P2W prefix, R-b111pre, R-b111ctl, R-bsnext, the omitted weak cell of
R-brHHTT, sub r14, st.w [sp], st.abs hi F' = 0, addq / cell E by value,
mtcsr of the accumulator CSRs, R-1eX0F0, R-movi6d; the lea, loop#, mtcsr,
movi #-32768 / #-32767, ld.h / ld.hu of R-1eX0 / R-ldF0 / R-hhttF0 / R-F0d,
op2z? and two R-waw vectors updated to the new readings; pseudo vectors for
the new rules; README, doc/usip.md (the loop cells, the post-layout splits
and the fusion, the explicit candidates, score rules and layout
constraints, the cells added with the table, the unverified fallback and
the order of the packet-level passes) and CHANGELOG.
2026-11-01 18:45:09 +01:00
n0p 85f40b5d6c md32: uSIP table update (726 forms), decoder rules and generator features
Regenerate isa/usip_isa.json and usip_opcodes.h (52 forms added, 13 retired,
14 modified): the loop #N count is the collected head field (bits 13:5, no
+1), loop #N with the F' = 0 tail exactly at +2, with h = 1 (N >= 128) and
with m = 1 (N >= 256, printed loop #N?), bz.s in 4- and 3-unit packets, the
split entries of the slot family stslot / stslot? / ldslot? (F-slot0,
S0-slot, the S3 cells ldslot/stslot #48-63), the fusion entry
op72? rA, #k, asr#b, op2z? rA, lsl/asr r8-r15, #B, the W6 cells
ld.h/st.h rB, [r8+rrr] and [rA]+ with h = 1, sub r14, #f12/2 with any F/F'
(spfix_sub_sp retired), st.w [sp], #imm14, st.abs with the hi F' = 0,
add rB, rB, #imm27 / st.h? [rB], #sat selected by the immediate value (also
the cell-E triples), add? rB, r15, #rot12 and ld.h? / ld.hu? rB, [r15+off]
with the F' = 0 tail, saturate? rd, rs1, movi #simm6 in the delay slot of a
validated branch, mtcsr #0x30-0x3e, ld.h / ld.hu / lea / movi #-32768 /
#-32767 / ld.hu [rA+rB] without the ?; the constraints of beq_imm8_d (the
op 9 beq.d with base sp and the adjacent tail is not a branch) and of the
seven op a *_s0f1 / *_imm8_F0 entries (the bgt rA, #-1 head class takes
only the adjacent S = 1 tail); the ABI passes eight arguments in r0-r7.

The generator emits the flags FUSE, SPLIT / SPLITBARE (with a USIP_FORM_*
index for every split and fusion entry), the constraint op instr_index,
any with up to four alternatives that may be a context condition or an
instruction position and six new context conditions; the loopn operand
type is the collected field.

The decoder keeps the score in twentieths (al1 = 0.3), adds the explicit
candidates loopcand / R-brHHTT / R-b111pre / R-b111ctl with the b111_layout
context extended to them, the bz.s rules of 4- and 3-unit packets (the rest
re-parsed as the delay slot, the 3-unit one with the target beyond the
packet and no write of rA), the S3 renames and the split pass of the slot
family with the split-tail reading and the nowaw guardian, the fusion after
the renames, R-movi6d, the R-w6h1 loop guard evaluated with the packet being
parsed, the R-loopA adjacency (.word at any distance but +2), rf0dmidwaw,
the score rules R-waw / al1 / R-bsnext / P2W-P2H prefix / sat40 H H T T /
rf0dpen and the instruction position of the renames. The loop entries with
the F' = 0 tail at +2 and the h = 1 heads with the S = 1 tail (and the
adjacent F = 0 tail) are accepted unverified; the m = 1 (loop #N?) and the
other h = 1 entries are not.

The arch plugin reports the stores of an immediate ([rB] / [sp], #imm) with
the value in val and [sp] as a stack access; the new mnemonics keep their
kinds (op2z? / op72? / saturate? are unknown operations). Pseudo syntax for
the stores of an immediate (also to [sp]), the absolute st.abs / ld.abs and
the ? of a count (loop#? #256?).
2026-11-01 18:45:09 +01:00
n0p a8d4a6a520 md32: tests and docs for the uSIP table update
Plugin vectors for the F = S = 0 and R-br0 cells (validated by the session,
rejected with fall-through, unknown backward, unverified forward), ret.s.d,
movi #-1 with F = 0, the op 2 shifts, the open b.s rejected and unverified,
bz.s P5, the m = 1 slots, loop.b1? and the ?ss=N loops, movi? #-32767, the
asr16 clamp, the bit-field pair and the BF_SEL cells, ld.bu/st.b with the
byte displacement, ld.b/st.h with F != S at +2 and the undo at +1, the
ld.h?/ld.hu? of R-hhttF0 / R-F0d, the sp adjustment pair, the ld.w?/st.w?
word classes, .stimm16? and mtcsr? with a named tail; the loop, open b.s,
mtcsr, movhi and save vectors updated; pseudo vectors; README, doc/usip.md
and CHANGELOG (the target validation filter, the address views, the F = 0
branch cells, the split and context groups, the new tie-breaks and what the
plugin can see of the guards).
2026-11-01 18:45:09 +01:00
n0p e108f90f79 md32: uSIP table update (687 forms), decoder rules and generator features
Regenerate isa/usip_isa.json and usip_opcodes.h (56 forms added, 3 retired,
71 modified): the b<cond>.d cells with head F = 0 (bne.d, ble.d, bgeu.d,
bge.d, bne.u8.d, beq.u8.d, bleu.d, read only in the (B class, tail distance)
cells of usip_br0_cells; the five weak cells of usip_br0_cells_weak are never
taken), bgt.d of the F = S = 0 cell back in the table, ret.s.d, loop.b1? and
the ?ss=N loops, bz.s with bit 3 = 0, the ld/st type 111 ld.bu / st.b with
the byte displacement (st.b? retired), ld.b / st.h with F != S of op 1
exactly at +2, ld.h? / ld.hu? with the F' = 0 tail as the second head of
H H T T and by the deferred tail of a head with base sp, the bit-field pair
in H H T T and the BF_SEL cells renamed from .bf?, the 16-bit op 2 shifts
lsl/lsl?/asr/asr? (addi4 retired), ldslot/stslot with m = 1, .stimm16?,
ld.w?/st.w? for the f12 = 1 / 3 mod 4 word classes, add r14 / sub r14,
movi? #-32767, movi rA, #-1 with F = 0, mtcsr? of the accumulator CSRs with
a named 16-bit tail; the known target as a validation filter on the
F = S = 0 cells, bne.d #-1, the loops with the F' = 1 tail, the open b.s and
the near b, with fall-through on failure (USIP_F_GUARDFT); the operand
conventions of six families revised. Thirty-one entries are post-layout
renames.

The generator emits the operand type offb, the flags GUARDFT / B111 / F0CTX
(flags is ut32), the constraint ops br0_cells / br0_cells_live, bf_valid,
the guards tail_named16 and target_packet_start_any_or_function_start, the
any sub-operations packet_len, guard, range and br0_cells, the large value
sets (usip_value_sets), the new context conditions and the tables
usip_br0_cells, usip_br0_cells_weak and usip_c48sym_table; form names are
sanitised into C identifiers.

The decoder implements the fall-through guards, the fifth guard kind with
the optional packet_start_any callback, the R-br0 cells by B class and tail
distance, the b111 group in the 2-unit packet and in H H T T (which now
takes tt = 11 tails), the context conditions b111_layout, hhtt_second_head,
f0d_triple, bfpair, bf_dst_not_written and rest_named_no_q, the f0ctx pass
on the .word pairs and the re-read of the R-F0d middle unit, R-op1fs, the
bz_s_P5 context rule, the m = 1 R7 slots, P2H, R-lr16, R-align48 and
R-c48sym in the score, the b.s guard admitting a function start and the
loop F' = 0 rule by field; immediates glued to a letter or '=' print in
decimal. The forms whose guard is a validation filter keep their reading
unverified when the target is unknown (conditional branches only forward);
a definite "not a packet start" rejects them.

The arch plugin initialises UsipGuardCtx by field, separates "not a packet
start" from "unknown" for backward targets with the lowest packet start
decoded in the session, reports offb as a disp operand in opex and sets the
stack deltas of addsp and the add r14, r14, #imm / sub r14, #imm pairs.
Pseudo syntax for the 2-operand lsl / asr.
2026-11-01 18:45:09 +01:00
n0p 5b9a32f8c4 md32: tests and docs for the uSIP table update
Plugin vectors for the 14-bit word displacement, nop.ctl, the bit-field
family, mtacc?, movi? #-32768, sgt #-1, the register bset/bext, the S0F1
branches (unverified, validated by the session, not validated), the r15-based
loads, .ldF0?, the F' = 0 ld.h?, the 48-bit value table, .3eX0?, the
lsl/sext16/zext16 renames, cop with addi, .hi, movhi with the tt = 10 tail
and .op4t0; the op8 slot split vector updated; pseudo vectors; README,
doc/usip.md and CHANGELOG (the post-layout renames, the K-tie cascade and
what the plugin can see of it, the extended target-guard fallback).
2026-11-01 18:45:09 +01:00
n0p 71ab77c46b md32: uSIP table update (634 forms), decoder rules and generator features
Regenerate isa/usip_isa.json and usip_opcodes.h (52 forms added, 12
retired, 103 modified): the 14-bit word displacement of the two-unit
ld.w/st.w, nop.ctl, the bit-field family fxtu/fxts/fup, movi? #-32768, the
register bset/bext, mtacc?, sgt #-1 with F = S = 1, addi #32 next to cop,
the 16-bit lsl/sext16/zext16 variants, the r15-based loads, .ldF0? and the
F' = 0 ld.h?/ld.hu?, .3eX0?, the b<cond>.d cells with the [0][1][tt] tail at
+2, movhi with the tt = 10 tail, the 48-bit constants admitted by value, the
double-class split; the .hi/.hilo pair entries and every not_hi1010
constraint are gone. Twenty entries are post-layout renames.

The generator emits the rename_from lists, the constraint ops tail_adjacent,
ieee_short and the 48-bit value table membership, the tail_adjacent
sub-operation of any, the context conditions cop_rA_in_packet and
not_c48_lo_follows, USIP_MAXVALS 12 and the tables usip_never_tail,
usip_never_head_with_tail and usip_c48_table; the rename entries are exempt
from the 16-bit position-class checks.

The decoder implements W14, the never-tail and never-head-with-tail units
from the tables, H1 (the forced hi(1010) decode is gone; .hi/.lo only after
a 48-bit-family head in the packet), the 48-bit value table in the partial
admission, P2W (no alignment penalty for the 2 mod 4 word offsets in the
H H T T layout), callin through a new guard callback, the post-layout
renames and the K-tie cascade: dedup and nowaw on the tied readings of the
packet, du2 over the neighbouring packets the caller supplies (packet_at /
branch_target callbacks, first-pass readings with USIP_GUARD_NO_CASCADE).
UsipPacket reports how a tie was decided; a failed guard of the op C
compare-and-branch cell prints the pair as .dsp. The unverified fallback
accepts the S0F1 cells whose unverified readings are right in nearly every
case and loop rX with the F' = 0 tail when the head has F = 1 and the S = 1
tail sits at +2.

The arch plugin answers the cascade callbacks with the packets decoded
earlier in the session, the first-pass readings of the packets inside the
bytes at hand and the branch targets seen so far, reports the tie-break in
opex, sets ireg for the r15-based loads and types fxtu/fxts as cast and fup
as or. Pseudo syntax for fxtu/fxts/fup, mtacc, the 2-operand addi,
sext16/zext16 and asr16; nop.ctl reads nop.
2026-11-01 18:45:09 +01:00
n0p b6da363492 md32: tests and docs for the uSIP table update
Plugin vectors for loop# (validated at +1 and +2, unverified), b<cond>.l
(validated, unverified, not taken, 64-bit), the signed 64-bit pairs, the
negative offsets, seq/sle/sgt, movhi, the 16-bit first-slot st.h, the
deferred st.h and its undo by a validated b.s, H H T T, the op8 split, the
0e10 trio, cop with F = S = 0, xor, movi rA!, bset rd!, osl and the
cmulr16/cmuli16 names; the jr, ld.d, movhi and bank-bit vectors updated;
pseudo vectors; README, doc/usip.md and CHANGELOG.
2026-11-01 18:45:09 +01:00
n0p 4932f31af9 md32: cmulr16/cmuli16 names follow the little-endian data memory
The names of 0xDA00 (cmulr16, a*c - b*d) and 0xDC00 (cmuli16, a*d + b*c)
follow the little-endian layout of the data memory: the high half of a 32-bit
complex word is at +2 and holds the real part. The earlier assignment assumed
a big-endian data memory.
2026-11-01 18:45:09 +01:00
n0p cdd0753019 md32: new uSIP mnemonics and conditions in the arch and asm plugins
Conditions bequ/bneu of the 48-bit forms, types for sle (cmp) and osl (shl),
val = N for loop #N, the signed offsets as disp operands in opex; pseudo
syntax for loop#, sle, bequ/bneu, the 2-operand xor, mfacc, the bset family
and osl; the ? marker is stripped repeatedly and ld.d/ld.w are not
sign-extended.
2026-11-01 18:45:09 +01:00
n0p 8b98f544d7 md32: uSIP table update (594 forms) and prior-free packet layout
The uSIP table grows to 594 forms (137 added, 18 retired, 76 modified): the
48/64-bit b<cond>.l prefix forms with the complete condition table, loop #N,
the op 6 set-on-compare family, movhi by its 20-bit value, the 48-bit
constants by one formula, the signed ld.d/st.d, the closed negative
half-word and word offsets, the deferred st.h tail, the position cells and
the slot split.

The generator gains the operand types offh/offw/loopn and the redefined
movhi, the flags ADJDEF/PREFIX/RENAME/HHTT, 8 ranges per field, 64-bit
entries, the constraints gt/odd/packet_len_ge/head_index/head_pos_ge/ctx and
the b.s guard, any with up to three alternatives, the bcc48 condition table
and save w=4.

The decoder matches the prefix forms before the layout, ranks the readings
with the prior-free total order (s0, I, Fv, -A, K) instead of the enumeration
order and the group count, and implements the grammar rules (5e0d never a
tail, the deferred st.h tail with its undo and the validated-b.s guard, the
.d SP-negative rejection, the 3-unit and H H T T candidates with the forced
decode of hi(1010) heads, the 0e10 trio, the loop #N adjacency, the op8
split), the new penalties, the deferred constraints with fall-through, the
late guard evaluation, three tails for the 64-bit forms and the unverified
fallback for the prefix forms and loop #N.
2026-11-01 18:45:09 +01:00
n0p 4ad1a62408 md32: docs for the uSIP table update
README uSIP mode: the F' = 0 control tails, the target guards and what the
plugin can check, the new mnemonics and analysis types.  doc/usip.md: the
packet-model rules R0/R7, the table additions (movhi, base/offset, scale, the
new constraints, kinds io/jr), confidence census, ABI without a classic CSR
bank, and a section on the target-guard knowledge and the fallbacks.
CHANGELOG entry.
2026-11-01 18:45:09 +01:00
n0p 39458c3b5b md32: tests for the uSIP table update
test_plugin.py: vectors for ior/iow, nop32, the 32-bit ret of the sys shape,
jr?, addsp, ld.d?, the first-slot ldslot/lea? forms, movhi? and the guarded
3-unit control packets (loop with the tail at +2 and the end at the next
packet, beqz.d validated by a forward packet start and read as data when the
target is not one, call with the F' = 0 tail read unverified); the mov32 and
ld.b vectors show the bank bit on the register it applies to (r15!).
test_parse.c: the new pseudo rules.
2026-11-01 18:45:09 +01:00
n0p b07fcf44b1 md32: new uSIP mnemonics in the arch and asm plugins
arch_md32.c: operation types for the forms of the updated table: io for
ior/iow (ireg = the port register), rjmp for jr? rB (register jump with the
delay slot of a return), ret for rti, trap for trap?, nop for nop32, add for
addsp, lea for lea?, cmp for sne/scmp?, cast for extu, refptr 8 for the
ld.d?/st.d? pairs; the mnemonic test ignores the trailing ? of the medium
forms; extended CSR names in opex.

asm_md32.c: pseudo syntax for ior/iow, mfcsr/mtcsr, addsp (sp += / sp -=),
ldslot/stslot, jr, nop32, sne, scmp, movhi, extu, cop, rti, ieon/ieoff, idle,
isync, lpexit, trap and the 64-bit loads/stores; the trailing ? marker of
lea?/jr?/call? is ignored.
2026-11-01 18:45:09 +01:00
n0p 58536ef4e9 md32: uSIP table update (475 forms), generator features and decoder rules
isa/usip_isa.json goes from 386 to 475 forms (110 added, 24 retired, 140
modified): port I/O ior/iow instead of mfcr/mtcr, mtcsr and mfcsr with F' = 0,
the sys shape split by its tail field (cop, rti, lpexit?, isync?, ieon, ieoff,
trap?, sys?, ret, ret?, jr?, call rB), nop32, cmov r15, add rB, rA, #rot12 for
the whole op 0 cell, sne, scmp?, movhi?, extu, movi #-1, addsp, lea?, the
stack-slot closers (ldslot/stslot, .slot?, lsl #1/#2, movi #simm6, .bit4?,
.ext8?), ld.d?/st.d? (64-bit pairs), st.b?, ld.hu for type 110, unsigned
12-bit ld/st offsets, ld.bu in the class-E cells, per-operand bank bits
(rB! by x, rA! by m, rs1! by ss), save w=2 = four words; the 48-bit call with
the far tail is retired (those packets are call/b with the F' = 0 tail at +2).

tools/gen_usip_tables.py: the movhi operand type, base as a constant added to
register/immediate fields, scale on immediates, the ge and any constraints,
membership masks for the large CSR sets, the target guards as constraint
records, flag USIP_F_ADJ2, kinds io/jr, the sys name table, extended CSR names
and kind names.  The header stays byte-identical on regeneration.

usip_decode.c: packet guards, adjacency rule and tie-breaks.  F' = 0 control
tails exactly at +2 (otherwise data), target guards evaluated after the mask
match (32-bit forms: a failure is .word, no fall-through; the 16-bit call.s
falls through), packet_len constraints with the real packet length so that
the stand-alone closers never count as named 16-bit ops, closers matched with
bit 15, rules R0 (the final closer may be its own group) and R7 (first-slot
ldslot/stslot/lea? when the rest of the packet is named), the 1dX6 + named
tail and validated-guard tie-breaks; the enumeration order is unchanged.

The decoder asks the caller about branch targets through UsipGuardCtx.  The
arch plugin answers packet starts from the stream: the packets decoded so far
in the session and a scan of the bytes radare2 hands it (64 bytes of
lookahead).  Function starts are not available to an arch plugin, so the
decoder applies a documented fallback: the call / b / compare-and-branch with
immediate forms whose F' = 0 tail sits at +2 and beqz.d are read unverified
(trailing ?, confidence medium, opex.target_guard = unverified); loop, far b,
bnez.d, the register compare-and-branch and the open-packet call.s are
treated as a failed guard.
2026-11-01 18:45:09 +01:00
n0p 3dfb0180d0 md32: changelog note for the emulator test expectation in usip mode 2026-11-01 18:45:09 +01:00
n0p 73831a1ee2 md32: emulator test expects the delayed-return stop in usip mode
With the packet decoder, 5ef9 is ret.d and the rest of the packet is its
delay slot, so md32-run stops with unsupported_delay_slot_execution and
zero completed steps, exactly as for the MS15E30 delayed branch.  README:
make test passes against radare2 6.2.5 on Linux as well.
2026-11-01 18:45:09 +01:00
n0p 68e9f6dfe3 md32: six group-5 16-bit forms missing from the MS15E30 table
rem16, ctz16 and the four complex half-word products (cmulcr16, cmuli16,
cmulr16, cmulci16) occupy 0xCA80-0xCABF, 0xCB00-0xCBFF and 0xD800-0xDFFF,
which the public table leaves undecoded.  Names and operand order follow
the neighbouring group-5 forms; the semantics are inferred from usage
(rem16 as a non-negative modulo, the cmul* variants as the cross-sum and
straight-difference terms) and are marked as such in the source.
2026-10-30 19:53:17 +01:00
n0p 2c54fda563 md32: document the uSIP mode
README: the uSIP mode section (packet model, what one pd line is,
mnemonics and operands, analysis, limitations) replaces the partial
support notes; build note for radare2 6.2.2.  CHANGELOG entry.
doc/usip.md: table format, constraint and context-rule semantics,
confidence levels, ABI, known limitations of the table-driven reading.
2026-10-30 16:28:21 +01:00
n0p a0ccecf534 md32: uSIP packet vectors in the plugin test
Exercise the usip mode on real packets: conditional branch with its tail,
b.s with a 48-bit delay-slot op, call with the S S O O layout (.unit
tails, delay = 3), interleaved 48-bit call.abs, hardware loop reference,
ret.d, the two-unit bz.s context rule, a truncated packet at the end of
the buffer, save/restore/ret and basic blocks that keep the delay slot.
2026-10-30 16:28:21 +01:00
n0p e94cc3060d md32: uSIP pseudo syntax
Pseudo rules for the uSIP mnemonics: # immediates, [base+off], [base]+
and [sp+off] memory operands, compare-and-branch, ld/st as dmemN[...],
movi/mov32, ALU, cmov, loop, save/restore.  test_parse.c updated.
2026-10-30 16:28:21 +01:00
n0p ac1af9c612 md32: wire the uSIP packet decoder into the arch plugin
In usip mode the plugin decodes whole packets (cached per session, 64
entries) and emits one op per run of adjacent units of the same
instruction; displaced tails become .unit ops that name their head.
Types, conditions, jump/fail/delay, loads/stores, immediates, save and
restore, sys and loop references come from the table; opex reports the
form, its confidence, the slot, the unit addresses and the packet
summary.  MAXOP_SIZE is 16 in this mode and regs() exposes the ABI.
The previous five hard-coded uSIP patterns are replaced by the table.
MS15E30 decoding is unchanged.
2026-10-30 16:28:21 +01:00
n0p 94bccea25c md32: table-driven VLIW packet decoder for uSIP
usip_decode.c reads the 16-bit big-endian units of a packet (closed by
bit 15), enumerates the group splits and head/tail assignments the table
allows (heads first, tails round-robin), scores them (unknown units,
instructions, groups, documented tie-break penalties), applies the
adjacency and context rules and returns one instruction per head with
the addresses of its units.  Control flow is restricted to slot 0 and
the rest of an open packet is its delay slot.
2026-10-30 16:28:21 +01:00
n0p 3f8dada173 md32: add the uSIP instruction table and its header generator
isa/usip_isa.json describes the uSIP (MD32 family, VLIW packet variant)
instruction set: 386 forms with masks, operand fields, constraints,
packet rules and a confidence level, plus the packet model (unit scan,
head/tail layout, tie-breaks, context rules) and the register/CSR model.
tools/gen_usip_tables.py generates usip_opcodes.h from it: every field
referenced by a format string or a constraint, constraints as structured
UsipCons records, 16-bit flags and the indices of the context-only forms.
2026-10-30 16:28:21 +01:00
pancake a21d29689b Add support for md32 architecture 2026-10-30 10:31:15 +01:00
pancake 099cb94abf Initial import of the gamegear emulator 2026-10-29 13:35:02 +01:00
pancake de674fbeb0 Add gameboy emulation documentation 2026-10-29 13:30:02 +01:00
pancake f81d48eaca Add gameboy usleep delays 2026-10-29 13:16:44 +01:00
pancake 610dd48707 Fix standalone r2gb test loading and ignore build outputs 2026-10-27 00:35:12 +01:00
pancake c6663b87e9 Initial import of the r2gb plugin 2026-10-27 00:30:24 +01:00
pancake f7dca9d52b Add asynchronous PCM streaming through auq 2026-10-27 00:28:32 +01:00
pancake f2a4b1dfa0 Fix the arpeggio demo 2026-10-26 23:37:46 +01:00
pancake b5b1101110 Fix the doom song 2026-10-26 23:30:34 +01:00
pancake 445c6a5a57 Fix build of r2au 2026-10-26 23:08:44 +01:00
Ricardo J. Rodríguez 5ab2c6aa7d msl: follow spec 1.1.1 register names and thread states
Thread Context (0x0011) changed in Memory Slice spec 1.1.0/1.1.1:
register names are now normative (Table 19g) and the block gained
Suspended/Partial flags, thread states 0x05-0x07 and a per-thread Arch
byte at +0x13.

debug.msl: map the ARM64 aliases fp/lr (and cpsr) that 1.1.0 producers
could write to x29/x30/pstate when seeding (consumer rule 29), using the
thread's Arch byte with the file header as fallback. `dpt` derives the
status letter from ThreadState (r/S/s/z) instead of only the Current flag.

core.msl (`dgm`): never write the fp/lr aliases on ARM64, tag the frame
pointer, status and link registers via r2's BP/SR/LR aliases, and set
Suspended alongside Current since the registers come from a halted
debuggee.
2026-10-26 18:38:36 +01:00
dependabot[bot] 1e989215d9 Bump libc from 0.2.190 to 0.2.191 in /x86iced
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.190 to 0.2.191.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.191/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.190...0.2.191)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.191
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-26 13:48:46 +01:00
dependabot[bot] a2692a2380 Bump libc from 0.2.172 to 0.2.190 in /x86iced
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.172 to 0.2.190.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.190/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.172...0.2.190)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.190
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-13 08:46:15 +02:00
pancake 614d94959e r2ida: import IDB C types into radare2 scripts 2026-09-27 07:36:17 +02:00
pancake 7a0269097b Fix PowerPC disassembler plugin 2026-09-27 07:14:34 +02:00
pancake c8f032c01d Fix MicroBlaze plugin build 2026-09-27 07:05:38 +02:00
pancake 188d102f6b Initial chrome devtools interface for radare2 2026-08-31 19:22:37 +02:00
Abhi 3275d32127 Build CI plugins from checkout 2026-07-29 15:13:39 +01:00
Abhi f4218097d2 Use radare2 GitHub Action in CI 2026-07-29 15:13:39 +01:00
Abhi 96dbf53b1c Fix x86iced for r2 6.1 ABI 2026-07-29 15:13:39 +01:00
pancake e3111fcba3 Remove the r2jadx attic 2026-06-27 18:51:29 +02:00
dependabot[bot] 5b0ea473f9 Bump libc from 0.2.154 to 0.2.172 in /x86iced
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.154 to 0.2.172.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.172/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.154...0.2.172)

---
updated-dependencies:
- dependency-name: libc
  dependency-version: 0.2.172
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-06-27 18:39:34 +02:00
f28a2ec94c Add plugins for memory slice (msl)
* msl: add Memory Slice (.msl) plugins (io/bin/debug/core)

Loadable radare2 plugins for the Memory Slice process-dump format:

- io_msl    open a slice as a process address space (msl://); verifies
            the SHA-256 block chain and End-of-Capture FileHash on open,
            warning on a truncated or tampered slice
- bin_msl   parse a slice as a CORE object (arch/bits/os, entry, maps)
- debug_msl emulated ESIL debug backend, incl. reverse exec (dsb/aesb)
- core_msl  producer: "dgm"/"dgma" write a slice from a live debug session

Build/install with the local Makefile (pkg-config r_core) or via r2pm.
test/genmsl.py emits a valid synthetic slice for exercising the read path.

* msl: select and list captured threads in the debug backend

A slice can capture several threads (one Thread Context block each); the debug
backend only seeded the Current thread. Expose every captured thread:

- .threads callback: `dpt` / `dptj` lists each captured thread with its PC.
- .select callback: `dpt=<tid>` re-seeds the register file from that thread
  and seeks to its captured PC, so any thread can be emulated.

msl_seed_regs now takes a target tid (>=0 exact, <0 Current with first-thread
fallback) and reports the chosen tid, which msl_ensure stores in dbg->tid.

Also fixes a double-unref of the mmap buffer (msl_seed_regs used to unref a
buffer owned by its caller), and accounts for r_debug_select swapping the
register arena after .select returns (seed both arenas so the new thread's
registers survive the swap). test/genmsl.py gains --mt to emit a second,
non-Current thread for exercising listing and selection.

* msl: resolve module symbols and PE exports in bin.msl

The bin plugin presented a slice as maps only. Parse ModuleEntry (0x0002)
blocks and expose them as libraries (`il`) and symbols (`is`): one symbol per
module base plus, for PE images whose headers are captured, one FUNC symbol per
exported function at its address. A VA->file-offset translator over the
captured-run maps reads the module image (DOS/PE headers, export directory).
radare2 then names call targets -- `sym.kernel32.dll_CreateFileW` -- instead of
bare addresses.

test/genmsl.py gains --pe (a minimal in-memory PE export table) and a
memory_region() helper. ELF .dynsym resolution is left to the Python tools.

* msl: note thread listing/selection in the plugin table

The debug.msl row documented reverse execution but not the `dpt` / `dpt=<tid>`
thread listing and selection; mention it for consistency with the body.

---------

Co-authored-by: Ricardo J. Rodríguez <ricardo@thanatos>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 09:51:49 +02:00
pancake 80925aeba6 r2jadx: Use r2.log instead of console.log 2026-05-11 15:44:41 +02:00
pancake 45f9bc841c r2jadx: Use better r2js and improve xrefs caching and more 2026-05-11 12:09:41 +02:00
pancake 69f93467a1 r2jadx: refactor into separate files 2026-05-11 07:53:17 +02:00
pancake 67b24be239 r2jadx: add -s to search words 2026-05-11 07:43:26 +02:00
pancake cd9cc5c190 r2jadx: add pd:j alias 2026-05-11 07:26:42 +02:00
pancake 568a7fd146 r2jadx: config -e with optable color and addresses 2026-05-11 07:22:21 +02:00
pancake d276feb5e8 Fix help message for r2jadx 2026-05-11 06:11:22 +02:00
pancake c3b9864f1d Rewrite r2jadx to typescript 2026-05-10 09:57:56 +02:00
Nerijus Bendžiūnas 7a6e3737c8 fix(r2k): allow Linux module to compile using clang
__FORCE_ORDER is a GCC-specific input constraint not supported
by clang. Use a "memory" clobber on clang instead.

Signed-off-by: Nerijus Bendžiūnas <[email protected]>
2026-02-28 17:46:22 +01:00
pancake 430b6fa559 Minor fix for the rar cpu code 2026-01-09 07:29:22 +01:00
pancake 1cf58513ad Fix r2doom build 2025-10-03 23:15:11 +02:00
Takumi Matsuura da8623bdbe Change to prioritize using uv 2025-10-03 13:58:38 +02:00
pancake 43057468c6 Add r2604 badge 2025-09-30 14:07:19 +02:00
pancake 7ed4cb393f Assume keys in afen can have spaces 2025-09-30 14:05:07 +02:00
pancake 031b7c20c0 Implement 'afen*' command to dump the database 2025-09-30 14:01:32 +02:00
196 changed files with 363618 additions and 15083 deletions

No files matched your search

+14 -13
View File
@@ -12,7 +12,8 @@ jobs:
name: Build radare2 extras and r2pipe
runs-on: ubuntu-22.04
env:
BUILDS: 'asahi blackfin blessr2 keystone-lib keystone r2svd unicorn-lib vc4 x86iced'
EXTERNAL_BUILDS: 'keystone-lib unicorn-lib'
LOCAL_BUILDS: 'asahi blackfin blessr2 keystone microblaze ppcdisasm r2svd vc4 x86iced'
TESTS: 'keystone vc4'
steps:
- uses: actions/checkout@v3
@@ -20,15 +21,9 @@ jobs:
repository: radareorg/radare2
ref: master
path: ./radare2
- name: Install dependencies
run: |
sudo apt-get --assume-yes update
sudo apt-get --assume-yes install libjansson-dev libboost-dev libcurl4-openssl-dev python3-wheel python3-setuptools && sudo pip3 install meson ninja
- name: Install radare2
run: |
export PATH=$PATH:/usr/local/bin
meson --prefix=/usr --buildtype=release build && ninja -C build && sudo ninja -C build install
working-directory: radare2
- uses: radareorg/github-actions@v3
with:
from-git: true
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
@@ -38,11 +33,16 @@ jobs:
- name: Compile and install plugins
run: |
set -e
for p in $BUILDS ; do
for p in $EXTERNAL_BUILDS ; do
echo $p
r2pm -i $p
done
set +e
R2PM_GITDIR="$(r2pm -H R2PM_GITDIR)"
for p in $LOCAL_BUILDS ; do
echo $p
ln -s "$GITHUB_WORKSPACE/radare2-extras" "$R2PM_GITDIR/$p"
R2PM_OFFLINE=1 r2pm -i $p
done
- name: Test plugins
working-directory: radare2/test
run: |
@@ -51,5 +51,6 @@ jobs:
echo $p
make $p
done
r2r -u "$GITHUB_WORKSPACE/radare2-extras/test/db/extras/microblaze"
r2r -u "$GITHUB_WORKSPACE/radare2-extras/test/db/extras/ppcdisasm"
set +e
+13 -11
View File
@@ -1,6 +1,7 @@
radare2-extras
==============
# radare2-extras
[![GithubCI Status](https://github.com/radareorg/radare2-extras/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/radareorg/radare2-extras/actions?query=workflow%3A%22ci%22)
[![radare2](https://img.shields.io/badge/radare2-6.0.4-green)](https://github.com/radareorg/radare2)
This repository contains extra plugins for radare2.
@@ -14,13 +15,6 @@ repository are the following:
* Duplicates functionality from r2 (no need to have
multiple disassemblers for the same arch in core)
The compiled plugins will be installed at
/usr/lib/radare2-extras/$(VERSION)
Some of the plugins/code doesnt compiles, it will be reviewed
and cleaned up, merged into core, updated or removed.
In addition, this repository will be useful to new contributors,
comers who want to write his own r2 plugins for example.
@@ -29,8 +23,16 @@ repository to be as concise as possible, and reduce the
amount of unnecessary plugins to shrink the install size and
keep it usable for 99% of users.
Building
========
## Building
The recommended way to build and install those plugins for users
is to use r2pm. See the [radare2 plugins](https://github.com/radareorg/radare2#plugins) documentation for details.
## Notable plugins
* [`msl`](msl/) — Memory Slice (`.msl`) support: open/emulate process memory
dumps (`io`/`bin`/`debug` plugins, with ESIL reverse execution) and produce
them from a live debug session (`dgm`/`dgma`, `core` plugin).
* [`r2gg`](r2gg/) — Game Gear emulation through Z80 ESIL, with color terminal
display, controls, Sega cartridge banking, interrupts, and stereo PSG audio.
+40 -21
View File
@@ -23,13 +23,11 @@ static void fini(RAsmPluginSession *aps) {
static char *parse(RCore *core, const char *data, HtUP *ht) {
char *out = strdup (data);
#if R2_VERSION_NUMBER >= 50909
RAnalFunction *fcn = r_anal_get_function_at (core->anal, core->addr);
#else
RAnalFunction *fcn = r_anal_get_function_at (core->anal, core->offset);
#endif
if (fcn) {
RVector *vec = ht_up_find (ht, fcn->addr, NULL);
if (vec) {
@@ -90,6 +88,17 @@ static inline void vector_value_free_afen(HtUPKv *kv) {
}
}
static bool print_afen_rules(void *user, const ut64 key, const void *value) {
RCore *core = (RCore *) user;
RVector *vec = (RVector *) value;
RAfenRepl *repl;
r_vector_foreach (vec, repl) {
r_cons_printf (core->cons, "'@0x%08"PFMT64x"'afen \"%s\" \"%s\"\n", key, repl->new_name, repl->old_name);
}
return true;
}
// setup afen parser
#if R2_VERSION_NUMBER >= 50909
static bool r_core_init_afen(RCorePluginSession *cps) {
@@ -97,15 +106,21 @@ static bool r_core_init_afen(RCorePluginSession *cps) {
r_asm_plugin_add (core->rasm, &r_parse_plugin_afen);
RAsmPluginSession *afen_aps;
RAsmPluginSession *afen_aps = NULL;
RListIter *iter;
RAsmPluginSession *aps;
r_list_foreach (core->rasm->sessions, iter, aps) {
if (!strcmp(aps->plugin->meta.name, "afen")) {
if (aps && aps->plugin && aps->plugin->meta.name &&
!strcmp(aps->plugin->meta.name, "afen")) {
afen_aps = aps;
break;
}
}
if (!afen_aps) {
R_LOG_ERROR ("Failed to find afen plugin session");
return false;
}
HtUP /*<ut64, RVector<RAfenRepl*>>*/ *ht = ht_up_new (NULL, vector_value_free_afen, NULL);
if (!ht) {
@@ -154,26 +169,22 @@ static bool r_core_fini_afen(void *user, const char *input) {
#endif
static bool check_for_afen_command(RCore *core, const char *input, HtUP *ht) {
if (r_str_startswith (input, "afen")) {
int argc;
if (r_str_startswith (input, "afen*")) {
ht_up_foreach(ht, print_afen_rules, core);
return true;
} else if (r_str_startswith (input, "afen")) {
int argc = 0;
char **argv = r_str_argv (input, &argc);
if (argc != 3) {
if (!argv || argc != 3) {
R_LOG_INFO ("Usage: afen new_name old_name");
r_str_argv_free (argv);
return true;
}
if (!argv) {
R_LOG_ERROR ("Can't get args");
return false;
}
#if R2_VERSION_NUMBER >= 50909
RAnalFunction *fcn = r_anal_get_function_at (core->anal, core->addr);
#else
RAnalFunction *fcn = r_anal_get_function_at (core->anal, core->offset);
#endif
if (!fcn) {
#if R2_VERSION_NUMBER >= 50909
R_LOG_ERROR ("No Function at 0x%08" PFMT64x, core->addr);
@@ -181,6 +192,7 @@ static bool check_for_afen_command(RCore *core, const char *input, HtUP *ht) {
R_LOG_ERROR ("No Function at 0x%08" PFMT64x, core->offset);
#endif
R_LOG_INFO ("Use afen inside a function!");
r_str_argv_free (argv);
return false;
}
@@ -194,21 +206,28 @@ static bool check_for_afen_command(RCore *core, const char *input, HtUP *ht) {
bool updated = false;
RAfenRepl *repl;
r_vector_foreach (vec, repl) {
if (!strcmp (repl->old_name, argv[2])) {
repl->new_name = argv[1];
if (repl->old_name && argv[2] && !strcmp (repl->old_name, argv[2])) {
if (repl->new_name) {
R_FREE (repl->new_name);
}
repl->new_name = strdup (argv[1]);
updated = true;
break;
}
}
if (updated) return true;
if (updated) {
r_str_argv_free (argv);
return true;
}
repl = R_NEW (RAfenRepl);
repl->new_name = argv[1];
repl->old_name = argv[2];
repl = R_NEW0 (RAfenRepl);
repl->new_name = strdup (argv[1]);
repl->old_name = strdup (argv[2]);
r_vector_push (vec, repl);
r_str_argv_free (argv);
return true;
}
return false;
+10
View File
@@ -0,0 +1,10 @@
# build artifacts
*.o
*.dylib
*.so
*.a
# editor / assistant caches
.pmai/
.DS_Store
build/
+68
View File
@@ -0,0 +1,68 @@
# Build the r2 vc4 asm/anal plugin straight from this directory.
# Mirrors libr/asm/p/vc4.mk + libr/plugs.mk but works standalone.
CC?=cc
VC4DIR=$(shell pwd)
PLUGDIR=$(abspath $(VC4DIR)/../../p)
PLUGSRC=$(PLUGDIR)/arch_vc4.c
R2_CFLAGS?=$(shell pkg-config --cflags r_arch r_asm r_util 2>/dev/null || pkg-config --cflags r_asm r_util 2>/dev/null || echo -I$(shell r2 -H R2_INCDIR 2>/dev/null))
R2_LDFLAGS?=$(shell pkg-config --libs r_arch r_asm r_util 2>/dev/null || pkg-config --libs r_asm r_util 2>/dev/null || echo -L$(shell r2 -H R2_LIBDIR 2>/dev/null) -lr_anal -lr_arch -lr_asm -lr_util)
R2HOME?=$(shell r2 -H R2_USER_PLUGINS 2>/dev/null || echo $(HOME)/.local/share/radare2/plugins)
UNAME_S?=$(shell uname -s)
ifeq ($(UNAME_S),Darwin)
LIBEXT?=dylib
else
LIBEXT?=so
endif
TARGET=arch_vc4.$(LIBEXT)
PSEUDO=asm_vc4.$(LIBEXT)
CFLAGS+=$(R2_CFLAGS)
CFLAGS+=-g -O2 -Wall -fPIC
CFLAGS+=-I$(VC4DIR) -I$(VC4DIR)/include -I$(VC4DIR)/include/opcode
CFLAGS+=-I$(VC4DIR)/../include
LDFLAGS+=$(R2_LDFLAGS) -lr_anal
LDFLAGS+=-shared
OBJ=arch_vc4.o \
vc4-dis.o vc4-asm.o vc4-desc.o vc4-ibld.o vc4-opc.o \
cgen-bitset.o cgen-dis.o cgen-asm.o cgen-opc.o
all: $(TARGET) $(PSEUDO)
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
# binutils/cgen generated sources use the dashes in their names
vc4-%.o: vc4-%.c
$(CC) -c $(CFLAGS) $< -o $@
# the r2 glue lives in libr/asm/p/
arch_vc4.o: $(PLUGSRC) vc4_anal.inc.c
$(CC) -c $(CFLAGS) $(PLUGSRC) -o $@
$(TARGET): $(OBJ)
$(CC) -o $(TARGET) $(OBJ) $(LDFLAGS)
$(PSEUDO): pseudo.o
$(CC) -o $(PSEUDO) pseudo.o $(LDFLAGS)
install: $(TARGET) $(PSEUDO)
mkdir -p $(R2HOME)
cp -f $(TARGET) $(R2HOME)
-codesign -f -s - $(R2HOME)/$(TARGET) 2>/dev/null || true
uninstall:
rm -f $(R2HOME)/$(TARGET) $(R2HOME)/$(PSEUDO)
clean:
rm -f $(OBJ) $(TARGET) pseudo.o $(PSEUDO)
mrproper: clean
$(MAKE) -C libvc4 clean 2>/dev/null || true
.PHONY: all install uninstall clean mrproper
+16
View File
@@ -0,0 +1,16 @@
/* Minimal safe-ctype.h for the vc4 cgen sources, avoids depending on
libiberty's _sch_istable. */
#ifndef SAFE_CTYPE_H
#define SAFE_CTYPE_H
#include <ctype.h>
#define ISALNUM(c) isalnum ((unsigned char) (c))
#define ISALPHA(c) isalpha ((unsigned char) (c))
#define ISDIGIT(c) isdigit ((unsigned char) (c))
#define ISSPACE(c) isspace ((unsigned char) (c))
#define TOLOWER(c) tolower ((unsigned char) (c))
#define TOUPPER(c) toupper ((unsigned char) (c))
#endif
+2
View File
@@ -28,6 +28,8 @@
file (check in a non-ANSI environment!), it should be. */
#include "ansidecl.h"
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
+291
View File
@@ -0,0 +1,291 @@
/* radare - LGPL - Copyright 2026 - pancake */
#include <r_asm.h>
static const char *pseudo_rules[] = {
"mov/2/$1 = $2",
"add/2/$1 += $2",
"add/3/$1 = $2 + $3",
"sub/2/$1 -= $2",
"sub/3/$1 = $2 - $3",
"rsub/2/$1 = $2 - $1",
"rsub/3/$1 = $3 - $2",
"mul/2/$1 *= $2",
"mul/3/$1 = $2 * $3",
"div/3/$1 = $2 / $3",
"mulhd/3/$1 = ($2 * $3) >> 32",
"and/2/$1 &= $2",
"and/3/$1 = $2 & $3",
"or/2/$1 |= $2",
"or/3/$1 = $2 | $3",
"eor/2/$1 ^= $2",
"eor/3/$1 = $2 ^ $3",
"bic/2/$1 &= ~$2",
"bic/3/$1 = $2 & ~$3",
"not/2/$1 = ~$2",
"neg/2/$1 = -$2",
"abs/2/$1 = abs ($2)",
"shl/2/$1 <<= $2",
"shl/3/$1 = $2 << $3",
"lsr/2/$1 >>= $2",
"lsr/3/$1 = $2 >> $3",
"asr/2/$1 = (st32)$1 >> $2",
"asr/3/$1 = (st32)$2 >> $3",
"ror/2/$1 = ror ($1, $2)",
"ror/3/$1 = ror ($2, $3)",
"min/2/$1 = min ($1, $2)",
"min/3/$1 = min ($2, $3)",
"max/2/$1 = max ($1, $2)",
"max/3/$1 = max ($2, $3)",
"bitset/2/$1 |= 1 << $2",
"bitset/3/$1 = $2 | (1 << $3)",
"bitclear/2/$1 &= ~(1 << $2)",
"bitclear/3/$1 = $2 & ~(1 << $3)",
"bitflip/2/$1 ^= 1 << $2",
"bitflip/3/$1 = $2 ^ (1 << $3)",
"btest/2/flags = $1 & (1 << $2)",
"bmask/2/$1 &= (1 << $2) - 1",
"bmask/3/$1 = $2 & ((1 << $3) - 1)",
"signext/2/$1 = signext ($1, $2)",
"signext/3/$1 = signext ($2, $3)",
"addscale/2/$1 += $2",
"addscale/3/$1 = $2 + $3",
"subscale/2/$1 -= $2",
"subscale/3/$1 = $2 - $3",
"adds/3/$1 = sat ($2 + $3)",
"subs/3/$1 = sat ($2 - $3)",
"msb/2/$1 = msb ($2)",
"count/2/$1 = popcount ($2)",
"brev/2/$1 = bitreverse ($2)",
"clamp16/2/$1 = clamp16 ($2)",
"cmp/2/flags = $1 - $2",
"cmn/2/flags = $1 + $2",
"fadd/3/$1 = $2 + $3",
"fsub/3/$1 = $2 - $3",
"frsub/3/$1 = $3 - $2",
"fmul/3/$1 = $2 * $3",
"fnmul/3/$1 = -($2 * $3)",
"fdiv/3/$1 = $2 / $3",
"fmax/3/$1 = fmax ($2, $3)",
"fmin/3/$1 = fmin ($2, $3)",
"fabs/2/$1 = fabs ($2)",
"frcp/2/$1 = 1 / $2",
"frsqrt/2/$1 = 1 / sqrt ($2)",
"fceil/2/$1 = ceil ($2)",
"ffloor/2/$1 = floor ($2)",
"flog2/2/$1 = log2 ($2)",
"fexp2/2/$1 = exp2 ($2)",
"fcmp/2/flags = $1 - $2",
"ftrunc/3/$1 = (int)($2 $3)",
"floor/3/$1 = (int)floor ($2 $3)",
"flts/3/$1 = (float)($2 $3)",
"fltu/3/$1 = (float)(unsigned)($2 $3)",
"lea/2/$1 = &$2",
"ld/2/$1 = (ut32)$2",
"ldh/2/$1 = (ut16)$2",
"ldsh/2/$1 = (st16)$2",
"ldb/2/$1 = (ut8)$2",
"ldsb/2/$1 = (st8)$2",
"st/2/(ut32)$2 = $1",
"sth/2/(ut16)$2 = $1",
"stb/2/(ut8)$2 = $1",
"b/1/goto $1",
"j/1/goto $1",
"bl/1/$1 ()",
"jl/1/$1 ()",
"tbb/1/goto table8[$1]",
"tbh/1/goto table16[$1]",
"version/1/$1 = cpuid",
"rts/0/return",
"rti/0/return from interrupt",
"swi/1/syscall ($1)",
"bkpt/0/breakpoint",
"ei/0/enable interrupts",
"di/0/disable interrupts",
"nop/0/",
NULL
};
// condition suffix and the operator used by compare and branch forms
static const char *conds[][2] = {
{ "eq", "==" }, { "ne", "!=" }, { "lt", "<" }, { "le", "<=" },
{ "gt", ">" }, { "ge", ">=" }, { "hi", ">u" }, { "ls", "<=u" },
{ "cs", "<u" }, { "lo", "<u" }, { "cc", ">=u" }, { "hs", ">=u" },
{ "mi", NULL }, { "pl", NULL }, { "vs", NULL }, { "vc", NULL },
{ "f", NULL }, { NULL, NULL }
};
static int cond_index(const char *s) {
int i;
for (i = 0; conds[i][0]; i++) {
if (!strcmp (s, conds[i][0])) {
return i;
}
}
return -1;
}
// "(r1+8)" -> "[r1 + 8]", "(r1)" -> "[r1]", "#imm" -> "imm"
static char *normalize(const char *data) {
RStrBuf *sb = r_strbuf_new ("");
const char *p;
for (p = data; *p; p++) {
if (*p == '#') {
continue;
}
if (*p == '(' && p > data && (p[-1] == ',' || p[-1] == ' ')) {
const char *close = strchr (p, ')');
if (close) {
char *in = r_str_ndup (p + 1, close - p - 1);
in = r_str_replace (in, "+-", " - ", 1);
in = r_str_replace (in, "+", " + ", 1);
in = r_str_replace (in, " + + ", "++", 1);
r_strbuf_appendf (sb, "[%s]", in);
free (in);
p = close;
continue;
}
}
r_strbuf_append_n (sb, p, 1);
}
return r_strbuf_drain (sb);
}
static char *transform(const char *data) {
char *res = r_str_pseudo_transform (pseudo_rules, data);
if (res && strcmp (res, data)) {
return res;
}
free (res);
return NULL;
}
// "addcmpb<cond> a,b,c,dst" and "b<cond> a,b,dst" compare after the add
static char *cmpbranch(const char *str, const char *cond, const char *op, bool add) {
char *rule;
if (!strcmp (cond, "f")) {
rule = add? strdup ("x/4/$1 += $2"): strdup ("x/3/");
} else if (add) {
rule = op? r_str_newf ("x/4/$1 += $2; if ($1 %s $3) goto $4", op)
: r_str_newf ("x/4/$1 += $2; if (%s ($1 - $3)) goto $4", cond);
} else {
rule = op? r_str_newf ("x/3/if ($1 %s $2) goto $3", op)
: r_str_newf ("x/3/if (%s ($1 - $2)) goto $3", cond);
}
const char *rules[] = { rule, NULL };
const char *args = strchr (str, ' ');
char *s = r_str_newf ("x%s", args? args: "");
char *res = r_str_pseudo_transform (rules, s);
free (s);
free (rule);
return res;
}
// r_str_pseudo_transform turns "a + -1" into "a -1", make it "a - 1"
static char *fix_minus(char *s) {
RStrBuf *sb = r_strbuf_new ("");
char *p;
for (p = s; *p; p++) {
r_strbuf_append_n (sb, p, 1);
if (p > s && p[0] == ' ' && p[1] == '-' && isdigit ((ut8)p[2]) && isalnum ((ut8)p[-1])) {
r_strbuf_append (sb, "- ");
p++;
}
}
free (s);
return r_strbuf_drain (sb);
}
static char *parse(RAsmPluginSession *aps, const char *data) {
char *str = normalize (data);
char *sp = strchr (str, ' ');
char *args = sp? sp: "";
char *mn = r_str_ndup (str, sp? sp - str: strlen (str));
// mnemonic: base[.cond][.m|.s][.ss|.su|.us|.uu]
int cond = -1;
char *dot = strchr (mn, '.');
if (dot) {
char *t = dot;
*dot = 0;
while (t) {
char *part = t + 1;
t = strchr (part, '.');
if (t) {
*t = 0;
}
int c = cond_index (part);
if (c >= 0) {
cond = c;
}
}
}
char *res = NULL;
int ci = -1;
int nargs = *args? (int)r_str_char_count (args, ',') + 1: 0;
if (r_str_startswith (mn, "addcmpb")) {
ci = mn[7]? cond_index (mn + 7): -1;
if (!mn[7]) {
char *s = r_str_newf ("x%s", args);
const char *rules[] = { "x/4/$1 += $2; goto $4", NULL };
res = r_str_pseudo_transform (rules, s);
free (s);
} else if (ci >= 0) {
res = cmpbranch (str, conds[ci][0], conds[ci][1], true);
}
} else if (mn[0] == 'b' && mn[1] && (ci = cond_index (mn + 1)) >= 0) {
if (nargs == 3) {
res = cmpbranch (str, conds[ci][0], conds[ci][1], false);
} else if (strcmp (conds[ci][0], "f")) {
res = r_str_newf ("if (%s) goto%s", conds[ci][0], args);
}
} else if (!strcmp (mn, "ldm") || !strcmp (mn, "stm")) {
// ldm r6-r7,pc,(sp++) / stm r6-r7,lr,(--sp)
char *regs = r_str_trim_dup (args);
char *last = (char *)r_str_rchr (regs, NULL, ',');
if (last) {
*last = 0;
}
bool ret = strstr (regs, ",pc") || !strcmp (regs, "pc");
res = r_str_newf ("%s %s%s", (*mn == 'l')? "pop": "push", regs, ret? "; return": "");
free (regs);
} else {
char *s = r_str_newf ("%s%s", mn, args);
char *body = transform (s);
free (s);
if (body) {
if (cond >= 0 && strcmp (conds[cond][0], "f")) {
res = r_str_newf ("if (%s) %s", conds[cond][0], body);
free (body);
} else if (cond >= 0) {
free (body);
} else {
res = body;
}
}
}
free (mn);
free (str);
if (!res) {
return strdup (data);
}
res = r_str_replace (res, "+= -", "-= ", 1);
return fix_minus (res);
}
RAsmPlugin r_asm_plugin_vc4 = {
.meta = {
.name = "vc4",
.desc = "VideoCore IV pseudo syntax",
.author = "pancake",
.license = "LGPL-3.0-only",
},
.parse = parse,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_vc4,
.version = R2_VERSION
};
#endif
+49
View File
@@ -0,0 +1,49 @@
# r2plugin
This directory contains the make and meson files required to link the
vc4 (VideoCore IV) plugins statically inside radare2, using the XPS
(static external plugins) mechanism. See `libr/xps/README.md` in the
radare2 source tree for the full details.
Two plugins are registered directly, without a core wrapper, so both
`rasm2` and `r2` get them:
- `arch.vc4`: cgen based disassembler and analysis, linked into libr_arch
- `asm.vc4`: pseudo syntax (`e asm.pseudo=true`), linked into libr_asm
## Usage
Run these commands from the radare2 source tree:
```console
mkdir -p libr/xps/p
ln -s ../../../../radare2-extras/libr/asm/arch/vc4 libr/xps/p/vc4
echo 'EXTERNAL_PLUGINS+=vc4' >> libr/xps/config.mk
make -C libr/xps clean all
```
Then build radare2 as usual:
```console
./configure && make -j
# or
meson setup build --reconfigure && meson compile -C build
```
The meson build needs a radare2 that provides `xps_arch_plugins`,
`xps_asm_plugins` and `r_{arch,asm}_additional_{sources,inc}`.
Verify the plugins are linked in:
```console
rasm2 -L | grep vc4
rasm2 -a vc4 -d 01000200
r2 -NN -qc 'La~vc4;Lp~vc4' --
```
## Notes
- `libvc4/` is not linked: the plugin only uses the cgen disassembler.
- `r2plugin/arch/arch_vc4.c` includes `libr/asm/p/arch_vc4.c`, which
lives outside this directory.
- Object files for the make build are stored under `build/r2plugin/`.
@@ -0,0 +1,4 @@
/* radare - LGPL - Copyright 2026 - pancake */
/* the r2 glue lives in libr/asm/p, outside the XPS directory */
#include "../../../../p/arch_vc4.c"
+5
View File
@@ -0,0 +1,5 @@
/* radare - LGPL - Copyright 2026 - pancake */
#ifdef R2_ARCH_H
extern RArchPlugin r_arch_plugin_vc4;
#endif
+22
View File
@@ -0,0 +1,22 @@
VC4_ARCH_WD=$(LIBR)/xps/p/vc4
VC4_ARCH_BUILD=$(VC4_ARCH_WD)/build/r2plugin/arch
VC4_ARCH_SRC=\
vc4-dis.c vc4-asm.c vc4-desc.c vc4-ibld.c vc4-opc.c \
cgen-bitset.c cgen-dis.c cgen-asm.c cgen-opc.c
VC4_ARCH_OBJ=$(addprefix $(VC4_ARCH_BUILD)/,$(VC4_ARCH_SRC:.c=.o))
VC4_ARCH_OBJ+=$(VC4_ARCH_BUILD)/arch_vc4.o
EXTERNAL_STATIC_OBJS+=$(VC4_ARCH_OBJ)
# must come before r2's libr/arch/include, which ships its own sysdep.h, mybfd.h..
VC4_ARCH_CFLAGS=-I$(VC4_ARCH_WD) -I$(VC4_ARCH_WD)/include \
-I$(VC4_ARCH_WD)/include/opcode -I$(VC4_ARCH_WD)/../include
$(VC4_ARCH_BUILD)/arch_vc4.o: $(VC4_ARCH_WD)/r2plugin/arch/arch_vc4.c $(VC4_ARCH_WD)/../../p/arch_vc4.c
@mkdir -p $(dir $@)
$(CC) -c $(VC4_ARCH_CFLAGS) $(CFLAGS) -o $@ $<
$(VC4_ARCH_BUILD)/%.o: $(VC4_ARCH_WD)/%.c
@mkdir -p $(dir $@)
$(CC) -c $(VC4_ARCH_CFLAGS) $(CFLAGS) -o $@ $<
-include $(VC4_ARCH_OBJ:.o=.d)
+5
View File
@@ -0,0 +1,5 @@
/* radare - LGPL - Copyright 2026 - pancake */
#ifdef R2_ASM_H
extern RAsmPlugin r_asm_plugin_vc4;
#endif
+9
View File
@@ -0,0 +1,9 @@
VC4_ASM_WD=$(LIBR)/xps/p/vc4
VC4_ASM_OBJ=$(VC4_ASM_WD)/build/r2plugin/asm/pseudo.o
EXTERNAL_STATIC_OBJS+=$(VC4_ASM_OBJ)
$(VC4_ASM_OBJ): $(VC4_ASM_WD)/pseudo.c
@mkdir -p $(dir $@)
$(CC) -c $(CFLAGS) -o $@ $<
-include $(VC4_ASM_OBJ:.o=.d)
+9
View File
@@ -0,0 +1,9 @@
EXTERNAL_PLUGINS+=vc4
.PHONY: vc4
vc4: p/vc4
p/vc4:
cd p && git clone --depth 1 https://github.com/radareorg/radare2-extras
ln -fs radare2-extras/libr/asm/arch/vc4 p/vc4
+35
View File
@@ -0,0 +1,35 @@
# vc4 XPS integration for radare2.
# Included from libr/xps/meson.build when this directory lives under
# libr/xps/p/vc4/. Links the arch plugin into libr_arch and the pseudo
# plugin into libr_asm, so rasm2 and r2 get vc4 without external files.
xps_arch_plugins += ['vc4']
# p/vc4 is usually a symlink, resolve it before going above this directory
vc4_shared_inc = run_command('realpath',
join_paths(meson.current_source_dir(), '..', '..', 'include'),
check: true).stdout().strip()
r_arch_additional_inc += include_directories(
'..',
'../include',
'../include/opcode',
vc4_shared_inc,
)
r_arch_additional_sources += files(
'../vc4-dis.c',
'../vc4-asm.c',
'../vc4-desc.c',
'../vc4-ibld.c',
'../vc4-opc.c',
'../cgen-bitset.c',
'../cgen-dis.c',
'../cgen-asm.c',
'../cgen-opc.c',
'arch/arch_vc4.c',
)
xps_asm_plugins += ['vc4']
r_asm_additional_sources += files('../pseudo.c')
+8
View File
@@ -0,0 +1,8 @@
/* radare - LGPL - Copyright 2026 - pancake */
#ifdef R2_ARCH_H
#include "arch/deps.h"
#endif
#ifdef R2_ASM_H
#include "asm/deps.h"
#endif
+2
View File
@@ -0,0 +1,2 @@
arch.vc4
asm.vc4
File diff suppressed because it is too large. Load diff
+52977 -655
View File
File diff suppressed because it is too large. Load diff
+128 -32
View File
@@ -42,12 +42,12 @@ This file is part of the GNU Binutils and/or GDB, the GNU debugger.
/* Maximum size of any insn (in bytes). */
#define CGEN_MAX_INSN_SIZE 10
#define CGEN_MAX_EXTRA_OPCODE_OPERANDS 2
#define CGEN_MAX_EXTRA_OPCODE_OPERANDS 3
#define CGEN_INT_INSN_P 0
/* Maximum number of syntax elements in an instruction. */
#define CGEN_ACTUAL_MAX_SYNTAX_ELEMENTS 20
#define CGEN_ACTUAL_MAX_SYNTAX_ELEMENTS 26
/* CGEN_MNEMONIC_OPERANDS is defined if mnemonics have operands.
e.g. In "b,a foo" the ",a" is an operand. If mnemonics have operands
@@ -55,7 +55,7 @@ This file is part of the GNU Binutils and/or GDB, the GNU debugger.
#define CGEN_MNEMONIC_OPERANDS
/* Maximum number of fields in an instruction. */
#define CGEN_ACTUAL_MAX_IFMT_OPERANDS 9
#define CGEN_ACTUAL_MAX_IFMT_OPERANDS 15
/* Enums. */
@@ -76,7 +76,7 @@ typedef enum insn_op15_13 {
/* Enum declaration for insn float ops. */
typedef enum insn_floatops {
ALUFOP_FADD, ALUFOP_FSUB, ALUFOP_FMUL, ALUFOP_FDIV
, ALUFOP_FCMP, ALUFOP_FABS, ALUFOP_FRSB, ALUFOP_FMAX
, ALUFOP_FCMP, ALUFOP_FABS, ALUFOP_FRSUB, ALUFOP_FMAX
, ALUFOP_FRCP, ALUFOP_FRSQRT, ALUFOP_FNMUL, ALUFOP_FMIN
, ALUFOP_FCEIL, ALUFOP_FFLOOR, ALUFOP_FLOG2, ALUFOP_FEXP2
} INSN_FLOATOPS;
@@ -86,7 +86,7 @@ typedef enum insn_alu16op {
ALU16OP_MOV, ALU16OP_CMN, ALU16OP_ADD, ALU16OP_BIC
, ALU16OP_MUL, ALU16OP_EOR, ALU16OP_SUB, ALU16OP_AND
, ALU16OP_NOT, ALU16OP_ROR, ALU16OP_CMP, ALU16OP_RSUB
, ALU16OP_BTST, ALU16OP_OR, ALU16OP_BMASK, ALU16OP_MAX
, ALU16OP_BTEST, ALU16OP_OR, ALU16OP_BMASK, ALU16OP_MAX
, ALU16OP_BSET, ALU16OP_MIN, ALU16OP_BCLR, ALU16OP_ADDS2
, ALU16OP_BCHG, ALU16OP_ADDS4, ALU16OP_ADDS8, ALU16OP_ADDS16
, ALU16OP_SIGNEXT, ALU16OP_NEG, ALU16OP_LSR, ALU16OP_MSB
@@ -96,7 +96,7 @@ typedef enum insn_alu16op {
/* Enum declaration for insn alu16 imm ops. */
typedef enum insn_alu16opi {
ALU16OPI_MOV, ALU16OPI_ADD, ALU16OPI_MUL, ALU16OPI_SUB
, ALU16OPI_NOT, ALU16OPI_CMP, ALU16OPI_BTST, ALU16OPI_BMASK
, ALU16OPI_NOT, ALU16OPI_CMP, ALU16OPI_BTEST, ALU16OPI_BMASK
, ALU16OPI_BSET, ALU16OPI_BCLR, ALU16OPI_BCHG, ALU16OPI_ADDS8
, ALU16OPI_SIGNEXT, ALU16OPI_LSR, ALU16OPI_SHL, ALU16OPI_ASR
} INSN_ALU16OPI;
@@ -106,7 +106,7 @@ typedef enum insn_alu32op {
ALU32OP_MOV, ALU32OP_CMN, ALU32OP_ADD, ALU32OP_BIC
, ALU32OP_MUL, ALU32OP_EOR, ALU32OP_SUB, ALU32OP_AND
, ALU32OP_NOT, ALU32OP_ROR, ALU32OP_CMP, ALU32OP_RSUB
, ALU32OP_BTST, ALU32OP_OR, ALU32OP_BMASK, ALU32OP_MAX
, ALU32OP_BTEST, ALU32OP_OR, ALU32OP_BMASK, ALU32OP_MAX
, ALU32OP_BSET, ALU32OP_MIN, ALU32OP_BCLR, ALU32OP_ADDS2
, ALU32OP_BCHG, ALU32OP_ADDS4, ALU32OP_ADDS8, ALU32OP_ADDS16
, ALU32OP_SIGNEXT, ALU32OP_NEG, ALU32OP_LSR, ALU32OP_MSB
@@ -141,10 +141,17 @@ typedef enum insn_op7_6 {
OP7_6_0, OP7_6_1, OP7_6_2, OP7_6_3
} INSN_OP7_6;
/* Enum declaration for access sizes. */
typedef enum accsz_op7_6 {
ACCSZ7_6_, ACCSZ7_6_H, ACCSZ7_6_B, ACCSZ7_6_SH
} ACCSZ_OP7_6;
/* Enum declaration for access size. */
typedef enum accsz_10_8 {
ACCSZ10_8_LD, ACCSZ10_8_ST, ACCSZ10_8_LDH, ACCSZ10_8_STH
, ACCSZ10_8_LDB, ACCSZ10_8_STB, ACCSZ10_8_LDSH, ACCSZ10_8_LDSB
} ACCSZ_10_8;
/* Enum declaration for access size (32 bit insns). */
typedef enum accsz_7_5 {
ACCSZ7_5_LD, ACCSZ7_5_ST, ACCSZ7_5_LDH, ACCSZ7_5_STH
, ACCSZ7_5_LDB, ACCSZ7_5_STB, ACCSZ7_5_LDSH, ACCSZ7_5_LDSB
} ACCSZ_7_5;
/* Enum declaration for insn bits 7-5. */
typedef enum insn_op7_5 {
@@ -205,6 +212,67 @@ typedef enum insn_op22_21 {
OP22_21_0, OP22_21_1, OP22_21_2, OP22_21_3
} INSN_OP22_21;
/* Enum declaration for Vector ops (< 48). */
typedef enum insn_vecops {
VECOP_MOV, VECOP_BITPLANES, VECOP_EVEN, VECOP_ODD
, VECOP_INTERL, VECOP_INTERH, VECOP_BITREV, VECOP_ROR
, VECOP_SHL, VECOP_SHLS, VECOP_LSR, VECOP_ASR
, VECOP_SIGNSHL, VECOP_OP13, VECOP_SIGNASL, VECOP_SIGNASLS
, VECOP_AND, VECOP_OR, VECOP_EOR, VECOP_BIC
, VECOP_COUNT, VECOP_MSB, VECOP_OP22, VECOP_OP23
, VECOP_MIN, VECOP_MAX, VECOP_DIST, VECOP_DISTS
, VECOP_CLIP, VECOP_SIGN, VECOP_CLIPS, VECOP_TESTMAG
, VECOP_ADD, VECOP_ADDS, VECOP_ADDC, VECOP_ADDSC
, VECOP_SUB, VECOP_SUBS, VECOP_SUBC, VECOP_SUBSC
, VECOP_RSUB, VECOP_RSUBS, VECOP_RSUBC, VECOP_RSUBSC
, VECOP_OP44, VECOP_OP45, VECOP_OP46, VECOP_OP47
} INSN_VECOPS;
/* Enum declaration for Vector memory ops. */
typedef enum insn_vecmemops {
VMEMOP_LD, VMEMOP_LOOKUPM, VMEMOP_LOOKUPML, VMEMOP_MEM03
, VMEMOP_ST, VMEMOP_INDEXWRITEM, VMEMOP_INDEXWRITEML, VMEMOP_MEM07
, VMEMOP_MEMREAD, VMEMOP_MEMWRITE, VMEMOP_MEM10, VMEMOP_MEM11
, VMEMOP_MEM12, VMEMOP_MEM13, VMEMOP_MEM14, VMEMOP_MEM15
, VMEMOP_MEM16, VMEMOP_MEM17, VMEMOP_MEM18, VMEMOP_MEM19
, VMEMOP_MEM20, VMEMOP_MEM21, VMEMOP_MEM22, VMEMOP_MEM23
, VMEMOP_GETACC, VMEMOP_MEM25, VMEMOP_MEM26, VMEMOP_MEM27
, VMEMOP_MEM28, VMEMOP_MEM29, VMEMOP_MEM30, VMEMOP_MEM31
} INSN_VECMEMOPS;
/* Enum declaration for Vector memory op width. */
typedef enum insn_memwidth {
VMEMWIDTH_8, VMEMWIDTH_16, VMEMWIDTH_32, VMEMWIDTH_UNK
} INSN_MEMWIDTH;
/* Enum declaration for Vector memory op saturation. */
typedef enum insn_memsat {
VMEMSAT_, VMEMSAT_S32, VMEMSAT_UNK, VMEMSAT_S16
} INSN_MEMSAT;
/* Enum declaration for Multiply operations. */
typedef enum insn_mulop {
VMULXOP_MULLSS = 48, VMULXOP_OP481 = 48, VMULXOP_MULLSSS = 49, VMULXOP_OP491 = 49
, VMULXOP_MULMSS = 50, VMULXOP_OP501 = 50, VMULXOP_MULMSSS = 51, VMULXOP_OP511 = 51
, VMULXOP_MULHDSS = 52, VMULXOP_MUL32SS = 52, VMULXOP_MULHDSU = 53, VMULXOP_MUL32SU = 53
, VMULXOP_MULHDUS = 54, VMULXOP_MUL32US = 54, VMULXOP_MULHDUU = 55, VMULXOP_MUL32UU = 55
, VMULXOP_MULHNSS = 56, VMULXOP_OP561 = 56, VMULXOP_MULHNSU = 57, VMULXOP_OP571 = 57
, VMULXOP_MULHNUS = 58, VMULXOP_OP581 = 58, VMULXOP_MULHNUU = 59, VMULXOP_OP591 = 59
, VMULXOP_MULHDTSS = 60, VMULXOP_OP601 = 60, VMULXOP_MULHDTSU = 61, VMULXOP_OP611 = 61
, VMULXOP_OP620 = 62, VMULXOP_OP621 = 62, VMULXOP_OP630 = 63, VMULXOP_OP631 = 63
} INSN_MULOP;
/* Enum declaration for SREG setting for 48-bit ops. */
typedef enum insn_sreg {
SREG_R0, SREG_R1, SREG_R2, SREG_R3
, SREG_R4, SREG_R5, SREG_R6, SREG_R7
} INSN_SREG;
/* Enum declaration for Vector direction bit for 48-bit ops. */
typedef enum insn_vec48dir {
VECDIR_H, VECDIR_V
} INSN_VEC48DIR;
/* Attributes. */
/* Enum declaration for machine type selection. */
@@ -248,7 +316,7 @@ typedef enum cgen_ifld_attr {
typedef enum ifield_type {
VC4_F_NIL, VC4_F_ANYOF, VC4_F_OPLEN, VC4_F_OP15_13
, VC4_F_OP15_11, VC4_F_OP11_8, VC4_F_LDSTOFF, VC4_F_OP11_9
, VC4_F_OP11_10, VC4_F_OP11, VC4_F_OP10_9, VC4_F_OP10_7
, VC4_F_OP11_10, VC4_F_OP11, VC4_F_OP10_8, VC4_F_OP10_7
, VC4_F_ADDSPOFFSET, VC4_F_OP10_0, VC4_F_ALU16OP, VC4_F_ALU16OPI
, VC4_F_OP9_8, VC4_F_OP9_5, VC4_F_SPOFFSET, VC4_F_OP8_5
, VC4_F_OP8_4, VC4_F_OP8_4_SHL3, VC4_F_OP8, VC4_F_OP7_4
@@ -265,7 +333,18 @@ typedef enum ifield_type {
, VC4_F_OP21_16S_SHL6, VC4_F_OP21_16S_SHL7, VC4_F_OP21_16S_SHL8, VC4_F_OP20_16
, VC4_F_OP47_16, VC4_F_PCREL32_48, VC4_F_OP47_43, VC4_F_OFFSET27_48
, VC4_F_PCREL27_48, VC4_F_OP79_48, VC4_F_OFFSET23BITS, VC4_F_OFFSET27BITS
, VC4_F_OFFSET12, VC4_F_MAX
, VC4_F_OFFSET12, VC4_F_OP15_10, VC4_F_OP9, VC4_F_OP8_3
, VC4_F_OP2_0, VC4_F_OP4_3, VC4_F_OP31_22, VC4_F_OP31_28
, VC4_F_OP31_29, VC4_F_OP28, VC4_F_OP27_22, VC4_F_OP21_19
, VC4_F_OP21_18, VC4_F_OP18, VC4_F_OP17_16, VC4_F_OP37_32
, VC4_F_OP38, VC4_F_OP41_32, VC4_F_OP42, VC4_F_OP43
, VC4_F_OP47_44, VC4_F_OP38_32, VC4_F_OP41_39, VC4_F_OP41_38
, VC4_F_OP51_48, VC4_F_OP57_52, VC4_F_OP63_58, VC4_F_OP69_64
, VC4_F_OP76_70, VC4_F_OP79_77, VC4_F_OP65_64, VC4_F_OP69_66
, VC4_F_VEC80DREG, VC4_F_VEC80AREG, VC4_F_VEC80BREG, VC4_F_VEC80MODS
, VC4_F_VEC80MODS_MEM, VC4_F_VEC80IMM, VC4_F_VEC80LDADDR, VC4_F_VEC80STADDR
, VC4_F_DUMMYABITS, VC4_F_VEC48DREG, VC4_F_VEC48AREG, VC4_F_VEC48BREG
, VC4_F_MAX
} IFIELD_TYPE;
#define MAX_IFLD ((int) VC4_F_MAX)
@@ -291,9 +370,9 @@ typedef enum cgen_hw_attr {
/* Enum declaration for vc4 hardware types. */
typedef enum cgen_hw_type {
HW_H_MEMORY, HW_H_SINT, HW_H_UINT, HW_H_ADDR
, HW_H_IADDR, HW_H_REG, HW_H_FASTREG, HW_H_PPREG
, HW_H_BASEREG, HW_H_COND, HW_H_ACCSZ, HW_H_PC
, HW_MAX
, HW_H_IADDR, HW_H_REG, HW_H_FASTREG, HW_H_PREG
, HW_H_PPREG, HW_H_BASEREG, HW_H_COND, HW_H_DOTCOND
, HW_H_PC, HW_H_ELTSIZE, HW_MAX
} CGEN_HW_TYPE;
#define MAX_HW ((int) HW_MAX)
@@ -325,23 +404,37 @@ typedef enum cgen_operand_attr {
typedef enum cgen_operand_type {
VC4_OPERAND_PC, VC4_OPERAND_CONDCODE, VC4_OPERAND_PCRELCC, VC4_OPERAND_LDSTOFF
, VC4_OPERAND_ALU16SREG, VC4_OPERAND_ALU16IMM, VC4_OPERAND_ALU16IMM_SHL3, VC4_OPERAND_ALU16DREG
, VC4_OPERAND_ALU32DREG, VC4_OPERAND_IMM6, VC4_OPERAND_FLOATIMM6, VC4_OPERAND_IMM6_SHL1
, VC4_OPERAND_IMM6_SHL2, VC4_OPERAND_IMM6_SHL3, VC4_OPERAND_IMM6_SHL4, VC4_OPERAND_IMM6_SHL5
, VC4_OPERAND_IMM6_SHL6, VC4_OPERAND_IMM6_SHL7, VC4_OPERAND_IMM6_SHL8, VC4_OPERAND_ALU32BREG
, VC4_OPERAND_PPSTARTREG, VC4_OPERAND_PPENDREG0, VC4_OPERAND_PPENDREG6, VC4_OPERAND_PPENDREG16
, VC4_OPERAND_PPENDREG24, VC4_OPERAND_SWI_IMM, VC4_OPERAND_SPOFFSET, VC4_OPERAND_ADDSPOFFSET
, VC4_OPERAND_ALU32AREG, VC4_OPERAND_ALU32COND, VC4_OPERAND_ALU48ISREG, VC4_OPERAND_ALU48IDREG
, VC4_OPERAND_ALU32DREG, VC4_OPERAND_PREGDST, VC4_OPERAND_PREGSRC, VC4_OPERAND_DISP5
, VC4_OPERAND_IMM6, VC4_OPERAND_FLOATIMM6, VC4_OPERAND_IMM6_SHL1, VC4_OPERAND_IMM6_SHL2
, VC4_OPERAND_IMM6_SHL3, VC4_OPERAND_IMM6_SHL4, VC4_OPERAND_IMM6_SHL5, VC4_OPERAND_IMM6_SHL6
, VC4_OPERAND_IMM6_SHL7, VC4_OPERAND_IMM6_SHL8, VC4_OPERAND_ALU32BREG, VC4_OPERAND_PPSTARTREG
, VC4_OPERAND_PPENDREG0, VC4_OPERAND_PPENDREG6, VC4_OPERAND_PPENDREG16, VC4_OPERAND_PPENDREG24
, VC4_OPERAND_SWI_IMM, VC4_OPERAND_SPOFFSET, VC4_OPERAND_ADDSPOFFSET, VC4_OPERAND_ALU32AREG
, VC4_OPERAND_ALU32MISSINGAREG, VC4_OPERAND_ALU32COND, VC4_OPERAND_ALU48ISREG, VC4_OPERAND_ALU48IDREG
, VC4_OPERAND_MEM48SREG, VC4_OPERAND_ALU48IMMU, VC4_OPERAND_ALU48PCREL, VC4_OPERAND_MEM48OFFSET27
, VC4_OPERAND_MEM48PCREL27, VC4_OPERAND_ACCSZ, VC4_OPERAND_ACCSZ32, VC4_OPERAND_CONDCODEBCC32
, VC4_OPERAND_BCC32SREG, VC4_OPERAND_PCREL10BITS, VC4_OPERAND_PCREL8BITS, VC4_OPERAND_BCC32IMM
, VC4_OPERAND_ADDCMPBAREG, VC4_OPERAND_ADDCMPBIMM, VC4_OPERAND_OFFSET23BITS, VC4_OPERAND_OFFSET27BITS
, VC4_OPERAND_OFFSET12, VC4_OPERAND_OFFSET16, VC4_OPERAND_OFFSET16_SHL1, VC4_OPERAND_OFFSET16_SHL2
, VC4_OPERAND_OFFSET16_SHL3, VC4_OPERAND_OFFSET16_SHL4, VC4_OPERAND_PCREL16, VC4_OPERAND_OFF16BASEREG
, VC4_OPERAND_OPERAND10_0, VC4_OPERAND_OPERAND47_16, VC4_OPERAND_OPERAND79_48, VC4_OPERAND_MAX
, VC4_OPERAND_MEM48PCREL27, VC4_OPERAND_CONDCODEBCC32, VC4_OPERAND_BCC32SREG, VC4_OPERAND_PCREL10BITS
, VC4_OPERAND_PCREL8BITS, VC4_OPERAND_BCC32IMM, VC4_OPERAND_ADDCMPBAREG, VC4_OPERAND_ADDCMPBIMM
, VC4_OPERAND_OFFSET23BITS, VC4_OPERAND_OFFSET27BITS, VC4_OPERAND_OFFSET12, VC4_OPERAND_OFFSET16
, VC4_OPERAND_OFFSET16_SHL1, VC4_OPERAND_OFFSET16_SHL2, VC4_OPERAND_OFFSET16_SHL3, VC4_OPERAND_OFFSET16_SHL4
, VC4_OPERAND_PCREL16, VC4_OPERAND_OFF16BASEREG, VC4_OPERAND_OPERAND10_0, VC4_OPERAND_OPERAND47_16
, VC4_OPERAND_OPERAND79_48, VC4_OPERAND_SHL1, VC4_OPERAND_SHL2, VC4_OPERAND_SHL3
, VC4_OPERAND_SHL4, VC4_OPERAND_SHL5, VC4_OPERAND_SHL6, VC4_OPERAND_SHL7
, VC4_OPERAND_SHL8, VC4_OPERAND_V80D32REG, VC4_OPERAND_V80A32REG, VC4_OPERAND_V80B32REG
, VC4_OPERAND_V80IMM, VC4_OPERAND_V80MODS, VC4_OPERAND_V80MODS_MEM, VC4_OPERAND_SETF_MOD
, VC4_OPERAND_V48IMM_MODS, VC4_OPERAND_VMEMWIDTH, VC4_OPERAND_VEC_LDADDR, VC4_OPERAND_VEC_STADDR
, VC4_OPERAND_DUMMYABITS, VC4_OPERAND_DUMMYDBITS, VC4_OPERAND_V48SCLR, VC4_OPERAND_V48IMM
, VC4_OPERAND_V48DREG_H, VC4_OPERAND_V48DREG_V, VC4_OPERAND_V48AREG_H, VC4_OPERAND_V48AREG_V
, VC4_OPERAND_V48BREG_H, VC4_OPERAND_V48BREG_V, VC4_OPERAND_DPLUS_SR0, VC4_OPERAND_APLUS_SR0
, VC4_OPERAND_BPLUS_SR0, VC4_OPERAND_DPLUS_SR1, VC4_OPERAND_APLUS_SR1, VC4_OPERAND_BPLUS_SR1
, VC4_OPERAND_DPLUS_SR2, VC4_OPERAND_APLUS_SR2, VC4_OPERAND_BPLUS_SR2, VC4_OPERAND_DPLUS_SR3
, VC4_OPERAND_APLUS_SR3, VC4_OPERAND_BPLUS_SR3, VC4_OPERAND_DPLUS_SR4, VC4_OPERAND_APLUS_SR4
, VC4_OPERAND_BPLUS_SR4, VC4_OPERAND_DPLUS_SR5, VC4_OPERAND_APLUS_SR5, VC4_OPERAND_BPLUS_SR5
, VC4_OPERAND_DPLUS_SR6, VC4_OPERAND_APLUS_SR6, VC4_OPERAND_BPLUS_SR6, VC4_OPERAND_DPLUS_SR7
, VC4_OPERAND_APLUS_SR7, VC4_OPERAND_BPLUS_SR7, VC4_OPERAND_MAX
} CGEN_OPERAND_TYPE;
/* Number of operands types. */
#define MAX_OPERANDS 59
#define MAX_OPERANDS 114
/* Maximum number of operands referenced by any insn. */
#define MAX_OPERAND_INSTANCES 8
@@ -352,8 +445,8 @@ typedef enum cgen_operand_type {
typedef enum cgen_insn_attr {
CGEN_INSN_ALIAS, CGEN_INSN_VIRTUAL, CGEN_INSN_UNCOND_CTI, CGEN_INSN_COND_CTI
, CGEN_INSN_SKIP_CTI, CGEN_INSN_DELAY_SLOT, CGEN_INSN_RELAXABLE, CGEN_INSN_RELAXED
, CGEN_INSN_NO_DIS, CGEN_INSN_PBB, CGEN_INSN_END_BOOLS, CGEN_INSN_START_NBOOLS = 31
, CGEN_INSN_MACH, CGEN_INSN_END_NBOOLS
, CGEN_INSN_NO_DIS, CGEN_INSN_PBB, CGEN_INSN_SWITCH, CGEN_INSN_END_BOOLS
, CGEN_INSN_START_NBOOLS = 31, CGEN_INSN_MACH, CGEN_INSN_END_NBOOLS
} CGEN_INSN_ATTR;
/* Number of non-boolean elements in cgen_insn_attr. */
@@ -371,6 +464,7 @@ typedef enum cgen_insn_attr {
#define CGEN_ATTR_CGEN_INSN_RELAXED_VALUE(attrs) (((attrs)->bool_ & (1 << CGEN_INSN_RELAXED)) != 0)
#define CGEN_ATTR_CGEN_INSN_NO_DIS_VALUE(attrs) (((attrs)->bool_ & (1 << CGEN_INSN_NO_DIS)) != 0)
#define CGEN_ATTR_CGEN_INSN_PBB_VALUE(attrs) (((attrs)->bool_ & (1 << CGEN_INSN_PBB)) != 0)
#define CGEN_ATTR_CGEN_INSN_SWITCH_VALUE(attrs) (((attrs)->bool_ & (1 << CGEN_INSN_SWITCH)) != 0)
/* cgen.h uses things we just defined. */
#include "opcode/cgen.h"
@@ -387,10 +481,12 @@ extern const CGEN_ATTR_TABLE vc4_cgen_insn_attr_table[];
extern CGEN_KEYWORD vc4_cgen_opval_h_reg;
extern CGEN_KEYWORD vc4_cgen_opval_h_fastreg;
extern CGEN_KEYWORD vc4_cgen_opval_h_preg;
extern CGEN_KEYWORD vc4_cgen_opval_h_ppreg;
extern CGEN_KEYWORD vc4_cgen_opval_h_basereg;
extern CGEN_KEYWORD vc4_cgen_opval_h_cond;
extern CGEN_KEYWORD vc4_cgen_opval_h_accsz;
extern CGEN_KEYWORD vc4_cgen_opval_h_dotcond;
extern CGEN_KEYWORD vc4_cgen_opval_h_eltsize;
extern const CGEN_HW_ENTRY vc4_cgen_hw_table[];
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+68136 -870
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+29 -13
View File
@@ -4,31 +4,47 @@
#include "ppc/ppc_disasm/ppc_disasm.h"
static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
if (!op->bytes || op->size < 4) {
return false;
}
ppc_word iaddr = (ppc_word)op->addr;
ppc_word bof[4];
ppc_word instr = R_ARCH_CONFIG_IS_BIG_ENDIAN(as->config)
? r_read_be32(op->bytes) : r_read_le32(op->bytes);
char opcode[128] = {0};
char operands[128] = {0};
struct DisasmPara_PPC dp = {0};
memcpy(bof, op->bytes, R_MIN(op->size, sizeof(bof)));
dp.opcode = opcode;
dp.operands = operands;
dp.iaddr = &iaddr;
dp.instr = bof;
PPC_Disassemble(&dp, as && as->config ? as->config->big_endian : 0);
if (mask & R_ARCH_OP_MASK_DISASM) {
char res[256];
snprintf(res, sizeof(res), "%s %s", opcode, operands);
op->mnemonic = strdup(res);
}
dp.instr = &instr;
PPC_Disassemble(&dp, 1);
op->size = 4;
if (dp.type == PPCINSTR_BRANCH && !(instr & 2)) {
char *target = strstr(operands, "0x");
if (target) {
snprintf(target, sizeof(operands) - (target - operands), "0x%x",
(unsigned int)(ppc_word)(op->addr + dp.displacement));
}
}
if (dp.flags & PPCF_ILLEGAL) {
op->type = R_ANAL_OP_TYPE_ILL;
return false;
}
if (mask & R_ARCH_OP_MASK_DISASM) {
op->mnemonic = r_str_newf("%s%s%s", opcode, *operands ? " " : "", operands);
}
return true;
}
static int info(RArchSession *s, ut32 q) {
switch (q) {
case R_ARCH_INFO_MINOP_SIZE: return 4;
case R_ARCH_INFO_MAXOP_SIZE: return 4;
default: return 4;
case R_ARCH_INFO_MINOP_SIZE:
case R_ARCH_INFO_MAXOP_SIZE:
case R_ARCH_INFO_INVOP_SIZE:
case R_ARCH_INFO_CODE_ALIGN:
return 4;
default:
return -1;
}
}
@@ -43,7 +59,7 @@ RArchPlugin r_arch_plugin_ppc_disasm = {
},
.arch = "ppc",
.bits = R_SYS_BITS_PACK1(32),
.endian = R_SYS_ENDIAN_BIG,
.endian = R_SYS_ENDIAN_BIG | R_SYS_ENDIAN_LITTLE,
.info = &info,
.decode = &decode,
};
+238 -892
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -8,3 +8,7 @@ ALL_TARGETS+=${TARGET_PPC_DISASM}
${TARGET_PPC_DISASM}: ${OBJ_PPC_DISASM}
${CC} ${CFLAGS} -o ${TARGET_PPC_DISASM} ${OBJ_PPC_DISASM} ${LDFLAGS}
# Keep older r2pm recipes working until they use the arch_ target.
asm_ppc_disasm.${LIBEXT}: ${TARGET_PPC_DISASM}
cp -f ${TARGET_PPC_DISASM} $@
+1 -16
View File
@@ -8,11 +8,6 @@ OBJ_VC4+=cgen-bitset.o
OBJ_VC4+=cgen-dis.o
OBJ_VC4+=cgen-asm.o
OBJ_VC4+=cgen-opc.o
OBJ_VC4+=eval.o
OBJ_VC4+=decode.o
OBJ_VC4+=vc4_decode.o
OBJ_VC4+=vc4_arch.o
OBJ_VC4+=vc4_util.o
SRC_VC4=$(patsubst %.o, %.c, $(OBJ_VC4))
@@ -20,7 +15,7 @@ CFLAGS_VC4=$(R2_CFLAGS)
CFLAGS_VC4+=-g -I$(TOP)/shlr
CFLAGS_VC4+=-Wall -shared -fPIC ${LDFLAGS_LIB}
CFLAGS_VC4+=-I../arch/vc4/ -I../arch/vc4/libvc4/ -I../arch/vc4/include/ -I../arch/vc4/include/opcode/ -I../arch/include/
CFLAGS_VC4+=-I../arch/vc4/ -I../arch/vc4/include/ -I../arch/vc4/include/opcode/ -I../arch/include/
CFLAGS+=$(CFLAGS_VC4)
STATIC_OBJ+=${OBJ_VC4}
@@ -46,16 +41,6 @@ cgen-asm.o: ../arch/vc4/cgen-asm.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
cgen-opc.o: ../arch/vc4/cgen-opc.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
eval.o: ../arch/vc4/libvc4/eval.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
decode.o: ../arch/vc4/libvc4/decode.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
vc4_decode.o: ../arch/vc4/libvc4/vc4_decode.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
vc4_arch.o: ../arch/vc4/libvc4/vc4_arch.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
vc4_util.o: ../arch/vc4/libvc4/vc4_util.c
$(CC) -c $(CFLAGS_VC4) $< -o $@
${TARGET_VC4}: ${OBJ_VC4}
${CC} $(call libname,arch_vc4) ${LDFLAGS} ${CFLAGS_VC4} -o arch_vc4.${LIBEXT} ${OBJ_VC4}
+10
View File
@@ -0,0 +1,10 @@
*.so
*.dylib
*.dSYM/
__pycache__/
test/test_md32
test/test_parse
tools/md32-run
.pmai/
usipdata/
+489
View File
@@ -0,0 +1,489 @@
# Changelog
## Unreleased
### SCQ16 table update (229 forms), the open-reading marker, the broadcast ports, the confidence levels and the public test vectors
* `isa/scq16_isa.json` / `scq16_opcodes.h`: 229 forms / 402 bit alternatives.
`jr rn` (6f ec) reads a 5-bit register field (r0-r31); `btst rd, rd, #pos`
(67 f8-fb) has a 5-bit position; `ctz` is its own form without the
hypothetical flag, `sqr?` belongs to the `op41` placeholder (hypothetical);
`and rd, rs, #0x1|#0x3|#0xf` (2 bytes), `add rd, rs, #-1` (2 bytes) and the
4-byte `sb rd, off(rb)` are `high`, the 4-byte `btst rd, rs, #pos` is `medium`
(settled by execution results).
* `tools/gen_scq16_tables.py`: the flag `SCQ16_FL_OPEN` (an open reading: a
family placeholder or a provisional name) and, for an open alternative
without a template, the uniform ESIL marker `REF,ALT,open`; `export --hyp`
applies the declared plugin hypotheses of `isa/scq16_hyp.json` and records
the file's sha256 in the JSON meta and in the header. The file is empty:
the packing ESIL / p-code of `op41.fld rd, rs, rt, #pos` is carried by the
ISA source (still flagged hypothetical), so every ESIL the plugin emits
comes from the source.
* `scq16_vu.inc.c` / `scq16_arch.inc.c`: the scalar ports `v0-v31` are the
broadcast ports of the vector register file: a write by `vbcast` /
`vbcast.i` / `vbcast.i?` reaches the 64 lanes of `vr_k` (an ESIL register-
write voyeur); the custom op `open` (no effect); `aoj` reports `open`,
`reading`, `hypothetical` and `confidence` per unit.
* `ctz` of a zero operand: -1 under the table ESIL (`31 - clz(0)`); documented
as an open question against the hardware (32).
* `asm_md32.c`: pseudo rule of `op41.fld`.
* `test/scq16_vectors.json` + `test/test_scq16_vectors.py`: 195 public
bytes-only vectors with the expected result in the big-endian regime of the
harness and in the little-endian regime of the core data memory;
`test/test_scq16.py` (267 checks) and `test/test_md32.c`: the new forms, the
marker, the broadcast, the confidence levels, `ctz(0)`.
* `doc/scq16.md`, README: the new forms, the open marker, the broadcast ports,
the declared limits of the vector unit model, the hypothesis file, the
confidence levels, `ctz(0)`.
### SCQ16 table update (402 bit alternatives), the `.post` convention and the vector unit model
* `isa/scq16_isa.json` / `scq16_opcodes.h`: 228 forms / 402 bit alternatives.
`ctz rd, rs` (41 f3 0x22) is an established operation; new provisional
readings: `sqr? rd, rs` (41 f3 0x23), `sra? rd, rd, #pos` (67 fc-fd),
`vrd? rd, vb1[x]` (1a xy), `and? #0xf000?` (67 98-9b and the 2-byte 00/01 with
k = 6), `and? #0xc000?` (67 b4-b7); `op41.fld rd, rs, rt, #pos` reads its third
field as a register; `lw.post rd, [rb] += 4` (21) has a 4-bit destination;
the 3-bit post-increment of the `6e` `.post` family is signed; `sra rd, rd, #imm`
(09) is established; the load templates with a post-increment write rd last
(with `rd == rb` the loaded value wins).
* `scq16_vu.inc.c` (new) / `scq16_arch.inc.c`: the vector unit model of the ESIL:
the register file `vr0-vr31` (128 bytes each, four 256-bit quarters in the
profile), `vp0-vp7` and `va0-va7`; `vop` models `v.mem.ld`, `v.mem.st` and
`v.move.mv` (loads/stores through the VU pointers with the count of `va[A]`
and a post-increment, register copies) and leaves every other op9 without effect.
* `asm_md32.c`: pseudo rule of `sqr`.
* `doc/scq16.md`, README: the new forms, the VU model, the `.post`
convention, the callee-saved profile (r16-r31).
* `test/test_scq16.py`, `test/test_md32.c`: the new forms and the VU model.
### SCQ16 table update (228 forms), the `j.d1` target rule and the vector names
* `isa/scq16_isa.json` / `scq16_opcodes.h`: 228 forms / 400 bit alternatives.
The exporter (`tools/gen_scq16_tables.py export`) takes the ESIL, the delayed
transfers (SETJT/SETD) and the hypothetical flag from the form-description
module itself (its HYP table) and applies no overlay of its own. Vector
names: the established operations `v.move.mv`, `v.mem.ld` and `v.mem.st`
print without the `?` (`vector.firm_op9`), the provisional ones keep it in
the table string.
New or renamed: `cmov.ge rd, rs, #imm, ra`, `cmov.eq rd, rs, rx, ra` (the
register form), the provisional `cmov.leu?` / `cmov.ltu?` / `cmov.lt?` /
`cmov.le?`, `mv rd, rs` (67 c8), `bset` / `btst rd, rd, #pos` (3 bytes),
`lbu.post rd, [rb] += inc` with 5-bit registers (6e q2) and the 23 page as
`lbu.post rd, [rb] += simm3` (-4..-1), `and rd, rs, #0x3fff` (67 a4-a7), the
6-byte `and rd, rs, #imm16` (74 sub 1), the provisional `ctz?` / `clb?` (41 f3),
`vcfg4c? v, rs, #0xc` with the hypothetical `p_k <- rs`, `ld46?` as a
hypothetical byte load with a post-increment, `lbu? rd, off(sp)` (ex `lb?`),
`addsp sp, #imm` (14 8x-fx), `fxts` without the `?`; `halt` and `sys41` carry
the empty ESIL (no modelled effect); `5c 40-6f` is the 4-byte `beq rA, rB`.
* `scq16_decode.c`: `j.d1 56 0x-3x` decided by its target (stand-alone units
and B slots); every ESIL memory access cuts its address to 32 bits (negative
frame offsets wrapped in the 64-bit ESIL machine); the `?` of a vector name
comes with the table string.
* `scq16_arch.inc.c`: a unit with the empty ESIL no longer blocks the ESIL of
its packet; opex `named` / `provisional` of vector ops follow the table name.
* `asm_md32.c`: pseudo rules of `cmov.ge`/`cmov.leu`/`cmov.le`, `sys41`, `ctz`/`clb`.
* `tools/scq16_flags.r2`: the data-space bases (DM, I/O window, DM2) as flags.
* `test/test_scq16.py`, `test/test_md32.c`: the new forms, `j.d1` by target,
`.d2` across two `aes` steps, negative stack offsets, the vector names.
### SCQ16 mode (cpu=scq16): the table (218 forms), the byte-granular packet decoder, analysis and ESIL
* `isa/scq16_isa.json` / `tools/gen_scq16_tables.py` / `scq16_opcodes.h` /
`scq16_decode.c`: the SCQ16 (MD32 family, byte-granular packet variant) as
a third cpu of the plugin: 218 forms / 389 bit alternatives generated from
the ISA table, which carries the plugin semantics (the VU ports as
registers, delayed transfers as SETJT/SETD, the hypothetical readings
flagged), the length rule by first-byte leaves, packets `{ A ‖ B }` with
the head shifted by the borrowed bit, masked orphans, 4-byte vector ops by
op9 class (v.<class>[.<name>?]) and 8-byte bundles.
* `arch_md32.c` / `scq16_arch.inc.c`: analysis (types, targets relative to the
packet start, delay slots counted in instructions, `do` as a loop
reference, opex with the unit/packet/bundle structure), ESIL for the scalar
forms with parallel packet semantics (temporaries t0-t3), delayed transfers
via SETJT/SETD, the VU scalar ports as registers (vu*, v*, va*, vp*, vs*,
vb1_/vb3_/vb5_*, udf*) and vector ops as the custom op `vop`; register
profile with sp/lr as csr14/csr15, a0 and the loop registers; byte alignment,
MAXOP 12.
* `asm_md32.c`: SCQ16 pseudo-syntax (packets as `parallel { ...; ...; }`).
* `test/test_scq16.py`, `test/test_md32.c`: bytes-only checks of cpu=scq16.
* `doc/scq16.md`, README "SCQ16 mode".
### uSIP table update (760 forms), the parallel packet, the 22-bit branches and the slot pairs
* `isa/usip_isa.json` / `usip_opcodes.h`: 760 forms (46 added, 12 retired,
41 modified). The packet executes in parallel and every read sees the
state before the packet (`structure.packet.execution`,
`USIP_PACKET_EXEC_PARALLEL`; no decoding rule depends on an intra-packet
order). New: `movi rB, #imm17` (the op 7 head with the null-field tail, a
17-bit rotated immediate; `movi_min16` / `movi_m32767` retired: with m = 0
their value was -65536 / -65535), `movhi` with the small F' = 0 tails
(`c001` = 0x10000000, even f12 = 0x1000..0x7000), the 16-bit `ld.w rB,
[rA -/+ 4]` of the first slot and the one-unit `st.w rB, [rA -/+ 4]`,
`addi rA, #-4 / -2 / -1` of the class-E cell, `loop rX, last, x16` (B = 1,
the former `loop.b1?`), the guard `loop_b4_straddle16` on the twelve
`loop #N` entries (bit 4 of the head must agree with "the packet at the end
crosses a 16-byte line") and the `loop #N?` rule
(`structure.packet.loop_imm_q`: N = 0 or the end at pc + 2^k), the m = 1
`loop #N` without the `?`, the 22-bit displacement of every `b<cond>.l`
(`U1[7:0] : T[13:12] : T[11:0]`: backward and +4096..+8191 cells) and the
fifteen `bcc48_*_64x` entries (the 64-bit variant whose extension unit is
any 16-bit unit X, with the guard `not_over_validated_call32`), the op c
F = 1 branches with the F' = 0 tail exactly at +2 (`beqz_F0c` / `bnez_F0c` /
`bleu_rr_F0c` / `bltu_rr_F0c`, `ctx:tail_at_plus2`) and the op 9 F = S = 0
cell of >= 4 units (`bne_imm8_F0s0` / `blt_imm8_F0s0`), `bz.s` with the P4
cell F = 0 in 2- and 3-unit packets (`bz_s_P4_F0`), the slot pairs
(`stslot_F0_k16`, the op c pairs `stslot_slotc_h / _t` and their `stslot?`
twins, `ctx:slotc_pair` / `ctx:call_within_2_packets`, `ctx:op4imm_head` on
`stslot_F0_q`, `ldslot_S0_pos1` without the `?`), the fusion at distance 2
`add rB, rA, #rot12` (`add2_rot12`, `fuse_pair` + `tail_adjacency` 2),
`ld.h / ld.hu` with the F' = 0 tail at +2 and any base (`rf0dall_ld_h /
ld_hu`, `ctx:rf0dall_guards`), `saturate`, `mfacc` of the C cell and
`sext16 / zext16` with S = 0 without the `?`; the CSR names `ag0-ag3.{base?,
len?, idx?}` for 0x30-0x3e; the score rules lr32 / rr7 / spodd / rf0dall
nowaw in `structure.packet.tie_breaks`.
* `tools/gen_usip_tables.py`: the guards `loop_b4_straddle16` (exported with
the head field `b4`) and `not_over_validated_call32`, seven new context
conditions, `FUSE | ADJ2` for the fusion at distance 2, the constants of the
packet model and of the `loop #N?` rule.
* `usip_decode.c`: slotpair0 before the layout, the 22-bit prefix
displacement and the 64-bit X variant (the branch keeps U0 U1 T, X is the
first instruction of the delay slot; the guard against a validated 32-bit
call / b of U0 + X), the straddle guard of `loop #N`
(`UsipGuardCtx.packet_units`) and the `loop #N?` text, `ctx:tail_at_plus2`
(fall-through, never `.word`; the fallback pairs carry their distance),
the rf0dall guardians in the f0ctx pass, `bz.s` of the P4 cell F = 0, the
post rules R-add2 (`.hword` heads only) / R-fslot0c before the split pass
and after the prefix paths, R-slotc in the split pass and `slotc_mark`
after the packet (the two following packets through `packet_at`), the
F-slot0 readings by rule, the score rules lr32 / rr7 / spodd / rf0dall
nowaw. Unverified policy: the `loop_imm_F1S0` reading is no longer
accepted (wrong too often), the R-F0c / R-F0s0 branches are (right in
nearly every case), the `b<cond>.l` prefix forms only in their forward cell
below 4096 units (the 22-bit cells and the X variant need the target
validated), the x16 loops as the other `loop rX`.
* `arch_md32.c`: the `packet_units` callback (the units of the packet at the
loop end, from the bytes at hand or the session's cache; an eight-unit run
without a closer is unknown).
* `asm_md32.c`: pseudo syntax for `loop rX, last, x16` and `saturate`.
* Tests: `test/test_plugin.py` vectors for the parallel packet, `movi #imm17`,
the small `movhi` cells, the 16-bit `ld.w` / `st.w` with +-4, the class-E
`addi`, `loop x16`, `loop #N` with the straddle guard (accepted and
rejected), `loop #N?` (validated and unverified), the backward 22-bit
`b<cond>.l` (validated and `.word`), the 64-bit X variant (validated and
fallback), `beqz` of R-F0c (validated and unverified), `blt.d` of R-F0s0
(validated and fallback), `bz.s` of the P4 cell F = 0 (2 and 3 units,
validated and unknown), slotpair0, the op c slot pairs with and without a
call, R-fslot0c, R-add2 and rf0dall; the `loop`, `asr16`, `ld_h_Fp0`,
`movi #-32768 / #-32767`, `loop.b1?`, `loop #256?`, `ldslot?`, `stslot?`
and `saturate?` vectors updated to the new readings; `test/test_parse.c`
vectors for the new pseudo rules.
### uSIP table update (726 forms), the loop cells and the slot splits
* `isa/usip_isa.json` / `usip_opcodes.h`: 726 forms (52 added, 13 retired,
14 modified). New: the `loop #N` count is the collected head field (bits
13:5, no +1), `loop #N` with the F' = 0 tail exactly at +2
(`loop_imm_*_F1_Fp0`), with h = 1 (N >= 128) and with m = 1 (N >= 256,
printed `loop #N?`), `bz.s` in 4- and 3-unit packets (`bz_s_P4_u4` /
`bz_s_P4_u3`), the split entries of the slot family `stslot` / `stslot?` /
`ldslot?` (F-slot0, S0-slot, the S3 cells `ldslot/stslot #48-63`;
`split_role`), the fusion entry `op72? rA, #k, asr#b` (`fuse_pair`),
`op2z? rA`, `lsl/asr r8-r15, #B`, the W6 cells `ld.h/st.h rB, [r8+rrr]` and
`[rA]+` with h = 1, `sub r14, #f12/2` with any F/F' (`spfix_sub_sp`
retired), `st.w [sp], #imm14`, `st.abs` with the hi F' = 0, `add rB, rB,
#imm27` / `st.h? [rB], #sat` selected by the immediate value (also the
cell-E triples), `add? rB, r15, #rot12` and `ld.h? / ld.hu? rB, [r15+off]`
with the F' = 0 tail, `saturate? rd, rs1`, `movi #simm6` in the delay slot
of a validated branch, `mtcsr #0x30-0x3e`, `ld.h / ld.hu / lea / movi
#-32768 / #-32767 / ld.hu [rA+rB]` without the `?` (twelve `*_q` ids
replaced); the constraints `any(rA != 14, m == 1, tail_adjacent == false)`
on `beq_imm8_d` (the op 9 `beq.d` with base sp and the adjacent tail is
not a branch) and `any(m == 1, rA >= 8, x == 0, rB != 15)` on the seven
op a `*_s0f1` / `*_imm8_F0` entries (the `bgt rA, #-1` head class takes
only the adjacent S = 1 tail); the ABI passes eight arguments in r0-r7.
* `tools/gen_usip_tables.py`: the flags `FUSE`, `SPLIT` / `SPLITBARE` (with a
`USIP_FORM_*` index for every split and fusion entry), the constraint op
`instr_index`, `any` with up to four alternatives that may be a context
condition or an instruction position, six new context conditions; the
`loopn` operand type is the collected field.
* `usip_decode.c`: the score in twentieths (al1 = 0.3), the explicit
candidates loopcand / R-brHHTT / R-b111pre / R-b111ctl with the
`b111_layout` context extended to them, the `bz.s` rules of 4- and 3-unit
packets (the rest re-parsed as the delay slot, the 3-unit one with the
target beyond the packet and no write of rA), the S3 renames and the split
pass of the slot family with the split-tail reading and the nowaw guardian,
the fusion after the renames, R-movi6d, the R-w6h1 loop guard evaluated
with the packet being parsed, the R-loopA adjacency (`.word` at any
distance but +2), rf0dmidwaw, the score rules R-waw / al1 / R-bsnext /
P2W-P2H prefix / sat40 H H T T / rf0dpen, the instruction position of the
renames. Unverified fallback: the loop entries with the F' = 0 tail at +2
and the h = 1 heads with the S = 1 tail (and the adjacent F = 0 tail) are
accepted; the m = 1 (`loop #N?`) and the other h = 1 entries are not (wrong
too often when unverified).
* `arch_md32.c`: the stores of an immediate (`[rB]` / `[sp], #imm`) carry the
value in `val` and `[sp]` the stack access; the new mnemonics keep their
kinds (`op2z?` / `op72?` / `saturate?` are unknown operations).
* `asm_md32.c`: pseudo syntax for the stores of an immediate (also to `[sp]`),
the absolute `st.abs` / `ld.abs`, the `?` of a count (`loop#? #256?`).
* Tests: `test/test_plugin.py` vectors for the loop count, the F' = 0 / h = 1 /
m = 1 loops (validated and unverified), the 4- and 3-unit `bz.s` (validated
and unknown), beq9sp, the `bgt rA, #-1` cell, the W6 h = 1 cells and the
loop guard, `op2z?`, the h = 1 shifts, the 16-bit `lea`, the splits
(R-slot4n, R-op4imm in 2 and more units, S0-slot, S3), `op72?`,
`saturate?`, sat40 H H T T, R-waw with the P2W prefix, R-b111pre,
R-b111ctl, R-bsnext, the omitted weak cell of R-brHHTT, `sub r14`,
`st.w [sp]`, `st.abs` hi F' = 0, addq / cell E by value, `mtcsr` of the
accumulator CSRs, R-1eX0F0, R-movi6d; the `lea`, `loop#`, `mtcsr`, `movi
#-32768 / #-32767`, `ld.h` / `ld.hu` of R-1eX0 / R-ldF0 / R-hhttF0 / R-F0d,
`op2z?` and two R-waw vectors updated to the new readings;
`test/test_parse.c` vectors for the new pseudo rules.
### uSIP table update (687 forms), the target filter and the F = 0 branch cells
* `isa/usip_isa.json` / `usip_opcodes.h`: 687 forms (56 added, 3 retired,
71 modified). New: the `b<cond>.d` cells with head F = 0 (`bne.d`, `ble.d`,
`bgeu.d`, `bge.d`, `bne.u8.d`, `beq.u8.d`, `bleu.d`, read only in the
(B class, tail distance) cells of `usip_br0_cells[]`; the five weak cells of
`usip_br0_cells_weak[]` are never taken), `bgt.d` of the F = S = 0 cell back
in the table, `ret.s.d`, `loop.b1? rX, last=` and the `?ss=N` loops, `bz.s`
with bit 3 = 0, the ld/st type 111 `ld.bu` / `st.b` (`st.b?` retired) with
the byte displacement `sext12(f[0]:f[10:1]:f[11])` (`USIP_A_OFFB`, also on
`ld.b`), `ld.b` / `st.h` with F != S of op 1 exactly at +2, `ld.h?` / `ld.hu?`
with the F' = 0 tail as the second head of H H T T and by the deferred tail
of a head with base sp (context group `f0ctx`), the bit-field pair
`fxtu`/`fxts`/`fup` in H H T T and the BF_SEL cells
`fup`/`fup?`/`fxtu`/`fxtu?`/`fxts?` (renamed from `.bf?` with the nowaw
guardian), the 16-bit op 2 shifts `lsl`/`lsl?`/`asr`/`asr? rA, #B` (`addi4`
retired), `ldslot`/`stslot` with m = 1, `.stimm16?`, `ld.w?`/`st.w?` for the
`f12 = 1 / 3 mod 4` word classes, `add r14` / `sub r14` (spfix),
`movi? #-32767`, `movi rA, #-1` with F = 0, `mtcsr?` of the accumulator CSRs
with a named 16-bit tail; the known target as a validation filter on the
F = S = 0 cells, `bne.d #-1`, the loops with the F' = 1 tail, the open `b.s`
(also a function start) and the near `b` (any packet start of the image or a
function start), with fall-through on failure for the F = S = 0 cells
(`USIP_F_GUARDFT`); the operand conventions of six families revised
(`mul`/`divs`/`divu`/`mulu64 A<k>, r<rrr>, rB`, the op 7f bit ops,
`cls1`/`clz`/`ctz`, the op 6 compare-immediates, the 48-bit `add`, `asr16`);
`known16 = false` on the four h = 0 word slots. Thirty-one entries are
post-layout renames.
* `tools/gen_usip_tables.py`: the operand type `offb`, the flags `GUARDFT` /
`B111` / `F0CTX` (`flags` is `ut32`), the constraint ops `br0_cells` /
`br0_cells_live` (cell masks), `bf_valid`, the guards `tail_named16` and
`target_packet_start_any_or_function_start`, the `any` sub-operations
`packet_len`, `guard`, `range`, `br0_cells`, large value sets
(`usip_value_sets[]`), the new context conditions, the tables
`usip_br0_cells[]`, `usip_br0_cells_weak[]`, `usip_c48sym_table[]`; form
names sanitised into C identifiers.
* `usip_decode.c`: the fall-through guards, the fifth guard kind with the
optional `UsipGuardCtx.packet_start_any` callback, the R-br0 cells by B class
and tail distance, the `b111` group in the 2-unit packet and in H H T T (which
now takes tt = 11 tails), the context conditions `b111_layout` /
`hhtt_second_head` / `f0d_triple` / `bfpair` / `bf_dst_not_written` /
`rest_named_no_q`, the `f0ctx` pass on the `.word` pairs and the re-read of
the R-F0d middle unit, R-op1fs (a ld/st entry with `ADJ2` undoes the pair at
any other distance), the `bz_s_P5` context rule, the m = 1 R7 slots, P2H,
R-lr16, R-align48 and R-c48sym in the score, the `b.s` guard admitting a
function start, the loop F' = 0 rule by field; immediates glued to a letter
or `=` (`A0`, `?ss=2`, `w=0`) printed in decimal. Unverified fallback: the
forms whose guard is a validation filter keep their reading when the target
is unknown (`b.s?`, `b?`, `loop?`, `bne.d?` ...; conditional branches only
forward), a definite "not a packet start" rejects them.
* `arch_md32.c`: `UsipGuardCtx` with designated initialisers; the lowest packet
start decoded in the session separates "not a packet start" from "unknown"
for backward targets; `offb` as a `disp` operand in `opex`; stack deltas for
`addsp` and the `add r14, r14, #imm` / `sub r14, #imm` pairs.
* `asm_md32.c`: pseudo syntax for the 2-operand `lsl` / `asr`.
* Tests: `test/test_plugin.py` vectors for the F = S = 0 and R-br0 cells
(validated by the session, rejected with fall-through, unknown backward,
unverified forward), `ret.s.d`, `movi #-1` F = 0, the op 2 shifts, the open
`b.s` rejected / unverified, `bz.s` P5 (2-unit, validated, unknown), the m = 1
slots, `loop.b1?` and the `?ss=N` loops, `movi? #-32767`, the `asr16` clamp,
the bit-field pair and the BF_SEL cells, `ld.bu`/`st.b` with the byte
displacement (plain, 2-unit b111, H H T T), `ld.b`/`st.h` F != S at +2 and
the undo at +1, `ld.h?`/`ld.hu?` of R-hhttF0 / R-F0d, spfix, NEGQ,
`.stimm16?`, `mtcsr?` with a named tail; the `loop`, open `b.s`, `mtcsr`,
`movhi` tt = 10 and `save w=` vectors updated; `test/test_parse.c` vectors
for the new pseudo rules.
### uSIP table update (634 forms), decoder rules and the K-tie cascade
* `isa/usip_isa.json` / `usip_opcodes.h`: 634 forms (52 added, 12 retired,
103 modified). New: the 14-bit word displacement of the two-unit
`ld.w`/`st.w` (W14: `f12 = 2 mod 4` is `+0x1000 + (f12 & ~3)`), `nop.ctl`
for `5e0d`/`1e0d`/`de0d` (never a tail, never a head with a tail), the
bit-field family `fxtu`/`fxts`/`fup`/`fup? rB, rs1, #w, #pos`,
`movi? rB, #-32768`, the register forms `bset`/`bext rd, rs1, rB`, `mtacc?
Ak, rs1:rB` (was `clr.a?`), `sgt rB, rA, #-1` with F = S = 1, `addi rA,
#32` next to `cop rA`, the 16-bit `lsl`/`lsl?`/`sext16`/`zext16` variants,
`ld.w?`/`ld.h?`/`ld.hu? rB, [r15+off]`, `.ldF0?` and the F' = 0 `ld.h?`/
`ld.hu?`, `.3eX0?`, the 20 `b<cond>.d` cells with the `[0][1][tt]` tail at
+2, `movhi` with the tt = 10 tail (IEEE test), 16 non-canonical 48-bit
constants admitted by a value table, the double-class split (`.op4t0`,
`.movi6?`, `.st.h?`/`.st.b?`/`.ld.h?`/`.ld.b?`, `ld.sp`); retired the
`.hi`/`.hilo` pair entries and every `not_hi1010` constraint (H1: a
hi(1010)-shaped unit is an ordinary head unless a 48-bit instruction claims
it), `osl_0_15_2`, `clr.a?`. Twenty entries are post-layout renames
(`rename_from`).
* `tools/gen_usip_tables.py`: `rename_from` lists per entry, the constraint
ops `tail_adjacent`, `ieee_short` and the 48-bit value table membership, the
`any` sub-operation `tail_adjacent`, the context conditions
`cop_rA_in_packet` / `not_c48_lo_follows`, `USIP_MAXVALS` 12, the tables
`usip_never_tail`, `usip_never_head_with_tail` and `usip_c48_table`; the
rename entries are exempt from the 16-bit position-class checks.
* `usip_decode.c`: W14, the never-tail and never-head-with-tail units from the
tables, H1 (the forced hi(1010) decode is gone; `.hi`/`.lo` only after a
48-bit-family head in the packet), the 48-bit value table in the partial
admission, P2W (no alignment penalty for the `= 2 mod 4` word offsets in the
H H T T layout), callin (`UsipGuardCtx.inside_function`), the post-layout
renames (`apply_renames`), the K-tie cascade (`dedup` and `nowaw` over the
tied readings of the packet; `du2` over the neighbouring packets the caller
supplies through `UsipGuardCtx.packet_at` / `branch_target`, first-pass
readings with `USIP_GUARD_NO_CASCADE`), `UsipPacket.tiebreak`; the failed op
C compare-and-branch guard is printed `.dsp` (unnamed data either way).
Unverified fallback extended to the S0F1 cells whose unverified readings are
right in nearly every case (`bne`/`blt`/`bgeu`/`bgtu`/`bge` with immediate,
`beqz.d`/`bnez.d`, `bleu` register) and to `loop rX` with the F' = 0 tail
when the head has F = 1 and the S = 1 tail sits at +2.
* `arch_md32.c`: the cascade context (packets decoded earlier in the session,
first-pass readings of the packets inside the bytes at hand, the branch
targets seen so far), `opex.packet.tiebreak`, `ireg` = `r15` for the r15-based
loads, types for `fxtu`/`fxts` (`cast`) and `fup` (`or`).
* `asm_md32.c`: pseudo syntax for `fxtu`/`fxts`/`fup`, `mtacc`, the 2-operand
`addi`, `sext16`/`zext16`, `asr16`; `nop.ctl` reads `nop`.
* Tests: `test/test_plugin.py` vectors for W14, `nop.ctl`, the bit-field
family, `mtacc?`, `movi? #-32768`, `sgt #-1`, the register `bset`/`bext`, the
S0F1 branches (unverified, validated by the session, not validated), the
r15-based loads, `.ldF0?`, the F' = 0 `ld.h?`, the 48-bit value table,
`.3eX0?`, the `lsl`/`sext16`/`zext16` renames, `cop ‖ addi`, `.hi`, `movhi`
tt = 10, `.op4t0`; the op8 slot split vector updated; `test/test_parse.c`
vectors for the new pseudo rules.
### uSIP table update (594 forms) and the prior-free packet parser
* `isa/usip_isa.json` / `usip_opcodes.h`: 594 forms (137 added, 18 retired,
76 modified). New: the 48/64-bit compare-and-branch prefix forms
`b<cond>.l rB, #imm9 / rA, target` (complete condition table incl. the
unsigned cells and the 64-bit twin with the `1e0d` unit), `loop# #N, last`,
the op 6 set-on-compare family `seq`/`sle`/`slt` and `sgt rB, rA, #rot10`,
`movhi` with the 20-bit value and the small cell, `movi?` for the op b class,
the 48-bit `mov32`/`add` constants by one formula, `st.w`/`st.h [rB], #imm`,
the cell-E triple by position, `ld.d`/`st.d` with the signed offset, the
closed negative half-word (`f12 - 0x1001`) and word (`4*sext10(f12 >> 2)`)
offsets, `add rB, rA, #rot12` for the odd f12 cell, `st.h` with the F' = 0
tail (deferred), the 16-bit first-slot `ld.h`/`st.h`, `movi rA!, #imm5`,
`xor rA, rB` alone, the position cells `ld.w?` (k = 0) / `mfacc?` (k >= 2),
6-bit `ldslot`/`stslot` and the `.op8?` split, `bset`/`bclr?`/`binv?`/`bext?
rd!`, `osl`, `jr rB`, the `sys` twins with F = S = 0, the H H T T loads/
stores, the bank bit of the compared register of the 32-bit branches;
retired `trap?`, `beqz.l`, `jr?`, `movhi?`, `ld.d?/st.d?` (now `ld.d`/`st.d`),
`.ld.abs`, `movi_max32`/`movi_sext27`/`add_r15_imm27` (now `mov32`/`add`),
`sgn?`/`scmp?` (now `sgt`), `bgt.d` of op a.
* `tools/gen_usip_tables.py`: operand types `offh`/`offw`/`loopn` and the
redefined `movhi`, flags `USIP_F_ADJDEF`/`PREFIX`/`RENAME`/`HHTT`, 8 bit
ranges per field, 64-bit entries, constraints `gt`, `odd`, `packet_len_ge`,
`head_index`/`head_pos_ge`, `ctx`, the guard `head_not_validated_bs`, `any`
with up to three alternatives incl. set membership, the `bcc48` condition
table, `save w=4` = `{LR, r8}`.
* `usip_decode.c`: the prefix forms matched on the first units before the
layout (failing guard = not taken), the prior-free total order
`(s0, I, Fv, -A, K)` replacing the enumeration order and the group count,
the grammar rules (`5e0d` never a tail, the deferred `st.h` tail with its undo
and the validated-`b.s` guard, the `.d` SP-negative rejection, the 3-unit and
H H T T explicit candidates with the forced decode of hi(1010) heads, the
`0e10 3fc0 c000` trio, the `loop #N` F = 1 adjacency, the op8 slot split), the
alignment exception `f12 = 3 mod 4 >= 0xf00`, the +0.5 add relabel, +1.0
identical units, +0.75 store-of-immediate triples and +0.5 mfcsr pair
penalties, the position cells (deferred `head_index`/`packet_len`/`ctx`
constraints with fall-through), the late guard evaluation with the undo of a
pair, three tails for the 64-bit forms. Unverified fallback extended to the
prefix forms and to `loop #N` (F = S = 1, F = 0 with the tail adjacent, F = 1
with the S = 0 tail at +2).
* `arch_md32.c`: conditions `bequ`/`bneu`, types for `sle` (`cmp`) and `osl`
(`shl`), `val` = N for `loop #N`, the signed offsets as `disp` operands in
`opex`.
* `asm_md32.c`: pseudo syntax for `loop#`, `sle`, `bequ`/`bneu`, the 2-operand
`xor`, `mfacc`, `bset`/`bclr`/`binv`/`bext`, `osl`; the `?` marker stripped
repeatedly (`ble.l??`); `ld.d`/`ld.w` no longer sign-extended.
* Tests: `test/test_plugin.py` vectors for `loop#` (validated at +1 and +2,
unverified), `b<cond>.l` (validated, unverified, not taken, 64-bit),
`ld.d`/`st.d` with a negative offset, the negative half-word and word offsets,
`seq`/`sle`/`sgt`, `movhi`, the 16-bit first-slot `st.h`, the deferred `st.h`
and its undo by a validated `b.s`, H H T T, the op8 split, the `0e10` trio,
`cop` with F = S = 0, `xor`, `movi rA!`, `bset rd!`, `osl`; the `jr`, `ld.d`,
`movhi` and bank-bit (`bnez r15!`, `beqz.d r2!`) vectors updated;
`test/test_parse.c` vectors for the new pseudo rules.
### MS15E30: `cmulr16`/`cmuli16` names
* `md32_opcodes.h`: the names of 0xDA00 (`cmulr16`, a*c - b*d) and 0xDC00
(`cmuli16`, a*d + b*c) follow the little-endian layout of the data memory (the
high half of a 32-bit complex word is at +2 and holds the real part); the
earlier assignment assumed a big-endian data memory. Vectors in
`test/test_md32.c` and `test/test_plugin.py`.
### uSIP table update (475 forms) and decoder rules
* `isa/usip_isa.json` / `usip_opcodes.h`: 475 forms (110 added, 24 retired,
140 modified). Port I/O `ior`/`iow` replace `mfcr`/`mtcr`; `mtcsr` is back and
`mfcsr` accepts F' = 0; the `sys` shape is split by its tail field (`cop`,
`rti`, `lpexit?`, `isync?`, `ieon`, `ieoff`, `trap?`, `sys?`, `ret`, `ret?`,
`jr?`, `call rB`); `nop32`, `cmov r15`, `add rB, rA, #rot12` for the whole
op 0 cell, `sne`, `scmp?`, `movhi?`, `extu`, `movi #-1`, `addsp`, `lea?`,
`st.w [sp+4/12/20]`, `ld.d?`/`st.d?` (64-bit pairs), `st.b?`, the stack-slot
closers (`ldslot`/`stslot`, `.slot?`, `lsl #1/#2`, `movi #simm6`, `.bit4?`,
`.ext8?`), `ld.hu` for type 110, unsigned 12-bit ld/st offsets, `ld.bu` in
the class-E cells, per-operand bank bits (`rB!` by `x`, `rA!` by `m`, `rs1!`
by `ss`), `save w=2` = four words; the 48-bit `call` with the far tail is
retired (those packets are `call`/`b` with the F' = 0 tail at +2).
* `tools/gen_usip_tables.py`: `movhi` operand type, `base` as a constant added
to register/immediate fields, `scale` on immediates, `ge` and `any`
constraints, membership masks for the large CSR sets, the target guards as
constraint records, flag `USIP_F_ADJ2`, kinds `io`/`jr`, the `sys` name
table, extended CSR names, kind names; the header stays reproducible.
* `usip_decode.c`: F' = 0 control tails exactly at +2, target guards evaluated
after the match (32-bit: failure = `.word`; 16-bit `call.s`: fall-through),
`packet_len` constraints with the real packet length (stand-alone closers),
16-bit closers matched with bit 15, rules R0 (the final closer may be its own
group) and R7 (first-slot `ldslot`/`stslot`/`lea?` when the rest of the
packet is named), the `1dX6 + named tail` and validated-guard tie-breaks;
`UsipGuardCtx` lets the caller answer the guards.
* `arch_md32.c`: packet starts for the guards from the session (packets seen)
and from the bytes at hand (64 bytes of lookahead); function starts are not
available to an arch plugin, the documented fallback applies (`call?`,
`beqz.d?`... = unverified, `opex.target_guard`, confidence `medium`); types
`io`, `rjmp` (`jr?`), `ret` for `rti`, `trap`, `refptr` 8 for the 64-bit
pairs, `cast` for `extu`; CSR names in `opex`.
* `asm_md32.c`: pseudo syntax for the new mnemonics (`ior`/`iow`, `mfcsr`/
`mtcsr`, `addsp`, `ldslot`/`stslot`, `jr`, `nop32`, `sne`, `scmp`, `movhi`,
`extu`, the system ops, 64-bit loads/stores, the `?` marker).
* Tests: `test/test_plugin.py` vectors for `ior`/`iow`, `nop32`, the 32-bit
`ret`, `jr?`, `addsp`, `ld.d?`, the first-slot slot/`lea?` forms, `movhi?`
and the guarded 3-unit control packets (validated, failed and unverified);
`test/test_parse.c` vectors for the new pseudo rules; the `mov32`/`ld.b`
vectors now show the bank bit of the register it applies to (`r15!`).
### uSIP mode rewritten as a table-driven VLIW packet decoder
* `isa/usip_isa.json`: the instruction table (386 forms with masks, operand
fields, constraints and a confidence level) and the packet model;
`tools/gen_usip_tables.py` generates `usip_opcodes.h` from it.
* `usip_decode.c`/`.h`: packet decoder (16-bit unit scan, packets closed by
bit 15, heads-first/round-robin head-tail pairing per group, slot-0-only
control flow, delay slot = rest of the packet, 48-bit forms with hi/lo units,
documented tie-breaks and context rules).
* `arch_md32.c` (`cpu=usip`): one op per run of adjacent units of the same
instruction, `.unit` ops for displaced tails, packet cache in the session,
analysis types/conditions/targets/delay/fail for the whole control-flow
family, loads/stores and immediates, `opex` with form/confidence/packet info,
a register profile with the ABI, `MAXOP_SIZE` 16 in this mode. The previous
five hard-coded patterns (`b0`-`b7`, `save16`, two call shapes) are replaced
by named conditions and the table.
* `asm_md32.c`: pseudo syntax for the uSIP mnemonics (`#` immediates, `[base+off]`
memory operands, compare-and-branch, `save`/`restore`, `loop`).
* `test/test_plugin.py`: uSIP packet vectors (control flow in slot 0, delay
slots, interleaved `S S O O` layout, 48-bit forms, hardware loop, context
rule, truncated packet); `test/test_parse.c` updated for the new mnemonics.
* `test/test_emulator.py`: in usip mode the probe `5ef9 901a` is a delayed return,
so the strict stop is `unsupported_delay_slot_execution` (still zero steps).
* Documentation: README "uSIP mode", `doc/usip.md`.
+61
View File
@@ -0,0 +1,61 @@
R2 ?= r2
PKG_CONFIG ?= pkg-config
PYTHON ?= python3
CC ?= cc
SO_EXT := $(shell $(R2) -H R2_LIBEXT)
PLUGIN = arch_md32.$(SO_EXT)
ASM_PLUGIN = asm_md32.$(SO_EXT)
R2_PLUGIN_PATH ?= $(shell $(R2) -H R2_USER_PLUGINS)
CPPFLAGS += $(shell $(PKG_CONFIG) --cflags r_arch)
CFLAGS += -Wall -Wextra -Wno-unused-parameter -fPIC -O2
LDLIBS += $(shell $(PKG_CONFIG) --libs r_arch)
all: $(PLUGIN) $(ASM_PLUGIN) tools/md32-run
USIP_SRC = usip_decode.c
USIP_HDR = usip_decode.h usip_opcodes.h
# the SCQ16 decoder is included by scq16_arch.inc.c (one table instance): dependencies only
SCQ16_HDR = scq16_arch.inc.c scq16_vu.inc.c scq16_decode.c scq16_decode.h scq16_opcodes.h
$(PLUGIN): arch_md32.c md32_opcodes.h $(USIP_SRC) $(USIP_HDR) $(SCQ16_HDR)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -o $@ arch_md32.c $(USIP_SRC) $(LDLIBS)
$(ASM_PLUGIN): asm_md32.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -shared -o $@ asm_md32.c $(shell $(PKG_CONFIG) --libs r_asm)
install: $(PLUGIN) $(ASM_PLUGIN) tools/md32-run
mkdir -p "$(DESTDIR)$(R2_PLUGIN_PATH)"
cp -f $(PLUGIN) $(ASM_PLUGIN) "$(DESTDIR)$(R2_PLUGIN_PATH)/"
uninstall:
rm -f "$(DESTDIR)$(R2_PLUGIN_PATH)/$(PLUGIN)"
rm -f "$(DESTDIR)$(R2_PLUGIN_PATH)/$(ASM_PLUGIN)"
tools/md32-run: tools/md32-run.c arch_md32.c md32_opcodes.h $(USIP_SRC) $(USIP_HDR) $(SCQ16_HDR)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ tools/md32-run.c $(USIP_SRC) $(shell $(PKG_CONFIG) --libs r_anal)
test/test_md32: test/test_md32.c arch_md32.c md32_opcodes.h $(USIP_SRC) $(USIP_HDR) $(SCQ16_HDR)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ test/test_md32.c $(USIP_SRC) $(LDLIBS)
test/test_parse: test/test_parse.c asm_md32.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ test/test_parse.c $(shell $(PKG_CONFIG) --libs r_asm)
test: $(PLUGIN) $(ASM_PLUGIN) tools/md32-run test/test_md32 test/test_parse
./test/test_md32
./test/test_parse
$(PYTHON) test/test_plugin.py
$(PYTHON) test/test_emulator.py
$(PYTHON) test/test_firmware.py
$(PYTHON) test/test_scq16.py
$(PYTHON) test/test_scq16_vectors.py
usip_opcodes.h: isa/usip_isa.json tools/gen_usip_tables.py
$(PYTHON) tools/gen_usip_tables.py $< -o $@
scq16_opcodes.h: isa/scq16_isa.json tools/gen_scq16_tables.py
$(PYTHON) tools/gen_scq16_tables.py $< -o $@
clean:
rm -f arch_md32.so arch_md32.dylib asm_md32.so asm_md32.dylib test/test_md32 test/test_parse tools/md32-run
.PHONY: all install uninstall test clean
+491
View File
@@ -0,0 +1,491 @@
# MD32 architecture plugin
Native radare2 `RArchPlugin` for the MediaTek MD32 **MS15E30** ISA. The decoder
covers all **258 encoding forms** in the public vendor disassembler, including
16/32-bit instructions, packed instruction pairs, DSP operations, control
registers, and hardware-loop setup. It has no runtime dependency on Python,
Ghidra, Capstone, or vendor binutils.
The plugin serves three encodings of the same modem DSP line, selected with
`asm.cpu`:
* **MS15E30** (the default): the public MD32 ISA, 16/32-bit instructions.
* **uSIP** (`e asm.cpu=usip`): a later MD32 variant that packs 16-bit units into
VLIW packets. Same register model and much of the same vocabulary as MS15E30,
but a different encoding; see [uSIP mode](#usip-mode).
* **SCQ16** (`e asm.cpu=scq16`): a byte-granular, big-endian packet encoding
that is **not** an MD32 encoding. It is served by this plugin because its
scalar programming model (32 general registers, `sp`/`lr` as control
registers, calling convention, the scalar operations and their ESIL) follows
MD32 closely; its instruction table is its own. See [SCQ16 mode](#scq16-mode).
The remaining DSP cores of the same modem use an unrelated SIMD ISA
(byte-granular, little-endian) and are out of this plugin's scope.
## Build and use
Requires radare2 development headers/libraries, `pkg-config`, a C compiler, and
make. Tested with radare2 6.2.5 on macOS arm64 and on Linux x86-64 (`make test`),
and 6.2.2 on Linux x86-64 (against 6.2.2 the optional `tools/md32-run` runner does not build because
`r_esil_set_fault` is missing from that release: build the two plugins with
`make arch_md32.so asm_md32.so`).
```sh
make
make install
r2 -a md32 -b 32 -e cfg.bigendian=true firmware.be.bin
```
`make install` copies the architecture and assembly parse plugins to
`r2 -H R2_USER_PLUGINS`. Override `R2_PLUGIN_PATH` to choose another location.
`make uninstall` removes both.
Without installation, load the built library using `L /absolute/path/arch_md32.dylib`
inside r2 (`.so` on Linux), then set `e asm.arch=md32`.
For pseudocode display, enable `e asm.pseudo=true`. The `md32` assembly parser
is selected automatically with `asm.arch=md32`; select it explicitly with
`e asm.parser=md32` if needed. Without installation, load `asm_md32.dylib`
alongside `arch_md32.dylib` before selecting the architecture.
The parser covers scalar moves, arithmetic, bit operations, signed/unsigned
comparisons, calls, jumps and Harvard data-memory accesses. For example,
`lw r2, (r1+=0x8)` becomes `r2 = dmem32[r1]; r1 += 0x8`. Signed byte/halfword
loads use `sext8`/`sext16`; packed pairs display as `parallel { ...; ...; }`.
Delayed control flow and unresolved DSP operations retain their assembly text.
In `usip` mode the same rules apply to the uSIP mnemonics (`ld.w r0, [r8+0x4]`
becomes `r0 = dmem32[r8 + 0x4]`, `bnez r15, 0x418` becomes
`if (r15 != 0) goto 0x418`); format-only `.xxx` forms and `.unit` tails keep
their text. This is a display parser; it does not add instruction encodings or
ESIL semantics.
Instructions use big-endian byte order and two-byte alignment. Older VPU/DSP
blobs store the instruction stream in reversed **four-byte storage words**.
Normalize those files before opening them:
```sh
python3 tools/swap_words.py vpu_p.bin firmware.be.bin
```
This is not ordinary per-instruction byte swapping: a four-byte storage word
may contain two instructions or part of an instruction crossing its boundary.
Do not apply this conversion to the already big-endian uSIP samples.
## Supplied uSIP samples
```sh
r2 -a md32 -b 32 -e cfg.bigendian=true -c 'e asm.cpu=usip' \
-m 0x400e0000 third_party/r2pkg/bin/usip_sec0.bin
r2 -a md32 -b 32 -e cfg.bigendian=true -c 'e asm.cpu=usip' \
-m 0x1ca34000 third_party/r2pkg/bin/usip_ddr.bin
```
Optional symbol flags, generated directly from the supplied inventory:
```sh
python3 tools/usip_symbols.py --section sec0 > /tmp/usip-sec0.r2
python3 tools/usip_symbols.py --section ddr > /tmp/usip-ddr.r2
```
Load the corresponding script with `. /tmp/usip-sec0.r2` or
`. /tmp/usip-ddr.r2`. The first 0x400 bytes of sec0 are the vector table;
use the symbol inventory to choose code entry points. These are supplied load
aliases; the reset-vector conflict reported by `tools/analyze_firmware.py`
means they are not a validated boot memory map.
## uSIP mode
`e asm.cpu=usip` selects the decoder for the **uSIP** variant of the MD32 family
(VLIW execute packets of 16-bit units). The instruction set is described by the
table `isa/usip_isa.json` (760 forms, generated into `usip_opcodes.h` by
`tools/gen_usip_tables.py`) and by the packet model implemented in
`usip_decode.c`. The table is an empirical description, not a vendor
specification: every form carries a confidence level (`high`, `medium`, `low`,
`format-only`) that the `aoj` output reports under `opex.confidence`.
### The packet model
* The unit is the 16-bit big-endian halfword. A **packet** is the run of units up
to and including the first one with bit 15 set (at most 8 units); packets start
at 2-byte alignment and the units of a packet execute in parallel.
* An **instruction** is a *head* (bit 15 = 0) plus 0, 1 or 2 *tails*
(16/32/48 bits; the 64-bit `b<cond>.l` prefix forms have three). Tails are not
necessarily adjacent to their head: a packet is a sequence of groups laid out
*heads first, then tails round-robin* (`S S O O` is two 32-bit instructions
interleaved; `x008 X hi lo hi' lo'` a 48-bit call interleaved with another
48-bit op). No bit selects the layout. The decoder enumerates every group split
and head/tail assignment the table allows, adds the explicit candidates of the
packet grammar (the 3-unit `[head][op16][F' = 0 tail]`, the 4-unit `H H T T`
pair of loads/stores) and keeps the smallest reading in the **prior-free total
order** `(s0, I, Fv, -A, K)`: unknown units plus the documented tie-break
penalties, then the number of instructions, then the heads whose tail
adjacency contradicts the F pattern (head F = 0 and next unit bit 14 = 1 means
an adjacent tail, otherwise a deferred one), then minus the number of adjacent
tails, then the lexicographic key of the assignment. Readings tied on the first
four go through the **K-tie cascade**: `dedup` (identical instruction texts),
`nowaw` (the only reading without a double register write) and `du2` (a
differential use-definition score over the neighbouring packets); a tie the
cascade decides is reported as `opex.packet.tiebreak`, one it cannot decide is
left to the key and reported as `opex.packet.ambiguous`.
* The **prefix forms**: a packet whose first three (four) units are a 48-bit
(64-bit, with the `1e0d` extension unit) `b<cond>.l rB, #imm9 / rA, target`
whose target is a packet start of the same function is that instruction plus
its delay slot (the rest of the packet); a failing guard just means the normal
layout. The absolute `call.abs`/`b.abs` prefix (`x008 + hi + lo`) is the other
prefix rule.
* Only **slot 0** (the first unit) can be control flow: `b<cond>`, `b`, `call`,
`loop rX`/`loop #N`, `ret`, the 16-bit `bz.s`/`bnz.s`/`b.s`/`call.s`,
`call.abs`/`b.abs`, the `b<cond>.l` prefix forms (the 32-bit `ret`/`ret?`,
`jr rB` and `call rB` of the `sys` shape may sit anywhere). The tail of a
branch/call/loop is at +1 or +2; the control tails with F' = 0 (`call`/`b`/
`b<cond>` with a 16-bit op in between) and `loop #N` with F = 1 and an S = 0
tail sit exactly at +2. When the control instruction does not close the
packet, the **rest of the packet is its delay slot** (both paths execute it).
The plugin reports `delay` = number of ops that follow in the packet and
`fail` = the next packet.
* Grammar rules of the layout: `5e0d` is never a tail and `5e0d`/`1e0d` never
take one (they are the structural no-op `nop.ctl`); a unit with bits 12:9 =
1010 is a `hi` syllable only inside a formed 48-bit instruction, otherwise an
ordinary head (a lone `.hi`/`.lo` only after a 48-bit-family head in the
packet); the `st.h` with the F' = 0 tail takes it at +2 or farther (an
adjacent tail is two 16-bit ops) and in the first slot loses against a `b.s`
head whose target is a packet start; an op 5 head on SP never takes a negative
`ld.d`/`st.d` tail; the immediate set-on-compare forms need an adjacent tail;
an `.op8?` pair of two slot-family units is two `ldslot`/`stslot` ops; an op 0
head takes `hi + lo` as one of 16 non-canonical 48-bit constants admitted by
value; the identical-bit cells `mfacc?` (k >= 2) and `add?`/`st.h?` (cell E)
are selected by the position of the head inside the packet. Once the layout
is decided, twenty **post-layout renames** relabel the instructions they
match (`nop.ctl`, the 16-bit `lsl`/`lsl?`/`sext16`/`zext16` variants, `addi
rA, #32` next to `cop rA`, `ld.w?` of cell A, the r15-based loads, the
register `bset`/`bext`, `.ldF0?` and the F' = 0 `ld.h?`/`ld.hu?`, `.3eX0?`).
* Some control forms carry a **target guard**: `loop`, `loop #N`, `b<cond>` and
`beqz.d`/`bnez.d` with the F' = 0 tail, the `b<cond>.d` with the `[0][1][tt]`
tail at +2 and the `b<cond>.l` prefix forms need a target at a packet start of
the same function, `call` with the F' = 0 tail and the open-packet `call.s`
need a function start, far `b` either. A 32-bit pair whose guard fails is data
(`.word`; the op C compare-and-branch cell leaves a `.dsp` pair); the 16-bit
`call.s` falls through to the plain reading; a prefix form is simply not
taken. A relative `call` into the middle of a known function pays a tie-break
penalty (`callin`) when the embedder can tell. See
[target guards](#target-guards) for what the plugin can check.
* Hardware loops: `loop rX, last=ADDR` / `loop# #N, last=ADDR` repeat the
following instructions up to `ADDR` (inclusive) `rX` / `N` times; they are
reported as a reference (`ptr`), not a branch (`val` = N).
### What one `pd` line is
A decode is one **op per run of adjacent units that belong to the same
instruction**: an instruction whose tails are adjacent is one op of 4, 6 or 8
bytes; an interleaved instruction shows its head as a 2-byte op with the full
operands and its displaced tails as `.unit 0xNNNN (mnemonic @ head)` ops, so that
every address of the stream is covered exactly once and the sizes add up. The
decoder needs the packet context: the decode at a packet start parses the whole
packet and caches it in the session, the following decodes inside the packet
(linear disassembly, function analysis) reuse that reading, and a decode at an
address that is not cached is treated as a packet start — true for branch
targets and function starts, not for an arbitrary mid-packet address, which may
then read differently than in the linear listing. `R_ARCH_INFO_MAXOP_SIZE` is
16 in this mode so that `ao`/analysis see the whole packet.
### Mnemonics and operands
Registers `r0-r15` (`r14` = SP, `r15` = LR); the second bank is written `rN!`
on the operand it applies to (bit `x` = the data/destination register, bit `m` =
the source/base register, also the compared register of the 32-bit branches and
the destination of `movi rA!, #imm5`, `ss & 2` = the first source of the
three-register ALU forms). Accumulators `A0-A3` (`mfacc rA, Ak.{lo,hi,ext}`),
port I/O `ior rB, [rA]`/`iow rB, [rA]` (`rA` holds the port number; there is no
classic CSR bank on this core), extended CSRs by number (`mfcsr rB, #nn`,
`mtcsr #nn, rA`), the system shape by name (`cop rA`, `rti`, `ieon`, `ieoff`,
`idle`, `lpexit?`, `isync?`, `sys #n`; with the F = S = 0 tail the packet stays
open), `nop32`, `addsp #imm` (SP adjust), `lea rA, [sp+off]`,
`ldslot`/`stslot rB, #k` (stack-slot access, 6-bit slot: the stack arguments
from the ninth on; `stslot?`/`ldslot?` where the slot or the role is open),
`sub r14, #imm` (frame allocation), `st.w [sp], #imm` (the ninth stack argument),
`ld.d`/`st.d rB:rB+1, [rA+off]` (64-bit pairs, signed offset multiple of 4),
`st.w`/`st.h [rB], #imm` (store of an immediate), `mov32 rB, #imm32` /
`add rB, rB, #imm32` (48-bit constants, by formula or by the value table),
`movhi rB, #imm` (20 bits << 12), `movi? rB, #imm20` / `movi? rB, #-32768`, the
set-on-compare family `seq`/`sne`/`sle`/`slt`/`sgt rB, rA, #imm`, `osl rB, rs1,
#1` (funnel shift), `bset`/`bclr?`/`binv?`/`bext? rd!, rs1, #imm5` and the
register forms `bset`/`bext rd, rs1, rB`, the bit-field family
`fxtu`/`fxts`/`fup rB, rs1, #w, #pos` (extract unsigned / signed, insert; `extu`,
`asr16`, `osl` keep their names), `mtacc? Ak, rs1:rB`, `nop.ctl`, `jr rB`.
Immediates carry a `#`; memory operands are `[rA+off]`, `[rA-off]` (the closed
negative bands of the half-word and word offsets; the word offsets have 14 bits:
`[rA+0x1xxx]`), `[rA]+`, `[rA+rB]`, `[sp+off]`, `[r15+off]`, `[#addr]`. Branch
targets and `loop` ends are printed absolute; the 48/64-bit compare-and-branch
forms are `b<cond>.l rB, #imm9, target` / `b<cond>.l rB, rA, target` (`bne beq
ble blt bge bgt bgeu bgtu`, the open cells `ble.l?`, `bneu.l?`, `bequ.l?`,
`bcc48?`), the delayed 32-bit ones `b<cond>.d` (also with the `[0][1][tt]` tail
at +2). Forms whose operation is unknown but whose grouping is established are
**format-only** and start with `.` (`.hword`, `.word`, `.w48`, `.dsp`, `.op4imm`,
`.op4t0`, `.ldF0?`, `.3eX0?`, `.slot?`, `.hi`/`.lo`/`.ext` ...) or end with `?`
(`ld.w?`, `mtacc?`, `fup?`): they are grouped correctly so that the packet and
delay slot model stays right, and reported with the `unk` type. `.hi`, `.lo`,
`.ext`, `.be79`, the 2- to 4-unit `bz.s`, the first-slot `ldslot`/`stslot`/`lea`, the
split `ldslot`/`stslot` halves (the `.op8?` pairs and the post-layout splits of the
slot family), the fused `op72?` pair, `movi #simm6` in a delay slot and the
`H H T T` loads/stores come from context rules, not plain table entries.
A control instruction printed with a trailing `?` (`call?`, `beqz.d?`, `bne.l?`,
`loop#?`, `bne.d?`) is one whose target guard could not be evaluated (see below).
### Target guards
The guarded forms need to know whether the target is a packet start of the
current function or a function start. The plugin derives **packet starts from
the stream**: the packets decoded earlier in the session (backward targets, and
forward targets of a region already decoded, e.g. after `aa` or on a second
`pd`) and a scan of the bytes radare2 hands the decoder past the current packet
(at most 64 bytes of lookahead). A forward target farther away is unknown.
**Function starts are not available** to an arch plugin (radare2 gives it no
typed access to RAnal). When a guard cannot be evaluated the plugin applies a
fallback measured on the sample the table was built from: the forms whose
unverified readings are right in nearly every case are read anyway, printed
with a trailing `?`, confidence `medium` and `opex.target_guard = "unverified"`
(`call` with the F' = 0 tail at +2, `b` with the F' = 0 tail, `b<cond> rA, #imm`
with the F' = 0 tail, `beqz.d`, the `b<cond>.l` prefix forms with a forward
target, `loop #N` with F = S = 1, with F = 0 and the tail adjacent, with F = 1
and the S = 0 tail at +2, with the F' = 0 tail at +2 and with h = 1 (N >= 128)
and the S = 1 tail or the adjacent F = 0 tail, `loop rX` with F = 1 and the
S = 1 F' = 0 tail at +2, and the `b<cond>.d` cells with the `[0][1][tt]` tail
`bne`/`blt`/`bgeu`/`bgtu`/`bge` with immediate, `beqz.d`/`bnez.d` and `bleu`
with registers); the others are treated as a failed guard (`loop rX` with the
other F' = 0 tails, `loop #N` with F = 0 and the tail at +2, the m = 1
`loop #N?` heads and the other h = 1 loop cells, far `b`, `bnez.d` of the
F = S = 0 cell, the register `b<cond>` with the F' = 0 tail, the other
`b<cond>.d` cells, the open-packet `call.s`), i.e. `.word`, the plain 16-bit
reading or the normal layout. The forms whose guard is a validation filter (the
F = S = 0 and the F = 0 `b<cond>.d` cells, `bne.d #-1`, the hardware loops with
the F' = 1 tail, the near `b`, the open `b.s`) keep their reading when the
target is unknown (printed with the `?` too) and are rejected by a known target
that is not a packet start; a conditional branch whose unknown target lies
backward is not read. A validated guard reports `opex.target_guard =
"validated"`. The K-tie cascade sees the packets decoded earlier in the session
and the ones inside the same lookahead (see `doc/usip.md` §7 for what is
approximated).
### Analysis
Operation types: `cjmp` with `cond` for the compare-and-branch forms (there are
no flags on this core; `bequ`/`bneu` are the unsigned equality cells of the
48-bit forms), `jmp`, `call`/`rcall`, `ret` (also `rti`), `rjmp` for `jr rB`,
`io` for `ior`/`iow` (`ireg` = the port register), `load`/`store` with
`refptr`/`ireg`/`disp` (`stackop` on `r14`; `refptr` 8 for `ld.d`/`st.d`; the
signed offsets also appear as the `off` operand of `opex`), `mov` with `val` for
the immediate moves (`movi`, `movi?`, `mov32`, `movhi`),
`add`/`sub`/`and`/`or`/`xor`/`shl`/`shr`/`sar`/`mul`/`div`/`cmp`/`cmov`/`cast`
(`osl` is a `shl`, the set-on-compare family `cmp`, `fxtu`/`fxts` `cast`, `fup`
`or`), `lea` for `lea`/`lea?`, `nop` for `nop32`/`nop.ctl`, `rpush`/`pop` for
`save`/`restore`, `trap` for `trap?`, `swi` for the other system ops; `loop #N`
carries the count in `val`. `opex` carries the form id, confidence, kind, slot,
unit addresses, the delay-slot flag, the target-guard status, the packet's
ambiguity / tie-break and the operands.
The register profile exposes the ABI (`SP=r14`, `LR=r15`, arguments `r0-r7`,
return `r0`). No ESIL is produced for uSIP.
### Limitations
* The reading is table-driven, deterministic and prior-free. Four kinds of
packets can read differently from the intended one: packets where several
head/tail assignments tie on the first four keys of the layout order and the
K-tie cascade cannot decide (flagged by `opex.packet.ambiguous`) or decides
them with a shorter context than a whole-function scan would give (`du2`),
control flow in open packets (`call`/`b`/`loop`/`loop #N`/`b.s` followed by
delay-slot ops) when the branch reading is not the cheapest one (a `loop #N`
reading with a 16-bit middle unit often writes a register twice with the
stolen tail, which the R-waw rule charges), the guarded forms whose
target the plugin cannot check (far `b` to another function or beyond the
lookahead, the open-packet `call.s`, `loop`/`bnez.d`, the `b<cond>.d` register
cells and the `bz.s` of 3-unit and longer packets with a forward target
beyond the lookahead), and the `call` pairs whose tail choice depends on the
`callin` tie-break. A disassembler that knows the function table can prefer
the reading whose target is a function start or a packet boundary and answer
`callin`; `UsipGuardCtx` in `usip_decode.h` is the hook for that knowledge.
* Open points of the ISA: the remaining layout ties, the sign and width of the
`?` forms (`ld.d?`/`st.d?` with b10, `ld.w?`/`st.w?` in the 0x800-0xeff band,
`seq?`/`sle?` with large immediates, `mov32?`, `lea?`, `mfacc?`, `add?`/`st.h?`
of cell E, `bcc48?`, `movi?`, `mtacc?`, `fup?`, `.ldF0?`), the `f12 = 1 / 3
mod 4` word displacements below 0x800, `.op4t0` (a stolen tail), `.3eX0?`,
`ld.w` vs `add` in the op 0 F = S = 1 cell, the exact
`save w` frame size, `sys #2/#4/#8`, `L_mult` vs `L_mult.e`, why the scheduler
inserts `nop.ctl`, the `?ss=N` loops, the sign of the
`ld.w?`/`st.w?` word classes, `.stimm16?`, the `lsl?`/`asr?` cells without an
idiom.
* A decode in the middle of a packet without the packet cached (e.g. `ao` at an
arbitrary address) sees a different packet than the linear listing.
* At the end of a map, `ao` pads the read with `0xff`, which is a legal closing
unit; the disassembly path (`pd`) gets the exact bytes and marks the packet
`truncated`.
See [doc/usip.md](doc/usip.md) for the table format and the constraint and
context-rule semantics.
## SCQ16 mode
`e asm.cpu=scq16` selects the decoder for the **SCQ16**, a byte-granular
encoding of the same modem DSP line (not an MD32 encoding; it shares the
scalar programming model, see the introduction) whose instruction stream is byte-granular: units of 2 to 6
bytes (vector operations are 4 bytes, bundles 8), optionally paired in a
**packet** `{ A ‖ B }` whose two units execute in parallel. The table is an
empirical description, not a vendor specification: every form carries a
confidence level (`high`, `medium`, `low`, `format-only`) that the `aoj` output
reports under `opex.units[].confidence`. Select it with
```sh
r2 -a md32 -b 32 -e cfg.bigendian=true -e asm.cpu=scq16 firmware.be.bin
```
Functions may start at odd addresses (`asm.cpu=scq16` sets the code and
function alignment to 1). Inside a session set `e asm.cpu=scq16` before
`e asm.arch=md32` (or re-select `asm.arch` afterwards): the register profile
of the SCQ16 (`sp`/`lr` as csr14/csr15, the VU ports) is read when the arch
session is created. The table is `isa/scq16_isa.json`, compiled into
`scq16_opcodes.h` by `tools/gen_scq16_tables.py`. See
[doc/scq16.md](doc/scq16.md) for the encoding model, the length rule and the
form families.
### What one `pd` line is
* a stand-alone unit: `lw r5, 0x4(r1)`, `jal.d1 0x00001010`;
* a packet: `{ mv r1, r3 ‖ beqz r5, #0, 0x0000100e }`: the first
byte of the head has its bit 7 set, the head is read shifted left by one bit
with the top bit of the second unit borrowed in (the `transformed` field of
`aoj` shows the head as decoded); control flow only lives in the second
slot, and its targets are relative to the packet start;
* a vector operation: `v.alu3 op9=0x1c1 A0 B0 F1,F2,F3`: the op9 code is
printed with its class (`v.<class>`), with an established operation name
when there is one (`v.move.mv`, `v.mem.ld`, `v.mem.st`) and with a
provisional name as `v.<class>.<name>?`; the fields A/B/F1-F3 are shown raw;
* a bundle: `vbundle v.mem.ld op9=0x00a … ‖ add r2, r6 ‖ add r3, r1`: a
32-bit vector operation and two 15-bit scalar slots in 8 bytes.
Provisional mnemonics carry a `?` (`sel?`, `cmov.leu?`, `vrd?`, `vwr?`,
`vpmem?`, `lhu.post?`, `ld46?`, `sqr?`, `sra?`, `vwait?`…): the encoding is fixed, the
operation is not. Family placeholders (`op41`, `op43`, `op67`, `op6f`, `alu41`…) print
their fields and are not named. Both are OPEN readings: `aoj` reports them with
`open: true` and `reading: provisional | placeholder` (`established` otherwise, and
`hypothetical: true` when the ESIL is the reading of a hypothesis), and an open
reading without semantics has the uniform ESIL marker `REF,ALT,open` (no effect).
### Analysis and ESIL
`aoj` gives the type, `jump`/`fail` (fall-through after the delay slots,
which are counted in instructions: `.d1` one, `.d2` two), `delay`, the loop
end of `do` as a reference (`ptr`) with the count in `reg`/`val`, loads and
stores with `refptr`/`ireg`/`disp`, the stack ops, and an `opex` with the
`shape` (unit/packet/bundle), the `units[]` (slot, address, size, mnemonic,
form, confidence, operands) and `parallel`.
The ESIL covers the scalar forms: a packet is emitted as A followed by B with
the registers that B reads and A writes copied to the temporaries `t0-t3`
first (parallel semantics), delayed transfers use `SETJT`/`SETD` so that `aes`
executes the slot before the jump (a `.d2` transfer takes two `aes` steps),
memory addresses are cut to 32 bits, `do` writes `lc0/le0/ls0`, division fills
`a0l`/`a0h`. The VU scalar ports are registers (`vu0-vu31` for `vget`,
`v0-v31` as the scalar shadow of `vbcast`, `va0-va7`, `vp0-vp7`, `vs0-vs15`,
`vb1_*`/`vb3_*`/`vb5_*`, `udf0-udf31`) and every vector operation is the custom
op `vop` (op9, A, B, F1, F2, F3). `vop` models the three established vector
operations on a vector register file `vr0-vr31` (128 bytes each, shown as the
256-bit quarters `vrK_0..vrK_3`): `v.mem.ld` loads `va[A]` halves from the
pointer `vp[2*F2 + F3[4]]` into `vr[F1 >> 2]`, `v.mem.st` stores them from `vr0`,
both post-incrementing the pointer, and `v.move.mv` copies `vr[F3]` into
`vr[F1]`; every other vector op has no effect. `vbcast v_k, rs` / `vbcast.i`
write the scalar port `v_k` and the plugin broadcasts the low 16 bits to the 64
lanes of `vr_k`. Forms without an established operation have the marker ESIL
`REF,ALT,open`, except the hypothetical readings flagged in the table (among them
the packing of `op41.fld`, carried by the ISA source; the file of
plugin hypotheses `isa/scq16_hyp.json` is empty). `ctz` of a zero operand gives
-1 in the ESIL (`31 - clz(0)`); whether the hardware returns 32 is open.
Registers: `r0-r31`, `pc`, `sr`/`ipc`/`isr`/`lf`, the loop registers
`ls0-2`/`le0-2`/`lc0-2`, `sp` and `lr` as the special registers csr14/csr15
(the public MD32 ABI: arguments r3-r6, return r1; r16-r31 are callee-saved,
r0-r15 caller-saved), the accumulator `a0` (`a0l`/`a0h`), the VU state.
The data-space bases (main data memory at 0, the I/O window at 0x8000, the
second data memory at 0x40000) can be added as flags with
`. tools/scq16_flags.r2`; the data memory is little-endian while the code is
big-endian. `j.d1 56` with the top bits of its second byte clear is decided by
its target (see doc/scq16.md).
## Analysis and emulation
The MS15E30 decoder provides operation types, direct targets, register-indirect
jumps/calls, returns, condition codes, delay counts, memory widths and signedness,
operand values, JSON operands, and the register profile. Relative targets wrap
at 32 bits. Delay-slot counts are in decoded instructions/bundles; the
fallthrough address is computed from their actual lengths when lookahead is
available. If lookahead is insufficient, delayed fallthrough remains unknown.
ESIL covers register/immediate moves, ordinary integer arithmetic and logic,
logical/arithmetic shifts, leading-bit counts, scalar min/max, conditional
inversion, multiplication, loads/stores, and immediate control flow,
including signed/unsigned comparisons. It does not model carry/borrow flags,
DSP saturation/rounding, interrupt status restoration, delayed control-flow
execution, hardware-loop execution, or parallel bundle execution. These
instructions still disassemble. Hardware-loop setup reports the loop-end
reference; automatic loop back edges are not inferred.
A packed pair is one four-byte decode unit, displayed as `slot0 | slot1`.
Control flow in its second slot is retained. Other pairs have `unk` as their
aggregate operation type, and operand JSON records their slot numbers. This
preserves decode boundaries without inventing a sequential execution model.
Program and data memory are separate on MD32; r2's usual flat memory view needs
appropriate maps for your firmware before memory emulation is useful.
## Verification
```sh
make test
```
This runs native decoder/parser tests, exhaustive halfword/truncation checks, actual r2
plugin/analysis/ESIL tests, and checks all **1,128 functions / 100,275 instruction
lengths** against the supplied uSIP inventory. The original private `dsp.bin` and
`DbgInfo` files are not required.
Optional independent vendor comparison (see [research notes](doc/research.md)
for downloads and checksums):
```sh
python3 -m venv /tmp/md32-verify
/tmp/md32-verify/bin/pip install unicorn
/tmp/md32-verify/bin/python test/test_oracle.py /tmp/md32-objdump
```
The test emulates the vendor's original x86-64 disassembly function using
Unicorn and compares decoded text, operands, validity, and lengths. Use
`--quick` for a smaller run or `--firmware firmware.be.bin` to add a firmware
image. The checked run included **141,924 cases with zero mismatches**, including
all halfword patterns, randomized operands for every vendor instruction form,
packed pairs, and the MT8173 VPU program image.
See [doc/research.md](doc/research.md) for provenance, the binutils findings,
MD380 distinction, and remaining research questions.
## Firmware exploration and strict ESIL execution
The newer `third_party/v2/r2pkg` bundle adds BRP and a CSV inventory covering
1,301 functions. `tools/analyze_firmware.py` reports the startup probes,
purpose/structure, points of interest, missing encodings, string scan, mapping
conflicts and plugin fixes, and writes the complete listings plus the
three-image loader script into a local `usipdata/` directory. Those artifacts
are derived from the third-party firmware, so the directory is gitignored and
not shipped.
```sh
make test
python3 tools/analyze_firmware.py
r2 -a md32 -c '. usipdata/load-usip.r2' malloc://1
tools/md32-run --cpu usip \
--image third_party/v2/r2pkg/bin/usip_sec0.bin@0x400e0000 --vector
```
The runner uses separate code/data maps. Add `--data FILE@BASE` for known data,
`--ram BASE:SIZE` for uninitialized RAM, `--zero BASE:SIZE` for deliberately
zeroed RAM, and `--reg NAME=VALUE` for known register state. It reports JSON and
stops on unresolved semantics, unknown memory/register state or unmodeled MMIO.
A reported stop is an analysis result, not a successful firmware boot. The
supplied uSIP reset/startup probes currently complete zero instructions.
+1407
View File
File diff suppressed because it is too large. Load diff
+779
View File
@@ -0,0 +1,779 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - pancake */
#include <r_asm.h>
typedef struct {
const char *name;
int nargs;
const char *text;
} Md32PseudoRule;
static const Md32PseudoRule rules[] = {
{ "mv", 2, "$1 = $2" },
{ "mv", 3, "a0l = $2; a0h = $3" },
{ "sethi", 2, "$1 = ($2 << 16)" },
{ "add", 2, "$1 += $2" },
{ "add", 3, "$1 = ($2 + $3)" },
{ "add", 4, "$1 = ($2 + $3 + $4)" },
{ "addsp", 2, "$1 += $2" },
{ "sub", 2, "$1 -= $2" },
{ "sub", 3, "$1 = ($2 - $3)" },
{ "and", 2, "$1 &= $2" },
{ "and", 3, "$1 = ($2 & $3)" },
{ "or", 2, "$1 |= $2" },
{ "or", 3, "$1 = ($2 | $3)" },
{ "xor", 2, "$1 ^= $2" },
{ "xor", 3, "$1 = ($2 ^ $3)" },
{ "mul", 3, "$1 = ($2 * $3)" },
{ "sll", 2, "$1 <<= $2" },
{ "sll", 3, "$1 = ($2 << $3)" },
{ "srl", 3, "$1 = ((uint32_t)$2 >> $3)" },
{ "sra", 2, "$1 = ((int32_t)$1 >> $2)" },
{ "sra", 3, "$1 = ((int32_t)$2 >> $3)" },
{ "clz", 2, "$1 = clz32($2)" },
{ "clb", 2, "$1 = clz32((int32_t)$2 < 0 ? ~$2 : $2)" },
{ "min", 2, "$1 = min_s32($1, $2)" },
{ "min", 3, "$1 = min_s32($2, $3)" },
{ "max", 2, "$1 = max_s32($1, $2)" },
{ "max", 3, "$1 = max_s32($2, $3)" },
{ "cinv", 3, "$1 = ((int32_t)$2 < 0 ? -$3 : $3)" },
{ "bclr", 2, "$1 &= ~(1U << $2)" },
{ "bclr", 3, "$1 = ($2 & ~(1U << $3))" },
{ "bset", 2, "$1 |= (1U << $2)" },
{ "bset", 3, "$1 = ($2 | (1U << $3))" },
{ "btgl", 2, "$1 ^= (1U << $2)" },
{ "btgl", 3, "$1 = ($2 ^ (1U << $3))" },
{ "beq", 3, "if ($1 == $2) goto $3" },
{ "bne", 3, "if ($1 != $2) goto $3" },
{ "ble", 3, "if ((int32_t)$1 <= (int32_t)$2) goto $3" },
{ "blt", 3, "if ((int32_t)$1 < (int32_t)$2) goto $3" },
{ "bleu", 3, "if ((uint32_t)$1 <= (uint32_t)$2) goto $3" },
{ "bltu", 3, "if ((uint32_t)$1 < (uint32_t)$2) goto $3" },
{ "j", 1, "goto $1" },
{ "jal", 1, "$1 ()" },
{ "call", 1, "$1 ()" },
{ "swi", 1, "software_interrupt($1)" },
{ "brk", 0, "breakpoint()" },
{ "halt", 0, "halt()" },
{ "nop", 0, "nop" },
};
static bool scq16_cpu(RAsmPluginSession *aps) {
const char *cpu = aps && aps->rasm && aps->rasm->config? aps->rasm->config->cpu: NULL;
return cpu && !strcmp (cpu, "scq16");
}
static bool usip_cpu(RAsmPluginSession *aps) {
const char *cpu = aps && aps->rasm && aps->rasm->config? aps->rasm->config->cpu: NULL;
return cpu && !strcmp (cpu, "usip");
}
static char *memory_pseudo(const char *name, char **args, int nargs) {
if (nargs != 2) {
return NULL;
}
const bool load = name[0] == 'l';
int bits = 0;
bool sign = false;
if (!strcmp (name, "lb") || !strcmp (name, "lbu") || !strcmp (name, "sb")) {
bits = 8;
sign = !strcmp (name, "lb");
} else if (!strcmp (name, "lh") || !strcmp (name, "lhu") || !strcmp (name, "sh")) {
bits = 16;
sign = !strcmp (name, "lh");
} else if (!strcmp (name, "lw") || !strcmp (name, "sw")) {
bits = 32;
} else {
return NULL;
}
char *mem = strdup (args[1]);
if (!mem) {
return NULL;
}
char *open = strchr (mem, '(');
char *close = strchr (mem, ')');
if (!open || !close || close < open || close[1] || strchr (open + 1, '(')) {
free (mem);
return NULL;
}
*open++ = 0;
*close = 0;
char *delta = mem;
char *base = open;
r_str_trim (delta);
r_str_trim (base);
char *update = strstr (base, "+=");
if (update) {
if (*delta) {
free (mem);
return NULL;
}
*update = 0;
delta = update + 2;
r_str_trim (delta);
r_str_trim (base);
}
// A load into its own updating base has unresolved hardware write ordering.
if (!*base || (update && (!*delta || (load && !strcmp (args[0], base))))) {
free (mem);
return NULL;
}
RStrBuf *sb = r_strbuf_new ("");
if (load) {
r_strbuf_appendf (sb, "%s = %s", args[0], sign? (bits == 8? "sext8(": "sext16("): "");
}
r_strbuf_appendf (sb, "dmem%d[%s", bits, base);
if (!update && *delta) {
r_strbuf_appendf (sb, " %c %s", *delta == '-'? '-': '+', *delta == '-'? delta + 1: delta);
}
r_strbuf_append (sb, "]");
if (load) {
if (sign) {
r_strbuf_append (sb, ")");
}
} else {
r_strbuf_appendf (sb, " = %s", args[0]);
}
if (update) {
r_strbuf_appendf (sb, "; %s += %s", base, delta);
}
free (mem);
return r_strbuf_drain (sb);
}
/* uSIP mnemonics: immediates carry a '#', memory operands are [base+off], [base]+, [sp+off], [#addr] */
static const Md32PseudoRule usip_rules[] = {
{ "movi", 2, "$1 = $2" },
{ "mov", 2, "$1 = $2" },
{ "mov32", 2, "$1 = $2" },
{ "lea", 2, "$1 = &$2" },
{ "add", 2, "$1 += $2" },
{ "add", 3, "$1 = ($2 + $3)" },
{ "addi", 3, "$1 = ($2 + $3)" },
{ "inc", 2, "$1 = ($2 + 1)" },
{ "dec", 2, "$1 = ($2 - 1)" },
{ "sub", 2, "$1 -= $2" },
{ "sub", 3, "$1 = ($2 - $3)" },
{ "rsub", 3, "$1 = ($3 - $2)" },
{ "neg", 2, "$1 = -$2" },
{ "and", 3, "$1 = ($2 & $3)" },
{ "or", 3, "$1 = ($2 | $3)" },
{ "xor", 2, "$1 ^= $2" },
{ "xor", 3, "$1 = ($2 ^ $3)" },
{ "lsl", 2, "$1 <<= $2" },
{ "lsl", 3, "$1 = ($2 << $3)" },
{ "lsr", 3, "$1 = ((uint32_t)$2 >> $3)" },
{ "asr", 2, "$1 = ((int32_t)$1 >> $2)" },
{ "asr", 3, "$1 = ((int32_t)$2 >> $3)" },
{ "seq", 3, "$1 = ($2 == $3)" },
{ "sle", 3, "$1 = ((int32_t)$2 <= (int32_t)$3)" },
{ "slt", 3, "$1 = ((int32_t)$2 < (int32_t)$3)" },
{ "sltu", 3, "$1 = ((uint32_t)$2 < (uint32_t)$3)" },
{ "sgt", 3, "$1 = ((int32_t)$2 > (int32_t)$3)" },
{ "cmov", 3, "$1 = ($2 != 0) ? $3 : $1" },
{ "clz", 2, "$1 = clz32($2)" },
{ "min", 3, "$1 = min_s32($2, $3)" },
{ "max", 3, "$1 = max_s32($2, $3)" },
{ "beq", 3, "if ($1 == $2) goto $3" },
{ "bne", 3, "if ($1 != $2) goto $3" },
{ "blt", 3, "if ((int32_t)$1 < (int32_t)$2) goto $3" },
{ "ble", 3, "if ((int32_t)$1 <= (int32_t)$2) goto $3" },
{ "bgt", 3, "if ((int32_t)$1 > (int32_t)$2) goto $3" },
{ "bge", 3, "if ((int32_t)$1 >= (int32_t)$2) goto $3" },
{ "bltu", 3, "if ((uint32_t)$1 < (uint32_t)$2) goto $3" },
{ "bleu", 3, "if ((uint32_t)$1 <= (uint32_t)$2) goto $3" },
{ "bgtu", 3, "if ((uint32_t)$1 > (uint32_t)$2) goto $3" },
{ "bgeu", 3, "if ((uint32_t)$1 >= (uint32_t)$2) goto $3" },
{ "bequ", 3, "if ((uint32_t)$1 == (uint32_t)$2) goto $3" },
{ "bneu", 3, "if ((uint32_t)$1 != (uint32_t)$2) goto $3" },
{ "beqz", 2, "if ($1 == 0) goto $2" },
{ "bnez", 2, "if ($1 != 0) goto $2" },
{ "bz", 2, "if ($1 == 0) goto $2" },
{ "bnz", 2, "if ($1 != 0) goto $2" },
{ "b", 1, "goto $1" },
{ "call", 1, "$1 ()" },
{ "ret", 0, "return" },
{ "loop", 2, "repeat $1 times until $2" },
{ "loop", 3, "repeat $1 times until $2 ($3)" },
{ "loop#", 2, "repeat $1 times until $2" },
{ "nop", 0, "nop" },
{ "nop32", 0, "nop" },
{ "sys", 1, "system($1)" },
{ "save", 1, "push_callee_saved($1)" },
{ "restore", 1, "pop_callee_saved($1)" },
{ "jr", 1, "goto $1" },
{ "ior", 2, "$1 = io[$2]" },
{ "iow", 2, "io[$2] = $1" },
{ "mfcsr", 2, "$1 = csr[$2]" },
{ "mtcsr", 2, "csr[$1] = $2" },
{ "ldslot", 2, "$1 = slot[$2]" },
{ "stslot", 2, "slot[$2] = $1" },
{ "sne", 3, "$1 = ($2 != $3)" },
{ "scmp", 3, "$1 = cmp($2, $3)" },
{ "movhi", 2, "$1 = $2" },
{ "mfacc", 2, "$1 = $2" },
{ "saturate", 2, "$1 = saturate($2)" },
{ "bset", 3, "$1 = ($2 | (1U << $3))" },
{ "bclr", 3, "$1 = ($2 & ~(1U << $3))" },
{ "binv", 3, "$1 = ($2 ^ (1U << $3))" },
{ "bext", 3, "$1 = (($2 >> $3) & 1)" },
{ "osl", 3, "$1 = (($2 << $3) | $1)" },
{ "extu", 4, "$1 = (($2 >> $4) & ((1 << $3) - 1))" },
{ "fxtu", 4, "$1 = (($2 >> $4) & ((1 << $3) - 1))" },
{ "fxts", 4, "$1 = sext($3)(($2 >> $4) & ((1 << $3) - 1))" },
{ "fup", 4, "$1 = (($1 & ~(((1 << $3) - 1) << $4)) | (($2 & ((1 << $3) - 1)) << $4))" },
{ "mtacc", 2, "$1 = $2" },
{ "addi", 2, "$1 += $2" },
{ "sext16", 2, "$1 = sext16($2)" },
{ "zext16", 2, "$1 = ($2 & 0xffff)" },
{ "asr16", 3, "$1 = sext16($2 << $3) >> $3" },
{ "cop", 1, "coprocessor($1)" },
{ "rti", 0, "return_from_interrupt" },
{ "ieon", 0, "interrupts_on()" },
{ "ieoff", 0, "interrupts_off()" },
{ "idle", 0, "idle()" },
{ "isync", 0, "isync()" },
{ "lpexit", 0, "loop_exit()" },
{ "trap", 0, "trap()" },
};
/* ld.w r1, [r2+0x4] -> r1 = dmem32[r2 + 0x4]; st.b r1, [r2]+ -> dmem8[r2] = r1; r2 += 1; st.abs r0, [#0xe20014] -> dmem32[0xe20014] = r0;
* st.w [sp], #0x35 -> dmem32[sp] = 0x35 (the stores of an immediate: the memory operand comes first) */
static char *usip_memory_pseudo(const char *name, char **args, int nargs) {
const bool load = !strncmp (name, "ld.", 3);
if (nargs != 2 || (!load && strncmp (name, "st.", 3))) {
return NULL;
}
const char w = name[3];
const int bits = w == 'w'? 32: w == 'h'? 16: w == 'b'? 8: w == 'd'? 64: w == 'a'? 32: 0; /* .abs: a word */
if (!bits) {
return NULL;
}
const bool sign = load && !name[4] && (w == 'h' || w == 'b'); /* ld.h / ld.b: signed; ld.w, ld.d, ld.hu, ld.bu, ld.h?...: left as is */
if (!load && args[0][0] == '[' && args[1][0] != '[') {
/* store of an immediate: [rB] / [sp], #imm */
const char *close = strchr (args[0], ']');
if (!close || close[1]) {
return NULL;
}
char *inner = r_str_ndup (args[0] + 1, close - args[0] - 1);
char *res = r_str_newf ("dmem%d[%s] = %s", bits, inner, args[1]);
free (inner);
return res;
}
const char *mem = strchr (args[1], '[');
const char *close = mem? strchr (mem, ']'): NULL;
if (!mem || !close || mem != args[1]) {
return NULL;
}
char *inner = r_str_ndup (mem + 1, close - mem - 1);
const bool post = close[1] == '+';
RStrBuf *sb = r_strbuf_new ("");
if (*inner == '#') {
memmove (inner, inner + 1, strlen (inner));
}
char *plus = strchr (inner, '+');
char *minus = strchr (inner, '-');
if (plus) {
*plus = 0;
r_strbuf_setf (sb, "%s + %s", inner, plus + 1);
} else if (minus) {
*minus = 0;
r_strbuf_setf (sb, "%s - %s", inner, minus + 1);
} else {
r_strbuf_set (sb, inner);
}
char *addr = r_strbuf_drain (sb);
sb = r_strbuf_new ("");
if (load) {
r_strbuf_appendf (sb, "%s = %sdmem%d[%s]%s", args[0], sign? (bits == 8? "sext8(": "sext16("): "", bits, addr, sign? ")": "");
} else {
r_strbuf_appendf (sb, "dmem%d[%s] = %s", bits, addr, args[0]);
}
if (post) {
r_strbuf_appendf (sb, "; %s += %d", inner, bits / 8);
}
free (addr);
free (inner);
return r_strbuf_drain (sb);
}
static char *usip_pseudo(const char *name, char **args, int nargs) {
int i;
for (i = 0; i < nargs; i++) {
if (args[i][0] == '#') {
args[i]++;
/* the uncertainty marker of a count (loop#? #256?, last=...) */
size_t len = strlen (args[i]);
if (len > 1 && args[i][len - 1] == '?') {
args[i][len - 1] = 0;
}
}
}
char *result = usip_memory_pseudo (name, args, nargs);
if (result) {
return result;
}
/* strip the variant suffix and the uncertainty marker: beq.d -> beq, bz.s -> bz, ret.d -> ret, add.2 -> add,
* bne.l -> bne, nop.ctl -> nop, lea? -> lea, call? -> call, ble.l?? -> ble, fup? -> fup, movi? -> movi */
char base[32];
r_str_ncpy (base, name, sizeof (base));
char *dot = strchr (base, '.');
if (dot) {
*dot = 0;
}
size_t blen = strlen (base);
while (blen > 1 && base[blen - 1] == '?') {
base[--blen] = 0;
}
if (nargs == 1 && !strcmp (base, "addsp")) {
/* addsp #-0x14 -> sp -= 0x14 */
return args[0][0] == '-'? r_str_newf ("sp -= %s", args[0] + 1): r_str_newf ("sp += %s", args[0]);
}
if (nargs == 2 && (!strcmp (base, "ior") || !strcmp (base, "iow")) && args[1][0] == '[') {
/* ior rB, [rA]: rA holds the port number */
char *close = strchr (++args[1], ']');
if (close) {
*close = 0;
}
}
size_t k;
for (k = 0; k < R_ARRAY_SIZE (usip_rules); k++) {
if (usip_rules[k].nargs == nargs && !strcmp (usip_rules[k].name, base)) {
const char *text = usip_rules[k].text;
RStrBuf *sb = r_strbuf_new ("");
for (; *text; text++) {
if (*text == '$' && text[1] >= '1' && text[1] <= '4') {
r_strbuf_append (sb, args[*++text - '1']);
} else {
r_strbuf_append_n (sb, text, 1);
}
}
return r_strbuf_drain (sb);
}
}
return NULL;
}
/* SCQ16 pseudo-syntax: immediates carry a '#', memory operands are off(base), [base] += inc, [base] -= dec, off(sp);
* a packet `{ A ‖ B }` is split by parse() into its two units. Mnemonic suffixes .d1/.d2/.d3 (delay slots) and the provisional
* marker '?' are stripped before the lookup. */
static const Md32PseudoRule scq16_rules[] = {
{ "mv", 2, "$1 = $2" },
{ "sethi", 2, "$1 = $2" },
{ "add", 2, "$1 += $2" },
{ "add", 3, "$1 = ($2 + $3)" },
{ "addsp", 2, "$1 += $2" },
{ "sub", 2, "$1 -= $2" },
{ "sub", 3, "$1 = ($2 - $3)" },
{ "and", 2, "$1 &= $2" },
{ "and", 3, "$1 = ($2 & $3)" },
{ "or", 2, "$1 |= $2" },
{ "or", 3, "$1 = ($2 | $3)" },
{ "xor", 3, "$1 = ($2 ^ $3)" },
{ "mul", 2, "$1 *= $2" },
{ "mul", 3, "$1 = ($2 * $3)" },
{ "div", 3, "a0l = ($2 / $3); a0h = ($2 % $3)" },
{ "sll", 3, "$1 = ($2 << $3)" },
{ "srl", 3, "$1 = ((uint32_t)$2 >> $3)" },
{ "sra", 3, "$1 = ((int32_t)$2 >> $3)" },
{ "clz", 2, "$1 = clz32($2)" },
{ "ctz", 2, "$1 = ctz32($2)" },
{ "sqr", 2, "$1 = ($2 * $2)" },
{ "op41.fld", 4, "$1 = (($2 << $4) | ($3 & ((1U << $4) - 1)))" },
{ "clb", 2, "$1 = clz32((int32_t)$2 < 0 ? ~$2 : $2)" },
{ "min", 3, "$1 = min_s32($2, $3)" },
{ "max", 2, "$1 = max_s32($1, $2)" },
{ "max", 3, "$1 = max_s32($2, $3)" },
{ "cinv", 3, "$1 = ((int32_t)$2 < 0 ? -$3 : $3)" },
{ "bclr", 3, "$1 = ($2 & ~(1U << $3))" },
{ "bset", 2, "$1 |= (1U << $2)" },
{ "bset", 3, "$1 = ($2 | (1U << $3))" },
{ "btgl", 3, "$1 = ($2 ^ (1U << $3))" },
{ "btst", 3, "$1 = (($2 >> $3) & 1)" },
{ "xtd", 3, "$1 = sext$3($2)" },
{ "fxtu", 4, "$1 = (($2 >> $4) & ((1U << $3) - 1))" },
{ "fxts", 4, "$1 = sext_bits(($2 >> $4), $3)" },
{ "fup", 4, "$1 = insert_bits($1, $2, $3, $4)" },
{ "cmov.lt", 4, "if ((int32_t)$2 < (int32_t)$3) $1 = $4" },
{ "cmov.ltu", 4, "if ((uint32_t)$2 < (uint32_t)$3) $1 = $4" },
{ "cmov.eq", 4, "if ($2 == $3) $1 = $4" },
{ "cmov.ne", 4, "if ($2 != $3) $1 = $4" },
{ "cmov.ge", 4, "if ((int32_t)$2 >= (int32_t)$3) $1 = $4" },
{ "cmov.leu", 4, "if ((uint32_t)$2 <= (uint32_t)$3) $1 = $4" },
{ "cmov.le", 4, "if ((int32_t)$2 <= (int32_t)$3) $1 = $4" },
{ "beq", 3, "if ($1 == $2) goto $3" },
{ "bne", 3, "if ($1 != $2) goto $3" },
{ "ble", 3, "if ((int32_t)$1 <= (int32_t)$2) goto $3" },
{ "blt", 3, "if ((int32_t)$1 < (int32_t)$2) goto $3" },
{ "bleu", 3, "if ((uint32_t)$1 <= (uint32_t)$2) goto $3" },
{ "bltu", 3, "if ((uint32_t)$1 < (uint32_t)$2) goto $3" },
{ "beqz", 3, "if ($1 == 0) goto $3" },
{ "beqz8", 3, "if ($1 == 0) goto $3" },
{ "bnez", 3, "if ($1 != 0) goto $3" },
{ "bnez8", 3, "if ($1 != 0) goto $3" },
{ "j", 1, "goto $1" },
{ "j8", 1, "goto $1" },
{ "j16", 1, "goto $1" },
{ "jr", 1, "goto $1" },
{ "jal", 1, "$1 ()" },
{ "jal11", 1, "$1 ()" },
{ "jalr", 1, "$1 ()" },
{ "ior", 2, "$1 = io32[$2]" },
{ "iow", 2, "io32[$2] = $1" },
{ "vget", 2, "$1 = $2" },
{ "vbcast", 2, "$1 = vbcast($2)" },
{ "vbcast.i", 2, "$1 = vbcast($2)" },
{ "vset.a", 2, "$1 = $2" },
{ "vmem.set", 2, "$1 = $2" },
{ "vins", 2, "$1 = $2" },
{ "vrd", 2, "$1 = $2" },
{ "vwr", 2, "$1 = $2" },
{ "udf.rd", 2, "$1 = $2" },
{ "udf.wr", 2, "$1 = $2" },
{ "sys", 1, "syscall($1)" },
{ "sys41", 1, "sys41($1)" },
{ "vwait", 0, "vwait()" },
{ "halt", 0, "halt()" },
{ "ieon", 0, "interrupts_on()" },
{ "ieoff", 0, "interrupts_off()" },
{ "loopend", 0, "loopend()" },
{ "nop", 0, "nop" },
};
/* lw r1, 0x4(r2) / lw.sp r1, -0x8(sp) / sw.lr lr, -0x4(sp) / lw.post r1, [r2] += 4 / sw.post r1, [r3] -= 0x10 / lbu r3, [r2] */
static char *scq16_memory_pseudo(const char *name, char **args, int nargs) {
if (nargs != 2) {
return NULL;
}
int bits = 0;
bool sign = false;
char base[16];
r_str_ncpy (base, name, sizeof (base));
char *dot = strchr (base, '.');
if (dot) {
*dot = 0;
}
const bool load = base[0] == 'l';
if (!strcmp (base, "lb") || !strcmp (base, "lbu") || !strcmp (base, "sb")) {
bits = 8;
sign = !strcmp (base, "lb");
} else if (!strcmp (base, "lh") || !strcmp (base, "lhu") || !strcmp (base, "sh")) {
bits = 16;
sign = !strcmp (base, "lh");
} else if (!strcmp (base, "lw") || !strcmp (base, "sw")) {
bits = 32;
} else {
return NULL;
}
char *mem = strdup (args[1]);
if (!mem) {
return NULL;
}
char *addr = NULL, *post = NULL;
char postop = 0;
if (mem[0] == '[') {
/* [base] += inc | [base] -= dec | [base] */
char *close = strchr (mem, ']');
if (!close) {
free (mem);
return NULL;
}
*close = 0;
addr = mem + 1;
char *p = close + 1;
r_str_trim (p);
if (!strncmp (p, "+=", 2) || !strncmp (p, "-=", 2)) {
postop = p[0];
post = p + 2;
r_str_trim (post);
} else if (*p) {
free (mem);
return NULL;
}
} else {
/* off(base) */
char *open = strchr (mem, '(');
char *close = strchr (mem, ')');
if (!open || !close || close < open || close[1]) {
free (mem);
return NULL;
}
*open++ = 0;
*close = 0;
r_str_trim (mem);
r_str_trim (open);
RStrBuf *ab = r_strbuf_new (open);
if (*mem && strcmp (mem, "0x0") && strcmp (mem, "0")) {
r_strbuf_appendf (ab, " %c %s", mem[0] == '-'? '-': '+', mem[0] == '-'? mem + 1: mem);
}
char *a = r_strbuf_drain (ab);
free (mem);
mem = a;
addr = mem;
}
RStrBuf *sb = r_strbuf_new ("");
if (load) {
r_strbuf_appendf (sb, "%s = %sdmem%d[%s]%s", args[0], sign? (bits == 8? "sext8(": "sext16("): "", bits, addr, sign? ")": "");
} else {
r_strbuf_appendf (sb, "dmem%d[%s] = %s", bits, addr, args[0]);
}
if (post) {
r_strbuf_appendf (sb, "; %s %c= %s", addr, postop, post);
}
free (mem);
return r_strbuf_drain (sb);
}
static char *scq16_pseudo(const char *name, char **args, int nargs) {
int i;
for (i = 0; i < nargs; i++) {
if (args[i][0] == '#') {
args[i]++;
}
}
char base[32];
r_str_ncpy (base, name, sizeof (base));
size_t blen = strlen (base);
while (blen > 1 && base[blen - 1] == '?') {
base[--blen] = 0;
}
/* .d1 / .d2 / .d3 delay-slot suffixes */
char *dot = strrchr (base, '.');
if (dot && dot[1] == 'd' && dot[2] >= '1' && dot[2] <= '3' && !dot[3]) {
*dot = 0;
}
if (!strcmp (base, "ret") || !strcmp (base, "rti")) {
return strdup (!strcmp (base, "ret")? "return": "return_from_interrupt");
}
if (!strcmp (base, "do") && nargs == 2) {
/* do r3, end=0x... (F=n) -> loop r3 times until 0x... */
char *fin = strstr (args[1], "end=");
char *end = fin? strchr (fin, ' '): NULL;
if (end) {
*end = 0;
}
return r_str_newf ("loop %s times until %s", args[0], fin? fin + 4: args[1]);
}
if (!strcmp (base, "add.sp") && nargs == 3) {
return args[2][0] == '-'? r_str_newf ("%s = (sp - %s)", args[0], args[2] + 1): r_str_newf ("%s = (sp + %s)", args[0], args[2]);
}
if (!strcmp (base, "sw.isr") && nargs == 2) {
return r_str_newf ("dmem32[sp] = %s; sp += %s", args[0], args[1] + 5);
}
char *result = scq16_memory_pseudo (base, args, nargs);
if (result) {
return result;
}
if (nargs == 2 && !strcmp (base, "addsp")) {
return args[1][0] == '-'? r_str_newf ("sp -= %s", args[1] + 1): r_str_newf ("sp += %s", args[1]);
}
if (nargs == 2 && (!strcmp (base, "ior") || !strcmp (base, "iow"))) {
/* ior rd, off(rb): port index rb + off */
char *open = strchr (args[1], '(');
char *close = strchr (args[1], ')');
if (open && close && close > open) {
*open = 0;
*close = 0;
char *port = (!strcmp (args[1], "0") || !strcmp (args[1], "0x0"))? strdup (open + 1): r_str_newf ("%s + %s", open + 1, args[1]);
char *r = r_str_newf (!strcmp (base, "ior")? "%s = io32[%s]": "io32[%s] = %s", !strcmp (base, "ior")? args[0]: port, !strcmp (base, "ior")? port: args[0]);
free (port);
return r;
}
return NULL;
}
size_t k;
for (k = 0; k < R_ARRAY_SIZE (scq16_rules); k++) {
if (scq16_rules[k].nargs == nargs && !strcmp (scq16_rules[k].name, base)) {
const char *text = scq16_rules[k].text;
RStrBuf *sb = r_strbuf_new ("");
for (; *text; text++) {
if (*text == '$' && text[1] >= '1' && text[1] <= '4') {
r_strbuf_append (sb, args[*++text - '1']);
} else {
r_strbuf_append_n (sb, text, 1);
}
}
return r_strbuf_drain (sb);
}
}
return NULL;
}
static char *parse_single(RAsmPluginSession *aps, const char *data) {
char *copy = r_str_trim_dup (data);
if (!copy) {
return NULL;
}
char *name = copy;
char *p = name;
while (*p && !IS_WHITESPACE (*p)) {
p++;
}
if (*p) {
*p++ = 0;
}
char *args[4];
int nargs = 0;
int depth = 0;
char *start = p;
for (;; p++) {
if (*p == '(') {
depth++;
} else if (*p == ')' && --depth < 0) {
goto fallback;
}
if (!*p || (*p == ',' && !depth)) {
const bool end = !*p;
*p = 0;
char *arg = start;
r_str_trim (arg);
if (*arg) {
if (nargs == R_ARRAY_SIZE (args)) {
goto fallback;
}
args[nargs++] = arg;
} else if (!end || nargs) {
goto fallback;
}
if (end) {
break;
}
start = p + 1;
}
}
if (depth) {
goto fallback;
}
if (scq16_cpu (aps)) {
if (name[0] == '.' || !strncmp (name, "v.", 2) || !strcmp (name, "vbundle")) {
goto fallback;
}
char *result = scq16_pseudo (name, args, nargs);
if (result) {
free (copy);
return result;
}
goto fallback;
}
if (usip_cpu (aps)) {
if (name[0] == '.') {
goto fallback;
}
char *result = usip_pseudo (name, args, nargs);
if (result) {
free (copy);
return result;
}
goto fallback;
}
// Keep delay and DSP suffixes intact. Only remove the scalar width suffix.
const size_t len = strlen (name);
if (len > 2 && !strcmp (name + len - 2, "16")) {
name[len - 2] = 0;
} else if (!strcmp (name, "mv16.l")) {
strcpy (name, "mv");
} else if (!strcmp (name, "nop16.l") || !strcmp (name, "nop16.r")) {
strcpy (name, "nop");
}
if (!strcmp (name, "mv") && nargs == 3 && strcmp (args[0], "a0")) {
goto fallback;
}
char *result = memory_pseudo (name, args, nargs);
if (result) {
free (copy);
return result;
}
const char *text = NULL;
if (nargs == 1 && !strcmp (name, "j") && !strcmp (args[0], "r15")) {
text = "return";
} else if (!nargs && !strcmp (name, "ret")) {
text = "return";
} else {
size_t i;
for (i = 0; i < R_ARRAY_SIZE (rules); i++) {
if (rules[i].nargs == nargs && !strcmp (rules[i].name, name)) {
text = rules[i].text;
break;
}
}
}
if (text) {
RStrBuf *sb = r_strbuf_new ("");
for (; *text; text++) {
if (*text == '$' && text[1] >= '1' && text[1] <= '4') {
r_strbuf_append (sb, args[*++text - '1']);
} else {
r_strbuf_append_n (sb, text, 1);
}
}
free (copy);
return r_strbuf_drain (sb);
}
fallback:
free (copy);
return strdup (data);
}
static char *parse(RAsmPluginSession *aps, const char *data) {
R_RETURN_VAL_IF_FAIL (data, NULL);
if (scq16_cpu (aps)) {
/* SCQ16 packet `{ A ‖ B }` -> parallel { a; b; }; the ESIL-style separator is U+2016 (e2 80 96) */
const char *sep = strstr (data, " \xe2\x80\x96 ");
if (r_str_startswith (data, "{ ") && sep) {
const char *end = strrchr (data, '}');
if (!end || end < sep) {
return strdup (data);
}
char *left = r_str_ndup (data + 2, sep - data - 2);
char *right = r_str_ndup (sep + 5, end - sep - 5);
r_str_trim (left);
r_str_trim (right);
char *a = parse_single (aps, left);
char *b = parse_single (aps, right);
char *result = a && b? r_str_newf ("parallel { %s; %s; }", a, b): NULL;
free (left);
free (right);
free (a);
free (b);
return result? result: strdup (data);
}
return parse_single (aps, data);
}
const char *pair = strstr (data, " | ");
if (!pair) {
return parse_single (aps, data);
}
if (strstr (pair + 3, " | ")) {
return strdup (data);
}
char *left = r_str_ndup (data, pair - data);
if (!left) {
return NULL;
}
char *a = parse_single (aps, left);
char *b = parse_single (aps, pair + 3);
char *result = a && b? r_str_newf ("parallel { %s; %s; }", a, b): NULL;
free (left);
free (a);
free (b);
return result;
}
RAsmPlugin r_asm_plugin_md32 = {
.meta = {
.name = "md32",
.desc = "MediaTek MD32 / uSIP pseudo syntax",
.author = "pancake",
.license = "LGPL-3.0-only",
},
.parse = parse,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_md32,
.version = R2_VERSION
};
#endif
+158
View File
@@ -0,0 +1,158 @@
# MD32 research and verification
Research date: 2026-10-29. The primary target is the MediaTek **MS15E30** MD32
implemented by the available vendor toolchain. The MT6877 uSIP samples are
handled as an explicitly separate, incompletely understood encoding.
## Sources and toolchain
- [cyrozap/ghidra-md32](https://github.com/cyrozap/ghidra-md32): public SLEIGH
instruction and register descriptions, Apache-2.0. The provided copy under
`third_party/ghidra-md32` was used to understand semantics and cross-check the
decoder. It describes its implementation as incomplete and calls out packed
instructions and variable-length delay slots as unresolved Ghidra issues.
- [cyrozap/mediatek-lte-baseband-re, MD32 tools](https://github.com/cyrozap/mediatek-lte-baseband-re/tree/master/DSP/MD32):
byte-order notes, ELF machine 0x2454, and a Python wrapper around the vendor
disassembler. Its binutils README identifies the Orange Pi Android 8.1 BSP
as a public distribution of the MD32 toolchain.
- [Orange Pi MD32 toolchain mirror](https://github.com/Iscle/OrangePi_4G-IOT_Android_8.1_BSP/tree/master/ccu_tool/md32ccu):
vendor compiler, assembler, linker, objcopy, objdump, libraries, and headers.
This avoids downloading the original multipart 22 GB Android archive.
- [Ghidra delay-slot issue #3942](https://github.com/NationalSecurityAgency/ghidra/issues/3942):
the MD32 processor-module author explains that the specification was recovered
by probing a closed-source disassembler and testing instructions on hardware;
no public ISA manual was available to them.
- `third_party/r2pkg`: the supplied MT6877 uSIP binaries, function inventory,
source-line examples, and reverse-engineering notes. The scripts' private
source blobs are not bundled, so our verification uses the two supplied
extracted binaries and the supplied Markdown function inventory directly.
There **is a vendor MD32 binutils port**. I did not locate its published
MD32-specific C source or a newer uSIP ISA manual/toolchain. Upstream hits for
`MD32` in `include/opcode/h8300.h` refer to an H8 operand macro and do not provide
a MediaTek decoder.
Download the reference executable outside the source tree:
```sh
curl -fL \
https://raw.githubusercontent.com/Iscle/OrangePi_4G-IOT_Android_8.1_BSP/master/ccu_tool/md32ccu/ToolChain/install_md32/md32-elf/bin/objdump \
-o /tmp/md32-objdump
```
SHA256: `ad768602af6c62049ae5338ce390bb4799e6f8c4a78488f64d58b14b4dc4ab04`.
It is an unstripped x86-64 Linux ELF executable, 1,095,597 bytes. It is neither
built into nor required by the plugin.
The new decoder is portable C. `tools/extract_opcodes.py` reads the reference
ELF's named `ms15e30_isa` and operand tables to recover encoding facts. It checks
the executable's hash before producing the checked-in `md32_opcodes.h`:
```sh
python3 tools/extract_opcodes.py /tmp/md32-objdump -o /tmp/md32_opcodes.h
cmp md32_opcodes.h /tmp/md32_opcodes.h
```
The tables have 154 32-bit forms and 104 16-bit forms, distributed over four
16-bit execution groups. Masked reserved operand fields are retained, including
reserved control-register numbers. Immediate decoders include split fields,
scaled signed offsets, and compressed register numbers. Packed pairs reconstruct
the first instruction as `(halfword0 << 1) | (halfword1 >> 15)` and the second
as `halfword1 | 0x8000`, with execution groups selected by the shared prefix.
This is more general than assuming that a pair consists of two ordinary
standalone halfwords.
The additional downloaded reference headers are preserved under
`third_party/reference`:
| File | Source within the Orange Pi mirror | SHA256 |
| --- | --- | --- |
| `builtins_md32s15.h` | `ccu_tool/md32ccu/Md32/include/builtins_md32s15.h` | `2f7a767cca8974d5327a2b7a97d97e2edb90d6245016dfc2a9ced46b17d7d490` |
| `vendor-setjmp.h` | `ccu_tool/md32ccu/Md32/nml/s15r30_md32_v3.0/nlib/include/machine/setjmp.h` | `77e854c41fb65393aeedcfc14467e6bb04877975fe2d0aee67a282849f18450e` |
The builtin names confirm the DSP and control-operation names. The setjmp header
also mentions an `SP_NE_R14` build option, so r14-as-SP should be understood as
the ABI represented by the public Ghidra specification, not a proven universal
rule for all MD32 generations. The argument aliases follow that specification
(r3-r10; return values r1-r2).
## Firmware verification
An independent real program image was downloaded from
[linux-firmware's MT8173 firmware directory](https://gitlab.com/kernel-firmware/linux-firmware/-/tree/main/mediatek/mt8173):
```sh
curl -fL \
https://gitlab.com/kernel-firmware/linux-firmware/-/raw/main/mediatek/mt8173/vpu_p.bin \
-o /tmp/md32-vpu_p.bin
python3 tools/swap_words.py /tmp/md32-vpu_p.bin /tmp/md32-vpu_p.be.bin
```
Both files have 131,072 bytes. SHA256:
- Downloaded storage image: `fb226004888a01c04590fe23df6dcc3a1735d7b4cbbb072f4dc3f920f463951f`.
- Canonical instruction stream: `036b5267c19ec4574c05784f9441b85689710a313e490ed2ca3754d59ac11835`.
The oracle executes the original vendor `print_insn_md32` function and operand
helpers in Unicorn, with adapters for reading code and collecting formatted
output. It does not call our C decoder or generate its expectations from the
new opcode table. The vendor's printed displacements are converted to 32-bit
absolute addresses for comparison, and its unsigned spelling of negative
immediates is normalized. Invalid halfwords and partially invalid pairs remain
invalid; the vendor returns a two-byte resynchronization step for them.
The full run compared **141,924 cases** with **zero mismatches**: 65,536 first
halfword patterns, 25,800 randomized encodings covering every table row, 10,000
random packed-pair patterns, and 40,588 firmware decode positions. These are
comparisons against the reference decoder, not proof of hardware semantics.
Padding and non-code bytes in firmware are included in the linear comparison.
Native tests also cover exact short-buffer reads under ASan/UBSan, reserved
encodings, CPU selection, decoded-operation masks, relative-address behavior,
and actual radare2 loading, mixed-width analysis, delay-slot CFG fallthrough,
operand JSON, integer ESIL, signed/unsigned comparisons, signed byte loads, and
indirect calls through the link register.
For the supplied uSIP corpus, all 1,128 function ranges end exactly at a decode
boundary, totaling 100,275 instructions. The supported patterns yield 785
returns, 996 restores, 852 saves and 196 calls; 30,407 halfwords and 67,039 words
remain unresolved. This verifies structure, not the semantics of those unknown
instructions or the generality of the observed call rule.
## MD380 and related naming
The repository's `md380` plugin handles the TYT MD380 radio's firmware container
and selects ARM Thumb (`arch="arm"`, 16 bits). It is unrelated to the MD32 DSP
instruction decoder. The [MD380tools project](https://github.com/travisgoodspeed/md380tools)
likewise targets STM32 radio firmware.
The containing MediaTek DSP reverse-engineering project also describes
Coresonic cores, but its notes explicitly distinguish their 25-bit instruction
streams from the MD32 core's instruction stream. A source path containing
`coresonic` is not enough to identify an instruction encoding.
Newer products also reuse the MD32 name for RISC-V based cores. MediaTek's
[MT8395 datasheet](https://mediatek-marketing.files.svdcdn.com/production/documents/MTK_MT8395_Application-Processor-Datasheet_v1.8.pdf?dm=1724252510),
section 3.5.4, describes MD32-RV as RISC-V with proprietary DSP extensions.
Those are outside the MS15E30 decoder's scope.
## Remaining work
- Recover the newer uSIP ALU/load/store/register and full branch encodings from
a matching compiler, disassembler, ISA manual, or hardware experiments. The
supplied source-line and function-boundary correlations are useful evidence
but do not establish a complete opcode specification.
- Establish delay/loop timing, carry/borrow behavior, DSP rounding/saturation,
postincrement alias behavior, and parallel-slot data dependencies on hardware
before extending ESIL to those operations.
- Model Harvard address-space mapping and hardware-loop back edges explicitly
if full decompilation/emulation is required. The architecture decoder alone
does not invent memory-map or cross-instruction state.
## Firmware follow-up
The initial validation figures above describe the earlier two-image bundle.
The newer three-image analysis, decoder corrections, strict ESIL runner and
current boot limitations are documented in the notes that
`tools/analyze_firmware.py` prints, whose generated inventory covers all 1,301
function records in the v2 bundle.
+163
View File
@@ -0,0 +1,163 @@
# SCQ16 (byte-granular packet encoding with an MD32-like scalar model) — notes for the `scq16` CPU mode
Hand-over notes for the `scq16` cpu of the md32 plugin: the encoding model the decoder implements, the table that drives it, the
analysis and ESIL conventions, and what is not modelled. See README.md "SCQ16 mode" for the user-facing description.
Relation to MD32: the SCQ16 instruction encoding is not an MD32 encoding (neither MS15E30 nor the uSIP VLIW variant): its
length rule, opcode space and vector operations are its own. What it shares with MD32 is the scalar programming model (32
general registers, `sp`/`lr` as control registers, the calling convention, the scalar operation set and therefore the ESIL),
which is why it is served as a cpu mode of this plugin rather than as a separate architecture. The other DSP cores of the
same modem (an unrelated SIMD ISA) are out of scope.
## 1. Encoding model
The SCQ16 is a byte-granular, big-endian instruction stream. The minimum unit is the byte; an INSTRUCTION is one of:
* a **stand-alone unit** of 2, 3, 4, 5 or 6 bytes (the length is decided by the first byte and, for some first bytes, a prefix of
the second: the "length leaves" `scq16_uleaves` of `scq16_opcodes.h`);
* a **packet** `{ A ‖ B }`: a head unit whose first byte has bit 7 set (`80-bf`, or `e0-ef` for a vector head) followed by a second
unit B. The head is decoded as A' = (unit << 1) | bit7(B): every byte shifted left by one with the top bit of the first byte of B
borrowed into the last bit. The length of the head is the length of A' (leaves `scq16_hleaves`). B is decoded with its bit 7
masked (`b0 & 0x7f`; a raw `c0-df` first byte is a 4-byte vector op). The two units execute in parallel: both read the
pre-packet state. Control flow is never a head; a B that is a transfer takes the packet start as the base of its displacement.
A head whose A' would be a control/system form is not a packet but a **masked orphan**: the unit decoded with `b0 & 0x7f` on its
own (`.p` suffix). `f8-ff` first bytes are orphans too;
* a **vector operation** of 4 bytes with first byte `c0-df` (`110 A B F1 F2 F3 op9`: a 3-bit A, a 2-bit B, three 5-bit register
fields and a 9-bit op9 code);
* a **bundle** of 8 bytes with first byte `f0-f7`: `{11 | vec32 | s1 | s2}`, a 32-bit vector operation and two 15-bit scalar slots
decoded as 2-byte units.
Delay slots: `.d1` / `.d2` / `.d3` mnemonic suffixes mean one, two or three INSTRUCTIONS (packets included) executed after the
transfer; `do` (hardware loop setup) also has `.d1`/`.d2` variants, and the loop body starts after the slots.
One length is decided by the target: a first byte `56` with the top two bits of the second byte clear is the 4-byte `j.d1` when
its 22-bit displacement (from the packet start in a B slot) lands on a plausible code address (no wrap below 0, below 4 MB), and
a formless 2-byte unit otherwise (the 2-byte reading has no form; a function table, which the plugin lacks, would decide by membership).
## 2. The table
`scq16_opcodes.h` is generated by `tools/gen_scq16_tables.py` from `isa/scq16_isa.json`, the ISA table (forms, bit
alternatives, operands, text and ESIL templates, the length-rule leaves, the vector classes and the register names; `make
scq16_opcodes.h` regenerates the header, and `tools/gen_scq16_tables.py export` builds the JSON from a Python module that
describes the forms and from the length-rule leaves): one
`Scq16Alt` per bit alternative with `(w & mask) == value` on the
transformed unit (w = big-endian integer of the unit, bit 0 = LSB of the last byte; bit 7 of byte 0 is never in a mask, so the
same entry serves a stand-alone unit, a head A' and a B slot), the operands (`Scq16Operand`: register fields r0-r31 / r0-r15 /
r1-r16 / r1-r8, csr, acc, immediates with up to two pieces, scale and bias, displacements, constants), the text template, the
ESIL template, the delay-slot count and flags (`SCQ16_FL_NAMED`: an established operation name; `SCQ16_FL_PROV`: provisional name
with `?`; `SCQ16_FL_TABLELEN`: a form tied to the length table; `SCQ16_FL_CONTROL`: never a packet head; `SCQ16_FL_HYP`: the ESIL
is a hypothesis; `SCQ16_FL_OPEN`: an OPEN reading, i.e. a family placeholder or a provisional name — the uniform marker of what the
table does not establish; an open alternative without a template carries the ESIL marker `REF,ALT,open`). `isa/scq16_hyp.json` holds
the PLUGIN HYPOTHESES (hypothetical templates for forms the ISA source lists as HYP without a template of its own): the exporter
applies them with `--hyp` and records the file's sha256 in the JSON meta and in the header (`SCQ16_PLUGIN_HYP_SHA256`). The file is
EMPTY (the packing of `op41.fld` moved into the ISA source), so every ESIL the plugin emits comes from
the source; the mechanism stays for future hypotheses. The confidence level of a form (`high` / `medium` / `low` / `format-only`,
reported by `aoj` under `opex.units[].confidence`) is the ISA table's: execution results raised `and rd, rs, #0x1|#0x3|#0xf` (2 bytes),
`add rd, rs, #-1` (2 bytes) and the 4-byte `sb rd, off(rb)` to `high` and lowered the 4-byte `btst rd, rs, #pos` to `medium` on
execution results; no text or encoding changed. Within one length the first matching alternative wins (the table is ordered by specificity). The vector
classes (`scq16_vclass[512]`) and names (`scq16_vname[512]`) are indexed by op9; a name carries its `?` when it is provisional
(`movi?`, `add?`, `addi?`, `adds?`, `difsc?`) and none when it is an established operation (`v.move.mv` = register copy v[F1] <- v[F3],
`v.mem.ld` = vector load into v[F1 >> 2], `v.mem.st` = vector store of v0): the decoder prints `v.<class>.<name>` as the table says.
Families (first byte): `00-0f` 2-byte ALU (`and`/`sll`/`add`/`xtd`/`mv`/`sub`/`sll`/`sra`/`and`/`mul`/`max`/`or`; `00`/`01` with k = 6 is the
provisional `and? #0xf000?`), `10-13` moves and
`add #imm`, `14-15` stack (`addsp`, `lw.lr`, `sb`), `16` `lhu`, `18-1f` VU scalar ports (`vget`, `vbcast`, `vset.a`, `vmem.set`,
`vins?`; `1a` is the provisional `vrd? rd, vb1[x]`, a 32-bit read of the VU scalar bank with `rd = w[3:0] + 1`; `1b`/`1d` stay
open), `20-23` `ret`/`nop`/`jalr`/`sw.lr`/`lw.post` (`21`: `lw.post rd, [rb] += 4` with a 4-bit destination r1-r16)/`lbu`/`lbu.post`
(`22`/`23` = `lbu.post rd, [rb] += simm3`: one signed 3-bit
increment over the two pages, +1..+3 on `22`, -4..-1 on `23`; `22` q3 = +3 keeps its `?`), `24-27` `beqz`/`bnez` (8-bit), `28-2f` `lw.sp`/`sw.sp`,
`30-3b` `lw`/`sw`/`sw.post` with 3-bit base, `3e-3f` `j8`; `40-4f` 4-byte immediates and memory (`mul`/`and`/`or`/`add`/`sethi`,
`41` csr/acc/bit-field/three-register ALU/`div`/`addsp`/`clz`, `42` `cmov.cc`/`sel?`, `43` `ior`/`iow`/`sw.isr`/`sw.csr`/`op43`,
`44-47` loads and stores with a 12-bit offset or a post-increment (`46` q0 is the provisional `ld46?`: a byte load with a
post-increment as its hypothetical ESIL), `48-49` `mv #simm20`, `4a-4b` `add`/`and`/`or`/`mul #imm`,
`4c-4f` `vcfg4c?` (`4c` q0 s0 with `#0xc` on an odd `v >= 17` carries the hypothetical ESIL `p_k <- rs`, k = (v - 17) / 2); the
`41 f3` sub-ops next to `clz` (0x21) are `ctz` (0x22, established: `rd = 31 - clz(rs & -rs)`, which yields -1 for `rs = 0` because
`clz(0) = 32`; whether the hardware returns 32 for `ctz(0)` instead is an OPEN question that only an execution on the core decides;
its own form, not hypothetical) and
the provisional `sqr?` (0x23, `rd = rs * rs` as its hypothetical ESIL; the `op41` family placeholder); `op41.fld rd, rs, rt, #pos` (41 8x)
is a field insertion with three registers whose operation is open: the ISA source carries the hypothetical packing
`rd = (rs << pos) | (rt & ((1 << pos) - 1))` (flagged `SCQ16_FL_HYP`; it was a plugin hypothesis of
`isa/scq16_hyp.json` before the source carried it, and that file is now empty); the `42` page is `cmov.<cc> rd, rs, x, ra` =
`rd = cond(rs, x) ? r[ra] : rd` with `cc` = `eq`/`ne` (immediate `#(x-7)` or a register), `lt`/`ltu`/`ge` (immediate), the provisional
`leu?` (register), `ltu?` (immediate), `lt?`/`le?` (immediate on the left: `#imm <s rs`), and `sel?` for the codes without a name); `50-5d` `bcc` (immediate or two registers) and `do`; `5e` `j`/`jal`/system; `60-6f` 3-byte forms (`add`/`mul
#imm7`, `sub`, `add rs + rt << k`, `sll`/`srl #imm`, the `67` page of two-address ALU (`mv rd, rs` at 67 c8 and 67 cc, `bset`/`btst
rd, rd, #pos` at 67 f4/f8 (`btst` with a 5-bit position 0-31, `bset` with 4 bits 0-15), `and #0x1fff/#0x3fff/#0x7fff/#0x8000` at 67 a0-ab/b0-b3, the provisional masks `and? #0xf000?` at 67 98-9b, `#0xfc00?` at
67 ac-af and `#0xc000?` at 67 b4-b7, the provisional `sra? rd, rd, #pos` at 67 fc-fd) and
`xtd`, `6a` `j16`, `6b`
`jal11`/`mv #imm9`/VU ports with 5-bit registers, `6c` `lw`, `6d` `lbu`, `6e` half-word and post-increment memory, `6f` stack
frames, `jr` (`6f ec`: a 5-bit register field, r0-r31, with `.d1`/`.d2` in bits 6:5), `mv acc` and byte/half loads, `6e` q2 = `lbu.post`/`lhu.post?` with 5-bit registers; the 3-bit post-increment of the `6e` `.post` family — `sh.post`, `lw.post`,
`lhu.post?`, `lbu.post` — is SIGNED: x = 4..7 means -4..-1 units); `70` `mv #imm32`, `74` 6-byte
`add #imm19` / `and #imm16` / `or #imm16` (sub 3 is open); `c0-df` vector ops; `e0-ef` vector heads; `f0-f7` bundles.
## 3. Analysis
`scq16_decode_r2()` (scq16_arch.inc.c) decodes one instruction and fills the RAnalOp: the type from the unit that carries the
control flow (the B slot of a packet, the s2 slot of a bundle, the unit itself), `jump` from the table displacement (base = packet
start), `fail` = the address after the delay slots (decoded ahead from the bytes at hand; MAXOP_SIZE is 12 so that the analysis
sees a 6+6 packet and the slots), `delay` = the slot count, `cond`/`sign` for branches, `reg` for register jumps/calls/returns,
`ptr` + `direction REF` + `reg`/`val` for `do`, `refptr`/`ireg`/`disp`/`stackop` for memory and stack forms, `val` for immediate
moves. A packet without control flow has type `unk` (two operations cannot be summarised by one scalar type): its two operations
are in `opex.units[]`.
## 4. ESIL
* A unit's ESIL is its table template with the operands substituted (`{op}` read, `{=op}` write, `{#op}` decimal value for
register names, `{d}` target, `{ret}` return address after the slots, `{mask}` of a bit-field width). A packet is A then B
with the registers that B reads and A writes copied to `t0-t3` first (parallel semantics). An OPEN reading without a template (a placeholder or a provisional name: `SCQ16_FL_OPEN`)
carries the uniform marker `REF,ALT,open` (the custom op `open` registered by `esilcb`: no effect), so the other unit of its packet keeps
its ESIL and an emulator can stop on it uniformly; only a unit without any template leaves the whole instruction without ESIL.
* Delayed transfers: `target,SETJT,n,SETD` (n = slots); `aes` executes the slot and then applies the jump. Conditional branches
wrap it in `?{ }`. A `.d2` transfer takes two `aes` steps: the first runs the transfer and its first slot (a packet counts as
one instruction), the second runs the second slot and applies the jump.
* Memory addresses: the ESIL machine computes in 64 bits and `aes` does not mask addresses, so every `[n]` / `=[n]` access
cuts its address to 32 bits (`0xffffffff,&`): negative frame offsets (`lw.sp r1, -0xfc(sp)`) reach the 32-bit frame.
* A unit whose ESIL is the empty string (no modelled effect: `nop`, `halt`, `ieon`/`ieoff`, `loopend`, `vwait?`, `sys41`)
does not block the ESIL of its packet.
* Memory order inside a packet: the ESIL runs A before B with B's register reads taken from the pre-packet values; a B load
therefore reads the memory AFTER a store of A, and a B store after a load of A (the hardware order between the two memory
accesses of one packet is not established). The two slots of a bundle run in order s1, s2 after the vector operation.
* Comparisons: `<`/`<=` read 32-bit registers with sign, so unsigned compares normalise both operands with `,0,+` and signed ones
extend with `32,x,~`; `div` writes `a0l` (quotient) and `a0h` (remainder), `~/`/`~%` for the signed variant.
* `do` writes `lc0`/`le0`/`ls0` (count, end, body start); the back edge is not applied by the ESIL (an emulator must do it).
* A load with a post-increment writes its destination LAST (load, increment the base, write rd): with `rd == rb` the loaded value
wins. This is a declared convention (no such unit is known); the public MD32 documents rd written first.
* The VU scalar ports are registers (`vu0-vu31`, `v0-v31`, `va0-va7`, `vp0-vp7`, `vs0-vs15`, `vb1_0-31`, `vb3_0-31`, `vb5_0-31`,
`udf0-udf31`); a vector operation is `F3,F2,F1,B,A,op9,vop` with the custom op `vop` registered by `esilcb`. The VU model
(`scq16_vu.inc.c`): a vector register file `vr0-vr31` of 128 bytes each (64 lanes of 16 bits; exposed as four 256-bit quarters
`vrK_0..vrK_3`, lane 16q being the low 16 bits of quarter q), the pointers `vp0-vp7` (written by `vmem.set` and the hypothetical
`vcfg4c? #0xc`) and the counts `va0-va7` (written by `vset.a`). `vop` models the three established operations and nothing else:
The scalar ports `v0-v31` are the BROADCAST ports: the ESIL of `vbcast v_k, rs` / `vbcast.i v_k, #imm` / `vbcast.i?` writes the port
(the ISA template) and the plugin broadcasts the low 16 bits to the 64 lanes of `vr_k` (an ESIL register-write voyeur registered by
`esilcb`); the port keeps the last scalar (`vu0-vu31`, read by `vget`, is a different port, not modelled).
`v.mem.ld` copies `n` halves from DM[p] into `vr[F1 >> 2]` and `v.mem.st` copies `n` halves from `vr0` to DM[p], with
`p = vp[2 * F2 + F3[4]]` when `F2 < 4`, `n = va[A]` when 1..64 (otherwise the whole register: the count field of a large packed A is
not established) and the pointer post-incremented by the bytes accessed; `v.move.mv` copies `vr[F3]` into `vr[F1]`. A memory op
with `F2 >= 4` (the second access family, whose pointer is not established), the pair mode `B = 2` of `v.move.mv` and every other
op9 have no effect.
* `SCQ16_FL_HYP` alternatives (`cmov.leu?`/`ltu?`/`lt?`/`le?`, `lhu.post?`, `ld46?`/`lhu?` of `46`, `lbu?`/`lh?` on the stack,
`sys41`, `vbcast.i?`, `vcfg4c?` `#0xc`, the VU bank ports, `sqr?`, `sra?`, `vrd?` 1a, `op41.fld`) carry the ESIL of their provisional
reading; an emulator that wants to be strict can refuse them. The table HYP of the ISA source is exported as the flag; the only
templates the exporter would add are the declared plugin hypotheses of `isa/scq16_hyp.json`, identified by their sha256 (none:
the file is empty). `ctz` is established but its value for a zero operand is open (-1 in the ESIL, section 2).
Register profile: `r0-r31`, `pc`, `sr`, `ipc`, `isr`, `csr3`, `lf`, `ls0-2`/`le0-2`/`lc0-2`, `sp` (csr14), `lr` (csr15),
`csr16-31`, `a0` (`a0l`:`a0h`), `t0-t3` (ESIL temporaries), the VU ports, `vr0_0-vr31_3` (the vector register file). ABI: arguments
r3-r6, return r1; callee-saved r16-r31 (a function that writes one of them saves and restores it on its frame), caller-saved
r0-r15 (frame stores of r1-r8 are spills), lr saved by the functions that call.
## 5. Limitations
* The vector unit is modelled only for `v.move.mv`, `v.mem.ld` and `v.mem.st` (section 4): the lane arithmetic of the other op9
codes, the pointer of the second access family (`F2 >= 4`), the count field of a large packed A, the pair mode `B = 2` of `v.move.mv`
and the role of `F1 != 0` / `B = 1` in the stores are not modelled (`vop` has no effect there); the source lane `F3[3:0]` of a store
and the count field of a large packed A are open readings; `v.move.mv` with `B = 2` pairs even registers (an open reading),
modelled as the plain copy. No semantics for the `op41`/`op43`/`op67`/`op6f`/`op4x`/`op6x` placeholders, `vcfg4c?` except `#0xc`,
`vpmem?`, `sys`, the `74` sub-op 3, the six `sel?` codes without a name, the provisional masks `and? #0xf000?`/`#0xfc00?`/`#0xc000?`:
all of them are OPEN readings with the marker ESIL `REF,ALT,open`; `op41.fld` has only the hypothetical packing of the ISA source;
`fup` with overlapping fields is a plain insert.
* The length of `j.d1 56 0x-3x` is decided by the target (section 1) because the plugin has no function table to decide by
membership: a `j.d1` whose target is outside the loaded code would still be read as `j.d1`.
* The plugin annotates no DM strings nor I/O registers; the I/O window (`ior`/`iow`) is modelled as memory at the port index.
The data-space bases of the SCQ16 are available as flags (`tools/scq16_flags.r2`: `scq16.dm_main` at 0, the I/O window at
0x8000, `scq16.dm2` at 0x40000); the data memory is little-endian, the instruction stream big-endian.
* Confidence levels live in the ISA table, not in the plugin; provisional names keep their `?`.
+573
View File
@@ -0,0 +1,573 @@
# uSIP (MD32 family, VLIW packet variant) — ISA table notes for the `usip` CPU mode
Reference for the table format, the constraint semantics and the packet model behind the `usip` CPU mode. Everything
in it is implemented by `usip_decode.c` (packet model), `tools/gen_usip_tables.py` (table) and `arch_md32.c` (RAnalOp
mapping); see README.md "uSIP mode" for the user-facing description. Files:
| file | what |
|---|---|
| `isa/usip_isa.json` | the machine-readable table: `meta`, `structure` (packet model incl. the layout order, the K-tie cascade, the prefix, grammar and rename rules, registers, CSRs, system ops, I/O ports, conditions incl. the 48-bit condition table and the S0F1 tail variants, displacement and immediate formulas, field types, context rules) and the ORDERED list `forms` |
| `usip_opcodes.h` | the same forms as a C table (`UsipOpcode usip_opcodes[]`), generated by `tools/gen_usip_tables.py`, plus `usip_never_tail[]`, `usip_never_head_with_tail[]`, `usip_c48_table[]`, `usip_br0_cells[]` / `usip_br0_cells_weak[]`, `usip_c48sym_table[]`, `usip_value_sets[]` and the `rename_from` lists |
| `usip_decode.c` | the packet decoder (`parse_group`, `group_parses`, `decode_asg`, `score`, `fviol`, `parse_packet`, `apply_renames`, `decide_ties`) |
## 1. The packet model
The earlier `usip_decode()` read one 16-bit halfword and, when bit 15 was 0, paired it with the next halfword. That is
wrong in general: **the unit is the 16-bit halfword and the instruction stream is a VLIW packet stream.**
1. **Unit scan.** Read halfwords (big-endian) from the packet start. A packet = consecutive units up to and
including the first unit with **bit 15 = 1** (max 8 units). Packets start at `addr % 4 == 2` as often as at
`addr % 4 == 0`: never assume 32-bit words. Branch targets are packet starts.
2. **Heads and tails.** Inside a packet, a unit with bit 15 = 0 is either a *head* (first unit of an instruction) or
a *tail* (second/third unit of a 32/48-bit instruction). Instructions are **head + 0, 1 or 2 tails**, 16/32/48 bits
(64 bits only for the `bcc64` prefix forms: head + `1e0d` + U1 + T). A head without a tail is a 16-bit op "with the
packet still open" (its bit 15 is 0 but it is decoded like the bit-15 = 1 form; most 16-bit entries of the table mask
bit 15 out, the stand-alone *closer* forms keep it).
3. **Prefix forms.** Before anything else, if the packet opens with the three (four) units of a `bcc48` (`bcc64`) entry
(`USIP_F_PREFIX`: U0 op b-f with F = 1 (F = 0 + `1e0d` for the 64-bit twin), U1 `[0][1][m'h'][rrr'x'][00000000]`, T
`[S][1][00][d12]`) AND the target `packet start + 2·sext12(d12)` is a packet start of the same function (≠ the packet
itself), those units are ONE instruction and the rest of the packet is its delay slot (parsed as an independent packet
without control flow). A failing guard means the entry is simply not taken (normal layout, never `.word`). The absolute
`call.abs/b.abs` prefix (`x008 + hi + lo`, rule R) is unchanged. `5e0d` as U0 (`bgt.l r0, #255`) and `1e0d` as the
bcc64 extension unit are matched here, before the never-tail rule of item 4.
4. **Layout (head/tail pairing).** Tails are not necessarily adjacent to their head: the packet is *heads first,
then tails*, and the tails are dealt **round-robin** among the heads that take tails, in head order (`S S O O` =
two 32-bit instructions interleaved). A packet may also be a sequence of *groups*, each laid out that way
(one-instruction groups = contiguous layout: `b dest ‖ delay-slot ops`). No bit selects the layout. The decoder
enumerates every group split and every head/tail assignment allowed by the table (a head may take a tail iff some
non-context, non-rename form matches head+tail; a head may stand alone unless it is in `structure.packet.must_pair`;
`ret/save/restore` never take tails; **the units of `usip_never_tail[]` (`5e0d`) are never a tail and the heads of
`usip_never_head_with_tail[]` (`5e0d`, `1e0d`) never take one**; **a 48-bit op 0 head also takes `hi + lo` when
`head[13:9]:hi[12:0]:lo[13:0]` is one of the values of `usip_c48_table[]`**, the non-canonical 48-bit constants
admitted by value — in the partial head:hi admission some table value must share the upper 18 bits), discards,
inside a group, the assignments with more bare heads that have no named 16-bit reading, adds the explicit candidates
(`[head][op16][F' = 0 tail]` for 3 units — not for a head that never takes a tail —, **H H T T** =
`[h0][h1][t0 S=0][t1 S=1]` with two named ld/st for 4 units, also with tt = 11 tails (`ld.bu` / `st.b`) and the
bit-field pair `[op 7][op 7][tt = 01 S = 0][tt = 01 S = 1]`, and loopcand, R-brHHTT, R-b111pre and R-b111ctl of
item 15) and ranks the readings with the **prior-free total
order** (`structure.packet.layout_order`): `(s0, I, Fv, −A, K)` ascending, where `s0` = unknown units (kind `other`:
1 per unit; kind `fmt`: 1 per instruction) + the tie-break penalties of `structure.packet.tie_breaks` (R7 credit, O
syllables, loose `4000`, alignment except `f12 ≡ 3 ≥ 0xf00` and except `f12 ≡ 2` in the H H T T / H H T T C layout
(P2W), the add relabel, 1dX6, `.hi/.hilo`, the fam48 r14/r15 destination, the store-of-immediate triples, the
identical-units rule of 4-unit packets, the mfcsr pair, the validated-guard −0.5, **callin**: +1.5 for a relative
`call` whose target is strictly inside a known function, **P2H**: the odd half-word offset is not penalised in
H H T T / H H T T C except `f12 = 0x001` with base sp, **R-lr16**: +0.5 for a 16-bit `ld/st.b|bu|h r15, [rA]` in a
2-unit packet, **R-align48**: +0.5 for a formed 48-bit hi(1010) ld/st trio whose address is misaligned,
**R-c48sym**: +0.5 for a reading that leaves a 48-bit-family head without tails while a later free hi(1010) + lo of
the packet encode an address of `usip_c48sym_table[]`, and the rules of item 15: **R-waw**, **al1**, **R-bsnext**,
**sat40 H H T T**, **rf0dpen**, the P2W / P2H exemption of the H H T T prefix), `I` = number of instructions, `Fv` =
heads with a tail
whose adjacency contradicts the **F pattern** (head F = 0 and next unit bit 14 = 1 ⇒ adjacent tail, otherwise
deferred), `−A` = minus the number of adjacent tails, `K` = the sorted tuple of `(head index, (tail indices…))`
(smallest first). Readings tied on `(s0, I, Fv, −A)` go through the **K-tie cascade** (item 12) and, when it does
not decide, are the **ambiguous** packets (K decides) unless the tied readings are identical. No learned prior and
no group count take part. Rule **R0**: the final closer (bit 15 = 1) may be a one-unit group of its own even when it
has no named 16-bit reading. Rule **R7**: a bare first-slot unit with bit 15 = 0 of the slot family (or of the `lea?`
shape with rA ≠ r14 when no other instruction of the packet has a tail) counts as a named unit when every other
instruction of the packet is named, and is printed through the context forms `ldslot_S0`/`stslot_S0`/`lea_sp_S0`.
When no group parse exists a greedy fallback pairs each head with the next free unit if some form matches.
5. **Slot 0.** Only the first unit of a packet can be control flow (branches, `call/b`, `loop`, `loop #N`,
`bcc48/bcc64`, 16-bit branches incl. `call.s` with bit 15 = 0, `beqz.d/bnez.d` with F = S = 0, `call.abs/b.abs`,
the `b<cond>.d` with the `[0][1][tt]` tail at +2: the S0F1 cells, the `b<cond>.d` with head F = 0 of the R-br0
cells (tail `[0][1][tt]` at +1 or +2, `USIP_C_BR0_CELLS`), `ret.s.d`, `loop.b1?`, `bz.s` with bit 3 = 0); forms
flagged `USIP_F_SLOT0` must not be matched elsewhere. The 32-bit `ret` (`40ff c000`), `ret?`, `jr rB`, `call rB` and
the `sys` twins with F = S = 0 are NOT slot-0 forms. Tail adjacency of the control forms (`USIP_F_ADJCTRL`: +1
with S = 1 or +2 with S = 0; `USIP_F_ADJ2`: exactly +2 for the F' = 0 control tails, for the S0F1 branch tails AND
for `loop #N` with F = 1, S = 0 — at +1 that one is two 16-bit ops, not `.word`; `loop_F0` also at +2 with S = 1).
**Target guards**
(`cons = "guard:target_*"`): for 32-bit forms evaluated AFTER the mask match, a failure makes the pair `.word` (no
fall-through; the op C compare-and-branch cell, `bnez.d` with F = S = 0, leaves a `.dsp` pair: the pair is unnamed
data either way, the name is the only difference) unless the entry carries `USIP_F_GUARDFT` (the F = S = 0
`b<cond>.d` cells, `bne.d #-1`, the R-br0 cells: the next entry of the cell is tried and, when none is left, the two
units are 16-bit ops — R-tgt, item 13: the known target is a filter, never a partition); for the 16-bit `call_s_S0`
and the open `b.s` (`b_s_open`: a packet start of the same function or a function start) a failure falls through;
for the prefix forms a failure means "not taken". The near `b` (`b_near`) needs a packet start of any function of
the image or a function start (`guard:target_packet_start_any_or_function_start`), the hardware loops with the
F' = 1 tail (`loop`, `loop …, x16`, their `?ss=N` twins) a packet start of the same function.
6. **Deferred tails and undo.** `USIP_F_ADJDEF` (`st.h` with the F' = 0 tail, R-st1h): the tail must be at +2 or
farther; an adjacent tail undoes the pair into two 16-bit ops. `guard:head_not_validated_bs`: in the first slot,
when the head is a 16-bit `b.s` shape (`5cX1/7cX1`, not m = 0 with d6 = 0) whose target is a packet start of the same
function, the pair is undone (the `b.s` wins). `USIP_F_ADJ1` (immediate set-on-compare, `add_FS12`, `seq/slt` of
op 6): a farther tail undoes the pair. `USIP_F_ADJ2` on a ld/st entry (`mem_ld_b_t4_F1S0`, `mem_st_h_t6_F1S0`,
R-op1fs: the F ≠ S `ld.b` / `st.h` of op 1): the tail must be exactly at +2; at any other distance the pair is
undone into two 16-bit ops (not `.word`).
7. **Position selectors.** Constraints `head_index == n` / `head_pos_ge n` (unit index of the head inside the packet)
and `packet_len >= n` select the identical-bit cells by position: `mfacc_q_C_k2` (k ≥ 2), `cellE_st_h_q_k1` /
`cellE_add_q_k2`; `ld_w16_A_k0` (k = 0) and `lsl16_F1_S0_k1_q` (k ≥ 1) are post-layout renames (item 11). Entries
whose deferred constraints fail FALL THROUGH to the next entry of the same cell. `ctx:not_ext_of_op67`
(`add_FS1_rot12_odd`) needs the first unit of the packet. `ldslot_S0_m1` / `stslot_S0_m1` (R-slotm1: the m = 1
slots of op 4/8/c, k ≥ 32) are R7 forms that also need `ctx:rest_named_no_q` (every other instruction named, none
with a `?`).
8. **Context forms (`USIP_F_CTX`).** `bz_s_P4`, `bz_s_P5` (R-bzs5: a first-slot `4X?5/6X?5` unit, rA not sp/LR,
whose best reading is a bare unnamed unit is `bz.s rA, dest` with the rest of the packet as its delay slot:
unconditionally in a 2-unit packet, in a longer one only with the target validated as a packet start of the same
function), `bz_s_P4_u4` / `bz_s_P4_u3` (the same unit opening a packet of exactly 4 / 3 units, item 15),
`movi6_dsbr` (R-movi6d, item 15), `ldslot_S0/stslot_S0/lea_sp_S0` and the m = 1 twins (R7 / R-slotm1),
`ldslot_split/stslot_split` (the
two halves of an `.op8?`/`.op8.t?` pair of slot-family units, context rule `op8_slot_split`), the `hhtt_*` ld/st
forms with F ≠ S and the `b111_*` ld.bu / st.b forms with F ≠ S or F = S = 0 (only inside the H H T T candidate:
`USIP_F_HHTT`, `ctx:b111_layout`), the `b111_*_F1S1` forms (`USIP_F_B111`: the 2-unit head + tail packet with a
named 16-bit tail, and H H T T), the bit-field pair `bfpair_*` (`ctx:bfpair`, `bf_valid`), the `f0ctx` group
`hhttF0_*` / `rf0d_*` (`USIP_F_F0CTX`, `ctx:hhtt_second_head` / `ctx:f0d_triple`: tried when the op 0 +
`[S][0][01|10]` pair at +2 reads `.word`; after an `rf0d_*` reading the unit between head and tail is re-read with
the first-slot 16-bit cell when that gives a named, non-control op that does not write r14: context rule
`f0d_middle_unit`) and `hi1010_loose` (`.hi`): a lone unit with bits 12:9 = 1010 is `.hi` (and the next `.hword`
`.lo`) only when a head of the 48-bit family precedes it in the packet (context rule `hi_lo_fam_before`); otherwise
it is an ordinary 16-bit head with base/source r10 (**H1**: a hi(1010)-shaped unit is a `hi` syllable only inside a
formed 48-bit instruction; the table has no `not_hi1010` constraints and no `.hi/.hilo` pair entries, and the
parser no longer forces the hi(1010) decode of such heads in 4-unit packets).
9. **Delay slot = the rest of the packet.** When the control instruction does not close the packet (`call`/`b` with
an S = 0 tail, `b<cond>.d` incl. the S0F1 forms, `b<cond>.l` with S = 0 on T, `ret.d`, `b.s` with bit 15 = 0,
`call rB`, `call.abs`), every other unit of the packet executes before the transfer (on both paths). For radare2 this
means `op->delay` = number of remaining ops in the packet, and the branch instruction's `size` covers only its own
units while the fall-through address of the packet is the next packet.
10. **Hardware loops.** `loop rX, last=pc+disp12`, `loop #N, last=pc+disp12` (`USIP_A_LOOPN`: N = head bits 13:5 =
`m:h:rrr:x:B[3:1]`, 0..511, the collected field as is; the heads with h = 1 (N ≥ 128) and with m = 1 (N ≥ 256,
printed `loop #N?`) are loops too, and so is the head whose F' = 0 tail sits exactly at +2 (`loop_imm_*_F1_Fp0`,
R-loopA: the middle unit is a 16-bit op of the packet, at any other distance the pair is `.word`); the explicit
layout candidate **loopcand** adds the `[loop head][op16][tail at +2]` reading that the group grammar drops when
the middle unit has no named 16-bit reading, item 15), `loop rX, last=…, x16` (the head with B = 1: the packet at
the loop end crosses a 16-byte line, a fetch hint that leaves the loop semantics unchanged; formerly `loop.b1?`)
and the `?ss=N` twins (`loop r<rrr>?ss=N`, `loop r<rrr>?ss=N, x16` for ss ≥ 2): `last` is the
address of the LAST instruction of the body (inclusive); the body is the following 1-16 instructions; `last` must
be a packet start of the same function and ≠ pc. `de0d` (`nop.ctl` with S = 1) closes most hardware-loop bodies.
11. **Post-layout renames (`USIP_F_RENAME`).** Forty-one entries (`nop_ctl`, `lsl16_S0`, `lsl16_F1_c`,
`lsl16_F1_S0_k1_q`, `sext16_F1_c`, `sext16_F1_S0_q`, `zext16_F1_c`, `zext16_F1_S0_q`, `addi32_cop`, `ld_w16_A_k0`,
`x1e0_ld_w_q`, `x1e0_ld_h`, `x1e0_ld_hu`, `x1e0F0_add_q`, `x1e0F0_ld_h_q`, `x1e0F0_ld_hu_q` (R-1eX0F0: the `.1eX0`
head with the F' = 0 tail), `op7e_0_bset_r`, `op7e_6_bext_r`, `ldF0_q_t1_adj`, `ldF0_q_t2_adj`, `ld_h_Fp0`,
`ld_hu_Fp0`, `x3eX0_q`, the BF_SEL cells `bfsel_fup`, `bfsel_fup_q`, `bfsel_fxtu`, `bfsel_fxtu_q`, `bfsel_fxts_q`
from `.bf?` with `ctx:bf_dst_not_written` — no other WAW-kind instruction of the packet writes rB(!x) —,
`negq_ld_w_c1/c3`, `negq_st_w_c1/c3` (`ld.w?` / `st.w?`), `spfix_add_sp` (from `ld.w`), `stimmsp_st_w_sp`
(`st.w [sp], #imm` from `mov32` / `st.w?`, R-stimmsp), the six split entries `stslot_F0`, `stslot_F0_q`,
`stslot_S0_pos1`, `ldslot_S0_pos1_q`, `ldslot_S3_F0`, `stslot_S3_F0` (`USIP_F_SPLIT`) and the fusion entry
`op72_q` (`USIP_F_FUSE`)) are NEVER used while pairing or scoring (the layout is decided with the underlying
reading: `.ctl15`/`.hword`, `.sub?`, `.1eX0`, `.word`, `.7e`, `mov32`/`add`). Once the layout is decided, for
each instruction the rename entries of its width are tried in table order: mask over the gathered units,
`rename_from` containing the current reading (a mnemonic, or `@kind` for every reading of that kind), the other
constraints (`head_pos_ge 1`, `instr_index n` — the position of the INSTRUCTION inside the decoded packet, not
the unit index —, `tail_adjacent == true/false`, `ctx:cop_rA_in_packet`, `ctx:not_c48_lo_follows`,
`u16 in [...]`) → the instruction takes the entry's mnemonic / kind / format; the score is untouched. The split
and the fusion entries are skipped by this pass: the split pass and the fusion of item 14 apply them by index.
`sub r14, #imm` (frame allocation) is no longer a rename but the parser form `spsub_sub_sp` (R-spsub, any
F / F').
12. **K-tie cascade.** For each packet whose readings tied on `(s0, I, Fv, −A)`: `dedup` (identical multisets of
instruction texts → the K reading), `nowaw` (exactly one reading without a double register write → chosen; all
with one → no decision), `du2` (a differential use-definition score over a window of eight packets before and
after the packet, decision margin 1: only the registers whose role differs between the tied readings score — in
the forward direction a use, an argument, a dead value or a clobber of the register; in the backward direction a
definition, a return value, a live or a clobbered register); the chosen reading replaces the K reading. A fourth
step, a vote of the debug line table over other packets of the same source line, is not reproducible from the
bytes and is left to K. A decoder without any function context reproduces the bare K key; the cascade is a
tie-break improvement, not an admission rule. §7 describes what the plugin can see.
13. **Address views (R-img) and the target filter (R-tgt).** Every function is validated with the packet starts,
function starts and ranges of its own image: a function table that holds two overlapping copies of the same code
(a routine mapped twice, each copy with its own ranges) must carry the image, or a merged table validates targets
of the other copy and rejects branches with the wrong function limits. A disassembler that works on one image at
a time has a single view by construction, so the plugin has nothing to do for R-img. R-tgt: a known target is a
filter, never a partition. The plugin asks the caller for packet starts and function starts (§6); a known target
that is not one rejects the reading (fall-through to the next entry of the cell for the `USIP_F_GUARDFT` entries,
the next 16-bit entry for the open `b.s`, `.word` for the near `b` and the loops), an unknown one keeps the
reading unverified for the forms whose guard is a validation filter.
14. **Post-layout splits and fusion of the slot family.** After the layout (the score is untouched, only the names
change) the slot family is re-read. First **S3**: a bare `.hword` `[0][0][1][1][rrr][x][B][o3 0 00]` (m = h = 1,
F = 0, S = 0) in the first slot of a multi-unit packet is `ldslot rB, #k` (k = 48..63, `ldslot_S3_F0`) and a bare
`.hword` closer `[1][0][1][1][rrr][x][B][o3 1 00]` is `stslot` (`stslot_S3_F0`). Then one pass with a **nowaw
guardian**: **R-slot4n** (a pair read `.op4t0` / `.op4t1` / `.op4t3` whose head is op 4 with F = 0 and whose
single tail is the next unit with S = 0, low 12 bits > 1 and not `4000`-shaped → head `stslot` / `stslot?`
(k = m:h:rrr:o3, the `?` with h or m = 1: `stslot_F0` / `stslot_F0_q`) ‖ the tail's split-tail reading),
**R-op4imm** (a pair read `.op4imm` with an op 4 head and a tail with F' = 0 and tt ∈ {00, 01}, ONLY when the
split-tail reading is named → head F = 1 `stslot`, F = 0 `stslot` in a 2-unit packet / `stslot?` in a longer one
(`ctx:op4imm_head_multiunit`) ‖ the tail), **S0-slot** (a bare `.hword` at INSTRUCTION position 1 with F = 1,
S = 0, low 12 bits > 1 and (m = 0 or op ≠ 0) → `stslot` for o2 = 1 / `ldslot?` for o2 = 0: `stslot_S0_pos1` /
`ldslot_S0_pos1_q`). The split-tail reading of a unit is: the slot family with F = 1 and S = 0 → `stslot` /
`ldslot?`; op 4 / op c with F = 0 → `stslot(?)`; else its ordinary 16-bit reading (unnamed: kept as is in
R-slot4n, no split in R-op4imm). The guardian returns a split whose new destination (WAW kinds; stores and `iow`
excluded) is written by another instruction of the packet to its original entry. The units of a split pair carry
`ctx:split_unit`. Then the generic renames (item 11) and the **fusion** `op72? rA, #k, asr#b` (`op72_q`,
`USIP_F_FUSE`): two ADJACENT one-unit instructions whose readings are `movi rA` (head
`[0][F][0][h][rrr][0][b][0111]`) and the 16-bit op 2 `asr? rA` with x = 1 and the same rrr (`eq_fields`) become
ONE two-unit instruction (the `lsl` cell, x = 0, is not fused; the pair does not count as a double write).
15. **Explicit candidates, score rules and layout constraints of the 726-form table.** The explicit candidates of
item 4 gain **loopcand** (op f head + tt = 10 tail at +1 / +2 reading `loop #N` — not `loop #N?` —, the middle
unit a bare head; the F' = 0 tail only at +2), **R-brHHTT** (`[branch head F = 0 at 0 → 2][op 0/1 head at 1 → 3]`
+ the rest in packets of ≥ 5 units whose units 2 and 3 have S = 0 and F' = 1, when the branch reads with a
validated target and the second pair reads ld/st), **R-b111pre** (the type-111 H H T T, both tails S = 0, as the
PREFIX of a packet of ≥ 5 units; the rest parsed as a group) and **R-b111ctl** (`[op e head at 0 → 2][op 1 head at
1 → 3]` in exactly 4 units, unit 2 S = 0 and F' = 1 with tt = 10/11, unit 3 S = 1: the round-robin of the type-111
store after a control head, which the group grammar does not generate; `400e 5c01 654c f8e2` = `b ‖ st.b r0,
[sp+0xe3]`); each is rejected by the grammar's own filters (a tail on `ret/save/restore` or on a head that never
takes one, a unit that is never a tail) and the b111 / hhtt context forms hold inside them (`ctx:b111_layout`).
The score (item 4) adds **R-waw** (+0.75 per register written by ≥ 2 instructions of the reading — the first
operand of a WAW-kind instruction followed by a comma; stores excluded; the adjacent bare pair `movi rA ‖ op 2 rA,
x = 1` of the same rrr is one instruction and does not count — the parallel-execution model), **al1** (the
`f12 ≡ 1` / `≡ 3` (< 0xf00) word tails pay 0.8 in all: 0.3 more than a double write), **R-bsnext** (+2.5 for a
first-slot 16-bit `b.s` whose target is exactly the next packet and not a function start: the unit is the
`st.h/st.b` head), the **P2W / P2H exemption** when H H T T is the prefix of a ≥ 5-unit packet, **sat40 H H T T**
(+3.0 for `[op c][op c][tail (8,c)][tail (8,c)]` not read as the pairs 0 → 2, 1 → 3), **rf0dpen** (+1.0 for the
bare unit after an R-F0d head whose destination is written twice; **rf0dmidwaw**: that middle unit is then not
re-read) and **R-movi6d** (the `.movi6?` closer in the delay slot of a validated branch at unit 0 counts as
`movi rA, #simm6`: `movi6_dsbr`, `ctx:delay_slot_of_validated_branch`); scores are kept in twentieths of a unit.
Three constraints are evaluated with the layout: **beq9sp** (`beq_imm8_d`: `any(rA ≠ 14, m = 1, tail_adjacent =
false)` — the op 9 `beq.d` with base sp and the adjacent tail is not a branch; a failure falls to the next
reading), the **`bgt rA, #-1` head class** (the seven op a `*_s0f1` / `*_imm8_F0` entries carry `any(m = 1,
rA ≥ 8, x = 0, rB ≠ 15)`: with ss = 00, x = 1 and B = 15 the head only takes the adjacent S = 1 tail) and the
**R-w6h1 loop guard** (`st_h16_f_slot0_h1`, the W6 cell `st.h rB, [r{8+rrr}]` with x = 0 and B ∈ {0, 1}, does
not match when a `[S][0][11][d12]` tail at +1 / +2 of the packet being parsed has its end at a packet start of
the function after the head: the unit is the head of a validated `loop r{8+rrr}`, `ctx:not_loop_reg_head`).
**`bz.s` in 4- and 3-unit packets** (`bz_s_P4_u4`, `bz_s_P4_u3`; item 8): the same `4X?d` unit (not `5cXd` /
`5e0d`, rA not sp) opening a packet of exactly 4 units whose best reading leaves it unnamed, with the target a
packet start of the same function, is `bz.s rA, dest` with the rest of the packet re-parsed as its delay slot; in
a 3-unit packet also with the target BEYOND the packet (`ctx:target_beyond_packet`) and a delay slot that does not
write rA (`ctx:delay_slot_not_writing_rA`). The ABI passes eight arguments in r0-r7 (§4).
16. **Cells added with the 726-form table** (besides the loops of item 10 and the splits of item 14): `op2z? rA`
(the 16-bit op 2 with x = 0 and B = 0 is not a shift by 0), `lsl / asr r8-r15, #B` (the h = 1 cells), the W6 cells
with h = 1 (`ld.h / st.h rB, [r8+rrr]` and `[rA]+` in the first slot), `sub r14, #f12/2` with any F / F'
(R-spsub), `st.w [sp], #imm14` (R-stimmsp: the ninth stack argument), `st.abs` with the hi F' = 0 (R-stabs0),
`add rB, rB, #imm27` / `st.h? [rB], #sat` selected by the immediate value (addq: the saturation constants 0x7fff,
0x8000, 0x7ffe, 0x3fff, 0x1fff, 0xfff are the `in` / `notin` value lists of the entries; also the cell-E
triples), `add? rB, r15, #rot12` / `ld.h? / ld.hu? rB, [r15±off]` with the F' = 0 tail (R-1eX0F0),
`saturate? rd, rs1` (op c with B = 0 and rd = rs1), `movi rA, #simm6` in the delay slot of a validated branch
(R-movi6d), `ld.h / ld.hu` without the `?` for R-ldF0 (S = 0), R-hhttF0, R-F0d and R-1eX0, `ld.hu rd, [rA+rB]`
(X = 4), the 16-bit `lea` (no `?`), `movi #-32768 / #-32767`, `mtcsr #0x30-0x3e` (`mtcsr_acc`).
17. **The parallel packet, the 22-bit branches, the slot pairs and the guards of the 760-form table.** The packet
model is settled: the instructions of a packet execute in parallel and every read sees the state before the
packet (`USIP_PACKET_EXEC_PARALLEL`; the R-waw penalty and the K-tie cascade stay, no rule depends on an
intra-packet order). **Before the layout**: slotpair0 (a 2-unit packet `[0][0][m][h][rrr][x][B][o3 100]`
`[1][0][…][o3' 100]`, low 12 bits ≠ 0 in both, whose 32-bit reading has no name — no entry, kind other / fmt, a
failed guard or an undone pair — is `stslot ‖ stslot` (`stslot_F0` / `stslot_F0_k16`), never ambiguous); the
prefix forms with the **22-bit displacement** (`disp` = 2·sext22(`U1[7:0] : T[13:12] : T[11:0]`); the U1 low byte
and the T tt are free in the masks) and the **64-bit X variant** (`bcc48_*_64x`: U0 with F = 0, any extension unit
with bit 15 = 0 other than `1e0d`; `guard:not_over_validated_call32`: not taken when U0 + X read, as an adjacent
pair in the first slot, as a `call` / `b` whose target is a function start; the branch keeps the three units
U0 U1 T, X is decoded on its own as the first 16-bit instruction of the delay slot, the units from 4 on are the
rest of it). **In the layout**: the guard `loop_b4_straddle16` of the twelve `loop #N` entries (bit 4 of the head,
field `b4`, must equal `straddle16(last)` = the packet that starts at `last` crosses a 16-byte line:
`(last / 16) != ((last + 2n − 2) / 16)` with n its units; a contradiction is `.word`, like a failing target guard;
an unknown packet at `last` accepts); the **`loop #N?` rule** (`USIP_LOOPQ_*`: a `loop#` reading with N = 0 or a
tail d12 in {0x100, 0x200, 0x400} prints `loop#? #N?, last=…`; the explicit candidate loopcand does not admit it);
`ctx:tail_at_plus2` (`beqz_F0c` / `bnez_F0c` / `bleu_rr_F0c` / `bltu_rr_F0c`, `bne_imm8_F0s0` / `blt_imm8_F0s0`:
the single tail EXACTLY at +2, at any other distance the entry falls through to the next one of the cell — the
DSP pairs of op c —, NOT `.word`; with the tail at +2 and a target that is not a packet start the pair is `.word`);
`ctx:rf0dall_guards` (`rf0dall_ld_h / ld_hu`, group f0ctx after `rf0d_*`: the tail f12 is even, the unit at +2
does not decode in tail position as a trusted 16-bit op — mov inc dec rsub add addi mul st.w ld.w mfacc lsl lsr
asr sub and or xor sext16 zext16 movi st.h ld.h ld.hu st.b ld.bu — nor as control, the head is not `4400` right
after an op f head with x = 1, the middle unit as a 16-bit op does not write r14; the f0ctx group is tried inside
the layout, before the post-layout renames: `ld_h_Fp0` never sees those pairs); the score rules lr32 (−1 for a
reading with a jump / call, or the rest of a `call.abs` / `b.abs` packet, that contains `mov32 r15, #X` with X a
packet start of the same function), rr7 (+0.25 for a `movi rB, #imm17` formed while an earlier op 7 head of the
reading with x = 0 stays bare), spodd (+1.0 for a bare 16-bit `addi r14, #imm` with imm not a multiple of 4) and
the rf0dall nowaw (+1.0 when another instruction of the reading writes the destination of an `rf0dall_*` head).
**After the layout**: `bz_s_P4_F0` (after `bz_s_P4_u4`, before the R7 gate: `000h rrrd dddd d101` at k = 0 of a
packet of 2 or 3 units whose head has no named reading and is not the bare `.hword` op 5 x = 0 unit, rA ≠ r14,
d6 > 0, the target a packet start of the same function beyond the packet: `bz.s rA` + the delay slot — at 2 units
the second unit as a 16-bit op, at 3 the rest re-parsed); the post rules of the final reading, before the split
pass and also after the prefix paths: **R-add2** (a packet of ≥ 3 units, a bare `.hword` op 0 F = 0 head at unit
i — `.hword` only: the bare m = h = 0 units of a non-first slot read `.ext` and never fuse — and a bare unnamed
closer `[1][1][00][f12 ≡ 0 mod 4]` at unit i + 2 become one `add rB(!x), rA(!m), #rot12` unless another
instruction of the packet writes rB) and **R-fslot0c** (a bare `.hword` / `.ext` F-slot0 unit
`[0][0][0][0][rrr][x][B][o3 100]`, low 12 bits > 1, at unit 1 of a packet whose instruction 0 is a call /
call.abs / callr / call16 is `stslot`); in the split pass **R-slotc** (a pair read with an op c head with F = 0
and a single tail with tt = 00, rd ∈ {4, c}, a ≠ 8, not the (0, 0) cell with B = 0, low 12 bits > 1 when F' = 1,
whose reading is `.dsp` / `.word`, `msu?` / `shr.a?` / `mac.9a?` / `mac.92?` or `mac` / `mul.a` / `mul3216.a` /
`L_mac.a` with rd > 3 → `stslot_slotc_h ‖ stslot_slotc_t`, with the nowaw guardian), the F-slot0 readings by rule
(R-slot4n / slotpair0 / R-fslot0c with k ≥ 16 → `stslot_F0_k16`; only the F = 0 head of a split `.op4imm` pair
with k ≥ 16 or in a multi-unit packet keeps `stslot_F0_q`) and `ldslot_S0_pos1` without the `?`; **after the
packet**: slotc_mark (the slot pairs without a call — kinds call / call16 / calla / callr — in their packet or in
the two following packets take the `stslot?` twins `stslot_slotc_h_q / _t_q`). The other cells added with the
table: `movi rB, #imm17` (the op 7 head with the null-field tail `S P00 … 00f1`: a 17-bit rotated immediate
`USIP_A_ROTIMM` with `nbits = 17`; with m = 0 the value is −65536 / −65535, not −32768 / −32767: `movi_min16` /
`movi_m32767` are gone), `movhi` with the small F' = 0 tails (`c001` → `0x10000000`, even f12 → `0x1000..0x7000`),
the 16-bit `ld.w rB, [rA ∓ 4]` of the first slot and the one-unit `st.w rB, [rA ∓ 4]` (R-ld16k0 / R-st16k0),
`addi rA, #−4 / −2 / −1` of the class-E cell (R-addiE), `loop rX, last, x16` (B = 1: the former `loop.b1?`), the
m = 1 `loop #N` without the `?`, `saturate`, `mfacc` of the C cell and `sext16 / zext16` with S = 0 without the
`?`, and the CSR names `ag0-ag3.{base?, len?, idx?}` for 0x30-0x3e.
Operand fields are given as bit ranges over the big-endian concatenation of the instruction's units (bit `width-1`
= bit 15 of the head): 32-bit forms = head bits 31..16, tail 15..0; 48-bit = head 47..32, hi 31..16, lo 15..0; 64-bit =
head 63..48, ext 47..32, U1 31..16, T 15..0. When the units are not adjacent in memory the plugin gathers them (head,
then its tails in round-robin order) before applying mask/value.
## 2. The table
`tools/gen_usip_tables.py` emits every field referenced by the format string or by a constraint (`nfields` >=
`nargs`), the constraints as structured `UsipCons` records (op, field indices, values), the `rename_from` lists of the
rename entries, the indices of the context-only forms (`USIP_FORM_*`, incl. `hi1010_loose`, the split and the fusion
entries) and the auxiliary tables
of the ISA description (`usip_must_pair`, `usip_sys_fld4[16]`, `usip_cond_bcc48[5][4]`, `usip_csr_ext[64]`,
`usip_kind_names[]`, `usip_save_set[8]`, `usip_never_tail[]`, `usip_never_head_with_tail[]`, `usip_c48_table[]`,
`usip_br0_cells[]` / `usip_br0_cells_weak[]` (`UsipBr0Cell`: op, tt, class, distance), the sorted `usip_c48sym_table[]`,
`usip_value_sets[]`); the generated header is `usip_opcodes.h` and regenerating it is byte-identical. Form names are
sanitised into C identifiers for the `USIP_FORM_*` indices. Limits: `USIP_MAXRANGES 8` (the movhi V20
field collects 8 ranges), `USIP_MAXFIELDS 12`, `USIP_MAXCONS 8`, `USIP_MAXVALS 12` (the `fup` cell set); the 16-bit
forms may only use `packet_len` 1/2, `packet_len_ge` 2, `head_index` 0 and `head_pos_ge` 2 (the 16-bit match cache is
keyed by those classes), except the rename and the context-only entries, which are evaluated with the exact head
index after the layout. The constraint ops `tail_adjacent` (`USIP_C_TAIL_ADJACENT`, `vals[0]` = 0/1; also the `any`
sub-operation `USIP_ANY_TAIL_ADJACENT` with field `USIP_NOFIELD`), `ieee_short` (`USIP_C_IEEE_SHORT`, `vals[0]` =
significant digits), the membership in the 48-bit value table (`USIP_C_IN_C48TAB`, emitted for an `in` set equal to
the table), `br0_cells` / `br0_cells_live` (`USIP_C_BR0_CELLS` / `USIP_C_BR0_CELLS_LIVE`: field = rB, `vals[0]` = a
cell mask, `USIP_BR0_BIT(class, dist)`), `bf_valid` (`USIP_C_BF_VALID`, fields w / pos), the guards `tail_named16`
(`USIP_C_GUARD_TAIL_NAMED16`) and `target_packet_start_any_or_function_start` (`USIP_C_GUARD_TARGET_PK_ANY_OR_FN`),
the value sets too large for `vals` (`USIP_C_IN_SET` / `USIP_C_NOTIN_SET` into `usip_value_sets[]`), the `any`
sub-operations `USIP_ANY_PACKET_LEN`, `USIP_ANY_GUARD` (`enum usip_guard_id`), `USIP_ANY_RANGE` (`lo | hi << 16`) and
`USIP_ANY_BR0_CELLS` / `USIP_ANY_BR0_CELLS_LIVE`, `USIP_ANY_INSTR_INDEX` and `USIP_ANY_CTX` (an `any` holds up to four
alternatives, `USIP_MAXANY 4`, which may be a context condition or an instruction position), the instruction position
`instr_index` (`USIP_C_INSTR_INDEX`, on the rename entries), and the context conditions `USIP_CTX_COP_RA_IN_PACKET` /
`USIP_CTX_NOT_C48_LO_FOLLOWS` / `USIP_CTX_B111_LAYOUT` … `USIP_CTX_REST_NAMED_NO_Q` / `USIP_CTX_NOT_LOOP_REG_HEAD` …
`USIP_CTX_SPLIT_UNIT`, are evaluated by the decoder with the packet context (`USIP_CTX_TARGET_BEYOND_PACKET`,
`USIP_CTX_DELAY_SLOT_NOT_WRITING_RA`, `USIP_CTX_OP4IMM_HEAD_MULTIUNIT`, `USIP_CTX_SPLIT_UNIT` and
`USIP_CTX_DELAY_SLOT_OF_VALIDATED_BRANCH` by the explicit procedures of items 14 and 15 — the `bz.s` rules, the split
pass, `decode_asg` — and accepted by the generic constraint check). `USIP_F_SPLIT` marks the entries of the split
procedure (`split_role` in the JSON: `either` = the head or the re-read tail of a split pair, `bare` = a bare unit
renamed before the pass, also `USIP_F_SPLITBARE`), `USIP_F_FUSE` the fusion entry; every one of them has a `USIP_FORM_*`
index.
With the 760-form table the header also carries the guards `loop_b4_straddle16` (`USIP_C_GUARD_LOOP_B4_STRADDLE16`,
exported with the head field `b4`) and `not_over_validated_call32` (`USIP_C_GUARD_NOT_OVER_VALIDATED_CALL32`), the
context conditions `USIP_CTX_TAIL_AT_PLUS2`, `USIP_CTX_HEAD_UNNAMED_NOT_LSL0`, `USIP_CTX_RF0DALL_GUARDS`,
`USIP_CTX_SLOTC_PAIR`, `USIP_CTX_CALL_WITHIN_2_PACKETS`, `USIP_CTX_OP4IMM_HEAD` and `USIP_CTX_ADD2_FREE_CLOSER`
(evaluated by the explicit procedures of item 17: the f0ctx pass, the post rules, the split pass and `slotc_mark`),
`USIP_F_FUSE | USIP_F_ADJ2` for the fusion at distance 2 (`add2_rot12`), the constants of the packet model
(`USIP_PACKET_EXEC_PARALLEL`) and of the `loop #N?` rule (`USIP_LOOPQ_ENABLED`, `USIP_LOOPQ_N_EQ`, `usip_loopq_d12[]`);
`USIP_A_ROTIMM` with `nbits = 17` and the three-range `USIP_A_DISP` of the 22-bit displacement need no generator
change, `usip_csr_ext[]` carries `ag0-ag3.{base?, len?, idx?}`, and the `rename_from` symbols `@slotpair0` /
`@fslot0c` / `@slotc_tail` / `@free_closer` are plain strings (the split and fusion entries are applied by index, never
by the generic rename pass).
`usip_opcodes[]` is ordered: the first entry of the right width whose `(units & mask) == value`, whose `flags`
allow the slot, and whose `cons` holds is the reading (entries with `USIP_F_RENAME` are excluded from that matching:
item 1.11). Fields of each entry: `name` (mnemonic), `form` (stable id = `usip_isa.json` `forms[].name`),
`value/mask`, `width/nunits` (16/32/48/64), `flags` (`USIP_F_SLOT0`, `NOTSLOT0`, `CTX`, `DELAY`, `ADJ1`, `ADJCTRL`,
`NAMED`, `NOTAIL`, `ADJ2`, `UNKNOWN16`, `ADJDEF`, `PREFIX`, `RENAME`, `HHTT`, `GUARDFT`, `B111`, `F0CTX`, `FUSE`,
`SPLIT`, `SPLITBARE`; a `ut32`),
`kind`, `conf`, `fmt` + `fields[]`
(operand template and bit fields, up to 8 ranges), `cons[]`, `rename_from` / `nrename_from` (the readings a rename
entry may replace).
Operand value transforms (`UsipField.type`): `reg` (`rN`; the `{N:!}` template token prints the high-bank `!`;
`base` holds a constant ADDED to the field — `rB1 = rB + 1` of `ld.d/st.d`, `sh = m + 1` of the 16-bit `lsl`, `12 + 8x`
of cell A), `imm` (× `scale` when declared: `addsp`, `lea?`, `ld.d? b10` ×4, `ldslot` k), `simm` (sign-extend to the
total field width, × `scale`: `ld.d/st.d` offset = 4·sext10(s:v9), the bcc48 imm9), `rotimm` (`rot(f,n)`: bit 0 is the
sign), `urot`, `disp` (signed, × `scale`, pc-relative: target = head address + value; for the prefix forms the head is
the packet start), `udisp` (loop end, inclusive), `absaddr` (`base | value`, `base = SEG[A] = A << 26`, `0x78000000` for
the hi(1010) family `lea/lea?`), `acc`, `accw`, `csr` (unused), `csr_ext`, `flag`, `movhi` (the collected bits — V20 =
`i:t1:t0:f12[0]:m:h:rrr:f12[11:1]` for `movhi`/`movhi_tt10_short`, `f12[11:1]` for `movhi_small` — `<< 12`), `offh`
(half-word offset: `f` even → `f`, odd → `f − 0x1001`), `offw` (W14, a 14-bit word displacement: `(f & 3) == 2` →
`0x1000 + (f & ~3)`; `(f & 3) == 3 && f >= 0x800` → `4·sext10(f >> 2)`; else `f`), `offb` (the byte displacement of
`ld.b/ld.bu/st.b`: `sext12(f[0] : f[10:1] : f[11])`, bit 11 of the field is bit 0 of the displacement and bit 0 the
sign; printed with its sign), `loopn` (the collected bits as is: head bits 13:5). An immediate glued to a letter or
to `=` (the accumulator index `A{k}`, the loop `?ss=N`, `save w=N`) is printed in decimal. The 48-bit constants use a
plain 32-bit `imm` field `val` =
`head[13:9] << 27 | hi[12:0] << 14 | lo[13:0]`. The bit-field position `pos` of `fxtu/fxts/fup` is a plain `imm`
whose ranges `[0,0],[4,1]` implement `rotr5(p)`.
Bank bits: `x` (head/unit bit 8) is the bank of **rB** (destination / data), `m` (bit 13) is the bank of **rA**
(source / base; also the compared register of the 32-bit `b<cond>` and the destination of the 16-bit `movi rA!,
#imm5`), and for op 7 `ss & 2` (head bit 13, field `ss1`) puts **rs1** in the high bank (also in `bset/bext rd, rs1,
rB` and the bit-field family). Templates print them as `{x:!}`, `{m:!}`, `{ss1:!}`.
Constraints that are not mask/value (`cons` records, evaluated by `cons_ok()`): address windows (`call.abs`, `mov32`
windows), `B != 0` for `sys`, `rA not in {14,15}` for the 16-bit `bz.s/bnz.s`, the near-`b` displacement range, the tail
guards (`setcc_tail`, `tail_not_named16`, `or2_tail`), `field >= n` / `> n`, `field in [..]` on hidden fields (`u9`,
`rA5`, `f12`, `csr`, `lo4`, `mh`, `op`, `A`, `hi12`, `u16` and `f12` of the bit-field cells; large sets as 64-bit
membership masks), `val` in the 48-bit value table, `even` / `odd`, `any` (up to three alternatives incl. set
membership and `tail_adjacent`), `packet_len == n` (stand-alone units; `== 3` for `add32_0eX0_L1`), `packet_len >= n`,
`head_index == n` / `head_pos_ge n`, `ctx:not_ext_of_op67`, `ctx:cop_rA_in_packet`, `ctx:not_c48_lo_follows`,
`tail_adjacent == true/false`, `ieee_short(imm, 3)`, the target guards and `guard:head_not_validated_bs`,
`br0_cells [[class, dist], …]` (the (B class, tail distance) cells of an F = 0 branch head: B0 = B == 0, B15 = B == 15,
B = other; distance 1 / 2; `br0_cells_live` for the five weak cells, which never hold in the plugin), `bf_valid(w,
pos)` (1 ≤ w and w + pos ≤ 32), the guards `tail_named16` (`hhtt_st_w_t5_F1S0`: the W14 text only with a named 16-bit
tail) and `target_packet_start_any_or_function_start` (`b_near`), the context conditions `b111_layout`,
`hhtt_second_head`, `f0d_triple`, `bfpair`, `bf_dst_not_written`, `between_dsp_head_and_tail` (unused),
`rest_named_no_q`, `any` with the sub-operations `packet_len == 2` / `guard:…` (`bz_s_P5`: evaluated by its context
rule), `guard:tail_not_named16` / `csr in [0x30, 0x3f)` (`mtcsr_q`, R-mtacc) and `br0_cells` / `br0_cells_live`, the
large value sets of the BF_SEL cells (`USIP_C_IN_SET` / `NOTIN_SET`), and the 16-bit entries whose **mask includes
bit 15 = 1** (closers). The context-free constraints are evaluated by the pairing
predicates; the position / length / context / adjacency constraints are deferred to the decode of an assignment (a
failure falls through to the next entry of the cell), followed by the b.s guard (undo) and the target guards (`.word`).
`kind` values: see `enum usip_kind`.
Context rules (not table entries, see `usip_isa.json structure.context_rules`): `hi_lo_fam_before` (`.hi/.lo`),
`.ext` (`fX?7` after a `ld/st` whose tail has S = 0), `.be79`, `bz_s_P4`, `slot_S0_R7` / `lea_sp_S0`,
`op8_slot_split`, `sgt_closer_pair` (`4NN7 cc00`), `cells_by_position`, `not_ext_of_op67`, `nop_ctl`, `cop_addi32`,
`renames_post_layout`, `bz_s_P5`, `f0d_middle_unit`. `structure.sys_ops` lists the `sys`-shape families by tail field
(fld 0 `ret`/`jr rB`, 1 `call rB`, 2 `ret?`, 4 `cop/rti/lpexit?/isync?/ieon/ieoff/sys? #13/idle`) and the F = S = 0 twin;
`structure.conditions.bcc48` the 48-bit condition table and `S0F1_tail_variants` the `[0][1][tt]` branch tails;
`structure.registers.io_ports` the port map of `ior/iow`; `structure.packet.kties` the K-tie cascade.
What the table cannot express (rules of the decoder, documented in `structure.packet`): the group / round-robin
enumeration and its bare-head filter, the explicit candidates (3-unit F' = 0, H H T T, `0e10 3fc0 c000`), the layout
order `(s0, I, Fv, −A, K)` and the tie-break penalties (incl. P2W and callin), the late evaluation of the guards, R0/R7,
the never-tail / never-head-with-tail rule, the op8 slot split, the partial admission of the 48-bit value table, the
fall-through guards (R-tgt), the `b111` and `f0ctx` groups, the re-read of the R-F0d middle unit, the `bz_s_P5` rule,
the post-layout renames, the K-tie cascade.
## 3. Confidence and what is format-only
* `high`: encoding and operation established by independent checks — the whole control flow (`b<cond>` incl. the
48/64-bit `b<cond>.l` and the `b<cond>.d` S0F1 cells, `call`, 16-bit branches, `loop` incl. `loop #N`, `call.abs`,
the F' = 0 tails), ld/st with offset incl. the signed `ld.d/st.d`, the negative half-word offsets and the 14-bit
word offsets (W14), the 16-bit ALU, the 32-bit ALU, `movi21`, `mov32/add` by formula and by the value table,
`movhi`, the basop/DSP family, mul/div, bit ops incl. `fxtu/fxts/fup` and the register `bset/bext`, CSR access,
port I/O, `save/restore`, `nop32`, `cmov r15`, `sne`, `seq`, `slt`, `sgt`, `addsp`, `osl`.
* `medium`: statistically established or with one confirmed example, or sign/width open (`ld.hu?`, `ld.bu?`, `st.h?`,
`ld.w?` stack slots, far `b`, SP adjustments, `rd!` twins, `st.b?`, `lea?`, `ldslot/stslot`, `mfcsr?`, `ret?`, `jr`,
`seq?/sle?` (|imm| > 256), `sle`, `ld.d?/st.d?` (b10), `ld.w?/st.w?` (the 0x800-0xeff band), `ld.b?` (f12 = 0),
`mov32?`, `lea?` (top5 = 8), `mfacc?` (cell C), `add?/st.h?` (cell E), the `idx_*_hi` / `idx_post_*` loads, `bcc48?`,
`ble.l?`, `bneu.l?`, `bequ.l?`, `movi?` (op b and `#-32768`), `bclr?/binv?/bext? rd!`, `lpexit?`, `isync?`, `extu`,
`ior?/iow?`, `nop.ctl`, `lsl/lsl?` (R-lsl0/1), `sext16/zext16(?)` (R-sxt1), `addi #32`, `sgt #-1` (F = S = 1),
`movhi` tt = 10, `ld.w?/ld.h?/ld.hu? [r15+off]`, `ld.h?/ld.hu?` with the F' = 0 tail, `fup?`, the op d / op 8
`b<cond>.d` S0F1 cells, `ret.s.d`, `bz.s` P5, the R-br0 cells of op 8 / op c, the `?ss=N`
loops, `lsl?/asr?` of the op 2 cells without idiom, `ldslot/stslot` with m = 1, `ld.h?/ld.hu?` of R-hhttF0 / R-F0d,
`ld.w?/st.w?` of the NEGQ classes (sign open), `movi? #-32767`, `fup?/fxtu?/fxts?` by BF_SEL cell, `mtcsr?` of the
accumulator CSRs, `add r14 / sub r14`, `loop #N?` (the m = 1 heads), `bz.s` of 3-unit packets, the split
`stslot? / ldslot?` and the 16-bit `lea`, `op2z?`, `saturate?`, `add? / ld.h? / ld.hu?` with base r15 and the
F' = 0 tail, `st.h? [rB], #sat` (addq / cell E), `mtcsr #0x30-0x3e` (CSR semantics open), `sub r14, #f12/2`,
`st.w [sp], #imm`, `movi #simm6` in a delay slot, the `ld.h / ld.hu` that lost their `?`).
* `low`: named hypotheses (`mtacc?`, `msu?`, `.cmp?`, `or.2`, `add.1`, accumulator shifts, `sys?`, `ld.sp`/`st.sp`,
`op72?`).
* `format-only`: the pairing is established but not the operation: mnemonics starting with `.` (`.hword`, `.word`,
`.w48`, `.dsp`, `.alu32`, `.op4imm/.op4i`, `.op4t0` (NOT ld.w: a stolen tail), `.op7t`, `.bf?`, `.idx?`,
`.ctl15/.ctl15.t?` (parser readings of `nop.ctl`), `.1eX0` (F' = 0), `.ldF0?` (width open), `.3eX0?` (not a
constant), `.movi6?`, `.st.h?/.ld.h?/.ld.b?`, `.stimm16?` (an immediate store of 17+ bits), `.6008`, `.hi/.lo/.ext`,
`.fill`, `.st7`, `.op5i?/.op5b10?`, `.op8?/.op8.t?/.opd?`, `.slot?`, `.sub?`, `.sp0?`, `.7e` (X = 2/7/8/9), …). The
plugin still groups their units and prints them as data.
Open points that matter for a disassembler: the remaining layout ties and fallback packets; the `f12 ≡ 1` /
`≡ 3 < 0x800` word displacements (`ld.w?/st.w?`: negative by the W14 formula or unsigned, unconfirmed); `.op4t0`
(stolen tails), `.ldF0?` (width), `.3eX0?` (a saturating ALU op?), `mtacc?`, `.bf?` (the cells without a confirmed
example), the `?ss=N` loops, the `lsl?/asr?` cells without idiom, the byte displacement of
the F ≠ S cell at `f12 = 0x7ff`, the layout-based `add`/`ld.w` selector of the op 0 F = S = 1 `f12 ≡ 0` cell, the
packets with an unformed 48-bit head and a hi(1010) unit read as `movi r10, #7`, why the scheduler inserts `nop.ctl`,
the 0x800-0xeff word-offset band, `.idx?` a = 2, `4028 4002`. Two printing conventions are not in the table: the
32-bit add-immediate is often listed as `add rB, rA, #imm` (record mnemonic `addi`) and `loop #N` as `loop#`
(`loop #N?` as `loop#?`); the plugin prints the record mnemonics.
## 4. ABI (for `regs()`)
`r14 = SP`, `r15 = LR`, arguments `r0-r7` (r0-r3 most used, stack args > r7 at `ldslot #k` = SP + 4k), return `r0`
(`r0:r1` for 64 bits; 64-bit products live in the accumulators `A0-A3`), callee-saved `r8-r10, r12, r13` (+ the
high-bank copies) via `save w` (`w = 2` saves `{r11!,r12!,r13!,r14!}`: four words; `w = 4` saves `{LR, r8}`: two
words), `r11` caller-saved. There is **no classic CSR bank** reachable through op 4: that shape is port I/O
(`ior rB, [rA]` / `iow rB, [rA]`). Extended CSRs `0x01-0x3e` via `mfcsr` and `mtcsr #nn, rA`. There are no flags: all
branches are compare-and-branch (incl. the 48-bit `b<cond>.l rB, #imm9 / rA`).
## 5. Known limitations of the table-driven reading
1. **Ties.** Readings tied on `(s0, I, Fv, −A)` go through the K-tie cascade (§1.12, §7); what it cannot decide is
left to the assignment key `K` and flagged `opex.packet.ambiguous` (a small fraction of the packets, mostly `S S O O`
and longer packets where several head/tail assignments have the same cost). The `du2` step sees a shorter context
than a scan of the whole function would give (§7), so a few of its decisions can differ from those of a decoder
that has the function in hand.
2. **Control flow in open packets** (`call`/`b`/`loop`/`loop #N`/`b.s` followed by delay-slot ops): when the branch
reading is not the cheapest one the packet reads as `add`/`st.w`/`st.h`/`movi` (a `loop #N` reading with a 16-bit
middle unit often writes a register twice with the stolen tail, which R-waw charges). A disassembler that knows the
function table can prefer the reading whose target is a function start or a packet boundary, and can answer the
`callin` tie-break (a relative `call` into the middle of a function, §6).
3. **Target guards that cannot be evaluated** (§6): far `b` to another function or beyond the lookahead (and the
readings of the packet behind it), the open-packet `call.s`, and `loop`/`bnez.d`/register `b<cond>`, the
`b<cond>.d` S0F1 and register cells and the `bz.s` of packets longer than two units with a forward target beyond
the lookahead are read as data or as the plain 16-bit units.
4. **Rules that need a validated target** (§6): the loop entries not accepted unverified (the m = 1 `loop #N` heads,
the h = 1 heads except with the S = 1 tail or the adjacent F = 0 tail, the F = 1 head with the S = 0 tail at +2),
the 22-bit cells and the 64-bit X variant of `b<cond>.l`, the 2-, 3- and 4-unit `bz.s`, R-brHHTT, R-movi6d and
the R-w6h1 loop guard have no unverified fallback: beyond the lookahead the plain 16-bit readings, the `.hword`,
the grammar's reading, the `.movi6?` closer and the W6 cell stand.
5. **Windows the plugin cannot always see** (§6): `slotc_mark` needs the two packets after a slot pair (inside the
lookahead they are first-pass readings, beyond it the window ends and the pair prints `stslot?`), the straddle
guard of `loop #N` needs the packet at the loop end (beyond the lookahead and the session's cache it is not
evaluated, and an eight-unit run without a closer counts as unknown), and `not_over_validated_call32` needs
function starts (without the `function_start` callback a target the session validated as a packet start stands
in, so a cross-function `call` of U0 + X the session has not seen does not block the 64-bit variant). The plugin
produces no ESIL for the uSIP mode: the parallel packet would require reading every operand of the packet before
any write.
## 6. Target guards: what the plugin knows
The decoder asks the caller through `UsipGuardCtx` (`packet_start`, `packet_start_any`, `function_start`,
`inside_function`: yes / no / unknown). `arch_md32.c` answers **packet starts from the stream**: the packet starts
decoded earlier in the session (a hash set in the session state: backward targets, and forward targets of a region
decoded before, e.g. after `aa` or on a second listing) and a scan of the bytes radare2 passes with the op past the
current packet (forward targets; radare2 caps the op bytes at 64, `r_anal_op_set_bytes`), so a forward target farther
than 64 bytes is unknown on a first linear pass. "Same function" is approximated by "reachable in the linear decode"
(no function bounds): a backward target below the lowest packet start decoded in the session (`lo_seen`) is unknown
(another function, or code never listed), one at or above it that was not seen as a packet start is not one. A
definite "not a packet start" rejects the reading even for the guards that also admit a function start: a target the
caller could look at that is not a packet start cannot be a function start either. `packet_start_any` is not
installed (the session's packet starts stand in).
**Function starts are unknown**: an arch plugin has no typed access to RAnal (`RArch.user` is an untyped pointer), so
neither `function_start` nor `inside_function` (the `callin` tie-break: a relative `call` whose target lies strictly
inside a known function pays the out-of-boundary penalty) is installed; both are the hooks for an embedder that has a
function table.
When a guard cannot be evaluated the decoder applies a fallback chosen by measurement on the sample the table was
built from (`unverified_ok()` in `usip_decode.c`, by form name, tail distance, head/tail bits and the direction of
the target): the forms whose
unverified readings are right in nearly every case where they compete are read anyway — `call_F0` (function start
unknown; with the tie-break bonus), `b_F0`, `beqz_d` and the immediate `b<cond>` forms with the F' = 0 tail
(`beq/bne/ble/blt/bgtu/bge/bgt_imm8_F0`, forward target beyond the lookahead), the `bcc48_*` / `bcc48_*_64` prefix
forms in their regular cell (U1 low byte 0 and tt = 00: a forward displacement below 4096 units, beyond the
lookahead), `loop_imm_F1S1`, `loop_imm_F0S0` and `loop_imm_h1_F0S0` with the tail adjacent, `loop_imm_F1_Fp0` (the
F' = 0 tail at +2), `loop_imm_h1_F1S1`, `loop_F0` when the head has F = 1 and the S = 1 tail sits at +2, the op c
F = 1 branches and the op 9 F = S = 0 cells with the F' = 0 tail at +2 (`beqz/bnez_F0c`, `bleu/bltu_rr_F0c`,
`bne/blt_imm8_F0s0`), and the S0F1 cells
`bne/blt/bgeu/bgtu/bge_imm8_s0f1`, `beqz/bnez_s0f1` and `bleu_rr_s0f1`, and the forms whose guard is a validation
filter (`b_s_open`, `b_near`, `loop` / `loop_ssq` / `loop_b1` / `loop_b1_ssq` (the x16 loops) with the F' = 1 tail, the F = S = 0
`b<cond>.d` cells, `bne_d_m1` and the R-br0 `*_f0s0` cells: they were read without a guard before the filter existed,
so an unknown target keeps the reading, the conditional ones only forward); the others are treated as a failed guard —
`call_s_S0` (only a small fraction of the shapes are calls), `b_far` (about a fifth of the unverified readings would
be wrong), `bnez_d` of the F = S = 0 cell (shared with the op C DSP pairs), `ble_rr_F0`/`blt_rr_F0` (shared with
`.ctl15`), the other `loop_F0` tails (`movi || mov` pairs), `loop_imm_F1S0` (the S = 0 tail at +2: wrong too often
when unverified), `loop_imm_F0S0` and `loop_imm_h1_F0S0` with the tail at +2, `loop_imm_h1_F1S0`,
`loop_imm_h1_F1_Fp0` and the m = 1 `loop_imm_m1_*` entries (the 16-bit readings stand), the 22-bit cells of
`b<cond>.l` (backward, or +4096..+8191) and the `bcc48_*_64x` variant with a free extension unit, the S0F1 cells
`beq/ble/bgt_imm8_s0f1`, the op 8 S0F1 cells and the op d S0F1 register cells. The rules that need a validated
target (the 2-, 3- and 4-unit `bz.s`, R-brHHTT, R-movi6d, the R-w6h1 loop guard) have no unverified reading;
slotpair0, R-slotc, R-fslot0c, R-add2 and rf0dall carry no target guard. Backward targets inside the decoded
region are decided by the session (seen = packet start, unseen = not a packet start); below it they are unknown:
unverified for `call`, `b` and `b.s`, never for a conditional branch (a conditional branch does not leave its
function). An accepted-unverified reading prints a trailing `?` (`call?`, `beqz.d?`, `blt?`, `bne.l?`, `loop#?`,
`bne.d?`, `b.s?`, `loop?`; the `loop #N?` rule keeps its own `?` after the count, so an unverified one prints
`loop#?? #N?`), reports `confidence = medium` and `target_guard = "unverified"` in `opex`; a validated
one reports `"validated"`. A 32-bit pair whose target guard failed is `.word`, except the op C compare-and-branch
cell (`bnez.d` with F = S = 0), whose failed pair keeps the DSP shape and is printed `.dsp 0x…` (unnamed data either
way), and the `USIP_F_GUARDFT` entries, whose failed guard falls through to the next entry of the cell (two 16-bit
ops when none is left).
Two guards of the 760-form table are not target guards. The straddle guard of `loop #N` (`loop_b4_straddle16`: bit 4
of the head must agree with "the packet at the loop end crosses a 16-byte line") is answered through
`UsipGuardCtx.packet_units`, which `arch_md32.c` serves from the bytes at hand or from a packet decoded earlier in the
session; a contradiction is `.word` like a failed target guard, an unknown packet accepts. `not_over_validated_call32`
(the 64-bit `b<cond>.l` with a free extension unit is not taken when U0 + X read as a `call` / `b` whose target is a
function start) is exact with the `function_start` callback; without it a target the session validated as a packet
start stands in.
## 7. The K-tie cascade in the plugin
`usip_decode_packet` runs the cascade of §1.12 on every packet whose readings tied on `(s0, I, Fv, −A)` (`cascade()`
in `usip_decode.c`: `decide_ties`, `regs_of`, `du2_scores`):
* **`dedup`** and **`nowaw`** only need the tied readings of the packet itself: the instruction texts (without the
unverified `?`) and the registers each instruction writes and reads are derived from the text (`rN`/`rN!`/`sp`/`LR`
tokens, the base of `[rA]+` as a destination, the first operand as the destination when it is a register or a pair,
the read-modify-write forms, stores and system ops reading everything).
* **`du2`** needs the neighbouring packets. The decoder asks the caller for them through `UsipGuardCtx.packet_at`
(the packet that ends at an address: the backward context; the packet that starts at an address: the forward
context) and `branch_target` (the join points). `arch_md32.c` supplies the packets decoded earlier in the session
(their final readings, from the packet cache) and the **first-pass readings** of the packets that follow inside the
bytes at hand (decoded with `USIP_GUARD_NO_CASCADE`, so a context packet never runs its own cascade), plus the
targets of every branch decoded so far. Approximations versus a scan of the whole function with first-pass
readings: the forward window ends at the 64 bytes radare2 passes (the cascade would look eight packets ahead), the
backward packets are already-cascaded readings, the join points only include the branches seen so far, and an
exhausted window is "no event" where a scan that knows the function start would report an argument / undefined
register. `dedup` and `nowaw` do not depend on the context; a few `du2` decisions do, and a tie `du2` cannot decide
with the shorter context is left to K (flagged ambiguous).
* `opex.packet.tiebreak` reports `dedup` / `nowaw` / `du2` for a packet decided by the cascade;
`opex.packet.ambiguous` is true only for the ties the cascade could not decide (K decides them).
* A packet read by the `bz_s_P5`, `bz_s_P4_u4`, `bz_s_P4_F0` or `bz_s_P4_u3` rule parses the rest of the packet as an independent
packet (its delay slot): the ties of that parse are the ones the cascade sees, and the rule is applied again to the
reading the cascade chooses.
* The packet-level rules run on the chosen reading in a fixed order — `bz_s_P4`, `bz_s_P4_u4`, `bz_s_P4_F0`, R7 /
`lea_sp_S0`, `bz_s_P4_u3`, S3, `.be79`, the post rules R-add2 / R-fslot0c, the split pass (items 14 and 17) — then
the generic renames, the fusion and `slotc_mark`; the cascade compares the renamed and fused texts, and after its
decision the same passes run again on the reading it chose.
+5
View File
@@ -0,0 +1,5 @@
{
"what": "plugin hypotheses: hypothetical ESIL / p-code templates the plugin adds to forms the ISA source lists in its HYP table without a template of its own. Empty: the packing of op41.fld is carried by the ISA source, so every ESIL and p-code the plugin emits comes from the source. The mechanism stays (tools/gen_scq16_tables.py export --hyp): an entry is applied only where the source has no template and the form is in HYP.",
"esil_conventions": "radare2 ESIL: A,B,- = B - A; A,B,<< = B << A (the operand pushed last is the left operand); {op} read, {=op} write",
"forms": {}
}
File diff suppressed because it is too large. Load diff
+158948
View File
File diff suppressed because it is too large. Load diff
+445
View File
@@ -0,0 +1,445 @@
/* Generated by tools/extract_opcodes.py; encoding facts, see doc/research.md. */
enum {
MD32_I_C0,
MD32_I_C1,
MD32_I_C10S_1_1709,
MD32_I_C10U_1809,
MD32_I_C10U_2_0700,
MD32_I_C12S_1908,
MD32_I_C12U_1908,
MD32_I_C15,
MD32_I_C16,
MD32_I_C16S_1_2208,
MD32_I_C16S_2308,
MD32_I_C16SU_2308,
MD32_I_C16U_1500,
MD32_I_C16U_2308,
MD32_I_C2,
MD32_I_C21S_2404,
MD32_I_C22S_1_2000,
MD32_I_C255,
MD32_I_C2U_0706,
MD32_I_C3,
MD32_I_C3S_0806,
MD32_I_C3S_0907,
MD32_I_C3U_0806,
MD32_I_C4S_1_0806,
MD32_I_C4S_1_0907,
MD32_I_C4U_0603,
MD32_I_C4U_0704,
MD32_I_C4U_0906,
MD32_I_C5S_0804,
MD32_I_C5S_2_0907,
MD32_I_C5U_1208,
MD32_I_C5U_1309,
MD32_I_C5U_1612,
MD32_I_C5U_1713,
MD32_I_C5U_1814,
MD32_I_C5U_2117,
MD32_I_C5U_2_0907,
MD32_I_C65280,
MD32_I_C65535,
MD32_I_C6U_1_0804,
MD32_I_C7,
MD32_I_C7S_1004,
MD32_I_C8,
MD32_I_C8S_1_0600,
MD32_I_C8S_N2_0804,
MD32_I_C8U_0700,
MD32_I_C8U_1104,
MD32_I_C9S_2118_0400,
MD32_I_C9S_N2_0904,
MD32_I_C9U_2118_0400,
MD32_R_14,
MD32_R_A0,
MD32_R_A0H,
MD32_R_A0L,
MD32_R_C3U,
MD32_R_C4U,
MD32_R_DBG,
MD32_R_IPC,
MD32_R_ISR,
MD32_R_LC0,
MD32_R_LC1,
MD32_R_LC2,
MD32_R_LE0,
MD32_R_LE1,
MD32_R_LE2,
MD32_R_LF,
MD32_R_LS0,
MD32_R_LS1,
MD32_R_LS2,
MD32_R_SR,
};
static const Md32Opcode md32_opcodes[] = {
{ "add", 0x08000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16S_2308, 8 } } },
{ "add", 0x0400e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "add", 0x20002000, 0xfffff000, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 }, { MD32_I_C1, -1 } } },
{ "add", 0x20003000, 0xfffff000, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 }, { MD32_I_C2, -1 } } },
{ "addx", 0x0a000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16S_2308, 8 } } },
{ "addx", 0x0402e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "and", 0x0c000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16U_2308, 8 } } },
{ "and", 0x0404e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "bclr", 0x0489a000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "bclr", 0x04882000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "beq", 0x1c800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "beq", 0x1c000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "beq.d1", 0x14800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "beq.d1", 0x14000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "ble", 0x18800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "ble", 0x18c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9S_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "ble", 0x18000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "ble.d1", 0x10800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "ble.d1", 0x10c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9S_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "ble.d1", 0x10000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bleu", 0x19800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9U_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bleu", 0x19c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9U_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bleu", 0x19000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bleu.d1", 0x11800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9U_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bleu.d1", 0x11c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9U_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bleu.d1", 0x11000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "blt", 0x1a800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "blt", 0x1ac00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9S_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "blt", 0x1a000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "blt.d1", 0x12800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "blt.d1", 0x12c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9S_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "blt.d1", 0x12000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bltu", 0x1b800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9U_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bltu", 0x1bc00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9U_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bltu", 0x1b000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bltu.d1", 0x13800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9U_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bltu.d1", 0x13c00000, 0xffc00000, 1, 0, 3, { { MD32_R_C4U, 5 }, { MD32_I_C9U_2118_0400, 0 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bltu.d1", 0x13000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bne", 0x1d800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bne", 0x1d000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "bne.d1", 0x15800000, 0xffc00000, 1, 0, 3, { { MD32_I_C9S_2118_0400, 0 }, { MD32_R_C4U, 5 }, { MD32_I_C10S_1_1709, 9 } } },
{ "bne.d1", 0x15000000, 0xff800000, 1, 0, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C16S_1_2208, 8 } } },
{ "brk", 0x05400000, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "brk.j", 0x05600000, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "bset", 0x04898000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "bset", 0x04880000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "btgl", 0x0489c000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "btgl", 0x04884000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "btst", 0x0489e000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "btst", 0x04886000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "cinv", 0x0407e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "clb", 0x04196000, 0xffffff00, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "cw", 0x07000100, 0xffffff00, 1, 2, 2, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 } } },
{ "do", 0x1cc00000, 0xfff00000, 1, 0, 2, { { MD32_I_C8U_0700, 0 }, { MD32_I_C12U_1908, 8 } } },
{ "do", 0x1ce00000, 0xfff000f0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C12U_1908, 8 } } },
{ "do.d2", 0x14c00000, 0xfff00000, 1, 0, 2, { { MD32_I_C8U_0700, 0 }, { MD32_I_C12U_1908, 8 } } },
{ "do.d2", 0x14e00000, 0xfff000f0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C12U_1908, 8 } } },
{ "fup", 0x20400000, 0xffc00000, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_I_C8U_1104, 4 }, { MD32_I_C5U_2117, 17 }, { MD32_I_C5U_1612, 12 } } },
{ "fup", 0x20100000, 0xfffc0000, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1713, 13 }, { MD32_I_C5U_1208, 8 } } },
{ "fxts", 0x20200100, 0xfff80100, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1814, 14 }, { MD32_I_C5U_1309, 9 } } },
{ "fxtu", 0x20200000, 0xfff80100, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1814, 14 }, { MD32_I_C5U_1309, 9 } } },
{ "halt", 0x05800000, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "ieoff", 0x07008021, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "ieon", 0x07008020, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "ior", 0x07004000, 0xffffe000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 }, { MD32_R_C4U, 0 } } },
{ "iow", 0x07006000, 0xffffe000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 }, { MD32_R_C4U, 0 } } },
{ "j", 0x1e000000, 0xffe00000, 1, 0, 1, { { MD32_I_C22S_1_2000, 0 } } },
{ "j.d1", 0x16000000, 0xffe00000, 1, 0, 1, { { MD32_I_C22S_1_2000, 0 } } },
{ "jal", 0x1f000000, 0xffe00000, 1, 0, 1, { { MD32_I_C22S_1_2000, 0 } } },
{ "jal", 0x1f800000, 0xfffffff0, 1, 0, 1, { { MD32_R_C4U, 0 } } },
{ "jal.d1", 0x17000000, 0xffe00000, 1, 0, 1, { { MD32_I_C22S_1_2000, 0 } } },
{ "lb", 0x06200000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "lb", 0x06a00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "lbu", 0x06000000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "lbu", 0x06800000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "lh", 0x06300000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "lh", 0x06b00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "lhu", 0x06100000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "lhu", 0x06900000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "lw", 0x06400000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "lw", 0x06c00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "max", 0x20000000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "maxd", 0x20005000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "min", 0x20001000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "mind", 0x20007000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "mul", 0x040ce000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "mv", 0x00000000, 0xfe000000, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C21S_2404, 4 } } },
{ "mv", 0x04162000, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_A0H, -1 } } },
{ "mv", 0x04172000, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_A0L, -1 } } },
{ "mv", 0x04148000, 0xfffff0ff, 1, 0, 2, { { MD32_R_A0H, -1 }, { MD32_R_C4U, 8 } } },
{ "mv", 0x04134000, 0xffffff0f, 1, 0, 2, { { MD32_R_A0L, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x07008000, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_DBG, -1 } } },
{ "mv", 0x07008010, 0xfffffff0, 1, 0, 2, { { MD32_R_DBG, -1 }, { MD32_R_C4U, 0 } } },
{ "mv", 0x04182000, 0xffffff0f, 1, 0, 2, { { MD32_R_SR, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182001, 0xffffff0f, 1, 0, 2, { { MD32_R_IPC, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182002, 0xffffff0f, 1, 0, 2, { { MD32_R_ISR, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182004, 0xffffff0f, 1, 0, 2, { { MD32_R_LF, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182005, 0xffffff0f, 1, 0, 2, { { MD32_R_LS0, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182006, 0xffffff0f, 1, 0, 2, { { MD32_R_LE0, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182007, 0xffffff0f, 1, 0, 2, { { MD32_R_LC0, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182008, 0xffffff0f, 1, 0, 2, { { MD32_R_LS1, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04182009, 0xffffff0f, 1, 0, 2, { { MD32_R_LE1, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x0418200a, 0xffffff0f, 1, 0, 2, { { MD32_R_LC1, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x0418200b, 0xffffff0f, 1, 0, 2, { { MD32_R_LS2, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x0418200c, 0xffffff0f, 1, 0, 2, { { MD32_R_LE2, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x0418200d, 0xffffff0f, 1, 0, 2, { { MD32_R_LC2, -1 }, { MD32_R_C4U, 4 } } },
{ "mv", 0x04154000, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_SR, -1 } } },
{ "mv", 0x04154010, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_IPC, -1 } } },
{ "mv", 0x04154020, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_ISR, -1 } } },
{ "mv", 0x04154040, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LF, -1 } } },
{ "mv", 0x04154050, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LS0, -1 } } },
{ "mv", 0x04154060, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LE0, -1 } } },
{ "mv", 0x04154070, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LC0, -1 } } },
{ "mv", 0x04154080, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LS1, -1 } } },
{ "mv", 0x04154090, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LE1, -1 } } },
{ "mv", 0x041540a0, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LC1, -1 } } },
{ "mv", 0x041540b0, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LS2, -1 } } },
{ "mv", 0x041540c0, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LE2, -1 } } },
{ "mv", 0x041540d0, 0xfffffff0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_LC2, -1 } } },
{ "mv", 0x0412c000, 0xfffff00f, 1, 0, 3, { { MD32_R_A0, -1 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "nop", 0x05000000, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "or", 0x0d000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16U_2308, 8 } } },
{ "or", 0x0405e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "osl", 0x20020000, 0xfffe0000, 1, 0, 4, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 }, { MD32_I_C5U_1612, 12 } } },
{ "rl", 0x07000000, 0xffffff00, 1, 2, 2, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 } } },
{ "rnds", 0x20080000, 0xfff80100, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C10U_1809, 9 } } },
{ "rndu", 0x20080100, 0xfff80100, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C10U_1809, 9 } } },
{ "rol", 0x048c3000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "ror", 0x048c2000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "rti", 0x05a00000, 0xffffffff, 1, 0, 0, { { 0, 0 } } },
{ "sb", 0x06500000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "sb", 0x06d00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "sdiv", 0x040dd000, 0xfffff0f0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 8 } } },
{ "sethi", 0x0f000000, 0xff0000f0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C16SU_2308, 8 } } },
{ "sh", 0x06600000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "sh", 0x06e00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "sll", 0x041e6000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "sll", 0x0409e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "sra", 0x041d6000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "sra", 0x040ae000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "srl", 0x041c6000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "srl", 0x040be000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "sub", 0x09000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16S_2308, 8 } } },
{ "sub", 0x0401e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "subx", 0x0b000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16S_2308, 8 } } },
{ "subx", 0x0403e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "sw", 0x06700000, 0xfff00000, 1, 1, 3, { { MD32_R_C4U, 4 }, { MD32_I_C12S_1908, 8 }, { MD32_R_C4U, 0 } } },
{ "sw", 0x06f00000, 0xfff00000, 1, 3, 3, { { MD32_R_C4U, 4 }, { MD32_R_C4U, 0 }, { MD32_I_C12S_1908, 8 } } },
{ "swi", 0x05c00000, 0xffff0000, 1, 0, 1, { { MD32_I_C16U_1500, 0 } } },
{ "udiv", 0x040dc000, 0xfffff0f0, 1, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 8 } } },
{ "xor", 0x0e000000, 0xff000000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C16U_2308, 8 } } },
{ "xor", 0x0406e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "xtd", 0x041b6000, 0xffffe000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_I_C5U_1208, 8 } } },
{ "xtd", 0x0408e000, 0xfffff000, 1, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 }, { MD32_R_C4U, 8 } } },
{ "add16", 0x00008600, 0x0000fe00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C5S_0804, 4 } } },
{ "add16", 0x00008800, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "add16", 0x00009400, 0x0000fc00, 4, 0, 3, { { MD32_R_C4U, 0 }, { MD32_R_14, -1 }, { MD32_I_C9S_N2_0904, 4 } } },
{ "add16", 0x00008400, 0x0000fe00, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 6 } } },
{ "addsp16", 0x00009200, 0x0000ff00, 4, 0, 2, { { MD32_R_14, -1 }, { MD32_I_C10U_2_0700, 0 } } },
{ "and16", 0x00008a00, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "and16", 0x00008040, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C1, -1 } } },
{ "and16", 0x00008080, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C3, -1 } } },
{ "and16", 0x000080c0, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C7, -1 } } },
{ "and16", 0x00008100, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C15, -1 } } },
{ "and16", 0x00008140, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C255, -1 } } },
{ "and16", 0x00008180, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C65280, -1 } } },
{ "and16", 0x000081c0, 0x0000ffc0, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C65535, -1 } } },
{ "clz16", 0x00008d00, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "mv16", 0x00009800, 0x0000f800, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C7S_1004, 4 } } },
{ "mv16", 0x00009300, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "nop16", 0x00008000, 0x0000ffff, 4, 0, 0, { { 0, 0 } } },
{ "nop16.r", 0x00008000, 0x0000ffff, 4, 0, 0, { { 0, 0 } } },
{ "or16", 0x00008b00, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "sll16", 0x00009100, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C4U_0704, 4 } } },
{ "sll16", 0x00008200, 0x0000fe00, 4, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C3U_0806, 6 } } },
{ "sra16", 0x00009000, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_I_C4U_0704, 4 } } },
{ "sub16", 0x00008900, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "xor16", 0x00008c00, 0x0000ff00, 4, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "beq16", 0x0000bc00, 0x0000fe00, 6, 0, 3, { { MD32_I_C0, -1 }, { MD32_R_C4U, 0 }, { MD32_I_C6U_1_0804, 4 } } },
{ "beq16.d1", 0x0000b800, 0x0000fe00, 6, 0, 3, { { MD32_I_C0, -1 }, { MD32_R_C4U, 0 }, { MD32_I_C6U_1_0804, 4 } } },
{ "bne16", 0x0000be00, 0x0000fe00, 6, 0, 3, { { MD32_I_C0, -1 }, { MD32_R_C4U, 0 }, { MD32_I_C6U_1_0804, 4 } } },
{ "bne16.d1", 0x0000ba00, 0x0000fe00, 6, 0, 3, { { MD32_I_C0, -1 }, { MD32_R_C4U, 0 }, { MD32_I_C6U_1_0804, 4 } } },
{ "brk16", 0x0000a002, 0x0000ffff, 6, 0, 0, { { 0, 0 } } },
{ "brk16.j", 0x0000a003, 0x0000ffff, 6, 0, 0, { { 0, 0 } } },
{ "j16", 0x0000a180, 0x0000ff80, 6, 0, 1, { { MD32_I_C8S_1_0600, 0 } } },
{ "j16", 0x0000a030, 0x0000fff0, 6, 0, 1, { { MD32_R_C4U, 0 } } },
{ "j16.d1", 0x0000a100, 0x0000ff80, 6, 0, 1, { { MD32_I_C8S_1_0600, 0 } } },
{ "j16.d2", 0x0000a020, 0x0000fff0, 6, 0, 1, { { MD32_R_C4U, 0 } } },
{ "lb16", 0x0000a280, 0x0000ffc0, 6, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 3 } } },
{ "lbu16", 0x0000a2c0, 0x0000ffc0, 6, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 3 } } },
{ "lh16", 0x0000a200, 0x0000ffc0, 6, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 3 } } },
{ "lhu16", 0x0000a240, 0x0000ffc0, 6, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 3 } } },
{ "lw16", 0x0000a800, 0x0000fc00, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C5U_2_0907, 7 }, { MD32_R_C3U, 4 } } },
{ "lw16", 0x0000a400, 0x0000fe00, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C8S_N2_0804, 4 }, { MD32_R_14, -1 } } },
{ "lw16", 0x0000b000, 0x0000fc00, 6, 3, 3, { { MD32_R_C4U, 0 }, { MD32_R_C3U, 4 }, { MD32_I_C5S_2_0907, 7 } } },
{ "nop16.l", 0x0000a000, 0x0000ffff, 6, 0, 0, { { 0, 0 } } },
{ "sb16", 0x0000a380, 0x0000ff80, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 4 } } },
{ "sh16", 0x0000a300, 0x0000ff80, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C0, -1 }, { MD32_R_C3U, 4 } } },
{ "sw16", 0x0000ac00, 0x0000fc00, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C5U_2_0907, 7 }, { MD32_R_C3U, 4 } } },
{ "sw16", 0x0000a600, 0x0000fe00, 6, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C8S_N2_0804, 4 }, { MD32_R_14, -1 } } },
{ "sw16", 0x0000b400, 0x0000fc00, 6, 3, 3, { { MD32_R_C4U, 0 }, { MD32_R_C3U, 4 }, { MD32_I_C5S_2_0907, 7 } } },
{ "bclr16", 0x0000c980, 0x0000ff80, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_I_C4U_0603, 3 } } },
{ "bset16", 0x0000c900, 0x0000ff80, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_I_C4U_0603, 3 } } },
{ "btgl16", 0x0000ca00, 0x0000ff80, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_I_C4U_0603, 3 } } },
{ "mac16", 0x0000c100, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "max16", 0x0000c400, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "maxd16", 0x0000c600, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "maxv16", 0x0000c200, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "min16", 0x0000c500, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "mind16", 0x0000c700, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "minv16", 0x0000c300, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "mv16", 0x0000c098, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_A0H, -1 } } },
{ "mv16", 0x0000c090, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_A0L, -1 } } },
{ "mv16", 0x0000c088, 0x0000fff8, 5, 0, 2, { { MD32_R_A0H, -1 }, { MD32_R_C3U, 0 } } },
{ "mv16", 0x0000c080, 0x0000fff8, 5, 0, 2, { { MD32_R_A0L, -1 }, { MD32_R_C3U, 0 } } },
{ "mv16", 0x0000c800, 0x0000ff8f, 5, 0, 2, { { MD32_R_SR, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c801, 0x0000ff8f, 5, 0, 2, { { MD32_R_IPC, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c802, 0x0000ff8f, 5, 0, 2, { { MD32_R_ISR, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c804, 0x0000ff8f, 5, 0, 2, { { MD32_R_LF, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c805, 0x0000ff8f, 5, 0, 2, { { MD32_R_LS0, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c806, 0x0000ff8f, 5, 0, 2, { { MD32_R_LE0, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c807, 0x0000ff8f, 5, 0, 2, { { MD32_R_LC0, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c808, 0x0000ff8f, 5, 0, 2, { { MD32_R_LS1, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c809, 0x0000ff8f, 5, 0, 2, { { MD32_R_LE1, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c80a, 0x0000ff8f, 5, 0, 2, { { MD32_R_LC1, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c80b, 0x0000ff8f, 5, 0, 2, { { MD32_R_LS2, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c80c, 0x0000ff8f, 5, 0, 2, { { MD32_R_LE2, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c80d, 0x0000ff8f, 5, 0, 2, { { MD32_R_LC2, -1 }, { MD32_R_C3U, 4 } } },
{ "mv16", 0x0000c880, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_SR, -1 } } },
{ "mv16", 0x0000c888, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_IPC, -1 } } },
{ "mv16", 0x0000c890, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_ISR, -1 } } },
{ "mv16", 0x0000c8a0, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LF, -1 } } },
{ "mv16", 0x0000c8a8, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LS0, -1 } } },
{ "mv16", 0x0000c8b0, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LE0, -1 } } },
{ "mv16", 0x0000c8b8, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LC0, -1 } } },
{ "mv16", 0x0000c8c0, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LS1, -1 } } },
{ "mv16", 0x0000c8c8, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LE1, -1 } } },
{ "mv16", 0x0000c8d0, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LC1, -1 } } },
{ "mv16", 0x0000c8d8, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LS2, -1 } } },
{ "mv16", 0x0000c8e0, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LE2, -1 } } },
{ "mv16", 0x0000c8e8, 0x0000fff8, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_LC2, -1 } } },
{ "osl16", 0x0000d000, 0x0000fc00, 5, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C4U_0906, 6 } } },
{ "rnds16", 0x0000cc00, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 6 } } },
{ "rndu16", 0x0000ce00, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 6 } } },
{ "xtd16", 0x0000c000, 0x0000ffc0, 5, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C8, -1 } } },
{ "xtd16", 0x0000c040, 0x0000ffc0, 5, 0, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C16, -1 } } },
/* Six group-5 16-bit forms absent from the MS15E30 objdump table: packed complex 16-bit multiplies
* rd = f(rs = {hi a, lo b}, rt = {hi c, lo d}): 0xD800 a*c+b*d, 0xDA00 a*c-b*d (Re rs*rt), 0xDC00 a*d+b*c (Im rs*rt),
* 0xDE00 b*c-a*d (sign inferred). The names of 0xDA00/0xDC00 follow the little-endian layout of the data memory
* (the high half of a 32-bit complex word is at +2 and holds the real part). */
{ "rem16", 0x0000ca80, 0x0000ffc0, 5, 0, 2, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 } } },
{ "ctz16", 0x0000cb00, 0x0000ff00, 5, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "cmulcr16", 0x0000d800, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 6 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 0 } } },
{ "cmulr16", 0x0000da00, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 6 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 0 } } },
{ "cmuli16", 0x0000dc00, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 6 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 0 } } },
{ "cmulci16", 0x0000de00, 0x0000fe00, 5, 0, 3, { { MD32_R_C3U, 6 }, { MD32_R_C3U, 3 }, { MD32_R_C3U, 0 } } },
{ "ior16", 0x0000f300, 0x0000ff00, 7, 1, 3, { { MD32_R_C3U, 3 }, { MD32_I_C2U_0706, 6 }, { MD32_R_C3U, 0 } } },
{ "iow16", 0x0000f200, 0x0000ff00, 7, 1, 3, { { MD32_R_C3U, 3 }, { MD32_I_C2U_0706, 6 }, { MD32_R_C3U, 0 } } },
{ "lb16", 0x0000fc00, 0x0000fe00, 7, 3, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C3S_0806, 6 } } },
{ "lbu16", 0x0000fe00, 0x0000fe00, 7, 3, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C3S_0806, 6 } } },
{ "lh16", 0x0000e000, 0x0000fe00, 7, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C4S_1_0806, 6 }, { MD32_R_C3U, 3 } } },
{ "lh16", 0x0000ec00, 0x0000fe00, 7, 3, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C4S_1_0806, 6 } } },
{ "lhu16", 0x0000e200, 0x0000fe00, 7, 1, 3, { { MD32_R_C3U, 0 }, { MD32_I_C4S_1_0806, 6 }, { MD32_R_C3U, 3 } } },
{ "lhu16", 0x0000ee00, 0x0000fe00, 7, 3, 3, { { MD32_R_C3U, 0 }, { MD32_R_C3U, 3 }, { MD32_I_C4S_1_0806, 6 } } },
{ "mv16.l", 0x0000f000, 0x0000ff00, 7, 0, 2, { { MD32_R_C4U, 0 }, { MD32_R_C4U, 4 } } },
{ "sb16", 0x0000f800, 0x0000fc00, 7, 3, 3, { { MD32_R_C4U, 3 }, { MD32_R_C3U, 0 }, { MD32_I_C3S_0907, 7 } } },
{ "sh16", 0x0000e400, 0x0000fc00, 7, 1, 3, { { MD32_R_C4U, 0 }, { MD32_I_C4S_1_0907, 7 }, { MD32_R_C3U, 4 } } },
{ "sh16", 0x0000e800, 0x0000fc00, 7, 3, 3, { { MD32_R_C4U, 3 }, { MD32_R_C3U, 0 }, { MD32_I_C4S_1_0907, 7 } } },
};
static st32 md32_argument_value(unsigned type, ut32 word, int shift) {
ut32 x = shift < 0? word: word >> shift;
switch (type) {
case MD32_I_C0: return 0;
case MD32_I_C1: return 1;
case MD32_I_C10S_1_1709: return md32_sign ((x & 0x1ff) * 2, 10);
case MD32_I_C10U_1809: return x & 0x3ff;
case MD32_I_C10U_2_0700: return (x & 0xff) * 4;
case MD32_I_C12S_1908: return md32_sign (x & 0xfff, 12);
case MD32_I_C12U_1908: return x & 0xfff;
case MD32_I_C15: return 15;
case MD32_I_C16: return 16;
case MD32_I_C16S_1_2208: return md32_sign ((x & 0x7fff) * 2, 16);
case MD32_I_C16S_2308: return md32_sign (x & 0xffff, 16);
case MD32_I_C16SU_2308: return x & 0xffff;
case MD32_I_C16U_1500: return x & 0xffff;
case MD32_I_C16U_2308: return x & 0xffff;
case MD32_I_C2: return 2;
case MD32_I_C21S_2404: return md32_sign (x & 0x1fffff, 21);
case MD32_I_C22S_1_2000: return md32_sign ((x & 0x1fffff) * 2, 22);
case MD32_I_C255: return 255;
case MD32_I_C2U_0706: return x & 0x3;
case MD32_I_C3: return 3;
case MD32_I_C3S_0806: return md32_sign (x & 0x7, 3);
case MD32_I_C3S_0907: return md32_sign (x & 0x7, 3);
case MD32_I_C3U_0806: return x & 0x7;
case MD32_I_C4S_1_0806: return md32_sign ((x & 0x7) * 2, 4);
case MD32_I_C4S_1_0907: return md32_sign ((x & 0x7) * 2, 4);
case MD32_I_C4U_0603: return x & 0xf;
case MD32_I_C4U_0704: return x & 0xf;
case MD32_I_C4U_0906: return x & 0xf;
case MD32_I_C5S_0804: return md32_sign (x & 0x1f, 5);
case MD32_I_C5S_2_0907: return md32_sign ((x & 0x7) * 4, 5);
case MD32_I_C5U_1208: return x & 0x1f;
case MD32_I_C5U_1309: return x & 0x1f;
case MD32_I_C5U_1612: return x & 0x1f;
case MD32_I_C5U_1713: return x & 0x1f;
case MD32_I_C5U_1814: return x & 0x1f;
case MD32_I_C5U_2117: return x & 0x1f;
case MD32_I_C5U_2_0907: return (x & 0x7) * 4;
case MD32_I_C65280: return 65280;
case MD32_I_C65535: return 65535;
case MD32_I_C6U_1_0804: return (x & 0x1f) * 2;
case MD32_I_C7: return 7;
case MD32_I_C7S_1004: return md32_sign (x & 0x7f, 7);
case MD32_I_C8: return 8;
case MD32_I_C8S_1_0600: return md32_sign ((x & 0x7f) * 2, 8);
case MD32_I_C8S_N2_0804: return ((x & 0x1f) * 4) - 128;
case MD32_I_C8U_0700: return x & 0xff;
case MD32_I_C8U_1104: return x & 0xff;
case MD32_I_C9S_2118_0400: return md32_sign ((word & 31) | ((word >> 13) & 0x1e0), 9);
case MD32_I_C9S_N2_0904: return ((x & 0x3f) * 4) - 256;
case MD32_I_C9U_2118_0400: return (word & 31) | ((word >> 13) & 0x1e0);
case MD32_R_14: return 14;
case MD32_R_A0: return 0;
case MD32_R_A0H: return 0;
case MD32_R_A0L: return 0;
case MD32_R_C3U: return (x & 7) + 1;
case MD32_R_C4U: return x & 15;
case MD32_R_DBG: return 0;
case MD32_R_IPC: return 0;
case MD32_R_ISR: return 0;
case MD32_R_LC0: return 0;
case MD32_R_LC1: return 0;
case MD32_R_LC2: return 0;
case MD32_R_LE0: return 0;
case MD32_R_LE1: return 0;
case MD32_R_LE2: return 0;
case MD32_R_LF: return 0;
case MD32_R_LS0: return 0;
case MD32_R_LS1: return 0;
case MD32_R_LS2: return 0;
case MD32_R_SR: return 0;
}
return 0;
}
static const char *md32_argument_register(unsigned type) {
switch (type) {
case MD32_R_14: return "r14";
case MD32_R_A0: return "a0";
case MD32_R_A0H: return "a0h";
case MD32_R_A0L: return "a0l";
case MD32_R_DBG: return "dbg";
case MD32_R_IPC: return "ipc";
case MD32_R_ISR: return "isr";
case MD32_R_LC0: return "lc0";
case MD32_R_LC1: return "lc1";
case MD32_R_LC2: return "lc2";
case MD32_R_LE0: return "le0";
case MD32_R_LE1: return "le1";
case MD32_R_LE2: return "le2";
case MD32_R_LF: return "lf";
case MD32_R_LS0: return "ls0";
case MD32_R_LS1: return "ls1";
case MD32_R_LS2: return "ls2";
case MD32_R_SR: return "sr";
}
return NULL;
}
+49
View File
@@ -0,0 +1,49 @@
# md32 `usip` mode: conditional branches + 16/32-bit `save` / `ret` / `restore`
Small patch (19 lines) against `md32/arch_md32.c` in radare2-extras (commit 5edf816,
"Add support for md32 architecture").
The `usip` CPU mode currently decodes `save`, `call`, `ret` and `restore`. This adds:
1. **Conditional branches** `b<cond>`: `hw1 & 0xF1FF == 0x400C`, tag in `{0xC,0xD,0xE}`,
condition in `hw1[11:9]` (all 8 values occur), target `pc + (hw2 & 0xFFE)`,
fallthrough `pc + 4`.
2. **16-bit `save`**: `hw & 0xFF0F == 0xDF05` (the 16-bit form of the 32-bit `0x5F05`).
3. **32-bit `ret`** `0x5EF9` and **32-bit `restore`** `hw & 0xFF0F == 0x7F05`.
## Evidence
Measured on the MediaTek MT6877 modem DSP ("uSIP" cores), on 5,392 functions /
787,948 instructions whose boundaries are known exactly from the build's `DbgInfo`
(a symbol-derived walk with the length rule `16 bits if byte & 0x80` lands on the
declared function end in **5392/5392** functions).
* Conditional branches: **981/985 (99.59 %)** land exactly on an instruction boundary
inside the same function, and 76 % additionally on a source-line boundary.
* All 8 condition values are present (frequencies 274, 131, 71, 40, 30, 17, 17, 12),
so the field is a full 3 bits. The mapping to eq/ne/lt/... is **not** determined;
the patch numbers them.
* 16-bit `save`: 260 uses; 32-bit `ret`: 121; 32-bit `restore`: 93.
* `save` takes the same `w` field values in both widths, which is how the 16-bit form
was identified (behavioural match, not similarity).
## Test
./test.sh
uses a 12-byte synthetic vector (no vendor code) and should print:
0x00000000 5f051e90 save 0x1e90, 0x0
0x00000004 000e78aa call 0x000008ae
0x00000008 460ce030 b3 0x00000038
0x0000000c def9 ret
## Known gap
ALU / load-store / immediate forms are still `.word` / `.hword` (~95 % of the corpus).
No contiguous opcode field could be found for them: a sweep over every bit window
(6-24 bits) x scales x bases x 32 rotations, using 1,301 function starts as the oracle,
produces no candidate above chance. The vendor toolchain for this core
(DSPTK 9.x "blaze", core Bril9, ISA `vdsp30`, binutils machine `md32sc-elf`) is not
public; the 2017 DSPTK 8.1 / MS15E30 toolchain decodes `ret` and `save` as unknown
opcodes, so it is a different generation.
+37
View File
@@ -0,0 +1,37 @@
diff --git a/md32/arch_md32.c b/md32/arch_md32.c
index b12263c..e5166b0 100644
--- a/md32/arch_md32.c
+++ b/md32/arch_md32.c
@@ -489,6 +489,9 @@ static bool usip_decode(RAnalOp *op, RArchDecodeMask mask) {
r_strbuf_set (&sb, "ret");
} else if ((h & 0xff0f) == 0xff05) {
r_strbuf_setf (&sb, "restore 0x%x", (h >> 4) & 15);
+ } else if ((h & 0xff0f) == 0xdf05) {
+ /* forma de 16 bits de `save` (doc 19 §11.2, 260 usos) */
+ r_strbuf_setf (&sb, "save16 0x%x", (h >> 4) & 15);
} else {
r_strbuf_setf (&sb, ".hword 0x%04x", h);
}
@@ -502,6 +505,22 @@ static bool usip_decode(RAnalOp *op, RArchDecodeMask mask) {
r_strbuf_setf (&sb, "call 0x%08"PFMT64x, op->jump);
} else if ((h & 0xff0f) == 0x5f05) {
r_strbuf_setf (&sb, "save 0x%04x, 0x%x", lo, (h >> 4) & 15);
+ } else if (h == 0x5ef9) {
+ /* ret en forma de 32 bits (doc 19 §11.2, 121 usos) */
+ op->type = R_ANAL_OP_TYPE_RET;
+ op->eob = true;
+ r_strbuf_set (&sb, "ret");
+ } else if ((h & 0xff0f) == 0x7f05) {
+ /* restore en forma de 32 bits (93 usos) */
+ r_strbuf_setf (&sb, "restore 0x%x", (h >> 4) & 15);
+ } else if ((h & 0xf1ff) == 0x400c && (lo & 0xf000) >= 0xc000) {
+ /* b<cond>: 8 condiciones en h[11:9], destino pc+(lo&0xFFE).
+ Validado 981/985 (99,59 %) en frontera de instruccion. */
+ const unsigned cond = (h >> 9) & 7;
+ op->type = R_ANAL_OP_TYPE_CJMP;
+ op->jump = relative_target (op->addr, lo & 0xffe);
+ op->fail = relative_target (op->addr, 4);
+ r_strbuf_setf (&sb, "b%u 0x%08"PFMT64x, cond, op->jump);
} else {
r_strbuf_setf (&sb, ".word 0x%04x%04x", h, lo);
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
# Verifica el parche con un vector sintético (sin código de fabricante).
r2 -q -a md32 -e cfg.bigendian=true -e asm.cpu=usip -c "pd 8 @ 0" test-vector.bin
+579
View File
@@ -0,0 +1,579 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - n0p */
/* scq16_arch.inc.c - cpu=scq16 of the md32 arch plugin: SCQ16 (MD32 family, byte-granular packet variant).
* Included by arch_md32.c; the decoder is scq16_decode.c (tables in scq16_opcodes.h, generated by
* tools/gen_scq16_tables.py from isa/scq16_isa.json).
*
* One scq16_decode_r2 covers one INSTRUCTION:
* a stand-alone unit, a packet {A || B} (two units executed in parallel: both read the pre-packet state) or an 8-byte
* bundle {vec32 || s1 || s2}. Control flow lives in the B slot of a packet (A' is never control) or in the s2 slot of a
* bundle (short beqz/bnez); its targets are relative to the packet start. Delay slots (.d1/.d2) count INSTRUCTIONS
* (packets included): op->delay carries the count and op->fail the fall-through address after them when the bytes at
* hand allow it. ESIL: the high/medium scalar subset; a packet is emitted as A then B with the registers that B reads and
* A writes copied to t0..t3 first (parallel semantics); delayed control flow uses the ESIL delay-slot mechanism
* (`target,SETJT,n,SETD`: the jump is applied after n more instructions, which `aes` honours: a `.d2` transfer takes two
* `aes` steps, the first one runs the transfer and its first slot, the second one the second slot and the jump); `do` sets
* lc0/le0/ls0 (the back edge is not inferred). Vector operations are the custom ESIL op `vop` (op9, A, B, F1, F2, F3
* on the stack; registered by esilcb): the three established operations are modelled on a vector register file
* (scq16_vu.inc.c: v.mem.ld / v.mem.st through the VU pointers vp0-vp7 and the counts va0-va7, v.move.mv as a register copy;
* vr0-vr31 of 128 bytes exposed as four 256-bit quarters each), every other op9 has no effect; the scalar ports of the VU
* are ESIL registers (vu, v, va, vp, vs, vb1_, vb3_, vb5_, udf, each followed by an index).
*/
#include "scq16_decode.c"
#include "scq16_vu.inc.c"
#define SCQ16_MAX_DELAY_LOOKAHEAD 3
static const char *const scq16_tmp[4] = { "t0", "t1", "t2", "t3" };
static bool scq16_cpu(RArchSession *s) {
return s && s->config && s->config->cpu && !strcmp (s->config->cpu, "scq16");
}
static const char *scq16_sym_hex(void *user, uint32_t addr, char *buf, size_t n) {
snprintf (buf, n, "0x%08x", (unsigned)addr);
return buf;
}
static bool scq16_is_ctl_kind(const Scq16Alt *a) {
return a && (a->kind == SCQ16_K_JUMP || a->kind == SCQ16_K_CALL || a->kind == SCQ16_K_BRANCH || a->kind == SCQ16_K_RET || a->kind == SCQ16_K_LOOP);
}
/* the unit that carries the control flow of the instruction (B of a packet, s2 of a bundle, the unit itself) */
static const Scq16Unit *scq16_flow_unit(const Scq16Insn *in) {
int i;
for (i = in->n - 1; i >= 0; i--) {
if (scq16_is_ctl_kind (in->u[i].alt)) {
return &in->u[i];
}
}
return NULL;
}
static bool scq16_mn_is(const char *mn, const char *name) {
const size_t len = strlen (name);
return !strncmp (mn, name, len) && (!mn[len] || mn[len] == '.' || mn[len] == '?');
}
static int scq16_unit_type(const Scq16Unit *u) {
const Scq16Alt *a = u->alt;
if (!a) {
return R_ANAL_OP_TYPE_UNK;
}
const char *mn = a->mn;
switch (a->kind) {
case SCQ16_K_JUMP:
return scq16_mn_is (mn, "jr") ? R_ANAL_OP_TYPE_RJMP : R_ANAL_OP_TYPE_JMP;
case SCQ16_K_CALL:
return scq16_mn_is (mn, "jalr") ? R_ANAL_OP_TYPE_RCALL : R_ANAL_OP_TYPE_CALL;
case SCQ16_K_BRANCH:
return R_ANAL_OP_TYPE_CJMP;
case SCQ16_K_RET:
return R_ANAL_OP_TYPE_RET;
case SCQ16_K_LOOP:
return R_ANAL_OP_TYPE_UNK;
case SCQ16_K_SYS:
if (scq16_mn_is (mn, "nop")) {
return R_ANAL_OP_TYPE_NOP;
}
if (scq16_mn_is (mn, "halt")) {
return R_ANAL_OP_TYPE_TRAP;
}
if (scq16_mn_is (mn, "sys") || scq16_mn_is (mn, "sys41") || scq16_mn_is (mn, "ieon") || scq16_mn_is (mn, "ieoff") || scq16_mn_is (mn, "vwait")) {
return R_ANAL_OP_TYPE_SWI;
}
return R_ANAL_OP_TYPE_UNK;
case SCQ16_K_MEM:
if (scq16_mn_is (mn, "ior") || scq16_mn_is (mn, "iow")) {
return R_ANAL_OP_TYPE_IO;
}
if (mn[0] == 'l') {
return R_ANAL_OP_TYPE_LOAD;
}
if (mn[0] == 's') {
return R_ANAL_OP_TYPE_STORE;
}
return R_ANAL_OP_TYPE_UNK;
case SCQ16_K_STACK:
if (scq16_mn_is (mn, "addsp") || scq16_mn_is (mn, "add.sp")) {
return R_ANAL_OP_TYPE_ADD;
}
return mn[0] == 'l' ? R_ANAL_OP_TYPE_LOAD : R_ANAL_OP_TYPE_STORE;
case SCQ16_K_MV:
return R_ANAL_OP_TYPE_MOV;
case SCQ16_K_VEC:
/* the scalar ports of the VU (vget/vbcast/vset.a/vmem.set/vins/vrd/vwr/udf) move a GPR to or from VU state */
if (scq16_mn_is (mn, "vget") || scq16_mn_is (mn, "vbcast") || scq16_mn_is (mn, "vset") || scq16_mn_is (mn, "vmem") || scq16_mn_is (mn, "vins") || scq16_mn_is (mn, "vrd")
|| scq16_mn_is (mn, "vwr") || scq16_mn_is (mn, "udf") || scq16_mn_is (mn, "vbcast.i")) {
return R_ANAL_OP_TYPE_MOV;
}
return R_ANAL_OP_TYPE_UNK;
default:
break;
}
static const struct { const char *name; int type; } table[] = {
{ "add", R_ANAL_OP_TYPE_ADD }, { "addsp", R_ANAL_OP_TYPE_ADD }, { "sub", R_ANAL_OP_TYPE_SUB }, { "and", R_ANAL_OP_TYPE_AND },
{ "bclr", R_ANAL_OP_TYPE_AND }, { "or", R_ANAL_OP_TYPE_OR }, { "bset", R_ANAL_OP_TYPE_OR }, { "xor", R_ANAL_OP_TYPE_XOR },
{ "btgl", R_ANAL_OP_TYPE_XOR }, { "btst", R_ANAL_OP_TYPE_AND }, { "sll", R_ANAL_OP_TYPE_SHL }, { "srl", R_ANAL_OP_TYPE_SHR },
{ "sra", R_ANAL_OP_TYPE_SAR }, { "mul", R_ANAL_OP_TYPE_MUL }, { "div", R_ANAL_OP_TYPE_DIV }, { "sethi", R_ANAL_OP_TYPE_MOV },
{ "cmov", R_ANAL_OP_TYPE_CMOV }, { "xtd", R_ANAL_OP_TYPE_CAST }, { "fxtu", R_ANAL_OP_TYPE_CAST }, { "fxts", R_ANAL_OP_TYPE_CAST },
{ "max", R_ANAL_OP_TYPE_CMOV }, { "min", R_ANAL_OP_TYPE_CMOV }, { "clz", R_ANAL_OP_TYPE_UNK }, { "cinv", R_ANAL_OP_TYPE_UNK },
{ "sel", R_ANAL_OP_TYPE_CMOV }, { "fup", R_ANAL_OP_TYPE_CAST },
};
size_t i;
for (i = 0; i < R_ARRAY_SIZE (table); i++) {
if (scq16_mn_is (mn, table[i].name)) {
return table[i].type;
}
}
return R_ANAL_OP_TYPE_UNK;
}
/* fall-through address after `delay` more instructions, decoded from the bytes at hand; UT64_MAX when they do not reach */
static ut64 scq16_after_delay(const ut8 *bytes, int len, ut64 addr, int size, int delay) {
int off = size, n;
for (n = 0; n < delay; n++) {
Scq16Insn in;
if (off >= len) {
return UT64_MAX;
}
int r = scq16_decode_insn (bytes + off, len - off, (uint32_t)(addr + off), &in);
if (r <= 0) {
return UT64_MAX;
}
off += r;
}
return (ut32)(addr + off);
}
static void scq16_branch_cond(RAnalOp *op, const char *mn) {
static const struct { const char *name; int cond; bool sign; } table[] = {
{ "beq", R_ANAL_CONDTYPE_EQ, false }, { "bne", R_ANAL_CONDTYPE_NE, false }, { "ble", R_ANAL_CONDTYPE_LE, true },
{ "bleu", R_ANAL_CONDTYPE_LS, false }, { "blt", R_ANAL_CONDTYPE_LT, true }, { "bltu", R_ANAL_CONDTYPE_LO, false },
{ "beqz", R_ANAL_CONDTYPE_EQ, false }, { "beqz8", R_ANAL_CONDTYPE_EQ, false }, { "bnez", R_ANAL_CONDTYPE_NE, false },
};
size_t i;
for (i = 0; i < R_ARRAY_SIZE (table); i++) {
if (scq16_mn_is (mn, table[i].name)) {
op->cond = table[i].cond;
op->sign = table[i].sign;
return;
}
}
}
static const char *scq16_unit_reg(const Scq16Unit *u, const char *name) {
int i = scq16_operand_index (u, name);
if (i < 0) {
return NULL;
}
switch (u->alt->ops[i].kind) {
case SCQ16_O_R5: case SCQ16_O_R4: case SCQ16_O_R4P: case SCQ16_O_R3P: return scq16_regs[u->vals[i] & 31];
case SCQ16_O_CSR: return scq16_csrs[u->vals[i] & 31];
case SCQ16_O_ACC: return scq16_accs[u->vals[i] & 31];
default: return NULL;
}
}
static void scq16_analyze(RAnalOp *op, const Scq16Insn *in, ut64 ret) {
const Scq16Unit *fu = scq16_flow_unit (in);
const Scq16Unit *u0 = &in->u[0];
op->delay = in->delay;
if (fu) {
const Scq16Alt *a = fu->alt;
op->type = scq16_unit_type (fu);
if (fu->has_target) {
op->jump = fu->target;
}
if (a->kind == SCQ16_K_BRANCH) {
scq16_branch_cond (op, a->mn);
op->fail = ret;
} else if (a->kind == SCQ16_K_CALL) {
op->fail = ret;
const char *rn = scq16_unit_reg (fu, "rn");
if (rn) {
op->reg = rn;
}
} else if (a->kind == SCQ16_K_JUMP) {
const char *rn = scq16_unit_reg (fu, "rn");
if (rn) {
op->reg = rn;
}
op->eob = true;
} else if (a->kind == SCQ16_K_RET) {
op->eob = true;
op->reg = scq16_mn_is (a->mn, "rti") ? "ipc" : "lr";
} else if (a->kind == SCQ16_K_LOOP) {
/* do: the loop end (inclusive) is a reference, the body starts after the delay slots; count in a register or immediate */
op->ptr = fu->target;
op->direction = R_ANAL_OP_DIR_REF;
const char *cnt = scq16_unit_reg (fu, "cnt");
if (cnt) {
op->reg = cnt;
} else {
int i = scq16_operand_index (fu, "cnt");
op->val = i >= 0 ? (ut64)(ut32)fu->vals[i] : 0;
}
}
if (in->n > 1 && !scq16_is_ctl_kind (u0->alt) && in->kind == SCQ16_I_PACKET) {
/* the A slot executes alongside: keep the flow type */
}
return;
}
if (in->kind == SCQ16_I_PACKET || in->kind == SCQ16_I_BUNDLE) {
/* two (or three) operations cannot be summarized by one scalar type */
op->type = R_ANAL_OP_TYPE_UNK;
return;
}
op->type = scq16_unit_type (u0);
if (!u0->alt) {
return;
}
const Scq16Alt *a = u0->alt;
if (op->type == R_ANAL_OP_TYPE_LOAD || op->type == R_ANAL_OP_TYPE_STORE || op->type == R_ANAL_OP_TYPE_IO) {
const char *mn = a->mn;
op->refptr = (scq16_mn_is (mn, "lw") || scq16_mn_is (mn, "sw") || scq16_mn_is (mn, "lw.post") || scq16_mn_is (mn, "sw.post") || scq16_mn_is (mn, "lw.sp") || scq16_mn_is (mn, "sw.sp")
|| scq16_mn_is (mn, "lw.lr") || scq16_mn_is (mn, "sw.lr") || scq16_mn_is (mn, "sw.isr") || scq16_mn_is (mn, "sw.csr") || scq16_mn_is (mn, "ior") || scq16_mn_is (mn, "iow")) ? 4 :
(scq16_mn_is (mn, "lh") || scq16_mn_is (mn, "lhu") || scq16_mn_is (mn, "sh") || scq16_mn_is (mn, "sh.post") || scq16_mn_is (mn, "lhu.post")) ? 2 : 1;
op->ptrsize = op->refptr;
op->sign = scq16_mn_is (mn, "lh") || scq16_mn_is (mn, "lb");
op->direction = op->type == R_ANAL_OP_TYPE_STORE ? R_ANAL_OP_DIR_WRITE : R_ANAL_OP_DIR_READ;
const char *rb = scq16_unit_reg (u0, "rb");
if (!rb) {
rb = scq16_unit_reg (u0, "rs");
}
if (a->kind == SCQ16_K_STACK) {
rb = "sp";
}
op->ireg = rb;
int oi = scq16_operand_index (u0, "off");
if (oi >= 0 && a->ops[oi].kind != SCQ16_O_R5) {
op->disp = (st64)u0->vals[oi];
}
if (rb && !strcmp (rb, "sp")) {
op->stackop = op->type == R_ANAL_OP_TYPE_LOAD ? R_ANAL_STACK_GET : R_ANAL_STACK_SET;
}
} else if (scq16_mn_is (a->mn, "addsp")) {
int i = scq16_operand_index (u0, "v");
if (i < 0) {
i = scq16_operand_index (u0, "imm");
}
if (i >= 0) {
op->stackop = R_ANAL_STACK_INC;
op->stackptr = -(st64)u0->vals[i];
}
} else if (op->type == R_ANAL_OP_TYPE_MOV || scq16_mn_is (a->mn, "sethi")) {
int i = scq16_operand_index (u0, "imm");
if (i >= 0 && a->ops[i].kind == SCQ16_O_IMM) {
op->val = (ut64)(ut32)u0->vals[i];
}
}
}
/* ESIL of the whole instruction: units in order, B/s2 with the registers written earlier in the packet read from temporaries */
static void scq16_insn_esil(RAnalOp *op, const Scq16Insn *in, ut64 ret) {
char part[3][512];
const char *written[3][16];
int nw[3] = { 0, 0, 0 };
int i, k;
const char *const remap[4][2] = { { "", "" }, { "", "" }, { "", "" }, { "", "" } };
const char *rm[4][2];
int nrm = 0;
(void)remap;
for (i = 0; i < in->n; i++) {
const Scq16Unit *u = &in->u[i];
/* the override hook (experiments) counts as the unit's ESIL, also inside a packet; a unit with the empty ESIL "" (no
* architectural effect: nop, halt, ieon/ieoff, loopend, vwait?, sys41) never blocks the ESIL of its instruction */
const char *ue = u->alt ? (scq16_esil_override ? scq16_esil_override (u->alt) : NULL) : NULL;
if (u->alt && !ue) {
ue = u->alt->esil;
}
if (!u->alt || !ue) {
/* a unit without any template leaves the whole instruction without ESIL (an OPEN reading carries the marker "REF,ALT,open"
* from the header, so the rest of its packet keeps its ESIL) */
if (!(u->alt && ue && !*ue)) {
return;
}
}
}
for (i = 0; i < in->n; i++) {
const Scq16Unit *u = &in->u[i];
/* reads of this unit that an earlier unit of the packet writes: parallel semantics -> copy to a temporary */
const char *reads[16];
int nr = scq16_unit_esil_regs (u, (uint32_t)ret, false, reads, 16);
int r, j;
nrm = 0;
for (r = 0; r < nr; r++) {
for (j = 0; j < i; j++) {
for (k = 0; k < nw[j]; k++) {
if (!strcmp (written[j][k], reads[r]) && nrm < 4) {
rm[nrm][0] = reads[r];
rm[nrm][1] = scq16_tmp[nrm];
nrm++;
}
}
}
}
if (scq16_unit_esil (u, part[i], sizeof (part[i]), (uint32_t)ret, nrm ? (const char *const (*)[2])rm : NULL, nrm) < 0) {
return;
}
nw[i] = scq16_unit_esil_regs (u, (uint32_t)ret, true, written[i], 16);
if (nrm) {
/* prefix: save the pre-packet values before the earlier unit writes them */
char pre[128] = "";
for (r = 0; r < nrm; r++) {
char t[32];
snprintf (t, sizeof (t), "%s%s,%s,=", r ? "," : "", rm[r][0], rm[r][1]);
r_str_ncpy (pre + strlen (pre), t, sizeof (pre) - strlen (pre));
}
/* the saves go before everything: rebuild */
char whole[1024];
snprintf (whole, sizeof (whole), "%s", pre);
for (j = 0; j < i; j++) {
if (part[j][0]) {
r_str_ncpy (whole + strlen (whole), ",", sizeof (whole) - strlen (whole));
r_str_ncpy (whole + strlen (whole), part[j], sizeof (whole) - strlen (whole));
}
}
r_strbuf_set (&op->esil, whole);
if (part[i][0]) {
r_strbuf_appendf (&op->esil, ",%s", part[i]);
}
continue;
}
if (i == 0) {
r_strbuf_set (&op->esil, part[0]);
} else if (part[i][0]) {
if (r_strbuf_length (&op->esil)) {
r_strbuf_appendf (&op->esil, ",%s", part[i]);
} else {
r_strbuf_set (&op->esil, part[i]);
}
}
}
}
/* the reading class of a unit for opex: established (an operation name), provisional (a name with ?) or placeholder (a family
* placeholder, or a vector op9 with a class but no name) */
static const char *scq16_reading_of(const Scq16Unit *u, bool isvec) {
if (isvec) {
const char *nm = scq16_vname[u->op9];
return !nm ? "placeholder" : scq16_vname_firm (nm) ? "established" : "provisional";
}
if (u->alt->flags & SCQ16_FL_NAMED) {
return "established";
}
return (u->alt->flags & SCQ16_FL_PROV) ? "provisional" : "placeholder";
}
static void scq16_fill_opex(RAnalOp *op, const Scq16Insn *in) {
PJ *pj = pj_new ();
int i, j;
if (!pj) {
return;
}
pj_o (pj);
pj_ks (pj, "encoding", "scq16");
pj_ks (pj, "shape", in->kind == SCQ16_I_PACKET ? "packet" : in->kind == SCQ16_I_BUNDLE ? "bundle" : "unit");
if (in->kind == SCQ16_I_PACKET) {
pj_kb (pj, "parallel", true);
}
pj_ki (pj, "delay", in->delay);
pj_ka (pj, "units");
for (i = 0; i < in->n; i++) {
const Scq16Unit *u = &in->u[i];
char buf[64];
static const char *const slots[] = { "unit", "A", "B", "orphan", "s1", "s2" };
pj_o (pj);
pj_ks (pj, "slot", slots[u->slot]);
pj_kn (pj, "addr", u->pc);
pj_ki (pj, "size", u->L);
pj_ks (pj, "mnemonic", scq16_unit_mnemonic (u, buf, sizeof (buf)));
if (u->slot == SCQ16_S_A) {
char hx[32] = "";
for (j = 0; j < u->L; j++) {
snprintf (hx + strlen (hx), sizeof (hx) - strlen (hx), "%s%02x", j ? " " : "", u->b[j]);
}
pj_ks (pj, "transformed", hx);
}
if (u->alt) {
pj_ks (pj, "form", u->alt->form_name);
pj_ki (pj, "form_index", u->alt->ref);
pj_ks (pj, "kind", scq16_kind_names[u->alt->kind]);
pj_ks (pj, "confidence", scq16_conf_names[u->alt->conf]);
const bool isvec = u->alt == &scq16_alts[SCQ16_VEC_ALT] || u->alt == &scq16_alts[SCQ16_BUNDLE_ALT];
/* a vector name without ? (v.move.mv, v.mem.ld, v.mem.st) is an established operation; one with ? is provisional */
pj_kb (pj, "named", isvec ? scq16_vname_firm (scq16_vname[u->op9]) : (u->alt->flags & SCQ16_FL_NAMED) != 0);
if ((!isvec && (u->alt->flags & SCQ16_FL_PROV)) || (isvec && scq16_vname[u->op9] && !scq16_vname_firm (scq16_vname[u->op9]))) {
pj_kb (pj, "provisional", true);
}
/* the uniform marker of an OPEN reading (SCQ16_FL_OPEN: a family placeholder or a provisional name; a vector op9 without an
* established name) and the reading class; `hypothetical` when the ESIL is the reading of a hypothesis (SCQ16_FL_HYP) */
pj_kb (pj, "open", isvec ? !scq16_vname_firm (scq16_vname[u->op9]) : (u->alt->flags & SCQ16_FL_OPEN) != 0);
pj_ks (pj, "reading", scq16_reading_of (u, isvec));
if (!isvec && (u->alt->flags & SCQ16_FL_HYP)) {
pj_kb (pj, "hypothetical", true);
}
if (isvec) {
pj_kn (pj, "op9", u->op9);
pj_ks (pj, "vclass", scq16_vclass_names[scq16_vclass[u->op9]]);
if (scq16_vname[u->op9]) {
pj_ks (pj, "vname", scq16_vname[u->op9]);
}
}
pj_ka (pj, "operands");
for (j = 0; j < u->alt->nops; j++) {
const Scq16Operand *o = &u->alt->ops[j];
pj_o (pj);
pj_ks (pj, "name", o->name);
switch (o->kind) {
case SCQ16_O_R5: case SCQ16_O_R4: case SCQ16_O_R4P: case SCQ16_O_R3P:
pj_ks (pj, "type", "reg"); pj_ks (pj, "value", scq16_regs[u->vals[j] & 31]); break;
case SCQ16_O_CSR:
pj_ks (pj, "type", "csr"); pj_ks (pj, "value", scq16_csrs[u->vals[j] & 31]); break;
case SCQ16_O_ACC:
pj_ks (pj, "type", "acc"); pj_ks (pj, "value", scq16_accs[u->vals[j] & 31]); break;
case SCQ16_O_DISP:
pj_ks (pj, "type", "addr"); pj_kn (pj, "value", (ut32)u->vals[j]); break;
default:
pj_ks (pj, "type", "imm"); pj_kN (pj, "value", u->vals[j]); break;
}
pj_end (pj);
}
pj_end (pj);
}
pj_end (pj);
}
pj_end (pj);
pj_end (pj);
r_strbuf_set (&op->opex, pj_string (pj));
pj_free (pj);
}
static bool scq16_decode_r2(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
const int available = op->size;
Scq16Insn in;
op->type = R_ANAL_OP_TYPE_ILL;
op->size = 2;
op->jump = op->fail = op->ptr = op->val = UT64_MAX;
op->delay = 0;
op->eob = false;
if (available < 1 || !op->bytes) {
return false;
}
int r = scq16_decode_insn (op->bytes, available, (ut32)op->addr, &in);
if (r <= 0) {
if (r == 0 && in.n == 1 && in.u[0].last_head && in.u[0].alt) {
/* a head whose B slot is beyond the bytes at hand: report the head alone (end of a function / of the buffer) */
in.size = in.u[0].L;
r = in.size;
} else {
if (mask & R_ARCH_OP_MASK_DISASM) {
op->mnemonic = strdup (r == 0 ? "truncated" : "invalid");
}
return false;
}
}
op->size = r;
op->id = in.u[0].alt ? (int)(in.u[0].alt - scq16_alts) + 1 : 0;
ut64 ret = scq16_after_delay (op->bytes, available, op->addr, r, in.delay);
if (ret == UT64_MAX) {
ret = op->addr + r; /* approximation when the delay slots are not visible */
}
scq16_analyze (op, &in, ret);
if (mask & R_ARCH_OP_MASK_DISASM) {
char txt[512];
scq16_insn_text (&in, txt, sizeof (txt), scq16_sym_hex, NULL);
op->mnemonic = strdup (txt);
}
if (mask & R_ARCH_OP_MASK_OPEX) {
scq16_fill_opex (op, &in);
}
if (mask & R_ARCH_OP_MASK_ESIL) {
scq16_insn_esil (op, &in, ret);
}
return true;
}
static int scq16_info(RArchSession *s, ut32 query) {
switch (query) {
case R_ARCH_INFO_MINOP_SIZE:
case R_ARCH_INFO_INVOP_SIZE:
case R_ARCH_INFO_CODE_ALIGN:
case R_ARCH_INFO_FUNC_ALIGN:
case R_ARCH_INFO_DATA_ALIGN:
return 1;
case R_ARCH_INFO_MAXOP_SIZE:
/* a packet reaches 6 + 6 bytes; the delay-slot fall-through needs up to three more instructions */
return 12;
}
return 0;
}
static char *scq16_regprofile(RArchSession *s) {
/* ABI = public MD32: args r3-r6, return r1, sp/lr are the special registers csr14/csr15 (not r14/r15); t0-t3 are the
* temporaries of the parallel packet semantics; the VU scalar ports follow (vu0-31 vget lanes, v0-31 scalar shadow of
* vbcast, va0-7, vp0-7, vs0-15, vb1_/vb3_/vb5_ 0-31, udf0-31) */
RStrBuf *sb = r_strbuf_new (
"=PC pc\n=SP sp\n=LR lr\n=A0 r3\n=A1 r4\n=A2 r5\n=A3 r6\n=R0 r1\n=R1 r2\n"
"gpr r0 .32 0 0\ngpr r1 .32 4 0\ngpr r2 .32 8 0\ngpr r3 .32 12 0\n"
"gpr r4 .32 16 0\ngpr r5 .32 20 0\ngpr r6 .32 24 0\ngpr r7 .32 28 0\n"
"gpr r8 .32 32 0\ngpr r9 .32 36 0\ngpr r10 .32 40 0\ngpr r11 .32 44 0\n"
"gpr r12 .32 48 0\ngpr r13 .32 52 0\ngpr r14 .32 56 0\ngpr r15 .32 60 0\n"
"gpr r16 .32 64 0\ngpr r17 .32 68 0\ngpr r18 .32 72 0\ngpr r19 .32 76 0\n"
"gpr r20 .32 80 0\ngpr r21 .32 84 0\ngpr r22 .32 88 0\ngpr r23 .32 92 0\n"
"gpr r24 .32 96 0\ngpr r25 .32 100 0\ngpr r26 .32 104 0\ngpr r27 .32 108 0\n"
"gpr r28 .32 112 0\ngpr r29 .32 116 0\ngpr r30 .32 120 0\ngpr r31 .32 124 0\n"
"gpr pc .32 128 0\n"
"gpr sr .32 132 0\ngpr ipc .32 136 0\ngpr isr .32 140 0\ngpr csr3 .32 144 0\n"
"gpr lf .32 148 0\ngpr ls0 .32 152 0\ngpr le0 .32 156 0\ngpr lc0 .32 160 0\n"
"gpr ls1 .32 164 0\ngpr le1 .32 168 0\ngpr lc1 .32 172 0\n"
"gpr ls2 .32 176 0\ngpr le2 .32 180 0\ngpr lc2 .32 184 0\n"
"gpr sp .32 188 0\ngpr lr .32 192 0\n"
"gpr csr16 .32 196 0\ngpr csr17 .32 200 0\ngpr csr18 .32 204 0\ngpr csr19 .32 208 0\n"
"gpr csr20 .32 212 0\ngpr csr21 .32 216 0\ngpr csr22 .32 220 0\ngpr csr23 .32 224 0\n"
"gpr csr24 .32 228 0\ngpr csr25 .32 232 0\ngpr csr26 .32 236 0\ngpr csr27 .32 240 0\n"
"gpr csr28 .32 244 0\ngpr csr29 .32 248 0\ngpr csr30 .32 252 0\ngpr csr31 .32 256 0\n"
"gpr a0 .64 260 0\ngpr a0l .32 260 0\ngpr a0h .32 264 0\n"
"gpr t0 .32 268 0\ngpr t1 .32 272 0\ngpr t2 .32 276 0\ngpr t3 .32 280 0\n");
int i, off = 284;
const int n = scq16_vu_nregs ();
for (i = 0; i < n; i++, off += 4) {
r_strbuf_appendf (sb, "gpr %s .32 %d 0\n", scq16_vu_name (i), off);
}
off = scq16_vu_vr_profile (sb, off); /* the vector register file vr0-vr31 (128 bytes each, four 256-bit quarters) */
return r_strbuf_drain (sb);
}
/* custom ESIL op `vop`: a vector operation (op9, A, B, F1, F2, F3 pushed in that order, op9 on top). The three established
* operations are modelled (scq16_vu.inc.c: v.mem.ld / v.mem.st on the vector register file through the VU pointers, v.move.mv as a
* register copy); every other op9 pops its operands and does nothing. */
static bool scq16_esil_vop(REsil *esil) {
ut64 v[6];
if (!scq16_vu_pop6 (esil, v)) {
return false;
}
scq16_vu_vop_public (esil, v);
return true;
}
/* custom ESIL op `open`: the uniform marker of an OPEN reading without semantics (a family placeholder or a provisional name
* whose operation is not established: the table flag SCQ16_FL_OPEN; the header carries "REF,ALT,open" where the ISA table has no
* template). It pops its two operands and has no effect; a strict emulator may stop on it. */
static bool scq16_esil_open(REsil *esil) {
return scq16_vu_pop_n (esil, 2, NULL);
}
static bool scq16_esilcb(RArchSession *s, REsil *esil, RArchEsilAction action) {
if (action == R_ARCH_ESIL_ACTION_INIT && esil) {
bool ok = r_esil_set_op (esil, "vop", scq16_esil_vop, 0, 6, R_ESIL_OP_TYPE_CUSTOM, "vector operation (op9, A, B, F1, F2, F3): v.mem.ld / v.mem.st / v.move.mv modelled on vr0-vr31, the rest no-op");
ok &= r_esil_set_op (esil, "open", scq16_esil_open, 0, 2, R_ESIL_OP_TYPE_CUSTOM, "open reading marker (ref, alt): a form without established semantics, no effect");
scq16_vu_esil_init (esil); /* a write of the scalar port v{k} (vbcast / vbcast.i) is broadcast to the 64 lanes of vr{k} */
return ok;
}
if (action == R_ARCH_ESIL_ACTION_FINI && esil) {
scq16_vu_esil_fini (esil);
}
return true;
}
+782
View File
@@ -0,0 +1,782 @@
/* scq16_decode.c - decoder of the SCQ16: the length and packet rule by bit leaves + the forms of scq16_opcodes.h.
* See scq16_decode.h. C99 without dependencies (included by scq16_arch.inc.c). */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "scq16_decode.h"
/* ------------------------------------------------------------------------------------------------ leaves --------- */
static int scq16_leaf_lookup(const Scq16Leaf *leaves, const uint16_t *start, uint8_t b0, uint8_t b1) {
int best_depth = -1, best = 0, i;
for (i = start[b0]; i < start[b0 + 1]; i++) {
const Scq16Leaf *l = &leaves[i];
if (l->depth == 0 || (b1 >> (8 - l->depth)) == l->prefix) {
if (l->depth > best_depth) {
best_depth = l->depth;
best = l->label;
}
}
}
return best_depth < 0 ? 0 : best;
}
int scq16_uleaf(uint8_t b0, uint8_t b1) {
return scq16_leaf_lookup (scq16_uleaves, scq16_uleaf_start, b0 & 0x7f, b1);
}
int scq16_hleaf(uint8_t a0, uint8_t a1) {
if (a0 >= 0x80) {
return 0;
}
return scq16_leaf_lookup (scq16_hleaves, scq16_hleaf_start, a0, a1);
}
/* ------------------------------------------------------------------------------------------------ unit ----------- */
static uint64_t scq16_be_int(const uint8_t *b, int L) {
uint64_t w = 0;
int i;
for (i = 0; i < L; i++) {
w = (w << 8) | b[i];
}
return w;
}
static int32_t scq16_operand_value(const Scq16Operand *op, uint64_t w) {
uint32_t v = 0;
int width = 0, i;
if (op->kind == SCQ16_O_CONST) {
return op->cval;
}
for (i = 0; i < op->npieces; i++) {
v = (v << op->piece[i].w) | (uint32_t)((w >> op->piece[i].lo) & ((1ULL << op->piece[i].w) - 1));
width += op->piece[i].w;
}
int64_t s = v;
if (op->is_signed && width < 32 && (v >> (width - 1))) {
s = (int64_t)v - (1LL << width);
} else if (op->is_signed && width == 32) {
s = (int32_t)v;
}
s = s * op->scale + op->bias;
if (op->kind == SCQ16_O_R4P || op->kind == SCQ16_O_R3P) {
s += 1;
}
return (int32_t)s;
}
static void scq16_decode_unit_alt(Scq16Unit *u, const uint8_t *b, int L, uint32_t pc, uint32_t base, uint8_t slot, const Scq16Alt *force) {
const uint16_t *idx = NULL;
int n = 0, i;
memset (u, 0, sizeof (*u));
u->pc = pc; u->L = L; u->slot = slot; u->base = base;
memcpy (u->b, b, L);
u->w = scq16_be_int (b, L);
if (force) {
if ((u->w & force->mask) == force->value) {
u->alt = force;
}
goto operands;
}
switch (L) {
case 2: idx = scq16_alts_L2; n = SCQ16_NALTS_L2; break;
case 3: idx = scq16_alts_L3; n = SCQ16_NALTS_L3; break;
case 4: idx = scq16_alts_L4; n = SCQ16_NALTS_L4; break;
case 5: idx = scq16_alts_L5; n = SCQ16_NALTS_L5; break;
case 6: idx = scq16_alts_L6; n = SCQ16_NALTS_L6; break;
case 8: idx = scq16_alts_L8; n = SCQ16_NALTS_L8; break;
default: return;
}
for (i = 0; i < n; i++) {
const Scq16Alt *a = &scq16_alts[idx[i]];
if ((u->w & a->mask) == a->value) {
u->alt = a;
break;
}
}
operands:
if (!u->alt) {
return;
}
for (i = 0; i < u->alt->nops && i < SCQ16_MAX_OPS; i++) {
const Scq16Operand *op = &u->alt->ops[i];
int32_t v = scq16_operand_value (op, u->w);
if (op->kind == SCQ16_O_DISP) {
v = (int32_t)(base + (uint32_t)v);
u->has_target = true;
u->target = (uint32_t)v;
}
u->vals[i] = v;
}
u->nvals = u->alt->nops;
if (u->alt->kind == SCQ16_K_VEC && (L == 4 || L == 8) && (u->alt->form_name[0] == 'v' && (u->alt == &scq16_alts[SCQ16_VEC_ALT] || u->alt == &scq16_alts[SCQ16_BUNDLE_ALT]))) {
u->vec32 = (L == 8) ? (uint32_t)((u->w >> 30) & 0xffffffffULL) : (uint32_t)u->w;
u->op9 = u->vec32 & 0x1ff;
}
}
void scq16_decode_unit(Scq16Unit *u, const uint8_t *b, int L, uint32_t pc, uint32_t base, uint8_t slot) {
scq16_decode_unit_alt (u, b, L, pc, base, slot, NULL);
}
static void scq16_shift_bytes(uint8_t *dst, const uint8_t *src, int L, int nb) {
int i;
for (i = 0; i < L; i++) {
dst[i] = (uint8_t)((src[i] << 1) | (i + 1 < L ? (src[i + 1] >> 7) : nb));
}
}
static int scq16_need(Scq16Insn *out, int n) {
out->size = 0;
out->need = n;
return 0;
}
/* j.d1 56 0x-3x: with a function table at hand the length is decided by context (4 bytes only when the 22-bit target falls
* inside a known function); the plugin has no function table and decides BY TARGET: the 2-byte reading has no form and the 4-byte one is j.d1 with
* target base + simm22, taken when that target is a plausible code address (no wrap below 0 and within SCQ16_JD1_REACH). The
* leaves 56 03 / 56 3f of the length table are subsumed. */
#ifndef SCQ16_JD1_REACH
#define SCQ16_JD1_REACH 0x400000u
#endif
static int scq16_jd1_len(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, int avail, uint32_t base, int leaf) {
if (avail < 4 || b0 != 0x56 || (b1 >> 6) != 0) {
return leaf;
}
uint32_t w = ((uint32_t)b1 << 16) | ((uint32_t)b2 << 8) | b3;
int32_t d = (int32_t)(w & 0x3fffff);
if (d & 0x200000) {
d -= 0x400000;
}
uint32_t t = base + (uint32_t)d;
return t < SCQ16_JD1_REACH ? 4 : leaf;
}
static int scq16_decode_second(const uint8_t *bytes, int len, int off, uint32_t addr, uint8_t nb, Scq16Insn *out) {
/* B slot after a head of `off` bytes */
uint8_t bop = bytes[off] & 0x7f, bv = bytes[off] >> 7;
int LB;
uint8_t bm[8];
if (bv && bop >= 0x40 && bop <= 0x5f) {
LB = 4;
} else {
LB = scq16_uleaf (bop, off + 1 < len ? bytes[off + 1] : 0);
}
if (LB <= 0) {
return -1;
}
LB = scq16_jd1_len (bop, off + 1 < len ? bytes[off + 1] : 0, off + 2 < len ? bytes[off + 2] : 0, off + 3 < len ? bytes[off + 3] : 0, len - off, addr, LB); /* j.d1 56 by target (base = packet start) */
if (off + LB > len) {
return scq16_need (out, off + LB);
}
memcpy (bm, bytes + off, LB);
bm[0] = bop;
scq16_decode_unit (&out->u[1], bm, LB, addr + off, addr, SCQ16_S_B);
memcpy (out->u[1].raw, bytes + off, LB);
out->u[1].nb = nb;
out->n = 2;
out->kind = SCQ16_I_PACKET;
out->size = off + LB;
return out->size;
}
int scq16_decode_insn(const uint8_t *bytes, int len, uint32_t addr, Scq16Insn *out) {
memset (out, 0, sizeof (*out));
out->addr = addr;
if (len < 1) {
return scq16_need (out, 2);
}
const uint8_t b0 = bytes[0];
const uint8_t b1 = len > 1 ? bytes[1] : 0;
uint8_t bm[8];
int L, i;
if (b0 < 0x80 || b0 >= 0xf8) {
/* ---- stand-alone (00-7f) / masked orphan (f8-ff) */
L = scq16_uleaf (b0 & 0x7f, b1);
if (L <= 0) {
return -1;
}
L = scq16_jd1_len (b0, b1, len > 2 ? bytes[2] : 0, len > 3 ? bytes[3] : 0, len, addr, L); /* j.d1 56 by target */
if (L > len) {
return scq16_need (out, L);
}
memcpy (bm, bytes, L);
bm[0] = b0 & 0x7f;
scq16_decode_unit (&out->u[0], bm, L, addr, addr, b0 >= 0xf8 ? SCQ16_S_ORPHAN : SCQ16_S_UNIT);
memcpy (out->u[0].raw, bytes, L);
out->u[0].masked = b0 >= 0xf8;
out->n = 1; out->kind = SCQ16_I_UNIT; out->size = L;
} else if (b0 >= 0xc0 && b0 <= 0xdf) {
/* ---- stand-alone 4-byte vector operation */
if (len < 4) {
return scq16_need (out, 4);
}
scq16_decode_unit_alt (&out->u[0], bytes, 4, addr, addr, SCQ16_S_UNIT, &scq16_alts[SCQ16_VEC_ALT]);
memcpy (out->u[0].raw, bytes, 4);
out->n = 1; out->kind = SCQ16_I_UNIT; out->size = 4;
} else if (b0 >= 0xf0 && b0 <= 0xf7) {
/* ---- 8-byte bundle */
if (len < 8) {
return scq16_need (out, 8);
}
scq16_decode_unit (&out->u[0], bytes, 8, addr, addr, SCQ16_S_UNIT);
memcpy (out->u[0].raw, bytes, 8);
uint64_t w = out->u[0].w;
uint32_t s1 = (uint32_t)((w >> 15) & 0x7fff), s2 = (uint32_t)(w & 0x7fff);
bm[0] = (s1 >> 8) & 0x7f; bm[1] = s1 & 0xff;
scq16_decode_unit (&out->u[1], bm, 2, addr, addr, SCQ16_S_S1);
memcpy (out->u[1].raw, bm, 2);
bm[0] = (s2 >> 8) & 0x7f; bm[1] = s2 & 0xff;
scq16_decode_unit (&out->u[2], bm, 2, addr, addr, SCQ16_S_S2);
memcpy (out->u[2].raw, bm, 2);
out->n = 3; out->kind = SCQ16_I_BUNDLE; out->size = 8;
} else {
/* ---- 80-bf scalar head / orphan, e0-ef vector head */
int LA;
if (b0 >= 0xe0) {
LA = 4;
} else {
const uint8_t b2 = len > 2 ? bytes[2] : 0;
const uint8_t a0 = (uint8_t)((b0 << 1) | (b1 >> 7)), a1 = (uint8_t)((b1 << 1) | (b2 >> 7));
int lab = scq16_hleaf (a0, a1);
if (lab == 0) {
return -1;
}
if (lab < 0) {
/* masked orphan: A' of control/system */
L = -lab;
if (L > len) {
return scq16_need (out, L);
}
memcpy (bm, bytes, L);
bm[0] = b0 & 0x7f;
scq16_decode_unit (&out->u[0], bm, L, addr, addr, SCQ16_S_ORPHAN);
memcpy (out->u[0].raw, bytes, L);
out->u[0].masked = true;
out->n = 1; out->kind = SCQ16_I_UNIT; out->size = L;
goto done;
}
LA = lab;
}
if (LA > len) {
return scq16_need (out, LA);
}
const uint8_t nb = LA < len ? (bytes[LA] >> 7) : 0;
scq16_shift_bytes (bm, bytes, LA, nb);
scq16_decode_unit_alt (&out->u[0], bm, LA, addr, addr, SCQ16_S_A, b0 >= 0xe0 ? &scq16_alts[SCQ16_VEC_ALT] : NULL);
memcpy (out->u[0].raw, bytes, LA);
out->u[0].nb = nb;
out->n = 1; out->kind = SCQ16_I_UNIT; out->size = LA;
if (LA >= len) {
/* head without a visible B slot (the buffer ends): reported alone, with the suffix ! */
out->u[0].last_head = true;
return scq16_need (out, LA + 2);
}
if (scq16_decode_second (bytes, len, LA, addr, nb, out) <= 0) {
return out->size;
}
}
done:
for (i = 0; i < out->n; i++) {
const Scq16Unit *u = &out->u[i];
if (u->alt && u->alt->slots > out->delay && (u->alt->kind == SCQ16_K_JUMP || u->alt->kind == SCQ16_K_CALL || u->alt->kind == SCQ16_K_BRANCH || u->alt->kind == SCQ16_K_RET || u->alt->kind == SCQ16_K_LOOP)) {
out->delay = u->alt->slots;
}
}
return out->size;
}
/* ------------------------------------------------------------------------------------------------ text ----------- */
int scq16_operand_index(const Scq16Unit *u, const char *name) {
int i;
if (!u->alt) {
return -1;
}
for (i = 0; i < u->alt->nops; i++) {
if (!strcmp (u->alt->ops[i].name, name)) {
return i;
}
}
return -1;
}
static int scq16_sbcat(char *buf, size_t n, size_t *pos, const char *s) {
size_t l = strlen (s);
if (*pos + l + 1 > n) {
l = n > *pos + 1 ? n - *pos - 1 : 0;
}
memcpy (buf + *pos, s, l);
*pos += l;
buf[*pos] = 0;
return (int)l;
}
static void scq16_fmt_hex(char *o, size_t n, int64_t v, const char *prefix) {
if (v < 0) {
snprintf (o, n, "%s-0x%x", prefix, (unsigned)(-(int64_t)v));
} else {
snprintf (o, n, "%s0x%x", prefix, (unsigned)v);
}
}
static void scq16_operand_text(const Scq16Unit *u, int i, char *o, size_t n, Scq16Symf sym, void *user) {
const Scq16Operand *op = &u->alt->ops[i];
int32_t v = u->vals[i];
/* unsigned immediates print as uint32 (sethi #0xfff00000) */
int64_t hv = (op->kind == SCQ16_O_IMM && !op->is_signed && op->bias >= 0) ? (int64_t)(uint32_t)v : (int64_t)v;
switch (op->kind) {
case SCQ16_O_R5: case SCQ16_O_R4: case SCQ16_O_R4P: case SCQ16_O_R3P:
snprintf (o, n, "%s", scq16_regs[v & 31]);
return;
case SCQ16_O_CSR:
snprintf (o, n, "%s", scq16_csrs[v & 31]);
return;
case SCQ16_O_ACC:
snprintf (o, n, "%s", scq16_accs[v & 31]);
return;
case SCQ16_O_DISP:
if (op->fmt == SCQ16_F_DISPX || !sym) {
if (op->fmt == SCQ16_F_DISPX) {
snprintf (o, n, "0x%x", (unsigned)v);
} else {
snprintf (o, n, "0x%08x", (unsigned)v);
}
} else {
const char *s = sym (user, (uint32_t)v, o, n);
if (s != o) {
snprintf (o, n, "%s", s);
}
}
return;
default:
break;
}
switch (op->fmt) {
case SCQ16_F_D: snprintf (o, n, "#%d", v); break;
case SCQ16_F_X: scq16_fmt_hex (o, n, hv, "#"); break;
case SCQ16_F_XB: scq16_fmt_hex (o, n, hv, ""); break;
case SCQ16_F_N: snprintf (o, n, "%d", v); break;
case SCQ16_F_INC:
if (v > -16 && v < 16) {
snprintf (o, n, "%d", v);
} else {
scq16_fmt_hex (o, n, v, "");
}
break;
case SCQ16_F_PM:
if (v >= 0) {
snprintf (o, n, "+= 0x%x", (unsigned)v);
} else {
snprintf (o, n, "-= 0x%x", (unsigned)(-(int64_t)v));
}
break;
case SCQ16_F_H2: snprintf (o, n, "%02x", (unsigned)v); break;
case SCQ16_F_HX: snprintf (o, n, "%x", (unsigned)v); break;
case SCQ16_F_B3: snprintf (o, n, "%d%d%d", (v >> 2) & 1, (v >> 1) & 1, v & 1); break;
case SCQ16_F_X04: snprintf (o, n, "0x%02x", (unsigned)v); break;
case SCQ16_F_X05: snprintf (o, n, "0x%03x", (unsigned)v); break;
case SCQ16_F_X06: snprintf (o, n, "0x%04x", (unsigned)v); break;
default: snprintf (o, n, "%d", v); break;
}
}
/* the vector name of the table (scq16_vname[op9]) carries the ? when the name is PROVISIONAL and none when it is an established
* operation (v.move.mv, v.mem.ld, v.mem.st); the decoder adds no ? of its own. */
static bool scq16_vname_firm(const char *nm) {
size_t l = nm ? strlen (nm) : 0;
return l > 0 && nm[l - 1] != '?';
}
const char *scq16_unit_mnemonic(const Scq16Unit *u, char *buf, size_t n) {
if (!u->alt) {
const char *tag = u->slot == SCQ16_S_A ? ".p" : u->slot == SCQ16_S_B ? ".q" : u->slot == SCQ16_S_ORPHAN ? ".o" : (u->slot == SCQ16_S_S1 || u->slot == SCQ16_S_S2) ? ".s" : ".b";
snprintf (buf, n, "%s%d%s", tag, u->L, u->last_head ? "!" : "");
return buf;
}
if (u->alt == &scq16_alts[SCQ16_VEC_ALT] || u->alt == &scq16_alts[SCQ16_BUNDLE_ALT]) {
const char *cls = scq16_vclass_names[scq16_vclass[u->op9]];
const char *nm = scq16_vname[u->op9];
if (u->alt == &scq16_alts[SCQ16_BUNDLE_ALT]) {
snprintf (buf, n, "vbundle");
} else if (nm) {
snprintf (buf, n, "v.%s.%s%s", cls, nm, u->last_head ? "!" : "");
} else {
snprintf (buf, n, "v.%s%s", cls, u->last_head ? "!" : "");
}
return buf;
}
snprintf (buf, n, "%s%s%s", u->alt->mn, u->masked ? ".p" : "", u->last_head ? "!" : "");
return buf;
}
int scq16_unit_operands(const Scq16Unit *u, char *buf, size_t n, Scq16Symf sym, void *user) {
size_t pos = 0;
buf[0] = 0;
if (!u->alt) {
int i;
for (i = 0; i < u->L; i++) {
char h[4];
snprintf (h, sizeof (h), "%s%02x", i ? " " : "", u->b[i]);
scq16_sbcat (buf, n, &pos, h);
}
return (int)pos;
}
const char *t = u->alt->fmt;
while (*t) {
if (*t == '{') {
const char *e = strchr (t, '}');
char name[16];
size_t l = e ? (size_t)(e - t - 1) : 0;
if (!e || l >= sizeof (name)) {
scq16_sbcat (buf, n, &pos, "{");
t++;
continue;
}
memcpy (name, t + 1, l);
name[l] = 0;
int idx = scq16_operand_index (u, name);
char o[48];
if (idx >= 0) {
scq16_operand_text (u, idx, o, sizeof (o), sym, user);
} else {
snprintf (o, sizeof (o), "{%s}", name);
}
scq16_sbcat (buf, n, &pos, o);
t = e + 1;
} else {
char c[2] = { *t, 0 };
scq16_sbcat (buf, n, &pos, c);
t++;
}
}
return (int)pos;
}
static const char *scq16_vec_operands(const Scq16Unit *u, char *o, size_t n) {
uint32_t v = u->vec32;
snprintf (o, n, "op9=0x%03x A%u B%u F%u,F%u,F%u", v & 0x1ff, (v >> 26) & 7, (v >> 24) & 3, (v >> 19) & 31, (v >> 14) & 31, (v >> 9) & 31);
return o;
}
int scq16_unit_text(const Scq16Unit *u, char *buf, size_t n, Scq16Symf sym, void *user) {
char mn[32], ops[160];
scq16_unit_mnemonic (u, mn, sizeof (mn));
if (u->alt == &scq16_alts[SCQ16_VEC_ALT] || u->alt == &scq16_alts[SCQ16_BUNDLE_ALT]) {
scq16_vec_operands (u, ops, sizeof (ops));
} else {
scq16_unit_operands (u, ops, sizeof (ops), sym, user);
}
if (ops[0]) {
return snprintf (buf, n, "%-9s %s", mn, ops);
}
return snprintf (buf, n, "%s", mn);
}
int scq16_insn_text(const Scq16Insn *in, char *buf, size_t n, Scq16Symf sym, void *user) {
char a[256], b[256], c[256];
if (in->kind == SCQ16_I_BUNDLE) {
char mn[32], vo[96];
const Scq16Unit *v = &in->u[0];
const char *cls = scq16_vclass_names[scq16_vclass[v->op9]];
const char *nm = scq16_vname[v->op9];
if (nm) {
snprintf (mn, sizeof (mn), "v.%s.%s", cls, nm);
} else {
snprintf (mn, sizeof (mn), "v.%s", cls);
}
scq16_vec_operands (v, vo, sizeof (vo));
/* the slots print as "mn ops" with a single space */
char m1[32], o1[160], m2[32], o2[160];
scq16_unit_mnemonic (&in->u[1], m1, sizeof (m1)); scq16_unit_operands (&in->u[1], o1, sizeof (o1), sym, user);
scq16_unit_mnemonic (&in->u[2], m2, sizeof (m2)); scq16_unit_operands (&in->u[2], o2, sizeof (o2), sym, user);
if (!in->u[1].alt) { snprintf (m1, sizeof (m1), ".s2"); }
if (!in->u[2].alt) { snprintf (m2, sizeof (m2), ".s2"); }
snprintf (a, sizeof (a), "%s%s%s", m1, o1[0] ? " " : "", o1);
snprintf (b, sizeof (b), "%s%s%s", m2, o2[0] ? " " : "", o2);
return snprintf (buf, n, "vbundle %s %s \xe2\x80\x96 %s \xe2\x80\x96 %s", mn, vo, a, b);
}
scq16_unit_text (&in->u[0], a, sizeof (a), sym, user);
if (in->kind == SCQ16_I_PACKET) {
scq16_unit_text (&in->u[1], c, sizeof (c), sym, user);
return snprintf (buf, n, "{ %s \xe2\x80\x96 %s }", a, c);
}
return snprintf (buf, n, "%s", a);
}
/* ------------------------------------------------------------------------------------------------ VU registers ---- */
/* scalar state of the VU exposed as ESIL registers: vu0-31 (vget), v0-31 (scalar shadow of vbcast),
* va0-7 (vset.a), vp0-7 (vmem.set), vs0-15 (vins?), vb1_/vb3_/vb5_ 0-31 (vrd?/vwr?), udf0-31 (the udf port) */
static const char *const scq16_vu_prefix[] = { "vu", "v", "va", "vp", "vs", "vb1_", "vb3_", "vb5_", "udf" };
static const int scq16_vu_count[] = { 32, 32, 8, 8, 16, 32, 32, 32, 32 };
#define SCQ16_NVU_PREFIX 9
static char scq16_vu_names[SCQ16_NVU_PREFIX][32][8];
static int scq16_vu_ready = 0;
static void scq16_vu_init(void) {
int p, k;
if (scq16_vu_ready) {
return;
}
for (p = 0; p < SCQ16_NVU_PREFIX; p++) {
for (k = 0; k < scq16_vu_count[p]; k++) {
snprintf (scq16_vu_names[p][k], sizeof (scq16_vu_names[p][k]), "%s%d", scq16_vu_prefix[p], k);
}
}
scq16_vu_ready = 1;
}
/* stable pointer to the name when s is a VU register, else NULL */
const char *scq16_vu_reg(const char *s) {
int p, k;
scq16_vu_init ();
for (p = 0; p < SCQ16_NVU_PREFIX; p++) {
size_t l = strlen (scq16_vu_prefix[p]);
if (strncmp (s, scq16_vu_prefix[p], l) || s[l] < '0' || s[l] > '9') {
continue;
}
k = atoi (s + l);
if (k < 0 || k >= scq16_vu_count[p] || strcmp (s, scq16_vu_names[p][k])) {
continue;
}
return scq16_vu_names[p][k];
}
return NULL;
}
/* total number of VU registers and the i-th name (register profile of the plugin) */
int scq16_vu_nregs(void) {
int p, n = 0;
for (p = 0; p < SCQ16_NVU_PREFIX; p++) {
n += scq16_vu_count[p];
}
return n;
}
const char *scq16_vu_name(int i) {
int p;
scq16_vu_init ();
for (p = 0; p < SCQ16_NVU_PREFIX; p++) {
if (i < scq16_vu_count[p]) {
return scq16_vu_names[p][i];
}
i -= scq16_vu_count[p];
}
return NULL;
}
/* ------------------------------------------------------------------------------------------------ ESIL ----------- */
static const char *scq16_reg_name_of(const Scq16Unit *u, int i) {
const Scq16Operand *op = &u->alt->ops[i];
switch (op->kind) {
case SCQ16_O_R5: case SCQ16_O_R4: case SCQ16_O_R4P: case SCQ16_O_R3P: return scq16_regs[u->vals[i] & 31];
case SCQ16_O_CSR: return scq16_csrs[u->vals[i] & 31];
case SCQ16_O_ACC: return scq16_accs[u->vals[i] & 31];
default: return NULL;
}
}
static const char *scq16_remapped(const char *reg, const char *const (*remap)[2], int nremap) {
int i;
for (i = 0; i < nremap; i++) {
if (!strcmp (remap[i][0], reg)) {
return remap[i][1];
}
}
return reg;
}
/* expand a reference {name} / {=name} / {#name} / {d} / {ret} / {mask} into o (returns false when the operand does not exist) */
static bool scq16_esil_ref(const Scq16Unit *u, const char *nm_in, size_t nl, uint32_t ret, const char *const (*remap)[2], int nremap, char *o, size_t on) {
char nm[32];
bool wr = false, dec = false;
const char *name = nm_in;
if (name[0] == '=') {
wr = true;
name++;
nl--;
} else if (name[0] == '#') {
dec = true;
name++;
nl--;
}
if (nl >= sizeof (nm)) {
nl = sizeof (nm) - 1;
}
memcpy (nm, name, nl);
nm[nl] = 0;
if (!strcmp (nm, "d")) {
snprintf (o, on, "0x%x", (unsigned)u->target);
} else if (!strcmp (nm, "ret")) {
snprintf (o, on, "0x%x", (unsigned)ret);
} else if (!strcmp (nm, "mask")) {
int wi = scq16_operand_index (u, "wd");
uint32_t wd = wi >= 0 ? (uint32_t)u->vals[wi] : 32;
snprintf (o, on, "0x%x", (unsigned)(wd >= 32 ? 0xffffffffu : ((1u << wd) - 1)));
} else {
int idx = scq16_operand_index (u, nm);
if (idx < 0) {
return false;
}
const char *reg = scq16_reg_name_of (u, idx);
if (dec) {
/* {#op}: decimal value (ESIL register names of the VU: vu{#k}, vb5_{#k}...) */
snprintf (o, on, "%d", (int)u->vals[idx]);
} else if (reg) {
snprintf (o, on, "%s", wr ? reg : scq16_remapped (reg, remap, nremap));
} else if (u->alt->ops[idx].kind == SCQ16_O_DISP) {
snprintf (o, on, "0x%x", (unsigned)u->vals[idx]);
} else {
snprintf (o, on, "0x%x", (unsigned)(uint32_t)u->vals[idx]);
}
}
return true;
}
/* hook that substitutes the ESIL template of an alternative (experiments); NULL = the table */
const char *(*scq16_esil_override)(const Scq16Alt *a) = NULL;
int scq16_unit_esil(const Scq16Unit *u, char *buf, size_t n, uint32_t ret, const char *const (*remap)[2], int nremap) {
size_t pos = 0;
buf[0] = 0;
if (!u->alt) {
return -1;
}
const char *t = scq16_esil_override ? scq16_esil_override (u->alt) : NULL;
if (!t) {
t = u->alt->esil;
}
if (!t) {
return -1;
}
/* literal tokens (sp, lr, pc, a0l, a0h, ipc) are remapped too when they are reads: a register token followed by "=" is
* a write; "=[n]" uses the previous token as the address (a read). A reference {..} may sit inside a token
* (vu{#k}: a VU register with a decimal index). */
while (*t) {
const char *e = strchr (t, ',');
size_t l = e ? (size_t)(e - t) : strlen (t);
char tok[64];
if (l >= sizeof (tok)) {
l = sizeof (tok) - 1;
}
memcpy (tok, t, l);
tok[l] = 0;
const char *next = e ? e + 1 : "";
bool is_write = next[0] == '=' && (next[1] == ',' || next[1] == 0);
char o[96];
if (strchr (tok, '{') && strchr (tok, '}') && tok[0] != '?' && tok[0] != '}') {
/* expand every reference {..} of the token (the control tokens ?{ }{ } pass through literally) */
char out[96] = "";
size_t op = 0;
const char *p = tok;
while (*p) {
if (*p == '{') {
const char *q = strchr (p, '}');
char ref[72];
if (!q) {
return -1;
}
if (!scq16_esil_ref (u, p + 1, (size_t)(q - p - 1), ret, remap, nremap, ref, sizeof (ref))) {
return -1;
}
size_t rl = strlen (ref);
if (op + rl >= sizeof (out)) {
return -1;
}
memcpy (out + op, ref, rl);
op += rl;
out[op] = 0;
p = q + 1;
} else {
if (op + 1 >= sizeof (out)) {
return -1;
}
out[op++] = *p++;
out[op] = 0;
}
}
/* a VU register read inside a packet is remapped too */
const char *vu = scq16_vu_reg (out);
if (vu && !is_write) {
snprintf (o, sizeof (o), "%s", scq16_remapped (vu, remap, nremap));
} else {
snprintf (o, sizeof (o), "%s", out);
}
} else if (!is_write && (!strcmp (tok, "sp") || !strcmp (tok, "lr") || !strcmp (tok, "pc") || !strcmp (tok, "a0l") || !strcmp (tok, "a0h") || !strcmp (tok, "ipc"))) {
snprintf (o, sizeof (o), "%s", scq16_remapped (tok, remap, nremap));
} else {
snprintf (o, sizeof (o), "%s", tok);
}
/* the ESIL machine of radare2 computes in 64 bits and `aes` does not mask the address of a memory access: a negative frame
* offset over a 32-bit base would wrap above 4 GB (read-err); the address of every [n] / =[n] is cut to 32 bits */
if ((o[0] == '[' || (o[0] == '=' && o[1] == '[')) && strchr (o, ']')) {
scq16_sbcat (buf, n, &pos, "0xffffffff,&,");
}
scq16_sbcat (buf, n, &pos, o);
if (e) {
scq16_sbcat (buf, n, &pos, ",");
t = e + 1;
} else {
break;
}
}
return (int)pos;
}
int scq16_unit_esil_regs(const Scq16Unit *u, uint32_t ret, bool written, const char **out, int max) {
char esil[512];
int cnt = 0;
if (scq16_unit_esil (u, esil, sizeof (esil), ret, NULL, 0) < 0) {
return 0;
}
char *s = esil;
while (*s) {
char *e = strchr (s, ',');
if (e) {
*e = 0;
}
const char *next = e ? e + 1 : "";
bool is_write = next[0] == '=' && (next[1] == ',' || next[1] == 0);
bool is_reg = (s[0] == 'r' && s[1] >= '0' && s[1] <= '9') || !strcmp (s, "sp") || !strcmp (s, "lr") || !strcmp (s, "a0l") || !strcmp (s, "a0h")
|| !strcmp (s, "ipc") || !strcmp (s, "sr") || !strcmp (s, "isr") || !strcmp (s, "lf") || (s[0] == 'l' && (s[1] == 's' || s[1] == 'e' || s[1] == 'c') && s[2] >= '0' && s[2] <= '2' && !s[3]) || !strncmp (s, "csr", 3)
|| scq16_vu_reg (s) != NULL;
if (is_reg && is_write == written && cnt < max) {
int k, dup = 0;
for (k = 0; k < cnt; k++) {
if (!strcmp (out[k], s)) {
dup = 1;
}
}
if (!dup) {
/* return stable pointers: look the name up in the tables */
const char *stable = NULL;
int r;
for (r = 0; r < 32 && !stable; r++) {
if (!strcmp (scq16_regs[r], s)) {
stable = scq16_regs[r];
}
}
for (r = 0; r < 32 && !stable; r++) {
if (!strcmp (scq16_csrs[r], s)) {
stable = scq16_csrs[r];
}
}
for (r = 0; r < 32 && !stable; r++) {
if (!strcmp (scq16_accs[r], s)) {
stable = scq16_accs[r];
}
}
if (!stable) {
stable = scq16_vu_reg (s);
}
if (stable) {
out[cnt++] = stable;
}
}
}
if (!e) {
break;
}
s = e + 1;
}
return cnt;
}
+93
View File
@@ -0,0 +1,93 @@
/* scq16_decode.h - decoder of the SCQ16 (MD32 family, byte-granular packet variant).
*
* Model: the minimum unit is the byte; an INSTRUCTION is a stand-alone unit, a PACKET {A ‖ B} of two units or an 8-byte
* BUNDLE {vec32 ‖ s1 ‖ s2}. Everything is decided by the first raw byte b0 and, when needed, a prefix of the second (the
* leaves scq16_uleaves / scq16_hleaves of scq16_opcodes.h):
* 00-7f stand-alone scalar unit: L by the leaf U(b0, prefix of b1); forms of L bytes over w (bit 7 of byte 0 = 0).
* 80-bf scalar head: A' = (w << 1) | bit7(B) (bit 7 of the next byte is bit 0 of A'); leaf H(A'[0], prefix of A'[1])
* -> L(A') (head of L bytes + B slot) or a masked orphan (A' of control/system: the unit b0 & 0x7f of Lm bytes).
* c0-df 4-byte vector operation (110 A B F1 F2 F3 op9), never a head.
* e0-ef 4-byte vector head (A' = c0-df) + B slot.
* f0-f7 8-byte bundle {11 | vec32 w[61:30] | s1 w[29:15] | s2 w[14:0]}; s1/s2 = 2-byte units (7-bit opcode).
* f8-ff masked orphan.
* B slot: byte 0 masked (& 0x7f); L = 4 when the raw byte is c0-df, else the leaf U; its displacements are measured from the
* packet start. Packet semantics: PARALLEL (both slots read the previous state). Delay slots (.d1/.d2): one/two
* INSTRUCTIONS (packets included) after the control one.
* Bit convention: w = big-endian integer of the transformed unit, bit 0 = LSB of the last byte. */
#ifndef SCQ16_DECODE_H
#define SCQ16_DECODE_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "scq16_opcodes.h"
#define SCQ16_MAX_OPS 8
enum scq16_slot { SCQ16_S_UNIT = 0, SCQ16_S_A, SCQ16_S_B, SCQ16_S_ORPHAN, SCQ16_S_S1, SCQ16_S_S2 };
enum scq16_ikind { SCQ16_I_UNIT = 0, SCQ16_I_PACKET, SCQ16_I_BUNDLE };
typedef struct {
uint32_t pc; /* address of the first byte */
uint8_t L; /* length in bytes (0..8) */
uint8_t slot; /* enum scq16_slot */
uint8_t raw[8]; /* bytes in memory */
uint8_t b[8]; /* transformed bytes (A' in the head; byte 0 masked in a B slot or an orphan) */
uint64_t w; /* big-endian integer of b */
const Scq16Alt *alt; /* matching alternative, NULL = no form */
int32_t vals[SCQ16_MAX_OPS]; /* operand values (register = number, immediate = value, disp = absolute target) */
uint8_t nvals;
uint32_t base; /* base of the displacements (packet start) */
uint8_t nb; /* bit borrowed from B (heads) */
bool has_target;
uint32_t target;
bool masked; /* masked orphan (suffix .p) */
bool last_head; /* head without a B slot (end of the buffer): suffix ! */
uint16_t op9; /* vector operation: op9 */
uint32_t vec32; /* bundle: the 32-bit vector operation */
} Scq16Unit;
typedef struct {
uint32_t addr;
int size; /* bytes of the whole instruction (unit, packet or bundle); 0 = undecodable */
uint8_t kind; /* enum scq16_ikind */
uint8_t n; /* units: 1 (stand-alone), 2 (packet), 3 (bundle: vec, s1, s2) */
Scq16Unit u[3];
uint8_t delay; /* delay slots of the instruction (the largest among its control units) */
int need; /* bytes needed when size == 0 (truncated buffer) */
} Scq16Insn;
typedef const char *(*Scq16Symf)(void *user, uint32_t addr, char *buf, size_t n);
/* decode the instruction that starts at bytes[0] (address addr); len = bytes available. Returns size (> 0), 0 when the
* buffer is too short (out->need = bytes that would be needed) or -1 when the byte has no leaf. */
int scq16_decode_insn(const uint8_t *bytes, int len, uint32_t addr, Scq16Insn *out);
/* decode a transformed unit b of L bytes (low-level API: forms of L bytes over w, the first that matches) */
void scq16_decode_unit(Scq16Unit *u, const uint8_t *b, int L, uint32_t pc, uint32_t base, uint8_t slot);
/* length by leaf: stand-alone unit / masked B (b0 & 0x7f, b1); 0 when there is no leaf */
int scq16_uleaf(uint8_t b0, uint8_t b1);
/* head leaf: > 0 L(A'), < 0 masked orphan of -L bytes, 0 no leaf */
int scq16_hleaf(uint8_t a0, uint8_t a1);
/* mnemonic of the unit (v.<class>[.<name>?] for a vector operation; .p for a masked orphan, ! for a head without its B slot) */
const char *scq16_unit_mnemonic(const Scq16Unit *u, char *buf, size_t n);
/* operand text (the template of the alternative); sym = symbol of a target (NULL -> 0x%08x) */
int scq16_unit_operands(const Scq16Unit *u, char *buf, size_t n, Scq16Symf sym, void *user);
/* "mnemonic operands" of a unit; for the bundle: "vbundle v.class op9=.. ‖ s1 ‖ s2" */
int scq16_unit_text(const Scq16Unit *u, char *buf, size_t n, Scq16Symf sym, void *user);
int scq16_insn_text(const Scq16Insn *in, char *buf, size_t n, Scq16Symf sym, void *user);
/* operand by name: index in u->alt->ops or -1 */
int scq16_operand_index(const Scq16Unit *u, const char *name);
/* ESIL (radare2) of the unit: the template of the alternative with the operands substituted ({op} read, {=op} write, {#op} decimal value, {d}, {ret}, {mask}); remap = registers
* that must be read from a temporary (parallel packet semantics: {reg, tmp} pairs, nremap pairs); ret = return address / start of
* the do body (after the delay slots). Returns the length or -1 when the form has no semantics. */
int scq16_unit_esil(const Scq16Unit *u, char *buf, size_t n, uint32_t ret, const char *const (*remap)[2], int nremap);
/* GPR/special registers written (dst) and read (src) by the ESIL of the unit: names in out[], returns how many */
int scq16_unit_esil_regs(const Scq16Unit *u, uint32_t ret, bool written, const char **out, int max);
/* ESIL registers of the VU (vu0-31, v0-31, va0-7, vp0-7, vs0-15, vb1_/vb3_/vb5_ 0-31, udf0-31): stable name or NULL; count and i-th name */
const char *scq16_vu_reg(const char *s);
/* hook that substitutes the ESIL template of an alternative (experiments): NULL = the template of the table */
extern const char *(*scq16_esil_override)(const Scq16Alt *a);
int scq16_vu_nregs(void);
const char *scq16_vu_name(int i);
#endif
+2823
View File
File diff suppressed because it is too large. Load diff
+239
View File
@@ -0,0 +1,239 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - n0p */
/* scq16_vu.inc.c - the public model of the SCQ16 vector unit for the ESIL of the plugin (cpu=scq16).
*
* State (ESIL registers of the register profile):
* vr0-vr31 the vector register file: 32 registers of 128 bytes = 64 lanes of 16 bits each (the lane model of the
* reference VU), exposed as four 256-bit quarters per register (`vrK_0` = lanes 0-15, `vrK_1` = 16-31,
* `vrK_2` = 32-47, `vrK_3` = 48-63; `ar vrK_q` prints the 32 bytes of a quarter as a little-endian number,
* so lane 16q is its low 16 bits);
* vp0-vp7 the VU pointers (written by vmem.set p_k, rs and by the hypothetical vcfg4c? #0xc);
* va0-va7 the VU count/modifier registers A (written by vset.a A_k, rs);
* v0-v31 the scalar BROADCAST PORTS: the ESIL of vbcast v_k, rs / vbcast.i v_k, #imm / vbcast.i? writes the 32-bit
* port v_k (the ISA template `{rs},v{#k},=`, unchanged); the plugin observes that write (an ESIL register-write voyeur
* registered by esilcb; the strict executor chains it in its own hook) and broadcasts the low 16 bits to the 64
* lanes of vr_k. The port keeps the last scalar written (an alias readable with `ar v3`); vu0-vu31 (vget) is a
* different port, not modelled.
* Operations modelled by the custom ESIL op `vop` (op9, A, B, F1, F2, F3), only for the three ESTABLISHED vector names of
* the table (scq16_vname[op9] without `?`):
* v.mem.ld vr[F1 >> 2][0 .. n) <- DM[p .. p + 2n): n = va[A] halves when 1 <= va[A] <= 64, else the whole register
* (va[A] = 0 or a larger packed parameter word: the count field of a large A is not established);
* p = vp[2 * F2 + F3[4]] for F2 < 4; the pointer is post-incremented by the bytes accessed;
* v.mem.st DM[p .. p + 2n) <- vr0[0 .. n) (the data register is the implicit vr0), same pointer and count, same
* post-increment;
* v.move.mv vr[F1] <- vr[F3] (the whole 128 bytes; the pair mode B = 2 is not modelled: an open reading).
* Limits (declared): no packed count field of a large A, no source lane F3[3:0] of the stores, no role for F1 != 0 / B = 1 in
* the stores, the pair mode B = 2 of v.move.mv, the second access family (F2 >= 4). A memory op with F2 >= 4 and every other
* op9 pop their operands and have no effect in the plugin (scq16-run, the strict executor, stops on them unless a marker/model
* mode is set). Data memory is read and written through the ESIL memory callbacks (little-endian halves are copied as bytes:
* the lane k of a register is bytes 2k, 2k + 1).
* The custom ESIL op `open` (REF,ALT,open) is the uniform marker of an OPEN reading without semantics (table flag
* SCQ16_FL_OPEN): the plugin pops its two operands and does nothing (scq16_vu_pop_n). */
#include <r_anal.h> /* struct r_anal_t (esil->anal->reg): the vector register file lives in the register arena */
#define SCQ16_VR_N 32
#define SCQ16_VR_BYTES 128
#define SCQ16_VR_LANES 64
#define SCQ16_VR_QUARTER 32
/* the vector register file in the register profile: four 256-bit quarters per register; returns the next free offset */
static int scq16_vu_vr_profile(RStrBuf *sb, int off) {
int k, q;
for (k = 0; k < SCQ16_VR_N; k++) {
for (q = 0; q < SCQ16_VR_BYTES / SCQ16_VR_QUARTER; q++, off += SCQ16_VR_QUARTER) {
r_strbuf_appendf (sb, "gpr vr%d_%d .256 %d 0\n", k, q, off);
}
}
return off;
}
/* pops n operands of a custom op into v[0..n) (v[0] = the top of the stack; v may be NULL to discard them) */
static bool scq16_vu_pop_n(REsil *esil, int n, ut64 *v) {
int i;
for (i = 0; i < n; i++) {
RStrs s = r_esil_pop (esil);
if (r_strs_empty (s)) {
return false;
}
char tmp[64];
size_t l = r_strs_len (s);
if (l >= sizeof (tmp)) {
l = sizeof (tmp) - 1;
}
memcpy (tmp, s.a, l);
tmp[l] = 0;
if (v) {
v[i] = r_num_get (NULL, tmp);
}
}
return true;
}
/* pops the six operands of `vop` (pushed F3, F2, F1, B, A, op9: op9 on top) into v[0] = op9, v[1] = A, v[2] = B, v[3] = F1,
* v[4] = F2, v[5] = F3 */
static bool scq16_vu_pop6(REsil *esil, ut64 *v) {
return scq16_vu_pop_n (esil, 6, v);
}
/* the 128 bytes of vector register k in the register arena (NULL when the profile has no vector register file) */
static ut8 *scq16_vu_vr_bytes(REsil *esil, int k) {
RReg *reg = (esil && esil->anal) ? esil->anal->reg : NULL;
if (!reg) {
return NULL;
}
char name[16];
snprintf (name, sizeof (name), "vr%d_0", k & (SCQ16_VR_N - 1));
RRegItem *ri = r_reg_get (reg, name, -1);
if (!ri || ri->offset < 0 || ri->offset % 8 || ri->size != SCQ16_VR_QUARTER * 8) {
return NULL;
}
RRegArena *a = reg->regset[ri->type].arena;
size_t off = (size_t)ri->offset / 8;
if (!a || !a->bytes || off + SCQ16_VR_BYTES > (size_t)a->size) {
return NULL;
}
return a->bytes + off;
}
/* the count of a vector memory op in bytes: va[A] halves when 1..64, else the whole register */
static int scq16_vu_nbytes(ut64 cnt) {
return (cnt == 0 || cnt > SCQ16_VR_LANES) ? SCQ16_VR_BYTES : (int)cnt * 2;
}
enum { SCQ16_VU_NONE = 0, SCQ16_VU_LD = 1, SCQ16_VU_ST = 2, SCQ16_VU_MV = 3 };
/* the public model: v = { op9, A, B, F1, F2, F3 }; returns SCQ16_VU_LD / ST / MV when the op was modelled (and executed), 0 when the
* op9 is not one of the three established names (or a memory op of the second access family): the caller decides (no-op or stop). */
static int scq16_vu_vop_public(REsil *esil, const ut64 *v) {
const int op9 = (int)(v[0] & 0x1ff), A = (int)(v[1] & 7), F1 = (int)(v[3] & 31), F2 = (int)(v[4] & 31), F3 = (int)(v[5] & 31);
const char *nm = scq16_vname[op9];
if (!nm || !scq16_vname_firm (nm)) {
return SCQ16_VU_NONE;
}
if (!strcmp (nm, "mv")) {
ut8 *dst = scq16_vu_vr_bytes (esil, F1), *src = scq16_vu_vr_bytes (esil, F3);
if (!dst || !src) {
return SCQ16_VU_NONE;
}
if (dst != src) {
memmove (dst, src, SCQ16_VR_BYTES);
}
return SCQ16_VU_MV;
}
const bool ld = !strcmp (nm, "ld"), st = !strcmp (nm, "st");
if ((!ld && !st) || F2 >= 4) {
return SCQ16_VU_NONE;
}
const int j = 2 * F2 + ((F3 >> 4) & 1);
char pname[8], aname[8];
snprintf (pname, sizeof (pname), "vp%d", j);
snprintf (aname, sizeof (aname), "va%d", A);
ut64 p = 0, cnt = 0;
if (!r_esil_reg_read (esil, pname, &p, NULL) || !r_esil_reg_read (esil, aname, &cnt, NULL)) {
return SCQ16_VU_NONE;
}
if (esil->parse_stop) { /* a strict executor refused an uninitialized pointer or count */
return ld ? SCQ16_VU_LD : SCQ16_VU_ST;
}
const int nb = scq16_vu_nbytes (cnt);
ut8 *vr = scq16_vu_vr_bytes (esil, ld ? (F1 >> 2) : 0);
if (!vr) {
return SCQ16_VU_NONE;
}
p &= 0xffffffffULL;
if (ld) {
ut8 buf[SCQ16_VR_BYTES];
if (!r_esil_mem_read (esil, p, buf, nb)) {
return SCQ16_VU_LD;
}
if (!esil->parse_stop) {
memcpy (vr, buf, nb);
}
} else {
if (!r_esil_mem_write (esil, p, vr, nb)) {
return SCQ16_VU_ST;
}
}
if (!esil->parse_stop) {
r_esil_reg_write (esil, pname, (p + (ut64)nb) & 0xffffffffULL);
}
return ld ? SCQ16_VU_LD : SCQ16_VU_ST;
}
/* ---- the broadcast ports v0-v31 ----------------------------------------------------------------------------------------- */
/* name is exactly "v<k>" with k in 0..31 (the scalar broadcast port of vbcast / vbcast.i); -1 otherwise */
static int scq16_vu_port_index(const char *name) {
if (!name || name[0] != 'v' || name[1] < '0' || name[1] > '9') {
return -1;
}
int k = 0;
const char *p = name + 1;
while (*p >= '0' && *p <= '9') {
k = k * 10 + (*p - '0');
p++;
if (k > 31) {
return -1;
}
}
return (*p || (name[1] == '0' && name[2])) ? -1 : k;
}
/* a write of the port v<k>: the low 16 bits of val go to the 64 lanes of vr<k> (little-endian halves); returns k, or -1 when
* name is not a broadcast port or the profile has no vector register file */
static int scq16_vu_broadcast_port(REsil *esil, const char *name, ut64 val) {
const int k = scq16_vu_port_index (name);
if (k < 0) {
return -1;
}
ut8 *vr = scq16_vu_vr_bytes (esil, k);
if (!vr) {
return -1;
}
const ut8 lo = (ut8)(val & 0xff), hi = (ut8)((val >> 8) & 0xff);
int i;
for (i = 0; i < SCQ16_VR_LANES; i++) {
vr[2 * i] = lo;
vr[2 * i + 1] = hi;
}
return k;
}
/* the register-write voyeur of the plugin (r_esil_add_voyeur): user = the REsil */
static void scq16_vu_voyeur_reg_write(void *user, const char *name, ut64 old, ut64 val) {
(void)old;
scq16_vu_broadcast_port ((REsil *)user, name, val);
}
/* one voyeur per ESIL instance (a handful of sessions at most) */
static struct { REsil *esil; ut32 vid; } scq16_vu_voyeurs[8];
static void scq16_vu_esil_init(REsil *esil) {
size_t i;
for (i = 0; i < R_ARRAY_SIZE (scq16_vu_voyeurs); i++) {
if (scq16_vu_voyeurs[i].esil == esil) {
return; /* already registered on this instance */
}
}
for (i = 0; i < R_ARRAY_SIZE (scq16_vu_voyeurs); i++) {
if (!scq16_vu_voyeurs[i].esil) {
ut32 vid = r_esil_add_voyeur (esil, esil, (void *)scq16_vu_voyeur_reg_write, R_ESIL_VOYEUR_REG_WRITE);
if (vid != R_ESIL_VOYEUR_ERR) {
scq16_vu_voyeurs[i].esil = esil;
scq16_vu_voyeurs[i].vid = vid;
}
return;
}
}
}
static void scq16_vu_esil_fini(REsil *esil) {
size_t i;
for (i = 0; i < R_ARRAY_SIZE (scq16_vu_voyeurs); i++) {
if (scq16_vu_voyeurs[i].esil == esil) {
r_esil_del_voyeur (esil, scq16_vu_voyeurs[i].vid);
scq16_vu_voyeurs[i].esil = NULL;
scq16_vu_voyeurs[i].vid = 0;
}
}
}
File diff suppressed because it is too large. Load diff
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Check ESIL results and strict handling of missing boot state."""
import json
import os
import subprocess
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RUNNER = Path(os.environ.get('MD32_RUNNER', ROOT / 'tools/md32-run'))
def execute(code, *args):
with tempfile.TemporaryDirectory(prefix='md32-run-') as temp:
path = Path(temp) / 'code.bin'
path.write_bytes(bytes.fromhex(code))
proc = subprocess.run([str(RUNNER), '--image', str(path) + '@0', '--steps', '1', *args],
text=True, capture_output=True, check=True)
return json.loads(proc.stdout)
def scalar():
# 16- and 32-bit arithmetic shifts, including negative operands and aliasing.
for word, regs, dest, expected in [
('9041', ['r1=0xfffffff0'], 'r1', 0xffffffff),
('041d6421', ['r2=0xfffffff0'], 'r1', 0xffffffff),
('040ae321', ['r2=0x80000000', 'r3=31'], 'r1', 0xffffffff),
('8d21', ['r2=0'], 'r1', 32),
('8d21', ['r2=0x80000000'], 'r1', 0),
('8d22', ['r2=1'], 'r2', 31),
('04196021', ['r2=0xffffffff'], 'r1', 32),
('04196021', ['r2=0x80000000'], 'r1', 1),
('04196021', ['r2=0x20000000'], 'r1', 2),
('20000321', ['r2=0xffffffff', 'r3=1'], 'r1', 1),
('20001321', ['r2=0xffffffff', 'r3=1'], 'r1', 0xffffffff),
('c421', ['r1=0xffffffff', 'r2=1'], 'r1', 1),
('c521', ['r1=0xffffffff', 'r2=1'], 'r1', 0xffffffff),
('0407e321', ['r2=0xffffffff', 'r3=7'], 'r1', 0xfffffff9),
('0407e321', ['r2=0', 'r3=7'], 'r1', 7)]:
args = [x for reg in regs for x in ['--reg', reg]]
out = execute(word, *args)
assert out['stop_reason'] == 'step_limit', (word, out)
assert out['completed_steps'] == 1 and out['initialized_registers'][dest] == expected, (word, out)
assert out['pc'] == len(bytes.fromhex(word)), out
print('Arithmetic shifts, bit counts, min/max and conditional inversion passed')
def boundaries():
# Assignment must not count ESIL's destination bookkeeping as a semantic read.
out = execute('9811')
assert out['completed_steps'] == 1 and out['initialized_registers']['r1'] == 1
out = execute('8811')
assert out['stop_reason'] == 'uninitialized_register_read' and out['fault_register'] == 'r1'
assert out['completed_steps'] == 0
out = execute('06400012', '--reg', 'r2=0x1000')
assert out['stop_reason'] == 'unmapped_data_read' and out['fault_address'] == 0x1000
out = execute('06400012', '--reg', 'r2=0x1000', '--ram', '0x1000:8')
assert out['stop_reason'] == 'uninitialized_data_read' and out['fault_address'] == 0x1000
out = execute('06400012', '--reg', 'r2=0x1000', '--zero', '0x1000:8')
assert out['completed_steps'] == 1 and out['initialized_registers']['r1'] == 0
# Store initializes previously unknown RAM; the following load sees its bytes.
out = execute('0670001206400032', '--steps', '2', '--reg', 'r1=0x12345678',
'--reg', 'r2=0x1000', '--ram', '0x1000:8')
assert out['completed_steps'] == 2 and out['initialized_registers']['r3'] == 0x12345678, out
assert [e['access'] for e in out['memory_accesses']] == ['write', 'read']
# Code memory does not silently act as initialized data in a Harvard CPU.
out = execute('06400012', '--reg', 'r2=0')
assert out['stop_reason'] == 'unmapped_data_read'
out = execute('98119822', '--entry', '1')
assert out['completed_steps'] == 0 and out['stop_reason'] == 'unaligned_instruction_fetch'
# No invented semantics for delayed branches: in usip mode 5ef9 is ret.d with the rest of the
# packet as its delay slot, so the strict stop is the delay-slot one (zero steps), as for MS15E30.
out = execute('5ef9901a', '--cpu', 'usip')
assert out['completed_steps'] == 0 and out['stop_reason'] == 'unsupported_delay_slot_execution'
out = execute('a02f80008000', '--reg', 'r15=0')
assert out['completed_steps'] == 0 and out['stop_reason'] == 'unsupported_delay_slot_execution'
# An unsupported pair that contains NOP is not silently skipped either.
out = execute('40002000')
assert out['completed_steps'] == 0 and out['stop_reason'] == 'unsupported_instruction_semantics'
print('Uninitialized registers/RAM, unmapped devices, Harvard maps and strict stops passed')
if __name__ == '__main__':
scalar()
boundaries()
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Validate all three supplied images and reproduce the boot limitations."""
import contextlib
import io
import json
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
from analyze_firmware import analyze, REGIONS
BUNDLE = ROOT / 'third_party/v2/r2pkg'
def main():
if not (BUNDLE / 'data/functions.csv').exists():
print('v2 bundle absent; skipping optional three-image analysis')
return
with tempfile.TemporaryDirectory(prefix='md32-analysis-') as temp:
with contextlib.redirect_stdout(io.StringIO()):
result = analyze(BUNDLE, Path(temp), ROOT / 'test/test_md32')
assert result['functions'] == 1301
assert [i['functions'] for i in result['images']] == [271, 857, 173]
assert sum(i['instruction_records'] for i in result['images']) == 125197
assert sum(i['code_instructions'] for i in result['images']) == 125197
assert sum(i['non_vector_instructions'] for i in result['images']) == 124941
assert all(not i['metadata_count_mismatches'] for i in result['images'])
assert all(i['long_string_candidates'] == 0 for i in result['images'])
assert result['string_pointer_candidates'] == []
vectors = result['vectors']
assert vectors[0]['address'] == 0x400e0000
assert vectors[0]['encoding'] == 0x400fd5c0
assert [v['index'] for v in vectors if v['target'] is None] == [0, 8]
assert sum(v['target'] == 0x400e08d0 for v in vectors) == 238
assert sum(v['self_loop'] for v in vectors) == 12
assert all(v['at_instruction_boundary'] for v in vectors if v['target'] is not None)
jumps = [f for f in result['flows'] if f['mnemonic'].startswith('j ')]
assert len(jumps) == 342 and all(f['target_is_boundary'] for f in jumps)
assert len(result['flows']) == 1278
assert sum(not f['target_is_boundary'] for f in result['flows']) == 6
cases = [('sec0', ['--vector'], 0x400e0000, '.word 0x400fd5c0', 0,
'unsupported_instruction_semantics'),
('ddr', ['--entry', '0x1ca74264'], 0x1ca74264, '.word 0x003ac808', 0,
'unsupported_instruction_semantics'),
('sec0', ['--entry', '0x400e0048'], 0x400e0048, 'j 0x400e08d0', 1,
'unsupported_instruction_semantics'),
('sec0', ['--entry', '0x400e0008'], 0x400e0008, 'j 0x400e0008', 1,
'unconditional_self_loop')]
for region, extra, address, instruction, completed, reason in cases:
proc = subprocess.run([str(ROOT / 'tools/md32-run'), '--cpu', 'usip', '--image',
str(BUNDLE / ('bin/usip_%s.bin' % region)) + '@%#x' % REGIONS[region],
*extra], capture_output=True, text=True, check=True)
report = json.loads(proc.stdout)
assert report['completed_steps'] == completed
assert report['stop_reason'] == reason
assert report['trace'][0]['pc'] == address
assert report['trace'][0]['instruction'] == instruction
print('All 1301 functions / 125197 records, executable vectors, strings and boot probes passed')
if __name__ == '__main__':
main()
+194
View File
@@ -0,0 +1,194 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - pancake */
#include <r_anal.h>
#define R2_PLUGIN_INCORE 1
#include "../arch_md32.c"
#include <assert.h>
static RArchConfig config = { .bits = 32, .endian = R_SYS_ENDIAN_BIG };
static RArchSession session = { .config = &config };
static bool run(RAnalOp *op, const ut8 *bytes, int size, ut64 addr, const char *cpu, unsigned mask) {
r_anal_op_init (op);
op->bytes = (ut8 *)bytes;
op->weakbytes = true;
op->size = size;
op->addr = addr;
config.cpu = (char *)cpu;
return decode (&session, op, mask);
}
static void check(ut32 word, int size, const char *text, int type) {
ut8 bytes[4];
if (size == 2) {
r_write_be16 (bytes, word);
} else {
r_write_be32 (bytes, word);
}
RAnalOp op;
assert (run (&op, bytes, size, 0x1000, "md32", R_ARCH_OP_MASK_ALL));
if (strcmp (op.mnemonic, text) || op.type != (ut32)type || op.size != size) {
fprintf (stderr, "%08x: got %s (%d, %d), expected %s (%d, %d)\n",
word, op.mnemonic, op.type, op.size, text, type, size);
abort ();
}
r_anal_op_fini (&op);
}
static void unit_tests(void) {
check (0x0400e321, 4, "add r1, r2, r3", R_ANAL_OP_TYPE_ADD);
check (0x08ffff21, 4, "add r1, r2, -0x1", R_ANAL_OP_TYPE_ADD);
check (0x01fffff1, 4, "mv r1, -0x1", R_ANAL_OP_TYPE_MOV);
check (0x0f123401, 4, "sethi r1, 0x1234", R_ANAL_OP_TYPE_MOV);
check (0x060fff21, 4, "lbu r2, -0x1(r1)", R_ANAL_OP_TYPE_LOAD);
check (0x06c00821, 4, "lw r2, (r1+=0x8)", R_ANAL_OP_TYPE_LOAD);
check (0x067ffc2e, 4, "sw r2, -0x4(r14)", R_ANAL_OP_TYPE_STORE);
check (0x04154061, 4, "mv r1, le0", R_ANAL_OP_TYPE_MOV);
check (0x04182025, 4, "mv ls0, r2", R_ANAL_OP_TYPE_MOV);
check (0x1e1fffff, 4, "j 0x00000ffe", R_ANAL_OP_TYPE_JMP);
check (0x1f000020, 4, "jal 0x00001040", R_ANAL_OP_TYPE_CALL);
check (0x1f800002, 4, "jal r2", R_ANAL_OP_TYPE_RCALL);
check (0xa03f, 2, "j16 r15", R_ANAL_OP_TYPE_RET);
check (0xa02f, 2, "j16.d2 r15", R_ANAL_OP_TYPE_RET);
check (0xa180, 2, "j16 0x00001000", R_ANAL_OP_TYPE_JMP);
check (0xa1ff, 2, "j16 0x00000ffe", R_ANAL_OP_TYPE_JMP);
check (0x9811, 2, "mv16 r1, 0x1", R_ANAL_OP_TYPE_MOV);
check (0x9ff1, 2, "mv16 r1, -0x1", R_ANAL_OP_TYPE_MOV);
check (0x81ff, 2, "and16 r8, r8, 0xffff", R_ANAL_OP_TYPE_AND);
check (0xa401, 2, "lw16 r1, -0x80(r14)", R_ANAL_OP_TYPE_LOAD);
check (0xb781, 2, "sw16 r1, (r1+=-0x4)", R_ANAL_OP_TYPE_STORE);
check (0xbc11, 2, "beq16 0x0, r1, 0x00001002", R_ANAL_OP_TYPE_CJMP);
check (0x4600203f, 4, "xor16 r0, r0 | j16 r15", R_ANAL_OP_TYPE_RET);
check (0x40002000, 4, "nop16 | nop16.l", R_ANAL_OP_TYPE_UNK);
check (0x20002000, 4, "add r0, r0, r0, 0x1", R_ANAL_OP_TYPE_ADD);
check (0x05000000, 4, "nop", R_ANAL_OP_TYPE_NOP);
// group-5 forms absent from the objdump table: 0xDA00/0xDC00 named after the little-endian data memory
check (0xda49, 2, "cmulr16 r2, r2, r2", R_ANAL_OP_TYPE_MUL);
check (0xdc49, 2, "cmuli16 r2, r2, r2", R_ANAL_OP_TYPE_MUL);
check (0xca89, 2, "rem16 r2, r2", R_ANAL_OP_TYPE_MOD);
RAnalOp op;
const ut8 bytes[] = { 0x14, 0x00, 0x02, 0x21, 0x98, 0x11, 0x98, 0x22 };
assert (run (&op, bytes, sizeof (bytes), 0x1000, "md32", R_ARCH_OP_MASK_ALL));
assert (op.type == R_ANAL_OP_TYPE_CJMP && op.delay == 1 && op.jump == 0x1004);
assert (!*r_strbuf_get (&op.esil));
assert (op.fail == 0x1006);
r_anal_op_fini (&op);
// A delayed conditional branch with no lookahead has no known fallthrough.
assert (run (&op, bytes, 4, 0x1000, "md32", R_ARCH_OP_MASK_ALL));
assert (op.fail == UT64_MAX && op.delay == 1);
r_anal_op_fini (&op);
const ut8 wrap[] = { 0x1f, 0x00, 0x00, 0x02 };
assert (run (&op, wrap, 4, 0xfffffffe, "md32", R_ARCH_OP_MASK_ALL));
assert (op.jump == 2 && op.fail == 2);
r_anal_op_fini (&op);
const ut8 high[] = { 0x0f, 0x12, 0x34, 0x01 };
assert (run (&op, high, 4, 0, "md32", R_ARCH_OP_MASK_ALL));
assert (op.val == 0x12340000);
assert (RVecRArchValue_at (&op.srcs, 0)->imm == 0x12340000);
r_anal_op_fini (&op);
const ut8 ret[] = { 0xde, 0xf9 };
assert (run (&op, ret, 2, 0x1000, "usip", R_ARCH_OP_MASK_ALL));
assert (op.type == R_ANAL_OP_TYPE_RET && op.eob);
r_anal_op_fini (&op);
const ut8 call[] = { 0x00, 0x0e, 0x78, 0xaa };
assert (run (&op, call, 4, 0x1ca7292c, "usip", R_ARCH_OP_MASK_ALL));
assert (op.type == R_ANAL_OP_TYPE_CALL && op.jump == 0x1ca731d6);
r_anal_op_fini (&op);
// Every truncated prefix must fail without reading beyond its buffer.
unsigned h, len;
for (h = 0; h <= 0xffff; h++) {
ut8 bytes[4];
r_write_be32 (bytes, h << 16);
for (len = 0; len <= 3; len++) {
ut8 *exact = malloc (len? len: 1);
memcpy (exact, bytes, len);
const bool ok = run (&op, exact, len, 0x1000, "md32", R_ARCH_OP_MASK_ALL);
assert (!ok || op.size <= (int)len);
assert (!ok || (op.size == 2 && op.mnemonic));
r_anal_op_fini (&op);
free (exact);
}
}
// Stateless decoding must give identical output regardless of history.
for (h = 0; h <= 0xffff; h++) {
ut8 bytes[4];
r_write_be32 (bytes, (h << 16) | 0xa03f);
const bool ok = run (&op, bytes, 4, 0x1000, "md32", R_ARCH_OP_MASK_ALL);
assert (op.size == 2 || op.size == 4);
if (ok) {
RAnalOp basic;
assert (run (&basic, bytes, 4, 0x1000, "md32", R_ARCH_OP_MASK_BASIC));
assert (!basic.mnemonic && basic.size == op.size && basic.type == op.type);
assert (basic.jump == op.jump && basic.delay == op.delay);
r_anal_op_fini (&basic);
}
r_anal_op_fini (&op);
}
puts ("MD32 unit, truncation, and exhaustive halfword checks passed");
}
static int stream(const char *cpu) {
char line[256];
while (fgets (line, sizeof (line), stdin)) {
unsigned word, len;
ut64 addr;
if (sscanf (line, "%x %u %"SCNx64, &word, &len, &addr) != 3 || len > 4) {
return 1;
}
ut8 bytes[4];
r_write_be32 (bytes, word);
RAnalOp op;
bool ok = run (&op, bytes, len, addr, cpu, R_ARCH_OP_MASK_ALL);
printf ("%u\t%d\t%d\t%"PFMT64x"\t%"PFMT64x"\t%d\t%s\t%s\n", ok,
op.size, op.type, op.jump, op.fail, op.delay, op.mnemonic? op.mnemonic: "",
r_strbuf_get (&op.esil));
r_anal_op_fini (&op);
}
return 0;
}
static void check_scq16(const char *hex, int size, const char *text, int type) {
ut8 bytes[16];
int n = 0;
for (; hex[0] && hex[1] && n < 16; hex += 2) {
unsigned v;
sscanf (hex, "%2x", &v);
bytes[n++] = (ut8)v;
}
RAnalOp op;
assert (run (&op, bytes, n, 0x1000, "scq16", R_ARCH_OP_MASK_ALL));
if (strcmp (op.mnemonic, text) || op.type != (ut32)type || op.size != size) {
fprintf (stderr, "scq16 %s: got %s (%d, %d), expected %s (%d, %d)\n", hex, op.mnemonic, op.type, op.size, text, type, size);
assert (0);
}
r_anal_op_fini (&op);
}
int main(int argc, char **argv) {
/* SCQ16 (cpu=scq16): stand-alone unit, packet, vector op */
check_scq16 ("2005", 2, "ret", R_ANAL_OP_TYPE_RET);
check_scq16 ("889026e4", 4, "{ mv r1, r3 \xe2\x80\x96 beqz r5, #0, 0x0000100e }", R_ANAL_OP_TYPE_CJMP);
check_scq16 ("c0000001", 4, "v.const.movi? op9=0x001 A0 B0 F0,F0,F0", R_ANAL_OP_TYPE_UNK);
/* cmov.ge, mv 67 c8, j.d1 56 0x-3x by target (0x1000 - 0x32), lbu.post of the 23 page, an established vector name */
check_scq16 ("4213b0c1", 4, "cmov.ge r1, r6, #5, r7", R_ANAL_OP_TYPE_CMOV);
check_scq16 ("67c875", 3, "mv r21, r3", R_ANAL_OP_TYPE_MOV);
check_scq16 ("563fffce", 4, "j.d1 0x00000fce", R_ANAL_OP_TYPE_JMP);
check_scq16 ("23fa", 2, "lbu.post r3, [r8] += -1", R_ANAL_OP_TYPE_LOAD);
check_scq16 ("c0000002", 4, "v.mem.ld op9=0x002 A0 B0 F0,F0,F0", R_ANAL_OP_TYPE_UNK);
/* ctz (established), lw.post of 2 bytes with a 4-bit destination, the provisional sra? of 67 fc, a vector load with its fields */
check_scq16 ("41f38800", 4, "ctz r0, r0", R_ANAL_OP_TYPE_UNK);
check_scq16 ("21aa", 2, "lw.post r11, [r3] += 4", R_ANAL_OP_TYPE_LOAD);
check_scq16 ("67fc75", 3, "sra? r21, r21, #3", R_ANAL_OP_TYPE_SAR);
check_scq16 ("c8204002", 4, "v.mem.ld op9=0x002 A2 B0 F4,F1,F0", R_ANAL_OP_TYPE_UNK);
/* jr with a 5-bit register field, btst rd, rd, #pos with a 5-bit position */
check_scq16 ("6fec9b", 3, "jr r27", R_ANAL_OP_TYPE_RJMP);
check_scq16 ("67fb41", 3, "btst r1, r1, #26", R_ANAL_OP_TYPE_AND);
if (argc > 1 && !strcmp (argv[1], "--stream")) {
return stream (argc > 2? argv[2]: "md32");
}
unit_tests ();
return 0;
}
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Differentially verify native decoding against the emulated vendor binary."""
import argparse
import collections
import json
import random
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
from vendor_oracle import Oracle
from extract_opcodes import tables
def normalize(text, addr):
text = text.split(';')[0].strip().replace('#', '')
text = re.sub(r'\s+', ' ', text)
# The vendor prints sign-extended immediates as unsigned 64-bit hex.
text = re.sub(r'0x[0-9a-f]+', lambda m: '-0x%x' % ((1 << 64) - int(m.group(), 16))
if int(m.group(), 16) >= 1 << 63 else m.group(), text)
chunks = text.split(' | ')
for i, chunk in enumerate(chunks):
name = chunk.split(' ')[0]
if re.fullmatch(r'(?:beq|bne|bleu?|bltu?|j|jal|do)(?:16)?(?:\.d[12])?', name):
m = re.search(r'(-?0x[0-9a-f]+)$', chunk)
if m:
dest = (addr + int(m.group(), 16)) & 0xffffffff
chunks[i] = chunk[:m.start()] + hex(dest)
text = ' | '.join(chunks)
return re.sub(r'(?<!-)0x0*([0-9a-f]+)', lambda m: hex(int(m.group(), 16)), text)
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('objdump')
p.add_argument('--quick', action='store_true')
p.add_argument('--firmware', type=Path, help='Canonical big-endian MD32 code')
args = p.parse_args()
vendor = Oracle(args.objdump)
rng = random.Random(0x4d443332)
cases = []
firmware_results = {}
# All standalone 16-bit encodings, with a fixed second halfword, also
# exercise every pair-prefix encoding and illegal short instruction.
for h in range(0x10000) if not args.quick else range(0, 0x10000, 79):
cases.append(((h << 16) | 0x203f, 0x1000))
# Exercise every named opcode with randomized operands, including all
# reserved bits constrained by the vendor's mask.
for row in tables(vendor.elf):
for _ in range(12 if args.quick else 100):
word = row['opcode'] | (rng.getrandbits(32) & ~row['mask'])
if row['group'] != 1:
word = ((word & 0xffff) << 16) | 0x8000
cases.append((word, rng.randrange(0, 0x100000000) & ~1))
# Packed pairs: no byte-order assumptions or previous-decode state.
for _ in range(200 if args.quick else 10000):
prefix = rng.choice([0x4000, 0x6000]) | rng.randrange(0x1000)
suffix = rng.choice([0x2000, 0x6000]) | rng.randrange(0x2000) | rng.choice([0, 0x8000])
cases.append(((prefix << 16) | suffix, 0x1000))
if args.firmware:
data = args.firmware.read_bytes()
offset = 0
# Use the vendor's instruction boundaries, not those of our decoder.
while offset + 4 <= len(data):
code = data[offset:offset + 4]
cases.append((int.from_bytes(code, 'big'), offset))
size, text = vendor.decode(code, offset)
firmware_results[len(cases) - 1] = (size, text)
offset += size
if len(firmware_results) % 10000 == 0:
print("Scanned", len(firmware_results), "firmware instructions", flush=True)
inp = ''.join('%08x 4 %x\n' % item for item in cases)
native = subprocess.run([str(ROOT / 'test/test_md32'), '--stream'],
input=inp, text=True, capture_output=True, check=True).stdout.splitlines()
assert len(native) == len(cases)
failures = []
counts = collections.Counter()
for i, ((word, addr), line) in enumerate(zip(cases, native)):
fields = line.split('\t')
valid, size = map(int, fields[:2])
expected_size, expected = firmware_results.get(i) or vendor.decode(word.to_bytes(4, 'big'), addr)
expected_valid = 'illegal' not in expected
counts['valid' if expected_valid else 'illegal'] += 1
actual = fields[6]
# Normalise our absolute target formatting without adding PC twice.
actual = re.sub(r'0x[0-9a-f]+', lambda m: hex(int(m.group(), 16)), actual)
if bool(valid) != expected_valid or size != expected_size or (valid and
actual != normalize(expected, addr)):
failures.append((hex(word), hex(addr), size, actual, expected_size, expected))
if len(failures) <= 15:
print('MISMATCH:', failures[-1], flush=True)
if (i + 1) % 10000 == 0:
print('Compared', i + 1, 'instructions;', len(failures), 'mismatches', flush=True)
print(json.dumps(dict(cases=len(cases), mismatches=len(failures), **counts)), flush=True)
if failures:
Path('/tmp/md32-oracle-mismatches.json').write_text(json.dumps(failures, indent=2))
raise SystemExit(1)
if __name__ == '__main__':
main()
+251
View File
@@ -0,0 +1,251 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - pancake */
#include <assert.h>
#define R2_PLUGIN_INCORE
#include "../asm_md32.c"
static void check(RAsm *a, const char *input, const char *expected) {
char *result = r_asm_parse_pseudo (a, input);
if (!result || strcmp (result, expected)) {
fprintf (stderr, "%s: got %s, expected %s\n", input, result? result: "NULL", expected);
abort ();
}
free (result);
}
int main(void) {
RAsm *a = r_asm_new ();
assert (a && r_asm_plugin_add (a, &r_asm_plugin_md32));
assert (r_asm_use_parser (a, "md32"));
check (a, "mv16 r1, -0x1", "r1 = -0x1");
check (a, "\tmv16\tr1,\tr2 ", "r1 = r2");
check (a, "mv a0, r1, r2", "a0l = r1; a0h = r2");
check (a, "sethi r1, 0x1234", "r1 = (0x1234 << 16)");
check (a, "add r1, r2, r3", "r1 = (r2 + r3)");
check (a, "add r1, r10, r3", "r1 = (r10 + r3)");
check (a, "add r0, r0, r0, 0x1", "r0 = (r0 + r0 + 0x1)");
check (a, "addsp16 r14, -0x4", "r14 += -0x4");
check (a, "sub16 r1, r2", "r1 -= r2");
check (a, "and16 r8, r8, 0xffff", "r8 = (r8 & 0xffff)");
check (a, "or16 r1, r2", "r1 |= r2");
check (a, "xor16 r1, r2", "r1 ^= r2");
check (a, "mul r1, r2, r3", "r1 = (r2 * r3)");
check (a, "sll16 r1, 0x2", "r1 <<= 0x2");
check (a, "srl r1, r2, 0x3", "r1 = ((uint32_t)r2 >> 0x3)");
check (a, "sra16 r1, 0x1", "r1 = ((int32_t)r1 >> 0x1)");
check (a, "clz16 r1, r2", "r1 = clz32(r2)");
check (a, "clb r1, r2", "r1 = clz32((int32_t)r2 < 0 ? ~r2 : r2)");
check (a, "max16 r1, r2", "r1 = max_s32(r1, r2)");
check (a, "min r1, r2, r3", "r1 = min_s32(r2, r3)");
check (a, "cinv r1, r2, r3", "r1 = ((int32_t)r2 < 0 ? -r3 : r3)");
check (a, "bclr16 r1, r2", "r1 &= ~(1U << r2)");
check (a, "bset r1, r2, 0x3", "r1 = (r2 | (1U << 0x3))");
check (a, "btgl r1, r2, 0x3", "r1 = (r2 ^ (1U << 0x3))");
check (a, "lb r2, -0x1(r1)", "r2 = sext8(dmem8[r1 - 0x1])");
check (a, "lbu r2, 0x1(r1)", "r2 = dmem8[r1 + 0x1]");
check (a, "lh16 r2, -0x2(r1)", "r2 = sext16(dmem16[r1 - 0x2])");
check (a, "lhu r2, 0x2(r1)", "r2 = dmem16[r1 + 0x2]");
check (a, "lw r2, (r1+=0x8)", "r2 = dmem32[r1]; r1 += 0x8");
check (a, "sw r2, -0x4(r14)", "dmem32[r14 - 0x4] = r2");
check (a, "sb16 r1, (r1+=-0x4)", "dmem8[r1] = r1; r1 += -0x4");
check (a, "sh r2, 0x4(r1)", "dmem16[r1 + 0x4] = r2");
check (a, "beq16 0x0, r1, sym.target", "if (0x0 == r1) goto sym.target");
check (a, "bne r1, r2, 0x1000", "if (r1 != r2) goto 0x1000");
check (a, "blt r1, -0x1, sym.target", "if ((int32_t)r1 < (int32_t)-0x1) goto sym.target");
check (a, "bleu r1, r2, sym.target", "if ((uint32_t)r1 <= (uint32_t)r2) goto sym.target");
check (a, "j16 r15", "return");
check (a, "j r2", "goto r2");
check (a, "jal sym.target", "sym.target ()");
check (a, "nop16", "nop");
check (a, "mv16.l r1, r2", "r1 = r2");
check (a, "nop16 | nop16.l", "parallel { nop; nop; }");
check (a, "xor16 r0, r0 | j16 r15", "parallel { r0 ^= r0; return; }");
const char *unchanged[] = {
"", ".word 0x400fd5c0", ".hword 0x8116", "unknown r1, r2",
"addx r1, r2, r3", "rnds r1, r2, r3", "maxd r1, r2, r3",
"j16.d2 r15", "beq.d1 r1, r2, 0x1000", "do r1, 0x1000",
"lw r1, (r1+=0x4)", "lw r1, (r2+=)", "lw r1, 0x4(r2",
"lw r1, 0x4(r2))", "mv r1,", "add r1,,r2", "add r1,r2,r3,r4,r5",
"mv r1, r2)", "mv r1, (r2", "save 0x1", "restore 0x1", "b0 0x1000",
"mv r1, r2, r3", "mv r1, r2 | mv r3, r4 | mv r5, r6",
};
size_t i;
for (i = 0; i < R_ARRAY_SIZE (unchanged); i++) {
check (a, unchanged[i], unchanged[i]);
}
r_arch_config_set_cpu (a->config, "usip");
check (a, "b 0x400e08d0", "goto 0x400e08d0");
check (a, "b.s 0x1000", "goto 0x1000");
check (a, "call sym.target", "sym.target ()");
check (a, "call.abs 0x1ccde9b0", "0x1ccde9b0 ()");
check (a, "call r2", "r2 ()");
check (a, "bne r3, #0x1, 0x1000", "if (r3 != 0x1) goto 0x1000");
check (a, "bltu r1, r2, 0x1000", "if ((uint32_t)r1 < (uint32_t)r2) goto 0x1000");
check (a, "blt.d r1, #-0x1, 0x1000", "if ((int32_t)r1 < (int32_t)-0x1) goto 0x1000");
check (a, "beqz r0, 0x14", "if (r0 == 0) goto 0x14");
check (a, "bz.s r2, 0x4d78", "if (r2 == 0) goto 0x4d78");
check (a, "ret", "return");
check (a, "ret.d", "return");
check (a, "movi r1, #0x230", "r1 = 0x230");
check (a, "mov32 r15, #0x1d07005c", "r15 = 0x1d07005c");
check (a, "lsr r15!, r15, #0x1a", "r15! = ((uint32_t)r15 >> 0x1a)");
check (a, "cmov r0, r1, r2", "r0 = (r1 != 0) ? r2 : r0");
check (a, "ld.w r0, [r8+0x0]", "r0 = dmem32[r8 + 0x0]");
check (a, "ld.b r0, [r15+0xa]", "r0 = sext8(dmem8[r15 + 0xa])");
check (a, "ld.hu r1, [r2-0x4]", "r1 = dmem16[r2 - 0x4]");
check (a, "ld.w r3, [sp+0x8]", "r3 = dmem32[sp + 0x8]");
check (a, "st.w r1, [r15+0x4]", "dmem32[r15 + 0x4] = r1");
check (a, "st.b r1, [r2]+", "dmem8[r2] = r1; r2 += 1");
check (a, "loop r3, last=0x00002488", "repeat r3 times until last=0x00002488");
check (a, "save w=0", "push_callee_saved(w=0)");
// Displaced tails and unmodelled operations keep their text (the store of an immediate has a pseudo form below).
// system, port I/O, CSR, slot and stack forms of the uSIP table.
check (a, "mfcsr r15!, #0x01", "r15! = csr[0x01]");
check (a, "mtcsr #0x20, r4!", "csr[0x20] = r4!");
check (a, "ior r1, [r2]", "r1 = io[r2]");
check (a, "iow r1, [r3]", "io[r3] = r1");
check (a, "jr? r2", "goto r2");
check (a, "ret?", "return");
check (a, "rti", "return_from_interrupt");
check (a, "cop r0", "coprocessor(r0)");
check (a, "nop32", "nop");
check (a, "addsp #-0x14", "sp -= 0x14");
check (a, "addsp #0x10", "sp += 0x10");
check (a, "ld.d? r0:r1, [r8+0x1f0]", "r0:r1 = dmem64[r8 + 0x1f0]");
check (a, "st.b? r0, [r11!-0x1]", "dmem8[r11! - 0x1] = r0");
check (a, "ldslot r0!, #0x1c", "r0! = slot[0x1c]");
check (a, "stslot r5, #0x3", "slot[0x3] = r5");
check (a, "lea? r8, [sp+0x14]", "r8 = &[sp+0x14]");
check (a, "call? 0x000017c0", "0x000017c0 ()");
check (a, "beqz.d? r4, 0x000007d4", "if (r4 == 0) goto 0x000007d4");
check (a, "sne r7, r7, #0x0", "r7 = (r7 != 0x0)");
check (a, "movhi? r9, #0x7ff00000", "r9 = 0x7ff00000");
check (a, "extu r10, r8!, #0xb, #0x14", "r10 = ((r8! >> 0x14) & ((1 << 0xb) - 1))");
check (a, "cmov r15, r14!, r0!", "r15 = (r14! != 0) ? r0! : r15");
// table update: immediate loops, 48/64-bit b<cond>.l, the set-on-compare family, signed 64-bit pairs, the closed
// negative offsets, the 16-bit first-slot st.h, movhi, jr, the high-bank bit ops and the funnel shift.
check (a, "loop# #4, last=0x00001c06", "repeat 4 times until last=0x00001c06");
check (a, "blt.l r2, #0x1, 0x00001582", "if ((int32_t)r2 < (int32_t)0x1) goto 0x00001582");
check (a, "beq.l? r4, #0x1, 0x00002226", "if (r4 == 0x1) goto 0x00002226");
check (a, "ble.l?? r4, #0x1, 0x00002226", "if ((int32_t)r4 <= (int32_t)0x1) goto 0x00002226");
check (a, "bneu.l? r1, #0x3, 0x1000", "if ((uint32_t)r1 != (uint32_t)0x3) goto 0x1000");
check (a, "sle r4, r9, #0x10", "r4 = ((int32_t)r9 <= (int32_t)0x10)");
check (a, "sgt r5, r0, #0x190", "r5 = ((int32_t)r0 > (int32_t)0x190)");
check (a, "ld.d r2:r3, [r9-0xa0]", "r2:r3 = dmem64[r9 - 0xa0]");
check (a, "st.d r8:r9, [r5-0x24]", "dmem64[r5 - 0x24] = r8:r9");
check (a, "ld.h r0, [r8-0x32]", "r0 = sext16(dmem16[r8 - 0x32])");
check (a, "ld.w r2, [r14-0x8]", "r2 = dmem32[r14 - 0x8]");
check (a, "st.h r8, [r0]", "dmem16[r0] = r8");
check (a, "movhi r0, #0x00020000", "r0 = 0x00020000");
check (a, "jr r2", "goto r2");
check (a, "xor r1, r8!", "r1 ^= r8!");
check (a, "bset r8!, r0!, #0x0", "r8! = (r0! | (1U << 0x0))");
check (a, "osl r5, r2, #1", "r5 = ((r2 << 1) | r5)");
check (a, "mfacc? r7, A0.lo", "r7 = A0.lo");
check (a, "ldslot r0!, #0x22", "r0! = slot[0x22]");
check (a, ".unit 0x400a (ld.b @ 0x0000041e)", ".unit 0x400a (ld.b @ 0x0000041e)");
check (a, ".hword 0xffff", ".hword 0xffff");
check (a, ".op4imm r0, r1, #0x6", ".op4imm r0, r1, #0x6");
// table update (634 forms): the bit-field family, nop.ctl, mtacc?, movi? #-32768, the register bset/bext, sgt #-1,
// addi #32, the lsl / sext16 / zext16 renames, the r15-based loads, the S0F1 branches and the format-only readings.
check (a, "fxtu r0, r0, #0x1, #0xf", "r0 = ((r0 >> 0xf) & ((1 << 0x1) - 1))");
check (a, "fxts r2!, r0, #0xf, #0x1", "r2! = sext(0xf)((r0 >> 0x1) & ((1 << 0xf) - 1))");
check (a, "fup r1, r0, #0x8, #0x8", "r1 = ((r1 & ~(((1 << 0x8) - 1) << 0x8)) | ((r0 & ((1 << 0x8) - 1)) << 0x8))");
check (a, "fup? r0, r1, #0x1c, #0x4", "r0 = ((r0 & ~(((1 << 0x1c) - 1) << 0x4)) | ((r1 & ((1 << 0x1c) - 1)) << 0x4))");
check (a, "nop.ctl", "nop");
check (a, "mtacc? A0, r2!:r4!", "A0 = r2!:r4!");
check (a, "movi? r8, #-32768", "r8 = -32768");
check (a, "bset r2, r2, r8", "r2 = (r2 | (1U << r8))");
check (a, "bext r0, r0, r8", "r0 = ((r0 >> r8) & 1)");
check (a, "sgt r0, r3, #-1", "r0 = ((int32_t)r3 > (int32_t)-1)");
check (a, "addi r0, #32", "r0 += 32");
check (a, "lsl? r0, r0, #0x1", "r0 = (r0 << 0x1)");
check (a, "sext16 r3, r2", "r3 = sext16(r2)");
check (a, "zext16? r3, r4", "r3 = (r4 & 0xffff)");
check (a, "asr16 r1, r2, #10", "r1 = sext16(r2 << 10) >> 10");
check (a, "ld.h? r8, [r15+0x150]", "r8 = dmem16[r15 + 0x150]");
check (a, "ld.w? r1, [r15+0x407]", "r1 = dmem32[r15 + 0x407]");
check (a, "st.w r1, [r8+0x11c4]", "dmem32[r8 + 0x11c4] = r1");
check (a, "bne.d? r15!, #0x1, 0x00000f1c", "if (r15! != 0x1) goto 0x00000f1c");
check (a, "beqz.d? r0, 0x0000200c", "if (r0 == 0) goto 0x0000200c");
check (a, "bleu.d r7, r1, 0x00000e3c", "if ((uint32_t)r7 <= (uint32_t)r1) goto 0x00000e3c");
check (a, "bne.u8.d r1, #0x3, 0x1000", "if (r1 != 0x3) goto 0x1000");
check (a, "mov32 r3, #0x7fff7fff", "r3 = 0x7fff7fff");
check (a, ".ldF0? r0!, [r7+0x240]", ".ldF0? r0!, [r7+0x240]");
check (a, ".3eX0? r6, 0x6e14 0xd000", ".3eX0? r6, 0x6e14 0xd000");
check (a, ".op4t0 r0, [r8+0x19]", ".op4t0 r0, [r8+0x19]");
check (a, ".hi 0x35ff", ".hi 0x35ff");
// table update (687 forms): the 16-bit shifts in place, ret.s.d, loop.b1?, the R-br0 branches, bz.s with bit 3 = 0, the
// byte loads/stores, the sp adjustment pair, the ss loops, the BF_SEL cells and the format-only immediate store.
check (a, "lsl r0, #0x2", "r0 <<= 0x2");
check (a, "asr? r2, #0xf", "r2 = ((int32_t)r2 >> 0xf)");
check (a, "ret.s.d", "return");
check (a, "loop.b1? r5, last=0x0000422c", "repeat r5 times until last=0x0000422c");
check (a, "loop.b1?? r1?ss=2, last=0x0000261e", "repeat r1?ss=2 times until last=0x0000261e");
check (a, "bne.u8.d r1, #0x7e, 0x00001b32", "if (r1 != 0x7e) goto 0x00001b32");
check (a, "bgeu.d r0, #0x2, 0x00000aa0", "if ((uint32_t)r0 >= (uint32_t)0x2) goto 0x00000aa0");
check (a, "bge.d r0, #0x0, 0x00000652", "if ((int32_t)r0 >= (int32_t)0x0) goto 0x00000652");
check (a, "bz.s r10, 0x00004f7e", "if (r10 == 0) goto 0x00004f7e");
check (a, "ld.bu r1, [r8+0x628]", "r1 = dmem8[r8 + 0x628]");
check (a, "st.b r0, [r14+0x66]", "dmem8[r14 + 0x66] = r0");
check (a, "ld.b r0, [r0-0xef]", "r0 = sext8(dmem8[r0 - 0xef])");
check (a, "ld.hu? r0, [r14+0x98]", "r0 = dmem16[r14 + 0x98]");
check (a, "add r14, r14, #0x4", "r14 = (r14 + 0x4)");
check (a, "sub r14, #0x1f8", "r14 -= 0x1f8");
check (a, "ldslot r8, #0x33", "r8 = slot[0x33]");
check (a, "movi? r2, #-32767", "r2 = -32767");
check (a, "fxts? r1, r0, #0x8, #0x8", "r1 = sext(0x8)((r0 >> 0x8) & ((1 << 0x8) - 1))");
check (a, "mtcsr? #0x39, r2!", "csr[0x39] = r2!");
check (a, "ld.w? r2, [r0!+0x305]", "r2 = dmem32[r0! + 0x305]");
check (a, ".stimm16? [r0], #0x36688", ".stimm16? [r0], #0x36688");
// table update (726 forms): the loop count as collected, loop #N? for the m = 1 heads, the split slot forms, the 16-bit lea,
// the stores of an immediate (also to [sp]), the absolute store, the r15-based add? / ld.h?, the op 2 shifts of r8-r15, the
// h = 1 half-word cells, the frame allocation sub r14, the 3- and 4-unit bz.s and the format-level op2z? / op72? / saturate?.
check (a, "loop# #15, last=0x1cb2d0f6", "repeat 15 times until last=0x1cb2d0f6");
check (a, "loop#? #256?, last=0x1cbec570", "repeat 256 times until last=0x1cbec570");
check (a, "stslot? r14!, #0x16", "slot[0x16] = r14!");
check (a, "ldslot? r2, #0x4", "r2 = slot[0x4]");
check (a, "lea r9, [sp+76]", "r9 = &[sp+76]");
check (a, "st.w [sp], #0x35", "dmem32[sp] = 0x35");
check (a, "st.h? [r3], #0x8000", "dmem16[r3] = 0x8000");
check (a, "st.w [r0], #0x28002", "dmem32[r0] = 0x28002");
check (a, "st.abs r0, [#0xe20014]", "dmem32[0xe20014] = r0");
check (a, "add? r1, r15, #0x420", "r1 = (r15 + 0x420)");
check (a, "ld.h? r7, [r15+0xe8]", "r7 = dmem16[r15 + 0xe8]");
check (a, "lsl r8, #0x1", "r8 <<= 0x1");
check (a, "asr r15, #0xf", "r15 = ((int32_t)r15 >> 0xf)");
check (a, "ld.h r10, [r14]", "r10 = sext16(dmem16[r14])");
check (a, "ld.h r10, [r12]+", "r10 = sext16(dmem16[r12]); r12 += 2");
check (a, "st.h r8, [r11]+", "dmem16[r11] = r8; r11 += 2");
check (a, "sub r14, #0x5b0", "r14 -= 0x5b0");
check (a, "bz.s r4, 0x1cad0504", "if (r4 == 0) goto 0x1cad0504");
check (a, "ld.hu r5, [r5+r4]", "r5 = dmem16[r5 + r4]");
check (a, "add r1, r1, #0x10000", "r1 = (r1 + 0x10000)");
check (a, "movi r3, #-32768", "r3 = -32768");
check (a, "mtcsr #0x35, r5!", "csr[0x35] = r5!");
check (a, "op2z? r0", "op2z? r0");
check (a, "op72? r1, #0x0, asr#0x1", "op72? r1, #0x0, asr#0x1");
check (a, "sat40.a? A0, A0", "sat40.a? A0, A0");
// table update (760 forms): loop x16, the loop #N? count, saturate without the ?, the 17-bit movi, the class-E addi, the 16-bit
// ld.w / st.w with +-4, the slot pairs, the 22-bit b<cond>.l, the op c branches with the F' = 0 tail and movhi of the small cell.
check (a, "loop r2, last=0x0000cf3e, x16", "repeat r2 times until last=0x0000cf3e (x16)");
check (a, "loop? r1?ss=2, last=0x0000261e, x16", "repeat r1?ss=2 times until last=0x0000261e (x16)");
check (a, "loop#? #16?, last=0x0000526c", "repeat 16 times until last=0x0000526c");
check (a, "saturate r1, r1", "r1 = saturate(r1)");
check (a, "saturate? r1, r1", "r1 = saturate(r1)");
check (a, "movi r2, #-0x10000", "r2 = -0x10000");
check (a, "addi r0, #-0x4", "r0 += -0x4");
check (a, "ld.w r2, [r1-0x4]", "r2 = dmem32[r1 - 0x4]");
check (a, "st.w r6, [r5-0x4]", "dmem32[r5 - 0x4] = r6");
check (a, "stslot? r2, #0x1", "slot[0x1] = r2");
check (a, "stslot r8!, #0x25", "slot[0x25] = r8!");
check (a, "add r1, r14, #0x594", "r1 = (r14 + 0x594)");
check (a, "bge.l r9, #0x1, 0x0001d6f6", "if ((int32_t)r9 >= (int32_t)0x1) goto 0x0001d6f6");
check (a, "beqz? r10!, 0x00007324", "if (r10! == 0) goto 0x00007324");
check (a, "bltu r3, r4, 0x00001000", "if ((uint32_t)r3 < (uint32_t)r4) goto 0x00001000");
check (a, "bz.s r0, 0x0000118a", "if (r0 == 0) goto 0x0000118a");
check (a, "movhi r1, #0x10000000", "r1 = 0x10000000");
check (a, "ld.hu r1, [r8!+0x810]", "r1 = dmem16[r8! + 0x810]");
r_asm_free (a);
puts ("MD32 pseudo syntax, Harvard addressing, parallel bundles and conservative fallbacks passed");
return 0;
}
+757
View File
@@ -0,0 +1,757 @@
#!/usr/bin/env python3
"""Exercise the actual plugin in r2 and the supplied uSIP firmware inventory."""
import collections
import json
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
from usip_symbols import functions
R2 = os.environ.get('R2', 'r2')
ext = subprocess.check_output([R2, '-H', 'R2_LIBEXT'], text=True).strip()
PLUGIN = ROOT / ('arch_md32.' + ext)
ASM_PLUGIN = ROOT / ('asm_md32.' + ext)
def r2(commands, size=128):
setup = 'e scr.color=0;L %s;L %s;e asm.arch=md32;e cfg.bigendian=true;' % (PLUGIN, ASM_PLUGIN)
proc = subprocess.run([R2, '-NN', '-q', '-c', setup + commands + ';q',
'malloc://%d' % size], capture_output=True, text=True, check=True)
return proc.stdout.strip().splitlines()
def integration():
# Load the actual RAsmPlugin and exercise the disassembly display path.
assert r2('e asm.parser')[0] == 'md32'
text = '\n'.join(r2('e asm.pseudo=true;wx 981198220400e321a03f;pD 10'))
assert 'r1 = 0x1' in text and 'r1 = (r2 + r3)' in text and 'return' in text
text = '\n'.join(r2('e asm.pseudo=true;wx 060fff2106c008214600203f;pD 12'))
assert 'r2 = dmem8[r1 - 0x1]' in text
assert 'r2 = dmem32[r1]; r1 += 0x8' in text
assert 'parallel { r0 ^= r0; return; }' in text
# uSIP packets: slot-0 control flow, delay slot, interleaved S S O O layout; the
# compared register of a 32-bit branch carries its bank bit (r15!).
text = '\n'.join(r2('e asm.cpu=usip;e asm.pseudo=true;wx 7e0cc00c5c3907f0541c805c@0x40c;pD 12@0x40c', 0x8000))
assert 'if (r15! != 0) goto 0x00000418' in text and 'goto 0x0000041e' in text and 'r15! = 0x1d07005c' in text
text = '\n'.join(r2('e asm.cpu=usip;e asm.pseudo=true;wx 3e015000400ac000def9@0x41e;pD 10@0x41e', 0x8000))
assert 'dmem32[r15! + 0x0] = r0' in text and 'r0 = 0x0' in text and 'return' in text
text = '\n'.join(r2('e asm.cpu=usip;e asm.pseudo=true;wx 44614a315000d000@0x68c;pD 8@0x68c', 0x8000))
assert 'dmem32[r2 + 0x0] = r6' in text and '.unit 0x5000 (st.w @ 0x0000068c)' in text and '.unit 0xd000 (st.w @ 0x0000068e)' in text
ops = json.loads(r2('wx 981198220400e321a03f;aoj 4')[0])
assert [o['size'] for o in ops] == [2, 2, 4, 2]
assert [o['type'] for o in ops] == ['mov', 'mov', 'add', 'ret']
assert ops[2]['opex']['operands'][2] == dict(type='reg', value='r3')
regs = json.loads(r2('wx 981198220400e321;aei;ar r3=4;aes;aes;aes;arj')[0])
assert regs['r1'] == 6 and regs['r2'] == 2 and regs['pc'] == 8
# Signed byte load, then a word store in the Harvard data-memory view.
regs = json.loads(r2('wx 06200012;wx 80 @ 32;aei;ar r2=32;aes;arj')[0])
assert regs['r1'] == 0xffffff80
# Signed and unsigned comparisons differ for 0xffffffff versus one.
for opcode, expected_pc in [('1a000421', 8), ('1b000421', 4)]:
regs = json.loads(r2('wx %s;aei;ar r2=0xffffffff;ar r1=1;aes;arj' % opcode)[0])
assert regs['pc'] == expected_pc, (opcode, regs)
regs = json.loads(r2('wx 1f80000f;aei;ar r15=32;aes;arj')[0])
assert regs['pc'] == 32 and regs['r15'] == 4
# Comparisons must also work when both operands cross the sign boundary.
for opcode, expected_pc in [('1a000421', 4), ('1b000421', 8)]:
regs = json.loads(r2('wx %s;aei;ar r2=0;ar r1=0xffffffff;aes;arj' % opcode)[0])
assert regs['pc'] == expected_pc, (opcode, regs)
# Delay-slot fallthrough must skip a two-byte instruction, not a fixed 4.
bbs = json.loads(r2('wx 14000621800005000000a03fa03f;af;afbj', 14)[0])
assert bbs[0]['size'] == 6 and bbs[0]['jump'] == 12 and bbs[0]['fail'] == 6
# bnez: conditional branch with its tail adjacent; fail = next packet.
ops = json.loads(r2('e asm.cpu=usip;wx 7e0cc00c@0x40c;aoj 1@0x40c', 0x8000)[0])
assert ops[0]['size'] == 4 and ops[0]['type'] == 'cjmp' and ops[0]['opcode'] == 'bnez r15!, 0x00000418'
assert ops[0]['jump'] == 0x418 and ops[0]['fail'] == 0x410 and ops[0]['cond'] == 'ne'
assert ops[0]['opex']['form'] == 'bnez' and ops[0]['opex']['units'] == [0x40c, 0x40e]
# b.s with the packet open: the 48-bit mov32 that follows is its delay slot (one op); the target is validated on the
# packet that starts there (the next two packets are part of the vector).
ops = json.loads(r2('e asm.cpu=usip;wx 5c3907f0541c805c07f0141cc06c@0x410;aoj 2@0x410', 0x8000)[0])
assert [o['size'] for o in ops] == [2, 6] and [o['type'] for o in ops] == ['jmp', 'mov']
assert ops[0]['jump'] == 0x41e and ops[0]['delay'] == 1 and ops[0]['opex']['packet']['delay']
assert ops[1]['opcode'] == 'mov32 r15!, #0x1d07005c' and ops[1]['opex']['delay_slot'] and ops[1]['val'] == 0x1d07005c
# call with S = 0 tail at +2 (S S O O): call, the parallel op, then the two tails as .unit; delay = 3 ops.
ops = json.loads(r2('e asm.cpu=usip;wx 400e40a07052c0fc@0xf18;aoj 4@0xf18', 0x8000)[0])
assert [o['size'] for o in ops] == [2, 2, 2, 2] and ops[0]['type'] == 'call'
assert ops[0]['jump'] == 0xf6a and ops[0]['fail'] == 0xf20 and ops[0]['delay'] == 3
assert ops[2]['opcode'].startswith('.unit 0x7052 (call @ 0x00000f18)') and ops[2]['opex']['tail']
assert ops[0]['opex']['units'] == [0xf18, 0xf1c]
# 48-bit absolute call (x008 X hi lo X' lo'): hi/lo by rule R at +2/+3, one 4-byte .unit; 5 ops in the packet.
ops = json.loads(r2('e asm.cpu=usip;wx 0608110b733769b012079407@0x1058;aoj 5@0x1058', 0x8000)[0])
assert ops[0]['opcode'] == 'call.abs 0x1ccde9b0' and ops[0]['jump'] == 0x1ccde9b0 and ops[0]['size'] == 2
assert ops[0]['opex']['units'] == [0x1058, 0x105c, 0x105e] and ops[0]['delay'] == 4 and ops[0]['fail'] == 0x1064
assert [o['size'] for o in ops] == [2, 2, 4, 2, 2]
assert ops[2]['opcode'] == '.unit 0x733769b0 (call.abs @ 0x00001058)'
# hardware loop: the end address is a reference, not a branch (its guard validates on the packet that starts there).
ops = json.loads(r2('e asm.cpu=usip;wx 060ff010e227e227e227e227e227e227e227@0x2478;aoj 1@0x2478', 0x8000)[0])
assert ops[0]['opcode'] == 'loop r3, last=0x00002488' and ops[0]['ptr'] == 0x2488 and 'jump' not in ops[0]
assert ops[0]['opex']['target_guard'] == 'validated'
# (the 760-form table reads the former loop vector as the half-word load with the deferred F' = 0 tail and any base: rf0dall)
ops = json.loads(r2('e asm.cpu=usip;wx 060f02277010786028104390c100c123e227@0x2478;aoj 6@0x2478', 0x8000)[0])
assert [o['opcode'] for o in ops[:3]] == ['movi r3, #0x1', 'cmov r0, r1, r2', 'ld.hu r1, [r8!+0x810]'] and ops[2]['opex']['form'] == 'rf0dall_ld_hu'
assert ops[5]['opcode'] == 'ld.w r9!, [r1+0x100]' and ops[2]['opex']['units'] == [0x247c, 0x2480]
# ret.d: the next op is its delay slot; ret closes a packet by itself.
ops = json.loads(r2('e asm.cpu=usip;wx 5ef9a10ddef9@0x10a2;aoj 3@0x10a2', 0x8000)[0])
assert ops[0]['opcode'] == 'ret.d' and ops[0]['type'] == 'ret' and ops[0]['delay'] == 1
assert ops[2]['opcode'] == 'ret' and ops[2]['type'] == 'ret' and ops[2]['size'] == 2
# 2-unit packet opened by 4X?d without a named pairing: context rule bz.s (P4).
ops = json.loads(r2('e asm.cpu=usip;wx 44cd8207@0x4d46;aoj 1@0x4d46', 0x8000)[0])
assert ops[0]['opcode'] == 'bz.s r2, 0x00004d78' and ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0x4d78
# The maintainer's original call vector at the end of the buffer: a truncated packet still yields the call
# (aoj would see r2's 0xff padding as a closing unit, so use the disassembly path, which gets the exact bytes).
text = '\n'.join(r2('e asm.cpu=usip;wx 000e78aa@0x7ffc;pD 4@0x7ffc', 0x8000))
assert 'call 0x000088a6' in text
# save/restore/ret 16-bit forms and an unknown unit.
ops = json.loads(r2('e asm.cpu=usip;wx 5f059ef9ff45ffff;aoj 4', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['save w=0', 'ret', 'restore w=4', '.fill 0xffff']
assert [o['type'] for o in ops] == ['rpush', 'ret', 'pop', 'unk'] and [o['size'] for o in ops] == [2, 2, 2, 2]
# Table update: port I/O, the 32-bit ret of the sys shape, nop32, register jump.
ops = json.loads(r2('e asm.cpu=usip;wx 0000c674001ac0024014c00040ffc000@0x1000;aoj 4@0x1000', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['add r0, r0, #0x33a', 'movi r1, #0x1', 'iow r1, [r0]', 'ret']
assert [o['type'] for o in ops] == ['add', 'mov', 'io', 'ret'] and ops[2]['ireg'] == 'r0' and ops[3]['opex']['form'] == 'ret32'
ops = json.loads(r2('e asm.cpu=usip;wx 0030c7004007fac04614c000@0x1100;aoj 3@0x1100', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['add r3, r0, #0x380', 'nop32', 'iow r1, [r3]'] and ops[1]['type'] == 'nop'
ops = json.loads(r2('e asm.cpu=usip;wx 4414c400@0x524;aoj 1@0x524', 0x8000)[0])
assert ops[0]['opcode'] == 'ior r1, [r2]' and ops[0]['type'] == 'io' and ops[0]['opex']['form'] == 'ior'
ops = json.loads(r2('e asm.cpu=usip;wx 402fc000@0x5d6;aoj 1@0x5d6', 0x8000)[0])
assert ops[0]['opcode'] == 'jr r2' and ops[0]['type'] == 'rjmp' and ops[0]['reg'] == 'r2'
# Stack adjust (16-bit closer, scaled signed immediate) and the 64-bit pair load (rB:rB+1, 2 * off11).
ops = json.loads(r2('e asm.cpu=usip;wx bdd8@0x100;aoj 1@0x100', 0x8000)[0])
assert ops[0]['opcode'] == 'addsp #-0x14' and ops[0]['type'] == 'add' and ops[0]['size'] == 2
ops = json.loads(r2('e asm.cpu=usip;wx 5005c0f8@0x1200;aoj 1@0x1200', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.d r0:r1, [r8+0x1f0]' and ops[0]['type'] == 'load' and ops[0]['refptr'] == 8 and ops[0]['disp'] == 0x1f0
# First-slot context forms (rule R7 / lea?) and the movhi value (20 bits << 12, high-bank destination).
ops = json.loads(r2('e asm.cpu=usip;wx 45045ad65002c002@0x9b0;aoj 1@0x9b0', 0x8000)[0])
assert ops[0]['opcode'] == 'stslot r0!, #0x4' and ops[0]['type'] == 'store' and ops[0]['opex']['form'] == 'stslot_S0'
ops = json.loads(r2('e asm.cpu=usip;wx 5d004b074fe3f867@0xebc;aoj 2@0xebc', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r0!, [r14-0x20]', 'cmov r7, r5, r0!'] and ops[0]['opex']['form'] == 'mem_ld_w_t4_F1S0'
ops = json.loads(r2('e asm.cpu=usip;wx 1028b425@0x16f6;aoj 1@0x16f6', 0x8000)[0])
assert ops[0]['opcode'] == 'lea r8, [sp+0x14]' and ops[0]['type'] == 'lea' and ops[0]['opex']['form'] == 'lea_sp_S0'
ops = json.loads(r2('e asm.cpu=usip;wx 3f980381fe015f05@0x4da8;aoj 1@0x4da8', 0x8000)[0])
assert ops[0]['opcode'] == 'movhi r9!, #0x7ff00000' and ops[0]['val'] == 0x7ff00000 and ops[0]['opex']['units'] == [0x4da8, 0x4dac]
# 48-bit control packets [head][op16][tail] with the tail at +2 and a target guard: the loop end at the next
# packet validates; beqz.d validates when the forward target is a packet start (scan of the bytes at hand) and
# is data when it is not; the call with the F' = 0 tail needs the function table, so it is read unverified (call?).
ops = json.loads(r2('e asm.cpu=usip;wx 4a0f5e0db0060fb34563166cc400@0x1300;aoj 1@0x1300', 0x8000)[0])
assert ops[0]['opcode'] == 'loop r5, last=0x00001306' and ops[0]['ptr'] == 0x1306 and ops[0]['opex']['form'] == 'loop_F0'
assert ops[0]['opex']['units'] == [0x1300, 0x1304] and ops[0]['opex']['target_guard'] == 'validated'
pk = 'wx 240c2132501800046520d0026730c0007ffe4f336ff1d0044871d000@0x752;'
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 1@0x752', 0x8000)[0])
assert ops[0]['opcode'] == 'beqz.d r2!, 0x0000076a' and ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0x76a and ops[0]['delay'] == 4
assert ops[0]['opex']['units'] == [0x752, 0x756] and ops[0]['opex']['target_guard'] == 'validated'
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'wx 4f33@0x768;aoj 1@0x752', 0x8000)[0])
assert ops[0]['opcode'].startswith('.dsp') and 'jump' not in ops[0]
ops = json.loads(r2('e asm.cpu=usip;wx 7ffe557ebcd1@0x1af0;aoj 2@0x1af0', 0x8000)[0])
assert ops[0]['opcode'] == 'call? 0x000017c0' and ops[0]['type'] == 'call' and ops[0]['jump'] == 0x17c0 and ops[0]['delay'] == 2
assert ops[0]['opex']['form'] == 'call_F0' and ops[0]['opex']['target_guard'] == 'unverified' and ops[0]['opex']['confidence'] == 'medium'
assert ops[1]['opcode'] == 'st.w r7, [r10+20]' and ops[1]['opex']['delay_slot']
text = '\n'.join(r2('e asm.cpu=usip;e asm.pseudo=true;wx 0000c674001ac0024014c00040ffc000@0x1000;pD 16@0x1000;wx 5005c0f8@0x1200;pD 4@0x1200;wx bdd8@0x100;pD 2@0x100', 0x8000))
assert 'io[r0] = r1' in text and 'r0:r1 = dmem64[r8 + 0x1f0]' in text and 'sp -= 0x14' in text
# Table update (594 forms): the immediate hardware loop with its target validated (the next packet is visible),
# with the S = 0 tail at +2 (the bytes up to the loop end validate it) and unverified (loop#?: the end lies beyond
# the bytes at hand). The count is the collected field (head bits 13:5) since the 726-form table.
ops = json.loads(r2('e asm.cpu=usip;wx 006f600cc207021640b0c30e@0x1bfa;aoj 2@0x1bfa', 0x8000)[0])
assert ops[0]['opcode'] == 'loop# #3, last=0x00001c06' and ops[0]['ptr'] == 0x1c06 and ops[0]['val'] == 3 and 'jump' not in ops[0]
assert ops[0]['opex']['form'] == 'loop_imm_F0S0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['size'] == 4
assert ops[1]['opcode'] == 'movi r1, #0x0'
pk = '404f4a26602ec2801028b425048778095ec1880740904000c005586b88214c7de00e04087bf9480b9813dc11f246140b'
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x16ee;aoj 2@0x16ee' % pk, 0x8000)[0])
assert ops[0]['opcode'] == 'loop# #2, last=0x0000171c' and ops[0]['opex']['units'] == [0x16ee, 0x16f2] and ops[0]['opex']['form'] == 'loop_imm_F1S0'
assert ops[1]['opcode'] == 'add r2, r5, #0x140'
text = '\n'.join(r2('e asm.cpu=usip;wx 006f600cc207@0x7ffa;pD 6@0x7ffa', 0x8000))
assert 'loop#? #3, last=0x00008006' in text
# 48-bit compare-and-branch prefix forms (b<cond>.l): validated (target +32 within the lookahead), unverified
# (target beyond it: confidence medium) and not taken (the target is not a packet start: normal layout).
pk = '402f6100c01004477b0348078a070a0b88652a4d8c1940606000c000483dfff34507fb034609f35a'
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x1562;aoj 2@0x1562' % pk, 0x8000)[0])
assert ops[0]['opcode'] == 'blt.l r2, #0x1, 0x00001582' and ops[0]['type'] == 'cjmp' and ops[0]['cond'] == 'lt' and ops[0]['sign']
assert ops[0]['size'] == 6 and ops[0]['jump'] == 0x1582 and ops[0]['fail'] == 0x1568 and ops[0]['delay'] == 0
assert ops[0]['opex']['form'] == 'bcc48_opf_mh2_blt' and ops[0]['opex']['target_guard'] == 'validated'
assert ops[0]['opex']['units'] == [0x1562, 0x1564, 0x1566] and ops[1]['opcode'] == 'lsl r3, r2, #0x4'
ops = json.loads(r2('e asm.cpu=usip;wx 404e6100c151@0x1f84;aoj 1@0x1f84', 0x8000)[0])
assert ops[0]['opcode'] == 'beq.l? r4, #0x1, 0x00002226' and ops[0]['jump'] == 0x2226 and ops[0]['cond'] == 'eq'
assert ops[0]['opex']['target_guard'] == 'unverified' and ops[0]['opex']['confidence'] == 'medium'
ops = json.loads(r2('e asm.cpu=usip;wx 404e6100c00440004000c000@0x1f84;aoj 1@0x1f84', 0x8000)[0])
assert ops[0]['opcode'] == 'st.w r4, [r0+12]' and 'jump' not in ops[0]
# The 64-bit twin (head with F = 0 followed by 1e0d): four units, one op, the packet closed by its last unit.
ops = json.loads(r2('e asm.cpu=usip;wx 002d1e0d6200c49e1c501c2040d840dc@0x1cca;aoj 1@0x1cca', 0x8000)[0])
assert ops[0]['opcode'] == 'blt.l? r2, #0x2, 0x00002606' and ops[0]['size'] == 8 and ops[0]['fail'] == 0x1cd2
assert ops[0]['opex']['units'] == [0x1cca, 0x1ccc, 0x1cce, 0x1cd0] and ops[0]['opex']['form'] == 'bcc48_opd_mh2_blt_64'
# 64-bit pair load/store with the sign bit of the offset (off = 4 * v9 - 2048 * s).
ops = json.loads(r2('e asm.cpu=usip;wx 5225c3b1@0x3054;aoj 1@0x3054', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.d r2:r3, [r9-0xa0]' and ops[0]['type'] == 'load' and ops[0]['refptr'] == 8
assert ops[0]['disp'] == (-0xa0) & 0xffffffffffffffff and dict(name='off', type='imm', value=-160) in ops[0]['opex']['operands']
ops = json.loads(r2('e asm.cpu=usip;wx 4a85cbef@0x141e;aoj 1@0x141e', 0x8000)[0])
assert ops[0]['opcode'] == 'st.d r8:r9, [r5-0x24]' and ops[0]['type'] == 'store' and ops[0]['refptr'] == 8
# Half-word and word offsets of the closed negative bands (odd f12; f12 = 3 mod 4 >= 0xf00).
ops = json.loads(r2('e asm.cpu=usip;wx 5000dfcf@0x24dc;aoj 1@0x24dc', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.h r0, [r8-0x32]' and ops[0]['sign'] and dict(name='off', type='disp', value=-50) in ops[0]['opex']['operands']
ops = json.loads(r2('e asm.cpu=usip;wx 1c201c104ffb4fff040749a72402bb66@0xe8c;aoj 2@0xe8c', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r2, [r14-0x8]', 'ld.w r1, [r14-0x4]'] and ops[0]['opex']['units'] == [0xe8c, 0xe90]
# Set-on-compare family: seq / sle of op 6 (tail adjacent), sgt of op 7 with the tail at +2.
ops = json.loads(r2('e asm.cpu=usip;wx 4216d402@0x100e;aoj 1@0x100e', 0x8000)[0])
assert ops[0]['opcode'] == 'seq r1, r1, #0x1' and ops[0]['type'] == 'cmp' and ops[0]['opex']['form'] == 'seq_op6_F1S1'
ops = json.loads(r2('e asm.cpu=usip;wx 4057600ecf20@0x144ce;aoj 1@0x144ce', 0x20000)[0])
assert ops[0]['opcode'] == 'sgt r5, r0, #0x190' and ops[0]['type'] == 'cmp' and ops[0]['opex']['units'] == [0x144ce, 0x144d2]
ops = json.loads(r2('e asm.cpu=usip;wx 12361246403000205820c00c@0x1d28;aoj 2@0x1d28', 0x8000)[0])
assert ops[1]['opcode'] == 'add r4, r9, #0x10' and ops[1]['type'] == 'add' and ops[1]['opex']['units'] == [0x1d2a, 0x1d2e]
# movhi: the small cell (f12 >> 1 << 12).
ops = json.loads(r2('e asm.cpu=usip;wx 703075220008001c4a57c040@0x1dba;aoj 3@0x1dba', 0x8000)[0])
assert ops[2]['opcode'] == 'movhi r0, #0x00020000' and ops[2]['val'] == 0x20000 and ops[2]['opex']['form'] == 'movhi_small'
# 16-bit st.h of the first slot (formerly movi/addi).
ops = json.loads(r2('e asm.cpu=usip;wx 408f44605000c004@0x76e;aoj 1@0x76e', 0x8000)[0])
assert ops[0]['opcode'] == 'st.h r8, [r0]' and ops[0]['type'] == 'store' and ops[0]['size'] == 2 and ops[0]['opex']['form'] == 'st_h16_f_slot0'
# st.h with the F' = 0 tail (R-st1h): the tail is deferred (+2 or farther) ...
ops = json.loads(r2('e asm.cpu=usip;wx 4c314c3fa004@0x1392;aoj 2@0x1392', 0x8000)[0])
assert ops[0]['opcode'] == 'st.h r3, [r6+0x4]' and ops[0]['opex']['units'] == [0x1392, 0x1396] and ops[0]['opex']['form'] == 'st_h_Fp0_F1S1'
assert ops[1]['opcode'] == 'movi r6, #0x7'
# ... and in the first slot a b.s head whose target is a packet start keeps its 16-bit reading (the pair is undone).
pk = '5c515c5f2014ca17'
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x2000;aoj 1@0x2000' % (pk + 'be79' * 5 + '4020c000be79'), 0x8000)[0])
assert ops[0]['opcode'] == 'st.h r5, [r14+0x14]' and ops[0]['opex']['units'] == [0x2000, 0x2004]
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x2000;aoj 3@0x2000' % (pk + 'be79' * 6 + '4020c000'), 0x8000)[0])
assert [o['opcode'] for o in ops] == ['b.s 0x00002014', 'movi r14, #0xb', '.op4i r1, r0, tail'] and ops[0]['delay'] == 2 and ops[2]['opex']['delay_slot']
# H H T T: two named stores interleaved with their tails at +2 in a 4-unit packet.
ops = json.loads(r2('e asm.cpu=usip;wx 44614a315000d000@0x68c;aoj 2@0x68c', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['st.w r6, [r2+0x0]', 'st.w r3, [r5+0x0]']
assert ops[0]['opex']['units'] == [0x68c, 0x690] and ops[1]['opex']['units'] == [0x68e, 0x692]
# The op8 slot split (the two slot-family halves of an .op8? pair), the 48-bit add trio and the system op whose
# F = S = 0 tail keeps the packet open.
ops = json.loads(r2('e asm.cpu=usip;wx 004842700787d03e@0x194c;aoj 3@0x194c', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ldslot r4, #0x1', 'ldslot r7, #0x2', 'fxtu r8!, r3, #0x1, #0xf'] and [o['size'] for o in ops] == [2, 2, 4]
assert ops[0]['opex']['form'] == 'ldslot_split' and ops[0]['type'] == 'load'
ops = json.loads(r2('e asm.cpu=usip;wx 0e103fc0c000@0x4d70;aoj 1@0x4d70', 0x8000)[0])
assert ops[0]['opcode'] == 'add r1, r1, #0x3ff00000' and ops[0]['size'] == 6
ops = json.loads(r2('e asm.cpu=usip;wx 041f5000e2aa@0x1042;aoj 2@0x1042', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['cop r2', 'ld.w r10, [r1+16]'] and ops[0]['size'] == 4 and ops[0]['opex']['form'] == 'cop_FS0'
# xor alone, movi into the high bank, bset with the high-bank destination, the funnel shift osl.
ops = json.loads(r2('e asm.cpu=usip;wx 838a@0x16bc;aoj 1@0x16bc', 0x8000)[0])
assert ops[0]['opcode'] == 'xor r1, r8!' and ops[0]['type'] == 'xor'
# (the table pairs this unit with its named 16-bit tail as mtcsr of an accumulator CSR: R-mtacc; without the ? since the
# 726-form table)
ops = json.loads(r2('e asm.cpu=usip;wx 2407e439@0xf14;aoj 1@0xf14', 0x8000)[0])
assert ops[0]['opcode'] == 'mtcsr #0x39, r2!' and ops[0]['opex']['form'] == 'mtcsr_acc' and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 00002007ff18@0x1c20;aoj 2@0x1c20', 0x8000)[0])
assert ops[1]['opcode'] == 'bset r8!, r0!, #0x0' and ops[1]['type'] == 'or'
ops = json.loads(r2('e asm.cpu=usip;wx 04570c419be2@0x409c;aoj 1@0x409c', 0x8000)[0])
assert ops[0]['opcode'] == 'osl r5, r2, #1' and ops[0]['type'] == 'shl' and ops[0]['opex']['units'] == [0x409c, 0x40a0]
text = '\n'.join(r2('e asm.cpu=usip;e asm.pseudo=true;wx 006f600cc207021640b0c30e@0x1bfa;pD 4@0x1bfa;wx 5225c3b1@0x3054;pD 4@0x3054;wx 12361246403000205820c00c@0x1d28;pD 12@0x1d28', 0x8000))
assert 'repeat 3 times until last=0x00001c06' in text and 'r2:r3 = dmem64[r9 - 0xa0]' in text and 'r4 = (r9 + 0x10)' in text
# Table update (634 forms): the 14-bit word displacement of the ld.w/st.w pairs (W14, here in the H H T T layout).
ops = json.loads(r2('e asm.cpu=usip;wx 5011501151c6d1ca@0x3000;aoj 2@0x3000', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['st.w r1, [r8+0x11c4]', 'st.w r1, [r8+0x11c8]'] and ops[0]['disp'] == 0x11c4
assert ops[0]['opex']['units'] == [0x3000, 0x3004] and dict(name='off', type='disp', value=0x11c4) in ops[0]['opex']['operands']
# nop.ctl: the structural no-op 5e0d (renamed after the layout; never a tail, never a head with a tail).
ops = json.loads(r2('e asm.cpu=usip;wx 5e0d3836020c8217@0x189c;aoj 3@0x189c', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['nop.ctl', 'add r3, r12!, #0x106', 'movi r1, #0x2']
assert ops[0]['type'] == 'nop' and ops[0]['size'] == 2 and ops[0]['opex']['form'] == 'nop_ctl' and ops[0]['opex']['kind'] == 'nop'
ops = json.loads(r2('e asm.cpu=usip;wx 5e0da487@0x19a0;aoj 2@0x19a0', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['nop.ctl', 'movi r2!, #0x10']
# The bit-field family: fxtu / fxts (extract), fup (insert, read-modify-write) and the fup? cell; pos = rotr5(p).
ops = json.loads(r2('e asm.cpu=usip;wx 0007d03e@0x2c42;aoj 1@0x2c42', 0x8000)[0])
assert ops[0]['opcode'] == 'fxtu r0, r0, #0x1, #0xf' and ops[0]['type'] == 'cast' and ops[0]['opex']['form'] == 'bf_fxtu' and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 020643d94127d5e2@0x1d1a;aoj 2@0x1d1a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['add r0, r1, #-0x14', 'fxts r2!, r0, #0xf, #0x1'] and ops[1]['type'] == 'cast' and ops[1]['opex']['form'] == 'bf_fxts'
ops = json.loads(r2('e asm.cpu=usip;wx 001700079910@0x124a;aoj 2@0x124a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['fup r1, r0, #0x8, #0x8', 'movi r0, #0x0'] and ops[0]['type'] == 'or' and ops[0]['opex']['units'] == [0x124a, 0x124e]
assert [op['value'] for op in ops[0]['opex']['operands'] if op['name'] in ('w', 'pos')] == [8, 8]
ops = json.loads(r2('e asm.cpu=usip;wx 0207db88@0x228;aoj 1@0x228', 0x8000)[0])
assert ops[0]['opcode'] == 'fup? r0, r1, #0x1c, #0x4' and ops[0]['opex']['form'] == 'bf_fup_q' and ops[0]['opex']['confidence'] == 'medium'
# mtacc? (the accumulator pair load, formerly clr.a?), movi? #-32768 and sgt with F = S = 1.
ops = json.loads(r2('e asm.cpu=usip;wx 1d401d50401400106547fd20@0xe10;aoj 5@0xe10', 0x8000)[0])
assert ops[4]['opcode'] == 'mtacc? A0, r2!:r4!' and ops[4]['opex']['form'] == 'op7d_2_mtacc_q' and ops[4]['opex']['units'] == [0xe18, 0xe1a]
assert ops[0]['opcode'] == 'ld.w r4!, [r14+0x14]' and ops[1]['opcode'] == 'addi r5!, r14, #0x8'
ops = json.loads(r2('e asm.cpu=usip;wx 10a77b004087c001@0xf20;aoj 2@0xf20', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['lsl r0, r8, #0xa', 'movi r8, #-0x10000'] and ops[1]['type'] == 'mov' and ops[1]['opex']['form'] == 'movi_imm17'
ops = json.loads(r2('e asm.cpu=usip;wx 1c300097410838654604ffff@0x46ba;aoj 5@0x46ba', 0x8000)[0])
assert ops[4]['opcode'] == 'sgt r0, r3, #-1' and ops[4]['type'] == 'cmp' and ops[4]['opex']['form'] == 'setcc_op4_t31_sgt_m1_F1S1'
assert [o['opcode'] for o in ops[:2]] == ['ld.w r3, [r14+0x108]', 'cmov r5, r0, r9']
# The register forms bset / bext rd, rs1, rB (7e X = 0 / 6, renamed from .7e after the layout).
ops = json.loads(r2('e asm.cpu=usip;wx 0487228abe02@0x3916;aoj 2@0x3916', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['bset r2, r2, r8', 'mul r1, r1, r8'] and ops[0]['type'] == 'or' and ops[0]['opex']['form'] == 'op7e_0_bset_r'
ops = json.loads(r2('e asm.cpu=usip;wx 0087fe60@0x5048;aoj 1@0x5048', 0x8000)[0])
assert ops[0]['opcode'] == 'bext r0, r0, r8' and ops[0]['type'] == 'shr' and ops[0]['opex']['form'] == 'op7e_6_bext_r' and ops[0]['size'] == 4
# b<cond>.d with the [0][1][tt] tail at +2 (S0F1): unverified when the forward target lies beyond the bytes at
# hand (bne / beqz are on the allow-list), validated by the session for a backward target (the register cells are
# not accepted unverified: without the target the units are 16-bit ops).
ops = json.loads(r2('e asm.cpu=usip;wx 7e096f204264cfdf@0xeb8;aoj 2@0xeb8', 0x8000)[0])
assert ops[0]['opcode'] == 'bne.d? r15!, #0x1, 0x00000f1c' and ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0xf1c and ops[0]['cond'] == 'ne'
assert ops[0]['opex']['form'] == 'bne_imm8_s0f1' and ops[0]['opex']['target_guard'] == 'unverified' and ops[0]['opex']['units'] == [0xeb8, 0xebc]
assert ops[1]['opcode'] == 'ld.w r2!, [r7!-0x24]' and ops[1]['opex']['delay_slot'] and ops[0]['delay'] == 3
ops = json.loads(r2('e asm.cpu=usip;wx 400c4d11505ed024@0x1fae;aoj 2@0x1fae', 0x8000)[0])
assert ops[0]['opcode'] == 'beqz.d? r0, 0x0000200c' and ops[0]['opex']['form'] == 'beqz_s0f1' and ops[1]['opcode'] == 'st.w r1!, [r6+0x24]'
pk = 'wx a03f@0x1704;wx 5e5d00667f832002bdd4@0x1782;'
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 1@0x1704;aoj 3@0x1782', 0x8000)[1])
assert ops[0]['opcode'] == 'blt.d r15, r5, 0x00001704' and ops[0]['jump'] == 0x1704 and ops[0]['opex']['target_guard'] == 'validated'
assert ops[0]['opex']['form'] == 'blt_rr_s0f1' and ops[0]['opex']['units'] == [0x1782, 0x1786] and ops[1]['opcode'] == 'and r6, r0, #0x1'
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 3@0x1782', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['.hword 0x5e5d', 'and r6, r0, #0x1', 'addi r15, #-0x10']
pk = 'wx a03f@0xe3c;wx 4f1c49226ff5c004@0xe48;'
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 1@0xe3c;aoj 2@0xe48', 0x8000)[1])
assert ops[0]['opcode'] == 'bleu.d r7, r1, 0x00000e3c' and ops[0]['cond'] == 'ls' and ops[0]['opex']['form'] == 'bleu_rr_s0f1' and ops[1]['opcode'] == 'add.2 r2!, r4, #0x2'
# The loads with the fixed base r15 (renamed from .1eX0), the F' = 0 half-word load and the open-width .ldF0?.
ops = json.loads(r2('e asm.cpu=usip;wx 1e80515011a1b721@0x683a;aoj 3@0x683a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r8, [r15+0x150]', 'mov r10!, r8', 'mov r2!, r11!'] and ops[0]['type'] == 'load' and ops[0]['ireg'] == 'r15'
assert ops[0]['opex']['form'] == 'x1e0_ld_h' and ops[0]['size'] == 4 and ops[0]['refptr'] == 2
ops = json.loads(r2('e asm.cpu=usip;wx 1e10424044078807@0x1e90;aoj 2@0x1e90', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.w? r1, [r15+0x407]', 'addi r4, r1, #-0x3fd'] and ops[0]['opex']['form'] == 'x1e0_ld_w_q' and ops[0]['opex']['units'] == [0x1e90, 0x1e94]
ops = json.loads(r2('e asm.cpu=usip;wx 4200790e9008@0x1bbc;aoj 2@0x1bbc', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r0, [r1+0x8]', 'st.w r0, [r12+24]'] and ops[0]['opex']['form'] == 'rf0dall_ld_h' and ops[0]['opex']['units'] == [0x1bbc, 0x1bc0]
# (the 760-form table reads this pair inside the layout as the F' = 0 half-word load with any base, rf0dall; the rename ld_h_Fp0 never sees it)
ops = json.loads(r2('e asm.cpu=usip;wx 0f009240@0x2176;aoj 1@0x2176', 0x8000)[0])
assert ops[0]['opcode'] == '.ldF0? r0!, [r7+0x240]' and ops[0]['type'] == 'unk' and ops[0]['opex']['form'] == 'ldF0_q_t1_adj'
# The 48-bit constants admitted by the value table (any op 0 head), and the .3eX0? trio that is not a constant.
ops = json.loads(r2('e asm.cpu=usip;wx 1e301ffdffff@0x2cd6;aoj 1@0x2cd6', 0x8000)[0])
assert ops[0]['opcode'] == 'mov32 r3, #0x7fff7fff' and ops[0]['size'] == 6 and ops[0]['val'] == 0x7fff7fff and ops[0]['opex']['form'] == 'c48tab_mov32'
ops = json.loads(r2('e asm.cpu=usip;wx 1e400a495fbfbfff@0x4dc8;aoj 2@0x4dc8', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['mov32 r4, #0x7fefffff', 'add r5, r4'] and ops[0]['opex']['units'] == [0x4dc8, 0x4dcc, 0x4dce]
ops = json.loads(r2('e asm.cpu=usip;wx 0a5660203e606e14d000@0x7b74;aoj 2@0x7b74', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['and r5, r5, #0x10', '.3eX0? r6, 0x6e14 0xd000'] and ops[1]['size'] == 6 and ops[1]['opex']['form'] == 'x3eX0_q'
# The 16-bit renames: lsl with S = 0, lsl? at k >= 1 with F = 1, sext16 / zext16 with F = 1, addi #32 next to cop.
ops = json.loads(r2('e asm.cpu=usip;wx 144a20358338@0x128a;aoj 2@0x128a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['movi r4, #0x519c', 'lsl r3, r0, #0x2'] and ops[1]['type'] == 'shl' and ops[1]['opex']['form'] == 'lsl16_S0'
ops = json.loads(r2('e asm.cpu=usip;wx 060840057337000229b08570@0x1108;aoj 2@0x1108', 0x8000)[0])
assert ops[0]['opcode'] == 'call.abs 0x1ccde9b0' and ops[1]['opcode'] == 'lsl? r0, r0, #0x1' and ops[1]['opex']['form'] == 'lsl16_F1_S0_k1_q'
ops = json.loads(r2('e asm.cpu=usip;wx 0c8778e7e43d@0x65e;aoj 2@0x65e', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['slt r7, r6, r8', 'sext16 r3, r2'] and ops[1]['type'] == 'cast' and ops[1]['opex']['form'] == 'sext16_F1_c'
ops = json.loads(r2('e asm.cpu=usip;wx 09077b27066a693d8a98@0x7d9e;aoj 3@0x7d9e', 0x8000)[0])
assert ops[2]['opcode'] == 'zext16 r3, r4' and ops[2]['opex']['form'] == 'zext16_F1_S0' and ops[1]['opcode'] == 'movi r6, #0x1d4c'
ops = json.loads(r2('e asm.cpu=usip;wx 001f5000c103@0x3338;aoj 2@0x3338', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['cop r0', 'addi r0, #32'] and ops[1]['type'] == 'add' and ops[1]['opex']['form'] == 'addi32_cop'
# H1: a loose hi syllable is .hi only after a head of the 48-bit family (here the ld.w); movhi with the tt = 10
# tail (the IEEE test); the .op4t0 shape is not a load.
ops = json.loads(r2('e asm.cpu=usip;wx 1bb048c435d735ffb90c@0x2352;aoj 3@0x2352', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r11!, [r13+0x8c4]', 'and r12, r10!, r13!', '.hi 0x35ff'] and ops[2]['opex']['form'] == 'hi1010_loose'
ops = json.loads(r2('e asm.cpu=usip;wx 0218a016@0x627c;aoj 1@0x627c', 0x8000)[0])
assert ops[0]['opcode'] == 'movhi r1, #0x4080b000' and ops[0]['val'] == 0x4080b000 and ops[0]['opex']['form'] == 'movhi_tt10_short'
ops = json.loads(r2('e asm.cpu=usip;wx 0e33240750043872c819@0x7ee;aoj 3@0x7ee', 0x8000)[0])
assert ops[2]['opcode'] == '.op4t0 r0, [r8+0x19]' and ops[2]['type'] == 'unk' and ops[0]['opcode'] == 'addi r7, #0x6'
# Table update (687 forms): the F = S = 0 compare-and-branch cells with a known target as a validation filter: validated by
# the session (the target is a packet start seen before), rejected (the target is inside a packet decoded before: the pair
# falls through to two 16-bit ops instead of .word) and unknown backward (a conditional branch is not read unverified
# backward); the R-br0 cells (head with F = 0 and the [0][1][tt] tail at +1 or +2) of bne.d / bleu.d / bge.d / beq.u8.d /
# bgeu.d / bne.u8.d / ble.d, the bgt.d cell back in the table, and an unverified forward one.
pk = 'wx 04094de7540ac044def9@0x2992;'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x2978;' + pk + 'aoj 1@0x2978;aoj 3@0x2992', 0x8000)[1])
assert ops[0]['opcode'] == 'bne.d r2, #0x6, 0x00002978' and ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0x2978 and ops[0]['cond'] == 'ne'
assert ops[0]['opex']['form'] == 'bne_imm8_f0s0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['delay'] == 1 and ops[1]['opcode'] == 'movi r0, #0x5022'
ops = json.loads(r2('e asm.cpu=usip;wx 00008000@0x2976;' + pk + 'aoj 1@0x2976;aoj 3@0x2992', 0x8000)[1])
assert [o['opcode'] for o in ops] == ['add r2, r0', 'st.h r14, [r6]+', 'movi r0, #0x5022'] and ops[0]['size'] == 2
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 3@0x2992', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['add r2, r0', 'st.h r14, [r6]+', 'movi r0, #0x5022']
ops = json.loads(r2('e asm.cpu=usip;wx 0c5c0801601c500c007a427701feb8014471d0007ffe42316d63d000@0x8a2;aoj 3@0x8a2', 0x8000)[0])
assert ops[0]['opcode'] == 'bleu.d r6, r5, 0x000008be' and ops[0]['cond'] == 'ls' and ops[0]['opex']['form'] == 'bleu_rr_f0s0'
assert ops[0]['opex']['target_guard'] == 'validated' and ops[0]['opex']['units'] == [0x8a2, 0x8a6] and ops[0]['delay'] == 7 and ops[1]['opcode'] == 'st.w r0, [r4+0xc]'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x652;wx 000a618dd4a6@0x6c6;aoj 1@0x652;aoj 2@0x6c6', 0x8000)[1])
assert ops[0]['opcode'] == 'bge.d r0, #0x0, 0x00000652' and ops[0]['cond'] == 'ge' and ops[0]['opex']['form'] == 'bge_imm8_f0s0' and ops[1]['opcode'] == 'inc r10, r10'
ops = json.loads(r2('e asm.cpu=usip;wx 1a181d215020504004d760573802ba1045045ad65002c0021a6c6ff97f07cfff@0x9a0;aoj 2@0x9a0', 0x8000)[0])
assert ops[0]['opcode'] == 'beq.u8.d r13, #0x8, 0x000009c0' and ops[0]['cond'] == 'eq' and ops[0]['opex']['form'] == 'beq.u8_imm8_f0s0' and ops[0]['opex']['units'] == [0x9a0, 0x9a4]
assert ops[1]['opcode'] == 'st.w r2!, [r14+0x40]' and ops[1]['opex']['delay_slot']
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0xaa0;wx 000a455dc207@0xb44;aoj 1@0xaa0;aoj 2@0xb44', 0x8000)[1])
assert ops[0]['opcode'] == 'bgeu.d r0, #0x2, 0x00000aa0' and ops[0]['cond'] == 'hs' and ops[0]['opex']['form'] == 'bgeu_imm8_f0s0'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x1b32;wx 02f838144dd920c04200c004@0x1b5a;aoj 1@0x1b32;aoj 2@0x1b5a', 0x8000)[1])
assert ops[0]['opcode'] == 'bne.u8.d r1, #0x7e, 0x00001b32' and ops[0]['opex']['form'] == 'bne.u8_imm8_f0s0' and ops[0]['opex']['units'] == [0x1b5a, 0x1b5e] and ops[1]['opcode'] == 'st.h r0, [r12+r1]'
ops = json.loads(r2('e asm.cpu=usip;wx 00096434c08100095888c22f4009cbe0142063c4c2378406053814015e3423c4c02f1430000643d02402c0814009ca9e400ee3de4009d258@0x47cc;aoj 3@0x47cc', 0x8000)[0])
assert ops[0]['opcode'] == 'ble.d r0, #0x2, 0x00004800' and ops[0]['opex']['form'] == 'ble_imm8_f0s0' and ops[0]['opex']['target_guard'] == 'validated'
assert ops[2]['opcode'] == 'beq.d? r0, #0x4, 0x0000485a' and ops[2]['opex']['form'] == 'beq_imm8_d' and ops[2]['opex']['target_guard'] == 'unverified'
ops = json.loads(r2('e asm.cpu=usip;wx 000a7624c0914009d23a4009c46a180053c0d417520d7f00c025800b5d69580123c0be794009d81e@0x3e06;aoj 2@0x3e06', 0x8000)[0])
assert ops[0]['opcode'] == 'bgt.d r0, #0x3, 0x00003e2a' and ops[0]['cond'] == 'gt' and ops[0]['opex']['form'] == 'bgt_imm8_d' and ops[0]['opex']['target_guard'] == 'validated'
ops = json.loads(r2('e asm.cpu=usip;wx 3e094268010762f72030b811@0xcca;aoj 3@0xcca', 0x8000)[0])
assert ops[0]['opcode'] == 'bne.d? r15!, #0x1, 0x00000d32' and ops[0]['opex']['form'] == 'bne_imm8_f0s0' and ops[0]['opex']['target_guard'] == 'unverified'
assert [o['opcode'] for o in ops[1:]] == ['mfcsr r0!, #0x30', 'add r1!, r1!, r15']
# ret.s.d (1ef1 in the first slot, the rest of the packet its delay slot), movi rA, #-1 with F = 0, the 16-bit op 2 shifts
# lsl / asr rA, #B (firm cells) and lsl? / asr? (the others), the open b.s validated by R-tgt: rejected when the target is
# inside a packet decoded before, kept unverified when it is unknown.
ops = json.loads(r2('e asm.cpu=usip;wx 1ef1a406@0x5168;aoj 2@0x5168', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ret.s.d', 'mfacc r2, A0.lo'] and ops[0]['type'] == 'ret' and ops[0]['delay'] == 1 and ops[0]['opex']['form'] == 'ret_s_d'
ops = json.loads(r2('e asm.cpu=usip;wx 01ff8207@0x1b56;aoj 2@0x1b56', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['movi r0, #-1', 'movi r1, #0x0'] and ops[0]['opex']['form'] == 'movi_m1_F0_S0' and ops[0]['type'] == 'mov'
ops = json.loads(r2('e asm.cpu=usip;wx 8022@0x1028;aoj 1@0x1028', 0x8000)[0])
assert ops[0]['opcode'] == 'lsl r0, #0x2' and ops[0]['type'] == 'shl' and ops[0]['opex']['form'] == 'lsl16_imm'
ops = json.loads(r2('e asm.cpu=usip;wx 020745f28479e5fe8c0b@0x7e4e;aoj 2@0x7e4e', 0x8000)[0])
assert ops[1]['opcode'] == 'asr r2, #0xf' and ops[1]['type'] == 'sar' and ops[1]['opex']['form'] == 'asr16_imm'
# (in the first slot the same unit is the 16-bit ld.w rB, [rA +/- 4] since the 760-form table)
ops = json.loads(r2('e asm.cpu=usip;wx 45f28479e5fe8c0b@0x7e50;aoj 1@0x7e50', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.w r15, [r2+0x4]' and ops[0]['type'] == 'load' and ops[0]['opex']['form'] == 'ld_w16_2_k0_F1'
ops = json.loads(r2('e asm.cpu=usip;wx 4006c402@0x818;aoj 2@0x818', 0x8000)[0])
assert ops[1]['opcode'] == 'op2z? r2' and ops[1]['opex']['form'] == 'op2z_q' and ops[1]['opex']['confidence'] == 'medium'
ops = json.loads(r2('e asm.cpu=usip;wx 8522@0x1000;aoj 1@0x1000', 0x8000)[0])
assert ops[0]['opcode'] == 'asr? r2, #0x2' and ops[0]['opex']['form'] == 'asr16_imm_q'
pk = 'wx 7c316612000204874000a03e@0x3856;'
ops = json.loads(r2('e asm.cpu=usip;wx 7c217aa15008f000@0x37de;' + pk + 'aoj 1@0x37de;aoj 2@0x3856', 0x8000)[1])
assert [o['opcode'] for o in ops] == ['mov r3, r14!', 'add.2 r1, r3!, #0x0']
ops = json.loads(r2('e asm.cpu=usip;' + pk + 'aoj 1@0x3856', 0x8000)[0])
assert ops[0]['opcode'] == 'b.s? 0x000037e2' and ops[0]['type'] == 'jmp' and ops[0]['opex']['form'] == 'b_s_open' and ops[0]['opex']['target_guard'] == 'unverified'
# bz.s with bit 3 = 0 (context rule bz_s_P5): unconditional in a 2-unit packet, validated in longer ones (a self-target, a
# forward target inside the lookahead) and left .hword when the target cannot be checked; the first-slot ldslot / stslot
# with m = 1 (k >= 32) when the rest of the packet is named without a ?.
ops = json.loads(r2('e asm.cpu=usip;wx 54458007@0x4f6e;aoj 2@0x4f6e', 0x8000)[0])
assert ops[0]['opcode'] == 'bz.s r10, 0x00004f7e' and ops[0]['type'] == 'cjmp' and ops[0]['delay'] == 1 and ops[0]['opex']['form'] == 'bz_s_P5' and ops[1]['opcode'] == 'movi r0, #0x0'
ops = json.loads(r2('e asm.cpu=usip;wx 4005400a3302422690c1@0x10c0;aoj 2@0x10c0', 0x8000)[0])
assert ops[0]['opcode'] == 'bz.s r0, 0x000010c0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['delay'] == 3 and ops[1]['opcode'] == 'ld.w r0, [r0+12]'
pk = 'wx 4c750247f817'
ops = json.loads(r2('e asm.cpu=usip;' + pk + '122a2f915141102479d7f9442649920be7417ffeef2f6f216fb1@0x1766;aoj 2@0x1766', 0x8000)[0])
assert ops[0]['opcode'] == 'bz.s r6, 0x00001782' and ops[0]['opex']['target_guard'] == 'validated' and ops[1]['opcode'] == 'add r7!, r1, r4'
ops = json.loads(r2('e asm.cpu=usip;' + pk + '@0x1766;aoj 2@0x1766', 0x8000)[0])
assert ops[0]['opcode'] == '.hword 0x4c75' and ops[1]['opcode'] == 'add r7!, r1, r4'
ops = json.loads(r2('e asm.cpu=usip;wx 72889821@0x4f04;aoj 2@0x4f04', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ldslot r8, #0x33', 'mov r2, r12'] and ops[0]['type'] == 'load' and ops[0]['opex']['form'] == 'ldslot_S0_m1'
ops = json.loads(r2('e asm.cpu=usip;wx 6c045848c020@0x361a;aoj 1@0x361a', 0x8000)[0])
assert ops[0]['opcode'] == 'stslot r0, #0x2c' and ops[0]['type'] == 'store' and ops[0]['opex']['form'] == 'stslot_S0_m1'
# Hardware loops: loop.b1? (B = 1) validated and unverified (its ss >= 2 twin), loop rX?ss=N; movi? #-32767; the asr16 clamp
# with the same register on both sides.
ops = json.loads(r2('e asm.cpu=usip;wx 0a1f08f7701c7b60300740073d03bac0024050024027fd0546f7fb63@0x4210;aoj 2@0x4210', 0x8000)[0])
assert ops[0]['opcode'] == 'loop r5, last=0x0000422c, x16' and ops[0]['ptr'] == 0x422c and ops[0]['opex']['form'] == 'loop_b1' and ops[0]['opex']['target_guard'] == 'validated'
assert ops[0]['opex']['units'] == [0x4210, 0x4214] and ops[1]['opcode'] == 'lsr r0, r4, #0xf'
ops = json.loads(r2('e asm.cpu=usip;wx 221f705cc029@0x25c2;aoj 1@0x25c2', 0x8000)[0])
assert ops[0]['opcode'] == 'loop? r1?ss=2, last=0x0000261e, x16' and ops[0]['opex']['form'] == 'loop_b1_ssq' and ops[0]['opex']['target_guard'] == 'unverified'
ops = json.loads(r2('e asm.cpu=usip;wx 340f3816701c4008002a40078060e233a62a86994630c0244636f0024606cc02@0x22c6;aoj 1@0x22c6', 0x8000)[0])
assert ops[0]['opcode'] == 'loop r2?ss=3, last=0x000022e2' and ops[0]['opex']['form'] == 'loop_ssq' and ops[0]['opex']['target_guard'] == 'validated'
ops = json.loads(r2('e asm.cpu=usip;wx 040c202700a0c003@0x6214;aoj 2@0x6214', 0x8000)[0])
assert ops[1]['opcode'] == 'movi r2, #-0x7fff' and ops[1]['opex']['form'] == 'movi_imm17' and ops[1]['opex']['units'] == [0x6216, 0x621a] and ops[0]['opcode'] == 'sub r0, r2, r0'
ops = json.loads(r2('e asm.cpu=usip;wx 0007d4d4060cc0c0c161@0xc0a;aoj 2@0xc0a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['asr16 r0, r0, #10', 'L_shr r0, r3, r0'] and ops[0]['opex']['form'] == 'asr16_clamp_u' and ops[0]['type'] == 'sar'
# The bit-field family by H H T T pair (R-bfpair) and by cell (the BF_SEL renames of .bf?: fup, fup?, fxtu, fxtu?, fxts?).
ops = json.loads(r2('e asm.cpu=usip;wx 063708471258d258@0x1fea;aoj 2@0x1fea', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['fxtu r3, r3, #0x12, #0xc', 'fxtu r4, r4, #0x12, #0xc'] and ops[0]['opex']['form'] == 'bfpair_fxtu' and ops[0]['opex']['units'] == [0x1fea, 0x1fee]
ops = json.loads(r2('e asm.cpu=usip;wx 10071027187bd286@0x4f9c;aoj 2@0x4f9c', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['fup r0, r8, #0x3, #0x1d', 'fxtu r2, r8, #0x14, #0x3'] and ops[0]['opex']['form'] == 'bfpair_fup' and ops[1]['opex']['form'] == 'bfpair_fxtu'
ops = json.loads(r2('e asm.cpu=usip;wx 0067d8305e0cc018@0x6ca6;aoj 1@0x6ca6', 0x8000)[0])
assert ops[0]['opcode'] == 'fup r6, r0, #0x1, #0x8' and ops[0]['opex']['form'] == 'bfsel_fup' and ops[0]['type'] == 'or'
ops = json.loads(r2('e asm.cpu=usip;wx 3be79961@0x735e;aoj 1@0x735e', 0x8000)[0])
assert ops[0]['opcode'] == 'fup? r14!, r13!, #0xb, #0x10' and ops[0]['opex']['form'] == 'bfsel_fup_q' and ops[0]['opex']['confidence'] == 'low'
ops = json.loads(r2('e asm.cpu=usip;wx 3fde78f94427d028@0x4b6e;aoj 2@0x4b6e', 0x8000)[0])
assert ops[1]['opcode'] == 'fxtu r2, r2, #0x1, #0x4' and ops[1]['opex']['form'] == 'bfsel_fxtu' and ops[1]['type'] == 'cast'
ops = json.loads(r2('e asm.cpu=usip;wx 0017d088@0x3344;aoj 1@0x3344', 0x8000)[0])
assert ops[0]['opcode'] == 'fxtu? r1, r0, #0x4, #0x4' and ops[0]['opex']['form'] == 'bfsel_fxtu_q'
ops = json.loads(r2('e asm.cpu=usip;wx 0017d510@0x666a;aoj 1@0x666a', 0x8000)[0])
assert ops[0]['opcode'] == 'fxts? r1, r0, #0x8, #0x8' and ops[0]['opex']['form'] == 'bfsel_fxts_q'
# The ld/st type 111 is ld.bu / st.b with the byte displacement (sext12 of the swapped bits, printed with its sign): the
# plain F = S forms, the 2-unit head + tail packet with a named 16-bit tail (group b111) and the H H T T layout; ld.b /
# st.h with F != S of op 1 exactly at +2 (at +1 the pair is undone).
ops = json.loads(r2('e asm.cpu=usip;wx 5001f800ff05def9@0x122c;aoj 1@0x122c', 0x8000)[0])
assert ops[0]['opcode'] == 'st.b r0, [r8+0x1]' and ops[0]['opex']['form'] == 'mem_st_b_t7_F1S1' and ops[0]['refptr'] == 1 and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 09814802002a7d288040@0x234c;aoj 1@0x234c', 0x8000)[0])
assert ops[0]['opcode'] == 'st.b r8!, [r4+0x529]' and ops[0]['opex']['form'] == 'mem_st_b_t7_F0S0' and ops[0]['opex']['units'] == [0x234c, 0x2352]
ops = json.loads(r2('e asm.cpu=usip;wx 4001cf11@0x104e;aoj 1@0x104e', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.b r0, [r0-0xef]' and ops[0]['opex']['form'] == 'mem_ld_b_t4_F1S1' and dict(name='off', type='disp', value=-0xef) in ops[0]['opex']['operands']
ops = json.loads(r2('e asm.cpu=usip;wx 5010f628@0x5a24;aoj 1@0x5a24', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.bu r1, [r8+0x628]' and ops[0]['opex']['form'] == 'b111_ld_bu_t7_F1S1' and ops[0]['type'] == 'load' and not ops[0].get('sign')
ops = json.loads(r2('e asm.cpu=usip;wx 5c01f066@0x24d6;aoj 1@0x24d6', 0x8000)[0])
assert ops[0]['opcode'] == 'st.b r0, [r14+0x66]' and ops[0]['opex']['form'] == 'b111_st_b_t7_F1S1'
ops = json.loads(r2('e asm.cpu=usip;wx 4201422148daf0b2@0x153a;aoj 2@0x153a', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.b r0, [r1+0xdb]', 'st.b r2, [r1+0xb2]'] and ops[0]['opex']['form'] == 'mem_ld_b_t4_F1S0' and ops[1]['opex']['form'] == 'b111_st_b_t7_F1S1'
ops = json.loads(r2('e asm.cpu=usip;wx 52315211780ef010@0x1dd2;aoj 2@0x1dd2', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['st.b r3, [r9+0xf]', 'st.b r1, [r9+0x10]'] and ops[0]['opex']['form'] == 'b111_st_b_t7_F1S0'
ops = json.loads(r2('e asm.cpu=usip;wx 522052a17802c800@0x75e8;aoj 2@0x75e8', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.bu r2, [r9+0x3]', 'ld.b r10, [r9+0x1]'] and ops[0]['opex']['form'] == 'b111_ld_bu_t7_F1S0'
ops = json.loads(r2('e asm.cpu=usip;wx 50110010400200008000@0x121a;aoj 2@0x121a', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.b r1, [r8+0x2]' and ops[0]['opex']['form'] == 'mem_ld_b_t4_F1S0' and ops[0]['opex']['units'] == [0x121a, 0x121e] and ops[1]['opcode'] == 'mov32 r1, #0x0'
ops = json.loads(r2('e asm.cpu=usip;wx 501140028000@0x1300;aoj 1@0x1300', 0x8000)[0])
assert ops[0]['opcode'] == 'mov r1, r8' and ops[0]['size'] == 2
ops = json.loads(r2('e asm.cpu=usip;wx 4c114c816fffe014@0x1380;aoj 2@0x1380', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['st.h r1, [r6-0x2]', 'st.h r8, [r6+0x14]'] and ops[0]['opex']['form'] == 'mem_st_h_t6_F1S0' and ops[0]['opex']['units'] == [0x1380, 0x1384]
# ld.h? / ld.hu? with the F' = 0 tail as the second head of H H T T (R-hhttF0) and by the deferred tail of a head with base
# sp (R-F0d, the middle unit re-read as a first-slot 16-bit op); the sp adjustment pair add r14, r14, #imm (spfix);
# ld.w? / st.w? of the f12 = 1 / 3 mod 4 classes (NEGQ); the immediate-store triple of 17+ bits (.stimm16?).
ops = json.loads(r2('e asm.cpu=usip;wx 1400142050041006@0x2262;aoj 2@0x2262', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r0, [r10+0x4]', 'ld.h r2, [r10+0x6]'] and ops[1]['opex']['form'] == 'hhttF0_ld_h' and ops[1]['opex']['units'] == [0x2264, 0x2268]
ops = json.loads(r2('e asm.cpu=usip;wx 1420144060282004@0x2974;aoj 2@0x2974', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.hu r2, [r10+0x28]', 'ld.hu r4, [r10+0x4]'] and ops[1]['opex']['form'] == 'hhttF0_ld_hu'
ops = json.loads(r2('e asm.cpu=usip;wx 5c0042542098ca07@0x2edc;aoj 2@0x2edc', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.hu r0, [r14+0x98]' and ops[0]['opex']['form'] == 'rf0d_ld_hu' and ops[0]['opex']['units'] == [0x2edc, 0x2ee0] and ops[0]['stack'] == 'get'
ops = json.loads(r2('e asm.cpu=usip;wx 5c50707211e2000b8443@0x4c00;aoj 2@0x4c00', 0x8000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r5, [r14+0x1e2]', 'add.2 r7, r8!, #-0x1fb'] and ops[0]['opex']['form'] == 'rf0d_ld_h'
ops = json.loads(r2('e asm.cpu=usip;wx 008f1cf0500000005ce0c008@0x678;aoj 5@0x678', 0x8000)[0])
assert ops[4]['opcode'] == 'add r14, r14, #0x4' and ops[4]['opex']['form'] == 'spfix_add_sp' and ops[4]['type'] == 'add' and ops[4]['stack'] == 'inc' and ops[4]['stackptr'] in (-4, (1 << 64) - 4)
ops = json.loads(r2('e asm.cpu=usip;wx 202032354305a134@0x1398;aoj 1@0x1398', 0x8000)[0])
assert ops[0]['opcode'] == 'ld.w? r2, [r0!+0x305]' and ops[0]['opex']['form'] == 'negq_ld_w_c1' and ops[0]['opex']['units'] == [0x1398, 0x139c]
ops = json.loads(r2('e asm.cpu=usip;wx 663265573b1200506707407f79f0bfff@0x714;aoj 3@0x714', 0x8000)[0])
assert ops[2]['opcode'] == 'sltu r0!, r3!, r0!' and ops[2]['opex']['form'] == 'alu_01f_sltu_hi' # (the 726-form table: R-waw keeps the all-named reading)
ops = json.loads(r2('e asm.cpu=usip;wx 001150351210502812716688c868@0x2f42;aoj 1@0x2f42', 0x8000)[0])
assert ops[0]['opcode'] == 'st.w? r1, [r0+0x35]' and ops[0]['opex']['form'] == 'negq_st_w_c1' and ops[0]['type'] == 'store'
ops = json.loads(r2('e asm.cpu=usip;wx 4608729108324000600de688@0x7c3e;aoj 2@0x7c3e', 0x8000)[0])
assert ops[1]['opcode'] == '.stimm16? [r0], #0x36688' and ops[1]['size'] == 6 and ops[1]['type'] == 'unk' and ops[1]['opex']['form'] == 'stimm16_q_F1'
ops = json.loads(r2('e asm.cpu=usip;wx 0110080f600aaccc@0x4c52;aoj 2@0x4c52', 0x8000)[0])
assert ops[0]['opcode'] == '.stimm16? [r1!], #0x2accc' and ops[0]['opex']['form'] == 'stimm16_q_F0_x1' and ops[0]['opex']['units'] == [0x4c52, 0x4c56, 0x4c58] and ops[1]['opcode'] == 'movi r4, #0x1'
# MS15E30 group-5 forms: 0xDA00 / 0xDC00 named after the little-endian layout of the data memory.
ops = json.loads(r2('wx da49dc49d849de49ca89cb21;aoj 6')[0])
assert [o['opcode'] for o in ops] == ['cmulr16 r2, r2, r2', 'cmuli16 r2, r2, r2', 'cmulcr16 r2, r2, r2', 'cmulci16 r2, r2, r2', 'rem16 r2, r2', 'ctz16 r1, r2']
assert [o['type'] for o in ops] == ['mul', 'mul', 'mul', 'mul', 'mod', 'unk']
# Function analysis: the delay slot stays in the branch's basic block.
bbs = json.loads(r2('e asm.cpu=usip;wx 7e0cc00c5c3907f0541c805c07f0141cc06c3e015000400ac000def9@0x40c;af@0x40c;afbj@0x40c', 0x8000)[0])
bb = {b['addr']: b for b in bbs}
assert bb[0x410]['size'] == 8 and bb[0x410]['jump'] == 0x41e and bb[0x40c]['jump'] == 0x418 and bb[0x40c]['fail'] == 0x410
ops = json.loads(r2('wx 0f123401;aoj 1')[0])
assert ops[0]['val'] == 0x12340000
# Table update (726 forms). loop #N with the F' = 0 tail at +2 (the middle unit a 16-bit op of the packet),
# validated and unverified (the end lies beyond the bytes at hand); the h = 1 heads (N >= 128) with the adjacent F = 0 tail and
# with the S = 1 tail; the m = 1 heads (N >= 256: loop #N?) validated, and left to the 16-bit readings when the end cannot be
# checked; the h = 1 head with the S = 0 tail at +2 validated and, unverified, the W6 h = 1 cell st.h rB, [r8+rrr] instead.
ops = json.loads(r2('e asm.cpu=usip;wx 41ef505fa03a5247f8065c444cb06027dcca047c06bc0547c54b0ebcc0470e0cc1471c4460c7427cc5454c60d0e0006cc5460c5cc0450a0cc145104460c5c813@0xd0bc;aoj 3@0xd0bc', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #15, last=0x0000d0f6' and ops[0]['ptr'] == 0xd0f6 and ops[0]['val'] == 15 and ops[0]['opex']['form'] == 'loop_imm_F1_Fp0'
assert ops[0]['opex']['target_guard'] == 'validated' and ops[0]['opex']['units'] == [0xd0bc, 0xd0c0] and ops[1]['opcode'] == 'movi r8, #0xb'
ops = json.loads(r2('e asm.cpu=usip;wx 41ef505fa03a@0xd0bc;aoj 2@0xd0bc', 0x20000)[0])
assert ops[0]['opcode'] == 'loop#? #15, last=0x0000d0f6' and ops[0]['opex']['target_guard'] == 'unverified' and ops[1]['opcode'] == 'movi r8, #0xb'
ops = json.loads(r2('e asm.cpu=usip;wx 1caf600ac6074434e004003460a4c623@0xf91a;aoj 2@0xf91a', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #229, last=0x0000f924' and ops[0]['val'] == 229 and ops[0]['opex']['form'] == 'loop_imm_h1_F0S0' and ops[1]['opcode'] == 'movi r3, #0x0'
ops = json.loads(r2('e asm.cpu=usip;wx 5e0fe00e10c438c760403801d8134201@0xa1de;aoj 2@0xa1de', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #240, last=0x0000a1ec' and ops[0]['opex']['form'] == 'loop_imm_h1_F1S1' and ops[0]['opex']['target_guard'] == 'validated'
assert ops[1]['opcode'] == 'ld.hu r0, [r8+r12]' and ops[1]['opex']['form'] == 'idx_4_ld_hu' and ops[1]['type'] == 'load'
ops = json.loads(r2('e asm.cpu=usip;wx 200f6012d6070eb4602a6c6d961314acc44a0ca7fc460c07@0xc55e;aoj 2@0xc55e', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #256, last=0x0000c570' and ops[0]['val'] == 256 and ops[0]['opex']['form'] == 'loop_imm_m1_F0S0'
assert ops[0]['opex']['target_guard'] == 'validated' and ops[0]['opex']['confidence'] == 'medium' and ops[1]['opcode'] == 'movi r11, #0x0'
ops = json.loads(r2('e asm.cpu=usip;wx 200f6012d607@0xc55e;aoj 2@0xc55e', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['movi r0!, #0x1', 'ld.w r1, [r0+0]']
ops = json.loads(r2('e asm.cpu=usip;wx 740f484c201ac8071844184760063805d9becb52064460a6c843cf56@0x1c5c;aoj 1@0x1c5c', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #416, last=0x00001c76' and ops[0]['opex']['form'] == 'loop_imm_m1_F1_Fp0' and ops[0]['opex']['target_guard'] == 'validated'
ops = json.loads(r2('e asm.cpu=usip;wx 500f5c066016c0045214e0424021efff10146042c2134023@0xf44c;aoj 2@0xf44c', 0x20000)[0])
assert ops[0]['opcode'] == 'loop# #128, last=0x0000f462' and ops[0]['opex']['form'] == 'loop_imm_h1_F1S0' and ops[1]['opcode'] == 'add r0, r14, #0x2'
ops = json.loads(r2('e asm.cpu=usip;wx 500f5c066016c004@0xf44c;aoj 2@0xf44c', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['st.h r0, [r8]', 'add r0, r14, #0x2'] and ops[0]['opex']['form'] == 'st_h16_f_slot0_h1'
# bz.s in 4- and 3-unit packets (the rest of the packet is the delay slot; the target must be a packet start: left .hword
# when it cannot be checked); the beq.d of op 9 with base sp and the adjacent tail is not a branch; the bgt rA, #-1 head
# class only takes the adjacent S = 1 tail (here two 16-bit ops around a cmov pair).
ops = json.loads(r2('e asm.cpu=usip;wx 48dd4a342023875e0450046050021004c80704205006083c485c00a380a5086c082c00a6c0a25c315c516054e0565c815c612040be794440@0x4ce;aoj 3@0x4ce', 0x20000)[0])
assert ops[0]['opcode'] == 'bz.s r4, 0x00000504' and ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0x504 and ops[0]['delay'] == 2 and ops[0]['cond'] == 'eq'
assert ops[0]['opex']['form'] == 'bz_s_P4_u4' and ops[0]['opex']['target_guard'] == 'validated' and [o['opcode'] for o in ops[1:]] == ['ld.h r3, [r5+r3]', 'add r2, r3, r5']
ops = json.loads(r2('e asm.cpu=usip;wx 48dd4a342023875e@0x4ce;aoj 2@0x4ce', 0x20000)[0])
assert ops[0]['opcode'] == '.hword 0x48dd' and ops[1]['opcode'] == 'ld.h r3, [r5+r3]'
ops = json.loads(r2('e asm.cpu=usip;wx 402d78fffed3042ac0026e21@0x19c6;aoj 3@0x19c6', 0x20000)[0])
assert ops[0]['opcode'] == 'bz.s r0, 0x000019d0' and ops[0]['opex']['form'] == 'bz_s_P4_u3' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['delay'] == 2
assert [o['opcode'] for o in ops[1:]] == ['movi r12!, #0x1f', 'addi r15, #-0x26']
ops = json.loads(r2('e asm.cpu=usip;wx 1ca964462849ebc0@0x1a60;aoj 3@0x1a60', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['sub r6, r10', 'xor r4, r2!, #0x1e0', 'and r4, r4']
ops = json.loads(r2('e asm.cpu=usip;wx 4bfa56177e0ef863@0xf834;aoj 3@0xf834', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r15, [r5+20]', 'cmov r3, r11, r1', 'st.w r0, [r15+16]']
# The W6 cells with h = 1 (rA = r8-r15), also with the post-increment, and the loop guard: the head of a validated loop rX keeps
# the loop (unverified: the st.h cell). The R-F0d middle unit re-read as the h = 1 cell.
ops = json.loads(r2('e asm.cpu=usip;wx 5c2f002cc120@0x324c;aoj 2@0x324c', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['st.h r2, [r14]', 'shl r0, r0, r2'] and ops[0]['opex']['form'] == 'st_h16_f_slot0_h1' and ops[0]['type'] == 'store' and ops[0]['stack'] == 'set'
ops = json.loads(r2('e asm.cpu=usip;wx 5c105cab9002@0x3260;aoj 2@0x3260', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r1, [r14+0x2]', 'ld.h r10, [r14]'] and ops[0]['opex']['form'] == 'rf0d_ld_h' and ops[1]['opex']['form'] == 'ld_h16_b_slot0_h1'
ops = json.loads(r2('e asm.cpu=usip;wx d787@0xf6c2;aoj 1@0xf6c2', 0x20000)[0])
assert ops[0]['opcode'] == 'st.h r8, [r11]+' and ops[0]['opex']['form'] == 'st_h16_7_slot0_h1' and ops[0]['ireg'] == 'r11'
ops = json.loads(r2('e asm.cpu=usip;wx 59a39596@0x3c46;aoj 2@0x3c46', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ld.h r10, [r12]+', 'dec r9, r10'] and ops[0]['opex']['form'] == 'ld_h16_3_slot0_h1' and ops[0]['sign']
ops = json.loads(r2('e asm.cpu=usip;wx 540ff00cf1ab543cf006f9570a0b@0xddd6;aoj 2@0xddd6', 0x20000)[0])
assert ops[0]['opcode'] == 'loop r10, last=0x0000dde2' and ops[0]['opex']['form'] == 'loop' and ops[0]['opex']['target_guard'] == 'validated' and ops[1]['opcode'] == 'addi r8, #-0xb'
ops = json.loads(r2('e asm.cpu=usip;wx 540ff00c@0xddd6;aoj 1@0xddd6', 0x20000)[0])
assert ops[0]['opcode'] == 'st.h r0, [r10]' and ops[0]['opex']['form'] == 'st_h16_f_slot0_h1'
# The 16-bit op 2 cells: op2z? (x = 0, B = 0), lsl / asr r8-r15 with h = 1; the 16-bit lea without the ?.
ops = json.loads(r2('e asm.cpu=usip;wx 409f42165000c002@0xaf98;aoj 3@0xaf98', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['st.h r9, [r0]', 'sne r1, r1, #0x0', 'op2z? r0'] and ops[2]['opex']['form'] == 'op2z_q' and ops[2]['type'] == 'unk'
ops = json.loads(r2('e asm.cpu=usip;wx 060810127337ea7c@0x4080;aoj 2@0x4080', 0x20000)[0])
assert ops[1]['opcode'] == 'lsl r8, #0x1' and ops[1]['type'] == 'shl' and ops[1]['opex']['form'] == 'lsl16_imm_h1' and ops[0]['opcode'] == 'call.abs 0x1ccdea7c'
ops = json.loads(r2('e asm.cpu=usip;wx 9ff2@0x96a4;aoj 1@0x96a4', 0x20000)[0])
assert ops[0]['opcode'] == 'asr r15, #0xf' and ops[0]['type'] == 'sar' and ops[0]['opex']['form'] == 'asr16_imm_h1'
ops = json.loads(r2('e asm.cpu=usip;wx 9298@0x251c;aoj 1@0x251c', 0x20000)[0])
assert ops[0]['opcode'] == 'lea r9, [sp+0x4c]' and ops[0]['type'] == 'lea' and ops[0]['opex']['form'] == 'lea_sp'
# The post-layout splits of the slot family: .op4t0 head + adjacent S = 0 tail (R-slot4n), .op4imm with a named tail in a 2-unit
# and in a longer packet (R-op4imm: stslot?), the S = 0 unit at instruction position 1 (S0-slot: stslot / ldslot?), the S3 cells
# ldslot / stslot rB, #48-63 in the first slot / as the closer.
ops = json.loads(r2('e asm.cpu=usip;wx 000443d43a019411@0xd7ee;aoj 4@0xd7ee', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot r0, #0x0', 'stslot r13!, #0x2', 'mov r0, r13!', 'mov r1, r10']
assert ops[0]['opex']['form'] == 'stslot_F0' and ops[1]['opex']['form'] == 'stslot_S0_pos1' and ops[0]['type'] == 'store' and ops[0]['size'] == 2
ops = json.loads(r2('e asm.cpu=usip;wx 17e48dbc@0xf0e6;aoj 2@0xf0e6', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot r14!, #0x16', 'stslot r11!, #0xd'] and ops[0]['opex']['form'] == 'stslot_F0_k16' and ops[1]['opex']['form'] == 'stslot_F0'
ops = json.loads(r2('e asm.cpu=usip;wx 2e1010201294040000048001@0x10f2;aoj 3@0x10f2', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['addi r1, r7!, #0x200', 'addi r2, r8, #0x2', 'stslot? r9, #0x12'] and ops[2]['opex']['form'] == 'stslot_F0_q'
ops = json.loads(r2('e asm.cpu=usip;wx 405e41c4b64a@0x4dbc;aoj 2@0x4dbc', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['call? 0x0000a406', 'stslot r12!, #0x0'] and ops[1]['opex']['form'] == 'stslot_S0_pos1' and ops[1]['opex']['delay_slot']
ops = json.loads(r2('e asm.cpu=usip;wx 5024442020040c47fcaf@0x17a8;aoj 2@0x17a8', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r4, [r8+r2]', 'ldslot r2, #0x4'] and ops[1]['opex']['form'] == 'ldslot_S0_pos1' and ops[1]['type'] == 'load'
ops = json.loads(r2('e asm.cpu=usip;wx 32907218b231@0x4d82;aoj 3@0x4d82', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ldslot r9, #0x32', 'ldslot r1, #0x33', 'mov r3, r9!'] and ops[0]['opex']['form'] == 'ldslot_S3_F0' and ops[1]['opex']['form'] == 'ldslot_S0_pos1'
ops = json.loads(r2('e asm.cpu=usip;wx 4a09006672b82002bdd4@0x16f8;aoj 5@0x16f8', 0x20000)[0])
assert ops[4]['opcode'] == 'stslot r13!, #0x3c' and ops[4]['opex']['form'] == 'stslot_S3_F0' and ops[0]['opcode'] == 'blt.d? r5, #0x1, 0x000017b0'
# The post-layout fusion op72? (movi rA || asr? rA of the same rrr), saturate? (op c with B = 0 and rd = rs1), the sat40.a? H H T T.
ops = json.loads(r2('e asm.cpu=usip;wx 02070312983f@0xe928;aoj 2@0xe928', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['op72? r1, #0x0, asr#0x1', 'movi r12, #0x7'] and ops[0]['opex']['form'] == 'op72_q' and ops[0]['opex']['units'] == [0xe928, 0xe92a]
assert ops[0]['size'] == 4 and ops[0]['opex']['confidence'] == 'low' and ops[0]['type'] == 'unk'
ops = json.loads(r2('e asm.cpu=usip;wx 020cc001@0xc1a4;aoj 1@0xc1a4', 0x20000)[0])
assert ops[0]['opcode'] == 'saturate r1, r1' and ops[0]['opex']['form'] == 'dsp_00_saturate' and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 000c020c08c0c8c1@0x1d92;aoj 2@0x1d92', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['sat40.a? A0, A0', 'sat40.a? A1, A1'] and ops[0]['opex']['units'] == [0x1d92, 0x1d96] and ops[1]['opex']['units'] == [0x1d94, 0x1d98]
# The score rules: R-waw with the P2W exemption of the H H T T prefix (ld.w || st.w of the = 2 class in an 8-unit packet), the
# type-111 H H T T prefix (R-b111pre), the control + type-111 store layout (R-b111ctl), the b.s to the next packet read as the
# st.h head (R-bsnext). The branch + ld/st prefix (R-brHHTT) is only known at a weak cell the table omits:
# the plugin keeps the grammar's reading there.
ops = json.loads(r2('e asm.cpu=usip;wx 1010101143f253d60c0c4e0c00c680c7@0xc7ae;aoj 6@0xc7ae', 0x20000)[0])
assert [o['opcode'] for o in ops][:2] == ['ld.w r1, [r8+0x13f0]', 'st.w r1, [r8+0x13d4]'] and ops[4]['opcode'] == 'L_shr r6, r6, r0' and ops[5]['opcode'] == 'L_shr r7, r7, r0'
assert ops[0]['opex']['units'] == [0xc7ae, 0xc7b2] and ops[1]['opex']['units'] == [0xc7b0, 0xc7b4]
ops = json.loads(r2('e asm.cpu=usip;wx 1c011c117016781600005818c404@0x4ea;aoj 7@0x4ea', 0x20000)[0])
assert [o['opcode'] for o in ops][:2] == ['st.b r0, [r14+0x16]', 'st.b r1, [r14+0x17]'] and ops[0]['opex']['form'] == 'b111_st_b_t7_F0S0' and ops[0]['opex']['units'] == [0x4ea, 0x4ee]
assert [o['opcode'] for o in ops][4:] == ['nop', 'ldslot r1, #0x19', 'stslot r0, #0x4']
ops = json.loads(r2('e asm.cpu=usip;wx 400e5c01654cf8e2@0xf5d4;aoj 2@0xf5d4', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['b? 0x0000fb20', 'st.b r0, [r14+0xe3]'] and ops[1]['opex']['form'] == 'b111_st_b_t7_F1S1' and ops[1]['opex']['units'] == [0xf5d6, 0xf5da]
ops = json.loads(r2('e asm.cpu=usip;wx 5c11e04e@0xd4c;aoj 1@0xd4c', 0x20000)[0])
assert ops[0]['opcode'] == 'st.h r1, [r14+0x4e]' and ops[0]['opex']['form'] == 'mem_st_h_t6_F1S1' and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 26090c2042187054044740473e65be60@0x7182;aoj 3@0x7182', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['and r3, r0', 'ld.w r2, [r6+0x218]', '.hword 0x7054']
# The frame allocation sub r14, #f12/2 with any F / F', the immediate store to [sp] (the ninth stack argument), st.abs with the
# hi F' = 0, add rB, rB, #imm27 / st.h? [rB], #sat by the immediate value (also the cell-E triples), mtcsr of the accumulator CSRs
# without the ?, the r15-based add? / ld.h? with the F' = 0 tail, movi #simm6 in the delay slot of a validated branch (R-movi6d).
ops = json.loads(r2('e asm.cpu=usip;wx 342140005ce0db60@0x400e;aoj 2@0x400e', 0x20000)[0])
assert ops[1]['opcode'] == 'sub r14, #0x5b0' and ops[1]['opex']['form'] == 'spsub_sub_sp' and ops[1]['stackptr'] == 0x5b0 and ops[1]['stack'] == 'inc' and ops[0]['opcode'] == 'ld.b? r2, [r10!+0x0]'
ops = json.loads(r2('e asm.cpu=usip;wx 40e0400100004a6ac99a@0x9812;aoj 2@0x9812', 0x20000)[0])
assert ops[0]['opcode'] == 'st.w [sp], #0x99a' and ops[0]['opex']['form'] == 'stimmsp_st_w_sp' and ops[0]['val'] == 0x99a and ops[0]['stack'] == 'set' and ops[0]['ireg'] == 'r14'
assert ops[0]['opex']['units'] == [0x9812, 0x9816, 0x981a] and ops[1]['opcode'] == 'ld.b r0, [r0+0x26b]'
ops = json.loads(r2('e asm.cpu=usip;wx 06086c067337238818f08014@0x32ce;aoj 2@0x32ce', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['call.abs 0x1ccdd8f0', 'st.abs r0, [#0xe20014]'] and ops[1]['opex']['form'] == 'st_abs_hiF0' and ops[1]['ptr'] == 0xe20014
assert ops[1]['opex']['units'] == [0x32d0, 0x32d4, 0x32d8]
ops = json.loads(r2('e asm.cpu=usip;wx 1c06301740547b0300106004c000@0x600a;aoj 5@0x600a', 0x20000)[0])
assert ops[4]['opcode'] == 'add r1, r1, #0x10000' and ops[4]['opex']['form'] == 'addq_add_F0' and ops[4]['size'] == 6 and ops[4]['type'] == 'add'
ops = json.loads(r2('e asm.cpu=usip;wx 003028566002c000@0x9824;aoj 2@0x9824', 0x20000)[0])
assert ops[0]['opcode'] == 'st.h? [r3], #0x8000' and ops[0]['opex']['form'] == 'addq_st_h_q_F0' and ops[0]['val'] == 0x8000 and ops[1]['opcode'] == 'mfacc r4, A1.ext'
ops = json.loads(r2('e asm.cpu=usip;wx 0000262960008fff@0x463e;aoj 1@0x463e', 0x20000)[0])
assert ops[0]['opcode'] == 'st.h? [r0], #0xfff' and ops[0]['opex']['form'] == 'cellE_st_h_q_Fpp0_sat' and ops[0]['val'] == 0xfff
ops = json.loads(r2('e asm.cpu=usip;wx 003001c260009000@0x5dbe;aoj 1@0x5dbe', 0x20000)[0])
assert ops[0]['opcode'] == 'add r3, r3, #0x1000' and ops[0]['opex']['form'] == 'cellE_add_Fpp0'
ops = json.loads(r2('e asm.cpu=usip;wx 0256600600406000c8e8@0x347c;aoj 2@0x347c', 0x20000)[0])
assert ops[1]['opcode'] == 'add r4, r4, #0x8e8' and ops[1]['opex']['form'] == 'cellE_add_k2' and ops[0]['opcode'] == 'and r5, r1, #0x3'
ops = json.loads(r2('e asm.cpu=usip;wx 05877b626a07e435@0x4d00;aoj 2@0x4d00', 0x20000)[0])
assert ops[1]['opcode'] == 'mtcsr #0x35, r5!' and ops[1]['opex']['form'] == 'mtcsr_acc' and ops[1]['opex']['confidence'] == 'medium'
ops = json.loads(r2('e asm.cpu=usip;wx 1e100840543f9518@0x60fe;aoj 3@0x60fe', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['add? r1, r15, #0x420', 'movi r10, #0x7', '.hword 0x9518'] and ops[0]['opex']['form'] == 'x1e0F0_add_q' and ops[0]['type'] == 'add'
ops = json.loads(r2('e asm.cpu=usip;wx 1e511e70423010e842499e5e@0x8fee;aoj 2@0x8fee', 0x20000)[0])
assert ops[1]['opcode'] == 'ld.h? r7, [r15+0xe8]' and ops[1]['opex']['form'] == 'x1e0F0_ld_h_q' and ops[1]['ireg'] == 'r15' and ops[0]['opcode'] == 'ld.b r5, [r15+0x230]'
ops = json.loads(r2('e asm.cpu=usip;wx 040a761240618fe748096d170200c946dce90677@0x1c7c;aoj 3@0x1c7c', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['bgt.d r2, #0x3, 0x00001c8e', 'mov r6, r0', 'movi r7, #-0x4'] and ops[2]['opex']['form'] == 'movi6_dsbr' and ops[2]['type'] == 'mov'
assert ops[0]['opex']['target_guard'] == 'validated' and ops[2]['opex']['delay_slot']
# Table update (760 forms). The packet executes in parallel: the second add reads the old r3.
ops = json.loads(r2('e asm.cpu=usip;wx 7a3646464004c3ff@0xc38c;aoj 2@0xc38c', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['add r3, r13!, #0x2', 'add r4, r3, #-0x1'] and ops[0]['opex']['units'] == [0xc38c, 0xc390] and ops[1]['opex']['units'] == [0xc38e, 0xc392]
# movi rB, #imm17: the op 7 head with the null-field tail (m = 0 is -65536 / -65535; the former -32768 / -32767 cells are the m = 1 ones).
ops = json.loads(r2('e asm.cpu=usip;wx 002702078001@0x4dce;aoj 2@0x4dce', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['movi r2, #-0x10000', 'movi r1, #0x0'] and ops[0]['opex']['form'] == 'movi_imm17' and ops[0]['opex']['units'] == [0x4dce, 0x4dd2]
assert ops[0]['val'] == (-0x10000) & 0xffffffffffffffff and ops[0]['type'] == 'mov'
ops = json.loads(r2('e asm.cpu=usip;wx 2067c001@0x78ac;aoj 1@0x78ac', 0x20000)[0])
assert ops[0]['opcode'] == 'movi r6, #-0x8000' and ops[0]['opex']['form'] == 'movi_imm17' and ops[0]['size'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx 3127434a00018000@0x6174;aoj 2@0x6174', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['movi r2!, #-0x4000', 'movi r4!, #0x800'] and ops[0]['opex']['form'] == 'movi_imm17' and ops[1]['opex']['form'] == 'movi21'
# movhi with the small F' = 0 tails: c001 (0x10000000) and the even f12 (0x1000..0x7000).
ops = json.loads(r2('e asm.cpu=usip;wx 0018c001@0x3000;aoj 1@0x3000', 0x20000)[0])
assert ops[0]['opcode'] == 'movhi r1, #0x10000000' and ops[0]['opex']['form'] == 'movhi_small_c001' and ops[0]['val'] == 0x10000000
ops = json.loads(r2('e asm.cpu=usip;wx 00288004@0x3010;aoj 1@0x3010', 0x20000)[0])
assert ops[0]['opcode'] == 'movhi r2, #0x00002000' and ops[0]['opex']['form'] == 'movhi_small_Fp0' and ops[0]['val'] == 0x2000
# The 16-bit ld.w rB, [rA -/+ 4] of the first slot, the one-unit st.w rB, [rA -/+ 4] and the class-E addi rA, #-4.
ops = json.loads(r2('e asm.cpu=usip;wx c10242075206790ccbdf@0x2ef6;aoj 1@0x2ef6', 0x20000)[0])
assert ops[0]['opcode'] == 'ld.w r0, [r0+0x4]' and ops[0]['opex']['form'] == 'ld_w16_2_k0_F1' and ops[0]['type'] == 'load' and ops[0]['disp'] == 4 and ops[0]['refptr'] == 4
ops = json.loads(r2('e asm.cpu=usip;wx c22294a97fee@0x2f5e;aoj 1@0x2f5e', 0x20000)[0])
assert ops[0]['opcode'] == 'ld.w r2, [r1-0x4]' and ops[0]['opex']['form'] == 'ld_w16_2_k0_F1' and ops[0]['size'] == 2
ops = json.loads(r2('e asm.cpu=usip;wx c862ca66c863ca67@0xcefa;aoj 2@0xcefa', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['ld.w r6, [r4-0x4]', 'st.w r6, [r5-0x4]'] and ops[1]['opex']['form'] == 'st_w16_6_alone_F1' and ops[1]['type'] == 'store'
ops = json.loads(r2('e asm.cpu=usip;wx 61e3a3e3@0xcf2a;aoj 2@0xcf2a', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['addi r0, #-0x4', 'addi r1, #-0x4'] and ops[0]['opex']['form'] == 'addi7_E_neg' and ops[0]['type'] == 'add'
# Hardware loops: loop rX, last, x16 (B = 1) validated; loop #N (m = 1, without the ?) with bit 4 of the head agreeing with the
# 16-byte line of its end packet (seen before, crossing a line) and rejected when that packet does not cross one; the
# loop #N? rule (the end at pc + 0x100), validated and unverified.
ops = json.loads(r2('e asm.cpu=usip;wx 041f701040398239c122c3264022cffb4223dffb@0xcf2e;aoj 1@0xcf2e', 0x20000)[0])
assert ops[0]['opcode'] == 'loop r2, last=0x0000cf3e, x16' and ops[0]['opex']['form'] == 'loop_b1' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['ptr'] == 0xcf3e
ops = json.loads(r2('e asm.cpu=usip;wx 00000000000000008000@0xcb08;wx 201f6062071044071356f8c0@0xcaa6;aoj 1@0xcb08;aoj 2@0xcaa6', 0x20000)[1])
assert ops[0]['opcode'] == 'loop# #256, last=0x0000cb08' and ops[0]['opex']['form'] == 'loop_imm_m1_F0S0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['val'] == 256
assert ops[1]['opcode'] == 'mov32 r1!, #0x1cd5b8c0'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0xdb08;wx 201f6062071044071356f8c0@0xdaa6;aoj 1@0xdb08;aoj 2@0xdaa6', 0x20000)[1])
assert [o['opcode'] for o in ops] == ['movi r0!, #0x3', 'ld.w r6, [r0+0]']
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x526c;wx 420fe100@0x516c;aoj 1@0x526c;aoj 1@0x516c', 0x20000)[1])
assert ops[0]['opcode'] == 'loop#? #16?, last=0x0000526c' and ops[0]['opex']['form'] == 'loop_imm_F1S1' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['val'] == 16
ops = json.loads(r2('e asm.cpu=usip;wx 420fe100@0x516c;aoj 1@0x516c', 0x20000)[0])
assert ops[0]['opcode'] == 'loop#?? #16?, last=0x0000526c' and ops[0]['opex']['target_guard'] == 'unverified'
# b<cond>.l with the 22-bit displacement (backward: validated by the session, else .word) and the 64-bit variant whose
# extension unit is any 16-bit unit X: the branch keeps U0 U1 T and X is the first instruction of the delay slot.
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x1d6f6;wx 409d51fffc92@0x1ddd2;aoj 1@0x1d6f6;aoj 1@0x1ddd2', 0x20000)[1])
assert ops[0]['opcode'] == 'bge.l r9, #0x1, 0x0001d6f6' and ops[0]['jump'] == 0x1d6f6 and ops[0]['cond'] == 'ge' and ops[0]['opex']['form'] == 'bcc48_opd_mh1_bge' and ops[0]['size'] == 6
ops = json.loads(r2('e asm.cpu=usip;wx 409d51fffc92@0x1edd2;aoj 1@0x1edd2', 0x20000)[0])
assert ops[0]['opcode'] == '.word 0x409d51ff'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x1e60;wx 021f20276100c10352845416@0x1c5a;aoj 1@0x1e60;aoj 5@0x1c5a', 0x20000)[1])
assert ops[0]['opcode'] == 'blt.l r1, #0x11, 0x00001e60' and ops[0]['opex']['form'] == 'bcc48_opf_mh2_blt_64x' and ops[0]['opex']['target_guard'] == 'validated'
assert ops[0]['size'] == 2 and ops[0]['delay'] == 2 and ops[0]['opex']['units'] == [0x1c5a, 0x1c5e, 0x1c60] and ops[0]['jump'] == 0x1e60
assert [o['opcode'] for o in ops[1:]] == ['movi r0!, #0x4', '.unit 0x6100c103 (blt.l @ 0x00001c5a)', 'stslot r8, #0x12', 'add r1, r10, #0x0'] and ops[1]['opex']['delay_slot']
ops = json.loads(r2('e asm.cpu=usip;wx 021f20276100c10352845416@0x1f5a;aoj 2@0x1f5a', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['movi r1, #0x3', '.op7t']
# The op c F = 1 branches with the F' = 0 tail at +2 (beqz / bnez: validated by the scan, unverified beyond it) and the
# op 9 F = S = 0 F' = 0 cell of >= 4 units (blt.d: validated, else the layout's DSP reading).
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x6324;wx 740c411690a4@0x6280;aoj 1@0x6324;aoj 3@0x6280', 0x20000)[1])
assert ops[0]['opcode'] == 'beqz r10!, 0x00006324' and ops[0]['opex']['form'] == 'beqz_F0c' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['jump'] == 0x6324
assert ops[0]['delay'] == 2 and ops[0]['cond'] == 'eq' and [o['opcode'] for o in ops[1:]] == ['dec r1, r0', '.unit 0x90a4 (beqz @ 0x00006280)'] and ops[1]['opex']['delay_slot']
ops = json.loads(r2('e asm.cpu=usip;wx 740c411690a4@0x7280;aoj 1@0x7280', 0x20000)[0])
assert ops[0]['opcode'] == 'beqz? r10!, 0x00007324' and ops[0]['opex']['target_guard'] == 'unverified' and ops[0]['opex']['confidence'] == 'medium'
pk = '4609424f342cc44100664004e75d02564004cb764e0ff016cd730e4c082c0167c1640e0c080c0147c144cb77'
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x951e;aoj 4@0x951e' % pk, 0x20000)[0])
assert ops[0]['opcode'] == 'blt.d r3, #0x2, 0x0000954a' and ops[0]['opex']['form'] == 'blt_imm8_F0s0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['cond'] == 'lt'
assert ops[0]['delay'] == 3 and [o['opcode'] for o in ops[1:]] == ['movi r1, #0x9', '.unit 0x342c (blt.d @ 0x0000951e)', 'mov r4, r2']
ops = json.loads(r2('e asm.cpu=usip;wx 4609424f342cc441@0xa51e;aoj 3@0xa51e', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['add r3, r0', 'movi r1, #0x9', 'abs_s r1, r10']
# bz.s with the P4 cell F = 0: in a 3-unit packet (the target validated by the scan, the rest the delay slot) and in a
# 2-unit one (the target seen before); with an unknown target the layout's reading stays.
pk = '00f5521fbe7946087338dad0000c4108c0817ffefee3a10d408cf10e400ade5682270017fca05e067488ea306e1440bac4004216d4025c490200f0005e067488ea306e1440bac400'
ops = json.loads(r2('e asm.cpu=usip;wx %s@0x114e;aoj 3@0x114e' % pk, 0x20000)[0])
assert ops[0]['opcode'] == 'bz.s r0, 0x0000118a' and ops[0]['opex']['form'] == 'bz_s_P4_F0' and ops[0]['opex']['target_guard'] == 'validated' and ops[0]['jump'] == 0x118a
assert ops[0]['delay'] == 2 and ops[0]['cond'] == 'eq' and [o['opcode'] for o in ops[1:]] == ['movi r9, #0x3', 'nop']
ops = json.loads(r2('e asm.cpu=usip;wx 00f5521fbe79@0x414e;aoj 1@0x414e', 0x20000)[0])
assert ops[0]['opcode'] == '.op5t1 r15, r0, #0x21f'
ops = json.loads(r2('e asm.cpu=usip;wx 8000@0x2de6;wx 03a58290@0x2d7e;aoj 1@0x2de6;aoj 2@0x2d7e', 0x20000)[1])
assert ops[0]['opcode'] == 'bz.s r1, 0x00002de6' and ops[0]['opex']['form'] == 'bz_s_P4_F0' and ops[0]['delay'] == 1 and ops[1]['opcode'] == '.ext'
ops = json.loads(r2('e asm.cpu=usip;wx 03a58290@0x3d7e;aoj 2@0x3d7e', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['.hword 0x03a5', '.ext']
# The slot family: slotpair0 (two F-slot0 units without a named 32-bit reading, k >= 16), the op c slot pairs firm with a
# call within two packets and stslot? without one, the frame store in the delay slot of call.abs (R-fslot0c), and the
# fusion at distance 2 add rB, rA, #rot12 (R-add2) next to the adjacent one.
ops = json.loads(r2('e asm.cpu=usip;wx 258ca584@0xfc10;aoj 2@0xfc10', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot r8!, #0x25', 'stslot r8!, #0x24'] and ops[0]['opex']['form'] == 'stslot_F0_k16' and ops[0]['type'] == 'store' and not ops[0]['opex']['packet']['ambiguous']
ops = json.loads(r2('e asm.cpu=usip;wx 002c40041201b4217f9efbe35210c0301c001230410c50b0@0xb28e;aoj 4@0xb28e', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot r2, #0x1', 'stslot r0, #0x0', 'mov r0, r9', 'mov r2, r10!'] and ops[0]['opex']['form'] == 'stslot_slotc_h' and ops[1]['opex']['form'] == 'stslot_slotc_t'
ops = json.loads(r2('e asm.cpu=usip;wx 002c40041201b421@0xd28e;aoj 2@0xd28e', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot? r2, #0x1', 'stslot? r0, #0x0'] and ops[0]['opex']['form'] == 'stslot_slotc_h_q' and ops[1]['opex']['form'] == 'stslot_slotc_t_q'
ops = json.loads(r2('e asm.cpu=usip;wx 0608021c729bc2c0@0x111c;aoj 2@0x111c', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['call.abs 0x1ca6c2c0', 'stslot r1, #0x3'] and ops[1]['opex']['form'] == 'stslot_F0' and ops[1]['opex']['delay_slot']
ops = json.loads(r2('e asm.cpu=usip;wx 0e044f9c1c001c100588cb28@0xd3e0;aoj 4@0xd3e0', 0x20000)[0])
assert [o['opcode'] for o in ops] == ['stslot r0, #0xe', 'stslot r9!, #0xf', 'addi r0, r14, #0x2c4', 'add r1, r14, #0x594']
assert ops[3]['opex']['form'] == 'add2_rot12' and ops[3]['opex']['units'] == [0xd3e6, 0xd3ea] and ops[3]['type'] == 'add' and ops[3]['size'] == 2
print('Plugin loading, analysis, delay slots, operands and ESIL passed')
def corpus():
bundle = ROOT / 'third_party/r2pkg'
if not (bundle / 'bin/usip_sec0.bin').exists():
print('r2pkg bundle absent; skipping optional corpus check')
return
blobs = {False: (bundle / 'bin/usip_sec0.bin').read_bytes(),
True: (bundle / 'bin/usip_ddr.bin').read_bytes()}
inp, expected = [], []
fs = list(functions())
assert len(fs) == 1128
for start, end, name in fs:
ddr = start >= 0x100000
data = blobs[ddr]
origin = 0x1ca34000 if ddr else 0
pos, stop = start - origin, end - origin
assert 0 <= pos < stop <= len(data), (name, start, end)
while pos < stop:
size = 2 if data[pos] & 0x80 else 4
assert pos + size <= stop, name
code = data[pos:pos + size].ljust(4, b'\0')
inp.append('%s %d %x\n' % (code.hex(), size, origin + pos))
expected.append((size, code[:size]))
pos += size
result = subprocess.run([str(ROOT / 'test/test_md32'), '--stream', 'usip'],
input=''.join(inp), text=True, capture_output=True, check=True)
lines = result.stdout.splitlines()
assert len(lines) == len(expected) == 100275
counts = collections.Counter()
for line, (size, code) in zip(lines, expected):
fields = line.split('\t')
assert fields[0] == '1' and int(fields[1]) == size, (code.hex(), line)
counts[fields[6].split()[0]] += 1
print('uSIP: all 1128 function boundaries and 100275 instruction lengths match')
print('uSIP decode counts:', json.dumps(dict(sorted(counts.items()))))
if __name__ == '__main__':
integration()
corpus()
+330
View File
@@ -0,0 +1,330 @@
#!/usr/bin/env python3
"""Exercise cpu=scq16 (SCQ16, MD32 family, byte-granular packet variant) inside r2 with loose bytes only: disassembly of
stand-alone units, packets `{ A ‖ B }`, vector ops and bundles; analysis (types, targets relative to the packet start,
delay slots counted in instructions, fall-through after the slots, loop references); opex; ESIL (parallel packet
semantics, delayed call via SETJT/SETD, loads/stores and post-increments, sign/zero extension, division, bit fields,
the VU scalar ports, vop); the newer forms (cmov.ge / cmov.eq with a register / the provisional cmov.*?, mv 67 c8, bset/btst
rd, rd, #pos, lbu.post with 5-bit registers and the 23 page with a signed post-increment, and #0x3fff, the 6-byte and #imm16, ctz?/clb?,
vcfg4c? #0xc, ld46?, the established vector names v.move.mv / v.mem.ld / v.mem.st, lbu? off(sp), halt and sys41 without effect),
j.d1 decided by its target, `.d2` across two `aes` steps, negative stack offsets; the newest forms (ctz, the provisional sqr? / sra? /
vrd?, op41.fld with three registers, the provisional and? masks, lw.post of 2 bytes with a 4-bit destination, the signed post-increment
of the 6e page, the .post templates writing rd last) and the public model of the VU (v.mem.ld / v.mem.st / v.move.mv on vr0-vr31 with
the pointers vp0-vp7 and the counts va0-va7); the newer forms (jr with a 5-bit register, btst rd, rd, #pos with a 5-bit position, ctz as its own
form without the HYP flag, op41.fld with the hypothetical packing ESIL of isa/scq16_hyp.json), the uniform marker of the OPEN readings
(REF,ALT,open; opex open / reading / hypothetical) and the broadcast ports (vbcast / vbcast.i write the 64 lanes of vr{k}); the pseudo
parser; the confidence levels settled by execution results, the packing ESIL of op41.fld carried by the ISA source, and
ctz(0) = -1 under the table ESIL (an open question against the hardware). No firmware is needed."""
import json
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
R2 = os.environ.get('R2', 'r2')
ext = subprocess.check_output([R2, '-H', 'R2_LIBEXT'], text=True).strip()
PLUGIN = ROOT / ('arch_md32.' + ext)
ASM_PLUGIN = ROOT / ('asm_md32.' + ext)
failures = 0
def r2(commands, size=0x8000):
# asm.cpu before asm.arch: the register profile is taken when the arch session is created
setup = 'e scr.color=0;L %s;L %s;e asm.cpu=scq16;e asm.arch=md32;e cfg.bigendian=true;' % (PLUGIN, ASM_PLUGIN)
proc = subprocess.run([R2, '-NN', '-q', '-c', setup + commands + ';q', 'malloc://%d' % size], capture_output=True, text=True, check=True)
return [l for l in proc.stdout.strip().splitlines() if not l.startswith(('WARN', 'INFO', 'ERROR'))]
def check(cond, msg):
global failures
print(('ok ' if cond else 'FAIL ') + msg)
if not cond:
failures += 1
# 1. disassembly: (bytes, size, text) — targets are absolute (code at 0x1000)
DISASM = [
('2005', 2, 'ret'), ('2006', 2, 'ret.d1'), ('2007', 2, 'ret.d2'), ('1402', 2, 'addsp sp, #0x8'), ('207e', 2, 'sw.lr lr, -0x8(sp)'),
('157f', 2, 'lw.lr lr, -0x4(sp)'), ('3084', 2, 'lw r5, 0x4(r1)'), ('3a20', 2, 'sw.post r1, [r3] -= 0x10'), ('2180', 2, 'lw.post r1, [r1] += 4'),
('208a', 2, 'lbu r3, [r2]'), ('2240', 2, 'lbu.post r1, [r1] += 1'), ('1600', 2, 'lhu r1, [r1]'), ('2c10', 2, 'sw.sp r1, -0xfc(sp)'),
('2810', 2, 'lw.sp r1, -0xfc(sp)'), ('0566', 2, 'xtd r7, r5, #16'), ('0540', 2, 'xtd r1, r1, #16'), ('0000', 2, 'and r1, r1, #0x1'),
('0e12', 2, 'mul r3, r2'), ('0f12', 2, 'max r3, r2'), ('0c12', 2, 'or r3, r2'), ('0d56', 2, 'and r7, r6'), ('0b50', 2, 'sub r1, r6'),
('0a12', 2, 'add r3, r2'), ('0912', 2, 'sra r3, r3, #1'), ('0811', 2, 'sll r2, r2, #1'), ('0718', 2, 'mv r9, #-15'),
('0445', 2, 'add r6, r1, #1'), ('0288', 2, 'sll r1, r2, #2'), ('2640', 2, 'beqz r1, #0, 0x00001004'), ('3f02', 2, 'j8 0x00001002'),
('3e00', 2, 'j . (trap)'), ('1810', 2, 'vget r1, vu[1]'), ('1c10', 2, 'vbcast v1, r1'), ('1f20', 2, 'vset.a A2, r1'), ('1fa0', 2, 'vmem.set p2, r1'),
('6a0010', 3, 'j16 0x00001010'), ('6b36ee', 3, 'jal11 0x00000eee'), ('6fecc5', 3, 'jr.d2 r5'), ('680010', 3, 'beqz8 r16, #0, 0x00001000'),
('690010', 3, 'beqz8.d1 r16, #0, 0x00001000'), ('6f0601', 3, 'sw.post r1, [r16] += 0x4'), ('67b068', 3, 'and r8, r3, #0x8000'), ('6b4c74', 3, 'vrd? r20, vb3[3]'),
('6e4020', 3, 'sw r0, 0x0(r1)'), ('6fe020', 3, 'sb r0, [r1]'), ('6584c1', 3, 'add r1, r6, r1 << 2'), ('6c0020', 3, 'lw r0, 0x0(r1)'),
('650020', 3, 'add r0, r1, r0 << 1'), ('67cc20', 3, 'mv r0, r1'), ('67d820', 3, 'add r0, r0, r1'), ('67e020', 3, 'and r0, r0, r1'),
('6d0020', 3, 'lbu r0, 0x0(r1)'),
('56400010', 4, 'jal.d1 0x00001010'), ('41e01904', 4, 'add r4, r8, r6'), ('5c800a21', 4, 'beq #81, r1, 0x00001000'), ('41f00140', 4, 'div a0, r10, r0'),
('48080001', 4, 'mv r1, #0x4000'), ('437acda0', 4, 'vpmem? p0, #0x2c, m13 (b1 7a)'), ('41b51461', 4, 'fxts r1, r3, #5, #10'),
('42141ce6', 4, 'cmov.ge r6, r7, #0, r8'), ('40078000', 4, 'vbcast.i v0, #-0x400 (sub 000)'), ('44000020', 4, 'lbu r0, 0x0(r1)'),
('47c00020', 4, 'sw r0, [r1] += 0'), ('42600000', 4, 'cmov.lt r0, r0, r0, r0'), ('41aaa8c1', 4, 'fxtu r1, r6, #10, #21'), ('4190410f', 4, 'fup r15, r8, #16, #0'),
('55d01b03', 4, 'do.d2 r3, end=0x0000101b (F=27)'), ('5e800000', 4, 'sys #0x0 (0x0000)'), ('41f38400', 4, 'clz r0, r0'), ('41f70008', 4, 'addsp sp, #0x8'),
('40200020', 4, 'mul r0, r0, #1'), ('4a000020', 4, 'add r0, r1, #0x0'), ('4b000020', 4, 'and r0, r1, #0x0'), ('c0000001', 4, 'v.const.movi? op9=0x001 A0 B0 F0,F0,F0'),
('889026e4', 4, '{ mv r1, r3 ‖ beqz r5, #0, 0x0000100e }'), ('90c821e3', 4, '{ lw.post r1, [r2] += 4 ‖ lw.post r4, [r7] += 4 }'),
('903fe90543', 5, '{ sw.lr lr, -0x4(sp) ‖ beqz8.d1 r3, #0, 0x0000102a }'), ('a40003d19125', 6, '{ mv r3, #0x3d ‖ mv r6, r3 }'),
('a17a0e7350813001', 8, '{ cmov.ne r6, r7, #0, r8 ‖ ble.d1 #0, r1, 0x00001013 }'),
('f000000285288a02', 8, 'vbundle v.mem.ld op9=0x00a A0 B0 F0,F0,F0 ‖ add r2, r6 ‖ add r3, r1'),
# the whole page 42 (cmov.ge, cmov.eq with a register, the provisional cmov.leu?/ltu?/lt?/le?, the remaining sel? codes),
# the 67 page (mv, bset/btst rd, rd, #pos), lbu.post? with +3, addsp 14 8x, halt, sys41 (no modelled effect), lbu? off(sp),
# the 4-byte beq rA, rB of 5c 40-6f and j.d1 56 0x-3x decided by its target (0x1000 - 0x32 = 0xfce)
('4213b0c1', 4, 'cmov.ge r1, r6, #5, r7'), ('42c2b5c8', 4, 'cmov.eq r8, r14, r13, r5'), ('42308864', 4, 'cmov.leu? r4, r3, r2, r1'),
('42b0a464', 4, 'cmov.ltu? r4, r3, #9, r1'), ('4270a464', 4, 'cmov.lt? r4, #2, r3, r1'), ('4240a464', 4, 'cmov.le? r4, #9, r3, r1'),
('42008864', 4, 'sel? r4, r3, r2, r1 (q0 c0)'), ('42e08864', 4, 'sel? r4, r3, r2, r1 (q3 c2)'), ('42508864', 4, 'sel? r4, r3, #2, r1 (q1 c1)'),
('42808864', 4, 'sel? r4, r3, x2, r1 (q2 c0)'), ('42d0a464', 4, 'cmov.eq r4, r3, #2, r1'),
('67c875', 3, 'mv r21, r3'), ('67f409', 3, 'bset r9, r9, #0'), ('67f865', 3, 'btst r5, r5, #3'), ('67fb41', 3, 'btst r1, r1, #26'),
('22c5', 2, 'lbu.post? r6, [r1] += 3'), ('148a', 2, 'addsp sp, #0x228'), ('5eb00000', 4, 'halt'),
('41f3a000', 4, 'sys41 sub 0x28'), ('6f2041', 3, 'lbu? r1, -0xfd(sp)'), ('5c68a8e6', 4, 'beq r7, r6, 0x00000a2a'),
('563fffce', 4, 'j.d1 0x00000fce'), ('56200000', 2, '.b2 56 20'), ('88904213b0c1', 6, '{ mv r1, r3 ‖ cmov.ge r1, r6, #5, r7 }'),
# lbu.post with 5-bit registers (6e q2 h0), the 23 page as lbu.post rd, [rb] += simm3 (-4..-1; the 22 page carries +1..+3),
# and #0x3fff (67 a4-a7), the 6-byte and rd, rs, #imm16 (74 sub 1; sub 3 stays open), the provisional ctz?/clb? next to clz,
# vcfg4c? v(17+2k), rs, #0xc (hypothetical ESIL p_k <- rs), ld46? with a post-increment, the established vector names
# v.move.mv / v.mem.ld / v.mem.st (no ?) next to the provisional v.alu3.acc.add? / difsc?, a bundle and a vector head with them
('6e850d', 3, 'lbu.post r13, [r8] += 0x1'), ('23fa', 2, 'lbu.post r3, [r8] += -1'), ('2302', 2, 'lbu.post r3, [r1] += -4'), ('2285', 2, 'lbu.post r6, [r1] += 2'),
('67a468', 3, 'and r8, r3, #0x3fff'), ('67ac68', 3, 'and? r8, r3, #0xfc00?'), ('740004500000', 6, 'and r0, r0, #0x1400'),
('740008500000', 6, 'or r0, r0, #0x1400'), ('74000c500000', 6, 'alu74? r0, r0, #0x1400 (sub 3)'), ('41f38800', 4, 'ctz r0, r0'),
('41f38c00', 4, 'sqr? r0, r0'), ('4c003231', 4, 'vcfg4c? v17, r17, #0xc (q0 s0)'), ('4600a020', 4, 'ld46? r0, [r1] += 0x28'),
('c0000109', 4, 'v.move.mv op9=0x109 A0 B0 F0,F0,F0'), ('c0000002', 4, 'v.mem.ld op9=0x002 A0 B0 F0,F0,F0'), ('c0000003', 4, 'v.mem.st op9=0x003 A0 B0 F0,F0,F0'),
('c000000e', 4, 'v.alu3.acc.add? op9=0x00e A0 B0 F0,F0,F0'), ('c000002e', 4, 'v.alu3.acc.difsc? op9=0x02e A0 B0 F0,F0,F0'),
('e00001092005', 6, '{ v.mem.ld op9=0x012 A0 B0 F0,F0,F1 ‖ ret }'), ('88906e850d', 5, '{ mv r1, r3 ‖ lbu.post r13, [r8] += 0x1 }'),
# ctz (41 f3 0x22, established) next to the provisional sqr? (0x23), the provisional sra? rd, rd, #pos (67 fc-fd) and
# vrd? rd, vb1[x] (1a xy), op41.fld rd, rs, rt, #pos (w[14:10] is a register), the provisional masks and? #0xf000? (67 98-9b, and the
# 2-byte 00/01 with k = 6) / #0xc000? (67 b4-b7), lw.post of 2 bytes with a 4-bit destination (21 aa = r11), the signed 3-bit
# post-increment of the 6e page (x = 4..7 -> -4..-1 units), vector ops with non-zero fields, a vector head and B slots with the newest forms
('67fc75', 3, 'sra? r21, r21, #3'), ('67fd75', 3, 'sra? r21, r21, #11'), ('1a76', 2, 'vrd? r7, vb1[7]'), ('1a00', 2, 'vrd? r1, vb1[0]'),
('418c0d27', 4, 'op41.fld r7, r9, r3, #24'), ('679868', 3, 'and? r8, r3, #0xf000?'), ('67b468', 3, 'and? r8, r3, #0xc000?'), ('018a', 2, 'and? r3, r2, #0xf000?'),
('21aa', 2, 'lw.post r11, [r3] += 4'), ('21a1', 2, 'lw.post r2, [r3] += 4'), ('6ed063', 3, 'lw.post r3, [r3] += -0x10'), ('6ef463', 3, 'sh.post r3, [r3] += -0x6'),
('6ee463', 3, 'sh.post r3, [r3] += 0x2'), ('0912', 2, 'sra r3, r3, #1'), ('c8204002', 4, 'v.mem.ld op9=0x002 A2 B0 F4,F1,F0'), ('c8006003', 4, 'v.mem.st op9=0x003 A2 B0 F0,F1,F16'),
('c0280109', 4, 'v.move.mv op9=0x109 A0 B0 F5,F0,F0'), ('e00807092005', 6, '{ v.mem.ld op9=0x012 A0 B0 F2,F0,F7 ‖ ret }'), ('88901a76', 4, '{ mv r1, r3 ‖ vrd? r7, vb1[7] }'),
('889021aa', 4, '{ mv r1, r3 ‖ lw.post r11, [r3] += 4 }'), ('889041f38800', 6, '{ mv r1, r3 ‖ ctz r0, r0 }'),
# jr rn with a 5-bit register field (r27 / r18 / r29; .d1 / .d2), btst rd, rd, #pos with a 5-bit position (#26, #27), ctz (form 72)
# next to sqr? (form op41, 229), vbcast of 2 and 3 bytes, vbcast.i / vbcast.i?, v.move.mv vr0 <- vr1, B slots with jr / btst / op41.fld / sel?
('6fec9b', 3, 'jr r27'), ('6fecbb', 3, 'jr.d1 r27'), ('6fecdb', 3, 'jr.d2 r27'), ('6fec92', 3, 'jr r18'), ('6fec9d', 3, 'jr r29'),
('67fb41', 3, 'btst r1, r1, #26'), ('67fb65', 3, 'btst r5, r5, #27'), ('6b6020', 3, 'vbcast v1, r0'), ('40100000', 4, 'vbcast.i? v0, #0x0 (sub 010)'),
('c0000309', 4, 'v.move.mv op9=0x109 A0 B0 F0,F0,F1'), ('88906fec9b', 5, '{ mv r1, r3 ‖ jr r27 }'), ('889067fb41', 5, '{ mv r1, r3 ‖ btst r1, r1, #26 }'),
('8890418c0d27', 6, '{ mv r1, r3 ‖ op41.fld r7, r9, r3, #24 }'), ('889042008864', 6, '{ mv r1, r3 ‖ sel? r4, r3, r2, r1 (q0 c0) }'),
# the forms whose confidence was settled by execution (and #0x1 / #0x3 of 2 bytes, add #-1, sb of 4 bytes, btst rd, rs, #pos of 4 bytes), max of 4 bytes, ctz r1, r1
('0001', 2, 'and r2, r1, #0x1'), ('0045', 2, 'and r6, r1, #0x3'), ('04ca', 2, 'add r3, r2, #-1'), ('45401023', 4, 'sb r3, 0x4(r1)'),
('41ee0c41', 4, 'btst r1, r2, #3'), ('41e78041', 4, 'max r1, r2, r0'), ('41f38821', 4, 'ctz r1, r1'), ('889004ca', 4, '{ mv r1, r3 ‖ add r3, r2, #-1 }'),
]
for hx, L, exp in DISASM:
out = r2('wx %s @ 0x1000;pi 1 @ 0x1000;ao 1 @ 0x1000~size' % hx)
text = out[0] if out else ''
check(text == exp and any(l.strip() == 'size: %d' % L for l in out), '%-18s -> %s' % (hx, text))
# 2. analysis
ops = json.loads(r2('wx 56400010 1402 207e 2005 @ 0x2000;aoj 4 @ 0x2000')[0])
check([o['size'] for o in ops] == [4, 2, 2, 2], 'sizes jal.d1/addsp/sw.lr/ret')
check(ops[0]['type'] == 'call' and ops[0]['jump'] == 0x2010 and ops[0].get('delay') == 1 and ops[0]['fail'] == 0x2006, 'jal.d1: call, jump, delay 1, fail after the slot')
check(ops[1]['type'] == 'add' and ops[1].get('stackptr') in (-8, 2**64 - 8), 'addsp sp,#8: stackptr -8')
check(ops[2]['type'] == 'store' and ops[2].get('stack') == 'set' and ops[2].get('ireg') == 'sp', 'sw.lr: store on the stack')
check(ops[3]['type'] == 'ret', 'ret')
ops = json.loads(r2('wx 5c800a21 2640 3f02 @ 0x3000;aoj 3 @ 0x3000')[0])
check(ops[0]['type'] == 'cjmp' and ops[0]['cond'] == 'eq' and ops[0]['jump'] == 0x3000 and ops[0]['fail'] == 0x3004, 'beq #imm, rn: cjmp eq, jump, fail')
check(ops[2]['type'] == 'jmp' and ops[2]['jump'] == 0x3008, 'j8: jump')
ops = json.loads(r2('wx 889026e4 @ 0x4000;aoj 1 @ 0x4000')[0])
check(ops[0]['type'] == 'cjmp' and ops[0]['jump'] == 0x400e and ops[0]['opex']['shape'] == 'packet' and ops[0]['opex']['parallel'] is True, 'packet {mv ‖ beqz}: cjmp with base = packet start, opex parallel')
check(ops[0]['opex']['units'][0]['transformed'] == '11 20' and ops[0]['opex']['units'][0]['form'] == 'mv', "opex: A' = 11 20 (mv r1,r3)")
ops = json.loads(r2('wx 55d01b03 @ 0x5000;aoj 1 @ 0x5000')[0])
check(ops[0].get('ptr') == 0x501b and ops[0].get('delay') == 2 and ops[0].get('reg') == 'r3', 'do.d2 r3: loop end reference, delay 2, count register')
ops = json.loads(r2('wx c0000001 @ 0x7000;aoj 1 @ 0x7000')[0])
check(ops[0]['opex']['units'][0]['vclass'] == 'const' and ops[0]['opex']['units'][0]['provisional'] is True and ops[0]['opex']['units'][0]['named'] is False, 'vector op: class const, provisional, unnamed')
# 2b. j.d1 by target (in a B slot the target is measured from the packet start), the 4-byte beq of 5c 40-6f, cmov types
ops = json.loads(r2('wx 563fffce @ 0x1036;aoj 1 @ 0x1036')[0])
check(ops[0]['size'] == 4 and ops[0]['type'] == 'jmp' and ops[0]['jump'] == 0x1004 and ops[0].get('delay') == 1, 'j.d1 56 3f: 4 bytes, jump by target, delay 1')
out = r2('wx 8890563fffce @ 0x1036;pi 1 @ 0x1036;ao 1 @ 0x1036~size')
check(out[0] == '{ mv r1, r3 ‖ j.d1 0x00001004 }' and any(l.strip() == 'size: 6' for l in out), 'j.d1 in a B slot: target from the packet start')
ops = json.loads(r2('wx 5c68a8e6 2005 @ 0x1000;aoj 2 @ 0x1000')[0])
check(ops[0]['size'] == 4 and ops[0]['type'] == 'cjmp' and ops[0]['cond'] == 'eq' and ops[1]['type'] == 'ret', '5c 68 a8 e6: a 4-byte beq rA, rB')
ops = json.loads(r2('wx 4213b0c1 42308864 23fa 5eb00000 41f3a000 @ 0x5000;aoj 5 @ 0x5000')[0])
check(ops[0]['type'] == 'cmov' and ops[0]['opex']['units'][0]['form'] == 'cmov.ge' and ops[0]['opex']['units'][0]['named'] is True, 'cmov.ge: cmov, named form')
check(ops[1]['type'] == 'cmov' and ops[1]['opex']['units'][0]['provisional'] is True and ops[1]['opex']['units'][0]['named'] is False, 'cmov.leu?: provisional')
check(ops[2]['type'] == 'load' and ops[2].get('refptr') == 1 and ops[2].get('ireg') == 'r8' and ops[2]['opex']['units'][0]['named'] is True, 'lbu.post rd, [rb] += -1 (23 xx): byte load through r8, named')
check(ops[3]['type'] == 'trap' and ops[4]['type'] == 'swi', 'halt = trap, sys41 = swi')
# 2c. the established vector names are named operations, the provisional ones carry the ? flag; and #imm16 of 6 bytes; ctz?
ops = json.loads(r2('wx c0000002 c000000e f000000285288a02 740004500000 41f38800 @ 0x5100;aoj 5 @ 0x5100')[0])
u = ops[0]['opex']['units'][0]
check(u['mnemonic'] == 'v.mem.ld' and u['named'] is True and 'provisional' not in u and u.get('vname') == 'ld', 'v.mem.ld: an established vector name is a named operation')
u = ops[1]['opex']['units'][0]
check(u['mnemonic'] == 'v.alu3.acc.add?' and u['named'] is False and u['provisional'] is True, 'v.alu3.acc.add?: provisional vector name')
u = ops[2]['opex']['units'][0]
check(ops[2]['opex']['shape'] == 'bundle' and u['named'] is True and u.get('vname') == 'ld', 'bundle with v.mem.ld: named')
check(ops[3]['size'] == 6 and ops[3]['type'] == 'and', 'and rd, rs, #imm16 (74 sub 1): 6 bytes, and')
check(ops[4]['opex']['units'][0]['mnemonic'] == 'ctz' and ops[4]['opex']['units'][0]['named'] is True, 'ctz: an established operation')
# 2d. ctz is an established name (no ?), sqr? / sra? / vrd? are provisional, op41.fld carries three registers, lw.post of 2 bytes
# has a 4-bit destination
ops = json.loads(r2('wx 41f38800 41f38c00 67fc75 1a76 418c0d27 21aa @ 0x5200;aoj 6 @ 0x5200')[0])
u = [o['opex']['units'][0] for o in ops]
check(u[0]['mnemonic'] == 'ctz' and u[0]['named'] is True and 'provisional' not in u[0], 'ctz: an established operation name')
check(u[1]['mnemonic'] == 'sqr?' and u[1]['provisional'] is True and u[1]['named'] is False, 'sqr?: provisional')
check(ops[2]['type'] == 'sar' and u[2]['mnemonic'] == 'sra?' and u[2]['provisional'] is True, 'sra? rd, rd, #pos (67 fc): sar, provisional')
check(ops[3]['type'] == 'mov' and u[3]['mnemonic'] == 'vrd?' and u[3]['provisional'] is True, 'vrd? rd, vb1[x] (1a): mov, provisional')
check(u[4]['form'] == 'op41.fld' and u[4]['named'] is False and [o['name'] for o in u[4]['operands']][:3] == ['rd', 'rs', 'rt'], 'op41.fld rd, rs, rt, #pos: three registers, unnamed')
check(ops[5]['type'] == 'load' and u[5]['operands'][0]['value'] == 'r11' and ops[5].get('ireg') == 'r3', 'lw.post r11, [r3] += 4: 4-bit destination, base r3')
# 2e. jr reads a 5-bit register, btst/3 a 5-bit position, ctz is its own form without the HYP flag, sqr? belongs to op41 (HYP),
# op41.fld carries the hypothetical packing ESIL, the uniform marker of the OPEN readings (open / reading / hypothetical in opex)
ops = json.loads(r2('wx 6fec9b 6fecbb 67fb41 41f38800 41f38c00 418c0d27 42008864 1c10 c0000001 c0000002 @ 0x5300;aoj 10 @ 0x5300')[0])
u = [o['opex']['units'][0] for o in ops]
check(u[0]['mnemonic'] == 'jr' and u[0]['operands'][0]['value'] == 'r27' and ops[0].get('reg') == 'r27', 'jr r27 (6f ec 9b): a 5-bit register field')
check(u[1]['mnemonic'] == 'jr.d1' and u[1]['operands'][0]['value'] == 'r27' and ops[1].get('delay') == 1, 'jr.d1 r27: 5-bit register, one slot')
check(u[2]['mnemonic'] == 'btst' and u[2]['named'] is True and ops[2]['type'] == 'and', 'btst r1, r1, #26 (67 fb 41): a 5-bit position')
check(u[3]['mnemonic'] == 'ctz' and u[3]['form'] == 'ctz' and u[3]['named'] is True and u[3]['open'] is False and u[3]['reading'] == 'established' and 'hypothetical' not in u[3], 'ctz: its own form, established, not hypothetical')
check(u[4]['mnemonic'] == 'sqr?' and u[4]['form'] == 'op41' and u[4]['open'] is True and u[4]['reading'] == 'provisional' and u[4].get('hypothetical') is True, 'sqr?: form op41, provisional, open, hypothetical')
check(u[5]['form'] == 'op41.fld' and u[5]['open'] is True and u[5]['reading'] == 'placeholder' and u[5].get('hypothetical') is True, 'op41.fld: open placeholder with the hypothetical packing ESIL')
check(u[6]['mnemonic'] == 'sel?' and u[6]['open'] is True and u[6]['reading'] == 'provisional' and ops[6]['esil'] == '108,0,open', 'sel?: open, the uniform marker REF,ALT,open')
check(u[7]['mnemonic'] == 'vbcast' and u[7]['open'] is False and u[7]['reading'] == 'established', 'vbcast: established')
check(u[8]['open'] is True and u[8]['reading'] == 'provisional' and u[9]['open'] is False and u[9]['reading'] == 'established', 'vector ops: a provisional name is open, an established one is not')
# 2f. confidence levels settled by execution results (and rd, rs, #0x1 / #0x3 / #0xf of 2 bytes, add rd, rs, #-1 and the 4-byte sb
# are high; the 4-byte btst rd, rs, #pos is medium); the packing ESIL of op41.fld now comes from the ISA source (isa/scq16_hyp.json is empty)
# and stays flagged hypothetical
ops = json.loads(r2('wx 0001 0045 04ca 45401023 41ee0c41 41e78041 418c0d27 @ 0x5400;aoj 7 @ 0x5400')[0])
u = [o['opex']['units'][0] for o in ops]
check([x['mnemonic'] for x in u[:6]] == ['and', 'and', 'add', 'sb', 'btst', 'max'] and [x['confidence'] for x in u[:6]] == ['high', 'high', 'high', 'high', 'medium', 'high'],
'confidences by execution: and #0x1 / #0x3 (2 bytes), add #-1, sb (4 bytes) high; btst rd, rs, #pos (4 bytes) medium; max high')
check(u[6]['form'] == 'op41.fld' and u[6].get('hypothetical') is True and u[6]['open'] is True and ops[6]['esil'] == '0x18,r9,<<,1,0x18,1,<<,-,r3,&,|,r7,=', 'op41.fld: the hypothetical packing ESIL comes from the ISA source (still hypothetical and open)')
# 3. ESIL
regs = json.loads(r2('wx a40003d19125 @ 0x6000;aei;ar r3=7;ar r6=0;s 0x6000;aeip;aes;arj')[0])
check(regs['r3'] == 0x3d and regs['r6'] == 7, 'parallel packet {mv r3,#0x3d ‖ mv r6,r3}: r6 gets the old r3')
regs = json.loads(r2('wx 90c821e3 @ 0x6000;wx 11223344 @ 0x100;wx aabbccdd @ 0x200;aei;ar r2=0x100;ar r7=0x200;s 0x6000;aeip;aes;arj')[0])
check(regs['r1'] == 0x11223344 and regs['r4'] == 0xaabbccdd and regs['r2'] == 0x104 and regs['r7'] == 0x204, 'lw.post ‖ lw.post: loads and post-increments')
regs = json.loads(r2('wx 56400010 1402 @ 0x2000;aei;aeim;ar sp=0x1800;s 0x2000;aeip;aes;arj')[0])
check(regs['pc'] == 0x2010 and regs['sp'] == 0x1808 and regs['lr'] == 0x2006, 'jal.d1 + slot: the slot runs before the call (SETJT/SETD)')
regs = json.loads(r2('wx 3a20 @ 0x6000;aei;ar r1=0x12345678;ar r3=0x300;s 0x6000;aeip;aes;arj')[0])
check(regs['r3'] == 0x2f0, 'sw.post -0x10: base decremented')
regs = json.loads(r2('wx 0566 @ 0x6000;aei;ar r5=0x8123;s 0x6000;aeip;aes;arj')[0])
check(regs['r7'] == 0xffff8123, 'xtd #16: sign extension')
regs = json.loads(r2('wx 41f00140 @ 0x6000;aei;ar r10=100;ar r0=7;s 0x6000;aeip;aes;arj')[0])
check(regs['a0l'] == 14 and regs['a0h'] == 2, 'div: quotient in a0l, remainder in a0h')
regs = json.loads(r2('wx 41aaa8c1 @ 0x6000;aei;ar r6=0xffc00000;s 0x6000;aeip;aes;arj')[0])
check(regs['r1'] == 0x3fe, 'fxtu r1, r6, #10, #21: bit field')
regs = json.loads(r2('wx 5b808a21 @ 0x6000;aei;ar r1=0xffffffff;s 0x6000;aeip;aes;arj')[0])
check(regs['pc'] == 0x6008, 'bltu #81, r1(-1), +8: unsigned compare taken')
regs = json.loads(r2('wx 5a800a21 @ 0x6000;aei;ar r1=0xffffffff;s 0x6000;aeip;aes;arj')[0])
check(regs['pc'] == 0x6004, 'blt #0, r1(-1): signed compare not taken')
regs = json.loads(r2('wx 1810 6b4c74 6b7407 @ 0x3000;aei;ar vu1=0x77;ar vb3_3=0x99;ar r7=0x55;s 0x3000;aeip;aes;aes;aes;arj')[0])
check(regs['r1'] == 0x77 and regs['r20'] == 0x99 and regs['vb5_0'] == 0x55, 'VU scalar ports as registers (vget, vrd?, vwr?)')
out = r2('wx c0000001 2005 @ 0x3000;ao 1 @ 0x3000~esil;aei;s 0x3000;aeip;aes;ar pc')
check(any('vop' in l for l in out) and out[-1].strip() == '0x00003004', 'vector op: ESIL vop, aes steps over it')
# 3b. the new semantics, negative stack offsets (ESIL addresses cut to 32 bits), halt/sys41 without effect, .d2 across two aes steps
regs = json.loads(r2('wx 4213b0c1 @ 0x3000;aei;ar r6=5;ar r7=4;ar r1=9;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 4, 'cmov.ge r1, r6, #5, r7 with r6 = 5: taken')
regs = json.loads(r2('wx 4213b0c1 @ 0x3000;aei;ar r6=0xfffffffc;ar r7=4;ar r1=9;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 9, 'cmov.ge with r6 = -4 (signed): not taken')
regs = json.loads(r2('wx 42c2b5c8 @ 0x3000;aei;ar r14=0x4000;ar r13=0x4000;ar r5=0x77;ar r8=1;s 0x3000;aeip;aes;arj')[0])
check(regs['r8'] == 0x77, 'cmov.eq r8, r14, r13, r5 (register form): taken')
regs = json.loads(r2('wx 42308864 @ 0x3000;aei;ar r3=0x100;ar r2=0x100;ar r1=0xaa;ar r4=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r4'] == 0xaa, 'cmov.leu? r4, r3, r2, r1 with r3 == r2: taken')
regs = json.loads(r2('wx 42b0a464 @ 0x3000;aei;ar r3=9;ar r1=0xbb;ar r4=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r4'] == 0, 'cmov.ltu? r4, r3, #9, r1 with r3 = 9: not taken (strict)')
regs = json.loads(r2('wx 4270a464 @ 0x3000;aei;ar r3=0xffffffff;ar r1=0xcc;ar r4=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r4'] == 0, 'cmov.lt? r4, #2, r3, r1 with r3 = -1: not taken (signed)')
regs = json.loads(r2('wx 4240a464 @ 0x3000;aei;ar r3=9;ar r1=0xdd;ar r4=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r4'] == 0xdd, 'cmov.le? r4, #9, r3, r1 with r3 = 9: taken')
regs = json.loads(r2('wx 67c875 67f409 67f865 @ 0x3000;aei;ar r3=0x12345678;ar r9=0x100;ar r5=8;s 0x3000;aeip;aes;aes;aes;arj')[0])
check(regs['r21'] == 0x12345678 and regs['r9'] == 0x101 and regs['r5'] == 1, 'mv r21, r3 (67 c8); bset r9, r9, #0; btst r5, r5, #3')
regs = json.loads(r2('wx 23fa 22c5 @ 0x3000;wx 807f @ 0x400;aei;ar r8=0x400;ar r1=0x401;s 0x3000;aeip;aes;aes;arj')[0])
check(regs['r3'] == 0x80 and regs['r8'] == 0x3ff and regs['r6'] == 0x7f and regs['r1'] == 0x404, 'lbu.post r3, [r8] += -1 (zero-extended byte, post-decrement); lbu.post? r6, [r1] += 3')
regs = json.loads(r2('wx 207e 157e 6f2041 2c10 2810 @ 0x3000;wx 80 @ 0x503;aei;ar sp=0x600;ar lr=0x1234;s 0x3000;aeip;aes;ar lr=0;aes;aes;ar r1=0x55;aes;ar r1=0;aes;arj')[0])
check(regs['lr'] == 0x1234 and regs['r1'] == 0x55, 'sw.lr / lw.lr -0x8(sp), lbu? -0xfd(sp), sw.sp / lw.sp -0xfc(sp): negative offsets reach the 32-bit frame')
regs = json.loads(r2('wx 5eb00000 41f3a000 2005 @ 0x3000;aei;ar lr=0x4000;s 0x3000;aeip;aes;aes;aes;arj')[0])
check(regs['pc'] == 0x4000, 'halt and sys41: no effect, aes steps over them')
regs = json.loads(r2('wx 2007 1402 0445 2005 @ 0x2000;aei;aeim;ar sp=0x1800;ar r1=10;ar lr=0x3000;s 0x2000;aeip;aes;arj')[0])
check(regs['pc'] == 0x2004 and regs['sp'] == 0x1808 and regs['r6'] == 0, 'ret.d2, first aes step: the first slot runs, no jump yet')
regs = json.loads(r2('wx 2007 1402 0445 2005 @ 0x2000;aei;aeim;ar sp=0x1800;ar r1=10;ar lr=0x3000;s 0x2000;aeip;aes;aes;arj')[0])
check(regs['pc'] == 0x3000 and regs['r6'] == 11, 'ret.d2, second aes step: the second slot runs and the return is taken')
regs = json.loads(r2('wx 6fecc5 a40003d19125 0445 2005 @ 0x2000;aei;aeim;ar r1=10;ar r3=7;ar r5=0x4000;s 0x2000;aeip;aes;aes;arj')[0])
check(regs['pc'] == 0x4000 and regs['r3'] == 0x3d and regs['r6'] == 11, 'jr.d2 r5 with a packet in the first slot: both slots, then the jump')
# 3c. lbu.post with 5-bit registers, and #0x3fff, the 6-byte and #imm16, vcfg4c? #0xc (hypothetical: p_k <- rs), ld46? (hypothetical byte load)
regs = json.loads(r2('wx 6e8501 @ 0x3000;wx 8081 @ 0x400;aei;ar r8=0x400;ar r1=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 0x80 and regs['r8'] == 0x401, 'lbu.post r1, [r8] += 0x1 (3 bytes): zero-extended byte, post-increment')
regs = json.loads(r2('wx 67a468 740004ffc063 @ 0x3000;aei;ar r3=0x12345678;ar r8=0;s 0x3000;aeip;aes;aes;arj')[0])
check(regs['r8'] == 0x1678 and regs['r3'] == 0x1670, 'and r8, r3, #0x3fff (67 a4); and r3, r3, #0x3ff0 (74 sub 1, 6 bytes)')
regs = json.loads(r2('wx 4c003231 4c003233 @ 0x3000;aei;ar r17=0x1234;ar vp0=0;ar vp1=0;s 0x3000;aeip;aes;aes;arj')[0])
check(regs['vp0'] == 0x1234 and regs['vp1'] == 0x1234, 'vcfg4c? v17 / v19, r17, #0xc: the hypothetical p_k <- rs (vp0, vp1)')
regs = json.loads(r2('wx 4600a020 @ 0x3000;wx 7f @ 0x400;aei;ar r1=0x400;ar r0=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r0'] == 0x7f and regs['r1'] == 0x428, 'ld46? r0, [r1] += 0x28: hypothetical zero-extended byte with a post-increment')
out = r2('wx c0000109 2005 @ 0x3000;ao 1 @ 0x3000~esil;aei;s 0x3000;aeip;aes;ar pc')
check(any('vop' in l for l in out) and out[-1].strip() == '0x00003004', 'v.move.mv: the ESIL is vop (modelled by the plugin), aes steps over it')
# 3d. ctz / sqr? / sra? / vrd? semantics, the .post templates write rd last (rd == rb: the loaded value wins), the signed
# post-increment of the 6e page, and the public model of the VU (vr0-vr31 of 128 bytes as four 256-bit quarters, vp0-vp7, va0-va7)
regs = json.loads(r2('wx 41f38820 @ 0x3000;aei;ar r1=0x8;ar r0=0xff;s 0x3000;aeip;aes;arj')[0])
check(regs['r0'] == 3, 'ctz r0, r1: ctz(8) = 3')
regs = json.loads(r2('wx 41f38c20 67fc75 @ 0x3000;aei;ar r1=7;ar r0=0;ar r21=0xfffffff0;s 0x3000;aeip;aes;aes;arj')[0])
check(regs['r0'] == 49 and regs['r21'] == 0xfffffffe, 'sqr? r0, r1 (7 * 7); sra? r21, r21, #3 (arithmetic shift of a negative value)')
regs = json.loads(r2('wx 1a76 @ 0x3000;aei;ar vb1_7=0x99;ar r7=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r7'] == 0x99, 'vrd? r7, vb1[7]: r7 <- vb1_7 (hypothetical)')
regs = json.loads(r2('wx 2300 @ 0x3000;wx 7f @ 0x404;aei;ar r1=0x404;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 0x7f, 'lbu.post r1, [r1] += -4 with rd == rb: the loaded value wins (rd is written last)')
regs = json.loads(r2('wx 6ed063 @ 0x3000;wx deadbeef @ 0x200;aei;ar r3=0x200;s 0x3000;aeip;aes;arj')[0])
check(regs['r3'] == 0xdeadbeef, 'lw.post r3, [r3] += -0x10 (signed increment x = 4 -> -16; rd == rb): the loaded value wins')
regs = json.loads(r2('wx 6ed068 6ef463 @ 0x3000;wx deadbeef @ 0x200;aei;ar r3=0x200;ar r8=0;s 0x3000;aeip;aes;ar r3=0x300;aes;arj')[0])
check(regs['r8'] == 0xdeadbeef and regs['r3'] == 0x2fa, 'lw.post r8, [r3] += -0x10 (post-decrement of 16); sh.post r3, [r3] += -0x6')
regs = json.loads(r2('wx 21aa @ 0x3000;wx 11223344 @ 0x100;aei;ar r3=0x100;ar r11=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r11'] == 0x11223344 and regs['r3'] == 0x104, 'lw.post r11, [r3] += 4 (21 aa): the destination is r11')
regs = json.loads(r2('wx 1f20 1fa0 @ 0x3000;aei;ar r1=3;s 0x3000;aeip;aes;ar r1=0x400;aes;arj')[0])
check(regs['va2'] == 3 and regs['vp2'] == 0x400, 'vset.a A2, r1 / vmem.set p2, r1: va2 and vp2 are registers')
out = r2('wx c8204002 @ 0x3000;wx 1122334455667788 @ 0x400;aei;ar va2=3;ar vp2=0x400;s 0x3000;aeip;aes;ar vr1_0;ar vp2')
check(int(out[-2], 16) == 0x665544332211 and int(out[-1], 16) == 0x406, 'v.mem.ld vr1 <- DM[vp2], 3 halves (va2 = 3): the lanes land in vr1_0 (little-endian), vp2 post-incremented by 6')
out = r2('wx c8004002 c8004003 @ 0x3000;wx 1122334455667788 @ 0x400;wx 0000000000000000 @ 0x500;aei;ar va2=3;ar vp2=0x400;s 0x3000;aeip;aes;ar vp2=0x500;aes;p8 8 @ 0x500;ar vp2')
check(out[-2].strip() == '1122334455660000' and int(out[-1], 16) == 0x506, 'v.mem.ld vr0 <- DM[0x400]; v.mem.st DM[0x500] <- vr0: 6 bytes copied through the VU, vp2 = 0x506')
out = r2('wx c8004002 c0280109 c0000b09 c8004003 @ 0x3000;wx 1122334455667788 @ 0x400;wx 0000000000000000 @ 0x500;aei;ar va2=3;ar vp2=0x400;s 0x3000;aeip;aes;aes;ar vr0_0=0;aes;ar vp2=0x500;aes;p8 8 @ 0x500')
check(out[-1].strip() == '1122334455660000', 'v.move.mv vr5 <- vr0, vr0 cleared, v.move.mv vr0 <- vr5, st: the register copy comes back to memory')
out = r2('wx c8010002 @ 0x3000;wx 1122334455667788 @ 0x400;aei;ar va2=3;ar vp2=0x400;ar vp4=0x400;s 0x3000;aeip;aes;ar vp2;ar vp4;ar vr1_0')
check(int(out[-3], 16) == 0x400 and int(out[-2], 16) == 0x400 and int(out[-1], 16) == 0, 'v.mem.ld with F2 = 4 (second access family, pointer not established): no effect')
out = r2('wx c8204002 @ 0x3000;wx 1122334455667788 @ 0x400;aei;ar va2=0;ar vp2=0x400;s 0x3000;aeip;aes;ar vp2;ar vr1_0')
check(int(out[-2], 16) == 0x480 and int(out[-1], 16) == 0x8877665544332211, 'v.mem.ld with va2 = 0: the whole 128-byte register, vp2 += 128')
out = r2('wx f000000285288a02 @ 0x3000;wx aabbccdd @ 0x400;aei;ar va0=2;ar vp0=0x400;ar r1=1;ar r2=2;ar r3=3;ar r6=6;s 0x3000;aeip;aes;ar vr0_0;ar vp0;ar r2;ar r3')
check([int(l, 16) for l in out[-4:]] == [0xddccbbaa, 0x404, 8, 4], 'bundle { v.mem.ld op9=0x00a ‖ add r2, r6 ‖ add r3, r1 }: the vector load and the two scalar slots')
out = r2('wx c0000001 c000000e @ 0x3000;aei;ar va0=2;ar vp0=0x400;s 0x3000;aeip;aes;aes;ar vp0;ar pc')
check([int(l, 16) for l in out[-2:]] == [0x400, 0x3008], 'v.const.movi? / v.alu3.acc.add?: no model (vop without effect), aes steps over them')
# 3e. jr by its 5-bit register, btst with a 5-bit position, the hypothetical packing of op41.fld, the open marker (no effect, the rest
# of the packet keeps its ESIL), the broadcast ports: vbcast / vbcast.i write the 64 lanes of vr{k} (the port v{k} keeps the scalar)
regs = json.loads(r2('wx 6fec9b @ 0x3000;aei;ar r27=0x4000;ar r11=0x5000;s 0x3000;aeip;aes;arj')[0])
check(regs['pc'] == 0x4000, 'jr r27: pc = r27')
regs = json.loads(r2('wx 67fb41 67fb65 @ 0x3000;aei;ar r1=0x04000000;ar r5=0x04000000;s 0x3000;aeip;aes;aes;arj')[0])
check(regs['r1'] == 1 and regs['r5'] == 0, 'btst r1, r1, #26 / btst r5, r5, #27: bit 26 of 0x04000000')
regs = json.loads(r2('wx 418c0d27 @ 0x3000;aei;ar r9=0x1234;ar r3=0xffffffff;ar r7=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r7'] == 0x34ffffff, 'op41.fld r7, r9, r3, #24 (hypothetical): r7 = (r9 << 24) | (r3 & 0xffffff)')
regs = json.loads(r2('wx 889042008864 @ 0x3000;aei;ar r1=0;ar r3=0x77;ar r4=0x11;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 0x77 and regs['r4'] == 0x11 and regs['pc'] == 0x3006, '{ mv r1, r3 ‖ sel? }: the open B slot no longer blanks the ESIL of the packet')
out = r2('wx 1c10 @ 0x3000;aei;ar r1=0xf9ff;s 0x3000;aeip;aes;ar vr1_0;ar vr1_3;ar v1')
check(int(out[-3], 16) == int('f9ff' * 16, 16) and int(out[-2], 16) == int('f9ff' * 16, 16) and int(out[-1], 16) == 0xf9ff, 'vbcast v1, r1: the 64 lanes of vr1 <- 0xf9ff, the port v1 keeps the scalar')
out = r2('wx 1c10 c0000309 c8006003 @ 0x3000;wx 0000000000000000 @ 0x500;aei;ar r1=0xf9ff;ar va2=3;ar vp3=0x500;s 0x3000;aeip;aes;aes;aes;p8 8 @ 0x500;ar vp3')
check(out[-2].strip() == 'fff9fff9fff90000' and int(out[-1], 16) == 0x506, 'vbcast v1 · v.move.mv vr0 <- vr1 · v.mem.st [p3] <- vr0: the broadcast reaches memory (fff9 x3), vp3 = 0x506')
out = r2('wx 40078000 40100000 6b6020 @ 0x3000;aei;ar r0=0x1234;s 0x3000;aeip;aes;ar vr0_0;aes;ar vr0_0;aes;ar vr1_0')
check(int(out[-3], 16) == int('fc00' * 16, 16) and int(out[-2], 16) == 0 and int(out[-1], 16) == int('1234' * 16, 16), 'vbcast.i v0, #-0x400 -> lanes 0xfc00; vbcast.i? v0, #0 -> 0; vbcast v1, r0 (6b 60) -> lanes 0x1234')
out = r2('wx 1c10 1810 @ 0x3000;aei;ar r1=0x55;ar vu1=0x77;s 0x3000;aeip;aes;aes;ar r1;ar v1')
check(int(out[-2], 16) == 0x77 and int(out[-1], 16) == 0x55, 'vget r1, vu[1] reads the port vu1 (not the broadcast port v1)')
# 3f. ctz(0) under the table ESIL is -1 (31 - clz(rs & -rs) with clz(0) = 32); whether the hardware returns 32 instead is an open
# question (doc/scq16.md), so the test pins the table's reading, not the hardware's; ctz(0x80) = 7 and ctz(0x80000000) = 31 as controls
regs = json.loads(r2('wx 41f38821 @ 0x3000;aei;ar r1=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 0xffffffff, 'ctz r1, r1 with r1 = 0: -1 under the table ESIL (open: the hardware may return 32)')
regs = json.loads(r2('wx 41f38821 @ 0x3000;aei;ar r1=0x80;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 7, 'ctz(0x80) = 7')
regs = json.loads(r2('wx 41f38821 @ 0x3000;aei;ar r1=0x80000000;s 0x3000;aeip;aes;arj')[0])
check(regs['r1'] == 31, 'ctz(0x80000000) = 31')
regs = json.loads(r2('wx 418c0d27 @ 0x3000;aei;ar r9=0x1234;ar r3=0xffffffff;ar r7=0;s 0x3000;aeip;aes;arj')[0])
check(regs['r7'] == 0x34ffffff, 'op41.fld r7, r9, r3, #24 from the ISA source: r7 = (r9 << 24) | (r3 & 0xffffff)')
# 4. pseudo
text = '\n'.join(r2('e asm.pseudo=true;wx 889026e4 3084 1402 0566 2005 @ 0x1000;pD 12 @ 0x1000'))
check('parallel { r1 = r3; if (r5 == 0) goto 0x0000100e; }' in text, 'pseudo: packet')
check('r5 = dmem32[r1 + 0x4]' in text and 'sp += 0x8' in text and 'r7 = sext16(r5)' in text and 'return' in text, 'pseudo: memory, stack, extension, return')
text = '\n'.join(r2('e asm.pseudo=true;wx 90c821e3 41aaa8c1 @ 0x1000;pD 8 @ 0x1000'))
check('r1 = dmem32[r2]; r2 += 4' in text and 'r1 = ((r6 >> 21) & ((1U << 10) - 1))' in text, 'pseudo: post-increment, bit field')
print('\n%s' % ('ALL OK' if not failures else '%d FAILURES' % failures))
sys.exit(1 if failures else 0)
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Exercise cpu=scq16 with the public bytes-only vectors of test/scq16_vectors.json: for every row the disassembly of each unit at its
address (text and size) and, when the row carries an expected result, the ESIL of the r2 command line (registers / memory bytes) in the
big-endian regime of this harness (`r2` / `expected`, cfg.bigendian=true) and, when the row carries `r2_le` / `expected_le`, in the
little-endian regime of the core's data memory (cfg.bigendian=false: scalar memory values are stored little-endian). Each row is
stand-alone: bytes, an initial state and the result the table predicts; no firmware is needed.
Usage: python3 test/test_scq16_vectors.py [--only ID,...]"""
import json
import os
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
R2 = os.environ.get('R2', 'r2')
ext = subprocess.check_output([R2, '-H', 'R2_LIBEXT'], text=True).strip()
PLUGIN = ROOT / ('arch_md32.' + ext)
ASM_PLUGIN = ROOT / ('asm_md32.' + ext)
ONLY = sys.argv[sys.argv.index('--only') + 1].split(',') if '--only' in sys.argv else None
failures = 0
def r2(commands, bigendian=True, size=0x10000):
setup = 'e scr.color=0;L %s;L %s;e asm.cpu=scq16;e asm.arch=md32;e cfg.bigendian=%s;' % (PLUGIN, ASM_PLUGIN, 'true' if bigendian else 'false')
proc = subprocess.run([R2, '-NN', '-q', '-c', setup + commands + ';q', 'malloc://%d' % size], capture_output=True, text=True, check=True)
return [l for l in proc.stdout.strip().splitlines() if not l.startswith(('WARN', 'INFO', 'ERROR'))]
def check(cond, msg):
global failures
print(('ok ' if cond else 'FAIL ') + msg)
if not cond:
failures += 1
def norm(t):
return re.sub(r'0x0*([0-9a-f]+)', lambda m: '0x' + (m.group(1) or '0'), t) # the plugin prints 8-digit targets
V = json.load(open(ROOT / 'test' / 'scq16_vectors.json'))['vectors']
n_dis = n_be = n_le = 0
for v in V:
if ONLY and v['id'] not in ONLY:
continue
for hx, L, exp, pc in v['disasm']:
out = r2('wx %s @ %#x;pi 1 @ %#x;ao 1 @ %#x~size' % (hx, pc, pc, pc))
text = out[0] if out else ''
check(norm(text) == norm(exp) and any(l.strip() == 'size: %d' % L for l in out), '%s %-18s -> %s' % (v['id'], hx, text))
n_dis += 1
for tag, cmd_key, exp_key, be in (('BE', 'r2', 'expected', True), ('LE', 'r2_le', 'expected_le', False)):
exp = v.get(exp_key)
if exp is None:
continue
out = r2(v[cmd_key], be)
got = {}
for k, line in zip(exp.keys(), out[-len(exp):]):
got[k] = line.strip() if k.startswith('mem[') else '%#x' % int(line, 16)
check(got == exp, '%s ESIL %s (%s): %s' % (v['id'], tag, v['hypothesis_of_table'], got))
if be:
n_be += 1
else:
n_le += 1
print('\n%s (%d disassembly + %d ESIL checks: %d big-endian + %d little-endian)' % ('ALL OK' if not failures else '%d FAILURES' % failures, n_dis, n_be + n_le, n_be, n_le))
sys.exit(1 if failures else 0)
+278
View File
@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""Reproduce the uSIP firmware inventory, annotated disassembly and flow audit.
Uses the actual native arch plugin via test_md32's stream interface. Symbol
ranges are evidence supplied with the images; their physical load aliases are
assumptions, not an independently recovered linker map.
"""
import argparse
import collections
import csv
import hashlib
import json
import re
import struct
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
# Default load aliases for the three supplied images. They are hypotheses, not a
# validated linker map; override them with --base NAME=0xADDR for other images.
REGIONS = {'sec0': 0x400e0000, 'ddr': 0x1ca34000, 'brp': 0x80c00000}
VECTOR_BYTES = 0x400
# Functions whose flow targets are not worth listing (e.g. the reset veneer).
SKIP_FLOW_FUNCTIONS = ('_ss_reset_entry',)
def parse_bases(items):
if not items:
return dict(REGIONS)
bases = {}
for item in items:
name, _, value = item.partition('=')
if not name or not value:
raise SystemExit('--base wants NAME=0xADDR, got %r' % item)
bases[name] = int(value, 0)
return bases
def region_for(addr, bases=None):
"""Pick the region whose base is nearest below addr, else the lowest."""
bases = REGIONS if bases is None else bases
below = [a for a in bases.values() if a <= addr]
wanted = max(below) if below else min(bases.values())
return next(k for k, a in bases.items() if a == wanted)
def display(path):
"""Keep generated output free of absolute paths when they are inside ROOT."""
path = Path(path).resolve()
return str(path.relative_to(ROOT)) if path.is_relative_to(ROOT) else str(path)
def image_path(bundle, region):
"""Accept both the vendor-prefixed and the plain per-region image name."""
for name in ('%s.bin' % region, 'usip_%s.bin' % region):
path = Path(bundle) / 'bin' / name
if path.exists():
return path
raise SystemExit('missing image for region %r under %s' % (region, Path(bundle) / 'bin'))
def inventory(bundle, bases=None, path=None):
bases = REGIONS if bases is None else bases
lowest = min(bases.values())
result = []
with Path(path or (Path(bundle) / 'data/functions.csv')).open() as fd:
for r in csv.DictReader(fd):
start, end = int(r['addr'], 16), int(r['end'], 16)
region = region_for(start, bases)
bias = bases[region] if start < lowest else 0
result.append(dict(name=r['name'], source=r['source'], region=region,
start=start + bias, end=end + bias,
expected_instructions=int(r['instructions'])))
return sorted(result, key=lambda f: (f['region'], f['start']))
def decode(items, decoder, cpu='usip'):
text = ''.join('%s %d %x\n' % (code.ljust(4, b'\0').hex(), len(code), addr)
for addr, code, _ in items)
proc = subprocess.run([str(decoder), '--stream', cpu], input=text, text=True,
capture_output=True, check=True)
lines = proc.stdout.splitlines()
if len(lines) != len(items):
raise ValueError('Native decoder did not return one result per instruction')
for (addr, code, function), line in zip(items, lines):
fields = line.split('\t')
valid, size, kind = map(int, fields[:3])
if not valid or size != len(code):
raise ValueError('Invalid/truncated instruction at %#x' % addr)
yield dict(address=addr, bytes=code.hex(), size=size, type=kind,
jump=None if fields[3] == 'ffffffffffffffff' else int(fields[3], 16),
mnemonic=fields[6], esil=fields[7], function=function)
def scan_strings(data, base):
result = []
# Strict printable C-string candidates; raw printable runs inside code
# are counted separately, not mislabeled as strings.
for m in re.finditer(rb'[ -~]{6,}\x00', data):
text = m.group()[:-1].decode('ascii')
if re.search(r'[A-Za-z]{4}', text):
result.append(dict(address=base + m.start(), encoding='ascii', value=text))
for order, pattern in [('utf16le', rb'(?:[ -~]\x00){6,}\x00\x00'),
('utf16be', rb'(?:\x00[ -~]){6,}\x00\x00')]:
for m in re.finditer(pattern, data):
text = m.group()[:-2].decode('utf-16-le' if order.endswith('le') else 'utf-16-be')
if re.search(r'[A-Za-z]{4}', text):
result.append(dict(address=base + m.start(), encoding=order, value=text))
return result
def analyze(bundle, output, decoder, bases=None, inventory_path=None,
vector_bytes=VECTOR_BYTES, cpu='usip', skip_flows=SKIP_FLOW_FUNCTIONS):
bases = dict(REGIONS if bases is None else bases)
boot = min(bases, key=bases.get)
boot_base = bases[boot]
output.mkdir(parents=True, exist_ok=True)
fs = inventory(bundle, bases, inventory_path)
by_region = {k: [f for f in fs if f['region'] == k] for k in bases}
instructions, image_stats, all_strings = [], [], []
starts = {f['start']: f for f in fs}
for region, base in bases.items():
path = image_path(bundle, region)
data = path.read_bytes()
items = []
mismatches = []
for f in by_region[region]:
off, end = f['start'] - base, f['end'] - base
if not 0 <= off < end <= len(data):
raise ValueError('Symbol outside image: %r' % f)
count = 0
while off < end:
size = 2 if data[off] & 0x80 else 4
if off + size > end:
raise ValueError('Function boundary splits instruction: %s' % f['name'])
items.append((base + off, data[off:off + size], f['name']))
off += size
count += 1
if count != f['expected_instructions']:
mismatches.append(f['name'])
decoded = list(decode(items, decoder, cpu))
instructions.extend(decoded)
# Vector slots are executable branch stubs, not address data.
code = decoded
body = [i for i in decoded
if not(region == boot and i['address'] < base + vector_bytes)]
counts = collections.Counter(i['mnemonic'].split()[0] for i in code)
unknown = collections.Counter((i['size'], i['bytes'][:4]) for i in code
if i['mnemonic'].startswith('.'))
strings = scan_strings(data, base)
all_strings.extend(strings)
(output / ('%s-strings.json' % region)).write_text(json.dumps(strings, indent=2) + '\n')
with (output / ('%s-disassembly.txt' % region)).open('w') as fd:
prev = None
for i in decoded:
if i['function'] != prev:
fd.write('\n; %s\n' % i['function'])
prev = i['function']
comment = ''
if region == boot and i['address'] < base + vector_bytes:
comment = ' ; executable vector %d' % ((i['address'] - base) // 4)
target = starts.get(i['jump'])
if target:
comment += ' -> ' + target['name']
elif i['jump'] == i['address']:
comment += ' (self-loop)'
elif i['jump'] is not None:
target = starts.get(i['jump'])
if target:
comment = ' ; -> ' + target['name']
fd.write('%08x %-8s %s%s\n' % (i['address'], i['bytes'], i['mnemonic'], comment))
image_stats.append(dict(region=region, file=display(path), base=base,
size=len(data), sha256=hashlib.sha256(data).hexdigest(),
functions=len(by_region[region]), instruction_records=len(decoded), code_instructions=len(code),
non_vector_instructions=len(body), counts=dict(counts), string_candidates=len(strings),
long_string_candidates=sum(len(x['value']) >= 8 for x in strings),
metadata_count_mismatches=mismatches,
unresolved_top=[dict(size=k[0], hw1=k[1], count=v)
for k, v in unknown.most_common(40)]))
boundaries = {i['address'] for i in instructions}
indexed = {i['address']: i for i in instructions}
flows, return_extensions = [], collections.Counter()
for i in instructions:
if i['function'] in skip_flows:
continue
if i['bytes'].startswith('5ef9'):
return_extensions[i['bytes'][4:]] += 1
if i['jump'] is not None:
f = next(f for f in fs if f['name'] == i['function'] and f['start'] <= i['address'] < f['end'])
flows.append(dict(address=i['address'], function=i['function'], mnemonic=i['mnemonic'],
target=i['jump'], target_is_boundary=i['jump'] in boundaries,
target_symbol=starts.get(i['jump'], {}).get('name'),
target_inside_function=f['start'] <= i['jump'] < f['end']))
data = image_path(bundle, boot).read_bytes()
vectors = []
old_pointer_targets = struct.unpack('>%dI' % (vector_bytes // 4), data[:vector_bytes])
for n, word in enumerate(old_pointer_targets):
address = boot_base + n * 4
instruction = indexed[address]
target = instruction['jump']
containing = next((f for f in fs if target is not None and f['start'] <= target < f['end']), None)
vectors.append(dict(index=n, address=address, encoding=word,
instruction=instruction['mnemonic'], target=target,
self_loop=target == address,
target_symbol=starts.get(target, {}).get('name'),
containing_symbol=containing['name'] if containing else None,
at_instruction_boundary=target in boundaries))
pointer_hypothesis = dict(rejected=True,
mapped_targets=sum(boot_base <= t < boot_base + len(data)
for t in old_pointer_targets),
boundary_targets=sum(t in boundaries for t in old_pointer_targets),
exact_symbols=sum(t in starts for t in old_pointer_targets))
aliases = collections.defaultdict(list)
tree = Path(bundle).parent.parent
for p in (sorted(tree.rglob('*.bin')) if tree.is_dir() else []):
aliases[hashlib.sha256(p.read_bytes()).hexdigest()].append(display(p))
candidate_refs = []
string_addresses = {s['address'] for s in all_strings}
for region, base in bases.items():
data = image_path(bundle, region).read_bytes()
for offset in range(0, len(data) - 3, 2):
for endian in ('big', 'little'):
target = int.from_bytes(data[offset:offset + 4], endian)
if target in string_addresses:
candidate_refs.append(dict(address=base + offset, target=target, endian=endian,
evidence='raw pointer candidate, not a decoded instruction reference'))
report = dict(images=image_stats, functions=len(fs), flows=flows, vectors=vectors,
legacy_pointer_hypothesis=pointer_hypothesis,
string_pointer_candidates=candidate_refs,
return32_extensions=return_extensions.most_common(),
duplicate_images=[v for v in aliases.values() if len(v) > 1])
(output / 'inventory.json').write_text(json.dumps(report, indent=2) + '\n')
with (output / 'functions.csv').open('w') as fd:
writer = csv.DictWriter(fd, fieldnames=list(fs[0]))
writer.writeheader()
writer.writerows(fs)
# Load each image at its supplied alias, then add names (not guessed CFGs).
with (output / 'load-usip.r2').open('w') as fd:
fd.write('e asm.arch=md32\ne asm.cpu=%s\ne cfg.bigendian=true\n' % cpu)
for region, base in bases.items():
path = image_path(bundle, region).resolve()
fd.write('on "%s" 0x%x r-x\n' % (display(path), base))
for f in fs:
name = re.sub('[^a-zA-Z0-9_.$]', '_', f['name'])
fd.write('f sym.%s %d @ 0x%x\n' % (name, f['end'] - f['start'], f['start']))
for v in vectors:
fd.write('f vector.%d 4 @ 0x%x\n' % (v['index'], boot_base + v['index'] * 4))
fd.write('s 0x%x\n' % vectors[0]['address'])
print(json.dumps(dict(images=image_stats, functions=len(fs), decoded=len(instructions),
flow_candidates=len(flows), invalid_flow_boundaries=sum(not f['target_is_boundary'] for f in flows),
decoded_vectors=sum(v['target'] is not None for v in vectors),
vector_self_loops=sum(v['self_loop'] for v in vectors),
reset=vectors[0], return32_extension_variants=len(return_extensions)), indent=2))
return report
if __name__ == '__main__':
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--bundle', type=Path, default=ROOT / 'third_party/v2/r2pkg')
p.add_argument('--output', type=Path, default=ROOT / 'usipdata')
p.add_argument('--decoder', type=Path, default=ROOT / 'test/test_md32')
p.add_argument('--inventory', type=Path, default=None,
help='function CSV (default: BUNDLE/data/functions.csv)')
p.add_argument('--base', action='append', metavar='NAME=0xADDR',
help='load alias per region, repeatable (default: %s)'
% ', '.join('%s=%#x' % kv for kv in REGIONS.items()))
p.add_argument('--vector-bytes', type=lambda s: int(s, 0), default=VECTOR_BYTES,
help='leading vector-table bytes in the boot image')
p.add_argument('--cpu', default='usip', help='CPU name passed to the decoder')
p.add_argument('--skip-flow-function', action='append', metavar='NAME',
default=list(SKIP_FLOW_FUNCTIONS),
help='function whose flow targets are not listed, repeatable')
args = p.parse_args()
analyze(args.bundle.resolve(), args.output.resolve(), args.decoder.resolve(),
bases=parse_bases(args.base),
inventory_path=args.inventory.resolve() if args.inventory else None,
vector_bytes=args.vector_bytes, cpu=args.cpu,
skip_flows=tuple(args.skip_flow_function))
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""Recover MD32 encoding facts from the public vendor objdump (see doc/research.md).
No vendor executable is needed to build or use the plugin. The SHA256 check
pins the input used to derive the checked-in opcode table.
"""
import argparse
import hashlib
import json
import re
import struct
from pathlib import Path
SHA256 = "ad768602af6c62049ae5338ce390bb4799e6f8c4a78488f64d58b14b4dc4ab04"
class Elf:
def __init__(self, path):
self.data = Path(path).read_bytes()
assert hashlib.sha256(self.data).hexdigest() == SHA256, "Unexpected vendor objdump"
shoff = struct.unpack_from("<Q", self.data, 40)[0]
shsize, shnum = struct.unpack_from("<HH", self.data, 58)
self.sections = [struct.unpack_from("<IIQQQQIIQQ", self.data, shoff + i * shsize)
for i in range(shnum)]
self.symbols = {}
for sh in self.sections:
if sh[1] != 2:
continue
strings = self.sections[sh[6]]
names = self.data[strings[4]:strings[4] + strings[5]]
for off in range(sh[4], sh[4] + sh[5], sh[9]):
name, _, _, _, addr, size = struct.unpack_from("<IBBHQQ", self.data, off)
name = names[name:].split(b"\0", 1)[0].decode()
self.symbols[name] = (addr, size)
def read(self, addr, size):
for sh in self.sections:
if sh[1] != 8 and sh[3] <= addr and addr + size <= sh[3] + sh[5]:
off = sh[4] + addr - sh[3]
return self.data[off:off + size]
raise ValueError(hex(addr))
def unpack(self, fmt, addr):
return struct.unpack(fmt, self.read(addr, struct.calcsize(fmt)))
def string(self, addr):
out = bytearray()
while self.read(addr + len(out), 1) != b"\0":
out += self.read(addr + len(out), 1)
return out.decode()
def tables(elf):
rows = []
base, size = elf.symbols["ms15e30_isa"]
for off in range(0, size, 24):
group, addr, _ = elf.unpack("<QQQ", base + off)
while True:
code, flags, name, opcode, mask, fmt, count = elf.unpack("<IIQIIII", addr)
name = elf.string(name)
if not name:
break
args = [elf.unpack("<Ii", addr + 32 + i * 8) for i in range(count)]
rows.append(dict(group=group, name=name, opcode=opcode, mask=mask,
format=fmt, args=args, flags=flags, code=code))
addr += 80
return rows
def generate(elf):
rows = tables(elf)
used = sorted({a for row in rows for a, shift in row["args"]})
makers = {addr: name.removesuffix("_maker") for name, (addr, _) in elf.symbols.items()
if name.endswith("_maker")}
argtab = elf.symbols["arg_make_table"][0]
names = {i: makers[elf.unpack("<Q", argtab + 8 * i)[0]] for i in used}
# Read/write operand variants share the same value decoder.
unique = sorted(set(names.values()))
out = ["/* Generated by tools/extract_opcodes.py; encoding facts, see doc/research.md. */",
"enum {"]
out += ["\tMD32_" + n.upper() + "," for n in unique]
out += ["};", "", "static const Md32Opcode md32_opcodes[] = {"]
for r in rows:
args = ", ".join("{ MD32_%s, %d }" % (names[a].upper(), shift)
for a, shift in r["args"]) or "{ 0, 0 }"
out.append('\t{ "%s", 0x%08x, 0x%08x, %d, %d, %d, { %s } },' %
(r["name"], r["opcode"], r["mask"], r["group"],
r["format"], len(r["args"]), args))
out += ["};", "", "static st32 md32_argument_value(unsigned type, ut32 word, int shift) {",
"\tut32 x = shift < 0? word: word >> shift;", "\tswitch (type) {"]
for n in unique:
if n == "r_c3u":
expr = "(x & 7) + 1"
elif n == "r_c4u":
expr = "x & 15"
elif n == "r_14":
expr = "14"
elif n.startswith("r_"):
expr = "0"
elif re.fullmatch(r"i_c[0-9]+", n):
expr = n[3:]
elif n in ("i_c9s_2118_0400", "i_c9u_2118_0400"):
expr = "(word & 31) | ((word >> 13) & 0x1e0)"
if "9s" in n:
expr = "md32_sign (" + expr + ", 9)"
else:
m = re.fullmatch(r"i_c([0-9]+)(su|s|u)(?:_(n?[0-9]+))?_([0-9]{4})", n)
assert m, n
bits, sign, scale, field = m.groups()
bits = int(bits)
negative = scale and scale.startswith("n")
scale = int((scale or "0").removeprefix("n"))
width = bits - scale - bool(negative)
expr = "x & 0x%x" % ((1 << width) - 1)
if scale:
expr = "(%s) * %d" % (expr, 1 << scale)
if negative:
expr = "(%s) - %d" % (expr, 1 << (bits - 1))
elif sign == "s":
expr = "md32_sign (%s, %d)" % (expr, bits)
out += ["\tcase MD32_%s: return %s;" % (n.upper(), expr)]
out += ["\t}", "\treturn 0;", "}", "",
"static const char *md32_argument_register(unsigned type) {", "\tswitch (type) {"]
for n in unique:
if n.startswith("r_") and n not in ("r_c3u", "r_c4u"):
out += ['\tcase MD32_%s: return "%s";' % (n.upper(), "r14" if n == "r_14" else n[2:])]
out += ["\t}", "\treturn NULL;", "}", ""]
return "\n".join(out)
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("objdump")
p.add_argument("-o", "--output", type=Path)
p.add_argument("--json", action="store_true")
args = p.parse_args()
elf = Elf(args.objdump)
out = json.dumps(tables(elf), indent=2) + "\n" if args.json else generate(elf)
if args.output:
args.output.write_text(out)
else:
print(out, end="")
if __name__ == "__main__":
main()
+352
View File
@@ -0,0 +1,352 @@
#!/usr/bin/env python3
"""Generate scq16_opcodes.h (the SCQ16 instruction table) from isa/scq16_isa.json.
python3 tools/gen_scq16_tables.py [isa/scq16_isa.json] [-o scq16_opcodes.h]
python3 tools/gen_scq16_tables.py export SOURCE.py LEAVES.json [--hyp isa/scq16_hyp.json] [-o isa/scq16_isa.json]
The header carries everything the C decoder (scq16_decode.c) needs: one Scq16Alt per bit alternative of a form (mask/value over
the transformed unit, the delay-slot count, the flags, the operand descriptors, the text template and the ESIL template), an
index of the alternatives per unit length, the length-rule leaves (stand-alone units and packet heads: first byte and a prefix of
the second byte -> length, or a masked orphan) with their start index per first byte, the vector op9 classes and provisional
names, and the register names. The output is deterministic: regenerating from the same JSON gives a byte-identical header.
Table layout (see README.md "SCQ16 mode" and doc/scq16.md):
Scq16Alt { form, ref, form_name, mn, L, kind, conf, slots, nops, flags, mask, value, ops, fmt, esil }
* mask/value are over w, the big-endian integer of the TRANSFORMED unit (A' in a packet head; byte 0 & 0x7f in a B slot or
an orphan), bit 0 = LSB of the last byte; bit 7 of byte 0 is never part of a mask, so the same entry serves a stand-alone
unit, a head and a B slot; within one length the first matching alternative wins (the list is ordered by specificity);
* Scq16Operand { name, kind, fmt, npieces, is_signed, piece[2] {lo, w}, scale, bias, cval }: a register field (r5: r0-r31,
r4: r0-r15, r4p: r1-r16, r3p: r1-r8, csr, acc), an immediate or a displacement collected from up to two bit pieces (the
first piece is the most significant), value = (signed? sext: raw) * scale + bias, or a constant (cval) of the alternative;
* fmt: the text template with {name} = the operand rendered by its fmt; esil: the ESIL template ({name} read, {=name} write,
{#name} decimal value, {d} target, {ret} return address after the delay slots, {mask} of a bit-field width), NULL = no
semantics, "" = no architectural effect;
* flags: NAMED (an established operation name), PROV (provisional name, ends in ?), TABLELEN (form tied to the length
table), CONTROL (control/system A': never a packet head), HYP (the ESIL is a hypothesis: the reading of a provisional
name or of an open sign), OPEN (an open reading: a family placeholder or a provisional name; the vector and bundle
forms never carry it, their openness is per op9 in scq16_vname[]). An OPEN alternative without an ESIL template
carries the uniform marker "REF,ALT,open" in the header (the custom ESIL op `open` has no effect; an emulator may
stop on it); the JSON keeps esil = null and records the marker as esil_plugin.
Scq16Leaf { b0, depth, prefix, label }: (b0 & 0x7f, the top `depth` bits of b1) -> label > 0: length; label < 0: masked orphan
of -label bytes; the deepest matching leaf wins.
`export` writes isa/scq16_isa.json from a form-description module (SOURCE: a Python description of the forms as Form/Alt objects
with their bit constraints, operands, text and ESIL templates, the vector classes and the register names) and the length-rule
leaves (LEAVES: a JSON with the lists "U" and "H" of [b0, depth, prefix, label, ...] rows; only those four fields are kept, the
head label ["o", n] meaning a masked orphan of n bytes). The plugin semantics are carried by the source itself: the ESIL templates (VU scalar ports as registers, vector operations as the custom op `vop`, the
no-effect system forms, delayed transfers as `SETJT`/`SETD`) and the set HYP of hypothetical readings (the reading of a
provisional name or of an open sign) are fields of the source module (T.HYP); the exporter applies no overlay. The vector
names (vector.name_by_op9) carry the `?` of a provisional name; a name without `?` is an established operation (the
source set VNAME_FIRM, also listed as vector.firm_op9). The notes and evidence fields of the source are not exported;
meta.source_sha256 is the sha256 of the source file. `--hyp HYP.json` adds the PLUGIN HYPOTHESES: hypothetical ESIL /
p-code templates for forms the source lists in HYP WITHOUT a template of its own (none at present); they are flagged HYP, exported
as esil_plugin with esil_origin = "plugin-hypothesis", and the file's sha256 goes to meta.plugin_hyp_sha256 and to the header.
"""
import hashlib
import importlib.util
import json
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
KINDS = ['alu', 'mv', 'mem', 'stack', 'branch', 'jump', 'call', 'ret', 'loop', 'sys', 'vec']
CONFS = ['high', 'medium', 'low', 'format-only']
OPKINDS = ['r5', 'r4', 'r4p', 'r3p', 'csr', 'acc', 'imm', 'disp', 'const']
FMTS = ['d', 'x', 'xb', 'n', 'inc', 'pm', 'h2', 'hx', 'b3', 'x04', 'x05', 'x06', 'disp', 'dispx', 'reg']
REGKINDS = ('r5', 'r4', 'r4p', 'r3p', 'csr', 'acc')
LENGTHS = (2, 3, 4, 5, 6, 8)
FL_NAMED, FL_PROV, FL_TABLELEN, FL_CONTROL, FL_HYP, FL_OPEN = 1, 2, 4, 8, 16, 32
CONFIDENCE_LEVELS = {
'high': 'encoding and operation established by independent checks',
'medium': 'established by a single kind of check; a sign or width may still be open',
'low': 'named hypothesis',
'format-only': 'the shape (length, fields) is established but the operation is not: the mnemonic is a family placeholder',
}
def cstr(s):
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
# ---------------------------------------------------------------------------------------------------------- export ----
def hypothetical(T):
"""the alternatives whose ESIL is a hypothesis: the forms listed in the source table HYP (the reading of a provisional name or
of an open sign); the plugin emits their ESIL, a strict emulator may refuse the SCQ16_FL_HYP alternatives."""
return {id(a) for fm in T.FORMS if fm.ref in getattr(T, 'HYP', {}) for a in fm.alts}
def load_source(path):
spec = importlib.util.spec_from_file_location('scq16_isa_source', path)
mod = importlib.util.module_from_spec(spec)
sys.path.insert(0, os.path.dirname(os.path.abspath(path)))
spec.loader.exec_module(mod)
return mod
def export(source_path, leaves_path, hyp_path=None):
T = load_source(source_path)
hyp = hypothetical(T)
src_sha = hashlib.sha256(open(source_path, 'rb').read()).hexdigest()
ph = json.load(open(hyp_path)) if hyp_path else {'forms': {}}
ph_sha = hashlib.sha256(open(hyp_path, 'rb').read()).hexdigest() if hyp_path else None
ph_forms = {int(k): v for k, v in ph.get('forms', {}).items()}
firm = set(getattr(T, 'VNAME_FIRM', ()))
pat = json.load(open(leaves_path))
leaves = {}
for key, name in (('U', 'unit'), ('H', 'head')):
rows = []
for row in pat[key]:
b0, depth, prefix, label = row[0], row[1], row[2], row[3]
if isinstance(label, list):
assert label[0] == 'o'
label = -int(label[1])
rows.append([int(b0), int(depth), int(prefix), int(label)])
leaves[name] = sorted(rows)
classes = sorted(set(T.VCLASS.values()) | {'unclassified'})
js = {
'meta': {
'isa': 'SCQ16 (MD32 family, byte-granular packet variant)',
'source_sha256': src_sha,
'plugin_hyp_sha256': ph_sha,
'plugin_hyp': 'hypothetical templates added by the plugin to forms of the source table HYP without a template (esil_origin = plugin-hypothesis); null = none',
'open': 'an open reading (flag OPEN): a family placeholder or a provisional name; without a template its header ESIL is the marker "REF,ALT,open" (esil_plugin, esil_origin = open-marker)',
'endianness': 'big',
'confidence_levels': CONFIDENCE_LEVELS,
'unit': 'the minimum unit is the byte; a stand-alone unit has 2 to 6 bytes, a vector operation 4, a bundle 8; the first byte and, for some first bytes, a prefix of the second decide the length (length_leaves)',
'packet': '{ A || B }: the head has bit 7 of its first byte set and is decoded as A\' = (unit << 1) | bit7(B); B is decoded with its first byte masked (& 0x7f); the two units execute in parallel; a transfer in B takes the packet start as the base of its displacement; a head whose A\' is a control/system form is a masked orphan (the unit decoded with b0 & 0x7f on its own)',
'bit_numbering': 'w = big-endian integer of the transformed unit (A\' in a packet head; byte 0 & 0x7f in a B slot or an orphan), bit 0 = LSB of the last byte; bit 7 of byte 0 is never part of a mask',
'form_order': 'within one length the first alternative whose (w & mask) == value matches wins; the list is ordered by specificity',
'operand': 'pieces = [lo, width] bit pieces (the first is the most significant); value = (signed ? sext : raw) * scale + bias; register kinds r5 (r0-r31), r4 (r0-r15), r4p (r1-r16), r3p (r1-r8), csr, acc; a const operand is a value of the alternative',
'esil': '{name} read, {=name} write, {#name} decimal value, {d} target, {ret} return address after the delay slots, {mask} of a bit-field width; null = no semantics, "" = no architectural effect; hypothetical = the reading of a provisional name or of an open sign',
'length_leaves': '[b0 & 0x7f, depth, prefix of the top depth bits of b1, label]; label > 0: length in bytes, label < 0: masked orphan of -label bytes; the deepest matching leaf wins; unit = stand-alone units and B slots, head = packet heads (A\')',
'delay_slots': 'slots = instructions (packets included) executed after a transfer or before the body of a loop',
},
'registers': {
'gpr': list(T.REGS),
'csr': [T.CSRN[i] for i in range(32)],
'acc': [T.ACCN[i] for i in range(32)],
},
'kinds': KINDS,
'forms': [],
'length_leaves': leaves,
'vector': {
'classes': classes,
'class_by_op9': {'0x%03x' % k: v for k, v in sorted(T.VCLASS.items())},
'name_by_op9': {'0x%03x' % k: (v if k in firm else v + '?') for k, v in sorted(T.VNAME.items())},
'firm_op9': ['0x%03x' % k for k in sorted(firm)],
},
}
for fm in T.FORMS:
fj = {'ref': fm.ref, 'name': fm.name, 'length': fm.L, 'kind': fm.kind, 'confidence': fm.conf, 'slots': fm.slots,
'table_length': bool(fm.table_len), 'control': bool(T.is_control_form(fm))}
if fm is T.VEC:
fj['role'] = 'vector'
elif fm is T.BUNDLE:
fj['role'] = 'bundle'
fj['alternatives'] = []
for ai, a in enumerate(fm.alts):
mask, value = a.mask_value()
ops = []
for k, op in fm.ops_of(a).items():
if isinstance(op, tuple):
ops.append({'name': k, 'kind': 'const', 'fmt': op[2], 'value': a.consts[op[1]]})
else:
ops.append({'name': k, 'kind': op.kind, 'fmt': 'reg' if op.kind in REGKINDS else op.fmt, 'pieces': [list(p) for p in op.pieces], 'signed': bool(op.signed),
'scale': op.scale, 'bias': op.bias})
named = bool(T.named_mn(fm, a.mn))
is_open = fm is not T.VEC and fm is not T.BUNDLE and not named
esil = fm.esil_of(a)
alt = {
'mnemonic': a.mn, 'match': '(w & %#x) == %#x' % (mask, value), 'mask': mask, 'value': value,
'confidence': fm.conf_of(a), 'slots': fm.slots_of(a), 'named': named,
'provisional': a.mn.endswith('?'), 'hypothetical': id(a) in hyp, 'open': is_open, 'operands': ops,
'text': fm.fmt_of(a), 'esil': esil}
if esil is None and id(a) in hyp and fm.ref in ph_forms:
alt['esil_plugin'] = ph_forms[fm.ref]['esil']; alt['esil_origin'] = 'plugin-hypothesis'
elif esil is None and is_open:
alt['esil_plugin'] = '%d,%d,open' % (fm.ref, ai); alt['esil_origin'] = 'open-marker'
fj['alternatives'].append(alt)
js['forms'].append(fj)
return js
# -------------------------------------------------------------------------------------------------------- generate ----
def generate(js):
forms = js['forms']
leaves = js['length_leaves']
vec = js['vector']
L = []
nalts = sum(len(f['alternatives']) for f in forms)
L.append('/* scq16_opcodes.h - %s instruction forms.' % js['meta']['isa'])
L.append(' * GENERATED by tools/gen_scq16_tables.py from isa/scq16_isa.json - do not edit by hand.')
if js['meta'].get('source_sha256'):
L.append(' * ISA source sha256 %s.' % js['meta']['source_sha256'])
L.append(' * %d forms, %d bit alternatives; length-rule leaves: %d of stand-alone units, %d of packet heads; %d op9 codes with a vector class.' % (
len(forms), nalts, len(leaves['unit']), len(leaves['head']), len(vec['class_by_op9'])))
L.append(' * scq16_vname[op9] carries the ? of a provisional vector name; a name without ? is an established operation (%d firm).' % len(vec.get('firm_op9', [])))
L.append(' * Convention: w = big-endian integer of the TRANSFORMED unit (A\' in a packet head; byte 0 & 0x7f in a B slot or an')
L.append(' * orphan), bit 0 = LSB of the last byte; bit 7 of byte 0 is never part of a mask. Alternative order = specificity:')
L.append(' * see README.md "SCQ16 mode" and doc/scq16.md. */')
L.append('#ifndef SCQ16_OPCODES_H')
L.append('#define SCQ16_OPCODES_H')
L.append('#include <stdint.h>')
if js['meta'].get('source_sha256'):
L.append('#define SCQ16_SRC_SHA256 "%s"' % js['meta']['source_sha256'])
if js['meta'].get('plugin_hyp_sha256'):
L.append('#define SCQ16_PLUGIN_HYP_SHA256 "%s"' % js['meta']['plugin_hyp_sha256'])
L.append('')
L.append('enum scq16_kind { ' + ', '.join('SCQ16_K_%s' % k.upper() for k in KINDS) + ' };')
L.append('enum scq16_conf { ' + ', '.join('SCQ16_C_%s' % c.upper().replace('-', '_') for c in CONFS) + ' };')
L.append('enum scq16_opkind { ' + ', '.join('SCQ16_O_%s' % k.upper() for k in OPKINDS) + ' };')
L.append('enum scq16_fmt { ' + ', '.join('SCQ16_F_%s' % f.upper() for f in FMTS) + ' };')
L.append('#define SCQ16_FL_NAMED 1 /* an established operation name (not a family placeholder nor a vector class) */')
L.append('#define SCQ16_FL_PROV 2 /* provisional name (ends in ?) */')
L.append('#define SCQ16_FL_TABLELEN 4 /* form tied to the length table */')
L.append('#define SCQ16_FL_CONTROL 8 /* control/system A\': never a packet head */')
L.append('#define SCQ16_FL_HYP 16 /* the ESIL is a hypothesis: the reading of a provisional name or of an open sign */')
L.append('#define SCQ16_FL_OPEN 32 /* an open reading: a family placeholder or a provisional name; without a template the ESIL is the marker "REF,ALT,open" */')
L.append('')
L.append('typedef struct { const char *name; uint8_t kind; uint8_t fmt; uint8_t npieces; uint8_t is_signed; struct { uint8_t lo, w; } piece[2]; int32_t scale, bias, cval; } Scq16Operand;')
L.append('typedef struct { uint16_t form; uint16_t ref; const char *form_name; const char *mn; uint8_t L; uint8_t kind, conf, slots, nops, flags; uint64_t mask, value; const Scq16Operand *ops; const char *fmt; const char *esil; } Scq16Alt;')
L.append('typedef struct { uint8_t b0, depth, prefix; int8_t label; } Scq16Leaf; /* label > 0: length; < 0: masked orphan of -label bytes */')
L.append('')
opsets = []
alt_rows = []
by_len = {Lb: [] for Lb in LENGTHS}
vec_alt = bundle_alt = None
ai_global = 0
for fi, fm in enumerate(forms):
assert fm['kind'] in KINDS and fm['length'] in LENGTHS, (fm['name'], fm['kind'], fm['length'])
for ai, a in enumerate(fm['alternatives']):
rows = []
for op in a['operands']:
assert op['fmt'] in FMTS and op['kind'] in OPKINDS, (fm['name'], op)
if op['kind'] == 'const':
rows.append('{ %s, SCQ16_O_CONST, SCQ16_F_%s, 0, 0, {{0,0},{0,0}}, 1, 0, %d }' % (cstr(op['name']), op['fmt'].upper(), op['value']))
else:
pcs = [list(p) for p in op['pieces']] + [[0, 0]] * (2 - len(op['pieces']))
assert len(op['pieces']) <= 2
rows.append('{ %s, SCQ16_O_%s, SCQ16_F_%s, %d, %d, {{%d,%d},{%d,%d}}, %d, %d, 0 }' % (
cstr(op['name']), op['kind'].upper(), op['fmt'].upper(), len(op['pieces']), int(op['signed']), pcs[0][0], pcs[0][1], pcs[1][0], pcs[1][1], op['scale'], op['bias']))
name = 'ops_%d_%d' % (fi, ai)
if rows:
opsets.append('static const Scq16Operand %s[] = {\n %s\n};' % (name, ',\n '.join(rows)))
else:
name = 'NULL'
mask, value = a['mask'], a['value']
assert fm.get('role') in ('vector', 'bundle') or not (mask >> (8 * fm['length'] - 1)) & 1, (fm['name'], 'mask over bit 7 of byte 0')
flags = 0
if a['named']:
flags |= FL_NAMED
if a['provisional']:
flags |= FL_PROV
if fm['table_length']:
flags |= FL_TABLELEN
if fm['control']:
flags |= FL_CONTROL
if a['hypothetical']:
flags |= FL_HYP
if a.get('open'):
flags |= FL_OPEN
assert a['confidence'] in CONFS, (fm['name'], a['confidence'])
esil = a.get('esil_plugin', a['esil'])
alt_rows.append(' { %d, %d, %s, %s, %d, SCQ16_K_%s, SCQ16_C_%s, %d, %d, %d, 0x%xULL, 0x%xULL, %s, %s, %s }' % (
fi, fm['ref'], cstr(fm['name']), cstr(a['mnemonic']), fm['length'], fm['kind'].upper(), a['confidence'].upper().replace('-', '_'),
a['slots'], len(rows), flags, mask, value, name, cstr(a['text']), cstr(esil) if esil is not None else 'NULL'))
by_len[fm['length']].append(ai_global)
if fm.get('role') == 'vector' and vec_alt is None:
vec_alt = ai_global
if fm.get('role') == 'bundle' and bundle_alt is None:
bundle_alt = ai_global
ai_global += 1
assert vec_alt is not None and bundle_alt is not None, 'the vector and bundle forms must carry their role'
L += opsets
L.append('')
L.append('#define SCQ16_NFORMS %d' % len(forms))
L.append('#define SCQ16_NALTS %d' % nalts)
L.append('static const Scq16Alt scq16_alts[SCQ16_NALTS] = {')
L.append(',\n'.join(alt_rows))
L.append('};')
for Lb in LENGTHS:
idx = by_len[Lb]
L.append('static const uint16_t scq16_alts_L%d[] = { %s };' % (Lb, ', '.join(str(i) for i in idx) if idx else '0'))
L.append('#define SCQ16_NALTS_L%d %d' % (Lb, len(idx)))
L.append('#define SCQ16_VEC_ALT %d' % vec_alt)
L.append('#define SCQ16_BUNDLE_ALT %d' % bundle_alt)
L.append('')
U = sorted(tuple(r) for r in leaves['unit'])
H = sorted(tuple(r) for r in leaves['head'])
L.append('/* length-rule leaves: (b0 & 0x7f, the top `depth` bits of b1) -> L; the deepest matching leaf wins */')
L.append('static const Scq16Leaf scq16_uleaves[] = {')
L.append(',\n'.join(' { 0x%02x, %d, 0x%02x, %d }' % (b0, d, p, lab) for b0, d, p, lab in U))
L.append('};')
L.append('#define SCQ16_NULEAVES %d' % len(U))
L.append('/* head leaves: (a0, prefix of a1) of the shifted unit A\' -> L(A\') or -Lm (masked orphan of Lm bytes) */')
L.append('static const Scq16Leaf scq16_hleaves[] = {')
L.append(',\n'.join(' { 0x%02x, %d, 0x%02x, %d }' % (b0, d, p, lab) for b0, d, p, lab in H))
L.append('};')
L.append('#define SCQ16_NHLEAVES %d' % len(H))
for nm, lst in (('u', U), ('h', H)):
starts = []
j = 0
for b0 in range(129):
while j < len(lst) and lst[j][0] < b0:
j += 1
starts.append(j)
L.append('static const uint16_t scq16_%sleaf_start[129] = { %s };' % (nm, ', '.join(str(s) for s in starts)))
L.append('')
classes = vec['classes']
L.append('static const char *const scq16_vclass_names[] = { %s };' % ', '.join(cstr(c) for c in classes))
vc = [classes.index(vec['class_by_op9'].get('0x%03x' % i, 'unclassified')) for i in range(512)]
L.append('static const uint8_t scq16_vclass[512] = {\n ' + ',\n '.join(', '.join(str(x) for x in vc[i:i + 32]) for i in range(0, 512, 32)) + '\n};')
vn = ['NULL'] * 512
for k, v in vec['name_by_op9'].items():
vn[int(k, 16)] = cstr(v)
L.append('static const char *const scq16_vname[512] = {\n ' + ',\n '.join(', '.join(vn[i:i + 16]) for i in range(0, 512, 16)) + '\n};')
L.append('')
regs = js['registers']
assert len(regs['gpr']) == 32 and len(regs['csr']) == 32 and len(regs['acc']) == 32
L.append('static const char *const scq16_regs[32] = { %s };' % ', '.join(cstr(r) for r in regs['gpr']))
L.append('static const char *const scq16_csrs[32] = { %s };' % ', '.join(cstr(r) for r in regs['csr']))
L.append('static const char *const scq16_accs[32] = { %s };' % ', '.join(cstr(r) for r in regs['acc']))
L.append('static const char *const scq16_kind_names[] = { %s };' % ', '.join(cstr(k) for k in KINDS))
L.append('static const char *const scq16_conf_names[] = { %s };' % ', '.join(cstr(c) for c in CONFS))
L.append('#endif')
return '\n'.join(L) + '\n'
def main(argv):
out = argv[argv.index('-o') + 1] if '-o' in argv else None
args = [a for i, a in enumerate(argv) if a != '-o' and (i == 0 or argv[i - 1] != '-o')]
hyp_path = None
if '--hyp' in args:
i = args.index('--hyp')
hyp_path = args[i + 1]
del args[i:i + 2]
if args and args[0] == 'export':
if len(args) != 3:
print(__doc__)
return 2
js = export(args[1], args[2], hyp_path)
out = out or os.path.join(ROOT, 'isa', 'scq16_isa.json')
with open(out, 'w', encoding='utf-8') as fh:
json.dump(js, fh, indent=1, ensure_ascii=False)
fh.write('\n')
print('wrote %s: %d forms, %d alternatives, %d unit leaves, %d head leaves' % (
out, len(js['forms']), sum(len(f['alternatives']) for f in js['forms']), len(js['length_leaves']['unit']), len(js['length_leaves']['head'])))
return 0
src = args[0] if args else os.path.join(ROOT, 'isa', 'scq16_isa.json')
out = out or os.path.join(ROOT, 'scq16_opcodes.h')
js = json.load(open(src, encoding='utf-8'))
text = generate(js)
with open(out, 'w', encoding='utf-8') as fh:
fh.write(text)
print('wrote %s: %d forms, %d alternatives' % (out, len(js['forms']), sum(len(f['alternatives']) for f in js['forms'])))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
+687
View File
@@ -0,0 +1,687 @@
#!/usr/bin/env python3
"""Generate usip_opcodes.h (the uSIP instruction table) from isa/usip_isa.json.
python3 tools/gen_usip_tables.py [isa/usip_isa.json] [-o usip_opcodes.h]
Compared with a plain mask/value table this header also carries everything the C packet decoder (usip_decode.c)
needs: every operand field referenced by the format string OR by a constraint, the constraints as structured data
(not strings), the entry flags, the indices of the context-only forms and the auxiliary tables of the ISA description
(must-pair heads, save/restore sets, sys-shape names, CSR names, the R-br0 cells, the 48-bit value and address
tables). The output is deterministic: regenerating from the same JSON gives a byte-identical header.
Table layout (see README.md "uSIP mode" and doc/usip.md):
UsipOpcode { name, form, value, mask, width, nunits, flags, kind, conf, fmt, nargs, nfields, fields[], ncons, cons[] }
* value/mask are over the big-endian concatenation of the units (bit width-1 = bit 15 of the head); 16-bit forms are
matched with the mask as given (bit 15 is the packet-close bit: forms whose mask includes it match only closers);
* fields[]: operand fields; the first nargs are the ones used by fmt, in order of first use; each one = type + up to
7 [hi,lo] bit ranges (MSB first, concatenated), USIP_BITS_ZERO(n) = n implicit zero bits; nbits/scale/base for the
value transform (base = segment base for absaddr, pc offset for disp, a constant added to the field for the other
integer types);
* fmt: operand template with {N} = fields[N] (rendered by type), {N:text} = text if the flag field N is set;
* cons[]: constraints that are not mask/value (register/address windows, guards, packet rules); the target guards
(guard_target_*) and the b.s guard are evaluated by the decoder after the match, with the packet/function context it
has; the position/length constraints (packet_len, packet_len_ge, head_index, head_index_ge, ctx, br0_cells) of the
32/48-bit entries are deferred too: when they fail the decoder falls through to the next entry of the same cell.
Operand types offh (half-word offset), offw (14-bit word displacement), offb (byte displacement), loopn (loop count)
and movhi (collected bits << 12) have their own value formula in the decoder; the flags ADJDEF (tail at +2 or farther),
PREFIX (packet-prefix entries matched on the first units of the packet before the layout), RENAME (post-layout rename:
the entry is never used by the packet parser; once the layout is decided an instruction whose reading is one of the
entry's rename_from mnemonics, or "@kind", and whose units match the entry and its constraints is relabelled), HHTT
(context entries admitted only inside the H H T T candidate), B111 (context entries admitted in the 2-unit head + tail
packet and inside the H H T T candidate), F0CTX (context entries tried by the decoder when an op 0 head with its F' = 0
tail at +2 reads .word) and GUARDFT (a failing target guard falls through to the next entry of the cell instead of
making the pair .word; no entry left = two 16-bit ops) complete the packet rules of the table. The constraints
tail_adjacent (the single tail is or is not the next unit), ieee_short (the value is the high word of a short IEEE-754
double), bf_valid (the bit-field cell is valid), br0_cells (the B class of the head and the distance of the tail are
one of the admitted cells; br0_cells_live lists the cells that only hold at the live-trace sites, which the plugin
never takes), the context conditions and the membership in the 48-bit value table (in_c48tab) are evaluated by the
decoder with the packet context; the auxiliary tables usip_never_tail / usip_never_head_with_tail (units that are never
a tail / heads that never take one), usip_c48_table (the 48-bit constants admitted by value), usip_br0_cells /
usip_br0_cells_weak (the R-br0 cells by (op, tt, B class, distance)) and usip_c48sym_table (the data addresses already
formed by the 48-bit family, for the R-c48sym tie-break) come from the JSON.
Table update (726 forms): the loopn operand type is the collected bits as is (loop #N = head bits 13:5, no +1: the
formula lives in the decoder); the flag FUSE (post-layout fusion: two adjacent one-unit instructions whose readings are
the two rename_from mnemonics, head and tail, and whose units match the entry become one two-unit instruction); the flag
SPLIT (an entry of the post-layout split procedure of the slot family, applied by the decoder's split pass and never by
the generic rename pass; the JSON key split_role: "either" = head or re-read tail of a split pair, "bare" = a bare unit
renamed before the pass) with a USIP_FORM_* index for every such entry; the constraint op instr_index (the position of
the INSTRUCTION inside the decoded packet, not the unit index); `any` with up to four alternatives, which may now be a
context condition or an instr_index test; six new context conditions (not_loop_reg_head, delay_slot_of_validated_branch,
target_beyond_packet, delay_slot_not_writing_rA, op4imm_head_multiunit, split_unit).
Table update (760 forms): the guards loop_b4_straddle16 (the loop #N entries: bit 4 of the head, field b4, must agree with
"the packet at the loop end crosses a 16-byte line"; exported with the field b4) and not_over_validated_call32 (the 64-bit
b<cond>.l entries whose extension unit is any 16-bit unit X: not taken over a validated 32-bit call / b read from U0 + X);
seven new context conditions (tail_at_plus2, head_unnamed_not_lsl0, rf0dall_guards, slotc_pair, call_within_2_packets,
op4imm_head, add2_free_closer); FUSE together with ADJ2 marks the fusion at distance 2 (add2_rot12); the loop #N? rule of
structure.packet.loop_imm_q (USIP_LOOPQ_N_EQ, usip_loopq_d12[]) and the packet execution model (USIP_PACKET_EXEC_PARALLEL)
are emitted as constants.
"""
import json, os, sys, collections
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
TYPES = ['reg', 'imm', 'simm', 'rotimm', 'urot', 'disp', 'udisp', 'absaddr', 'acc', 'accw', 'csr', 'csr_ext', 'flag', 'movhi',
'offh', 'offw', 'loopn', 'offb']
CONF = {'high': 'USIP_CONF_HIGH', 'medium-high': 'USIP_CONF_HIGH', 'medium': 'USIP_CONF_MEDIUM',
'low-medium': 'USIP_CONF_MEDIUM', 'low': 'USIP_CONF_LOW', 'format-only': 'USIP_CONF_FORMAT'}
CONS_OPS = ['eq', 'ne', 'lt', 'ge', 'gt', 'in', 'notin', 'inmask', 'notinmask', 'range', 'ranges', 'even', 'odd', 'not_hi1010',
'eq_fields', 'ne_fields', 'packet_len', 'packet_len_ge', 'head_index', 'head_index_ge', 'ctx', 'instr_index',
'slot0_excludes_head', 'any',
'guard_setcc_tail', 'guard_tail_not_named16', 'guard_or2_tail',
'guard_target_packet_start', 'guard_target_function_start', 'guard_target_fn_or_packet_start',
'guard_head_not_validated_bs',
'tail_adjacent', 'ieee_short', 'in_c48tab',
'guard_target_pk_any_or_fn', 'guard_tail_named16', 'br0_cells', 'br0_cells_live', 'bf_valid', 'in_set', 'notin_set',
'guard_loop_b4_straddle16', 'guard_not_over_validated_call32']
ANY_OPS = {'eq': 0, 'ne': 1, 'lt': 2, 'ge': 3, 'gt': 4, 'inmask': 5, 'notinmask': 6, 'tail_adjacent': 7,
'packet_len': 8, 'guard': 9, 'range': 10, 'br0_cells': 11, 'br0_cells_live': 12, 'instr_index': 13, 'ctx': 14}
# guards that may appear inside an `any` (USIP_ANY_GUARD: value = enum usip_guard_id)
GUARD_IDS = {'tail_not_named16': 0, 'target_packet_start_same_function': 1}
CTX_IDS = {'not_ext_of_op67': 0, 'cop_rA_in_packet': 1, 'not_c48_lo_follows': 2,
'b111_layout': 3, 'hhtt_second_head': 4, 'f0d_triple': 5, 'bfpair': 6, 'bf_dst_not_written': 7,
'between_dsp_head_and_tail': 8, 'rest_named_no_q': 9,
'not_loop_reg_head': 10, 'delay_slot_of_validated_branch': 11, 'target_beyond_packet': 12,
'delay_slot_not_writing_rA': 13, 'op4imm_head_multiunit': 14, 'split_unit': 15,
'tail_at_plus2': 16, 'head_unnamed_not_lsl0': 17, 'rf0dall_guards': 18, 'slotc_pair': 19, 'call_within_2_packets': 20,
'op4imm_head': 21, 'add2_free_closer': 22}
CTX_FIELDS = {'cop_rA_in_packet': ['rA'], 'bf_dst_not_written': ['rB', 'x']}
GUARDS = {'setcc_tail': 'guard_setcc_tail', 'tail_not_named16': 'guard_tail_not_named16', 'or2_tail': 'guard_or2_tail',
'target_packet_start_same_function': 'guard_target_packet_start',
'target_function_start': 'guard_target_function_start',
'target_fn_or_packet_start_same_function': 'guard_target_fn_or_packet_start',
'head_not_validated_bs': 'guard_head_not_validated_bs',
'target_packet_start_any_or_function_start': 'guard_target_pk_any_or_fn',
'tail_named16': 'guard_tail_named16',
'loop_b4_straddle16': 'guard_loop_b4_straddle16',
'not_over_validated_call32': 'guard_not_over_validated_call32'}
# fields a guard reads beyond the tail (exported after the format fields)
GUARD_FIELDS = {'or2_tail': ['u12'], 'loop_b4_straddle16': ['b4']}
# R-br0 cells: B class of the head (B0 = B == 0, B15 = B == 15, B = any other) x tail distance (1 / 2) as a bit mask
BR0_CLASS = {'B0': 0, 'B15': 1, 'B': 2}
MAXFIELDS = 12
MAXCONS = 8
MAXVALS = 12
MAXRANGES = 8
MAXANY = 4
SAVE_SET = ['r8,r9,r10,LR', 'r12,r8!,r9!,r10!', 'r11!,r12!,r13!,r14!', None, 'LR,r8', 'r12,r8!', 'r11!,r12!', None]
def cstr(s):
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
def cident(s):
"""a form name as a C identifier fragment"""
return ''.join(ch if ch.isalnum() or ch == '_' else '_' for ch in s)
def br0_mask(cells):
m = 0
for cls, dist in cells:
if cls not in BR0_CLASS or dist not in (1, 2):
raise SystemExit('unsupported br0 cell %r' % ((cls, dist),))
m |= 1 << (2 * BR0_CLASS[cls] + dist - 1)
return m
C48 = [] # the 48-bit value table (the `in` set of the c48tab entries), filled by emit()
SETS = [] # the large value sets (USIP_C_IN_SET / USIP_C_NOTIN_SET: vals[0] = index into usip_value_sets[]), filled by set_cons()
def simple_cons(c):
"""(op, value) of a sub-constraint of `any`: a comparison, a set as a 31-bit membership mask, tail_adjacent, a packet
length, a guard, a half-open range (value = lo | hi << 16) or an R-br0 cell list (value = cell mask)"""
if 'tail_adjacent' in c:
return 'tail_adjacent', 1 if c['tail_adjacent'] else 0
if 'packet_len' in c:
return 'packet_len', c['packet_len']
if 'guard' in c:
if c['guard'] not in GUARD_IDS:
raise SystemExit('unsupported guard inside any: %r' % c)
return 'guard', GUARD_IDS[c['guard']]
if 'range' in c:
lo, hi = c['range']
if not (0 <= lo < 0x10000 and 0 <= hi < 0x8000):
raise SystemExit('range sub-constraint out of the packed value: %r' % c)
return 'range', lo | (hi << 16)
if 'br0_cells' in c:
return 'br0_cells', br0_mask(c['br0_cells'])
if 'br0_cells_live' in c:
return 'br0_cells_live', br0_mask(c['br0_cells_live'])
if 'instr_index' in c:
return 'instr_index', c['instr_index']
if 'ctx' in c:
if c['ctx'] not in CTX_IDS:
raise SystemExit('unknown context condition inside any: %s' % c['ctx'])
return 'ctx', CTX_IDS[c['ctx']]
for op in ('eq', 'ne', 'lt', 'ge', 'gt'):
if op in c:
return op, c[op]
for op in ('in', 'notin'):
if op in c:
if not all(isinstance(v, int) and 0 <= v < 31 for v in c[op]):
raise SystemExit('set sub-constraint out of the mask range: %r' % c)
m = 0
for v in c[op]:
m |= 1 << v
return op + 'mask', m
raise SystemExit('unsupported sub-constraint %r' % c)
def any_field(a):
"""the field a sub-constraint of `any` reads (None when it has none)"""
if 'field' in a:
return a['field']
if 'br0_cells' in a or 'br0_cells_live' in a:
return 'rB'
return None
def set_cons(op, f, vals):
"""in/notin: a value list when it fits, else a 64-bit membership mask (all values in 0..63); the 48-bit value table by reference"""
vals = list(vals)
if op == 'in' and C48 and sorted(vals) == sorted(C48):
return ('in_c48tab', f, 0xff, [])
if len(vals) <= MAXVALS:
return (op, f, 0xff, vals)
if all(isinstance(v, int) and 0 <= v < 64 for v in vals):
m = 0
for v in vals:
m |= 1 << v
return (op + 'mask', f, 0xff, [m & 0xffffffff, m >> 32])
if not all(isinstance(v, int) for v in vals):
raise SystemExit('set constraint too large: %r' % vals)
key = tuple(sorted(set(vals)))
if key not in SETS:
SETS.append(key)
return (op + '_set', f, 0xff, [SETS.index(key)])
def cons_entries(c, fidx, width, rename=False, ctxform=False):
"""one constraint dict -> (op, field, field2, [vals]); rename = the entry is a post-layout rename (its position
constraints are evaluated with the exact head index, not through the 16-bit match cache); ctxform = a context-only
entry (matched by the decoder's context rules, never through the cache)"""
if 'any' in c:
alts = c['any']
if not 1 <= len(alts) <= MAXANY:
raise SystemExit('unsupported any-constraint: %r' % c)
vals = []
for a in alts:
op, v = simple_cons(a)
if op == 'packet_len' and width == 16 and not rename and not ctxform and v not in (1, 2):
raise SystemExit('packet_len %r inside any: the decoder caches 16-bit matches by packet length class 1 / 2 / 3+' % v)
fl = any_field(a)
vals += [fidx[fl] if fl is not None else 0xff, ANY_OPS[op], v]
return ('any', 0xff, 0xff, vals)
if 'tail_adjacent' in c:
return ('tail_adjacent', 0xff, 0xff, [1 if c['tail_adjacent'] else 0])
if 'ieee_short' in c:
return ('ieee_short', fidx[c['field']], 0xff, [c['ieee_short']])
if 'br0_cells' in c:
return ('br0_cells', fidx['rB'], 0xff, [br0_mask(c['br0_cells'])])
if 'br0_cells_live' in c:
return ('br0_cells_live', fidx['rB'], 0xff, [br0_mask(c['br0_cells_live'])])
if 'bf_valid' in c:
return ('bf_valid', fidx['w'], fidx['pos'], [])
if 'guard' in c:
gd = c['guard']
if gd not in GUARDS:
raise SystemExit('unknown guard %s' % gd)
if gd == 'setcc_tail':
return (GUARDS[gd], 0xff, 0xff, c.get('small', [0, 2, 4, 0xFFE, 0xFFF]))
if gd == 'or2_tail':
return (GUARDS[gd], fidx['u12'], 0xff, [])
return (GUARDS[gd], 0xff, 0xff, [])
if 'eq_fields' in c:
a, b = c['eq_fields']
return ('eq_fields', fidx[a], fidx[b], [])
if 'ne_fields' in c:
a, b = c['ne_fields']
return ('ne_fields', fidx[a], fidx[b], [])
if 'packet_len' in c:
if width == 16 and not rename and not ctxform and c['packet_len'] not in (1, 2):
raise SystemExit('packet_len %r: the decoder caches 16-bit matches by packet length class 1 / 2 / 3+' % c['packet_len'])
return ('packet_len', 0xff, 0xff, [c['packet_len']])
if 'packet_len_ge' in c:
if width == 16 and not rename and not ctxform and c['packet_len_ge'] != 2:
raise SystemExit('packet_len_ge %r: the decoder caches 16-bit matches by packet length class 1 / 2 / 3+' % c['packet_len_ge'])
return ('packet_len_ge', 0xff, 0xff, [c['packet_len_ge']])
if 'head_index' in c:
if width == 16 and not rename and not ctxform and c['head_index'] != 0:
raise SystemExit('head_index %r: the decoder caches 16-bit matches by head index class 0 / 1 / 2+' % c['head_index'])
return ('head_index', 0xff, 0xff, [c['head_index']])
if 'instr_index' in c:
return ('instr_index', 0xff, 0xff, [c['instr_index']])
if 'head_index_ge' in c or 'head_pos_ge' in c:
# (head_pos_ge: the spelling of the public copy of the table)
v = c.get('head_index_ge', c.get('head_pos_ge'))
if width == 16 and not rename and not ctxform and v != 2:
raise SystemExit('head_index_ge %r: the decoder caches 16-bit matches by head index class 0 / 1 / 2+' % v)
return ('head_index_ge', 0xff, 0xff, [v])
if 'ctx' in c:
if c['ctx'] not in CTX_IDS:
raise SystemExit('unknown context condition %s' % c['ctx'])
return ('ctx', 0xff, 0xff, [CTX_IDS[c['ctx']]])
if 'slot0_excludes_head' in c:
m, v = c['slot0_excludes_head']
return ('slot0_excludes_head', 0xff, 0xff, [m, v])
f = fidx[c['field']]
for op in ('eq', 'ne', 'lt', 'ge', 'gt'):
if op in c:
return (op, f, 0xff, [c[op]])
if 'in' in c:
return set_cons('in', f, c['in'])
if 'notin' in c:
return set_cons('notin', f, c['notin'])
if 'range' in c:
return ('range', f, 0xff, list(c['range']))
if 'ranges' in c:
return ('ranges', f, 0xff, [v for lo, hi in c['ranges'] for v in (lo, hi)])
if 'even' in c:
return ('even', f, 0xff, [])
if 'odd' in c:
return ('odd', f, 0xff, [])
if 'not_hi1010' in c:
return ('not_hi1010', f, 0xff, [])
raise SystemExit('unknown constraint %r' % c)
def cons_fields(c):
if 'any' in c:
return [any_field(a) for a in c['any'] if any_field(a) is not None] + [f for a in c['any'] if 'ctx' in a for f in CTX_FIELDS.get(a['ctx'], [])]
if 'guard' in c:
return list(GUARD_FIELDS.get(c['guard'], []))
if 'eq_fields' in c:
return list(c['eq_fields'])
if 'ne_fields' in c:
return list(c['ne_fields'])
if 'br0_cells' in c or 'br0_cells_live' in c:
return ['rB']
if 'bf_valid' in c:
return ['w', 'pos']
if 'ctx' in c:
return CTX_FIELDS.get(c['ctx'], [])
return [c['field']] if 'field' in c else []
def exported_fields(form):
"""(template with {N}, nargs, [field dicts]): format fields first (order of first use), then constraint-only fields"""
fmt = form['format']; fields = {f['name']: f for f in form['fields']}
names = []; out = []; i = 0
while i < len(fmt):
if fmt[i] == '{':
j = fmt.index('}', i); tok = fmt[i + 1:j]; i = j + 1
nm, txt = (tok.split(':', 1) + [None])[:2]
if nm not in fields:
out.append('{' + tok + '}'); continue
if nm not in names:
names.append(nm)
k = names.index(nm)
out.append('{%d}' % k if txt is None else '{%d:%s}' % (k, txt))
else:
out.append(fmt[i]); i += 1
nargs = len(names)
for c in form.get('constraints', []):
for nm in cons_fields(c):
if nm not in fields:
raise SystemExit('%s: constraint field %s missing' % (form['name'], nm))
if nm not in names:
names.append(nm)
return ''.join(out), nargs, [fields[n] for n in names]
def field_base(a):
"""UsipField.base: segment base (absaddr), pc offset (disp) or the constant added to the field value"""
if a['type'] == 'absaddr':
return '0x%08x' % (a.get('seg', 0) << 26)
if a['type'] == 'disp':
return str(a.get('pc_offset', 0))
return str(a.get('offset', 0))
def br0_rows(cells):
rows = []
for op, tt, cls, dist in cells:
if cls not in BR0_CLASS or dist not in (1, 2):
raise SystemExit('unsupported br0 cell %r' % ((op, tt, cls, dist),))
rows.append('{ 0x%x, %d, %d, %d }' % (op, tt, BR0_CLASS[cls], dist))
return rows
def emit(isa, out_path):
forms = isa['forms']
kinds = sorted({f['kind'] for f in forms})
del C48[:]
del SETS[:]
for f in forms:
if f.get('group') == 'c48tab':
for c in f.get('constraints', []):
if c.get('field') == 'val' and 'in' in c:
C48.extend(c['in'])
break
L = []
A = L.append
A('/* usip_opcodes.h - uSIP (MD32 family, VLIW packet variant) instruction forms.')
A(' * GENERATED by tools/gen_usip_tables.py from isa/usip_isa.json - do not edit by hand.')
A(' * Units are 16-bit big-endian halfwords; bit 15 of a unit closes the packet; an instruction is a head (bit 15 = 0) plus')
A(' * 0, 1 or 2 tails that are NOT necessarily adjacent (heads first, tails round-robin): see README.md "uSIP mode". */')
A('#ifndef USIP_OPCODES_H')
A('#define USIP_OPCODES_H')
A('')
A('enum usip_arg_type {')
for t in TYPES:
A('\tUSIP_A_%s,' % t.upper())
A('};')
A('')
A('enum usip_kind {')
for k in kinds:
A('\tUSIP_K_%s,' % k.upper().replace('.', '_'))
A('};')
A('')
A('enum usip_conf { USIP_CONF_HIGH, USIP_CONF_MEDIUM, USIP_CONF_LOW, USIP_CONF_FORMAT };')
A('')
A('enum usip_cons_op {')
for op in CONS_OPS:
A('\tUSIP_C_%s,' % op.upper())
A('};')
A('')
A('/* sub-operations of USIP_C_ANY: vals = { field, op, value } triplets (up to %d), true when one of them holds;' % MAXANY)
A(' * inmask/notinmask: value = membership mask of the field value (bit n = value n); tail_adjacent: value = 0 / 1 (no field);')
A(' * packet_len: value = the packet length (no field); guard: value = enum usip_guard_id (no field; a target guard inside an')
A(' * any is evaluated by the context rule of the entry); range: value = lo | hi << 16 (lo <= field < hi);')
A(' * br0_cells / br0_cells_live: value = cell mask (see USIP_C_BR0_CELLS), field = rB; instr_index: value = the position')
A(' * of the instruction inside the decoded packet (no field); ctx: value = enum usip_ctx_cond (no field) */')
A('enum usip_any_op { USIP_ANY_EQ, USIP_ANY_NE, USIP_ANY_LT, USIP_ANY_GE, USIP_ANY_GT, USIP_ANY_INMASK, USIP_ANY_NOTINMASK, USIP_ANY_TAIL_ADJACENT,')
A('\tUSIP_ANY_PACKET_LEN, USIP_ANY_GUARD, USIP_ANY_RANGE, USIP_ANY_BR0_CELLS, USIP_ANY_BR0_CELLS_LIVE, USIP_ANY_INSTR_INDEX, USIP_ANY_CTX };')
A('')
A('/* guards that may appear inside USIP_C_ANY (USIP_ANY_GUARD) */')
A('enum usip_guard_id { USIP_GD_TAIL_NOT_NAMED16, USIP_GD_TARGET_PACKET_START };')
A('')
A('/* named context conditions of USIP_C_CTX (vals[0]): not the extension word of an op 6/7 head; some `cop rA` with the same rA in the')
A(' * packet; the tail is not 5ffd followed by bfff/ffff (the 48-bit constant 0x7fff7fff); the ld.bu / st.b pair of type 111 only in')
A(' * the 2-unit head + tail packet or in the H H T T layout (never an O syllable after an op 7 head); the second head of H H T T')
A(' * after a named ld/st; the [head][head-shaped unit][F\' = 0 tail at +2] triple; the bit-field pair layout; no other WAW-kind')
A(' * instruction of the packet writes rB; unit 1 between a DSP head and its tail; the rest of the packet named without a ?;')
A(' * the first-slot unit is not the head of a validated `loop rX` (a [S][0][11][d12] tail at +1 / +2 with its end a packet')
A(' * start of the function after it); the unit is a .movi6? closer in the delay slot of a validated branch at unit 0; the')
A(' * target of the 3-unit bz.s lies beyond the packet; its re-parsed delay slot does not write rA; the F = 0 head of a split')
A(' * .op4imm pair in a packet of more than two units; the unit is the head or the re-read tail of a split pair; the single tail')
A(' * is EXACTLY at +2 (else the entry falls through); the best reading leaves the first unit unnamed and it is not the bare')
A(" * op 5 x = 0 unit; the align guardians of the F' = 0 half-word load with any base (the unit at +2 is not a trusted 16-bit op")
A(' * nor control, the head is not 4400 after an op f x = 1 head, the middle unit does not write sp); the op c head with F = 0')
A(' * and its single unnamed tail form a slot pair; some call in the packet or in the two following ones; the F = 0 head of a')
A(' * split .op4imm pair; the bare .hword head and the bare unnamed closer at +2 fuse into add rB, rA, #rot12 (nowaw) */')
A('enum usip_ctx_cond { USIP_CTX_NOT_EXT_OF_OP67, USIP_CTX_COP_RA_IN_PACKET, USIP_CTX_NOT_C48_LO_FOLLOWS, USIP_CTX_B111_LAYOUT,')
A('\tUSIP_CTX_HHTT_SECOND_HEAD, USIP_CTX_F0D_TRIPLE, USIP_CTX_BFPAIR, USIP_CTX_BF_DST_NOT_WRITTEN, USIP_CTX_BETWEEN_DSP_HEAD_AND_TAIL,')
A('\tUSIP_CTX_REST_NAMED_NO_Q, USIP_CTX_NOT_LOOP_REG_HEAD, USIP_CTX_DELAY_SLOT_OF_VALIDATED_BRANCH, USIP_CTX_TARGET_BEYOND_PACKET,')
A('\tUSIP_CTX_DELAY_SLOT_NOT_WRITING_RA, USIP_CTX_OP4IMM_HEAD_MULTIUNIT, USIP_CTX_SPLIT_UNIT, USIP_CTX_TAIL_AT_PLUS2,')
A('\tUSIP_CTX_HEAD_UNNAMED_NOT_LSL0, USIP_CTX_RF0DALL_GUARDS, USIP_CTX_SLOTC_PAIR, USIP_CTX_CALL_WITHIN_2_PACKETS, USIP_CTX_OP4IMM_HEAD,')
A('\tUSIP_CTX_ADD2_FREE_CLOSER };')
A('')
A('#define USIP_F_SLOT0 0x001 /* only valid as the first unit of a packet */')
A('#define USIP_F_NOTSLOT0 0x002 /* never the first unit of a packet */')
A('#define USIP_F_CTX 0x004 /* context rule: never matched directly (bz.s P4/P5, the R7 slot forms, the hhtt / b111 / f0ctx groups) */')
A('#define USIP_F_DELAY 0x008 /* rest of the packet is a delay slot when the instruction does not close it */')
A('#define USIP_F_ADJ1 0x010 /* tail must be the next unit */')
A('#define USIP_F_ADJCTRL 0x020 /* tail at +1 (S=1) or +2 (S=0) */')
A('#define USIP_F_NAMED 0x040 /* operation known (not format-only) */')
A('#define USIP_F_NOTAIL 0x080 /* 16-bit op that never takes a tail (ret/save/restore) */')
A("#define USIP_F_ADJ2 0x100 /* control tail with F' = 0 EXACTLY at +2 (the unit in between is the delay slot); any other distance = .word (loop# with F = 1 at +1, and a ld/st entry: two 16-bit ops) */")
A('#define USIP_F_UNKNOWN16 0x200 /* 16-bit shape that does not count as a named 16-bit op (loose syllables, .hword, stand-alone closers) */')
A('#define USIP_F_ADJDEF 0x400 /* the tail must be at +2 or FARTHER; an adjacent tail undoes the pair into two 16-bit ops */')
A('#define USIP_F_PREFIX 0x800 /* packet-prefix entry (48/64-bit b<cond>.l): matched on the first units of the packet BEFORE the layout, with its target guard; a failing guard = not taken (never .word); the rest of the packet is its delay slot */')
A('#define USIP_F_RENAME 0x1000 /* post-layout rename: never used by the packet parser; once the layout is decided, an instruction whose reading is one of rename_from (mnemonic or @kind) and whose units match the entry and its constraints takes its name */')
A('#define USIP_F_HHTT 0x2000 /* context entry admitted only inside the H H T T candidate of a 4-unit packet */')
A('#define USIP_F_GUARDFT 0x4000 /* a failing target guard falls through to the next entry of the cell (no entry left: two 16-bit ops), never .word */')
A('#define USIP_F_B111 0x8000 /* context entry (ld.bu / st.b with a named 16-bit tail) admitted in the 2-unit head + tail packet and inside the H H T T candidate */')
A("#define USIP_F_F0CTX 0x10000 /* context entry tried by the decoder when an op 0 head with its F' = 0 tail at +2 reads .word (never by the pairing) */")
A('#define USIP_F_FUSE 0x20000 /* post-layout FUSION: two ADJACENT one-unit instructions whose readings are the two rename_from mnemonics (head, tail) and whose units match the entry (+ its constraints) become ONE two-unit instruction of this entry; never used by the parser nor by the generic rename pass; with USIP_F_ADJ2 the fusion is at DISTANCE 2 (a bare head at i and a bare unnamed closer at i + 2) */')
A('#define USIP_F_SPLIT 0x40000 /* entry of the post-layout split procedure of the slot family (the decoder applies it in its split pass, by USIP_FORM_* index; never the parser nor the generic rename pass); USIP_F_SPLITBARE marks the ones applied to bare units before the pass */')
A('#define USIP_F_SPLITBARE 0x80000')
A('')
A('#define USIP_BITS_ZERO(n) { 0xff, (n) } /* n implicit zero bits in the operand value */')
A('#define USIP_MAXRANGES %d' % MAXRANGES)
A('#define USIP_MAXFIELDS %d' % MAXFIELDS)
A('#define USIP_MAXCONS %d' % MAXCONS)
A('#define USIP_MAXVALS %d' % MAXVALS)
A('#define USIP_NOFIELD 0xff')
A('')
A('/* USIP_C_BR0_CELLS / USIP_C_BR0_CELLS_LIVE: vals[0] = mask of the admitted (B class, tail distance) cells of the F = 0 branch head:')
A(' * bit 2 * class + distance - 1, class 0 = B == 0, 1 = B == 15, 2 = any other B, distance 1 / 2 (the tail at +1 or +2) */')
A('#define USIP_BR0_BIT(cls, dist) (1 << (2 * (cls) + (dist) - 1))')
A('')
pkx = isa['structure']['packet']
A('/* packet execution model (structure.packet.execution): the instructions of a packet execute in PARALLEL and every read sees')
A(' * the state BEFORE the packet (an emulator reads all the operands of the packet before any write; two writes of the same')
A(' * register in one packet are an impossible layout) */')
A('#define USIP_PACKET_EXEC_PARALLEL %d' % (1 if 'execution' in pkx else 0))
lq = pkx.get('loop_imm_q', {})
A('/* the loop #N? rule (structure.packet.loop_imm_q): a loop #N reading with N == USIP_LOOPQ_N_EQ or whose tail displacement d12')
A(' * is one of usip_loopq_d12[] prints loop #N? (mnemonic loop#?); the layout candidate loopcand does not admit it */')
A('#define USIP_LOOPQ_N_EQ %d' % lq.get('N_eq', 0))
A('#define USIP_LOOPQ_ENABLED %d' % (1 if lq else 0))
A('static const ut16 usip_loopq_d12[%d] = { %s };' % (max(1, len(lq.get('last_d12_in', []))), ', '.join('0x%04x' % int(x, 16) for x in lq.get('last_d12_in', [])) or '0'))
A('')
A('typedef struct { ut8 hi, lo; } UsipBits;')
A('typedef struct {')
A('\tconst char *name; /* operand name in usip_isa.json */')
A('\tut8 type; /* enum usip_arg_type */')
A('\tut8 nranges;')
A('\tUsipBits r[USIP_MAXRANGES]; /* MSB first, concatenated */')
A('\tut8 nbits; /* rotimm/urot: rotation width; 0 = total bits */')
A('\tut8 scale; /* imm/simm/disp/udisp: multiplier (2 = halfword units, 4 = words); offh/offw/offb/loopn/movhi have their own formula */')
A('\tut32 base; /* absaddr: OR-ed segment base; disp: pc offset of the target; reg/imm/udisp/...: constant ADDED to the field value */')
A('} UsipField;')
A('')
A('typedef struct {')
A('\tut8 op; /* enum usip_cons_op */')
A('\tut8 field, field2; /* indices into UsipOpcode.fields (USIP_NOFIELD when unused) */')
A('\tut8 nvals;')
A('\tst32 vals[USIP_MAXVALS]; /* eq/ne/lt/ge/gt: [v]; in/notin: set; inmask/notinmask: [lo32, hi32] of a 64-bit membership mask;')
A('\t * range: [lo, hi); ranges: pairs; slot0_excludes_head: [mask, value]; packet_len(_ge) / head_index(_ge) / instr_index: [n];')
A('\t * ctx: [enum usip_ctx_cond]; any: [field, op, value] triplets (enum usip_any_op; field = USIP_NOFIELD when the')
A('\t * sub-operation has none); guard_setcc_tail: the small set; tail_adjacent: [0 | 1]; ieee_short: [significant digits];')
A('\t * in_c48tab: none; br0_cells(_live): [cell mask] with field = rB; bf_valid: none (field = w, field2 = pos);')
A('\t * in_set / notin_set: [index into usip_value_sets] (the sets too large for vals) */')
A('} UsipCons;')
A('')
A('typedef struct usip_opcode_t {')
A('\tconst char *name; /* mnemonic (format-only forms start with "." or end with "?") */')
A('\tconst char *form; /* stable form id (usip_isa.json) */')
A('\tut64 value; /* over the concatenated units, bit width-1 = head bit 15 */')
A('\tut64 mask;')
A('\tut8 width; /* 16, 32, 48, 64 (the 64-bit packet-prefix entries) */')
A('\tut8 nunits;')
A('\tut32 flags; /* USIP_F_* */')
A('\tut8 kind; /* enum usip_kind */')
A('\tut8 conf; /* enum usip_conf */')
A('\tconst char *fmt; /* operand template: {N} = fields[N], {N:text} = text when flag fields[N] is 1 */')
A('\tut8 nargs; /* fields used by fmt */')
A('\tut8 nfields; /* nargs + fields referenced only by constraints */')
A('\tUsipField fields[USIP_MAXFIELDS];')
A('\tut8 ncons;')
A('\tUsipCons cons[USIP_MAXCONS];')
A('\tconst char *const *rename_from; /* USIP_F_RENAME: the readings the entry may replace (mnemonics, or "@kind") */')
A('\tut8 nrename_from;')
A('} UsipOpcode;')
A('')
A('#ifndef USIP_OPCODES_NO_TABLE /* define it to get the types and enums only */')
A('/* The table is ORDERED: the first entry of the right width whose (units & mask) == value, whose flags allow the slot')
A(' * and whose constraints hold is the reading. The target guards (USIP_C_GUARD_TARGET_*) are evaluated by the decoder')
A(' * AFTER the match: for 32-bit entries a failing guard makes the pair .word (no fall-through) unless the entry carries')
A(' * USIP_F_GUARDFT (then the next entry of the cell is tried; none left = two 16-bit ops), for 16-bit entries it falls')
A(' * through to the next entry; for the USIP_F_PREFIX entries a failing guard means the entry is not taken. The')
A(' * position / length constraints (USIP_C_PACKET_LEN(_GE), USIP_C_HEAD_INDEX(_GE), USIP_C_CTX, USIP_C_BR0_CELLS) need the')
A(' * packet context (unknown = the constraint fails, except the context conditions and the R-br0 cells, which hold when the')
A(' * layout is unknown) and, on the 32/48-bit entries, a failure falls through to the next entry of the cell;')
A(' * USIP_C_GUARD_HEAD_NOT_VALIDATED_BS undoes the pair into two 16-bit ops. USIP_C_TAIL_ADJACENT, USIP_C_IEEE_SHORT, USIP_C_BF_VALID,')
A(' * USIP_C_INSTR_INDEX, the context conditions and USIP_C_IN_C48TAB are evaluated by the decoder (see usip_decode.c). */')
for f in forms:
if f.get('rename_only'):
A('static const char *const usip_rename_from_%s[] = { %s };' % (cident(f['name']), ', '.join(cstr(x) for x in f.get('rename_from', []))))
T = []
A = T.append
A('static const UsipOpcode usip_opcodes[] = {')
ctx_index = {}
for idx, f in enumerate(forms):
fl = []
if f.get('slot0_only'): fl.append('USIP_F_SLOT0')
if f.get('not_slot0'): fl.append('USIP_F_NOTSLOT0')
if f.get('context_only'):
fl.append('USIP_F_CTX'); ctx_index[f['name']] = idx
if f.get('delay_slot'): fl.append('USIP_F_DELAY')
if f.get('tail_adjacency') == 1: fl.append('USIP_F_ADJ1')
if f.get('tail_adjacency') == 'ctrl': fl.append('USIP_F_ADJCTRL')
if f.get('named'): fl.append('USIP_F_NAMED')
if f['width'] == 16 and 'never takes a tail' in f.get('packet_rules', ''): fl.append('USIP_F_NOTAIL')
if f.get('tail_adjacency') == 2: fl.append('USIP_F_ADJ2')
if not f.get('known16', True): fl.append('USIP_F_UNKNOWN16')
if f.get('tail_adjacency') == 'defer': fl.append('USIP_F_ADJDEF')
if f.get('packet_prefix'): fl.append('USIP_F_PREFIX')
if f.get('rename_only'): fl.append('USIP_F_RENAME')
if f.get('group') == 'hhtt': fl.append('USIP_F_HHTT')
if f.get('guard_failure') == 'fallthrough': fl.append('USIP_F_GUARDFT')
if f.get('group') == 'b111': fl.append('USIP_F_B111')
if f.get('group') == 'f0ctx': fl.append('USIP_F_F0CTX')
if f.get('fuse_pair'):
fl.append('USIP_F_FUSE'); ctx_index[f['name']] = idx
if f.get('split_role'):
fl.append('USIP_F_SPLIT'); ctx_index[f['name']] = idx
if f['split_role'] == 'bare': fl.append('USIP_F_SPLITBARE')
elif f['split_role'] != 'either': raise SystemExit('%s: unknown split_role %r' % (f['name'], f['split_role']))
flags = ' | '.join(fl) if fl else '0'
tmpl, nargs, fields = exported_fields(f)
if len(fields) > MAXFIELDS:
raise SystemExit('%s: too many fields' % f['name'])
fidx = {x['name']: i for i, x in enumerate(fields)}
cons = [cons_entries(c, fidx, f['width'], bool(f.get('rename_only')), bool(f.get('context_only'))) for c in f.get('constraints', [])]
if len(cons) > MAXCONS:
raise SystemExit('%s: too many constraints' % f['name'])
n = f['width'] // 16
A('\t/* %s: %s */' % (f['name'], f.get('semantics', '')[:110].replace('*/', '* /')))
A('\t{ %s, %s, 0x%0*xULL, 0x%0*xULL, %d, %d,' % (cstr(f['mnemonic']), cstr(f['name']), 4 * n, int(f['match']['value'], 16),
4 * n, int(f['match']['mask'], 16), f['width'], n))
A('\t %s, USIP_K_%s, %s, %s, %d, %d, {' % (flags, f['kind'].upper().replace('.', '_'), CONF[f['confidence']], cstr(tmpl), nargs, len(fields)))
for a in fields:
rs = []
for r in a['bits']:
rs.append('USIP_BITS_ZERO(%d)' % r[1] if r[0] == 'z' else '{%d, %d}' % (r[0], r[1]))
if len(rs) > MAXRANGES:
raise SystemExit('%s/%s: too many ranges' % (f['name'], a['name']))
A('\t\t{ %s, USIP_A_%s, %d, { %s }, %d, %d, %s },' % (cstr(a['name']), a['type'].upper(), len(rs), ', '.join(rs), a.get('nbits', 0), a.get('scale', 1), field_base(a)))
rn = ('usip_rename_from_%s, %d' % (cident(f['name']), len(f.get('rename_from', [])))) if f.get('rename_only') else 'NULL, 0'
if cons:
A('\t }, %d, {' % len(cons))
for op, fi, fi2, vals in cons:
if len(vals) > MAXVALS:
raise SystemExit('%s: too many constraint values' % f['name'])
A('\t\t{ USIP_C_%s, %s, %s, %d, { %s } },' % (op.upper(), fi if fi != 0xff else 'USIP_NOFIELD', fi2 if fi2 != 0xff else 'USIP_NOFIELD',
len(vals), ', '.join(str(v) for v in vals) if vals else '0'))
A('\t }, %s },' % rn)
else:
A('\t }, 0, { { 0, 0, 0, 0, { 0 } } }, %s },' % rn)
A('};')
A = L.append
A('/* the large value sets of USIP_C_IN_SET / USIP_C_NOTIN_SET (sorted), by index */')
for i, vals in enumerate(SETS):
A('static const st32 usip_set_%d[%d] = { %s };' % (i, len(vals), ', '.join(str(v) for v in vals)))
A('typedef struct { const st32 *v; ut16 n; } UsipValueSet;')
A('static const UsipValueSet usip_value_sets[%d] = { %s };' % (max(1, len(SETS)), ', '.join('{ usip_set_%d, %d }' % (i, len(v)) for i, v in enumerate(SETS)) or '{ NULL, 0 }'))
L.extend(T)
A('')
A('/* kind names, in enum usip_kind order */')
A('static const char *const usip_kind_names[] = { ' + ', '.join(cstr(k) for k in kinds) + ' };')
A('')
A('/* context-only forms (matched by the packet decoder only), the post-layout split entries and the fusion entries */')
for nm, idx in sorted(ctx_index.items()):
A('#define USIP_FORM_%s %d' % (cident(nm).upper(), idx))
A('')
A('/* condition codes by (op nibble, tail bits 13:12) */')
A('static const char *const usip_cond_imm8[3][4] = { /* op 9 */ {"ne", "eq", "le", "lt"}, /* op 8 */ {"ne.u8", "eq.u8", "leu", "ltu"}, /* op a */ {"geu", "gtu", "ge", "gt"} };')
A('static const char *const usip_cond_reg[2][4] = { /* op d */ {"ne", "eq", "le", "lt"}, /* op c: t2 0/1 = zero forms */ {"nez", "eqz", "leu", "ltu"} };')
A("/* 48/64-bit b<cond>.l (the USIP_F_PREFIX entries) by (op nibble of the head - 0xb, bits 13:12 of the second unit); NULL = condition open (bcc48?).")
A(" * op b = register-register, op c = unsigned immediate, op d-f = signed immediate */")
bcc = isa['structure'].get('conditions', {}).get('bcc48', {})
rows = []
for op in ('op b', 'op c', 'op d', 'op e', 'op f'):
cells = bcc.get(op, {})
rows.append('{ ' + ', '.join((cstr(cells[k].split(' ')[0].replace('.l', '')) if k in cells else 'NULL') for k in ('00', '01', '10', '11')) + ' }')
A('static const char *const usip_cond_bcc48[5][4] = { ' + ', '.join(rows) + ' };')
A('')
regs = isa['structure']['registers']
csr = regs['csr_classic']
A('/* classic CSR names (the md32 csr field type; no uSIP form uses it) */')
A('static const char *const usip_csr_classic[16] = { ' + ', '.join(cstr(x) for x in csr) + ' };')
names = regs.get('csr_extended_names', {})
A('/* extended CSR names (mfcsr/mtcsr number): index = CSR number 0x00-0x3f, NULL = unnamed */')
A('static const char *const usip_csr_ext[64] = { ' + ', '.join(cstr(names['0x%02x' % i].split(' ')[0]) if '0x%02x' % i in names else 'NULL' for i in range(64)) + ' };')
A('static const ut32 usip_seg[8] = { 0x00000000, 0x04000000, 0x08000000, 0x0c000000, 0x10000000, 0x14000000, 0x18000000, 0x1c000000 }; /* SEG[A] = A << 26; only A = 0, 6, 7 observed */')
A('')
A('/* save/restore register sets: index = w (w = 2 is four words, w = 4 two) */')
A('static const char *const usip_save_set[8] = { ' + ', '.join(cstr(x) if x else 'NULL' for x in SAVE_SET) + ' };')
A('')
sysops = isa['structure'].get('sys_ops', {}).get('fld_4', {})
A('/* names of the `sys` shape [0][1][00][rrr][x][B][1111] + d000 (tail field fld = 4) by B; NULL = plain sys #n.')
A(' * fld 0: B = 15 ret / jr rB; fld 1: call rB; fld 2: ret? (B = 15); the F = S = 0 twins (tail 5000) use the same names */')
A('static const char *const usip_sys_fld4[16] = { ' + ', '.join(cstr(sysops[str(i)].split(' ')[0]) if str(i) in sysops else 'NULL' for i in range(16)) + ' };')
A('')
A('/* heads that must take a tail (structure.packet.must_pair): mask, value, allowed tail counts (bit n = n tails) */')
A('typedef struct { ut16 mask, value; ut8 tails; } UsipMustPair;')
A('static const UsipMustPair usip_must_pair[] = {')
for d in isa['structure']['packet'].get('must_pair', []):
bits = 0
for t in d['tails']:
bits |= 1 << t
A('\t{ 0x%04x, 0x%04x, 0x%x }, /* %s */' % (int(d['mask'], 16), int(d['value'], 16), bits, d.get('why', '')))
A('};')
A('')
pk = isa['structure']['packet']
A('/* units that are never a tail, and heads that never take a tail (the grammar rejects the assignment) */')
A('static const ut16 usip_never_tail[] = { ' + ', '.join('0x%04x' % int(x, 16) for x in pk.get('never_tail', [])) + ' };')
A('static const ut16 usip_never_head_with_tail[] = { ' + ', '.join('0x%04x' % int(x, 16) for x in pk.get('never_head_with_tail', [])) + ' };')
A('')
A('/* the 48-bit constants admitted by value (USIP_C_IN_C48TAB: imm32 = head[13:9] << 27 | hi[12:0] << 14 | lo[13:0]); the head:hi partial')
A(' * admission of the parser compares the upper 18 bits */')
A('static const ut32 usip_c48_table[%d] = { ' % len(C48) + ', '.join('0x%08x' % v for v in C48) + ' };')
A('')
br0 = pk.get('br0_cells', {})
A('/* R-br0: the (op nibble of the head, tail bits 13:12, B class, tail distance) cells of the F = 0 branch heads read as b<cond>.d')
A(' * (the entries *_f0s0 and the F = S = 0 .d entries carry them as USIP_C_BR0_CELLS); class 0 = B == 0, 1 = B == 15, 2 = any other B.')
A(' * The weak cells hold only at the origins of a live trace (USIP_C_BR0_CELLS_LIVE): the decoder never takes them. */')
A('typedef struct { ut8 op, tt, bclass, dist; } UsipBr0Cell;')
strict = br0_rows(br0.get('strict', [])); weak = br0_rows(br0.get('weak_live_only', []))
A('static const UsipBr0Cell usip_br0_cells[%d] = { ' % len(strict) + ', '.join(strict) + ' };')
A('static const UsipBr0Cell usip_br0_cells_weak[%d] = { ' % len(weak) + ', '.join(weak) + ' };')
A('')
sym = sorted(set(pk.get('c48sym_known_addresses', [])))
A('/* R-c48sym: data addresses already formed by the 48-bit hi(1010) family (sorted); a reading that leaves a family head without tails')
A(' * while a later free hi + lo of the packet encode one of them pays +0.5 (structure.packet.tie_breaks) */')
A('static const ut32 usip_c48sym_table[%d] = {' % len(sym))
for i in range(0, len(sym), 8):
A('\t' + ', '.join('0x%08x' % v for v in sym[i:i + 8]) + ',')
A('};')
A('#endif /* USIP_OPCODES_NO_TABLE */')
A('')
A('#endif /* USIP_OPCODES_H */')
with open(out_path, 'w') as fh:
fh.write('\n'.join(L) + '\n')
print('%s: %d entries; by confidence %s' % (out_path, len(forms), dict(collections.Counter(f['confidence'] for f in forms))))
def main():
args = sys.argv[1:]
out = os.path.join(ROOT, 'usip_opcodes.h')
if '-o' in args:
out = args[args.index('-o') + 1]
del args[args.index('-o'):args.index('-o') + 2]
src = args[0] if args else os.path.join(ROOT, 'isa', 'usip_isa.json')
with open(src) as fh:
emit(json.load(fh), out)
if __name__ == '__main__':
main()
+463
View File
@@ -0,0 +1,463 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - pancake */
#include <r_anal.h>
#include <errno.h>
#define R2_PLUGIN_INCORE 1
#include "../arch_md32.c"
#define MAX_MAPS 32
#define MAX_BYTES (64 * 1024 * 1024)
typedef struct {
ut64 base;
size_t size;
ut8 *bytes;
ut8 *initialized;
bool code;
const char *name;
} Map;
typedef struct {
Map maps[MAX_MAPS];
unsigned nmaps;
RReg *reg;
ut8 reg_initialized[256];
const char *reason;
ut64 fault;
char fault_reg[32];
PJ *events;
REsil *esil;
} Machine;
static Map *find_map(Machine *m, ut64 addr, int len, bool code) {
unsigned i;
for (i = 0; i < m->nmaps; i++) {
Map *map = &m->maps[i];
if (map->code == code && len > 0 && addr >= map->base
&& addr - map->base < map->size && (ut64)len <= map->size - (addr - map->base)) {
return map;
}
}
return NULL;
}
static void event(Machine *m, const char *kind, ut64 addr, int len) {
pj_o (m->events);
pj_ks (m->events, "access", kind);
pj_kn (m->events, "address", addr);
pj_ki (m->events, "size", len);
pj_end (m->events);
}
// Backing reads also serve ESIL's internal pre-store flag bookkeeping.
// Those reads are not firmware reads and must not report uninitialized RAM.
static bool read_memory(void *user, ut64 addr, ut8 *buf, int len) {
Machine *m = user;
Map *map = find_map (m, addr, len, false);
if (!map) {
return false;
}
memcpy (buf, map->bytes + addr - map->base, len);
return true;
}
static bool check_memory(REsil *esil, ut64 addr, ut8 *buf, int len) {
Machine *m = esil->cb.user;
Map *map = find_map (m, addr, len, false);
event (m, "read", addr, len);
if (!map) {
m->reason = "unmapped_data_read";
m->fault = addr;
} else {
const size_t off = addr - map->base;
int i;
for (i = 0; i < len; i++) {
if (!map->initialized[off + i]) {
m->reason = "uninitialized_data_read";
m->fault = addr + i;
break;
}
}
}
if (!m->reason) {
return false;
}
r_esil_set_fault (esil, R_ANAL_TRAP_READ_ERR, m->fault);
esil->parse_stop = 1;
memset (buf, 0, len);
return true;
}
static bool write_memory(void *user, ut64 addr, const ut8 *buf, int len) {
Machine *m = user;
Map *map = find_map (m, addr, len, false);
event (m, "write", addr, len);
if (!map) {
m->reason = "unmapped_data_write";
m->fault = addr;
return false;
}
const size_t off = addr - map->base;
memcpy (map->bytes + off, buf, len);
memset (map->initialized + off, 1, len);
return true;
}
static bool reg_range(Machine *m, const char *name, size_t *off, size_t *size) {
RRegItem *ri = r_reg_get (m->reg, name, -1);
if (!ri || ri->offset < 0 || ri->size <= 0 || ri->offset % 8 || ri->size % 8) {
return false;
}
*off = ri->offset / 8;
*size = ri->size / 8;
return *off < sizeof (m->reg_initialized) && *size <= sizeof (m->reg_initialized) - *off;
}
// Only semantic reads go through this hook. ESIL's silent read of a write
// destination's old value is bookkeeping, not a firmware use of that value.
static bool check_register(REsil *esil, const char *name, ut64 *value, int *bits) {
Machine *m = esil->cb.user;
size_t off, size;
if (!reg_range (m, name, &off, &size)) {
return false;
}
size_t i;
for (i = 0; i < size; i++) {
if (!m->reg_initialized[off + i]) {
m->reason = "uninitialized_register_read";
r_str_ncpy (m->fault_reg, name, sizeof (m->fault_reg));
esil->parse_stop = 1;
*value = 0;
if (bits) {
*bits = size * 8;
}
return true;
}
}
return false;
}
static bool mark_register(REsil *esil, const char *name, ut64 *value) {
Machine *m = esil->cb.user;
size_t off, size;
if (reg_range (m, name, &off, &size)) {
memset (m->reg_initialized + off, 1, size);
}
return false;
}
static bool set_register(Machine *m, const char *name, ut64 value) {
size_t off, size;
if (!reg_range (m, name, &off, &size) || !r_reg_setv (m->reg, name, value)) {
return false;
}
memset (m->reg_initialized + off, 1, size);
return true;
}
static bool number(const char *str, ut64 *value) {
char *end;
if (!str || !*str || *str == '-') {
return false;
}
errno = 0;
*value = strtoull (str, &end, 0);
return !errno && !*end;
}
static bool add_map(Machine *m, const char *arg, bool code, bool zero, bool file) {
if (m->nmaps == MAX_MAPS) {
return false;
}
char *left = strdup (arg);
char *sep = strrchr (left, file? '@': ':');
ut64 base, size;
if (!sep) {
free (left);
return false;
}
*sep++ = 0;
Map map = { .code = code, .name = arg };
if (file) {
if (!number (sep, &base)) {
free (left);
return false;
}
const ut64 disk_size = r_file_size (left);
if (!disk_size || disk_size > MAX_BYTES) {
free (left);
return false;
}
size_t bytes = 0;
map.bytes = (ut8 *)r_file_slurp (left, &bytes);
size = bytes;
} else {
if (!number (left, &base) || !number (sep, &size)) {
free (left);
return false;
}
}
free (left);
if (!size || size > MAX_BYTES || base > UT32_MAX || size > (1ULL << 32) - base) {
free (map.bytes);
return false;
}
map.base = base;
map.size = size;
unsigned i;
for (i = 0; i < m->nmaps; i++) {
Map *old = &m->maps[i];
if (old->code == code && base < old->base + old->size && old->base < base + size) {
free (map.bytes);
return false;
}
}
if (!map.bytes) {
if (file) {
return false;
}
map.bytes = calloc (size, 1);
}
map.initialized = malloc (size);
if (!map.bytes || !map.initialized) {
free (map.bytes);
free (map.initialized);
return false;
}
memset (map.initialized, file || zero, size);
m->maps[m->nmaps++] = map;
return true;
}
static void usage(void) {
fprintf (stderr, "Usage: md32-run [--cpu md32|usip] --image FILE@BASE [--image ...]\n"
" --entry ADDRESS | --vector (execute vector-0 instruction at first image base)\n"
" --data FILE@BASE --ram BASE:SIZE --zero BASE:SIZE\n"
" --reg NAME=VALUE --steps COUNT (default 100)\n"
"Code and data maps are separate. RAM/registers start uninitialized.\n"
"JSON trace stops on missing semantics or external/uninitialized state.\n");
}
int main(int argc, char **argv) {
Machine m = { 0 };
const char *cpu = "md32";
ut64 entry = UT64_MAX, steps = 100;
bool vector = false;
int rc = 1, i;
RAnal *anal = NULL;
PJ *output = pj_new ();
m.events = pj_new ();
if (!output || !m.events) {
goto cleanup;
}
for (i = 1; i < argc; i++) {
const char *opt = argv[i];
if (!strcmp (opt, "--vector")) {
vector = true;
continue;
}
if (i + 1 == argc) {
goto invalid;
}
const char *arg = argv[++i];
if (!strcmp (opt, "--cpu")) {
cpu = arg;
if (strcmp (cpu, "md32") && strcmp (cpu, "ms15e30") && strcmp (cpu, "usip")) {
goto invalid;
}
} else if (!strcmp (opt, "--image") || !strcmp (opt, "--data")) {
if (!add_map (&m, arg, !strcmp (opt, "--image"), false, true)) {
goto invalid;
}
} else if (!strcmp (opt, "--ram") || !strcmp (opt, "--zero")) {
if (!add_map (&m, arg, false, !strcmp (opt, "--zero"), false)) {
goto invalid;
}
} else if (!strcmp (opt, "--entry")) {
if (!number (arg, &entry) || entry > UT32_MAX) {
goto invalid;
}
} else if (!strcmp (opt, "--steps")) {
if (!number (arg, &steps) || !steps || steps > 1000000) {
goto invalid;
}
} else if (strcmp (opt, "--reg")) {
goto invalid;
}
}
Map *first = NULL;
for (i = 0; i < (int)m.nmaps; i++) {
if (m.maps[i].code) {
first = &m.maps[i];
break;
}
}
if (!first || (vector && entry != UT64_MAX)) {
goto invalid;
}
if (vector) {
if (first->size < 4) {
goto invalid;
}
// These firmware vectors contain instructions, not address pointers.
entry = first->base;
} else if (entry == UT64_MAX) {
entry = first->base;
}
anal = r_anal_new ();
if (!anal) {
goto cleanup;
}
anal->config->bits = 32;
anal->config->endian = R_SYS_ENDIAN_BIG;
RArchConfig cfg = { .bits = 32, .endian = R_SYS_ENDIAN_BIG, .cpu = (char *)cpu };
RArchSession session = { .config = &cfg };
m.reg = anal->reg;
char *profile = regs (&session);
bool ok = r_reg_set_profile_string (m.reg, profile);
free (profile);
if (!ok || !set_register (&m, "pc", entry)) {
goto cleanup;
}
for (i = 1; i + 1 < argc; i++) {
if (strcmp (argv[i], "--reg")) {
continue;
}
char *arg = strdup (argv[++i]);
char *equal = strchr (arg, '=');
ut64 value;
ok = false;
if (equal) {
*equal++ = 0;
ok = number (equal, &value) && set_register (&m, arg, value);
}
free (arg);
if (!ok) {
goto invalid;
}
}
REsilOptions opts = r_esil_options (m.reg, NULL);
opts.addrsize = 32;
opts.iotrap = true;
opts.ifaces.mem = (REsilMemInterface) { .user = &m, .mem_read = read_memory, .mem_write = write_memory };
m.esil = r_esil_new (&opts);
if (!m.esil) {
goto cleanup;
}
m.esil->anal = anal;
m.esil->cb.user = &m;
m.esil->cb.hook_mem_read = check_memory;
m.esil->cb.hook_reg_read = check_register;
m.esil->cb.hook_reg_write = mark_register;
pj_a (m.events);
pj_o (output);
pj_ks (output, "cpu", cpu);
pj_kn (output, "entry", entry);
pj_ks (output, "entry_source", vector? "vector0_instruction": "explicit_or_image_base");
pj_ka (output, "trace");
ut64 completed = 0, stopped_pc = entry;
bool partial = false;
while (completed < steps) {
const ut64 pc = r_reg_getv (m.reg, "pc");
stopped_pc = pc;
if (pc & 1) {
m.reason = "unaligned_instruction_fetch";
m.fault = pc;
break;
}
Map *map = find_map (&m, pc, 2, true);
if (!map) {
m.reason = "unmapped_instruction_fetch";
m.fault = pc;
break;
}
const size_t off = pc - map->base;
RAnalOp op;
r_anal_op_init (&op);
op.addr = pc;
op.bytes = map->bytes + off;
op.weakbytes = true;
op.size = R_MIN (64, map->size - off);
ok = decode (&session, &op, R_ARCH_OP_MASK_ALL);
pj_o (output);
pj_kn (output, "pc", pc);
pj_ki (output, "size", op.size);
pj_ks (output, "instruction", op.mnemonic? op.mnemonic: "invalid");
pj_ks (output, "esil", r_strbuf_get (&op.esil));
pj_end (output);
if (!ok) {
m.reason = "invalid_or_truncated_instruction";
} else if (op.delay) {
m.reason = "unsupported_delay_slot_execution";
} else if (!*r_strbuf_get (&op.esil) && op.type != R_ANAL_OP_TYPE_NOP) {
m.reason = "unsupported_instruction_semantics";
}
if (m.reason) {
r_anal_op_fini (&op);
break;
}
r_esil_set_pc (m.esil, pc);
set_register (&m, "pc", (ut32)(pc + op.size));
ok = op.type == R_ANAL_OP_TYPE_NOP || r_esil_parse (m.esil, r_strbuf_get (&op.esil));
const bool self_loop = op.type == R_ANAL_OP_TYPE_JMP && op.jump == pc;
r_esil_stack_free (m.esil);
r_anal_op_fini (&op);
if (!ok || m.reason || m.esil->trap) {
partial = true;
if (!m.reason) {
m.reason = "esil_execution_fault";
}
break;
}
completed++;
if (self_loop) {
m.reason = "unconditional_self_loop";
break;
}
}
pj_end (output);
pj_kn (output, "completed_steps", completed);
pj_kn (output, "last_attempt_pc", stopped_pc);
pj_kb (output, "partial_instruction_state", partial);
pj_kn (output, "pc", r_reg_getv (m.reg, "pc"));
pj_ks (output, "stop_reason", m.reason? m.reason: "step_limit");
if (m.reason && (strstr (m.reason, "data_") || strstr (m.reason, "fetch"))) {
pj_kn (output, "fault_address", m.fault);
}
if (*m.fault_reg) {
pj_ks (output, "fault_register", m.fault_reg);
}
pj_ko (output, "initialized_registers");
RListIter *it;
RRegItem *ri;
r_list_foreach (r_reg_get_list (m.reg, R_REG_TYPE_GPR), it, ri) {
size_t off, size, j;
if (!reg_range (&m, ri->name, &off, &size)) {
continue;
}
for (j = 0; j < size && m.reg_initialized[off + j]; j++) {
}
if (j == size) {
pj_kn (output, ri->name, r_reg_get_value (m.reg, ri));
}
}
pj_end (output);
pj_end (m.events);
pj_k (output, "memory_accesses");
pj_j (output, pj_string (m.events));
pj_end (output);
puts (pj_string (output));
rc = 0;
goto cleanup;
invalid:
usage ();
cleanup:
r_esil_free (m.esil);
r_anal_free (anal);
pj_free (output);
pj_free (m.events);
for (i = 0; i < (int)m.nmaps; i++) {
free (m.maps[i].bytes);
free (m.maps[i].initialized);
}
return rc;
}
+9
View File
@@ -0,0 +1,9 @@
# SCQ16: the data-space bases as flags. Load it in a session on the program memory image: `. tools/scq16_flags.r2`.
# The data memory is LITTLE-endian (the instruction stream is big-endian): read its words with `e cfg.bigendian=false` or `pxw`
# on a little-endian view. `ior`/`iow` address the I/O space by port index (base + offset), not by byte. The flag sizes are
# the windows the bases span (the main DM up to the I/O window), not measured extents.
fs scq16
f scq16.dm_main 0x8000 @ 0x0
f scq16.io_window 0x2000 @ 0x8000
f scq16.dm2 0x20000 @ 0x40000
fs *
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""Convert little-endian 32-bit firmware storage to the MD32 instruction stream.
This reverses each storage word, not each variable-length instruction. It is
for older VPU/MD32 firmware blobs; do not apply it to the supplied uSIP files.
"""
import argparse
from pathlib import Path
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('input', type=Path)
p.add_argument('output', type=Path)
args = p.parse_args()
data = args.input.read_bytes()
if len(data) % 4:
p.error('input size must be a multiple of four bytes')
if args.input.resolve() == args.output.resolve():
p.error('use a separate output file')
args.output.write_bytes(b''.join(data[i:i + 4][::-1] for i in range(0, len(data), 4)))
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Emit r2 symbol flags from the supplied uSIP function inventory.
This needs only the supplied r2pkg bundle, not the original private dsp.bin or
DbgInfo files. Addresses in the sec0 inventory are relative to 0x400e0000.
"""
import argparse
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INVENTORY_GLOBS = ('third_party/*/docs/*usip*dump*.md', 'third_party/*/docs/*dump*.md')
PATTERN = re.compile(r'^\| `([0-9a-f]+)`-`([0-9a-f]+)` \| (\d+) \| .*? \| `([^`]+)` \|')
def default_inventory():
"""Find the supplied function inventory instead of hardcoding a bundle path."""
for pattern in INVENTORY_GLOBS:
found = sorted(ROOT.glob(pattern))
if found:
return found[0]
return None
def functions(path=None):
path = path or default_inventory()
if path is None:
raise SystemExit('no function inventory found; pass --inventory PATH')
for line in Path(path).read_text().splitlines():
m = PATTERN.match(line)
if m:
start, end, size, name = m.groups()
start, end, size = int(start, 16), int(end, 16), int(size)
assert end - start == size
yield start, end, name
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--section', choices=['sec0', 'ddr', 'all'], default='all')
p.add_argument('--inventory', type=Path, default=None,
help='function inventory Markdown (default: auto-detected)')
p.add_argument('--sec0-base', type=lambda s: int(s, 0), default=0x400e0000,
help='load alias added to the relative sec0 addresses')
args = p.parse_args()
for start, end, name in functions(args.inventory):
section = 'sec0' if start < 0x100000 else 'ddr'
if args.section not in (section, 'all'):
continue
addr = start + args.sec0_base if section == 'sec0' else start
name = re.sub(r'[^a-zA-Z0-9_.$]', '_', name)
print('f sym.%s %d @ 0x%08x' % (name, end - start, addr))
if __name__ == '__main__':
main()
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""Run the vendor disassembler's x86-64 code under Unicorn, on any host.
Optional verification tool: pip install unicorn. See doc/research.md for the
public objdump download and checksum. This does not execute the vendor program
as a host process; only its disassembly function and operand helpers run.
"""
import json
import re
import struct
import subprocess
from extract_opcodes import Elf
from unicorn import Uc, UC_ARCH_X86, UC_MODE_64, UC_HOOK_CODE
from unicorn.x86_const import (UC_X86_REG_RAX, UC_X86_REG_RDI, UC_X86_REG_RSI,
UC_X86_REG_RDX, UC_X86_REG_RCX, UC_X86_REG_R8,
UC_X86_REG_R9, UC_X86_REG_RSP, UC_X86_REG_RIP)
class Oracle:
def __init__(self, path):
self.elf = Elf(path)
self.uc = u = Uc(UC_ARCH_X86, UC_MODE_64)
u.mem_map(0, 0x1000)
u.mem_map(0x400000, 0x300000)
u.mem_map(0x1000000, 0x20000)
data = self.elf.data
phoff = struct.unpack_from('<Q', data, 32)[0]
entsize, count = struct.unpack_from('<HH', data, 54)
for i in range(count):
typ, flags, off, va, pa, filesz, memsz, align = struct.unpack_from(
'<IIQQQQQQ', data, phoff + i * entsize)
if typ == 1:
u.mem_write(va, data[off:off + filesz])
self.out = []
self.code = b''
self.base = 0
self.hooks = []
self.hook(0x1000000, self.printf)
self.hook(0x1000010, self.read_memory)
self.hook(0x1000020, self.address)
self.hook(0x1000030, lambda: self.ret(0))
self.info = 0x1001000
u.mem_write(self.info, bytes(256))
for off, addr in [(0, 0x1000000), (0x70, 0x1000010), (0x78, 0x1000030),
(0x80, 0x1000020)]:
u.mem_write(self.info + off, struct.pack('<Q', addr))
self.hook(self.elf.symbols['xmalloc'][0], lambda: self.ret(0x1002000))
imports = json.loads(subprocess.check_output(
['r2', '-N', '-q', '-c', 'iij', str(path)], stderr=subprocess.DEVNULL))
callbacks = {'free': lambda: self.ret(0), 'strcmp': self.strcmp,
'__stack_chk_fail': self.fail}
for imp in imports:
if imp['name'] in callbacks:
self.hook(imp['plt'], callbacks[imp['name']])
# Do not depend on the vendor default ISA selection.
u.mem_write(self.elf.symbols['md32_isa_index'][0], struct.pack('<I', 0))
def hook(self, address, fn):
self.hooks.append(self.uc.hook_add(UC_HOOK_CODE, lambda u, a, s, _: fn(),
begin=address, end=address))
def reg(self, reg):
return self.uc.reg_read(reg)
def string(self, addr):
out = bytearray()
while True:
ch = self.uc.mem_read(addr + len(out), 1)
if ch == b'\0':
return out.decode()
out += ch
def ret(self, value=0):
sp = self.reg(UC_X86_REG_RSP)
dest = struct.unpack('<Q', self.uc.mem_read(sp, 8))[0]
self.uc.reg_write(UC_X86_REG_RAX, value & ((1 << 64) - 1))
self.uc.reg_write(UC_X86_REG_RSP, sp + 8)
self.uc.reg_write(UC_X86_REG_RIP, dest)
def strcmp(self):
a = self.string(self.reg(UC_X86_REG_RDI))
b = self.string(self.reg(UC_X86_REG_RSI))
self.ret((a > b) - (a < b))
def fail(self):
raise RuntimeError('Vendor stack check failed')
def printf(self):
fmt = self.string(self.reg(UC_X86_REG_RSI))
vals = iter(self.reg(r) for r in [UC_X86_REG_RDX, UC_X86_REG_RCX,
UC_X86_REG_R8, UC_X86_REG_R9])
def replace(m):
v = next(vals)
t = m.group()[-1]
if t == 's':
return self.string(v)
if t in 'di':
return str(v if v < (1 << 63) else v - (1 << 64))
if t in 'xX':
return format(v, t)
if t == 'u':
return str(v)
raise ValueError(m.group())
self.out.append(re.sub(r'%[-#0-9.*lhz]*[sdixXu]', replace, fmt))
self.ret(len(self.out[-1]))
def address(self):
self.out.append(hex(self.reg(UC_X86_REG_RDI)))
self.ret()
def read_memory(self):
addr = self.reg(UC_X86_REG_RDI) - self.base
dst = self.reg(UC_X86_REG_RSI)
size = self.reg(UC_X86_REG_RDX)
self.uc.mem_write(dst, (self.code[addr:addr + size] + bytes(size))[:size])
self.ret()
def call(self, addr, *args):
sp = 0x101fff0
self.uc.mem_write(sp, struct.pack('<Q', 0x1000100))
self.uc.reg_write(UC_X86_REG_RSP, sp)
for r, v in zip([UC_X86_REG_RDI, UC_X86_REG_RSI, UC_X86_REG_RDX], args):
self.uc.reg_write(r, v & ((1 << 64) - 1))
self.uc.emu_start(addr, 0x1000100, count=100000)
assert self.reg(UC_X86_REG_RIP) == 0x1000100, 'Emulation did not return'
return self.reg(UC_X86_REG_RAX)
def decode(self, code, addr=0):
self.out = []
self.code = code
self.base = addr
size = self.call(self.elf.symbols['print_insn_md32'][0], addr, self.info)
return size, ''.join(self.out)
def operand(self, index, shift, word):
table = self.elf.symbols['arg_make_table'][0]
fn, = self.elf.unpack('<Q', table + index * 8)
arg = 0x1003000
self.uc.mem_write(arg, bytes(64))
self.uc.mem_write(arg + 24, struct.pack('<Q', arg + 32))
self.call(fn, arg, shift, word)
return struct.unpack('<qi', self.uc.mem_read(arg, 12))
if __name__ == '__main__':
import argparse
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('objdump')
p.add_argument('hex')
args = p.parse_args()
print(Oracle(args.objdump).decode(bytes.fromhex(args.hex)))
+4799
View File
File diff suppressed because it is too large. Load diff
+159
View File
@@ -0,0 +1,159 @@
/* radare2 - LGPL-3.0-only - Copyright 2026 - n0p */
/* uSIP (MD32 family, VLIW packet variant) packet decoder.
*
* The unit is the 16-bit big-endian halfword. A packet is the run of units up to and including the first one with
* bit 15 set (at most 8 units). Inside a packet every instruction is a head (bit 15 = 0) plus 0, 1 or 2 tails that are
* not necessarily adjacent to it: the packet is a sequence of groups, each laid out heads first and then the tails
* dealt round-robin among the heads that take tails. No bit selects the layout, so the decoder enumerates every
* group split and head/tail assignment allowed by the form table (usip_opcodes.h), adds the explicit candidates of
* the packet grammar and keeps the smallest reading in the prior-free total order (s0, I, Fv, -A, K): unknown units
* plus the documented tie-break penalties, then the number of instructions, then the tails whose adjacency
* contradicts the F pattern, then (minus) the adjacent tails, then the lexicographic key of the assignment. Readings
* tied on the first four are passed through the K-tie cascade (dedup, nowaw, du2: see usip_decode.c) before K decides.
* The 48/64-bit b<cond>.l prefix forms are matched on the first units of the packet before the layout. Only slot 0
* (the first unit) can hold control flow and, when the control instruction does not close the packet, the rest of the
* packet is its delay slot. Once the layout is decided the post-layout rename entries of the table relabel the
* instructions they match. Some control forms carry a target guard (the target must be a packet start of the same
* function or a function start): the caller supplies that knowledge through UsipGuardCtx, the decoder applies the
* documented fallback when it is missing. The instructions of a packet execute in parallel and every read sees the
* state before the packet (USIP_PACKET_EXEC_PARALLEL): the decoder never depends on an intra-packet order. See
* README.md "uSIP mode". */
#ifndef USIP_DECODE_H
#define USIP_DECODE_H
#include <r_types.h>
#include <r_util.h>
#define USIP_MAX_UNITS 8
#define USIP_MAX_VALS 12
#define USIP_MAX_TAILS 3 /* 48-bit forms have two tails; the 64-bit packet-prefix forms three */
typedef struct usip_opcode_t UsipOpcode;
enum usip_pseudo {
USIP_P_FORM = 0, /* a table form (UsipInsn.op) */
USIP_P_HWORD, /* 16-bit unit without a reading */
USIP_P_WORD, /* head + tail without a reading */
USIP_P_WORD_CTRL, /* control-flow form whose tail is at the wrong distance or whose target guard failed (data) */
USIP_P_W48, /* head + hi + lo without a reading */
USIP_P_HI, /* context rule hi_lo_fam_before: a loose hi syllable after a 48-bit-family head (form hi1010_loose) */
USIP_P_LO, /* context rule hi_lo_fam_before: the unit after that hi syllable */
USIP_P_EXT, /* context rule ext_after_ldst */
USIP_P_BE79, /* context rule be79_prefix (2 units) */
};
/* target-guard status of an instruction */
enum usip_guard {
USIP_G_NONE = 0, /* the form has no target guard (or it was not evaluated: greedy fallback) */
USIP_G_OK, /* the guard validated (target at a packet start of the function / at a function start) */
USIP_G_UNVERIFIED, /* accepted by the fallback: the target could not be checked (printed with a trailing ?) */
};
/* how the reading of an ambiguous packet was decided (UsipPacket.tiebreak) */
enum usip_tiebreak {
USIP_T_NONE = 0, /* no tie, or the tie stays with the assignment key K (the packet is ambiguous) */
USIP_T_DEDUP, /* the tied readings have identical instruction texts: the K reading */
USIP_T_NOWAW, /* the only tied reading without a double register write */
USIP_T_DU2, /* the differential use-definition score over the neighbouring packets */
};
typedef struct {
ut8 head; /* unit index of the head */
ut8 ntails; /* 0, 1, 2 (or 3 for the 64-bit prefix forms) */
ut8 tails[USIP_MAX_TAILS]; /* unit indices of the tails (hi, lo) */
ut8 pseudo; /* enum usip_pseudo */
ut8 guard; /* enum usip_guard */
ut8 loopq; /* a loop #N reading printed loop #N? (N = 0 or the end at pc + 2^k: the loop #N? rule of the table) */
const UsipOpcode *op; /* NULL when pseudo != USIP_P_FORM (USIP_P_HI carries the context form) */
ut8 kind; /* enum usip_kind (USIP_K_OTHER / USIP_K_FMT for the pseudo forms) */
const char *mnem; /* mnemonic */
ut64 w; /* the units concatenated big-endian (head first) */
bool has_target;
ut64 target; /* branch/call/loop-end target */
ut8 nvals; /* decoded field values (op->fields order) */
st64 vals[USIP_MAX_VALS];
} UsipInsn;
typedef struct {
ut64 addr; /* address of the first unit */
ut8 nunits;
ut16 units[USIP_MAX_UNITS];
ut8 ninsn;
UsipInsn insn[USIP_MAX_UNITS]; /* sorted by head unit */
ut8 owner[USIP_MAX_UNITS]; /* instruction index owning each unit */
bool ambiguous; /* several readings tied on (s0, I, Fv, -A) and the cascade could not decide */
ut8 tiebreak; /* enum usip_tiebreak */
bool truncated; /* the buffer ended before the close bit */
bool delay; /* slot 0 is control flow and the rest of the packet is its delay slot */
} UsipPacket;
/* UsipGuardCtx.flags */
#define USIP_GUARD_NO_CASCADE 1 /* first-pass reading: the K-tie cascade is not run (the context packets of du2) */
/* What the decoder may ask the caller. Each callback answers 1 = yes, 0 = no, -1 = unknown; a NULL callback means
* unknown. The target guards of the table:
* packet_start: the address is the start of a packet of the function being decoded;
* function_start: the address is the start of a function.
* When the answer is unknown, a few forms are still read, marked USIP_G_UNVERIFIED (printed with a trailing ?): the
* call / b / compare-and-branch-with-immediate forms whose F' = 0 tail sits at +2, beqz.d, the b<cond>.l prefix forms,
* loop #N and the S0F1 compare-and-branch cells of the allow-list (their unverified readings are right in nearly every
* case, see unverified_ok in usip_decode.c), and the forms whose guard is a validation filter (the F = S = 0
* b<cond>.d cells, the F = 0 R-br0 cells, bne.d #-1, loop rX / loop.b1? with the F' = 1 tail, the near b, the open
* b.s: a known target that is not a packet start rejects them, an unknown one keeps the reading); the others (loop rX
* with the F' = 0 tail, far b, bnez.d, the register compare-and-branch, the 16-bit call.s) are data / fall through, as
* when the guard fails.
* The tie-break callin: inside_function answers 1 when the address lies strictly inside a known function (not at its
* start): a relative call to such a target pays the out-of-boundary penalty; 0 / unknown change nothing.
* The K-tie cascade (du2) scans the packets around the one being decoded: packet_at returns the packet that ENDS at
* addr (before = true: decoded earlier by the caller) or the one that STARTS at addr (before = false: the first-pass
* reading of the bytes at hand, decoded with USIP_GUARD_NO_CASCADE), NULL when unknown; branch_target answers whether a
* branch decoded so far targets addr (the join points of the scan). Without them the cascade still applies dedup and
* nowaw (they only need the packet) and du2 scores the packet alone. */
typedef struct {
void *user;
int (*packet_start)(void *user, ut64 addr);
int (*function_start)(void *user, ut64 addr);
int (*inside_function)(void *user, ut64 addr);
const UsipPacket *(*packet_at)(void *user, ut64 addr, bool before);
int (*branch_target)(void *user, ut64 addr);
/* packet_start_any: the address is the start of a packet of ANY function of the image (the near b admits the exception
* vectors); NULL = the same-function packet starts stand in */
int (*packet_start_any)(void *user, ut64 addr);
unsigned flags;
/* packet_units: the number of units (1..8) of the packet that STARTS at addr, -1 when unknown (the loop #N guard: bit 4 of
* the head must agree with "the packet at the loop end crosses a 16-byte line"; unknown = the guard is not evaluated).
* The two packets after the one being decoded (packet_at with before = false) also decide the stslot / stslot? twins of
* the op c slot pairs (a call within two packets); when they cannot be seen the pair prints stslot?. */
int (*packet_units)(void *user, ut64 addr);
} UsipGuardCtx;
/* Collect the units of the packet that starts at buf (len bytes available). Returns the number of units (0 when
* len < 2) and sets *truncated when the close bit was not reached. */
int usip_scan_units(const ut8 *buf, int len, ut16 *units, bool *truncated);
/* Decode a whole packet (n units, 1..USIP_MAX_UNITS) starting at addr; g may be NULL (no outside knowledge). */
void usip_decode_packet(UsipPacket *pk, ut64 addr, const ut16 *units, int n, bool truncated, const UsipGuardCtx *g);
/* Mnemonic and operands of instruction i of the packet. */
void usip_insn_text(const UsipPacket *pk, int i, RStrBuf *sb);
/* Address of a unit / of the head of an instruction. */
static inline ut64 usip_unit_addr(const UsipPacket *pk, int unit) {
return pk->addr + 2 * (ut64)unit;
}
/* Table accessors (the table itself lives in usip_decode.c). */
const char *usip_kind_name(int kind);
const char *usip_conf_name(int conf);
int usip_op_index(const UsipOpcode *op);
const char *usip_op_form(const UsipOpcode *op);
int usip_op_kind(const UsipOpcode *op);
int usip_op_conf(const UsipOpcode *op);
unsigned usip_op_flags(const UsipOpcode *op);
int usip_op_nargs(const UsipOpcode *op);
const char *usip_op_field_name(const UsipOpcode *op, int i);
int usip_op_field_type(const UsipOpcode *op, int i);
const char *usip_csr_ext_name(int csr);
#endif
+7526
View File
File diff suppressed because it is too large. Load diff
-6
View File
@@ -1,18 +1,12 @@
ANAL_DIR=anal
ASM_DIR=asm
all:
make -C $(ANAL_DIR) all
make -C $(ASM_DIR) all
install:
make -C $(ANAL_DIR) install
make -C $(ASM_DIR) install
uninstall:
make -C $(ANAL_DIR) uninstall
make -C $(ASM_DIR) uninstall
clean:
make -C $(ANAL_DIR) clean
make -C $(ASM_DIR) clean
+9 -14
View File
@@ -1,25 +1,20 @@
radare2_microblaze_plugins
radare2 MicroBlaze plugin
==========================
Support for the microblaze microprocessor in radare2
* The asm plugin is completed.
The plugin uses the current `RArchPlugin` API and provides disassembly and
analysis in one shared object.
* The anal plugin is a WIP and shouldn't be trusted
Directories
===========
* asm/
Contains the asm plugin
* anal/
Contains the anal plugin.
The analysis support is still a work in progress and should not be trusted.
Building
--------
Just type `make`.
After installing the plugin, run its regression tests from the repository root:
```
r2r -u -C . test/db/extras/microblaze
```
+7 -7
View File
@@ -1,8 +1,8 @@
NAME=anal_microblaze_gnu
NAME=arch_microblaze_gnu
R2_PLUGIN_PATH=$(shell r2 -hh|grep R2_LIBR_PLUGINS|awk '{print $$2}')
CFLAGS=-g -fPIC -Iinclude -Imicroblaze/gnu $(shell pkg-config --cflags r_anal)
LDFLAGS=-shared $(shell pkg-config --libs r_anal)
OBJS=$(NAME).o
CFLAGS=-g -fPIC -Iinclude -Imicroblaze/gnu $(shell pkg-config --cflags r_arch)
LDFLAGS=-shared $(shell pkg-config --libs r_arch)
OBJS=anal_microblaze_gnu.o microblaze/gnu/microblaze-dis.o
SO_EXT=$(shell uname|grep -q Darwin && echo dylib || echo so)
LIB=$(NAME).$(SO_EXT)
@@ -12,10 +12,10 @@ clean:
rm -f $(LIB) $(OBJS)
$(LIB): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(LIB)
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(LIB)
install:
cp -f $(NAME).$(SO_EXT) $(R2_PLUGIN_PATH)
cp -f $(LIB) $(R2_PLUGIN_PATH)
uninstall:
rm -f $(R2_PLUGIN_PATH)/$(NAME).$(SO_EXT)
rm -f $(R2_PLUGIN_PATH)/$(LIB)
+110 -85
View File
@@ -1,10 +1,8 @@
/* GPL, Copyright 2015 - tic */
#include <stdarg.h>
#include <string.h>
#include <r_types.h>
#include <r_lib.h>
#include <r_asm.h>
#include <r_anal.h>
#include <r_arch.h>
#include "microblaze-opc.h"
#include "dis-asm.h"
@@ -26,7 +24,6 @@ struct mb_anal_ctx {
ut16 immval;
bool immfound;
ut32 immfound_addr;
RAnal *anal;
RAnalOp *op;
};
@@ -36,14 +33,31 @@ static char *get_field(long instr, long mask, unsigned short low) {
return tmpstr;
}
static unsigned char bytes[4];
static int microblaze_read_memory(bfd_vma memaddr, bfd_byte *myaddr,
unsigned int length,
struct disassemble_info *info) {
memcpy(myaddr, bytes, length);
if (memaddr < info->buffer_vma || length > info->buffer_length ||
memaddr - info->buffer_vma > info->buffer_length - length) {
return -1;
}
memcpy (myaddr, info->buffer + (memaddr - info->buffer_vma), length);
return 0;
}
static int microblaze_fprintf(void *stream, const char *format, ...) {
RStrBuf *sb = stream;
va_list ap;
int ret;
va_start (ap, format);
ret = r_strbuf_vappendf (sb, format, ap);
va_end (ap);
return ret;
}
static void microblaze_print_address(bfd_vma address, struct disassemble_info *info) {
r_strbuf_appendf (info->stream, "0x%08"PFMT64x, (ut64)address);
}
ut32 microblaze_our_get_target_address(long inst, bool immfound, int immval,
ut32 pcval, long r1val, long r2val,
bool *targetvalid,
@@ -509,7 +523,7 @@ static void analyse_div_inst(struct mb_anal_ctx *ctx, unsigned long insn,
char *rb = get_field_r2 (insn);
char *rd = get_field_rd (insn);
r_strbuf_setf(&op->esil, "");
r_strbuf_set (&op->esil, "");
switch (mb_op->instr) {
case idiv:
r_strbuf_setf (&op->esil, "%s,%s,/,%s,=", ra, rb, rd);
@@ -545,7 +559,7 @@ static void analyse_branch_inst_imm(struct mb_anal_ctx *ctx, unsigned long insn,
char *rd = get_field_rd (insn);
char *imm;
ut32 jump_addr = 0;
r_strbuf_setf (&op->esil, "");
r_strbuf_set (&op->esil, "");
jump_addr = microblaze_our_get_target_address(
insn, ctx->immfound, ctx->immval, ctx->op->addr, r1, r2, &targetvalid,
@@ -693,7 +707,7 @@ static void analyse_branch_inst(struct mb_anal_ctx *ctx, unsigned long insn,
char *rd = get_field_rd (insn);
char *imm;
ut32 jump_addr = 0;
r_strbuf_setf (&op->esil, "");
r_strbuf_set (&op->esil, "");
jump_addr = microblaze_our_get_target_address(
insn, ctx->immfound, ctx->immval, ctx->op->addr, r1, r2, &targetvalid,
@@ -1088,130 +1102,137 @@ static void analyse_barrel_shift_inst(struct mb_anal_ctx *ctx, unsigned long ins
}
static int microblaze_op(RAnal *a, RAnalOp *op, ut64 addr, const ut8 *buf, int len, RAnalOpMask mask) {
int oplen = 4;
static struct mb_anal_ctx ctx;
static bool first_time = true;
struct disassemble_info info;
static bool microblaze_init(RArchSession *session) {
session->data = R_NEW0 (struct mb_anal_ctx);
return session->data != NULL;
}
static bool microblaze_fini(RArchSession *session) {
if (session) {
free (session->data);
}
return true;
}
static bool microblaze_op(RArchSession *session, RAnalOp *op, RArchDecodeMask mask) {
struct mb_anal_ctx *ctx = session->data;
struct disassemble_info info = {0};
unsigned long insn;
struct op_code_struct *mb_op;
/* initialize immediate value, belongs to init,
* there are some corner cases here */
if (len < 4) {
return -1;
if (!ctx || !op->bytes || op->size < 4) {
return false;
}
if (first_time) {
ctx.immval = 0;
ctx.immfound = false;
ctx.immfound_addr = 0;
}
first_time = false;
ctx.anal = a;
ctx.op = op;
/* update bytes */
memcpy (bytes, buf, oplen);
ctx->op = op;
info.buffer = op->bytes;
info.buffer_length = 4;
info.read_memory_func = microblaze_read_memory;
if (a->config->big_endian) {
if (R_ARCH_CONFIG_IS_BIG_ENDIAN (session->config)) {
info.endian = BFD_ENDIAN_BIG;
} else {
info.endian = BFD_ENDIAN_LITTLE;
}
if (op == NULL) {
return oplen;
}
memset (op, 0, sizeof (RAnalOp));
op->type = R_ANAL_OP_TYPE_NULL;
op->jump = op->fail = -1;
op->size = oplen;
op->size = 4;
op->delay = 0;
op->addr = addr;
op->ptr = op->val = -1;
op->ptr = -1;
op->val = UT64_MAX;
op->refptr = 0;
r_strbuf_init (&op->esil);
r_strbuf_set (&op->esil, "");
/* get microblaze insn */
insn = read_insn_microblaze (0, &info, &mb_op);
if (insn == 0) {
if (insn == 0 || !mb_op->name) {
op->type = R_ANAL_OP_TYPE_ILL;
return oplen;
return false;
}
if (mask & R_ANAL_OP_MASK_DISASM) {
op->mnemonic = strdup (mb_op->name);
if (mask & R_ARCH_OP_MASK_DISASM) {
RStrBuf *sb = r_strbuf_new (NULL);
if (!sb) {
return false;
}
info.buffer_vma = op->addr;
info.fprintf_func = microblaze_fprintf;
info.print_address_func = microblaze_print_address;
info.stream = sb;
print_insn_microblaze (op->addr, &info);
op->mnemonic = r_strbuf_drain (sb);
}
r_strbuf_setf (&op->esil, "");
handle_immediate_inst (&ctx, insn, mb_op);
handle_immediate_inst (ctx, insn, mb_op);
if (ctx.immfound_addr == ctx.op->addr - 4) ctx.immfound = true;
else ctx.immfound = false;
ctx->immfound = ctx->immfound_addr == ctx->op->addr - 4;
switch (mb_op->instr_type) {
case arithmetic_inst:
analyse_arithmetic_inst (&ctx, insn, mb_op);
analyse_arithmetic_inst (ctx, insn, mb_op);
break;
case arithmetic_inst_imm:
analyse_arithmetic_inst_imm (&ctx, insn, mb_op);
analyse_arithmetic_inst_imm (ctx, insn, mb_op);
break;
case logical_inst:
analyse_logical_inst (&ctx, insn, mb_op);
analyse_logical_inst (ctx, insn, mb_op);
break;
case logical_inst_imm:
analyse_logical_inst_imm (&ctx, insn, mb_op);
analyse_logical_inst_imm (ctx, insn, mb_op);
break;
case mult_inst:
analyse_mult_inst (&ctx, insn, mb_op);
analyse_mult_inst (ctx, insn, mb_op);
break;
case div_inst:
analyse_div_inst (&ctx, insn, mb_op);
analyse_div_inst (ctx, insn, mb_op);
break;
case branch_inst:
analyse_branch_inst (&ctx, insn, mb_op);
analyse_branch_inst (ctx, insn, mb_op);
break;
case branch_inst_imm:
analyse_branch_inst_imm (&ctx, insn, mb_op);
analyse_branch_inst_imm (ctx, insn, mb_op);
break;
case return_inst:
analyse_return_inst (&ctx, insn, mb_op);
analyse_return_inst (ctx, insn, mb_op);
break;
case special_inst:
analyse_special_inst (&ctx, insn, mb_op);
analyse_special_inst (ctx, insn, mb_op);
break;
case memory_load_inst:
analyse_memory_load_inst (&ctx, insn, mb_op);
analyse_memory_load_inst (ctx, insn, mb_op);
break;
case memory_load_inst_imm:
analyse_memory_load_inst_imm (&ctx, insn, mb_op);
analyse_memory_load_inst_imm (ctx, insn, mb_op);
break;
case memory_store_inst:
analyse_memory_store_inst (&ctx, insn, mb_op);
analyse_memory_store_inst (ctx, insn, mb_op);
break;
case memory_store_inst_imm:
analyse_memory_store_inst_imm (&ctx, insn, mb_op);
analyse_memory_store_inst_imm (ctx, insn, mb_op);
break;
case barrel_shift_inst:
analyse_barrel_shift_inst (&ctx, insn, mb_op);
analyse_barrel_shift_inst (ctx, insn, mb_op);
break;
case anyware_inst:
analyse_anyware_inst (&ctx, insn, mb_op);
analyse_anyware_inst (ctx, insn, mb_op);
break;
default:
break;
}
return oplen;
return true;
}
static int archinfo(RAnal *anal, int q) {
return 4;
static int archinfo(RArchSession *session, ut32 q) {
switch (q) {
case R_ARCH_INFO_MINOP_SIZE:
case R_ARCH_INFO_MAXOP_SIZE:
case R_ARCH_INFO_INVOP_SIZE:
case R_ARCH_INFO_CODE_ALIGN:
return 4;
}
return -1;
}
static int microblaze_set_reg_profile(RAnal* anal) {
static char *microblaze_regs(RArchSession *session) {
const char *p =
"=PC pc\n"
"=SP r1\n"
@@ -1271,25 +1292,29 @@ static int microblaze_set_reg_profile(RAnal* anal) {
// _immf
// this is the temporary flag set when an imm value has been set.
"gpr _immf .8 164 0\n";
return r_reg_set_profile_string(anal->reg, p);
return strdup (p);
}
struct r_anal_plugin_t r_anal_plugin_microblaze_gnu = {
.name = "microblaze.gnu",
.desc = "MICROBLAZE code analysis plugin",
.license = "LGPL3",
const RArchPlugin r_arch_plugin_microblaze_gnu = {
.meta = {
.name = "microblaze.gnu",
.desc = "MicroBlaze GNU architecture plugin",
.license = "GPL-3.0-or-later",
},
.arch = "microblaze",
.bits = 32,
.esil = true,
.archinfo = archinfo,
.op = &microblaze_op,
.set_reg_profile = microblaze_set_reg_profile,
.bits = R_SYS_BITS_PACK1 (32),
.endian = R_SYS_ENDIAN_BIG | R_SYS_ENDIAN_LITTLE,
.init = microblaze_init,
.fini = microblaze_fini,
.info = archinfo,
.decode = microblaze_op,
.regs = microblaze_regs,
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ANAL,
.data = &r_anal_plugin_microblaze_gnu,
.version = R2_VERSION
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_ARCH,
.data = (void *)&r_arch_plugin_microblaze_gnu,
.version = R2_VERSION
};
#endif
@@ -30,6 +30,10 @@
#include "microblaze-opc.h"
#include "microblaze-dis.h"
char register_prefix[] = "r";
char fsl_register_prefix[] = "rfsl";
char pvr_register_prefix[] = "rpvr";
#define get_field_rd(instr) get_field (instr, RD_MASK, RD_LOW)
#define get_field_r1(instr) get_field (instr, RA_MASK, RA_LOW)
#define get_field_r2(instr) get_field (instr, RB_MASK, RB_LOW)
@@ -411,9 +411,9 @@ static struct op_code_struct
};
/* Prefix for register names. */
char register_prefix[] = "r";
char fsl_register_prefix[] = "rfsl";
char pvr_register_prefix[] = "rpvr";
extern char register_prefix[];
extern char fsl_register_prefix[];
extern char pvr_register_prefix[];
/* #defines for valid immediate range. */
@@ -427,4 +427,3 @@ char pvr_register_prefix[] = "rpvr";
#define MAX_IMM5 ((int) 0x0000001f)
#endif /* MICROBLAZE_OPC */
-29
View File
@@ -1,29 +0,0 @@
NAME=asm_microblaze_gnu
R2_PLUGIN_PATH=$(shell r2 -hh|grep R2_LIBR_PLUGINS|awk '{print $$2}')
CFLAGS_MICROBLAZE=-g -fPIC -Iinclude $(shell pkg-config --cflags r_asm)
LDFLAGS=-shared $(shell pkg-config --libs r_asm)
OBJS=$(NAME).o
OBJS+=microblaze/gnu/microblaze-dis.o
SO_EXT=$(shell uname|grep -q Darwin && echo dylib || echo so)
LIB=$(NAME).$(SO_EXT)
all: $(LIB)
.c:
$(CC) $(LDFLAGS) -c $(CFLAGS_MICROBLAZE) -o $@ $<
.c.o:
$(CC) -c $(CFLAGS_MICROBLAZE) -o $@ $<
clean:
rm -f $(LIB) $(OBJS)
$(LIB): $(OBJS)
$(CC) $(CFLAGS_MICROBLAZE) $(LDFLAGS) $(OBJS) -o $(LIB)
install:
cp -f asm_microblaze_gnu.$(SO_EXT) $(R2_PLUGIN_PATH)
uninstall:
rm -f $(R2_PLUGIN_PATH)/$name.$(SO_EXT)
-95
View File
@@ -1,95 +0,0 @@
/* GPL, Copyright 2015 - tic */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <r_types.h>
#include <r_lib.h>
#include <r_util.h>
#include <r_asm.h>
#include "dis-asm.h"
static unsigned long Offset = 0;
static RStrBuf *buf_global = NULL;
static unsigned char bytes[4];
static int microblaze_buffer_read_memory (bfd_vma memaddr, bfd_byte *myaddr, unsigned int length, struct disassemble_info *info) {
memcpy (myaddr, bytes, length);
return 0;
}
static int symbol_at_address(bfd_vma addr, struct disassemble_info * info) {
return 0;
}
static void memory_error_func(int status, bfd_vma memaddr, struct disassemble_info *info) {
}
static void print_address(bfd_vma address, struct disassemble_info *info) {
if (buf_global == NULL)
return;
r_strbuf_appendf (buf_global, "0x%08"PFMT64x, (ut64)address);
}
static int buf_fprintf(void *stream, const char *format, ...) {
va_list ap;
if (buf_global == NULL || format == NULL)
return false;
va_start (ap, format);
r_strbuf_vappendf (buf_global, format, ap);
va_end (ap);
return true;
}
static int disassemble(struct r_asm_t *a, struct r_asm_op_t *op, const ut8 *buf, int len) {
static struct disassemble_info disasm_obj;
if (len<4) return -1;
buf_global = &op->buf_asm;
Offset = a->pc;
if (Offset % 4) return -1;
memcpy (bytes, buf, 4);
/* prepare disassembler */
memset (&disasm_obj,'\0', sizeof (struct disassemble_info));
disasm_obj.arch = 0;
disasm_obj.buffer = bytes;
disasm_obj.read_memory_func = &microblaze_buffer_read_memory;
disasm_obj.symbol_at_address_func = &symbol_at_address;
disasm_obj.memory_error_func = &memory_error_func;
disasm_obj.print_address_func = &print_address;
disasm_obj.buffer_vma = Offset;
disasm_obj.buffer_length = 4;
//disasm_obj.endian = a->big_endian ? BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
// only support little endian for now
disasm_obj.endian = !a->config->big_endian;
disasm_obj.fprintf_func = &buf_fprintf;
disasm_obj.stream = stdout;
r_strbuf_set (&op->buf_asm, "");
op->size = print_insn_microblaze ((bfd_vma)Offset, &disasm_obj);
if (op->size == -1)
op->size = 4;
return op->size;
}
RAsmPlugin r_asm_plugin_microblaze_gnu = {
.name = "microblaze.gnu",
.arch = "microblaze",
.license = "GPL3",
.bits = 32,
.desc = "MICROBLAZE CPU",
.disassemble = &disassemble,
};
#ifndef CORELIB
RLibStruct radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_microblaze_gnu,
.version = R2_VERSION
};
#endif
-369
View File
@@ -1,369 +0,0 @@
/* ANSI and traditional C compatability macros
Copyright 1991, 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
2002, 2003, 2004, 2005, 2006, 2007, 2009, 2010
Free Software Foundation, Inc.
This file is part of the GNU C Library.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */
/* ANSI and traditional C compatibility macros
ANSI C is assumed if __STDC__ is #defined.
Macro ANSI C definition Traditional C definition
----- ---- - ---------- ----------- - ----------
ANSI_PROTOTYPES 1 not defined
PTR `void *' `char *'
PTRCONST `void *const' `char *'
LONG_DOUBLE `long double' `double'
const not defined `'
volatile not defined `'
signed not defined `'
VA_START(ap, var) va_start(ap, var) va_start(ap)
Note that it is safe to write "void foo();" indicating a function
with no return value, in all K+R compilers we have been able to test.
For declaring functions with prototypes, we also provide these:
PARAMS ((prototype))
-- for functions which take a fixed number of arguments. Use this
when declaring the function. When defining the function, write a
K+R style argument list. For example:
char *strcpy PARAMS ((char *dest, char *source));
...
char *
strcpy (dest, source)
char *dest;
char *source;
{ ... }
VPARAMS ((prototype, ...))
-- for functions which take a variable number of arguments. Use
PARAMS to declare the function, VPARAMS to define it. For example:
int printf PARAMS ((const char *format, ...));
...
int
printf VPARAMS ((const char *format, ...))
{
...
}
For writing functions which take variable numbers of arguments, we
also provide the VA_OPEN, VA_CLOSE, and VA_FIXEDARG macros. These
hide the differences between K+R <varargs.h> and C89 <stdarg.h> more
thoroughly than the simple VA_START() macro mentioned above.
VA_OPEN and VA_CLOSE are used *instead of* va_start and va_end.
Immediately after VA_OPEN, put a sequence of VA_FIXEDARG calls
corresponding to the list of fixed arguments. Then use va_arg
normally to get the variable arguments, or pass your va_list object
around. You do not declare the va_list yourself; VA_OPEN does it
for you.
Here is a complete example:
int
printf VPARAMS ((const char *format, ...))
{
int result;
VA_OPEN (ap, format);
VA_FIXEDARG (ap, const char *, format);
result = vfprintf (stdout, format, ap);
VA_CLOSE (ap);
return result;
}
You can declare variables either before or after the VA_OPEN,
VA_FIXEDARG sequence. Also, VA_OPEN and VA_CLOSE are the beginning
and end of a block. They must appear at the same nesting level,
and any variables declared after VA_OPEN go out of scope at
VA_CLOSE. Unfortunately, with a K+R compiler, that includes the
argument list. You can have multiple instances of VA_OPEN/VA_CLOSE
pairs in a single function in case you need to traverse the
argument list more than once.
For ease of writing code which uses GCC extensions but needs to be
portable to other compilers, we provide the GCC_VERSION macro that
simplifies testing __GNUC__ and __GNUC_MINOR__ together, and various
wrappers around __attribute__. Also, __extension__ will be #defined
to nothing if it doesn't work. See below.
This header also defines a lot of obsolete macros:
CONST, VOLATILE, SIGNED, PROTO, EXFUN, DEFUN, DEFUN_VOID,
AND, DOTS, NOARGS. Don't use them. */
#ifndef _ANSIDECL_H
#define _ANSIDECL_H 1
#ifdef __cplusplus
extern "C" {
#endif
/* Every source file includes this file,
so they will all get the switch for lint. */
/* LINTLIBRARY */
/* Using MACRO(x,y) in cpp #if conditionals does not work with some
older preprocessors. Thus we can't define something like this:
#define HAVE_GCC_VERSION(MAJOR, MINOR) \
(__GNUC__ > (MAJOR) || (__GNUC__ == (MAJOR) && __GNUC_MINOR__ >= (MINOR)))
and then test "#if HAVE_GCC_VERSION(2,7)".
So instead we use the macro below and test it against specific values. */
/* This macro simplifies testing whether we are using gcc, and if it
is of a particular minimum version. (Both major & minor numbers are
significant.) This macro will evaluate to 0 if we are not using
gcc at all. */
#ifndef GCC_VERSION
#define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__)
#endif /* GCC_VERSION */
#if defined (__STDC__) || defined(__cplusplus) || defined (_AIX) || (defined (__mips) && defined (_SYSTYPE_SVR4)) || defined(_WIN32)
/* All known AIX compilers implement these things (but don't always
define __STDC__). The RISC/OS MIPS compiler defines these things
in SVR4 mode, but does not define __STDC__. */
/* [email protected]: The Compaq C++ compiler, unlike many other
C++ compilers, does not define __STDC__, though it acts as if this
was so. (Verified versions: 5.7, 6.2, 6.3, 6.5) */
#define ANSI_PROTOTYPES 1
#define PTR void *
#define PTRCONST void *const
#define LONG_DOUBLE long double
/* PARAMS is often defined elsewhere (e.g. by libintl.h), so wrap it in
a #ifndef. */
#ifndef PARAMS
#define PARAMS(ARGS) ARGS
#endif
#define VPARAMS(ARGS) ARGS
#define VA_START(VA_LIST, VAR) va_start(VA_LIST, VAR)
/* variadic function helper macros */
/* "struct Qdmy" swallows the semicolon after VA_OPEN/VA_FIXEDARG's
use without inhibiting further decls and without declaring an
actual variable. */
#define VA_OPEN(AP, VAR) { va_list AP; va_start(AP, VAR); { struct Qdmy
#define VA_CLOSE(AP) } va_end(AP); }
#define VA_FIXEDARG(AP, T, N) struct Qdmy
#undef const
#undef volatile
#undef signed
/* inline requires special treatment; it's in C99, and GCC >=2.7 supports
it too, but it's not in C89. */
#undef inline
#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) || (defined(__SUNPRO_C) && defined(__C99FEATURES__))
/* it's a keyword */
#else
# if GCC_VERSION >= 2007
# define inline __inline__ /* __inline__ prevents -pedantic warnings */
# else
# define inline /* nothing */
# endif
#endif
/* These are obsolete. Do not use. */
#ifndef IN_GCC
#define CONST const
#define VOLATILE volatile
#define SIGNED signed
#define PROTO(type, name, arglist) type name arglist
#define EXFUN(name, proto) name proto
#define DEFUN(name, arglist, args) name(args)
#define DEFUN_VOID(name) name(void)
#define AND ,
#define DOTS , ...
#define NOARGS void
#endif /* ! IN_GCC */
#else /* Not ANSI C. */
#undef ANSI_PROTOTYPES
#define PTR char *
#define PTRCONST PTR
#define LONG_DOUBLE double
#define PARAMS(args) ()
#define VPARAMS(args) (va_alist) va_dcl
#define VA_START(va_list, var) va_start(va_list)
#define VA_OPEN(AP, VAR) { va_list AP; va_start(AP); { struct Qdmy
#define VA_CLOSE(AP) } va_end(AP); }
#define VA_FIXEDARG(AP, TYPE, NAME) TYPE NAME = va_arg(AP, TYPE)
/* some systems define these in header files for non-ansi mode */
#undef const
#undef volatile
#undef signed
#undef inline
#define const
#define volatile
#define signed
#define inline
#ifndef IN_GCC
#define CONST
#define VOLATILE
#define SIGNED
#define PROTO(type, name, arglist) type name ()
#define EXFUN(name, proto) name()
#define DEFUN(name, arglist, args) name arglist args;
#define DEFUN_VOID(name) name()
#define AND ;
#define DOTS
#define NOARGS
#endif /* ! IN_GCC */
#endif /* ANSI C. */
/* Define macros for some gcc attributes. This permits us to use the
macros freely, and know that they will come into play for the
version of gcc in which they are supported. */
#if (GCC_VERSION < 2007)
# define __attribute__(x)
#endif
/* Attribute __malloc__ on functions was valid as of gcc 2.96. */
#ifndef ATTRIBUTE_MALLOC
# if (GCC_VERSION >= 2096)
# define ATTRIBUTE_MALLOC __attribute__ ((__malloc__))
# else
# define ATTRIBUTE_MALLOC
# endif /* GNUC >= 2.96 */
#endif /* ATTRIBUTE_MALLOC */
/* Attributes on labels were valid as of gcc 2.93 and g++ 4.5. For
g++ an attribute on a label must be followed by a semicolon. */
#ifndef ATTRIBUTE_UNUSED_LABEL
# ifndef __cplusplus
# if GCC_VERSION >= 2093
# define ATTRIBUTE_UNUSED_LABEL ATTRIBUTE_UNUSED
# else
# define ATTRIBUTE_UNUSED_LABEL
# endif
# else
# if GCC_VERSION >= 4005
# define ATTRIBUTE_UNUSED_LABEL ATTRIBUTE_UNUSED ;
# else
# define ATTRIBUTE_UNUSED_LABEL
# endif
# endif
#endif
/* Similarly to ARG_UNUSED below. Prior to GCC 3.4, the C++ frontend
couldn't parse attributes placed after the identifier name, and now
the entire compiler is built with C++. */
#ifndef ATTRIBUTE_UNUSED
#if GCC_VERSION >= 3004
# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
#else
#define ATTRIBUTE_UNUSED
#endif
#endif /* ATTRIBUTE_UNUSED */
/* Before GCC 3.4, the C++ frontend couldn't parse attributes placed after the
identifier name. */
#if ! defined(__cplusplus) || (GCC_VERSION >= 3004)
# define ARG_UNUSED(NAME) NAME ATTRIBUTE_UNUSED
#else /* !__cplusplus || GNUC >= 3.4 */
# define ARG_UNUSED(NAME) NAME
#endif /* !__cplusplus || GNUC >= 3.4 */
#ifndef ATTRIBUTE_NORETURN
#define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__))
#endif /* ATTRIBUTE_NORETURN */
/* Attribute `nonnull' was valid as of gcc 3.3. */
#ifndef ATTRIBUTE_NONNULL
# if (GCC_VERSION >= 3003)
# define ATTRIBUTE_NONNULL(m) __attribute__ ((__nonnull__ (m)))
# else
# define ATTRIBUTE_NONNULL(m)
# endif /* GNUC >= 3.3 */
#endif /* ATTRIBUTE_NONNULL */
/* Attribute `pure' was valid as of gcc 3.0. */
#ifndef ATTRIBUTE_PURE
# if (GCC_VERSION >= 3000)
# define ATTRIBUTE_PURE __attribute__ ((__pure__))
# else
# define ATTRIBUTE_PURE
# endif /* GNUC >= 3.0 */
#endif /* ATTRIBUTE_PURE */
/* Use ATTRIBUTE_PRINTF when the format specifier must not be NULL.
This was the case for the `printf' format attribute by itself
before GCC 3.3, but as of 3.3 we need to add the `nonnull'
attribute to retain this behavior. */
#ifndef ATTRIBUTE_PRINTF
#define ATTRIBUTE_PRINTF(m, n) __attribute__ ((__format__ (__printf__, m, n))) ATTRIBUTE_NONNULL(m)
#define ATTRIBUTE_PRINTF_1 ATTRIBUTE_PRINTF(1, 2)
#define ATTRIBUTE_PRINTF_2 ATTRIBUTE_PRINTF(2, 3)
#define ATTRIBUTE_PRINTF_3 ATTRIBUTE_PRINTF(3, 4)
#define ATTRIBUTE_PRINTF_4 ATTRIBUTE_PRINTF(4, 5)
#define ATTRIBUTE_PRINTF_5 ATTRIBUTE_PRINTF(5, 6)
#endif /* ATTRIBUTE_PRINTF */
/* Use ATTRIBUTE_FPTR_PRINTF when the format attribute is to be set on
a function pointer. Format attributes were allowed on function
pointers as of gcc 3.1. */
#ifndef ATTRIBUTE_FPTR_PRINTF
# if (GCC_VERSION >= 3001)
# define ATTRIBUTE_FPTR_PRINTF(m, n) ATTRIBUTE_PRINTF(m, n)
# else
# define ATTRIBUTE_FPTR_PRINTF(m, n)
# endif /* GNUC >= 3.1 */
# define ATTRIBUTE_FPTR_PRINTF_1 ATTRIBUTE_FPTR_PRINTF(1, 2)
# define ATTRIBUTE_FPTR_PRINTF_2 ATTRIBUTE_FPTR_PRINTF(2, 3)
# define ATTRIBUTE_FPTR_PRINTF_3 ATTRIBUTE_FPTR_PRINTF(3, 4)
# define ATTRIBUTE_FPTR_PRINTF_4 ATTRIBUTE_FPTR_PRINTF(4, 5)
# define ATTRIBUTE_FPTR_PRINTF_5 ATTRIBUTE_FPTR_PRINTF(5, 6)
#endif /* ATTRIBUTE_FPTR_PRINTF */
/* Use ATTRIBUTE_NULL_PRINTF when the format specifier may be NULL. A
NULL format specifier was allowed as of gcc 3.3. */
#ifndef ATTRIBUTE_NULL_PRINTF
# if (GCC_VERSION >= 3003)
# define ATTRIBUTE_NULL_PRINTF(m, n) __attribute__ ((__format__ (__printf__, m, n)))
# else
# define ATTRIBUTE_NULL_PRINTF(m, n)
# endif /* GNUC >= 3.3 */
# define ATTRIBUTE_NULL_PRINTF_1 ATTRIBUTE_NULL_PRINTF(1, 2)
# define ATTRIBUTE_NULL_PRINTF_2 ATTRIBUTE_NULL_PRINTF(2, 3)
# define ATTRIBUTE_NULL_PRINTF_3 ATTRIBUTE_NULL_PRINTF(3, 4)
# define ATTRIBUTE_NULL_PRINTF_4 ATTRIBUTE_NULL_PRINTF(4, 5)
# define ATTRIBUTE_NULL_PRINTF_5 ATTRIBUTE_NULL_PRINTF(5, 6)
#endif /* ATTRIBUTE_NULL_PRINTF */
/* Attribute `sentinel' was valid as of gcc 3.5. */
#ifndef ATTRIBUTE_SENTINEL
# if (GCC_VERSION >= 3005)
# define ATTRIBUTE_SENTINEL __attribute__ ((__sentinel__))
# else
# define ATTRIBUTE_SENTINEL
# endif /* GNUC >= 3.5 */
#endif /* ATTRIBUTE_SENTINEL */
#ifndef ATTRIBUTE_ALIGNED_ALIGNOF
# if (GCC_VERSION >= 3000)
# define ATTRIBUTE_ALIGNED_ALIGNOF(m) __attribute__ ((__aligned__ (__alignof__ (m))))
# else
# define ATTRIBUTE_ALIGNED_ALIGNOF(m)
# endif /* GNUC >= 3.0 */
#endif /* ATTRIBUTE_ALIGNED_ALIGNOF */
/* Useful for structures whose layout must much some binary specification
regardless of the alignment and padding qualities of the compiler. */
#ifndef ATTRIBUTE_PACKED
# define ATTRIBUTE_PACKED __attribute__ ((packed))
#endif
/* Attribute `hot' and `cold' was valid as of gcc 4.3. */
#ifndef ATTRIBUTE_COLD
# if (GCC_VERSION >= 4003)
# define ATTRIBUTE_COLD __attribute__ ((__cold__))
# else
# define ATTRIBUTE_COLD
# endif /* GNUC >= 4.3 */
#endif /* ATTRIBUTE_COLD */
#ifndef ATTRIBUTE_HOT
# if (GCC_VERSION >= 4003)
# define ATTRIBUTE_HOT __attribute__ ((__hot__))
# else
# define ATTRIBUTE_HOT
# endif /* GNUC >= 4.3 */
#endif /* ATTRIBUTE_HOT */
/* We use __extension__ in some places to suppress -pedantic warnings
about GCC extensions. This feature didn't work properly before
gcc 2.8. */
#if GCC_VERSION < 2008
#define __extension__
#endif
/* This is used to declare a const variable which should be visible
outside of the current compilation unit. Use it as
EXPORTED_CONST int i = 1;
This is because the semantics of const are different in C and C++.
"extern const" is permitted in C but it looks strange, and gcc
warns about it when -Wc++-compat is not used. */
#ifdef __cplusplus
#define EXPORTED_CONST extern const
#else
#define EXPORTED_CONST const
#endif
/* Be conservative and only use enum bitfields with C++ or GCC.
FIXME: provide a complete autoconf test for buggy enum bitfields. */
#ifdef __cplusplus
#define ENUM_BITFIELD(TYPE) enum TYPE
#elif (GCC_VERSION > 2000)
#define ENUM_BITFIELD(TYPE) __extension__ enum TYPE
#else
#define ENUM_BITFIELD(TYPE) unsigned int
#endif
#ifdef __cplusplus
}
#endif
#endif /* ansidecl.h */
-380
View File
@@ -1,380 +0,0 @@
/* Interface between the opcode library and its callers.
Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010,
2011, 2012 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street - Fifth Floor,
Boston, MA 02110-1301, USA.
Written by Cygnus Support, 1993.
The opcode library (libopcodes.a) provides instruction decoders for
a large variety of instruction sets, callable with an identical
interface, for making instruction-processing programs more independent
of the instruction set being processed. */
#ifndef DIS_ASM_H
#define DIS_ASM_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include "mybfd.h"
typedef int (*fprintf_ftype) (void *, const char*, ...) ATTRIBUTE_FPTR_PRINTF_2;
enum dis_insn_type
{
dis_noninsn, /* Not a valid instruction. */
dis_nonbranch, /* Not a branch instruction. */
dis_branch, /* Unconditional branch. */
dis_condbranch, /* Conditional branch. */
dis_jsr, /* Jump to subroutine. */
dis_condjsr, /* Conditional jump to subroutine. */
dis_dref, /* Data reference instruction. */
dis_dref2 /* Two data references in instruction. */
};
/* This struct is passed into the instruction decoding routine,
and is passed back out into each callback. The various fields are used
for conveying information from your main routine into your callbacks,
for passing information into the instruction decoders (such as the
addresses of the callback functions), or for passing information
back from the instruction decoders to their callers.
It must be initialized before it is first passed; this can be done
by hand, or using one of the initialization macros below. */
typedef struct disassemble_info
{
fprintf_ftype fprintf_func;
void *stream;
void *application_data;
/* Target description. We could replace this with a pointer to the bfd,
but that would require one. There currently isn't any such requirement
so to avoid introducing one we record these explicitly. */
/* The bfd_flavour. This can be bfd_target_unknown_flavour. */
enum bfd_flavour flavour;
/* The bfd_arch value. */
enum bfd_architecture arch;
/* The bfd_mach value. */
unsigned long mach;
/* Endianness (for bi-endian cpus). Mono-endian cpus can ignore this. */
enum bfd_endian endian;
/* Endianness of code, for mixed-endian situations such as ARM BE8. */
enum bfd_endian endian_code;
/* An arch/mach-specific bitmask of selected instruction subsets, mainly
for processors with run-time-switchable instruction sets. The default,
zero, means that there is no constraint. CGEN-based opcodes ports
may use ISA_foo masks. */
void *insn_sets;
/* Some targets need information about the current section to accurately
display insns. If this is NULL, the target disassembler function
will have to make its best guess. */
asection *section;
/* An array of pointers to symbols either at the location being disassembled
or at the start of the function being disassembled. The array is sorted
so that the first symbol is intended to be the one used. The others are
present for any misc. purposes. This is not set reliably, but if it is
not NULL, it is correct. */
asymbol **symbols;
/* Number of symbols in array. */
int num_symbols;
/* Symbol table provided for targets that want to look at it. This is
used on Arm to find mapping symbols and determine Arm/Thumb code. */
asymbol **symtab;
int symtab_pos;
int symtab_size;
/* For use by the disassembler.
The top 16 bits are reserved for public use (and are documented here).
The bottom 16 bits are for the internal use of the disassembler. */
unsigned long flags;
/* Set if the disassembler has determined that there are one or more
relocations associated with the instruction being disassembled. */
#define INSN_HAS_RELOC (1 << 31)
/* Set if the user has requested the disassembly of data as well as code. */
#define DISASSEMBLE_DATA (1 << 30)
/* Set if the user has specifically set the machine type encoded in the
mach field of this structure. */
#define USER_SPECIFIED_MACHINE_TYPE (1 << 29)
/* Use internally by the target specific disassembly code. */
void *private_data;
/* Function used to get bytes to disassemble. MEMADDR is the
address of the stuff to be disassembled, MYADDR is the address to
put the bytes in, and LENGTH is the number of bytes to read.
INFO is a pointer to this struct.
Returns an errno value or 0 for success. */
int (*read_memory_func)
(bfd_vma memaddr, bfd_byte *myaddr, unsigned int length,
struct disassemble_info *dinfo);
/* Function which should be called if we get an error that we can't
recover from. STATUS is the errno value from read_memory_func and
MEMADDR is the address that we were trying to read. INFO is a
pointer to this struct. */
void (*memory_error_func)
(int status, bfd_vma memaddr, struct disassemble_info *dinfo);
/* Function called to print ADDR. */
void (*print_address_func)
(bfd_vma addr, struct disassemble_info *dinfo);
/* Function called to determine if there is a symbol at the given ADDR.
If there is, the function returns 1, otherwise it returns 0.
This is used by ports which support an overlay manager where
the overlay number is held in the top part of an address. In
some circumstances we want to include the overlay number in the
address, (normally because there is a symbol associated with
that address), but sometimes we want to mask out the overlay bits. */
int (* symbol_at_address_func)
(bfd_vma addr, struct disassemble_info *dinfo);
/* Function called to check if a SYMBOL is can be displayed to the user.
This is used by some ports that want to hide special symbols when
displaying debugging outout. */
bfd_boolean (* symbol_is_valid)
(asymbol *, struct disassemble_info *dinfo);
/* These are for buffer_read_memory. */
bfd_byte *buffer;
bfd_vma buffer_vma;
unsigned int buffer_length;
/* This variable may be set by the instruction decoder. It suggests
the number of bytes objdump should display on a single line. If
the instruction decoder sets this, it should always set it to
the same value in order to get reasonable looking output. */
int bytes_per_line;
/* The next two variables control the way objdump displays the raw data. */
/* For example, if bytes_per_line is 8 and bytes_per_chunk is 4, the */
/* output will look like this:
00: 00000000 00000000
with the chunks displayed according to "display_endian". */
int bytes_per_chunk;
enum bfd_endian display_endian;
/* Number of octets per incremented target address
Normally one, but some DSPs have byte sizes of 16 or 32 bits. */
unsigned int octets_per_byte;
/* The number of zeroes we want to see at the end of a section before we
start skipping them. */
unsigned int skip_zeroes;
/* The number of zeroes to skip at the end of a section. If the number
of zeroes at the end is between SKIP_ZEROES_AT_END and SKIP_ZEROES,
they will be disassembled. If there are fewer than
SKIP_ZEROES_AT_END, they will be skipped. This is a heuristic
attempt to avoid disassembling zeroes inserted by section
alignment. */
unsigned int skip_zeroes_at_end;
/* Whether the disassembler always needs the relocations. */
bfd_boolean disassembler_needs_relocs;
/* Results from instruction decoders. Not all decoders yet support
this information. This info is set each time an instruction is
decoded, and is only valid for the last such instruction.
To determine whether this decoder supports this information, set
insn_info_valid to 0, decode an instruction, then check it. */
char insn_info_valid; /* Branch info has been set. */
char branch_delay_insns; /* How many sequential insn's will run before
a branch takes effect. (0 = normal) */
char data_size; /* Size of data reference in insn, in bytes */
enum dis_insn_type insn_type; /* Type of instruction */
bfd_vma target; /* Target address of branch or dref, if known;
zero if unknown. */
bfd_vma target2; /* Second target address for dref2 */
/* Command line options specific to the target disassembler. */
char * disassembler_options;
} disassemble_info;
/* Standard disassemblers. Disassemble one instruction at the given
target address. Return number of octets processed. */
typedef int (*disassembler_ftype) (bfd_vma, disassemble_info *);
extern int print_insn_aarch64 (bfd_vma, disassemble_info *);
extern int print_insn_alpha (bfd_vma, disassemble_info *);
extern int print_insn_avr (bfd_vma, disassemble_info *);
extern int print_insn_bfin (bfd_vma, disassemble_info *);
extern int print_insn_big_arm (bfd_vma, disassemble_info *);
extern int print_insn_big_mips (bfd_vma, disassemble_info *);
extern int print_insn_big_or32 (bfd_vma, disassemble_info *);
extern int print_insn_big_powerpc (bfd_vma, disassemble_info *);
extern int print_insn_big_score (bfd_vma, disassemble_info *);
extern int print_insn_cr16 (bfd_vma, disassemble_info *);
extern int print_insn_crx (bfd_vma, disassemble_info *);
extern int print_insn_d10v (bfd_vma, disassemble_info *);
extern int print_insn_d30v (bfd_vma, disassemble_info *);
extern int print_insn_dlx (bfd_vma, disassemble_info *);
extern int print_insn_epiphany (bfd_vma, disassemble_info *);
extern int print_insn_fr30 (bfd_vma, disassemble_info *);
extern int print_insn_frv (bfd_vma, disassemble_info *);
extern int print_insn_h8300 (bfd_vma, disassemble_info *);
extern int print_insn_h8300h (bfd_vma, disassemble_info *);
extern int print_insn_h8300s (bfd_vma, disassemble_info *);
extern int print_insn_h8500 (bfd_vma, disassemble_info *);
extern int print_insn_hppa (bfd_vma, disassemble_info *);
extern int print_insn_i370 (bfd_vma, disassemble_info *);
extern int print_insn_i386 (bfd_vma, disassemble_info *);
extern int print_insn_i386_att (bfd_vma, disassemble_info *);
extern int print_insn_i386_intel (bfd_vma, disassemble_info *);
extern int print_insn_i860 (bfd_vma, disassemble_info *);
extern int print_insn_i960 (bfd_vma, disassemble_info *);
extern int print_insn_ia64 (bfd_vma, disassemble_info *);
extern int print_insn_ip2k (bfd_vma, disassemble_info *);
extern int print_insn_iq2000 (bfd_vma, disassemble_info *);
extern int print_insn_little_arm (bfd_vma, disassemble_info *);
extern int print_insn_little_mips (bfd_vma, disassemble_info *);
extern int print_insn_little_or32 (bfd_vma, disassemble_info *);
extern int print_insn_little_powerpc (bfd_vma, disassemble_info *);
extern int print_insn_little_score (bfd_vma, disassemble_info *);
extern int print_insn_lm32 (bfd_vma, disassemble_info *);
extern int print_insn_m32c (bfd_vma, disassemble_info *);
extern int print_insn_m32r (bfd_vma, disassemble_info *);
extern int print_insn_m68hc11 (bfd_vma, disassemble_info *);
extern int print_insn_m68hc12 (bfd_vma, disassemble_info *);
extern int print_insn_m9s12x (bfd_vma, disassemble_info *);
extern int print_insn_m9s12xg (bfd_vma, disassemble_info *);
extern int print_insn_m68k (bfd_vma, disassemble_info *);
extern int print_insn_m88k (bfd_vma, disassemble_info *);
extern int print_insn_mcore (bfd_vma, disassemble_info *);
extern int print_insn_mep (bfd_vma, disassemble_info *);
extern int print_insn_microblaze (bfd_vma, disassemble_info *);
extern int print_insn_mmix (bfd_vma, disassemble_info *);
extern int print_insn_mn10200 (bfd_vma, disassemble_info *);
extern int print_insn_mn10300 (bfd_vma, disassemble_info *);
extern int print_insn_moxie (bfd_vma, disassemble_info *);
extern int print_insn_msp430 (bfd_vma, disassemble_info *);
extern int print_insn_mt (bfd_vma, disassemble_info *);
extern int print_insn_ns32k (bfd_vma, disassemble_info *);
extern int print_insn_openrisc (bfd_vma, disassemble_info *);
extern int print_insn_pdp11 (bfd_vma, disassemble_info *);
extern int print_insn_pj (bfd_vma, disassemble_info *);
extern int print_insn_rs6000 (bfd_vma, disassemble_info *);
extern int print_insn_s390 (bfd_vma, disassemble_info *);
extern int print_insn_shb (bfd_vma, disassemble_info *);
extern int print_insn_shl (bfd_vma, disassemble_info *);
extern int print_insn_sh64 (bfd_vma, disassemble_info *);
extern int print_insn_sh64x_media (bfd_vma, disassemble_info *);
extern int print_insn_sparc (bfd_vma, disassemble_info *);
extern int print_insn_spu (bfd_vma, disassemble_info *);
extern int print_insn_tic30 (bfd_vma, disassemble_info *);
extern int print_insn_tic4x (bfd_vma, disassemble_info *);
extern int print_insn_tic54x (bfd_vma, disassemble_info *);
extern int print_insn_tic6x (bfd_vma, disassemble_info *);
extern int print_insn_tic80 (bfd_vma, disassemble_info *);
extern int print_insn_tilegx (bfd_vma, disassemble_info *);
extern int print_insn_tilepro (bfd_vma, disassemble_info *);
extern int print_insn_v850 (bfd_vma, disassemble_info *);
extern int print_insn_vax (bfd_vma, disassemble_info *);
extern int print_insn_w65 (bfd_vma, disassemble_info *);
extern int print_insn_xc16x (bfd_vma, disassemble_info *);
extern int print_insn_xgate (bfd_vma, disassemble_info *);
extern int print_insn_xstormy16 (bfd_vma, disassemble_info *);
extern int print_insn_xtensa (bfd_vma, disassemble_info *);
extern int print_insn_z80 (bfd_vma, disassemble_info *);
extern int print_insn_z8001 (bfd_vma, disassemble_info *);
extern int print_insn_z8002 (bfd_vma, disassemble_info *);
extern int print_insn_rx (bfd_vma, disassemble_info *);
extern int print_insn_rl78 (bfd_vma, disassemble_info *);
extern disassembler_ftype arc_get_disassembler (bfd *);
extern disassembler_ftype cris_get_disassembler (bfd *);
extern void print_aarch64_disassembler_options (FILE *);
extern void print_i386_disassembler_options (FILE *);
extern void print_mips_disassembler_options (FILE *);
extern void print_ppc_disassembler_options (FILE *);
extern void print_arm_disassembler_options (FILE *);
extern void parse_arm_disassembler_option (char *);
extern void print_s390_disassembler_options (FILE *);
extern int get_arm_regname_num_options (void);
extern int set_arm_regname_option (int);
extern int get_arm_regnames (int, const char **, const char **, const char *const **);
extern bfd_boolean aarch64_symbol_is_valid (asymbol *, struct disassemble_info *);
extern bfd_boolean arm_symbol_is_valid (asymbol *, struct disassemble_info *);
extern void disassemble_init_powerpc (struct disassemble_info *);
/* Fetch the disassembler for a given BFD, if that support is available. */
extern disassembler_ftype disassembler (bfd *);
/* Amend the disassemble_info structure as necessary for the target architecture.
Should only be called after initialising the info->arch field. */
extern void disassemble_init_for_target (struct disassemble_info * dinfo);
/* Document any target specific options available from the disassembler. */
extern void disassembler_usage (FILE *);
/* This block of definitions is for particular callers who read instructions
into a buffer before calling the instruction decoder. */
/* Here is a function which callers may wish to use for read_memory_func.
It gets bytes from a buffer. */
extern int buffer_read_memory
(bfd_vma, bfd_byte *, unsigned int, struct disassemble_info *);
/* This function goes with buffer_read_memory.
It prints a message using info->fprintf_func and info->stream. */
extern void perror_memory (int, bfd_vma, struct disassemble_info *);
/* Just print the address in hex. This is included for completeness even
though both GDB and objdump provide their own (to print symbolic
addresses). */
extern void generic_print_address
(bfd_vma, struct disassemble_info *);
/* Always true. */
extern int generic_symbol_at_address
(bfd_vma, struct disassemble_info *);
/* Also always true. */
extern bfd_boolean generic_symbol_is_valid
(asymbol *, struct disassemble_info *);
/* Method to initialize a disassemble_info struct. This should be
called by all applications creating such a struct. */
extern void init_disassemble_info (struct disassemble_info *dinfo, void *stream,
fprintf_ftype fprintf_func);
/* For compatibility with existing code. */
#define INIT_DISASSEMBLE_INFO(INFO, STREAM, FPRINTF_FUNC) \
init_disassemble_info (&(INFO), (STREAM), (fprintf_ftype) (FPRINTF_FUNC))
#define INIT_DISASSEMBLE_INFO_NO_ARCH(INFO, STREAM, FPRINTF_FUNC) \
init_disassemble_info (&(INFO), (STREAM), (fprintf_ftype) (FPRINTF_FUNC))
#ifdef __cplusplus
}
#endif
#endif /* ! defined (DIS_ASM_H) */
File diff suppressed because it is too large. Load diff
-46
View File
@@ -1,46 +0,0 @@
/* Symbol concatenation utilities.
Copyright (C) 1998, 2000, 2010 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */
#ifndef SYM_CAT_H
#define SYM_CAT_H
#if defined (__STDC__) || defined (ALMOST_STDC) || defined (HAVE_STRINGIZE)
#define CONCAT2(a,b) a##b
#define CONCAT3(a,b,c) a##b##c
#define CONCAT4(a,b,c,d) a##b##c##d
#define CONCAT5(a,b,c,d,e) a##b##c##d##e
#define CONCAT6(a,b,c,d,e,f) a##b##c##d##e##f
#define STRINGX(s) #s
#else
/* Note one should never pass extra whitespace to the CONCATn macros,
e.g. CONCAT2(foo, bar) because traditonal C will keep the space between
the two labels instead of concatenating them. Instead, make sure to
write CONCAT2(foo,bar). */
#define CONCAT2(a,b) a/**/b
#define CONCAT3(a,b,c) a/**/b/**/c
#define CONCAT4(a,b,c,d) a/**/b/**/c/**/d
#define CONCAT5(a,b,c,d,e) a/**/b/**/c/**/d/**/e
#define CONCAT6(a,b,c,d,e,f) a/**/b/**/c/**/d/**/e/**/f
#define STRINGX(s) "s"
#endif
#define XCONCAT2(a,b) CONCAT2(a,b)
#define XCONCAT3(a,b,c) CONCAT3(a,b,c)
#define XCONCAT4(a,b,c,d) CONCAT4(a,b,c,d)
#define XCONCAT5(a,b,c,d,e) CONCAT5(a,b,c,d,e)
#define XCONCAT6(a,b,c,d,e,f) CONCAT6(a,b,c,d,e,f)
/* Note the layer of indirection here is typically used to allow
stringification of the expansion of macros. I.e. "#define foo
bar", "XSTRING(foo)", to yield "bar". Be aware that this only
works for __STDC__, not for traditional C which will still resolve
to "foo". */
#define XSTRING(s) STRINGX(s)
#endif /* SYM_CAT_H */
-42
View File
@@ -1,42 +0,0 @@
/* Random host-dependent support code.
Copyright 1995, 1997, 2000, 2007 Free Software Foundation, Inc.
Written by Ken Raeburn.
This file is part of the GNU opcodes library.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
/* Do system-dependent stuff, mainly driven by autoconf-detected info.
Well, some generic common stuff is done here too, like including
ansidecl.h. That's because the .h files in bfd/hosts files I'm
trying to replace often did that. If it can be dropped from this
file (check in a non-ANSI environment!), it should be. */
#include "ansidecl.h"
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif
@@ -1,538 +0,0 @@
/* Disassemble Xilinx microblaze instructions.
Copyright 2009, 2012 Free Software Foundation, Inc.
This file is part of the GNU opcodes library.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this file; see the file COPYING. If not, write to the
Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#include "sysdep.h"
#define STATIC_TABLE
#define DEFINE_TABLE
#include "dis-asm.h"
#include <stdlib.h>
#include <string.h>
#include <r_asm.h>
#include "microblaze-opc.h"
#include "microblaze-dis.h"
#define get_field_rd(instr) get_field (instr, RD_MASK, RD_LOW)
#define get_field_r1(instr) get_field (instr, RA_MASK, RA_LOW)
#define get_field_r2(instr) get_field (instr, RB_MASK, RB_LOW)
#define get_int_field_imm(instr) ((instr & IMM_MASK) >> IMM_LOW)
#define get_int_field_r1(instr) ((instr & RA_MASK) >> RA_LOW)
static char *
get_field (long instr, long mask, unsigned short low)
{
char tmpstr[25];
sprintf (tmpstr, "%s%d", register_prefix, (int)((instr & mask) >> low));
return (strdup (tmpstr));
}
static char *
get_field_imm (long instr)
{
char tmpstr[25];
sprintf (tmpstr, "%d", (short)((instr & IMM_MASK) >> IMM_LOW));
return (strdup (tmpstr));
}
static char *
get_field_imm5 (long instr)
{
char tmpstr[25];
sprintf (tmpstr, "%d", (short)((instr & IMM5_MASK) >> IMM_LOW));
return (strdup (tmpstr));
}
static char *
get_field_imm5_mbar (long instr)
{
char tmpstr[25];
sprintf(tmpstr, "%d", (short)((instr & IMM5_MBAR_MASK) >> IMM_MBAR));
return(strdup(tmpstr));
}
static char *
get_field_rfsl (long instr)
{
char tmpstr[25];
sprintf (tmpstr, "%s%d", fsl_register_prefix,
(short)((instr & RFSL_MASK) >> IMM_LOW));
return (strdup (tmpstr));
}
static char *
get_field_imm15 (long instr)
{
char tmpstr[25];
sprintf (tmpstr, "%d", (short)((instr & IMM15_MASK) >> IMM_LOW));
return (strdup (tmpstr));
}
static char *
get_field_special (long instr, struct op_code_struct * op)
{
char *tmpstr;
char spr[6];
switch ((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask))
{
case REG_MSR_MASK :
strcpy (spr, "msr");
break;
case REG_PC_MASK :
strcpy (spr, "pc");
break;
case REG_EAR_MASK :
strcpy (spr, "ear");
break;
case REG_ESR_MASK :
strcpy (spr, "esr");
break;
case REG_FSR_MASK :
strcpy (spr, "fsr");
break;
case REG_BTR_MASK :
strcpy (spr, "btr");
break;
case REG_EDR_MASK :
strcpy (spr, "edr");
break;
case REG_PID_MASK :
strcpy (spr, "pid");
break;
case REG_ZPR_MASK :
strcpy (spr, "zpr");
break;
case REG_TLBX_MASK :
strcpy (spr, "tlbx");
break;
case REG_TLBLO_MASK :
strcpy (spr, "tlblo");
break;
case REG_TLBHI_MASK :
strcpy (spr, "tlbhi");
break;
case REG_TLBSX_MASK :
strcpy (spr, "tlbsx");
break;
case REG_SHR_MASK :
strcpy (spr, "shr");
break;
case REG_SLR_MASK :
strcpy (spr, "slr");
break;
default :
if (((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask) & 0xE000)
== REG_PVR_MASK)
{
tmpstr = r_str_newf ("%spvr%d", register_prefix,
(unsigned short)(((instr & IMM_MASK) >> IMM_LOW)
^ op->immval_mask) ^ REG_PVR_MASK);
return (tmpstr);
}
else
strcpy (spr, "pc");
break;
}
tmpstr = r_str_newf ("%s", spr);
return (tmpstr);
}
static unsigned long
read_insn_microblaze (bfd_vma memaddr,
struct disassemble_info *info,
struct op_code_struct **opr)
{
unsigned char ibytes[4];
int status;
struct op_code_struct * op;
unsigned long inst;
status = info->read_memory_func (memaddr, ibytes, 4, info);
if (status != 0)
{
info->memory_error_func (status, memaddr, info);
return 0;
}
if (info->endian == BFD_ENDIAN_BIG)
inst = (ibytes[0] << 24) | (ibytes[1] << 16) | (ibytes[2] << 8) | ibytes[3];
else if (info->endian == BFD_ENDIAN_LITTLE)
inst = (ibytes[3] << 24) | (ibytes[2] << 16) | (ibytes[1] << 8) | ibytes[0];
else
abort ();
/* Just a linear search of the table. */
for (op = opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
*opr = op;
return inst;
}
int
print_insn_microblaze (bfd_vma memaddr, struct disassemble_info * info)
{
fprintf_ftype print_func = info->fprintf_func;
void * stream = info->stream;
unsigned long inst, prev_inst;
struct op_code_struct * op, *pop;
int immval = 0;
bfd_boolean immfound = FALSE;
static bfd_vma prev_insn_addr = -1; /* Init the prev insn addr. */
static int prev_insn_vma = -1; /* Init the prev insn vma. */
int curr_insn_vma = info->buffer_vma;
info->bytes_per_chunk = 4;
inst = read_insn_microblaze (memaddr, info, &op);
if (inst == 0)
return -1;
if (prev_insn_vma == curr_insn_vma)
{
if (memaddr-(info->bytes_per_chunk) == prev_insn_addr)
{
prev_inst = read_insn_microblaze (prev_insn_addr, info, &pop);
if (prev_inst == 0)
return -1;
if (pop->instr == imm)
{
immval = (get_int_field_imm (prev_inst) << 16) & 0xffff0000;
immfound = TRUE;
}
else
{
immval = 0;
immfound = FALSE;
}
}
}
/* Make curr insn as prev insn. */
prev_insn_addr = memaddr;
prev_insn_vma = curr_insn_vma;
if (op->name == NULL)
print_func (stream, ".short 0x%04x", (unsigned int) inst);
else
{
print_func (stream, "%s", op->name);
switch (op->inst_type)
{
case INST_TYPE_RD_R1_R2:
print_func (stream, " %s, %s, %s", get_field_rd (inst),
get_field_r1(inst), get_field_r2 (inst));
break;
case INST_TYPE_RD_R1_IMM:
print_func (stream, " %s, %s, %s", get_field_rd (inst),
get_field_r1(inst), get_field_imm (inst));
if (info->print_address_func && get_int_field_r1 (inst) == 0
&& info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, " // ");
info->print_address_func (immval, info);
}
}
break;
case INST_TYPE_RD_R1_IMM5:
print_func (stream, " %s, %s, %s", get_field_rd (inst),
get_field_r1(inst), get_field_imm5 (inst));
break;
case INST_TYPE_RD_RFSL:
print_func (stream, " %s, %s", get_field_rd (inst), get_field_rfsl (inst));
break;
case INST_TYPE_R1_RFSL:
print_func (stream, " %s, %s", get_field_r1 (inst), get_field_rfsl (inst));
break;
case INST_TYPE_RD_SPECIAL:
print_func (stream, " %s, %s", get_field_rd (inst),
get_field_special (inst, op));
break;
case INST_TYPE_SPECIAL_R1:
print_func (stream, " %s, %s", get_field_special (inst, op),
get_field_r1(inst));
break;
case INST_TYPE_RD_R1:
print_func (stream, " %s, %s", get_field_rd (inst), get_field_r1 (inst));
break;
case INST_TYPE_R1_R2:
print_func (stream, " %s, %s", get_field_r1 (inst), get_field_r2 (inst));
break;
case INST_TYPE_R1_IMM:
print_func (stream, " %s, %s", get_field_r1 (inst), get_field_imm (inst));
/* The non-pc relative instructions are returns, which shouldn't
have a label printed. */
if (info->print_address_func && op->inst_offset_type == INST_PC_OFFSET
&& info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
immval += memaddr;
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, " // ");
info->print_address_func (immval, info);
}
else
{
print_func (stream, " // ");
print_func (stream, "%x", immval);
}
}
break;
case INST_TYPE_RD_IMM:
print_func (stream, " %s, %s", get_field_rd (inst), get_field_imm (inst));
if (info->print_address_func && info->symbol_at_address_func)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
immval += (int) memaddr;
if (info->symbol_at_address_func (immval, info))
{
print_func (stream, " // ");
info->print_address_func (immval, info);
}
}
break;
case INST_TYPE_IMM:
print_func (stream, " %s", get_field_imm (inst));
if (info->print_address_func && info->symbol_at_address_func
&& op->instr != imm)
{
if (immfound)
immval |= (get_int_field_imm (inst) & 0x0000ffff);
else
{
immval = get_int_field_imm (inst);
if (immval & 0x8000)
immval |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
immval += (int) memaddr;
if (immval > 0 && info->symbol_at_address_func (immval, info))
{
print_func (stream, " // ");
info->print_address_func (immval, info);
}
else if (op->inst_offset_type == INST_PC_OFFSET)
{
print_func (stream, " // ");
print_func (stream, "%x", immval);
}
}
break;
case INST_TYPE_RD_R2:
print_func (stream, " %s, %s", get_field_rd (inst), get_field_r2 (inst));
break;
case INST_TYPE_R2:
print_func (stream, " %s", get_field_r2 (inst));
break;
case INST_TYPE_R1:
print_func (stream, " %s", get_field_r1 (inst));
break;
case INST_TYPE_R1_R2_SPECIAL:
print_func (stream, " %s, %s", get_field_r1 (inst), get_field_r2 (inst));
break;
case INST_TYPE_RD_IMM15:
print_func (stream, " %s, %s", get_field_rd (inst), get_field_imm15 (inst));
break;
/* For mbar insn. */
case INST_TYPE_IMM5:
print_func (stream, " %s", get_field_imm5_mbar (inst));
break;
/* For mbar 16 or sleep insn. */
case INST_TYPE_NONE:
break;
/* For tuqula instruction */
case INST_TYPE_RD:
print_func (stream, " %s", get_field_rd (inst));
break;
case INST_TYPE_RFSL:
print_func (stream, " %s", get_field_rfsl (inst));
break;
default:
/* If the disassembler lags the instruction set. */
print_func (stream, "undecoded operands, inst is 0x%04x", (unsigned int) inst);
break;
}
}
/* Say how many bytes we consumed. */
return 4;
}
enum microblaze_instr
get_insn_microblaze (long inst,
bfd_boolean *isunsignedimm,
enum microblaze_instr_type *insn_type,
short *delay_slots)
{
struct op_code_struct * op;
*isunsignedimm = FALSE;
/* Just a linear search of the table. */
for (op = opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
if (op->name == 0)
return invalid_inst;
else
{
*isunsignedimm = (op->inst_type == INST_TYPE_RD_R1_UNSIGNED_IMM);
*insn_type = op->instr_type;
*delay_slots = op->delay_slots;
return op->instr;
}
}
enum microblaze_instr
microblaze_decode_insn (long insn, int *rd, int *ra, int *rb, int *immed)
{
enum microblaze_instr op;
bfd_boolean t1;
enum microblaze_instr_type t2;
short t3;
op = get_insn_microblaze (insn, &t1, &t2, &t3);
*rd = (insn & RD_MASK) >> RD_LOW;
*ra = (insn & RA_MASK) >> RA_LOW;
*rb = (insn & RB_MASK) >> RB_LOW;
t3 = (insn & IMM_MASK) >> IMM_LOW;
*immed = (int) t3;
return (op);
}
unsigned long
microblaze_get_target_address (long inst, bfd_boolean immfound, int immval,
long pcval, long r1val, long r2val,
bfd_boolean *targetvalid,
bfd_boolean *unconditionalbranch)
{
struct op_code_struct * op;
long targetaddr = 0;
*unconditionalbranch = FALSE;
/* Just a linear search of the table. */
for (op = opcodes; op->name != 0; op ++)
if (op->bit_sequence == (inst & op->opcode_mask))
break;
if (op->name == 0)
{
*targetvalid = FALSE;
}
else if (op->instr_type == branch_inst)
{
switch (op->inst_type)
{
case INST_TYPE_R2:
*unconditionalbranch = TRUE;
/* Fall through. */
case INST_TYPE_RD_R2:
case INST_TYPE_R1_R2:
targetaddr = r2val;
*targetvalid = TRUE;
if (op->inst_offset_type == INST_PC_OFFSET)
targetaddr += pcval;
break;
case INST_TYPE_IMM:
*unconditionalbranch = TRUE;
/* Fall through. */
case INST_TYPE_RD_IMM:
case INST_TYPE_R1_IMM:
if (immfound)
{
targetaddr = (immval << 16) & 0xffff0000;
targetaddr |= (get_int_field_imm (inst) & 0x0000ffff);
}
else
{
targetaddr = get_int_field_imm (inst);
if (targetaddr & 0x8000)
targetaddr |= 0xFFFF0000;
}
if (op->inst_offset_type == INST_PC_OFFSET)
targetaddr += pcval;
*targetvalid = TRUE;
break;
default:
*targetvalid = FALSE;
break;
}
}
else if (op->instr_type == return_inst)
{
if (immfound)
{
targetaddr = (immval << 16) & 0xffff0000;
targetaddr |= (get_int_field_imm (inst) & 0x0000ffff);
}
else
{
targetaddr = get_int_field_imm (inst);
if (targetaddr & 0x8000)
targetaddr |= 0xFFFF0000;
}
targetaddr += r1val;
*targetvalid = TRUE;
}
else
*targetvalid = FALSE;
return targetaddr;
}
@@ -1,34 +0,0 @@
/* Disassemble Xilinx microblaze instructions.
Copyright 2009 Free Software Foundation, Inc.
This file is part of the GNU opcodes library.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this file; see the file COPYING. If not, write to the
Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#ifndef MICROBLAZE_DIS_H
#define MICROBLAZE_DIS_H 1
extern enum microblaze_instr microblaze_decode_insn (long, int *, int *,
int *, int *);
extern unsigned long microblaze_get_target_address (long, bfd_boolean, int,
long, long, long, bfd_boolean *, bfd_boolean *);
extern enum microblaze_instr get_insn_microblaze (long, bfd_boolean *,
enum microblaze_instr_type *,
short *);
#endif /* microblaze-dis.h */
@@ -1,431 +0,0 @@
/* microblaze-opc.h -- MicroBlaze Opcodes
Copyright 2009 Free Software Foundation, Inc.
This file is part of the GNU opcodes library.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this file; see the file COPYING. If not, write to the
Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#ifndef MICROBLAZE_OPC
#define MICROBLAZE_OPC
#include "microblaze-opcm.h"
#define INST_TYPE_RD_R1_R2 0
#define INST_TYPE_RD_R1_IMM 1
#define INST_TYPE_RD_R1_UNSIGNED_IMM 2
#define INST_TYPE_RD_R1 3
#define INST_TYPE_RD_R2 4
#define INST_TYPE_RD_IMM 5
#define INST_TYPE_R2 6
#define INST_TYPE_R1_R2 7
#define INST_TYPE_R1_IMM 8
#define INST_TYPE_IMM 9
#define INST_TYPE_SPECIAL_R1 10
#define INST_TYPE_RD_SPECIAL 11
#define INST_TYPE_R1 12
/* New instn type for barrel shift imms. */
#define INST_TYPE_RD_R1_IMM5 13
#define INST_TYPE_RD_RFSL 14
#define INST_TYPE_R1_RFSL 15
/* New insn type for insn cache. */
#define INST_TYPE_R1_R2_SPECIAL 16
/* New insn type for msrclr, msrset insns. */
#define INST_TYPE_RD_IMM15 17
/* New insn type for tuqula rd - addik rd, r0, 42. */
#define INST_TYPE_RD 18
/* New insn type for t*put. */
#define INST_TYPE_RFSL 19
/* For mbar. */
#define INST_TYPE_IMM5 20
#define INST_TYPE_NONE 25
/* Instructions where the label address is resolved as a PC offset
(for branch label). */
#define INST_PC_OFFSET 1
/* Instructions where the label address is resolved as an absolute
value (for data mem or abs address). */
#define INST_NO_OFFSET 0
#define IMMVAL_MASK_NON_SPECIAL 0x0000
#define IMMVAL_MASK_MTS 0x4000
#define IMMVAL_MASK_MFS 0x0000
#define OPCODE_MASK_H 0xFC000000 /* High 6 bits only. */
#define OPCODE_MASK_H1 0xFFE00000 /* High 11 bits. */
#define OPCODE_MASK_H2 0xFC1F0000 /* High 6 and bits 20-16. */
#define OPCODE_MASK_H12 0xFFFF0000 /* High 16. */
#define OPCODE_MASK_H4 0xFC0007FF /* High 6 and low 11 bits. */
#define OPCODE_MASK_H13S 0xFFE0E7F0 /* High 11 16:18 21:27 bits, 19:20 bits
and last nibble of last byte for spr. */
#define OPCODE_MASK_H23S 0xFC1FC000 /* High 6, 20-16 and 15:1 bits and last
nibble of last byte for spr. */
#define OPCODE_MASK_H34 0xFC00FFFF /* High 6 and low 16 bits. */
#define OPCODE_MASK_H14 0xFFE007FF /* High 11 and low 11 bits. */
#define OPCODE_MASK_H24 0xFC1F07FF /* High 6, bits 20-16 and low 11 bits. */
#define OPCODE_MASK_H124 0xFFFF07FF /* High 16, and low 11 bits. */
#define OPCODE_MASK_H1234 0xFFFFFFFF /* All 32 bits. */
#define OPCODE_MASK_H3 0xFC000600 /* High 6 bits and bits 21, 22. */
#define OPCODE_MASK_H32 0xFC00FC00 /* High 6 bits and bit 16-21. */
#define OPCODE_MASK_H34B 0xFC0000FF /* High 6 bits and low 8 bits. */
#define OPCODE_MASK_H35B 0xFC0004FF /* High 6 bits and low 9 bits. */
#define OPCODE_MASK_H34C 0xFC0007E0 /* High 6 bits and bits 21-26. */
/* New Mask for msrset, msrclr insns. */
#define OPCODE_MASK_H23N 0xFC1F8000 /* High 6 and bits 11 - 16. */
/* Mask for mbar insn. */
#define OPCODE_MASK_HN 0xFF020004 /* High 16 bits and bits 14, 29. */
#define DELAY_SLOT 1
#define NO_DELAY_SLOT 0
#define MAX_OPCODES 291
static struct op_code_struct
{
char *name;
short inst_type; /* Registers and immediate values involved. */
short inst_offset_type; /* Immediate vals offset from PC? (= 1 for branches). */
short delay_slots; /* Info about delay slots needed after this instr. */
short immval_mask;
unsigned long bit_sequence; /* All the fixed bits for the op are set and
all the variable bits (reg names, imm vals)
are set to 0. */
unsigned long opcode_mask; /* Which bits define the opcode. */
enum microblaze_instr instr;
enum microblaze_instr_type instr_type;
/* More info about output format here. */
} opcodes[MAX_OPCODES] =
{
{"add", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x00000000, OPCODE_MASK_H4, add, arithmetic_inst },
{"rsub", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x04000000, OPCODE_MASK_H4, rsub, arithmetic_inst },
{"addc", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x08000000, OPCODE_MASK_H4, addc, arithmetic_inst },
{"rsubc", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x0C000000, OPCODE_MASK_H4, rsubc, arithmetic_inst },
{"addk", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x10000000, OPCODE_MASK_H4, addk, arithmetic_inst },
{"rsubk", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x14000000, OPCODE_MASK_H4, rsubk, arithmetic_inst },
{"cmp", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x14000001, OPCODE_MASK_H4, cmp, arithmetic_inst },
{"cmpu", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x14000003, OPCODE_MASK_H4, cmpu, arithmetic_inst },
{"addkc", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x18000000, OPCODE_MASK_H4, addkc, arithmetic_inst },
{"rsubkc",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x1C000000, OPCODE_MASK_H4, rsubkc, arithmetic_inst },
{"addi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x20000000, OPCODE_MASK_H, addi, arithmetic_inst },
{"rsubi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x24000000, OPCODE_MASK_H, rsubi, arithmetic_inst },
{"addic", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x28000000, OPCODE_MASK_H, addic, arithmetic_inst },
{"rsubic",INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x2C000000, OPCODE_MASK_H, rsubic, arithmetic_inst },
{"addik", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x30000000, OPCODE_MASK_H, addik, arithmetic_inst },
{"rsubik",INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x34000000, OPCODE_MASK_H, rsubik, arithmetic_inst },
{"addikc",INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x38000000, OPCODE_MASK_H, addikc, arithmetic_inst },
{"rsubikc",INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x3C000000, OPCODE_MASK_H, rsubikc, arithmetic_inst },
{"mul", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x40000000, OPCODE_MASK_H4, mul, mult_inst },
{"mulh", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x40000001, OPCODE_MASK_H4, mulh, mult_inst },
{"mulhu", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x40000003, OPCODE_MASK_H4, mulhu, mult_inst },
{"mulhsu",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x40000002, OPCODE_MASK_H4, mulhsu, mult_inst },
{"idiv", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x48000000, OPCODE_MASK_H4, idiv, div_inst },
{"idivu", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x48000002, OPCODE_MASK_H4, idivu, div_inst },
{"bsll", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x44000400, OPCODE_MASK_H3, bsll, barrel_shift_inst },
{"bsra", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x44000200, OPCODE_MASK_H3, bsra, barrel_shift_inst },
{"bsrl", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x44000000, OPCODE_MASK_H3, bsrl, barrel_shift_inst },
{"get", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C000000, OPCODE_MASK_H32, get, anyware_inst },
{"put", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C008000, OPCODE_MASK_H32, put, anyware_inst },
{"nget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C004000, OPCODE_MASK_H32, nget, anyware_inst },
{"nput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00C000, OPCODE_MASK_H32, nput, anyware_inst },
{"cget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C002000, OPCODE_MASK_H32, cget, anyware_inst },
{"cput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00A000, OPCODE_MASK_H32, cput, anyware_inst },
{"ncget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C006000, OPCODE_MASK_H32, ncget, anyware_inst },
{"ncput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00E000, OPCODE_MASK_H32, ncput, anyware_inst },
{"muli", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x60000000, OPCODE_MASK_H, muli, mult_inst },
{"bslli", INST_TYPE_RD_R1_IMM5, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x64000400, OPCODE_MASK_H3, bslli, barrel_shift_inst },
{"bsrai", INST_TYPE_RD_R1_IMM5, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x64000200, OPCODE_MASK_H3, bsrai, barrel_shift_inst },
{"bsrli", INST_TYPE_RD_R1_IMM5, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x64000000, OPCODE_MASK_H3, bsrli, barrel_shift_inst },
{"or", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x80000000, OPCODE_MASK_H4, or, logical_inst },
{"and", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x84000000, OPCODE_MASK_H4, and, logical_inst },
{"xor", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x88000000, OPCODE_MASK_H4, xor, logical_inst },
{"andn", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x8C000000, OPCODE_MASK_H4, andn, logical_inst },
{"pcmpbf",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x80000400, OPCODE_MASK_H4, pcmpbf, logical_inst },
{"pcmpbc",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x84000400, OPCODE_MASK_H4, pcmpbc, logical_inst },
{"pcmpeq",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x88000400, OPCODE_MASK_H4, pcmpeq, logical_inst },
{"pcmpne",INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x8C000400, OPCODE_MASK_H4, pcmpne, logical_inst },
{"sra", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000001, OPCODE_MASK_H34, sra, logical_inst },
{"src", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000021, OPCODE_MASK_H34, src, logical_inst },
{"srl", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000041, OPCODE_MASK_H34, srl, logical_inst },
{"sext8", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000060, OPCODE_MASK_H34, sext8, logical_inst },
{"sext16",INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000061, OPCODE_MASK_H34, sext16, logical_inst },
{"wic", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000068, OPCODE_MASK_H34B, wic, special_inst },
{"wdc", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000064, OPCODE_MASK_H34B, wdc, special_inst },
{"wdc.clear", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000066, OPCODE_MASK_H34B, wdcclear, special_inst },
{"wdc.ext.clear", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000466, OPCODE_MASK_H35B, wdcextclear, special_inst },
{"wdc.flush", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000074, OPCODE_MASK_H34B, wdcflush, special_inst },
{"wdc.ext.flush", INST_TYPE_R1_R2_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x90000476, OPCODE_MASK_H35B, wdcextflush, special_inst },
{"mts", INST_TYPE_SPECIAL_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_MTS, 0x9400C000, OPCODE_MASK_H13S, mts, special_inst },
{"mfs", INST_TYPE_RD_SPECIAL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_MFS, 0x94008000, OPCODE_MASK_H23S, mfs, special_inst },
{"br", INST_TYPE_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x98000000, OPCODE_MASK_H124, br, branch_inst },
{"brd", INST_TYPE_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x98100000, OPCODE_MASK_H124, brd, branch_inst },
{"brld", INST_TYPE_RD_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x98140000, OPCODE_MASK_H24, brld, branch_inst },
{"bra", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x98080000, OPCODE_MASK_H124, bra, branch_inst },
{"brad", INST_TYPE_R2, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x98180000, OPCODE_MASK_H124, brad, branch_inst },
{"brald", INST_TYPE_RD_R2, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x981C0000, OPCODE_MASK_H24, brald, branch_inst },
{"brk", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x980C0000, OPCODE_MASK_H24, microblaze_brk, branch_inst },
{"beq", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9C000000, OPCODE_MASK_H14, beq, branch_inst },
{"beqd", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9E000000, OPCODE_MASK_H14, beqd, branch_inst },
{"bne", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9C200000, OPCODE_MASK_H14, bne, branch_inst },
{"bned", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9E200000, OPCODE_MASK_H14, bned, branch_inst },
{"blt", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9C400000, OPCODE_MASK_H14, blt, branch_inst },
{"bltd", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9E400000, OPCODE_MASK_H14, bltd, branch_inst },
{"ble", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9C600000, OPCODE_MASK_H14, ble, branch_inst },
{"bled", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9E600000, OPCODE_MASK_H14, bled, branch_inst },
{"bgt", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9C800000, OPCODE_MASK_H14, bgt, branch_inst },
{"bgtd", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9E800000, OPCODE_MASK_H14, bgtd, branch_inst },
{"bge", INST_TYPE_R1_R2, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9CA00000, OPCODE_MASK_H14, bge, branch_inst },
{"bged", INST_TYPE_R1_R2, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x9EA00000, OPCODE_MASK_H14, bged, branch_inst },
{"ori", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xA0000000, OPCODE_MASK_H, ori, logical_inst },
{"andi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xA4000000, OPCODE_MASK_H, andi, logical_inst },
{"xori", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xA8000000, OPCODE_MASK_H, xori, logical_inst },
{"andni", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xAC000000, OPCODE_MASK_H, andni, logical_inst },
{"imm", INST_TYPE_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB0000000, OPCODE_MASK_H12, imm, immediate_inst },
{"rtsd", INST_TYPE_R1_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB6000000, OPCODE_MASK_H1, rtsd, return_inst },
{"rtid", INST_TYPE_R1_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB6200000, OPCODE_MASK_H1, rtid, return_inst },
{"rtbd", INST_TYPE_R1_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB6400000, OPCODE_MASK_H1, rtbd, return_inst },
{"rted", INST_TYPE_R1_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB6800000, OPCODE_MASK_H1, rted, return_inst },
{"bri", INST_TYPE_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8000000, OPCODE_MASK_H12, bri, branch_inst },
{"brid", INST_TYPE_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8100000, OPCODE_MASK_H12, brid, branch_inst },
{"brlid", INST_TYPE_RD_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8140000, OPCODE_MASK_H2, brlid, branch_inst },
{"brai", INST_TYPE_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8080000, OPCODE_MASK_H12, brai, branch_inst },
{"braid", INST_TYPE_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8180000, OPCODE_MASK_H12, braid, branch_inst },
{"bralid",INST_TYPE_RD_IMM, INST_NO_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB81C0000, OPCODE_MASK_H2, bralid, branch_inst },
{"brki", INST_TYPE_RD_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB80C0000, OPCODE_MASK_H2, brki, branch_inst },
{"beqi", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBC000000, OPCODE_MASK_H1, beqi, branch_inst },
{"beqid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBE000000, OPCODE_MASK_H1, beqid, branch_inst },
{"bnei", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBC200000, OPCODE_MASK_H1, bnei, branch_inst },
{"bneid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBE200000, OPCODE_MASK_H1, bneid, branch_inst },
{"blti", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBC400000, OPCODE_MASK_H1, blti, branch_inst },
{"bltid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBE400000, OPCODE_MASK_H1, bltid, branch_inst },
{"blei", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBC600000, OPCODE_MASK_H1, blei, branch_inst },
{"bleid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBE600000, OPCODE_MASK_H1, bleid, branch_inst },
{"bgti", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBC800000, OPCODE_MASK_H1, bgti, branch_inst },
{"bgtid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBE800000, OPCODE_MASK_H1, bgtid, branch_inst },
{"bgei", INST_TYPE_R1_IMM, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBCA00000, OPCODE_MASK_H1, bgei, branch_inst },
{"bgeid", INST_TYPE_R1_IMM, INST_PC_OFFSET, DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBEA00000, OPCODE_MASK_H1, bgeid, branch_inst },
{"lbu", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC0000000, OPCODE_MASK_H4, lbu, memory_load_inst },
{"lbur", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC0000200, OPCODE_MASK_H4, lbur, memory_load_inst },
{"lhu", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC4000000, OPCODE_MASK_H4, lhu, memory_load_inst },
{"lhur", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC4000200, OPCODE_MASK_H4, lhur, memory_load_inst },
{"lw", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC8000000, OPCODE_MASK_H4, lw, memory_load_inst },
{"lwr", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC8000200, OPCODE_MASK_H4, lwr, memory_load_inst },
{"lwx", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xC8000400, OPCODE_MASK_H4, lwx, memory_load_inst },
{"sb", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD0000000, OPCODE_MASK_H4, sb, memory_store_inst },
{"sbr", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD0000200, OPCODE_MASK_H4, sbr, memory_store_inst },
{"sh", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD4000000, OPCODE_MASK_H4, sh, memory_store_inst },
{"shr", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD4000200, OPCODE_MASK_H4, shr, memory_store_inst },
{"sw", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD8000000, OPCODE_MASK_H4, sw, memory_store_inst },
{"swr", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD8000200, OPCODE_MASK_H4, swr, memory_store_inst },
{"swx", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xD8000400, OPCODE_MASK_H4, swx, memory_store_inst },
{"lbui", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xE0000000, OPCODE_MASK_H, lbui, memory_load_inst },
{"lhui", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xE4000000, OPCODE_MASK_H, lhui, memory_load_inst },
{"lwi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xE8000000, OPCODE_MASK_H, lwi, memory_load_inst },
{"sbi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xF0000000, OPCODE_MASK_H, sbi, memory_store_inst },
{"shi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xF4000000, OPCODE_MASK_H, shi, memory_store_inst },
{"swi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xF8000000, OPCODE_MASK_H, swi, memory_store_inst },
{"nop", INST_TYPE_NONE, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x80000000, OPCODE_MASK_H1234, invalid_inst, logical_inst }, /* translates to or r0, r0, r0. */
{"la", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x30000000, OPCODE_MASK_H, invalid_inst, arithmetic_inst }, /* la translates to addik. */
{"tuqula",INST_TYPE_RD, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x3000002A, OPCODE_MASK_H, invalid_inst, arithmetic_inst }, /* tuqula rd translates to addik rd, r0, 42. */
{"not", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xA800FFFF, OPCODE_MASK_H34, invalid_inst, logical_inst }, /* not translates to xori rd,ra,-1. */
{"neg", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x04000000, OPCODE_MASK_H, invalid_inst, arithmetic_inst }, /* neg translates to rsub rd, ra, r0. */
{"rtb", INST_TYPE_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB6000004, OPCODE_MASK_H1, invalid_inst, return_inst }, /* rtb translates to rts rd, 4. */
{"sub", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x04000000, OPCODE_MASK_H, invalid_inst, arithmetic_inst }, /* sub translates to rsub rd, rb, ra. */
{"lmi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xE8000000, OPCODE_MASK_H, invalid_inst, memory_load_inst },
{"smi", INST_TYPE_RD_R1_IMM, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xF8000000, OPCODE_MASK_H, invalid_inst, memory_store_inst },
{"msrset",INST_TYPE_RD_IMM15, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x94100000, OPCODE_MASK_H23N, msrset, special_inst },
{"msrclr",INST_TYPE_RD_IMM15, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x94110000, OPCODE_MASK_H23N, msrclr, special_inst },
{"fadd", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000000, OPCODE_MASK_H4, fadd, arithmetic_inst },
{"frsub", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000080, OPCODE_MASK_H4, frsub, arithmetic_inst },
{"fmul", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000100, OPCODE_MASK_H4, fmul, arithmetic_inst },
{"fdiv", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000180, OPCODE_MASK_H4, fdiv, arithmetic_inst },
{"fcmp.lt", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000210, OPCODE_MASK_H4, fcmp_lt, arithmetic_inst },
{"fcmp.eq", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000220, OPCODE_MASK_H4, fcmp_eq, arithmetic_inst },
{"fcmp.le", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000230, OPCODE_MASK_H4, fcmp_le, arithmetic_inst },
{"fcmp.gt", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000240, OPCODE_MASK_H4, fcmp_gt, arithmetic_inst },
{"fcmp.ne", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000250, OPCODE_MASK_H4, fcmp_ne, arithmetic_inst },
{"fcmp.ge", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000260, OPCODE_MASK_H4, fcmp_ge, arithmetic_inst },
{"fcmp.un", INST_TYPE_RD_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000200, OPCODE_MASK_H4, fcmp_un, arithmetic_inst },
{"flt", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000280, OPCODE_MASK_H4, flt, arithmetic_inst },
{"fint", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000300, OPCODE_MASK_H4, fint, arithmetic_inst },
{"fsqrt", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x58000380, OPCODE_MASK_H4, fsqrt, arithmetic_inst },
{"tget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C001000, OPCODE_MASK_H32, tget, anyware_inst },
{"tcget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C003000, OPCODE_MASK_H32, tcget, anyware_inst },
{"tnget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C005000, OPCODE_MASK_H32, tnget, anyware_inst },
{"tncget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C007000, OPCODE_MASK_H32, tncget, anyware_inst },
{"tput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C009000, OPCODE_MASK_H32, tput, anyware_inst },
{"tcput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00B000, OPCODE_MASK_H32, tcput, anyware_inst },
{"tnput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00D000, OPCODE_MASK_H32, tnput, anyware_inst },
{"tncput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00F000, OPCODE_MASK_H32, tncput, anyware_inst },
{"eget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C000400, OPCODE_MASK_H32, eget, anyware_inst },
{"ecget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C002400, OPCODE_MASK_H32, ecget, anyware_inst },
{"neget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C004400, OPCODE_MASK_H32, neget, anyware_inst },
{"necget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C006400, OPCODE_MASK_H32, necget, anyware_inst },
{"eput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C008400, OPCODE_MASK_H32, eput, anyware_inst },
{"ecput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00A400, OPCODE_MASK_H32, ecput, anyware_inst },
{"neput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00C400, OPCODE_MASK_H32, neput, anyware_inst },
{"necput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00E400, OPCODE_MASK_H32, necput, anyware_inst },
{"teget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C001400, OPCODE_MASK_H32, teget, anyware_inst },
{"tecget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C003400, OPCODE_MASK_H32, tecget, anyware_inst },
{"tneget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C005400, OPCODE_MASK_H32, tneget, anyware_inst },
{"tnecget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C007400, OPCODE_MASK_H32, tnecget, anyware_inst },
{"teput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C009400, OPCODE_MASK_H32, teput, anyware_inst },
{"tecput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00B400, OPCODE_MASK_H32, tecput, anyware_inst },
{"tneput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00D400, OPCODE_MASK_H32, tneput, anyware_inst },
{"tnecput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00F400, OPCODE_MASK_H32, tnecput, anyware_inst },
{"aget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C000800, OPCODE_MASK_H32, aget, anyware_inst },
{"caget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C002800, OPCODE_MASK_H32, caget, anyware_inst },
{"naget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C004800, OPCODE_MASK_H32, naget, anyware_inst },
{"ncaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C006800, OPCODE_MASK_H32, ncaget, anyware_inst },
{"aput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C008800, OPCODE_MASK_H32, aput, anyware_inst },
{"caput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00A800, OPCODE_MASK_H32, caput, anyware_inst },
{"naput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00C800, OPCODE_MASK_H32, naput, anyware_inst },
{"ncaput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00E800, OPCODE_MASK_H32, ncaput, anyware_inst },
{"taget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C001800, OPCODE_MASK_H32, taget, anyware_inst },
{"tcaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C003800, OPCODE_MASK_H32, tcaget, anyware_inst },
{"tnaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C005800, OPCODE_MASK_H32, tnaget, anyware_inst },
{"tncaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C007800, OPCODE_MASK_H32, tncaget, anyware_inst },
{"taput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C009800, OPCODE_MASK_H32, taput, anyware_inst },
{"tcaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00B800, OPCODE_MASK_H32, tcaput, anyware_inst },
{"tnaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00D800, OPCODE_MASK_H32, tnaput, anyware_inst },
{"tncaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00F800, OPCODE_MASK_H32, tncaput, anyware_inst },
{"eaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C000C00, OPCODE_MASK_H32, eget, anyware_inst },
{"ecaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C002C00, OPCODE_MASK_H32, ecget, anyware_inst },
{"neaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C004C00, OPCODE_MASK_H32, neget, anyware_inst },
{"necaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C006C00, OPCODE_MASK_H32, necget, anyware_inst },
{"eaput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C008C00, OPCODE_MASK_H32, eput, anyware_inst },
{"ecaput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00AC00, OPCODE_MASK_H32, ecput, anyware_inst },
{"neaput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00CC00, OPCODE_MASK_H32, neput, anyware_inst },
{"necaput", INST_TYPE_R1_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00EC00, OPCODE_MASK_H32, necput, anyware_inst },
{"teaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C001C00, OPCODE_MASK_H32, teaget, anyware_inst },
{"tecaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C003C00, OPCODE_MASK_H32, tecaget, anyware_inst },
{"tneaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C005C00, OPCODE_MASK_H32, tneaget, anyware_inst },
{"tnecaget", INST_TYPE_RD_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C007C00, OPCODE_MASK_H32, tnecaget, anyware_inst },
{"teaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C009C00, OPCODE_MASK_H32, teaput, anyware_inst },
{"tecaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00BC00, OPCODE_MASK_H32, tecaput, anyware_inst },
{"tneaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00DC00, OPCODE_MASK_H32, tneaput, anyware_inst },
{"tnecaput", INST_TYPE_RFSL, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x6C00FC00, OPCODE_MASK_H32, tnecaput, anyware_inst },
{"getd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000000, OPCODE_MASK_H34C, getd, anyware_inst },
{"tgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000080, OPCODE_MASK_H34C, tgetd, anyware_inst },
{"cgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000100, OPCODE_MASK_H34C, cgetd, anyware_inst },
{"tcgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000180, OPCODE_MASK_H34C, tcgetd, anyware_inst },
{"ngetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000200, OPCODE_MASK_H34C, ngetd, anyware_inst },
{"tngetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000280, OPCODE_MASK_H34C, tngetd, anyware_inst },
{"ncgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000300, OPCODE_MASK_H34C, ncgetd, anyware_inst },
{"tncgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000380, OPCODE_MASK_H34C, tncgetd, anyware_inst },
{"putd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000400, OPCODE_MASK_H34C, putd, anyware_inst },
{"tputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000480, OPCODE_MASK_H34C, tputd, anyware_inst },
{"cputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000500, OPCODE_MASK_H34C, cputd, anyware_inst },
{"tcputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000580, OPCODE_MASK_H34C, tcputd, anyware_inst },
{"nputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000600, OPCODE_MASK_H34C, nputd, anyware_inst },
{"tnputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000680, OPCODE_MASK_H34C, tnputd, anyware_inst },
{"ncputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000700, OPCODE_MASK_H34C, ncputd, anyware_inst },
{"tncputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000780, OPCODE_MASK_H34C, tncputd, anyware_inst },
{"egetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000020, OPCODE_MASK_H34C, egetd, anyware_inst },
{"tegetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0000A0, OPCODE_MASK_H34C, tegetd, anyware_inst },
{"ecgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000120, OPCODE_MASK_H34C, ecgetd, anyware_inst },
{"tecgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0001A0, OPCODE_MASK_H34C, tecgetd, anyware_inst },
{"negetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000220, OPCODE_MASK_H34C, negetd, anyware_inst },
{"tnegetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0002A0, OPCODE_MASK_H34C, tnegetd, anyware_inst },
{"necgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000320, OPCODE_MASK_H34C, necgetd, anyware_inst },
{"tnecgetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0003A0, OPCODE_MASK_H34C, tnecgetd, anyware_inst },
{"eputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000420, OPCODE_MASK_H34C, eputd, anyware_inst },
{"teputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0004A0, OPCODE_MASK_H34C, teputd, anyware_inst },
{"ecputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000520, OPCODE_MASK_H34C, ecputd, anyware_inst },
{"tecputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0005A0, OPCODE_MASK_H34C, tecputd, anyware_inst },
{"neputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000620, OPCODE_MASK_H34C, neputd, anyware_inst },
{"tneputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0006A0, OPCODE_MASK_H34C, tneputd, anyware_inst },
{"necputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000720, OPCODE_MASK_H34C, necputd, anyware_inst },
{"tnecputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0007A0, OPCODE_MASK_H34C, tnecputd, anyware_inst },
{"agetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000040, OPCODE_MASK_H34C, agetd, anyware_inst },
{"tagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0000C0, OPCODE_MASK_H34C, tagetd, anyware_inst },
{"cagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000140, OPCODE_MASK_H34C, cagetd, anyware_inst },
{"tcagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0001C0, OPCODE_MASK_H34C, tcagetd, anyware_inst },
{"nagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000240, OPCODE_MASK_H34C, nagetd, anyware_inst },
{"tnagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0002C0, OPCODE_MASK_H34C, tnagetd, anyware_inst },
{"ncagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000340, OPCODE_MASK_H34C, ncagetd, anyware_inst },
{"tncagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0003C0, OPCODE_MASK_H34C, tncagetd, anyware_inst },
{"aputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000440, OPCODE_MASK_H34C, aputd, anyware_inst },
{"taputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0004C0, OPCODE_MASK_H34C, taputd, anyware_inst },
{"caputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000540, OPCODE_MASK_H34C, caputd, anyware_inst },
{"tcaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0005C0, OPCODE_MASK_H34C, tcaputd, anyware_inst },
{"naputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000640, OPCODE_MASK_H34C, naputd, anyware_inst },
{"tnaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0006C0, OPCODE_MASK_H34C, tnaputd, anyware_inst },
{"ncaputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000740, OPCODE_MASK_H34C, ncaputd, anyware_inst },
{"tncaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0007C0, OPCODE_MASK_H34C, tncaputd, anyware_inst },
{"eagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000060, OPCODE_MASK_H34C, eagetd, anyware_inst },
{"teagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0000E0, OPCODE_MASK_H34C, teagetd, anyware_inst },
{"ecagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000160, OPCODE_MASK_H34C, ecagetd, anyware_inst },
{"tecagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0001E0, OPCODE_MASK_H34C, tecagetd, anyware_inst },
{"neagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000260, OPCODE_MASK_H34C, neagetd, anyware_inst },
{"tneagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0002E0, OPCODE_MASK_H34C, tneagetd, anyware_inst },
{"necagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000360, OPCODE_MASK_H34C, necagetd, anyware_inst },
{"tnecagetd", INST_TYPE_RD_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0003E0, OPCODE_MASK_H34C, tnecagetd, anyware_inst },
{"eaputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000460, OPCODE_MASK_H34C, eaputd, anyware_inst },
{"teaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0004E0, OPCODE_MASK_H34C, teaputd, anyware_inst },
{"ecaputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000560, OPCODE_MASK_H34C, ecaputd, anyware_inst },
{"tecaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0005E0, OPCODE_MASK_H34C, tecaputd, anyware_inst },
{"neaputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000660, OPCODE_MASK_H34C, neaputd, anyware_inst },
{"tneaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0006E0, OPCODE_MASK_H34C, tneaputd, anyware_inst },
{"necaputd", INST_TYPE_R1_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C000760, OPCODE_MASK_H34C, necaputd, anyware_inst },
{"tnecaputd", INST_TYPE_R2, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x4C0007E0, OPCODE_MASK_H34C, tnecaputd, anyware_inst },
{"clz", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x900000E0, OPCODE_MASK_H34, clz, special_inst },
{"mbar", INST_TYPE_IMM5, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xB8020004, OPCODE_MASK_HN, mbar, special_inst },
{"sleep", INST_TYPE_NONE, INST_PC_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0xBA020004, OPCODE_MASK_HN, invalid_inst, special_inst }, /* translates to mbar 16. */
{"swapb", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x900001E0, OPCODE_MASK_H4, swapb, arithmetic_inst },
{"swaph", INST_TYPE_RD_R1, INST_NO_OFFSET, NO_DELAY_SLOT, IMMVAL_MASK_NON_SPECIAL, 0x900001E2, OPCODE_MASK_H4, swaph, arithmetic_inst },
{NULL, 0, 0, 0, 0, 0, 0, 0, 0},
};
/* Prefix for register names. */
char register_prefix[] = "r";
char special_register_prefix[] = "spr";
char fsl_register_prefix[] = "rfsl";
char pvr_register_prefix[] = "rpvr";
/* #defines for valid immediate range. */
#define MIN_IMM ((int) 0x80000000)
#define MAX_IMM ((int) 0x7fffffff)
#define MIN_IMM15 ((int) 0x0000)
#define MAX_IMM15 ((int) 0x7fff)
#define MIN_IMM5 ((int) 0x00000000)
#define MAX_IMM5 ((int) 0x0000001f)
#endif /* MICROBLAZE_OPC */
@@ -1,148 +0,0 @@
/* microblaze-opcm.h -- Header used in microblaze-opc.h
Copyright 2009 Free Software Foundation, Inc.
This file is part of the GNU opcodes library.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this file; see the file COPYING. If not, write to the
Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#ifndef MICROBLAZE_OPCM
#define MICROBLAZE_OPCM
enum microblaze_instr
{
add, rsub, addc, rsubc, addk, rsubk, addkc, rsubkc, clz, cmp, cmpu,
addi, rsubi, addic, rsubic, addik, rsubik, addikc, rsubikc, mul,
mulh, mulhu, mulhsu,swapb,swaph,
idiv, idivu, bsll, bsra, bsrl, get, put, nget, nput, cget, cput,
ncget, ncput, muli, bslli, bsrai, bsrli, mului, or, and, xor,
andn, pcmpbf, pcmpbc, pcmpeq, pcmpne, sra, src, srl, sext8, sext16,
wic, wdc, wdcclear, wdcextclear, wdcflush, wdcextflush, mts, mfs, mbar,
br, brd, brld, bra, brad, brald, microblaze_brk, beq, beqd, bne, bned,
blt, bltd, ble, bled, bgt, bgtd, bge, bged, ori, andi, xori, andni,
imm, rtsd, rtid, rtbd, rted, bri, brid, brlid, brai, braid, bralid,
brki, beqi, beqid, bnei, bneid, blti, bltid, blei, bleid, bgti,
bgtid, bgei, bgeid, lbu, lbur, lhu, lhur, lw, lwr, lwx, sb, sbr, sh,
shr, sw, swr, swx, lbui, lhui, lwi,
sbi, shi, swi, msrset, msrclr, tuqula, fadd, frsub, fmul, fdiv,
fcmp_lt, fcmp_eq, fcmp_le, fcmp_gt, fcmp_ne, fcmp_ge, fcmp_un, flt,
fint, fsqrt,
tget, tcget, tnget, tncget, tput, tcput, tnput, tncput,
eget, ecget, neget, necget, eput, ecput, neput, necput,
teget, tecget, tneget, tnecget, teput, tecput, tneput, tnecput,
aget, caget, naget, ncaget, aput, caput, naput, ncaput,
taget, tcaget, tnaget, tncaget, taput, tcaput, tnaput, tncaput,
eaget, ecaget, neaget, necaget, eaput, ecaput, neaput, necaput,
teaget, tecaget, tneaget, tnecaget, teaput, tecaput, tneaput, tnecaput,
getd, tgetd, cgetd, tcgetd, ngetd, tngetd, ncgetd, tncgetd,
putd, tputd, cputd, tcputd, nputd, tnputd, ncputd, tncputd,
egetd, tegetd, ecgetd, tecgetd, negetd, tnegetd, necgetd, tnecgetd,
eputd, teputd, ecputd, tecputd, neputd, tneputd, necputd, tnecputd,
agetd, tagetd, cagetd, tcagetd, nagetd, tnagetd, ncagetd, tncagetd,
aputd, taputd, caputd, tcaputd, naputd, tnaputd, ncaputd, tncaputd,
eagetd, teagetd, ecagetd, tecagetd, neagetd, tneagetd, necagetd, tnecagetd,
eaputd, teaputd, ecaputd, tecaputd, neaputd, tneaputd, necaputd, tnecaputd,
invalid_inst
};
enum microblaze_instr_type
{
arithmetic_inst, logical_inst, mult_inst, div_inst, branch_inst,
return_inst, immediate_inst, special_inst, memory_load_inst,
memory_store_inst, barrel_shift_inst, anyware_inst
};
#define INST_WORD_SIZE 4
/* Gen purpose regs go from 0 to 31. */
/* Mask is reg num - max_reg_num, ie reg_num - 32 in this case. */
#define REG_PC_MASK 0x8000
#define REG_MSR_MASK 0x8001
#define REG_EAR_MASK 0x8003
#define REG_ESR_MASK 0x8005
#define REG_FSR_MASK 0x8007
#define REG_BTR_MASK 0x800b
#define REG_EDR_MASK 0x800d
#define REG_PVR_MASK 0xa000
#define REG_SLR_MASK 0x8800
#define REG_SHR_MASK 0x8802
#define REG_PID_MASK 0x9000
#define REG_ZPR_MASK 0x9001
#define REG_TLBX_MASK 0x9002
#define REG_TLBLO_MASK 0x9003
#define REG_TLBHI_MASK 0x9004
#define REG_TLBSX_MASK 0x9005
#define MIN_REGNUM 0
#define MAX_REGNUM 31
#define MIN_PVR_REGNUM 0
#define MAX_PVR_REGNUM 15
#define REG_PC 32 /* PC. */
#define REG_MSR 33 /* Machine status reg. */
#define REG_EAR 35 /* Exception reg. */
#define REG_ESR 37 /* Exception reg. */
#define REG_FSR 39 /* FPU Status reg. */
#define REG_BTR 43 /* Branch Target reg. */
#define REG_EDR 45 /* Exception reg. */
#define REG_SHR 50 /* Stack High reg. */
#define REG_SLR 51 /* Stack Low reg. */
#define REG_PVR 40960 /* Program Verification reg. */
#define REG_PID 36864 /* MMU: Process ID reg. */
#define REG_ZPR 36865 /* MMU: Zone Protect reg. */
#define REG_TLBX 36866 /* MMU: TLB Index reg. */
#define REG_TLBLO 36867 /* MMU: TLB Low reg. */
#define REG_TLBHI 36868 /* MMU: TLB High reg. */
#define REG_TLBSX 36869 /* MMU: TLB Search Index reg. */
/* Alternate names for gen purpose regs. */
#define REG_SP 1 /* stack pointer. */
#define REG_ROSDP 2 /* read-only small data pointer. */
#define REG_RWSDP 13 /* read-write small data pointer. */
/* Assembler Register - Used in Delay Slot Optimization. */
#define REG_AS 18
#define REG_ZERO 0
#define RD_LOW 21 /* Low bit for RD. */
#define RA_LOW 16 /* Low bit for RA. */
#define RB_LOW 11 /* Low bit for RB. */
#define IMM_LOW 0 /* Low bit for immediate. */
#define IMM_MBAR 21 /* low bit for mbar instruction. */
#define RD_MASK 0x03E00000
#define RA_MASK 0x001F0000
#define RB_MASK 0x0000F800
#define IMM_MASK 0x0000FFFF
/* Imm mask for barrel shifts. */
#define IMM5_MASK 0x0000001F
/* Imm mask for mbar. */
#define IMM5_MBAR_MASK 0x03E00000
/* FSL imm mask for get, put instructions. */
#define RFSL_MASK 0x000000F
/* Imm mask for msrset, msrclr instructions. */
#define IMM15_MASK 0x00007FFF
#endif /* MICROBLAZE-OPCM */
+4
View File
@@ -0,0 +1,4 @@
*.o
*.so
*.dylib
*.dll
+36
View File
@@ -0,0 +1,36 @@
# Memory Slice (.msl) plugins for radare2
#
# io_msl read a .msl as a process address space (msl://)
# bin_msl parse a .msl: maps -> sections, regs -> a thread context
# debug_msl static debug backend over a .msl, incl. reverse exec (dsb/aesb)
# core_msl producer: "dgm"/"dgma" write a .msl from a live debug session
LIBEXT?=$(shell r2 -H R2_LIBEXT)
R2PM_PLUGDIR?=$(shell r2 -H R2_USER_PLUGINS)
CFLAGS+=-fPIC -shared
CFLAGS+=$(shell pkg-config --cflags r_core)
LDFLAGS+=$(shell pkg-config --libs r_core)
PLUGINS=io_msl.$(LIBEXT) bin_msl.$(LIBEXT) debug_msl.$(LIBEXT) core_msl.$(LIBEXT)
all: $(PLUGINS)
%.$(LIBEXT): %.c
$(CC) -o $@ $< $(CFLAGS) $(LDFLAGS)
install: all
mkdir -p "$(R2PM_PLUGDIR)"
for a in $(PLUGINS) ; do rm -f "$(R2PM_PLUGDIR)/$$a" ; cp -f "$$a" "$(R2PM_PLUGDIR)" ; \
codesign -f -s - "$(R2PM_PLUGDIR)/$$a" 2>/dev/null || true ; done
uninstall:
for a in $(PLUGINS) ; do rm -f "$(R2PM_PLUGDIR)/$$a" ; done
user-install: install
user-uninstall: uninstall
clean:
rm -f $(PLUGINS) *.o
.PHONY: all install uninstall user-install user-uninstall clean
+207
View File
@@ -0,0 +1,207 @@
# Memory Slice (.msl) plugins for radare2
radare2 can open and emulate **Memory Slice** (`.msl`) process memory dumps
produced by [memslicer](https://github.com/MemorySlice/memslicer). A slice is a
static snapshot of a single process: its virtual address space (with per-page
acquisition state) plus, when captured, the per-thread CPU registers. Because a
slice is static, "execution" is driven by **emulation (ESIL)**.
This directory ships the support as loadable radare2 plugins.
| Plugin | File | Role |
|--------|------|------|
| `io.msl` | `io_msl.c` | Exposes the virtual address space (`msl://`), and acts as a debug IO so a slice can be opened in debug mode without spawning a process. Verifies the SHA-256 integrity chain on open. |
| `bin.msl` | `bin_msl.c` | Parses the slice as a CORE object: architecture/bits/OS, entrypoint (the captured PC), one memory map per contiguous run of captured pages, and symbols/libraries from the captured modules (incl. PE export tables, so call targets are named). |
| `debug.msl` | `debug_msl.c` | Emulated debug backend: seeds the register file from the captured Thread Context and steps via ESIL, including reverse execution (`dsb`/`aesb`). Lists and selects captured threads (`dpt` / `dpt=<tid>`). |
| `core.msl` | `core_msl.c` | Producer: adds `dgm`/`dgma` to write a `.msl` from a live debug session. |
**Compression:** lz4-compressed regions are decoded transparently by the
`io.msl` plugin (open the slice as `msl://…`). zstd-compressed regions cannot
be decoded — radare2 has no zstd — and read back as the fill byte with a
warning; recapture with `memslicer … -c lz4` (or `-c none`), or use the
Python `memslicer-emu` tool, which handles both codecs. The `bin.msl` map path
(`r2 dump.msl`) cannot express compressed regions as file maps either, so for
compressed slices use the `msl://` URI. **Encrypted** slices are not supported.
Failed and unmapped pages (and unbacked addresses) read back as the fill byte.
## Build & install
Requires a radare2 development install (`pkg-config --exists r_core`).
```
make
make install # installs the 4 plugins into R2_USER_PLUGINS
```
`make install` copies the `.so`/`.dylib` into `$(R2PM_PLUGDIR)` (the per-user
plugin directory reported by `r2 -H R2_USER_PLUGINS`). Verify they loaded:
```
$ r2 -qc 'Lo~msl; Lb~msl; Ld~msl; Lc~msl' --
```
## Inspecting a slice (analysis)
Open the file normally — `bin.msl` provides the maps, architecture and
entrypoint:
```
$ r2 dump.msl
[0x00401000]> i~type,arch,bits # CORE, x86, 64
[0x00401000]> iS # memory maps (one per captured run)
[0x00401000]> il # captured modules (libraries)
[0x00401000]> is # symbols: one per module + PE exports
[0x00401000]> s entry0; pd 8 # disassemble from the captured PC
[0x00401000]> px @ 0x7ffff000 # read captured memory
```
Modules captured in the slice (ModuleEntry blocks) become libraries (`il`) and
symbols (`is`): one symbol at each module base plus, for PE images whose headers
are captured, one `FUNC` symbol per exported function at its address. radare2
then names call targets — e.g. `call sym.kernel32.dll_CreateFileW` — instead of
bare addresses. (ELF `.dynsym` export resolution is handled by the Python
`memslicer` tools for now, not yet by this plugin.)
For a raw virtual-address view without bin metadata, use the `msl://` URI, which
goes straight through `io.msl`:
```
$ r2 msl://dump.msl
[0x00000000]> px @ 0x401000
```
On open, `io.msl` recomputes the per-block SHA-256 chain and the End-of-Capture
`FileHash`; a mismatch (truncated or tampered slice) prints a warning but the
slice is still indexed and readable.
## Emulated debugging (advancing execution)
Open the slice as an emulated debug target. `io.msl` is a debug IO, so this does
**not** spawn a process:
```
$ r2 -D msl -d msl://dump.msl
```
On attach the backend sets the architecture from the slice header, initializes
the ESIL VM, enables `io.cache` (so emulated writes don't touch the dump) and
**seeds every register from the Current thread's Thread Context**, then seeks to
the captured program counter.
Single-step and inspect with the usual debugger commands:
```
[0x00401000]> dr rip rax rbx # registers as captured
[0x00401000]> ds # step one instruction
[0x00401000]> dr rax # observe the change
[0x00401000]> dso # step over
[0x00401000]> dsb # step BACK (reverse / time-travel)
[0x00401000]> db 0x401040 ; dc # run to a breakpoint
[0x00401000]> dr rcx=0x10 # modify a register
[0x00401000]> pd 4 @ rip # disassemble at the current PC
```
**Reverse execution.** The backend records ESIL step history
(`esil.maxbacksteps`, default 256), so `dsb` (or `aesb`) steps execution
*backwards*, reverting both the program counter and register/memory changes —
useful for walking back from a fault or an interesting state in the snapshot.
**Multiple threads.** A slice can capture more than one thread (one Thread
Context block each). The backend seeds the Current thread by default, but every
captured thread is listed and selectable:
```
[0x00401000]> dpt # list captured threads (PC, '*' = active)
* 7 r msl (0x401000)
- 8 s msl (0x401100)
[0x00401000]> dpt=8 # re-seed from thread 8 and seek to its PC
[0x00401100]> dr rip rax # now thread 8's captured registers
```
`dpt=<tid>` re-seeds the register file from that thread's Thread Context and
seeks to its captured PC; stepping then continues from there. Reverse history
resets at the switch (you cannot step back across into another thread). The
status letter comes from the block's `ThreadState` (`r` running, `S`
sleeping/waiting, `s` stopped/halted, `z` zombie); when the producer left it
Unknown, the Current thread shows `r` and the rest `s`.
Register names follow spec 1.1.1 (Table 19g). Slices from 1.1.0 producers may
still name the ARM64 frame/link registers `fp`/`lr`; the backend maps them to
`x29`/`x30` (and `cpsr` to `pstate`) when seeding, and `dgm` never writes the
aliases.
The backend clears `cfg.debug` after seeding so that `ds`/`dso` dispatch to the
ESIL stepper instead of radare2's `r_debug_step` machinery (software
breakpoints, arena swap, trace), which assumes a live ptrace process. The raw
ESIL commands (`aes`, `aesu 0x401020`, `aer rax=...`) work as well.
`dc` runs to a breakpoint via ESIL. A slice has no program exit and unmapped
fetches read as the fill byte (which decodes as instructions), so `dc` *without*
a breakpoint is bounded by `esil.maxsteps` (defaulted to 1,000,000 by this
backend) rather than running forever — set `e esil.maxsteps=0` for unlimited.
The seek follows the PC after each step (`dbg.follow=1`), so the disassembly and
visual modes track execution.
> Note: routing `dc` to ESIL when `cfg.debug` is unset is a small radare2 core
> change (it benefits any emulated backend, not just msl). It is proposed
> separately upstream; without it, use `aec` instead of `dc` on older r2.
## Producing a slice from radare2
The `core.msl` plugin adds `dgm` (debug-generate memory-slice). While debugging
a process, write the current state as a slice:
```
[0xphysical]> dgm dump.msl # or just `dgm` -> <pid>.msl
[0xphysical]> dgma dump.msl # include all maps (no large-map skip)
```
It writes the file header, a Process Identity block, a Thread Context with the
current thread's registers, and one Memory Region per debug map (three-state
page map: pages that read back become Captured, the rest Failed). The integrity
chain uses SHA-256 (radare2 has no BLAKE3; the format allows SHA-256 via
HashAlgo 0x01). The resulting `.msl` is readable by these plugins and by
memslicer / `memslicer-emu`.
Maps are read in 1 MiB chunks; unreadable maps are skipped. By default `dgm`
skips very large maps (over 512 MiB — e.g. the macOS dyld shared cache) so the
dump stays small and fast; use `dgma` to include everything.
## Testing
`test/genmsl.py` writes a small but fully-formed `.msl` (file header, Process
Identity, Thread Context, one Memory Region, End-of-Capture) with a valid
SHA-256 chain — handy for exercising the read path without a live target:
```
$ python3 test/genmsl.py /tmp/sample.msl
$ r2 -qc 'iS' /tmp/sample.msl # bin: region.0 @ 0x1000
$ r2 -qc 's 0x1000; p8 8' msl:///tmp/sample.msl # io: captured bytes
$ r2 -D msl -d msl:///tmp/sample.msl # emulated debug; dr, ds, dsb
```
Add `--mt` to emit a second (non-Current) thread, to exercise thread listing
and selection:
```
$ python3 test/genmsl.py --mt /tmp/mt.msl
$ r2 -D msl -q -c 'dpt' -d msl:///tmp/mt.msl # lists tid 7 (*) and 8
$ r2 -D msl -q -c 'dpt=8; dr rip' -d msl:///tmp/mt.msl # 0x1100 (thread 8's PC)
```
Add `--pe` to emit a module with a minimal in-memory PE export table, to
exercise symbol/export resolution:
```
$ python3 test/genmsl.py --pe /tmp/pe.msl
$ r2 -q -c 'is~MyExport' /tmp/pe.msl # FUNC test.dll MyExport @ 0x10300
$ r2 -q -c 'il' /tmp/pe.msl # C:\Windows\System32\test.dll
```
## Format reference
The `.msl` binary format is specified in the
[Memory Slice specification](https://github.com/MemorySlice). The plugins read:
the 64/128-byte file header (`MEMSLICE` magic, OS/arch/PID), Memory Region
blocks (`0x0001`, with the two-bit page-state map), and Thread Context blocks
(`0x0011`, per-thread register file, incl. the 1.1 `Arch`/`ThreadState` bytes).
+629
View File
@@ -0,0 +1,629 @@
/* radare - LGPL - Copyright 2026 - memslicer */
// RBin plugin for Memory Slice (.msl) process memory dumps.
//
// Presents an .msl as a CORE-like object: architecture/bits/OS from the file
// header, the program counter of the Current thread as entry0, and one memory
// map per contiguous run of Captured pages (vaddr -> file offset of PageData).
// Failed/Unmapped pages are left unmapped so radare2 fills them with io.0xff,
// exactly like the ELF coredump loader.
//
// Open a slice with `r2 dump.msl` (default file IO). For a raw virtual-address
// view without bin metadata, the companion io plugin handles `msl://dump.msl`.
//
// MVP scope: uncompressed, unencrypted slices.
#include <r_bin.h>
#define MSL_FILE_MAGIC "MEMSLICE"
#define MSL_BLOCK_MAGIC "MSLC"
#define MSL_HDR_FLAG_ENCRYPTED 0x4
#define MSL_BLOCK_FLAG_COMPRESSED 0x1
#define MSL_BT_MEMORY_REGION 0x0001
#define MSL_BT_MODULE_ENTRY 0x0002
#define MSL_BT_THREAD_CONTEXT 0x0011
#define MSL_BT_END_OF_CAPTURE 0x0FFF
#define MSL_BLOCK_HEADER_SIZE 80
#define MSL_REG_FLAG_PC 0x1
#define MSL_THREAD_FLAG_CURRENT 0x1
#define MSL_MAX_PAGES (1ULL << 28)
#define MSL_MAX_EXPORTS 65536
#define MSL_MAX_NAME 256
typedef struct {
ut64 base;
ut64 size;
char *path;
} MslModule;
typedef struct {
RList *maps; // RBinMap*
RList *modules; // MslModule*
ut64 entry; // PC of the Current thread
bool has_entry;
ut16 os_type;
ut16 arch_type;
int bits;
int compressed_skipped; // compressed regions that can't be file-mapped
} RBinMslObj;
static void msl_module_free(void *p) {
MslModule *m = p;
if (m) {
free (m->path);
free (m);
}
}
// Basename of a module path (handles both '\\' and '/').
static const char *msl_basename(const char *path) {
if (!path) {
return "module";
}
const char *b = path;
const char *p;
for (p = path; *p; p++) {
if (*p == '/' || *p == '\\') {
b = p + 1;
}
}
return *b? b: path;
}
static inline ut64 msl_pad8(ut64 n) {
return (n + 7) & ~(ut64)7;
}
static const char *msl_arch_str(ut16 arch, int *bits) {
switch (arch) {
case 0: *bits = 32; return "x86"; // x86
case 1: *bits = 64; return "x86"; // x86_64
case 2: *bits = 64; return "arm"; // ARM64
case 3: *bits = 32; return "arm"; // ARM32
case 4: *bits = 32; return "mips"; // MIPS32
case 5: *bits = 64; return "mips"; // MIPS64
case 6: *bits = 32; return "riscv"; // RV32
case 7: *bits = 64; return "riscv"; // RV64
case 8: *bits = 32; return "ppc"; // PPC32
case 9: *bits = 64; return "ppc"; // PPC64
default: *bits = 64; return "x86";
}
}
static const char *msl_os_str(ut16 os) {
switch (os) {
case 0: return "windows";
case 1: return "linux";
case 2: return "macos";
case 3: return "android";
case 4: return "ios";
default: return "unknown";
}
}
static int msl_page_state(ut8 *psm, ut64 page) {
ut8 byte = psm[page >> 2];
int bitpos = 6 - (int)((page & 3) * 2);
return (byte >> bitpos) & 3;
}
// Append one RBinMap per contiguous run of Captured pages in a region.
static void msl_region_maps(RBinMslObj *o, RBuffer *b, ut64 payload_off, ut16 bflags) {
ut8 p[32];
if (r_buf_read_at (b, payload_off, p, sizeof (p)) != sizeof (p)) {
return;
}
if (bflags & MSL_BLOCK_FLAG_COMPRESSED) {
// Compressed PageData cannot be expressed as a vaddr->file-offset
// map. Open the slice with the `msl://` URI instead (the io plugin
// decompresses lz4 in memory).
o->compressed_skipped++;
return;
}
ut64 base = r_read_le64 (p);
ut64 size = r_read_le64 (p + 8);
ut8 prot = p[16];
ut8 psl = p[18];
if (psl < 10 || psl > 40 || size == 0) {
return;
}
ut64 page_size = 1ULL << psl;
if (size & (page_size - 1)) {
return;
}
ut64 npages = size >> psl;
if (npages > MSL_MAX_PAGES) {
return;
}
ut64 psm_bytes = msl_pad8 ((npages + 3) / 4);
ut8 *psm = malloc (psm_bytes? (size_t)psm_bytes: 1);
if (!psm) {
return;
}
if (psm_bytes && r_buf_read_at (b, payload_off + 32, psm, psm_bytes) != (st64)psm_bytes) {
free (psm);
return;
}
ut64 data_off = payload_off + 32 + psm_bytes;
int perm = ((prot & 1)? R_PERM_R: 0) | ((prot & 2)? R_PERM_W: 0) | ((prot & 4)? R_PERM_X: 0);
ut64 cap_count = 0; // captured pages seen so far (file offset cursor)
ut64 run_start = 0; // page index where current run began
ut64 run_foff = 0; // file offset of current run start
bool in_run = false;
ut64 i;
for (i = 0; i < npages; i++) {
bool captured = msl_page_state (psm, i) == 0;
if (captured && !in_run) {
in_run = true;
run_start = i;
run_foff = data_off + cap_count * page_size;
} else if (!captured && in_run) {
RBinMap *m = R_NEW0 (RBinMap);
if (m) {
m->addr = base + run_start * page_size;
m->offset = run_foff;
m->size = (int)((i - run_start) * page_size);
m->perms = perm;
m->file = strdup ("msl");
r_list_append (o->maps, m);
}
in_run = false;
}
if (captured) {
cap_count++;
}
}
if (in_run) {
RBinMap *m = R_NEW0 (RBinMap);
if (m) {
m->addr = base + run_start * page_size;
m->offset = run_foff;
m->size = (int)((npages - run_start) * page_size);
m->perms = perm;
m->file = strdup ("msl");
r_list_append (o->maps, m);
}
}
free (psm);
}
// Extract the program counter from a Thread Context block payload.
static bool msl_thread_pc(RBuffer *b, ut64 payload_off, ut64 payload_len, ut64 *pc, bool *is_current) {
ut8 hdr[32];
if (payload_len < sizeof (hdr) || r_buf_read_at (b, payload_off, hdr, sizeof (hdr)) != sizeof (hdr)) {
return false;
}
ut16 tflags = r_read_le16 (hdr + 16);
ut32 regcount = r_read_le32 (hdr + 20);
ut16 namelen = r_read_le16 (hdr + 24);
ut64 off = payload_off + 32 + msl_pad8 (namelen);
ut64 end = payload_off + payload_len;
ut32 r;
for (r = 0; r < regcount; r++) {
ut8 e[8];
if (off + 8 > end || r_buf_read_at (b, off, e, sizeof (e)) != sizeof (e)) {
return false;
}
ut8 rnamelen = e[0];
ut8 width = e[1];
ut16 rflags = r_read_le16 (e + 2);
ut64 name_pad = msl_pad8 (rnamelen);
ut64 val_off = off + 8 + name_pad;
if (rflags & MSL_REG_FLAG_PC) {
ut8 v[8] = {0};
int n = (width > 8)? 8: width;
if (r_buf_read_at (b, val_off, v, n) != n) {
return false;
}
*pc = r_read_le64 (v);
*is_current = (tflags & MSL_THREAD_FLAG_CURRENT) != 0;
return true;
}
off = val_off + msl_pad8 (width);
}
return false;
}
// Translate a virtual address to a file offset using the captured-run maps and
// read *len* bytes. Reads must fall inside a single captured run.
static bool msl_va_read(RBinMslObj *o, RBuffer *b, ut64 va, ut8 *out, int len) {
RListIter *it;
RBinMap *m;
r_list_foreach (o->maps, it, m) {
ut64 msize = (ut64)(ut32) m->size;
if (va >= m->addr && va + (ut64) len <= m->addr + msize) {
ut64 foff = m->offset + (va - m->addr);
return r_buf_read_at (b, foff, out, len) == len;
}
}
return false;
}
static ut32 msl_va_u32(RBinMslObj *o, RBuffer *b, ut64 va, bool *ok) {
ut8 v[4];
if (msl_va_read (o, b, va, v, 4)) {
*ok = true;
return r_read_le32 (v);
}
*ok = false;
return 0;
}
// Read a NUL-terminated ASCII string at *va* into *out* (capped). Returns its
// length, or 0 if unreadable.
static int msl_va_cstr(RBinMslObj *o, RBuffer *b, ut64 va, char *out, int cap) {
int i;
for (i = 0; i < cap - 1; i++) {
ut8 c;
if (!msl_va_read (o, b, va + i, &c, 1) || c == 0) {
break;
}
out[i] = (c >= 0x20 && c < 0x7f)? (char) c: '_';
}
out[i] = 0;
return i;
}
// Parse the PE export directory of a module mapped at mod->base and emplace one
// FUNC symbol per named export. No-op (returns 0) for non-PE images.
static int msl_pe_exports(RBinMslObj *o, RBuffer *b, MslModule *mod, RVecRBinSymbol *vec) {
ut64 base = mod->base;
ut8 mz[2];
if (!msl_va_read (o, b, base, mz, 2) || mz[0] != 'M' || mz[1] != 'Z') {
return 0;
}
bool ok;
ut32 lfanew = msl_va_u32 (o, b, base + 0x3c, &ok);
if (!ok || lfanew > 0x10000) {
return 0;
}
ut8 sig[4];
if (!msl_va_read (o, b, base + lfanew, sig, 4) || memcmp (sig, "PE\0\0", 4)) {
return 0;
}
ut64 opt = base + lfanew + 24; // optional header
ut8 magic[2];
if (!msl_va_read (o, b, opt, magic, 2)) {
return 0;
}
ut16 omagic = r_read_le16 (magic);
// DataDirectory[0] (export table) lives at a magic-dependent offset.
ut64 dd0 = opt + ((omagic == 0x20b)? 112: 96); // PE32+ : PE32
ut32 exp_rva = msl_va_u32 (o, b, dd0, &ok);
if (!ok || exp_rva == 0) {
return 0;
}
ut64 ed = base + exp_rva; // IMAGE_EXPORT_DIRECTORY
ut32 n_names = msl_va_u32 (o, b, ed + 0x18, &ok);
if (!ok || n_names == 0 || n_names > MSL_MAX_EXPORTS) {
return 0;
}
ut32 funcs_rva = msl_va_u32 (o, b, ed + 0x1c, &ok); if (!ok) return 0;
ut32 names_rva = msl_va_u32 (o, b, ed + 0x20, &ok); if (!ok) return 0;
ut32 ords_rva = msl_va_u32 (o, b, ed + 0x24, &ok); if (!ok) return 0;
const char *lib = msl_basename (mod->path);
int emitted = 0;
ut32 i;
for (i = 0; i < n_names; i++) {
ut32 name_rva = msl_va_u32 (o, b, base + names_rva + i * 4, &ok);
if (!ok || name_rva == 0) {
continue;
}
char name[MSL_MAX_NAME];
if (msl_va_cstr (o, b, base + name_rva, name, sizeof (name)) <= 0) {
continue;
}
ut8 ob[2];
if (!msl_va_read (o, b, base + ords_rva + i * 2, ob, 2)) {
continue;
}
ut16 ord = r_read_le16 (ob);
ut32 func_rva = msl_va_u32 (o, b, base + funcs_rva + (ut64) ord * 4, &ok);
if (!ok || func_rva == 0) {
continue;
}
RBinSymbol *sym = RVecRBinSymbol_emplace_back (vec);
if (!sym) {
break;
}
memset (sym, 0, sizeof (*sym));
sym->name = r_bin_name_new (name);
sym->libname = strdup (lib);
sym->vaddr = base + func_rva;
sym->paddr = base + func_rva;
sym->type = R_BIN_TYPE_FUNC_STR;
sym->bind = R_BIN_BIND_GLOBAL_STR;
sym->ordinal = ord;
sym->bits = o->bits;
emitted++;
}
return emitted;
}
static bool msl_parse(RBinMslObj *o, RBuffer *b) {
ut8 h[16];
if (r_buf_read_at (b, 0, h, sizeof (h)) != sizeof (h)) {
return false;
}
if (memcmp (h, MSL_FILE_MAGIC, 8)) {
return false;
}
ut32 flags = r_read_le32 (h + 12);
if (flags & MSL_HDR_FLAG_ENCRYPTED) {
R_LOG_ERROR ("msl: encrypted slices are not supported yet");
return false;
}
ut8 header_size = h[9];
ut8 osarch[4];
if (r_buf_read_at (b, 0x30, osarch, sizeof (osarch)) == sizeof (osarch)) {
o->os_type = r_read_le16 (osarch);
o->arch_type = r_read_le16 (osarch + 2);
}
o->maps = r_list_newf (free);
o->modules = r_list_newf (msl_module_free);
bool have_current_pc = false;
ut64 fsize = r_buf_size (b);
ut64 off = header_size;
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (b, off, bh, sizeof (bh)) != sizeof (bh)) {
break;
}
if (memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut16 bflags = r_read_le16 (bh + 6);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE) {
break;
}
ut64 payload_off = off + MSL_BLOCK_HEADER_SIZE;
ut64 payload_len = blen - MSL_BLOCK_HEADER_SIZE;
if (btype == MSL_BT_MEMORY_REGION) {
msl_region_maps (o, b, payload_off, bflags);
} else if (btype == MSL_BT_MODULE_ENTRY) {
// ModuleEntry: BaseAddr(8) ModuleSize(8) PathLen(2) VerLen(2) rsv(4)
// followed by the path. Used to resolve exports and label modules.
ut8 mh[24];
if (payload_len >= sizeof (mh)
&& r_buf_read_at (b, payload_off, mh, sizeof (mh)) == sizeof (mh)) {
ut16 pathlen = r_read_le16 (mh + 16);
MslModule *mod = R_NEW0 (MslModule);
if (mod) {
mod->base = r_read_le64 (mh);
mod->size = r_read_le64 (mh + 8);
if (pathlen > 0 && pathlen < 0x1000
&& 24 + (ut64) pathlen <= payload_len) {
mod->path = calloc (1, (size_t) pathlen + 1);
if (mod->path) {
r_buf_read_at (b, payload_off + 24, (ut8 *) mod->path, pathlen);
}
}
r_list_append (o->modules, mod);
}
}
} else if (btype == MSL_BT_THREAD_CONTEXT && !have_current_pc) {
ut64 pc = 0;
bool is_current = false;
if (msl_thread_pc (b, payload_off, payload_len, &pc, &is_current)) {
// Prefer the Current thread; otherwise keep the first PC seen.
if (is_current || !o->has_entry) {
o->entry = pc;
o->has_entry = true;
}
if (is_current) {
have_current_pc = true;
}
}
} else if (btype == MSL_BT_END_OF_CAPTURE) {
break;
}
off += blen;
}
return true;
}
static bool check(RBinFile *bf, RBuffer *b) {
ut8 magic[8];
if (r_buf_read_at (b, 0, magic, sizeof (magic)) != sizeof (magic)) {
return false;
}
return !memcmp (magic, MSL_FILE_MAGIC, 8);
}
static bool load(RBinFile *bf, RBuffer *buf, ut64 loadaddr) {
RBinMslObj *o = R_NEW0 (RBinMslObj);
if (!o) {
return false;
}
if (!msl_parse (o, buf)) {
r_list_free (o->maps);
free (o);
return false;
}
if (o->compressed_skipped > 0) {
R_LOG_WARN ("msl: %d compressed region(s) are not mapped by the bin "
"plugin; open the slice as 'msl://%s' to read them (the io "
"plugin decompresses lz4).", o->compressed_skipped, bf->file);
}
int bits = 64;
msl_arch_str (o->arch_type, &bits); // so symbols carry the right width
o->bits = bits;
bf->bo->bin_obj = o;
return true;
}
static void destroy(RBinFile *bf) {
if (bf && bf->bo && bf->bo->bin_obj) {
RBinMslObj *o = bf->bo->bin_obj;
r_list_free (o->maps);
r_list_free (o->modules);
free (o);
bf->bo->bin_obj = NULL;
}
}
// One symbol per loaded module (at its base) plus, for PE images, one FUNC
// symbol per exported function -- so radare2 names call targets like
// `kernel32.dll!CreateFileW` instead of bare addresses.
static bool symbols_vec(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, false);
RBinMslObj *o = bf->bo->bin_obj;
RVecRBinSymbol *vec = &bf->bo->symbols_vec;
RBuffer *b = bf->buf;
RListIter *it;
MslModule *mod;
r_list_foreach (o->modules, it, mod) {
RBinSymbol *sym = RVecRBinSymbol_emplace_back (vec);
if (sym) {
memset (sym, 0, sizeof (*sym));
sym->name = r_bin_name_new (msl_basename (mod->path));
sym->vaddr = mod->base;
sym->paddr = mod->base;
sym->type = R_BIN_TYPE_OBJECT_STR;
sym->bind = R_BIN_BIND_GLOBAL_STR;
sym->size = mod->size;
sym->bits = o->bits;
}
msl_pe_exports (o, b, mod, vec);
}
return true;
}
// Expose captured modules as libraries (so `il` lists them).
static RList *libs(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
RBinMslObj *o = bf->bo->bin_obj;
RList *ret = r_list_newf (free);
if (!ret) {
return NULL;
}
RListIter *it;
MslModule *mod;
r_list_foreach (o->modules, it, mod) {
if (mod->path) {
r_list_append (ret, strdup (mod->path));
}
}
return ret;
}
static RList *maps(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
RBinMslObj *o = bf->bo->bin_obj;
RList *ret = r_list_newf (free);
if (!ret) {
return NULL;
}
RListIter *it;
RBinMap *m;
r_list_foreach (o->maps, it, m) {
RBinMap *c = R_NEW0 (RBinMap);
if (c) {
*c = *m;
c->file = m->file? strdup (m->file): NULL;
r_list_append (ret, c);
}
}
return ret;
}
// Sections with add=true are what radare2 turns into IO maps (the RBinMap
// list is only used to rename them for CORE files). One section per captured
// run; failed/unmapped pages stay unmapped and read back as io.0xff.
static bool sections_vec(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, false);
RBinMslObj *o = bf->bo->bin_obj;
RVecRBinSection_clear (&bf->bo->sections_vec);
RListIter *it;
RBinMap *m;
int i = 0;
r_list_foreach (o->maps, it, m) {
RBinSection *s = RVecRBinSection_emplace_back (&bf->bo->sections_vec);
if (!s) {
continue;
}
memset (s, 0, sizeof (*s));
s->name = r_str_newf ("region.%d", i++);
s->paddr = m->offset;
s->vaddr = m->addr;
s->size = (ut64)(ut32)m->size;
s->vsize = s->size;
s->perm = m->perms;
s->add = true;
}
return true;
}
static RList *entries(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
RBinMslObj *o = bf->bo->bin_obj;
RList *ret = r_list_newf (free);
if (!ret || !o->has_entry) {
return ret;
}
RBinAddr *a = R_NEW0 (RBinAddr);
if (a) {
a->vaddr = o->entry;
a->paddr = o->entry;
a->bits = o->bits;
r_list_append (ret, a);
}
return ret;
}
static RBinInfo *info(RBinFile *bf) {
R_RETURN_VAL_IF_FAIL (bf && bf->bo && bf->bo->bin_obj, NULL);
RBinMslObj *o = bf->bo->bin_obj;
RBinInfo *ret = R_NEW0 (RBinInfo);
if (!ret) {
return NULL;
}
int bits = 64;
const char *arch = msl_arch_str (o->arch_type, &bits);
o->bits = bits;
ret->file = strdup (bf->file);
ret->type = strdup ("CORE");
ret->rclass = strdup ("msl");
ret->bclass = strdup ("Memory Slice");
ret->arch = strdup (arch);
ret->machine = strdup ("Memory Slice dump");
ret->os = strdup (msl_os_str (o->os_type));
ret->bits = bits;
ret->big_endian = false;
ret->has_va = true;
return ret;
}
static ut64 baddr(RBinFile *bf) {
return 0;
}
RBinPlugin r_bin_plugin_msl = {
.meta = {
.name = "msl",
.desc = "Memory Slice (.msl) process memory dump",
.author = "memslicer",
.license = "LGPL-3.0-only",
},
.load = &load,
.destroy = &destroy,
.check = &check,
.baddr = &baddr,
.entries = &entries,
.maps = &maps,
.sections_vec = &sections_vec,
.symbols_vec = &symbols_vec,
.libs = &libs,
.info = &info,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_BIN,
.data = &r_bin_plugin_msl,
.version = R2_VERSION
};
#endif
+396
View File
@@ -0,0 +1,396 @@
/* radare - LGPL - Copyright 2026 - memslicer */
// Core plugin for Memory Slice (.msl) production.
//
// Adds the "dgm"/"dgma" commands that write the producer side of the format
// consumed by the io/bin/debug "msl" plugins and by memslicer
// (https://github.com/MemorySlice/memslicer): a file header, Process Identity,
// a Thread Context with the current registers, and one Memory Region per debug
// map (three-state page map + captured bytes). The integrity chain uses
// SHA-256 (HashAlgo 0x01) since radare2 has no BLAKE3.
//
// This lives in radare2-extras as a loadable core plugin instead of in the r2
// core. Load it (after `make install`) and the commands appear under "dg".
#include <r_core.h>
#include <r_cmd.h>
#include <r_lib.h>
#include <fcntl.h>
#define MSLW_HASH R_HASH_SHA256
typedef struct {
RBuffer *out;
RHash *fh; // cumulative file hash (incremental, for End-of-Capture)
ut8 prev[32]; // hash of the previous element (the chain)
} MslDump;
static void mslw_uuid(ut8 *u) {
int i;
for (i = 0; i < 16; i++) {
u[i] = (ut8)r_num_rand (256);
}
u[6] = (u[6] & 0x0f) | 0x40; // version 4
u[8] = (u[8] & 0x3f) | 0x80; // variant
}
static void mslw_sha256(const ut8 *a, ut64 alen, const ut8 *b, ut64 blen, ut8 *out32) {
RHash *h = r_hash_new (true, MSLW_HASH);
r_hash_do_begin (h, MSLW_HASH);
if (alen) {
r_hash_do_sha256 (h, a, alen);
}
if (blen) {
r_hash_do_sha256 (h, b, blen);
}
r_hash_do_end (h, MSLW_HASH);
memcpy (out32, h->digest, 32);
r_hash_free (h);
}
static void mslw_append_pad8(RBuffer *b, const ut8 *data, ut64 len) {
if (len) {
r_buf_append_bytes (b, data, len);
}
ut64 pad = ((len + 7) & ~(ut64)7) - len;
if (pad) {
ut8 z[8] = {0};
r_buf_append_bytes (b, z, pad);
}
}
// Emit one block: 80-byte header + 8-byte-padded payload; advance the chain.
static void mslw_block(MslDump *d, ut16 type, const ut8 *payload, ut64 plen, bool feed_file) {
ut64 padded = (plen + 7) & ~(ut64)7;
ut8 *pl = calloc (1, padded? (size_t)padded: 8);
if (plen && pl) {
memcpy (pl, payload, plen);
}
ut8 hdr[80] = {0};
memcpy (hdr, "MSLC", 4);
r_write_le16 (hdr + 4, type);
r_write_le32 (hdr + 8, (ut32)(80 + padded));
r_write_le16 (hdr + 12, 1); // payload version
mslw_uuid (hdr + 16);
memcpy (hdr + 48, d->prev, 32);
r_buf_append_bytes (d->out, hdr, 80);
if (padded) {
r_buf_append_bytes (d->out, pl, padded);
}
if (feed_file) {
r_hash_do_sha256 (d->fh, hdr, 80);
if (padded) {
r_hash_do_sha256 (d->fh, pl, padded);
}
}
mslw_sha256 (hdr, 80, pl, padded, d->prev);
free (pl);
}
static ut16 mslw_arch_code(const char *arch, int bits) {
if (arch && !strcmp (arch, "x86")) {
return bits == 64? 1: 0;
}
if (arch && !strcmp (arch, "arm")) {
return bits == 64? 2: 3;
}
return 0xFFFF;
}
static ut16 mslw_os_code(const char *os) {
if (os) {
if (strstr (os, "linux")) { return 1; }
if (strstr (os, "macos") || strstr (os, "darwin")) { return 2; }
if (strstr (os, "windows")) { return 0; }
if (strstr (os, "android")) { return 3; }
if (strstr (os, "ios")) { return 4; }
}
return 0xFFFF;
}
static bool mslw_dump(RCore *core, const char *path, bool full) {
if (!core->dbg || core->dbg->pid == -1) {
R_LOG_ERROR ("Not debugging; cannot write a memory slice");
return false;
}
r_debug_map_sync (core->dbg);
r_debug_reg_sync (core->dbg, R_REG_TYPE_ALL, false);
r_file_rm (path);
RBuffer *out = r_buf_new_file (path, O_RDWR | O_CREAT, 0644);
if (!out) {
R_LOG_ERROR ("Cannot create %s", path);
return false;
}
MslDump d = {0};
d.out = out;
d.fh = r_hash_new (true, MSLW_HASH);
r_hash_do_begin (d.fh, MSLW_HASH);
int bits = r_config_get_i (core->config, "asm.bits");
ut16 arch_type = mslw_arch_code (r_config_get (core->config, "asm.arch"), bits);
ut16 os_type = mslw_os_code (r_config_get (core->config, "asm.os"));
ut64 now_ns = r_time_now () * 1000;
// Build the Thread Context payload (current thread) first, to know whether
// to advertise the ThreadContexts capability bit.
RReg *reg = core->dbg->reg;
const char *pcname = r_reg_alias_getname (reg, R_REG_ALIAS_PC);
const char *spname = r_reg_alias_getname (reg, R_REG_ALIAS_SP);
const char *bpname = r_reg_alias_getname (reg, R_REG_ALIAS_BP);
const char *srname = r_reg_alias_getname (reg, R_REG_ALIAS_SR);
const char *lrname = r_reg_alias_getname (reg, R_REG_ALIAS_LR);
const bool arm64 = arch_type == 2;
RBuffer *regs = r_buf_new ();
ut32 regcount = 0;
RList *items = r_reg_get_list (reg, R_REG_TYPE_GPR);
RListIter *it;
RRegItem *item;
r_list_foreach (items, it, item) {
if (!item->name || item->size != bits) {
continue; // emit canonical full-width registers only
}
// Spec Table 19g: ARM64 fp/lr are aliases of x29/x30 and MUST NOT be
// written; r2's profile lists both at the same offset.
if (arm64 && (!strcmp (item->name, "fp") || !strcmp (item->name, "lr"))) {
continue;
}
int width = item->size / 8;
if (width < 1 || width > 8) {
continue;
}
ut64 val = r_reg_get_value (reg, item);
int nlen = (int)strlen (item->name) + 1;
ut8 e[8] = {0};
e[0] = (ut8)nlen;
e[1] = (ut8)width;
ut16 rflags = 0; // Table 19b: PC, SP, FP, FLAGS, TLS, LR
if (pcname && !strcmp (item->name, pcname)) {
rflags |= 1;
} else if (spname && !strcmp (item->name, spname)) {
rflags |= 2;
} else if (bpname && !strcmp (item->name, bpname)) {
rflags |= 4;
} else if (srname && !strcmp (item->name, srname)) {
rflags |= 8;
} else if (lrname && !strcmp (item->name, lrname)) {
rflags |= 0x20;
}
r_write_le16 (e + 2, rflags);
r_buf_append_bytes (regs, e, 8);
mslw_append_pad8 (regs, (const ut8 *)item->name, nlen);
ut8 vbuf[8] = {0};
r_write_le64 (vbuf, val);
mslw_append_pad8 (regs, vbuf, width);
regcount++;
}
ut64 cap = (1ULL << 0) | (1ULL << 8); // MemoryRegions | ProcessIdentity
if (regcount > 0) {
cap |= (1ULL << 2); // ThreadContexts
}
// File header (64 bytes)
ut8 fhdr[64] = {0};
memcpy (fhdr, "MEMSLICE", 8);
fhdr[8] = 1; // endianness: little
fhdr[9] = 64; // header size
r_write_le16 (fhdr + 10, 0x0101); // version 1.1
r_write_le64 (fhdr + 16, cap);
mslw_uuid (fhdr + 24);
r_write_le64 (fhdr + 40, now_ns);
r_write_le16 (fhdr + 48, os_type);
r_write_le16 (fhdr + 50, arch_type);
r_write_le32 (fhdr + 52, (ut32)core->dbg->pid);
fhdr[61] = 0x01; // HashAlgo: SHA-256
r_buf_append_bytes (out, fhdr, 64);
r_hash_do_sha256 (d.fh, fhdr, 64);
mslw_sha256 (fhdr, 64, NULL, 0, d.prev);
// Block 0: Process Identity (0x0040)
{
const char *exe = (core->io && core->io->desc)? core->io->desc->name: "";
if (r_str_startswith (exe, "dbg://")) {
exe += strlen ("dbg://");
}
int elen = (int)strlen (exe) + 1;
RBuffer *pi = r_buf_new ();
ut8 h[24] = {0};
r_write_le16 (h + 16, (ut16)elen); // ExePathLen
r_buf_append_bytes (pi, h, 24);
mslw_append_pad8 (pi, (const ut8 *)exe, elen);
ut64 sz = 0;
const ut8 *bytes = r_buf_data (pi, &sz);
mslw_block (&d, 0x0040, bytes, sz, true);
r_unref (pi);
}
// Thread Context (0x0011)
if (regcount > 0) {
RBuffer *tc = r_buf_new ();
ut8 h[32] = {0};
r_write_le64 (h, (ut64)core->dbg->tid); // ThreadID
r_write_le16 (h + 16, 1 | 4); // Flags: Current | Suspended (debugger-halted)
h[18] = 3; // ThreadState: Stopped
r_write_le32 (h + 20, regcount);
r_buf_append_bytes (tc, h, 32);
ut64 rsz = 0;
const ut8 *rbytes = r_buf_data (regs, &rsz);
if (rsz) {
r_buf_append_bytes (tc, rbytes, rsz);
}
ut64 sz = 0;
const ut8 *bytes = r_buf_data (tc, &sz);
mslw_block (&d, 0x0011, bytes, sz, true);
r_unref (tc);
}
r_unref (regs);
// Memory Region blocks (0x0001), one per debug map.
// Read in 1 MiB chunks (not page-by-page) for speed; skip unreadable maps
// and, unless 'full', skip very large maps (e.g. the macOS dyld shared
// cache is multi-GB and would dominate the dump). Use "dgma" for everything.
const ut64 page = 4096;
const ut64 CHUNK = 1024 * 1024;
const ut64 CAP = 512ULL * 1024 * 1024;
ut8 *cbuf = malloc (CHUNK);
r_cons_break_push (core->cons, NULL, NULL);
RDebugMap *map;
r_list_foreach (core->dbg->maps, it, map) {
if (r_cons_is_breaked (core->cons)) {
break;
}
ut64 base = map->addr;
ut64 size = map->addr_end - map->addr;
if (size == 0 || (size % page) || !cbuf) {
continue;
}
if (!(map->perm & R_PERM_R)) {
continue; // cannot read it anyway
}
if (!full && size > CAP) {
R_LOG_WARN ("msl: skipping large map 0x%"PFMT64x" (%"PFMT64u" MB); use dgma for a full dump",
base, size >> 20);
continue;
}
ut64 npages = size / page;
ut64 psm_bytes = (((npages + 3) / 4) + 7) & ~(ut64)7;
ut8 *psm = calloc (1, psm_bytes? (size_t)psm_bytes: 1);
RBuffer *pdata = r_buf_new ();
if (!psm || !pdata) {
free (psm);
r_unref (pdata);
continue;
}
ut64 done = 0, pidx = 0;
while (done < size && !r_cons_is_breaked (core->cons)) {
ut64 clen = R_MIN (CHUNK, size - done);
ut64 cpages = clen / page;
if (r_io_read_at (core->io, base + done, cbuf, (int)clen)) {
r_buf_append_bytes (pdata, cbuf, clen); // whole chunk CAPTURED
} else {
ut64 j; // partial: fall back to page granularity for this chunk
for (j = 0; j < cpages; j++) {
ut64 pi = pidx + j;
if (r_io_read_at (core->io, base + done + j * page, cbuf, (int)page)) {
r_buf_append_bytes (pdata, cbuf, page);
} else {
psm[pi >> 2] |= 1 << (6 - (int)(pi & 3) * 2); // FAILED
}
}
}
pidx += cpages;
done += clen;
}
int prot = ((map->perm & R_PERM_R)? 1: 0)
| ((map->perm & R_PERM_W)? 2: 0)
| ((map->perm & R_PERM_X)? 4: 0);
RBuffer *mr = r_buf_new ();
ut8 h[32] = {0};
r_write_le64 (h, base);
r_write_le64 (h + 8, size);
h[16] = (ut8)prot;
h[18] = 12; // PageSizeLog2 (4096)
r_write_le64 (h + 24, now_ns);
r_buf_append_bytes (mr, h, 32);
r_buf_append_bytes (mr, psm, psm_bytes);
ut64 dsz = 0;
const ut8 *dbytes = r_buf_data (pdata, &dsz);
if (dsz) {
r_buf_append_bytes (mr, dbytes, dsz);
}
ut64 sz = 0;
const ut8 *bytes = r_buf_data (mr, &sz);
mslw_block (&d, 0x0001, bytes, sz, true);
r_unref (mr);
r_unref (pdata);
free (psm);
}
r_cons_break_pop (core->cons);
free (cbuf);
// End-of-Capture (0x0FFF): FileHash over everything before this block.
r_hash_do_end (d.fh, MSLW_HASH);
ut8 eoc[48] = {0};
memcpy (eoc, d.fh->digest, 32);
r_write_le64 (eoc + 32, r_time_now () * 1000);
mslw_block (&d, 0x0FFF, eoc, sizeof (eoc), false);
r_hash_free (d.fh);
r_unref (out);
R_LOG_INFO ("Wrote memory slice to %s", path);
return true;
}
static const char *help_msg_dgm[] = {
"Usage:", "dgm", "[a] ([file]) # Generate a Memory Slice (.msl)",
"dgm", " [file]", "generate a Memory Slice of the debuggee (skips huge maps)",
"dgma", " [file]", "generate a Memory Slice including all maps",
NULL
};
static bool cmd_dgm(RCore *core, const char *input) {
// input is the full command line, e.g. "dgm /tmp/a.msl" or "dgma".
if (input[3] == '?') {
r_core_cmd_help (core, help_msg_dgm);
return true;
}
if (!core->dbg || core->dbg->pid == -1) {
R_LOG_ERROR ("Not debugging; cannot write a memory slice");
return true;
}
bool full = (input[3] == 'a');
const char *filename = strchr (input, ' ');
char *out = (filename && filename[1])
? r_str_trim_dup (filename + 1)
: r_str_newf ("%d.msl", core->dbg->pid);
mslw_dump (core, out, full);
free (out);
return true;
}
static bool msl_call(RCorePluginSession *cps, const char *input) {
// Intercept "dgm"/"dgma" before the builtin "dg" coredump handler.
if (r_str_startswith (input, "dgm")) {
return cmd_dgm (cps->core, input);
}
return false;
}
RCorePlugin r_core_plugin_msl = {
.meta = {
.name = "msl",
.desc = "Memory Slice (.msl) producer: dgm/dgma",
.author = "memslicer",
.license = "LGPL-3.0-only",
},
.call = msl_call,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_msl,
.version = R2_VERSION
};
#endif
+597
View File
@@ -0,0 +1,597 @@
/* radare - LGPL - Copyright 2026 - memslicer */
// Debug backend for Memory Slice (.msl) process memory dumps.
//
// A .msl is a static snapshot, so "execution" is driven by emulation: this
// backend wires the standard debugger commands (ds / dso / dc / dr) onto
// radare2's ESIL engine. On attach it initializes the ESIL VM and seeds the
// full register file from the Current thread's Thread Context block (0x0011),
// so stepping resumes from the real captured CPU state.
//
// A slice can capture several threads (one Thread Context each). `dpt` lists
// them (with their captured PC) and `dpt=<tid>` re-seeds the register file from
// another thread and seeks to its PC, so any captured thread can be emulated.
//
// Usage: r2 -d msl://dump.msl (or: r2 dump.msl; e dbg.backend=msl; ood)
//
// Memory comes from the maps created by the bin/io plugins; this backend only
// provides registers + stepping. MVP scope: uncompressed, unencrypted slices.
#include <r_core.h>
#include <r_debug.h>
#define MSL_FILE_MAGIC "MEMSLICE"
#define MSL_BLOCK_MAGIC "MSLC"
#define MSL_HDR_FLAG_ENCRYPTED 0x4
#define MSL_BT_THREAD_CONTEXT 0x0011
#define MSL_BT_END_OF_CAPTURE 0x0FFF
#define MSL_BLOCK_HEADER_SIZE 80
#define MSL_THREAD_FLAG_CURRENT 0x1
#define MSL_REG_FLAG_PC 0x1
#define MSL_ARCH_ARM64 2
// Table 19a ThreadState codes.
#define MSL_TS_RUNNING 0x01
#define MSL_TS_SLEEPING 0x02
#define MSL_TS_STOPPED 0x03
#define MSL_TS_WAITING 0x04
#define MSL_TS_UNINTERRUPTIBLE 0x05
#define MSL_TS_HALTED 0x06
#define MSL_TS_ZOMBIE 0x07
static inline ut64 msl_pad8(ut64 n) {
return (n + 7) & ~(ut64)7;
}
static const char *msl_arch_str(ut16 arch, int *bits) {
switch (arch) {
case 0: *bits = 32; return "x86";
case 1: *bits = 64; return "x86";
case 2: *bits = 64; return "arm";
case 3: *bits = 32; return "arm";
case 4: *bits = 32; return "mips";
case 5: *bits = 64; return "mips";
case 8: *bits = 32; return "ppc";
case 9: *bits = 64; return "ppc";
default: *bits = 64; return "x86";
}
}
// Spec 1.1.1 consumer rule 29: files from 1.1.0 producers may carry the ARM64
// aliases forbidden by Table 19g; map them to the canonical names. cpsr is not
// a 1.1.0 alias but r2's arm64 profile only knows pstate.
static const char *msl_canonical_reg(const char *name, ut16 arch) {
if (arch == MSL_ARCH_ARM64) {
if (!strcmp (name, "fp")) {
return "x29";
}
if (!strcmp (name, "lr")) {
return "x30";
}
if (!strcmp (name, "cpsr")) {
return "pstate";
}
}
return name;
}
// RDebugPid status for a Thread Context (Table 19a ThreadState). Unknown
// state falls back to the Current flag: the resumption thread reads as
// running, the rest as stopped.
static char msl_thread_status(ut8 state, ut16 tflags) {
switch (state) {
case MSL_TS_RUNNING: return R_DBG_PROC_RUN;
case MSL_TS_SLEEPING:
case MSL_TS_WAITING:
case MSL_TS_UNINTERRUPTIBLE: return R_DBG_PROC_SLEEP;
case MSL_TS_STOPPED:
case MSL_TS_HALTED: return R_DBG_PROC_STOP;
case MSL_TS_ZOMBIE: return R_DBG_PROC_ZOMBIE;
default: return (tflags & MSL_THREAD_FLAG_CURRENT)? R_DBG_PROC_RUN: R_DBG_PROC_STOP;
}
}
// Seed core->anal->reg from a thread's Thread Context register file. If
// want_tid >= 0 the thread with that id is selected; if want_tid < 0 the
// Current thread is used (falling back to the first thread context). Returns
// true when a thread was seeded and, if out_tid is non-NULL, writes the chosen
// thread id. The buffer is owned by the caller.
static bool msl_seed_regs(RCore *core, RBuffer *b, int want_tid, ut64 *out_tid) {
RReg *reg = core->anal->reg;
ut8 h[16];
if (r_buf_read_at (b, 0, h, sizeof (h)) != sizeof (h)) {
return false;
}
ut64 fsize = r_buf_size (b);
ut64 off = h[9];
// First pass: find the offset of the chosen thread's Thread Context block.
ut64 chosen_off = 0, chosen_tid = 0;
bool found = false;
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (b, off, bh, sizeof (bh)) != sizeof (bh) || memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE) {
break;
}
if (btype == MSL_BT_THREAD_CONTEXT) {
ut8 th[32];
if (r_buf_read_at (b, off + MSL_BLOCK_HEADER_SIZE, th, sizeof (th)) == sizeof (th)) {
ut64 tid = r_read_le64 (th);
ut16 tflags = r_read_le16 (th + 16);
if (want_tid >= 0) {
if ((ut64) want_tid == tid) {
chosen_off = off;
chosen_tid = tid;
found = true;
break;
}
} else {
if (!found) { // first thread = fallback when no Current flag
chosen_off = off;
chosen_tid = tid;
found = true;
}
if (tflags & MSL_THREAD_FLAG_CURRENT) {
chosen_off = off;
chosen_tid = tid;
found = true;
break; // Current thread wins
}
}
}
} else if (btype == MSL_BT_END_OF_CAPTURE) {
break;
}
off += blen;
}
if (!found) {
return false;
}
// Second pass: seed the register file from the chosen thread block.
ut8 th[32];
if (r_buf_read_at (b, chosen_off + MSL_BLOCK_HEADER_SIZE, th, sizeof (th)) != sizeof (th)) {
return false;
}
// Table 19a: Arch at +0x13 is this thread's mode, 0 = file header ArchType.
ut8 ab[2];
ut16 arch = th[19];
if (!arch && r_buf_read_at (b, 0x32, ab, sizeof (ab)) == sizeof (ab)) {
arch = r_read_le16 (ab);
}
ut32 regcount = r_read_le32 (th + 20);
ut16 namelen = r_read_le16 (th + 24);
ut64 ro = chosen_off + MSL_BLOCK_HEADER_SIZE + 32 + msl_pad8 (namelen);
ut32 i;
for (i = 0; i < regcount; i++) {
ut8 e[8];
if (r_buf_read_at (b, ro, e, sizeof (e)) != sizeof (e)) {
break;
}
ut8 rnamelen = e[0];
ut8 width = e[1];
char name[64] = {0};
if (rnamelen > 0 && rnamelen < sizeof (name)) {
r_buf_read_at (b, ro + 8, (ut8 *)name, rnamelen);
name[sizeof (name) - 1] = 0;
}
ut8 v[8] = {0};
int n = (width > 8)? 8: width;
r_buf_read_at (b, ro + 8 + msl_pad8 (rnamelen), v, n);
if (*name) {
RRegItem *ri = r_reg_get (reg, msl_canonical_reg (name, arch), -1);
if (ri) {
r_reg_set_value (reg, ri, r_read_le64 (v));
}
}
ro += 8 + msl_pad8 (rnamelen) + msl_pad8 (width);
}
if (out_tid) {
*out_tid = chosen_tid;
}
return true;
}
// One-time-per-core initialization: set up the ESIL VM, seed the register
// file from the Thread Context, and seek to the captured PC. Tracked by core
// pointer so reopening a different slice re-initializes.
static void *g_inited_core = NULL;
static void msl_ensure(RDebug *dbg) {
RCore *core = dbg->coreb.core;
if (!core || g_inited_core == core) {
return;
}
g_inited_core = core;
dbg->pid = 1;
dbg->tid = 1;
// Force hard stepping: r_debug_step must call our plugin->step (which
// drives ESIL), not the software-breakpoint step_soft path which assumes
// a real process. Set the struct field directly so it takes effect before
// the first `ds` (the cached config value would apply too late).
dbg->options.swstep = false;
const char *path = (core->io && core->io->desc)? core->io->desc->name: NULL;
if (!path) {
return;
}
if (r_str_startswith (path, "msl://")) {
path += strlen ("msl://");
}
RBuffer *b = r_buf_new_mmap (path, R_PERM_R);
if (!b) {
return;
}
ut8 h[16];
if (r_buf_read_at (b, 0, h, sizeof (h)) != sizeof (h) || memcmp (h, MSL_FILE_MAGIC, 8)
|| (r_read_le32 (h + 12) & MSL_HDR_FLAG_ENCRYPTED)) {
r_unref (b);
return;
}
// Set the architecture from the file header BEFORE initializing ESIL, so
// the register profile matches (over msl:// there is no bin to do this).
ut8 ab[4];
if (r_buf_read_at (b, 0x30, ab, sizeof (ab)) == sizeof (ab) && dbg->coreb.cmdf) {
int bits = 64;
const char *arch = msl_arch_str (r_read_le16 (ab + 2), &bits);
dbg->coreb.cmdf (core, "e asm.arch=%s", arch);
dbg->coreb.cmdf (core, "e asm.bits=%d", bits);
}
// Capture ESIL writes into the io cache (the dump itself is read-only)
// and step via the plugin (hard step) rather than software breakpoints.
dbg->coreb.cmd (core, "e io.cache=true");
dbg->coreb.cmd (core, "e dbg.swstep=false");
// Safety net: a snapshot has no program exit, and unmapped fetches read
// back as the fill byte (which decodes as instructions), so `dc`/`aec`
// without a breakpoint would run forever. Bound it; override with
// `e esil.maxsteps=0` for unlimited.
dbg->coreb.cmd (core, "e esil.maxsteps=1000000");
// Enable ESIL step-back recording (reverse/time-travel debugging via
// dsb/aesb). The config callback only applies once esil.reg exists, i.e.
// after aei, so (re)set it here.
dbg->coreb.cmd (core, "e esil.maxbacksteps=256");
// Follow the PC after each step (the default 32-byte threshold leaves the
// seek behind for small instructions).
dbg->coreb.cmd (core, "e dbg.follow=1");
// The emulator is the source of truth for registers; r2's debug trace
// would re-read PC at sync points and fight the ESIL state.
if (dbg->trace) {
dbg->trace->enabled = false;
}
dbg->coreb.cmd (core, "aei");
// The debugger register arena was built with the host arch at startup;
// rebuild it with the (now arch-correct) anal/ESIL profile so that
// dr / trace_pc read the right register slots.
char *prof = r_anal_get_reg_profile (core->anal);
if (prof) {
r_reg_set_profile_string (dbg->reg, prof);
free (prof);
}
ut64 seeded_tid = 0;
if (msl_seed_regs (core, b, -1, &seeded_tid)) {
dbg->tid = (int) seeded_tid; // so `dpt` marks the active thread
}
r_unref (b);
// Seek to the captured program counter.
const char *pcname = r_reg_alias_getname (core->anal->reg, R_REG_ALIAS_PC);
if (pcname && dbg->coreb.cmdf) {
RRegItem *pc = r_reg_get (core->anal->reg, pcname, -1);
if (pc) {
dbg->coreb.cmdf (core, "s 0x%"PFMT64x, r_reg_get_value (core->anal->reg, pc));
}
}
// Pull the seeded ESIL/anal register state into the debugger arena so the
// r_debug step machinery (PC, trace) sees the captured values.
r_debug_reg_sync (dbg, R_REG_TYPE_ALL, false);
// Route ds/dso/dc to the ESIL stepper: with cfg.debug set, those commands
// go through r_debug_step (arena swap + trace + recoil) which assumes a
// live ptrace process and breaks on a static, emulated snapshot. With it
// cleared, the debug command handlers dispatch to ESIL (aes/aec) instead.
dbg->coreb.cmd (core, "e cfg.debug=false");
}
static bool __msl_attach(RDebug *dbg, int pid) {
RCore *core = dbg->coreb.core;
if (!core) {
return false;
}
g_inited_core = NULL; // force re-init on explicit attach
msl_ensure (dbg);
return true;
}
static bool __msl_detach(RDebug *dbg, int pid) {
return true;
}
// Switch the active thread: re-seed the register file from thread *tid*'s
// captured Thread Context and seek to its PC. Invoked by r_debug_select, e.g.
// `dpt=<tid>` (list captured threads with `dpt`). Returns false (leaving the
// current thread untouched) if the slice has no thread with that id.
static bool __msl_select(RDebug *dbg, int pid, int tid) {
RCore *core = dbg->coreb.core;
if (!core) {
return false;
}
msl_ensure (dbg);
const char *path = (core->io && core->io->desc)? core->io->desc->name: NULL;
if (!path) {
return false;
}
if (r_str_startswith (path, "msl://")) {
path += strlen ("msl://");
}
RBuffer *b = r_buf_new_mmap (path, R_PERM_R);
if (!b) {
return false;
}
bool ok = msl_seed_regs (core, b, tid, NULL);
if (ok) {
// r_debug_select() swaps the register arena and re-syncs from the
// plugin right after this returns, which would discard a seed made only
// into the current arena. The debugger and anal/ESIL share one RReg
// here, so seed the shadow arena too (a no-op when the pool has a single
// arena) so the new thread's registers survive the swap.
r_reg_arena_swap (core->anal->reg, false);
msl_seed_regs (core, b, tid, NULL);
}
r_unref (b);
if (!ok) {
// r2 auto-selects the whole process (pid == tid) on attach; that is
// not a captured thread id, so treat it as a no-op (the Current thread
// was already seeded by msl_ensure). Only a genuine `dpt=<tid>` for an
// absent thread is an error.
if (pid == tid) {
return true;
}
R_LOG_ERROR ("msl: no thread with tid %d in this slice", tid);
return false;
}
// Seek to the selected thread's captured PC. The ESIL/anal arena is the
// source of truth; r_debug_select syncs it into the debugger arena after
// this returns (and sets core->addr to the new PC).
const char *pcname = r_reg_alias_getname (core->anal->reg, R_REG_ALIAS_PC);
if (pcname && dbg->coreb.cmdf) {
RRegItem *pc = r_reg_get (core->anal->reg, pcname, -1);
if (pc) {
dbg->coreb.cmdf (core, "s 0x%"PFMT64x, r_reg_get_value (core->anal->reg, pc));
}
}
return true;
}
static bool __msl_step(RDebug *dbg) {
RCore *core = dbg->coreb.core;
if (!core) {
return false;
}
msl_ensure (dbg);
// r2 re-enables these when entering debug mode (after attach); keep them
// off at step time so ESIL stepping isn't fought by trace/software-step.
if (dbg->trace) {
dbg->trace->enabled = false;
}
dbg->options.swstep = false;
dbg->coreb.cmd (core, "aes");
return true;
}
static bool __msl_step_over(RDebug *dbg) {
RCore *core = dbg->coreb.core;
if (!core) {
return false;
}
msl_ensure (dbg);
dbg->coreb.cmd (core, "aeso");
return true;
}
static bool __msl_continue(RDebug *dbg, int pid, int tid, int sig) {
RCore *core = dbg->coreb.core;
if (!core) {
return false;
}
msl_ensure (dbg);
// ESIL continue: runs until a breakpoint, trap or invalid instruction.
dbg->coreb.cmd (core, "aec");
return true;
}
static RDebugReasonType __msl_wait(RDebug *dbg, int pid) {
return R_DEBUG_REASON_NONE;
}
static char *__msl_reg_profile(RDebug *dbg) {
return r_anal_get_reg_profile (dbg->anal);
}
// Expose the captured memory regions as debug maps (so dm/om work and `dgm`
// can re-dump). Uncompressed regions only.
static RList *__msl_map_get(RDebug *dbg) {
RCore *core = dbg->coreb.core;
const char *path = (core && core->io && core->io->desc)? core->io->desc->name: NULL;
if (!path) {
return NULL;
}
if (r_str_startswith (path, "msl://")) {
path += strlen ("msl://");
}
RBuffer *b = r_buf_new_mmap (path, R_PERM_R);
if (!b) {
return NULL;
}
RList *list = r_list_newf ((RListFree)r_debug_map_free);
ut8 h[16];
if (r_buf_read_at (b, 0, h, sizeof (h)) != sizeof (h) || memcmp (h, MSL_FILE_MAGIC, 8)
|| (r_read_le32 (h + 12) & MSL_HDR_FLAG_ENCRYPTED)) {
r_unref (b);
return list;
}
ut64 fsize = r_buf_size (b);
ut64 off = h[9];
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (b, off, bh, sizeof (bh)) != sizeof (bh) || memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut16 bflags = r_read_le16 (bh + 6);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE) {
break;
}
if (btype == 0x0001 && !(bflags & 1)) { // uncompressed Memory Region
ut8 p[32];
if (r_buf_read_at (b, off + MSL_BLOCK_HEADER_SIZE, p, sizeof (p)) == sizeof (p)) {
ut64 base = r_read_le64 (p);
ut64 size = r_read_le64 (p + 8);
ut8 prot = p[16];
int perm = ((prot & 1)? R_PERM_R: 0) | ((prot & 2)? R_PERM_W: 0)
| ((prot & 4)? R_PERM_X: 0);
char *nm = strdup ("msl");
RDebugMap *m = r_debug_map_new (nm, base, base + size, perm, 0);
free (nm);
if (m) {
r_list_append (list, m);
}
}
}
if (btype == 0x0FFF) {
break;
}
off += blen;
}
r_unref (b);
return list;
}
// Mirror the ESIL/anal register arena so `dr` reflects emulation progress.
static bool __msl_reg_read(RDebug *dbg, int type, ut8 *buf, int size) {
RCore *core = dbg->coreb.core;
msl_ensure (dbg);
RReg *reg = (core && core->anal)? core->anal->reg: dbg->reg;
int sz = 0;
ut8 *bytes = r_reg_get_bytes (reg, type, &sz);
if (!bytes) {
return false;
}
memcpy (buf, bytes, R_MIN (size, sz));
free (bytes);
return true;
}
static bool __msl_reg_write(RDebug *dbg, int type, const ut8 *buf, int size) {
RCore *core = dbg->coreb.core;
RReg *reg = (core && core->anal)? core->anal->reg: dbg->reg;
return r_reg_set_bytes (reg, type, buf, size);
}
// List the captured threads (one Thread Context block each) so `dpt` shows
// them with their captured PC. The Current thread is reported as running ('r'),
// the rest as stopped ('s'); r_debug_thread_list marks dbg->tid with '*'.
static RList *__msl_threads(RDebug *dbg, int pid) {
RCore *core = dbg->coreb.core;
const char *path = (core && core->io && core->io->desc)? core->io->desc->name: NULL;
if (!path) {
return NULL;
}
if (r_str_startswith (path, "msl://")) {
path += strlen ("msl://");
}
RBuffer *b = r_buf_new_mmap (path, R_PERM_R);
if (!b) {
return NULL;
}
RList *list = r_list_newf ((RListFree)r_debug_pid_free);
ut8 h[16];
if (r_buf_read_at (b, 0, h, sizeof (h)) != sizeof (h) || memcmp (h, MSL_FILE_MAGIC, 8)
|| (r_read_le32 (h + 12) & MSL_HDR_FLAG_ENCRYPTED)) {
r_unref (b);
return list;
}
ut64 fsize = r_buf_size (b);
ut64 off = h[9];
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (b, off, bh, sizeof (bh)) != sizeof (bh) || memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE) {
break;
}
if (btype == MSL_BT_THREAD_CONTEXT) {
ut8 th[32];
if (r_buf_read_at (b, off + MSL_BLOCK_HEADER_SIZE, th, sizeof (th)) == sizeof (th)) {
ut64 tid = r_read_le64 (th);
ut16 tflags = r_read_le16 (th + 16);
ut32 regcount = r_read_le32 (th + 20);
ut16 namelen = r_read_le16 (th + 24);
ut64 ro = off + MSL_BLOCK_HEADER_SIZE + 32 + msl_pad8 (namelen);
ut64 pc = 0;
ut32 i;
for (i = 0; i < regcount; i++) {
ut8 e[8];
if (r_buf_read_at (b, ro, e, sizeof (e)) != sizeof (e)) {
break;
}
ut8 rnamelen = e[0];
ut8 width = e[1];
ut16 rflags = r_read_le16 (e + 2);
if (rflags & MSL_REG_FLAG_PC) {
ut8 v[8] = {0};
int n = (width > 8)? 8: width;
r_buf_read_at (b, ro + 8 + msl_pad8 (rnamelen), v, n);
pc = r_read_le64 (v);
}
ro += 8 + msl_pad8 (rnamelen) + msl_pad8 (width);
}
char status = msl_thread_status (th[18], tflags);
RDebugPid *p = r_debug_pid_new ("msl", (int) tid, 0, status, pc);
if (p) {
r_list_append (list, p);
}
}
} else if (btype == MSL_BT_END_OF_CAPTURE) {
break;
}
off += blen;
}
r_unref (b);
return list;
}
RDebugPlugin r_debug_plugin_msl = {
.meta = {
.name = "msl",
.author = "memslicer",
.desc = "Memory Slice (.msl) emulated debug backend (ESIL)",
.license = "LGPL-3.0-only",
},
.arch = "any",
.bits = R_SYS_BITS_PACK2 (32, 64),
.canstep = 1,
.attach = &__msl_attach,
.detach = &__msl_detach,
.select = &__msl_select,
.threads = &__msl_threads,
.step = &__msl_step,
.step_over = &__msl_step_over,
.cont = &__msl_continue,
.wait = &__msl_wait,
.reg_profile = &__msl_reg_profile,
.reg_read = &__msl_reg_read,
.reg_write = &__msl_reg_write,
.map_get = &__msl_map_get,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_DBG,
.data = &r_debug_plugin_msl,
.version = R2_VERSION
};
#endif
+636
View File
@@ -0,0 +1,636 @@
/* radare - LGPL - Copyright 2026 - memslicer */
// IO plugin for Memory Slice (.msl) process memory dumps.
//
// The plugin exposes the captured process *virtual* address space: an IO
// offset is interpreted as a virtual address. Each Memory Region block
// (type 0x0001) is indexed at open time; reads translate the requested
// address to the matching page in the region's PageData, honoring the
// three-state page map (Captured / Failed / Unmapped). Failed, unmapped
// and unbacked addresses read back as the IO fill byte.
//
// MVP scope: uncompressed, unencrypted slices. Compressed regions are
// indexed but read back as fill bytes (decode is future work); encrypted
// files are rejected.
#include <r_io.h>
#include <r_hash.h>
#define MSL_FILE_MAGIC "MEMSLICE"
#define MSL_BLOCK_MAGIC "MSLC"
#define MSL_HDR_FLAG_ENCRYPTED 0x4
#define MSL_BLOCK_FLAG_COMPRESSED 0x1
#define MSL_COMPALGO_ZSTD 1
#define MSL_COMPALGO_LZ4 2
#define MSL_BT_MEMORY_REGION 0x0001
#define MSL_BT_END_OF_CAPTURE 0x0FFF
#define MSL_BLOCK_HEADER_SIZE 80
// Guard against absurd page counts from a corrupt region (256M pages).
#define MSL_MAX_PAGES (1ULL << 28)
// Forward declaration: external plugins no longer get this from r_io.h.
extern RIOPlugin r_io_plugin_msl;
typedef struct {
ut64 vaddr; // region base virtual address
ut64 vsize; // region size in bytes
ut32 psl; // page size log2
ut64 npages;
ut64 data_off; // offset of PageData within the source (file or mem)
ut32 *cumcap; // cumcap[i] = captured pages in [0, i); length npages + 1
ut8 *mem; // decompressed payload (NULL = read PageData from file)
} RMslRegion;
typedef struct {
RBuffer *b;
RMslRegion *regions;
int nregions;
ut64 cur; // current virtual address
ut64 maxaddr; // highest vaddr + vsize seen
int pid; // PID from the file header (for debug mode)
int unsupported_comp; // regions skipped due to an undecodable codec (zstd)
} RIOMsl;
static inline ut64 msl_pad8(ut64 n) {
return (n + 7) & ~(ut64)7;
}
// Decompress one LZ4 block. memslicer emits `lz4.block` (size-prefixed); the
// caller passes the raw block (after the 4-byte size prefix) and the exact
// uncompressed length, so decoding stops once `dlen` bytes are produced and
// any trailing 8-byte alignment padding is ignored. Returns bytes written or
// -1 on malformed input.
static int msl_lz4_block(const ut8 *src, int slen, ut8 *dst, int dlen) {
int sp = 0, dp = 0;
while (dp < dlen) {
if (sp >= slen) {
return -1;
}
ut8 token = src[sp++];
int litlen = token >> 4;
if (litlen == 15) {
ut8 e;
do {
if (sp >= slen) {
return -1;
}
e = src[sp++];
litlen += e;
} while (e == 255);
}
if (litlen > dlen - dp || litlen > slen - sp) {
return -1;
}
memcpy (dst + dp, src + sp, litlen);
dp += litlen;
sp += litlen;
if (dp >= dlen) {
break; // final literal run
}
if (sp + 2 > slen) {
return -1;
}
int offset = src[sp] | (src[sp + 1] << 8);
sp += 2;
if (offset == 0 || offset > dp) {
return -1;
}
int matchlen = token & 0xf;
if (matchlen == 15) {
ut8 e;
do {
if (sp >= slen) {
return -1;
}
e = src[sp++];
matchlen += e;
} while (e == 255);
}
matchlen += 4;
if (matchlen > dlen - dp) {
matchlen = dlen - dp;
}
int mpos = dp - offset;
int k;
for (k = 0; k < matchlen; k++) {
dst[dp + k] = dst[mpos + k];
}
dp += matchlen;
}
return dp;
}
static int msl_page_state(ut8 *psm, ut64 page) {
ut8 byte = psm[page >> 2];
int bitpos = 6 - (int)((page & 3) * 2);
return (byte >> bitpos) & 3;
}
// Decompress a compressed region payload into a freshly malloc'd buffer.
// On-disk layout (spec 4.2.1): UncompressedSize(8B) + CompressedData, padded.
// memslicer's lz4 codec is `lz4.block` (a 4-byte size prefix then the block).
// Returns the buffer (caller frees) and sets *out_len, or NULL on failure /
// unsupported codec (zstd has no decoder in radare2).
static ut8 *msl_decompress_payload(RIOMsl *mo, ut64 payload_off, ut32 blen,
int algo, ut64 *out_len) {
if (algo != MSL_COMPALGO_LZ4) {
mo->unsupported_comp++; // zstd / unknown: cannot decode here
return NULL;
}
if (blen < MSL_BLOCK_HEADER_SIZE + 8 + 4) {
return NULL;
}
ut8 szbuf[8];
if (r_buf_read_at (mo->b, payload_off, szbuf, 8) != 8) {
return NULL;
}
ut64 ulen = r_read_le64 (szbuf);
if (ulen == 0 || ulen > (1ULL << 32)) {
return NULL;
}
ut64 comp_len = (ut64)blen - MSL_BLOCK_HEADER_SIZE - 8;
ut8 *cbuf = malloc ((size_t)comp_len);
if (!cbuf) {
return NULL;
}
if (r_buf_read_at (mo->b, payload_off + 8, cbuf, comp_len) != (st64)comp_len) {
free (cbuf);
return NULL;
}
ut8 *mem = malloc ((size_t)ulen);
if (!mem) {
free (cbuf);
return NULL;
}
// Skip the 4-byte lz4.block size prefix; decode the raw block.
int n = msl_lz4_block (cbuf + 4, (int)(comp_len - 4), mem, (int)ulen);
free (cbuf);
if (n != (int)ulen) {
R_LOG_WARN ("msl: lz4 decode failed for a compressed region");
free (mem);
return NULL;
}
*out_len = ulen;
return mem;
}
static bool msl_add_region(RIOMsl *mo, ut64 payload_off, ut16 bflags, ut32 blen) {
ut8 *mem = NULL; // decompressed payload (compressed regions)
ut64 mem_len = 0;
if (bflags & MSL_BLOCK_FLAG_COMPRESSED) {
mem = msl_decompress_payload (mo, payload_off, blen,
(bflags >> 1) & 3, &mem_len);
if (!mem) {
return false;
}
}
// Read the fixed 32-byte region header from the decompressed buffer or
// straight from the file.
ut8 p[32];
if (mem) {
if (mem_len < 32) {
free (mem);
return false;
}
memcpy (p, mem, 32);
} else if (r_buf_read_at (mo->b, payload_off, p, sizeof (p)) != sizeof (p)) {
return false;
}
ut64 base = r_read_le64 (p);
ut64 size = r_read_le64 (p + 8);
ut8 psl = p[18];
if (psl < 10 || psl > 40) {
R_LOG_WARN ("msl: skipping region @ 0x%"PFMT64x" with invalid PageSizeLog2 %d", base, psl);
free (mem);
return false;
}
ut64 page_size = 1ULL << psl;
if (size == 0 || (size & (page_size - 1))) {
R_LOG_WARN ("msl: skipping region @ 0x%"PFMT64x" with misaligned size", base);
free (mem);
return false;
}
ut64 npages = size >> psl;
if (npages > MSL_MAX_PAGES) {
R_LOG_WARN ("msl: skipping oversized region @ 0x%"PFMT64x" (%"PFMT64u" pages)", base, npages);
free (mem);
return false;
}
ut64 psm_bytes = msl_pad8 ((npages + 3) / 4);
// Locate the PageStateMap (in mem or file) and the PageData offset.
ut64 data_off;
ut8 *psm_tmp = NULL;
const ut8 *psm;
if (mem) {
if (32 + psm_bytes > mem_len) {
free (mem);
return false;
}
psm = mem + 32;
data_off = 32 + psm_bytes; // offset within mem
} else {
data_off = payload_off + 32 + psm_bytes; // file offset
psm_tmp = malloc (psm_bytes? (size_t)psm_bytes: 1);
if (!psm_tmp) {
return false;
}
if (psm_bytes && r_buf_read_at (mo->b, payload_off + 32, psm_tmp, psm_bytes) != (st64)psm_bytes) {
free (psm_tmp);
return false;
}
psm = psm_tmp;
}
ut32 *cumcap = malloc (sizeof (ut32) * (size_t)(npages + 1));
if (!cumcap) {
free (mem);
free (psm_tmp);
return false;
}
ut32 running = 0;
ut64 i;
for (i = 0; i < npages; i++) {
cumcap[i] = running;
if (msl_page_state ((ut8 *)psm, i) == 0) {
running++;
}
}
cumcap[npages] = running;
free (psm_tmp);
RMslRegion *nr = realloc (mo->regions, sizeof (RMslRegion) * (mo->nregions + 1));
if (!nr) {
free (cumcap);
free (mem);
return false;
}
mo->regions = nr;
RMslRegion *rg = &mo->regions[mo->nregions++];
rg->vaddr = base;
rg->vsize = size;
rg->psl = psl;
rg->npages = npages;
rg->data_off = data_off;
rg->cumcap = cumcap;
rg->mem = mem;
if (base + size > mo->maxaddr) {
mo->maxaddr = base + size;
}
return true;
}
static int msl_region_cmp(const void *a, const void *b) {
const RMslRegion *ra = a;
const RMslRegion *rb = b;
if (ra->vaddr < rb->vaddr) {
return -1;
}
if (ra->vaddr > rb->vaddr) {
return 1;
}
return 0;
}
static RMslRegion *msl_region_at(RIOMsl *mo, ut64 addr) {
int lo = 0, hi = mo->nregions - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
RMslRegion *rg = &mo->regions[mid];
if (addr < rg->vaddr) {
hi = mid - 1;
} else if (addr >= rg->vaddr + rg->vsize) {
lo = mid + 1;
} else {
return rg;
}
}
return NULL;
}
// Smallest region base strictly greater than addr, or UT64_MAX.
static ut64 msl_next_region(RIOMsl *mo, ut64 addr) {
ut64 best = UT64_MAX;
int i;
for (i = 0; i < mo->nregions; i++) {
ut64 base = mo->regions[i].vaddr;
if (base > addr && base < best) {
best = base;
}
}
return best;
}
// HashAlgo byte (file header offset 0x3d). Only SHA-256 chains are checked.
#define MSL_HASHALGO_OFF 0x3d
#define MSL_HASHALGO_SHA256 0x01
// SHA-256 over [off, off+len) of mo->b, streamed in chunks. The digest goes to
// out32 (if set) and, when 'cumulative' is given, the same bytes are also fed
// into that running hash (for the End-of-Capture FileHash). Returns false on a
// short read.
static bool msl_hash_range(RBuffer *b, ut64 off, ut64 len, RHash *cumulative, ut8 *out32) {
RHash *h = r_hash_new (true, R_HASH_SHA256);
if (!h) {
return false;
}
r_hash_do_begin (h, R_HASH_SHA256);
ut8 buf[0x10000];
ut64 done = 0;
while (done < len) {
ut64 n = R_MIN ((ut64)sizeof (buf), len - done);
if (r_buf_read_at (b, off + done, buf, (int)n) != (int)n) {
r_hash_free (h);
return false;
}
r_hash_do_sha256 (h, buf, (int)n);
if (cumulative) {
r_hash_do_sha256 (cumulative, buf, (int)n);
}
done += n;
}
r_hash_do_end (h, R_HASH_SHA256);
if (out32) {
memcpy (out32, h->digest, 32);
}
r_hash_free (h);
return true;
}
// Verify the per-block hash chain and the End-of-Capture FileHash. The producer
// seeds the chain with SHA-256(file header) and stores, in each block's [48:80]
// PrevHash, the SHA-256 of the previous element (header + padded payload). The
// EoC block carries a FileHash over the file header and every preceding block.
// A mismatch means the slice was truncated or tampered with: we warn and keep
// going so a damaged-but-readable slice is still usable.
static void msl_verify_chain(RIOMsl *mo, ut8 header_size, ut64 fsize) {
ut8 algo = 0;
if (r_buf_read_at (mo->b, MSL_HASHALGO_OFF, &algo, 1) != 1) {
return;
}
if (algo != MSL_HASHALGO_SHA256) {
return; // unknown/absent algorithm: nothing we can check
}
RHash *fh = r_hash_new (true, R_HASH_SHA256);
if (!fh) {
return;
}
r_hash_do_begin (fh, R_HASH_SHA256);
ut8 chain[32];
// Seed: SHA-256(file header), also the first bytes of the FileHash.
if (!msl_hash_range (mo->b, 0, header_size, fh, chain)) {
r_hash_free (fh);
return;
}
bool chain_ok = true;
bool have_eoc = false;
bool file_ok = true;
ut64 off = header_size;
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (mo->b, off, bh, sizeof (bh)) != sizeof (bh)) {
break;
}
if (memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE || off + blen > fsize) {
break;
}
// Every block records the hash of the previous element at [48:80].
if (memcmp (bh + 48, chain, 32)) {
chain_ok = false;
}
if (btype == MSL_BT_END_OF_CAPTURE) {
have_eoc = true;
r_hash_do_end (fh, R_HASH_SHA256);
ut8 stored[32];
if (r_buf_read_at (mo->b, off + MSL_BLOCK_HEADER_SIZE, stored, sizeof (stored)) != sizeof (stored)
|| memcmp (stored, fh->digest, 32)) {
file_ok = false;
}
break; // EoC itself is excluded from the FileHash
}
// Fold this block into the FileHash and advance the chain to its hash.
if (!msl_hash_range (mo->b, off, blen, fh, chain)) {
break;
}
off += blen;
}
if (!chain_ok) {
R_LOG_WARN ("msl: block hash chain mismatch; the slice may be truncated or tampered with");
}
if (have_eoc && !file_ok) {
R_LOG_WARN ("msl: End-of-Capture FileHash mismatch; the slice may be corrupt");
}
r_hash_free (fh);
}
static bool msl_parse(RIOMsl *mo) {
ut8 h[16];
if (r_buf_read_at (mo->b, 0, h, sizeof (h)) != sizeof (h)) {
return false;
}
if (memcmp (h, MSL_FILE_MAGIC, 8)) {
return false;
}
ut32 flags = r_read_le32 (h + 12);
if (flags & MSL_HDR_FLAG_ENCRYPTED) {
R_LOG_ERROR ("msl: encrypted slices are not supported yet");
return false;
}
ut8 header_size = h[9];
ut8 pidbuf[4];
if (r_buf_read_at (mo->b, 0x34, pidbuf, sizeof (pidbuf)) == sizeof (pidbuf)) {
mo->pid = (int)r_read_le32 (pidbuf);
}
if (mo->pid <= 0) {
mo->pid = 1;
}
ut64 fsize = r_buf_size (mo->b);
ut64 off = header_size;
while (off + MSL_BLOCK_HEADER_SIZE <= fsize) {
ut8 bh[MSL_BLOCK_HEADER_SIZE];
if (r_buf_read_at (mo->b, off, bh, sizeof (bh)) != sizeof (bh)) {
break;
}
if (memcmp (bh, MSL_BLOCK_MAGIC, 4)) {
break;
}
ut16 btype = r_read_le16 (bh + 4);
ut16 bflags = r_read_le16 (bh + 6);
ut32 blen = r_read_le32 (bh + 8);
if (blen < MSL_BLOCK_HEADER_SIZE) {
break;
}
if (btype == MSL_BT_MEMORY_REGION) {
msl_add_region (mo, off + MSL_BLOCK_HEADER_SIZE, bflags, blen);
}
if (btype == MSL_BT_END_OF_CAPTURE) {
break;
}
off += blen;
}
if (mo->nregions > 1) {
qsort (mo->regions, mo->nregions, sizeof (RMslRegion), msl_region_cmp);
}
msl_verify_chain (mo, header_size, fsize);
if (mo->unsupported_comp > 0) {
R_LOG_WARN ("msl: %d region(s) use zstd compression, which radare2 "
"cannot decode; those addresses read as the fill byte. "
"Recapture with '-c lz4' or '-c none', or use memslicer-emu.",
mo->unsupported_comp);
}
return true;
}
static int __read(RIO *io, RIODesc *fd, ut8 *buf, int count) {
if (!fd || !fd->data || count < 0) {
return -1;
}
RIOMsl *mo = fd->data;
memset (buf, io->Oxff, count);
ut64 addr = mo->cur;
int i = 0;
while (i < count) {
ut64 cur = addr + i;
RMslRegion *rg = msl_region_at (mo, cur);
if (!rg) {
ut64 next = msl_next_region (mo, cur);
if (next == UT64_MAX || next >= addr + count) {
break;
}
i = (int)(next - addr);
continue;
}
ut64 roff = cur - rg->vaddr;
ut64 page = roff >> rg->psl;
ut64 page_size = 1ULL << rg->psl;
ut64 in_page = roff & (page_size - 1);
ut64 want = page_size - in_page;
ut64 region_left = rg->vsize - roff;
want = R_MIN (want, region_left);
want = R_MIN (want, (ut64)(count - i));
// Page state is recomputed from cumcap deltas to avoid keeping the
// raw map resident: a page is captured iff its cumulative count
// increases at the next index.
bool captured = page < rg->npages
&& rg->cumcap[page + 1] > rg->cumcap[page];
if (captured) {
ut64 src_off = rg->data_off + (ut64)rg->cumcap[page] * page_size + in_page;
if (rg->mem) {
memcpy (buf + i, rg->mem + src_off, want);
} else {
r_buf_read_at (mo->b, src_off, buf + i, want);
}
}
i += (int)want;
}
mo->cur += count;
return count;
}
static ut64 __lseek(RIO *io, RIODesc *fd, ut64 offset, int whence) {
if (!fd || !fd->data) {
return offset;
}
RIOMsl *mo = fd->data;
switch (whence) {
case R_IO_SEEK_SET:
mo->cur = offset;
break;
case R_IO_SEEK_CUR:
mo->cur += offset;
break;
case R_IO_SEEK_END:
mo->cur = mo->maxaddr;
break;
}
return mo->cur;
}
static bool __check(RIO *io, const char *pathname, bool many) {
return r_str_startswith (pathname, "msl://");
}
static int __getpid(RIODesc *desc) {
RIOMsl *mo = (desc && desc->data)? desc->data: NULL;
return mo? mo->pid: -1;
}
static int __gettid(RIODesc *desc) {
return __getpid (desc);
}
static RIODesc *__open(RIO *io, const char *pathname, int rw, int mode) {
if (!__check (io, pathname, false)) {
return NULL;
}
const char *path = pathname + strlen ("msl://");
RBuffer *b = r_buf_new_mmap (path, R_PERM_R);
if (!b) {
R_LOG_ERROR ("msl: cannot open %s", path);
return NULL;
}
RIOMsl *mo = R_NEW0 (RIOMsl);
if (!mo) {
r_unref (b);
return NULL;
}
mo->b = b;
if (!msl_parse (mo)) {
R_LOG_ERROR ("msl: %s is not a valid Memory Slice file", path);
r_unref (b);
free (mo->regions);
free (mo);
return NULL;
}
R_LOG_INFO ("msl: indexed %d memory region(s), VA up to 0x%"PFMT64x, mo->nregions, mo->maxaddr);
return r_io_desc_new (io, &r_io_plugin_msl, pathname, R_PERM_R, mode, mo);
}
static bool __close(RIODesc *fd) {
if (!fd || !fd->data) {
return false;
}
RIOMsl *mo = fd->data;
int i;
for (i = 0; i < mo->nregions; i++) {
free (mo->regions[i].cumcap);
free (mo->regions[i].mem);
}
free (mo->regions);
r_unref (mo->b);
free (mo);
fd->data = NULL;
return true;
}
RIOPlugin r_io_plugin_msl = {
.meta = {
.name = "msl",
.desc = "Memory Slice (.msl) process memory dump",
.author = "memslicer",
.license = "LGPL-3.0-only",
},
.uris = "msl://",
.isdbg = true,
.open = __open,
.close = __close,
.read = __read,
.check = __check,
.seek = __lseek,
.getpid = __getpid,
.gettid = __gettid,
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_IO,
.data = &r_io_plugin_msl,
.version = R2_VERSION
};
#endif
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env python3
# Minimal .msl generator mirroring radare2-extras msl/core_msl.c (mslw_*).
# Produces a valid file header + Process Identity + Thread Context + one
# Memory Region + End-of-Capture, with the exact SHA-256 chain + FileHash.
import struct, hashlib, sys
def sha(a, b=b''):
h = hashlib.sha256()
if a: h.update(a)
if b: h.update(b)
return h.digest()
def pad8(d):
return d + b'\x00' * ((-len(d)) % 8)
class Dump:
def __init__(self):
self.out = b''
self.fh = hashlib.sha256() # cumulative FileHash
self.prev = b''
def seed(self, filehdr):
self.out += filehdr
self.fh.update(filehdr)
self.prev = sha(filehdr) # mslw_sha256(fhdr,64,NULL,0)
def block(self, btype, payload, feed=True):
padded = pad8(payload)
hdr = bytearray(80)
hdr[0:4] = b'MSLC'
struct.pack_into('<H', hdr, 4, btype)
struct.pack_into('<I', hdr, 8, 80 + len(padded))
struct.pack_into('<H', hdr, 12, 1) # payload version
# uuid [16:32] left as zeros (random in producer; irrelevant to parsing)
hdr[48:80] = self.prev
self.out += bytes(hdr) + padded
if feed:
self.fh.update(bytes(hdr)); self.fh.update(padded)
self.prev = sha(bytes(hdr), padded)
d = Dump()
ARCH_X86_64, OS_LINUX, PID = 1, 1, 1234
cap = (1 << 0) | (1 << 8) | (1 << 2) # MemoryRegions|ProcessIdentity|ThreadContexts
fhdr = bytearray(64)
fhdr[0:8] = b'MEMSLICE'
fhdr[8] = 1; fhdr[9] = 64
struct.pack_into('<H', fhdr, 10, 0x0101)
struct.pack_into('<Q', fhdr, 16, cap)
struct.pack_into('<Q', fhdr, 40, 0)
struct.pack_into('<H', fhdr, 48, OS_LINUX)
struct.pack_into('<H', fhdr, 50, ARCH_X86_64)
struct.pack_into('<I', fhdr, 52, PID)
fhdr[61] = 0x01 # HashAlgo SHA-256
d.seed(bytes(fhdr))
# Process Identity (0x0040)
exe = b'/tmp/sample\x00'
pi = bytearray(24)
struct.pack_into('<H', pi, 16, len(exe))
pi = bytes(pi) + pad8(exe)
d.block(0x0040, pi)
# Thread Context (0x0011): regs rip (pc), rsp (sp), rax
def reg(name, width, val, flags):
nm = name.encode() + b'\x00'
e = bytearray(8)
e[0] = len(nm); e[1] = width
struct.pack_into('<H', e, 2, flags)
return bytes(e) + pad8(nm) + pad8(struct.pack('<Q', val)[:width])
def thread_block(tid, current, rip, rsp, rax):
regs = reg('rip', 8, rip, 1) + reg('rsp', 8, rsp, 2) + reg('rax', 8, rax, 0)
tc = bytearray(32)
struct.pack_into('<Q', tc, 0, tid) # ThreadID
struct.pack_into('<H', tc, 16, 1 if current else 0) # Flags: Current
tc[18] = 3 # ThreadState: Stopped
struct.pack_into('<I', tc, 20, 3) # RegCount
d.block(0x0011, bytes(tc) + regs)
thread_block(7, True, 0x1000, 0x2ff0, 0x2a)
# With --mt, add a second (non-Current) thread parked further into the region,
# to exercise thread listing (dpt) and selection (dpt=<tid>).
if '--mt' in sys.argv:
thread_block(8, False, 0x1100, 0x2fe0, 0xbb)
def memory_region(base, prot, payload):
"""Emit a Memory Region (0x0001) with all pages captured holding *payload*
(padded to a whole 4 KiB page count)."""
page = 4096
size = (len(payload) + page - 1) // page * page or page
payload = payload.ljust(size, b'\x90')
npages = size // page
psm_bytes = (((npages + 3) // 4) + 7) & ~7 # all zero = all CAPTURED
psm = b'\x00' * psm_bytes
mr = bytearray(32)
struct.pack_into('<Q', mr, 0, base)
struct.pack_into('<Q', mr, 8, size)
mr[16] = prot
mr[18] = 12 # PageSizeLog2
d.block(0x0001, bytes(mr) + psm + payload)
# Memory Region (0x0001): base 0x1000, size 0x2000 (2 pages), all captured.
memory_region(0x1000, 1 | 4, bytes(((0x1000 + i) & 0xff) for i in range(0x2000)))
# With --pe, add a module with a minimal in-memory PE export table, to exercise
# symbol/export resolution (bin.msl `is` / `il`).
if '--pe' in sys.argv:
MOD_BASE = 0x10000
# ModuleEntry (0x0002): BaseAddr, ModuleSize, PathLen, VerLen, rsv, path
mpath = b'C:\\Windows\\System32\\test.dll\x00'
me = bytearray(24)
struct.pack_into('<Q', me, 0, MOD_BASE)
struct.pack_into('<Q', me, 8, 0x1000)
struct.pack_into('<H', me, 16, len(mpath))
d.block(0x0002, bytes(me) + pad8(mpath))
img = bytearray(0x1000)
img[0:2] = b'MZ'
struct.pack_into('<I', img, 0x3c, 0x80) # e_lfanew
img[0x80:0x84] = b'PE\x00\x00'
struct.pack_into('<H', img, 0x84, 0x8664) # Machine x86_64
struct.pack_into('<H', img, 0x94, 0xf0) # SizeOfOptionalHeader
struct.pack_into('<H', img, 0x98, 0x20b) # Optional magic: PE32+
struct.pack_into('<I', img, 0x108, 0x200) # DataDirectory[0].RVA (export)
struct.pack_into('<I', img, 0x10c, 0x80) # DataDirectory[0].Size
# IMAGE_EXPORT_DIRECTORY at RVA 0x200
struct.pack_into('<I', img, 0x200 + 0x14, 1) # NumberOfFunctions
struct.pack_into('<I', img, 0x200 + 0x18, 1) # NumberOfNames
struct.pack_into('<I', img, 0x200 + 0x1c, 0x240) # AddressOfFunctions
struct.pack_into('<I', img, 0x200 + 0x20, 0x250) # AddressOfNames
struct.pack_into('<I', img, 0x200 + 0x24, 0x260) # AddressOfNameOrdinals
struct.pack_into('<I', img, 0x240, 0x300) # functions[0] RVA
struct.pack_into('<I', img, 0x250, 0x270) # names[0] RVA
struct.pack_into('<H', img, 0x260, 0) # ordinals[0]
img[0x270:0x279] = b'MyExport\x00' # export name
img[0x300] = 0xc3 # exported function body (ret)
memory_region(MOD_BASE, 1 | 4, bytes(img))
# End-of-Capture (0x0FFF): FileHash + timestamp, not fed into FileHash.
eoc = bytearray(48)
eoc[0:32] = d.fh.digest()
d.block(0x0FFF, bytes(eoc), feed=False)
outpath = next((a for a in sys.argv[1:] if not a.startswith('-')), None)
if not outpath:
sys.exit("usage: genmsl.py [--mt] OUTPUT.msl")
open(outpath, 'wb').write(d.out)
print("wrote %s (%d bytes)" % (outpath, len(d.out)))
+1 -1
View File
@@ -25,7 +25,7 @@ arch_au.$(LIBEXT): arch_au.c
#anal_au.$(LIBEXT): anal_au.c
# $(CC) $(CFLAGS) $(LDFLAGS) anal_au.c -o anal_au.$(LIBEXT)
core_au.$(LIBEXT): core_au.c noise.c
core_au.$(LIBEXT): core_au.c noise.c stream.inc.c
$(CC) $(CFLAGS) $(LDFLAGS) noise.c core_au.c -o core_au.$(LIBEXT)
user-install install:
+21
View File
@@ -8,3 +8,24 @@ play sounds from the custom visual mode.
Slides: [RidingTheWave.pdf](https://github.com/radareorg/r2con2018/raw/master/talks/riding-the-wave/r2con2018-RidingTheWave.pdf)
--pancake
## Streaming PCM
`auq` queues signed little-endian PCM for a background playback thread. It
leaves the current architecture, seek, and block size unchanged. Samples are
copied from mapped IO; the worker only touches its queue and audio device.
- `auqi [rate bits channels [driver]]`: initialize (default 44100 Hz, 16-bit,
stereo). Only 16-bit mono/stereo is accepted. The optional libao driver name
supports `null` for headless tests. `$?` is 1 on success, 0 on failure.
- `auq address length`: enqueue whole sample frames. `$?` is the accepted byte
count, or 0 when the bounded 32768-byte queue is full or playback failed.
Retry after it drains; there is no blocking wait for queue space.
- `auqs`: show queued bytes, or -1 on device failure.
- `auq-`: stop playback, discard pending samples, join the thread, and close
its device. Plugin unload performs the same cleanup.
- `auq?`: help and capability probe (`$? = 1`).
This interface is used by radare2's `r2gb play` and `r2gb audio` commands.
Use `python3 test-stream.py` after building for silent streaming/lifecycle
checks. Set `R2` to select the radare2 executable.
Loaded 100 of 196 files, more files were not shown because too many files have changed in this diff. Show more