mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-27 15:30:35 +00:00
1637 lines
46 KiB
C++
1637 lines
46 KiB
C++
/* disarm -- a simple disassembler for ARM instructions
|
|
* (c) 2000 Gareth McCaughan
|
|
*
|
|
* This file may be distributed and used freely provided:
|
|
* 1. You do not distribute any version that lacks this
|
|
* copyright notice (exactly as it appears here, extending
|
|
* from the start to the end of the C-language comment
|
|
* containing these words)); and,
|
|
* 2. If you distribute any modified version, its source
|
|
* contains a clear description of the ways in which
|
|
* it differs from the original version, and a clear
|
|
* indication that the changes are not mine.
|
|
* There is no restriction on your permission to use and
|
|
* distribute object code or executable code derived from
|
|
* this.
|
|
*
|
|
* The original version of this file (or perhaps a later
|
|
* version by the original author) may or may not be
|
|
* available at http://web.ukonline.co.uk/g.mccaughan/g/software.html .
|
|
*
|
|
* Share and enjoy! -- g
|
|
*/
|
|
|
|
/* (*This* comment is NOT part of the notice mentioned in the
|
|
* distribution conditions above.)
|
|
*
|
|
* The bulk of this code was ripped brutally from the middle
|
|
* of a much more interesting piece of software whose purpose
|
|
* is to disassemble object files in the format known as AOF;
|
|
* it's quite clever at spotting blocks of non-code embedded
|
|
* in code, identifying labels, and so on.
|
|
*
|
|
* This program, on the other hand, is very much simpler.
|
|
* It simply disassembles one instruction at a time. Some
|
|
* traces of the original purpose can be seen here and there.
|
|
* You might want to make this do a two-phase disassembly,
|
|
* adding labels etc the second time around. I've made this
|
|
* work by loading the whole file into memory first, partly
|
|
* because that makes a two-pass approach easier.
|
|
*
|
|
* One word of warning: I believe that the syntax this program
|
|
* uses for the MSR instruction is now obsolete.
|
|
*
|
|
* Usage:
|
|
* disarm <filename> <base-address>
|
|
* will disassemble every word in <filename>.
|
|
*
|
|
* <base-address> should be something understood by strtol.
|
|
* So you can get hex (which is probably what you want)
|
|
* by prefixing "0x".
|
|
*
|
|
* The -r option will byte-reverse each word before it's
|
|
* disassembled.
|
|
*
|
|
* The code is rather unmaintainable. I'm sorry.
|
|
*
|
|
* Changes since original release:
|
|
* ????-??-?? v0.00 Initial release.
|
|
* 2007-09-02 v0.11 Change %X to %lX in a format string.
|
|
* (Thanks to Vincent Zweije for reporting this.)
|
|
*/
|
|
|
|
#ifdef __clang__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wtautological-compare" //used to avoid warning, force compiler to accept it.
|
|
#pragma GCC diagnostic ignored "-Wstring-plus-int"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "base/basictypes.h"
|
|
#include "Common/ArmEmitter.h"
|
|
#include "ext/disarm.h"
|
|
|
|
static const char *CCFlagsStr[] = {
|
|
"EQ", // Equal
|
|
"NEQ", // Not equal
|
|
"CS", // Carry Set
|
|
"CC", // Carry Clear
|
|
"MI", // Minus (Negative)
|
|
"PL", // Plus
|
|
"VS", // Overflow
|
|
"VC", // No Overflow
|
|
"HI", // Unsigned higher
|
|
"LS", // Unsigned lower or same
|
|
"GE", // Signed greater than or equal
|
|
"LT", // Signed less than
|
|
"GT", // Signed greater than
|
|
"LE", // Signed less than or equal
|
|
"", // Always (unconditional) 14
|
|
};
|
|
|
|
int GetVd(uint32_t op, bool quad = false, bool dbl = false) {
|
|
int val;
|
|
if (!quad && !dbl) {
|
|
val = ((op >> 22) & 1) | ((op >> 11) & 0x1E);
|
|
} else {
|
|
val = ((op >> 18) & 0x10) | ((op >> 12) & 0xF);
|
|
}
|
|
if (quad)
|
|
val >>= 1;
|
|
return val;
|
|
}
|
|
|
|
int GetVn(uint32_t op, bool quad = false, bool dbl = false) {
|
|
int val;
|
|
if (!quad && !dbl) {
|
|
val = ((op >> 7) & 1) | ((op >> 15) & 0x1E);
|
|
} else {
|
|
val = ((op >> 16) & 0xF) | ((op >> 3) & 0x10);
|
|
}
|
|
if (quad)
|
|
val >>= 1;
|
|
return val;
|
|
}
|
|
|
|
int GetVm(uint32_t op, bool quad = false, bool dbl = false) {
|
|
int val;
|
|
if (!quad && !dbl) {
|
|
val = ((op >> 5) & 1) | ((op << 1) & 0x1E);
|
|
} else {
|
|
val = ((op >> 1) & 0x10) | (op & 0xF);
|
|
}
|
|
if (quad)
|
|
val >>= 1;
|
|
return val;
|
|
}
|
|
|
|
|
|
// Modern VFP disassembler, written entirely separately because I can't figure out the old stuff :P
|
|
// Horrible array of hacks but hey. Can be cleaned up later.
|
|
|
|
bool DisasmVFP(uint32_t op, char *text) {
|
|
#if defined(__ANDROID__) && defined(_M_IX86)
|
|
// Prevent linking errors with ArmEmitter which I've excluded on x86 android.
|
|
strcpy(text, "ARM disasm not available");
|
|
#else
|
|
const char *cond = CCFlagsStr[op >> 28];
|
|
switch ((op >> 24) & 0xF) {
|
|
case 0xC:
|
|
// VLDMIA/VSTMIA
|
|
{
|
|
bool single_reg = ((op >> 8) & 0xF) == 10;
|
|
int freg = ((op >> 11) & 0x1E) | ((op >> 22) & 1);
|
|
int base = (op >> 16) & 0xF;
|
|
bool load = (op >> 20) & 1;
|
|
bool writeback = (op >> 21) & 1;
|
|
int numregs = op & 0xF;
|
|
bool add = (op >> 23) & 1;
|
|
if (add && writeback && load && base == 13) {
|
|
if (single_reg)
|
|
sprintf(text, "VPOP%s {s%i-s%i}", cond, freg, freg-1+numregs);
|
|
else
|
|
sprintf(text, "VPOP%s {d%i-d%i}", cond, freg, freg-1+(numregs/2));
|
|
|
|
return true;
|
|
}
|
|
if (single_reg)
|
|
sprintf(text, "%s%s r%i%s, {s%i-s%i}", load ? "VLDMIA" : "VSTMIA", cond, base, writeback ? "!":"", freg, freg-1+numregs);
|
|
else
|
|
sprintf(text, "%s%s r%i%s, {d%i-d%i}", load ? "VLDMIA" : "VSTMIA", cond, base, writeback ? "!":"", freg, freg-1+(numregs/2));
|
|
|
|
return true;
|
|
}
|
|
case 0xD:
|
|
// VLDR/VSTR/VLDMDB/VSTMDB
|
|
{
|
|
bool single_reg = ((op >> 8) & 0xF) == 10;
|
|
int freg = ((op >> 11) & 0x1E) | ((op >> 22) & 1);
|
|
int base = (op >> 16) & 0xF;
|
|
bool load = (op >> 20) & 1;
|
|
bool add = (op >> 23) & 1;
|
|
bool writeback = (op >> 21) & 1;
|
|
if (writeback) { // Multiple
|
|
int numregs = op & 0xF;
|
|
if (!add && !load && base == 13) {
|
|
if (single_reg)
|
|
sprintf(text, "VPUSH%s {s%i-s%i}", cond, freg, freg-1+numregs);
|
|
else
|
|
sprintf(text, "VPUSH%s {d%i-d%i}", cond, freg, freg-1+(numregs/2));
|
|
|
|
return true;
|
|
}
|
|
|
|
if (single_reg)
|
|
sprintf(text, "%s%s r%i, {s%i-s%i}", load ? "VLDMDB" : "VSTMDB", cond, base, freg, freg-1+numregs);
|
|
else
|
|
sprintf(text, "%s%s r%i, {d%i-d%i}", load ? "VLDMDB" : "VSTMDB", cond, base, freg, freg-1+(numregs/2));
|
|
} else {
|
|
int offset = (op & 0xFF) << 2;
|
|
if (!add) offset = -offset;
|
|
sprintf(text, "%s%s s%i, [r%i, #%i]", load ? "VLDR" : "VSTR", cond, freg, base, offset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case 0xE:
|
|
{
|
|
switch ((op >> 20) & 0xF) {
|
|
case 0xE: // VMSR
|
|
if ((op & 0xFFF) != 0xA10)
|
|
break;
|
|
sprintf(text, "VMSR%s r%i", cond, (op >> 12) & 0xF);
|
|
return true;
|
|
case 0xF: // VMRS
|
|
if ((op & 0xFFF) != 0xA10)
|
|
break;
|
|
if (op == 0xEEF1FA10) {
|
|
sprintf(text, "VMRS%s APSR", cond);
|
|
} else {
|
|
sprintf(text, "VMRS%s r%i", cond, (op >> 12) & 0xF);
|
|
}
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (((op >> 19) & 0x7) == 0x7) {
|
|
// VCVT
|
|
sprintf(text, "VCVT ...");
|
|
return true;
|
|
}
|
|
|
|
bool quad_reg = (op >> 6) & 1;
|
|
bool double_reg = (op >> 8) & 1;
|
|
char c = double_reg ? 'd' : 's';
|
|
|
|
int part1 = ((op >> 23) & 0x1F);
|
|
int part2 = ((op >> 9) & 0x7) ;
|
|
int part3 = ((op >> 20) & 0x3) ;
|
|
if (part3 == 3 && part2 == 5 && part1 == 0x1D) {
|
|
// VMOV, VCMP
|
|
int vn = GetVn(op);
|
|
if (vn != 1 && vn != 2 && vn != 3) {
|
|
int vm = GetVm(op, false, double_reg);
|
|
int vd = GetVd(op, false, double_reg);
|
|
|
|
const char *name = "VMOV";
|
|
if (op & 0x40000)
|
|
name = (op & 0x80) ? "VCMPE" : "VCMP";
|
|
sprintf(text, "%s%s %c%i, %c%i", name, cond, c, vd, c, vm);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Moves between single precision registers and GPRs
|
|
if (((op >> 20) & 0xFFE) == 0xEE0) {
|
|
int vd = ((op >> 15) & 0x1E) | ((op >> 7) & 0x1);
|
|
int src = (op >> 12) & 0xF;
|
|
|
|
if (op & (1 << 20))
|
|
sprintf(text, "VMOV r%i, s%i", src, vd);
|
|
else
|
|
sprintf(text, "VMOV s%i, r%i", vd, src);
|
|
return true;
|
|
}
|
|
|
|
// Arithmetic
|
|
|
|
int opnum = -1;
|
|
int opc1 = (op >> 20) & 0xFB;
|
|
int opc2 = (op >> 4) & 0xAC;
|
|
for (int i = 0; i < 16; i++) {
|
|
// What the hell?
|
|
int fixed_opc2 = opc2;
|
|
if (!(ArmGen::VFPOps[i][0].opc2 & 0x8))
|
|
fixed_opc2 &= 0xA7;
|
|
if (ArmGen::VFPOps[i][0].opc1 == opc1 && ArmGen::VFPOps[i][0].opc2 == fixed_opc2) {
|
|
opnum = i;
|
|
break;
|
|
}
|
|
}
|
|
if (opnum < 0)
|
|
return false;
|
|
switch (opnum) {
|
|
case 8:
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
case 14:
|
|
{
|
|
quad_reg = false;
|
|
int vd = GetVd(op, quad_reg, double_reg);
|
|
int vn = GetVn(op, quad_reg, true);
|
|
int vm = GetVm(op, quad_reg, double_reg);
|
|
if (opnum == 8 && vn == 0x11)
|
|
opnum += 3;
|
|
sprintf(text, "%s%s %c%i, %c%i", ArmGen::VFPOpNames[opnum], cond, c, vd, c, vm);
|
|
return true;
|
|
}
|
|
default:
|
|
{
|
|
quad_reg = false;
|
|
int vd = GetVd(op, quad_reg, double_reg);
|
|
int vn = GetVn(op, quad_reg, double_reg);
|
|
int vm = GetVm(op, quad_reg, double_reg);
|
|
sprintf(text, "%s%s %c%i, %c%i, %c%i", ArmGen::VFPOpNames[opnum], cond, c, vd, c, vn, c, vm);
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static const char *GetSizeString(int sz) {
|
|
switch (sz) {
|
|
case 0:
|
|
return "8";
|
|
case 1:
|
|
return "16";
|
|
case 2:
|
|
return "32";
|
|
case 3:
|
|
return "64";
|
|
default:
|
|
return "(err)";
|
|
}
|
|
}
|
|
|
|
static const char *GetISizeString(int sz) {
|
|
switch (sz) {
|
|
case 0:
|
|
return "i8";
|
|
case 1:
|
|
return "i16";
|
|
case 2:
|
|
return "i32";
|
|
case 3:
|
|
return "i64";
|
|
default:
|
|
return "(err)";
|
|
}
|
|
}
|
|
|
|
static int GetRegCount(int type) {
|
|
switch (type) {
|
|
case 7: return 1;
|
|
case 10: return 2;
|
|
case 6: return 3;
|
|
case 4: return 4;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// VLD1 / VST1
|
|
static bool DisasmNeonLDST(uint32_t op, char *text) {
|
|
bool load = (op >> 21) & 1;
|
|
int Rn = (op >> 16) & 0xF;
|
|
int Rm = (op & 0xF);
|
|
int Vd = GetVd(op, false, true);
|
|
|
|
const char *name = load ? "LD" : "ST";
|
|
const char *suffix = "";
|
|
if (Rm == 13)
|
|
suffix = "!";
|
|
|
|
if ((op & (1 << 23)) == 0) {
|
|
int sz = (op >> 6) & 3;
|
|
int regCount = GetRegCount((op >> 8) & 0xF);
|
|
|
|
int startReg = Vd;
|
|
int endReg = Vd + regCount - 1;
|
|
|
|
if (Rm != 15 && Rm != 13) {
|
|
sprintf(text, "V%s1 - regsum", name);
|
|
} else {
|
|
if (startReg == endReg)
|
|
sprintf(text, "V%s1.%s {d%i}, [r%i]%s", name, GetSizeString(sz), startReg, Rn, suffix);
|
|
else
|
|
sprintf(text, "V%s1.%s {d%i-d%i}, [r%i]%s", name, GetSizeString(sz), startReg, endReg, Rn, suffix);
|
|
}
|
|
} else {
|
|
int reg = Vd;
|
|
int sz = (op >> 10) & 3;
|
|
int index_align = (op >> 4) & 0xF;
|
|
int lane = 0;
|
|
switch (sz) {
|
|
case 0: lane = index_align >> 1; break;
|
|
case 1: lane = index_align >> 2; break;
|
|
case 2: lane = index_align >> 3; break;
|
|
}
|
|
if (Rm != 15) {
|
|
sprintf(text, "V%s1 d[0] - regsum", name);
|
|
} else {
|
|
sprintf(text, "V%s1.%s {d%i[%i]}, [r%i]%s", name, sz == 2 ? GetSizeString(sz) : GetISizeString(sz), reg, lane, Rn, suffix);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmArithNeon(uint32_t op, const char *opname, char *text, bool includeSuffix = true) {
|
|
bool quad = ((op >> 6) & 1);
|
|
int size = (op >> 20) & 3;
|
|
int type = (op >> 8) & 0xF;
|
|
char r = quad ? 'q' : 'd';
|
|
const char *szname = GetISizeString(size);
|
|
if (type == 0xD || type == 0xF)
|
|
szname = "f32";
|
|
|
|
int Vd = GetVd(op, quad, true);
|
|
int Vn = GetVn(op, quad, true);
|
|
int Vm = GetVm(op, quad, true);
|
|
sprintf(text, "V%s%s%s %c%i, %c%i, %c%i", opname, includeSuffix ? "." : "", includeSuffix ? szname : "", r, Vd, r, Vn, r, Vm);
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmNeonImmVal(uint32_t op, char *text) {
|
|
using namespace ArmGen;
|
|
int opcode = (op >> 5) & 1;
|
|
int cmode = (op >> 8) & 0xF;
|
|
int imm = ((op >> 17) & 0x80) | ((op >> 12) & 0x70) | (op & 0xF);
|
|
int quad = (op >> 6) & 1;
|
|
const char *operation = "MOV";
|
|
const char *size = "(unk)";
|
|
char temp[256] = "(unk)";
|
|
switch (cmode) {
|
|
case VIMM___x___x:
|
|
case VIMM___x___x + 1:
|
|
sprintf(temp, "000000%02x_000000%02x", imm, imm);
|
|
size = ".i32";
|
|
break;
|
|
case VIMM__x___x_:
|
|
case VIMM__x___x_ + 1:
|
|
sprintf(temp, "0000%02x00_0000%02x00", imm, imm);
|
|
size = ".i32";
|
|
break;
|
|
case VIMM_x___x__:
|
|
case VIMM_x___x__ + 1:
|
|
sprintf(temp, "00%02x0000_00%02x0000", imm, imm);
|
|
size = ".i32";
|
|
break;
|
|
case VIMMx___x___:
|
|
case VIMMx___x___ + 1:
|
|
sprintf(temp, "%02x000000_%02x000000", imm, imm);
|
|
size = ".i32";
|
|
break;
|
|
|
|
// TODO: More
|
|
|
|
case VIMMf000f000:
|
|
if (opcode == 0) {
|
|
// TODO: Do this properly
|
|
float f = 1337;
|
|
switch (imm) {
|
|
case 0: f = 0.0f; break;
|
|
case 0x78: f = 1.5; break;
|
|
case 0x70: f = 1.0; break;
|
|
case 0xF0: f = -1.0; break;
|
|
}
|
|
sprintf(temp, "%1.1f", f);
|
|
size = "";
|
|
break;
|
|
}
|
|
}
|
|
char c = quad ? 'q' : 'd';
|
|
sprintf(text, "V%s%s %c%i, %s", operation, size, c, GetVd(op, false, false), temp);
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmNeon2Op(uint32_t op, char *text) {
|
|
const char *opname = "(unk2op)";
|
|
|
|
bool quad = (op >> 6) & 1;
|
|
bool quadD = quad;
|
|
bool doubleD = false;
|
|
// VNEG, VABS
|
|
if (op & (1 << 16))
|
|
opname = "NEG";
|
|
|
|
int opcode = (op >> 6) & 0xF;
|
|
int sz = (op >> 18) & 3;
|
|
const char *size = "f32";
|
|
switch (opcode) {
|
|
case 0xE:
|
|
opname = "NEG";
|
|
size = GetISizeString(sz);
|
|
break;
|
|
case 0xD:
|
|
opname = "ABS";
|
|
size = GetISizeString(sz);
|
|
break;
|
|
case 0x7:
|
|
opname = "MVN";
|
|
size = ""; // MVN surely has no "size"?
|
|
break;
|
|
case 0x8:
|
|
opname = "MOVN"; // narrow, not negate
|
|
size = GetISizeString(sz + 1);
|
|
quad = true;
|
|
quadD = false;
|
|
doubleD = true;
|
|
break;
|
|
case 0xC:
|
|
opname = "SHLL"; // widen and shift
|
|
size = GetISizeString(sz);
|
|
quad = false;
|
|
quadD = true;
|
|
doubleD = true;
|
|
break;
|
|
}
|
|
|
|
int Vd = GetVd(op, quadD, doubleD);
|
|
int Vm = GetVm(op, quad, false);
|
|
char cD = quadD ? 'q' : 'd';
|
|
char c = quad ? 'q' : 'd';
|
|
if (opcode == 0xC) {
|
|
sprintf(text, "V%s%s%s %c%i, %c%i, #%d", opname, strlen(size) ? "." : "", size, cD, Vd, c, Vm, 8 << sz);
|
|
} else {
|
|
sprintf(text, "V%s%s%s %c%i, %c%i", opname, strlen(size) ? "." : "", size, cD, Vd, c, Vm);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmVdup(uint32_t op, char *text) {
|
|
bool quad = (op >> 6) & 1;
|
|
int imm4 = (op >> 16) & 0xF;
|
|
int Vd = GetVd(op, quad, false);
|
|
int Vm = GetVm(op, false, true);
|
|
char c = quad ? 'q' : 'd';
|
|
int index = 0;
|
|
int size = 0;
|
|
if (imm4 & 1) {
|
|
index = imm4 >> 1;
|
|
size = 0;
|
|
} else if (imm4 & 2) {
|
|
index = imm4 >> 2;
|
|
size = 1;
|
|
} else if (imm4 & 4) {
|
|
index = imm4 >> 3;
|
|
size = 2;
|
|
}
|
|
|
|
sprintf(text, "VDUP.%s %c%i, d%i[%i]", GetSizeString(size), c, Vd, Vm, index);
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmNeonVecScalar(uint32_t op, char *text) {
|
|
bool quad = (op >> 24) & 1;
|
|
|
|
int Vd = GetVd(op, quad, true);
|
|
int Vn = GetVn(op, quad, true);
|
|
int Vm = GetVm(op, false, false);
|
|
|
|
char c = quad ? 'q' : 'd';
|
|
|
|
const char *opname = "(unk)";
|
|
const char *size = "f32";
|
|
|
|
switch ((op >> 4) & 0xFF) {
|
|
case 0x94:
|
|
case 0x9C:
|
|
opname = "VMUL";
|
|
break;
|
|
case 0x14:
|
|
case 0x1C:
|
|
case 0x1E: // Hmmm.. Should look this up :P
|
|
opname = "VMLA";
|
|
break;
|
|
}
|
|
|
|
int part = Vm & 1;
|
|
int reg = Vm >> 1;
|
|
sprintf(text, "%s.%s %c%i, %c%i, d%i[%i]", opname, size, c, Vd, c, Vn, reg, part);
|
|
return true;
|
|
}
|
|
|
|
// This needs a rewrite, those gotos are quite ugly...
|
|
const char *DecodeSizeAndShiftImm7(bool U, bool sign, bool inverse, int imm7, bool incSize, int *shift) {
|
|
if (imm7 & 64) {
|
|
if (inverse) {
|
|
*shift = 64 - (imm7 & 63);
|
|
} else {
|
|
*shift = imm7 & 63;
|
|
}
|
|
to64:
|
|
return U ? "u64" : (sign ? "s64" : "i64");
|
|
} else if (imm7 & 32) {
|
|
if (inverse) {
|
|
*shift = 32 - (imm7 & 31);
|
|
} else {
|
|
*shift = imm7 & 31;
|
|
}
|
|
if (incSize) goto to64;
|
|
to32:
|
|
return U ? "u32" : (sign ? "s32" : "i32");
|
|
} else if (imm7 & 16) {
|
|
if (inverse) {
|
|
*shift = 16 - (imm7 & 15);
|
|
} else {
|
|
*shift = imm7 & 15;
|
|
}
|
|
if (incSize) goto to32;
|
|
to16:
|
|
return U ? "u16" : (sign ? "s16" : "i16");
|
|
} else if (imm7 & 8) {
|
|
if (inverse) {
|
|
*shift = 8 - (imm7 & 7);
|
|
} else {
|
|
*shift = imm7 & 7;
|
|
}
|
|
if (incSize) goto to16;
|
|
return U ? "u8" : (sign ? "s8" : "i8");
|
|
} else {
|
|
// Invalid encoding
|
|
*shift = -1;
|
|
}
|
|
return "i32";
|
|
}
|
|
|
|
// What a horror show!
|
|
static bool DisasmNeon2RegShiftImm(uint32_t op, char *text) {
|
|
bool U = (op >> 24) & 1;
|
|
bool quadDest = false;
|
|
bool quadSrc = false;
|
|
bool incSize = false;
|
|
|
|
const char *opname = "(unk)";
|
|
int opcode = (op >> 8) & 0xF;
|
|
bool inverse = false;
|
|
bool sign = false;
|
|
switch (opcode) {
|
|
case 0x5: opname = "VSHL"; quadDest = quadSrc = ((op >> 6) & 1); break;
|
|
case 0xA: opname = "VSHLL"; quadDest = true; quadSrc = false; sign = true; break;
|
|
case 0x0: opname = "VSHR"; sign = true; quadDest = quadSrc = ((op >> 6) & 1); inverse = true; break;
|
|
case 0x8: opname = "VSHRN"; quadDest = false; quadSrc = true; inverse = true; incSize = true; break;
|
|
default:
|
|
// Immediate value ops!
|
|
return DisasmNeonImmVal(op, text);
|
|
}
|
|
|
|
int Vd = GetVd(op, quadDest, true);
|
|
int Vm = GetVm(op, quadSrc, true);
|
|
|
|
char c1 = quadDest ? 'q' : 'd';
|
|
char c2 = quadSrc ? 'q' : 'd';
|
|
int imm7 = ((op >> 16) & 0x3f) | ((op & 0x80) >> 1);
|
|
int shift;
|
|
|
|
const char *size;
|
|
if (opcode == 0xA) {
|
|
if (imm7 & 0x40) {
|
|
sprintf(text, "neon2regshiftimm undefined %08x", op);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
size = DecodeSizeAndShiftImm7(U, sign, inverse, imm7, incSize, &shift);
|
|
|
|
if (opcode == 0xA && shift == 0) {
|
|
opname = "VMOVL";
|
|
sprintf(text, "%s.%s %c%i, %c%i", opname, size, c1, Vd, c2, Vm);
|
|
} else {
|
|
sprintf(text, "%s.%s %c%i, %c%i, #%i", opname, size, c1, Vd, c2, Vm, shift);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmNeonF2F3(uint32_t op, char *text) {
|
|
sprintf(text, "NEON F2");
|
|
if (((op >> 20) & 0xFF8) == 0xF20 || ((op >> 20) & 0xFF8) == 0xF30) {
|
|
const char *opname = "(unk)";
|
|
bool includeSuffix = true;
|
|
int temp;
|
|
switch ((op >> 20) & 0xFF) {
|
|
case 0x20:
|
|
temp = (op >> 4) & 0xF1;
|
|
switch (temp) {
|
|
case 0x11:
|
|
opname = "AND";
|
|
includeSuffix = false;
|
|
break;
|
|
case 0xd1:
|
|
opname = "MLA";
|
|
break;
|
|
case 0x80:
|
|
case 0xd0:
|
|
opname = "ADD";
|
|
break;
|
|
case 0xF0:
|
|
opname = "MAX";
|
|
break;
|
|
}
|
|
return DisasmArithNeon(op, opname, text, includeSuffix);
|
|
case 0x22:
|
|
case 0x24:
|
|
temp = (op >> 4) & 0xF1;
|
|
switch (temp) {
|
|
case 0xF0:
|
|
opname = "MIN";
|
|
break;
|
|
case 0x11:
|
|
opname = "ORR";
|
|
includeSuffix = false;
|
|
break;
|
|
case 0x80:
|
|
case 0xd0:
|
|
opname = "ADD";
|
|
break;
|
|
case 0xd1:
|
|
opname = "MLS";
|
|
break;
|
|
default:
|
|
opname = "???";
|
|
break;
|
|
}
|
|
return DisasmArithNeon(op, opname, text, includeSuffix);
|
|
case 0x31:
|
|
if (op & 0x100)
|
|
opname = "MLS";
|
|
else
|
|
opname = "SUB";
|
|
return DisasmArithNeon(op, opname, text);
|
|
case 0x30:
|
|
case 0x34:
|
|
temp = (op >> 4) & 0xF1;
|
|
switch (temp) {
|
|
case 0x11:
|
|
opname = "EOR";
|
|
includeSuffix = false;
|
|
break;
|
|
case 0xd0:
|
|
opname = "PADD";
|
|
break;
|
|
default:
|
|
opname = "MUL";
|
|
}
|
|
return DisasmArithNeon(op, opname, text, includeSuffix);
|
|
}
|
|
} else if ((op & 0xFE800010) == 0xF2800010) {
|
|
// Two regs and a shift amount
|
|
return DisasmNeon2RegShiftImm(op, text);
|
|
} else if ((op >> 20) == 0xF3E || (op >> 20) == 0xF2E || (op >> 20) == 0xF3A || (op >> 20) == 0xF2A) {
|
|
return DisasmNeonVecScalar(op, text);
|
|
} else if ((op >> 20) == 0xF3B && ((op >> 4) & 1) == 0) {
|
|
return DisasmNeon2Op(op, text);
|
|
} else if ((op >> 20) == 0xF3F) {
|
|
return DisasmVdup(op, text);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool DisasmNeon(uint32_t op, char *text) {
|
|
switch (op >> 24) {
|
|
case 0xF4:
|
|
return DisasmNeonLDST(op, text);
|
|
case 0xF2:
|
|
case 0xF3:
|
|
return DisasmNeonF2F3(op, text);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info) {
|
|
*info = {};
|
|
info->instructionSize = 4;
|
|
|
|
// TODO
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
typedef unsigned int word;
|
|
typedef unsigned int address;
|
|
typedef unsigned int addrdiff;
|
|
#define W(x) ((word*)(x))
|
|
|
|
#define declstruct(name) typedef struct name s##name, * p##name
|
|
#define defstruct(name) struct name
|
|
#define defequiv(new,old) typedef struct old s##new, * p##new
|
|
|
|
declstruct(DisOptions);
|
|
declstruct(Instruction);
|
|
|
|
typedef enum {
|
|
target_None, /* instruction doesn't refer to an address */
|
|
target_Data, /* instruction refers to address of data */
|
|
target_FloatS, /* instruction refers to address of single-float */
|
|
target_FloatD, /* instruction refers to address of double-float */
|
|
target_FloatE, /* blah blah extended-float */
|
|
target_FloatP, /* blah blah packed decimal float */
|
|
target_Code, /* instruction refers to address of code */
|
|
target_Unknown /* instruction refers to address of *something* */
|
|
} eTargetType;
|
|
|
|
defstruct(Instruction) {
|
|
char text[128]; /* the disassembled instruction */
|
|
int undefined; /* non-0 iff it's an undefined instr */
|
|
int badbits; /* non-0 iff something reserved has the wrong value */
|
|
int oddbits; /* non-0 iff something unspecified isn't 0 */
|
|
int is_SWI; /* non-0 iff it's a SWI */
|
|
word swinum; /* only set for SWIs */
|
|
address target; /* address instr refers to */
|
|
eTargetType target_type; /* and what we expect to be there */
|
|
int offset; /* offset from register in LDR or STR or similar */
|
|
char * addrstart; /* start of address part of instruction, or 0 */
|
|
};
|
|
|
|
#define disopt_SWInames 1 /* use names, not &nnnn */
|
|
#define disopt_CommaSpace 2 /* put spaces after commas */
|
|
#define disopt_FIXS 4 /* bogus FIX syntax for ObjAsm */
|
|
#define disopt_ReverseBytes 8 /* byte-reverse words first */
|
|
|
|
defstruct(DisOptions) {
|
|
word flags;
|
|
const char * * regnames; /* pointer to 16 |char *|s: register names */
|
|
};
|
|
|
|
static pInstruction instr_disassemble(word, address, pDisOptions);
|
|
|
|
#define INSTR_grok_v4
|
|
|
|
/* Preprocessor defs you can give to affect this stuff:
|
|
* INSTR_grok_v4 understand ARMv4 instructions (halfword & sign-ext LDR/STR)
|
|
* INSTR_new_msr be prepared to produce new MSR syntax if asked
|
|
* The first of these is supported; the second isn't.
|
|
*/
|
|
|
|
/* Some important single-bit fields. */
|
|
|
|
#define Sbit (1<<20) /* set condition codes (data processing) */
|
|
#define Lbit (1<<20) /* load, not store (data transfer) */
|
|
#define Wbit (1<<21) /* writeback (data transfer) */
|
|
#define Bbit (1<<22) /* single byte (data transfer, SWP) */
|
|
#define Ubit (1<<23) /* up, not down (data transfer) */
|
|
#define Pbit (1<<24) /* pre-, not post-, indexed (data transfer) */
|
|
#define Ibit (1<<25) /* non-immediate (data transfer) */
|
|
/* immediate (data processing) */
|
|
#define SPSRbit (1<<22) /* SPSR, not CPSR (MRS, MSR) */
|
|
|
|
/* Some important 4-bit fields. */
|
|
|
|
#define RD(x) ((x)<<12) /* destination register */
|
|
#define RN(x) ((x)<<16) /* operand/base register */
|
|
#define CP(x) ((x)<<8) /* coprocessor number */
|
|
#define RDbits RD(15)
|
|
#define RNbits RN(15)
|
|
#define CPbits CP(15)
|
|
#define RD_is(x) ((instr&RDbits)==RD(x))
|
|
#define RN_is(x) ((instr&RNbits)==RN(x))
|
|
#define CP_is(x) ((instr&CPbits)==CP(x))
|
|
|
|
/* A slightly efficient way of telling whether two bits are the same
|
|
* or not. It's assumed that a<b.
|
|
*/
|
|
#define BitsDiffer(a,b) ((instr^(instr>>(b-a)))&(1<<a))
|
|
|
|
/* op = append(op,ip) === op += sprintf(op,"%s",ip),
|
|
* except that it's faster.
|
|
*/
|
|
static char * append(char * op, const char *ip) {
|
|
char c;
|
|
while ((c=*ip++)!=0) *op++=c;
|
|
return op;
|
|
}
|
|
|
|
/* op = hex8(op,w) === op += sprintf(op,"&%08lX",w), but faster.
|
|
*/
|
|
static char * hex8(char * op, word w) {
|
|
int i;
|
|
*op++='&';
|
|
for (i=28; i>=0; i-=4) *op++ = "0123456789ABCDEF"[(w>>i)&15];
|
|
return op;
|
|
}
|
|
|
|
/* op = reg(op,'x',n) === op += sprintf(op,"x%lu",n&15).
|
|
*/
|
|
static char * reg(char * op, char c, word n) {
|
|
*op++=c;
|
|
n&=15;
|
|
if (n>=10) { *op++='1'; n+='0'-10; } else n+='0';
|
|
*op++=(char)n;
|
|
return op;
|
|
}
|
|
|
|
/* op = num(op,n) appends n in decimal or &n in hex
|
|
* depending on whether n<100. It's assumed that n>=0.
|
|
*/
|
|
static char * num(char * op, word w) {
|
|
if (w>=100) {
|
|
int i;
|
|
word t;
|
|
*op++='&';
|
|
for (i=28; (t=(w>>i)&15)==0; i-=4) ;
|
|
for (; i>=0; i-=4) *op++ = "0123456789ABCDEF"[(w>>i)&15];
|
|
}
|
|
else {
|
|
/* divide by 10. You can prove this works by exhaustive search. :-) */
|
|
word t = w-(w>>2); t=(t+(t>>4)) >> 3;
|
|
{ word u = w-10*t;
|
|
if (u==10) { u=0; ++t; }
|
|
if (t) *op++=(char)(t+'0');
|
|
*op++=(char)(u+'0');
|
|
}
|
|
}
|
|
return op;
|
|
}
|
|
|
|
/* instr_disassemble
|
|
* Disassemble a single instruction.
|
|
*
|
|
* args: instr a single ARM instruction
|
|
* addr the address it's presumed to have come from
|
|
* opts cosmetic preferences for our output
|
|
*
|
|
* reqs: opts must be filled in right. In particular, it must contain
|
|
* a list of register names.
|
|
*
|
|
* return: a pointer to a structure containing the disassembled instruction
|
|
* and some other information about it.
|
|
*
|
|
* This is basically a replacement for the SWI Debugger_Disassemble,
|
|
* but it has the following advantages:
|
|
*
|
|
* + it's 3-4 times as fast
|
|
* + it's better at identifying undefined instructions,
|
|
* and instructions not invariant under { disassemble; ObjAsm; }
|
|
* + it provides some other useful information as well
|
|
* + its output syntax is the same as ObjAsm's input syntax
|
|
* (where possible)
|
|
* + it doesn't disassemble FIX incorrectly unless you ask it to
|
|
* + it's more configurable in some respects
|
|
*
|
|
* It also has the following disadvantages:
|
|
*
|
|
* - it increases the size of ObjDism
|
|
* - it doesn't provide so many `helpful' usage comments etc
|
|
* - it's less configurable in some respects
|
|
* - it doesn't (yet) know about ARMv4 instructions
|
|
*
|
|
* This function proceeds in two phases. The first is very simple:
|
|
* it works out what sort of instruction it's looking at and sets up
|
|
* three strings:
|
|
* - |mnemonic| (the basic mnemonic: LDR or whatever)
|
|
* - |flagchars| (things to go after the cond code: B or whatever)
|
|
* - |format| (a string describing how to display the instruction)
|
|
* The second phase consists of interpreting |format|, character by
|
|
* character. Some characters (e.g., letters) just mean `append this
|
|
* character to the output string'; some mean more complicated things
|
|
* like `append the name of the register whose number is in bits 12..15'
|
|
* or, worse, `append a description of the <op2> field'.
|
|
*
|
|
* I'm afraid the magic characters in |format| are rather arbitrary.
|
|
* One criterion in choosing them was that they should form a contiguous
|
|
* subrange of the character set! Sorry.
|
|
*
|
|
* Things I still want to do:
|
|
*
|
|
* - more configurability?
|
|
* - make it much faster, if possible
|
|
* - make it much smaller, if possible
|
|
*
|
|
* Format characters:
|
|
*
|
|
* \01..\05 copro register number from nybble (\001 == nybble 0, sorry)
|
|
* $ SWI number
|
|
* % register set for LDM/STM (takes note of bit 22 for ^)
|
|
* & address for B/BL
|
|
* ' ! if bit 21 set, else nothing (mnemonic: half a !)
|
|
* ( #regs for SFM (bits 22,15 = fpn, assumed already tweaked)
|
|
* ) copro opcode in bits 20..23 (for CDP)
|
|
* * op2 (takes note of bottom 12 bits, and bit 25)
|
|
* + FP register or immediate value: bits 0..3
|
|
* , comma or comma-space
|
|
* - copro extra info in bits 5..7 preceded by , omitted if 0
|
|
* . address in ADR instruction
|
|
* / address for LDR/STR (takes note of bit 23 & reg in bits 16..19)
|
|
* 0..4 register number from nybble
|
|
* 5..9 FP register number from nybble
|
|
* : copro opcode in bits 21..23 (for MRC/MCR)
|
|
* ; copro number in bits 8..11
|
|
*
|
|
* ADDED BY HRYDGARD:
|
|
* ^ 16-bit immediate
|
|
* > 5-bit immediate at 11..7 (lsb)
|
|
* < 5-bit immediate at 20..16 with +1 or -lsb if bit 6 set
|
|
*
|
|
* NB that / takes note of bit 22, too, and does its own ! when
|
|
* appropriate.
|
|
*
|
|
* On typical instructions this seems to take about 100us on my ARM6;
|
|
* that's about 3000 cycles, which seems grossly excessive. I'm not
|
|
* sure where all those cycles are being spent. Perhaps it's possible
|
|
* to make it much, much faster. Most of this time is spent on phase 2.
|
|
*/
|
|
|
|
extern pInstruction
|
|
instr_disassemble(word instr, address addr, pDisOptions opts) {
|
|
static char flagchars[4];
|
|
static sInstruction result;
|
|
const char * mnemonic = 0;
|
|
char * flagp = flagchars;
|
|
const char * format = 0;
|
|
word fpn;
|
|
eTargetType poss_tt = target_None;
|
|
#ifdef INSTR_grok_v4
|
|
int is_v4 = 0;
|
|
#endif
|
|
|
|
/* PHASE 0. Set up default values for |result|. */
|
|
|
|
if (opts->flags & disopt_ReverseBytes) {
|
|
instr = ((instr & 0xFF00FF00) >> 8) | ((instr & 0x00FF00FF) << 8);
|
|
instr = (instr >> 16) | (instr << 16);
|
|
}
|
|
|
|
fpn = ((instr>>15)&1) + ((instr>>21)&2);
|
|
|
|
result.undefined = 0;
|
|
result.badbits = 0;
|
|
result.oddbits = 0;
|
|
result.is_SWI = 0;
|
|
result.target_type = target_None;
|
|
result.offset = 0x80000000;
|
|
result.addrstart = 0;
|
|
|
|
/* PHASE 1. Decode and classify instruction. */
|
|
|
|
switch ((instr>>24)&15) {
|
|
case 0:
|
|
/* multiply or data processing, or LDRH etc */
|
|
if ((instr&(15<<4))!=(9<<4)) goto lMaybeLDRHetc;
|
|
/* multiply */
|
|
if (instr&(1<<23)) {
|
|
/* int multiply */
|
|
mnemonic = "UMULL\0UMLAL\0SMULL\0SMLAL" + 6*((instr>>21)&3);
|
|
format = "3,4,0,2";
|
|
}
|
|
else {
|
|
if (instr&(1<<22)) goto lUndefined; /* "class C" */
|
|
/* short multiply */
|
|
if (instr&(1<<21)) {
|
|
mnemonic = "MLA";
|
|
format = "4,0,2,3";
|
|
}
|
|
else {
|
|
mnemonic = "MUL";
|
|
format = "4,0,2";
|
|
}
|
|
}
|
|
if (instr&Sbit) *flagp++='S';
|
|
break;
|
|
case 1:
|
|
if ((instr & 0x0FFFFFF0) == ((18 << 20) | (0xFFF << 8) | (1 << 4))) {
|
|
mnemonic = "B";
|
|
format = "0";
|
|
break;
|
|
} else if ((instr & 0x0FFFFFF0) == 0x012FFF30) {
|
|
mnemonic = "BL";
|
|
format = "0";
|
|
break;
|
|
} else if ((instr & 0x0FF000F0) == 0x01200070) {
|
|
int imm = ((instr & 0xFFF00) >> 4) | (instr & 0xF);
|
|
snprintf(result.text, sizeof(result.text), "BKPT %d", imm);
|
|
result.undefined = 0;
|
|
return &result;
|
|
}
|
|
case 3:
|
|
if (instr >> 24 == 0xF3) {
|
|
if (!DisasmNeon(instr, result.text)) {
|
|
goto lUndefined;
|
|
}
|
|
result.undefined = 0;
|
|
return &result;
|
|
}
|
|
/* SWP or MRS/MSR or data processing */
|
|
// hrydgard addition: MOVW/MOVT
|
|
if ((instr & 0x0FF00000) == 0x03000000) {
|
|
mnemonic = "MOVW";
|
|
format = "3,^";
|
|
break;
|
|
}
|
|
else if ((instr & 0x0FF00000) == 0x03400000) {
|
|
mnemonic = "MOVT";
|
|
format = "3,^";
|
|
break;
|
|
}
|
|
else if ((instr&0x02B00FF0)==0x00000090) {
|
|
/* SWP */
|
|
mnemonic = "SWP";
|
|
format = "3,0,[4]";
|
|
if (instr&Bbit) *flagp++='B';
|
|
break;
|
|
}
|
|
else if ((instr&0x02BF0FFF)==0x000F0000) {
|
|
/* MRS */
|
|
mnemonic = "MRS";
|
|
format = (instr&SPSRbit) ? "3,SPSR" : "3,CPSR";
|
|
break;
|
|
}
|
|
else if ((instr&0x02BFFFF0)==0x0029F000) {
|
|
/* MSR psr<P=0/1...>,Rs */
|
|
mnemonic = "MSR";
|
|
format = (instr&SPSRbit) ? "SPSR,0" : "CPSR,0";
|
|
break;
|
|
}
|
|
else if ((instr&0x00BFF000)==0x0028F000) {
|
|
/* MSR {C,S}PSR_flag,op2 */
|
|
mnemonic = "MSR";
|
|
format = (instr&SPSRbit) ? "SPSR_flg,*" : "CPSR_flg,*";
|
|
if (!(instr&Ibit) && (instr&(15<<4)))
|
|
#ifdef INSTR_grok_v4
|
|
goto lMaybeLDRHetc;
|
|
#else
|
|
goto lUndefined; /* shifted reg in MSR illegal */
|
|
#endif
|
|
break;
|
|
}
|
|
/* fall through here */
|
|
lMaybeLDRHetc:
|
|
#ifdef INSTR_grok_v4
|
|
if ((instr&(14<<24))==0
|
|
&& ((instr&(9<<4))==(9<<4))) {
|
|
/* Might well be LDRH or similar. */
|
|
if ((instr&(Wbit+Pbit))==Wbit) goto lUndefined; /* "class E", case 1 */
|
|
if ((instr&(Lbit+(1<<6)))==(1<<6)) goto lUndefined; /* STRSH etc */
|
|
mnemonic = "STR\0LDR" + ((instr&Lbit) >> 18);
|
|
if (instr&(1<<6)) *flagp++='S';
|
|
*flagp++ = (instr&(1<<5)) ? 'B' : 'H';
|
|
format = "3,/";
|
|
/* aargh: */
|
|
if (!(instr&(1<<22))) instr |= Ibit;
|
|
is_v4=1;
|
|
break;
|
|
}
|
|
#endif
|
|
case 2:
|
|
if (instr >> 24 == 0xF2) {
|
|
if (!DisasmNeon(instr, result.text)) {
|
|
goto lUndefined;
|
|
}
|
|
result.undefined = 0;
|
|
return &result;
|
|
}
|
|
/* data processing */
|
|
{ word op21 = instr&(15<<21);
|
|
if ((op21==(2<<21) || (op21==(4<<21))) /* ADD or SUB */
|
|
&& ((instr&(RNbits+Ibit+Sbit))==RN(15)+Ibit) /* imm, no S */
|
|
/*&& ((instr&(30<<7))==0 || (instr&3))*/) { /* normal rot */
|
|
/* ADD ...,pc,#... or SUB ...,pc,#...: turn into ADR */
|
|
mnemonic = "ADR";
|
|
format = "3,.";
|
|
if ((instr&(30<<7))!=0 && !(instr&3)) result.oddbits=1;
|
|
break;
|
|
}
|
|
mnemonic = "AND\0EOR\0SUB\0RSB\0ADD\0ADC\0SBC\0RSC\0"
|
|
"TST\0TEQ\0CMP\0CMN\0ORR\0MOV\0BIC\0MVN" /* \0 */
|
|
+ (op21 >> 19);
|
|
/* Rd needed for all but TST,TEQ,CMP,CMN (8..11) */
|
|
/* Rn needed for all but MOV,MVN (13,15) */
|
|
if (op21 < ( 8<<21)) format = "3,4,*";
|
|
else if (op21 < (12<<21)) {
|
|
format = "4,*";
|
|
if (instr&RDbits) {
|
|
if ((instr&Sbit) && RD_is(15))
|
|
*flagp++='P';
|
|
else result.oddbits=1;
|
|
}
|
|
if (!(instr&Sbit)) goto lUndefined; /* CMP etc, no S bit */
|
|
}
|
|
else if (op21 & (1<<21)) {
|
|
format = "3,*";
|
|
if (instr&RNbits) result.oddbits=1;
|
|
}
|
|
else format = "3,4,*";
|
|
if (instr&Sbit && (op21<(8<<21) || op21>=(12<<21))) *flagp++='S';
|
|
}
|
|
break;
|
|
case 4:
|
|
if ((instr >> 24) == 0xF4) {
|
|
if (!DisasmNeon(instr, result.text)) {
|
|
goto lUndefined;
|
|
}
|
|
result.undefined = 0;
|
|
return &result;
|
|
}
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
/* STR/LDR/BFI/BFC/UBFX/SBFX or undefined */
|
|
if ((instr&Ibit) && (instr&(1<<4))) {
|
|
switch ((instr >> 21) & 7) {
|
|
case 5:
|
|
case 7:
|
|
/* SBFX/UBFX */
|
|
if (((instr>>4) & 7) != 5) {
|
|
goto lUndefined;
|
|
}
|
|
mnemonic = (instr & (1 << 22)) ? "UBFX" : "SBFX";
|
|
format = "3,0,>,<";
|
|
break;
|
|
case 6:
|
|
/* BFI/BFC */
|
|
if (((instr>>4) & 7) != 1) {
|
|
goto lUndefined;
|
|
}
|
|
if ((instr & 15) == 15) {
|
|
mnemonic = "BFC";
|
|
format = "3,>,<";
|
|
} else {
|
|
mnemonic = "BFI";
|
|
format = "3,0,>,<";
|
|
}
|
|
break;
|
|
default:
|
|
goto lUndefined; /* "class A" */
|
|
}
|
|
} else {
|
|
mnemonic = "STR\0LDR" + ((instr&Lbit) >> 18);
|
|
format = "3,/";
|
|
if (instr&Bbit) *flagp++='B';
|
|
if ((instr&(Wbit+Pbit))==Wbit) *flagp++='T';
|
|
poss_tt = target_Data;
|
|
}
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
/* STM/LDM */
|
|
mnemonic = "STM\0LDM" + ((instr&Lbit) >> 18);
|
|
if (RN_is(13)) {
|
|
/* r13, so treat as stack */
|
|
word x = (instr&(3<<23)) >> 22;
|
|
if (instr&Lbit) x^=6;
|
|
{ const char * foo = "EDEAFDFA"+x;
|
|
*flagp++ = *foo++;
|
|
*flagp++ = *foo;
|
|
}
|
|
}
|
|
else {
|
|
/* not r13, so don't treat as stack */
|
|
*flagp++ = (instr&Ubit) ? 'I' : 'D';
|
|
*flagp++ = (instr&Pbit) ? 'B' : 'A';
|
|
}
|
|
format = "4',%";
|
|
break;
|
|
case 10:
|
|
case 11:
|
|
/* B or BL */
|
|
mnemonic = "B\0BL"+((instr&(1<<24))>>23);
|
|
format = "&";
|
|
break;
|
|
case 12:
|
|
case 13:
|
|
case 14: // FPU
|
|
{
|
|
if (!DisasmVFP(instr, result.text)) {
|
|
goto lUndefined;
|
|
}
|
|
result.undefined = 0;
|
|
return &result;
|
|
}
|
|
break;
|
|
case 15:
|
|
/* SWI */
|
|
mnemonic = "SWI";
|
|
format = "$";
|
|
break;
|
|
/* Nasty hack: this is code that won't be reached in the normal
|
|
* course of events, and after the last case of the switch is a
|
|
* convenient place for it.
|
|
*/
|
|
lUndefined:
|
|
strcpy(result.text, "Undefined instruction");
|
|
result.undefined = 1;
|
|
return &result;
|
|
}
|
|
*flagp=0;
|
|
|
|
/* PHASE 2. Produce string. */
|
|
|
|
{ char * op = result.text;
|
|
|
|
/* 2a. Mnemonic. */
|
|
|
|
op = append(op,mnemonic);
|
|
|
|
/* 2b. Condition code. */
|
|
|
|
{ word cond = instr>>28;
|
|
if (cond!=14) {
|
|
const char * ip = "EQNECSCCMIPLVSVCHILSGELTGTLEALNV"+2*cond;
|
|
*op++ = *ip++;
|
|
*op++ = *ip;
|
|
}
|
|
}
|
|
|
|
/* 2c. Flags. */
|
|
|
|
{ const char * ip = flagchars;
|
|
while (*ip) *op++ = *ip++;
|
|
}
|
|
|
|
/* 2d. A tab character. */
|
|
|
|
*op++ = '\t';
|
|
|
|
/* 2e. Other stuff, determined by format string. */
|
|
|
|
{ const char * ip = format;
|
|
char c;
|
|
|
|
const char * * regnames = opts->regnames;
|
|
word oflags = opts->flags;
|
|
|
|
while ((c=*ip++) != 0) {
|
|
switch(c) {
|
|
case '^': // hrydgard addition
|
|
{
|
|
unsigned short imm16 = ((instr & 0x000F0000) >> 4) | (instr & 0x0FFF);
|
|
op += sprintf(op, "%04x", imm16);
|
|
}
|
|
break;
|
|
case '$':
|
|
result.is_SWI = 1;
|
|
result.swinum = instr&0x00FFFFFF;
|
|
result.addrstart = op;
|
|
op += sprintf(op, "&%X", result.swinum);
|
|
break;
|
|
case '%':
|
|
*op++='{';
|
|
{ word w = instr&0xFFFF;
|
|
int i=0;
|
|
while (w) {
|
|
int j;
|
|
while (!(w&(1ul<<i))) ++i;
|
|
for (j=i+1; w&(1ul<<j); ++j) ;
|
|
--j;
|
|
/* registers [i..j] */
|
|
op = append(op, regnames[i]);
|
|
if (j-i) {
|
|
*op++ = (j-i>1) ? '-' : ',';
|
|
op = append(op, regnames[j]);
|
|
}
|
|
i=j; w=(w>>(j+1))<<(j+1);
|
|
if (w) *op++=',';
|
|
}
|
|
}
|
|
*op++='}';
|
|
if (instr&(1<<22)) *op++='^';
|
|
break;
|
|
case '&':
|
|
{ address target = (addr+8 + ((((int)instr)<<8)>>6)) & 0x03FFFFFC;
|
|
result.addrstart = op;
|
|
op = hex8(op, target);
|
|
result.target_type = target_Code;
|
|
result.target = target;
|
|
}
|
|
break;
|
|
case '\'':
|
|
lPling:
|
|
if (instr&Wbit) *op++='!';
|
|
break;
|
|
case '(':
|
|
*op++ = (char)('0'+fpn);
|
|
break;
|
|
case ')':
|
|
{ word w = (instr>>20)&15;
|
|
if (w>=10) { *op++='1'; *op++=(char)('0'-10+w); }
|
|
else *op++=(char)(w+'0');
|
|
}
|
|
break;
|
|
case '*':
|
|
case '.':
|
|
if (instr&Ibit) {
|
|
/* immediate constant */
|
|
word imm8 = (instr&255);
|
|
word rot = (instr>>7)&30;
|
|
if (rot && !(imm8&3) && c=='*') {
|
|
/* Funny immediate const. Guaranteed not '.', btw */
|
|
*op++='#'; *op++='&';
|
|
*op++="0123456789ABCDEF"[imm8>>4];
|
|
*op++="0123456789ABCDEF"[imm8&15];
|
|
*op++=',';
|
|
op = num(op, rot);
|
|
}
|
|
else {
|
|
if (rot != 0) {
|
|
imm8 = (imm8>>rot) | (imm8<<(32-rot));
|
|
}
|
|
if (c=='*') {
|
|
*op++='#';
|
|
if (imm8>256 && ((imm8&(imm8-1))==0)) {
|
|
/* only one bit set, and that later than bit 8.
|
|
* Represent as 1<<... .
|
|
*/
|
|
op = append(op,"1<<");
|
|
{ int n=0;
|
|
while (!(imm8&15)) { n+=4; imm8=imm8>>4; }
|
|
/* Now imm8 is 1, 2, 4 or 8. */
|
|
n += (0x30002010 >> 4*(imm8-1))&15;
|
|
op = num(op, n);
|
|
}
|
|
}
|
|
else {
|
|
if (((int)imm8)<0 && ((int)imm8)>-100) {
|
|
*op++='-'; imm8=-(int)imm8;
|
|
}
|
|
op = num(op, imm8);
|
|
}
|
|
}
|
|
else {
|
|
address a = addr+8;
|
|
if (instr&(1<<22)) a-=imm8; else a+=imm8;
|
|
result.addrstart=op;
|
|
op = hex8(op, a);
|
|
result.target=a; result.target_type=target_Unknown;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* rotated register */
|
|
const char * rot = "LSL\0LSR\0ASR\0ROR" + ((instr&(3<<5)) >> 3);
|
|
op = append(op, regnames[instr&15]);
|
|
if (instr&(1<<4)) {
|
|
/* register rotation */
|
|
if (instr&(1<<7)) goto lUndefined;
|
|
*op++=','; if (oflags&disopt_CommaSpace) *op++=' ';
|
|
op = append(op,rot); *op++=' ';
|
|
op = append(op,regnames[(instr&(15<<8))>>8]);
|
|
}
|
|
else {
|
|
/* constant rotation */
|
|
word n = instr&(31<<7);
|
|
if (!n) {
|
|
if (!(instr&(3<<5))) break;
|
|
else if ((instr&(3<<5))==(3<<5)) {
|
|
op = append(op, ",RRX");
|
|
break;
|
|
}
|
|
else n=32<<7;
|
|
}
|
|
*op++ = ','; if (oflags&disopt_CommaSpace) *op++=' ';
|
|
op = num(append(append(op,rot)," #"),n>>7);
|
|
}
|
|
}
|
|
break;
|
|
case '+':
|
|
if (instr&(1<<3)) {
|
|
word w = instr&7;
|
|
*op++='#';
|
|
if (w<6) *op++=(char)('0'+w);
|
|
else op = append(op, w==6 ? "0.5" : "10");
|
|
}
|
|
else {
|
|
*op++='f';
|
|
*op++=(char)('0'+(instr&7));
|
|
}
|
|
break;
|
|
case ',':
|
|
*op++=',';
|
|
if (oflags&disopt_CommaSpace) *op++=' ';
|
|
break;
|
|
case '-':
|
|
{ word w = instr&(7<<5);
|
|
if (w) {
|
|
*op++=',';
|
|
if (oflags&disopt_CommaSpace) *op++=' ';
|
|
*op++ = (char)('0'+(w>>5));
|
|
}
|
|
}
|
|
break;
|
|
case '/':
|
|
result.addrstart = op;
|
|
*op++='[';
|
|
op = append(op, regnames[(instr&RNbits)>>16]);
|
|
if (!(instr&Pbit)) *op++=']';
|
|
*op++=','; if (oflags&disopt_CommaSpace) *op++=' ';
|
|
/* For following, NB that bit 25 is always 0 for LDC, SFM etc */
|
|
if (instr&Ibit) {
|
|
/* shifted offset */
|
|
if (!(instr&Ubit)) *op++='-';
|
|
/* We're going to transfer to '*', basically. The stupid
|
|
* thing is that the meaning of bit 25 is reversed there;
|
|
* I don't know why the designers of the ARM did that.
|
|
*/
|
|
instr ^= Ibit;
|
|
if (instr&(1<<4)) {
|
|
#ifdef INSTR_grok_v4
|
|
if (is_v4 && !(instr&(15<<8))) {
|
|
ip = (instr&Pbit) ? "0]" : "0";
|
|
break;
|
|
}
|
|
#else
|
|
goto lUndefined; /* LSL r3 forbidden */
|
|
#endif
|
|
}
|
|
/* Need a ] iff it was pre-indexed; and an optional ! iff
|
|
* it's pre-indexed *or* a copro instruction,
|
|
* except that FPU operations don't need the !. Bletch.
|
|
*/
|
|
if (instr&Pbit) ip="*]'";
|
|
else if (instr&(1<<27)) {
|
|
if (CP_is(1) || CP_is(2)) {
|
|
if (!(instr&Wbit)) goto lUndefined;
|
|
ip="*";
|
|
}
|
|
else ip="*'";
|
|
}
|
|
else ip="*";
|
|
}
|
|
else {
|
|
/* immediate offset */
|
|
word offset;
|
|
if (instr&(1<<27)) {
|
|
/* LDF or LFM or similar */
|
|
offset = (instr&255)<<2;
|
|
}
|
|
#ifdef INSTR_grok_v4
|
|
else if (is_v4) offset = (instr&15) + ((instr&(15<<8))>>4);
|
|
#endif
|
|
else {
|
|
/* LDR or STR */
|
|
offset = instr&0xFFF;
|
|
}
|
|
*op++='#';
|
|
if (!(instr&Ubit)) {
|
|
if (offset) *op++='-';
|
|
else result.oddbits=1;
|
|
result.offset = -(int)offset;
|
|
}
|
|
else result.offset = offset;
|
|
op = num(op, offset);
|
|
if (RN_is(15) && (instr&Pbit)) {
|
|
/* Immediate, pre-indexed and PC-relative. Set target. */
|
|
result.target_type = poss_tt;
|
|
result.target = (instr&Ubit) ? addr+8 + offset
|
|
: addr+8 - offset;
|
|
if (!(instr&Wbit)) {
|
|
/* no writeback, either. Use friendly form. */
|
|
op = hex8(result.addrstart, result.target);
|
|
break;
|
|
}
|
|
}
|
|
if (instr&Pbit) { *op++=']'; goto lPling; }
|
|
else if (instr&(1<<27)) {
|
|
if (CP_is(1) || CP_is(2)) {
|
|
if (!(instr&Wbit)) goto lUndefined;
|
|
}
|
|
else goto lPling;
|
|
}
|
|
}
|
|
break;
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
op = append(op, regnames[(instr>>(4*(c-'0')))&15]);
|
|
break;
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
*op++='f';
|
|
*op++=(char)('0' + ((instr>>(4*(c-'5')))&7));
|
|
break;
|
|
case ':':
|
|
*op++ = (char)('0' + ((instr>>21)&7));
|
|
break;
|
|
case ';':
|
|
op = reg(op, 'p', instr>>8);
|
|
break;
|
|
case '>':
|
|
*op++='#';
|
|
op = num(op, (instr >> 7) & 31);
|
|
break;
|
|
case '<':
|
|
*op++='#';
|
|
if (instr & (1 << 6)) {
|
|
op = num(op, ((instr >> 16) & 31) + 1);
|
|
} else {
|
|
op = num(op, ((instr >> 16) & 31) + 1 - ((instr >> 7) & 31));
|
|
}
|
|
break;
|
|
default:
|
|
if (c<=5)
|
|
op = reg(op, 'c', instr >> (4*(c-1)));
|
|
else *op++ = c;
|
|
}
|
|
}
|
|
*op=0;
|
|
}
|
|
}
|
|
|
|
/* DONE! */
|
|
|
|
return &result;
|
|
}
|
|
|
|
static const char * reg_names[16] = {
|
|
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"r8", "r9", "r10", "r11", "ip", "sp", "lr", "pc"
|
|
};
|
|
|
|
static sDisOptions options = {
|
|
disopt_CommaSpace,
|
|
reg_names
|
|
};
|
|
|
|
const char *ArmRegName(int r) {
|
|
return reg_names[r];
|
|
}
|
|
|
|
void ArmDis(unsigned int addr, unsigned int w, char *output, int bufsize, bool includeWord) {
|
|
pInstruction instr = instr_disassemble(w, addr, &options);
|
|
char temp[256];
|
|
if (includeWord) {
|
|
snprintf(output, bufsize, "%08x\t%s", w, instr->text);
|
|
} else {
|
|
snprintf(output, bufsize, "%s", instr->text);
|
|
}
|
|
if (instr->undefined || instr->badbits || instr->oddbits) {
|
|
if (instr->undefined) snprintf(output, bufsize, "%08x\t[undefined instr]", w);
|
|
if (instr->badbits) snprintf(output, bufsize, "%08x\t[illegal bits]", w);
|
|
|
|
// HUH? LDR and STR gets this a lot
|
|
// strcat(output, " ? (extra bits)");
|
|
if (instr->oddbits) {
|
|
snprintf(temp, sizeof(temp), " [unexpected bits %08x]", w);
|
|
strcat(output, temp);
|
|
}
|
|
}
|
|
// zap tabs
|
|
while (*output) {
|
|
if (*output == '\t')
|
|
*output = ' ';
|
|
output++;
|
|
}
|
|
}
|
|
|
|
#ifdef __clang__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|