Index: runtime/vm/disassembler_arm64.cc |
=================================================================== |
--- runtime/vm/disassembler_arm64.cc (revision 0) |
+++ runtime/vm/disassembler_arm64.cc (revision 0) |
@@ -0,0 +1,486 @@ |
+// Copyright (c) 2014, 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_ARM64. |
+#if defined(TARGET_ARCH_ARM64) |
+#include "platform/assert.h" |
+ |
+namespace dart { |
+ |
+class ARM64Decoder : public ValueObject { |
+ public: |
+ ARM64Decoder(char* buffer, size_t buffer_size) |
+ : buffer_(buffer), |
+ buffer_size_(buffer_size), |
+ buffer_pos_(0) { |
+ buffer_[buffer_pos_] = '\0'; |
+ } |
+ |
+ ~ARM64Decoder() {} |
+ |
+ // Writes one disassembled instruction into 'buffer' (0-terminated). |
+ // Returns true if the instruction was successfully decoded, false otherwise. |
+ void InstructionDecode(uword pc); |
+ |
+ private: |
+ // Bottleneck functions to print into the out_buffer. |
+ void Print(const char* str); |
+ |
+ // Printing of common values. |
+ void PrintRegister(int reg); |
+ void PrintShiftExtendRm(Instr* instr); |
+ void PrintS(Instr* instr); |
+ |
+ // Handle formatting of instructions and their options. |
+ int FormatRegister(Instr* instr, const char* option); |
+ int FormatOption(Instr* instr, const char* format); |
+ void Format(Instr* instr, const char* format); |
+ void Unknown(Instr* instr); |
+ |
+ // Decode instructions. |
+ #define DECODE_OP(op) \ |
+ void Decode##op(Instr* instr); |
+ APPLY_OP_LIST(DECODE_OP) |
+ #undef DECODE_OP |
+ |
+ |
+ // 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(ARM64Decoder); |
+}; |
+ |
+ |
+// Support for assertions in the ARM64Decoder 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 ARM64Decoder::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'; |
+} |
+ |
+ |
+// These register names are defined in a way to match the native disassembler |
+// formatting, except for register aliases ctx (r9) and pp (r10). |
+// See for example the command "objdump -d <binary file>". |
+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", "ip0", "ip1", "pp", "ctx", "fp", "lr", "r31", |
+}; |
+ |
+ |
+// Print the register name according to the active name converter. |
+void ARM64Decoder::PrintRegister(int reg) { |
+ ASSERT(0 <= reg); |
+ ASSERT(reg < kNumberOfCpuRegisters); |
+ Print(reg_names[reg]); |
+} |
+ |
+ |
+// These shift names are defined in a way to match the native disassembler |
+// formatting. See for example the command "objdump -d <binary file>". |
+static const char* shift_names[kMaxShift] = { |
+ "lsl", "lsr", "asr", "ror" |
+}; |
+ |
+ |
+static const char* extend_names[kMaxExtend] = { |
+ "uxtb", "uxth", "uxtw", "uxtx", |
+ "sxtb", "sxth", "sxtw", "sxtx", |
+}; |
+ |
+ |
+// Print the register shift operands for the instruction. Generally used for |
+// data processing instructions. |
+void ARM64Decoder::PrintShiftExtendRm(Instr* instr) { |
+ int rm = instr->RmField(); |
+ Shift shift = instr->ShiftTypeField(); |
+ int shift_amount = instr->ShiftAmountField(); |
+ Extend extend = instr->ExtendTypeField(); |
+ int extend_shift_amount = instr->ExtShiftAmountField(); |
+ |
+ PrintRegister(rm); |
+ |
+ if (instr->IsShift() && (shift == LSL) && (shift_amount == 0)) { |
+ // Special case for using rm only. |
+ return; |
+ } |
+ if (instr->IsShift()) { |
+ // by immediate |
+ if ((shift == ROR) && (shift_amount == 0)) { |
+ Print(", RRX"); |
+ return; |
+ } else if (((shift == LSR) || (shift == ASR)) && (shift_amount == 0)) { |
+ shift_amount = 32; |
+ } |
+ buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
+ remaining_size_in_buffer(), |
+ ", %s #%d", |
+ shift_names[shift], |
+ shift_amount); |
+ } else { |
+ ASSERT(instr->IsExtend()); |
+ // by register |
+ buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
+ remaining_size_in_buffer(), |
+ ", %s", |
+ extend_names[extend]); |
+ if (((instr->SFField() == 1) && (extend == UXTX)) || |
+ ((instr->SFField() == 0) && (extend == UXTW))) { |
+ // Shift amount. |
+ buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
+ remaining_size_in_buffer(), |
+ " %d", |
+ extend_shift_amount); |
+ } |
+ } |
+} |
+ |
+ |
+// Handle all register based formatting in these functions to reduce the |
+// complexity of FormatOption. |
+int ARM64Decoder::FormatRegister(Instr* instr, const char* format) { |
+ ASSERT(format[0] == 'r'); |
+ if (format[1] == 'n') { // 'rn: Rn register |
+ int reg = instr->RnField(); |
+ PrintRegister(reg); |
+ return 2; |
+ } else if (format[1] == 'd') { // 'rd: Rd register |
+ int reg = instr->RdField(); |
+ PrintRegister(reg); |
+ return 2; |
+ } else if (format[1] == 'm') { // 'rm: Rm register |
+ int reg = instr->RmField(); |
+ PrintRegister(reg); |
+ return 2; |
+ } |
+ UNREACHABLE(); |
+ return -1; |
+} |
+ |
+ |
+// 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 ARM64Decoder::FormatOption(Instr* instr, const char* format) { |
+ switch (format[0]) { |
+ case 'i': { // 'imm12, imm16 |
+ uint64_t imm; |
+ int ret = 5; |
+ if (format[4] == '2') { |
+ ASSERT(STRING_STARTS_WITH(format, "imm12")); |
+ imm = instr->Imm12Field(); |
+ if (format[5] == 's') { |
+ // shifted immediate. |
+ if (instr->Imm12ShiftField() == 1) { |
+ imm = imm << 12; |
+ } else if ((instr->Imm12ShiftField() & 0x2) != 0) { |
+ Print("Unknown Shift"); |
+ } |
+ ret = 6; |
+ } |
+ } else { |
+ ASSERT(STRING_STARTS_WITH(format, "imm16")); |
+ imm = instr->Imm16Field(); |
+ } |
+ buffer_pos_ += OS::SNPrint(current_position_in_buffer(), |
+ remaining_size_in_buffer(), |
+ "0x%"Px64, |
+ imm); |
+ return ret; |
+ } |
+ case 's': { // 's: S flag. |
+ if (format[1] == 'h') { |
+ ASSERT(STRING_STARTS_WITH(format, "shift_op")); |
+ PrintShiftExtendRm(instr); |
+ return 8; |
+ } else if (format[1] == 'f') { |
+ ASSERT(STRING_STARTS_WITH(format, "sf")); |
+ if (instr->SFField() == 1) { |
+ // TODO(zra): If we don't use the w form much, we can omit printing |
+ // this x. |
+ Print("x"); |
+ } else { |
+ Print("w"); |
+ } |
+ return 2; |
+ } else if (format[1] == ' ') { |
+ if (instr->HasS()) { |
+ Print("s"); |
+ } |
+ return 1; |
+ } else { |
+ UNREACHABLE(); |
+ } |
+ } |
+ case 'r': { |
+ return FormatRegister(instr, format); |
+ } |
+ default: { |
+ UNREACHABLE(); |
+ break; |
+ } |
+ } |
+ 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 ARM64Decoder::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 ARM64Decoder::Unknown(Instr* instr) { |
+ Format(instr, "unknown"); |
+} |
+ |
+ |
+void ARM64Decoder::DecodeMoveWide(Instr* instr) { |
+ switch (instr->Bits(29, 2)) { |
+ case 0: |
+ Format(instr, "movn'sf 'rd, 'imm16"); |
+ break; |
+ case 2: |
+ Format(instr, "movz'sf 'rd, 'imm16"); |
+ break; |
+ case 3: |
+ Format(instr, "movk'sf 'rd, 'imm16"); |
+ break; |
+ default: |
+ Unknown(instr); |
+ break; |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeAddSubImm(Instr* instr) { |
+ switch (instr->Bit(30)) { |
+ case 0: |
+ Format(instr, "addi'sf's 'rd, 'rn, 'imm12s"); |
+ break; |
+ case 1: |
+ Format(instr, "subi'sf's 'rd, 'rn, 'imm12s"); |
+ break; |
+ default: |
+ Unknown(instr); |
+ break; |
+ } |
+} |
+ |
+void ARM64Decoder::DecodeDPImmediate(Instr* instr) { |
+ if (instr->IsMoveWideOp()) { |
+ DecodeMoveWide(instr); |
+ } else if (instr->IsAddSubImmOp()) { |
+ DecodeAddSubImm(instr); |
+ } else { |
+ Unknown(instr); |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeExceptionGen(Instr* instr) { |
+ if ((instr->Bits(0, 2) == 1) && (instr->Bits(2, 3) == 0) && |
+ (instr->Bits(21, 3) == 0)) { |
+ Format(instr, "svc 'imm16"); |
+ } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) && |
+ (instr->Bits(21, 3) == 1)) { |
+ Format(instr, "brk 'imm16"); |
+ } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) && |
+ (instr->Bits(21, 3) == 2)) { |
+ Format(instr, "hlt 'imm16"); |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeSystem(Instr* instr) { |
+ if ((instr->Bits(0, 8) == 0x5f) && (instr->Bits(12, 4) == 2) && |
+ (instr->Bits(16, 3) == 3) && (instr->Bits(19, 2) == 0) && |
+ (instr->Bit(21) == 0)) { |
+ if (instr->Bits(8, 4) == 0) { |
+ Format(instr, "nop"); |
+ } else { |
+ Unknown(instr); |
+ } |
+ } else { |
+ Unknown(instr); |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeUnconditionalBranchReg(Instr* instr) { |
+ if ((instr->Bits(0, 5) == 0) && (instr->Bits(10, 5) == 0) && |
+ (instr->Bits(16, 5) == 0x1f)) { |
+ switch (instr->Bits(21, 4)) { |
+ case 0: |
+ Format(instr, "br 'rn"); |
+ break; |
+ case 1: |
+ Format(instr, "blr 'rn"); |
+ break; |
+ case 2: |
+ Format(instr, "ret 'rn"); |
+ break; |
+ default: |
+ Unknown(instr); |
+ break; |
+ } |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeCompareBranch(Instr* instr) { |
+ if (instr->IsExceptionGenOp()) { |
+ DecodeExceptionGen(instr); |
+ } else if (instr->IsSystemOp()) { |
+ DecodeSystem(instr); |
+ } else if (instr->IsUnconditionalBranchRegOp()) { |
+ DecodeUnconditionalBranchReg(instr); |
+ } else { |
+ Unknown(instr); |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeLoadStore(Instr* instr) { |
+ Unknown(instr); |
+} |
+ |
+ |
+void ARM64Decoder::DecodeAddSubShiftExt(Instr* instr) { |
+ switch (instr->Bit(30)) { |
+ case 0: |
+ Format(instr, "add'sf's 'rd, 'rn, 'shift_op"); |
+ break; |
+ case 1: |
+ Format(instr, "sub'sf's 'rd, 'rn, 'shift_op"); |
+ break; |
+ default: |
+ UNREACHABLE(); |
+ break; |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeDPRegister(Instr* instr) { |
+ if (instr->IsAddSubShiftExtOp()) { |
+ DecodeAddSubShiftExt(instr); |
+ } else { |
+ Unknown(instr); |
+ } |
+} |
+ |
+ |
+void ARM64Decoder::DecodeDPSimd1(Instr* instr) { |
+ Unknown(instr); |
+} |
+ |
+ |
+void ARM64Decoder::DecodeDPSimd2(Instr* instr) { |
+ Unknown(instr); |
+} |
+ |
+ |
+void ARM64Decoder::InstructionDecode(uword pc) { |
+ Instr* instr = Instr::At(pc); |
+ |
+ if (instr->IsDPImmediateOp()) { |
+ DecodeDPImmediate(instr); |
+ } else if (instr->IsCompareBranchOp()) { |
+ DecodeCompareBranch(instr); |
+ } else if (instr->IsLoadStoreOp()) { |
+ DecodeLoadStore(instr); |
+ } else if (instr->IsDPRegisterOp()) { |
+ DecodeDPRegister(instr); |
+ } else if (instr->IsDPSimd1Op()) { |
+ DecodeDPSimd1(instr); |
+ } else { |
+ ASSERT(instr->IsDPSimd2Op()); |
+ DecodeDPSimd2(instr); |
+ } |
+} |
+ |
+ |
+void Disassembler::DecodeInstruction(char* hex_buffer, intptr_t hex_size, |
+ char* human_buffer, intptr_t human_size, |
+ int* out_instr_size, uword pc) { |
+ ARM64Decoder decoder(human_buffer, human_size); |
+ decoder.InstructionDecode(pc); |
+ int32_t instruction_bits = Instr::At(pc)->InstructionBits(); |
+ OS::SNPrint(hex_buffer, hex_size, "%08x", instruction_bits); |
+ if (out_instr_size) { |
+ *out_instr_size = 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; |
+ } |
+} |
+ |
+} // namespace dart |
+ |
+#endif // defined TARGET_ARCH_ARM |