/*
 * GDB remote debugging request handling
 *
 * Copyright (C) 2001 Ove Kåven (based on request.c by Alexandre Julliard)
 */

#include "config.h"

#ifdef ENABLE_GDB_REMOTE

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#include <unistd.h>

#include "winnt.h"
#include "winbase.h"
#include "wincon.h"
#include "thread.h"
#include "process.h"
#include "request.h"
#include "wine/port.h"

#define BUFMAX 400

#ifdef __i386__
#define STEP_FLAG 0x00000100
#endif

#define STATE_RUNNING   0
#define STATE_SUSPENDED 1
#define STATE_TRAPPED   2
#define STATE_DETACHING 3

#define STEP_FREE    0
#define STEP_TRACE_1 1
#define STEP_TRACE_2 2

int create_suspended = 0;

struct remote_gdb
{
    struct object       obj;            /* object header */
    struct process     *process;        /* process to debug */
    int                 state, step;    /* debug state */
    char                inbuf[BUFMAX];  /* incoming packet */
    char                outbuf[BUFMAX]; /* outbound packet */
    unsigned int        inpos, outpos;
    unsigned int        inlen, outlen;
    unsigned int        outpk;
    int                 last_sig, is_first, is_fatal;
    struct thread      *run_thread, *dbg_thread, *q_thread;
};

static void remote_gdb_dump( struct object *obj, int verbose );
static void remote_gdb_poll_event( struct object *obj, int event );
static void remote_gdb_destroy( struct object *obj );

static const struct object_ops remote_gdb_ops =
{
    sizeof(struct remote_gdb),     /* size */
    remote_gdb_dump,               /* dump */
    no_add_queue,                  /* add_queue */
    NULL,                          /* remove_queue */
    NULL,                          /* signaled */
    NULL,                          /* satisfied */
    NULL,                          /* get_poll_events */
    remote_gdb_poll_event,         /* poll_event */
    no_get_fd,                     /* get_fd */
    no_flush,                      /* flush */
    no_get_file_info,              /* get_file_info */
    remote_gdb_destroy             /* destroy */
};
const struct object_ops* get_this_process_remote_gdb_ops(void) { return &remote_gdb_ops; }

static void gdb_socket_detach( struct gdb_socket *sock, struct remote_gdb *gdb );

static const char hexd[16]="0123456789abcdef";

/* from context_i386.c */
extern void get_thread_context( struct thread *thread, unsigned int flags, CONTEXT *context );
extern void set_thread_context( struct thread *thread, unsigned int flags, CONTEXT *context );

/* from debugger.c */
extern int continue_debug_event( struct process *process, struct thread *thread, int status );
extern int create_gdb_ctx( struct process *process );
extern void destroy_gdb_ctx( struct process *process );
extern void send_gdb_events( struct process *process );

