//===-- DNBArchImpl.cpp -----------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // Created by Greg Clayton on 6/25/07. // //===----------------------------------------------------------------------===// #if defined (__arm__) #include "MacOSX/arm/DNBArchImpl.h" #include "MacOSX/MachProcess.h" #include "MacOSX/MachThread.h" #include "DNBBreakpoint.h" #include "DNBLog.h" #include "DNBRegisterInfo.h" #include "DNB.h" #include "ARM_GCC_Registers.h" #include "ARM_DWARF_Registers.h" #include // BCR address match type #define BCR_M_IMVA_MATCH ((uint32_t)(0u << 21)) #define BCR_M_CONTEXT_ID_MATCH ((uint32_t)(1u << 21)) #define BCR_M_IMVA_MISMATCH ((uint32_t)(2u << 21)) #define BCR_M_RESERVED ((uint32_t)(3u << 21)) // Link a BVR/BCR or WVR/WCR pair to another #define E_ENABLE_LINKING ((uint32_t)(1u << 20)) // Byte Address Select #define BAS_IMVA_PLUS_0 ((uint32_t)(1u << 5)) #define BAS_IMVA_PLUS_1 ((uint32_t)(1u << 6)) #define BAS_IMVA_PLUS_2 ((uint32_t)(1u << 7)) #define BAS_IMVA_PLUS_3 ((uint32_t)(1u << 8)) #define BAS_IMVA_0_1 ((uint32_t)(3u << 5)) #define BAS_IMVA_2_3 ((uint32_t)(3u << 7)) #define BAS_IMVA_ALL ((uint32_t)(0xfu << 5)) // Break only in priveleged or user mode #define S_RSVD ((uint32_t)(0u << 1)) #define S_PRIV ((uint32_t)(1u << 1)) #define S_USER ((uint32_t)(2u << 1)) #define S_PRIV_USER ((S_PRIV) | (S_USER)) #define BCR_ENABLE ((uint32_t)(1u)) #define WCR_ENABLE ((uint32_t)(1u)) // Watchpoint load/store #define WCR_LOAD ((uint32_t)(1u << 3)) #define WCR_STORE ((uint32_t)(1u << 4)) //#define DNB_ARCH_MACH_ARM_DEBUG_SW_STEP 1 static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 }; static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE }; // ARM constants used during decoding #define REG_RD 0 #define LDM_REGLIST 1 #define PC_REG 15 #define PC_REGLIST_BIT 0x8000 // ARM conditions #define COND_EQ 0x0 #define COND_NE 0x1 #define COND_CS 0x2 #define COND_HS 0x2 #define COND_CC 0x3 #define COND_LO 0x3 #define COND_MI 0x4 #define COND_PL 0x5 #define COND_VS 0x6 #define COND_VC 0x7 #define COND_HI 0x8 #define COND_LS 0x9 #define COND_GE 0xA #define COND_LT 0xB #define COND_GT 0xC #define COND_LE 0xD #define COND_AL 0xE #define COND_UNCOND 0xF #define MASK_CPSR_T (1u << 5) #define MASK_CPSR_J (1u << 24) #define MNEMONIC_STRING_SIZE 32 #define OPERAND_STRING_SIZE 128 void DNBArchMachARM::Initialize() { DNBArchPluginInfo arch_plugin_info = { CPU_TYPE_ARM, DNBArchMachARM::Create, DNBArchMachARM::GetRegisterSetInfo, DNBArchMachARM::SoftwareBreakpointOpcode }; // Register this arch plug-in with the main protocol class DNBArchProtocol::RegisterArchPlugin (arch_plugin_info); } DNBArchProtocol * DNBArchMachARM::Create (MachThread *thread) { return new DNBArchMachARM (thread); } const uint8_t * const DNBArchMachARM::SoftwareBreakpointOpcode (nub_size_t byte_size) { switch (byte_size) { case 2: return g_thumb_breakpooint_opcode; case 4: return g_arm_breakpoint_opcode; } return NULL; } uint32_t DNBArchMachARM::GetCPUType() { return CPU_TYPE_ARM; } uint64_t DNBArchMachARM::GetPC(uint64_t failValue) { // Get program counter if (GetGPRState(false) == KERN_SUCCESS) return m_state.context.gpr.__pc; return failValue; } kern_return_t DNBArchMachARM::SetPC(uint64_t value) { // Get program counter kern_return_t err = GetGPRState(false); if (err == KERN_SUCCESS) { m_state.context.gpr.__pc = value; err = SetGPRState(); } return err == KERN_SUCCESS; } uint64_t DNBArchMachARM::GetSP(uint64_t failValue) { // Get stack pointer if (GetGPRState(false) == KERN_SUCCESS) return m_state.context.gpr.__sp; return failValue; } kern_return_t DNBArchMachARM::GetGPRState(bool force) { int set = e_regSetGPR; // Check if we have valid cached registers if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) return KERN_SUCCESS; // Read the registers from our thread mach_msg_type_number_t count = ARM_THREAD_STATE_COUNT; kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_THREAD_STATE, (thread_state_t)&m_state.context.gpr, &count); uint32_t *r = &m_state.context.gpr.__r[0]; DNBLogThreadedIf(LOG_THREAD, "thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x (count = %u) regs r0=%8.8x r1=%8.8x r2=%8.8x r3=%8.8x r4=%8.8x r5=%8.8x r6=%8.8x r7=%8.8x r8=%8.8x r9=%8.8x r10=%8.8x r11=%8.8x s12=%8.8x sp=%8.8x lr=%8.8x pc=%8.8x cpsr=%8.8x", m_thread->ThreadID(), ARM_THREAD_STATE, ARM_THREAD_STATE_COUNT, kret, count, r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], r[16]); m_state.SetError(set, Read, kret); return kret; } kern_return_t DNBArchMachARM::GetVFPState(bool force) { int set = e_regSetVFP; // Check if we have valid cached registers if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) return KERN_SUCCESS; // Read the registers from our thread mach_msg_type_number_t count = ARM_VFP_STATE_COUNT; kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_VFP_STATE, (thread_state_t)&m_state.context.vfp, &count); if (DNBLogEnabledForAny (LOG_THREAD)) { uint32_t *r = &m_state.context.vfp.__r[0]; DNBLogThreaded ("thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x (count => %u)", m_thread->ThreadID(), ARM_THREAD_STATE, ARM_THREAD_STATE_COUNT, kret, count); DNBLogThreaded(" s0=%8.8x s1=%8.8x s2=%8.8x s3=%8.8x s4=%8.8x s5=%8.8x s6=%8.8x s7=%8.8x",r[ 0],r[ 1],r[ 2],r[ 3],r[ 4],r[ 5],r[ 6],r[ 7]); DNBLogThreaded(" s8=%8.8x s9=%8.8x s10=%8.8x s11=%8.8x s12=%8.8x s13=%8.8x s14=%8.8x s15=%8.8x",r[ 8],r[ 9],r[10],r[11],r[12],r[13],r[14],r[15]); DNBLogThreaded(" s16=%8.8x s17=%8.8x s18=%8.8x s19=%8.8x s20=%8.8x s21=%8.8x s22=%8.8x s23=%8.8x",r[16],r[17],r[18],r[19],r[20],r[21],r[22],r[23]); DNBLogThreaded(" s24=%8.8x s25=%8.8x s26=%8.8x s27=%8.8x s28=%8.8x s29=%8.8x s30=%8.8x s31=%8.8x",r[24],r[25],r[26],r[27],r[28],r[29],r[30],r[31]); DNBLogThreaded(" s32=%8.8x s33=%8.8x s34=%8.8x s35=%8.8x s36=%8.8x s37=%8.8x s38=%8.8x s39=%8.8x",r[32],r[33],r[34],r[35],r[36],r[37],r[38],r[39]); DNBLogThreaded(" s40=%8.8x s41=%8.8x s42=%8.8x s43=%8.8x s44=%8.8x s45=%8.8x s46=%8.8x s47=%8.8x",r[40],r[41],r[42],r[43],r[44],r[45],r[46],r[47]); DNBLogThreaded(" s48=%8.8x s49=%8.8x s50=%8.8x s51=%8.8x s52=%8.8x s53=%8.8x s54=%8.8x s55=%8.8x",r[48],r[49],r[50],r[51],r[52],r[53],r[54],r[55]); DNBLogThreaded(" s56=%8.8x s57=%8.8x s58=%8.8x s59=%8.8x s60=%8.8x s61=%8.8x s62=%8.8x s63=%8.8x fpscr=%8.8x",r[56],r[57],r[58],r[59],r[60],r[61],r[62],r[63],r[64]); } m_state.SetError(set, Read, kret); return kret; } kern_return_t DNBArchMachARM::GetEXCState(bool force) { int set = e_regSetEXC; // Check if we have valid cached registers if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) return KERN_SUCCESS; // Read the registers from our thread mach_msg_type_number_t count = ARM_EXCEPTION_STATE_COUNT; kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_EXCEPTION_STATE, (thread_state_t)&m_state.context.exc, &count); m_state.SetError(set, Read, kret); return kret; } static void DumpDBGState(const DNBArchMachARM::DBG& dbg) { uint32_t i = 0; for (i=0; i<16; i++) DNBLogThreadedIf(LOG_STEP, "BVR%-2u/BCR%-2u = { 0x%8.8x, 0x%8.8x } WVR%-2u/WCR%-2u = { 0x%8.8x, 0x%8.8x }", i, i, dbg.__bvr[i], dbg.__bcr[i], i, i, dbg.__wvr[i], dbg.__wcr[i]); } kern_return_t DNBArchMachARM::GetDBGState(bool force) { int set = e_regSetDBG; // Check if we have valid cached registers if (!force && m_state.GetError(set, Read) == KERN_SUCCESS) return KERN_SUCCESS; // Read the registers from our thread mach_msg_type_number_t count = ARM_DEBUG_STATE_COUNT; kern_return_t kret = ::thread_get_state(m_thread->ThreadID(), ARM_DEBUG_STATE, (thread_state_t)&m_state.dbg, &count); m_state.SetError(set, Read, kret); return kret; } kern_return_t DNBArchMachARM::SetGPRState() { int set = e_regSetGPR; kern_return_t kret = ::thread_set_state(m_thread->ThreadID(), ARM_THREAD_STATE, (thread_state_t)&m_state.context.gpr, ARM_THREAD_STATE_COUNT); m_state.SetError(set, Write, kret); // Set the current write error for this register set m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently return kret; // Return the error code } kern_return_t DNBArchMachARM::SetVFPState() { int set = e_regSetVFP; kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_VFP_STATE, (thread_state_t)&m_state.context.vfp, ARM_VFP_STATE_COUNT); m_state.SetError(set, Write, kret); // Set the current write error for this register set m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently return kret; // Return the error code } kern_return_t DNBArchMachARM::SetEXCState() { int set = e_regSetEXC; kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_EXCEPTION_STATE, (thread_state_t)&m_state.context.exc, ARM_EXCEPTION_STATE_COUNT); m_state.SetError(set, Write, kret); // Set the current write error for this register set m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently return kret; // Return the error code } kern_return_t DNBArchMachARM::SetDBGState() { int set = e_regSetDBG; kern_return_t kret = ::thread_set_state (m_thread->ThreadID(), ARM_DEBUG_STATE, (thread_state_t)&m_state.dbg, ARM_DEBUG_STATE_COUNT); m_state.SetError(set, Write, kret); // Set the current write error for this register set m_state.InvalidateRegisterSetState(set); // Invalidate the current register state in case registers are read back differently return kret; // Return the error code } void DNBArchMachARM::ThreadWillResume() { // Do we need to step this thread? If so, let the mach thread tell us so. if (m_thread->IsStepping()) { bool step_handled = false; // This is the primary thread, let the arch do anything it needs if (NumSupportedHardwareBreakpoints() > 0) { #if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) bool half_step = m_hw_single_chained_step_addr != INVALID_NUB_ADDRESS; #endif step_handled = EnableHardwareSingleStep(true) == KERN_SUCCESS; #if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) if (!half_step) step_handled = false; #endif } if (!step_handled) { SetSingleStepSoftwareBreakpoints(); } } } bool DNBArchMachARM::ThreadDidStop() { bool success = true; m_state.InvalidateRegisterSetState (e_regSetALL); // Are we stepping a single instruction? if (GetGPRState(true) == KERN_SUCCESS) { // We are single stepping, was this the primary thread? if (m_thread->IsStepping()) { #if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) success = EnableHardwareSingleStep(false) == KERN_SUCCESS; // Hardware single step must work if we are going to test software // single step functionality assert(success); if (m_hw_single_chained_step_addr == INVALID_NUB_ADDRESS && m_sw_single_step_next_pc != INVALID_NUB_ADDRESS) { uint32_t sw_step_next_pc = m_sw_single_step_next_pc & 0xFFFFFFFEu; bool sw_step_next_pc_is_thumb = (m_sw_single_step_next_pc & 1) != 0; bool actual_next_pc_is_thumb = (m_state.context.gpr.__cpsr & 0x20) != 0; if (m_state.context.gpr.__pc != sw_step_next_pc) { DNBLogError("curr pc = 0x%8.8x - calculated single step target PC was incorrect: 0x%8.8x != 0x%8.8x", m_state.context.gpr.__pc, sw_step_next_pc, m_state.context.gpr.__pc); exit(1); } if (actual_next_pc_is_thumb != sw_step_next_pc_is_thumb) { DNBLogError("curr pc = 0x%8.8x - calculated single step calculated mode mismatch: sw single mode = %s != %s", m_state.context.gpr.__pc, actual_next_pc_is_thumb ? "Thumb" : "ARM", sw_step_next_pc_is_thumb ? "Thumb" : "ARM"); exit(1); } m_sw_single_step_next_pc = INVALID_NUB_ADDRESS; } #else // Are we software single stepping? if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id) || m_sw_single_step_itblock_break_count) { // Remove any software single stepping breakpoints that we have set // Do we have a normal software single step breakpoint? if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) { DNBLogThreadedIf(LOG_STEP, "%s: removing software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_break_id); success = m_thread->Process()->DisableBreakpoint(m_sw_single_step_break_id, true); m_sw_single_step_break_id = INVALID_NUB_BREAK_ID; } // Do we have any Thumb IT breakpoints? if (m_sw_single_step_itblock_break_count > 0) { // See if we hit one of our Thumb IT breakpoints? DNBBreakpoint *step_bp = m_thread->Process()->Breakpoints().FindByAddress(m_state.context.gpr.__pc); if (step_bp) { // We did hit our breakpoint, tell the breakpoint it was // hit so that it can run its callback routine and fixup // the PC. DNBLogThreadedIf(LOG_STEP, "%s: IT software single step breakpoint hit (breakID=%u)", __FUNCTION__, step_bp->GetID()); step_bp->BreakpointHit(m_thread->Process()->ProcessID(), m_thread->ThreadID()); } // Remove all Thumb IT breakpoints for (int i = 0; i < m_sw_single_step_itblock_break_count; i++) { if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) { DNBLogThreadedIf(LOG_STEP, "%s: removing IT software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_itblock_break_id[i]); success = m_thread->Process()->DisableBreakpoint(m_sw_single_step_itblock_break_id[i], true); m_sw_single_step_itblock_break_id[i] = INVALID_NUB_BREAK_ID; } } m_sw_single_step_itblock_break_count = 0; #if defined (USE_ARM_DISASSEMBLER_FRAMEWORK) // Decode instructions up to the current PC to ensure the internal decoder state is valid for the IT block // The decoder has to decode each instruction in the IT block even if it is not executed so that // the fields are correctly updated DecodeITBlockInstructions(m_state.context.gpr.__pc); #endif } } else success = EnableHardwareSingleStep(false) == KERN_SUCCESS; #endif } else { // The MachThread will automatically restore the suspend count // in ThreadDidStop(), so we don't need to do anything here if // we weren't the primary thread the last time } } return success; } bool DNBArchMachARM::StepNotComplete () { if (m_hw_single_chained_step_addr != INVALID_NUB_ADDRESS) { kern_return_t kret = KERN_INVALID_ARGUMENT; kret = GetGPRState(false); if (kret == KERN_SUCCESS) { if (m_state.context.gpr.__pc == m_hw_single_chained_step_addr) { DNBLogThreadedIf(LOG_STEP, "Need to step some more at 0x%8.8x", m_hw_single_chained_step_addr); return true; } } } m_hw_single_chained_step_addr = INVALID_NUB_ADDRESS; return false; } #if defined (USE_ARM_DISASSEMBLER_FRAMEWORK) void DNBArchMachARM::DecodeITBlockInstructions(nub_addr_t curr_pc) { uint16_t opcode16; uint32_t opcode32; nub_addr_t next_pc_in_itblock; nub_addr_t pc_in_itblock = m_last_decode_pc; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); // Decode IT block instruction from the instruction following the m_last_decoded_instruction at // PC m_last_decode_pc upto and including the instruction at curr_pc if (m_thread->Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) { opcode32 = opcode16; pc_in_itblock += 2; // Check for 32 bit thumb opcode and read the upper 16 bits if needed if (((opcode32 & 0xE000) == 0xE000) && opcode32 & 0x1800) { // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for // a 32 bit Thumb opcode // Read bits 31:16 of a 32 bit Thumb opcode if (m_thread->Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) { pc_in_itblock += 2; // 32 bit thumb opcode opcode32 = (opcode32 << 16) | opcode16; } else { DNBLogError("%s: Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8llx", __FUNCTION__, (uint64_t)pc_in_itblock); } } } else { DNBLogError("%s: Error reading 16-bit Thumb instruction at pc=0x%8.8x", __FUNCTION__, pc_in_itblock); } DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: pc_in_itblock=0x%8.8x, curr_pc=0x%8.8x", __FUNCTION__, pc_in_itblock, curr_pc); next_pc_in_itblock = pc_in_itblock; while (next_pc_in_itblock <= curr_pc) { arm_error_t decodeError; m_last_decode_pc = pc_in_itblock; decodeError = DecodeInstructionUsingDisassembler(pc_in_itblock, m_state.context.gpr.__cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc_in_itblock); pc_in_itblock = next_pc_in_itblock; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: next_pc_in_itblock=0x%8.8x", __FUNCTION__, next_pc_in_itblock); } } #endif // Set the single step bit in the processor status register. kern_return_t DNBArchMachARM::EnableHardwareSingleStep (bool enable) { DNBError err; DNBLogThreadedIf(LOG_STEP, "%s( enable = %d )", __FUNCTION__, enable); err = GetGPRState(false); if (err.Fail()) { err.LogThreaded("%s: failed to read the GPR registers", __FUNCTION__); return err.Error(); } err = GetDBGState(false); if (err.Fail()) { err.LogThreaded("%s: failed to read the DBG registers", __FUNCTION__); return err.Error(); } const uint32_t i = 0; if (enable) { m_hw_single_chained_step_addr = INVALID_NUB_ADDRESS; // Save our previous state m_dbg_save = m_state.dbg; // Set a breakpoint that will stop when the PC doesn't match the current one! m_state.dbg.__bvr[i] = m_state.context.gpr.__pc & 0xFFFFFFFCu; // Set the current PC as the breakpoint address m_state.dbg.__bcr[i] = BCR_M_IMVA_MISMATCH | // Stop on address mismatch S_USER | // Stop only in user mode BCR_ENABLE; // Enable this breakpoint if (m_state.context.gpr.__cpsr & 0x20) { // Thumb breakpoint if (m_state.context.gpr.__pc & 2) m_state.dbg.__bcr[i] |= BAS_IMVA_2_3; else m_state.dbg.__bcr[i] |= BAS_IMVA_0_1; uint16_t opcode; if (sizeof(opcode) == m_thread->Process()->Task().ReadMemory(m_state.context.gpr.__pc, sizeof(opcode), &opcode)) { if (((opcode & 0xE000) == 0xE000) && opcode & 0x1800) { // 32 bit thumb opcode... if (m_state.context.gpr.__pc & 2) { // We can't take care of a 32 bit thumb instruction single step // with just IVA mismatching. We will need to chain an extra // hardware single step in order to complete this single step... m_hw_single_chained_step_addr = m_state.context.gpr.__pc + 2; } else { // Extend the number of bits to ignore for the mismatch m_state.dbg.__bcr[i] |= BAS_IMVA_ALL; } } } } else { // ARM breakpoint m_state.dbg.__bcr[i] |= BAS_IMVA_ALL; // Stop when any address bits change } DNBLogThreadedIf(LOG_STEP, "%s: BVR%u=0x%8.8x BCR%u=0x%8.8x", __FUNCTION__, i, m_state.dbg.__bvr[i], i, m_state.dbg.__bcr[i]); for (uint32_t j=i+1; j<16; ++j) { // Disable all others m_state.dbg.__bvr[j] = 0; m_state.dbg.__bcr[j] = 0; } } else { // Just restore the state we had before we did single stepping m_state.dbg = m_dbg_save; } return SetDBGState(); } // return 1 if bit "BIT" is set in "value" static inline uint32_t bit(uint32_t value, uint32_t bit) { return (value >> bit) & 1u; } // return the bitfield "value[msbit:lsbit]". static inline uint32_t bits(uint32_t value, uint32_t msbit, uint32_t lsbit) { assert(msbit >= lsbit); uint32_t shift_left = sizeof(value) * 8 - 1 - msbit; value <<= shift_left; // shift anything above the msbit off of the unsigned edge value >>= shift_left + lsbit; // shift it back again down to the lsbit (including undoing any shift from above) return value; // return our result } bool DNBArchMachARM::ConditionPassed(uint8_t condition, uint32_t cpsr) { uint32_t cpsr_n = bit(cpsr, 31); // Negative condition code flag uint32_t cpsr_z = bit(cpsr, 30); // Zero condition code flag uint32_t cpsr_c = bit(cpsr, 29); // Carry condition code flag uint32_t cpsr_v = bit(cpsr, 28); // Overflow condition code flag switch (condition) { case COND_EQ: // (0x0) if (cpsr_z == 1) return true; break; case COND_NE: // (0x1) if (cpsr_z == 0) return true; break; case COND_CS: // (0x2) if (cpsr_c == 1) return true; break; case COND_CC: // (0x3) if (cpsr_c == 0) return true; break; case COND_MI: // (0x4) if (cpsr_n == 1) return true; break; case COND_PL: // (0x5) if (cpsr_n == 0) return true; break; case COND_VS: // (0x6) if (cpsr_v == 1) return true; break; case COND_VC: // (0x7) if (cpsr_v == 0) return true; break; case COND_HI: // (0x8) if ((cpsr_c == 1) && (cpsr_z == 0)) return true; break; case COND_LS: // (0x9) if ((cpsr_c == 0) || (cpsr_z == 1)) return true; break; case COND_GE: // (0xA) if (cpsr_n == cpsr_v) return true; break; case COND_LT: // (0xB) if (cpsr_n != cpsr_v) return true; break; case COND_GT: // (0xC) if ((cpsr_z == 0) && (cpsr_n == cpsr_v)) return true; break; case COND_LE: // (0xD) if ((cpsr_z == 1) || (cpsr_n != cpsr_v)) return true; break; default: return true; break; } return false; } #if defined (USE_ARM_DISASSEMBLER_FRAMEWORK) bool DNBArchMachARM::ComputeNextPC(nub_addr_t currentPC, arm_decoded_instruction_t decodedInstruction, bool currentPCIsThumb, nub_addr_t *targetPC) { nub_addr_t myTargetPC, addressWherePCLives; pid_t mypid; uint32_t cpsr_c = bit(m_state.context.gpr.__cpsr, 29); // Carry condition code flag uint32_t firstOperand=0, secondOperand=0, shiftAmount=0, secondOperandAfterShift=0, immediateValue=0; uint32_t halfwords=0, baseAddress=0, immediateOffset=0, addressOffsetFromRegister=0, addressOffsetFromRegisterAfterShift; uint32_t baseAddressIndex=INVALID_NUB_HW_INDEX; uint32_t firstOperandIndex=INVALID_NUB_HW_INDEX; uint32_t secondOperandIndex=INVALID_NUB_HW_INDEX; uint32_t addressOffsetFromRegisterIndex=INVALID_NUB_HW_INDEX; uint32_t shiftRegisterIndex=INVALID_NUB_HW_INDEX; uint16_t registerList16, registerList16NoPC; uint8_t registerList8; uint32_t numRegistersToLoad=0; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: instruction->code=%d", __FUNCTION__, decodedInstruction.instruction->code); // Get the following in this switch statement: // - firstOperand, secondOperand, immediateValue, shiftAmount: For arithmetic, logical and move instructions // - baseAddress, immediateOffset, shiftAmount: For LDR // - numRegistersToLoad: For LDM and POP instructions switch (decodedInstruction.instruction->code) { // Arithmetic operations that can change the PC case ARM_INST_ADC: case ARM_INST_ADCS: case ARM_INST_ADD: case ARM_INST_ADDS: case ARM_INST_AND: case ARM_INST_ANDS: case ARM_INST_ASR: case ARM_INST_ASRS: case ARM_INST_BIC: case ARM_INST_BICS: case ARM_INST_EOR: case ARM_INST_EORS: case ARM_INST_ORR: case ARM_INST_ORRS: case ARM_INST_RSB: case ARM_INST_RSBS: case ARM_INST_RSC: case ARM_INST_RSCS: case ARM_INST_SBC: case ARM_INST_SBCS: case ARM_INST_SUB: case ARM_INST_SUBS: switch (decodedInstruction.addressMode) { case ARM_ADDR_DATA_IMM: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get firstOperand register value (at index=1) firstOperandIndex = decodedInstruction.op[1].value; // first operand register index firstOperand = m_state.context.gpr.__r[firstOperandIndex]; // Get immediateValue (at index=2) immediateValue = decodedInstruction.op[2].value; break; case ARM_ADDR_DATA_REG: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get firstOperand register value (at index=1) firstOperandIndex = decodedInstruction.op[1].value; // first operand register index firstOperand = m_state.context.gpr.__r[firstOperandIndex]; // Get secondOperand register value (at index=2) secondOperandIndex = decodedInstruction.op[2].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; break; case ARM_ADDR_DATA_SCALED_IMM: if (decodedInstruction.numOperands != 4) { DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get firstOperand register value (at index=1) firstOperandIndex = decodedInstruction.op[1].value; // first operand register index firstOperand = m_state.context.gpr.__r[firstOperandIndex]; // Get secondOperand register value (at index=2) secondOperandIndex = decodedInstruction.op[2].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; // Get shiftAmount as immediate value (at index=3) shiftAmount = decodedInstruction.op[3].value; break; case ARM_ADDR_DATA_SCALED_REG: if (decodedInstruction.numOperands != 4) { DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get firstOperand register value (at index=1) firstOperandIndex = decodedInstruction.op[1].value; // first operand register index firstOperand = m_state.context.gpr.__r[firstOperandIndex]; // Get secondOperand register value (at index=2) secondOperandIndex = decodedInstruction.op[2].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; // Get shiftAmount from register (at index=3) shiftRegisterIndex = decodedInstruction.op[3].value; // second operand register index shiftAmount = m_state.context.gpr.__r[shiftRegisterIndex]; break; case THUMB_ADDR_HR_HR: if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get firstOperand register value (at index=0) firstOperandIndex = decodedInstruction.op[0].value; // first operand register index firstOperand = m_state.context.gpr.__r[firstOperandIndex]; // Get secondOperand register value (at index=1) secondOperandIndex = decodedInstruction.op[1].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; break; default: break; } break; // Logical shifts and move operations that can change the PC case ARM_INST_LSL: case ARM_INST_LSLS: case ARM_INST_LSR: case ARM_INST_LSRS: case ARM_INST_MOV: case ARM_INST_MOVS: case ARM_INST_MVN: case ARM_INST_MVNS: case ARM_INST_ROR: case ARM_INST_RORS: case ARM_INST_RRX: case ARM_INST_RRXS: // In these cases, the firstOperand is always 0, as if it does not exist switch (decodedInstruction.addressMode) { case ARM_ADDR_DATA_IMM: if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get immediateValue (at index=1) immediateValue = decodedInstruction.op[1].value; break; case ARM_ADDR_DATA_REG: if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get secondOperand register value (at index=1) secondOperandIndex = decodedInstruction.op[1].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; break; case ARM_ADDR_DATA_SCALED_IMM: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get secondOperand register value (at index=1) secondOperandIndex = decodedInstruction.op[2].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; // Get shiftAmount as immediate value (at index=2) shiftAmount = decodedInstruction.op[2].value; break; case ARM_ADDR_DATA_SCALED_REG: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get secondOperand register value (at index=1) secondOperandIndex = decodedInstruction.op[1].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; // Get shiftAmount from register (at index=2) shiftRegisterIndex = decodedInstruction.op[2].value; // second operand register index shiftAmount = m_state.context.gpr.__r[shiftRegisterIndex]; break; case THUMB_ADDR_HR_HR: if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } if (decodedInstruction.op[0].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } // Get secondOperand register value (at index=1) secondOperandIndex = decodedInstruction.op[1].value; // second operand register index secondOperand = m_state.context.gpr.__r[secondOperandIndex]; break; default: break; } break; // Simple branches, used to hop around within a routine case ARM_INST_B: *targetPC = decodedInstruction.targetPC; // Known targetPC return true; break; // Branch-and-link, used to call ARM subroutines case ARM_INST_BL: *targetPC = decodedInstruction.targetPC; // Known targetPC return true; break; // Branch-and-link with exchange, used to call opposite-mode subroutines case ARM_INST_BLX: if ((decodedInstruction.addressMode == ARM_ADDR_BRANCH_IMM) || (decodedInstruction.addressMode == THUMB_ADDR_UNCOND)) { *targetPC = decodedInstruction.targetPC; // Known targetPC return true; } else // addressMode == ARM_ADDR_BRANCH_REG { // Unknown target unless we're branching to the PC itself, // although this may not work properly with BLX if (decodedInstruction.op[REG_RD].value == PC_REG) { // this should (almost) never happen *targetPC = decodedInstruction.targetPC; // Known targetPC return true; } // Get the branch address and return if (decodedInstruction.numOperands != 1) { DNBLogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get branch address in register (at index=0) *targetPC = m_state.context.gpr.__r[decodedInstruction.op[0].value]; return true; } break; // Branch with exchange, used to hop to opposite-mode code // Branch to Jazelle code, used to execute Java; included here since it // acts just like BX unless the Jazelle unit is active and JPC is // already loaded into it. case ARM_INST_BX: case ARM_INST_BXJ: // Unknown target unless we're branching to the PC itself, // although this can never switch to Thumb mode and is // therefore pretty much useless if (decodedInstruction.op[REG_RD].value == PC_REG) { // this should (almost) never happen *targetPC = decodedInstruction.targetPC; // Known targetPC return true; } // Get the branch address and return if (decodedInstruction.numOperands != 1) { DNBLogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get branch address in register (at index=0) *targetPC = m_state.context.gpr.__r[decodedInstruction.op[0].value]; return true; break; // Compare and branch on zero/non-zero (Thumb-16 only) // Unusual condition check built into the instruction case ARM_INST_CBZ: case ARM_INST_CBNZ: // Branch address is known at compile time // Get the branch address and return if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get branch address as an immediate value (at index=1) *targetPC = decodedInstruction.op[1].value; return true; break; // Load register can be used to load PC, usually with a function pointer case ARM_INST_LDR: if (decodedInstruction.op[REG_RD].value != PC_REG) { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } switch (decodedInstruction.addressMode) { case ARM_ADDR_LSWUB_IMM: case ARM_ADDR_LSWUB_IMM_PRE: case ARM_ADDR_LSWUB_IMM_POST: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get baseAddress from register (at index=1) baseAddressIndex = decodedInstruction.op[1].value; baseAddress = m_state.context.gpr.__r[baseAddressIndex]; // Get immediateOffset (at index=2) immediateOffset = decodedInstruction.op[2].value; break; case ARM_ADDR_LSWUB_REG: case ARM_ADDR_LSWUB_REG_PRE: case ARM_ADDR_LSWUB_REG_POST: if (decodedInstruction.numOperands != 3) { DNBLogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get baseAddress from register (at index=1) baseAddressIndex = decodedInstruction.op[1].value; baseAddress = m_state.context.gpr.__r[baseAddressIndex]; // Get immediateOffset from register (at index=2) addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; addressOffsetFromRegister = m_state.context.gpr.__r[addressOffsetFromRegisterIndex]; break; case ARM_ADDR_LSWUB_SCALED: case ARM_ADDR_LSWUB_SCALED_PRE: case ARM_ADDR_LSWUB_SCALED_POST: if (decodedInstruction.numOperands != 4) { DNBLogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get baseAddress from register (at index=1) baseAddressIndex = decodedInstruction.op[1].value; baseAddress = m_state.context.gpr.__r[baseAddressIndex]; // Get immediateOffset from register (at index=2) addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; addressOffsetFromRegister = m_state.context.gpr.__r[addressOffsetFromRegisterIndex]; // Get shiftAmount (at index=3) shiftAmount = decodedInstruction.op[3].value; break; default: break; } break; // 32b load multiple operations can load the PC along with everything else, // usually to return from a function call case ARM_INST_LDMDA: case ARM_INST_LDMDB: case ARM_INST_LDMIA: case ARM_INST_LDMIB: if (decodedInstruction.op[LDM_REGLIST].value & PC_REGLIST_BIT) { if (decodedInstruction.numOperands != 2) { DNBLogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); return false; } // Get baseAddress from register (at index=0) baseAddressIndex = decodedInstruction.op[0].value; baseAddress = m_state.context.gpr.__r[baseAddressIndex]; // Get registerList from register (at index=1) registerList16 = (uint16_t)decodedInstruction.op[1].value; // Count number of registers to load in the multiple register list excluding the PC registerList16NoPC = registerList16&0x3FFF; // exclude the PC numRegistersToLoad=0; for (int i = 0; i < 16; i++) { if (registerList16NoPC & 0x1) numRegistersToLoad++; registerList16NoPC = registerList16NoPC >> 1; } } else { DNBLogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); return false; } break; // Normal 16-bit LD multiple can't touch R15, but POP can case ARM_INST_POP: // Can also get the PC & updates SP // Get baseAddress from SP (at index=0) baseAddress = m_state.context.gpr.__sp; if (decodedInstruction.thumb16b) { // Get registerList from register (at index=0) registerList8 = (uint8_t)decodedInstruction.op[0].value; // Count number of registers to load in the multiple register list numRegistersToLoad=0; for (int i = 0; i < 8; i++) { if (registerList8 & 0x1) numRegistersToLoad++; registerList8 = registerList8 >> 1; } } else { // Get registerList from register (at index=0) registerList16 = (uint16_t)decodedInstruction.op[0].value; // Count number of registers to load in the multiple register list excluding the PC registerList16NoPC = registerList16&0x3FFF; // exclude the PC numRegistersToLoad=0; for (int i = 0; i < 16; i++) { if (registerList16NoPC & 0x1) numRegistersToLoad++; registerList16NoPC = registerList16NoPC >> 1; } } break; // 16b TBB and TBH instructions load a jump address from a table case ARM_INST_TBB: case ARM_INST_TBH: // Get baseAddress from register (at index=0) baseAddressIndex = decodedInstruction.op[0].value; baseAddress = m_state.context.gpr.__r[baseAddressIndex]; // Get immediateOffset from register (at index=1) addressOffsetFromRegisterIndex = decodedInstruction.op[1].value; addressOffsetFromRegister = m_state.context.gpr.__r[addressOffsetFromRegisterIndex]; break; // ThumbEE branch-to-handler instructions: Jump to handlers at some offset // from a special base pointer register (which is unknown at disassembly time) case ARM_INST_HB: case ARM_INST_HBP: // TODO: ARM_INST_HB, ARM_INST_HBP break; case ARM_INST_HBL: case ARM_INST_HBLP: // TODO: ARM_INST_HBL, ARM_INST_HBLP break; // Breakpoint and software interrupt jump to interrupt handler (always ARM) case ARM_INST_BKPT: case ARM_INST_SMC: case ARM_INST_SVC: // Return from exception, obviously modifies PC [interrupt only!] case ARM_INST_RFEDA: case ARM_INST_RFEDB: case ARM_INST_RFEIA: case ARM_INST_RFEIB: // Other instructions either can't change R15 or are "undefined" if you do, // so no sane compiler should ever generate them & we don't care here. // Also, R15 can only legally be used in a read-only manner for the // various ARM addressing mode (to get PC-relative addressing of constants), // but can NOT be used with any of the update modes. default: DNBLogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); return false; break; } // Adjust PC if PC is one of the input operands if (baseAddressIndex == PC_REG) { if (currentPCIsThumb) baseAddress += 4; else baseAddress += 8; } if (firstOperandIndex == PC_REG) { if (currentPCIsThumb) firstOperand += 4; else firstOperand += 8; } if (secondOperandIndex == PC_REG) { if (currentPCIsThumb) secondOperand += 4; else secondOperand += 8; } if (addressOffsetFromRegisterIndex == PC_REG) { if (currentPCIsThumb) addressOffsetFromRegister += 4; else addressOffsetFromRegister += 8; } DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: firstOperand=%8.8x, secondOperand=%8.8x, immediateValue = %d, shiftAmount = %d, baseAddress = %8.8x, addressOffsetFromRegister = %8.8x, immediateOffset = %d, numRegistersToLoad = %d", __FUNCTION__, firstOperand, secondOperand, immediateValue, shiftAmount, baseAddress, addressOffsetFromRegister, immediateOffset, numRegistersToLoad); // Calculate following values after applying shiftAmount: // - immediateOffsetAfterShift, secondOperandAfterShift switch (decodedInstruction.scaleMode) { case ARM_SCALE_NONE: addressOffsetFromRegisterAfterShift = addressOffsetFromRegister; secondOperandAfterShift = secondOperand; break; case ARM_SCALE_LSL: // Logical shift left addressOffsetFromRegisterAfterShift = addressOffsetFromRegister << shiftAmount; secondOperandAfterShift = secondOperand << shiftAmount; break; case ARM_SCALE_LSR: // Logical shift right addressOffsetFromRegisterAfterShift = addressOffsetFromRegister >> shiftAmount; secondOperandAfterShift = secondOperand >> shiftAmount; break; case ARM_SCALE_ASR: // Arithmetic shift right asm("mov %0, %1, asr %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); asm("mov %0, %1, asr %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); break; case ARM_SCALE_ROR: // Rotate right asm("mov %0, %1, ror %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); asm("mov %0, %1, ror %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); break; case ARM_SCALE_RRX: // Rotate right, pulling in carry (1-bit shift only) asm("mov %0, %1, rrx" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister)); asm("mov %0, %1, rrx" : "=r" (secondOperandAfterShift) : "r" (secondOperand)); break; } // Emulate instruction to calculate targetPC // All branches are already handled in the first switch statement. A branch should not reach this switch switch (decodedInstruction.instruction->code) { // Arithmetic operations that can change the PC case ARM_INST_ADC: case ARM_INST_ADCS: // Add with Carry *targetPC = firstOperand + (secondOperandAfterShift + immediateValue) + cpsr_c; break; case ARM_INST_ADD: case ARM_INST_ADDS: *targetPC = firstOperand + (secondOperandAfterShift + immediateValue); break; case ARM_INST_AND: case ARM_INST_ANDS: *targetPC = firstOperand & (secondOperandAfterShift + immediateValue); break; case ARM_INST_ASR: case ARM_INST_ASRS: asm("mov %0, %1, asr %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; case ARM_INST_BIC: case ARM_INST_BICS: asm("bic %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; case ARM_INST_EOR: case ARM_INST_EORS: asm("eor %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; case ARM_INST_ORR: case ARM_INST_ORRS: asm("orr %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; case ARM_INST_RSB: case ARM_INST_RSBS: asm("rsb %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; case ARM_INST_RSC: case ARM_INST_RSCS: myTargetPC = secondOperandAfterShift - (firstOperand + !cpsr_c); *targetPC = myTargetPC; break; case ARM_INST_SBC: case ARM_INST_SBCS: asm("sbc %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue + !cpsr_c)); *targetPC = myTargetPC; break; case ARM_INST_SUB: case ARM_INST_SUBS: asm("sub %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); *targetPC = myTargetPC; break; // Logical shifts and move operations that can change the PC case ARM_INST_LSL: case ARM_INST_LSLS: case ARM_INST_LSR: case ARM_INST_LSRS: case ARM_INST_MOV: case ARM_INST_MOVS: case ARM_INST_ROR: case ARM_INST_RORS: case ARM_INST_RRX: case ARM_INST_RRXS: myTargetPC = secondOperandAfterShift + immediateValue; *targetPC = myTargetPC; break; case ARM_INST_MVN: case ARM_INST_MVNS: myTargetPC = !(secondOperandAfterShift + immediateValue); *targetPC = myTargetPC; break; // Load register can be used to load PC, usually with a function pointer case ARM_INST_LDR: switch (decodedInstruction.addressMode) { case ARM_ADDR_LSWUB_IMM_POST: case ARM_ADDR_LSWUB_REG_POST: case ARM_ADDR_LSWUB_SCALED_POST: addressWherePCLives = baseAddress; break; case ARM_ADDR_LSWUB_IMM: case ARM_ADDR_LSWUB_REG: case ARM_ADDR_LSWUB_SCALED: case ARM_ADDR_LSWUB_IMM_PRE: case ARM_ADDR_LSWUB_REG_PRE: case ARM_ADDR_LSWUB_SCALED_PRE: addressWherePCLives = baseAddress + (addressOffsetFromRegisterAfterShift + immediateOffset); break; default: break; } mypid = m_thread->ProcessID(); if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); return false; } *targetPC = myTargetPC; break; // 32b load multiple operations can load the PC along with everything else, // usually to return from a function call case ARM_INST_LDMDA: mypid = m_thread->ProcessID(); addressWherePCLives = baseAddress; if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); return false; } *targetPC = myTargetPC; break; case ARM_INST_LDMDB: mypid = m_thread->ProcessID(); addressWherePCLives = baseAddress - 4; if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); return false; } *targetPC = myTargetPC; break; case ARM_INST_LDMIB: mypid = m_thread->ProcessID(); addressWherePCLives = baseAddress + numRegistersToLoad*4 + 4; if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); return false; } *targetPC = myTargetPC; break; case ARM_INST_LDMIA: // same as pop // Normal 16-bit LD multiple can't touch R15, but POP can case ARM_INST_POP: // Can also get the PC & updates SP mypid = m_thread->ProcessID(); addressWherePCLives = baseAddress + numRegistersToLoad*4; if (DNBProcessMemoryRead(mypid, addressWherePCLives, sizeof(nub_addr_t), &myTargetPC) != sizeof(nub_addr_t)) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); return false; } *targetPC = myTargetPC; break; // 16b TBB and TBH instructions load a jump address from a table case ARM_INST_TBB: mypid = m_thread->ProcessID(); addressWherePCLives = baseAddress + addressOffsetFromRegisterAfterShift; if (DNBProcessMemoryRead(mypid, addressWherePCLives, 1, &halfwords) != 1) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the TBB instruction!", addressWherePCLives); return false; } // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords *targetPC = (currentPC + 4) + 2*halfwords; break; case ARM_INST_TBH: mypid = m_thread->ProcessID(); addressWherePCLives = ((baseAddress + (addressOffsetFromRegisterAfterShift << 1)) & ~0x1); if (DNBProcessMemoryRead(mypid, addressWherePCLives, 2, &halfwords) != 2) { DNBLogError("Could not read memory at %8.8x to get targetPC when processing the TBH instruction!", addressWherePCLives); return false; } // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords *targetPC = (currentPC + 4) + 2*halfwords; break; // ThumbEE branch-to-handler instructions: Jump to handlers at some offset // from a special base pointer register (which is unknown at disassembly time) case ARM_INST_HB: case ARM_INST_HBP: // TODO: ARM_INST_HB, ARM_INST_HBP break; case ARM_INST_HBL: case ARM_INST_HBLP: // TODO: ARM_INST_HBL, ARM_INST_HBLP break; // Breakpoint and software interrupt jump to interrupt handler (always ARM) case ARM_INST_BKPT: case ARM_INST_SMC: case ARM_INST_SVC: // TODO: ARM_INST_BKPT, ARM_INST_SMC, ARM_INST_SVC break; // Return from exception, obviously modifies PC [interrupt only!] case ARM_INST_RFEDA: case ARM_INST_RFEDB: case ARM_INST_RFEIA: case ARM_INST_RFEIB: // TODO: ARM_INST_RFEDA, ARM_INST_RFEDB, ARM_INST_RFEIA, ARM_INST_RFEIB break; // Other instructions either can't change R15 or are "undefined" if you do, // so no sane compiler should ever generate them & we don't care here. // Also, R15 can only legally be used in a read-only manner for the // various ARM addressing mode (to get PC-relative addressing of constants), // but can NOT be used with any of the update modes. default: DNBLogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); return false; break; } return true; } void DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup(nub_addr_t currentPC, uint32_t cpsr, bool currentPCIsThumb, nub_addr_t *nextPC, bool *nextPCIsThumb) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "DNBArchMachARM::EvaluateNextInstructionForSoftwareBreakpointSetup() called"); nub_addr_t targetPC = INVALID_NUB_ADDRESS; uint32_t registerValue; arm_error_t decodeError; nub_addr_t currentPCInITBlock, nextPCInITBlock; int i; bool last_decoded_instruction_executes = true; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: default nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); // Update *nextPC and *nextPCIsThumb for special cases if (m_last_decode_thumb.itBlockRemaining) // we are in an IT block { // Set the nextPC to the PC of the instruction which will execute in the IT block // If none of the instruction execute in the IT block based on the condition flags, // then point to the instruction immediately following the IT block const int itBlockRemaining = m_last_decode_thumb.itBlockRemaining; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: itBlockRemaining=%8.8x", __FUNCTION__, itBlockRemaining); // Determine the PC at which the next instruction resides if (m_last_decode_arm.thumb16b) currentPCInITBlock = currentPC + 2; else currentPCInITBlock = currentPC + 4; for (i = 0; i < itBlockRemaining; i++) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: currentPCInITBlock=%8.8x", __FUNCTION__, currentPCInITBlock); decodeError = DecodeInstructionUsingDisassembler(currentPCInITBlock, cpsr, &m_last_decode_arm, &m_last_decode_thumb, &nextPCInITBlock); if (decodeError != ARM_SUCCESS) DNBLogError("unable to disassemble instruction at 0x%8.8llx", (uint64_t)currentPCInITBlock); DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: condition=%d", __FUNCTION__, m_last_decode_arm.condition); if (ConditionPassed(m_last_decode_arm.condition, cpsr)) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition codes matched for instruction %d", __FUNCTION__, i); break; // break from the for loop } else { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition codes DID NOT matched for instruction %d", __FUNCTION__, i); } // update currentPC and nextPCInITBlock currentPCInITBlock = nextPCInITBlock; } if (i == itBlockRemaining) // We came out of the IT block without executing any instructions last_decoded_instruction_executes = false; *nextPC = currentPCInITBlock; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: After IT block step-through: *nextPC=%8.8x", __FUNCTION__, *nextPC); } DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: cpsr = %8.8x, thumb16b = %d, thumb = %d, branch = %d, conditional = %d, knownTarget = %d, links = %d, canSwitchMode = %d, doesSwitchMode = %d", __FUNCTION__, cpsr, m_last_decode_arm.thumb16b, m_last_decode_arm.thumb, m_last_decode_arm.branch, m_last_decode_arm.conditional, m_last_decode_arm.knownTarget, m_last_decode_arm.links, m_last_decode_arm.canSwitchMode, m_last_decode_arm.doesSwitchMode); if (last_decoded_instruction_executes && // Was this a conditional instruction that did execute? m_last_decode_arm.branch && // Can this instruction change the PC? (m_last_decode_arm.instruction->code != ARM_INST_SVC)) // If this instruction is not an SVC instruction { // Set targetPC. Compute if needed. if (m_last_decode_arm.knownTarget) { // Fixed, known PC-relative targetPC = m_last_decode_arm.targetPC; } else { // if targetPC is not known at compile time (PC-relative target), compute targetPC if (!ComputeNextPC(currentPC, m_last_decode_arm, currentPCIsThumb, &targetPC)) { DNBLogError("%s: Unable to compute targetPC for instruction at 0x%8.8llx", __FUNCTION__, (uint64_t)currentPC); targetPC = INVALID_NUB_ADDRESS; } } DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: targetPC=0x%8.8x, cpsr=0x%8.8x, condition=0x%hhx", __FUNCTION__, targetPC, cpsr, m_last_decode_arm.condition); // Refine nextPC computation if ((m_last_decode_arm.instruction->code == ARM_INST_CBZ) || (m_last_decode_arm.instruction->code == ARM_INST_CBNZ)) { // Compare and branch on zero/non-zero (Thumb-16 only) // Unusual condition check built into the instruction registerValue = m_state.context.gpr.__r[m_last_decode_arm.op[REG_RD].value]; if (m_last_decode_arm.instruction->code == ARM_INST_CBZ) { if (registerValue == 0) *nextPC = targetPC; } else { if (registerValue != 0) *nextPC = targetPC; } } else if (m_last_decode_arm.conditional) // Is the change conditional on flag results? { if (ConditionPassed(m_last_decode_arm.condition, cpsr)) // conditions match { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition matched!", __FUNCTION__); *nextPC = targetPC; } else { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Condition did not match!", __FUNCTION__); } } else { *nextPC = targetPC; } // Refine nextPCIsThumb computation if (m_last_decode_arm.doesSwitchMode) { *nextPCIsThumb = !currentPCIsThumb; } else if (m_last_decode_arm.canSwitchMode) { // Legal to switch ARM <--> Thumb mode with this branch // dependent on bit[0] of targetPC *nextPCIsThumb = (*nextPC & 1u) != 0; } else { *nextPCIsThumb = currentPCIsThumb; } } DNBLogThreadedIf(LOG_STEP, "%s: calculated nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); } arm_error_t DNBArchMachARM::DecodeInstructionUsingDisassembler(nub_addr_t curr_pc, uint32_t curr_cpsr, arm_decoded_instruction_t *decodedInstruction, thumb_static_data_t *thumbStaticData, nub_addr_t *next_pc) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: pc=0x%8.8x, cpsr=0x%8.8x", __FUNCTION__, curr_pc, curr_cpsr); const uint32_t isetstate_mask = MASK_CPSR_T | MASK_CPSR_J; const uint32_t curr_isetstate = curr_cpsr & isetstate_mask; uint32_t opcode32; nub_addr_t nextPC = curr_pc; arm_error_t decodeReturnCode = ARM_SUCCESS; m_last_decode_pc = curr_pc; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); switch (curr_isetstate) { case 0x0: // ARM Instruction // Read the ARM opcode if (m_thread->Process()->Task().ReadMemory(curr_pc, 4, &opcode32) != 4) { DNBLogError("unable to read opcode bits 31:0 for an ARM opcode at 0x%8.8llx", (uint64_t)curr_pc); decodeReturnCode = ARM_ERROR; } else { nextPC += 4; decodeReturnCode = ArmDisassembler((uint64_t)curr_pc, opcode32, false, decodedInstruction, NULL, 0, NULL, 0); if (decodeReturnCode != ARM_SUCCESS) DNBLogError("Unable to decode ARM instruction 0x%8.8x at 0x%8.8llx", opcode32, (uint64_t)curr_pc); } break; case 0x20: // Thumb Instruction uint16_t opcode16; // Read the a 16 bit Thumb opcode if (m_thread->Process()->Task().ReadMemory(curr_pc, 2, &opcode16) != 2) { DNBLogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8llx", (uint64_t)curr_pc); decodeReturnCode = ARM_ERROR; } else { nextPC += 2; opcode32 = opcode16; decodeReturnCode = ThumbDisassembler((uint64_t)curr_pc, opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); switch (decodeReturnCode) { case ARM_SKIP: // 32 bit thumb opcode nextPC += 2; if (m_thread->Process()->Task().ReadMemory(curr_pc+2, 2, &opcode16) != 2) { DNBLogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8llx", (uint64_t)curr_pc+2); } else { opcode32 = (opcode32 << 16) | opcode16; decodeReturnCode = ThumbDisassembler((uint64_t)(curr_pc+2), opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); if (decodeReturnCode != ARM_SUCCESS) DNBLogError("Unable to decode 2nd half of Thumb instruction 0x%8.4hx at 0x%8.8llx", opcode16, (uint64_t)curr_pc+2); break; } break; case ARM_SUCCESS: // 16 bit thumb opcode; at this point we are done decoding the opcode break; default: DNBLogError("Unable to decode Thumb instruction 0x%8.4hx at 0x%8.8llx", opcode16, (uint64_t)curr_pc); decodeReturnCode = ARM_ERROR; break; } } break; default: break; } if (next_pc) *next_pc = nextPC; return decodeReturnCode; } #endif nub_bool_t DNBArchMachARM::BreakpointHit (nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton) { nub_addr_t bkpt_pc = (nub_addr_t)baton; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s(pid = %i, tid = %4.4x, breakID = %u, baton = %p): Setting PC to 0x%8.8x", __FUNCTION__, pid, tid, breakID, baton, bkpt_pc); DNBRegisterValue pc_value; DNBThreadGetRegisterValueByID (pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_PC, &pc_value); pc_value.value.uint32 = bkpt_pc; return DNBThreadSetRegisterValueByID (pid, tid, REGISTER_SET_GENERIC, GENERIC_REGNUM_PC, &pc_value); } // Set the single step bit in the processor status register. kern_return_t DNBArchMachARM::SetSingleStepSoftwareBreakpoints() { DNBError err; #if defined (USE_ARM_DISASSEMBLER_FRAMEWORK) err = GetGPRState(false); if (err.Fail()) { err.LogThreaded("%s: failed to read the GPR registers", __FUNCTION__); return err.Error(); } nub_addr_t curr_pc = m_state.context.gpr.__pc; uint32_t curr_cpsr = m_state.context.gpr.__cpsr; nub_addr_t next_pc = curr_pc; bool curr_pc_is_thumb = (m_state.context.gpr.__cpsr & 0x20) != 0; bool next_pc_is_thumb = curr_pc_is_thumb; uint32_t curr_itstate = ((curr_cpsr & 0x6000000) >> 25) | ((curr_cpsr & 0xFC00) >> 8); bool inITBlock = (curr_itstate & 0xF) ? 1 : 0; bool lastInITBlock = ((curr_itstate & 0xF) == 0x8) ? 1 : 0; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: curr_pc=0x%8.8x (%s), curr_itstate=0x%x, inITBlock=%d, lastInITBlock=%d", __FUNCTION__, curr_pc, curr_pc_is_thumb ? "Thumb" : "ARM", curr_itstate, inITBlock, lastInITBlock); // If the instruction is not in the IT block, then decode using the Disassembler and compute next_pc if (!inITBlock) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Decoding an instruction NOT in the IT block", __FUNCTION__); arm_error_t decodeReturnCode = DecodeInstructionUsingDisassembler(curr_pc, curr_cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc); if (decodeReturnCode != ARM_SUCCESS) { err = KERN_INVALID_ARGUMENT; DNBLogError("DNBArchMachARM::SetSingleStepSoftwareBreakpoints: Unable to disassemble instruction at 0x%8.8llx", (uint64_t)curr_pc); } } else { next_pc = curr_pc + ((m_last_decode_arm.thumb16b) ? 2 : 4); } // Instruction is NOT in the IT block OR if (!inITBlock) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: normal instruction", __FUNCTION__); EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.context.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); } else if (inITBlock && !m_last_decode_arm.setsFlags) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction that doesn't set flags", __FUNCTION__); EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.context.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); } else if (lastInITBlock && m_last_decode_arm.branch) { DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction which last in the IT block and is a branch", __FUNCTION__); EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.context.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); } else { // Instruction is in IT block and can modify the CPSR flags DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: IT instruction that sets flags", __FUNCTION__); // NOTE: When this point of code is reached, the instruction at curr_pc has already been decoded // inside the function ThreadDidStop(). Therefore m_last_decode_arm, m_last_decode_thumb // reflect the decoded instruction at curr_pc // If we find an instruction inside the IT block which will set/modify the condition flags (NZCV bits in CPSR), // we set breakpoints at all remaining instructions inside the IT block starting from the instruction immediately // following this one AND a breakpoint at the instruction immediately following the IT block. We do this because // we cannot determine the next_pc until the instruction at which we are currently stopped executes. Hence we // insert (m_last_decode_thumb.itBlockRemaining+1) 16-bit Thumb breakpoints at consecutive memory locations // starting at addrOfNextInstructionInITBlock. We record these breakpoints in class variable m_sw_single_step_itblock_break_id[], // and also record the total number of IT breakpoints set in the variable 'm_sw_single_step_itblock_break_count'. // The instructions inside the IT block, which are replaced by the 16-bit Thumb breakpoints (opcode=0xDEFE) // instructions, can be either Thumb-16 or Thumb-32. When a Thumb-32 instruction (say, inst#1) is replaced Thumb // by a 16-bit breakpoint (OS only supports 16-bit breakpoints in Thumb mode and 32-bit breakpoints in ARM mode), the // breakpoint for the next instruction (say instr#2) is saved in the upper half of this Thumb-32 (instr#1) // instruction. Hence if the execution stops at Breakpoint2 corresponding to instr#2, the PC is offset by 16-bits. // We therefore have to keep track of PC of each instruction in the IT block that is being replaced with the 16-bit // Thumb breakpoint, to ensure that when the breakpoint is hit, the PC is adjusted to the correct value. We save // the actual PC corresponding to each instruction in the IT block by associating a call back with each breakpoint // we set and passing it as a baton. When the breakpoint hits and the callback routine is called, the routine // adjusts the PC based on the baton that is passed to it. nub_addr_t addrOfNextInstructionInITBlock, pcInITBlock, nextPCInITBlock, bpAddressInITBlock; uint16_t opcode16; uint32_t opcode32; addrOfNextInstructionInITBlock = (m_last_decode_arm.thumb16b) ? curr_pc + 2 : curr_pc + 4; pcInITBlock = addrOfNextInstructionInITBlock; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: itBlockRemaining=%d", __FUNCTION__, m_last_decode_thumb.itBlockRemaining); m_sw_single_step_itblock_break_count = 0; for (int i = 0; i <= m_last_decode_thumb.itBlockRemaining; i++) { if (NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) { DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Array m_sw_single_step_itblock_break_id should not contain any valid breakpoint IDs at this point. But found a valid breakID=%d at index=%d", m_sw_single_step_itblock_break_id[i], i); } else { nextPCInITBlock = pcInITBlock; // Compute nextPCInITBlock based on opcode present at pcInITBlock if (m_thread->Process()->Task().ReadMemory(pcInITBlock, 2, &opcode16) == 2) { opcode32 = opcode16; nextPCInITBlock += 2; // Check for 32 bit thumb opcode and read the upper 16 bits if needed if (((opcode32 & 0xE000) == 0xE000) && (opcode32 & 0x1800)) { // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for // a 32 bit Thumb opcode // Read bits 31:16 of a 32 bit Thumb opcode if (m_thread->Process()->Task().ReadMemory(pcInITBlock+2, 2, &opcode16) == 2) { // 32 bit thumb opcode opcode32 = (opcode32 << 16) | opcode16; nextPCInITBlock += 2; } else { DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8llx", (uint64_t)nextPCInITBlock); } } } else { DNBLogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Error reading 16-bit Thumb instruction at pc=0x%8.8x", nextPCInITBlock); } // Set breakpoint and associate a callback function with it bpAddressInITBlock = addrOfNextInstructionInITBlock + 2*i; DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Setting IT breakpoint[%d] at address: 0x%8.8x", __FUNCTION__, i, bpAddressInITBlock); m_sw_single_step_itblock_break_id[i] = m_thread->Process()->CreateBreakpoint(bpAddressInITBlock, 2, false, m_thread->ThreadID()); if (!NUB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) err = KERN_INVALID_ARGUMENT; else { DNBLogThreadedIf(LOG_STEP, "%s: Set IT breakpoint[%i]=%d set at 0x%8.8x for instruction at 0x%8.8x", __FUNCTION__, i, m_sw_single_step_itblock_break_id[i], bpAddressInITBlock, pcInITBlock); // Set the breakpoint callback for these special IT breakpoints // so that if one of these breakpoints gets hit, it knows to // update the PC to the original address of the conditional // IT instruction. DNBBreakpointSetCallback(m_thread->ProcessID(), m_sw_single_step_itblock_break_id[i], DNBArchMachARM::BreakpointHit, (void*)pcInITBlock); m_sw_single_step_itblock_break_count++; } } pcInITBlock = nextPCInITBlock; } DNBLogThreadedIf(LOG_STEP | LOG_VERBOSE, "%s: Set %u IT software single breakpoints.", __FUNCTION__, m_sw_single_step_itblock_break_count); } DNBLogThreadedIf(LOG_STEP, "%s: next_pc=0x%8.8x (%s)", __FUNCTION__, next_pc, next_pc_is_thumb ? "Thumb" : "ARM"); if (next_pc & 0x1) { assert(next_pc_is_thumb); } if (next_pc_is_thumb) { next_pc &= ~0x1; } else { assert((next_pc & 0x3) == 0); } if (!inITBlock || (inITBlock && !m_last_decode_arm.setsFlags) || (lastInITBlock && m_last_decode_arm.branch)) { err = KERN_SUCCESS; #if defined DNB_ARCH_MACH_ARM_DEBUG_SW_STEP m_sw_single_step_next_pc = next_pc; if (next_pc_is_thumb) m_sw_single_step_next_pc |= 1; // Set bit zero if the next PC is expected to be Thumb #else const DNBBreakpoint *bp = m_thread->Process()->Breakpoints().FindByAddress(next_pc); if (bp == NULL) { m_sw_single_step_break_id = m_thread->Process()->CreateBreakpoint(next_pc, next_pc_is_thumb ? 2 : 4, false, m_thread->ThreadID()); if (!NUB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) err = KERN_INVALID_ARGUMENT; DNBLogThreadedIf(LOG_STEP, "%s: software single step breakpoint with breakID=%d set at 0x%8.8x", __FUNCTION__, m_sw_single_step_break_id, next_pc); } #endif } #else err.LogThreaded("%s: ARMDisassembler.framework support is disabled", __FUNCTION__); #endif return err.Error(); } uint32_t DNBArchMachARM::NumSupportedHardwareBreakpoints() { // Set the init value to something that will let us know that we need to // autodetect how many breakpoints are supported dynamically... static uint32_t g_num_supported_hw_breakpoints = UINT_MAX; if (g_num_supported_hw_breakpoints == UINT_MAX) { // Set this to zero in case we can't tell if there are any HW breakpoints g_num_supported_hw_breakpoints = 0; size_t len; uint32_t n = 0; len = sizeof (n); if (::sysctlbyname("hw.optional.breakpoint", &n, &len, NULL, 0) == 0) { g_num_supported_hw_breakpoints = n; DNBLogThreadedIf(LOG_THREAD, "hw.optional.breakpoint=%u", n); } else { // Read the DBGDIDR to get the number of available hardware breakpoints // However, in some of our current armv7 processors, hardware // breakpoints/watchpoints were not properly connected. So detect those // cases using a field in a sysctl. For now we are using "hw.cpusubtype" // field to distinguish CPU architectures. This is a hack until we can // get fixed, at which point we will switch to // using a different sysctl string that will tell us how many BRPs // are available to us directly without having to read DBGDIDR. uint32_t register_DBGDIDR; asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); uint32_t numBRPs = bits(register_DBGDIDR, 27, 24); // Zero is reserved for the BRP count, so don't increment it if it is zero if (numBRPs > 0) numBRPs++; DNBLogThreadedIf(LOG_THREAD, "DBGDIDR=0x%8.8x (number BRP pairs = %u)", register_DBGDIDR, numBRPs); if (numBRPs > 0) { uint32_t cpusubtype; len = sizeof(cpusubtype); // TODO: remove this hack and change to using hw.optional.xx when implmented if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) { DNBLogThreadedIf(LOG_THREAD, "hw.cpusubtype=%d", cpusubtype); if (cpusubtype == CPU_SUBTYPE_ARM_V7) DNBLogThreadedIf(LOG_THREAD, "Hardware breakpoints disabled for armv7 (rdar://problem/6372672)"); else g_num_supported_hw_breakpoints = numBRPs; } } } } return g_num_supported_hw_breakpoints; } uint32_t DNBArchMachARM::NumSupportedHardwareWatchpoints() { // Set the init value to something that will let us know that we need to // autodetect how many watchpoints are supported dynamically... static uint32_t g_num_supported_hw_watchpoints = UINT_MAX; if (g_num_supported_hw_watchpoints == UINT_MAX) { // Set this to zero in case we can't tell if there are any HW breakpoints g_num_supported_hw_watchpoints = 0; size_t len; uint32_t n = 0; len = sizeof (n); if (::sysctlbyname("hw.optional.watchpoint", &n, &len, NULL, 0) == 0) { g_num_supported_hw_watchpoints = n; DNBLogThreadedIf(LOG_THREAD, "hw.optional.watchpoint=%u", n); } else { // Read the DBGDIDR to get the number of available hardware breakpoints // However, in some of our current armv7 processors, hardware // breakpoints/watchpoints were not properly connected. So detect those // cases using a field in a sysctl. For now we are using "hw.cpusubtype" // field to distinguish CPU architectures. This is a hack until we can // get fixed, at which point we will switch to // using a different sysctl string that will tell us how many WRPs // are available to us directly without having to read DBGDIDR. uint32_t register_DBGDIDR; asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); uint32_t numWRPs = bits(register_DBGDIDR, 31, 28) + 1; DNBLogThreadedIf(LOG_THREAD, "DBGDIDR=0x%8.8x (number WRP pairs = %u)", register_DBGDIDR, numWRPs); if (numWRPs > 0) { uint32_t cpusubtype; size_t len; len = sizeof(cpusubtype); // TODO: remove this hack and change to using hw.optional.xx when implmented if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) { DNBLogThreadedIf(LOG_THREAD, "hw.cpusubtype=0x%d", cpusubtype); if (cpusubtype == CPU_SUBTYPE_ARM_V7) DNBLogThreadedIf(LOG_THREAD, "Hardware watchpoints disabled for armv7 (rdar://problem/6372672)"); else g_num_supported_hw_watchpoints = numWRPs; } } } } return g_num_supported_hw_watchpoints; } uint32_t DNBArchMachARM::EnableHardwareBreakpoint (nub_addr_t addr, nub_size_t size) { // Make sure our address isn't bogus if (addr & 1) return INVALID_NUB_HW_INDEX; kern_return_t kret = GetDBGState(false); if (kret == KERN_SUCCESS) { const uint32_t num_hw_breakpoints = NumSupportedHardwareBreakpoints(); uint32_t i; for (i=0; i 0x%8.8x.", kret); if (kret == KERN_SUCCESS) return i; } else { DNBLogThreadedIf (LOG_BREAKPOINTS, "DNBArchMachARM::EnableHardwareBreakpoint(addr = 0x%8.8llx, size = %zu) => all hardware breakpoint resources are being used.", (uint64_t)addr, size); } } return INVALID_NUB_HW_INDEX; } bool DNBArchMachARM::DisableHardwareBreakpoint (uint32_t hw_index) { kern_return_t kret = GetDBGState(false); const uint32_t num_hw_points = NumSupportedHardwareBreakpoints(); if (kret == KERN_SUCCESS) { if (hw_index < num_hw_points) { m_state.dbg.__bcr[hw_index] = 0; DNBLogThreadedIf(LOG_BREAKPOINTS, "DNBArchMachARM::SetHardwareBreakpoint( %u ) - BVR%u = 0x%8.8x BCR%u = 0x%8.8x", hw_index, hw_index, m_state.dbg.__bvr[hw_index], hw_index, m_state.dbg.__bcr[hw_index]); kret = SetDBGState(); if (kret == KERN_SUCCESS) return true; } } return false; } uint32_t DNBArchMachARM::EnableHardwareWatchpoint (nub_addr_t addr, nub_size_t size, bool read, bool write) { DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint(addr = 0x%8.8llx, size = %zu, read = %u, write = %u)", (uint64_t)addr, size, read, write); const uint32_t num_hw_watchpoints = NumSupportedHardwareWatchpoints(); // Can't watch zero bytes if (size == 0) return INVALID_NUB_HW_INDEX; // We must watch for either read or write if (read == false && write == false) return INVALID_NUB_HW_INDEX; // Can't watch more than 4 bytes per WVR/WCR pair if (size > 4) return INVALID_NUB_HW_INDEX; // We can only watch up to four bytes that follow a 4 byte aligned address // per watchpoint register pair. Since we can only watch until the next 4 // byte boundary, we need to make sure we can properly encode this. uint32_t addr_word_offset = addr % 4; DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint() - addr_word_offset = 0x%8.8x", addr_word_offset); uint32_t byte_mask = ((1u << size) - 1u) << addr_word_offset; DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint() - byte_mask = 0x%8.8x", byte_mask); if (byte_mask > 0xfu) return INVALID_NUB_HW_INDEX; // Read the debug state kern_return_t kret = GetDBGState(false); if (kret == KERN_SUCCESS) { // Check to make sure we have the needed hardware support uint32_t i = 0; for (i=0; i 0x%8.8x.", kret); if (kret == KERN_SUCCESS) return i; } else { DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::EnableHardwareWatchpoint(): All hardware resources (%u) are in use.", num_hw_watchpoints); } } return INVALID_NUB_HW_INDEX; } bool DNBArchMachARM::DisableHardwareWatchpoint (uint32_t hw_index) { kern_return_t kret = GetDBGState(false); const uint32_t num_hw_points = NumSupportedHardwareWatchpoints(); if (kret == KERN_SUCCESS) { if (hw_index < num_hw_points) { m_state.dbg.__wcr[hw_index] = 0; DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchMachARM::ClearHardwareWatchpoint( %u ) - WVR%u = 0x%8.8x WCR%u = 0x%8.8x", hw_index, hw_index, m_state.dbg.__wvr[hw_index], hw_index, m_state.dbg.__wcr[hw_index]); kret = SetDBGState(); if (kret == KERN_SUCCESS) return true; } } return false; } //---------------------------------------------------------------------- // Register information defintions for 32 bit ARMV6. //---------------------------------------------------------------------- enum gpr_regnums { gpr_r0 = 0, gpr_r1, gpr_r2, gpr_r3, gpr_r4, gpr_r5, gpr_r6, gpr_r7, gpr_r8, gpr_r9, gpr_r10, gpr_r11, gpr_r12, gpr_sp, gpr_lr, gpr_pc, gpr_cpsr }; enum { vfp_s0 = 0, vfp_s1, vfp_s2, vfp_s3, vfp_s4, vfp_s5, vfp_s6, vfp_s7, vfp_s8, vfp_s9, vfp_s10, vfp_s11, vfp_s12, vfp_s13, vfp_s14, vfp_s15, vfp_s16, vfp_s17, vfp_s18, vfp_s19, vfp_s20, vfp_s21, vfp_s22, vfp_s23, vfp_s24, vfp_s25, vfp_s26, vfp_s27, vfp_s28, vfp_s29, vfp_s30, vfp_s31, vfp_d0, vfp_d1, vfp_d2, vfp_d3, vfp_d4, vfp_d5, vfp_d6, vfp_d7, vfp_d8, vfp_d9, vfp_d10, vfp_d11, vfp_d12, vfp_d13, vfp_d14, vfp_d15, vfp_d16, vfp_d17, vfp_d18, vfp_d19, vfp_d20, vfp_d21, vfp_d22, vfp_d23, vfp_d24, vfp_d25, vfp_d26, vfp_d27, vfp_d28, vfp_d29, vfp_d30, vfp_d31, vfp_fpscr }; enum { exc_exception, exc_fsr, exc_far, }; enum { gdb_r0 = 0, gdb_r1, gdb_r2, gdb_r3, gdb_r4, gdb_r5, gdb_r6, gdb_r7, gdb_r8, gdb_r9, gdb_r10, gdb_r11, gdb_r12, gdb_sp, gdb_lr, gdb_pc, gdb_f0, gdb_f1, gdb_f2, gdb_f3, gdb_f4, gdb_f5, gdb_f6, gdb_f7, gdb_f8, gdb_cpsr, gdb_s0, gdb_s1, gdb_s2, gdb_s3, gdb_s4, gdb_s5, gdb_s6, gdb_s7, gdb_s8, gdb_s9, gdb_s10, gdb_s11, gdb_s12, gdb_s13, gdb_s14, gdb_s15, gdb_s16, gdb_s17, gdb_s18, gdb_s19, gdb_s20, gdb_s21, gdb_s22, gdb_s23, gdb_s24, gdb_s25, gdb_s26, gdb_s27, gdb_s28, gdb_s29, gdb_s30, gdb_s31, gdb_fpscr, gdb_d0, gdb_d1, gdb_d2, gdb_d3, gdb_d4, gdb_d5, gdb_d6, gdb_d7, gdb_d8, gdb_d9, gdb_d10, gdb_d11, gdb_d12, gdb_d13, gdb_d14, gdb_d15 }; #define GPR_OFFSET_IDX(idx) (offsetof (DNBArchMachARM::GPR, __r[idx])) #define GPR_OFFSET_NAME(reg) (offsetof (DNBArchMachARM::GPR, __##reg)) #define VFP_S_OFFSET_IDX(idx) (offsetof (DNBArchMachARM::FPU, __r[(idx)]) + offsetof (DNBArchMachARM::Context, vfp)) #define VFP_D_OFFSET_IDX(idx) (VFP_S_OFFSET_IDX ((idx) * 2)) #define VFP_OFFSET_NAME(reg) (offsetof (DNBArchMachARM::FPU, __##reg) + offsetof (DNBArchMachARM::Context, vfp)) #define EXC_OFFSET(reg) (offsetof (DNBArchMachARM::EXC, __##reg) + offsetof (DNBArchMachARM::Context, exc)) // These macros will auto define the register name, alt name, register size, // register offset, encoding, format and native register. This ensures that // the register state structures are defined correctly and have the correct // sizes and offsets. #define DEFINE_GPR_IDX(idx, reg, alt, gen) { e_regSetGPR, gpr_##reg, #reg, alt, Uint, Hex, 4, GPR_OFFSET_IDX(idx), gcc_##reg, dwarf_##reg, gen, gdb_##reg } #define DEFINE_GPR_NAME(reg, alt, gen) { e_regSetGPR, gpr_##reg, #reg, alt, Uint, Hex, 4, GPR_OFFSET_NAME(reg), gcc_##reg, dwarf_##reg, gen, gdb_##reg } //#define FLOAT_FORMAT Float #define FLOAT_FORMAT Hex #define DEFINE_VFP_S_IDX(idx) { e_regSetVFP, vfp_s##idx, "s" #idx, NULL, IEEE754, FLOAT_FORMAT, 4, VFP_S_OFFSET_IDX(idx), INVALID_NUB_REGNUM, dwarf_s##idx, INVALID_NUB_REGNUM, gdb_s##idx } //#define DEFINE_VFP_D_IDX(idx) { e_regSetVFP, vfp_d##idx, "d" #idx, NULL, IEEE754, Float, 8, VFP_D_OFFSET_IDX(idx), INVALID_NUB_REGNUM, dwarf_d##idx, INVALID_NUB_REGNUM, gdb_d##idx } #define DEFINE_VFP_D_IDX(idx) { e_regSetVFP, vfp_d##idx, "d" #idx, NULL, IEEE754, FLOAT_FORMAT, 8, VFP_D_OFFSET_IDX(idx), INVALID_NUB_REGNUM, dwarf_d##idx, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM } // General purpose registers const DNBRegisterInfo DNBArchMachARM::g_gpr_registers[] = { DEFINE_GPR_IDX ( 0, r0,"arg1", GENERIC_REGNUM_ARG1 ), DEFINE_GPR_IDX ( 1, r1,"arg2", GENERIC_REGNUM_ARG2 ), DEFINE_GPR_IDX ( 2, r2,"arg3", GENERIC_REGNUM_ARG3 ), DEFINE_GPR_IDX ( 3, r3,"arg4", GENERIC_REGNUM_ARG4 ), DEFINE_GPR_IDX ( 4, r4, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX ( 5, r5, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX ( 6, r6, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX ( 7, r7, "fp", GENERIC_REGNUM_FP ), DEFINE_GPR_IDX ( 8, r8, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX ( 9, r9, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX (10, r10, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX (11, r11, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_IDX (12, r12, NULL, INVALID_NUB_REGNUM ), DEFINE_GPR_NAME (sp, "r13", GENERIC_REGNUM_SP ), DEFINE_GPR_NAME (lr, "r14", GENERIC_REGNUM_RA ), DEFINE_GPR_NAME (pc, "r15", GENERIC_REGNUM_PC ), DEFINE_GPR_NAME (cpsr, "flags", GENERIC_REGNUM_FLAGS ) }; // Floating point registers const DNBRegisterInfo DNBArchMachARM::g_vfp_registers[] = { DEFINE_VFP_S_IDX ( 0), DEFINE_VFP_S_IDX ( 1), DEFINE_VFP_S_IDX ( 2), DEFINE_VFP_S_IDX ( 3), DEFINE_VFP_S_IDX ( 4), DEFINE_VFP_S_IDX ( 5), DEFINE_VFP_S_IDX ( 6), DEFINE_VFP_S_IDX ( 7), DEFINE_VFP_S_IDX ( 8), DEFINE_VFP_S_IDX ( 9), DEFINE_VFP_S_IDX (10), DEFINE_VFP_S_IDX (11), DEFINE_VFP_S_IDX (12), DEFINE_VFP_S_IDX (13), DEFINE_VFP_S_IDX (14), DEFINE_VFP_S_IDX (15), DEFINE_VFP_S_IDX (16), DEFINE_VFP_S_IDX (17), DEFINE_VFP_S_IDX (18), DEFINE_VFP_S_IDX (19), DEFINE_VFP_S_IDX (20), DEFINE_VFP_S_IDX (21), DEFINE_VFP_S_IDX (22), DEFINE_VFP_S_IDX (23), DEFINE_VFP_S_IDX (24), DEFINE_VFP_S_IDX (25), DEFINE_VFP_S_IDX (26), DEFINE_VFP_S_IDX (27), DEFINE_VFP_S_IDX (28), DEFINE_VFP_S_IDX (29), DEFINE_VFP_S_IDX (30), DEFINE_VFP_S_IDX (31), DEFINE_VFP_D_IDX (0), DEFINE_VFP_D_IDX (1), DEFINE_VFP_D_IDX (2), DEFINE_VFP_D_IDX (3), DEFINE_VFP_D_IDX (4), DEFINE_VFP_D_IDX (5), DEFINE_VFP_D_IDX (6), DEFINE_VFP_D_IDX (7), DEFINE_VFP_D_IDX (8), DEFINE_VFP_D_IDX (9), DEFINE_VFP_D_IDX (10), DEFINE_VFP_D_IDX (11), DEFINE_VFP_D_IDX (12), DEFINE_VFP_D_IDX (13), DEFINE_VFP_D_IDX (14), DEFINE_VFP_D_IDX (15), DEFINE_VFP_D_IDX (16), DEFINE_VFP_D_IDX (17), DEFINE_VFP_D_IDX (18), DEFINE_VFP_D_IDX (19), DEFINE_VFP_D_IDX (20), DEFINE_VFP_D_IDX (21), DEFINE_VFP_D_IDX (22), DEFINE_VFP_D_IDX (23), DEFINE_VFP_D_IDX (24), DEFINE_VFP_D_IDX (25), DEFINE_VFP_D_IDX (26), DEFINE_VFP_D_IDX (27), DEFINE_VFP_D_IDX (28), DEFINE_VFP_D_IDX (29), DEFINE_VFP_D_IDX (30), DEFINE_VFP_D_IDX (31), { e_regSetVFP, vfp_fpscr, "fpscr", NULL, Uint, Hex, 4, VFP_OFFSET_NAME(fpscr), INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, gdb_fpscr } }; // Exception registers const DNBRegisterInfo DNBArchMachARM::g_exc_registers[] = { { e_regSetVFP, exc_exception , "exception" , NULL, Uint, Hex, 4, EXC_OFFSET(exception) , INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM }, { e_regSetVFP, exc_fsr , "fsr" , NULL, Uint, Hex, 4, EXC_OFFSET(fsr) , INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM }, { e_regSetVFP, exc_far , "far" , NULL, Uint, Hex, 4, EXC_OFFSET(far) , INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM, INVALID_NUB_REGNUM } }; // Number of registers in each register set const size_t DNBArchMachARM::k_num_gpr_registers = sizeof(g_gpr_registers)/sizeof(DNBRegisterInfo); const size_t DNBArchMachARM::k_num_vfp_registers = sizeof(g_vfp_registers)/sizeof(DNBRegisterInfo); const size_t DNBArchMachARM::k_num_exc_registers = sizeof(g_exc_registers)/sizeof(DNBRegisterInfo); const size_t DNBArchMachARM::k_num_all_registers = k_num_gpr_registers + k_num_vfp_registers + k_num_exc_registers; //---------------------------------------------------------------------- // Register set definitions. The first definitions at register set index // of zero is for all registers, followed by other registers sets. The // register information for the all register set need not be filled in. //---------------------------------------------------------------------- const DNBRegisterSetInfo DNBArchMachARM::g_reg_sets[] = { { "ARM Registers", NULL, k_num_all_registers }, { "General Purpose Registers", g_gpr_registers, k_num_gpr_registers }, { "Floating Point Registers", g_vfp_registers, k_num_vfp_registers }, { "Exception State Registers", g_exc_registers, k_num_exc_registers } }; // Total number of register sets for this architecture const size_t DNBArchMachARM::k_num_register_sets = sizeof(g_reg_sets)/sizeof(DNBRegisterSetInfo); const DNBRegisterSetInfo * DNBArchMachARM::GetRegisterSetInfo(nub_size_t *num_reg_sets) { *num_reg_sets = k_num_register_sets; return g_reg_sets; } bool DNBArchMachARM::GetRegisterValue(int set, int reg, DNBRegisterValue *value) { if (set == REGISTER_SET_GENERIC) { switch (reg) { case GENERIC_REGNUM_PC: // Program Counter set = e_regSetGPR; reg = gpr_pc; break; case GENERIC_REGNUM_SP: // Stack Pointer set = e_regSetGPR; reg = gpr_sp; break; case GENERIC_REGNUM_FP: // Frame Pointer set = e_regSetGPR; reg = gpr_r7; // is this the right reg? break; case GENERIC_REGNUM_RA: // Return Address set = e_regSetGPR; reg = gpr_lr; break; case GENERIC_REGNUM_FLAGS: // Processor flags register set = e_regSetGPR; reg = gpr_cpsr; break; default: return false; } } if (GetRegisterState(set, false) != KERN_SUCCESS) return false; const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); if (regInfo) { value->info = *regInfo; switch (set) { case e_regSetGPR: if (reg < k_num_gpr_registers) { value->value.uint32 = m_state.context.gpr.__r[reg]; return true; } break; case e_regSetVFP: if (reg <= vfp_s31) { value->value.uint32 = m_state.context.vfp.__r[reg]; return true; } else if (reg <= vfp_d31) { uint32_t d_reg_idx = reg - vfp_d0; uint32_t s_reg_idx = d_reg_idx * 2; value->value.v_sint32[0] = m_state.context.vfp.__r[s_reg_idx + 0]; value->value.v_sint32[1] = m_state.context.vfp.__r[s_reg_idx + 1]; return true; } else if (reg == vfp_fpscr) { value->value.uint32 = m_state.context.vfp.__fpscr; return true; } break; case e_regSetEXC: if (reg < k_num_exc_registers) { value->value.uint32 = (&m_state.context.exc.__exception)[reg]; return true; } break; } } return false; } bool DNBArchMachARM::SetRegisterValue(int set, int reg, const DNBRegisterValue *value) { if (set == REGISTER_SET_GENERIC) { switch (reg) { case GENERIC_REGNUM_PC: // Program Counter set = e_regSetGPR; reg = gpr_pc; break; case GENERIC_REGNUM_SP: // Stack Pointer set = e_regSetGPR; reg = gpr_sp; break; case GENERIC_REGNUM_FP: // Frame Pointer set = e_regSetGPR; reg = gpr_r7; break; case GENERIC_REGNUM_RA: // Return Address set = e_regSetGPR; reg = gpr_lr; break; case GENERIC_REGNUM_FLAGS: // Processor flags register set = e_regSetGPR; reg = gpr_cpsr; break; default: return false; } } if (GetRegisterState(set, false) != KERN_SUCCESS) return false; bool success = false; const DNBRegisterInfo *regInfo = m_thread->GetRegisterInfo(set, reg); if (regInfo) { switch (set) { case e_regSetGPR: if (reg < k_num_gpr_registers) { m_state.context.gpr.__r[reg] = value->value.uint32; success = true; } break; case e_regSetVFP: if (reg <= vfp_s31) { m_state.context.vfp.__r[reg] = value->value.uint32; success = true; } else if (reg <= vfp_d31) { uint32_t d_reg_idx = reg - vfp_d0; uint32_t s_reg_idx = d_reg_idx * 2; m_state.context.vfp.__r[s_reg_idx + 0] = value->value.v_sint32[0]; m_state.context.vfp.__r[s_reg_idx + 1] = value->value.v_sint32[1]; success = true; } else if (reg == vfp_fpscr) { m_state.context.vfp.__fpscr = value->value.uint32; success = true; } break; case e_regSetEXC: if (reg < k_num_exc_registers) { (&m_state.context.exc.__exception)[reg] = value->value.uint32; success = true; } break; } } if (success) return SetRegisterState(set) == KERN_SUCCESS; return false; } kern_return_t DNBArchMachARM::GetRegisterState(int set, bool force) { switch (set) { case e_regSetALL: return GetGPRState(force) | GetVFPState(force) | GetEXCState(force) | GetDBGState(force); case e_regSetGPR: return GetGPRState(force); case e_regSetVFP: return GetVFPState(force); case e_regSetEXC: return GetEXCState(force); case e_regSetDBG: return GetDBGState(force); default: break; } return KERN_INVALID_ARGUMENT; } kern_return_t DNBArchMachARM::SetRegisterState(int set) { // Make sure we have a valid context to set. kern_return_t err = GetRegisterState(set, false); if (err != KERN_SUCCESS) return err; switch (set) { case e_regSetALL: return SetGPRState() | SetVFPState() | SetEXCState() | SetDBGState(); case e_regSetGPR: return SetGPRState(); case e_regSetVFP: return SetVFPState(); case e_regSetEXC: return SetEXCState(); case e_regSetDBG: return SetDBGState(); default: break; } return KERN_INVALID_ARGUMENT; } bool DNBArchMachARM::RegisterSetStateIsValid (int set) const { return m_state.RegsAreValid(set); } nub_size_t DNBArchMachARM::GetRegisterContext (void *buf, nub_size_t buf_len) { nub_size_t size = sizeof (m_state.context); if (buf && buf_len) { if (size > buf_len) size = buf_len; bool force = false; if (GetGPRState(force) | GetVFPState(force) | GetEXCState(force)) return 0; ::memcpy (buf, &m_state.context, size); } DNBLogThreadedIf (LOG_THREAD, "DNBArchMachARM::GetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); // Return the size of the register context even if NULL was passed in return size; } nub_size_t DNBArchMachARM::SetRegisterContext (const void *buf, nub_size_t buf_len) { nub_size_t size = sizeof (m_state.context); if (buf == NULL || buf_len == 0) size = 0; if (size) { if (size > buf_len) size = buf_len; ::memcpy (&m_state.context, buf, size); SetGPRState(); SetVFPState(); SetEXCState(); } DNBLogThreadedIf (LOG_THREAD, "DNBArchMachARM::SetRegisterContext (buf = %p, len = %zu) => %zu", buf, buf_len, size); return size; } #endif // #if defined (__arm__)