| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "A64DOpcode.h" |
| |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| |
| namespace JSC { namespace ARM64Disassembler { |
| |
| A64DOpcode::OpcodeGroup* A64DOpcode::opcodeTable[32]; |
| |
| const char* const A64DOpcode::s_conditionNames[16] = { |
| "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", |
| "hi", "ls", "ge", "lt", "gt", "le", "al", "ne" |
| }; |
| |
| const char* const A64DOpcode::s_optionName[8] = { |
| "uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx" |
| }; |
| |
| const char* const A64DOpcode::s_shiftNames[4] = { |
| "lsl", "lsr", "asl", "ror" |
| }; |
| |
| const char A64DOpcode::s_FPRegisterPrefix[5] = { |
| 'b', 'h', 's', 'd', 'q' |
| }; |
| |
| struct OpcodeGroupInitializer { |
| unsigned m_opcodeGroupNumber; |
| uint32_t m_mask; |
| uint32_t m_pattern; |
| const char* (*m_format)(A64DOpcode*); |
| }; |
| |
| #define OPCODE_GROUP_ENTRY(groupIndex, groupClass) \ |
| { groupIndex, groupClass::mask, groupClass::pattern, groupClass::format } |
| |
| static OpcodeGroupInitializer opcodeGroupList[] = { |
| OPCODE_GROUP_ENTRY(0x0a, A64DOpcodeLogicalShiftedRegister), |
| OPCODE_GROUP_ENTRY(0x0b, A64DOpcodeAddSubtractExtendedRegister), |
| OPCODE_GROUP_ENTRY(0x0b, A64DOpcodeAddSubtractShiftedRegister), |
| OPCODE_GROUP_ENTRY(0x11, A64DOpcodeAddSubtractImmediate), |
| OPCODE_GROUP_ENTRY(0x12, A64DOpcodeMoveWide), |
| OPCODE_GROUP_ENTRY(0x12, A64DOpcodeLogicalImmediate), |
| OPCODE_GROUP_ENTRY(0x13, A64DOpcodeBitfield), |
| OPCODE_GROUP_ENTRY(0x13, A64DOpcodeExtract), |
| OPCODE_GROUP_ENTRY(0x14, A64DOpcodeUnconditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x14, A64DOpcodeConditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x14, A64DOpcodeCompareAndBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x14, A64OpcodeExceptionGeneration), |
| OPCODE_GROUP_ENTRY(0x15, A64DOpcodeUnconditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x15, A64DOpcodeConditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x15, A64DOpcodeCompareAndBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x15, A64DOpcodeHint), |
| OPCODE_GROUP_ENTRY(0x16, A64DOpcodeUnconditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x16, A64DOpcodeUnconditionalBranchRegister), |
| OPCODE_GROUP_ENTRY(0x16, A64DOpcodeTestAndBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x17, A64DOpcodeUnconditionalBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x17, A64DOpcodeUnconditionalBranchRegister), |
| OPCODE_GROUP_ENTRY(0x17, A64DOpcodeTestAndBranchImmediate), |
| OPCODE_GROUP_ENTRY(0x18, A64DOpcodeLoadStoreImmediate), |
| OPCODE_GROUP_ENTRY(0x18, A64DOpcodeLoadStoreRegisterOffset), |
| OPCODE_GROUP_ENTRY(0x19, A64DOpcodeLoadStoreUnsignedImmediate), |
| OPCODE_GROUP_ENTRY(0x1a, A64DOpcodeConditionalSelect), |
| OPCODE_GROUP_ENTRY(0x1a, A64DOpcodeDataProcessing2Source), |
| OPCODE_GROUP_ENTRY(0x1b, A64DOpcodeDataProcessing3Source), |
| OPCODE_GROUP_ENTRY(0x1c, A64DOpcodeLoadStoreImmediate), |
| OPCODE_GROUP_ENTRY(0x1c, A64DOpcodeLoadStoreRegisterOffset), |
| OPCODE_GROUP_ENTRY(0x1d, A64DOpcodeLoadStoreUnsignedImmediate), |
| OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointCompare), |
| OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointDataProcessing2Source), |
| OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointDataProcessing1Source), |
| OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingFixedPointConversions), |
| OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointIntegerConversions), |
| }; |
| |
| bool A64DOpcode::s_initialized = false; |
| |
| void A64DOpcode::init() |
| { |
| if (s_initialized) |
| return; |
| |
| OpcodeGroup* lastGroups[32]; |
| |
| for (unsigned i = 0; i < 32; i++) { |
| opcodeTable[i] = 0; |
| lastGroups[i] = 0; |
| } |
| |
| for (unsigned i = 0; i < sizeof(opcodeGroupList) / sizeof(struct OpcodeGroupInitializer); i++) { |
| OpcodeGroup* newOpcodeGroup = new OpcodeGroup(opcodeGroupList[i].m_mask, opcodeGroupList[i].m_pattern, opcodeGroupList[i].m_format); |
| uint32_t opcodeGroupNumber = opcodeGroupList[i].m_opcodeGroupNumber; |
| |
| if (!opcodeTable[opcodeGroupNumber]) |
| opcodeTable[opcodeGroupNumber] = newOpcodeGroup; |
| else |
| lastGroups[opcodeGroupNumber]->setNext(newOpcodeGroup); |
| lastGroups[opcodeGroupNumber] = newOpcodeGroup; |
| } |
| |
| s_initialized = true; |
| } |
| |
| void A64DOpcode::setPCAndOpcode(uint32_t* newPC, uint32_t newOpcode) |
| { |
| m_currentPC = newPC; |
| m_opcode = newOpcode; |
| m_bufferOffset = 0; |
| m_formatBuffer[0] = '\0'; |
| } |
| |
| const char* A64DOpcode::disassemble(uint32_t* currentPC) |
| { |
| setPCAndOpcode(currentPC, *currentPC); |
| |
| OpcodeGroup* opGroup = opcodeTable[opcodeGroupNumber(m_opcode)]; |
| |
| while (opGroup) { |
| if (opGroup->matches(m_opcode)) |
| return opGroup->format(this); |
| opGroup = opGroup->next(); |
| } |
| |
| return A64DOpcode::format(); |
| } |
| |
| void A64DOpcode::bufferPrintf(const char* format, ...) |
| { |
| if (m_bufferOffset >= bufferSize) |
| return; |
| |
| va_list argList; |
| va_start(argList, format); |
| |
| m_bufferOffset += vsnprintf(m_formatBuffer + m_bufferOffset, bufferSize - m_bufferOffset, format, argList); |
| |
| va_end(argList); |
| } |
| |
| const char* A64DOpcode::format() |
| { |
| bufferPrintf(" .long %08x", m_opcode); |
| return m_formatBuffer; |
| } |
| |
| void A64DOpcode::appendRegisterName(unsigned registerNumber, bool is64Bit) |
| { |
| if (registerNumber == 30) { |
| bufferPrintf(is64Bit ? "lr" : "wlr"); |
| return; |
| } |
| |
| bufferPrintf("%c%u", is64Bit ? 'x' : 'w', registerNumber); |
| } |
| |
| void A64DOpcode::appendFPRegisterName(unsigned registerNumber, unsigned registerSize) |
| { |
| bufferPrintf("%c%u", FPRegisterPrefix(registerSize), registerNumber); |
| } |
| |
| const char* const A64DOpcodeAddSubtract::s_opNames[4] = { "add", "adds", "sub", "subs" }; |
| |
| const char* A64DOpcodeAddSubtractImmediate::format() |
| { |
| if (isCMP()) |
| appendInstructionName(cmpName()); |
| else { |
| if (isMovSP()) |
| appendInstructionName("mov"); |
| else |
| appendInstructionName(opName()); |
| appendSPOrRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| } |
| appendSPOrRegisterName(rn(), is64Bit()); |
| |
| if (!isMovSP()) { |
| appendSeparator(); |
| appendUnsignedImmediate(immed12()); |
| if (shift()) { |
| appendSeparator(); |
| appendString(shift() == 1 ? "lsl" : "reserved"); |
| } |
| } |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeAddSubtractExtendedRegister::format() |
| { |
| if (immediate3() > 4) |
| return A64DOpcode::format(); |
| |
| if (isCMP()) |
| appendInstructionName(cmpName()); |
| else { |
| appendInstructionName(opName()); |
| appendSPOrRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| } |
| appendSPOrRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendZROrRegisterName(rm(), is64Bit() && ((option() & 0x3) == 0x3)); |
| appendSeparator(); |
| if (option() == 0x2 && ((rd() == 31) || (rn() == 31))) |
| appendString("lsl"); |
| else |
| appendString(optionName()); |
| if (immediate3()) { |
| appendCharacter(' '); |
| appendUnsignedImmediate(immediate3()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeAddSubtractShiftedRegister::format() |
| { |
| if (!is64Bit() && immediate6() & 0x20) |
| return A64DOpcode::format(); |
| |
| if (shift() == 0x3) |
| return A64DOpcode::format(); |
| |
| if (isCMP()) |
| appendInstructionName(cmpName()); |
| else { |
| if (isNeg()) |
| appendInstructionName(cmpName()); |
| else |
| appendInstructionName(opName()); |
| appendSPOrRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| } |
| if (!isNeg()) { |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| } |
| appendZROrRegisterName(rm(), is64Bit()); |
| if (immediate6()) { |
| appendSeparator(); |
| appendShiftType(shift()); |
| appendUnsignedImmediate(immediate6()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeBitfield::s_opNames[3] = { "sbfm", "bfm", "ubfm" }; |
| const char* const A64DOpcodeBitfield::s_extendPseudoOpNames[3][3] = { |
| { "sxtb", "sxth", "sxtw" }, { 0, 0, 0} , { "uxtb", "uxth", "uxtw" } }; |
| const char* const A64DOpcodeBitfield::s_insertOpNames[3] = { "sbfiz", "bfi", "ubfiz" }; |
| const char* const A64DOpcodeBitfield::s_extractOpNames[3] = { "sbfx", "bf", "ubfx" }; |
| |
| const char* A64DOpcodeBitfield::format() |
| { |
| if (opc() == 0x3) |
| return A64DOpcode::format(); |
| |
| if (is64Bit() != nBit()) |
| return A64DOpcode::format(); |
| |
| if (!is64Bit() && ((immediateR() & 0x20) || (immediateS() & 0x20))) |
| return A64DOpcode::format(); |
| |
| if (!(opc() & 0x1) && !immediateR()) { |
| // [un]signed {btye,half-word,word} extend |
| bool isSTXType = false; |
| if (immediateS() == 7) { |
| appendInstructionName(extendPseudoOpNames(0)); |
| isSTXType = true; |
| } else if (immediateS() == 15) { |
| appendInstructionName(extendPseudoOpNames(1)); |
| isSTXType = true; |
| } else if (immediateS() == 31 && is64Bit()) { |
| appendInstructionName(extendPseudoOpNames(2)); |
| isSTXType = true; |
| } |
| |
| if (isSTXType) { |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), false); |
| |
| return m_formatBuffer; |
| } |
| } |
| |
| if (opc() == 0x2 && immediateS() == (immediateR() + 1)) { |
| // lsl |
| appendInstructionName("lsl"); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate((is64Bit() ? 63u : 31u) - immediateR()); |
| |
| return m_formatBuffer; |
| } else if (!(opc() & 0x1) && ((immediateS() & 0x1f) == 0x1f) && (is64Bit() == (immediateS() >> 5))) { |
| // asr/lsr |
| appendInstructionName(!opc() ? "ars" : "lsr"); |
| |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateR()); |
| |
| return m_formatBuffer; |
| } else if (immediateS() < immediateR()) { |
| // bit field insert |
| appendInstructionName(insertOpNames()); |
| |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate((is64Bit() ? 64u : 32u) - immediateR()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateS() + 1); |
| |
| return m_formatBuffer; |
| } else { |
| // bit field extract |
| appendInstructionName(extractOpNames()); |
| |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateR()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateS() - immediateR() + 1); |
| |
| return m_formatBuffer; |
| } |
| |
| appendInstructionName(opName()); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateR()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateS()); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeCompareAndBranchImmediate::format() |
| { |
| appendInstructionName(opBit() ? "cbnz" : "cbz"); |
| appendRegisterName(rt(), is64Bit()); |
| appendSeparator(); |
| appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate19())); |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeConditionalBranchImmediate::format() |
| { |
| bufferPrintf(" b.%-5.5s", conditionName(condition())); |
| appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate19())); |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeConditionalSelect::s_opNames[4] = { |
| "csel", "csinc", "csinv", "csneg" |
| }; |
| |
| const char* A64DOpcodeConditionalSelect::format() |
| { |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (op2() & 0x2) |
| return A64DOpcode::format(); |
| |
| if (rn() == rm() && (opNum() == 1 || opNum() == 2)) { |
| if (rn() == 31) { |
| appendInstructionName((opNum() == 1) ? "cset" : "csetm"); |
| appendRegisterName(rd(), is64Bit()); |
| } else { |
| appendInstructionName((opNum() == 1) ? "cinc" : "cinv"); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendZROrRegisterName(rn(), is64Bit()); |
| } |
| appendSeparator(); |
| appendString(conditionName(condition() ^ 0x1)); |
| |
| return m_formatBuffer; |
| } |
| |
| appendInstructionName(opName()); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendZROrRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendZROrRegisterName(rm(), is64Bit()); |
| appendSeparator(); |
| appendString(conditionName(condition())); |
| |
| return m_formatBuffer; |
| |
| } |
| |
| const char* const A64DOpcodeDataProcessing2Source::s_opNames[8] = { |
| 0, 0, "udiv", "sdiv", "lsl", "lsr", "asr", "ror" // We use the pseudo-op names for the shift/rotate instructions |
| }; |
| |
| const char* A64DOpcodeDataProcessing2Source::format() |
| { |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (!(opCode() & 0x3e)) |
| return A64DOpcode::format(); |
| |
| if (opCode() & 0x30) |
| return A64DOpcode::format(); |
| |
| if ((opCode() & 0x34) == 0x4) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rm(), is64Bit()); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeDataProcessing3Source::s_opNames[16] = { |
| "madd", "msub", "smaddl", "smsubl", "smulh", 0, 0, 0, |
| 0, 0, "umaddl", "umsubl", "umulh", 0, 0, 0 |
| }; |
| |
| const char* const A64DOpcodeDataProcessing3Source::s_pseudoOpNames[16] = { |
| "mul", "mneg", "smull", "smnegl", "smulh", 0, 0, 0, |
| 0, 0, "umull", "umnegl", "umulh", 0, 0, 0 |
| }; |
| |
| const char* A64DOpcodeDataProcessing3Source::format() |
| { |
| if (op54()) |
| return A64DOpcode::format(); |
| |
| if (opNum() > 12) |
| return A64DOpcode::format(); |
| |
| if (!is64Bit() && opNum() > 1) |
| return A64DOpcode::format(); |
| |
| if (!opName()) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| bool srcOneAndTwoAre64Bit = is64Bit() & !(opNum() & 0x2); |
| appendRegisterName(rn(), srcOneAndTwoAre64Bit); |
| appendSeparator(); |
| appendRegisterName(rm(), srcOneAndTwoAre64Bit); |
| |
| if ((ra() != 31) || !(opNum() & 0x4)) { |
| appendSeparator(); |
| appendRegisterName(ra(), is64Bit()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64OpcodeExceptionGeneration::format() |
| { |
| const char* opname = 0; |
| if (!op2()) { |
| switch (opc()) { |
| case 0x0: // SVC, HVC & SMC |
| switch (ll()) { |
| case 0x1: |
| opname = "svc"; |
| break; |
| case 0x2: |
| opname = "hvc"; |
| break; |
| case 0x3: |
| opname = "smc"; |
| break; |
| } |
| break; |
| case 0x1: // BRK |
| if (!ll()) |
| opname = "brk"; |
| break; |
| case 0x2: // HLT |
| if (!ll()) |
| opname = "hlt"; |
| break; |
| case 0x5: // DPCS1-3 |
| switch (ll()) { |
| case 0x1: |
| opname = "dpcs1"; |
| break; |
| case 0x2: |
| opname = "dpcs2"; |
| break; |
| case 0x3: |
| opname = "dpcs3"; |
| break; |
| } |
| break; |
| } |
| } |
| |
| if (!opname) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opname); |
| appendUnsignedImmediate(immediate16()); |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeExtract::format() |
| { |
| if (!op21() || !o0Bit()) |
| return A64DOpcode::format(); |
| |
| if (is64Bit() != nBit()) |
| return A64DOpcode::format(); |
| |
| if (is64Bit() && (immediateS() & 0x20)) |
| return A64DOpcode::format(); |
| |
| const char* opName = (rn() == rm()) ? "ror" : "extr"; |
| |
| appendInstructionName(opName); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| appendRegisterName(rm(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediateS()); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeFloatingPointCompare::format() |
| { |
| if (mBit()) |
| return A64DOpcode::format(); |
| |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (type() & 0x2) |
| return A64DOpcode::format(); |
| |
| if (op()) |
| return A64DOpcode::format(); |
| |
| if (opCode2() & 0x7) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| unsigned registerSize = type() + 2; |
| appendFPRegisterName(rn(), registerSize); |
| appendSeparator(); |
| if (opCode2() & 0x8) |
| bufferPrintf("#0.0"); |
| else |
| appendFPRegisterName(rm(), registerSize); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeFloatingPointDataProcessing1Source::s_opNames[16] = { |
| "fmov", "fabs", "fneg", "fsqrt", "fcvt", "fcvt", 0, "fcvt", |
| "frintn", "frintp", "frintm", "frintz", "frinta", 0, "frintx", "frinti" |
| }; |
| |
| const char* A64DOpcodeFloatingPointDataProcessing1Source::format() |
| { |
| if (mBit()) |
| return A64DOpcode::format(); |
| |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (opNum() > 16) |
| return A64DOpcode::format(); |
| |
| switch (type()) { |
| case 0: |
| if ((opNum() == 0x4) || (opNum() == 0x6) || (opNum() == 0xd)) |
| return A64DOpcode::format(); |
| break; |
| case 1: |
| if ((opNum() == 0x5) || (opNum() == 0x6) || (opNum() == 0xd)) |
| return A64DOpcode::format(); |
| break; |
| case 2: |
| return A64DOpcode::format(); |
| case 3: |
| if ((opNum() < 0x4) || (opNum() > 0x5)) |
| return A64DOpcode::format(); |
| break; |
| } |
| |
| appendInstructionName(opName()); |
| if ((opNum() >= 0x4) && (opNum() <= 0x7)) { |
| unsigned srcRegisterSize = type() ^ 0x2; // 0:s, 1:d & 3:h |
| unsigned destRegisterSize = (opNum() & 0x3) ^ 0x2; |
| appendFPRegisterName(rd(), destRegisterSize); |
| appendSeparator(); |
| appendFPRegisterName(rn(), srcRegisterSize); |
| } else { |
| unsigned registerSize = type() + 2; |
| appendFPRegisterName(rd(), registerSize); |
| appendSeparator(); |
| appendFPRegisterName(rn(), registerSize); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeFloatingPointDataProcessing2Source::s_opNames[16] = { |
| "fmul", "fdiv", "fadd", "fsub", "fmax", "fmin", "fmaxnm", "fminnm", "fnmul" |
| }; |
| |
| const char* A64DOpcodeFloatingPointDataProcessing2Source::format() |
| { |
| if (mBit()) |
| return A64DOpcode::format(); |
| |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (type() & 0x2) |
| return A64DOpcode::format(); |
| |
| if (opNum() > 8) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| unsigned registerSize = type() + 2; |
| appendFPRegisterName(rd(), registerSize); |
| appendSeparator(); |
| appendFPRegisterName(rn(), registerSize); |
| appendSeparator(); |
| appendFPRegisterName(rm(), registerSize); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeFloatingFixedPointConversions::s_opNames[4] = { |
| "fcvtzs", "fcvtzu", "scvtf", "ucvtf" |
| }; |
| |
| const char* A64DOpcodeFloatingFixedPointConversions::format() |
| { |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (type() & 0x2) |
| return A64DOpcode::format(); |
| |
| if (opcode() & 0x4) |
| return A64DOpcode::format(); |
| |
| if (!(rmode() & 0x1) && !(opcode() & 0x6)) |
| return A64DOpcode::format(); |
| |
| if ((rmode() & 0x1) && (opcode() & 0x6) == 0x2) |
| return A64DOpcode::format(); |
| |
| if (!(rmode() & 0x2) && !(opcode() & 0x6)) |
| return A64DOpcode::format(); |
| |
| if ((rmode() & 0x2) && (opcode() & 0x6) == 0x2) |
| return A64DOpcode::format(); |
| |
| if (!is64Bit() && scale() >= 32) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| unsigned FPRegisterSize = type() + 2; |
| bool destIsFP = !rmode(); |
| |
| if (destIsFP) { |
| appendFPRegisterName(rd(), FPRegisterSize); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| } else { |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendFPRegisterName(rn(), FPRegisterSize); |
| } |
| appendSeparator(); |
| appendUnsignedImmediate(64 - scale()); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeFloatingPointIntegerConversions::s_opNames[32] = { |
| "fcvtns", "fcvtnu", "scvtf", "ucvtf", "fcvtas", "fcvtau", "fmov", "fmov", |
| "fcvtps", "fcvtpu", 0, 0, 0, 0, "fmov", "fmov", |
| "fcvtms", "fcvtmu", 0, 0, 0, 0, 0, 0, |
| "fcvtzs", "fcvtzu", 0, 0, 0, 0, 0, 0 |
| }; |
| |
| const char* A64DOpcodeFloatingPointIntegerConversions::format() |
| { |
| if (sBit()) |
| return A64DOpcode::format(); |
| |
| if (type() == 0x3) |
| return A64DOpcode::format(); |
| |
| if (((rmode() & 0x1) || (rmode() & 0x2)) && (((opcode() & 0x6) == 0x2) || ((opcode() & 0x6) == 0x4))) |
| return A64DOpcode::format(); |
| |
| if ((type() == 0x2) && (!(opcode() & 0x4) || ((opcode() & 0x6) == 0x4))) |
| return A64DOpcode::format(); |
| |
| if (!type() && (rmode() & 0x1) && ((opcode() & 0x6) == 0x6)) |
| return A64DOpcode::format(); |
| |
| if (is64Bit() && type() == 0x2 && ((opNum() & 0xe) == 0x6)) |
| return A64DOpcode::format(); |
| |
| if (!opName()) |
| return A64DOpcode::format(); |
| |
| if ((opNum() & 0x1e) == 0xe) { |
| // Handle fmov to/from upper half of quad separately |
| if (!is64Bit() || (type() != 0x2)) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| if (opcode() & 0x1) { |
| // fmov Vd.D[1], Xn |
| bufferPrintf("V%u.D[1]", rd()); |
| appendSeparator(); |
| appendRegisterName(rn()); |
| } else { |
| // fmov Xd, Vn.D[1] |
| appendRegisterName(rd()); |
| appendSeparator(); |
| bufferPrintf("V%u.D[1]", rn()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| appendInstructionName(opName()); |
| unsigned FPRegisterSize = type() + 2; |
| bool destIsFP = ((opNum() == 2) || (opNum() == 3) || (opNum() == 7)); |
| |
| if (destIsFP) { |
| appendFPRegisterName(rd(), FPRegisterSize); |
| appendSeparator(); |
| appendRegisterName(rn(), is64Bit()); |
| } else { |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendFPRegisterName(rn(), FPRegisterSize); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeHint::s_opNames[6] = { |
| "nop", "yield", "wfe", "wfi", "sev", "sevl" |
| }; |
| |
| const char* A64DOpcodeHint::format() |
| { |
| appendInstructionName(opName()); |
| |
| if (immediate7() > 5) |
| appendUnsignedImmediate(immediate7()); |
| |
| return m_formatBuffer; |
| } |
| |
| // A zero in an entry of the table means the instruction is Unallocated |
| const char* const A64DOpcodeLoadStore::s_opNames[32] = { |
| "strb", "ldrb", "ldrsb", "ldrsb", "str", "ldr", "str", "ldr", |
| "strh", "ldrh", "ldrsh", "ldrsh", "str", "ldr", 0, 0, |
| "str", "ldr", "ldrsw", 0, "str", "ldr", 0, 0, |
| "str", "ldr", 0, 0, "str", "ldr", 0, 0 |
| }; |
| |
| // A zero in an entry of the table means the instruction is Unallocated |
| const char* const A64DOpcodeLoadStoreImmediate::s_unprivilegedOpNames[32] = { |
| "sttrb", "ldtrb", "ldtrsb", "ldtrsb", 0, 0, 0, 0, |
| "sttrh", "ldtrh", "ldtrsh", "ldtrsh", 0, 0, 0, 0, |
| "sttr", "ldtr", "ldtrsw", 0, 0, 0, 0, 0, |
| "sttr", "ldtr", 0, 0, 0, 0, 0, 0 |
| }; |
| |
| // A zero in an entry of the table means the instruction is Unallocated |
| const char* const A64DOpcodeLoadStoreImmediate::s_unscaledOpNames[32] = { |
| "sturb", "ldurb", "ldursb", "ldursb", "stur", "ldur", "stur", "ldur", |
| "sturh", "ldurh", "ldursh", "ldursh", "stur", "ldur", 0, 0, |
| "stur", "ldur", "ldursw", 0, "stur", "ldur", 0, 0, |
| "stur", "ldur", "prfum", 0, "stur", "ldur", 0, 0 |
| }; |
| |
| const char* A64DOpcodeLoadStoreImmediate::format() |
| { |
| const char* thisOpName; |
| |
| if (type() & 0x1) |
| thisOpName = opName(); |
| else if (!type()) |
| thisOpName = unscaledOpName(); |
| else |
| thisOpName = unprivilegedOpName(); |
| |
| if (!thisOpName) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(thisOpName); |
| if (vBit()) |
| appendFPRegisterName(rt(), size()); |
| else |
| appendRegisterName(rt(), is64BitRT()); |
| appendSeparator(); |
| appendCharacter('['); |
| appendSPOrRegisterName(rn()); |
| |
| switch (type()) { |
| case 0: // Unscaled Immediate |
| if (immediate9()) { |
| appendSeparator(); |
| appendSignedImmediate(immediate9()); |
| } |
| appendCharacter(']'); |
| break; |
| case 1: // Immediate Post-Indexed |
| appendCharacter(']'); |
| if (immediate9()) { |
| appendSeparator(); |
| appendSignedImmediate(immediate9()); |
| } |
| break; |
| case 2: // Unprivileged |
| if (immediate9()) { |
| appendSeparator(); |
| appendSignedImmediate(immediate9()); |
| } |
| appendCharacter(']'); |
| break; |
| case 3: // Immediate Pre-Indexed |
| if (immediate9()) { |
| appendSeparator(); |
| appendSignedImmediate(immediate9()); |
| } |
| appendCharacter(']'); |
| appendCharacter('!'); |
| break; |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeLoadStoreRegisterOffset::format() |
| { |
| const char* thisOpName = opName(); |
| |
| if (!thisOpName) |
| return A64DOpcode::format(); |
| |
| if (!(option() & 0x2)) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(thisOpName); |
| unsigned scale; |
| if (vBit()) { |
| appendFPRegisterName(rt(), size()); |
| scale = ((opc() & 2)<<1) | size(); |
| } else { |
| appendRegisterName(rt(), is64BitRT()); |
| scale = size(); |
| } |
| appendSeparator(); |
| appendCharacter('['); |
| appendSPOrRegisterName(rn()); |
| appendSeparator(); |
| appendZROrRegisterName(rm(), (option() & 0x3) == 0x3); |
| |
| unsigned shift = sBit() ? scale : 0; |
| |
| if (option() == 0x3) { |
| if (shift) { |
| appendSeparator(); |
| appendString("lsl "); |
| appendUnsignedImmediate(shift); |
| } |
| } else { |
| appendSeparator(); |
| appendString(optionName()); |
| if (shift) |
| appendUnsignedImmediate(shift); |
| } |
| |
| appendCharacter(']'); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeLoadStoreUnsignedImmediate::format() |
| { |
| const char* thisOpName = opName(); |
| |
| if (!thisOpName) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(thisOpName); |
| unsigned scale; |
| if (vBit()) { |
| appendFPRegisterName(rt(), size()); |
| scale = ((opc() & 2)<<1) | size(); |
| } else { |
| appendRegisterName(rt(), is64BitRT()); |
| scale = size(); |
| } |
| appendSeparator(); |
| appendCharacter('['); |
| appendSPOrRegisterName(rn()); |
| |
| if (immediate12()) { |
| appendSeparator(); |
| appendUnsignedImmediate(immediate12() << scale); |
| } |
| |
| appendCharacter(']'); |
| |
| return m_formatBuffer; |
| } |
| |
| // A zero in an entry of the table means the instruction is Unallocated |
| const char* const A64DOpcodeLogical::s_opNames[8] = { |
| "and", "bic", "orr", "orn", "eor", "eon", "ands", "bics" |
| }; |
| |
| const char* A64DOpcodeLogicalShiftedRegister::format() |
| { |
| if (!is64Bit() && immediate6() & 0x20) |
| return A64DOpcode::format(); |
| |
| if (isTst()) |
| appendInstructionName("tst"); |
| else { |
| if (isMov()) |
| appendInstructionName("mov"); |
| else |
| appendInstructionName(opName(opNumber())); |
| appendSPOrRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| } |
| |
| if (!isMov()) { |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| } |
| |
| appendZROrRegisterName(rm(), is64Bit()); |
| if (immediate6()) { |
| appendSeparator(); |
| appendShiftType(shift()); |
| appendUnsignedImmediate(immediate6()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| static unsigned highestBitSet(unsigned value) |
| { |
| unsigned result = 0; |
| |
| while (value >>= 1) |
| result++; |
| |
| return result; |
| } |
| |
| static uint64_t rotateRight(uint64_t value, unsigned width, unsigned shift) |
| { |
| uint64_t result = value; |
| |
| if (shift) |
| result = (value >> (shift % width)) | (value << (width - shift)); |
| |
| return result; |
| } |
| |
| static uint64_t replicate(uint64_t value, unsigned width) |
| { |
| uint64_t result = 0; |
| |
| for (unsigned totalBits = 0; totalBits < 64; totalBits += width) |
| result = (result << width) | value; |
| |
| return result; |
| } |
| |
| const char* A64DOpcodeLogicalImmediate::format() |
| { |
| if (!is64Bit() && nBit()) |
| return A64DOpcode::format(); |
| |
| unsigned len = highestBitSet(nBit() << 6 | (immediateS() ^ 0x3f)); |
| unsigned levels = (1 << len) - 1; // len number of 1 bits starting at LSB |
| |
| if ((immediateS() & levels) == levels) |
| return A64DOpcode::format(); |
| |
| unsigned r = immediateR() & levels; |
| unsigned s = immediateS() & levels; |
| unsigned eSize = 1 << len; |
| uint64_t pattern = rotateRight((1ull << (s + 1)) - 1, eSize, r); |
| |
| uint64_t immediate = replicate(pattern, eSize); |
| |
| if (!is64Bit()) |
| immediate &= 0xffffffffull; |
| |
| if (isTst()) |
| appendInstructionName("tst"); |
| else { |
| if (isMov()) |
| appendInstructionName("mov"); |
| else |
| appendInstructionName(opName(opNumber())); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| } |
| if (!isMov()) { |
| appendRegisterName(rn(), is64Bit()); |
| appendSeparator(); |
| } |
| appendUnsignedImmediate64(immediate); |
| |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeMoveWide::s_opNames[4] = { "movn", "", "movz", "movk" }; |
| |
| const char* A64DOpcodeMoveWide::format() |
| { |
| if (opc() == 1) |
| return A64DOpcode::format(); |
| if (!size() && hw() >= 2) |
| return A64DOpcode::format(); |
| |
| appendInstructionName(opName()); |
| appendRegisterName(rd(), is64Bit()); |
| appendSeparator(); |
| appendUnsignedImmediate(immediate16()); |
| if (hw()) { |
| appendSeparator(); |
| appendShiftAmount(hw()); |
| } |
| |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeTestAndBranchImmediate::format() |
| { |
| appendInstructionName(opBit() ? "tbnz" : "tbz"); |
| appendRegisterName(rt()); |
| appendSeparator(); |
| appendUnsignedImmediate(bitNumber()); |
| appendSeparator(); |
| appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate14())); |
| return m_formatBuffer; |
| } |
| |
| const char* A64DOpcodeUnconditionalBranchImmediate::format() |
| { |
| appendInstructionName(op() ? "bl" : "b"); |
| appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate26())); |
| return m_formatBuffer; |
| } |
| |
| const char* const A64DOpcodeUnconditionalBranchRegister::s_opNames[8] = { "br", "blr", "ret", "", "eret", "drps", "", "" }; |
| |
| const char* A64DOpcodeUnconditionalBranchRegister::format() |
| { |
| unsigned opcValue = opc(); |
| if (opcValue == 3 || opcValue > 5) |
| return A64DOpcode::format(); |
| if (((opcValue & 0xe) == 0x4) && rn() != 0x1f) |
| return A64DOpcode::format(); |
| appendInstructionName(opName()); |
| if (opcValue <= 2) |
| appendRegisterName(rn()); |
| return m_formatBuffer; |
| } |
| |
| } } // namespace JSC::ARM64Disassembler |