/****************************************************************************

   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.

****************************************************************************/

/****************************************************************************
 *  Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $
 *
 *  Module name: remcom.c $
 *  Revision: 1.34 $
 *  Date: 91/03/09 12:29:49 $
 *  Contributor:     Lake Stevens Instrument Division$
 *
 *  Description:     low level support for gdb debugger. $
 *
 *  Considerations:  only works on target hardware $
 *
 *  Written by:      Glenn Engel $
 *  ModuleState:     Experimental $
 *
 *  NOTES:           See Below $
 *
 *  Modified for 386 by Jim Kingdon, Cygnus Support.
 *  Modified for ReactOS by Casper S. Hornstrup <chorns@users.sourceforge.net>
 *
 *  To enable debugger support, two things need to happen.  One, setting
 *  up a routine so that it is in the exception path, is necessary in order
 *  to allow any breakpoints or error conditions to be properly intercepted
 *  and reported to gdb.
 *  Two, a breakpoint needs to be generated to begin communication.
 *
 *  Because gdb will sometimes write to the stack area to execute function
 *  calls, this program cannot rely on using the supervisor stack so it
 *  uses it's own stack area.
 *
 *************
 *
 *    The following gdb commands are supported:
 *
 * command          function                               Return value
 *
 *    g             return the value of the CPU Registers  hex data or ENN
 *    G             set the value of the CPU Registers     OK or ENN
 *
 *    mAA..AA,LLLL  Read LLLL bytes at address AA..AA      hex data or ENN
 *    MAA..AA,LLLL: Write LLLL bytes at address AA.AA      OK or ENN
 *
 *    c             Resume at current address              SNN   ( signal NN)
 *    cAA..AA       Continue at address AA..AA             SNN
 *
 *    s             Step one instruction                   SNN
 *    sAA..AA       Step one instruction from AA..AA       SNN
 *
 *    k             kill
 *
 *    ?             What was the last sigval ?             SNN   (signal NN)
 *
 * All commands and responses are sent with a packet which includes a
 * Checksum.  A packet consists of
 *
 * $<packet info>#<Checksum>.
 *
 * where
 * <packet info> :: <characters representing the command or response>
 * <Checksum>    :: < two hex digits computed as modulo 256 sum of <packetinfo>>
 *
 * When a packet is received, it is first acknowledged with either '+' or '-'.
 * '+' indicates a successful transfer.  '-' indicates a failed transfer.
 *
 * Example:
 *
 * Host:                  Reply:
 * $m0,10#2a               +$00010203040506070809101112131415#42
 *
 ****************************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <internal/debug.h>

/************************************************************************/
/* BUFMAX defines the maximum number of characters in inbound/outbound buffers*/
/* at least NUMREGBYTES*2 are needed for register packets */
#define BUFMAX 1000

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;
KD_PORT_INFORMATION GdbPortInfo = { 2, 115200, 0 }; /* FIXME hardcoded for COM2, 115200 baud */

/* 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] =
{
};

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;
}

static CHAR GspInBuffer[BUFMAX];
static CHAR GspOutBuffer[BUFMAX];

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()
{
  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 < BUFMAX)
        {
          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 volatile void *GspAccessLocation = NULL;

static CHAR
GspReadMemSafe(PCHAR Address)
{
  CHAR ch;

  if (NULL == Address)
    {
      GspMemoryError = TRUE;
      return '\0';
    }

  GspAccessLocation = Address;
  ch = *Address;
  GspAccessLocation = NULL;

  return ch;
}

/* 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;
  ULONG OldProt = 0;

  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);
        }
      if (MayFault)
        {
          OldProt = MmGetPageProtect(NULL, Address);
          MmSetPageProtect(NULL, Address, PAGE_EXECUTE_READWRITE);
        }

      for (i = 0; i < CountInPage && ! GspMemoryError; i++)
        {
          ch = (*GetContent)(Context, Current - Address);

          if (MayFault)
            {
              GspAccessLocation = Current;
            }
          *Current = ch;
          if (MayFault)
            {
              GspAccessLocation = NULL;
            }
          Current++;
        }
      if (MayFault)
        {
          MmSetPageProtect(NULL, Page, OldProt);
          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;
}

static CHAR
GspWriteMemSafeGetContent(PVOID Context, ULONG Offset)
{
  ASSERT(0 == Offset);

  return *((PCHAR) Context);
}

static void
GspWriteMemSafe(PCHAR Address,
  CHAR Ch)
{
  GspWriteMem(Address, 1, TRUE, GspWriteMemSafeGetContent, &Ch);
}


/* This function takes the 386 exception vector and attempts to
   translate this number into a unix compatible signal value */
ULONG
GspComputeSignal(NTSTATUS ExceptionCode)
{
  ULONG SigVal;

  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" */
    }
  return SigVal;
}


/**********************************************/
/* 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->Gpr1 : (LONG)TrapFrame->Gpr1;
}


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)
            {
            }
          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);
        }
    }
}

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 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 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");
    }
  else
    {
      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)
{
  unsigned Index;

  DPRINT("GspRemoveSwBreakpoint(0x%p)\n", Address);
  for (Index = 0; Index < GspSwBreakpointCount; Index++)
    {
      if (GspSwBreakpoints[Index].Address == Address)
        {
          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
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++)
    {
      if (GspHwBreakpointCount + Index < MAX_HW_BREAKPOINTS)
        {
          DPRINT("Implementing software interrupt using hardware register\n");
          GspLoadHwBreakpoint(TrapFrame, GspHwBreakpointCount + Index,
                              GspSwBreakpoints[Index].Address, 0,
                              I386_BP_TYPE_EXECUTE);
          GspSwBreakpoints[Index].Active = FALSE;
        }
      else
        {
          DPRINT("Using real software breakpoint\n");
          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);
    DPRINT1("Successfully set software breakpoint at 0x%p\n", GspSwBreakpoints[Index].Address);
            }
        }
    }

  DPRINT("Final DR7 value 0x%08x\n", TrapFrame->Dr7);
}

static void
GspUnloadBreakpoints(PKTRAP_FRAME TrapFrame)
{
  unsigned Index;

  DPRINT("GspUnloadHwBreakpoints\n");

  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 BOOLEAN gdb_attached_yet = FALSE;
/*
 * This function does all command procesing for interfacing to gdb.
 */
