#define _CRT_SECURE_NO_WARNINGS #include #include #include #include "binaryninjaapi.h" // registers, etc. #include "arch_armv7.h" #include "spec.h" #include "disassembler.h" #include "il.h" using namespace BinaryNinja; using namespace armv7; using namespace std; #if defined(_MSC_VER) #define snprintf _snprintf #endif static Ref get_msr_op_enum() { EnumerationBuilder builder; builder.AddMemberWithValue("msp", REGS_MSP); builder.AddMemberWithValue("psp", REGS_PSP); builder.AddMemberWithValue("basepri", REGS_BASEPRI); builder.AddMemberWithValue("basepri_max", REGS_BASEPRI_MAX); builder.AddMemberWithValue("primask", REGS_PRIMASK); builder.AddMemberWithValue("faultmask", REGS_FAULTMASK); builder.AddMemberWithValue("control", REGS_CONTROL); builder.AddMemberWithValue("ipsr", REGS_IPSR); builder.AddMemberWithValue("epsr", REGS_EPSR); builder.AddMemberWithValue("iepsr", REGS_IEPSR); builder.AddMemberWithValue("apsr", REGS_APSR); builder.AddMemberWithValue("apsr_g", REGS_APSR_G); builder.AddMemberWithValue("apsr_nzcvq", REGS_APSR_NZCVQ); builder.AddMemberWithValue("apsr_nzcvqg", REGS_APSR_NZCVQG); builder.AddMemberWithValue("iapsr", REGS_IAPSR); builder.AddMemberWithValue("iapsr_g", REGS_IAPSR_G); builder.AddMemberWithValue("iapsr_nzcvq", REGS_IAPSR_NZCVQ); builder.AddMemberWithValue("iapsr_nzcvqg", REGS_IAPSR_NZCVQG); builder.AddMemberWithValue("eapsr", REGS_EAPSR); builder.AddMemberWithValue("eapsr_g", REGS_EAPSR_G); builder.AddMemberWithValue("eapsr_nzcvq", REGS_EAPSR_NZCVQ); builder.AddMemberWithValue("eapsr_nzcvqg", REGS_EAPSR_NZCVQG); builder.AddMemberWithValue("xpsr", REGS_XPSR); builder.AddMemberWithValue("xpsr_g", REGS_XPSR_G); builder.AddMemberWithValue("xpsr_nzcvq", REGS_XPSR_NZCVQ); builder.AddMemberWithValue("xpsr_nzcvqg", REGS_XPSR_NZCVQG); builder.AddMemberWithValue("cpsr", REGS_CPSR); builder.AddMemberWithValue("cpsr_c", REGS_CPSR_C); builder.AddMemberWithValue("cpsr_x", REGS_CPSR_X); builder.AddMemberWithValue("cpsr_xc", REGS_CPSR_XC); builder.AddMemberWithValue("cpsr_s", REGS_CPSR_S); builder.AddMemberWithValue("cpsr_sc", REGS_CPSR_SC); builder.AddMemberWithValue("cpsr_sx", REGS_CPSR_SX); builder.AddMemberWithValue("cpsr_sxc", REGS_CPSR_SXC); builder.AddMemberWithValue("cpsr_f", REGS_CPSR_F); builder.AddMemberWithValue("cpsr_fc", REGS_CPSR_FC); builder.AddMemberWithValue("cpsr_fx", REGS_CPSR_FX); builder.AddMemberWithValue("cpsr_fxc", REGS_CPSR_FXC); builder.AddMemberWithValue("cpsr_fs", REGS_CPSR_FS); builder.AddMemberWithValue("cpsr_fsc", REGS_CPSR_FSC); builder.AddMemberWithValue("cpsr_fsx", REGS_CPSR_FSX); builder.AddMemberWithValue("cpsr_fsxc", REGS_CPSR_FSXC); builder.AddMemberWithValue("spsr", REGS_SPSR); builder.AddMemberWithValue("spsr_c", REGS_SPSR_C); builder.AddMemberWithValue("spsr_x", REGS_SPSR_X); builder.AddMemberWithValue("spsr_xc", REGS_SPSR_XC); builder.AddMemberWithValue("spsr_s", REGS_SPSR_S); builder.AddMemberWithValue("spsr_sc", REGS_SPSR_SC); builder.AddMemberWithValue("spsr_sx", REGS_SPSR_SX); builder.AddMemberWithValue("spsr_sxc", REGS_SPSR_SXC); builder.AddMemberWithValue("spsr_f", REGS_SPSR_F); builder.AddMemberWithValue("spsr_fc", REGS_SPSR_FC); builder.AddMemberWithValue("spsr_fx", REGS_SPSR_FX); builder.AddMemberWithValue("spsr_fxc", REGS_SPSR_FXC); builder.AddMemberWithValue("spsr_fs", REGS_SPSR_FS); builder.AddMemberWithValue("spsr_fsc", REGS_SPSR_FSC); builder.AddMemberWithValue("spsr_fsx", REGS_SPSR_FSX); builder.AddMemberWithValue("spsr_fsxc", REGS_SPSR_FSXC); builder.AddMemberWithValue("apsr_nzcv", REGS_APSR_NZCV); Ref _enum = builder.Finalize(); return _enum; } /* class Architecture from binaryninjaapi.h */ class Thumb2Architecture: public ArmCommonArchitecture { protected: virtual std::string GetAssemblerTriple() override { if(m_endian == BigEndian) return "thumbv7eb-none-none"; return "thumbv7-none-none"; } bool populateDecomposeRequest(decomp_request *req, const uint8_t *data, size_t len, uint64_t addr, int inIfThen, int inIfThenLast) { if (!data || len < 2) return false; req->instr_word16 = 0; req->instr_word32 = 0; if(m_endian == LittleEndian) { req->instr_word16 = *(uint16_t *)data; if(len >= 4) { req->instr_word32 = ((*(uint16_t *)data)<<16) | *(uint16_t *)(data + 2); } } else { req->instr_word16 = (data[0] << 8) | data[1]; if(len >= 4) { req->instr_word32 = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; } } req->arch = ARCH_ARMv7; req->instrSet = INSTRSET_THUMB2; req->inIfThen = inIfThen; req->inIfThenLast = inIfThenLast; req->carry_in = 0; req->addr = (uint32_t)addr; return true; } virtual bool Disassemble(const uint8_t* data, uint64_t addr, size_t maxLen, decomp_result& result) { (void)addr; (void)maxLen; decomp_request request; if (!populateDecomposeRequest(&request, data, maxLen, addr, IFTHEN_UNKNOWN, IFTHENLAST_UNKNOWN)) return false; memset(&result, 0, sizeof(result)); if (thumb_decompose(&request, &result) != STATUS_OK) return false; return true; } public: /* initialization list */ Thumb2Architecture(const char* name, BNEndianness endian): ArmCommonArchitecture(name, endian) { } /*************************************************************************/ virtual size_t GetMaxInstructionLength() const override { return 18; // IT blocks can have up to four following associated instructions } virtual size_t GetInstructionAlignment() const override { return 2; } virtual size_t GetOpcodeDisplayLength() const override { return 4; } /* think "GetInstructionBranchBehavior()" populates struct Instruction Info (api/binaryninjaapi.h) which extends struct BNInstructionInfo (core/binaryninjacore.h) tasks: 1) set the length 2) invoke AddBranch() for every non-sequential execution possibility */ virtual bool GetInstructionInfo(const uint8_t* data, uint64_t addr, size_t maxLen, InstructionInfo& result) override { decomp_request request; decomp_result decomp; if (!populateDecomposeRequest(&request, data, maxLen, addr, IFTHEN_UNKNOWN, IFTHENLAST_UNKNOWN)) return false; if (thumb_decompose(&request, &decomp) != STATUS_OK) return false; if ((decomp.instrSize / 8) > maxLen) return false; if ((decomp.status & STATUS_UNDEFINED) || (!decomp.format)) return false; result.length = decomp.instrSize / 8; if ((decomp.mnem == armv7::ARMV7_IT) && (decomp.fields[FIELD_mask] != 0)) { // IT block: consume all instructions and handle contained branches uint32_t offset = decomp.instrSize / 8; uint32_t mask = decomp.fields[FIELD_mask]; uint32_t cond = decomp.fields[FIELD_firstcond]; size_t instrCount; if (mask & 1) instrCount = 4; else if (mask & 2) instrCount = 3; else if (mask & 4) instrCount = 2; else instrCount = 1; // First branch/return in each condition path bool trueTerminated = false; bool falseTerminated = false; bool trueBranched = false; bool falseBranched = false; bool trueReturned = false; bool falseReturned = false; uint64_t trueBranchTargetAddr = 0; uint64_t falseBranchTargetAddr = 0; for (size_t i = 0; i < instrCount; i++) { bool isTrue = (i == 0) || (((mask >> (4 - i)) & 1) == (cond & 1)); InstructionInfo innerResult; if (!GetInstructionInfo(data + offset, addr + offset, maxLen - offset, innerResult)) break; if ((offset + innerResult.length) > maxLen) break; bool& terminated = isTrue ? trueTerminated : falseTerminated; bool& branched = isTrue ? trueBranched : falseBranched; bool& returned = isTrue ? trueReturned : falseReturned; uint64_t& branchTarget = isTrue ? trueBranchTargetAddr : falseBranchTargetAddr; // Only process if the conditional branch we're following isn't terminated // Otherwise, just track if the arch is switching and the offset if (!terminated) { for (size_t j = 0; j < innerResult.branchCount; j++) { switch (innerResult.branchType[j]) { case UnconditionalBranch: case TrueBranch: case FalseBranch: branched = true; terminated = true; branchTarget = innerResult.branchTarget[j]; break; case FunctionReturn: returned = true; terminated = true; break; case CallDestination: result.AddBranch(CallDestination, innerResult.branchTarget[j], innerResult.branchArch[j] ? m_armArch : this); break; case UnresolvedBranch: case IndirectBranch: case ExceptionBranch: // We don't know the branch target so just set terminated terminated = true; break; default: break; } } } if (innerResult.archTransitionByTargetAddr) result.archTransitionByTargetAddr = true; offset += innerResult.length; } result.length = offset; uint64_t fallThroughAddr = (addr + offset) & 0xffffffffLL; // The targets for true/false branches either go somewhere or fall through to the next instr uint64_t trueTargetAddr = trueBranched ? trueBranchTargetAddr : fallThroughAddr; uint64_t falseTargetAddr = falseBranched ? falseBranchTargetAddr : fallThroughAddr; if (trueReturned && falseReturned) { // If both paths return result.AddBranch(FunctionReturn); } else if (trueTargetAddr != falseTargetAddr) { // True and false go to different locations (either branch or fallthrough) result.AddBranch(TrueBranch, trueTargetAddr, this); result.AddBranch(FalseBranch, falseTargetAddr, this); } else if (trueTargetAddr != fallThroughAddr) { // True and false branch to the same location that isn't the fallthrough address result.AddBranch(UnconditionalBranch, trueTargetAddr, this); } return true; } switch (decomp.mnem) { case armv7::ARMV7_LDR: if ((decomp.format->operands[0].type == OPERAND_FORMAT_REG) && (decomp.fields[decomp.format->operands[0].field0] == 15)) { result.AddBranch(UnresolvedBranch); result.archTransitionByTargetAddr = true; } break; case ARMV7_LDM: case ARMV7_LDMDA: case ARMV7_LDMDB: case ARMV7_LDMIA: // defaults to ARMV7_LDM case ARMV7_LDMIB: if ((decomp.format->operands[0].type == OPERAND_FORMAT_REG) && (decomp.fields[decomp.format->operands[0].field0] == 15)) { result.AddBranch(UnresolvedBranch); result.archTransitionByTargetAddr = true; } break; case armv7::ARMV7_ADD: case armv7::ARMV7_ADC: case armv7::ARMV7_EOR: case armv7::ARMV7_SUB: case armv7::ARMV7_SBC: case armv7::ARMV7_RSB: case armv7::ARMV7_RSC: case armv7::ARMV7_BIC: case armv7::ARMV7_ORR: case armv7::ARMV7_LSL: case armv7::ARMV7_LSR: case armv7::ARMV7_ASR: case armv7::ARMV7_ROR: case armv7::ARMV7_RRX: case armv7::ARMV7_MOV: case armv7::ARMV7_MVN: case armv7::ARMV7_MOVW: case armv7::ARMV7_MOVT: case armv7::ARMV7_LDRT: case armv7::ARMV7_LDRH: case armv7::ARMV7_LDRHT: case armv7::ARMV7_LDRB: case armv7::ARMV7_LDRBT: case armv7::ARMV7_LDRSH: case armv7::ARMV7_LDRSHT: case armv7::ARMV7_LDRSB: case armv7::ARMV7_LDRSBT: case armv7::ARMV7_LDRD: case armv7::ARMV7_ADR: case armv7::ARMV7_UBFX: case armv7::ARMV7_UXTAB: case armv7::ARMV7_UXTB: case armv7::ARMV7_UXTH: case armv7::ARMV7_MUL: case armv7::ARMV7_SDIV: case armv7::ARMV7_UDIV: case armv7::ARMV7_SBFX: case armv7::ARMV7_SXTB: case armv7::ARMV7_SXTH: case armv7::ARMV7_BFC: case armv7::ARMV7_BFI: case armv7::ARMV7_CLZ: if ((decomp.format->operands[0].type == OPERAND_FORMAT_REG) && (decomp.fields[decomp.format->operands[0].field0] == 15)) result.AddBranch(UnresolvedBranch); break; case armv7::ARMV7_B: if ((!(decomp.format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)) || (decomp.fields[FIELD_cond] == COND_AL)) { result.AddBranch(UnconditionalBranch, (decomp.fields[decomp.format->operands[0].field0] + 4 + addr) & 0xffffffffLL, this); } else { result.AddBranch(TrueBranch, (decomp.fields[decomp.format->operands[0].field0] + 4 + addr) & 0xffffffffLL, this); result.AddBranch(FalseBranch, (addr + result.length) & 0xffffffffLL, this); } break; case armv7::ARMV7_BX: if ((!(decomp.format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)) || (decomp.fields[FIELD_cond] == COND_AL)) { if ((decomp.format->operands[0].type == OPERAND_FORMAT_LR) || ((decomp.format->operands[0].type == OPERAND_FORMAT_REG) && (decomp.fields[decomp.format->operands[0].field0] == 14))) { result.AddBranch(FunctionReturn); result.archTransitionByTargetAddr = true; } else { result.AddBranch(UnresolvedBranch); result.archTransitionByTargetAddr = true; } } break; case armv7::ARMV7_BL: if ((!(decomp.format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)) || (decomp.fields[FIELD_cond] == COND_AL)) { result.AddBranch(CallDestination, (decomp.fields[decomp.format->operands[0].field0] + 4 + addr) & 0xffffffffLL, this); } break; case armv7::ARMV7_BLX: if ((!(decomp.format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)) || (decomp.fields[FIELD_cond] == COND_AL)) { if (decomp.format->operands[0].type == OPERAND_FORMAT_IMM) { uint64_t target; if (addr & 2) target = (decomp.fields[decomp.format->operands[0].field0] + 2 + addr) & 0xffffffffLL; else target = (decomp.fields[decomp.format->operands[0].field0] + 4 + addr) & 0xffffffffLL; result.AddBranch(CallDestination, target, m_armArch); } else if ((decomp.format->operands[0].type == OPERAND_FORMAT_LR) || ((decomp.format->operands[0].type == OPERAND_FORMAT_REG) && (decomp.fields[decomp.format->operands[0].field0] == 14))) { result.AddBranch(FunctionReturn); // initially indicate "blx lr" as a return since this is common and conservative; subsequent analysis determines if it's a function call } result.archTransitionByTargetAddr = true; } break; case armv7::ARMV7_CBNZ: case armv7::ARMV7_CBZ: result.AddBranch(TrueBranch, (decomp.fields[decomp.format->operands[1].field0] + addr) & 0xffffffffLL, this); result.AddBranch(FalseBranch, (addr + result.length) & 0xffffffffLL, this); break; case armv7::ARMV7_POP: if ((decomp.format->operands[0].type == OPERAND_FORMAT_REGISTERS) && (decomp.fields[FIELD_registers] & (1 << 15))) { result.AddBranch(FunctionReturn); } break; case armv7::ARMV7_SVC: result.AddBranch(SystemCall); break; case armv7::ARMV7_TBB: case armv7::ARMV7_TBH: result.AddBranch(UnresolvedBranch); break; case armv7::ARMV7_UDF: result.AddBranch(ExceptionBranch); break; default: break; } return true; } /* populate the vector result with InstructionTextToken */ virtual bool GetInstructionText(const uint8_t* data, uint64_t addr, size_t& len, vector& result) override { decomp_request request; decomp_result decomp; if (!populateDecomposeRequest(&request, data, len, addr, IFTHEN_UNKNOWN, IFTHENLAST_UNKNOWN)) return false; if (thumb_decompose(&request, &decomp) != STATUS_OK) return false; if (decomp.status & STATUS_UNDEFINED) { len = decomp.instrSize / 8; result.emplace_back(InstructionToken, "undefined"); return true; } if ((decomp.instrSize / 8) > len) return false; if (!decomp.format) return false; char padding[9]; memset(padding, 0x20, sizeof(padding)); string operation = get_thumb_operation_name(&decomp); size_t operationLen = operation.size(); if (operationLen < 8) { padding[8-operationLen] = '\0'; } else padding[1] = '\0'; result.emplace_back(InstructionToken, operation); if (decomp.format->operandCount > 0) result.emplace_back(TextToken, padding); for (size_t i = 0; i < decomp.format->operandCount; i++) { int j; const instruction_operand_format& operand = decomp.format->operands[i]; uint32_t value, r, bits, shift_t, shift_n, add, imm32, reg; char buf[16]; char offset[32]; char regname[16]; char secondname[16]; bool first; switch (operand.type) { case OPERAND_FORMAT_MEMORY_ONE_REG: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_ONE_REG_IMM: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(value) { result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "#"); if (value < 10) snprintf(offset, sizeof(offset), "%d", value); else snprintf(offset, sizeof(offset), "0x%x", value); result.emplace_back(IntegerToken, offset, value); } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_ONE_REG_NEG_IMM: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "#"); value = decomp.fields[operand.field1]; if (value < 10) snprintf(offset, sizeof(offset), "-%d", value); else snprintf(offset, sizeof(offset), "-0x%x", value); result.emplace_back(IntegerToken, offset, -(int64_t)value); result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_ONE_REG_ADD_IMM: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) strcpy(regname, "undefined"); if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(decomp.fields[FIELD_add] && value==0) { /* omit the case where we are adding 0 */ while(0); } else { result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "#"); const char *fmt; if(decomp.fields[FIELD_add]) fmt = (value < 10) ? "%d":"0x%x"; else fmt = (value < 10) ? "-%d":"-0x%x"; snprintf(offset, sizeof(offset), fmt, value); result.emplace_back(IntegerToken, offset, -(int64_t)value); } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_ONE_REG_OPTIONAL_IMM: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if (value != 0) { result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "#"); if (value < 10) snprintf(offset, sizeof(offset), "%d", value); else snprintf(offset, sizeof(offset), "0x%x", value); result.emplace_back(IntegerToken, offset, value); } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_ONE_REG_OPTIONAL_ADD_IMM: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(!(decomp.fields[FIELD_add] && value == 0)) { result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "#"); if(decomp.fields[FIELD_add]) { if (value < 10) snprintf(offset, sizeof(offset), "%d", value); else snprintf(offset, sizeof(offset), "0x%x", value); result.emplace_back(IntegerToken, offset, value); } else { if (value < 10) snprintf(offset, sizeof(offset), "-%d", value); else snprintf(offset, sizeof(offset), "-0x%x", value); result.emplace_back(IntegerToken, offset, -(int64_t)value); } } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_TWO_REG: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(0 != get_reg_name(value, secondname)) { strcpy(secondname, "undefined"); } result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(RegisterToken, secondname); result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_TWO_REG_SHIFT: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(0 != get_reg_name(value, secondname)) { strcpy(secondname, "undefined"); } result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(RegisterToken, secondname); shift_t = decomp.fields[FIELD_shift_t]; shift_n = decomp.fields[FIELD_shift_n]; if(shift_n != 0) { result.emplace_back(OperandSeparatorToken, ", "); if(shift_t == SRType_LSL) { snprintf(offset, sizeof(offset), "%d", shift_n); result.emplace_back(TextToken, "lsl #"); result.emplace_back(IntegerToken, offset, shift_n); } else if(shift_t == SRType_LSR) { snprintf(offset, sizeof(offset), "%d", shift_n); result.emplace_back(TextToken, "lsr #"); result.emplace_back(IntegerToken, offset, shift_n); } else if(shift_t == SRType_ASR) { snprintf(offset, sizeof(offset), "%d", shift_n); result.emplace_back(TextToken, "asr #"); result.emplace_back(IntegerToken, offset, shift_n); } else if(shift_t == SRType_RRX) { if(shift_n != 1) { snprintf(offset, sizeof(offset), "%d", shift_n); result.emplace_back(TextToken, "rrx #"); result.emplace_back(IntegerToken, offset, shift_n); } else { result.emplace_back(TextToken, "rrx"); } } else if(shift_t == SRType_ROR) { snprintf(offset, sizeof(offset), "%d", shift_n); result.emplace_back(TextToken, "ror #"); result.emplace_back(IntegerToken, offset, shift_n); } else { result.emplace_back(TextToken, "undefined"); } } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_ROTATION: value = decomp.fields[FIELD_rotation]; if(value) { if(i>0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "ror #"); snprintf(buf, sizeof(buf), "%d", value); result.emplace_back(IntegerToken, buf, 1); } break; case OPERAND_FORMAT_MEMORY_TWO_REG_LSL_ONE: value = decomp.fields[operand.field0]; if(0 != get_reg_name(value, regname)) { strcpy(regname, "undefined"); } if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, regname); value = decomp.fields[operand.field1]; if(0 != get_reg_name(value, secondname)) { strcpy(secondname, "undefined"); } result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(RegisterToken, secondname); result.emplace_back(TextToken, ", lsl #"); result.emplace_back(IntegerToken, "1", 1); result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_SP_IMM: if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, "sp"); value = decomp.fields[operand.field0]; if(value) { result.emplace_back(TextToken, ", #"); if (value < 10) snprintf(offset, sizeof(offset), "%d", value); else snprintf(offset, sizeof(offset), "0x%x", value); result.emplace_back(IntegerToken, offset, value); } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_SP_OPTIONAL_IMM: if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(BeginMemoryOperandToken, "["); result.emplace_back(RegisterToken, "sp"); value = decomp.fields[operand.field0]; if (value != 0) { result.emplace_back(TextToken, ", #"); if (value < 10) snprintf(offset, sizeof(offset), "%d", value); else snprintf(offset, sizeof(offset), "0x%x", value); result.emplace_back(IntegerToken, offset, value); } result.emplace_back(EndMemoryOperandToken, "]"); break; case OPERAND_FORMAT_MEMORY_PC: if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); result.emplace_back(TextToken, "["); result.emplace_back(RegisterToken, "pc"); result.emplace_back(TextToken, "]"); break; case OPERAND_FORMAT_IMM64: /* 64 bit immediate fields */ if (i > 0) result.emplace_back(OperandSeparatorToken, ", "); if(IS_FIELD_PRESENT(&decomp, FIELD_imm64h) && IS_FIELD_PRESENT(&decomp, FIELD_imm64l)){ uint64_t imm64 = 0; imm64 |= decomp.fields[FIELD_imm64h]; imm64 <<= 32; imm64 |= decomp.fields[FIELD_imm64l]; /* this will be '#' for lone numerals, 'p' for coprocessor, etc. */ if (operand.prefix[0] != 0) result.emplace_back(TextToken, operand.prefix); if(imm64 < 10) snprintf(offset, sizeof(offset), "%" PRIu64, imm64); else snprintf(offset, sizeof(offset), "0x%" PRIx64, imm64); result.emplace_back(IntegerToken, offset, imm64); } /* could be closing '}' for stuff like coprocessor {