reactos/ntoskrnl/kd/wrappers/gdbstub.c

1591 lines
44 KiB
C

/****************************************************************************
THIS SOFTWARE IS NOT COPYRIGHTED
HP offers the following for use in the public domain. HP makes no
warranty with regard to the software or it's performance and the
user accepts the software "AS IS" with all faults.
HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD
TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
****************************************************************************/
/****************************************************************************
* Contributor: Lake Stevens Instrument Division$
*
* Description: low level support for gdb debugger. $
*
* Written by: Glenn Engel $
* ModuleState: Experimental $
*
* Modified for 386 by Jim Kingdon, Cygnus Support.
* Modified for ReactOS by Casper S. Hornstrup <chorns@users.sourceforge.net>
*
****************************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/************************************************************************/
static BOOLEAN GspInitialized;
static BOOLEAN GspRemoteDebug;
static CONST CHAR HexChars[] = "0123456789abcdef";
static PETHREAD GspRunThread; /* NULL means run all threads */
static PETHREAD GspDbgThread;
static PETHREAD GspEnumThread;
static FAST_MUTEX GspLock;
extern LIST_ENTRY PsActiveProcessHead;
/* FIXME hardcoded for COM2, 115200 baud */
ULONG GdbPortNumber = DEFAULT_DEBUG_PORT;
CPPORT GdbPortInfo = {0, DEFAULT_DEBUG_BAUD_RATE, 0};
static CHAR GspInBuffer[1000];
static CHAR GspOutBuffer[1000];
/* Number of Registers. */
#define NUMREGS 16
enum REGISTER_NAMES
{
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI,
PC /* also known as eip */ ,
PS /* also known as eflags */ ,
CS, SS, DS, ES, FS, GS
};
typedef struct _CPU_REGISTER
{
ULONG Size;
ULONG OffsetInTF;
ULONG OffsetInContext;
BOOLEAN SetInContext;
} CPU_REGISTER, *PCPU_REGISTER;
static CPU_REGISTER GspRegisters[NUMREGS] =
{
{ 4, FIELD_OFFSET(KTRAP_FRAME, Eax), FIELD_OFFSET(CONTEXT, Eax), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, Ecx), FIELD_OFFSET(CONTEXT, Ecx), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, Edx), FIELD_OFFSET(CONTEXT, Edx), FALSE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, Ebx), FIELD_OFFSET(CONTEXT, Ebx), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, HardwareEsp), FIELD_OFFSET(CONTEXT, Esp), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, DbgEbp), FIELD_OFFSET(CONTEXT, Ebp), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, Esi), FIELD_OFFSET(CONTEXT, Esi), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, Edi), FIELD_OFFSET(CONTEXT, Edi), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, DbgEip), FIELD_OFFSET(CONTEXT, Eip), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, EFlags), FIELD_OFFSET(CONTEXT, EFlags), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, SegCs), FIELD_OFFSET(CONTEXT, SegCs), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, HardwareSegSs), FIELD_OFFSET(CONTEXT, SegSs), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, SegDs), FIELD_OFFSET(CONTEXT, SegDs), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, SegEs), FIELD_OFFSET(CONTEXT, SegEs), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, SegFs), FIELD_OFFSET(CONTEXT, SegFs), TRUE },
{ 4, FIELD_OFFSET(KTRAP_FRAME, SegGs), FIELD_OFFSET(CONTEXT, SegGs), TRUE }
};
static PCHAR GspThreadStates[DeferredReady + 1] =
{
"Initialized",
"Ready",
"Running",
"Standby",
"Terminated",
"Waiting",
"Transition",
"DeferredReady"
};
LONG
HexValue(CHAR ch)
{
if ((ch >= '0') && (ch <= '9'))
return (ch - '0');
if ((ch >= 'a') && (ch <= 'f'))
return (ch - 'a' + 10);
if ((ch >= 'A') && (ch <= 'F'))
return (ch - 'A' + 10);
return -1;
}
VOID
GdbPutChar(UCHAR Value)
{
KdPortPutByteEx(&GdbPortInfo, Value);
}
UCHAR
GdbGetChar(VOID)
{
UCHAR Value;
while (!KdPortGetByteEx(&GdbPortInfo, &Value)) ;
return Value;
}
/* scan for the sequence $<data>#<Checksum> */
PCHAR
GspGetPacket(VOID)
{
PCHAR Buffer = &GspInBuffer[0];
CHAR Checksum;
CHAR XmitChecksum;
ULONG Count;
CHAR ch;
while (TRUE)
{
/* wait around for the start character, ignore all other characters */
while ((ch = GdbGetChar()) != '$') ;
retry:
Checksum = 0;
XmitChecksum = -1;
Count = 0;
/* now, read until a # or end of Buffer is found */
while (Count < sizeof(GspInBuffer) - 1)
{
ch = GdbGetChar();
if (ch == '$')
goto retry;
if (ch == '#')
break;
Checksum = Checksum + ch;
Buffer[Count] = ch;
Count = Count + 1;
}
Buffer[Count] = 0;
if (ch == '#')
{
ch = GdbGetChar();
XmitChecksum = (CHAR)(HexValue(ch) << 4);
ch = GdbGetChar();
XmitChecksum += (CHAR)(HexValue(ch));
if (Checksum != XmitChecksum)
{
GdbPutChar('-'); /* failed checksum */
}
else
{
GdbPutChar('+'); /* successful transfer */
return &Buffer[0];
}
}
}
}
/* send the packet in Buffer. */
VOID
GspPutPacket(PCHAR Buffer)
{
CHAR Checksum;
LONG Count;
CHAR ch;
/* $<packet info>#<Checksum>. */
do
{
GdbPutChar('$');
Checksum = 0;
Count = 0;
while ((ch = Buffer[Count]))
{
GdbPutChar(ch);
Checksum += ch;
Count += 1;
}
GdbPutChar('#');
GdbPutChar(HexChars[(Checksum >> 4) & 0xf]);
GdbPutChar(HexChars[Checksum & 0xf]);
}
while (GdbGetChar() != '+');
}
VOID
GspPutPacketNoWait(PCHAR Buffer)
{
CHAR Checksum;
LONG Count;
CHAR ch;
/* $<packet info>#<Checksum>. */
GdbPutChar('$');
Checksum = 0;
Count = 0;
while ((ch = Buffer[Count]))
{
GdbPutChar(ch);
Checksum += ch;
Count += 1;
}
GdbPutChar('#');
GdbPutChar(HexChars[(Checksum >> 4) & 0xf]);
GdbPutChar(HexChars[Checksum & 0xf]);
}
/* Indicate to caller of GspMem2Hex or GspHex2Mem that there has been an error. */
static volatile BOOLEAN GspMemoryError = FALSE;
static CHAR
GspReadMemSafe(PCHAR Address)
{
CHAR ch = 0;
if (!KdpSafeReadMemory((ULONG_PTR)Address, 1, &ch))
GspMemoryError = TRUE;
return ch;
}
static void
GspWriteMemSafe(PCHAR Address, CHAR Ch)
{
if (!KdpSafeWriteMemory((ULONG_PTR)Address, 1, Ch))
GspMemoryError = TRUE;
}
/* Convert the memory pointed to by Address into hex, placing result in Buffer
* Return a pointer to the last char put in Buffer (null)
* If MayFault is TRUE, then we should set GspMemoryError in response to
* a fault; if FALSE treat a fault like any other fault in the stub.
*/
static PCHAR
GspMem2Hex(PCHAR Address, PCHAR Buffer, LONG Count, BOOLEAN MayFault)
{
ULONG i;
CHAR ch;
for (i = 0; i < (ULONG)Count; i++)
{
if (MayFault)
{
ch = GspReadMemSafe(Address);
if (GspMemoryError)
return Buffer;
}
else
{
ch = *Address;
}
*Buffer++ = HexChars[(ch >> 4) & 0xf];
*Buffer++ = HexChars[ch & 0xf];
Address++;
}
*Buffer = 0;
return Buffer;
}
static ULONG
GspWriteMem(PCHAR Address, ULONG Count, BOOLEAN MayFault,
CHAR (*GetContent)(PVOID Context, ULONG Offset), PVOID Context)
{
PCHAR Current;
PCHAR Page;
ULONG CountInPage;
ULONG i;
CHAR ch;
Current = Address;
while (Current < Address + Count)
{
Page = (PCHAR)PAGE_ROUND_DOWN(Current);
if (Address + Count <= Page + PAGE_SIZE)
{
/* Fits in this page */
CountInPage = Count;
}
else
{
/* Flows into next page, handle only current page in this iteration */
CountInPage = PAGE_SIZE - (Address - Page);
}
for (i = 0; i < CountInPage && !GspMemoryError; i++)
{
ch = (*GetContent)(Context, Current - Address);
if (MayFault)
GspWriteMemSafe(Current, ch);
else
*Current = ch;
Current++;
}
if (MayFault)
{
if (GspMemoryError)
return Current - Address;
}
}
return Current - Address;
}
static CHAR
GspHex2MemGetContent(PVOID Context, ULONG Offset)
{
return (CHAR)((HexValue(*((PCHAR)Context + 2 * Offset)) << 4) +
HexValue(*((PCHAR)Context + 2 * Offset + 1)));
}
/* Convert the hex array pointed to by Buffer into binary to be placed at Address
* Return a pointer to the character AFTER the last byte read from Buffer */
static PCHAR
GspHex2Mem(PCHAR Buffer, PCHAR Address, ULONG Count, BOOLEAN MayFault)
{
Count = GspWriteMem(Address, Count, MayFault, GspHex2MemGetContent, Buffer);
return Buffer + 2 * Count;
}
/**********************************************/
/* WHILE WE FIND NICE HEX CHARS, BUILD A LONG */
/* RETURN NUMBER OF CHARS PROCESSED */
/**********************************************/
LONG
GspHex2Long(PCHAR *Address, PLONG Value)
{
LONG NumChars = 0;
LONG Hex;
*Value = 0;
while (**Address)
{
Hex = HexValue(**Address);
if (Hex >= 0)
{
*Value = (*Value << 4) | Hex;
NumChars++;
}
else
{
break;
}
(*Address)++;
}
return NumChars;
}
VOID
GspLong2Hex(PCHAR *Address, LONG Value)
{
LONG Save;
Save = (((Value >> 0) & 0xff) << 24) | (((Value >> 8) & 0xff) << 16) |
(((Value >> 16) & 0xff) << 8) | (((Value >> 24) & 0xff) << 0);
*Address = GspMem2Hex((PCHAR)&Save, *Address, 4, FALSE);
}
/*
* When coming from kernel mode, Esp is not stored in the trap frame.
* Instead, it was pointing to the location of the TrapFrame Esp member
* when the exception occured. When coming from user mode, Esp is just
* stored in the TrapFrame Esp member.
*/
static LONG
GspGetEspFromTrapFrame(PKTRAP_FRAME TrapFrame)
{
return KeGetPreviousMode() == KernelMode ?
(LONG)&TrapFrame->HardwareEsp : (LONG)TrapFrame->HardwareEsp;
}
static VOID
GspGetRegisters(PCHAR Address, PKTRAP_FRAME TrapFrame)
{
ULONG_PTR Value;
PULONG p;
ULONG i;
PETHREAD Thread;
ULONG_PTR *KernelStack;
if (NULL == GspDbgThread)
{
Thread = PsGetCurrentThread();
}
else
{
TrapFrame = GspDbgThread->Tcb.TrapFrame;
Thread = GspDbgThread;
}
if (Waiting == Thread->Tcb.State)
{
KernelStack = Thread->Tcb.KernelStack;
for (i = 0; i < sizeof(GspRegisters) / sizeof(GspRegisters[0]); i++)
{
switch (i)
{
case EBP:
Value = KernelStack[3];
break;
case EDI:
Value = KernelStack[4];
break;
case ESI:
Value = KernelStack[5];
break;
case EBX:
Value = KernelStack[6];
break;
case PC:
Value = KernelStack[7];
break;
case ESP:
Value = (ULONG_PTR)(KernelStack + 8);
break;
case CS:
Value = KGDT_R0_CODE;
break;
case DS:
Value = KGDT_R0_DATA;
break;
default:
Value = 0;
break;
}
Address = GspMem2Hex((PCHAR)&Value, Address, GspRegisters[i].Size, FALSE);
}
}
else
{
for (i = 0; i < sizeof(GspRegisters) / sizeof(GspRegisters[0]); i++)
{
if (TrapFrame)
{
if (ESP == i)
{
Value = GspGetEspFromTrapFrame(TrapFrame);
}
else
{
p = (PULONG)((ULONG_PTR)TrapFrame + GspRegisters[i].OffsetInTF);
Value = *p;
}
}
else if (i == PC)
{
/*
* This thread has not been sheduled yet so assume it
* is still in PsBeginThreadWithContextInternal().
*/
Value = (ULONG)KiThreadStartup;
}
else
{
Value = 0;
}
Address = GspMem2Hex((PCHAR)&Value, Address, GspRegisters[i].Size, FALSE);
}
}
}
VOID
GspSetRegistersInTrapFrame(PCHAR Address, PCONTEXT Context, PKTRAP_FRAME TrapFrame)
{
ULONG Value;
PCHAR Buffer;
PULONG p;
ULONG i;
if (!TrapFrame)
return;
Buffer = Address;
for (i = 0; i < NUMREGS; i++)
{
if (GspRegisters[i].SetInContext)
{
p = (PULONG)((ULONG_PTR)Context + GspRegisters[i].OffsetInContext);
}
else
{
p = (PULONG)((ULONG_PTR)TrapFrame + GspRegisters[i].OffsetInTF);
}
Value = 0;
Buffer = GspHex2Mem(Buffer, (PCHAR)&Value, GspRegisters[i].Size, FALSE);
*p = Value;
}
}
VOID
GspSetSingleRegisterInTrapFrame(PCHAR Address, LONG Number, PCONTEXT Context, PKTRAP_FRAME TrapFrame)
{
ULONG Value;
PULONG p;
if (!TrapFrame)
return;
if (GspRegisters[Number].SetInContext)
{
p = (PULONG)((ULONG_PTR)Context + GspRegisters[Number].OffsetInContext);
}
else
{
p = (PULONG)((ULONG_PTR)TrapFrame + GspRegisters[Number].OffsetInTF);
}
Value = 0;
GspHex2Mem(Address, (PCHAR)&Value, GspRegisters[Number].Size, FALSE);
*p = Value;
}
BOOLEAN
GspFindThread(PCHAR Data, PETHREAD *Thread)
{
PETHREAD ThreadInfo = NULL;
if (strcmp(Data, "-1") == 0)
{
/* All threads */
ThreadInfo = NULL;
}
else
{
ULONG uThreadId;
HANDLE ThreadId;
PCHAR ptr = &Data[0];
GspHex2Long(&ptr, (PLONG)&uThreadId);
ThreadId = (HANDLE)uThreadId;
if (!NT_SUCCESS(PsLookupThreadByThreadId(ThreadId, &ThreadInfo)))
{
*Thread = NULL;
return FALSE;
}
}
*Thread = ThreadInfo;
return TRUE;
}
VOID
GspSetThread(PCHAR Request)
{
PETHREAD ThreadInfo;
PCHAR ptr = &Request[1];
switch (Request[0])
{
case 'c': /* Run thread */
if (GspFindThread(ptr, &ThreadInfo))
{
GspOutBuffer[0] = 'O';
GspOutBuffer[1] = 'K';
if (NULL != GspRunThread)
ObDereferenceObject(GspRunThread);
GspRunThread = ThreadInfo;
if (NULL != GspRunThread)
ObReferenceObject(GspRunThread);
}
else
{
GspOutBuffer[0] = 'E';
}
break;
case 'g': /* Debug thread */
if (GspFindThread(ptr, &ThreadInfo))
{
GspOutBuffer[0] = 'O';
GspOutBuffer[1] = 'K';
if (NULL != GspDbgThread)
ObDereferenceObject(GspDbgThread);
if (ThreadInfo == PsGetCurrentThread())
{
GspDbgThread = NULL;
ObDereferenceObject(ThreadInfo);
}
else
{
GspDbgThread = ThreadInfo;
}
}
else
{
GspOutBuffer[0] = 'E';
}
break;
default:
break;
}
}
VOID
GspQuery(PCHAR Request)
{
ULONG Value;
if (strncmp(Request, "C", 1) == 0)
{
PCHAR ptr = &GspOutBuffer[2];
/* Get current thread id */
GspOutBuffer[0] = 'Q';
GspOutBuffer[1] = 'C';
if (NULL != GspDbgThread)
Value = (ULONG)GspDbgThread->Cid.UniqueThread;
else
Value = (ULONG)PsGetCurrentThread()->Cid.UniqueThread;
GspLong2Hex(&ptr, Value);
}
else if (strncmp(Request, "fThreadInfo", 11) == 0)
{
PEPROCESS Process;
PLIST_ENTRY AThread, AProcess;
PCHAR ptr = &GspOutBuffer[1];
/* Get first thread id */
GspEnumThread = NULL;
AProcess = PsActiveProcessHead.Flink;
while (AProcess != &PsActiveProcessHead)
{
Process = CONTAINING_RECORD(AProcess, EPROCESS, ActiveProcessLinks);
AThread = Process->ThreadListHead.Flink;
if (AThread != &Process->ThreadListHead)
{
GspEnumThread = CONTAINING_RECORD(Process->ThreadListHead.Flink,
ETHREAD, ThreadListEntry);
break;
}
AProcess = AProcess->Flink;
}
if (GspEnumThread != NULL)
{
GspOutBuffer[0] = 'm';
Value = (ULONG)GspEnumThread->Cid.UniqueThread;
GspLong2Hex(&ptr, Value);
}
else
{
/* FIXME - what to do here? This case should never happen though, there
should always be at least one thread on the system... */
/* GspOutBuffer[0] = 'l'; */
}
}
else if (strncmp(Request, "sThreadInfo", 11) == 0)
{
PEPROCESS Process;
PLIST_ENTRY AThread, AProcess;
PCHAR ptr = &GspOutBuffer[1];
/* Get next thread id */
if (GspEnumThread != NULL)
{
/* find the next thread */
Process = GspEnumThread->ThreadsProcess;
if (GspEnumThread->ThreadListEntry.Flink != &Process->ThreadListHead)
{
GspEnumThread = CONTAINING_RECORD(GspEnumThread->ThreadListEntry.Flink,
ETHREAD, ThreadListEntry);
}
else
{
PETHREAD Thread = NULL;
AProcess = Process->ActiveProcessLinks.Flink;
while (AProcess != &PsActiveProcessHead)
{
Process = CONTAINING_RECORD(AProcess, EPROCESS, ActiveProcessLinks);
AThread = Process->ThreadListHead.Flink;
if (AThread != &Process->ThreadListHead)
{
Thread = CONTAINING_RECORD(Process->ThreadListHead.Flink,
ETHREAD, ThreadListEntry);
break;
}
AProcess = AProcess->Flink;
}
GspEnumThread = Thread;
}
if (GspEnumThread != NULL)
{
/* return the ID */
GspOutBuffer[0] = 'm';
Value = (ULONG)GspEnumThread->Cid.UniqueThread;
GspLong2Hex(&ptr, Value);
}
else
{
GspOutBuffer[0] = 'l';
}
}
else
{
GspOutBuffer[0] = 'l';
}
}
else if (strncmp(Request, "ThreadExtraInfo", 15) == 0)
{
PETHREAD ThreadInfo;
/* Get thread information */
if (GspFindThread(Request + 16, &ThreadInfo))
{
char Buffer[64];
PEPROCESS Proc;
Proc = (PEPROCESS)ThreadInfo->ThreadsProcess;
Buffer[0] = '\0';
if (NULL != Proc)
sprintf(Buffer, "%s [%d:0x%x], ",
Proc->ImageFileName,
(int)Proc->UniqueProcessId,
(int)ThreadInfo->Cid.UniqueThread);
strcpy(Buffer + strlen(Buffer), GspThreadStates[ThreadInfo->Tcb.State]);
ObDereferenceObject(ThreadInfo);
GspMem2Hex(Buffer, &GspOutBuffer[0], strlen(Buffer), FALSE);
}
}
else if (strncmp(Request, "Supported", 9) == 0)
{
/* tell maximum incoming packet size */
sprintf(GspOutBuffer, "PacketSize=%u", sizeof(GspInBuffer) - 1);
}
else if (strncmp(Request, "Rcmd,", 5) == 0)
{
}
}
VOID
GspQueryThreadStatus(PCHAR Request)
{
PETHREAD ThreadInfo;
PCHAR ptr = &Request[0];
if (GspFindThread(ptr, &ThreadInfo))
{
ObDereferenceObject(ThreadInfo);
GspOutBuffer[0] = 'O';
GspOutBuffer[1] = 'K';
GspOutBuffer[2] = '\0';
}
else
{
GspOutBuffer[0] = 'E';
GspOutBuffer[1] = '\0';
}
}
#define DR6_BS 0x00004000 /* Single step */
#define DR7_L0 0x00000001 /* Local breakpoint 0 enable */
#define DR7_G0 0x00000002 /* Global breakpoint 0 enable */
#define DR7_L1 0x00000004 /* Local breakpoint 1 enable */
#define DR7_G1 0x00000008 /* Global breakpoint 1 enable */
#define DR7_L2 0x00000010 /* Local breakpoint 2 enable */
#define DR7_G2 0x00000020 /* Global breakpoint 2 enable */
#define DR7_L3 0x00000040 /* Local breakpoint 3 enable */
#define DR7_G3 0x00000080 /* Global breakpoint 3 enable */
#define DR7_LE 0x00000100 /* Local exact breakpoint enable (old) */
#define DR7_GE 0x00000200 /* Global exact breakpoint enable (old) */
#define DR7_GD 0x00002000 /* General detect enable */
#define DR7_TYPE0_MASK 0x00030000 /* Breakpoint 0 condition */
#define DR7_LEN0_MASK 0x000c0000 /* Breakpoint 0 length */
#define DR7_TYPE1_MASK 0x00300000 /* Breakpoint 1 condition */
#define DR7_LEN1_MASK 0x00c00000 /* Breakpoint 1 length */
#define DR7_TYPE2_MASK 0x03000000 /* Breakpoint 2 condition */
#define DR7_LEN2_MASK 0x0c000000 /* Breakpoint 2 length */
#define DR7_TYPE3_MASK 0x30000000 /* Breakpoint 3 condition */
#define DR7_LEN3_MASK 0xc0000000 /* Breakpoint 3 length */
#define DR7_GLOBAL_ENABLE(Bp) (2 << (2 * (Bp)))
#define DR7_TYPE(Bp, Type) ((Type) << (16 + 4 * (Bp)))
#define DR7_LEN(Bp, Len) ((Len) << (18 + 4 * (Bp)))
#define I386_BP_TYPE_EXECUTE 0
#define I386_BP_TYPE_DATA_WRITE 1
#define I386_BP_TYPE_DATA_READWRITE 3
#define I386_OPCODE_INT3 0xcc
#define GDB_ZTYPE_MEMORY_BREAKPOINT 0
#define GDB_ZTYPE_HARDWARE_BREAKPOINT 1
#define GDB_ZTYPE_WRITE_WATCHPOINT 2
#define GDB_ZTYPE_READ_WATCHPOINT 3
#define GDB_ZTYPE_ACCESS_WATCHPOINT 4
typedef struct _GSPHWBREAKPOINT
{
ULONG Type;
ULONG_PTR Address;
ULONG Length;
} GSPHWBREAKPOINT;
#define MAX_HW_BREAKPOINTS 4
static unsigned GspHwBreakpointCount = 0;
static GSPHWBREAKPOINT GspHwBreakpoints[MAX_HW_BREAKPOINTS];
typedef struct _GSPSWBREAKPOINT
{
ULONG_PTR Address;
CHAR PrevContent;
BOOLEAN Active;
} GSPSWBREAKPOINT;
#define MAX_SW_BREAKPOINTS 64
static unsigned GspSwBreakpointCount = 0;
static GSPSWBREAKPOINT GspSwBreakpoints[MAX_SW_BREAKPOINTS];
static void
GspSetHwBreakpoint(ULONG Type, ULONG_PTR Address, ULONG Length)
{
DPRINT("GspSetHwBreakpoint(%lu, 0x%p, %lu)\n", Type, Address, Length);
if (GDB_ZTYPE_READ_WATCHPOINT == Type)
{
DPRINT1("Read watchpoint not supported\n");
strcpy(GspOutBuffer, "E22");
}
else if (GDB_ZTYPE_HARDWARE_BREAKPOINT == Type && 1 != Length)
{
DPRINT1("Invalid length %lu for hardware breakpoint\n", Length);
strcpy(GspOutBuffer, "E22");
}
else if (1 != Length && 2 != Length && 4 != Length)
{
DPRINT1("Invalid length %lu for GDB Z type %lu\n", Length, Type);
strcpy(GspOutBuffer, "E22");
}
else if (0 != (Address & (Length - 1)))
{
DPRINT1("Invalid alignment for address 0x%p and length %d\n", Address, Length);
strcpy(GspOutBuffer, "E22");
}
else if (MAX_HW_BREAKPOINTS == GspHwBreakpointCount)
{
DPRINT1("Trying to set too many hardware breakpoints\n");
strcpy(GspOutBuffer, "E22");
}
else
{
DPRINT("Stored at index %u\n", GspHwBreakpointCount);
GspHwBreakpoints[GspHwBreakpointCount].Type = Type;
GspHwBreakpoints[GspHwBreakpointCount].Address = Address;
GspHwBreakpoints[GspHwBreakpointCount].Length = Length;
GspHwBreakpointCount++;
strcpy(GspOutBuffer, "OK");
}
}
static void
GspRemoveHwBreakpoint(ULONG Type, ULONG_PTR Address, ULONG Length)
{
unsigned Index;
DPRINT("GspRemoveHwBreakpoint(%lu, 0x%p, %lu)\n", Type, Address, Length);
for (Index = 0; Index < GspHwBreakpointCount; Index++)
{
if (GspHwBreakpoints[Index].Type == Type &&
GspHwBreakpoints[Index].Address == Address &&
GspHwBreakpoints[Index].Length == Length)
{
DPRINT("Found match at index %u\n", Index);
if (Index + 1 < GspHwBreakpointCount)
memmove(GspHwBreakpoints + Index,
GspHwBreakpoints + (Index + 1),
(GspHwBreakpointCount - Index - 1) * sizeof(GSPHWBREAKPOINT));
GspHwBreakpointCount--;
strcpy(GspOutBuffer, "OK");
return;
}
}
DPRINT1("Not found\n");
strcpy(GspOutBuffer, "E22");
}
static BOOLEAN
GspFindSwBreakpoint(ULONG_PTR Address, PULONG PIndex)
{
ULONG Index;
for (Index = 0; Index < GspSwBreakpointCount; Index++)
{
if (GspSwBreakpoints[Index].Address == Address)
{
if (PIndex)
*PIndex = Index;
return TRUE;
}
}
return FALSE;
}
static void
GspSetSwBreakpoint(ULONG_PTR Address)
{
DPRINT("GspSetSwBreakpoint(0x%p)\n", Address);
if (MAX_SW_BREAKPOINTS == GspSwBreakpointCount)
{
DPRINT1("Trying to set too many software breakpoints\n");
strcpy(GspOutBuffer, "E22");
return;
}
if (GspFindSwBreakpoint(Address, NULL))
{
strcpy(GspOutBuffer, "E22");
return;
}
DPRINT("Stored at index %u\n", GspSwBreakpointCount);
GspSwBreakpoints[GspSwBreakpointCount].Address = Address;
GspSwBreakpoints[GspSwBreakpointCount].Active = FALSE;
GspSwBreakpointCount++;
strcpy(GspOutBuffer, "OK");
}
static void
GspRemoveSwBreakpoint(ULONG_PTR Address)
{
ULONG Index;
DPRINT("GspRemoveSwBreakpoint(0x%p)\n", Address);
if (GspFindSwBreakpoint(Address, &Index))
{
DPRINT("Found match at index %u\n", Index);
ASSERT(!GspSwBreakpoints[Index].Active);
if (Index + 1 < GspSwBreakpointCount)
memmove(GspSwBreakpoints + Index,
GspSwBreakpoints + (Index + 1),
(GspSwBreakpointCount - Index - 1) * sizeof(GSPSWBREAKPOINT));
GspSwBreakpointCount--;
strcpy(GspOutBuffer, "OK");
return;
}
DPRINT1("Not found\n");
strcpy(GspOutBuffer, "E22");
}
static void
GspLoadHwBreakpoint(PKTRAP_FRAME TrapFrame, unsigned BpIndex,
ULONG_PTR Address, ULONG Length, ULONG Type)
{
DPRINT("GspLoadHwBreakpoint(0x%p, %d, 0x%p, %d)\n",
TrapFrame, BpIndex, Address, Type);
/* Set the DR7_Gx bit to globally enable the breakpoint */
TrapFrame->Dr7 |= DR7_GLOBAL_ENABLE(BpIndex) |
DR7_LEN(BpIndex, Length) |
DR7_TYPE(BpIndex, Type);
switch (BpIndex)
{
case 0:
DPRINT("Setting DR0 to 0x%p\n", Address);
TrapFrame->Dr0 = Address;
break;
case 1:
DPRINT("Setting DR1 to 0x%p\n", Address);
TrapFrame->Dr1 = Address;
break;
case 2:
DPRINT("Setting DR2 to 0x%p\n", Address);
TrapFrame->Dr2 = Address;
break;
case 3:
DPRINT("Setting DR3 to 0x%p\n", Address);
TrapFrame->Dr3 = Address;
break;
}
}
static void
GspLoadSwBreakpoint(ULONG Index)
{
GspMemoryError = FALSE;
GspSwBreakpoints[Index].PrevContent = GspReadMemSafe((PCHAR)GspSwBreakpoints[Index].Address);
if (!GspMemoryError)
GspWriteMemSafe((PCHAR)GspSwBreakpoints[Index].Address, I386_OPCODE_INT3);
GspSwBreakpoints[Index].Active = !GspMemoryError;
if (GspMemoryError)
DPRINT1("Failed to set software breakpoint at 0x%p\n", GspSwBreakpoints[Index].Address);
else
DPRINT("Successfully set software breakpoint at 0x%p\n", GspSwBreakpoints[Index].Address);
}
static void
GspLoadBreakpoints(PKTRAP_FRAME TrapFrame)
{
unsigned Index;
ULONG i386Type;
DPRINT("GspLoadBreakpoints\n");
DPRINT("DR7 on entry: 0x%08x\n", TrapFrame->Dr7);
/* Remove all breakpoints */
TrapFrame->Dr7 &= ~(DR7_L0 | DR7_L1 | DR7_L2 | DR7_L3 |
DR7_G0 | DR7_G1 | DR7_G2 | DR7_G3 |
DR7_TYPE0_MASK | DR7_LEN0_MASK |
DR7_TYPE1_MASK | DR7_LEN1_MASK |
DR7_TYPE2_MASK | DR7_LEN2_MASK |
DR7_TYPE3_MASK | DR7_LEN3_MASK);
for (Index = 0; Index < GspHwBreakpointCount; Index++)
{
switch (GspHwBreakpoints[Index].Type)
{
case GDB_ZTYPE_HARDWARE_BREAKPOINT:
i386Type = I386_BP_TYPE_EXECUTE;
break;
case GDB_ZTYPE_WRITE_WATCHPOINT:
i386Type = I386_BP_TYPE_DATA_WRITE;
break;
case GDB_ZTYPE_ACCESS_WATCHPOINT:
i386Type = I386_BP_TYPE_DATA_READWRITE;
break;
default:
ASSERT(FALSE);
i386Type = I386_BP_TYPE_EXECUTE;
break;
}
GspLoadHwBreakpoint(TrapFrame, Index, GspHwBreakpoints[Index].Address,
GspHwBreakpoints[Index].Length - 1, i386Type);
}
for (Index = 0; Index < GspSwBreakpointCount; Index++)
{
DPRINT("Using real software breakpoint\n");
GspLoadSwBreakpoint(Index);
}
DPRINT("Final DR7 value 0x%08x\n", TrapFrame->Dr7);
}
static void
GspUnloadBreakpoints(void)
{
unsigned Index;
DPRINT("GspUnloadBreakpoints\n");
/* Disable hardware debugging while we are inside the stub */
__writedr(7, 0);
for (Index = 0; Index < GspSwBreakpointCount; Index++)
{
if (GspSwBreakpoints[Index].Active)
{
GspMemoryError = FALSE;
GspWriteMemSafe((PCHAR)GspSwBreakpoints[Index].Address,
GspSwBreakpoints[Index].PrevContent);
GspSwBreakpoints[Index].Active = FALSE;
if (GspMemoryError)
{
DPRINT1("Failed to remove software breakpoint from 0x%p\n",
GspSwBreakpoints[Index].Address);
}
else
{
DPRINT("Successfully removed software breakpoint from 0x%p\n",
GspSwBreakpoints[Index].Address);
}
}
}
}
static void
GspStopReply(NTSTATUS ExceptionCode, PKTRAP_FRAME TrapFrame)
{
PCHAR ptr = GspOutBuffer;
ULONG SigVal;
LONG Esp;
switch (ExceptionCode)
{
case STATUS_INTEGER_DIVIDE_BY_ZERO:
SigVal = 8; /* divide by zero */
break;
case STATUS_SINGLE_STEP:
case STATUS_BREAKPOINT:
SigVal = 5; /* breakpoint */
break;
case STATUS_INTEGER_OVERFLOW:
case STATUS_ARRAY_BOUNDS_EXCEEDED:
SigVal = 16; /* bound instruction */
break;
case STATUS_ILLEGAL_INSTRUCTION:
SigVal = 4; /* Invalid opcode */
break;
case STATUS_STACK_OVERFLOW:
case STATUS_DATATYPE_MISALIGNMENT:
case STATUS_ACCESS_VIOLATION:
SigVal = 11; /* access violation */
break;
default:
SigVal = 7; /* "software generated" */
}
ptr = GspOutBuffer;
*ptr++ = 'T'; /* notify gdb with signo, PC, FP and SP */
*ptr++ = HexChars[(SigVal >> 4) & 0xf];
*ptr++ = HexChars[SigVal & 0xf];
*ptr++ = HexChars[ESP];
*ptr++ = ':';
Esp = GspGetEspFromTrapFrame(TrapFrame); /* SP */
ptr = GspMem2Hex((PCHAR)&Esp, ptr, 4, 0);
*ptr++ = ';';
*ptr++ = HexChars[EBP];
*ptr++ = ':';
ptr = GspMem2Hex((PCHAR)&TrapFrame->Ebp, ptr, 4, 0); /* FP */
*ptr++ = ';';
*ptr++ = HexChars[PC];
*ptr++ = ':';
ptr = GspMem2Hex((PCHAR)&TrapFrame->Eip, ptr, 4, 0); /* PC */
*ptr++ = ';';
*ptr = '\0';
}
/*
* This function does all command procesing for interfacing to GDB.
*/
KD_CONTINUE_TYPE
NTAPI
KdpGdbEnterDebuggerException(PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context,
PKTRAP_FRAME TrapFrame)
{
static BOOLEAN GdbAttached = FALSE;
BOOLEAN Stepping = FALSE;
NTSTATUS ExceptionCode;
LONG Address;
LONG Length;
PCHAR ptr;
/* FIXME: Stop on other CPUs too */
DPRINT("Thread %p entering stub\n", PsGetCurrentThread());
ExceptionCode = (NTSTATUS)ExceptionRecord->ExceptionCode;
/* Can only debug 1 thread at a time... */
ExAcquireFastMutex(&GspLock);
DPRINT("Thread %p acquired mutex\n", PsGetCurrentThread());
GspUnloadBreakpoints();
/* Make sure we're debugging the current thread. */
if (NULL != GspDbgThread)
{
DPRINT1("Internal error: entering stub with non-NULL GspDbgThread\n");
ObDereferenceObject(GspDbgThread);
GspDbgThread = NULL;
}
if (GdbAttached)
{
GspStopReply(ExceptionCode, TrapFrame);
GspPutPacket(GspOutBuffer);
// DbgPrint(">>> (%s) >>>\n", GspOutBuffer);
}
else
{
GdbAttached = TRUE;
}
while (TRUE)
{
/* Zero the buffer now so we don't have to worry about the terminating zero character */
memset(GspOutBuffer, 0, sizeof(GspOutBuffer));
ptr = GspGetPacket();
// DbgPrint("<<< (%s) <<<\n", ptr);
switch (*ptr++)
{
case '?':
/* a little hack to send more complete status information */
GspStopReply(ExceptionCode, TrapFrame);
break;
case 'd':
GspRemoteDebug = !GspRemoteDebug; /* toggle debug flag */
break;
case 'g': /* return the value of the CPU Registers */
GspGetRegisters(GspOutBuffer, TrapFrame);
break;
case 'G': /* set the value of the CPU Registers - return OK */
if (NULL != GspDbgThread)
GspSetRegistersInTrapFrame(ptr, Context, GspDbgThread->Tcb.TrapFrame);
else
GspSetRegistersInTrapFrame(ptr, Context, TrapFrame);
strcpy(GspOutBuffer, "OK");
break;
case 'P': /* set the value of a single CPU register - return OK */
{
LONG Register;
if ((GspHex2Long(&ptr, &Register)) && (*ptr++ == '='))
{
if ((Register >= 0) && (Register < NUMREGS))
{
if (GspDbgThread)
{
GspSetSingleRegisterInTrapFrame(ptr, Register, Context,
GspDbgThread->Tcb.TrapFrame);
}
else
{
GspSetSingleRegisterInTrapFrame(ptr, Register, Context, TrapFrame);
}
strcpy(GspOutBuffer, "OK");
break;
}
}
strcpy(GspOutBuffer, "E01");
break;
}
/* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
case 'm':
{
/* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */
if (GspHex2Long(&ptr, &Address) && *(ptr++) == ',' && GspHex2Long(&ptr, &Length))
{
PEPROCESS DbgProcess = NULL;
ptr = NULL;
if (NULL != GspDbgThread &&
PsGetCurrentProcess() != GspDbgThread->ThreadsProcess)
{
DbgProcess = GspDbgThread->ThreadsProcess;
KeAttachProcess(&DbgProcess->Pcb);
}
GspMemoryError = FALSE;
GspMem2Hex((PCHAR)Address, GspOutBuffer, Length, 1);
if (NULL != DbgProcess)
KeDetachProcess();
if (GspMemoryError)
{
strcpy(GspOutBuffer, "E03");
DPRINT1("Fault during memory read\n");
}
ptr = NULL;
}
if (NULL != ptr)
strcpy(GspOutBuffer, "E01");
break;
}
/* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
case 'M':
{
/* TRY TO READ '%x,%x:'. IF SUCCEED, SET PTR = 0 */
if (GspHex2Long(&ptr, &Address))
{
if (*(ptr++) == ',' && GspHex2Long(&ptr, &Length) && *(ptr++) == ':')
{
PEPROCESS DbgProcess = NULL;
if (NULL != GspDbgThread &&
PsGetCurrentProcess() != GspDbgThread->ThreadsProcess)
{
DbgProcess = GspDbgThread->ThreadsProcess;
KeAttachProcess(&DbgProcess->Pcb);
}
GspMemoryError = FALSE;
GspHex2Mem(ptr, (PCHAR)Address, Length, TRUE);
if (NULL != DbgProcess)
KeDetachProcess();
if (GspMemoryError)
{
strcpy(GspOutBuffer, "E03");
DPRINT1("Fault during memory write\n");
}
else
{
strcpy(GspOutBuffer, "OK");
}
ptr = NULL;
}
}
if (NULL != ptr)
strcpy(GspOutBuffer, "E02");
break;
}
/* cAA..AA Continue at address AA..AA */
/* sAA..AA Step one instruction from AA..AA */
case 's':
Stepping = TRUE;
case 'c':
{
ULONG BreakpointNumber;
ULONG Dr6;
/* try to read optional parameter, pc changed if param */
if (GspHex2Long(&ptr, &Address))
{
Context->Eip = Address;
}
else if (ExceptionCode == STATUS_BREAKPOINT)
{
if (GspReadMemSafe((PCHAR)Context->Eip) == (CHAR)I386_OPCODE_INT3)
Context->Eip++;
}
/* clear the trace bit */
Context->EFlags &= ~EFLAGS_TF;
/* set the trace bit if we're Stepping */
if (Stepping)
Context->EFlags |= EFLAGS_TF;
Dr6 = __readdr(6);
if (!(Dr6 & DR6_BS))
{
for (BreakpointNumber = 0;
BreakpointNumber < MAX_HW_BREAKPOINTS;
BreakpointNumber++)
{
if (Dr6 & (1 << BreakpointNumber))
{
if (GspHwBreakpoints[BreakpointNumber].Type == I386_BP_TYPE_EXECUTE)
{
/* Set restore flag */
Context->EFlags |= EFLAGS_RF;
break;
}
}
}
}
GspLoadBreakpoints(TrapFrame);
__writedr(6, 0);
if (NULL != GspDbgThread)
{
ObDereferenceObject(GspDbgThread);
GspDbgThread = NULL;
}
DPRINT("Thread %p releasing mutex\n", PsGetCurrentThread());
ExReleaseFastMutex(&GspLock);
DPRINT("Thread %p leaving stub\n", PsGetCurrentThread());
if (ExceptionCode == STATUS_BREAKPOINT ||
ExceptionCode == STATUS_SINGLE_STEP)
return kdContinue;
return kdHandleException;
}
case 'k': /* kill the program */
strcpy(GspOutBuffer, "OK");
break;
case 'H': /* Set thread */
GspSetThread(ptr);
break;
case 'q': /* Query */
GspQuery(ptr);
break;
case 'T': /* Query thread status */
GspQueryThreadStatus(ptr);
break;
case 'Z':
{
LONG Type;
LONG Address;
LONG Length;
GspHex2Long(&ptr, &Type);
ptr++;
GspHex2Long(&ptr, &Address);
ptr++;
GspHex2Long(&ptr, &Length);
if (0 == Type)
GspSetSwBreakpoint((ULONG_PTR)Address);
else
GspSetHwBreakpoint(Type, (ULONG_PTR)Address, Length);
break;
}
case 'z':
{
LONG Type;
LONG Address;
LONG Length;
GspHex2Long(&ptr, &Type);
ptr++;
GspHex2Long(&ptr, &Address);
ptr++;
GspHex2Long(&ptr, &Length);
if (0 == Type)
GspRemoveSwBreakpoint((ULONG_PTR)Address);
else
GspRemoveHwBreakpoint(Type, (ULONG_PTR)Address, Length);
break;
}
default:
break;
}
/* reply to the request */
GspPutPacket(GspOutBuffer);
// DbgPrint(">>> (%s) >>>\n", GspOutBuffer);
}
}
BOOLEAN
NTAPI
GspBreakIn(PKINTERRUPT Interrupt, PVOID ServiceContext)
{
PKTRAP_FRAME TrapFrame;
BOOLEAN DoBreakIn;
CONTEXT Context;
KIRQL OldIrql;
UCHAR Value;
DPRINT("Break In\n");
DoBreakIn = FALSE;
while (KdPortGetByteEx(&GdbPortInfo, &Value))
{
if (Value == 0x03)
DoBreakIn = TRUE;
}
if (!DoBreakIn)
return TRUE;
KeRaiseIrql(HIGH_LEVEL, &OldIrql);
TrapFrame = PsGetCurrentThread()->Tcb.TrapFrame;
KeTrapFrameToContext(TrapFrame, NULL, &Context);
KdpGdbEnterDebuggerException(NULL, &Context, TrapFrame);
KeContextToTrapFrame(&Context, NULL, TrapFrame, Context.ContextFlags, KernelMode);
KeLowerIrql(OldIrql);
return TRUE;
}
VOID
NTAPI
KdpGdbDebugPrint(PCH Message, ULONG Length)
{
}
/* Initialize the GDB stub */
VOID
NTAPI
KdpGdbStubInit(PKD_DISPATCH_TABLE WrapperTable, ULONG BootPhase)
{
if (!KdDebuggerEnabled || !KdpDebugMode.Gdb)
return;
if (BootPhase == 0)
{
ExInitializeFastMutex(&GspLock);
/* Write out the functions that we support for now */
WrapperTable->KdpInitRoutine = KdpGdbStubInit;
WrapperTable->KdpPrintRoutine = KdpGdbDebugPrint;
WrapperTable->KdpExceptionRoutine = KdpGdbEnterDebuggerException;
/* Initialize the Port */
KdPortInitializeEx(&GdbPortInfo, GdbPortNumber);
}
else if (BootPhase == 1)
{
GspInitialized = TRUE;
GspRunThread = NULL;
GspDbgThread = NULL;
GspEnumThread = NULL;
HalDisplayString("Waiting for GDB to attach\r\n");
DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C);
}
else if (BootPhase == 2)
{
HalDisplayString("\r\n GDB debugging enabled\r\n\r\n");
}
}
/* EOF */