/** @file Processor specific parts of the GDB stub Copyright (c) 2008-2009, Apple Inc. All rights reserved. All rights reserved. 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. **/ /** @file Copyright (c) 2008, Apple, Inc All rights reserved. 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 #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; } /** ‘p n’ 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); } /** ‘g’ 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 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 >= 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((EFI_D_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((EFI_D_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; } /** ‘c [addr ]’ 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((EFI_D_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((EFI_D_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((EFI_D_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; }