KD_CONTINUE_TYPE
STDCALL
KdpGdbEnterDebuggerException(PEXCEPTION_RECORD ExceptionRecord,
                             PCONTEXT Context,
                             PKTRAP_FRAME TrapFrame)
{
  BOOLEAN Stepping;
  LONG Address;
  LONG Length;
  LONG SigVal = 0;
  //LONG NewPC;
  PCHAR ptr;

  /* FIXME: Stop on other CPUs too */

  if (STATUS_ACCESS_VIOLATION == (NTSTATUS) ExceptionRecord->ExceptionCode &&
      NULL != GspAccessLocation &&
      (ULONG_PTR) GspAccessLocation ==
      (ULONG_PTR) ExceptionRecord->ExceptionInformation[1])
    {
      GspAccessLocation = NULL;
      GspMemoryError = TRUE;
      //Context->Eip += 3;
    }
  else
    {
      DPRINT("Thread %p entering stub\n", PsGetCurrentThread());
      /* Can only debug 1 thread at a time... */
      ExAcquireFastMutex(&GspLock);
      DPRINT("Thread %p acquired mutex\n", PsGetCurrentThread());

      /* Disable hardware debugging while we are inside the stub */
      //Ke386SetDr7(0);
      GspUnloadBreakpoints(TrapFrame);

      /* 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;
        }

      /* ugly hack to avoid attempting to send status at the very
       * beginning, right when GDB is trying to query the stub */
      if (gdb_attached_yet)
        {
          LONG Esp;

          stop_reply:
          /* reply to host that an exception has occurred */
          SigVal = GspComputeSignal(ExceptionRecord->ExceptionCode);

          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';

          GspPutPacket(&GspOutBuffer[0]);
        }
      else
        {
          gdb_attached_yet = 1;
        }

      Stepping = FALSE;

      while (TRUE)
        {
          /* Zero the buffer now so we don't have to worry about the terminating zero character */
          memset(GspOutBuffer, 0, sizeof(GspInBuffer));
          ptr = GspGetPacket();

          switch(*ptr++)
            {
            case '?':
              /* a little hack to send more complete status information */
              goto stop_reply;
              GspOutBuffer[0] = 'S';
              GspOutBuffer[1] = HexChars[SigVal >> 4];
              GspOutBuffer[2] = HexChars[SigVal % 16];
              GspOutBuffer[3] = 0;
              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");
                      DPRINT("Fault during memory read\n");
                    }
                }

              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");
                          DPRINT("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(optional) */
            /* sAA..AA   Step one instruction from AA..AA(optional) */
            case 's':
              Stepping = TRUE;
            case 'c':
              {
                ULONG BreakpointNumber;
                ULONG dr6_ = 0;

                /* try to read optional parameter, pc unchanged if no parm */
                if (GspHex2Long (&ptr, &Address))
                  {
                      //Context->Eip = Address;
                  }

                //NewPC = Context->Eip;

                /* clear the trace bit */
                //Context->EFlags &= 0xfffffeff;

                /* set the trace bit if we're Stepping */
                if (Stepping)
                  {
                      //Context->EFlags |= 0x100;
                  }

                // XXX arty load dr6
                if (!(dr6_ & 0x4000))
                  {
                    for (BreakpointNumber = 0; BreakpointNumber < 4; ++BreakpointNumber)
                      {
                        if (dr6_ & (1 << BreakpointNumber))
                          {
                            if (GspHwBreakpoints[BreakpointNumber].Type == 0)
                              {
                                /* Set restore flag */
                                  //Context->EFlags |= 0x10000;
                                break;
                              }
                          }
                      }
                  }
                GspLoadBreakpoints(TrapFrame);
                // XXX load dr6
                if (NULL != GspDbgThread)
                  {
                    ObDereferenceObject(GspDbgThread);
                    GspDbgThread = NULL;
                  }

                DPRINT("Thread %p releasing mutex\n", PsGetCurrentThread());
                ExReleaseFastMutex(&GspLock);
                DPRINT("Thread %p leaving stub\n", PsGetCurrentThread());
                return kdContinue;
                break;
              }

            case 'k':  /* kill the program */
              strcpy(GspOutBuffer, "OK");
              break;
              /* kill the program */

            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);
        }

      /* not reached */
      ASSERT(0);
    }

  if (NULL != GspDbgThread)
    {
      ObDereferenceObject(GspDbgThread);
      GspDbgThread = NULL;
    }

  return kdContinue;
}


BOOLEAN
STDCALL
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
STDCALL
KdpGdbDebugPrint(PCH Message, ULONG Length)
{
}

/* Initialize the GDB stub */
VOID
STDCALL
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, 0, 0);

      KdpPort = GdbPortInfo.ComPort;
    }
  else if (BootPhase == 1)
    {
      GspInitialized = TRUE;

      GspRunThread = NULL;
      GspDbgThread = NULL;
      GspEnumThread = NULL;

      HalDisplayString("Waiting for GDB to attach\n");
      DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C);
    }
  else if (BootPhase == 2)
    {
      HalDisplayString("\n   GDB debugging enabled\n\n");
    }
}

/* EOF */
