WineDbg: adding a GDB remote proxy (part II)

Eric Pouech eric.pouech at wanadoo.fr
Tue Jul 9 15:58:56 CDT 2002


the code for the gdb proxy.

the way to use it:
0/ invoke winegdb with 
	winegdb -- --gdb my_pgm
you can also add options for my_pgm

basically, winegdb will:
1/ start my_pgm
2/ start gdb and tell gdb to make a remote connection to winedbg (acting
as a server for the gdb remote debugging protocol)
3/ then winedbg will act as a proxy and transform all the requests from
gdb into Win32 Api calls

in step 0, you can also use instead of the name of a program to be
started the (Win32-)pid of a running program

------------------------

there are a few available options when starting winedbg in the gdb proxy
mode:
--no-start this will not trigger the step 2 above. this is useful when
using another front end to winegdb (like ddd). see below
--with-xterm this will start gdb in its own xterm window so that gdb
output isn't mixed up with the program output (and the proxy one...)

------------------------
a few commands have been added to gdb (using the monitor command):

monitor wnd
monitor window	lists all known window in the system

monitor proc
monitor process	lists all known processes in the system

monitor mem	displays the mapping of memory of the debugged process.
relies on a correct implementation of VirtualQueryEx which isn't in Wine
yet


two other commands exist, but their lifetime is unknown:
monitor trace	let you decide on the level of trace you want for the
proxy
monitor linear	transform a segmented address into a linear one (in the
debugged process context)

------------------------
using the proxy with another front-end

I only tested ddd (so YMMV with GVD, emacs...)

basically, most of the graphical front ends have a way to tell to use
gdb in a remote way
for ddd:
a/ start winedbg with the --no-start option. you'll get a message like
"target remote localhost:12345" (port number will vary)
b/ start ddd
c/ in the console window of ddd, type:
	file /home/eric/wine/miscemu/wine	(change of course with the path to
wine)
	target remote localhost:12345		(this should be exact message as output
in step a)
	sharedlibrary
and this should be it...
as of today, the steps abc are done automatically for gdb... some future
version are likely to have their own configuration options to let you
choose the front end you'd like

A+
-------------- next part --------------
Name:          wdbg_gdb
ChangeLog:     added a remote proxy for gdb
License:       X11
GenDate:       2002/07/09 20:29:11 UTC
ModifiedFiles: debugger-merge/Makefile.in debugger-merge/stabs.c
AddedFiles:    debugger-merge/gdbproxy.c
===================================================================
RCS file: /home/cvs/cvsroot/wine/wine/debugger/Makefile.in,v
retrieving revision 1.29
diff -u -u -r1.29 Makefile.in
--- debugger-merge/Makefile.in	21 May 2002 19:42:29 -0000	1.29
+++ debugger-merge/Makefile.in	21 May 2002 20:03:50 -0000
@@ -1,4 +1,4 @@
-EXTRADEFS = -DSTRICT
+
 TOPSRCDIR = @top_srcdir@
 TOPOBJDIR = ..
 SRCDIR    = @srcdir@
@@ -14,6 +14,7 @@
 	display.c \
 	expr.c \
 	ext_debugger.c \
+	gdbproxy.c \
 	hash.c \
 	info.c \
 	memory.c \
Index: debugger-merge/stabs.c
===================================================================
RCS file: /home/cvs/cvsroot/wine/wine/debugger/stabs.c,v
retrieving revision 1.48
diff -u -u -r1.48 stabs.c
--- debugger-merge/stabs.c	31 May 2002 23:06:47 -0000	1.48
+++ debugger-merge/stabs.c	8 Jun 2002 15:27:35 -0000
@@ -82,7 +82,8 @@
 #define N_EXCL		0xc2
 #define N_RBRAC		0xe0
 
-typedef struct tagELF_DBG_INFO {
+typedef struct tagELF_DBG_INFO 
+{
     unsigned long	elf_addr;
 } ELF_DBG_INFO;
 
@@ -1099,7 +1100,7 @@
     int		stabsect;
     int		stabstrsect;
 
-    if (module->type != DMT_ELF || ! module->elf_info) {
+    if (module->type != DMT_ELF || !module->elf_info) {
 	DEBUG_Printf(DBG_CHN_ERR, "Bad elf module '%s'\n", module->module_name);
 	return DIL_ERROR;
     }
@@ -1192,7 +1193,7 @@
 					     unsigned int load_offset,
 					     unsigned int* dyn_addr)
 {
-    static const unsigned char elf_signature[4] = { 0x7f, 'E', 'L', 'F' };
+    static const unsigned char elf_signature[4] = { ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3 };
     enum DbgInfoLoad dil = DIL_ERROR;
     char*	addr = (char*)0xffffffff;
     int		fd = -1;
@@ -1249,14 +1250,17 @@
 	    size = ppnt[i].p_vaddr - delta + ppnt[i].p_memsz;
     }
 
-    for (i = 0; i < ehptr->e_shnum; i++) {
+    for (i = 0; i < ehptr->e_shnum; i++) 
+    {
 	if (strcmp(shstrtab + spnt[i].sh_name, ".bss") == 0 &&
-	    spnt[i].sh_type == SHT_NOBITS) {
+	    spnt[i].sh_type == SHT_NOBITS) 
+        {
 	    if (size < spnt[i].sh_addr - delta + spnt[i].sh_size)
 		size = spnt[i].sh_addr - delta + spnt[i].sh_size;
 	}
 	if (strcmp(shstrtab + spnt[i].sh_name, ".dynamic") == 0 &&
-	    spnt[i].sh_type == SHT_DYNAMIC) {
+	    spnt[i].sh_type == SHT_DYNAMIC) 
+        {
 	    if (dyn_addr) *dyn_addr = spnt[i].sh_addr;
 	}
     }
@@ -1329,6 +1333,8 @@
       dil = DEBUG_ProcessElfFileFromPath(filename, load_offset, dyn_addr, getenv("PATH"));
       if (dil == DIL_ERROR)
 	dil = DEBUG_ProcessElfFileFromPath(filename, load_offset, dyn_addr, getenv("LD_LIBRARY_PATH"));
+      if (dil == DIL_ERROR)
+	dil = DEBUG_ProcessElfFileFromPath(filename, load_offset, dyn_addr, getenv("WINEDLLPATH"));
    }
 
    DEBUG_ReportDIL(dil, "ELF", filename, load_offset);
@@ -1435,6 +1441,46 @@
 
  leave:
     return dil;
+}
+
+/* FIXME: merge with some of the routines above */
+int read_elf_info(const char* filename, unsigned long tab[])
+{
+    static const unsigned char elf_signature[4] = { ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3 };
+    char*	addr;
+    Elf32_Ehdr* ehptr;
+    Elf32_Shdr* spnt;
+    char*       shstrtab;
+    int	       	i;
+    int         ret = 0;
+    HANDLE      hFile;
+    HANDLE      hMap = 0;
+
+    addr = NULL;
+    hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, 
+                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hFile == INVALID_HANDLE_VALUE) goto leave;
+    hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+    if (hMap == 0) goto leave;
+    addr = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+    if (addr == NULL) goto leave;
+
+    ehptr = (Elf32_Ehdr*) addr;
+    if (memcmp(ehptr->e_ident, elf_signature, sizeof(elf_signature))) goto leave;
+
+    spnt = (Elf32_Shdr*) (addr + ehptr->e_shoff);
+    shstrtab = (addr + spnt[ehptr->e_shstrndx].sh_offset);
+
+    tab[0] = tab[1] = tab[2] = 0;
+    for (i = 0; i < ehptr->e_shnum; i++) 
+    {
+    }
+    ret = 1;
+ leave:
+    if (addr != NULL) UnmapViewOfFile(addr);
+    if (hMap != 0) CloseHandle(hMap);
+    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
+    return ret;
 }
 
 #else	/* !__ELF__ */
