Chromium Code Reviews| Index: runtime/vm/disassembler_arm64.cc |
| =================================================================== |
| --- runtime/vm/disassembler_arm64.cc (revision 0) |
| +++ runtime/vm/disassembler_arm64.cc (revision 0) |
| @@ -0,0 +1,484 @@ |
| +// 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) { |
| + Print("x"); |
|
regis
2014/04/01 22:25:15
You could skip printing the 'x', to be in sync wit
zra
2014/04/02 00:04:25
I'll keep the x for now while I'm getting started,
|
| + } 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 |