| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "vm/disassembler.h" |
| |
| #include "vm/globals.h" // Needed here to get TARGET_ARCH_MIPS. |
| #if defined(TARGET_ARCH_MIPS) |
| #include "platform/assert.h" |
| |
| namespace dart { |
| |
| class MIPSDecoder : public ValueObject { |
| public: |
| MIPSDecoder(char* buffer, size_t buffer_size) |
| : buffer_(buffer), |
| buffer_size_(buffer_size), |
| buffer_pos_(0) { |
| buffer_[buffer_pos_] = '\0'; |
| } |
| |
| ~MIPSDecoder() {} |
| |
| // Writes one disassembled instruction into 'buffer' (0-terminated). |
| // Returns true if the instruction was successfully decoded, false otherwise. |
| void InstructionDecode(Instr* instr); |
| |
| private: |
| // Bottleneck functions to print into the out_buffer. |
| void Print(const char* str); |
| |
| // Printing of common values. |
| void PrintRegister(Register reg); |
| void PrintFRegister(FRegister reg); |
| void PrintFormat(Instr* instr); |
| |
| int FormatRegister(Instr* instr, const char* format); |
| int FormatFRegister(Instr* instr, const char* format); |
| int FormatOption(Instr* instr, const char* format); |
| void Format(Instr* instr, const char* format); |
| void Unknown(Instr* instr); |
| |
| void DecodeSpecial(Instr* instr); |
| void DecodeSpecial2(Instr* instr); |
| void DecodeRegImm(Instr* instr); |
| void DecodeCop1(Instr* instr); |
| |
| // Convenience functions. |
| char* get_buffer() const { return buffer_; } |
| char* current_position_in_buffer() { return buffer_ + buffer_pos_; } |
| size_t remaining_size_in_buffer() { return buffer_size_ - buffer_pos_; } |
| |
| char* buffer_; // Decode instructions into this buffer. |
| size_t buffer_size_; // The size of the character buffer. |
| size_t buffer_pos_; // Current character position in buffer. |
| |
| DISALLOW_ALLOCATION(); |
| DISALLOW_COPY_AND_ASSIGN(MIPSDecoder); |
| }; |
| |
| |
| // Support for assertions in the MIPSDecoder formatting functions. |
| #define STRING_STARTS_WITH(string, compare_string) \ |
| (strncmp(string, compare_string, strlen(compare_string)) == 0) |
| |
| |
| // Append the str to the output buffer. |
| void MIPSDecoder::Print(const char* str) { |
| char cur = *str++; |
| while (cur != '\0' && (buffer_pos_ < (buffer_size_ - 1))) { |
| buffer_[buffer_pos_++] = cur; |
| cur = *str++; |
| } |
| buffer_[buffer_pos_] = '\0'; |
| } |
| |
| |
| static const char* reg_names[kNumberOfCpuRegisters] = { |
| "r0" , "r1" , "r2" , "r3" , "r4" , "r5" , "r6" , "r7" , |
| "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", |
| "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", |
| "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", |
| }; |
| |
| |
| static const char* freg_names[kNumberOfFRegisters] = { |
| "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , |
| "f8" , "f9" , "f10", "f11", "f12", "f13", "f14", "f15", |
| "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", |
| "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", |
| }; |
| |
| |
| void MIPSDecoder::PrintRegister(Register reg) { |
| ASSERT(0 <= reg); |
| ASSERT(reg < kNumberOfCpuRegisters); |
| Print(reg_names[reg]); |
| } |
| |
| |
| void MIPSDecoder::PrintFRegister(FRegister reg) { |
| ASSERT(0 <= reg); |
| ASSERT(reg < kNumberOfFRegisters); |
| Print(freg_names[reg]); |
| } |
| |
| |
| // Handle all register based formatting in these functions to reduce the |
| // complexity of FormatOption. |
| int MIPSDecoder::FormatRegister(Instr* instr, const char* format) { |
| ASSERT(format[0] == 'r'); |
| switch (format[1]) { |
| case 's': { // 'rs: Rs register |
| PrintRegister(instr->RsField()); |
| return 2; |
| } |
| case 't': { // 'rt: Rt register |
| PrintRegister(instr->RtField()); |
| return 2; |
| } |
| case 'd': { // 'rd: Rd register |
| PrintRegister(instr->RdField()); |
| return 2; |
| } |
| } |
| UNREACHABLE(); |
| return -1; |
| } |
| |
| |
| int MIPSDecoder::FormatFRegister(Instr* instr, const char* format) { |
| ASSERT(format[0] == 'f'); |
| switch (format[1]) { |
| case 's': { // 'fs: Fs register |
| PrintFRegister(instr->FsField()); |
| return 2; |
| } |
| case 't': { // 'ft: Ft register |
| PrintFRegister(instr->FtField()); |
| return 2; |
| } |
| case 'd': { // 'fd: Fd register |
| PrintFRegister(instr->FdField()); |
| return 2; |
| } |
| } |
| UNREACHABLE(); |
| return -1; |
| } |
| |
| |
| void MIPSDecoder::PrintFormat(Instr *instr) { |
| switch (instr->FormatField()) { |
| case FMT_S: { |
| Print("s"); |
| break; |
| } |
| case FMT_D: { |
| Print("d"); |
| break; |
| } |
| case FMT_W: { |
| Print("w"); |
| break; |
| } |
| case FMT_L: { |
| Print("l"); |
| break; |
| } |
| case FMT_PS: { |
| Print("ps"); |
| break; |
| } |
| default: { |
| Print("unknown"); |
| break; |
| } |
| } |
| } |
| |
| |
| // FormatOption takes a formatting string and interprets it based on |
| // the current instructions. The format string points to the first |
| // character of the option string (the option escape has already been |
| // consumed by the caller.) FormatOption returns the number of |
| // characters that were consumed from the formatting string. |
| int MIPSDecoder::FormatOption(Instr* instr, const char* format) { |
| switch (format[0]) { |
| case 'c': { |
| ASSERT(STRING_STARTS_WITH(format, "code")); |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| "%d", instr->BreakCodeField()); |
| return 4; |
| } |
| case 'h': { |
| ASSERT(STRING_STARTS_WITH(format, "hint")); |
| if (instr->SaField() == 0x10) { |
| // The high bit of the SA field is the only one that means something for |
| // JALR and JR. TODO(zra): Fill in the other cases for PREF if needed. |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| ".hb"); |
| } else if (instr->SaField() != 0) { |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| ".unknown"); |
| } |
| return 4; |
| } |
| case 'd': { |
| ASSERT(STRING_STARTS_WITH(format, "dest")); |
| int off = instr->SImmField() << 2; |
| uword destination = |
| reinterpret_cast<uword>(instr) + off + Instr::kInstrSize; |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| "%#"Px"", |
| destination); |
| return 4; |
| } |
| case 'i': { |
| ASSERT(STRING_STARTS_WITH(format, "imm")); |
| if (format[3] == 'u') { |
| int32_t imm = instr->UImmField(); |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| "0x%x", |
| imm); |
| } else { |
| ASSERT(STRING_STARTS_WITH(format, "imms")); |
| int32_t imm = instr->SImmField(); |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| "%d", |
| imm); |
| } |
| return 4; |
| } |
| case 'r': { |
| return FormatRegister(instr, format); |
| } |
| case 'f': { |
| if (format[1] == 'm') { |
| ASSERT(STRING_STARTS_WITH(format, "fmt")); |
| PrintFormat(instr); |
| return 3; |
| } else { |
| return FormatFRegister(instr, format); |
| } |
| } |
| case 's': { |
| ASSERT(STRING_STARTS_WITH(format, "sa")); |
| buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
| remaining_size_in_buffer(), |
| "%d", |
| instr->SaField()); |
| return 2; |
| } |
| default: { |
| UNREACHABLE(); |
| } |
| } |
| UNREACHABLE(); |
| return -1; |
| } |
| |
| |
| // Format takes a formatting string for a whole instruction and prints it into |
| // the output buffer. All escaped options are handed to FormatOption to be |
| // parsed further. |
| void MIPSDecoder::Format(Instr* instr, const char* format) { |
| char cur = *format++; |
| while ((cur != 0) && (buffer_pos_ < (buffer_size_ - 1))) { |
| if (cur == '\'') { // Single quote is used as the formatting escape. |
| format += FormatOption(instr, format); |
| } else { |
| buffer_[buffer_pos_++] = cur; |
| } |
| cur = *format++; |
| } |
| buffer_[buffer_pos_] = '\0'; |
| } |
| |
| |
| // For currently unimplemented decodings the disassembler calls Unknown(instr) |
| // which will just print "unknown" of the instruction bits. |
| void MIPSDecoder::Unknown(Instr* instr) { |
| Format(instr, "unknown"); |
| } |
| |
| |
| void MIPSDecoder::DecodeSpecial(Instr* instr) { |
| ASSERT(instr->OpcodeField() == SPECIAL); |
| switch (instr->FunctionField()) { |
| case ADDU: { |
| Format(instr, "addu 'rd, 'rs, 'rt"); |
| break; |
| } |
| case AND: { |
| Format(instr, "and 'rd, 'rs, 'rt"); |
| break; |
| } |
| case BREAK: { |
| Format(instr, "break 'code"); |
| break; |
| } |
| case DIV: { |
| Format(instr, "div 'rs, 'rt"); |
| break; |
| } |
| case DIVU: { |
| Format(instr, "divu 'rs, 'rt"); |
| break; |
| } |
| case JALR: { |
| Format(instr, "jalr'hint 'rd, 'rs"); |
| break; |
| } |
| case JR: { |
| Format(instr, "jr'hint 'rs"); |
| break; |
| } |
| case MFHI: { |
| Format(instr, "mfhi 'rd"); |
| break; |
| } |
| case MFLO: { |
| Format(instr, "mflo 'rd"); |
| break; |
| } |
| case MOVN: { |
| Format(instr, "movn 'rd, 'rs, 'rt"); |
| break; |
| } |
| case MOVZ: { |
| Format(instr, "movz 'rd, 'rs, 'rt"); |
| break; |
| } |
| case MULT: { |
| Format(instr, "mult 'rs, 'rt"); |
| break; |
| } |
| case MULTU: { |
| Format(instr, "multu 'rs, 'rt"); |
| break; |
| } |
| case NOR: { |
| Format(instr, "nor 'rd, 'rs, 'rt"); |
| break; |
| } |
| case OR: { |
| if (instr->RsField() == 0 && instr->RtField() == 0) { |
| Format(instr, "mov 'rd, 0"); |
| } else if (instr->RsField() == R0) { |
| Format(instr, "mov 'rd, 'rt"); |
| } else if (instr->RtField() == R0) { |
| Format(instr, "mov 'rd, 'rs"); |
| } else { |
| Format(instr, "or 'rd, 'rs, 'rt"); |
| } |
| break; |
| } |
| case SLL: { |
| if ((instr->RdField() == R0) && |
| (instr->RtField() == R0) && |
| (instr->SaField() == 0)) { |
| Format(instr, "nop"); |
| } else { |
| Format(instr, "sll 'rd, 'rt, 'sa"); |
| } |
| break; |
| } |
| case SLLV: { |
| Format(instr, "sllv 'rd, 'rt, 'rs"); |
| break; |
| } |
| case SLT: { |
| Format(instr, "slt 'rd, 'rs, 'rt"); |
| break; |
| } |
| case SLTU: { |
| Format(instr, "sltu 'rd, 'rs, 'rt"); |
| break; |
| } |
| case SRA: { |
| if (instr->RsField() == 0) { |
| Format(instr, "sra 'rd, 'rt, 'sa"); |
| } else { |
| Unknown(instr); |
| } |
| break; |
| } |
| case SRAV: { |
| Format(instr, "srav 'rd, 'rt, 'rs"); |
| break; |
| } |
| case SRL: { |
| if (instr->RsField() == 0) { |
| Format(instr, "srl 'rd, 'rt, 'sa"); |
| } else { |
| Unknown(instr); |
| } |
| break; |
| } |
| case SRLV: { |
| if (instr->SaField() == 0) { |
| Format(instr, "srlv 'rd, 'rt, 'rs"); |
| } else { |
| Unknown(instr); |
| } |
| break; |
| } |
| case SUBU: { |
| Format(instr, "subu 'rd, 'rs, 'rt"); |
| break; |
| } |
| case XOR: { |
| Format(instr, "xor 'rd, 'rs, 'rt"); |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } |
| |
| |
| void MIPSDecoder::DecodeSpecial2(Instr* instr) { |
| ASSERT(instr->OpcodeField() == SPECIAL2); |
| switch (instr->FunctionField()) { |
| case CLO: { |
| Format(instr, "clo 'rd, 'rs"); |
| break; |
| } |
| case CLZ: { |
| Format(instr, "clz 'rd, 'rs"); |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } |
| |
| |
| void MIPSDecoder::DecodeRegImm(Instr* instr) { |
| ASSERT(instr->OpcodeField() == REGIMM); |
| switch (instr->RegImmFnField()) { |
| case BGEZ: { |
| Format(instr, "bgez 'rs, 'dest"); |
| break; |
| } |
| case BGEZAL: { |
| Format(instr, "bgezal 'rs, 'dest"); |
| break; |
| } |
| case BGEZL: { |
| Format(instr, "bgezl 'rs, 'dest"); |
| break; |
| } |
| case BLTZ: { |
| Format(instr, "bltz 'rs, 'dest"); |
| break; |
| } |
| case BLTZL: { |
| Format(instr, "bltzl 'rs, 'dest"); |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } |
| |
| void MIPSDecoder::DecodeCop1(Instr* instr) { |
| ASSERT(instr->OpcodeField() == COP1); |
| if (instr->HasFormat()) { |
| // If the rs field is a valid format, then the function field identifies |
| // the instruction. |
| switch (instr->Cop1FunctionField()) { |
| case COP1_ADD: { |
| Format(instr, "add.'fmt 'fd, 'fs, 'ft"); |
| break; |
| } |
| case COP1_MOV: { |
| Format(instr, "mov.'fmt 'fd, 'fs"); |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } else { |
| // If the rs field isn't a valid format, then it must be a sub-opcode. |
| switch (instr->Cop1SubField()) { |
| case COP1_MF: { |
| if (instr->Bits(0, 11) != 0) { |
| Unknown(instr); |
| } else { |
| Format(instr, "mfc1 'rt, 'fs"); |
| } |
| break; |
| } |
| case COP1_MT: { |
| if (instr->Bits(0, 11) != 0) { |
| Unknown(instr); |
| } else { |
| Format(instr, "mtc1 'rt, 'fs"); |
| } |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } |
| } |
| |
| void MIPSDecoder::InstructionDecode(Instr* instr) { |
| switch (instr->OpcodeField()) { |
| case SPECIAL: { |
| DecodeSpecial(instr); |
| break; |
| } |
| case SPECIAL2: { |
| DecodeSpecial2(instr); |
| break; |
| } |
| case REGIMM: { |
| DecodeRegImm(instr); |
| break; |
| } |
| case COP1: { |
| DecodeCop1(instr); |
| break; |
| } |
| case ADDIU: { |
| Format(instr, "addiu 'rt, 'rs, 'imms"); |
| break; |
| } |
| case ANDI: { |
| Format(instr, "andi 'rt, 'rs, 'immu"); |
| break; |
| } |
| case BEQ: { |
| Format(instr, "beq 'rs, 'rt, 'dest"); |
| break; |
| } |
| case BEQL: { |
| Format(instr, "beql 'rs, 'rt, 'dest"); |
| break; |
| } |
| case BGTZ: { |
| Format(instr, "bgtz 'rs, 'dest"); |
| break; |
| } |
| case BGTZL: { |
| Format(instr, "bgtzl 'rs, 'dest"); |
| break; |
| } |
| case BLEZ: { |
| Format(instr, "blez 'rs, 'dest"); |
| break; |
| } |
| case BLEZL: { |
| Format(instr, "blezl 'rs, 'dest"); |
| break; |
| } |
| case BNE: { |
| Format(instr, "bne 'rs, 'rt, 'dest"); |
| break; |
| } |
| case BNEL: { |
| Format(instr, "bnel 'rs, 'rt, 'dest"); |
| break; |
| } |
| case LB: { |
| Format(instr, "lb 'rt, 'imms('rs)"); |
| break; |
| } |
| case LBU: { |
| Format(instr, "lbu 'rt, 'imms('rs)"); |
| break; |
| } |
| case LDC1: { |
| Format(instr, "ldc1 'ft, 'imms('rs)"); |
| break; |
| } |
| case LH: { |
| Format(instr, "lh 'rt, 'imms('rs)"); |
| break; |
| } |
| case LHU: { |
| Format(instr, "lhu 'rt, 'imms('rs)"); |
| break; |
| } |
| case LUI: { |
| Format(instr, "lui 'rt, 'immu"); |
| break; |
| } |
| case LW: { |
| Format(instr, "lw 'rt, 'imms('rs)"); |
| break; |
| } |
| case LWC1: { |
| Format(instr, "lwc1 'ft, 'imms('rs)"); |
| break; |
| } |
| case ORI: { |
| Format(instr, "ori 'rt, 'rs, 'immu"); |
| break; |
| } |
| case SB: { |
| Format(instr, "sb 'rt, 'imms('rs)"); |
| break; |
| } |
| case SH: { |
| Format(instr, "sh 'rt, 'imms('rs)"); |
| break; |
| } |
| case SDC1: { |
| Format(instr, "sdc1 'ft, 'imms('rs)"); |
| break; |
| } |
| case SW: { |
| Format(instr, "sw 'rt, 'imms('rs)"); |
| break; |
| } |
| case SWC1: { |
| Format(instr, "swc1 'ft, 'imms('rs)"); |
| break; |
| } |
| case XORI: { |
| Format(instr, "xori 'rt, 'rs, 'immu"); |
| break; |
| } |
| default: { |
| Unknown(instr); |
| break; |
| } |
| } |
| } |
| |
| |
| void Disassembler::DecodeInstruction(char* hex_buffer, intptr_t hex_size, |
| char* human_buffer, intptr_t human_size, |
| int* out_instr_len, uword pc) { |
| MIPSDecoder decoder(human_buffer, human_size); |
| Instr* instr = Instr::At(pc); |
| decoder.InstructionDecode(instr); |
| OS::SNPrint(hex_buffer, hex_size, "%08x", instr->InstructionBits()); |
| if (out_instr_len) { |
| *out_instr_len = Instr::kInstrSize; |
| } |
| } |
| |
| |
| void Disassembler::Disassemble(uword start, |
| uword end, |
| DisassemblyFormatter* formatter, |
| const Code::Comments& comments) { |
| ASSERT(formatter != NULL); |
| char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form. |
| char human_buffer[kUserReadableBufferSize]; // Human-readable instruction. |
| uword pc = start; |
| intptr_t comment_finger = 0; |
| while (pc < end) { |
| const intptr_t offset = pc - start; |
| while (comment_finger < comments.Length() && |
| comments.PCOffsetAt(comment_finger) <= offset) { |
| formatter->Print( |
| " ;; %s\n", |
| String::Handle(comments.CommentAt(comment_finger)).ToCString()); |
| comment_finger++; |
| } |
| int instruction_length; |
| DecodeInstruction(hex_buffer, sizeof(hex_buffer), |
| human_buffer, sizeof(human_buffer), |
| &instruction_length, pc); |
| |
| formatter->ConsumeInstruction(hex_buffer, |
| sizeof(hex_buffer), |
| human_buffer, |
| sizeof(human_buffer), |
| pc); |
| pc += instruction_length; |
| } |
| |
| return; |
| } |
| |
| } // namespace dart |
| |
| #endif // defined TARGET_ARCH_MIPS |