fabc87d633
Now, all VM tests that pass on SIMARM, pass on ARM. Also, more assertions in the ARM disassembler and simulator to make sure that condition codes aren't used with bkpt. R=regis@google.com Review URL: https://codereview.chromium.org//14784011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22471 260f80e4-7a28-3924-810f-c04153c831b5
1316 lines
38 KiB
C++
1316 lines
38 KiB
C++
// 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_ARM.
|
|
#if defined(TARGET_ARCH_ARM)
|
|
#include "platform/assert.h"
|
|
|
|
namespace dart {
|
|
|
|
class ARMDecoder : public ValueObject {
|
|
public:
|
|
ARMDecoder(char* buffer, size_t buffer_size)
|
|
: buffer_(buffer),
|
|
buffer_size_(buffer_size),
|
|
buffer_pos_(0) {
|
|
buffer_[buffer_pos_] = '\0';
|
|
}
|
|
|
|
~ARMDecoder() {}
|
|
|
|
// 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 PrintSRegister(int reg);
|
|
void PrintDRegister(int reg);
|
|
void PrintCondition(Instr* instr);
|
|
void PrintShiftRm(Instr* instr);
|
|
void PrintShiftImm(Instr* instr);
|
|
void PrintPU(Instr* instr);
|
|
|
|
// Handle formatting of instructions and their options.
|
|
int FormatRegister(Instr* instr, const char* option);
|
|
int FormatSRegister(Instr* instr, const char* option);
|
|
int FormatDRegister(Instr* instr, const char* option);
|
|
int FormatOption(Instr* instr, const char* option);
|
|
void Format(Instr* instr, const char* format);
|
|
void Unknown(Instr* instr);
|
|
|
|
// Each of these functions decodes one particular instruction type, a 3-bit
|
|
// field in the instruction encoding.
|
|
// Types 0 and 1 are combined as they are largely the same except for the way
|
|
// they interpret the shifter operand.
|
|
void DecodeType01(Instr* instr);
|
|
void DecodeType2(Instr* instr);
|
|
void DecodeType3(Instr* instr);
|
|
void DecodeType4(Instr* instr);
|
|
void DecodeType5(Instr* instr);
|
|
void DecodeType6(Instr* instr);
|
|
void DecodeType7(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(ARMDecoder);
|
|
};
|
|
|
|
|
|
// Support for assertions in the ARMDecoder 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 ARMDecoder::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 condition names are defined in a way to match the native disassembler
|
|
// formatting. See for example the command "objdump -d <binary file>".
|
|
static const char* cond_names[kMaxCondition] = {
|
|
"eq", "ne", "cs" , "cc" , "mi" , "pl" , "vs" , "vc" ,
|
|
"hi", "ls", "ge", "lt", "gt", "le", "", "invalid",
|
|
};
|
|
|
|
|
|
// Print the condition guarding the instruction.
|
|
void ARMDecoder::PrintCondition(Instr* instr) {
|
|
Print(cond_names[instr->ConditionField()]);
|
|
}
|
|
|
|
|
|
// 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", "ctx", "pp", "fp", "ip", "sp", "lr", "pc",
|
|
};
|
|
|
|
|
|
// Print the register name according to the active name converter.
|
|
void ARMDecoder::PrintRegister(int reg) {
|
|
ASSERT(0 <= reg);
|
|
ASSERT(reg < kNumberOfCpuRegisters);
|
|
Print(reg_names[reg]);
|
|
}
|
|
|
|
|
|
void ARMDecoder::PrintSRegister(int reg) {
|
|
ASSERT(0 <= reg);
|
|
ASSERT(reg < kNumberOfSRegisters);
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"s%d", reg);
|
|
}
|
|
|
|
|
|
void ARMDecoder::PrintDRegister(int reg) {
|
|
ASSERT(0 <= reg);
|
|
ASSERT(reg < kNumberOfDRegisters);
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"d%d", 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"
|
|
};
|
|
|
|
|
|
// Print the register shift operands for the instruction. Generally used for
|
|
// data processing instructions.
|
|
void ARMDecoder::PrintShiftRm(Instr* instr) {
|
|
Shift shift = instr->ShiftField();
|
|
int shift_amount = instr->ShiftAmountField();
|
|
int rm = instr->RmField();
|
|
|
|
PrintRegister(rm);
|
|
|
|
if ((instr->RegShiftField() == 0) && (shift == LSL) && (shift_amount == 0)) {
|
|
// Special case for using rm only.
|
|
return;
|
|
}
|
|
if (instr->RegShiftField() == 0) {
|
|
// 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 {
|
|
// by register
|
|
int rs = instr->RsField();
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
", %s ",
|
|
shift_names[shift]);
|
|
PrintRegister(rs);
|
|
}
|
|
}
|
|
|
|
|
|
// Print the immediate operand for the instruction. Generally used for data
|
|
// processing instructions.
|
|
void ARMDecoder::PrintShiftImm(Instr* instr) {
|
|
int rotate = instr->RotateField() * 2;
|
|
int immed8 = instr->Immed8Field();
|
|
int imm = (immed8 >> rotate) | (immed8 << (32 - rotate));
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"#%d",
|
|
imm);
|
|
}
|
|
|
|
|
|
// Print PU formatting to reduce complexity of FormatOption.
|
|
void ARMDecoder::PrintPU(Instr* instr) {
|
|
switch (instr->PUField()) {
|
|
case 0: {
|
|
Print("da");
|
|
break;
|
|
}
|
|
case 1: {
|
|
Print("ia");
|
|
break;
|
|
}
|
|
case 2: {
|
|
Print("db");
|
|
break;
|
|
}
|
|
case 3: {
|
|
Print("ib");
|
|
break;
|
|
}
|
|
default: {
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Handle all register based formatting in these functions to reduce the
|
|
// complexity of FormatOption.
|
|
int ARMDecoder::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);
|
|
if (format[2] == '2') { // 'rd2: possibly Rd, Rd+1 register pair
|
|
if (instr->HasSign() && !instr->HasL()) {
|
|
if ((reg % 2) != 0) {
|
|
Print(" *** unknown (odd register pair) ***");
|
|
} else {
|
|
Print(", ");
|
|
PrintRegister(reg + 1);
|
|
}
|
|
}
|
|
return 3;
|
|
}
|
|
return 2;
|
|
} else if (format[1] == 's') { // 'rs: Rs register
|
|
int reg = instr->RsField();
|
|
PrintRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'm') { // 'rm: Rm register
|
|
int reg = instr->RmField();
|
|
PrintRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'l') {
|
|
// 'rlist: register list for load and store multiple instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "rlist"));
|
|
int rlist = instr->RlistField();
|
|
int reg = 0;
|
|
Print("{");
|
|
// Print register list in ascending order, by scanning the bit mask.
|
|
while (rlist != 0) {
|
|
if ((rlist & 1) != 0) {
|
|
PrintRegister(reg);
|
|
if ((rlist >> 1) != 0) {
|
|
Print(", ");
|
|
}
|
|
}
|
|
reg++;
|
|
rlist >>= 1;
|
|
}
|
|
Print("}");
|
|
return 5;
|
|
}
|
|
UNREACHABLE();
|
|
return -1;
|
|
}
|
|
|
|
|
|
int ARMDecoder::FormatSRegister(Instr* instr, const char* format) {
|
|
ASSERT(format[0] == 's');
|
|
if (format[1] == 'n') { // 'sn: Sn register
|
|
int reg = instr->SnField();
|
|
PrintSRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'd') { // 'sd: Sd register
|
|
int reg = instr->SdField();
|
|
PrintSRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'm') {
|
|
int reg = instr->SmField();
|
|
if (format[2] == '1') { // 'sm1: S[m+1] register
|
|
reg++;
|
|
ASSERT(reg < kNumberOfSRegisters);
|
|
PrintSRegister(reg);
|
|
return 3;
|
|
} else { // 'sm: Sm register
|
|
PrintSRegister(reg);
|
|
return 2;
|
|
}
|
|
} else if (format[1] == 'l') {
|
|
ASSERT(STRING_STARTS_WITH(format, "slist"));
|
|
int reg_count = instr->Bits(0, 8);
|
|
int start = instr->Bit(22) | (instr->Bits(12, 4) << 1);
|
|
Print("{");
|
|
for (int i = start; i < start + reg_count; i++) {
|
|
PrintSRegister(i);
|
|
if (i != start + reg_count - 1) {
|
|
Print(", ");
|
|
}
|
|
}
|
|
Print("}");
|
|
return 5;
|
|
}
|
|
UNREACHABLE();
|
|
return -1;
|
|
}
|
|
|
|
|
|
int ARMDecoder::FormatDRegister(Instr* instr, const char* format) {
|
|
ASSERT(format[0] == 'd');
|
|
if (format[1] == 'n') { // 'dn: Dn register
|
|
int reg = instr->DnField();
|
|
PrintDRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'd') { // 'dd: Dd register
|
|
int reg = instr->DdField();
|
|
PrintDRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'm') { // 'dm: Dm register
|
|
int reg = instr->DmField();
|
|
PrintDRegister(reg);
|
|
return 2;
|
|
} else if (format[1] == 'l') {
|
|
ASSERT(STRING_STARTS_WITH(format, "dlist"));
|
|
int reg_count = instr->Bits(0, 8) >> 1;
|
|
int start = (instr->Bit(22) << 4) | instr->Bits(12, 4);
|
|
Print("{");
|
|
for (int i = start; i < start + reg_count; i++) {
|
|
PrintDRegister(i);
|
|
if (i != start + reg_count - 1) {
|
|
Print(", ");
|
|
}
|
|
}
|
|
Print("}");
|
|
return 5;
|
|
}
|
|
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 ARMDecoder::FormatOption(Instr* instr, const char* format) {
|
|
switch (format[0]) {
|
|
case 'a': { // 'a: accumulate multiplies
|
|
if (instr->Bit(21) == 0) {
|
|
Print("ul");
|
|
} else {
|
|
Print("la");
|
|
}
|
|
return 1;
|
|
}
|
|
case 'b': { // 'b: byte loads or stores
|
|
if (instr->HasB()) {
|
|
Print("b");
|
|
}
|
|
return 1;
|
|
}
|
|
case 'c': { // 'cond: conditional execution
|
|
ASSERT(STRING_STARTS_WITH(format, "cond"));
|
|
PrintCondition(instr);
|
|
return 4;
|
|
}
|
|
case 'd': {
|
|
if (format[1] == 'e') { // 'dest: branch destination
|
|
ASSERT(STRING_STARTS_WITH(format, "dest"));
|
|
int off = (instr->SImmed24Field() << 2) + 8;
|
|
uword destination = reinterpret_cast<uword>(instr) + off;
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%#"Px"",
|
|
destination);
|
|
return 4;
|
|
} else {
|
|
return FormatDRegister(instr, format);
|
|
}
|
|
}
|
|
case 'i': { // 'imm12_4, imm4_12, immf, or immd
|
|
uint16_t immed16;
|
|
if (format[3] == 'f') {
|
|
ASSERT(STRING_STARTS_WITH(format, "immf"));
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%f",
|
|
instr->ImmFloatField());
|
|
return 4;
|
|
} else if (format[3] == 'd') {
|
|
ASSERT(STRING_STARTS_WITH(format, "immd"));
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%g",
|
|
instr->ImmDoubleField());
|
|
return 4;
|
|
} else if (format[3] == '1') {
|
|
ASSERT(STRING_STARTS_WITH(format, "imm12_4"));
|
|
immed16 = instr->BkptField();
|
|
} else {
|
|
ASSERT(STRING_STARTS_WITH(format, "imm4_12"));
|
|
immed16 = instr->MovwField();
|
|
}
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"0x%x",
|
|
immed16);
|
|
return 7;
|
|
}
|
|
case 'l': { // 'l: branch and link
|
|
if (instr->HasLink()) {
|
|
Print("l");
|
|
}
|
|
return 1;
|
|
}
|
|
case 'm': { // 'memop: load/store instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "memop"));
|
|
if (instr->HasL() ||
|
|
// Extra load/store instructions.
|
|
((instr->TypeField() == 0) && instr->HasSign() && !instr->HasH())) {
|
|
Print("ldr");
|
|
} else {
|
|
Print("str");
|
|
}
|
|
return 5;
|
|
}
|
|
case 'o': {
|
|
if (format[3] == '1') {
|
|
if (format[4] == '0') {
|
|
// 'off10: 10-bit offset for VFP load and store instructions
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%d",
|
|
instr->Bits(0, 8) << 2);
|
|
} else {
|
|
// 'off12: 12-bit offset for load and store instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "off12"));
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%d",
|
|
instr->Offset12Field());
|
|
}
|
|
return 5;
|
|
}
|
|
// 'off8: 8-bit offset for extra load and store instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "off8"));
|
|
int offs8 = (instr->ImmedHField() << 4) | instr->ImmedLField();
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%d",
|
|
offs8);
|
|
return 4;
|
|
}
|
|
case 'p': { // 'pu: P and U bits for load and store instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "pu"));
|
|
PrintPU(instr);
|
|
return 2;
|
|
}
|
|
case 'r': {
|
|
return FormatRegister(instr, format);
|
|
}
|
|
case 's': {
|
|
if (format[1] == 'h') { // 'shift_op or 'shift_rm
|
|
if (format[6] == 'o') { // 'shift_op
|
|
ASSERT(STRING_STARTS_WITH(format, "shift_op"));
|
|
if (instr->TypeField() == 0) {
|
|
PrintShiftRm(instr);
|
|
} else {
|
|
ASSERT(instr->TypeField() == 1);
|
|
PrintShiftImm(instr);
|
|
}
|
|
return 8;
|
|
} else { // 'shift_rm
|
|
ASSERT(STRING_STARTS_WITH(format, "shift_rm"));
|
|
PrintShiftRm(instr);
|
|
return 8;
|
|
}
|
|
} else if (format[1] == 'v') { // 'svc
|
|
ASSERT(STRING_STARTS_WITH(format, "svc"));
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"0x%x",
|
|
instr->SvcField());
|
|
return 3;
|
|
} else if (format[1] == ' ') {
|
|
// 's: S field of data processing instructions
|
|
if (instr->HasS()) {
|
|
Print("s");
|
|
}
|
|
return 1;
|
|
} else {
|
|
return FormatSRegister(instr, format);
|
|
}
|
|
}
|
|
case 't': { // 'target: target of branch instructions
|
|
ASSERT(STRING_STARTS_WITH(format, "target"));
|
|
int off = (instr->SImmed24Field() << 2) + 8;
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
"%+d",
|
|
off);
|
|
return 6;
|
|
}
|
|
case 'u': { // 'u: signed or unsigned multiplies
|
|
if (instr->Bit(22) == 0) {
|
|
Print("u");
|
|
} else {
|
|
Print("s");
|
|
}
|
|
return 1;
|
|
}
|
|
case 'w': { // 'w: W field of load and store instructions
|
|
if (instr->HasW()) {
|
|
Print("!");
|
|
}
|
|
return 1;
|
|
}
|
|
case 'x': { // 'x: type of extra load/store instructions
|
|
if (!instr->HasSign()) {
|
|
Print("h");
|
|
} else if (instr->HasL()) {
|
|
if (instr->HasH()) {
|
|
Print("sh");
|
|
} else {
|
|
Print("sb");
|
|
}
|
|
} else {
|
|
Print("d");
|
|
}
|
|
return 1;
|
|
}
|
|
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 ARMDecoder::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 ARMDecoder::Unknown(Instr* instr) {
|
|
Format(instr, "unknown");
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType01(Instr* instr) {
|
|
if (!instr->IsDataProcessing()) {
|
|
// miscellaneous, multiply, sync primitives, extra loads and stores.
|
|
if (instr->IsMiscellaneous()) {
|
|
switch (instr->Bits(4, 3)) {
|
|
case 1: {
|
|
if (instr->Bits(21, 2) == 0x3) {
|
|
Format(instr, "clz'cond 'rd, 'rm");
|
|
} else if (instr->Bits(21, 2) == 0x1) {
|
|
Format(instr, "bx'cond 'rm");
|
|
} else {
|
|
Unknown(instr);
|
|
}
|
|
break;
|
|
}
|
|
case 3: {
|
|
if (instr->Bits(21, 2) == 0x1) {
|
|
Format(instr, "blx'cond 'rm");
|
|
} else {
|
|
// Could be inlined constant.
|
|
Unknown(instr);
|
|
}
|
|
break;
|
|
}
|
|
case 7: {
|
|
if ((instr->Bits(21, 2) == 0x1) && (instr->ConditionField() == AL)) {
|
|
Format(instr, "bkpt #'imm12_4");
|
|
} else {
|
|
// Format(instr, "smc'cond");
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr); // Not used.
|
|
break;
|
|
}
|
|
}
|
|
} else if (instr->IsMultiplyOrSyncPrimitive()) {
|
|
if (instr->Bit(24) == 0) {
|
|
// multiply instructions
|
|
switch (instr->Bits(21, 3)) {
|
|
case 0: {
|
|
// Assembler registers rd, rn, rm are encoded as rn, rm, rs.
|
|
Format(instr, "mul'cond's 'rn, 'rm, 'rs");
|
|
break;
|
|
}
|
|
case 1: {
|
|
// Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd.
|
|
Format(instr, "mla'cond's 'rn, 'rm, 'rs, 'rd");
|
|
break;
|
|
}
|
|
case 3: {
|
|
// Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd.
|
|
Format(instr, "mls'cond's 'rn, 'rm, 'rs, 'rd");
|
|
break;
|
|
}
|
|
case 4: {
|
|
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
|
|
Format(instr, "umull'cond's 'rd, 'rn, 'rm, 'rs");
|
|
break;
|
|
}
|
|
case 6: {
|
|
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
|
|
Format(instr, "smull'cond's 'rd, 'rn, 'rm, 'rs");
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr); // Not used.
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// synchronization primitives
|
|
switch (instr->Bits(20, 4)) {
|
|
case 8: {
|
|
Format(instr, "strex'cond 'rd, 'rm, ['rn]");
|
|
break;
|
|
}
|
|
case 9: {
|
|
Format(instr, "ldrex'cond 'rd, ['rn]");
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr); // Not used.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (instr->Bit(25) == 1) {
|
|
// 16-bit immediate loads, msr (immediate), and hints
|
|
switch (instr->Bits(20, 5)) {
|
|
case 16: {
|
|
Format(instr, "movw'cond 'rd, #'imm4_12");
|
|
break;
|
|
}
|
|
case 18: {
|
|
if ((instr->Bits(16, 4) == 0) && (instr->Bits(0, 8) == 0)) {
|
|
Format(instr, "nop'cond");
|
|
} else {
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
case 20: {
|
|
Format(instr, "movt'cond 'rd, #'imm4_12");
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr); // Not used.
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// extra load/store instructions
|
|
switch (instr->PUField()) {
|
|
case 0: {
|
|
if (instr->Bit(22) == 0) {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn], -'rm");
|
|
} else {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn], #-'off8");
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
if (instr->Bit(22) == 0) {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn], +'rm");
|
|
} else {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn], #+'off8");
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
if (instr->Bit(22) == 0) {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn, -'rm]'w");
|
|
} else {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn, #-'off8]'w");
|
|
}
|
|
break;
|
|
}
|
|
case 3: {
|
|
if (instr->Bit(22) == 0) {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn, +'rm]'w");
|
|
} else {
|
|
Format(instr, "'memop'cond'x 'rd2, ['rn, #+'off8]'w");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
// The PU field is a 2-bit field.
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
switch (instr->OpcodeField()) {
|
|
case AND: {
|
|
Format(instr, "and'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case EOR: {
|
|
Format(instr, "eor'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case SUB: {
|
|
Format(instr, "sub'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case RSB: {
|
|
Format(instr, "rsb'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case ADD: {
|
|
Format(instr, "add'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case ADC: {
|
|
Format(instr, "adc'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case SBC: {
|
|
Format(instr, "sbc'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case RSC: {
|
|
Format(instr, "rsc'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case TST: {
|
|
if (instr->HasS()) {
|
|
Format(instr, "tst'cond 'rn, 'shift_op");
|
|
} else {
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
case TEQ: {
|
|
if (instr->HasS()) {
|
|
Format(instr, "teq'cond 'rn, 'shift_op");
|
|
} else {
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
case CMP: {
|
|
if (instr->HasS()) {
|
|
Format(instr, "cmp'cond 'rn, 'shift_op");
|
|
} else {
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
case CMN: {
|
|
if (instr->HasS()) {
|
|
Format(instr, "cmn'cond 'rn, 'shift_op");
|
|
} else {
|
|
Unknown(instr); // Not used.
|
|
}
|
|
break;
|
|
}
|
|
case ORR: {
|
|
Format(instr, "orr'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case MOV: {
|
|
Format(instr, "mov'cond's 'rd, 'shift_op");
|
|
break;
|
|
}
|
|
case BIC: {
|
|
Format(instr, "bic'cond's 'rd, 'rn, 'shift_op");
|
|
break;
|
|
}
|
|
case MVN: {
|
|
Format(instr, "mvn'cond's 'rd, 'shift_op");
|
|
break;
|
|
}
|
|
default: {
|
|
// The Opcode field is a 4-bit field.
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType2(Instr* instr) {
|
|
switch (instr->PUField()) {
|
|
case 0: {
|
|
if (instr->HasW()) {
|
|
Unknown(instr); // Not used.
|
|
} else {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn], #-'off12");
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
if (instr->HasW()) {
|
|
Unknown(instr); // Not used.
|
|
} else {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn], #+'off12");
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn, #-'off12]'w");
|
|
break;
|
|
}
|
|
case 3: {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn, #+'off12]'w");
|
|
break;
|
|
}
|
|
default: {
|
|
// The PU field is a 2-bit field.
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType3(Instr* instr) {
|
|
if (instr->IsDivision()) {
|
|
if (instr->Bit(21)) {
|
|
Format(instr, "udiv'cond 'rn, 'rs, 'rm");
|
|
} else {
|
|
Format(instr, "sdiv'cond 'rn, 'rs, 'rm");
|
|
}
|
|
return;
|
|
}
|
|
switch (instr->PUField()) {
|
|
case 0: {
|
|
if (instr->HasW()) {
|
|
Unknown(instr);
|
|
} else {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn], -'shift_rm");
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
if (instr->HasW()) {
|
|
Unknown(instr);
|
|
} else {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn], +'shift_rm");
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn, -'shift_rm]'w");
|
|
break;
|
|
}
|
|
case 3: {
|
|
Format(instr, "'memop'cond'b 'rd, ['rn, +'shift_rm]'w");
|
|
break;
|
|
}
|
|
default: {
|
|
// The PU field is a 2-bit field.
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType4(Instr* instr) {
|
|
if (instr->Bit(22) == 1) {
|
|
Unknown(instr); // Privileged mode currently not supported.
|
|
} else if (instr->HasL()) {
|
|
Format(instr, "ldm'cond'pu 'rn'w, 'rlist");
|
|
} else {
|
|
Format(instr, "stm'cond'pu 'rn'w, 'rlist");
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType5(Instr* instr) {
|
|
Format(instr, "b'l'cond 'target ; 'dest");
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType6(Instr* instr) {
|
|
if (instr->IsVFPDoubleTransfer()) {
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(20) == 1) {
|
|
Format(instr, "vmovrrs'cond 'rd, 'rn, {'sm, 'sm1}");
|
|
} else {
|
|
Format(instr, "vmovsrr'cond {'sm, 'sm1}, 'rd, 'rn");
|
|
}
|
|
} else {
|
|
if (instr->Bit(20) == 1) {
|
|
Format(instr, "vmovrrd'cond 'rd, 'rn, 'dm");
|
|
} else {
|
|
Format(instr, "vmovdrr'cond 'dm, 'rd, 'rn");
|
|
}
|
|
}
|
|
} else if (instr-> IsVFPLoadStore()) {
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(20) == 1) { // vldrs
|
|
if (instr->Bit(23) == 1) {
|
|
Format(instr, "vldrs'cond 'sd, ['rn, #+'off10]");
|
|
} else {
|
|
Format(instr, "vldrs'cond 'sd, ['rn, #-'off10]");
|
|
}
|
|
} else { // vstrs
|
|
if (instr->Bit(23) == 1) {
|
|
Format(instr, "vstrs'cond 'sd, ['rn, #+'off10]");
|
|
} else {
|
|
Format(instr, "vstrs'cond 'sd, ['rn, #-'off10]");
|
|
}
|
|
}
|
|
} else {
|
|
if (instr->Bit(20) == 1) { // vldrd
|
|
if (instr->Bit(23) == 1) {
|
|
Format(instr, "vldrd'cond 'dd, ['rn, #+'off10]");
|
|
} else {
|
|
Format(instr, "vldrd'cond 'dd, ['rn, #-'off10]");
|
|
}
|
|
} else { // vstrd
|
|
if (instr->Bit(23) == 1) {
|
|
Format(instr, "vstrd'cond 'dd, ['rn, #+'off10]");
|
|
} else {
|
|
Format(instr, "vstrd'cond 'dd, ['rn, #-'off10]");
|
|
}
|
|
}
|
|
}
|
|
} else if (instr->IsVFPMultipleLoadStore()) {
|
|
if (instr->HasL()) { // vldm
|
|
if (instr->Bit(8)) { // vldmd
|
|
Format(instr, "vldmd'cond'pu 'rn'w, 'dlist");
|
|
} else { // vldms
|
|
Format(instr, "vldms'cond'pu 'rn'w, 'slist");
|
|
}
|
|
} else { // vstm
|
|
if (instr->Bit(8)) { // vstmd
|
|
Format(instr, "vstmd'cond'pu 'rn'w, 'dlist");
|
|
} else { // vstms
|
|
Format(instr, "vstms'cond'pu 'rn'w, 'slist");
|
|
}
|
|
}
|
|
} else {
|
|
Unknown(instr);
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::DecodeType7(Instr* instr) {
|
|
if (instr->Bit(24) == 1) {
|
|
Format(instr, "svc'cond #'svc");
|
|
if (instr->SvcField() == kStopMessageSvcCode) {
|
|
const char* message = *reinterpret_cast<const char**>(
|
|
reinterpret_cast<intptr_t>(instr) - Instr::kInstrSize);
|
|
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
|
|
remaining_size_in_buffer(),
|
|
" ; \"%s\"",
|
|
message);
|
|
}
|
|
} else if (instr->IsVFPDataProcessingOrSingleTransfer()) {
|
|
if (instr->Bit(4) == 0) {
|
|
// VFP Data Processing
|
|
switch (instr->Bits(20, 4) & 0xb) {
|
|
case 0: { // vmla, vmls floating-point
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(6) == 0) {
|
|
Format(instr, "vmlas'cond 'sd, 'sn, 'sm");
|
|
} else {
|
|
Format(instr, "vmlss'cond 'sd, 'sn, 'sm");
|
|
}
|
|
} else {
|
|
if (instr->Bit(6) == 0) {
|
|
Format(instr, "vmlad'cond 'dd, 'dn, 'dm");
|
|
} else {
|
|
Format(instr, "vmlsd'cond 'dd, 'dn, 'dm");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 1: // vnmla, vnmls, vnmul
|
|
default: {
|
|
Unknown(instr);
|
|
break;
|
|
}
|
|
case 2: { // vmul
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vmuls'cond 'sd, 'sn, 'sm");
|
|
} else {
|
|
Format(instr, "vmuld'cond 'dd, 'dn, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
case 8: { // vdiv
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vdivs'cond 'sd, 'sn, 'sm");
|
|
} else {
|
|
Format(instr, "vdivd'cond 'dd, 'dn, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
case 3: { // vadd, vsub floating-point
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(6) == 0) {
|
|
Format(instr, "vadds'cond 'sd, 'sn, 'sm");
|
|
} else {
|
|
Format(instr, "vsubs'cond 'sd, 'sn, 'sm");
|
|
}
|
|
} else {
|
|
if (instr->Bit(6) == 0) {
|
|
Format(instr, "vaddd'cond 'dd, 'dn, 'dm");
|
|
} else {
|
|
Format(instr, "vsubd'cond 'dd, 'dn, 'dm");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 0xb: { // Other VFP data-processing instructions
|
|
if (instr->Bit(6) == 0) { // vmov immediate
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vmovs'cond 'sd, #'immf");
|
|
} else {
|
|
Format(instr, "vmovd'cond 'dd, #'immd");
|
|
}
|
|
break;
|
|
}
|
|
switch (instr->Bits(16, 4)) {
|
|
case 0: { // vmov register, vabs
|
|
switch (instr->Bits(6, 2)) {
|
|
case 1: { // vmov register
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vmovs'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vmovd'cond 'dd, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
case 3: { // vabs
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vabss'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vabsd'cond 'dd, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 1: { // vneg, vsqrt
|
|
switch (instr->Bits(6, 2)) {
|
|
case 1: { // vneg
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vnegs'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vnegd'cond 'dd, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
case 3: { // vsqrt
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vsqrts'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vsqrtd'cond 'dd, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
Unknown(instr);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 4: // vcmp, vcmpe
|
|
case 5: { // vcmp #0.0, vcmpe #0.0
|
|
if (instr->Bit(7) == 1) { // vcmpe
|
|
Unknown(instr);
|
|
} else {
|
|
if (instr->Bit(8) == 0) { // vcmps
|
|
if (instr->Bit(16) == 0) {
|
|
Format(instr, "vcmps'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vcmps'cond 'sd, #0.0");
|
|
}
|
|
} else { // vcmpd
|
|
if (instr->Bit(16) == 0) {
|
|
Format(instr, "vcmpd'cond 'dd, 'dm");
|
|
} else {
|
|
Format(instr, "vcmpd'cond 'dd, #0.0");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 7: { // vcvt between double-precision and single-precision
|
|
if (instr->Bit(8) == 0) {
|
|
Format(instr, "vcvtds'cond 'dd, 'sm");
|
|
} else {
|
|
Format(instr, "vcvtsd'cond 'sd, 'dm");
|
|
}
|
|
break;
|
|
}
|
|
case 8: { // vcvt, vcvtr between floating-point and integer
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(7) == 0) {
|
|
Format(instr, "vcvtsu'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vcvtsi'cond 'sd, 'sm");
|
|
}
|
|
} else {
|
|
if (instr->Bit(7) == 0) {
|
|
Format(instr, "vcvtdu'cond 'dd, 'sm");
|
|
} else {
|
|
Format(instr, "vcvtdi'cond 'dd, 'sm");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 12:
|
|
case 13: { // vcvt, vcvtr between floating-point and integer
|
|
if (instr->Bit(7) == 0) {
|
|
// We only support round-to-zero mode
|
|
Unknown(instr);
|
|
break;
|
|
}
|
|
if (instr->Bit(8) == 0) {
|
|
if (instr->Bit(16) == 0) {
|
|
Format(instr, "vcvtus'cond 'sd, 'sm");
|
|
} else {
|
|
Format(instr, "vcvtis'cond 'sd, 'sm");
|
|
}
|
|
} else {
|
|
if (instr->Bit(16) == 0) {
|
|
Format(instr, "vcvtud'cond 'sd, 'dm");
|
|
} else {
|
|
Format(instr, "vcvtid'cond 'sd, 'dm");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 2: // vcvtb, vcvtt
|
|
case 3: // vcvtb, vcvtt
|
|
case 9: // undefined
|
|
case 10: // vcvt between floating-point and fixed-point
|
|
case 11: // vcvt between floating-point and fixed-point
|
|
case 14: // vcvt between floating-point and fixed-point
|
|
case 15: // vcvt between floating-point and fixed-point
|
|
default: {
|
|
Unknown(instr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// 8, 16, or 32-bit Transfer between ARM Core and VFP
|
|
if ((instr->Bits(21, 3) == 0) && (instr->Bit(8) == 0)) {
|
|
if (instr->Bit(20) == 0) {
|
|
Format(instr, "vmovs'cond 'sn, 'rd");
|
|
} else {
|
|
Format(instr, "vmovr'cond 'rd, 'sn");
|
|
}
|
|
} else if ((instr->Bits(20, 4) == 0xf) && (instr->Bit(8) == 0) &&
|
|
(instr->Bits(12, 4) == 0xf)) {
|
|
Format(instr, "vmstat'cond");
|
|
} else {
|
|
Unknown(instr);
|
|
}
|
|
}
|
|
} else if (instr->IsMrcIdIsar0()) {
|
|
Format(instr, "mrc'cond p15, 0, 'rd, c0, c2, 0");
|
|
} else {
|
|
Unknown(instr);
|
|
}
|
|
}
|
|
|
|
|
|
void ARMDecoder::InstructionDecode(uword pc) {
|
|
Instr* instr = Instr::At(pc);
|
|
|
|
if (instr->ConditionField() == kSpecialCondition) {
|
|
if (instr->InstructionBits() == static_cast<int32_t>(0xf57ff01f)) {
|
|
Format(instr, "clrex");
|
|
} else {
|
|
Unknown(instr);
|
|
}
|
|
} else {
|
|
switch (instr->TypeField()) {
|
|
case 0:
|
|
case 1: {
|
|
DecodeType01(instr);
|
|
break;
|
|
}
|
|
case 2: {
|
|
DecodeType2(instr);
|
|
break;
|
|
}
|
|
case 3: {
|
|
DecodeType3(instr);
|
|
break;
|
|
}
|
|
case 4: {
|
|
DecodeType4(instr);
|
|
break;
|
|
}
|
|
case 5: {
|
|
DecodeType5(instr);
|
|
break;
|
|
}
|
|
case 6: {
|
|
DecodeType6(instr);
|
|
break;
|
|
}
|
|
case 7: {
|
|
DecodeType7(instr);
|
|
break;
|
|
}
|
|
default: {
|
|
// The type field is 3-bits in the ARM encoding.
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Disassembler::DecodeInstruction(char* hex_buffer, intptr_t hex_size,
|
|
char* human_buffer, intptr_t human_size,
|
|
int* out_instr_size, uword pc) {
|
|
ARMDecoder 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
|