static inline int dhex(unsigned 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 void memhex(const char*mem, char*hex, unsigned int count)
{
    unsigned int c;
    for (c=0; c<count; c++) {
        unsigned char ch = mem[c];
        *hex++ = hexd[ch >> 4];
        *hex++ = hexd[ch & 0xf];
    }
}

static void hexmem(const char*hex, char*mem, unsigned int count)
{
    unsigned int c;
    for (c=0; c<count; c++) {
        unsigned ch = dhex(*hex++) << 4;
        ch += dhex(*hex++);
        mem[c] = ch;
    }
}

static void whex(unsigned long m, char*hex, unsigned int count)
{
    unsigned int c, s = (count-1)*4;
    for (c=0; c<count; c++,s-=4)
        *hex++ = hexd[(m >> s) & 0xf];
}

static unsigned long rhex(const char*hex, unsigned int count)
{
    unsigned long m = 0;
    unsigned int c;
    for (c=0; c<count; c++) {
        int ch = dhex(*hex++);
        if (ch == -1) return m;
        m = (m << 4) | ch;
    }
    return m;
}

static void start_gdb_packet( struct remote_gdb *gdb )
{
    gdb->outbuf[gdb->outlen++] = '$';
    gdb->outpk = gdb->outlen;
}

static void send_gdb_packet( struct remote_gdb *gdb )
{
    /* compute checksum */
    unsigned int c;
    char cksum = 0;
    for (c=gdb->outpk; c<gdb->outlen; c++) cksum += gdb->outbuf[c];
    gdb->outbuf[gdb->outlen++] = '#';
    memhex(&cksum, gdb->outbuf + gdb->outlen, 1);
    gdb->outlen+=2;
#ifdef COMMAND_DEBUG
    fprintf(stderr, "reply: %*.*s\n", (int)(gdb->outlen-gdb->outpk)+1, (int)(gdb->outlen-gdb->outpk)+1, gdb->outbuf+gdb->outpk-1);
#endif
    if (gdb->outpk == 1) set_select_events( &gdb->obj, POLLIN | POLLOUT, NULL );
}

static void send_status_reply( struct remote_gdb *gdb, int e )
{
    start_gdb_packet( gdb );
    if (e > 0) {
        gdb->outbuf[gdb->outlen++] = 'E';
        whex(e, gdb->outbuf + gdb->outlen, 2);
        gdb->outlen += 2;
    }
    else if (e == 0) {
        gdb->outbuf[gdb->outlen++] = 'O';
        gdb->outbuf[gdb->outlen++] = 'K';
    }
    send_gdb_packet( gdb );
}

static void send_signal_reply( struct remote_gdb *gdb )
{
    start_gdb_packet( gdb );
    gdb->outbuf[gdb->outlen++] = 'T';
    whex(gdb->last_sig, gdb->outbuf + gdb->outlen, 2);
    gdb->outlen += 2;
    strcpy(&gdb->outbuf[gdb->outlen], "thread:");
    gdb->outlen += strlen("thread:");
    whex(gdb->dbg_thread->unix_pid, gdb->outbuf + gdb->outlen, 4);
    gdb->outlen += 4;
    /* FIXME: also give gdb eip, esp, and ebp, like gdbserver does */
    gdb->outbuf[gdb->outlen++] = ';';
    send_gdb_packet( gdb );
}

static struct thread *find_thread( struct remote_gdb *gdb, char *data )
{
    struct thread *thread;
    if (strcmp(data, "-1") == 0) {
        /* "all threads" */
        thread = NULL;
    }
    else if (strcmp(data, "0") == 0) {
        /* "pick any thread", pick the first thread,
         * which is what most people are interested in */
        thread = gdb->process->thread_list;
        if (!thread) return thread;
        while (thread->proc_next)
            thread = thread->proc_next;
    }
    else {
        unsigned pid = rhex(data, 8);
        thread = gdb->process->thread_list;
        while (thread && thread->unix_pid != pid)
            thread = thread->proc_next;
    }
    return thread;
}

static void set_thread( struct remote_gdb *gdb, char t, char *data )
{
    struct thread *thread = find_thread( gdb, data );
    switch (t) {
    case 'c': /* run thread */
        if (strcmp(data, "-1") == 0) gdb->run_thread = NULL; /* all threads */
        else if (thread) gdb->run_thread = thread;
        else fprintf(stderr, "unknown run thread: %s\n", data);
        break;
    case 'g': /* debug thread */
        if (thread) gdb->dbg_thread = thread;
        else fprintf(stderr, "unknown debug thread: %s\n", data);
        break;
    }
    send_status_reply( gdb, 0 );
}

static void check_gdb_event( struct remote_gdb *gdb )
{
    send_gdb_events( gdb->process );
}

static int init_step( struct remote_gdb *gdb )
{
    int ret = STEP_FREE;
#ifdef __i386__
#ifdef RUN_DEBUG
    fprintf(stderr, "initiating single stepping\n");
#endif
    if (gdb->dbg_thread->context)
    {
        if (gdb->dbg_thread->context->EFlags & STEP_FLAG) {
            ret = STEP_TRACE_2;
        } else {
            ret = STEP_TRACE_1;
            gdb->dbg_thread->context->EFlags |= STEP_FLAG;
        }
    }
    else if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        CONTEXT context;
        get_thread_context( gdb->dbg_thread, CONTEXT_CONTROL, &context );
        if (context.EFlags & STEP_FLAG) {
            ret = STEP_TRACE_2;
        } else {
            ret = STEP_TRACE_1;
            context.EFlags |= STEP_FLAG;
            set_thread_context( gdb->dbg_thread, CONTEXT_CONTROL, &context );
        }
        resume_thread( gdb->dbg_thread );
    }
    else fprintf(stderr, "cannot single step\n");
#endif
    return ret;
}

static void clean_step( struct remote_gdb *gdb )
{
#ifdef __i386__
    if (gdb->step != STEP_TRACE_1) return;
#ifdef RUN_DEBUG
    fprintf(stderr, "clearing single stepping\n");
#endif
    if (gdb->dbg_thread->context)
    {
        gdb->dbg_thread->context->EFlags &= ~STEP_FLAG;
    }
    else if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        CONTEXT context;
        get_thread_context( gdb->dbg_thread, CONTEXT_CONTROL, &context );
        if (context.EFlags & STEP_FLAG) {
            context.EFlags &= ~STEP_FLAG;
            set_thread_context( gdb->dbg_thread, CONTEXT_CONTROL, &context );
        }
        resume_thread( gdb->dbg_thread );
    }
    else fprintf(stderr, "cannot clear single step\n");
