/* This file is included into vbu.c and cannot be compiled on its own. */ #ifdef VBUAPI /********************************* Constants *********************************/ /* Operand types */ #define DASM_BCOND 0 #define DASM_DISP9 1 #define DASM_DISP26 2 #define DASM_IMM5S 3 #define DASM_IMM5U 4 #define DASM_IMM16S 5 #define DASM_IMM16U 6 #define DASM_JMP 7 #define DASM_MEMORY 8 #define DASM_REG1 9 #define DASM_REG2 10 #define DASM_SETF 11 #define DASM_SYSTEM 12 /******************************** Lookup Data ********************************/ /* Instruction descriptor */ typedef struct { char mnemonic[8]; /* Mnemonic, uppercase */ uint8_t operandsLength; /* Number of operands */ uint8_t operands[3]; /* Operand types */ } OpDef; /* Instruction descriptors */ static const OpDef OPDEFS[] = { { "MOV" , 2, { DASM_REG1, DASM_REG2 } }, /* 000000 */ { "ADD" , 2, { DASM_REG1, DASM_REG2 } }, { "SUB" , 2, { DASM_REG1, DASM_REG2 } }, { "CMP" , 2, { DASM_REG1, DASM_REG2 } }, { "SHL" , 2, { DASM_REG1, DASM_REG2 } }, { "SHR" , 2, { DASM_REG1, DASM_REG2 } }, { "JMP" , 1, { DASM_JMP } }, { "SAR" , 2, { DASM_REG1, DASM_REG2 } }, { "MUL" , 2, { DASM_REG1, DASM_REG2 } }, { "DIV" , 2, { DASM_REG1, DASM_REG2 } }, { "MULU" , 2, { DASM_REG1, DASM_REG2 } }, { "DIVU" , 2, { DASM_REG1, DASM_REG2 } }, { "OR" , 2, { DASM_REG1, DASM_REG2 } }, { "AND" , 2, { DASM_REG1, DASM_REG2 } }, { "XOR" , 2, { DASM_REG1, DASM_REG2 } }, { "NOT" , 1, { DASM_REG1 } }, { "MOV" , 2, { DASM_IMM5S, DASM_REG2 } }, /* 010000 */ { "ADD" , 2, { DASM_IMM5S, DASM_REG2 } }, { "" , 0, { 0 } }, /* SETF */ { "CMP" , 2, { DASM_IMM5S, DASM_REG2 } }, { "SHL" , 2, { DASM_IMM5U, DASM_REG2 } }, { "SHR" , 2, { DASM_IMM5U, DASM_REG2 } }, { "CLI" , 0, { 0 } }, { "SAR" , 2, { DASM_IMM5U, DASM_REG2 } }, { "TRAP" , 1, { DASM_IMM5U } }, { "RETI" , 0, { 0 } }, { "HALT" , 0, { 0 } }, { "---" , 0, { 0 } }, { "LDSR" , 2, { DASM_REG2, DASM_SYSTEM } }, { "STSR" , 2, { DASM_SYSTEM, DASM_REG2 } }, { "SEI" , 0, { 0 } }, { "" , 0, { 0 } }, /* Bit string */ { "" , 0, { 0 } }, /* BCOND */ /* 100000 */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "" , 0, { 0 } }, /* BCOND */ { "MOVEA", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } }, { "ADDI" , 3, { DASM_IMM16S, DASM_REG1, DASM_REG2 } }, { "JR" , 1, { DASM_DISP26 } }, { "JAL" , 1, { DASM_DISP26 } }, { "ORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } }, { "ANDI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } }, { "XORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } }, { "MOVHI", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } }, { "LD.B" , 2, { DASM_MEMORY, DASM_REG2 } }, /* 110000 */ { "LD.H" , 2, { DASM_MEMORY, DASM_REG2 } }, { "---" , 0, { 0 } }, { "LD.W" , 2, { DASM_MEMORY, DASM_REG2 } }, { "ST.B" , 2, { DASM_REG2, DASM_MEMORY } }, { "ST.H" , 2, { DASM_REG2, DASM_MEMORY } }, { "---" , 0, { 0 } }, { "ST.W" , 2, { DASM_REG2, DASM_MEMORY } }, { "IN.B" , 2, { DASM_MEMORY, DASM_REG2 } }, { "IN.H" , 2, { DASM_MEMORY, DASM_REG2 } }, { "CAXI" , 0, { DASM_MEMORY, DASM_REG2 } }, { "IN.W" , 2, { DASM_MEMORY, DASM_REG2 } }, { "OUT.B", 2, { DASM_REG2, DASM_MEMORY } }, { "OUT.H", 2, { DASM_REG2, DASM_MEMORY } }, { "" , 0, { 0 } }, /* Floating-point/Nintendo */ { "OUT.W", 2, { DASM_REG2, DASM_MEMORY } } }; /* Bit string instruction descriptors */ static const OpDef OPDEFS_BITSTRING[] = { { "SCH0BSU", 0, { 0 } }, /* 00000 */ { "SCH0BSD", 0, { 0 } }, { "SCH1BSU", 0, { 0 } }, { "SCH1BSD", 0, { 0 } }, { "---" , 0, { 0 } }, { "---" , 0, { 0 } }, { "---" , 0, { 0 } }, { "---" , 0, { 0 } }, { "ORBSU" , 0, { 0 } }, { "ANDBSU" , 0, { 0 } }, { "XORBSU" , 0, { 0 } }, { "MOVBSU" , 0, { 0 } }, { "ORNBSU" , 0, { 0 } }, { "ANDNBSU", 0, { 0 } }, { "XORNBSU", 0, { 0 } }, { "NOTBSU" , 0, { 0 } } /* +16 invalid opcodes */ }; /* Floating-point/Nintendo instruction descriptors */ static const OpDef OPDEFS_FLOATENDO[] = { { "CMPF.S" , 2, { DASM_REG1, DASM_REG2 } }, { "---" , 0, { 0 } }, { "CVT.WS" , 2, { DASM_REG1, DASM_REG2 } }, { "CVT.SW" , 2, { DASM_REG1, DASM_REG2 } }, { "ADDF.S" , 2, { DASM_REG1, DASM_REG2 } }, { "SUBF.S" , 2, { DASM_REG1, DASM_REG2 } }, { "MULF.S" , 2, { DASM_REG1, DASM_REG2 } }, { "DIVF.S" , 2, { DASM_REG1, DASM_REG2 } }, { "XB" , 1, { DASM_REG2 } }, { "XH" , 1, { DASM_REG2 } }, { "REV" , 2, { DASM_REG1, DASM_REG2 } }, { "TRNC.SW", 2, { DASM_REG1, DASM_REG2 } }, { "MPYHW" , 2, { DASM_REG1, DASM_REG2 } }, /* +51 invalid opcodes */ }; /* Invalid instruction descriptors */ static const OpDef OPDEF_ILLEGAL = { "---", 0, { 0 } }; /***************************** Module Functions ******************************/ /* Select the display text for a condition */ static char* dasmCondition(VBU_DasmConfig *config, int cond) { switch (cond) { case 0: return "V" ; case 1: return config->conditionCL == VBU_C ? "C" : "L"; case 2: return config->conditionEZ == VBU_E ? "E" : "Z"; case 3: return "NH"; case 4: return "N" ; case 5: return "T" ; case 6: return "LT"; case 7: return "LE"; case 8: return "NV"; case 9: return config->conditionCL == VBU_C ? "NC" : "NL"; case 10: return config->conditionEZ == VBU_E ? "NE" : "NZ"; case 11: return "H" ; case 12: return "P" ; case 13: return "F" ; case 14: return "GE"; } return "GT"; } /* Format a number as hexadecimal */ static void dasmToHex(char *dest, VBU_DasmConfig *config, int minDigits, int32_t value) { char format[9]; char *prefix = ""; char *suffix = ""; switch (config->hexNotation) { case VBU_0X : prefix = "0x"; break; case VBU_DOLLAR: prefix = "$" ; break; case VBU_H : suffix = "h" ; break; } sprintf(format, "%%s%%0%d%s%%s", minDigits, config->hexCase == VBU_LOWER ? "x" : "X"); sprintf(dest, format, prefix, value, suffix); if (config->hexNotation != VBU_H || *dest <= '9') return; *dest++ = '0'; sprintf(dest, format, prefix, value, suffix); } /* Convert a string to lowercase */ static void dasmToLower(char *str) { for (; *str; str++) { if (*str >= 'A' && *str <= 'Z') *str += 'a' - 'A'; } } /* Format a program register */ static void dasmProgram(char *dest, VBU_DasmConfig *config, int id) { char *text = NULL; if (config->programNotation != VBU_NUMBERS) { switch (id) { case 2: text = "HP"; break; case 3: text = "SP"; break; case 4: text = "GP"; break; case 5: text = "TP"; break; case 31: text = "LP"; break; } } if (text == NULL) sprintf(dest, "R%d", id); else strcpy(dest, text); if (config->programCase == VBU_LOWER) dasmToLower(dest); } /**************************** Operand Formatters *****************************/ /* Format the condition operand of a split BCOND instruction */ static void dasmOpBCOND(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int cond = line->code[1] >> 2 & 15; if (config->conditionNotation == VBU_NUMBERS) sprintf(dest, "%d", cond); else { strcpy(dest, dasmCondition(config, cond)); if (config->conditionCase == VBU_LOWER) dasmToLower(dest); } } /* Format a 9-bit displacement operand */ static void dasmOpDisp9(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int32_t disp = SignExtend((int32_t) line->code[1] << 8 | line->code[0], 9); sprintf(dest, config->hexCase == VBU_LOWER ? "%08x" : "%08X", line->address + disp); } /* Format a 26-bit displacement operand */ static void dasmOpDisp26(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int32_t disp = SignExtend( (int32_t) line->code[1] << 24 | (int32_t) line->code[0] << 16 | (int32_t) line->code[3] << 8 | line->code[2] , 26); sprintf(dest, config->hexCase == VBU_LOWER ? "%08x" : "%08X", line->address + disp); } /* Format a 5-bit sign-extended immediate operand */ static void dasmOpImm5S(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){ if (config->immediateNotation == VBU_NUMBER) *dest++ = '#'; sprintf(dest, "%d", SignExtend(line->code[0], 5)); } /* Format a 5-bit zero-filled immediate operand */ static void dasmOpImm5U(char *dest, VBU_DasmConfig*config, VBU_DasmLine *line){ if (config->immediateNotation == VBU_NUMBER) *dest++ = '#'; sprintf(dest, "%d", line->code[0] & 31); } /* Format a 16-bit sign-extended immediate operand */ static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]); if (config->immediateNotation == VBU_NUMBER) *dest++ = '#'; if (imm >= -256 && imm <= 256) { sprintf(dest, "%d", imm); return; } if (imm < 0) { *dest++ = '-'; imm = -imm; } dasmToHex(dest, config, 0, imm); } /* Format a 16-bit zero-filled immediate operand */ static void dasmOpImm16U(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { uint16_t imm = (uint16_t) line->code[3] << 8 | line->code[2]; if (config->immediateNotation == VBU_NUMBER) *dest++ = '#'; dasmToHex(dest, config, 4, imm); } /* Format the operand of a JMP instruction */ static void dasmOpJMP(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { *dest++ = '['; dasmProgram(dest, config, line->code[0] & 31); strcat(dest, "]"); } /* Format a memory operand */ static void dasmOpMemory(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int reg1 = line->code[0] & 31; int32_t disp = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]); /* Displacement of zero */ if (disp == 0) { *dest++ = '['; dasmProgram(dest, config, reg1); strcat(dest, "]"); return; } /* Inside */ if (config->memoryNotation == VBU_INSIDE) { *dest++ = '['; dasmProgram(dest, config, reg1); dest += strlen(dest); if (disp < 0) { strcpy(dest, " - "); disp = -disp; } else strcpy(dest, " + "); dest += 3; if (disp <= 256) sprintf(dest, "%d", disp); else dasmToHex(dest, config, 0, disp); strcat(dest, "]"); } /* Outside */ else { if (disp < 0) { *dest++ = '-'; disp = -disp; } if (disp <= 256) sprintf(dest, "%d", disp); else dasmToHex(dest, config, 0, disp); dest += strlen(dest); *dest++ = '['; dasmProgram(dest, config, reg1); strcat(dest, "]"); } } /* Format a source register operand */ static void dasmOpReg1(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { dasmProgram(dest, config, line->code[0] & 31); } /* Format a destination register operand */ static void dasmOpReg2(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { dasmProgram(dest, config, ((uint16_t) line->code[1] << 8 | line->code[0]) >> 5 & 31); } /* Format the condition operand of a split SETF instruction */ static void dasmOpSETF(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int cond = line->code[0] & 15; if (config->conditionNotation == VBU_NUMBERS) sprintf(dest, "%d", cond); else { strcpy(dest, dasmCondition(config, cond)); if (config->conditionCase == VBU_LOWER) dasmToLower(dest); } } /* Format a system register operand */ static void dasmOpSystem(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) { int id = line->code[0] & 31; char *text = NULL; if (config->systemNotation != VBU_NUMBERS) { switch (id) { case 0: text = "EIPC" ; break; case 1: text = "EIPSW"; break; case 2: text = "FEPC" ; break; case 3: text = "FEPSW"; break; case 4: text = "ECR" ; break; case 5: text = "PSW" ; break; case 6: text = "PIR" ; break; case 7: text = "TKCW" ; break; case 24: text = "CHCW" ; break; case 25: text = "ADTRE"; break; } } if (text == NULL) { sprintf(dest, "%d", id); return; } strcpy(dest, text); if (config->systemCase == VBU_LOWER) dasmToLower(dest); } /***************************** Module Functions ******************************/ /* Prepare an OpDef for a BCOND instruction */ static OpDef* dasmBCOND(VBU_DasmConfig *config, VBU_DasmLine *line, OpDef *opdef) { int cond; /* Condition code */ char *text; /* Mnemonic text */ /* Split notation */ if (config->bcondNotation == VBU_SPLIT) { strcpy(opdef->mnemonic, "BCOND"); opdef->operandsLength = 2; opdef->operands[0] = DASM_BCOND; opdef->operands[1] = DASM_DISP9; return opdef; } /* Joined notation */ cond = line->code[1] >> 1 & 15; switch (cond) { case 0: text = "BV" ; break; case 1: text = config->conditionCL == VBU_C ? "BC" : "BL"; break; case 2: text = config->conditionEZ == VBU_E ? "BE" : "BZ"; break; case 3: text = "BNH"; break; case 4: text = "BN" ; break; case 5: text = "BR" ; break; case 6: text = "BLT"; break; case 7: text = "BLE"; break; case 8: text = "BNV"; break; case 9: text = config->conditionCL == VBU_C ? "BNC" : "BNL"; break; case 10: text = config->conditionEZ == VBU_E ? "BNE" : "BNZ"; break; case 11: text = "BH" ; break; case 12: text = "BP" ; break; case 13: text = "NOP"; break; case 14: text = "BGE"; break; default: text = "BGT"; } strcpy(opdef->mnemonic, text); opdef->operandsLength = cond != 13; opdef->operands[0] = DASM_DISP9; return opdef; } /* Ensure an output buffer matches the required size */ static VBU_DasmLine* dasmGrow( VBU_DasmLine **lines, size_t *size, size_t target, unsigned index) { if (*size < target) { while (*size < target) *size *= 2; *lines = VBU_REALLOC(*lines, *size); } return *lines == NULL ? NULL : &(*lines)[index]; } /* Format an operand */ static void dasmOperand(char *dest, VBU_DasmConfig *config, VBU_DasmLine *line, uint8_t type) { switch (type) { case DASM_BCOND : dasmOpBCOND (dest, config, line); break; case DASM_DISP9 : dasmOpDisp9 (dest, config, line); break; case DASM_DISP26: dasmOpDisp26(dest, config, line); break; case DASM_IMM5S : dasmOpImm5S (dest, config, line); break; case DASM_IMM5U : dasmOpImm5U (dest, config, line); break; case DASM_IMM16S: dasmOpImm16S(dest, config, line); break; case DASM_IMM16U: dasmOpImm16U(dest, config, line); break; case DASM_JMP : dasmOpJMP (dest, config, line); break; case DASM_MEMORY: dasmOpMemory(dest, config, line); break; case DASM_REG1 : dasmOpReg1 (dest, config, line); break; case DASM_REG2 : dasmOpReg2 (dest, config, line); break; case DASM_SETF : dasmOpSETF (dest, config, line); break; case DASM_SYSTEM: dasmOpSystem(dest, config, line); break; } } /* Prepare an OpDef for a SETF instruction */ static OpDef* dasmSETF(VBU_DasmConfig *config, VBU_DasmLine *line, OpDef *opdef) { /* Split notation */ if (config->bcondNotation == VBU_SPLIT) { strcpy(opdef->mnemonic, "SETF"); opdef->operandsLength = 2; opdef->operands[0] = DASM_SETF; opdef->operands[1] = DASM_REG2; return opdef; } /* Joined notation */ sprintf(opdef->mnemonic, "SETF%s", dasmCondition(config, line->code[1] >> 1 & 15)); opdef->operandsLength = 1; opdef->operands[0] = DASM_REG2; return opdef; } /* Disassemble one instruction */ static int dasmLine(VB *sim, uint32_t *address, uint32_t pc, VBU_DasmConfig *config, VBU_DasmLine **lines, size_t *size, size_t *offset, unsigned index) { char *dest; /* Text output pointer */ char *format; /* Hexadecimal format string */ VBU_DasmLine *line; /* Output line */ size_t more; /* Number of bytes to output */ OpDef *opdef; /* Instruction descriptor */ OpDef special; /* Special-case descriptor */ char tAddress[9]; /* Address display text */ char tCode[4][3]; /* Code display text */ char tMnemonic[8]; /* Mnemonic display text */ char tOperands[3][15]; /* Operands display text */ uint32_t x; /* Scratch and iterator */ /* Process non-text members */ line = &(*lines)[index]; line->address = *address; line->codeLength = vbuCodeSize(sim, *address); line->isPC = *address == pc; for (x = 0; x < line->codeLength; x++) line->code[x] = vbRead(sim, *address + x, VB_U8); *address += pc != *address && pc - *address < line->codeLength ? pc - *address : line->codeLength; /* Do not process text members */ if (config == NULL) { line->text.address = NULL; line->text.mnemonic = NULL; line->text.operandsLength = 0; for (x = 0; x < line->codeLength; x++) line->text.code[x] = NULL; return 0; } /* Select instruction descriptor */ x = line->code[1] >> 2 & 63; switch (x) { case 0x12: /* SETF */ opdef = dasmSETF(config, line, &special); break; case 0x1F: /* Bit string */ x = line->code[0] & 31; opdef = (OpDef *) (x < 16 ? &OPDEFS_BITSTRING[x] : &OPDEF_ILLEGAL); break; case 0x20: case 0x21: case 0x22: case 0x23: /* BCOND */ case 0x24: case 0x25: case 0x26: case 0x27: opdef = dasmBCOND(config, line, &special); break; case 0x3E: /* Floating-point/Nintendo */ x = line->code[3] >> 2 & 63; opdef = (OpDef *) (x < 13 ? &OPDEFS_FLOATENDO[x] : &OPDEF_ILLEGAL); break; default: opdef = (OpDef *) &OPDEFS[x]; /* Other */ } /* Address */ format = config->hexCase == VBU_LOWER ? "%08x" : "%08X"; sprintf(tAddress, format, line->address); more = 9; /* Code */ format = config->hexCase == VBU_LOWER ? "%02x" : "%02X"; for (x = 0; x < line->codeLength; x++) { sprintf(tCode[x], format, (int) line->code[x]); more += 3; } /* Mnemonic */ strcpy(tMnemonic, opdef->mnemonic); if (config->mnemonicCase == VBU_LOWER) dasmToLower(tMnemonic); more += strlen(tMnemonic) + 1; /* Operands */ line->text.operandsLength = opdef->operandsLength; for (x = 0; x < opdef->operandsLength; x++) { dasmOperand(tOperands[x], config, line, opdef->operands[x]); more += strlen(tOperands[x]) + 1; } /* Grow the text buffer */ line = dasmGrow(lines, size, *offset + more, index); if (line == NULL) return 1; /* Working variables */ dest = &((char *) *lines)[*offset]; *offset += more; /* Address */ strcpy(dest, tAddress); line->text.address = dest; dest += 9; /* Code */ for (x = 0; x < line->codeLength; x++) { strcpy(dest, tCode[x]); line->text.code[x] = dest; dest += 3; } /* Mnemonic */ strcpy(dest, tMnemonic); line->text.mnemonic = dest; dest += strlen(dest) + 1; /* Operands */ for (x = 0; x < opdef->operandsLength; x++) { strcpy(dest, tOperands[ config->operandOrder == VBU_DEST_FIRST ? opdef->operandsLength - 1 - x : x ]); line->text.operands[x] = dest; dest += strlen(dest) + 1; } return 0; } /***************************** Library Functions *****************************/ /* Disassemble from a simulation */ static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address, VBU_DasmConfig *config, unsigned length, int line) { uint32_t addr; /* Working address */ uint32_t *circle = NULL; /* Circular address buffer */ VBU_DasmLine *lines = NULL; /* Output line buffer */ size_t offset; /* Offset into lines buffer */ uint32_t pc; /* Program counter */ size_t size; /* Number of bytes in lines */ unsigned x; /* Iterator */ /* Nothing to disassemble */ if (length == 0) return NULL; /* Establish a circular buffer */ if (line > 0) { circle = VBU_REALLOC(NULL, (size_t) (line + 1) * sizeof (uint32_t)); if (circle == NULL) goto catch; } /* Begin decoding from at least 10 lines before first/reference */ addr = (address & 0xFFFFFFFE) - (int32_t) (line <= 0 ? 10 : line + 10) * 4; /* Locate the address of the line containing the reference address */ pc = vbGetProgramCounter(sim); for (x = 0;;) { /* Track the address in the circular buffer */ if (line > 0) { circle[x] = addr; if (++x > (size_t) line) x = 0; } /* Check if the instruction contains the reference address */ size = vbuCodeSize(sim, addr); if (address - addr < size) break; addr += addr != pc && pc - addr < size ? pc - addr : size; } /* Address of first line is in the circular buffer */ if (line > 0) { addr = circle[x]; circle = VBU_REALLOC(circle, 0); } /* Keep decoding until the first line of output */ else for (; line < 0; line++) addr += vbuCodeSize(sim, addr); /* Working variables */ size = length * sizeof (VBU_DasmLine); lines = VBU_REALLOC(NULL, size); offset = size; if (lines == NULL) goto catch; /* Process output lines */ for (x = 0; x < length; x++) { if (dasmLine(sim, &addr, pc, config, &lines, &size, &offset, x)) goto catch; } return VBU_REALLOC(lines, offset); /* Exception handler */ catch: circle = VBU_REALLOC(circle, 0); lines = VBU_REALLOC(lines , 0); return NULL; } #endif /* VBUAPI */