mirror of
https://github.com/radareorg/radare2-extras.git
synced 2026-11-07 14:48:14 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee031a7021 | ||
|
|
0a0327aa45 | ||
|
|
9870775224 | ||
|
|
d5e7a28186 | ||
|
|
c9571534b9 | ||
|
|
5fff0016a3 | ||
|
|
f2199032a7 | ||
|
|
5d28dc6de4 | ||
|
|
645f4154ff | ||
|
|
899e3344ff | ||
|
|
6493987c72 | ||
|
|
ba096f3546 | ||
|
|
bfab794507 | ||
|
|
619f60a7df | ||
|
|
f4f11b90c3 | ||
|
|
f58de93399 | ||
|
|
58a5ac18e6 | ||
|
|
9b0adabca8 | ||
|
|
219b1f7c07 | ||
|
|
a1b8861f9d | ||
|
|
85f40b5d6c | ||
|
|
a8d4a6a520 | ||
|
|
e108f90f79 | ||
|
|
5b9a32f8c4 | ||
|
|
71ab77c46b | ||
|
|
b6da363492 | ||
|
|
4932f31af9 | ||
|
|
cdd0753019 | ||
|
|
8b98f544d7 | ||
|
|
4ad1a62408 | ||
|
|
39458c3b5b | ||
|
|
b07fcf44b1 | ||
|
|
58536ef4e9 | ||
|
|
3dfb0180d0 | ||
|
|
73831a1ee2 | ||
|
|
68e9f6dfe3 | ||
|
|
2c54fda563 | ||
|
|
a0ccecf534 | ||
|
|
e94cc3060d | ||
|
|
ac1af9c612 | ||
|
|
94bccea25c | ||
|
|
3f8dada173 | ||
|
|
a21d29689b | ||
|
|
099cb94abf | ||
|
|
de674fbeb0 | ||
|
|
f81d48eaca | ||
|
|
610dd48707 | ||
|
|
c6663b87e9 | ||
|
|
f7dca9d52b | ||
|
|
f2a4b1dfa0 | ||
|
|
b5b1101110 | ||
|
|
445c6a5a57 | ||
|
|
5ab2c6aa7d | ||
|
|
1e989215d9 | ||
|
|
a2692a2380 | ||
|
|
614d94959e | ||
|
|
7a0269097b | ||
|
|
c8f032c01d | ||
|
|
188d102f6b | ||
|
|
3275d32127 | ||
|
|
f4218097d2 | ||
|
|
96dbf53b1c | ||
|
|
e3111fcba3 | ||
|
|
5b0ea473f9 | ||
|
|
f28a2ec94c | ||
|
|
80925aeba6 | ||
|
|
45f9bc841c | ||
|
|
69f93467a1 | ||
|
|
67b24be239 | ||
|
|
cd9cc5c190 | ||
|
|
568a7fd146 | ||
|
|
d276feb5e8 | ||
|
|
c3b9864f1d | ||
|
|
7a6e3737c8 | ||
|
|
430b6fa559 | ||
|
|
1cf58513ad | ||
|
|
da8623bdbe | ||
|
|
43057468c6 | ||
|
|
7ed4cb393f | ||
|
|
031b7c20c0 |
No files matched your search
+14
-13
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
radare2-extras
|
||||
==============
|
||||
# radare2-extras
|
||||
|
||||
[](https://github.com/radareorg/radare2-extras/actions?query=workflow%3A%22ci%22)
|
||||
[](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
@@ -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;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# build artifacts
|
||||
*.o
|
||||
*.dylib
|
||||
*.so
|
||||
*.a
|
||||
|
||||
# editor / assistant caches
|
||||
.pmai/
|
||||
.DS_Store
|
||||
build/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,5 @@
|
||||
/* radare - LGPL - Copyright 2026 - pancake */
|
||||
|
||||
#ifdef R2_ARCH_H
|
||||
extern RArchPlugin r_arch_plugin_vc4;
|
||||
#endif
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
/* radare - LGPL - Copyright 2026 - pancake */
|
||||
|
||||
#ifdef R2_ASM_H
|
||||
extern RAsmPlugin r_asm_plugin_vc4;
|
||||
#endif
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
arch.vc4
|
||||
asm.vc4
|
||||
+1347
-52
File diff suppressed because it is too large.
Load diff
+52977
-655
File diff suppressed because it is too large.
Load diff
+128
-32
@@ -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[];
|
||||
|
||||
|
||||
+825
-85
File diff suppressed because it is too large.
Load diff
+1379
-48
File diff suppressed because it is too large.
Load diff
+68136
-870
File diff suppressed because it is too large.
Load diff
+2702
-37
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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}
|
||||
@@ -0,0 +1,10 @@
|
||||
*.so
|
||||
*.dylib
|
||||
*.dSYM/
|
||||
__pycache__/
|
||||
test/test_md32
|
||||
test/test_parse
|
||||
tools/md32-run
|
||||
|
||||
.pmai/
|
||||
usipdata/
|
||||
@@ -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`.
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+779
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 `?`.
|
||||
@@ -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.
|
||||
@@ -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": {}
|
||||
}
|
||||
+27263
File diff suppressed because it is too large.
Load diff
+158948
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
Executable
+3
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -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:]))
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 *
|
||||
@@ -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)))
|
||||
@@ -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()
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
@@ -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 = µblaze_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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = µblaze_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
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,4 @@
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
@@ -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
@@ -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
@@ -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 = §ions_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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
Reference in new issue
Block a user