#endif
}

static void continue_signal( struct remote_gdb *gdb, char *data, int step )
{
    int nstep = STEP_FREE;
    char*sdata = strtok(data, ";");
    char*adata = strtok(NULL, "");
    if (*sdata) {
        int sig = rhex(sdata, 2);
        if (sig != gdb->last_sig)
            fprintf(stderr, "can't resume signal %d\n", sig);
    }
    if (adata && *adata) {
        unsigned long addr = rhex(adata, 8);
        fprintf(stderr, "FIXME: resume at addr %08lx\n", addr);
    }

    if (step) nstep = init_step( gdb );

    switch (gdb->state) {
    case STATE_SUSPENDED:
#ifdef RUN_DEBUG
        fprintf(stderr, "resuming from suspension\n");
#endif
        fprintf(stderr, "can't resume signal %d\n", gdb->last_sig);
        gdb->state = STATE_RUNNING;
        gdb->step = nstep;
        resume_process( gdb->process );
        break;
    case STATE_TRAPPED:
#ifdef RUN_DEBUG
        fprintf(stderr, "continuing from exception fault\n");
#endif
        gdb->state = STATE_RUNNING;
        gdb->step = nstep;
        continue_debug_event( gdb->process, gdb->dbg_thread, DBG_EXCEPTION_NOT_HANDLED );
        break;
    }

    /* check for other pending exceptions */
    check_gdb_event( gdb );
}

static void continue_exec( struct remote_gdb *gdb, char *data, int step )
{
    int ostep = gdb->step, nstep = STEP_FREE;
    if (*data) {
        unsigned long addr = rhex(data, 8);
        fprintf(stderr, "FIXME: resume at addr %08lx\n", addr);
    }

    if (step) nstep = init_step( gdb );

    switch (gdb->state) {
    case STATE_SUSPENDED:
#ifdef RUN_DEBUG
        fprintf(stderr, "resuming from suspension\n");
#endif
        gdb->state = STATE_RUNNING;
        gdb->step = nstep;
        resume_process( gdb->process );
        break;
    case STATE_TRAPPED:
#ifdef RUN_DEBUG
        fprintf(stderr, "continuing from exception fault\n");
#endif
        gdb->state = STATE_RUNNING;
        gdb->step = nstep;
        continue_debug_event( gdb->process, gdb->dbg_thread, (ostep == STEP_TRACE_2) ?
                                                             DBG_EXCEPTION_NOT_HANDLED :
                                                             DBG_CONTINUE );
        break;
    }

    /* check for other pending exceptions */
    check_gdb_event( gdb );
}

static void detach_exec( struct remote_gdb *gdb )
{
    do {
        switch (gdb->state) {
        case STATE_SUSPENDED:
            resume_process( gdb->process );
            break;
        case STATE_TRAPPED:
            if (gdb->last_sig == SIGTRAP)
                continue_debug_event( gdb->process, gdb->dbg_thread, DBG_CONTINUE );
            else
                continue_debug_event( gdb->process, gdb->dbg_thread, DBG_EXCEPTION_NOT_HANDLED );
            break;
        }

        /* kill remaining pending exceptions */
        gdb->state = STATE_DETACHING;
        check_gdb_event( gdb );
    } while (gdb->state == STATE_TRAPPED);
}

static void read_regs( struct remote_gdb *gdb )
{
    CONTEXT context;
    if (gdb->dbg_thread->context)
    {
        context = *gdb->dbg_thread->context;
    }
    else if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        get_thread_context( gdb->dbg_thread, CONTEXT_FULL, &context );
        resume_thread( gdb->dbg_thread );
    }
#ifdef __i386__
    {
        unsigned regs[16];
        regs[0] = context.Eax;
        regs[1] = context.Ecx;
        regs[2] = context.Edx;
        regs[3] = context.Ebx;
        regs[4] = context.Esp;
        regs[5] = context.Ebp;
        regs[6] = context.Esi;
        regs[7] = context.Edi;
        regs[8] = context.Eip;
        regs[9] = context.EFlags;
        regs[10] = context.SegCs;
        regs[11] = context.SegSs;
        regs[12] = context.SegDs;
        regs[13] = context.SegEs;
        regs[14] = context.SegFs;
        regs[15] = context.SegGs;

        start_gdb_packet( gdb );
        memhex((char*)&regs, gdb->outbuf + gdb->outlen, sizeof(regs));
        gdb->outlen += sizeof(regs)*2;
        send_gdb_packet( gdb );
    }
