/** @file | |
Processor specific parts of the GDB stub | |
Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <GdbStubInternal.h> | |
#include <Library/CacheMaintenanceLib.h> | |
#include <Library/PrintLib.h> | |
// | |
// Array of exception types that need to be hooked by the debugger | |
// (efi, gdb) //efi number | |
// | |
EFI_EXCEPTION_TYPE_ENTRY gExceptionType[] = { | |
{ EXCEPT_ARM_SOFTWARE_INTERRUPT, GDB_SIGTRAP } | |
// { EXCEPT_ARM_UNDEFINED_INSTRUCTION, GDB_SIGTRAP }, | |
// { EXCEPT_ARM_PREFETCH_ABORT, GDB_SIGTRAP }, | |
// { EXCEPT_ARM_DATA_ABORT, GDB_SIGEMT }, | |
// { EXCEPT_ARM_RESERVED, GDB_SIGILL } | |
}; | |
UINTN gRegisterOffsets[] = { | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R0), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R1), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R2), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R3), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R4), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R5), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R6), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R7), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R8), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R9), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R10), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R11), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, R12), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, SP), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, LR), | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, PC), | |
0x00000F01, // f0 | |
0x00000F02, | |
0x00000F03, | |
0x00000F11, // f1 | |
0x00000F12, | |
0x00000F13, | |
0x00000F21, // f2 | |
0x00000F22, | |
0x00000F23, | |
0x00000F31, // f3 | |
0x00000F32, | |
0x00000F33, | |
0x00000F41, // f4 | |
0x00000F42, | |
0x00000F43, | |
0x00000F51, // f5 | |
0x00000F52, | |
0x00000F53, | |
0x00000F61, // f6 | |
0x00000F62, | |
0x00000F63, | |
0x00000F71, // f7 | |
0x00000F72, | |
0x00000F73, | |
0x00000FFF, // fps | |
OFFSET_OF (EFI_SYSTEM_CONTEXT_ARM, CPSR) | |
}; | |
/** | |
Return the number of entries in the gExceptionType[] | |
@retval UINTN, the number of entries in the gExceptionType[] array. | |
**/ | |
UINTN | |
MaxEfiException ( | |
VOID | |
) | |
{ | |
return sizeof (gExceptionType) / sizeof (EFI_EXCEPTION_TYPE_ENTRY); | |
} | |
/** | |
Return the number of entries in the gRegisters[] | |
@retval UINTN, the number of entries (registers) in the gRegisters[] array. | |
**/ | |
UINTN | |
MaxRegisterCount ( | |
VOID | |
) | |
{ | |
return sizeof (gRegisterOffsets) / sizeof (UINTN); | |
} | |
/** | |
Check to see if the ISA is supported. | |
ISA = Instruction Set Architecture | |
@retval TRUE if Isa is supported | |
**/ | |
BOOLEAN | |
CheckIsa ( | |
IN EFI_INSTRUCTION_SET_ARCHITECTURE Isa | |
) | |
{ | |
if (Isa == IsaArm) { | |
return TRUE; | |
} else { | |
return FALSE; | |
} | |
} | |
/** | |
This takes in the register number and the System Context, and returns a pointer to the RegNumber-th register in gdb ordering | |
It is, by default, set to find the register pointer of the ARM member | |
@param SystemContext Register content at time of the exception | |
@param RegNumber The register to which we want to find a pointer | |
@retval the pointer to the RegNumber-th pointer | |
**/ | |
UINTN * | |
FindPointerToRegister ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN UINTN RegNumber | |
) | |
{ | |
UINT8 *TempPtr; | |
ASSERT (gRegisterOffsets[RegNumber] < 0xF00); | |
TempPtr = ((UINT8 *)SystemContext.SystemContextArm) + gRegisterOffsets[RegNumber]; | |
return (UINT32 *)TempPtr; | |
} | |
/** | |
Adds the RegNumber-th register's value to the output buffer, starting at the given OutBufPtr | |
@param SystemContext Register content at time of the exception | |
@param RegNumber the number of the register that we want to read | |
@param OutBufPtr pointer to the output buffer's end. the new data will be added from this point on. | |
@retval the pointer to the next character of the output buffer that is available to be written on. | |
**/ | |
CHAR8 * | |
BasicReadRegister ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN UINTN RegNumber, | |
IN CHAR8 *OutBufPtr | |
) | |
{ | |
UINTN RegSize; | |
CHAR8 Char; | |
if (gRegisterOffsets[RegNumber] > 0xF00) { | |
AsciiSPrint (OutBufPtr, 9, "00000000"); | |
OutBufPtr += 8; | |
return OutBufPtr; | |
} | |
RegSize = 0; | |
while (RegSize < 32) { | |
Char = mHexToStr[(UINT8)((*FindPointerToRegister (SystemContext, RegNumber) >> (RegSize+4)) & 0xf)]; | |
if ((Char >= 'A') && (Char <= 'F')) { | |
Char = Char - 'A' + 'a'; | |
} | |
*OutBufPtr++ = Char; | |
Char = mHexToStr[(UINT8)((*FindPointerToRegister (SystemContext, RegNumber) >> RegSize) & 0xf)]; | |
if ((Char >= 'A') && (Char <= 'F')) { | |
Char = Char - 'A' + 'a'; | |
} | |
*OutBufPtr++ = Char; | |
RegSize = RegSize + 8; | |
} | |
return OutBufPtr; | |
} | |
/** | |
Reads the n-th register's value into an output buffer and sends it as a packet | |
@param SystemContext Register content at time of the exception | |
@param InBuffer Pointer to the input buffer received from gdb server | |
**/ | |
VOID | |
ReadNthRegister ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *InBuffer | |
) | |
{ | |
UINTN RegNumber; | |
CHAR8 OutBuffer[9]; // 1 reg=8 hex chars, and the end '\0' (escape seq) | |
CHAR8 *OutBufPtr; // pointer to the output buffer | |
RegNumber = AsciiStrHexToUintn (&InBuffer[1]); | |
if (RegNumber >= MaxRegisterCount ()) { | |
SendError (GDB_EINVALIDREGNUM); | |
return; | |
} | |
OutBufPtr = OutBuffer; | |
OutBufPtr = BasicReadRegister (SystemContext, RegNumber, OutBufPtr); | |
*OutBufPtr = '\0'; // the end of the buffer | |
SendPacket (OutBuffer); | |
} | |
/** | |
Reads the general registers into an output buffer and sends it as a packet | |
@param SystemContext Register content at time of the exception | |
**/ | |
VOID | |
EFIAPI | |
ReadGeneralRegisters ( | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
UINTN Index; | |
CHAR8 *OutBuffer; | |
CHAR8 *OutBufPtr; | |
UINTN RegisterCount = MaxRegisterCount (); | |
// It is not safe to allocate pool here.... | |
OutBuffer = AllocatePool ((RegisterCount * 8) + 1); // 8 bytes per register in string format plus a null to terminate | |
OutBufPtr = OutBuffer; | |
for (Index = 0; Index < RegisterCount; Index++) { | |
OutBufPtr = BasicReadRegister (SystemContext, Index, OutBufPtr); | |
} | |
*OutBufPtr = '\0'; | |
SendPacket (OutBuffer); | |
FreePool (OutBuffer); | |
} | |
/** | |
Adds the RegNumber-th register's value to the output buffer, starting at the given OutBufPtr | |
@param SystemContext Register content at time of the exception | |
@param RegNumber the number of the register that we want to write | |
@param InBufPtr pointer to the output buffer. the new data will be extracted from the input buffer from this point on. | |
@retval the pointer to the next character of the input buffer that can be used | |
**/ | |
CHAR8 | |
* | |
BasicWriteRegister ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN UINTN RegNumber, | |
IN CHAR8 *InBufPtr | |
) | |
{ | |
UINTN RegSize; | |
UINTN TempValue; // the value transferred from a hex char | |
UINT32 NewValue; // the new value of the RegNumber-th Register | |
if (gRegisterOffsets[RegNumber] > 0xF00) { | |
return InBufPtr + 8; | |
} | |
NewValue = 0; | |
RegSize = 0; | |
while (RegSize < 32) { | |
TempValue = HexCharToInt (*InBufPtr++); | |
if ((INTN)TempValue < 0) { | |
SendError (GDB_EBADMEMDATA); | |
return NULL; | |
} | |
NewValue += (TempValue << (RegSize+4)); | |
TempValue = HexCharToInt (*InBufPtr++); | |
if ((INTN)TempValue < 0) { | |
SendError (GDB_EBADMEMDATA); | |
return NULL; | |
} | |
NewValue += (TempValue << RegSize); | |
RegSize = RegSize + 8; | |
} | |
*(FindPointerToRegister (SystemContext, RegNumber)) = NewValue; | |
return InBufPtr; | |
} | |
/** ‘P n...=r...’ | |
Writes the new value of n-th register received into the input buffer to the n-th register | |
@param SystemContext Register content at time of the exception | |
@param InBuffer Pointer to the input buffer received from gdb server | |
**/ | |
VOID | |
WriteNthRegister ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *InBuffer | |
) | |
{ | |
UINTN RegNumber; | |
CHAR8 RegNumBuffer[MAX_REG_NUM_BUF_SIZE]; // put the 'n..' part of the message into this array | |
CHAR8 *RegNumBufPtr; | |
CHAR8 *InBufPtr; // pointer to the input buffer | |
// find the register number to write | |
InBufPtr = &InBuffer[1]; | |
RegNumBufPtr = RegNumBuffer; | |
while (*InBufPtr != '=') { | |
*RegNumBufPtr++ = *InBufPtr++; | |
} | |
*RegNumBufPtr = '\0'; | |
RegNumber = AsciiStrHexToUintn (RegNumBuffer); | |
// check if this is a valid Register Number | |
if (RegNumber >= MaxRegisterCount ()) { | |
SendError (GDB_EINVALIDREGNUM); | |
return; | |
} | |
InBufPtr++; // skips the '=' character | |
BasicWriteRegister (SystemContext, RegNumber, InBufPtr); | |
SendSuccess (); | |
} | |
/** ‘G XX...’ | |
Writes the new values received into the input buffer to the general registers | |
@param SystemContext Register content at time of the exception | |
@param InBuffer Pointer to the input buffer received from gdb server | |
**/ | |
VOID | |
EFIAPI | |
WriteGeneralRegisters ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *InBuffer | |
) | |
{ | |
UINTN i; | |
CHAR8 *InBufPtr; /// pointer to the input buffer | |
UINTN MinLength; | |
UINTN RegisterCount = MaxRegisterCount (); | |
MinLength = (RegisterCount * 8) + 1; // 'G' plus the registers in ASCII format | |
if (AsciiStrLen (InBuffer) < MinLength) { | |
// Bad message. Message is not the right length | |
SendError (GDB_EBADBUFSIZE); | |
return; | |
} | |
InBufPtr = &InBuffer[1]; | |
// Read the new values for the registers from the input buffer to an array, NewValueArray. | |
// The values in the array are in the gdb ordering | |
for (i = 0; i < RegisterCount; i++) { | |
InBufPtr = BasicWriteRegister (SystemContext, i, InBufPtr); | |
} | |
SendSuccess (); | |
} | |
// What about Thumb? | |
// Use SWI 0xdbdbdb as the debug instruction | |
#define GDB_ARM_BKPT 0xefdbdbdb | |
BOOLEAN mSingleStepActive = FALSE; | |
UINT32 mSingleStepPC; | |
UINT32 mSingleStepData; | |
UINTN mSingleStepDataSize; | |
typedef struct { | |
LIST_ENTRY Link; | |
UINT64 Signature; | |
UINT32 Address; | |
UINT32 Instruction; | |
} ARM_SOFTWARE_BREAKPOINT; | |
#define ARM_SOFTWARE_BREAKPOINT_SIGNATURE SIGNATURE_64('A', 'R', 'M', 'B', 'R', 'K', 'P', 'T') | |
#define ARM_SOFTWARE_BREAKPOINT_FROM_LINK(a) CR(a, ARM_SOFTWARE_BREAKPOINT, Link, ARM_SOFTWARE_BREAKPOINT_SIGNATURE) | |
LIST_ENTRY BreakpointList; | |
/** | |
Insert Single Step in the SystemContext | |
@param SystemContext Register content at time of the exception | |
**/ | |
VOID | |
AddSingleStep ( | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
if (mSingleStepActive) { | |
// Currently don't support nesting | |
return; | |
} | |
mSingleStepActive = TRUE; | |
mSingleStepPC = SystemContext.SystemContextArm->PC; | |
mSingleStepDataSize = sizeof (UINT32); | |
mSingleStepData = (*(UINT32 *)mSingleStepPC); | |
*(UINT32 *)mSingleStepPC = GDB_ARM_BKPT; | |
if (*(UINT32 *)mSingleStepPC != GDB_ARM_BKPT) { | |
// For some reason our breakpoint did not take | |
mSingleStepActive = FALSE; | |
} | |
InvalidateInstructionCacheRange ((VOID *)mSingleStepPC, mSingleStepDataSize); | |
// DEBUG((DEBUG_ERROR, "AddSingleStep at 0x%08x (was: 0x%08x is:0x%08x)\n", SystemContext.SystemContextArm->PC, mSingleStepData, *(UINT32 *)mSingleStepPC)); | |
} | |
/** | |
Remove Single Step in the SystemContext | |
@param SystemContext Register content at time of the exception | |
**/ | |
VOID | |
RemoveSingleStep ( | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
if (!mSingleStepActive) { | |
return; | |
} | |
if (mSingleStepDataSize == sizeof (UINT16)) { | |
*(UINT16 *)mSingleStepPC = (UINT16)mSingleStepData; | |
} else { | |
// DEBUG((DEBUG_ERROR, "RemoveSingleStep at 0x%08x (was: 0x%08x is:0x%08x)\n", SystemContext.SystemContextArm->PC, *(UINT32 *)mSingleStepPC, mSingleStepData)); | |
*(UINT32 *)mSingleStepPC = mSingleStepData; | |
} | |
InvalidateInstructionCacheRange ((VOID *)mSingleStepPC, mSingleStepDataSize); | |
mSingleStepActive = FALSE; | |
} | |
/** | |
Continue. addr is Address to resume. If addr is omitted, resume at current | |
Address. | |
@param SystemContext Register content at time of the exception | |
**/ | |
VOID | |
EFIAPI | |
ContinueAtAddress ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *PacketData | |
) | |
{ | |
if (PacketData[1] != '\0') { | |
SystemContext.SystemContextArm->PC = AsciiStrHexToUintn (&PacketData[1]); | |
} | |
} | |
/** ‘s [addr ]’ | |
Single step. addr is the Address at which to resume. If addr is omitted, resume | |
at same Address. | |
@param SystemContext Register content at time of the exception | |
**/ | |
VOID | |
EFIAPI | |
SingleStep ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *PacketData | |
) | |
{ | |
SendNotSupported (); | |
} | |
UINTN | |
GetBreakpointDataAddress ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN UINTN BreakpointNumber | |
) | |
{ | |
return 0; | |
} | |
UINTN | |
GetBreakpointDetected ( | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
return 0; | |
} | |
BREAK_TYPE | |
GetBreakpointType ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN UINTN BreakpointNumber | |
) | |
{ | |
return NotSupported; | |
} | |
ARM_SOFTWARE_BREAKPOINT * | |
SearchBreakpointList ( | |
IN UINT32 Address | |
) | |
{ | |
LIST_ENTRY *Current; | |
ARM_SOFTWARE_BREAKPOINT *Breakpoint; | |
Current = GetFirstNode (&BreakpointList); | |
while (!IsNull (&BreakpointList, Current)) { | |
Breakpoint = ARM_SOFTWARE_BREAKPOINT_FROM_LINK (Current); | |
if (Address == Breakpoint->Address) { | |
return Breakpoint; | |
} | |
Current = GetNextNode (&BreakpointList, Current); | |
} | |
return NULL; | |
} | |
VOID | |
SetBreakpoint ( | |
IN UINT32 Address | |
) | |
{ | |
ARM_SOFTWARE_BREAKPOINT *Breakpoint; | |
Breakpoint = SearchBreakpointList (Address); | |
if (Breakpoint != NULL) { | |
return; | |
} | |
// create and fill breakpoint structure | |
Breakpoint = AllocatePool (sizeof (ARM_SOFTWARE_BREAKPOINT)); | |
Breakpoint->Signature = ARM_SOFTWARE_BREAKPOINT_SIGNATURE; | |
Breakpoint->Address = Address; | |
Breakpoint->Instruction = *(UINT32 *)Address; | |
// Add it to the list | |
InsertTailList (&BreakpointList, &Breakpoint->Link); | |
// Insert the software breakpoint | |
*(UINT32 *)Address = GDB_ARM_BKPT; | |
InvalidateInstructionCacheRange ((VOID *)Address, 4); | |
// DEBUG((DEBUG_ERROR, "SetBreakpoint at 0x%08x (was: 0x%08x is:0x%08x)\n", Address, Breakpoint->Instruction, *(UINT32 *)Address)); | |
} | |
VOID | |
ClearBreakpoint ( | |
IN UINT32 Address | |
) | |
{ | |
ARM_SOFTWARE_BREAKPOINT *Breakpoint; | |
Breakpoint = SearchBreakpointList (Address); | |
if (Breakpoint == NULL) { | |
return; | |
} | |
// Add it to the list | |
RemoveEntryList (&Breakpoint->Link); | |
// Restore the original instruction | |
*(UINT32 *)Address = Breakpoint->Instruction; | |
InvalidateInstructionCacheRange ((VOID *)Address, 4); | |
// DEBUG((DEBUG_ERROR, "ClearBreakpoint at 0x%08x (was: 0x%08x is:0x%08x)\n", Address, GDB_ARM_BKPT, *(UINT32 *)Address)); | |
FreePool (Breakpoint); | |
} | |
VOID | |
EFIAPI | |
InsertBreakPoint ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *PacketData | |
) | |
{ | |
UINTN Type; | |
UINTN Address; | |
UINTN Length; | |
UINTN ErrorCode; | |
ErrorCode = ParseBreakpointPacket (PacketData, &Type, &Address, &Length); | |
if (ErrorCode > 0) { | |
SendError ((UINT8)ErrorCode); | |
return; | |
} | |
switch (Type) { | |
case 0: // Software breakpoint | |
break; | |
default: | |
DEBUG ((DEBUG_ERROR, "Insert breakpoint default: %x\n", Type)); | |
SendError (GDB_EINVALIDBRKPOINTTYPE); | |
return; | |
} | |
SetBreakpoint (Address); | |
SendSuccess (); | |
} | |
VOID | |
EFIAPI | |
RemoveBreakPoint ( | |
IN EFI_SYSTEM_CONTEXT SystemContext, | |
IN CHAR8 *PacketData | |
) | |
{ | |
UINTN Type; | |
UINTN Address; | |
UINTN Length; | |
UINTN ErrorCode; | |
// Parse breakpoint packet data | |
ErrorCode = ParseBreakpointPacket (PacketData, &Type, &Address, &Length); | |
if (ErrorCode > 0) { | |
SendError ((UINT8)ErrorCode); | |
return; | |
} | |
switch (Type) { | |
case 0: // Software breakpoint | |
break; | |
default: | |
SendError (GDB_EINVALIDBRKPOINTTYPE); | |
return; | |
} | |
ClearBreakpoint (Address); | |
SendSuccess (); | |
} | |
VOID | |
InitializeProcessor ( | |
VOID | |
) | |
{ | |
// Initialize breakpoint list | |
InitializeListHead (&BreakpointList); | |
} | |
BOOLEAN | |
ValidateAddress ( | |
IN VOID *Address | |
) | |
{ | |
if ((UINT32)Address < 0x80000000) { | |
return FALSE; | |
} else { | |
return TRUE; | |
} | |
} | |
BOOLEAN | |
ValidateException ( | |
IN EFI_EXCEPTION_TYPE ExceptionType, | |
IN OUT EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
UINT32 ExceptionAddress; | |
UINT32 Instruction; | |
// Is it a debugger SWI? | |
ExceptionAddress = SystemContext.SystemContextArm->PC -= 4; | |
Instruction = *(UINT32 *)ExceptionAddress; | |
if (Instruction != GDB_ARM_BKPT) { | |
return FALSE; | |
} | |
// Special for SWI-based exception handling. SWI sets up the context | |
// to return to the instruction following the SWI instruction - NOT what we want | |
// for a debugger! | |
SystemContext.SystemContextArm->PC = ExceptionAddress; | |
return TRUE; | |
} |