--- /dev/null	Thu Jan  1 01:00:00 1970
+++ debugger-merge/gdbproxy.c	Tue Jul  9 21:50:59 2002
@@ -0,0 +1,1949 @@
+/*
+ * A Win32 based proxy implementing the GBD remote protocol
+ * This allows to debug Wine (and any "emulated" program) under
+ * Linux using GDB
+ *
+ * (c) Eric Pouech 2002
+ */
+
+#include "config.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "tlhelp32.h"
+
+/* those two are needed only for the SHOWNORMAL flag */
+#include "wingdi.h"
+#include "winuser.h"
+
+#include "debugger.h"
+
+#define GDBPXY_TRC_LOWLEVEL             0x01
+#define GDBPXY_TRC_PACKET               0x02
+#define GDBPXY_TRC_COMMAND              0x04
+#define GDBPXY_TRC_COMMAND_ERROR        0x08
+#define GDBPXY_TRC_WIN32_EVENT          0x10
+#define GDBPXY_TRC_WIN32_ERROR          0x20
+
+struct gdb_ctx_Xpoint
+{
+    int                         type;   /* -1 means free */
+    void*                       addr;
+    unsigned long               val;
+};
+
+struct gdb_context 
+{
+    /* gdb information */
+    int                         sock;
+    /* incoming buffer */
+    char*                       in_buf;
+    int                         in_buf_alloc;
+    int                         in_len;
+    /* split into individual packet */
+    char*                       in_packet;
+    int                         in_packet_len;
+    /* outgoing buffer */
+    char*                       out_buf;
+    int                         out_buf_alloc;
+    int                         out_len;
+    int                         out_curr_packet;
+    /* generic GDB thread information */
+    unsigned                    exec_thread;    /* thread used in step & continue */
+    unsigned                    other_thread;   /* thread to be used in any other operation */
+    unsigned                    trace;
+    /* current Win32 trap env */
+    unsigned                    last_sig;
+    BOOL                        in_trap;
+    CONTEXT                     context;
+    /* Win32 information */
+    DBG_PROCESS*                process;
+#define NUM_XPOINT      32
+    struct gdb_ctx_Xpoint       Xpoints[NUM_XPOINT];
+    /* Unix environment */
+    unsigned long               wine_segs[3];   /* load addresses of the ELF wine exec segments (text, bss and data) */
+};
+
+/* =============================================== *
+ *       B A S I C   M A N I P U L A T I O N S     *
+ * =============================================== *
+ */
+
+static inline int hex_from0(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;
+    assert(0);
+}
+
+static inline unsigned char hex_to0(int x)
+{
+    assert(x >= 0 && x < 16);
+    return "0123456789abcdef"[x];
+}
+
+static void hex_from(void* dst, const char* src, size_t len)
+{
+    while (len--)
+    {
+        *(unsigned char*)dst++ = (hex_from0(src[0]) << 4) | hex_from0(src[1]);
+        src += 2;
+    }
+}
+
+static void hex_to(char* dst, const void* src, size_t len)
+{
+    while (len--)
+    {
+        *dst++ = hex_to0(*(const unsigned char*)src >> 4);
+        *dst++ = hex_to0(*(const unsigned char*)src & 0x0F);
+        src++;
+    }
+}
+
+static unsigned char checksum(const char* ptr, int len)
+{
+    unsigned cksum = 0;
+
+    while (len-- > 0)
+        cksum += (unsigned char)*ptr++;
+    return cksum;
+}
+
+/* =============================================== *
+ *              C P U   H A N D L E R S            *
+ * =============================================== *
+ */
+
+#define	OFFSET_OF(__c,__f)	((int)(((char*)&(((__c*)0)->__f))-((char*)0)))
+
+#ifdef __i386__
+static size_t cpu_register_map[] = {
+    OFFSET_OF(CONTEXT, Eax),
+    OFFSET_OF(CONTEXT, Ecx),
+    OFFSET_OF(CONTEXT, Edx),
+    OFFSET_OF(CONTEXT, Ebx),
+    OFFSET_OF(CONTEXT, Esp),
+    OFFSET_OF(CONTEXT, Ebp),
+    OFFSET_OF(CONTEXT, Esi),
+    OFFSET_OF(CONTEXT, Edi),
+    OFFSET_OF(CONTEXT, Eip),
+    OFFSET_OF(CONTEXT, EFlags),
+    OFFSET_OF(CONTEXT, SegCs),
+    OFFSET_OF(CONTEXT, SegSs),
+    OFFSET_OF(CONTEXT, SegDs),
+    OFFSET_OF(CONTEXT, SegEs),
+    OFFSET_OF(CONTEXT, SegFs),
+    OFFSET_OF(CONTEXT, SegGs),
+};
+#else
+#error "Define the registers map for your CPU"
+#endif
+#undef OFFSET_OF
+
+static const size_t cpu_num_regs = (sizeof(cpu_register_map) / sizeof(cpu_register_map[0]));
+
+static inline unsigned long* cpu_register(struct gdb_context* gdbctx, unsigned idx)
+{
+    assert(idx < cpu_num_regs);
+    return (unsigned long*)((char*)&gdbctx->context + cpu_register_map[idx]);
+}
+
+static inline BOOL     cpu_enter_stepping(struct gdb_context* gdbctx)
+{
+#ifdef __i386__
+    gdbctx->context.EFlags |= 0x100;
+    return TRUE;
+#else
+#error "Define step mode enter for your CPU"
+#endif
+    return FALSE;
+}
+
+static inline BOOL     cpu_leave_stepping(struct gdb_context* gdbctx)
+{
+#ifdef __i386__
+    /* The Win32 debug API always resets the Step bit in EFlags after
+     * a single step instruction, so we don't need to clear when the
+     * step is done.
+     */
+    return TRUE;
+#else
+#error "Define step mode leave for your CPU"
+#endif
+    return FALSE;
+}
+
+#ifdef __i386__
+#define DR7_CONTROL_SHIFT	16
+#define DR7_CONTROL_SIZE 	4
+
+#define DR7_RW_EXECUTE 		(0x0)
+#define DR7_RW_WRITE		(0x1)
+#define DR7_RW_READ		(0x3)
+
+#define DR7_LEN_1		(0x0)
+#define DR7_LEN_2		(0x4)
+#define DR7_LEN_4		(0xC)
+
+#define DR7_LOCAL_ENABLE_SHIFT	0
+#define DR7_GLOBAL_ENABLE_SHIFT 1
+#define DR7_ENABLE_SIZE 	2
+
+#define DR7_LOCAL_ENABLE_MASK	(0x55)
+#define DR7_GLOBAL_ENABLE_MASK	(0xAA)
+
+#define DR7_CONTROL_RESERVED	(0xFC00)
+#define DR7_LOCAL_SLOWDOWN	(0x100)
+#define DR7_GLOBAL_SLOWDOWN	(0x200)
+
+#define	DR7_ENABLE_MASK(dr)	(1<<(DR7_LOCAL_ENABLE_SHIFT+DR7_ENABLE_SIZE*(dr)))
+#define	IS_DR7_SET(ctrl,dr) 	((ctrl)&DR7_ENABLE_MASK(dr))
+
+static inline int       i386_get_unused_DR(struct gdb_context* gdbctx, 
+                                           unsigned long** r)
+{
+    if (!IS_DR7_SET(gdbctx->context.Dr7, 0)) 
+    {
+        *r = &gdbctx->context.Dr0;
+        return 0;
+    }
+    if (!IS_DR7_SET(gdbctx->context.Dr7, 1)) 
+    {
+        *r = &gdbctx->context.Dr1;
+        return 1;
+    }
+    if (!IS_DR7_SET(gdbctx->context.Dr7, 2)) 
+    {
+        *r = &gdbctx->context.Dr2;
+        return 2;
+    }
+    if (!IS_DR7_SET(gdbctx->context.Dr7, 3)) 
+    {
+        *r = &gdbctx->context.Dr3;
+        return 3;
+    }
+    return -1;
+}
+#endif
+
+/******************************************************************
+ *		cpu_insert_Xpoint
+ *
+ * returns  1 if ok
+ *          0 if error
+ *         -1 if operation isn't supported by CPU
+ */
+static inline int      cpu_insert_Xpoint(struct gdb_context* gdbctx, 
+                                         struct gdb_ctx_Xpoint* xpt, size_t len)
+{
+#ifdef __i386__
+    unsigned char       ch;
+    unsigned long       sz;
+    unsigned long*      pr;
+    int                 reg;
+    unsigned long       bits;
+
+    switch (xpt->type)
+    {
+    case '0':
+        if (len != 1) return 0;
+        if (!ReadProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0;
+        xpt->val = ch;
+        ch = 0xcc;
+        if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0;
+        break;
+    case '1':
+        bits = DR7_RW_EXECUTE;
+        goto hw_bp;
+    case '2':
+        bits = DR7_RW_READ;
+        goto hw_bp;
+    case '3':
+        bits = DR7_RW_WRITE;
+    hw_bp:
+        if ((reg = i386_get_unused_DR(gdbctx, &pr)) == -1) return 0;
+        *pr = (unsigned long)xpt->addr;
+        if (xpt->type != '1') switch (len)
+        {
+        case 4: bits |= DR7_LEN_4; break;
+        case 2: bits |= DR7_LEN_2; break;
+        case 1: bits |= DR7_LEN_1; break;
+        default: return 0;
+        }
+        xpt->val = reg;
+        /* clear old values */
+        gdbctx->context.Dr7 &= ~(0x0F << (DR7_CONTROL_SHIFT + DR7_CONTROL_SIZE * reg));
+        /* set the correct ones */
+        gdbctx->context.Dr7 |= bits << (DR7_CONTROL_SHIFT + DR7_CONTROL_SIZE * reg);
+	gdbctx->context.Dr7 |= DR7_ENABLE_MASK(reg) | DR7_LOCAL_SLOWDOWN;
+        break;
+    default:
+        fprintf(stderr, "Unknown bp type %c\n", xpt->type);
+        return 0;
+    }
+    return 1;
+#else
+#error "Define insert Xpoint for your CPU"
+#endif
+    return -1;
+}
+
+/******************************************************************
+ *		cpu_remove_Xpoint
+ *
+ * returns  1 if ok
+ *          0 if error
+ *         -1 if operation isn't supported by CPU
+ */
+static inline BOOL      cpu_remove_Xpoint(struct gdb_context* gdbctx, 
+                                          struct gdb_ctx_Xpoint* xpt, size_t len)
+{
+#ifdef __i386__
+    unsigned long       sz;
+    unsigned char       ch;
+
+    switch (xpt->type)
+    {
+    case '0':
+        if (len != 1) return 0;
+        ch = (unsigned char)xpt->val;
+        if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0;
+        break;
+    case '1':
+    case '2':
+    case '3':
+        /* simply disable the entry */
+        gdbctx->context.Dr7 &= ~DR7_ENABLE_MASK(xpt->val);
+        break;
+    default:
+        fprintf(stderr, "Unknown bp type %c\n", xpt->type);
+        return 0;
+    }
+    return 1;
+#else
+#error "Define remove Xpoint for your CPU"
+#endif
+    return -1;
+}
+/* =============================================== *
+ *    W I N 3 2   D E B U G   I N T E R F A C E    *
+ * =============================================== *
+ */
+
+static BOOL handle_exception(struct gdb_context* gdbctx, EXCEPTION_DEBUG_INFO* exc)
+{
+    EXCEPTION_RECORD*   rec = &exc->ExceptionRecord;
+    BOOL                ret = FALSE;
+
+    switch (rec->ExceptionCode) 
+    {
+    case EXCEPTION_ACCESS_VIOLATION:
+    case EXCEPTION_PRIV_INSTRUCTION:
+    case EXCEPTION_STACK_OVERFLOW:
+    case EXCEPTION_GUARD_PAGE:
+        gdbctx->last_sig = SIGSEGV;
+        ret = TRUE;
+        break;
+    case EXCEPTION_DATATYPE_MISALIGNMENT:
+        gdbctx->last_sig = SIGBUS;
+        ret = TRUE;
+        break;
+    case EXCEPTION_SINGLE_STEP:
+        /* fall thru */
+    case EXCEPTION_BREAKPOINT:
+        gdbctx->last_sig = SIGTRAP;
+        ret = TRUE;
+        break;
+    case EXCEPTION_FLT_DENORMAL_OPERAND:
+    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+    case EXCEPTION_FLT_INEXACT_RESULT:
+    case EXCEPTION_FLT_INVALID_OPERATION:
+    case EXCEPTION_FLT_OVERFLOW:
+    case EXCEPTION_FLT_STACK_CHECK:
+    case EXCEPTION_FLT_UNDERFLOW:
+        gdbctx->last_sig = SIGFPE;
+        ret = TRUE;
+        break;
+    case EXCEPTION_INT_DIVIDE_BY_ZERO:
+    case EXCEPTION_INT_OVERFLOW:
+        gdbctx->last_sig = SIGFPE;
+        ret = TRUE;
+        break;
+    case EXCEPTION_ILLEGAL_INSTRUCTION:
+        gdbctx->last_sig = SIGILL;
+        ret = TRUE;
+        break;
+    case CONTROL_C_EXIT:
+        gdbctx->last_sig = SIGINT;
+        ret = TRUE;
+        break;
+    case EXCEPTION_CRITICAL_SECTION_WAIT:
+        gdbctx->last_sig = SIGALRM;
+        ret = TRUE;
+        /* FIXME: we could also add here a O packet with additional information */
+        break;
+    default:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "unhandled exception code %08lx\n", rec->ExceptionCode);
+        gdbctx->last_sig = SIGABRT;
+        ret = TRUE;
+        break;
+    }
+    return ret;
+}
+
+static	void	handle_debug_event(struct gdb_context* gdbctx, DEBUG_EVENT* de)
+{
+    char                buffer[256];
+
+    DEBUG_CurrThread = DEBUG_GetThread(gdbctx->process, de->dwThreadId);
+        
+    switch (de->dwDebugEventCode) 
+    {
+    case CREATE_PROCESS_DEBUG_EVENT:
+        DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), 
+                                       de->u.CreateProcessInfo.hProcess, 
+                                       de->u.CreateProcessInfo.lpImageName);
+
+        /* FIXME unicode ? de->u.CreateProcessInfo.fUnicode */
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: create process '%s'/%p @%08lx (%ld<%ld>)\n", 
+                    de->dwProcessId, de->dwThreadId, 
+                    buffer, de->u.CreateProcessInfo.lpImageName,
+                    (unsigned long)(LPVOID)de->u.CreateProcessInfo.lpStartAddress,
+                    de->u.CreateProcessInfo.dwDebugInfoFileOffset,
+                    de->u.CreateProcessInfo.nDebugInfoSize);
+	    
+        gdbctx->process = DEBUG_AddProcess(de->dwProcessId, 
+                                           de->u.CreateProcessInfo.hProcess,
+                                           buffer);
+        /* de->u.CreateProcessInfo.lpStartAddress; */
+
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: create thread I @%08lx\n", 
+                    de->dwProcessId, de->dwThreadId, 
+                    (unsigned long)(LPVOID)de->u.CreateProcessInfo.lpStartAddress);
+	    
+        assert(DEBUG_CurrThread == NULL); /* shouldn't be there */
+        DEBUG_AddThread(gdbctx->process, de->dwThreadId, 
+                        de->u.CreateProcessInfo.hThread, 
+                        de->u.CreateProcessInfo.lpStartAddress,
+                        de->u.CreateProcessInfo.lpThreadLocalBase);
+#if 0
+        DEBUG_LoadModule32(DEBUG_CurrProcess->imageName, de->u.CreateProcessInfo.hFile, 
+                           (DWORD)de->u.CreateProcessInfo.lpBaseOfImage);
+        
+        if (buffer[0])  /* we got a process name */
+        {
+            DWORD type;
+            if (!GetBinaryTypeA( buffer, &type ))
+            {
+                /* not a Windows binary, assume it's a Unix executable then */
+                char unixname[MAX_PATH];
+                /* HACK!! should fix DEBUG_ReadExecutableDbgInfo to accept DOS filenames */
+                if (wine_get_unix_file_name( buffer, unixname, sizeof(unixname) ))
+                {
+                    DEBUG_ReadExecutableDbgInfo( unixname );
+                    break;
+                }
+            }
+        }
+        /* if it is a Windows binary, or an invalid or missing file name,
+         * we use wine itself as the main executable */
+        DEBUG_ReadExecutableDbgInfo( "wine" );
+#endif
+        break;
+	    
+    case LOAD_DLL_DEBUG_EVENT:
+        assert(DEBUG_CurrThread);
+        DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), 
+                                       gdbctx->process->handle, 
+                                       de->u.LoadDll.lpImageName);
+	    
+        /* FIXME unicode: de->u.LoadDll.fUnicode */
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: loads DLL %s @%08lx (%ld<%ld>)\n", 
+                    de->dwProcessId, de->dwThreadId, 
+                    buffer, (unsigned long)de->u.LoadDll.lpBaseOfDll,
+                    de->u.LoadDll.dwDebugInfoFileOffset,
+                    de->u.LoadDll.nDebugInfoSize);
+#if 0
+        _strupr(buffer);
+        DEBUG_LoadModule32(buffer, de->u.LoadDll.hFile, (DWORD)de->u.LoadDll.lpBaseOfDll);
+        DEBUG_CheckDelayedBP();
+        if (DBG_IVAR(BreakOnDllLoad)) 
+        {
+            DEBUG_Printf(DBG_CHN_MESG, "Stopping on DLL %s loading at %08lx\n", 
+                         buffer, (unsigned long)de->u.LoadDll.lpBaseOfDll);
+            DEBUG_Parser();
+        }
+#endif
+        break;
+	    
+    case UNLOAD_DLL_DEBUG_EVENT:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: unload DLL @%08lx\n", 
+                    de->dwProcessId, de->dwThreadId, (unsigned long)de->u.UnloadDll.lpBaseOfDll);
+        break;
+	    
+    case EXCEPTION_DEBUG_EVENT:
+        assert(DEBUG_CurrThread);
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: exception code=%08lx\n", 
+                    de->dwProcessId, de->dwThreadId, 
+                    de->u.Exception.ExceptionRecord.ExceptionCode);
+        
+        gdbctx->context.ContextFlags =  CONTEXT_CONTROL
+                                      | CONTEXT_INTEGER
+#ifdef CONTEXT_SEGMENTS
+	                              | CONTEXT_SEGMENTS
+#endif
+#ifdef CONTEXT_DEBUG_REGISTERS
+		                      | CONTEXT_DEBUG_REGISTERS
+#endif
+				      ;
+        if (!GetThreadContext(DEBUG_CurrThread->handle, &gdbctx->context)) 
+        {
+            if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR)
+                fprintf(stderr, "Can't get thread's context\n");
+            break;
+        }
+        gdbctx->in_trap = handle_exception(gdbctx, &de->u.Exception);
+        break;
+	    
+    case CREATE_THREAD_DEBUG_EVENT:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: create thread D @%08lx\n", 
+                    de->dwProcessId, de->dwThreadId, (unsigned long)(LPVOID)de->u.CreateThread.lpStartAddress);
+
+        DEBUG_AddThread(gdbctx->process,
+                        de->dwThreadId,
+                        de->u.CreateThread.hThread,
+                        de->u.CreateThread.lpStartAddress,
+                        de->u.CreateThread.lpThreadLocalBase);
+        break;
+	    
+    case EXIT_THREAD_DEBUG_EVENT:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: exit thread (%ld)\n", 
+                    de->dwProcessId, de->dwThreadId, de->u.ExitThread.dwExitCode);
+	    
+        assert(DEBUG_CurrThread);
+        DEBUG_DelThread(DEBUG_CurrThread);
+        break;
+	    
+    case EXIT_PROCESS_DEBUG_EVENT:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: exit process (%ld)\n", 
+                    de->dwProcessId, de->dwThreadId, de->u.ExitProcess.dwExitCode);
+
+        DEBUG_DelProcess(gdbctx->process);
+        gdbctx->process = NULL;
+        /* now signal gdb that we're done */
+        gdbctx->last_sig = SIGTERM;
+        gdbctx->in_trap = TRUE;
+        break;
+	    
+    case OUTPUT_DEBUG_STRING_EVENT:
+        assert(DEBUG_CurrThread);
+        DEBUG_ProcessGetString(buffer, sizeof(buffer), 
+                               gdbctx->process->handle, 
+                               de->u.DebugString.lpDebugStringData);
+        /* FIXME unicode de->u.DebugString.fUnicode ? */
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: output debug string (%s)\n", 
+                    de->dwProcessId, de->dwThreadId, buffer);
+        break;
+	    
+    case RIP_EVENT:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: rip error=%ld type=%ld\n", 
+                    de->dwProcessId, de->dwThreadId, de->u.RipInfo.dwError, 
+                    de->u.RipInfo.dwType);
+        break;
+	    
+    default:
+        if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT)
+            fprintf(stderr, "%08lx:%08lx: unknown event (%ld)\n", 
+                    de->dwProcessId, de->dwThreadId, de->dwDebugEventCode);
+    }
+}
+
+static void    resume_debuggee(struct gdb_context* gdbctx, unsigned long cont)
+{
+    if (DEBUG_CurrThread)
+    {
+        if (!SetThreadContext(DEBUG_CurrThread->handle, &gdbctx->context))
+            if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR)
+                fprintf(stderr, "cannot set ctx on %lu\n", DEBUG_CurrThread->tid);
+        if (!ContinueDebugEvent(gdbctx->process->pid, DEBUG_CurrThread->tid, cont))
+            if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR)
+                fprintf(stderr, "cannot continue on %lu (%lu)\n",
+                        DEBUG_CurrThread->tid, cont);
+    }
+    else if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR)
+        fprintf(stderr, "cannot find last thread (%lu)\n", DEBUG_CurrThread->tid);
+}
+
+static void    wait_for_debuggee(struct gdb_context* gdbctx)
+{
+    DEBUG_EVENT         de;
+
+    gdbctx->in_trap = FALSE;
+    while (WaitForDebugEvent(&de, INFINITE))
+    {
+        handle_debug_event(gdbctx, &de);
+        assert(!gdbctx->process ||
+               gdbctx->process->pid == 0 || 
+               de.dwProcessId == gdbctx->process->pid);
+        assert(!DEBUG_CurrThread || de.dwThreadId == DEBUG_CurrThread->tid);
+        if (gdbctx->in_trap) break;
+        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
+    }
+}
+
+static void detach_debuggee(struct gdb_context* gdbctx, BOOL kill)
+{
+    cpu_leave_stepping(gdbctx);
+    resume_debuggee(gdbctx, DBG_CONTINUE);
+    if (!kill)
+        DebugActiveProcessStop(gdbctx->process->pid);
+    DEBUG_DelProcess(gdbctx->process);
+    gdbctx->process = NULL;
+}
+
+static void get_process_info(struct gdb_context* gdbctx, char* buffer, size_t len)
+{
+    unsigned long       status;
+
+    if (!GetExitCodeProcess(gdbctx->process->handle, &status))
+    {
+        strcpy(buffer, "Unknown process");
+        return;
+    }
+    if (status == STILL_ACTIVE)
+    {
+        strcpy(buffer, "Running");
+    }
+    else
+        sprintf(buffer, "Terminated (%lu)", status);
+
+    switch (GetPriorityClass(gdbctx->process->handle))
+    {
+    case 0: break;
+#ifdef ABOVE_NORMAL_PRIORITY_CLASS
+    case ABOVE_NORMAL_PRIORITY_CLASS:   strcat(buffer, ", above normal priority");      break;
+#endif
+#ifdef BELOW_NORMAL_PRIORITY_CLASS
+    case BELOW_NORMAL_PRIORITY_CLASS:   strcat(buffer, ", below normal priotity");      break;
+#endif
+    case HIGH_PRIORITY_CLASS:           strcat(buffer, ", high priority");              break;
+    case IDLE_PRIORITY_CLASS:           strcat(buffer, ", idle priority");              break;
+    case NORMAL_PRIORITY_CLASS:         strcat(buffer, ", normal priority");            break;
+    case REALTIME_PRIORITY_CLASS:       strcat(buffer, ", realtime priority");          break;
+    }
+    strcat(buffer, "\n");
+}
+
+static void get_thread_info(struct gdb_context* gdbctx, unsigned tid, 
+                            char* buffer, size_t len)
+{
+    DBG_THREAD*         thd;
+    unsigned long       status;
+    int                 prio;
+
+    /* FIXME: use the size of buffer */
+    thd = DEBUG_GetThread(gdbctx->process, tid);
+    if (thd == NULL)
+    {
+        strcpy(buffer, "No information");
+        return;
+    }
+    if (GetExitCodeThread(thd->handle, &status))
+    {
+        if (status == STILL_ACTIVE)
+        {
+            /* FIXME: this is a bit brutal... some nicer way shall be found */
+            switch (status = SuspendThread(thd->handle))
+            {
+            case -1: break;
+            case 0:  strcpy(buffer, "Running"); break;
+            default: sprintf(buffer, "Suspended (%lu)", status - 1);
+            }
+            ResumeThread(thd->handle);
+        }
+        else
+            sprintf(buffer, "Terminated (exit code = %lu)", status);
+    }
+    else
+    {
+        strcpy(buffer, "Unknown threadID");
+    }
+    switch (prio = GetThreadPriority(thd->handle))
+    {
+    case THREAD_PRIORITY_ERROR_RETURN:  break;
+    case THREAD_PRIORITY_ABOVE_NORMAL:  strcat(buffer, ", priority +1 above normal"); break;
+    case THREAD_PRIORITY_BELOW_NORMAL:  strcat(buffer, ", priority -1 below normal"); break;
+    case THREAD_PRIORITY_HIGHEST:       strcat(buffer, ", priority +2 above normal"); break;
+    case THREAD_PRIORITY_LOWEST:        strcat(buffer, ", priority -2 below normal"); break;
+    case THREAD_PRIORITY_IDLE:          strcat(buffer, ", priority idle"); break;
+    case THREAD_PRIORITY_NORMAL:        strcat(buffer, ", priority normal"); break;
+    case THREAD_PRIORITY_TIME_CRITICAL: strcat(buffer, ", priority time-critical"); break;
+    default: sprintf(buffer + strlen(buffer), ", priority = %d", prio);
+    }
+    assert(strlen(buffer) < len);
+}
+
+/* =============================================== *
+ *          P A C K E T        U T I L S           *
+ * =============================================== *
+ */
+
+enum packet_return {packet_error = 0x00, packet_ok = 0x01, packet_done = 0x02, 
+                    packet_last_f = 0x80};
+
+static void packet_reply_grow(struct gdb_context* gdbctx, size_t size)
+{
+    if (gdbctx->out_buf_alloc < gdbctx->out_len + size)
+    {
+        gdbctx->out_buf_alloc = ((gdbctx->out_len + size) / 32 + 1) * 32;
+        gdbctx->out_buf = realloc(gdbctx->out_buf, gdbctx->out_buf_alloc);
+    }
+}
+
+static void packet_reply_hex_to(struct gdb_context* gdbctx, const void* src, int len)
+{
+    packet_reply_grow(gdbctx, len * 2);
+    hex_to(&gdbctx->out_buf[gdbctx->out_len], src, len);
+    gdbctx->out_len += len * 2;
+}
+
+static void packet_reply_val(struct gdb_context* gdbctx, unsigned long val, int len)
+{
+    int i, shift;
+    
+    shift = (len - 1) * 8;
+    packet_reply_grow(gdbctx, len * 2);
+    for (i = 0; i < len; i++, shift -= 8)
+    {
+        gdbctx->out_buf[gdbctx->out_len++] = hex_to0((val >> (shift + 4)) & 0x0F);
+        gdbctx->out_buf[gdbctx->out_len++] = hex_to0((val >>  shift     ) & 0x0F);
+    }
+}
+
+static inline void packet_reply_add(struct gdb_context* gdbctx, const char* str, int len)
+{
+    packet_reply_grow(gdbctx, len);
+    memcpy(&gdbctx->out_buf[gdbctx->out_len], str, len);
+    gdbctx->out_len += len;
+}
+
+static inline void packet_reply_cat(struct gdb_context* gdbctx, const char* str)
+{
+    packet_reply_add(gdbctx, str, strlen(str));
+}
+
+static inline void packet_reply_catc(struct gdb_context* gdbctx, char ch)
+{
+    packet_reply_add(gdbctx, &ch, 1);
+}
+
+static void packet_reply_open(struct gdb_context* gdbctx)
+{
+    assert(gdbctx->out_curr_packet == -1);
+    packet_reply_catc(gdbctx, '$');
+    gdbctx->out_curr_packet = gdbctx->out_len;
+}
+
+static void packet_reply_close(struct gdb_context* gdbctx)
+{
+    unsigned char       cksum;
+    unsigned            plen;
+
+    plen = gdbctx->out_len - gdbctx->out_curr_packet;
+    packet_reply_catc(gdbctx, '#');
+    cksum = checksum(&gdbctx->out_buf[gdbctx->out_curr_packet], plen);
+    packet_reply_hex_to(gdbctx, &cksum, 1);
+    if (gdbctx->trace & GDBPXY_TRC_PACKET)
+        fprintf(stderr, "Reply : %*.*s\n", 
+                plen, plen, &gdbctx->out_buf[gdbctx->out_curr_packet]);
+    gdbctx->out_curr_packet = -1;
+}
+
+static enum packet_return packet_reply(struct gdb_context* gdbctx, const char* packet, int len)
+{
+    packet_reply_open(gdbctx);
+
+    if (len == -1) len = strlen(packet);
+    assert(memchr(packet, '$', len) == NULL && memchr(packet, '#', len) == NULL);
+    
+    packet_reply_add(gdbctx, packet, len);
+
+    packet_reply_close(gdbctx);
+
+    return packet_done;
+}
+
+static enum packet_return packet_reply_error(struct gdb_context* gdbctx, int error)
+{
+    packet_reply_open(gdbctx);
+
+    packet_reply_add(gdbctx, "E", 1);
+    packet_reply_val(gdbctx, error, 1);
+
+    packet_reply_close(gdbctx);
+
+    return packet_done;
+}
+
+/* =============================================== *
+ *          P A C K E T   H A N D L E R S          *
+ * =============================================== *
+ */
+
+static enum packet_return packet_reply_status(struct gdb_context* gdbctx)
+{
+    enum packet_return ret = packet_done;
+
+    packet_reply_open(gdbctx);
+
+    if (gdbctx->process != NULL)
+    {
+        unsigned char           sig;
+        unsigned                i;
+
+        packet_reply_catc(gdbctx, 'T');
+        sig = gdbctx->last_sig;
+        packet_reply_val(gdbctx, sig, 1);
+        packet_reply_add(gdbctx, "thread:", 7);
+        packet_reply_val(gdbctx, DEBUG_CurrThread->tid, 4);
+        packet_reply_catc(gdbctx, ';');
+
+        for (i = 0; i < cpu_num_regs; i++)
+        {
+            /* FIXME: this call will also grow the buffer... 
+             * unneeded, but not harmful
+             */
+            packet_reply_val(gdbctx, i, 1);
+            packet_reply_catc(gdbctx, ':');
+            packet_reply_hex_to(gdbctx, cpu_register(gdbctx, i), 4);
+            packet_reply_catc(gdbctx, ';');
+        }
+    }
+    else
+    {
+        /* Try to put an exit code
+         * Cannot use GetExitCodeProcess, wouldn't fit in a 8 bit value, so
+         * just indicate the end of process and exit */
+        packet_reply_add(gdbctx, "W00", 3);
+        /*if (!gdbctx->extended)*/ ret |= packet_last_f;
+    }
+    
+    packet_reply_close(gdbctx);
+
+    return ret;
+}
+
+#if 0
+static enum packet_return packet_extended(struct gdb_context* gdbctx)
+{
+    gdbctx->extended = 1;
+    return packet_ok;
+}
+#endif
+
+static enum packet_return packet_last_signal(struct gdb_context* gdbctx)
+{
+    assert(gdbctx->in_packet_len == 0);
+    return packet_reply_status(gdbctx);
+}
+
+static enum packet_return packet_continue(struct gdb_context* gdbctx)
+{
+    /* FIXME: add support for address in packet */
+    assert(gdbctx->in_packet_len == 0);
+    if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: cont on %u, while last thd is %lu\n", 
+                    gdbctx->exec_thread, DEBUG_CurrThread->tid);
+    resume_debuggee(gdbctx, DBG_CONTINUE);
+    wait_for_debuggee(gdbctx);
+    return packet_reply_status(gdbctx);
+}
+
+static enum packet_return packet_continue_signal(struct gdb_context* gdbctx)
+{
+    unsigned char sig;
+
+    /* FIXME: add support for address in packet */
+    assert(gdbctx->in_packet_len == 2);
+    if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: cont/sig on %u, while last thd is %lu\n", 
+                    gdbctx->exec_thread, DEBUG_CurrThread->tid);
+    hex_from(&sig, gdbctx->in_packet, 1);
+    /* cannot change signals on the fly */
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "sigs: %u %u\n", sig, gdbctx->last_sig);
+    if (sig != gdbctx->last_sig)
+        return packet_error;
+    resume_debuggee(gdbctx, DBG_EXCEPTION_NOT_HANDLED);
+    wait_for_debuggee(gdbctx);
+    return packet_reply_status(gdbctx);
+}
+
+static enum packet_return packet_detach(struct gdb_context* gdbctx)
+{
+    detach_debuggee(gdbctx, FALSE);
+    return packet_ok | packet_last_f;
+}
+
+static enum packet_return packet_read_registers(struct gdb_context* gdbctx)
+{
+    int                 i;
+
+    assert(gdbctx->in_trap);
+    if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: read regs on %u, while last thd is %lu\n", 
+                    gdbctx->other_thread, DEBUG_CurrThread->tid);
+
+    packet_reply_open(gdbctx);
+
+    for (i = 0; i < cpu_num_regs; i++)
+    {
+        packet_reply_hex_to(gdbctx, cpu_register(gdbctx, i), 4);
+    }
+
+    packet_reply_close(gdbctx);
+    return packet_done;
+}
+
+static enum packet_return packet_write_registers(struct gdb_context* gdbctx)
+{
+    unsigned    i;
+
+    assert(gdbctx->in_trap);
+    if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: write regs on %u, while last thd is %lu\n", 
+                    gdbctx->other_thread, DEBUG_CurrThread->tid);
+    if (gdbctx->in_packet_len < cpu_num_regs * 2) return packet_error;
+    for (i = 0; i < cpu_num_regs; i++)
+        hex_from(cpu_register(gdbctx, i), &gdbctx->in_packet[8 * i], 4);
+    return packet_ok;
+}
+
+static enum packet_return packet_kill(struct gdb_context* gdbctx)
+{
+    detach_debuggee(gdbctx, TRUE);
+#if 0
+    if (!gdbctx->extended)
+        /* dunno whether GDB cares or not */
+#endif
+    wait(NULL);
+    exit(0);
+    /* assume we can't really answer something here */
+    /* return packet_done; */
+}
+
+static enum packet_return packet_thread(struct gdb_context* gdbctx)
+{
+    char* end;
+    unsigned thread;
+
+    switch (gdbctx->in_packet[0])
+    {
+    case 'c':
+    case 'g':
+        if (gdbctx->in_packet[1] == '-')
+            thread = -strtol(gdbctx->in_packet + 2, &end, 16);
+        else
+            thread = strtol(gdbctx->in_packet + 1, &end, 16);
+        if (end == NULL || end > gdbctx->in_packet + gdbctx->in_packet_len)
+        {
+            if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+                fprintf(stderr, "Cannot get threadid %*.*s\n", 
+                        gdbctx->in_packet_len - 1, gdbctx->in_packet_len - 1, 
+                        gdbctx->in_packet + 1);
+            return packet_error;
+        }
+        if (gdbctx->in_packet[0] == 'c')
+            gdbctx->exec_thread = thread;
+        else
+            gdbctx->other_thread = thread;
+        return packet_ok;
+    default:
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "Unknown thread sub-command %c\n", gdbctx->in_packet[0]);
+        return packet_error;
+    }
+}
+
+static enum packet_return packet_read_memory(struct gdb_context* gdbctx)
+{
+    const char*         addr;
+    size_t              len, blk_len, nread;
+    char                buffer[32];
+    unsigned long       r = 0;
+
+    assert(gdbctx->in_trap);
+    /* FIXME:check in_packet_len for reading %p,%x */
+    if (sscanf(gdbctx->in_packet, "%p,%x", &addr, &len) != 2) return packet_error;
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "read mem at %p for %u bytes\n", addr, len);
+    for (nread = 0; nread < len > 0; nread += r, addr += r)
+    {
+        blk_len = min(sizeof(buffer), len - nread);
+        if (!ReadProcessMemory(gdbctx->process->handle, addr, buffer, blk_len, &r) ||
+            r == 0)
+        {
+            /* fail at first address, return error */
+            if (nread == 0) return packet_reply_error(gdbctx, EFAULT);
+            /* something has already been read, return partial information */
+            break;
+        }
+        if (nread == 0) packet_reply_open(gdbctx);
+        packet_reply_hex_to(gdbctx, buffer, r);
+    }
+    packet_reply_close(gdbctx);
+    return packet_done;
+}
+
+static enum packet_return packet_write_memory(struct gdb_context* gdbctx)
+{
+    char*               addr;
+    size_t              len, blk_len;
+    char*               ptr;
+    char                buffer[32];
+    unsigned long       w;
+
+    assert(gdbctx->in_trap);
+    ptr = memchr(gdbctx->in_packet, ':', gdbctx->in_packet_len);
+    if (ptr == NULL) 
+    {
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "cannot find ':' in %*.*s\n", 
+                    gdbctx->in_packet_len, gdbctx->in_packet_len, gdbctx->in_packet);
+        return packet_error;
+    }
+    *ptr++ = '\0';
+
+    if (sscanf(gdbctx->in_packet, "%p,%x", &addr, &len) != 2)
+    {
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "cannot scan addr,len in %s\n", gdbctx->in_packet);
+        return packet_error;
+    }
+    if (ptr - gdbctx->in_packet + len * 2 != gdbctx->in_packet_len) 
+    {
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "wrong sizes %u <> %u\n", 
+                    ptr - gdbctx->in_packet + len * 2, gdbctx->in_packet_len); 
+        return packet_error;
+    }
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "write %u bytes at %p\n", len, addr);
+    while (len > 0)
+    {
+        blk_len = min(sizeof(buffer), len);
+        hex_from(buffer, ptr, blk_len);
+        {
+            BOOL ret;
+            
+            ret = WriteProcessMemory(gdbctx->process->handle, addr, buffer, blk_len, &w);
+            if (!ret || w != blk_len)
+                break;
+        }
+        addr += w;
+        len -= w;
+        ptr += w;
+    }
+    return packet_ok; /* FIXME: error while writing ? */
+}
+
+static enum packet_return packet_write_register(struct gdb_context* gdbctx)
+{
+    unsigned            reg;
+    char*               ptr;
+    char*               end;
+
+    assert(gdbctx->in_trap);
+    if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: read reg on %u, while last thd is %lu\n", 
+                    gdbctx->other_thread, DEBUG_CurrThread->tid);
+
+    ptr = memchr(gdbctx->in_packet, '=', gdbctx->in_packet_len);
+    *ptr++ = '\0';
+    reg = strtoul(gdbctx->in_packet, &end, 16);
+    if (end == NULL || reg > cpu_num_regs)
+    {
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "invalid register index %s\n", gdbctx->in_packet);
+        /* FIXME: if just the reg is above cpu_num_regs, don't tell gdb
+         *        it wouldn't matter too much, and it fakes our support for all regs
+         */
+        return (end == NULL) ? packet_error : packet_ok;
+    }
+    if (ptr + 8 - gdbctx->in_packet != gdbctx->in_packet_len)
+    {
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "wrong sizes %u <> %u\n", 
+                    ptr + 8 - gdbctx->in_packet, gdbctx->in_packet_len); 
+        return packet_error;
+    }
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "Writing reg %u <= %*.*s\n", 
+                reg, gdbctx->in_packet_len - (ptr - gdbctx->in_packet),
+                gdbctx->in_packet_len - (ptr - gdbctx->in_packet), ptr);
+    hex_from(cpu_register(gdbctx, reg), ptr, 4);
+    return packet_ok;
+}
+
+static void packet_query_monitor_wnd_helper(struct gdb_context* gdbctx, HWND hWnd, int indent)
+{
+    char        buffer[128];
+    char	clsName[128];
+    char	wndName[128];
+    HWND	child;
+
+    do {
+       if (!GetClassName(hWnd, clsName, sizeof(clsName)))
+	  strcpy(clsName, "-- Unknown --");
+       if (!GetWindowText(hWnd, wndName, sizeof(wndName)))
+	  strcpy(wndName, "-- Empty --");
+       
+       packet_reply_open(gdbctx);
+       packet_reply_catc(gdbctx, 'O');
+       sprintf(buffer, "%*s%04x%*s%-17.17s %08lx %08lx %.14s\n", 
+               indent, "", (UINT)hWnd, 13 - indent, "", 
+               clsName, GetWindowLong(hWnd, GWL_STYLE),
+               GetWindowLong(hWnd, GWL_WNDPROC), wndName);
+       packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+       packet_reply_close(gdbctx);
+
+       if ((child = GetWindow(hWnd, GW_CHILD)) != 0)
+	  packet_query_monitor_wnd_helper(gdbctx, child, indent + 1);
+    } while ((hWnd = GetWindow(hWnd, GW_HWNDNEXT)) != 0);
+}
+
+static void packet_query_monitor_wnd(struct gdb_context* gdbctx, int len, const char* str)
+{
+    char        buffer[128];
+
+    /* we do the output in several 'O' packets, with the last one being just OK for
+     * marking the end of the output */
+    packet_reply_open(gdbctx);
+    packet_reply_catc(gdbctx, 'O');
+    sprintf(buffer, "%-16.16s %-17.17s %-8.8s %s\n",
+            "hwnd", "Class Name", " Style", " WndProc Text");
+    packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+    packet_reply_close(gdbctx);
+
+    /* FIXME: could also add a pmt to this command in str... */
+    packet_query_monitor_wnd_helper(gdbctx, GetDesktopWindow(), 0);
+    packet_reply(gdbctx, "OK", 2);
+}
+
+static void packet_query_monitor_process(struct gdb_context* gdbctx, int len, const char* str)
+{
+    HANDLE              snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    char                buffer[128];
+    char                deco;
+    PROCESSENTRY32      entry;
+    BOOL                ok;
+
+    if (snap == INVALID_HANDLE_VALUE)
+        return;
+
+    entry.dwSize = sizeof(entry);
+    ok = Process32First( snap, &entry );
+
+    /* we do the output in several 'O' packets, with the last one being just OK for
+     * marking the end of the output */
+
+    packet_reply_open(gdbctx);
+    packet_reply_catc(gdbctx, 'O');
+    sprintf(buffer, " %-8.8s %-8.8s %-8.8s %s\n",
+            "pid", "threads", "parent", "executable" );
+    packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+    packet_reply_close(gdbctx);
+    
+    while (ok)
+    {
+        deco = ' ';
+        if (entry.th32ProcessID == gdbctx->process->pid) deco = '>';
+        packet_reply_open(gdbctx);
+        packet_reply_catc(gdbctx, 'O');
+        sprintf(buffer, "%c%08lx %-8ld %08lx '%s'\n",
+                deco, entry.th32ProcessID, entry.cntThreads,
+                entry.th32ParentProcessID, entry.szExeFile);
+        packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+        packet_reply_close(gdbctx);
+        ok = Process32Next(snap, &entry);
+    }
+    CloseHandle(snap);
+    packet_reply(gdbctx, "OK", 2);
+}
+
+static void packet_query_monitor_mem(struct gdb_context* gdbctx, int len, const char* str)
+{
+    MEMORY_BASIC_INFORMATION    mbi;
+    char*                       addr = 0;
+    char*                       state;
+    char*                       type;
+    char                        prot[3+1];
+    char                        buffer[128];
+
+    /* we do the output in several 'O' packets, with the last one being just OK for
+     * marking the end of the output */
+    packet_reply_open(gdbctx);
+    packet_reply_catc(gdbctx, 'O');
+    sprintf(buffer, "Address  Size     State   Type    RWX\n");
+    packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+    packet_reply_close(gdbctx);
+
+    while (VirtualQueryEx(gdbctx->process->handle, addr, &mbi, sizeof(mbi)) >= sizeof(mbi))
+    {
+        switch (mbi.State)
+        {
+        case MEM_COMMIT:        state = "commit "; break;
+        case MEM_FREE:          state = "free   "; break;
+        case MEM_RESERVE:       state = "reserve"; break;
+        default:                state = "???    "; break;
+        }
+        if (mbi.State != MEM_FREE)
+        {
+            switch (mbi.Type)
+            {
+            case MEM_IMAGE:         type = "image  "; break;
+            case MEM_MAPPED:        type = "mapped "; break;
+            case MEM_PRIVATE:       type = "private"; break;
+            case 0:                 type = "       "; break;
+            default:                type = "???    "; break;
+            }
+            memset(prot, ' ' , sizeof(prot)-1);
+            prot[sizeof(prot)-1] = '\0';
+            if (mbi.AllocationProtect & (PAGE_READONLY|PAGE_READWRITE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE))
+                prot[0] = 'R';
+            if (mbi.AllocationProtect & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE))
+                prot[1] = 'W';
+            if (mbi.AllocationProtect & (PAGE_WRITECOPY|PAGE_EXECUTE_WRITECOPY))
+                prot[1] = 'C';
+            if (mbi.AllocationProtect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE))
+                prot[2] = 'X';
+        }
+        else
+        {
+            type = "";
+            prot[0] = '\0';
+        }
+        packet_reply_open(gdbctx);
+        sprintf(buffer, "%08lx %08lx %s %s %s\n",
+                (DWORD)addr, mbi.RegionSize, state, type, prot);
+        packet_reply_catc(gdbctx, 'O');
+        packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+        packet_reply_close(gdbctx);
+
+        if (addr + mbi.RegionSize < addr) /* wrap around ? */
+            break;
+        addr += mbi.RegionSize;
+    }
+    packet_reply(gdbctx, "OK", 2);
+}
+
+static void packet_query_monitor_trace(struct gdb_context* gdbctx, 
+                                       int len, const char* str)
+{
+    char        buffer[128];
+
+    if (len == 0)
+    {
+        sprintf(buffer, "trace=%x\n", gdbctx->trace);
+    }
+    else if (len >= 2 && str[0] == '=')
+    {
+        unsigned val = atoi(&str[1]);
+        sprintf(buffer, "trace: %x => %x\n", gdbctx->trace, val);
+        gdbctx->trace = val;
+    }
+    else
+    {
+        /* FIXME: ugly but can use error packet here */
+        packet_reply_cat(gdbctx, "E00");
+        return;
+    }
+    packet_reply_open(gdbctx);
+    packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+    packet_reply_close(gdbctx);
+}
+
+#ifdef __i386__
+static void packet_query_monitor_linear(struct gdb_context* gdbctx, 
+                                       int len, const char* str)
+{
+    unsigned    seg, ofs;
+    LDT_ENTRY	le;
+    unsigned    linear;
+    char        buffer[32];
+
+    while (len > 0 && (*str == ' ' || *str == '\t'))
+    {
+        str++; len--;
+    }
+    /* FIXME: do a better scanning (allow both decimal and hex numbers) */
+    if (!len || sscanf(str, "%x:%x", &seg, &ofs) != 2)
+    {
+        packet_reply_error(gdbctx, 0);
+        return;
+    }
+    
+    /* V86 mode ? */
+    if (gdbctx->context.EFlags & 0x00020000) linear = (LOWORD(seg) << 4) + ofs;
+    /* linux system selector ? */
+    else if (!(seg & 4) || ((seg >> 3) < 17)) linear = ofs;
+    /* standard selector */
+    else if (GetThreadSelectorEntry(gdbctx->process->threads->handle, seg, &le))
+        linear = (le.HighWord.Bits.BaseHi << 24) + (le.HighWord.Bits.BaseMid << 16) +
+            le.BaseLow + ofs;
+    /* error */
+    else linear = 0;
+    sprintf(buffer, "0x%x", linear);
+    packet_reply_open(gdbctx);
+    packet_reply_hex_to(gdbctx, buffer, strlen(buffer));
+    packet_reply_close(gdbctx);
+}
+#endif
+
+struct query_detail
+{
+    int         with_arg;
+    const char* name;
+    size_t      len;
+    void        (*handler)(struct gdb_context*, int, const char*);
+} query_details[] = 
+{
+    {0, "wnd",     3, packet_query_monitor_wnd},
+    {0, "window",  6, packet_query_monitor_wnd},
+    {0, "proc",    4, packet_query_monitor_process},
+    {0, "process", 7, packet_query_monitor_process},
+    {0, "mem",     3, packet_query_monitor_mem},
+    {1, "trace",   5, packet_query_monitor_trace},
+#ifdef __i386__
+    {1, "linear",  6, packet_query_monitor_linear},
+#endif
+    {0, NULL,      0, NULL},
+};
+
+static enum packet_return packet_query_remote_command(struct gdb_context* gdbctx, 
+                                                      const char* hxcmd, size_t len)
+{
+    char                        buffer[128];
+    struct query_detail*        qd;
+
+    assert((len & 1) == 0 && len < 2 * sizeof(buffer));
+    len /= 2;
+    hex_from(buffer, hxcmd, len);
+
+    for (qd = &query_details[0]; qd->name != NULL; qd++)
+    {
+        if (len < qd->len || strncmp(buffer, qd->name, qd->len) != 0) continue;
+        if (!qd->with_arg && len != qd->len) continue;
+
+        (qd->handler)(gdbctx, len - qd->len, buffer + qd->len);
+        return packet_done;
+    } 
+    return packet_reply_error(gdbctx, EINVAL);
+}
+
+static enum packet_return packet_query(struct gdb_context* gdbctx)
+{
+    switch (gdbctx->in_packet[0])
+    {
+    case 'f':
+        if (strncmp(gdbctx->in_packet + 1, "ThreadInfo", gdbctx->in_packet_len - 1) == 0)
+        {
+            DBG_THREAD* thd;
+
+            packet_reply_open(gdbctx);
+            packet_reply_add(gdbctx, "m", 1);
+            for (thd = gdbctx->process->threads; thd; thd = thd->next)
+            {
+                packet_reply_val(gdbctx, thd->tid, 4);
+                if (thd->next != NULL)
+                    packet_reply_add(gdbctx, ",", 1);
+            }
+            packet_reply_close(gdbctx);
+            return packet_done;
+        }
+        else if (strncmp(gdbctx->in_packet + 1, "ProcessInfo", gdbctx->in_packet_len - 1) == 0)
+        {
+            char        result[128];
+
+            packet_reply_open(gdbctx);
+            packet_reply_catc(gdbctx, 'O');
+            get_process_info(gdbctx, result, sizeof(result));
+            packet_reply_hex_to(gdbctx, result, strlen(result));
+            packet_reply_close(gdbctx);
+            return packet_done;
+        }
+        break;
+    case 's':
+        if (strncmp(gdbctx->in_packet + 1, "ThreadInfo", gdbctx->in_packet_len - 1) == 0)
+        {
+            packet_reply(gdbctx, "l", 1);
+            return packet_done;
+        }
+        else if (strncmp(gdbctx->in_packet + 1, "ProcessInfo", gdbctx->in_packet_len - 1) == 0)
+        {
+            packet_reply(gdbctx, "l", 1);
+            return packet_done;
+        }
+        break;
+    case 'C':
+        if (gdbctx->in_packet_len == 1)
+        {
+            DBG_THREAD* thd;
+            /* FIXME: doc says 16 bit val ??? */
+            /* grab first created thread, aka last in list */
+            assert(gdbctx->process && gdbctx->process->threads);
+            for (thd = gdbctx->process->threads; thd->next; thd = thd->next);
+            packet_reply_open(gdbctx);
+            packet_reply_add(gdbctx, "QC", 2);
+            packet_reply_val(gdbctx, thd->tid, 4);
+            packet_reply_close(gdbctx);
+            return packet_done;
+        }
+        break;
+    case 'O':
+        if (strncmp(gdbctx->in_packet, "Offsets", gdbctx->in_packet_len) == 0)
+        {
+            char    buf[64];
+
+            if (gdbctx->wine_segs[0] == 0 && gdbctx->wine_segs[1] == 0 && 
+                gdbctx->wine_segs[2] == 0)
+                return packet_error;
+            sprintf(buf, "Text=%08lx;Data=%08lx;Bss=%08lx", 
+                    gdbctx->wine_segs[0], gdbctx->wine_segs[1], 
+                    gdbctx->wine_segs[2]);
+            return packet_reply(gdbctx, buf, -1);
+        }
+        break;
+    case 'R':
+        if (gdbctx->in_packet_len > 5 && strncmp(gdbctx->in_packet, "Rcmd,", 5) == 0)
+        {
+            return packet_query_remote_command(gdbctx, gdbctx->in_packet + 5, 
+                                               gdbctx->in_packet_len - 5);
+        }
+        break;
+    case 'S':
+        if (strncmp(gdbctx->in_packet, "Symbol::", gdbctx->in_packet_len) == 0)
+            return packet_ok;
+        break;
+    case 'T':
+        if (gdbctx->in_packet_len > 15 &&
+            strncmp(gdbctx->in_packet, "ThreadExtraInfo", 15) == 0 &&
+            gdbctx->in_packet[15] == ',')
+        {
+            unsigned    tid;
+            char*       end;
+            char        result[128];
+
+            tid = strtol(gdbctx->in_packet + 16, &end, 16);
+            if (end == NULL) break;
+            get_thread_info(gdbctx, tid, result, sizeof(result));
+            packet_reply_open(gdbctx);
+            packet_reply_hex_to(gdbctx, result, strlen(result));
+            packet_reply_close(gdbctx);
+            return packet_done;
+        }
+        break;
+    }
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+        fprintf(stderr, "Unknown or malformed query %*.*s\n", 
+                gdbctx->in_packet_len, gdbctx->in_packet_len, gdbctx->in_packet);
+    return packet_error;
+}
+
+static enum packet_return packet_step(struct gdb_context* gdbctx)
+{
+    /* FIXME: add support for address in packet */
+    assert(gdbctx->in_packet_len == 0);
+    if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: step on %u, while last thd is %lu\n", 
+                    gdbctx->exec_thread, DEBUG_CurrThread->tid);
+    if (!cpu_enter_stepping(gdbctx)) return packet_error;
+    resume_debuggee(gdbctx, DBG_CONTINUE);
+    wait_for_debuggee(gdbctx);
+    if (!cpu_leave_stepping(gdbctx)) return packet_error;
+    return packet_reply_status(gdbctx);
+}
+
+#if 0
+static enum packet_return packet_step_signal(struct gdb_context* gdbctx)
+{
+    unsigned char sig;
+
+    /* FIXME: add support for address in packet */
+    assert(gdbctx->in_packet_len == 2);
+    if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread)
+        if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR)
+            fprintf(stderr, "NIY: step/sig on %u, while last thd is %u\n", 
+                    gdbctx->exec_thread, DEBUG_CurrThread->tid);
+    hex_from(&sig, gdbctx->in_packet, 1);
+    /* cannot change signals on the fly */
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "sigs: %u %u\n", sig, gdbctx->last_sig);
+    if (sig != gdbctx->last_sig)
+        return packet_error;
+    resume_debuggee(gdbctx, DBG_EXCEPTION_NOT_HANDLED);
+    wait_for_debuggee(gdbctx);
+    return packet_reply_status(gdbctx);
+}
+#endif
+
+static enum packet_return packet_thread_alive(struct gdb_context* gdbctx)
+{
+    char*       end;
+    unsigned    tid;
+
+    tid = strtol(gdbctx->in_packet, &end, 16);
+    if (tid == -1 || tid == 0)
+        return packet_reply_error(gdbctx, EINVAL);
+    if (DEBUG_GetThread(gdbctx->process, tid) != NULL)
+        return packet_ok;
+    return packet_reply_error(gdbctx, ESRCH);
+}
+
+static enum packet_return packet_remove_breakpoint(struct gdb_context* gdbctx)
+{
+    void*                       addr;
+    unsigned                    len;
+    struct gdb_ctx_Xpoint*      xpt;
+
+    /* FIXME: check packet_len */
+    if (gdbctx->in_packet[0] < '0' || gdbctx->in_packet[0] > '4' ||
+        gdbctx->in_packet[1] != ',' ||
+        sscanf(gdbctx->in_packet + 2, "%p,%x", &addr, &len) != 2)
+        return packet_error;
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "remove bp %p[%u] typ=%c\n",
+                addr, len, gdbctx->in_packet[0]);
+    for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--)
+    {
+        if (xpt->addr == addr && xpt->type == gdbctx->in_packet[0]) 
+        {
+            switch (cpu_remove_Xpoint(gdbctx, xpt, len))
+            {
+            case  1:    xpt->type = -1; return packet_ok;
+            case  0:                    return packet_error;
+            case -1:                    return packet_done;
+            default:                    assert(0);
+            }
+        }
+    }
+    return packet_error;
+}
+
+static enum packet_return packet_set_breakpoint(struct gdb_context* gdbctx)
+{
+    void*                       addr;
+    unsigned                    len;
+    struct gdb_ctx_Xpoint*      xpt;
+
+    /* FIXME: check packet_len */
+    if (gdbctx->in_packet[0] < '0' || gdbctx->in_packet[0] > '4' ||
+        gdbctx->in_packet[1] != ',' ||
+        sscanf(gdbctx->in_packet + 2, "%p,%x", &addr, &len) != 2)
+        return packet_error;
+    if (gdbctx->trace & GDBPXY_TRC_COMMAND)
+        fprintf(stderr, "set bp %p[%u] typ=%c\n",
+                addr, len, gdbctx->in_packet[0]);
+    /* because of packet command handling, this should be made idempotent */
+    for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--)
+    {
+        if (xpt->addr == addr && xpt->type == gdbctx->in_packet[0]) 
+            return packet_ok; /* nothing to do */
+    }
+    /* really set the Xpoint */
+    for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--)
+    {
+        if (xpt->type == -1)
+        {
+            xpt->addr = addr;
+            xpt->type = gdbctx->in_packet[0];
+            switch (cpu_insert_Xpoint(gdbctx, xpt, len))
+            {
+            case  1:    return packet_ok;
+            case  0:    return packet_error;
+            case -1:    return packet_done;
+            default: assert(0);
+            }
+        }
+    }
+    /* no more entries... eech */
+    fprintf(stderr, "Running out of spot for {break|watcgh}points\n");
+    return packet_error;
+}
+
+/* =============================================== *
+ *    P A C K E T  I N F R A S T R U C T U R E     *
+ * =============================================== *
+ */
+
+struct packet_entry 
+{
+    char                key;
+    enum packet_return  (*handler)(struct gdb_context* gdbctx);
+};
+
+static struct packet_entry packet_entries[] =
+{
+/*        {'!', packet_extended}, */
+        {'?', packet_last_signal},
+        {'c', packet_continue},
+        {'C', packet_continue_signal},
+        {'D', packet_detach},
+        {'g', packet_read_registers},
+        {'G', packet_write_registers},
+        {'k', packet_kill},
+        {'H', packet_thread},
+        {'m', packet_read_memory},
+        {'M', packet_write_memory},
+        /* {'p', packet_read_register}, doesn't seem needed */
+        {'P', packet_write_register},
+        {'q', packet_query},
+        {'s', packet_step},
+        /*{'S', packet_step_signal}, hard(er) to implement */
+        {'T', packet_thread_alive},
+        {'z', packet_remove_breakpoint},
+        {'Z', packet_set_breakpoint},
+};
+
+static BOOL extract_packets(struct gdb_context* gdbctx)
+{
+    char*               end;
+    int                 plen;
+    unsigned char       in_cksum, loc_cksum;
+    char*               ptr;
+    enum packet_return  ret = packet_error;
+
+    while ((ret & packet_last_f) == 0)
+    {
+        if (gdbctx->in_len && (gdbctx->trace & GDBPXY_TRC_LOWLEVEL))
+            fprintf(stderr, "in-buf: %*.*s\n", 
+                    gdbctx->in_len, gdbctx->in_len, gdbctx->in_buf);
+        ptr = memchr(gdbctx->in_buf, '$', gdbctx->in_len);
+        if (ptr == NULL) return FALSE;
+        if (ptr != gdbctx->in_buf)
+        {
+            int glen = ptr - gdbctx->in_buf; /* garbage len */
+            if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+                fprintf(stderr, "removing garbage: %*.*s\n", 
+                        glen, glen, gdbctx->in_buf);
+            gdbctx->in_len -= glen;
+            memmove(gdbctx->in_buf, ptr, gdbctx->in_len);
+        }
+        end = memchr(gdbctx->in_buf + 1, '#', gdbctx->in_len);
+        if (end == NULL) return FALSE;
+        /* no checksum yet */
+        if (end + 3 > gdbctx->in_buf + gdbctx->in_len) return FALSE;
+        plen = end - gdbctx->in_buf - 1;
+        hex_from(&in_cksum, end + 1, 1);
+        loc_cksum = checksum(gdbctx->in_buf + 1, plen);
+        if (loc_cksum == in_cksum)
+        {
+            int                 i;
+
+            ret = packet_error;
+
+            write(gdbctx->sock, "+", 1);
+            assert(plen);
+            
+            /* FIXME: should use bsearch if packet_entries was sorted */
+            for (i = 0; i < sizeof(packet_entries)/sizeof(packet_entries[0]); i++)
+            {
+                if (packet_entries[i].key == gdbctx->in_buf[1]) break;
+            }
+            if (i == sizeof(packet_entries)/sizeof(packet_entries[0]))
+            {
+                if (gdbctx->trace & GDBPXY_TRC_PACKET)
+                    fprintf(stderr, "Unknown packet request %*.*s\n", 
+                            plen, plen, &gdbctx->in_buf[1]);
+            }
+            else
+            {
+                gdbctx->in_packet = gdbctx->in_buf + 2;
+                gdbctx->in_packet_len = plen - 1;
+                if (gdbctx->trace & GDBPXY_TRC_PACKET)
+                    fprintf(stderr, "Packet: %c%*.*s\n", 
+                            gdbctx->in_buf[1],
+                            gdbctx->in_packet_len, gdbctx->in_packet_len, 
+                            gdbctx->in_packet);
+                ret = (packet_entries[i].handler)(gdbctx);
+            }
+            switch (ret & ~packet_last_f)
+            {
+            case packet_error:  packet_reply(gdbctx, "", 0); break;
+            case packet_ok:     packet_reply(gdbctx, "OK", 2); break;
+            case packet_done:   break;
+            }
+            if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+                fprintf(stderr, "reply-full: %*.*s\n", 
+                        gdbctx->out_len, gdbctx->out_len, gdbctx->out_buf);
+            i = write(gdbctx->sock, gdbctx->out_buf, gdbctx->out_len);
+            assert(i == gdbctx->out_len);
+            /* if this fails, we'll have to use POLLOUT...
+             */
+            gdbctx->out_len = 0;
+        } 
+        else if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+        {
+            write(gdbctx->sock, "+", 1);
+            fprintf(stderr, "dropping packet, invalid checksum %d <> %d\n", in_cksum, loc_cksum);
+        }
+        gdbctx->in_len -= plen + 4;
+        memmove(gdbctx->in_buf, end + 3, gdbctx->in_len);
+    }
+    return TRUE;
+}
+
+static int fetch_data(struct gdb_context* gdbctx)
+{
+    int len, in_len = gdbctx->in_len;
+
+    assert(gdbctx->in_len <= gdbctx->in_buf_alloc);
+    for (;;)
+    {
+#define STEP 128
+        if (gdbctx->in_len + STEP > gdbctx->in_buf_alloc)
+            gdbctx->in_buf = realloc(gdbctx->in_buf, gdbctx->in_buf_alloc += STEP);
+#undef STEP
+        if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+            fprintf(stderr, "%d %d %*.*s\n", 
+                    gdbctx->in_len, gdbctx->in_buf_alloc, 
+                    gdbctx->in_len, gdbctx->in_len, gdbctx->in_buf);
+        len = read(gdbctx->sock, gdbctx->in_buf + gdbctx->in_len, gdbctx->in_buf_alloc - gdbctx->in_len);
+        if (len <= 0) break;
+        gdbctx->in_len += len;
+        assert(gdbctx->in_len <= gdbctx->in_buf_alloc);
+        if (len < gdbctx->in_buf_alloc - gdbctx->in_len) break;
+    }
+    if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+        fprintf(stderr, "=> %d\n", gdbctx->in_len - in_len);
+    return gdbctx->in_len - in_len;
+}
+
+static BOOL gdb_startup(struct gdb_context* gdbctx, DEBUG_EVENT* de, unsigned flags)
+{
+    int                 sock;
+    struct sockaddr_in  s_addr;
+    socklen_t           s_len = sizeof(s_addr);
+    struct pollfd       pollfd;
+    char                wine_path[MAX_PATH];
+    char*               ptr;
+
+    /* step 1: create socket for gdb connection request */
+    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
+    {
+        if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+            fprintf(stderr, "Can't create socket");
+        return FALSE;
+    }
+
+    if (listen(sock, 1) == -1 ||
+        getsockname(sock, (struct sockaddr*)&s_addr, &s_len) == -1)
+        return FALSE;
+    
+    /* step 2: find out wine executable location (as a Unix filename) */
+    ptr = getenv("WINELOADER");
+    strcpy(wine_path, ptr ? ptr : "wine");
+
+    fprintf(stderr, "using wine_path: %s\n", wine_path);
+    read_elf_info(wine_path, gdbctx->wine_segs);
+    
+    /* step 3: fire up gdb (if requested) */
+    if (flags & 1) 
+        fprintf(stderr, "target remote localhost:%d\n", ntohs(s_addr.sin_port));
+    else
+        switch (fork())
+        {
+        case -1: /* error in parent... */
+            fprintf(stderr, "Cannot create gdb\n");
+            return FALSE;
+            break;
+        default: /* in parent... success */
+            break;
+        case 0: /* in child... and alive */
+            {
+                char    buf[MAX_PATH];
+                char*   gdb_path;
+                FILE*   f;
+                
+                if (!(gdb_path = getenv("WINE_GDB"))) gdb_path = "gdb";
+                if (!tmpnam(buf) || (f = fopen(buf, "w+")) == NULL) return FALSE;
+                fprintf(f, "file %s\n", wine_path);
+                fprintf(f, "target remote localhost:%d\n", ntohs(s_addr.sin_port));
+                fprintf(f, "monitor trace=0\n");
+                fprintf(f, "set prompt Wine-gdb>\\ \n");
+                /* tell gdb to delete this file when done handling it... */
+                fprintf(f, "shell rm -f \"%s\"\n", buf);
+                fclose(f);
+                if (flags & 2)
+                    execlp("xterm", "xterm", "-e", gdb_path, "-x", buf, NULL);
+                else
+                    execlp(gdb_path, gdb_path, "-x", buf, NULL);
+                assert(0); /* never reached */
+                break; 
+            }
+            break;
+        }
+
+    /* step 4: do the process internal creation */
+    handle_debug_event(gdbctx, de);
+
+    /* step 5: wait for gdb to connect actually */
+    pollfd.fd = sock;
+    pollfd.events = POLLIN;
+    pollfd.revents = 0;
+
+    switch (poll(&pollfd, 1, -1))
+    {
+    case 1:
+        if (pollfd.revents & POLLIN)
+        {
+            int dummy = 1;
+            gdbctx->sock = accept(sock, (struct sockaddr*)&s_addr, &s_len);
+            if (gdbctx->sock == -1)
+                break;
+            if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+                fprintf(stderr, "Connected on %d\n", gdbctx->sock);
+            /* don't keep our small packets too long: send them ASAP back to GDB
+             * without this, GDB really crawls
+             */
+            setsockopt(gdbctx->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&dummy, sizeof(dummy));
+        }
+        break;
+    case 0:
+        if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+            fprintf(stderr, "poll for cnx failed (timeout)\n");
+        return FALSE;
+    case -1: 
+        if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)
+            fprintf(stderr, "poll for cnx failed (error)\n");
+        return FALSE;
+    default: 
+        assert(0);
+    }
+
+    close(sock);
+    return TRUE;
+}
+
+static BOOL gdb_init_context(struct gdb_context* gdbctx, unsigned flags)
+{
+    DEBUG_EVENT         de;
+    int                 i;
+
+    gdbctx->sock = -1;
+    gdbctx->in_buf = NULL;
+    gdbctx->in_buf_alloc = 0;
+    gdbctx->in_len = 0;
+    gdbctx->out_buf = NULL;
+    gdbctx->out_buf_alloc = 0;
+    gdbctx->out_len = 0;
+    gdbctx->out_curr_packet = -1;
+
+    gdbctx->exec_thread = gdbctx->other_thread = 0;
+    gdbctx->last_sig = 0;
+    gdbctx->in_trap = FALSE;
+    gdbctx->trace = /*GDBPXY_TRC_PACKET | GDBPXY_TRC_COMMAND |*/ GDBPXY_TRC_COMMAND_ERROR | GDBPXY_TRC_WIN32_EVENT;
+    gdbctx->process = NULL;
+    for (i = 0; i < NUM_XPOINT; i++)
+        gdbctx->Xpoints[i].type = -1;
+
+    /* wait for first trap */
+    while (WaitForDebugEvent(&de, INFINITE))
+    {
+        if (de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
+        {
+            /* this should be the first event we get, 
+             * and the only one of this type  */
+            assert(gdbctx->process == NULL && de.dwProcessId == DEBUG_CurrPid);
+            //gdbctx->dwProcessId = pid;
+            if (!gdb_startup(gdbctx, &de, flags)) return FALSE;
+            assert(!gdbctx->in_trap);
+        }
+        else
+        {
+            handle_debug_event(gdbctx, &de);
+            if (gdbctx->in_trap) break;
+        }
+        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
+    }
+    return TRUE;
+}
+
+BOOL DEBUG_GdbRemote(unsigned flags)
+{
+    struct pollfd       pollfd;
+    struct gdb_context  gdbctx;
+    BOOL                doLoop;
+
+    for (doLoop = gdb_init_context(&gdbctx, flags); doLoop;)
+    {
+        pollfd.fd = gdbctx.sock;
+        pollfd.events = POLLIN;
+        pollfd.revents = 0;
+
+        switch (poll(&pollfd, 1, -1))
+        {
+        case 1:
+            /* got something */
+            if (pollfd.revents & (POLLHUP | POLLERR))
+            {
+                if (gdbctx.trace & GDBPXY_TRC_LOWLEVEL)
+                    fprintf(stderr, "Gdb hung up\n");
+                /* kill also debuggee process - questionnable - */
+                detach_debuggee(&gdbctx, TRUE);
+                doLoop = FALSE;
+                break;
+            }
+            if ((pollfd.revents & POLLIN) && fetch_data(&gdbctx) > 0)
+            {
+                if (extract_packets(&gdbctx)) doLoop = FALSE;
+            }
+            break;
+        case 0:
+            /* timeout, should never happen (infinite timeout) */
+            break;
+        case -1: 
+            if (gdbctx.trace & GDBPXY_TRC_LOWLEVEL)
+                fprintf(stderr, "poll failed\n");
+            doLoop = FALSE;
+            break;
+        }
+    }
+    wait(NULL);
+    return 0;
+}
+


More information about the wine-patches mailing list