#endif
}

static void write_regs( struct remote_gdb *gdb, char *data )
{
    CONTEXT context;
#ifdef __i386__
    {
        unsigned regs[16];
        hexmem(data, (char*)&regs, sizeof(regs));

        context.Eax = regs[0];
        context.Ecx = regs[1];
        context.Edx = regs[2];
        context.Ebx = regs[3];
        context.Esp = regs[4];
        context.Ebp = regs[5];
        context.Esi = regs[6];
        context.Edi = regs[7];
        context.Eip = regs[8];
        context.EFlags = regs[9];
        context.SegCs = regs[10];
        context.SegSs = regs[11];
        context.SegDs = regs[12];
        context.SegEs = regs[13];
        context.SegFs = regs[14];
        context.SegGs = regs[15];
    }
#endif
    if (gdb->dbg_thread->context)
    {
        *gdb->dbg_thread->context = context;
    }
    else if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        set_thread_context( gdb->dbg_thread, CONTEXT_FULL, &context );
        resume_thread( gdb->dbg_thread );
    }
    send_status_reply( gdb, 0 );
}

static void read_memory( struct remote_gdb *gdb, char *data )
{
    void *addr;
    unsigned long len, ilen;
    if (sscanf(data, "%p,%lx", &addr, &len) != 2)
    {
        send_status_reply( gdb, 1 );
        return;
    }
    start_gdb_packet( gdb );
    if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        unsigned int tmp;
        while (len > 0)
        {
            ilen = (len > sizeof(int)) ? sizeof(int) : len;
            if (read_thread_int( gdb->dbg_thread, addr, &tmp ) == -1) break;
            memhex((char*)&tmp, gdb->outbuf + gdb->outlen, ilen);
            gdb->outlen += ilen*2;
            addr += ilen;
            len -= ilen;
        }
        resume_thread( gdb->dbg_thread );
    }
    send_gdb_packet( gdb );
}

static void write_memory( struct remote_gdb *gdb, char *data )
{
    void *addr;
    unsigned long len, ilen;
    char *mdata;
    if (sscanf(strtok(data, ":"), "%p,%lx", &addr, &len) != 2)
    {
        send_status_reply( gdb, 1 );
        return;
    }
    mdata = strtok(NULL, "");
    if (suspend_for_ptrace( gdb->dbg_thread ))
    {
        unsigned int tmp;
        while (len > 0)
        {
            ilen = (len > sizeof(int)) ? sizeof(int) : len;
            if (ilen < sizeof(int))
                if (read_thread_int( gdb->dbg_thread, addr, &tmp ) == -1) break;
            hexmem(mdata, (char*)&tmp, ilen);
            if (write_thread_int( gdb->dbg_thread, addr, tmp, ~0 ) == -1) break;
            mdata += ilen*2;
            addr += ilen;
            len -= ilen;
        }
        resume_thread( gdb->dbg_thread );
        send_status_reply( gdb, 0 );
    }
    else send_status_reply( gdb, 1 );
}

static void answer_query( struct remote_gdb *gdb, char *data )
{
    char *cmd;
    cmd = strtok(data, ",");
    if (strcmp(cmd, "C") == 0) { /* get current thread id */
        start_gdb_packet( gdb );
        gdb->outbuf[gdb->outlen++] = 'Q';
        gdb->outbuf[gdb->outlen++] = 'C';
        whex(gdb->dbg_thread->unix_pid, gdb->outbuf + gdb->outlen, 4);
        gdb->outlen += 4;
        send_gdb_packet( gdb );
        return;
    }
    else if (strcmp(cmd, "fThreadInfo") == 0) { /* get first thread id */
        assert( gdb->state != STATE_RUNNING );
        gdb->q_thread = gdb->process->thread_list;
        start_gdb_packet( gdb );
        gdb->outbuf[gdb->outlen++] = 'm';
        whex(gdb->q_thread->unix_pid, gdb->outbuf + gdb->outlen, 4);
        gdb->outlen += 4;
        send_gdb_packet( gdb );
    }
    else if (strcmp(cmd, "sThreadInfo") == 0) { /* get next thread id */
        assert( gdb->state != STATE_RUNNING );
        gdb->q_thread = gdb->q_thread->proc_next;
        start_gdb_packet( gdb );
        if (gdb->q_thread) {
            gdb->outbuf[gdb->outlen++] = 'm';
            whex(gdb->q_thread->unix_pid, gdb->outbuf + gdb->outlen, 4);
            gdb->outlen += 4;
        }
        else gdb->outbuf[gdb->outlen++] = 'l';
        send_gdb_packet( gdb );
    }
    else if (strcmp(cmd, "ThreadExtraInfo") == 0) { /* get thread info */
        struct thread *thread = find_thread( gdb, strtok(NULL, "") );
        start_gdb_packet( gdb );
        if (thread) {
            char text[128];
            /* any text is allowed... */
            sprintf(text, "teb=%p, state=%s", thread->teb,
                    thread->context ? "FAULTED" :
                    thread->suspend ? "SUSPENDED" :
                    thread->state == TERMINATED ? "TERMINATED" : "RUNNING");
            memhex(text, gdb->outbuf + gdb->outlen, strlen(text));
            gdb->outlen += strlen(text)*2;
        }
        send_gdb_packet( gdb );
    }
    else {
         fprintf(stderr, "unknown query: %s\n", cmd);
         send_status_reply( gdb, -1 );
    }
}

static void process_gdb_packet( struct remote_gdb *gdb )
{
    /* compute checksum */
    unsigned int c;
    char cksum = 0, ck, *data;
    for (c=1; c<gdb->inlen; c++) cksum += gdb->inbuf[c];
    hexmem(gdb->inbuf + gdb->inlen + 1, &ck, 1);

#ifdef COMMAND_DEBUG
    fprintf(stderr, "command (cks=%02x, ck=%02x): %*.*s\n", cksum, ck, (int)gdb->inlen+3, (int)gdb->inlen+3, gdb->inbuf);
#endif

    /* null-terminate data */
    gdb->inbuf[gdb->inlen] = 0;
    data = gdb->inbuf + 2;

    if (cksum == ck) {
        /* packet OK */
        struct thread *old_current = current;
        gdb->outbuf[gdb->outlen++] = '+';
        current = NULL;
        switch (gdb->inbuf[1]) {
        case '?': /* last signal */
            send_signal_reply( gdb );
            break;
        case 'C': /* continue with signal */
            continue_signal( gdb, data, 0 );
            break;
        case 'D': /* detach */
            /* don't need to do anything, we detach
               when the connection is closed anyway */
            break;
        case 'G': /* write registers */
            write_regs( gdb, data );
            break;
        case 'H': /* set thread */
            set_thread( gdb, *data, data+1 );
            break;
        case 'M': /* write memory */
            write_memory( gdb, data );
            break;
        case 'S': /* step with signal */
            continue_signal( gdb, data, 1 );
            break;
        case 'T': /* thread still alive */
            send_status_reply( gdb, find_thread( gdb, data ) ? 0 : 1 );
            break;
        case 'c': /* continue running */
            continue_exec( gdb, data, 0 );
            break;
        case 'g': /* read registers */
            read_regs( gdb );
            break;
        case 'm': /* read memory */
            read_memory( gdb, data );
            break;
        case 'q': /* query */
            answer_query( gdb, data );
            break;
        case 's': /* step */
            continue_exec( gdb, data, 1 );
            break;
        default:
            fprintf(stderr, "unknown gdb packet type %c\n", gdb->inbuf[1]);
            send_status_reply( gdb, -1 );
        }
        current = old_current;
    }
    else {
        /* failed checksum */
        fprintf(stderr, "bad gdb packet checksum, type=%c\n", gdb->inbuf[1]);
        gdb->outbuf[gdb->outlen++] = '-';
    }
    set_select_events( &gdb->obj, POLLIN | POLLOUT, NULL );
}

static void process_gdb_event( struct remote_gdb *gdb, struct thread *thread,
                               debug_event_t *data, CONTEXT *context )
{
    EXCEPTION_RECORD *rec;
    struct thread *old_current = current;

    assert( data->code == EXCEPTION_DEBUG_EVENT );
    assert( gdb->state == STATE_RUNNING || gdb->state == STATE_DETACHING );

    rec = &data->info.exception.record;

#ifdef RUN_DEBUG
    fprintf(stderr, "received exception %08lx\n", rec->ExceptionCode);
#endif

    current = NULL;

    switch (rec->ExceptionCode) {
    case EXCEPTION_ACCESS_VIOLATION:
    case EXCEPTION_PRIV_INSTRUCTION:
    case EXCEPTION_STACK_OVERFLOW:
    case EXCEPTION_GUARD_PAGE:
        gdb->last_sig = SIGSEGV;
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        gdb->last_sig = SIGBUS;
        break;
    case EXCEPTION_SINGLE_STEP:
        if (gdb->step == STEP_FREE || thread != gdb->dbg_thread) {
            /* the app is doing something strange, let it */
            continue_debug_event( gdb->process, thread, DBG_EXCEPTION_NOT_HANDLED );
            goto dont_process;
        }
#ifdef RUN_DEBUG
        fprintf(stderr, "completed single stepping\n");
#endif
    case EXCEPTION_BREAKPOINT:
        gdb->last_sig = SIGTRAP;
        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:
        gdb->last_sig = SIGFPE;
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
    case EXCEPTION_INT_OVERFLOW:
        gdb->last_sig = SIGFPE;
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        gdb->last_sig = SIGILL;
        break;
    case CONTROL_C_EXIT:
        gdb->last_sig = SIGINT;
        break;
    case EXCEPTION_CRITICAL_SECTION_WAIT:
        /* grr... let's just keep these at bay so they don't disturb us... */
        if (thread != gdb->dbg_thread)
            continue_debug_event( gdb->process, thread, DBG_CONTINUE );
        goto dont_process;
    default:
        fprintf(stderr, "unhandled exception code %08lx\n", rec->ExceptionCode);
        gdb->last_sig = SIGABRT;
        break;
    }

    clean_step( gdb );

    gdb->is_first = data->info.exception.first;
    gdb->is_fatal = (rec->ExceptionFlags & EH_NONCONTINUABLE) != 0;
    if (gdb->run_thread) gdb->run_thread = thread;
    gdb->dbg_thread = thread;
    if (gdb->state == STATE_RUNNING) send_signal_reply( gdb );
    gdb->state = STATE_TRAPPED;
dont_process:
    current = old_current;
}

static void exit_gdb_process( struct remote_gdb *gdb )
{
    assert( gdb->state == STATE_RUNNING || gdb->state == STATE_DETACHING );
    if (gdb->state == STATE_RUNNING) {
        start_gdb_packet( gdb );
        gdb->outbuf[gdb->outlen++] = 'W';
        whex(gdb->process->exit_code, gdb->outbuf + gdb->outlen, 2);
        gdb->outlen += 2;
        send_gdb_packet( gdb );
    }
}

static struct remote_gdb *create_remote_gdb( int fd, struct process *process )
{
    struct remote_gdb *gdb;

    if (!(gdb = alloc_object( &remote_gdb_ops, fd ))) return NULL;
    gdb->process = (struct process *)grab_object( process );
    gdb->inpos = 0;
    gdb->outpos = 0;
    gdb->inlen = 0;
    gdb->outlen = 0;
    gdb->outpk = 0;

    fprintf(stderr, "gdb connected on fd %d\n", gdb->obj.fd);

    /* gdb expects a breakpoint when attaching */
    if (!create_suspended) suspend_process( gdb->process );
    gdb->state = STATE_SUSPENDED;
    gdb->step = STEP_FREE;
    gdb->last_sig = SIGTRAP;
    gdb->run_thread = NULL;
    /* find the first thread, which is what most people are interested in */
    gdb->dbg_thread = gdb->process->thread_list;
    while (gdb->dbg_thread->proc_next)
        gdb->dbg_thread = gdb->dbg_thread->proc_next;

    set_select_events( &gdb->obj, POLLIN, NULL );

    return gdb;
}

static void remote_gdb_dump( struct object *obj, int verbose )
{
    struct remote_gdb *gdb = (struct remote_gdb *)obj;
    assert( obj->ops == remote_gdb_ops_tag );
    fprintf( stderr, "GDB debug socket fd=%d\n", gdb->obj.fd );
}

