summaryrefslogtreecommitdiff
path: root/src/debug/ee/arm64/arm64walker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/ee/arm64/arm64walker.cpp')
-rw-r--r--src/debug/ee/arm64/arm64walker.cpp476
1 files changed, 476 insertions, 0 deletions
diff --git a/src/debug/ee/arm64/arm64walker.cpp b/src/debug/ee/arm64/arm64walker.cpp
new file mode 100644
index 0000000000..96aff1708f
--- /dev/null
+++ b/src/debug/ee/arm64/arm64walker.cpp
@@ -0,0 +1,476 @@
+// 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.
+//*****************************************************************************
+// File: Arm64walker.cpp
+//
+
+//
+// ARM64 instruction decoding/stepping logic
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "walker.h"
+#include "frames.h"
+#include "openum.h"
+
+#ifdef _TARGET_ARM64_
+
+PCODE Expand19bitoffset(PCODE opcode)
+{
+ opcode = opcode >> 5;
+ PCODE offset = (opcode & 0x7FFFF) << 2; //imm19:00 -> 21 bits
+
+ //Sign Extension
+ if ((offset & 0x100000)) //Check for 21'st bit
+ {
+ offset = offset | 0xFFFFFFFFFFE00000;
+ }
+ return offset;
+}
+
+void NativeWalker::Decode()
+{
+
+ PT_CONTEXT context = NULL;
+ int RegNum = -1;
+ PCODE offset = MAX_INSTRUCTION_LENGTH;
+
+ //Reset so that we do not provide bogus info
+ m_type = WALK_UNKNOWN;
+ m_skipIP = NULL;
+ m_nextIP = NULL;
+
+ if (m_registers == NULL)
+ {
+ //walker does not use WALK_NEXT
+ //Without registers decoding will work only for handful of instructions
+ return;
+ }
+
+ m_skipIP = m_ip + MAX_INSTRUCTION_LENGTH;
+
+ context = m_registers->pCurrentContext;
+ // Fetch first word of the current instruction.If the current instruction is a break instruction, we'll
+ // need to check the patch table to get the correct instruction.
+ PRD_TYPE opcode = CORDbgGetInstruction(m_ip);
+ PRD_TYPE unpatchedOpcode;
+ if (DebuggerController::CheckGetPatchedOpcode(m_ip, &unpatchedOpcode))
+ {
+ opcode = unpatchedOpcode;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decode instruction at %p, opcode: %x\n", m_ip,opcode));
+
+
+
+ if (NativeWalker::DecodeCallInst(opcode, RegNum, m_type)) //Unconditional Branch (register) instructions
+ {
+ if (m_type == WALK_RETURN)
+ {
+ m_skipIP = NULL;
+ }
+ m_nextIP = (BYTE*)GetReg(context, RegNum);
+ return;
+ }
+
+
+ if (NativeWalker::DecodePCRelativeBranchInst(context, opcode, offset, m_type))
+ {
+ if (m_type == WALK_BRANCH)
+ {
+ m_skipIP = NULL;
+ }
+ }
+
+ m_nextIP = m_ip + offset;
+
+
+ return;
+}
+
+
+//When control reaches here m_pSharedPatchBypassBuffer has the original instructions in m_pSharedPatchBypassBuffer->PatchBypass
+BYTE* NativeWalker::SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, SharedPatchBypassBuffer* m_pSharedPatchBypassBuffer, const BYTE *address, PRD_TYPE opcode)
+{
+
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ PCODE offset = 0;
+ PCODE ip = 0;
+ WALK_TYPE walk = WALK_UNKNOWN;
+ int RegNum =-1;
+
+
+ /*
+ Modify the patchBypass if the opcode is IP-relative, otherwise return it
+ The following are the instructions that are IP-relative :
+ • ADR and ADRP.
+ • The Load register (literal) instruction class.
+ • Direct branches that use an immediate offset.
+ • The unconditional branch with link instructions, BL and BLR, that use the PC to create the return link
+ address.
+ */
+
+ _ASSERTE((UINT_PTR)address == context->Pc);
+
+ if ((opcode & 0x1F000000) == 0x10000000) //ADR & ADRP
+ {
+
+ TADDR immhigh = ((opcode >> 5) & 0x007FFFF) << 2;
+ TADDR immlow = (opcode & 0x60000000) >> 29;
+ offset = immhigh | immlow; //ADR
+ RegNum = (opcode & 0x1F);
+
+ //Sign Extension
+ if ((offset & 0x100000)) //Check for 21'st bit
+ {
+ offset = offset | 0xFFFFFFFFFFE00000;
+ }
+
+ if ((opcode & 0x80000000) != 0) //ADRP
+ {
+ offset = offset << 12;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADRP X%d %p\n", opcode, RegNum, offset));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADR X%d %p\n", opcode, RegNum, offset));
+ }
+
+
+ }
+
+ else if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal (General or SIMD)
+ {
+
+ offset = Expand19bitoffset(opcode);
+ RegNum = (opcode & 0x1F);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR[SW] | PRFM X%d %p\n", opcode, RegNum, offset));
+ }
+ else if (NativeWalker::DecodePCRelativeBranchInst(context,opcode, offset, walk))
+ {
+ _ASSERTE(RegNum == -1);
+ }
+ else if (NativeWalker::DecodeCallInst(opcode, RegNum, walk))
+ {
+ _ASSERTE(offset == 0);
+ }
+ //else Just execute the opcodes as is
+ //{
+ //}
+
+ if (offset != 0) // calculate the next ip from current ip
+ {
+ ip = (PCODE)address + offset;
+ }
+ else if(RegNum >= 0)
+ {
+ ip = GetReg(context, RegNum);
+ }
+
+ //Do instruction emulation inplace here
+
+ if (walk == WALK_BRANCH || walk == WALK_CALL || walk == WALK_RETURN)
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer
+
+ m_pSharedPatchBypassBuffer->RipTargetFixup = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Control Flow instr \n", opcode));
+
+ if (walk == WALK_CALL) //initialize Lr
+ {
+ SetLR(context, (PCODE)address + MAX_INSTRUCTION_LENGTH);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Call instr, setting LR to %p \n", opcode,GetLR(context)));
+ }
+ }
+ else if(RegNum >= 0)
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer
+
+ PCODE RegContents;
+ if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal
+ {
+ RegContents = (PCODE)GetMem(ip);
+ if ((opcode & 0x4000000)) //LDR literal for SIMD
+ {
+ NEON128 SimdRegContents;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR V%d %p\n", opcode, RegNum, offset));
+ short opc = (opcode >> 30);
+ switch (opc)
+ {
+ case 0: //4byte data into St
+ RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bit
+ SetReg(context, RegNum, RegContents);
+ case 1: //8byte data into Dt
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 2: //SIMD 16 byte data
+ SimdRegContents = GetSimdMem(ip);
+ SetSimdReg(context, RegNum, SimdRegContents);
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(litera,SIMD &FP)] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Simulated Unknown opcode"));
+
+ }
+ }
+ else
+ {
+ short opc = (opcode >> 30);
+ switch (opc)
+ {
+ case 0: //4byte data into Wt
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR W%d %p\n", opcode, RegNum, offset));
+ RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bits
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 1: //8byte data into Xt
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR X%d %p\n", opcode, RegNum, offset));
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 2: //LDRSW
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDRSW X%d %p\n", opcode, RegNum, offset));
+ RegContents = 0xFFFFFFFF & RegContents;
+
+ if (RegContents & 0x80000000) //Sign extend the Word
+ {
+ RegContents = 0xFFFFFFFF00000000 | RegContents;
+ }
+ SetReg(context, RegNum, RegContents);
+ break;
+ case 3:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x as PRFM ,but do nothing \n", opcode));
+
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(literal)] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Simulated Unknown opcode"));
+
+ }
+ }
+ }
+ else
+ {
+ RegContents = ip;
+ SetReg(context, RegNum, RegContents);
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to update Reg X[V]%d, as %p \n", opcode, RegNum, GetReg(context, RegNum)));
+ }
+ //else Just execute the opcodes as IS
+ //{
+ //}
+
+ return patchBypass;
+}
+
+//Decodes PC Relative Branch Instructions
+//This code is shared between the NativeWalker and DebuggerPatchSkip.
+//So ENSURE THIS FUNCTION DOES NOT CHANGE ANY STATE OF THE DEBUGEE
+//This Function Decodes :
+// BL offset
+// B offset
+// B.Cond offset
+// CB[N]Z X<r> offset
+// TB[N]Z X<r> offset
+
+//Output of the Function are:
+//offset - Offset from current PC to which control will go next
+//WALK_TYPE
+
+BOOL NativeWalker::DecodePCRelativeBranchInst(PT_CONTEXT context, const PRD_TYPE& opcode, PCODE& offset, WALK_TYPE& walk)
+{
+#ifdef _DEBUG
+ PCODE incomingoffset = offset;
+ WALK_TYPE incomingwalk = walk;
+#endif
+
+ if ((opcode & 0x7C000000) == 0x14000000) // Decode B & BL
+ {
+ offset = (opcode & 0x03FFFFFF) << 2;
+ // Sign extension
+ if ((offset & 0x4000000)) //Check for 26'st bit
+ {
+ offset = offset | 0xFFFFFFFFF8000000;
+ }
+
+ if ((opcode & 0x80000000) != 0) //BL
+ {
+ walk = WALK_CALL;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BL %p \n", opcode, offset));
+ }
+ else
+ {
+ walk = WALK_BRANCH; //B
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B %p \n", opcode, offset));
+ }
+ return TRUE;
+ }
+
+ //Conditional Branches
+ _ASSERTE(context != NULL);
+
+
+ if ((opcode & 0xFF000010) == 0x54000000) // B.cond
+ {
+ WORD cond = opcode & 0xF;
+ bool result = false;
+ switch (cond >> 1)
+ {
+ case 0x0: result = (context->Cpsr & NZCV_Z) != 0; // EQ or NE
+ break;
+ case 0x1: result = (context->Cpsr & NZCV_C) != 0; // CS or CC
+ break;
+ case 0x2: result = (context->Cpsr & NZCV_N) != 0; // MI or PL
+ break;
+ case 0x3: result = (context->Cpsr & NZCV_V) != 0; // VS or VC
+ break;
+ case 0x4: result = ((context->Cpsr & NZCV_C) != 0) && ((context->Cpsr & NZCV_Z) == 0); // HI or LS
+ break;
+ case 0x5: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT); // GE or LT
+ break;
+ case 0x6: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT) && ((context->Cpsr & NZCV_Z) == 0); // GT or LE
+ break;
+ case 0x7: result = true; // AL
+ break;
+ }
+
+ if ((cond & 0x1) && (cond & 0xF) != 0) { result = !result; }
+
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = Expand19bitoffset(opcode);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond %p \n", opcode, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+ return TRUE;
+
+ }
+
+
+ int RegNum = opcode & 0x1F;
+ PCODE RegContent = GetReg(context, RegNum);
+
+ if ((opcode & 0xFE000000) == 0x34000000) // CBNZ || CBZ
+ {
+ bool result = false;
+
+ if (!(opcode & 0x80000000)) //if sf == '1' the 64 else 32
+ {
+ RegContent = 0xFFFFFFFF & RegContent; //zero the upper 32bit
+ }
+
+ if (opcode & 0x01000000) //CBNZ
+ {
+ result = RegContent != 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBNZ X%d \n", opcode, RegNum));
+ }
+ else //CBZ
+ {
+ result = RegContent == 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBZ X%d \n", opcode, RegNum));
+ }
+
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = Expand19bitoffset(opcode);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d %p \n", opcode, RegNum, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+
+ return TRUE;
+ }
+ if ((opcode & 0x7E000000) == 0x36000000) // TBNZ || TBZ
+ {
+ bool result = false;
+ int bit_pos = ((opcode >> 19) & 0x1F);
+
+ if (opcode & 0x80000000)
+ {
+ bit_pos = bit_pos + 32;
+ }
+
+ PCODE bit_val = 1 << bit_pos;
+ if (opcode & 0x01000000) //TBNZ
+ {
+ result = (RegContent & bit_val) != 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TBNZ X%d \n", opcode, RegNum));
+ }
+ else //TBZ
+ {
+ result = (RegContent & bit_val) == 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d \n", opcode, RegNum));
+ }
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = ((opcode >> 5) & 0x3FFF) << 2; //imm14:00 -> 16 bits
+ if (offset & 0x8000) //sign extension check for 16'th bit
+ {
+ offset = offset | 0xFFFFFFFFFFFF0000;
+ }
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TB[N]Z X%d %p \n", opcode, RegNum, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+ return TRUE;
+ }
+
+ _ASSERTE(offset == incomingoffset);
+ _ASSERTE(walk == incomingwalk);
+ return FALSE;
+}
+
+BOOL NativeWalker::DecodeCallInst(const PRD_TYPE& opcode, int& RegNum, WALK_TYPE& walk)
+{
+ if ((opcode & 0xFF9FFC1F) == 0xD61F0000) // BR, BLR or RET -Unconditional Branch (register) instructions
+ {
+
+ RegNum = (opcode & 0x3E0) >> 5;
+
+
+ short op = (opcode & 0x00600000) >> 21; //Checking for 23 and 22 bits
+ switch (op)
+ {
+ case 0: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BR X%d\n", opcode, RegNum));
+ walk = WALK_BRANCH;
+ break;
+ case 1: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BLR X%d\n", opcode, RegNum));
+ walk = WALK_CALL;
+ break;
+ case 2: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to Ret X%d\n", opcode, RegNum));
+ walk = WALK_RETURN;
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [Branch] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Decoded Unknown opcode"));
+ }
+
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif