/* Target CPU: ARM946E-S (Nintendo DS main CPU) and ARM7TDMI (Nintendo DS secondary CPU, Nintendo GBA main CPU) Target Architecture: ARMv5TE and ARMv4T ARM version: 5, 4 THUMB version: 2, 1 Documentation: https://www.intel.com/content/dam/support/us/en/programmable/support-resources/bulk-container/pdfs/literature/third-party/ddi0100e-arm-arm.pdf */ /* Instructions marked with an asterisk * are not available in ARMv4T */ /* Alphabetical list of ARM instructions (number of variants) */ /* ADC Add with Carry ADD Add AND Logical AND B Branch BL Branch and Link BIC Bit Clear BKPT Breakpoint* BLX (2) Branch with Link and Exchange* BX Branch and Exchange CDP Coprocessor Data Processing CLZ Count Leading Zeros* CMN Compare Negative CMP Compare EOR Logical Exclusive OR LDC Load Coprocessor LDC2 Load Coprocessor 2* LDM (3) Load Multiple LDR Load Register LDRB Load Register Byte LDRBT Load Register Byte with Translation LDRH Load Register Halfword LDRSB Load Register Signed Byte LDRSH Load Register Signed Halfword LDRT Load Register with Translation MCR Move to Coprocessor from ARM Register MCR2 Move to Coprocessor from ARM Register 2* MLA Multiply Accumulate MOV Move MRC Move to ARM Register from Coprocessor MRC2 Move to ARM Register from Coprocessor 2* MRS Move PSR to General-purpose Register MSR Move to Status Register from ARM Register MUL Multiply MVN Move Negative ORR Logical OR RSB Reverse Substract RSC Reverse Substract with Carry SBC Substract with Carry SMLAL Signed Multply Accumulate Long SMULL Signed Multply Long STC Store Coprocessor STC2 Store Coprocessor 2* STM (2) Store Multiple STR Store Register STRB Store Register Byte STRBT Store Register Byte with Translation STRH Store Register Halfword STRT Store Register with Translation SUB Substract SWI Software Interrupt SWP Swap SWPB Swap Byte TEQ Test Equivalence TST Test UMLAL Unsigned Multply Accumulate Long UMULL Unsigned Multply Long //DSP enhanced (ARMv5TE exclusive) LDRD Load Register Dual MCRR Move to Coprocessor from Registers MRRC Move to Registers from Coprocessor PLD Preload Data QADD Saturating signed Add QDADD ;Performs a saturated integer doubling of one operand followed by a saturated integer addition with the other operand QDSUB ;Performs a saturated integer doubling of one operand followed by a saturated integer substraction from the other operand QSUB Saturating signed Subtraction SMLA Signed Multiply Accumulate SMLAL Signed Multiply Accumulate Long SMLAW Signed Multiply Accumulate Word SMUL Signed Multiply SMULW Signed Multiply Word STRD Store Register Dual */ /* Alphabetical list of THUMB instructions (number of variants) */ /* ADC Add with Carry ADD (7) Add AND Logical AND ASR (2) Arithmetic Shift Right B (2) Branch BIC Bit Clear BKPT Breakpoint* BL Branch with Link BLX (2) Branch with Link and Exchange* BX Branch and Exchange CMN Compare Negative CMP (3) Compare EOR Logical Exclusive OR LDMIA Load Multiple Increment After LDR (4) Load Register LDRB (2) Load Register Byte LDRH (2) Load Register Halfword LDRSB Load Register Signed Byte LDRSH Load Register Signed Halfword LSL (2) Logical Shift Left LSR (2) Logical Shift Right MOV (3) Move MUL Multiply MVN Move NOT NEG Negate ORR Logical OR POP Pop Multiple Registers PUSH Push Multiple Registers ROR Rotate Right Register SBC Substract with Carry STMIA Store Multiple Increment After STR (3) Store Register STRB (2) Store Register Byte STRH (2) Store Register Halfword SUB (4) Substract SWI Software Interrupt TST Test */ /* INCLUDES */ #include #include #include #include #include /* MACROS */ //#define _CRT_SECURE_NO_WARNINGS 1 #define STRING_LENGTH (80) //can fail at 64 in SubstituteSubString #define CONDITIONS_MAX (16) #define BITS(x, b, n) ((x >> b) & ((1 << n) - 1)) //retrieves n bits from x starting at bit b #define SIGNEX32_BITS(x, b, n) ((BITS(x,b,n) ^ (1<<(n-1))) - (1<<(n-1))) //convert n-bit value to signed 32 bits #define SIGNEX32_VAL(x, n) ((x ^ (1<<(n-1))) - (1<<(n-1))) //convert n-bit value to signed 32 bits #define ROR(x, n) ((x>>n)|(x<<(32-n))) //rotate right 32-bit value x by n bits /* TYPEDEFS */ typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8; typedef enum { SIZE_16, SIZE_32 }THUMBSIZE; typedef enum { ARMv4T, //ARM v4, THUMB v1 ARMv5TE, //ARM v5, THUMB v2 ARMv6 //ARM v6, THUMB v3 }ARMARCH; //only 32-bit legacy architectures with THUMB support typedef enum { EQ, //equal, Z set NE, //not equal, Z clear CS, //carry set, C set (or HS, unsigned higher or same) CC, //carry clear, C clear (or LO, unsigned lower) MI, //minus/negative, N set PL, //plus/positive/zero, N clear VS, //overflow, V set VC, //no overflow, V clear HI, //unsigned higher, C set and Z clear LS, //unsigned lower or same, C clear or Z set GE, //signed greater than or equal, N==V LT, //signed less than, N!=V GT, //signed greater than, Z==0 and N==V LE, //signed less than or equal, Z==1 or N!=V AL, //unconditional, only with IT instructions NV //unconditional, usually undefined }CONDITION; /* GLOBALS */ //todo: maybe put "2" instead of "nv" (or nothing) in the last one const char Conditions[CONDITIONS_MAX][3] = { "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "", "" }; //last two are "al" and "nv", but never displayed const char AddressingModes[4][3] = { "da", //Decrement after "ia", //Increment after "db", //Decrement before "ib" //Increment before }; const char DataProcessing_thumb[16][4] = { "and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror", "tst", "neg", "cmp", "cmn", "orr", "mul", "bic", "mvn" }; const char DataProcessing_arm[16][4] = { "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", "tst", //no s "teq", //no s "cmp", //no s "cmn", //no s "orr", "mov", //only 1 source operand "bic", "mvn" //only 1 source operand }; const char MSR_cxsf[16][5] = { "", "c", "x", "xc", "s", "sc", "sx", "sxc", "f", "fc", "fx", "fxc", "fs", "fsc", "fsx", "fsxc" }; const char LoadStoreRegister[8][6] = { "str", //STR (2) "strh", //STRH (2) "strb", //STRB (2) "ldrsb", //LDRSB "ldr", //LDR (2) "ldrh", //LDRH (2) "ldrb", //LDRB (2) "ldrsh" //LDRSH }; const char DSP_AddSub[4][6] = { "qadd", "qsub", "qdadd", "qdsub" }; const char DSP_Multiplies[4][6] = { "smla", "", "smlal", "smul" }; //slot 1 empty, decided elsehow const char MultiplyLong[4][6] = { "umull", "umlal", "smull", "smlal" }; const char AddCmpMovHighRegisters[3][4] = { "add", "cmp", "mov" }; const char MovCmpAddSubImmediate[4][4] = { "mov", "cmp", "add", "sub" }; const char Shifters[4][4] = { "lsl", "lsr", "asr", "ror" }; //+rrx const char ShiftImmediate[4][4] = { "lsl", "lsr", "asr", "" }; //thumb u32 debug_na_count = 0; /* LIBRARY FUNCTIONS */ static void SubstituteSubString(char dst[STRING_LENGTH], u32 index, const char* sub, u32 size) { /* Insert sub string of length size (< STRING_LENGTH) at dst[index] */ char tmp[STRING_LENGTH] = { 0 }; //zinit memcpy(&tmp, sub, size); //init memcpy(&tmp[size], &dst[index + size + 1], STRING_LENGTH - index - size - 1); //save memcpy(&dst[index], tmp, STRING_LENGTH - index - 1); //restore } static void CheckSpecialRegister(char* str, int size) { /* Replace special registers by their common names */ //(r12->ip), r13->sp, r14->lr, r15->pc //todo: check for size != -1 for (int i = 0; i < size; i++) //when using a variable string size (return value of sprintf), 10% faster than constant size of STRING_LENGTH 64 { if (str[i] == 'r') { switch (*(u16*)&str[i + 1]) { case 0x3331: //"13" { SubstituteSubString(str, i, "sp", sizeof("sp") - 1); break; } case 0x3431: //"14" { SubstituteSubString(str, i, "lr", sizeof("lr") - 1); break; } case 0x3531: //"15" { SubstituteSubString(str, i, "pc", sizeof("pc") - 1); break; } } } } } static u32 FormatStringRegisterList_thumb(char str[STRING_LENGTH], u16 reg, const char* pclr, u8 pclr_size) { /* Format str according to the reg bifield, group consecutive registers together */ /* Support for a special 9th register name */ u32 bits = 0; //return value, number of 1 bits in reg u32 streak = 0; //current streak, used to group registers together u32 pos = 0; //position in the str array char tmp[4] = { 0 }; //temporary buffer for register write for (u32 i = 0; i < 9; i++) //reg is a 9-bit value { if (BITS(reg, i, 1)) { bits++; switch (streak) { case 0: //streak ended, print current register { if (i == 8) { sprintf(tmp, "%s", pclr); //pc or lr override memcpy(&str[pos], tmp, sizeof(tmp)); //write pos += pclr_size; //sizeof(pclr) } else { sprintf(tmp, "r%u,", i); //r0-r7 memcpy(&str[pos], tmp, sizeof(tmp)); //write pos += 3; //sizeof("r0") } break; } case 1: break; //used to catch default cases later case 2: //hyphenation if at least 3 in a row { str[pos - 1] = '-'; //replaces the comma break; } default: { //todo: a bit repetitive, tidy up if (i == 8) //if on last bit, close the current streak by writing previous and last register { sprintf(tmp, "r%u,", i - 1); //previous register (can't be LR/PC) memcpy(&str[pos], tmp, sizeof(tmp)); //write pos += 3; //depends on size of tmp sprintf(tmp, "%s", pclr); //pc or lr override memcpy(&str[pos], tmp, sizeof(tmp)); //write pos += pclr_size; //sizeof(pclr) } } } streak = (i < 8) ? streak + 1 : 0; //avoids grouping LR/PC } else { if (streak > 1) //if broke with at least 3 in a row, close { sprintf(tmp, "r%u,", i - 1); //previous register (can't be LR/PC) memcpy(&str[pos], tmp, sizeof(tmp)); //write pos += 3; //depends on size of tmp } streak = 0; //reset } } if (pos) str[pos - 1] = 0; //removes the coma on the last register return bits; //number of 1 bits } static u32 FormatStringRegisterList_arm(char str[STRING_LENGTH], u16 reg) { /* Format str according to the reg bifield, group consecutive registers together */ u32 bits = 0; //return value, number of 1 bits in reg u32 streak = 0; //current streak, used to group registers together u32 pos = 0; //position in the str array char tmp[5] = { 0 }; //temporary buffer for register write for (u32 i = 0; i < 16; i++) //all registers { if (BITS(reg, i, 1)) { bits++; switch (streak) { case 0: //streak started, print current register { sprintf(tmp, "r%u,", i); //r0-r15 memcpy(&str[pos], tmp, sizeof(tmp)); //write pos = (i < 10) ? pos + sizeof("r0") : pos + sizeof("r10"); break; } case 1: break; case 2: //hyphenation if at least 3 in a row { str[pos - 1] = '-'; //replaces the comma break; } default: { if (i == 15) //if on last bit, close the current streak by writing last register { str[pos++] = 'p'; str[pos++] = 'c'; str[pos++] = ','; //comma will get deleted later } } } streak++; } else { if (streak > 1) //if broke with at least 3 in a row, close { sprintf(tmp, "r%u,", i - 1); //previous register memcpy(&str[pos], tmp, sizeof(tmp)); //write pos = (i - 1 < 10) ? pos + sizeof("r0") : pos + sizeof("r10"); } streak = 0; //reset } } if (pos) str[pos - 1] = 0; //removes the coma on the last register return bits; //number of 1 bits } static int FormatExtraLoadStore(u32 c, char* str, u8 cond, const char* op) { /* */ u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); u8 w = BITS(c, 21, 1); u8 p = BITS(c, 24, 1); if (BITS(c, 22, 1)) //immediate { u8 ofs = (BITS(c, 8, 4) << 4) | BITS(c, 0, 4); const char* sign = BITS(c, 23, 1) ? "+" : "-"; if (p) //offset, pre-indexed { const char* pre = w ? "!" : ""; return sprintf(str, "%s%s r%u, [r%u, #%s0x%X]%s", op, Conditions[cond], rd, rn, sign, ofs, pre); } else //post-indexed { if (w) return 0; //w must be 0, else UNPREDICTABLE //todo: check if really invalid return sprintf(str, "%s%s r%u, [r%u], #%s0x%X", op, Conditions[cond], rd, rn, sign, ofs); } } else //register { u8 rm = BITS(c, 0, 4); const char* sign = BITS(c, 23, 1) ? "" : "-"; //implicit "+" in front of registers if (p) //offset, pre-indexed { const char* pre = w ? "!" : ""; return sprintf(str, "%s%s r%u, [r%u, %sr%u]%s", op, Conditions[cond], rd, rn, sign, rm, pre); } else //post-indexed { if (w) return 0; //w must be 0, else UNPREDICTABLE //todo: check if really invalid return sprintf(str, "%s%s r%u, [r%u], %sr%u", op, Conditions[cond], rd, rn, sign, rm); } } } u32 Disassemble_thumb(u32 code, char str[STRING_LENGTH], ARMARCH tv) { /* Convert a code into a string, return size of the processed code (SIZE_16 or SIZE_32) */ THUMBSIZE thumb_size = SIZE_16; //return value u16 c = code & 0xffff; //low 16 bits int size = 0; //return value of sprintf to be passed to CheckSpecialRegister switch (c >> 13) { case 0: //0x0000 //LSL, LSR, ASR, ADD, SUB { u8 rd = BITS(c, 0, 3); u8 rn = BITS(c, 3, 3); u8 index = BITS(c, 11, 2); if (index == 3) //ADD, SUB, MOV { if (BITS(c, 6, 5) == 16) //MOV (2) (technically ADD (1) with imm==0) { size = sprintf(str, "mov r%u, r%u", rd, rn); } else { u8 rm_imm = BITS(c, 6, 3); const char* op = (BITS(c, 9, 1)) ? "sub" : "add"; if (BITS(c, 10, 1)) //ADD (1), SUB (1) -immediate { size = sprintf(str, "%s r%u, r%u, #0x%X", op, rd, rn, rm_imm); } else //ADD (3), SUB (3) -register { size = sprintf(str, "%s r%u, r%u, r%u", op, rd, rn, rm_imm); } } } else //Shift by immediate: LSL (1), LSR (1), ASR (1) { size = sprintf(str, "%s r%u, r%u, #0x%X", ShiftImmediate[index], rd, rn, BITS(c, 6, 5)); } break; } case 1: //0x2000 //MOV (1), CMP (1), ADD (2), SUB (2) { size = sprintf(str, "%s r%u, #0x%X", MovCmpAddSubImmediate[BITS(c, 11, 2)], BITS(c, 8, 3), BITS(c, 0, 8)); break; } case 2: //0x4000 //lots... { switch (BITS(c, 10, 3)) { case 0: //Data-processing registers { size = sprintf(str, "%s r%u, r%u", DataProcessing_thumb[BITS(c, 6, 4)], BITS(c, 0, 3), BITS(c, 3, 3)); break; } case 1: //Special data processing { u8 rd = (BITS(c, 7, 1) << 3) | (BITS(c, 0, 3)); u8 rm = BITS(c, 3, 4); u8 op = BITS(c, 8, 2); if (op == 3) //Branch/exchange instruction set { if (BITS(c, 0, 3)) break; //Should-Be-Zero if (BITS(c, 7, 1)) //BLX (2) { if (tv < ARMv5TE) break; //UNPREDICTABLE prior to ARM version 5 size = sprintf(str, "blx r%u", rm); } else //BX { size = sprintf(str, "bx r%u", rm); } } else //ADD (4), CMP (3), MOV (3) { if (!BITS(c, 6, 2)) break; //UNPREDICTABLE size = sprintf(str, "%s r%u, r%u", AddCmpMovHighRegisters[op], rd, rm); } break; } default: //Load from literal pool, Load/Store register offset { if (BITS(c, 12, 1)) //Load/store register offset { size = sprintf(str, "%s r%u, [r%u, r%u]", LoadStoreRegister[BITS(c, 9, 3)], BITS(c, 0, 3), BITS(c, 3, 3), BITS(c, 6, 3)); } else //LDR (3) { if (!BITS(c, 11, 1)) break; //Should-Be-One size = sprintf(str, "ldr r%u, [pc, #0x%X]", BITS(c, 8, 3), 4 * BITS(c, 0, 8)); } } } break; } case 3: //0x6000 //STR, LDR, STRB, LDRB { switch (BITS(c, 11, 2)) { case 0: //STR (1) { size = sprintf(str, "str r%u, [r%u, #0x%X]", BITS(c, 0, 3), BITS(c, 3, 3), 4 * BITS(c, 6, 5)); break; } case 1: //LDR (1) { size = sprintf(str, "ldr r%u, [r%u, #0x%X]", BITS(c, 0, 3), BITS(c, 3, 3), 4 * BITS(c, 6, 5)); break; } case 2: //STRB (1) { size = sprintf(str, "strb r%u, [r%u, #0x%X]", BITS(c, 0, 3), BITS(c, 3, 3), BITS(c, 6, 5)); break; } case 3: //LDRB (1) { size = sprintf(str, "ldrb r%u, [r%u, #0x%X]", BITS(c, 0, 3), BITS(c, 3, 3), BITS(c, 6, 5)); break; } } break; } case 4: //0x8000 //STR, LDR, STRH, LDRH { if (BITS(c, 12, 1)) //LDR (4), STR (3) { const char* op = (BITS(c, 11, 1)) ? "ldr" : "str"; size = sprintf(str, "%s r%u, [sp, #0x%X]", op, BITS(c, 8, 3), 4 * BITS(c, 0, 8)); } else //LDRH (1), STRH (1) { const char* op = (BITS(c, 11, 1)) ? "ldrh" : "strh"; size = sprintf(str, "%s r%u, [r%u, #0x%X]", op, BITS(c, 0, 3), BITS(c, 3, 3), 2 * BITS(c, 6, 5)); } break; } case 5: //0xA000 //Misc and ADD to sp or pc { if (BITS(c, 12, 1)) //Misc, fig 6-2 { switch (BITS(c, 8, 4)) { case 0: //ADD (4), SUB (7) to/from SP { const char* op = (BITS(c, 7, 1)) ? "sub" : "add"; size = sprintf(str, "%s sp, #0x%X", op, 4 * BITS(c, 0, 7)); break; } //PUSH/POP case 4: case 5: case 12: case 13: { char reglist[STRING_LENGTH] = { 0 }; u16 registers = BITS(c, 0, 9); if (BITS(c, 11, 1)) //POP { if (FormatStringRegisterList_thumb(reglist, registers, "pc", sizeof("pc"))) //if BitCount(registers) < 1 then UNPREDICTABLE { size = sprintf(str, "pop {%s}", reglist); } } else //PUSH { if (FormatStringRegisterList_thumb(reglist, registers, "lr", sizeof("lr"))) //if BitCount(registers) < 1 then UNPREDICTABLE { size = sprintf(str, "push {%s}", reglist); } } break; } case 14: //BKPT { if (tv >= ARMv5TE) //undefined prior to ARM version 5 { size = sprintf(str, "bkpt #0x%X", BITS(c, 0, 8)); } break; } } } else //ADD (5), ADD (6) to SP or PC { const char* sppc = (BITS(c, 11, 2)) ? "sp" : "pc"; size = sprintf(str, "add r%u, %s, #0x%X", BITS(c, 8, 3), sppc, 4 * BITS(c, 0, 8)); } break; } case 6: //0xC000 //B, SWI, LDMIA, STMIA { if (BITS(c, 12, 1)) //Conditional branch, Undefined, System call { switch (BITS(c, 8, 4)) { case 14: //UDF "Permanently undefined space", OS dependant { //size = sprintf(str, "udf #0x%X", BITS(c, 0, 8)); //note: not an instruction in ARMv5TE, just UNDEFINED break; } case 15: //SWI { size = sprintf(str, "swi #0x%X", BITS(c, 0, 8)); break; } default: //B (1) conditional { size = sprintf(str, "b%s #0x%X", Conditions[BITS(c, 8, 4)], 4 + 2 * SIGNEX32_BITS(c, 0, 8)); } } } else //LDMIA/STMIA { char reglist[STRING_LENGTH] = { 0 }; const char* op = (BITS(c, 11, 1)) ? "ldmia" : "stmia"; if (FormatStringRegisterList_thumb(reglist, BITS(c, 0, 8), "", 0)) { size = sprintf(str, "%s r%u!, {%s}", op, BITS(c, 8, 3), reglist); } } break; } case 7: //0xE000 //B, then 32-bit instructions { switch (BITS(c, 11, 2)) { case 0: { size = sprintf(str, "b #0x%X", 4 + 2 * SIGNEX32_BITS(c, 0, 11)); //11 bits to signed 32 bits break; } //case 1: break; //undefined on first pass case 2: //BL/BLX prefix { c = code >> 16; //get high 16 bits if (c >> 13 == 7) { switch (BITS(c, 11, 2)) { case 1: //BLX (1) { if (tv < ARMv5TE) break; if (!BITS(c, 0, 1)) { thumb_size = SIZE_32; int ofs = (BITS((code & 0xffff), 0, 11)) << 12; ofs = SIGNEX32_VAL(ofs, 23); ofs += 4; ofs += 2 * BITS(c, 0, 11); //note: bit 1 should be cleared (word aligned target address) size = sprintf(str, "blx #0x%X", ofs); } break; } //case 2: break; //BL/BLX prefix case 3: //BL { thumb_size = SIZE_32; int ofs = (BITS((code & 0xffff), 0, 11)) << 12; ofs = SIGNEX32_VAL(ofs, 23); ofs += 4; ofs += 2 * BITS(c, 0, 11); size = sprintf(str, "bl #0x%X", ofs); break; } } } break; } } break; } } if (!str[0]) //in case nothing was written to it { debug_na_count++; sprintf(str, "n/a"); } else { CheckSpecialRegister(str, size); //formatting } return thumb_size; } void Disassemble_arm(u32 code, char str[STRING_LENGTH], ARMARCH av) { /**/ //Reference: page 68 of 811 from the ARM Architecture reference manual june 2000 edition //todo: proper support for ARMv4T //todo: extra caution for UNPREDICTABLE instructions, need to remove them? or decode regardless? int size = 0; //return value of sprintf to be passed to CheckSpecialRegister u32 c = code; //alias u8 cond = BITS(c, 28, 4); //condition bits //todo: check unconditional instructions first to avoid putting a check later at each stage switch (BITS(c, 25, 3)) { case 0: //Data processing, DSP instructions, ... { if (cond == NV) break; //undefined if (BITS(c, 4, 1)) { if (BITS(c, 7, 1)) //Multiplies, extra load/stores: see fig 3-2 { u8 oplo = BITS(c, 5, 2); if (!oplo) { if (!BITS(c, 22, 3)) //Multiply (accumulate) { u8 rm = BITS(c, 0, 4); u8 rs = BITS(c, 8, 4); u8 rn = BITS(c, 12, 4); u8 rd = BITS(c, 16, 4); const char* s = BITS(c, 20, 1) ? "s" : ""; if (BITS(c, 21, 1)) //MLA { size = sprintf(str, "mla%s%s r%u, r%u, r%u, r%u", s, Conditions[cond], rd, rm, rs, rn); } else //MUL { if (rn) break; //Should-Be-Zero size = sprintf(str, "mul%s%s r%u, r%u, r%u", s, Conditions[cond], rd, rm, rs); } } else if (BITS(c, 23, 1)) //Multiply (accumulate) long { const char* s = BITS(c, 20, 1) ? "s" : ""; size = sprintf(str, "%s%s%s r%u, r%u, r%u, r%u", MultiplyLong[BITS(c, 21, 2)], s, Conditions[cond], BITS(c, 12, 4), BITS(c, 16, 4), BITS(c, 0, 4), BITS(c, 8, 4)); } else //Swap/swap byte (SWP, SWPB) { if (BITS(c, 8, 4)) break; //Should-Be-Zero const char* b = BITS(c, 22, 1) ? "b" : ""; //byte or no size = sprintf(str, "swp%s%s r%u, r%u, [r%u]", b, Conditions[cond], BITS(c, 12, 4), BITS(c, 0, 4), BITS(c, 16, 4)); } } else { if (!BITS(c, 22, 1) && BITS(c, 8, 4)) break; //Should-Be-Zero if register offset if (oplo == 1) //Load/store halfword { const char* l = BITS(c, 20, 1) ? "ldrh" : "strh"; //load or store size = FormatExtraLoadStore(c, str, cond, l); } else { if (BITS(c, 20, 1)) //Load signed halfword/byte { const char* h = BITS(c, 5, 1) ? "ldrsh" : "ldrsb"; //halfword/byte size = FormatExtraLoadStore(c, str, cond, h); } else //Load/store two words { if (BITS(c, 12, 1)) break; //undefined if Rd is odd const char* s = BITS(c, 5, 1) ? "strd" : "ldrd"; //store or load size = FormatExtraLoadStore(c, str, cond, s); } } } } else { if (BITS(c, 23, 2) == 2 && !BITS(c, 20, 1)) //Miscellanous instructions, see fig 3-3 { u8 oplo = BITS(c, 5, 2); u8 ophi = BITS(c, 21, 2); switch (oplo) { case 0: { if (ophi == 3) //CLZ { //note: if PC is in either register, UNPREDICTABLE if (av < ARMv5TE) break; if (!BITS(c, 16, 4) || !BITS(c, 8, 4)) break; //Should-Be-One size = sprintf(str, "clz%s r%u, r%u", Conditions[cond], BITS(c, 12, 4), BITS(c, 0, 4)); } else if (ophi == 1)//Branch/exchange instruction set (BX) { if (BITS(c, 8, 12) != 0xfff) break; //Should-Be-One size = sprintf(str, "bx%s r%u", Conditions[cond], BITS(c, 0, 4)); } break; } case 1: //BLX (2) { if (av < ARMv5TE) break; if (ophi != 1) break; if (BITS(c, 8, 12) != 0xfff) break; //Should-Be-One size = sprintf(str, "blx%s r%u", Conditions[cond], BITS(c, 0, 4)); break; } case 2: //Enhanced DSP add/sub (QADD, QDADD, QSUB, QDSUB) { //note: if PC is in either register, UNPREDICTABLE if (av < ARMv5TE) break; if (BITS(c, 8, 4)) break; //Should-Be-Zero size = sprintf(str, "%s%s r%u, r%u, r%u", DSP_AddSub[ophi], Conditions[cond], BITS(c, 12, 4), BITS(c, 0, 4), BITS(c, 16, 4)); break; } case 3: //Software breakpoint (BKPT) { if (av < ARMv5TE) break; if (ophi != 1) break; size = sprintf(str, "bkpt #0x%X", (BITS(c, 8, 12) << 4) | BITS(c, 0, 4)); break; } } } else //Data processing register shift { u8 rm = BITS(c, 0, 4); u8 shift = BITS(c, 5, 2); u8 rs = BITS(c, 8, 4); u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); u8 op = BITS(c, 21, 4); const char* s = BITS(c, 20, 1) ? "s" : ""; switch (op) { case 8: //TST case 9: //TEQ case 10: //CMP case 11: //CMN { size = sprintf(str, "%s%s r%u, r%u, %s r%u", DataProcessing_arm[op], Conditions[cond], rn, rm, Shifters[shift], rs); break; } case 13: //MOV case 15: //MVN { size = sprintf(str, "%s%s%s r%u, r%u, %s r%u", DataProcessing_arm[op], s, Conditions[cond], rd, rm, Shifters[shift], rs); break; } default: { size = sprintf(str, "%s%s%s r%u, r%u, r%u, %s r%u", DataProcessing_arm[op], s, Conditions[cond], rd, rn, rm, Shifters[shift], rs); } } } } } else //bit 4 == 0 { if (BITS(c, 23, 2) == 2 && !BITS(c, 20, 1)) //Miscellanous instructions, see fig 3-3 { if (BITS(c, 7, 1)) //Enhanced DSP multiplies { if (av < ARMv5TE) break; //note: PC for any register is UNPREDICTABLE u8 rm = BITS(c, 0, 4); const char* x = BITS(c, 5, 1) ? "t" : "b"; const char* y = BITS(c, 6, 1) ? "t" : "b"; u8 rs = BITS(c, 8, 4); u8 rn_rdlo = BITS(c, 12, 4); u8 rd_rdhi = BITS(c, 16, 4); u8 op = BITS(c, 21, 2); switch (op) { case 0: //SMLA { size = sprintf(str, "%s%s%s%s r%u, r%u, r%u, r%u", DSP_Multiplies[op], x, y, Conditions[cond], rd_rdhi, rm, rs, rn_rdlo); break; } case 1: //SMLAW, SMULW { if (BITS(c, 5, 1)) //SMULW { if (rn_rdlo) break; //Should-Be-Zero size = sprintf(str, "smulw%s%s r%u, r%u, r%u", y, Conditions[cond], rd_rdhi, rm, rs); } else //SMLAW { size = sprintf(str, "smlaw%s%s r%u, r%u, r%u, r%u", y, Conditions[cond], rd_rdhi, rm, rs, rn_rdlo); } break; } case 2: //SMLAL { size = sprintf(str, "%s%s%s%s r%u, r%u, r%u, r%u", DSP_Multiplies[op], x, y, Conditions[cond], rn_rdlo, rd_rdhi, rm, rs); break; } case 3: //SMUL { if (rn_rdlo) break; //Should-Be-Zero size = sprintf(str, "%s%s%s%s r%u, r%u, r%u", DSP_Multiplies[op], x, y, Conditions[cond], rd_rdhi, rm, rs); break; } } } else { if (!BITS(c, 0, 12) && BITS(c, 16, 4) == 15) //Move status reg to reg (MRS) { //note: if Rd == PC, UNPREDICTABLE char sreg = BITS(c, 22, 1) ? 's' : 'c'; //SPSR (1) or CPSR (0) size = sprintf(str, "mrs%s r%u, %cpsr", Conditions[cond], BITS(c, 12, 4), sreg); } else if (BITS(c, 12, 4) == 15 && !BITS(c, 4, 8) && BITS(c, 21, 1)) //Move reg to status reg (MSR register) { char sreg = BITS(c, 22, 1) ? 's' : 'c'; //SPSR (1) or CPSR (0) size = sprintf(str, "msr%s %cpsr_%s, r%u", Conditions[cond], sreg, MSR_cxsf[BITS(c, 16, 4)], BITS(c, 0, 4)); } } } else //Data processing immediate shift { //todo: write a function for all 3 Data Processing switch-cases u8 op = BITS(c, 21, 4); u8 rm = BITS(c, 0, 4); u8 shift = BITS(c, 5, 2); u8 shift_imm = BITS(c, 7, 5); char sstr[STRING_LENGTH] = "rrx"; //default, overwrite if incorrect if ((shift == 1 || shift == 2) && !shift_imm) shift_imm = 32; //0~31 for LSL, 1~32 for LSR, ASR and ROR, always 0 for RRX if (!(shift == 3 && !shift_imm)) sprintf(sstr, "%s #%u", Shifters[shift], shift_imm); u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); const char* s = BITS(c, 20, 1) ? "s" : ""; switch (op) { case 8: //TST case 9: //TEQ case 10: //CMP case 11: //CMN { size = sprintf(str, "%s%s r%u, r%u, %s", DataProcessing_arm[op], Conditions[cond], rn, rm, sstr); break; } case 13: //MOV case 15: //MVN { size = sprintf(str, "%s%s%s r%u, r%u, %s", DataProcessing_arm[op], s, Conditions[cond], rd, rm, sstr); break; } default: { size = sprintf(str, "%s%s%s r%u, r%u, r%u, %s", DataProcessing_arm[op], s, Conditions[cond], rd, rn, rm, sstr); } } } } break; } case 1: //Data processing and MSR immediate { if (cond == NV) break; //undefined u32 imm = ROR(BITS(c, 0, 8), 2 * BITS(c, 8, 4)); if (BITS(c, 12, 4) == 15 && !BITS(c, 20, 1)) //MSR immediate { char sreg = BITS(c, 22, 1) ? 's' : 'c'; //SPSR (1) or CPSR (0) size = sprintf(str, "msr%s %cpsr_%s, #0x%X", Conditions[cond], sreg, MSR_cxsf[BITS(c, 16, 4)], imm); } else //Data processing immediate { u8 op = BITS(c, 21, 4); u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); const char* s = BITS(c, 20, 1) ? "s" : ""; switch (op) { case 8: //TST case 9: //TEQ case 10: //CMP case 11: //CMN //always update the condition codes: {} , { size = sprintf(str, "%s%s r%u, #0x%X", DataProcessing_arm[op], Conditions[cond], rn, imm); break; } case 13: //MOV case 15: //MVN //only one source operand: {}{S} , { size = sprintf(str, "%s%s%s r%u, #0x%X", DataProcessing_arm[op], s, Conditions[cond], rd, imm); break; } default: //others: {}{S} , , { size = sprintf(str, "%s%s%s r%u, r%u, #0x%X", DataProcessing_arm[op], s, Conditions[cond], rd, rn, imm); } } } break; } case 2: //Load/store immediate offset { if (cond == NV) break; //undefined //bits 25, 24, 23 and 21 decide the addressing mode u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); u16 imm = BITS(c, 0, 12); //12 bits for LDR and LDRB (8 bits for LDRH and LDRSB) const char* sign = BITS(c, 23, 1) ? "+" : "-"; //sign of the immediate offset const char* ls = BITS(c, 20, 1) ? "ldr" : "str"; //load or store const char* b = BITS(c, 22, 1) ? "b" : ""; //byte or word if (BITS(c, 24, 1)) //offset or pre-indexed { const char* w = BITS(c, 21, 1) ? "!" : ""; //pre-indexed if 1 size = sprintf(str, "%s%s%s r%u, [r%u, #%s0x%X]%s", ls, b, Conditions[cond], rd, rn, sign, imm, w); } else //post-indexed { const char* w = BITS(c, 21, 1) ? "t" : ""; //user mode size = sprintf(str, "%s%s%s%s r%u, [r%u], #%s0x%X", ls, b, w, Conditions[cond], rd, rn, sign, imm); } break; } case 3: //Load/store register offset { if (cond == NV) break; //undefined if (BITS(c, 4, 1)) break; //undefined u8 rm = BITS(c, 0, 4); u8 shift = BITS(c, 5, 2); u16 shift_imm = BITS(c, 7, 5); u8 rd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); const char* sign = BITS(c, 23, 1) ? "" : "-"; //sign of the immediate offset, + implicit const char* ls = BITS(c, 20, 1) ? "ldr" : "str"; //load or store const char* b = BITS(c, 22, 1) ? "b" : ""; //byte or word char sstr[STRING_LENGTH] = "rrx"; //default, overwrite if incorrect if ((shift == 1 || shift == 2) && !shift_imm) shift_imm = 32; //0~31 for LSL, 1~32 for LSR, ASR and ROR, always 0 for RRX if (!(shift == 3 && !shift_imm)) sprintf(sstr, "%s #%u", Shifters[shift], shift_imm); //note: if rm==r15 or rn==r15 then UNPREDICTABLE //note: if rn==rm then UNPREDICTABLE if (BITS(c, 24, 1)) //offset or pre-indexed { const char* w = BITS(c, 21, 1) ? "!" : ""; //pre-indexed if 1 size = sprintf(str, "%s%s%s r%u, [r%u, %sr%u, %s]%s", ls, b, Conditions[cond], rd, rn, sign, rm, sstr, w); } else //post-indexed { const char* w = BITS(c, 21, 1) ? "t" : ""; //user mode size = sprintf(str, "%s%s%s%s r%u, [r%u], %sr%u, %s", ls, b, w, Conditions[cond], rd, rn, sign, rm, sstr); } break; } case 4: //Load/store multiple { if (cond == NV) break; //undefined char reglist[STRING_LENGTH] = { 0 }; FormatStringRegisterList_arm(reglist, BITS(c, 0, 16)); u8 rn = BITS(c, 16, 4); const char* w = BITS(c, 21, 1) ? "!" : ""; //W bit const char* s = BITS(c, 22, 1) ? "^" : ""; //S bit u8 am = BITS(c, 23, 2); //PU bits const char* op = (BITS(c, 20, 1)) ? "ldm" : "stm"; //LDM or STM size = sprintf(str, "%s%s%s r%u%s, {%s}%s", op, AddressingModes[am], Conditions[cond], rn, w, reglist, s); break; } case 5: //Branch instructions { if (cond == NV && av >= ARMv5TE) //BLX (1) { size = sprintf(str, "blx #0x%X", 8 + 4 * SIGNEX32_BITS(c, 0, 24) + 2 * BITS(c, 24, 1)); } else //B, BL { const char* l = (BITS(c, 24, 1)) ? "l" : ""; size = sprintf(str, "b%s%s #0x%X", l, Conditions[cond], 8 + 4 * SIGNEX32_BITS(c, 0, 24)); } break; } case 6: //Coprocessor load/store, Double register transfers { if (av < ARMv5TE) break; //if (cond == NV) break; //only unpredictable prior to ARMv5 if (BITS(c, 21, 4) == 2) //MCRR, MRRC { //note: if PC is specified for Rn or Rd, UNPREDICTABLE const char* op = BITS(c, 20, 1) ? "mrrc" : "mcrr"; size = sprintf(str, "%s%s p%u, #0x%X, r%u, r%u, c%u", op, Conditions[cond], BITS(c, 8, 4), BITS(c, 4, 4), BITS(c, 12, 4), BITS(c, 16, 4), BITS(c, 0, 4)); } else //LDC, STC { //todo: reformat the cond checks into only 1 if (cond == NV && av < ARMv5TE) break; const char* op = BITS(c, 20, 1) ? "ldc" : "stc"; const char* str_cond = (cond == NV) ? "2" : Conditions[cond]; //LDC2, STC2 const char* l = BITS(c, 22, 1) ? "l" : ""; //long char str_cond_long[8] = { 0 }; if (cond == NV) sprintf(str_cond_long, "%s%s", str_cond, l); else sprintf(str_cond_long, "%s%s", l, str_cond); u8 ofs_opt = BITS(c, 0, 8); u8 cp_num = BITS(c, 8, 4); u8 crd = BITS(c, 12, 4); u8 rn = BITS(c, 16, 4); const char* sign = BITS(c, 23, 1) ? "+" : "-"; switch ((2 * BITS(c, 24, 1)) | BITS(c, 21, 1)) //(p*2) | w { case 0: //p==0, w==0 //unindexed: [],