// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // ==++== // // // ==--== #ifndef _TARGET_ARM_ #define _TARGET_ARM_ #endif #include "strike.h" #include "util.h" #include #include "disasm.h" #include "../../../inc/corhdr.h" #include "../../../inc/cor.h" #include "../../../inc/dacprivate.h" #ifndef FEATURE_PAL namespace ARMGCDump { #undef _TARGET_X86_ #undef LIMITED_METHOD_CONTRACT #define LIMITED_METHOD_DAC_CONTRACT #define SUPPORTS_DAC #define LF_GCROOTS #define LL_INFO1000 #define LOG(x) #define LOG_PIPTR(pObjRef, gcFlags, hCallBack) #define DAC_ARG(x) #include "gcdumpnonx86.cpp" } #endif // !FEATURE_PAL #if defined(_TARGET_WIN64_) #error This file does not support SOS targeting ARM from a 64-bit debugger #endif #if !defined(SOS_TARGET_ARM) #error This file should be used to support SOS targeting ARM debuggees #endif #ifdef SOS_TARGET_ARM ARMMachine ARMMachine::s_ARMMachineInstance; // Decodes the target label of the immediate form of bl and blx instructions. The PC given is that of the // start of the instruction. static TADDR DecodeCallTarget(TADDR PC, WORD rgInstr[2]) { // Displacement is spread across several bitfields in the two words of the instruction. Using the same // bitfield names as the ARM Architecture Reference Manual. DWORD S = (rgInstr[0] & 0x0400) >> 10; DWORD imm10 = rgInstr[0] & 0x03ff; DWORD J1 = (rgInstr[1] & 0x2000) >> 13; DWORD J2 = (rgInstr[1] & 0x0800) >> 11; DWORD imm11 = rgInstr[1] & 0x07ff; // For reasons that escape me the I1 and I2 fields are computed by XOR'ing J1 and J2 with S. DWORD I1 = (~J1 ^ S) & 0x1; DWORD I2 = (~J2 ^ S) & 0x1; // The final displacement is put together as: SignExtend(S:I1:I2:imm10:imm11:0) DWORD highByte = S ? 0xff000000 : 0x00000000; DWORD disp = highByte | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1); // The displacement is relative to the PC but the PC for a given instruction reads as the PC for the // beginning of the instruction plus 4. return PC + 4 + disp; } // Validate that a potential call target points to readable memory. If so, and the code appears to be one of // our standard jump thunks we'll deference through that and return the real target. Returns 0 if any checks // fail. static TADDR GetRealCallTarget(TADDR PC) { WORD instr[2]; // Read the minimum (a WORD) first in case we're calling to a single WORD method at the end of a page // (e.g. BLX ). if (g_ExtData->ReadVirtual(TO_CDADDR(PC), &instr[0], sizeof(WORD), NULL) != S_OK) return 0; // All the jump thunks we handle start with the literal form of LDR (i.e. LDR , [PC +/- ]). It's // always the two word form since we're either loading R12 or PC. We never use the decrement version of // the instruction (U == 0). // If it's not an instruction of that form we can return immediately. if (instr[0] != 0xf8df) return PC; // The first instruction is defintely a LDR of the form we expect so it's OK to read the second half of // the encoding. if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 2), &instr[1], sizeof(WORD), NULL) != S_OK) return 0; // Determine which register we're loading. There are three cases: // 1) PC: we're jumping, perform final calculation of the jump target // 2) R12: we're possibly setting up a special argument to the jump target. Ignore this instruction and // check for a LDR PC in the next instruction // 3) Any other register: we don't recognize this instruction sequence, just return the PC we have WORD reg = (instr[1] & 0xf000) >> 12; if (reg == 12) { // Possibly a LDR R12, [...]; LDR PC, [...] thunk. Overwrite the current instruction with the next and // then fall through into the common LDR PC, [...] handling below. If we fail to read the next word // we're not really looking at valid code. But we need to be more careful reading the second word of // the potential instruction since there are valid sequences that would terminate with a single word // at the end of page. if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 4), &instr[0], sizeof(WORD), NULL) != S_OK) return 0; // Following instruction is not a LDR . Return this PC as the real target. if (instr[0] != 0xf8df) return PC; // Read second half of the LDR instruction. if (g_ExtData->ReadVirtual(TO_CDADDR(PC + 6), &instr[1], sizeof(WORD), NULL) != S_OK) return 0; // Determine the target register. If it's not the PC then return this PC as the real target. reg = (instr[1] & 0xf000) >> 12; if (reg != 12) return PC; // Fall through to process this LDR PC, [...] instruction. Update the input PC because it figures into // the calculation below. PC += 4; } else if (reg == 15) { // First instruction was a LDR PC, [...] Just fall through to common handling below. } else { // Any other target register is unrecognized. Just return what we have as the final target. return PC; } // Decode the LDR PC, [PC + ] to find the jump target. // The displacement is in the low order 12 bits of the second instruction word. DWORD disp = instr[1] & 0x0fff; // The PC used for the effective address calculation is the PC from the start of the instruction rounded // down to 4-byte alignment then incremented by 4. TADDR targetAddress = (PC & ~3) + 4 + disp; // Read the target address from this routine. TADDR target; if (g_ExtData->ReadVirtual(TO_CDADDR(targetAddress), &target, sizeof(target), NULL) != S_OK) return 0; // Clear the low-bit in the target used to indicate a Thumb mode destination. If this is not set we can't // be looking at one of our jump thunks (in fact ARM mode code is illegal under CoreARM so this would // indicate an issue). _ASSERTE((target & 1) == 1); target &= ~1; // Recursively call ourselves on this target in case we have any double jump thunks. return GetRealCallTarget(target); } // Determine (heuristically, basically a best effort guess) whether an address on the stack represents a // return address. This is achieved by looking at the memory prior to the potential return address and // disassembling it to see whether it looks like a potential call. If possible the target of the callsite is // also returned. // // Result is returned in whereCalled: // 0 : retAddr doesn't look like a return address // 0xffffffff : retAddr looks like a return address but we couldn't tell where the call site was targeted // : retAddr looks like a return address, *whereCalled set to target address void ARMMachine::IsReturnAddress(TADDR retAddr, TADDR* whereCalled) const { *whereCalled = 0; // If retAddr doesn't have the low-order bit set (indicating a return to Thumb code) then it can't be a // legal return address. if ((retAddr & 1) == 0) return; retAddr &= ~1; // Potential calling instructions may have been one or two WORDs in length. WORD rgPrevious[2]; move_xp(rgPrevious, retAddr - sizeof(rgPrevious)); // Check two-word variants first. if (((rgPrevious[0] & 0xf800) == 0xf000) && ((rgPrevious[1] & 0xd000) == 0xd000)) { // BL