diff options
author | AJFISH <AJFISH@6f19259b-4bc3-4df7-8a09-765794883524> | 2009-12-06 01:57:05 +0000 |
---|---|---|
committer | AJFISH <AJFISH@6f19259b-4bc3-4df7-8a09-765794883524> | 2009-12-06 01:57:05 +0000 |
commit | 2ef2b01e07c02db339f34004445734a2dbdd80e1 (patch) | |
tree | 19532a6be8d8bdb0aef04bd00c1efb582f6dc841 /EmbeddedPkg/GdbStub/GdbStub.c | |
parent | f7753a96ba1653ddd31b01c198a352f6332ac404 (diff) | |
download | edk2-2ef2b01e07c02db339f34004445734a2dbdd80e1.tar.gz edk2-2ef2b01e07c02db339f34004445734a2dbdd80e1.tar.bz2 edk2-2ef2b01e07c02db339f34004445734a2dbdd80e1.zip |
Adding support for BeagleBoard.
ArmPkg - Supoprt for ARM specific things that can change as the architecture changes. Plus semihosting JTAG drivers.
EmbeddedPkg - Generic support for an embeddded platform. Including a light weight command line shell.
BeagleBoardPkg - Platform specifics for BeagleBoard. SD Card works, but USB has issues. Looks like a bug in the open source USB stack (Our internal stack works fine).
git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@9518 6f19259b-4bc3-4df7-8a09-765794883524
Diffstat (limited to 'EmbeddedPkg/GdbStub/GdbStub.c')
-rw-r--r-- | EmbeddedPkg/GdbStub/GdbStub.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/EmbeddedPkg/GdbStub/GdbStub.c b/EmbeddedPkg/GdbStub/GdbStub.c new file mode 100644 index 0000000000..b121e413f3 --- /dev/null +++ b/EmbeddedPkg/GdbStub/GdbStub.c @@ -0,0 +1,1264 @@ +/** @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; + } +} |