/* handle a socket event */
static void remote_gdb_poll_event( struct object *obj, int event )
{
    struct remote_gdb *gdb = (struct remote_gdb *)obj;
    assert( obj->ops == remote_gdb_ops_tag );

    if (event & (POLLERR | POLLHUP)) {
        fprintf(stderr, "gdb hup\n");
        release_object( gdb );
        return;
    }
    if (event & POLLIN) {
        int len = read( gdb->obj.fd, gdb->inbuf + gdb->inpos, BUFMAX - gdb->inpos );
        if (len > 0) {
            char *bp, *cp, *ep;
            int pl = gdb->inlen ? (gdb->inlen + 3) : 0;
            cp = pl ? (gdb->inbuf + pl) : NULL;
            ep = NULL;
#ifdef PACKET_DEBUG
            fprintf(stderr, "cmd from gdb: %*.*s\n", len, len, gdb->inbuf + gdb->inpos);
            fprintf(stderr, "i: inpos=%d, inlen=%d, len=%d, pl=%d, buf=%p, cp=%p, ep=%p\n", gdb->inpos, gdb->inlen, len, pl, gdb->inbuf, cp, ep);
#endif
            do {
                /* locate packet begin markers */
                bp = memchr(gdb->inbuf + gdb->inpos, '$', len);
                if (!cp) {
                    /* locate packet end markers */
                    ep = memchr(gdb->inbuf + gdb->inpos, '#', len);
                    if (ep) {
                        cp = ep + 3;
                        pl = cp - gdb->inbuf;
                    }
                }
                /* discard garbage and incomplete packets */
                if (bp && bp != gdb->inbuf && (!cp || bp < cp)) {
                    int d = bp - (gdb->inbuf + gdb->inpos);
#ifdef PACKET_DEBUG
                    fprintf(stderr, "g: inpos=%d, d=%d, len=%d, pl=%d, buf=%p, bp=%p, cp=%p, ep=%p\n", gdb->inpos, d, len, pl, gdb->inbuf, bp, cp, ep);
#endif
                    len -= d;
                    if (len) memmove(gdb->inbuf, bp, len);
                    gdb->inpos = 0;
                    gdb->inlen = 0;
                    pl = 0;
                    cp = NULL;
                    continue;
                }
                /* check packets */
#ifdef PACKET_DEBUG
                fprintf(stderr, "c: inpos=%d, inlen=%d, len=%d, pl=%d, buf=%p, bp=%p, cp=%p, ep=%p\n", gdb->inpos, gdb->inlen, len, pl, gdb->inbuf, bp, cp, ep);
#endif
                gdb->inpos += len;
                len = 0;
                if (ep) {
                    if (pl > BUFMAX) {
                        /* packet too long, abort */
                        gdb->inpos = 0;
                        gdb->inlen = 0;
                        break;
                    }
                    gdb->inlen = ep - gdb->inbuf;
                    assert( pl == (gdb->inlen+3) );
                }
                else if (gdb->inpos >= BUFMAX) {
                    /* packet too long, abort */
                    gdb->inpos = 0;
                    gdb->inlen = 0;
                    break;
                }
                /* process complete packets */
#ifdef PACKET_DEBUG
                fprintf(stderr, "p: inpos=%d, inlen=%d, len=%d, pl=%d, buf=%p, bp=%p, cp=%p, ep=%p\n", gdb->inpos, gdb->inlen, len, pl, gdb->inbuf, bp, cp, ep);
#endif
                if (pl && gdb->inpos >= pl) {
                    assert( gdb->inbuf[0] == '$' );
                    process_gdb_packet( gdb );
                    /* remove processed packet and restart */
                    len = gdb->inpos - pl;
                    if (len) memmove(gdb->inbuf, cp, len);
                    gdb->inpos = 0;
                    gdb->inlen = 0;
                    pl = 0;
                    cp = NULL;
                }
                else break;
            } while (1);
        } else {
            if (gdb->outlen)
                set_select_events( &gdb->obj, POLLOUT );
            else {
                release_object( gdb );
                return;
            }
        }
    }
    if (event & POLLOUT) {
        if (gdb->outpos < gdb->outlen) {
            int len = write( gdb->obj.fd, gdb->outbuf + gdb->outpos, gdb->outlen - gdb->outpos );
            if (len > 0) {
#ifdef PACKET_DEBUG
                fprintf(stderr, "reply to gdb: %*.*s\n", len, len, gdb->outbuf + gdb->outpos);
#endif
                gdb->outpos += len;
            }
            if (gdb->outpos < gdb->outlen)
                return;
        }
        gdb->outpos = 0;
        gdb->outlen = 0;
        set_select_events( &gdb->obj, POLLIN );
    }
}

static void remote_gdb_destroy( struct object *obj )
{
    struct remote_gdb *gdb = (struct remote_gdb *)obj;
    assert( obj->ops == remote_gdb_ops_tag );
    clean_step( gdb );
    if (create_suspended) suspend_process( gdb->process );
    if (gdb->state != STATE_RUNNING)
        detach_exec( gdb );
    fprintf( stderr, "gdb detached" );
    release_object( gdb->process );
    gdb_socket_detach( gdb->process->gdb_socket, gdb );
}

