| /** @file |
| UEFI driver that implements a GDB stub |
| |
| Note: Any code in the path of the Serial IO output can not call DEBUG as will |
| will blow out the stack. Serial IO calls DEBUG, debug calls Serail IO, ... |
| |
| |
| 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. |
| |
| **/ |
| |
| #include <GdbStubInternal.h> |
| #include <Protocol/DebugPort.h> |
| |
| |
| UINTN gMaxProcessorIndex = 0; |
| |
| // |
| // Buffers for basic gdb communication |
| // |
| CHAR8 gInBuffer[MAX_BUF_SIZE]; |
| CHAR8 gOutBuffer[MAX_BUF_SIZE]; |
| |
| // Assume gdb does a "qXfer:libraries:read::offset,length" when it connects so we can default |
| // this value to FALSE. Since gdb can reconnect its self a global default is not good enough |
| BOOLEAN gSymbolTableUpdate = FALSE; |
| EFI_EVENT gEvent; |
| VOID *gGdbSymbolEventHandlerRegistration = NULL; |
| |
| // |
| // Globals for returning XML from qXfer:libraries:read packet |
| // |
| UINTN gPacketqXferLibraryOffset = 0; |
| UINTN gEfiDebugImageTableEntry = 0; |
| EFI_DEBUG_IMAGE_INFO_TABLE_HEADER *gDebugImageTableHeader = NULL; |
| EFI_DEBUG_IMAGE_INFO *gDebugTable = NULL; |
| CHAR8 gXferLibraryBuffer[2000]; |
| |
| GLOBAL_REMOVE_IF_UNREFERENCED CONST CHAR8 mHexToStr[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; |
| |
| |
| VOID |
| EFIAPI |
| GdbSymbolEventHandler ( |
| IN EFI_EVENT Event, |
| IN VOID *Context |
| ) |
| { |
| } |
| |
| |
| /** |
| The user Entry Point for Application. The user code starts with this function |
| as the real entry point for the image goes into a library that calls this |
| function. |
| |
| @param[in] ImageHandle The firmware allocated handle for the EFI image. |
| @param[in] SystemTable A pointer to the EFI System Table. |
| |
| @retval EFI_SUCCESS The entry point is executed successfully. |
| @retval other Some error occurs when executing this entry point. |
| |
| **/ |
| EFI_STATUS |
| EFIAPI |
| GdbStubEntry ( |
| IN EFI_HANDLE ImageHandle, |
| IN EFI_SYSTEM_TABLE *SystemTable |
| ) |
| |
| { |
| EFI_STATUS Status; |
| EFI_DEBUG_SUPPORT_PROTOCOL *DebugSupport; |
| UINTN HandleCount; |
| EFI_HANDLE *Handles; |
| UINTN Index; |
| UINTN Processor; |
| BOOLEAN IsaSupported; |
| |
| |
| Status = EfiGetSystemConfigurationTable (&gEfiDebugImageInfoTableGuid, (VOID **)&gDebugImageTableHeader); |
| if (EFI_ERROR (Status)) { |
| gDebugImageTableHeader = NULL; |
| } |
| |
| Status = gBS->LocateHandleBuffer ( |
| ByProtocol, |
| &gEfiDebugSupportProtocolGuid, |
| NULL, |
| &HandleCount, |
| &Handles |
| ); |
| if (EFI_ERROR (Status)) { |
| DEBUG ((EFI_D_ERROR, "Debug Support Protocol not found\n")); |
| |
| return Status; |
| } |
| |
| DebugSupport = NULL; |
| IsaSupported = FALSE; |
| do { |
| HandleCount--; |
| Status = gBS->HandleProtocol ( |
| Handles[HandleCount], |
| &gEfiDebugSupportProtocolGuid, |
| (VOID **) &DebugSupport |
| ); |
| if (!EFI_ERROR (Status)) { |
| if (CheckIsa (DebugSupport->Isa)) { |
| // We found what we are looking for so break out of the loop |
| IsaSupported = TRUE; |
| break; |
| } |
| } |
| } while (HandleCount > 0); |
| FreePool (Handles); |
| |
| if (!IsaSupported) { |
| DEBUG ((EFI_D_ERROR, "Debug Support Protocol does not support our ISA\n")); |
| |
| return EFI_NOT_FOUND; |
| } |
| |
| Status = DebugSupport->GetMaximumProcessorIndex (DebugSupport, &gMaxProcessorIndex); |
| ASSERT_EFI_ERROR (Status); |
| |
| // Make this an EFI_D_INFO after we get everything debugged. |
| DEBUG ((EFI_D_ERROR, "Debug Support Protocol ISA %x\n", DebugSupport->Isa)); |
| DEBUG ((EFI_D_ERROR, "Debug Support Protocol Processor Index %d\n", gMaxProcessorIndex)); |
| |
| // Call processor-specific init routine |
| InitializeProcessor(); |
| |
| for (Processor = 0; Processor <= gMaxProcessorIndex; Processor++) { |
| |
| for (Index = 0; Index < MaxEfiException (); Index++) { |
| Status = DebugSupport->RegisterExceptionCallback (DebugSupport, Processor, GdbExceptionHandler, gExceptionType[Index].Exception); |
| ASSERT_EFI_ERROR (Status); |
| } |
| // |
| // Current edk2 DebugPort is not interrupt context safe so we can not use it |
| // |
| Status = DebugSupport->RegisterPeriodicCallback (DebugSupport, Processor, GdbPeriodicCallBack); |
| ASSERT_EFI_ERROR (Status); |
| } |
| |
| // |
| // This even fires every time an image is added. This allows the stub to know when gdb needs |
| // to update the symbol table. |
| // |
| Status = gBS->CreateEvent ( |
| EVT_NOTIFY_SIGNAL, |
| TPL_CALLBACK, |
| GdbSymbolEventHandler, |
| NULL, |
| &gEvent |
| ); |
| ASSERT_EFI_ERROR (Status); |
| |
| // |
| // Register for protocol notifactions on this event |
| // |
| Status = gBS->RegisterProtocolNotify ( |
| &gEfiLoadedImageProtocolGuid, |
| gEvent, |
| &gGdbSymbolEventHandlerRegistration |
| ); |
| ASSERT_EFI_ERROR (Status); |
| |
| |
| if (PcdGetBool (PcdGdbSerial)) { |
| GdbInitializeSerialConsole (); |
| } |
| |
| return EFI_SUCCESS; |
| } |
| |
| |
| |
| /** |
| Transfer length bytes of input buffer, starting at Address, to memory. |
| |
| @param length the number of the bytes to be transferred/written |
| @param *address the start address of the transferring/writing the memory |
| @param *new_data the new data to be written to memory |
| **/ |
| |
| VOID |
| TransferFromInBufToMem ( |
| IN UINTN Length, |
| IN unsigned char *Address, |
| IN CHAR8 *NewData |
| ) |
| { |
| CHAR8 c1; |
| CHAR8 c2; |
| |
| while (Length-- > 0) { |
| c1 = (CHAR8)HexCharToInt (*NewData++); |
| c2 = (CHAR8)HexCharToInt (*NewData++); |
| |
| if ((c1 < 0) || (c2 < 0)) { |
| Print ((CHAR16 *)L"Bad message from write to memory..\n"); |
| SendError (GDB_EBADMEMDATA); |
| return; |
| } |
| *Address++ = (UINT8)((c1 << 4) + c2); |
| } |
| |
| SendSuccess(); |
| } |
| |
| |
| /** |
| Transfer Length bytes of memory starting at Address to an output buffer, OutBuffer. This function will finally send the buffer |
| as a packet. |
| |
| @param Length the number of the bytes to be transferred/read |
| @param *address pointer to the start address of the transferring/reading the memory |
| **/ |
| |
| VOID |
| TransferFromMemToOutBufAndSend ( |
| IN UINTN Length, |
| IN unsigned char *Address |
| ) |
| { |
| // there are Length bytes and every byte is represented as 2 hex chars |
| CHAR8 OutBuffer[MAX_BUF_SIZE]; |
| CHAR8 *OutBufPtr; // pointer to the output buffer |
| CHAR8 Char; |
| |
| if (ValidateAddress(Address) == FALSE) { |
| SendError(14); |
| return; |
| } |
| |
| OutBufPtr = OutBuffer; |
| while (Length > 0) { |
| |
| Char = mHexToStr[*Address >> 4]; |
| if ((Char >= 'A') && (Char <= 'F')) { |
| Char = Char - 'A' + 'a'; |
| } |
| *OutBufPtr++ = Char; |
| |
| Char = mHexToStr[*Address & 0x0f]; |
| if ((Char >= 'A') && (Char <= 'F')) { |
| Char = Char - 'A' + 'a'; |
| } |
| *OutBufPtr++ = Char; |
| |
| Address++; |
| Length--; |
| } |
| |
| *OutBufPtr = '\0' ; // the end of the buffer |
| SendPacket (OutBuffer); |
| } |
| |
| |
| |
| /** |
| Send a GDB Remote Serial Protocol Packet |
| |
| $PacketData#checksum PacketData is passed in and this function adds the packet prefix '$', |
| the packet teminating character '#' and the two digit checksum. |
| |
| If an ack '+' is not sent resend the packet, but timeout eventually so we don't end up |
| in an infinit loop. This is so if you unplug the debugger code just keeps running |
| |
| @param PacketData Payload data for the packet |
| |
| |
| @retval Number of bytes of packet data sent. |
| |
| **/ |
| UINTN |
| SendPacket ( |
| IN CHAR8 *PacketData |
| ) |
| { |
| UINT8 CheckSum; |
| UINTN Timeout; |
| CHAR8 *Ptr; |
| CHAR8 TestChar; |
| UINTN Count; |
| |
| Timeout = PcdGet32 (PcdGdbMaxPacketRetryCount); |
| |
| Count = 0; |
| do { |
| |
| Ptr = PacketData; |
| |
| if (Timeout-- == 0) { |
| // Only try a finite number of times so we don't get stuck in the loop |
| return Count; |
| } |
| |
| // Packet prefix |
| GdbPutChar ('$'); |
| |
| for (CheckSum = 0, Count =0 ; *Ptr != '\0'; Ptr++, Count++) { |
| GdbPutChar (*Ptr); |
| CheckSum = CheckSum + *Ptr; |
| } |
| |
| // Packet terminating character and checksum |
| GdbPutChar ('#'); |
| GdbPutChar (mHexToStr[CheckSum >> 4]); |
| GdbPutChar (mHexToStr[CheckSum & 0x0F]); |
| |
| TestChar = GdbGetChar (); |
| } while (TestChar != '+'); |
| |
| return Count; |
| } |
| |
| /** |
| Receive a GDB Remote Serial Protocol Packet |
| |
| $PacketData#checksum PacketData is passed in and this function adds the packet prefix '$', |
| the packet teminating character '#' and the two digit checksum. |
| |
| If host re-starts sending a packet without ending the previous packet, only the last valid packet is proccessed. |
| (In other words, if received packet is '$12345$12345$123456#checksum', only '$123456#checksum' will be processed.) |
| |
| If an ack '+' is not sent resend the packet |
| |
| @param PacketData Payload data for the packet |
| |
| @retval Number of bytes of packet data received. |
| |
| **/ |
| UINTN |
| ReceivePacket ( |
| OUT CHAR8 *PacketData, |
| IN UINTN PacketDataSize |
| ) |
| { |
| UINT8 CheckSum; |
| UINTN Index; |
| CHAR8 Char; |
| CHAR8 SumString[3]; |
| CHAR8 TestChar; |
| |
| ZeroMem (PacketData, PacketDataSize); |
| |
| for (;;) { |
| // wait for the start of a packet |
| TestChar = GdbGetChar (); |
| while (TestChar != '$') { |
| TestChar = GdbGetChar (); |
| }; |
| |
| retry: |
| for (Index = 0, CheckSum = 0; Index < (PacketDataSize - 1); Index++) { |
| Char = GdbGetChar (); |
| if (Char == '$') { |
| goto retry; |
| } |
| if (Char == '#') { |
| break; |
| } |
| |
| PacketData[Index] = Char; |
| CheckSum = CheckSum + Char; |
| } |
| PacketData[Index] = '\0'; |
| |
| if (Index == PacketDataSize) { |
| continue; |
| } |
| |
| SumString[0] = GdbGetChar (); |
| SumString[1] = GdbGetChar (); |
| SumString[2] = '\0'; |
| |
| if (AsciiStrHexToUintn (SumString) == CheckSum) { |
| // Ack: Success |
| GdbPutChar ('+'); |
| |
| // Null terminate the callers string |
| PacketData[Index] = '\0'; |
| return Index; |
| } else { |
| // Ack: Failure |
| GdbPutChar ('-'); |
| } |
| } |
| |
| //return 0; |
| } |
| |
| |
| /** |
| Empties the given buffer |
| @param Buf pointer to the first element in buffer to be emptied |
| **/ |
| VOID |
| EmptyBuffer ( |
| IN CHAR8 *Buf |
| ) |
| { |
| *Buf = '\0'; |
| } |
| |
| |
| /** |
| Converts an 8-bit Hex Char into a INTN. |
| |
| @param Char the hex character to be converted into UINTN |
| @retval a INTN, from 0 to 15, that corressponds to Char |
| -1 if Char is not a hex character |
| **/ |
| INTN |
| HexCharToInt ( |
| IN CHAR8 Char |
| ) |
| { |
| if ((Char >= 'A') && (Char <= 'F')) { |
| return Char - 'A' + 10; |
| } else if ((Char >= 'a') && (Char <= 'f')) { |
| return Char - 'a' + 10; |
| } else if ((Char >= '0') && (Char <= '9')) { |
| return Char - '0'; |
| } else { // if not a hex value, return a negative value |
| return -1; |
| } |
| } |
| |
| // 'E' + the biggest error number is 255, so its 2 hex digits + buffer end |
| CHAR8 *gError = "E__"; |
| |
| /** 'E NN' |
| Send an error with the given error number after converting to hex. |
| The error number is put into the buffer in hex. '255' is the biggest errno we can send. |
| ex: 162 will be sent as A2. |
| |
| @param errno the error number that will be sent |
| **/ |
| VOID |
| EFIAPI |
| SendError ( |
| IN UINT8 ErrorNum |
| ) |
| { |
| // |
| // Replace _, or old data, with current errno |
| // |
| gError[1] = mHexToStr [ErrorNum >> 4]; |
| gError[2] = mHexToStr [ErrorNum & 0x0f]; |
| |
| SendPacket (gError); // send buffer |
| } |
| |
| |
| |
| /** |
| Send 'OK' when the function is done executing successfully. |
| **/ |
| VOID |
| EFIAPI |
| SendSuccess ( |
| VOID |
| ) |
| { |
| SendPacket ("OK"); // send buffer |
| } |
| |
| |
| /** |
| Send empty packet to specify that particular command/functionality is not supported. |
| **/ |
| VOID |
| EFIAPI |
| SendNotSupported ( |
| VOID |
| ) |
| { |
| SendPacket (""); |
| } |
| |
| |
| |
| /** |
| 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 |
| GdbSendTSignal ( |
| IN EFI_SYSTEM_CONTEXT SystemContext, |
| IN UINT8 GdbExceptionType |
| ) |
| { |
| CHAR8 TSignalBuffer[128]; |
| CHAR8 *TSignalPtr; |
| UINTN BreakpointDetected; |
| BREAK_TYPE BreakType; |
| UINTN DataAddress; |
| CHAR8 *WatchStrPtr = NULL; |
| UINTN RegSize; |
| |
| TSignalPtr = &TSignalBuffer[0]; |
| |
| //Construct TSignal packet |
| *TSignalPtr++ = 'T'; |
| |
| // |
| // replace _, or previous value, with Exception type |
| // |
| *TSignalPtr++ = mHexToStr [GdbExceptionType >> 4]; |
| *TSignalPtr++ = mHexToStr [GdbExceptionType & 0x0f]; |
| |
| if (GdbExceptionType == GDB_SIGTRAP) { |
| if (gSymbolTableUpdate) { |
| // |
| // We can only send back on reason code. So if the flag is set it means the breakpoint is from our event handler |
| // |
| WatchStrPtr = "library:;"; |
| while (*WatchStrPtr != '\0') { |
| *TSignalPtr++ = *WatchStrPtr++; |
| } |
| gSymbolTableUpdate = FALSE; |
| } else { |
| |
| |
| // |
| // possible n:r pairs |
| // |
| |
| //Retrieve the breakpoint number |
| BreakpointDetected = GetBreakpointDetected (SystemContext); |
| |
| //Figure out if the exception is happend due to watch, rwatch or awatch. |
| BreakType = GetBreakpointType (SystemContext, BreakpointDetected); |
| |
| //INFO: rwatch is not supported due to the way IA32 debug registers work |
| if ((BreakType == DataWrite) || (BreakType == DataRead) || (BreakType == DataReadWrite)) { |
| |
| //Construct n:r pair |
| DataAddress = GetBreakpointDataAddress (SystemContext, BreakpointDetected); |
| |
| //Assign appropriate buffer to print particular watchpoint type |
| if (BreakType == DataWrite) { |
| WatchStrPtr = "watch"; |
| } else if (BreakType == DataRead) { |
| WatchStrPtr = "rwatch"; |
| } else if (BreakType == DataReadWrite) { |
| WatchStrPtr = "awatch"; |
| } |
| |
| while (*WatchStrPtr != '\0') { |
| *TSignalPtr++ = *WatchStrPtr++; |
| } |
| |
| *TSignalPtr++ = ':'; |
| |
| //Set up series of bytes in big-endian byte order. "awatch" won't work with little-endian byte order. |
| RegSize = REG_SIZE; |
| while (RegSize > 0) { |
| RegSize = RegSize-4; |
| *TSignalPtr++ = mHexToStr[(UINT8)(DataAddress >> RegSize) & 0xf]; |
| } |
| |
| //Always end n:r pair with ';' |
| *TSignalPtr++ = ';'; |
| } |
| } |
| } |
| |
| *TSignalPtr = '\0'; |
| |
| SendPacket (TSignalBuffer); |
| } |
| |
| |
| /** |
| Translates the EFI mapping to GDB mapping |
| |
| @param EFIExceptionType EFI Exception that is being processed |
| @retval UINTN that corresponds to EFIExceptionType's GDB exception type number |
| **/ |
| UINT8 |
| ConvertEFItoGDBtype ( |
| IN EFI_EXCEPTION_TYPE EFIExceptionType |
| ) |
| { |
| UINTN i; |
| |
| for (i=0; i < MaxEfiException() ; i++) { |
| if (gExceptionType[i].Exception == EFIExceptionType) { |
| return gExceptionType[i].SignalNo; |
| } |
| } |
| return GDB_SIGTRAP; // this is a GDB trap |
| } |
| |
| |
| /** "m addr,length" |
| Find the Length of the area to read and the start addres. Finally, pass them to |
| another function, TransferFromMemToOutBufAndSend, that will read from that memory space and |
| send it as a packet. |
| **/ |
| |
| VOID |
| EFIAPI |
| ReadFromMemory ( |
| CHAR8 *PacketData |
| ) |
| { |
| UINTN Address; |
| UINTN Length; |
| CHAR8 AddressBuffer[MAX_ADDR_SIZE]; // the buffer that will hold the address in hex chars |
| CHAR8 *AddrBufPtr; // pointer to the address buffer |
| CHAR8 *InBufPtr; /// pointer to the input buffer |
| |
| AddrBufPtr = AddressBuffer; |
| InBufPtr = &PacketData[1]; |
| while (*InBufPtr != ',') { |
| *AddrBufPtr++ = *InBufPtr++; |
| } |
| *AddrBufPtr = '\0'; |
| |
| InBufPtr++; // this skips ',' in the buffer |
| |
| /* Error checking */ |
| if (AsciiStrLen(AddressBuffer) >= MAX_ADDR_SIZE) { |
| Print((CHAR16 *)L"Address is too long\n"); |
| SendError (GDB_EBADMEMADDRBUFSIZE); |
| return; |
| } |
| |
| // 2 = 'm' + ',' |
| if (AsciiStrLen(PacketData) - AsciiStrLen(AddressBuffer) - 2 >= MAX_LENGTH_SIZE) { |
| Print((CHAR16 *)L"Length is too long\n"); |
| SendError (GDB_EBADMEMLENGTH); |
| return; |
| } |
| |
| Address = AsciiStrHexToUintn (AddressBuffer); |
| Length = AsciiStrHexToUintn (InBufPtr); |
| |
| TransferFromMemToOutBufAndSend (Length, (unsigned char *)Address); |
| } |
| |
| |
| /** "M addr,length :XX..." |
| Find the Length of the area in bytes to write and the start addres. Finally, pass them to |
| another function, TransferFromInBufToMem, that will write to that memory space the info in |
| the input buffer. |
| **/ |
| VOID |
| EFIAPI |
| WriteToMemory ( |
| IN CHAR8 *PacketData |
| ) |
| { |
| UINTN Address; |
| UINTN Length; |
| UINTN MessageLength; |
| CHAR8 AddressBuffer[MAX_ADDR_SIZE]; // the buffer that will hold the Address in hex chars |
| CHAR8 LengthBuffer[MAX_LENGTH_SIZE]; // the buffer that will hold the Length in hex chars |
| CHAR8 *AddrBufPtr; // pointer to the Address buffer |
| CHAR8 *LengthBufPtr; // pointer to the Length buffer |
| CHAR8 *InBufPtr; /// pointer to the input buffer |
| |
| AddrBufPtr = AddressBuffer; |
| LengthBufPtr = LengthBuffer; |
| InBufPtr = &PacketData[1]; |
| |
| while (*InBufPtr != ',') { |
| *AddrBufPtr++ = *InBufPtr++; |
| } |
| *AddrBufPtr = '\0'; |
| |
| InBufPtr++; // this skips ',' in the buffer |
| |
| while (*InBufPtr != ':') { |
| *LengthBufPtr++ = *InBufPtr++; |
| } |
| *LengthBufPtr = '\0'; |
| |
| InBufPtr++; // this skips ':' in the buffer |
| |
| Address = AsciiStrHexToUintn (AddressBuffer); |
| Length = AsciiStrHexToUintn (LengthBuffer); |
| |
| /* Error checking */ |
| |
| //Check if Address is not too long. |
| if (AsciiStrLen(AddressBuffer) >= MAX_ADDR_SIZE) { |
| Print ((CHAR16 *)L"Address too long..\n"); |
| SendError (GDB_EBADMEMADDRBUFSIZE); |
| return; |
| } |
| |
| //Check if message length is not too long |
| if (AsciiStrLen(LengthBuffer) >= MAX_LENGTH_SIZE) { |
| Print ((CHAR16 *)L"Length too long..\n"); |
| SendError (GDB_EBADMEMLENGBUFSIZE); |
| return; |
| } |
| |
| // Check if Message is not too long/short. |
| // 3 = 'M' + ',' + ':' |
| MessageLength = (AsciiStrLen(PacketData) - AsciiStrLen(AddressBuffer) - AsciiStrLen(LengthBuffer) - 3); |
| if (MessageLength != (2*Length)) { |
| //Message too long/short. New data is not the right size. |
| SendError (GDB_EBADMEMDATASIZE); |
| return; |
| } |
| TransferFromInBufToMem (Length, (unsigned char *)Address, InBufPtr); |
| } |
| |
| /** |
| Parses breakpoint packet data and captures Breakpoint type, Address and length. |
| In case of an error, function returns particular error code. Returning 0 meaning |
| no error. |
| |
| @param PacketData Pointer to the payload data for the packet. |
| @param Type Breakpoint type |
| @param Address Breakpoint address |
| @param Length Breakpoint length in Bytes (1 byte, 2 byte, 4 byte) |
| |
| @retval 1 Success |
| @retval {other} Particular error code |
| |
| **/ |
| UINTN |
| ParseBreakpointPacket ( |
| IN CHAR8 *PacketData, |
| OUT UINTN *Type, |
| OUT UINTN *Address, |
| OUT UINTN *Length |
| ) |
| { |
| CHAR8 AddressBuffer[MAX_ADDR_SIZE]; |
| CHAR8 *AddressBufferPtr; |
| CHAR8 *PacketDataPtr; |
| |
| PacketDataPtr = &PacketData[1]; |
| AddressBufferPtr = AddressBuffer; |
| |
| *Type = AsciiStrHexToUintn (PacketDataPtr); |
| |
| //Breakpoint/watchpoint type should be between 0 to 4 |
| if (*Type > 4) { |
| Print ((CHAR16 *)L"Type is invalid\n"); |
| return 22; //EINVAL: Invalid argument. |
| } |
| |
| //Skip ',' in the buffer. |
| while (*PacketDataPtr++ != ','); |
| |
| //Parse Address information |
| while (*PacketDataPtr != ',') { |
| *AddressBufferPtr++ = *PacketDataPtr++; |
| } |
| *AddressBufferPtr = '\0'; |
| |
| //Check if Address is not too long. |
| if (AsciiStrLen(AddressBuffer) >= MAX_ADDR_SIZE) { |
| Print ((CHAR16 *)L"Address too long..\n"); |
| return 40; //EMSGSIZE: Message size too long. |
| } |
| |
| *Address = AsciiStrHexToUintn (AddressBuffer); |
| |
| PacketDataPtr++; //This skips , in the buffer |
| |
| //Parse Length information |
| *Length = AsciiStrHexToUintn (PacketDataPtr); |
| |
| //Length should be 1, 2 or 4 bytes |
| if (*Length > 4) { |
| Print ((CHAR16 *)L"Length is invalid\n"); |
| return 22; //EINVAL: Invalid argument |
| } |
| |
| return 0; //0 = No error |
| } |
| |
| UINTN |
| gXferObjectReadResponse ( |
| IN CHAR8 Type, |
| IN CHAR8 *Str |
| ) |
| { |
| CHAR8 *OutBufPtr; // pointer to the output buffer |
| CHAR8 Char; |
| UINTN Count; |
| |
| // responce starts with 'm' or 'l' if it is the end |
| OutBufPtr = gOutBuffer; |
| *OutBufPtr++ = Type; |
| Count = 1; |
| |
| // Binary data encoding |
| OutBufPtr = gOutBuffer; |
| while (*Str != '\0') { |
| Char = *Str++; |
| if ((Char == 0x7d) || (Char == 0x23) || (Char == 0x24) || (Char == 0x2a)) { |
| // escape character |
| *OutBufPtr++ = 0x7d; |
| |
| Char ^= 0x20; |
| } |
| *OutBufPtr++ = Char; |
| Count++; |
| } |
| |
| *OutBufPtr = '\0' ; // the end of the buffer |
| SendPacket (gOutBuffer); |
| |
| return Count; |
| } |
| |
| |
| /** |
| Note: This should be a library function. In the Apple case you have to add |
| the size of the PE/COFF header into the starting address to make things work |
| right as there is no way to pad the Mach-O for the size of the PE/COFF header. |
| |
| |
| Returns a pointer to the PDB file name for a PE/COFF image that has been |
| loaded into system memory with the PE/COFF Loader Library functions. |
| |
| Returns the PDB file name for the PE/COFF image specified by Pe32Data. If |
| the PE/COFF image specified by Pe32Data is not a valid, then NULL is |
| returned. If the PE/COFF image specified by Pe32Data does not contain a |
| debug directory entry, then NULL is returned. If the debug directory entry |
| in the PE/COFF image specified by Pe32Data does not contain a PDB file name, |
| then NULL is returned. |
| If Pe32Data is NULL, then ASSERT(). |
| |
| @param Pe32Data Pointer to the PE/COFF image that is loaded in system |
| memory. |
| @param DebugBase Address that the debugger would use as the base of the image |
| |
| @return The PDB file name for the PE/COFF image specified by Pe32Data or NULL |
| if it cannot be retrieved. DebugBase is only valid if PDB file name is |
| valid. |
| |
| **/ |
| VOID * |
| EFIAPI |
| PeCoffLoaderGetDebuggerInfo ( |
| IN VOID *Pe32Data, |
| OUT VOID **DebugBase |
| ) |
| { |
| EFI_IMAGE_DOS_HEADER *DosHdr; |
| EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr; |
| EFI_IMAGE_DATA_DIRECTORY *DirectoryEntry; |
| EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *DebugEntry; |
| UINTN DirCount; |
| VOID *CodeViewEntryPointer; |
| INTN TEImageAdjust; |
| UINT32 NumberOfRvaAndSizes; |
| UINT16 Magic; |
| UINTN SizeOfHeaders; |
| |
| ASSERT (Pe32Data != NULL); |
| |
| TEImageAdjust = 0; |
| DirectoryEntry = NULL; |
| DebugEntry = NULL; |
| NumberOfRvaAndSizes = 0; |
| SizeOfHeaders = 0; |
| |
| DosHdr = (EFI_IMAGE_DOS_HEADER *)Pe32Data; |
| if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { |
| // |
| // DOS image header is present, so read the PE header after the DOS image header. |
| // |
| Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)((UINTN) Pe32Data + (UINTN) ((DosHdr->e_lfanew) & 0x0ffff)); |
| } else { |
| // |
| // DOS image header is not present, so PE header is at the image base. |
| // |
| Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)Pe32Data; |
| } |
| |
| if (Hdr.Te->Signature == EFI_TE_IMAGE_HEADER_SIGNATURE) { |
| if (Hdr.Te->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress != 0) { |
| DirectoryEntry = &Hdr.Te->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG]; |
| TEImageAdjust = sizeof (EFI_TE_IMAGE_HEADER) - Hdr.Te->StrippedSize; |
| DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *)((UINTN) Hdr.Te + |
| Hdr.Te->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress + |
| TEImageAdjust); |
| } |
| SizeOfHeaders = sizeof (EFI_TE_IMAGE_HEADER) + (UINTN)Hdr.Te->BaseOfCode - (UINTN)Hdr.Te->StrippedSize; |
| |
| // __APPLE__ check this math... |
| *DebugBase = ((CHAR8 *)Pe32Data) - TEImageAdjust; |
| } else if (Hdr.Pe32->Signature == EFI_IMAGE_NT_SIGNATURE) { |
| |
| *DebugBase = Pe32Data; |
| |
| |
| // |
| // NOTE: We use Machine field to identify PE32/PE32+, instead of Magic. |
| // It is due to backward-compatibility, for some system might |
| // generate PE32+ image with PE32 Magic. |
| // |
| switch (Hdr.Pe32->FileHeader.Machine) { |
| case EFI_IMAGE_MACHINE_IA32: |
| // |
| // Assume PE32 image with IA32 Machine field. |
| // |
| Magic = EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC; |
| break; |
| case EFI_IMAGE_MACHINE_X64: |
| case EFI_IMAGE_MACHINE_IA64: |
| // |
| // Assume PE32+ image with X64 or IPF Machine field |
| // |
| Magic = EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC; |
| break; |
| default: |
| // |
| // For unknow Machine field, use Magic in optional Header |
| // |
| Magic = Hdr.Pe32->OptionalHeader.Magic; |
| } |
| |
| if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { |
| // |
| // Use PE32 offset get Debug Directory Entry |
| // |
| SizeOfHeaders = Hdr.Pe32->OptionalHeader.SizeOfHeaders; |
| NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes; |
| DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]); |
| DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) ((UINTN) Pe32Data + DirectoryEntry->VirtualAddress); |
| } else if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { |
| // |
| // Use PE32+ offset get Debug Directory Entry |
| // |
| SizeOfHeaders = Hdr.Pe32Plus->OptionalHeader.SizeOfHeaders; |
| NumberOfRvaAndSizes = Hdr.Pe32Plus->OptionalHeader.NumberOfRvaAndSizes; |
| DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32Plus->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]); |
| DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) ((UINTN) Pe32Data + DirectoryEntry->VirtualAddress); |
| } |
| |
| if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_DEBUG) { |
| DirectoryEntry = NULL; |
| DebugEntry = NULL; |
| } |
| } else { |
| return NULL; |
| } |
| |
| if (DebugEntry == NULL || DirectoryEntry == NULL) { |
| return NULL; |
| } |
| |
| for (DirCount = 0; DirCount < DirectoryEntry->Size; DirCount += sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY), DebugEntry++) { |
| if (DebugEntry->Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) { |
| if (DebugEntry->SizeOfData > 0) { |
| CodeViewEntryPointer = (VOID *) ((UINTN) DebugEntry->RVA + ((UINTN)Pe32Data) + (UINTN)TEImageAdjust); |
| switch (* (UINT32 *) CodeViewEntryPointer) { |
| case CODEVIEW_SIGNATURE_NB10: |
| return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY)); |
| case CODEVIEW_SIGNATURE_RSDS: |
| return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY)); |
| case CODEVIEW_SIGNATURE_MTOC: |
| *DebugBase = (VOID *)(UINTN)((UINTN)DebugBase - SizeOfHeaders); |
| return (VOID *) ((CHAR8 *)CodeViewEntryPointer + sizeof (EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY)); |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| (void)SizeOfHeaders; |
| return NULL; |
| } |
| |
| |
| |
| /** |
| Process "qXfer:object:read:annex:offset,length" request. |
| |
| Returns an XML document that contains loaded libraries. In our case it is |
| infomration in the EFI Debug Inmage Table converted into an XML document. |
| |
| GDB will call with an arbitrary length (it can't know the real length and |
| will reply with chunks of XML that are easy for us to deal with. Gdb will |
| keep calling until we say we are done. XML doc looks like: |
| |
| <library-list> |
| <library name="/a/a/c/d.dSYM"><segment address="0x10000000"/></library> |
| <library name="/a/m/e/e.pdb"><segment address="0x20000000"/></library> |
| <library name="/a/l/f/f.dll"><segment address="0x30000000"/></library> |
| </library-list> |
| |
| Since we can not allocate memory in interupt context this module has |
| assumptions about how it will get called: |
| 1) Length will generally be max remote packet size (big enough) |
| 2) First Offset of an XML document read needs to be 0 |
| 3) This code will return back small chunks of the XML document on every read. |
| Each subseqent call will ask for the next availble part of the document. |
| |
| Note: The only variable size element in the XML is: |
| " <library name=\"%s\"><segment address=\"%p\"/></library>\n" and it is |
| based on the file path and name of the symbol file. If the symbol file name |
| is bigger than the max gdb remote packet size we could update this code |
| to respond back in chunks. |
| |
| @param Offset offset into special data area |
| @param Length number of bytes to read starting at Offset |
| |
| **/ |
| VOID |
| QxferLibrary ( |
| IN UINTN Offset, |
| IN UINTN Length |
| ) |
| { |
| VOID *LoadAddress; |
| CHAR8 *Pdb; |
| UINTN Size; |
| |
| if (Offset != gPacketqXferLibraryOffset) { |
| SendError (GDB_EINVALIDARG); |
| Print (L"\nqXferLibrary (%d, %d) != %d\n", Offset, Length, gPacketqXferLibraryOffset); |
| |
| // Force a retry from the beginning |
| gPacketqXferLibraryOffset = 0; |
| return; |
| } |
| |
| if (Offset == 0) { |
| gPacketqXferLibraryOffset += gXferObjectReadResponse ('m', "<library-list>\n"); |
| |
| // The owner of the table may have had to ralloc it so grab a fresh copy every time |
| // we assume qXferLibrary will get called over and over again until the entire XML table is |
| // returned in a tight loop. Since we are in the debugger the table should not get updated |
| gDebugTable = gDebugImageTableHeader->EfiDebugImageInfoTable; |
| gEfiDebugImageTableEntry = 0; |
| return; |
| } |
| |
| if (gDebugTable != NULL) { |
| for (; gEfiDebugImageTableEntry < gDebugImageTableHeader->TableSize; gEfiDebugImageTableEntry++, gDebugTable++) { |
| if (gDebugTable->NormalImage != NULL) { |
| if ((gDebugTable->NormalImage->ImageInfoType == EFI_DEBUG_IMAGE_INFO_TYPE_NORMAL) && |
| (gDebugTable->NormalImage->LoadedImageProtocolInstance != NULL)) { |
| Pdb = PeCoffLoaderGetDebuggerInfo ( |
| gDebugTable->NormalImage->LoadedImageProtocolInstance->ImageBase, |
| &LoadAddress |
| ); |
| if (Pdb != NULL) { |
| Size = AsciiSPrint ( |
| gXferLibraryBuffer, |
| sizeof (gXferLibraryBuffer), |
| " <library name=\"%a\"><segment address=\"0x%p\"/></library>\n", |
| Pdb, |
| LoadAddress |
| ); |
| if ((Size != 0) && (Size != (sizeof (gXferLibraryBuffer) - 1))) { |
| gPacketqXferLibraryOffset += gXferObjectReadResponse ('m', gXferLibraryBuffer); |
| |
| // Update loop variables so we are in the right place when we get back |
| gEfiDebugImageTableEntry++; |
| gDebugTable++; |
| return; |
| } else { |
| // We could handle <library> entires larger than sizeof (gXferLibraryBuffer) here if |
| // needed by breaking up into N packets |
| // "<library name=\"%s |
| // the rest of the string (as many packets as required |
| // \"><segment address=\"%d\"/></library> (fixed size) |
| // |
| // But right now we just skip any entry that is too big |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| gXferObjectReadResponse ('l', "</library-list>\n"); |
| gPacketqXferLibraryOffset = 0; |
| return; |
| } |
| |
| |
| /** |
| Exception Hanldler for GDB. It will be called for all exceptions |
| registered via the gExceptionType[] array. |
| |
| @param ExceptionType Exception that is being processed |
| @param SystemContext Register content at time of the exception |
| **/ |
| VOID |
| EFIAPI |
| GdbExceptionHandler ( |
| IN EFI_EXCEPTION_TYPE ExceptionType, |
| IN OUT EFI_SYSTEM_CONTEXT SystemContext |
| ) |
| { |
| UINT8 GdbExceptionType; |
| CHAR8 *Ptr; |
| |
| |
| if (ValidateException(ExceptionType, SystemContext) == FALSE) { |
| return; |
| } |
| |
| RemoveSingleStep (SystemContext); |
| |
| GdbExceptionType = ConvertEFItoGDBtype (ExceptionType); |
| GdbSendTSignal (SystemContext, GdbExceptionType); |
| |
| for( ; ; ) { |
| ReceivePacket (gInBuffer, MAX_BUF_SIZE); |
| |
| switch (gInBuffer[0]) { |
| case '?': |
| GdbSendTSignal (SystemContext, GdbExceptionType); |
| break; |
| |
| case 'c': |
| ContinueAtAddress (SystemContext, gInBuffer); |
| return; |
| |
| case 'g': |
| ReadGeneralRegisters (SystemContext); |
| break; |
| |
| case 'G': |
| WriteGeneralRegisters (SystemContext, gInBuffer); |
| break; |
| |
| case 'H': |
| //Return "OK" packet since we don't have more than one thread. |
| SendSuccess (); |
| break; |
| |
| case 'm': |
| ReadFromMemory (gInBuffer); |
| break; |
| |
| case 'M': |
| WriteToMemory (gInBuffer); |
| break; |
| |
| case 'P': |
| WriteNthRegister (SystemContext, gInBuffer); |
| break; |
| |
| // |
| // Still debugging this code. Not used in Darwin |
| // |
| case 'q': |
| // General Query Packets |
| if (AsciiStrnCmp (gInBuffer, "qSupported", 10) == 0) { |
| // return what we currently support, we don't parse what gdb suports |
| AsciiSPrint (gOutBuffer, MAX_BUF_SIZE, "qXfer:libraries:read+;PacketSize=%d", MAX_BUF_SIZE); |
| SendPacket (gOutBuffer); |
| } else if (AsciiStrnCmp (gInBuffer, "qXfer:libraries:read::", 22) == 0) { |
| // ‘qXfer:libraries:read::offset,length |
| // gInBuffer[22] is offset string, ++Ptr is length string’ |
| for (Ptr = &gInBuffer[22]; *Ptr != ','; Ptr++); |
| |
| // Not sure if multi-radix support is required. Currently only support decimal |
| QxferLibrary (AsciiStrHexToUintn (&gInBuffer[22]), AsciiStrHexToUintn (++Ptr)); |
| } if (AsciiStrnCmp (gInBuffer, "qOffsets", 10) == 0) { |
| AsciiSPrint (gOutBuffer, MAX_BUF_SIZE, "Text=1000;Data=f000;Bss=f000"); |
| SendPacket (gOutBuffer); |
| } else { |
| //Send empty packet |
| SendNotSupported (); |
| } |
| break; |
| |
| case 's': |
| SingleStep (SystemContext, gInBuffer); |
| return; |
| |
| case 'z': |
| RemoveBreakPoint (SystemContext, gInBuffer); |
| break; |
| |
| case 'Z': |
| InsertBreakPoint (SystemContext, gInBuffer); |
| break; |
| |
| default: |
| //Send empty packet |
| SendNotSupported (); |
| break; |
| } |
| } |
| } |
| |
| |
| /** |
| Periodic callback for GDB. This function is used to catch a ctrl-c or other |
| break in type command from GDB. |
| |
| @param SystemContext Register content at time of the call |
| **/ |
| VOID |
| EFIAPI |
| GdbPeriodicCallBack ( |
| IN OUT EFI_SYSTEM_CONTEXT SystemContext |
| ) |
| { |
| // |
| // gCtrlCBreakFlag may have been set from a previous F response package |
| // and we set the global as we need to process it at a point where we |
| // can update the system context. If we are in the middle of processing |
| // a F Packet it is not safe to read the GDB serial stream so we need |
| // to skip it on this check |
| // |
| if (!gCtrlCBreakFlag && !gProcessingFPacket) { |
| // |
| // Ctrl-C was not pending so grab any pending characters and see if they |
| // are a Ctrl-c (0x03). If so set the Ctrl-C global. |
| // |
| while (TRUE) { |
| if (!GdbIsCharAvailable ()) { |
| // |
| // No characters are pending so exit the loop |
| // |
| break; |
| } |
| |
| if (GdbGetChar () == 0x03) { |
| gCtrlCBreakFlag = TRUE; |
| // |
| // We have a ctrl-c so exit the loop |
| // |
| break; |
| } |
| } |
| } |
| |
| if (gCtrlCBreakFlag) { |
| // |
| // Update the context to force a single step trap when we exit the GDB |
| // stub. This will trasfer control to GdbExceptionHandler () and let |
| // us break into the program. We don't want to break into the GDB stub. |
| // |
| AddSingleStep (SystemContext); |
| gCtrlCBreakFlag = FALSE; |
| } |
| } |