/** @file
Processor specific parts of the GDB stub
Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
//
// 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 }
};
// Shut up some annoying RVCT warnings
#ifdef __CC_ARM
#pragma diag_suppress 1296
#endif
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)
};
// restore warnings for RVCT
#ifdef __CC_ARM
#pragma diag_default 1296
#endif
/**
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;
}