2013-01-25 12:28:06 +01:00
|
|
|
|
/** @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.<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 <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);
|
|
|
|
|
|
|
|
|
|
DEBUG ((EFI_D_INFO, "Debug Support Protocol ISA %x\n", DebugSupport->Isa));
|
|
|
|
|
DEBUG ((EFI_D_INFO, "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;
|
|
|
|
|
}
|
|
|
|
|
}
|