From 65f8430c3215c6367e12db3f29ef7aab30d6ab63 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Tue, 30 Jul 2013 21:39:04 +0200 Subject: [PATCH] Quick hacked-up ARM VFP disassembler. Buggy. --- Common/ArmEmitter.cpp | 11 +- Common/ArmEmitter.h | 8 ++ ext/disarm.cpp | 325 ++++++++++++++++++++++++++---------------- unittest/UnitTest.cpp | 53 ++++++- 4 files changed, 258 insertions(+), 139 deletions(-) diff --git a/Common/ArmEmitter.cpp b/Common/ArmEmitter.cpp index 3baac60b93..9045c048e8 100644 --- a/Common/ArmEmitter.cpp +++ b/Common/ArmEmitter.cpp @@ -920,14 +920,8 @@ void ARMXEmitter::VSUB(IntegerSize Size, ARMReg Vd, ARMReg Vn, ARMReg Vm) | ((Vm & 0x10) << 2) | (Vm & 0xF)); } -// VFP Specific -struct VFPEnc -{ - s16 opc1; - s16 opc2; -}; // Double/single, Neon -const VFPEnc VFPOps[][2] = { +extern const VFPEnc VFPOps[16][2] = { {{0xE0, 0xA0}, {0x20, 0xD1}}, // 0: VMLA {{0xE1, 0xA4}, { -1, -1}}, // 1: VNMLA {{0xE0, 0xA4}, {0x22, 0xD1}}, // 2: VMLS @@ -944,7 +938,8 @@ const VFPEnc VFPOps[][2] = { {{0xEB, 0xAC}, { -1, -1}}, // 13: VCMPE (Vn(0x4 | #0 ? 1 : 0) used for encoding) {{ -1, -1}, {0x3B, 0x30}}, // 14: VABSi }; -const char *VFPOpNames[] = { + +extern const char *VFPOpNames[16] = { "VMLA", "VNMLA", "VMLS", diff --git a/Common/ArmEmitter.h b/Common/ArmEmitter.h index 208d5794ac..4ce0cda1e1 100644 --- a/Common/ArmEmitter.h +++ b/Common/ArmEmitter.h @@ -656,6 +656,14 @@ public: } }; +// VFP Specific +struct VFPEnc { + s16 opc1; + s16 opc2; +}; +extern const VFPEnc VFPOps[16][2]; +extern const char *VFPOpNames[16]; + } // namespace #endif // _DOLPHIN_INTEL_CODEGEN_ diff --git a/ext/disarm.cpp b/ext/disarm.cpp index a700847367..cada7c3ef0 100644 --- a/ext/disarm.cpp +++ b/ext/disarm.cpp @@ -63,6 +63,194 @@ #include #include #include + +#include "base/basictypes.h" +#include "Common/ArmEmitter.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) { + if (!quad && !dbl) { + return ((op >> 22) & 1) | ((op >> 11) & 0x1E); + } + return 0; +} + +int GetVn(uint32_t op, bool quad = false, bool dbl = false) { + if (!quad && !dbl) { + return ((op >> 7) & 1) | ((op >> 15) & 0x1E); + } else if (dbl) { + return ((op >> 16) & 0xF) | ((op >> 3) & 0x10); + } + return 0; +} + +int GetVm(uint32_t op, bool quad = false, bool dbl = false) { + if (!quad && !dbl) { + return ((op >> 5) & 1) | ((op << 1) & 0x1E); + } + return 0; +} + + +// 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) { + const char *cond = CCFlagsStr[op >> 28]; + switch ((op >> 24) & 0xF) { + case 0xD: + // VLDR/VSTR + { + int base = (op >> 16) & 0xF; + bool add = (op >> 23) & 1; + int freg = ((op >> 11) & 0x1E) | ((op >> 22) & 1); + int offset = (op & 0xFF) << 2; + if (!add) offset = -offset; + bool vldr = (op >> 20) & 1; + bool single_reg = ((op >> 8) & 0xF) == 10; + + sprintf(text, "%s%s s%i, [r%i, #%i]", vldr ? "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 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; + } + + int part1 = ((op >> 23) & 0x1F); + int part2 = ((op >> 9) & 0x7) ; + int part3 = ((op >> 20) & 0x3) ; + if (part3 == 3 && part2 == 5 && part1 == 0x1D && (op & (1<<6))) { + // VMOV + int vn = GetVn(op); + if (vn != 1 && vn != 3) { + int vm = GetVm(op); + int vd = GetVd(op); + sprintf(text, "VMOV%s s%i, s%i", cond, vd, vm); + return true; + } + } + + // Arithmetic (buggy!) + + bool quad_reg = (op >> 6) & 1; + bool double_reg = (op >> 8) & 1; + int opnum = -1; + int opc1 = (op >> 20) & 0xFB; + int opc2 = (op >> 4) & 0xAC; + for (int i = 0; i < 16; i++) { + if (ArmGen::VFPOps[i][0].opc1 == opc1 && ArmGen::VFPOps[i][0].opc2 == 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 s%i, s%i", ArmGen::VFPOpNames[opnum], cond, vd, 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 s%i, s%i, s%i", ArmGen::VFPOpNames[opnum], cond, vd, vn, vm); + return true; + } + } + return true; + } + break; + } + return false; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + typedef unsigned int word; typedef unsigned int address; @@ -489,132 +677,17 @@ lMaybeLDRHetc: break; case 12: case 13: - /* STC or LDC */ - if (CP_is(1)) { - /* copro 1: FPU. This is STF or LDF. */ - mnemonic = "STF\0LDF" + ((instr&Lbit) >> 18); - format = "8,/"; - *flagp++ = "SDEP"[fpn]; - poss_tt = (eTargetType)(target_FloatS+fpn); - } - else if (CP_is(2)) { - /* copro 2: this is LFM or SFM. */ - mnemonic = "SFM\0LFM" + ((instr&Lbit) >> 18); - if (!fpn) fpn=4; - if (RN_is(13) && BitsDiffer(23,24)) { - if ((instr&255)!=fpn) goto lNonStackLFM; - /* r13 and U!=P, so treat as stack */ - if (BitsDiffer(20,24)) { - /* L != P, so FD */ - *flagp++ = 'F'; *flagp++ = 'D'; - } - else { - /* L == P, so EA */ - *flagp++ = 'E'; *flagp++ = 'A'; - } - format = "8,(,[4]'"; - } - else { -lNonStackLFM: - /* not r13 or U=P or wrong offset, so don't treat as stack */ - format = "8,(,/"; - poss_tt = target_FloatE; - } - } - else { - /* some other copro number: STC or LDC. */ - mnemonic = "STC\0LDC" + ((instr&Lbit) >> 18); - format = ";,\004,/"; - if (instr&(1<<22)) *flagp++ = 'L'; - poss_tt = target_Unknown; - } - break; - case 14: - /* CDP or MRC/MCR */ - if (instr&(1<<4)) { - /* MRC/MCR. */ - if (CP_is(1)) { - /* copro 1: FPU. */ - if ((instr&Lbit) && RD_is(15)) { - /* MCR in FPU with Rd=r15: comparison (ugh) */ - if (!(instr&(1<<23))) goto lUndefined; /* unused operation */ - mnemonic = "CMF\0\0CNF\0\0CMFE\0CNFE" + (5*(instr&(3<<21)) >> 21); - format = "9,+"; - if (instr&((1<<19)+(7<<5))) - result.badbits=1; /* size,rmode reseved */ - } - else { - /* normal FPU MCR/MRC */ - word op20 = instr&(15<<20); - if (op20>=6<<20) goto lUndefined; - mnemonic = "FLT\0FIX\0WFS\0RFS\0WFC\0RFC" + (op20>>18); - if (op20==0) { - /* FLT instruction */ - format = "9,3"; - { char c = "SDE*"[((instr>>7)&1) + ((instr>>18)&2)]; - if (c=='*') goto lUndefined; else *flagp++=c; - } - if (instr&15) result.oddbits=1; /* Fm and const flag unused */ - } - else { - /* not FLT instruction */ - if (instr&((1<<7)+(1<<19))) - result.badbits=1; /* size bits reserved */ - if (op20==1<<20) { - /* FIX instruction */ - format = "3,+"; - if (opts->flags&disopt_FIXS) - *flagp++ = "SDEP"[((instr>>7)&1) + ((instr>>18)&2)]; - *flagp++ = "\0PMZ"[(instr&(3<<5))>>5]; - if (instr&(7<<15)) result.oddbits=1; /* Fn unused */ - if (instr&(1<<3)) result.badbits=1; /* no immediate consts */ - } - else { - /* neither FLT nor FIX */ - format = "3"; - if (instr&(3<<5)) result.badbits=1; /* rmode reserved */ - if (instr&(15+(7<<15))) result.oddbits=1;/* iFm, Fn unused */ - } - } - } - } - else { - /* some other copro number. Not FPU. */ - /* NB that ObjAsm documentation gets MCR and MRC the wrong way round! - */ - mnemonic = "MCR\0MRC"; - mnemonic += (instr&Lbit) >> 18; - format = ";,:,3,\005,\001-"; - } - } - else { - /* CDP. */ - if (CP_is(1)) { - /* copro 1: FPU. */ - mnemonic = /* dyadics: */ - "ADF\0MUF\0SUF\0RSF\0" - "DVF\0RDF\0POW\0RPW\0" - "RMF\0FML\0FDV\0FRD\0" - "POL\0***\0***\0***\0" - /* monadics: */ - "MVF\0MNF\0ABS\0RND\0" - "SQT\0LOG\0LGN\0EXP\0" - "SIN\0COS\0TAN\0ASN\0" - "ACS\0ATN\0URD\0NRM\0" - + ((instr&(15<<20)) >> 18) /* opcode -> bits 5432 */ - + ((instr&(1<<15)) >> 9); /* monadicP -> bit 6 */ - format = (instr&(1<<15)) ? "8,+" : "8,9,+"; - *flagp++ = "SDE*"[((instr>>7)&1) + ((instr>>18)&2)]; - *flagp++ = "\0PMZ"[(instr&(3<<5))>>5]; - /* NB that foregoing relies on this being the last flag! */ - if (*mnemonic=='*' || *flagchars=='*') goto lUndefined; - } - else { - /* some other copro number. Not FPU. */ - mnemonic = "CDP"; - format = ";,),\004,\005,\001-"; - } - } + case 14: // FPU + { + char text[256]; + if (!DisasmVFP(instr, text)) { + goto lUndefined; + break; + } + strcpy(result.text, text); + result.undefined = 0; + return &result; + } break; case 15: /* SWI */ diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index fbffcd8799..cb77b0e1a8 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -37,7 +37,20 @@ #define EXPECT_TRUE(a) if (!(a)) { printf(__FUNCTION__ ":%i: Test Fail\n", __LINE__); return false; } #define EXPECT_FALSE(a) if ((a)) { printf(__FUNCTION__ ":%i: Test Fail\n", __LINE__); return false; } #define EXPECT_EQ_FLOAT(a, b) if ((a) != (b)) { printf(__FUNCTION__ ":" __LINE__ ": Test Fail\n%f\nvs\n%f\n", a, b); return false; } -#define EXPECT_EQ_STR(a, b) if ((a) != (b)) { printf(__FUNCTION__ ": Test Fail\n%s\nvs\n%s\n", a.c_str(), b.c_str()); return false; } +#define EXPECT_EQ_STR(a, b) if (a != b) { printf(__FUNCTION__ ": Test Fail\n%s\nvs\n%s\n", a.c_str(), b.c_str()); return false; } + +#define RET(a) if (!(a)) { return false; } + + +bool CheckLast(ArmGen::ARMXEmitter &emit, const char *comp) { + u32 instr; + memcpy(&instr, emit.GetCodePtr() - 4, 4); + char disasm[512]; + ArmDis(0, instr, disasm); + EXPECT_EQ_STR(std::string(disasm), std::string(comp)); + return true; +} + bool TestArmEmitter() { using namespace ArmGen; @@ -45,12 +58,42 @@ bool TestArmEmitter() { u32 code[512]; ARMXEmitter emitter((u8 *)code); emitter.LDR(R3, R7); + RET(CheckLast(emitter, "e5973000 LDR r3, [r7, #0]")); + emitter.VLDR(S3, R8, 48); + RET(CheckLast(emitter, "edd81a0c VLDR s3, [r8, #48]")); + emitter.VSTR(S5, R12, -36); + RET(CheckLast(emitter, "ed4c2a09 VSTR s5, [r12, #-36]")); + emitter.VADD(S1, S2, S3); + RET(CheckLast(emitter, "ee710a21 VADD s1, s2, s3")); + emitter.VMUL(S7, S8, S9); + RET(CheckLast(emitter, "ee643a24 VMUL s7, s8, s9")); + emitter.VMLA(S7, S8, S9); + RET(CheckLast(emitter, "ee443a24 VMLA s7, s8, s9")); + emitter.VNMLA(S7, S8, S9); + RET(CheckLast(emitter, "ee543a64 VNMLA s7, s8, s9")); + emitter.VABS(S1, S2); + RET(CheckLast(emitter, "eef00ac1 VABS s1, s2")); + emitter.VSQRT(S1, S2); + RET(CheckLast(emitter, "eef10ac1 VSQRT s1, s2")); + emitter.VDIV(S1, S2, S3); + RET(CheckLast(emitter, "eec10a21 VDIV s1, s2, s3")); + emitter.VMRS(R1); + RET(CheckLast(emitter, "eef11a10 VMRS r1")); + emitter.VMSR(R7); + RET(CheckLast(emitter, "eee17a10 VMSR r7")); + emitter.VMRS_APSR(); + RET(CheckLast(emitter, "eef1fa10 VMRS APSR")); + emitter.VCVT(S0, S1, TO_INT | IS_SIGNED); + RET(CheckLast(emitter, "eebd0a60 VCVT ...")); - char disasm[512]; - ArmDis(0, code[0] & 0xFFFFFFFF, disasm); - std::string dis(disasm); - EXPECT_EQ_STR(dis, std::string("e4973000 LDR r3, [r7, #0]")); + // WTF? + //emitter.VSUB(S4, S5, S6); + //RET(CheckLast(emitter, "ee322ac3 VSUB s4, s5, s6")); + + + emitter.VMOV(S3, S6); + RET(CheckLast(emitter, "eef01a43 VMOV s3, s6")); return true; }