| /** @file | |
| Processor specific parts of the GDB stub | |
| Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include <GdbDebugAgent.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/ArmLib.h> | |
| // | |
| // Externs from the exception handler assembly file | |
| // | |
| VOID | |
| ExceptionHandlersStart ( | |
| VOID | |
| ); | |
| VOID | |
| ExceptionHandlersEnd ( | |
| VOID | |
| ); | |
| VOID | |
| CommonExceptionEntry ( | |
| VOID | |
| ); | |
| VOID | |
| AsmCommonExceptionEntry ( | |
| VOID | |
| ); | |
| // | |
| // 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_SIGTRAP }, // GDB_SIGEMT | |
| { EXCEPT_ARM_RESERVED, GDB_SIGTRAP }, // GDB_SIGILL | |
| { EXCEPT_ARM_FIQ, GDB_SIGINT } // Used for ctrl-c | |
| }; | |
| // 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); | |
| } | |
| /** | |
| 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 >= (sizeof (gRegisterOffsets)/sizeof (UINTN))) { | |
| 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; | |
| // a UINT32 takes 8 ascii characters | |
| CHAR8 OutBuffer[(sizeof (gRegisterOffsets) * 2) + 1]; | |
| CHAR8 *OutBufPtr; | |
| // It is not safe to allocate pool here.... | |
| OutBufPtr = OutBuffer; | |
| for (Index = 0; Index < (sizeof (gRegisterOffsets)/sizeof (UINTN)); Index++) { | |
| OutBufPtr = BasicReadRegister (SystemContext, Index, OutBufPtr); | |
| } | |
| *OutBufPtr = '\0'; | |
| SendPacket(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 Ponter 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 >= (sizeof (gRegisterOffsets)/sizeof (UINTN))) { | |
| 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 = (sizeof (gRegisterOffsets)/sizeof (UINTN)); | |
| 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 (); | |
| } | |
| /** | |
| 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(); | |
| } | |
| VOID | |
| EFIAPI | |
| InsertBreakPoint ( | |
| IN EFI_SYSTEM_CONTEXT SystemContext, | |
| IN CHAR8 *PacketData | |
| ) | |
| { | |
| SendNotSupported (); | |
| } | |
| VOID | |
| EFIAPI | |
| RemoveBreakPoint ( | |
| IN EFI_SYSTEM_CONTEXT SystemContext, | |
| IN CHAR8 *PacketData | |
| ) | |
| { | |
| SendNotSupported (); | |
| } | |
| /** | |
| Send the T signal with the given exception type (in gdb order) and possibly | |
| with n:r pairs related to the watchpoints | |
| @param SystemContext Register content at time of the exception | |
| @param GdbExceptionType GDB exception type | |
| **/ | |
| VOID | |
| ProcessorSendTSignal ( | |
| IN EFI_SYSTEM_CONTEXT SystemContext, | |
| IN UINT8 GdbExceptionType, | |
| IN OUT CHAR8 *TSignalPtr, | |
| IN UINTN SizeOfBuffer | |
| ) | |
| { | |
| *TSignalPtr = '\0'; | |
| } | |
| /** | |
| FIQ state is only changed by FIQ exception. We don't want to take FIQ | |
| ticks in the GDB stub. The stub disables FIQ on entry, but this is the | |
| third instruction that executes in the execption handler. Thus we have | |
| a crack we need to test for. | |
| @param PC PC of execption | |
| @return TRUE We are in the GDB stub exception preamble | |
| @return FALSE We are not in GDB stub code | |
| **/ | |
| BOOLEAN | |
| InFiqCrack ( | |
| IN UINT32 PC | |
| ) | |
| { | |
| UINT64 VectorBase = PcdGet64 (PcdCpuVectorBaseAddress); | |
| UINT32 Length = (UINTN)ExceptionHandlersEnd - (UINTN)ExceptionHandlersStart; | |
| if ((PC >= VectorBase) && (PC <= (VectorBase + Length))) { | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| Check to see if this exception is related to ctrl-c handling. | |
| In this scheme we dedicate FIQ to the ctrl-c handler so it is | |
| independent of the rest of the system. | |
| SaveAndSetDebugTimerInterrupt () can be used to | |
| @param ExceptionType Exception that is being processed | |
| @param SystemContext Register content at time of the exception | |
| @return TRUE This was a ctrl-c check that did not find a ctrl-c | |
| @return FALSE This was not a ctrl-c check or some one hit ctrl-c | |
| **/ | |
| BOOLEAN | |
| ProcessorControlC ( | |
| IN EFI_EXCEPTION_TYPE ExceptionType, | |
| IN OUT EFI_SYSTEM_CONTEXT SystemContext | |
| ) | |
| { | |
| CHAR8 Char; | |
| BOOLEAN Return = TRUE; | |
| if (ExceptionType != EXCEPT_ARM_FIQ) { | |
| // Skip it as it is not related to ctrl-c | |
| return FALSE; | |
| } | |
| if (InFiqCrack (SystemContext.SystemContextArm->PC)) { | |
| // We are in our own interrupt preable, so skip this tick. | |
| // We never want to let gdb see the debug stub running if we can help it | |
| return FALSE; | |
| } | |
| while (TRUE) { | |
| if (!GdbIsCharAvailable ()) { | |
| // | |
| // No characters are pending so exit the loop | |
| // | |
| Return = TRUE; | |
| break; | |
| } | |
| Char = GdbGetChar (); | |
| if (Char == 0x03) { | |
| // | |
| // We have a ctrl-c so exit and process exception for ctrl-c | |
| // | |
| Return = FALSE; | |
| break; | |
| } | |
| } | |
| DebugAgentTimerEndOfInterrupt (); | |
| // Force an exit from the exception handler as we are done | |
| return Return; | |
| } | |
| /** | |
| Enable/Disable the interrupt of debug timer and return the interrupt state | |
| prior to the operation. | |
| If EnableStatus is TRUE, enable the interrupt of debug timer. | |
| If EnableStatus is FALSE, disable the interrupt of debug timer. | |
| @param[in] EnableStatus Enable/Disable. | |
| @retval TRUE Debug timer interrupt were enabled on entry to this call. | |
| @retval FALSE Debug timer interrupt were disabled on entry to this call. | |
| **/ | |
| BOOLEAN | |
| EFIAPI | |
| SaveAndSetDebugTimerInterrupt ( | |
| IN BOOLEAN EnableStatus | |
| ) | |
| { | |
| BOOLEAN FiqEnabled; | |
| FiqEnabled = ArmGetFiqState (); | |
| if (EnableStatus) { | |
| DebugAgentTimerSetPeriod (PcdGet32 (PcdGdbTimerPeriodMilliseconds)); | |
| ArmEnableFiq (); | |
| } else { | |
| DebugAgentTimerSetPeriod (0); | |
| ArmDisableFiq (); | |
| } | |
| return FiqEnabled; | |
| } | |
| VOID | |
| GdbFPutString ( | |
| IN CHAR8 *String | |
| ); | |
| /** | |
| Initialize debug agent. | |
| This function is used to set up debug environment to support source level debugging. | |
| If certain Debug Agent Library instance has to save some private data in the stack, | |
| this function must work on the mode that doesn't return to the caller, then | |
| the caller needs to wrap up all rest of logic after InitializeDebugAgent() into one | |
| function and pass it into InitializeDebugAgent(). InitializeDebugAgent() is | |
| responsible to invoke the passing-in function at the end of InitializeDebugAgent(). | |
| If the parameter Function is not NULL, Debug Agent Library instance will invoke it by | |
| passing in the Context to be its parameter. | |
| If Function() is NULL, Debug Agent Library instance will return after setup debug | |
| environment. | |
| @param[in] InitFlag Init flag is used to decide the initialize process. | |
| @param[in] Context Context needed according to InitFlag; it was optional. | |
| @param[in] Function Continue function called by debug agent library; it was | |
| optional. | |
| **/ | |
| VOID | |
| EFIAPI | |
| InitializeDebugAgent ( | |
| IN UINT32 InitFlag, | |
| IN VOID *Context, OPTIONAL | |
| IN DEBUG_AGENT_CONTINUE Function OPTIONAL | |
| ) | |
| { | |
| UINTN Offset; | |
| UINTN Length; | |
| BOOLEAN IrqEnabled; | |
| UINT64 *VectorBase; | |
| // | |
| // Disable interrupts | |
| // | |
| IrqEnabled = ArmGetInterruptState (); | |
| ArmDisableInterrupts (); | |
| ArmDisableFiq (); | |
| // | |
| // Copy an implementation of the ARM exception vectors to PcdCpuVectorBaseAddress. | |
| // | |
| Length = (UINTN)ExceptionHandlersEnd - (UINTN)ExceptionHandlersStart; | |
| // | |
| // Reserve space for the exception handlers | |
| // | |
| VectorBase = (UINT64 *)(UINTN)PcdGet64 (PcdCpuVectorBaseAddress); | |
| // Copy our assembly code into the page that contains the exception vectors. | |
| CopyMem ((VOID *)VectorBase, (VOID *)ExceptionHandlersStart, Length); | |
| // | |
| // Patch in the common Assembly exception handler | |
| // | |
| Offset = (UINTN)CommonExceptionEntry - (UINTN)ExceptionHandlersStart; | |
| *(UINTN *) (((UINT8 *)VectorBase) + Offset) = (UINTN)AsmCommonExceptionEntry; | |
| // Flush Caches since we updated executable stuff | |
| InvalidateInstructionCacheRange ((VOID *)(UINTN)PcdGet64(PcdCpuVectorBaseAddress), Length); | |
| // setup a timer so gdb can break in via ctrl-c | |
| DebugAgentTimerIntialize (); | |
| if (IrqEnabled) { | |
| ArmEnableInterrupts (); | |
| } | |
| if (Function != NULL) { | |
| Function (Context); | |
| } | |
| return; | |
| } | |