,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: + /* 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: + case 5: + case 6: + case 7: + /* undefined or STR/LDR */ + if ((instr&Ibit) && (instr&(1<<4))) goto lUndefined; /* "class A" */ + 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: + /* 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-"; + } + } + 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 '$': + result.is_SWI = 1; + result.swinum = instr&0x00FFFFFF; + result.addrstart = op; + op += sprintf(op, "&%lX", result.swinum); + break; + case '%': + *op++='{'; + { word w = instr&0xFFFF; + int i=0; + while (w) { + int j; + while (!(w&(1ul<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 { + 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=-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 = -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; + 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 +}; + +void ArmDis(unsigned int addr, unsigned int w, char *output) { + pInstruction instr = instr_disassemble(w, addr, &options); + sprintf(output, "%.6lX %.8lX\t%s", addr, w, instr->text); + if (instr->undefined || instr->badbits || instr->oddbits) { + if (instr->undefined) sprintf(output, " [undefined instr]"); + if (instr->badbits) sprintf(output, " [illegal bits]"); + if (instr->oddbits) sprintf(output, " [unexpected bits]"); + } + // zap tabs + while (*output) { + if (*output == '\t') + *output = ' '; + output++; + } +} \ No newline at end of file diff --git a/ext/disarm.h b/ext/disarm.h new file mode 100644 index 0000000000..124a1a572f --- /dev/null +++ b/ext/disarm.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012- PPSSPP Project. + +// 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, version 2.0 or later versions. + +// 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + + +// This stuff only disassembles very old ARM but should be sufficient +// for the basics except for MOVW/MOVT. + + + +// Disarm itself has the license you can see in the cpp file. +// I'm not entirely sure it's 100% gpl compatible but it's nearly +// public domain so meh. + +// The only changes I've done is C++ compat and replaced the main +// program with this function. + +void ArmDis(unsigned int addr, unsigned int w, char *output);