struct gdb_socket
{
    struct object       obj;         /* object header */
    struct process     *process;     /* process to debug */
    struct remote_gdb  *current;     /* current debugger */
};

static void gdb_socket_dump( struct object *obj, int verbose );
static void gdb_socket_poll_event( struct object *obj, int event );
static void gdb_socket_destroy( struct object *obj );

static const struct object_ops gdb_socket_ops =
{
    sizeof(struct gdb_socket),     /* size */
    gdb_socket_dump,               /* dump */
    no_add_queue,                  /* add_queue */
    NULL,                          /* remove_queue */
    NULL,                          /* signaled */
    NULL,                          /* satisfied */
    NULL,                          /* get_poll_events */
    gdb_socket_poll_event,         /* poll_event */
    no_get_fd,                     /* get_fd */
    no_flush,                      /* flush */
    no_get_file_info,              /* get_file_info */
    gdb_socket_destroy             /* destroy */
};
const struct object_ops* get_this_process_gdb_socket_ops(void) { return &gdb_socket_ops; }

static void gdb_socket_dump( struct object *obj, int verbose )
{
    struct gdb_socket *sock = (struct gdb_socket *)obj;
    assert( obj->ops == gdb_socket_ops_tag );
    fprintf( stderr, "GDB listen socket fd=%d\n", sock->obj.fd );
}

/* handle a socket event */
static void gdb_socket_poll_event( struct object *obj, int event )
{
    struct gdb_socket *sock = (struct gdb_socket *)obj;
    assert( obj->ops == gdb_socket_ops_tag );

    if (event & (POLLERR | POLLHUP))
    {
        /* this is not supposed to happen */
        fprintf( stderr, "wineserver: Error on gdb socket\n" );
        release_object( obj );
    }
    else if (event & POLLIN)
    {
        struct sockaddr_in dummy;
        int len = sizeof(dummy);
        int client = accept( sock->obj.fd, (struct sockaddr *) &dummy, &len );
        if (client != -1) {
            if (sock->current || sock->process->debugger) {
                /* already debugging, reject new debugger */
                fprintf( stderr, "process already debugged, rejecting new connection\n" );
                close(client);
            }
            else {
                if (create_gdb_ctx( sock->process))
                    sock->current = create_remote_gdb( client, sock->process );
                else {
                    fprintf( stderr, "failed to create debug context, rejecting connection\n" );
                    close(client);
                }
            }
        }
    }
}

static void gdb_socket_destroy( struct object *obj )
{
    struct gdb_socket *sock = (struct gdb_socket *)obj;
    assert( obj->ops == gdb_socket_ops_tag );
    if (sock->current)
       exit_gdb_process( sock->current );
}

static void gdb_socket_detach( struct gdb_socket *sock, struct remote_gdb *gdb )
{
    destroy_gdb_ctx( sock->process );
    if (sock->current == gdb) sock->current = NULL;
}

struct gdb_socket *open_gdb_socket( struct process *process )
{
    struct gdb_socket *sock;
    struct sockaddr_in addr;
    socklen_t slen = sizeof(addr);
    int fd;

    if ((fd = socket( AF_INET, SOCK_STREAM, 0 )) == -1)
    {
        perror( "socket" );
        return NULL;
    }
    if (listen( fd, 5 ) == -1)
    {
        perror( "listen" );
        goto sock_error;
    }
    if (getsockname( fd, (struct sockaddr *)&addr, &slen ) == -1)
    {
        perror( "getsockname" );
        goto sock_error;
    }
    if (!(sock = alloc_object( &gdb_socket_ops, fd ))) {
        fprintf( stderr, "alloc_object: out of memory" );
        goto sock_error;
    }
    sock->process = process;
    sock->current = NULL;
    set_select_events( &sock->obj, POLLIN );

    fprintf(stderr, "gdb socket ready at: target remote localhost:%d\n", ntohs(addr.sin_port));
    if (create_suspended) suspend_process( process );
    return sock;

sock_error:
    close(fd);
    return NULL;
}

void close_gdb_socket( struct gdb_socket *sock )
{
    release_object( sock );
}

void notify_gdb_event( struct gdb_socket *sock )
{
    if (sock && sock->current && sock->current->state == STATE_RUNNING)
        check_gdb_event( sock->current );
}

void recv_gdb_event( struct gdb_socket *sock, struct thread *thread,
                     debug_event_t *data, CONTEXT *context )
{
    if (sock && sock->current)
        process_gdb_event( sock->current, thread, data, context );
}

#endif /* ENABLE_GDB_REMOTE */
