/* -*- tab-width: 8; c-basic-offset: 4 -*- */

/* Wine internal debugger
 * Interface to Windows debugger API
 * Copyright 2000 Eric Pouech
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "debugger.h"

#include "winternl.h"
#include "thread.h"
#include "wincon.h"
#include "winreg.h"
#include "wingdi.h"
#include "winuser.h"
#include "msvcrt/excpt.h"
#include "wine/library.h"

DBG_PROCESS*	DEBUG_CurrProcess = NULL;
DBG_THREAD*	DEBUG_CurrThread = NULL;
DWORD		DEBUG_CurrTid;
DWORD		DEBUG_CurrPid;
CONTEXT         DEBUG_context;
BOOL		DEBUG_InteractiveP = FALSE;
static BOOL     DEBUG_InException = FALSE;
int 		curr_frame = 0;
static char*	DEBUG_LastCmdLine = NULL;

static DBG_PROCESS* DEBUG_ProcessList = NULL;
static enum {none_mode = 0, winedbg_mode, automatic_mode, gdb_mode} local_mode;

DBG_INTVAR DEBUG_IntVars[DBG_IV_LAST];

void	DEBUG_OutputA(int chn, const char* buffer, int len)
{
    if (DBG_IVAR(ConChannelMask) & chn)
	WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, len, NULL, NULL);
    if (DBG_IVAR(StdChannelMask) & chn)
	fwrite(buffer, len, 1, stderr);
}

void	DEBUG_OutputW(int chn, const WCHAR* buffer, int len)
{
    /* FIXME: this won't work is std output isn't attached to a console */
    if (DBG_IVAR(ConChannelMask) & chn)
	WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), buffer, len, NULL, NULL);
    /* simplistic Unicode to ANSI conversion */
    if (DBG_IVAR(StdChannelMask) & chn)
        while (len--) fputc((char)*buffer++, stderr);
}

int	DEBUG_Printf(int chn, const char* format, ...)
{
static    char	buf[4*1024];
    va_list 	valist;
    int		len;

    va_start(valist, format);
    len = vsnprintf(buf, sizeof(buf), format, valist);
    va_end(valist);

    if (len <= -1 || len >= sizeof(buf)) {
	len = sizeof(buf) - 1;
	buf[len] = 0;
	buf[len - 1] = buf[len - 2] = buf[len - 3] = '.';
    }
    DEBUG_OutputA(chn, buf, len);
    return len;
}

static	BOOL DEBUG_IntVarsRW(int read)
{
    HKEY	hkey;
    DWORD 	type = REG_DWORD;
    DWORD	val;
    DWORD 	count = sizeof(val);
    int		i;
    DBG_INTVAR* div = DEBUG_IntVars;

    if (read) {
/* initializes internal vars table */
#define  INTERNAL_VAR(_var,_val,_ref,_typ) 			\
        div->val = _val; div->name = #_var; div->pval = _ref;	\
        div->type = DEBUG_GetBasicType(_typ); div++;
#include "intvar.h"
#undef   INTERNAL_VAR
    }

    if (RegCreateKeyA(HKEY_CURRENT_USER, "Software\\Wine\\WineDbg", &hkey)) {
	/* since the IVars are not yet setup, DEBUG_Printf doesn't work,
	 * so don't use it */
	fprintf(stderr, "Cannot create WineDbg key in registry\n");
	return FALSE;
    }

    for (i = 0; i < DBG_IV_LAST; i++) {
	if (read) {
	    if (!DEBUG_IntVars[i].pval) {
		if (!RegQueryValueEx(hkey, DEBUG_IntVars[i].name, 0,
				     &type, (LPSTR)&val, &count))
		    DEBUG_IntVars[i].val = val;
		DEBUG_IntVars[i].pval = &DEBUG_IntVars[i].val;
	    } else {
		*DEBUG_IntVars[i].pval = 0;
	    }
	} else {
	    /* FIXME: type should be infered from basic type -if any- of intvar */
	    if (DEBUG_IntVars[i].pval == &DEBUG_IntVars[i].val)
		RegSetValueEx(hkey, DEBUG_IntVars[i].name, 0,
			      type, (LPCVOID)DEBUG_IntVars[i].pval, count);
	}
    }
    RegCloseKey(hkey);
    return TRUE;
}

DBG_INTVAR*	DEBUG_GetIntVar(const char* name)
{
    int		i;

    for (i = 0; i < DBG_IV_LAST; i++) {
	if (!strcmp(DEBUG_IntVars[i].name, name))
	    return &DEBUG_IntVars[i];
    }
    return NULL;
}

DBG_PROCESS*	DEBUG_GetProcess(DWORD pid)
{
    DBG_PROCESS*	p;

    for (p = DEBUG_ProcessList; p; p = p->next)
	if (p->pid == pid) break;
    return p;
}

DBG_PROCESS*	DEBUG_AddProcess(DWORD pid, HANDLE h, const char* imageName)
{
    DBG_PROCESS*	p;

    if ((p = DEBUG_GetProcess(pid)))
    {
        if (p->handle != 0)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Process (%lu) is already defined\n", pid);
        }
        else
        {
            p->handle = h;
            p->imageName = imageName ? DBG_strdup(imageName) : NULL;
        }
        return p;
    }

    if (!(p = DBG_alloc(sizeof(DBG_PROCESS)))) return NULL;
    p->handle = h;
    p->pid = pid;
    p->imageName = imageName ? DBG_strdup(imageName) : NULL;
    p->threads = NULL;
    p->num_threads = 0;
    p->continue_on_first_exception = FALSE;
    p->modules = NULL;
    p->num_modules = 0;
    p->next_index = 0;
    p->dbg_hdr_addr = 0;
    p->delayed_bp = NULL;
    p->num_delayed_bp = 0;

    p->next = DEBUG_ProcessList;
    p->prev = NULL;
    if (DEBUG_ProcessList) DEBUG_ProcessList->prev = p;
    DEBUG_ProcessList = p;
    return p;
}

void DEBUG_DelProcess(DBG_PROCESS* p)
{
    int	i;

    while (p->threads) DEBUG_DelThread(p->threads);

    for (i = 0; i < p->num_delayed_bp; i++)
        if (p->delayed_bp[i].is_symbol)
            DBG_free(p->delayed_bp[i].u.symbol.name);

    DBG_free(p->delayed_bp);
    if (p->prev) p->prev->next = p->next;
    if (p->next) p->next->prev = p->prev;
    if (p == DEBUG_ProcessList) DEBUG_ProcessList = p->next;
    if (p == DEBUG_CurrProcess) DEBUG_CurrProcess = NULL;
    DBG_free((char*)p->imageName);
    DBG_free(p);
}

static	void			DEBUG_InitCurrProcess(void)
{
}

BOOL DEBUG_ProcessGetString(char* buffer, int size, HANDLE hp, LPSTR addr)
{
    DWORD sz;
    *(WCHAR*)buffer = 0;
    return (addr && ReadProcessMemory(hp, addr, buffer, size, &sz));
}

BOOL DEBUG_ProcessGetStringIndirect(char* buffer, int size, HANDLE hp, LPVOID addr)
{
    LPVOID	ad;
    DWORD	sz;

    if (   addr
	&& ReadProcessMemory(hp, addr, &ad, sizeof(ad), &sz)
	&& sz == sizeof(ad)
        && ad
        && ReadProcessMemory(hp, ad, buffer, size, &sz))
	return TRUE;
    *(WCHAR*)buffer = 0;
    return FALSE;
}

DBG_THREAD*	DEBUG_GetThread(DBG_PROCESS* p, DWORD tid)
{
    DBG_THREAD*	t;

    if (!p) return NULL;
    for (t = p->threads; t; t = t->next)
	if (t->tid == tid) break;
    return t;
}

DBG_THREAD*	DEBUG_AddThread(DBG_PROCESS* p, DWORD tid,
                                HANDLE h, LPVOID start, LPVOID teb)
{
    DBG_THREAD*	t = DBG_alloc(sizeof(DBG_THREAD));
    if (!t)
	return NULL;

    t->handle = h;
    t->tid = tid;
    t->start = start;
    t->teb = teb;
    t->process = p;
    t->wait_for_first_exception = 0;
    t->exec_mode = EXEC_CONT;
    t->exec_count = 0;

    sprintf(t->name, "%08lx", tid);

    p->num_threads++;
    t->next = p->threads;
    t->prev = NULL;
    if (p->threads) p->threads->prev = t;
    p->threads = t;

    return t;
}

static	void			DEBUG_InitCurrThread(void)
{
    if (DEBUG_CurrThread->start) {
	if (DEBUG_CurrThread->process->num_threads == 1 ||
	    DBG_IVAR(BreakAllThreadsStartup)) {
	    DBG_VALUE	value;

	    DEBUG_SetBreakpoints(FALSE);
	    value.type = NULL;
	    value.cookie = DV_TARGET;
	    value.addr.seg = 0;
	    value.addr.off = (DWORD)DEBUG_CurrThread->start;
	    DEBUG_AddBreakpointFromValue(&value);
	    DEBUG_SetBreakpoints(TRUE);
	}
    } else {
	DEBUG_CurrThread->wait_for_first_exception = 1;
    }
}

void			DEBUG_DelThread(DBG_THREAD* t)
{
    if (t->prev) t->prev->next = t->next;
    if (t->next) t->next->prev = t->prev;
    if (t == t->process->threads) t->process->threads = t->next;
    t->process->num_threads--;
    if (t == DEBUG_CurrThread) DEBUG_CurrThread = NULL;
    DBG_free(t);
}

BOOL				DEBUG_Attach(DWORD pid, BOOL cofe)
{
    if (!(DEBUG_CurrProcess = DEBUG_AddProcess(pid, 0, NULL))) return FALSE;

    if (!DebugActiveProcess(pid)) {
        DEBUG_Printf(DBG_CHN_MESG, "Can't attach process %lx: error %ld\n", pid, GetLastError());
        DEBUG_DelProcess(DEBUG_CurrProcess);
        DEBUG_CurrProcess = NULL;
	return FALSE;
    }
    DEBUG_CurrProcess->continue_on_first_exception = cofe;
    return TRUE;
}

BOOL				DEBUG_Detach(void)
{
    /* remove all set breakpoints in debuggee code */
    DEBUG_SetBreakpoints(FALSE);
    /* needed for single stepping (ugly).
     * should this be handled inside the server ??? */
#ifdef __i386__
    DEBUG_context.EFlags &= ~STEP_FLAG;
#endif
    SetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context);
    DebugActiveProcessStop(DEBUG_CurrProcess->pid);
    DEBUG_DelProcess(DEBUG_CurrProcess);
    DEBUG_CurrProcess = NULL;
    /* FIXME: should zero out the symbol table too */
    return TRUE;
}

static BOOL                     DEBUG_FetchContext(void)
{
    DEBUG_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, &DEBUG_context))
    {
        DEBUG_Printf(DBG_CHN_WARN, "Can't get thread's context\n");
        return FALSE;
    }
    return TRUE;
}

static  BOOL	DEBUG_ExceptionProlog(BOOL is_debug, BOOL force, DWORD code)
{
    DBG_ADDR	addr;
    int		newmode;

    DEBUG_InException = TRUE;
    DEBUG_GetCurrentAddress(&addr);
    DEBUG_SuspendExecution();

    if (!is_debug)
    {
        if (!addr.seg)
            DEBUG_Printf(DBG_CHN_MESG, " in 32-bit code (0x%08lx)", addr.off);
        else
            switch(DEBUG_GetSelectorType(addr.seg))
            {
            case MODE_32:
                DEBUG_Printf(DBG_CHN_MESG, " in 32-bit code (%04lx:%08lx)", addr.seg, addr.off);
                break;
            case MODE_16:
                DEBUG_Printf(DBG_CHN_MESG, " in 16-bit code (%04lx:%04lx)", addr.seg, addr.off);
                break;
            case MODE_VM86:
                DEBUG_Printf(DBG_CHN_MESG, " in vm86 code (%04lx:%04lx)", addr.seg, addr.off);
                break;
            case MODE_INVALID:
                DEBUG_Printf(DBG_CHN_MESG, " bad CS (%lx)", addr.seg);
                break;
        }
	DEBUG_Printf(DBG_CHN_MESG, ".\n");
    }

    DEBUG_LoadEntryPoints("Loading new modules symbols:\n");

    if (!force && is_debug &&
	DEBUG_ShouldContinue(&addr, code,
			     &DEBUG_CurrThread->exec_count))
	return FALSE;

    if ((newmode = DEBUG_GetSelectorType(addr.seg)) == MODE_INVALID) newmode = MODE_32;
    if (newmode != DEBUG_CurrThread->dbg_mode)
    {
        static const char * const names[] = { "???", "16-bit", "32-bit", "vm86" };
        DEBUG_Printf(DBG_CHN_MESG,"In %s mode.\n", names[newmode] );
        DEBUG_CurrThread->dbg_mode = newmode;
    }

    DEBUG_DoDisplay();

    if (is_debug || force) {
	/*
	 * Do a quiet backtrace so that we have an idea of what the situation
	 * is WRT the source files.
	 */
	DEBUG_BackTrace(DEBUG_CurrTid, FALSE);
    } else {
	/* This is a real crash, dump some info */
	DEBUG_InfoRegisters(&DEBUG_context);
	DEBUG_InfoStack();
#ifdef __i386__
	if (DEBUG_CurrThread->dbg_mode == MODE_16) {
	    DEBUG_InfoSegments(DEBUG_context.SegDs >> 3, 1);
	    if (DEBUG_context.SegEs != DEBUG_context.SegDs)
		DEBUG_InfoSegments(DEBUG_context.SegEs >> 3, 1);
	}
	DEBUG_InfoSegments(DEBUG_context.SegFs >> 3, 1);
#endif
	DEBUG_BackTrace(DEBUG_CurrTid, TRUE);
    }

    if (!is_debug ||
	(DEBUG_CurrThread->exec_mode == EXEC_STEPI_OVER) ||
	(DEBUG_CurrThread->exec_mode == EXEC_STEPI_INSTR)) {

	struct list_id list;

	/* Show where we crashed */
	curr_frame = 0;
	DEBUG_DisassembleInstruction(&addr);

	/* resets list internal arguments so we can look at source code when needed */
	DEBUG_FindNearestSymbol(&addr, TRUE, NULL, 0, &list);
	if (list.sourcefile) DEBUG_List(&list, NULL, 0);
    }
    return TRUE;
}

static  void	DEBUG_ExceptionEpilog(void)
{
    DEBUG_RestartExecution(DEBUG_CurrThread->exec_count);
    /*
     * This will have gotten absorbed into the breakpoint info
     * if it was used.  Otherwise it would have been ignored.
     * In any case, we don't mess with it any more.
     */
    if (DEBUG_CurrThread->exec_mode == EXEC_CONT)
	DEBUG_CurrThread->exec_count = 0;
    DEBUG_InException = FALSE;
}

static	BOOL DEBUG_HandleException(EXCEPTION_RECORD *rec, BOOL first_chance, BOOL force)
{
    BOOL             is_debug = FALSE;
    THREADNAME_INFO *pThreadName;
    DBG_THREAD      *pThread;

    assert(DEBUG_CurrThread);

    switch (rec->ExceptionCode)
    {
    case EXCEPTION_BREAKPOINT:
    case EXCEPTION_SINGLE_STEP:
        is_debug = TRUE;
        break;
    case EXCEPTION_NAME_THREAD:
        pThreadName = (THREADNAME_INFO*)(rec->ExceptionInformation);
        if (pThreadName->dwThreadID == -1)
            pThread = DEBUG_CurrThread;
        else
            pThread = DEBUG_GetThread(DEBUG_CurrProcess, pThreadName->dwThreadID);

        if (ReadProcessMemory(DEBUG_CurrThread->process->handle, pThreadName->szName,
                              pThread->name, 9, NULL))
            DEBUG_Printf (DBG_CHN_MESG,
                          "Thread ID=0x%lx renamed using MS VC6 extension (name==\"%s\")\n",
                          pThread->tid, pThread->name);
        return FALSE;
    }

    if (first_chance && !is_debug && !force && !DBG_IVAR(BreakOnFirstChance))
    {
        /* pass exception to program except for debug exceptions */
        return FALSE;
    }

    if (!is_debug)
    {
        /* print some infos */
        DEBUG_Printf(DBG_CHN_MESG, "%s: ",
		      first_chance ? "First chance exception" : "Unhandled exception");
        switch (rec->ExceptionCode)
        {
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            DEBUG_Printf(DBG_CHN_MESG, "divide by zero");
            break;
        case EXCEPTION_INT_OVERFLOW:
            DEBUG_Printf(DBG_CHN_MESG, "overflow");
            break;
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            DEBUG_Printf(DBG_CHN_MESG, "array bounds ");
            break;
        case EXCEPTION_ILLEGAL_INSTRUCTION:
            DEBUG_Printf(DBG_CHN_MESG, "illegal instruction");
            break;
        case EXCEPTION_STACK_OVERFLOW:
            DEBUG_Printf(DBG_CHN_MESG, "stack overflow");
            break;
        case EXCEPTION_PRIV_INSTRUCTION:
            DEBUG_Printf(DBG_CHN_MESG, "privileged instruction");
            break;
        case EXCEPTION_ACCESS_VIOLATION:
            if (rec->NumberParameters == 2)
                DEBUG_Printf(DBG_CHN_MESG, "page fault on %s access to 0x%08lx",
			      rec->ExceptionInformation[0] ? "write" : "read",
			      rec->ExceptionInformation[1]);
            else
                DEBUG_Printf(DBG_CHN_MESG, "page fault");
            break;
        case EXCEPTION_DATATYPE_MISALIGNMENT:
            DEBUG_Printf(DBG_CHN_MESG, "Alignment");
            break;
	case DBG_CONTROL_C:
            DEBUG_Printf(DBG_CHN_MESG, "^C");
            break;
        case CONTROL_C_EXIT:
            DEBUG_Printf(DBG_CHN_MESG, "^C");
            break;
        case EXCEPTION_CRITICAL_SECTION_WAIT:
	    {
		DBG_ADDR	addr;

		addr.seg = 0;
		addr.off = rec->ExceptionInformation[0];

		DEBUG_Printf(DBG_CHN_MESG, "wait failed on critical section ");
		DEBUG_PrintAddress(&addr, DEBUG_CurrThread->dbg_mode, FALSE);
	    }
	    if (!DBG_IVAR(BreakOnCritSectTimeOut))
	    {
		DEBUG_Printf(DBG_CHN_MESG, "\n");
		return FALSE;
	    }
            break;
        case EXCEPTION_WINE_STUB:
            {
                char dll[32], name[64];
                DEBUG_ProcessGetString( dll, sizeof(dll), DEBUG_CurrThread->process->handle,
                                        (char *)rec->ExceptionInformation[0] );
                DEBUG_ProcessGetString( name, sizeof(name), DEBUG_CurrThread->process->handle,
                                        (char *)rec->ExceptionInformation[1] );
                DEBUG_Printf(DBG_CHN_MESG, "unimplemented function %s.%s called", dll, name );
            }
            break;
        case EXCEPTION_VM86_INTx:
            DEBUG_Printf(DBG_CHN_MESG, "interrupt %02lx in vm86 mode",
                         rec->ExceptionInformation[0]);
            break;
        case EXCEPTION_VM86_STI:
            DEBUG_Printf(DBG_CHN_MESG, "sti in vm86 mode");
            break;
        case EXCEPTION_VM86_PICRETURN:
            DEBUG_Printf(DBG_CHN_MESG, "PIC return in vm86 mode");
            break;
        default:
            DEBUG_Printf(DBG_CHN_MESG, "%08lx", rec->ExceptionCode);
            break;
        }
    }

    if (local_mode == automatic_mode)
    {
        DEBUG_ExceptionProlog(is_debug, FALSE, rec->ExceptionCode);
        DEBUG_ExceptionEpilog();
        return TRUE;  /* terminate execution */
    }

    if (DEBUG_ExceptionProlog(is_debug, force, rec->ExceptionCode))
    {
	DEBUG_InteractiveP = TRUE;
        return TRUE;
    }
    DEBUG_ExceptionEpilog();

    return FALSE;
}

static	BOOL	DEBUG_HandleDebugEvent(DEBUG_EVENT* de)
{
    char		buffer[256];
    BOOL                ret = FALSE;

    DEBUG_CurrPid = de->dwProcessId;
    DEBUG_CurrTid = de->dwThreadId;

    if ((DEBUG_CurrProcess = DEBUG_GetProcess(de->dwProcessId)) != NULL)
        DEBUG_CurrThread = DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId);
    else
        DEBUG_CurrThread = NULL;

    switch (de->dwDebugEventCode)
    {
    case EXCEPTION_DEBUG_EVENT:
        if (!DEBUG_CurrThread)
        {
            DEBUG_Printf(DBG_CHN_ERR, "%08lx:%08lx: not a registered process or thread (perhaps a 16 bit one ?)\n",
                         de->dwProcessId, de->dwThreadId);
            break;
        }

        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exception code=%08lx\n",
                     de->dwProcessId, de->dwThreadId,
                     de->u.Exception.ExceptionRecord.ExceptionCode);

        if (DEBUG_CurrProcess->continue_on_first_exception)
        {
            DEBUG_CurrProcess->continue_on_first_exception = FALSE;
            if (!DBG_IVAR(BreakOnAttach)) break;
        }

        if (DEBUG_FetchContext())
        {
            ret = DEBUG_HandleException(&de->u.Exception.ExceptionRecord,
                                        de->u.Exception.dwFirstChance,
                                        DEBUG_CurrThread->wait_for_first_exception);
            if (!ret && DEBUG_CurrThread)
            {
                DEBUG_CurrThread->wait_for_first_exception = 0;
                SetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context);
            }
        }
        break;

    case CREATE_THREAD_DEBUG_EVENT:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: create thread D @%08lx\n", de->dwProcessId, de->dwThreadId,
                     (unsigned long)(LPVOID)de->u.CreateThread.lpStartAddress);

        if (DEBUG_CurrProcess == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Unknown process\n");
            break;
        }
        if (DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId) != NULL)
        {
            DEBUG_Printf(DBG_CHN_TRACE, "Thread already listed, skipping\n");
            break;
        }

        DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess,
                                           de->dwThreadId,
                                           de->u.CreateThread.hThread,
                                           de->u.CreateThread.lpStartAddress,
                                           de->u.CreateThread.lpThreadLocalBase);
        if (!DEBUG_CurrThread)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Couldn't create thread\n");
            break;
        }
        DEBUG_InitCurrThread();
        break;

    case CREATE_PROCESS_DEBUG_EVENT:
        DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer),
                                       de->u.CreateProcessInfo.hProcess,
                                       de->u.CreateProcessInfo.lpImageName);

        /* FIXME unicode ? de->u.CreateProcessInfo.fUnicode */
        DEBUG_Printf(DBG_CHN_TRACE, "%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);

        DEBUG_CurrProcess = DEBUG_AddProcess(de->dwProcessId,
                                             de->u.CreateProcessInfo.hProcess,
                                             buffer[0] ? buffer : "<Debugged Process>");
        if (DEBUG_CurrProcess == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Couldn't create process\n");
            break;
        }

        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: create thread I @%08lx\n",
                     de->dwProcessId, de->dwThreadId,
                     (unsigned long)(LPVOID)de->u.CreateProcessInfo.lpStartAddress);

        DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess,
                                           de->dwThreadId,
                                           de->u.CreateProcessInfo.hThread,
                                           de->u.CreateProcessInfo.lpStartAddress,
                                           de->u.CreateProcessInfo.lpThreadLocalBase);
        if (!DEBUG_CurrThread)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Couldn't create thread\n");
            break;
        }

        DEBUG_InitCurrProcess();
        DEBUG_InitCurrThread();

        /* module is either PE, NE or ELF module (for WineLib), but all
         * are loaded with wine, so load its symbols, then the main module
         */
        do
        {
            char*   ptr = getenv("WINELOADER");

            if (!ptr || DEBUG_ReadExecutableDbgInfo( ptr ) == DIL_ERROR)
                DEBUG_ReadExecutableDbgInfo( "wine" );
        } while (0);

        DEBUG_LoadModule32(DEBUG_CurrProcess->imageName, de->u.CreateProcessInfo.hFile,
                           (DWORD)de->u.CreateProcessInfo.lpBaseOfImage);

        break;

    case EXIT_THREAD_DEBUG_EVENT:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exit thread (%ld)\n",
                     de->dwProcessId, de->dwThreadId, de->u.ExitThread.dwExitCode);

        if (DEBUG_CurrThread == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
            break;
        }
        /* FIXME: remove break point set on thread startup */
        DEBUG_DelThread(DEBUG_CurrThread);
        break;

    case EXIT_PROCESS_DEBUG_EVENT:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: exit process (%ld)\n",
                     de->dwProcessId, de->dwThreadId, de->u.ExitProcess.dwExitCode);

        if (DEBUG_CurrProcess == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Unknown process\n");
            break;
        }
        /* just in case */
        DEBUG_SetBreakpoints(FALSE);
        /* kill last thread */
        DEBUG_DelThread(DEBUG_CurrProcess->threads);
        DEBUG_DelProcess(DEBUG_CurrProcess);

        DEBUG_Printf(DBG_CHN_MESG, "Process of pid=%08lx has terminated\n", DEBUG_CurrPid);
        break;

    case LOAD_DLL_DEBUG_EVENT:
        if (DEBUG_CurrThread == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
            break;
        }
        DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer),
                                       DEBUG_CurrThread->process->handle,
                                       de->u.LoadDll.lpImageName);

        /* FIXME unicode: de->u.LoadDll.fUnicode */
        DEBUG_Printf(DBG_CHN_TRACE, "%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);
        _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);
            ret = DEBUG_FetchContext();
        }
        break;

    case UNLOAD_DLL_DEBUG_EVENT:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: unload DLL @%08lx\n", de->dwProcessId, de->dwThreadId,
                     (unsigned long)de->u.UnloadDll.lpBaseOfDll);
        break;

    case OUTPUT_DEBUG_STRING_EVENT:
        if (DEBUG_CurrThread == NULL)
        {
            DEBUG_Printf(DBG_CHN_ERR, "Unknown thread\n");
            break;
        }

        DEBUG_ProcessGetString(buffer, sizeof(buffer),
                               DEBUG_CurrThread->process->handle,
                               de->u.DebugString.lpDebugStringData);

        /* FIXME unicode de->u.DebugString.fUnicode ? */
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: output debug string (%s)\n",
                     de->dwProcessId, de->dwThreadId, buffer);
        break;

    case RIP_EVENT:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: rip error=%ld type=%ld\n",
                     de->dwProcessId, de->dwThreadId, de->u.RipInfo.dwError,
                     de->u.RipInfo.dwType);
        break;

    default:
        DEBUG_Printf(DBG_CHN_TRACE, "%08lx:%08lx: unknown event (%ld)\n",
                     de->dwProcessId, de->dwThreadId, de->dwDebugEventCode);
    }

    return ret;
}

static void                     DEBUG_ResumeDebuggee(DWORD cont)
{
    if (DEBUG_InException)
    {
        DEBUG_ExceptionEpilog();
#if 1
        DEBUG_Printf(DBG_CHN_TRACE,
                     "Exiting debugger      PC=%lx EFL=%08lx mode=%d count=%d\n",
#ifdef __i386__
                     DEBUG_context.Eip, DEBUG_context.EFlags,
#else
                     0L, 0L,
#endif
                     DEBUG_CurrThread->exec_mode, DEBUG_CurrThread->exec_count);
#endif
        if (DEBUG_CurrThread)
        {
            if (!SetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context))
                DEBUG_Printf(DBG_CHN_MESG, "Cannot set ctx on %lu\n", DEBUG_CurrTid);
            DEBUG_CurrThread->wait_for_first_exception = 0;
        }
    }
    DEBUG_InteractiveP = FALSE;
    if (!ContinueDebugEvent(DEBUG_CurrPid, DEBUG_CurrTid, cont))
        DEBUG_Printf(DBG_CHN_MESG, "Cannot continue on %lu (%lu)\n",
                     DEBUG_CurrTid, cont);
}

void                            DEBUG_WaitNextException(DWORD cont, int count, int mode)
{
    DEBUG_EVENT         de;

    if (cont == DBG_CONTINUE)
    {
        DEBUG_CurrThread->exec_count = count;
        DEBUG_CurrThread->exec_mode = mode;
    }
    DEBUG_ResumeDebuggee(cont);

    while (DEBUG_CurrProcess && WaitForDebugEvent(&de, INFINITE))
    {
        if (DEBUG_HandleDebugEvent(&de)) break;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
    }
    if (!DEBUG_CurrProcess) return;
    DEBUG_InteractiveP = TRUE;
#if 1
    DEBUG_Printf(DBG_CHN_TRACE,
                 "Entering debugger     PC=%lx EFL=%08lx mode=%d count=%d\n",
#ifdef __i386__
                 DEBUG_context.Eip, DEBUG_context.EFlags,
#else
                 0L, 0L,
#endif
                 DEBUG_CurrThread->exec_mode, DEBUG_CurrThread->exec_count);
#endif
}

static	DWORD	DEBUG_MainLoop(void)
{
    DEBUG_EVENT		de;

    DEBUG_Printf(DBG_CHN_MESG, "WineDbg starting on pid %lx\n", DEBUG_CurrPid);

    /* wait for first exception */
    while (WaitForDebugEvent(&de, INFINITE))
    {
        if (DEBUG_HandleDebugEvent(&de)) break;
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
    }
    if (local_mode == automatic_mode)
    {
        /* print some extra information */
        DEBUG_Printf(DBG_CHN_MESG, "Modules:\n");
        DEBUG_WalkModules();
        DEBUG_Printf(DBG_CHN_MESG, "Threads:\n");
        DEBUG_WalkThreads();
    }
    else
    {
        DEBUG_InteractiveP = TRUE;
        DEBUG_Parser(NULL);
    }
    DEBUG_Printf(DBG_CHN_MESG, "WineDbg terminated on pid %lx\n", DEBUG_CurrPid);

    return 0;
}

static	BOOL	DEBUG_Start(LPSTR cmdLine)
{
    PROCESS_INFORMATION	info;
    STARTUPINFOA	startup;

    memset(&startup, 0, sizeof(startup));
    startup.cb = sizeof(startup);
    startup.dwFlags = STARTF_USESHOWWINDOW;
    startup.wShowWindow = SW_SHOWNORMAL;

    /* FIXME: shouldn't need the CREATE_NEW_CONSOLE, but as usual CUI:s need it
     * while GUI:s don't
     */
    if (!CreateProcess(NULL, cmdLine, NULL, NULL,
		       FALSE, DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS|CREATE_NEW_CONSOLE,
                       NULL, NULL, &startup, &info))
    {
	DEBUG_Printf(DBG_CHN_MESG, "Couldn't start process '%s'\n", cmdLine);
	return FALSE;
    }
    DEBUG_CurrPid = info.dwProcessId;
    if (!(DEBUG_CurrProcess = DEBUG_AddProcess(DEBUG_CurrPid, 0, NULL))) return FALSE;

    return TRUE;
}

void	DEBUG_Run(const char* args)
{
    DBG_MODULE*	wmod = DEBUG_GetProcessMainModule(DEBUG_CurrProcess);
    const char* pgm = (wmod) ? wmod->module_name : "none";

    if (args) {
	DEBUG_Printf(DBG_CHN_MESG, "Run (%s) with '%s'\n", pgm, args);
    } else {
	if (!DEBUG_LastCmdLine) {
	    DEBUG_Printf(DBG_CHN_MESG, "Cannot find previously used command line.\n");
	    return;
	}
	DEBUG_Start(DEBUG_LastCmdLine);
    }
}

static BOOL WINAPI DEBUG_CtrlCHandler(DWORD dwCtrlType)
{
    if (dwCtrlType == CTRL_C_EVENT)
    {
        DEBUG_Printf(DBG_CHN_MESG, "Ctrl-C: stopping debuggee\n");
        /* FIXME: since we likely have a single process, signal the first process
         * in list
         */
        return DEBUG_ProcessList && DebugBreakProcess(DEBUG_ProcessList->handle);
    }
    return FALSE;
}

static void DEBUG_InitConsole(void)
{
    DWORD	mode;

    /* keep it as a cuiexe for now, so that Wine won't touch the Unix stdin,
     * stdout and stderr streams
     */
    if (DBG_IVAR(UseXTerm))
    {
	FreeConsole();
	AllocConsole();
    }

    /* put the line editing mode with the nice emacs features (FIXME: could be triggered by a IVAR) */
    if (GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &mode))
        SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode | WINE_ENABLE_LINE_INPUT_EMACS);

    /* set our control-C handler */
    SetConsoleCtrlHandler(DEBUG_CtrlCHandler, TRUE);

    /* set our own title */
    SetConsoleTitle("Wine Debugger");
}

static int DEBUG_Usage(void)
{
    DEBUG_Printf(DBG_CHN_MESG, "Usage: winedbg [--debugmsg dbgoptions] [--auto] [--gdb] cmdline\n" );
    return 1;
}

int main(int argc, char** argv)
{
    DWORD	retv = 0;
    unsigned    gdb_flags = 0;

    /* Initialize the type handling stuff. */
    DEBUG_InitTypes();
    DEBUG_InitCVDataTypes();

    /* Initialize internal vars (types must have been initialized before) */
    if (!DEBUG_IntVarsRW(TRUE)) return -1;

    /* parse options */
    while (argc > 1 && argv[1][0] == '-')
    {
        if (!strcmp( argv[1], "--auto" ))
        {
            if (local_mode != none_mode) return DEBUG_Usage();
            local_mode = automatic_mode;
            /* force some internal variables */
            DBG_IVAR(UseXTerm) = 0;
            DBG_IVAR(BreakOnDllLoad) = 0;
            DBG_IVAR(ConChannelMask) = 0;
            DBG_IVAR(StdChannelMask) = DBG_CHN_MESG;
            argc--; argv++;
            continue;
        }
        if (!strcmp( argv[1], "--gdb" ))
        {
            if (local_mode != none_mode) return DEBUG_Usage();
            local_mode = gdb_mode;
            argc--; argv++;
            continue;
        }
        if (strcmp( argv[1], "--no-start") == 0 && local_mode == gdb_mode)
        {
            gdb_flags |= 1;
            argc--; argv++; /* as we don't use argv[0] */
            continue;
        }
        if (strcmp(argv[1], "--with-xterm") == 0 && local_mode == gdb_mode)
        {
            gdb_flags |= 2;
            argc--; argv++; /* as we don't use argv[0] */
            continue;
        }
        if (!strcmp( argv[1], "--debugmsg" ) && argv[2])
        {
            wine_dbg_parse_options( argv[2] );
            argc -= 2;
            argv += 2;
            continue;
        }
        return DEBUG_Usage();
    }

    if (local_mode == none_mode) local_mode = winedbg_mode;

    /* try the from <myself> pid */
    if (DEBUG_CurrPid == 0 && argc == 2)
    {
        char*   ptr;

        DEBUG_CurrPid = strtol(argv[1], &ptr, 10);
        if (DEBUG_CurrPid == 0 || ptr == NULL ||
            !DEBUG_Attach(DEBUG_CurrPid, local_mode != gdb_mode))
            DEBUG_CurrPid = 0;
    }

    /* try the from <myself> pid evt (Win32 JIT debugger) */
    if (DEBUG_CurrPid == 0 && argc == 3)
    {
	HANDLE	hEvent;
	DWORD	pid;
        char*   ptr;

	if ((pid = strtol(argv[1], &ptr, 10)) != 0 && ptr != NULL &&
            (hEvent = (HANDLE)strtol(argv[2], &ptr, 10)) != 0 && ptr != NULL)
        {
	    if (!DEBUG_Attach(pid, TRUE))
            {
		/* don't care about result */
		SetEvent(hEvent);
		goto leave;
	    }
	    if (!SetEvent(hEvent))
            {
		DEBUG_Printf(DBG_CHN_ERR, "Invalid event handle: %p\n", hEvent);
		goto leave;
	    }
            CloseHandle(hEvent);
	    DEBUG_CurrPid = pid;
	}
    }

    if (DEBUG_CurrPid == 0 && argc > 1)
    {
	int	i, len;
	LPSTR	cmdLine;

	if (!(cmdLine = DBG_alloc(len = 1))) goto oom_leave;
	cmdLine[0] = '\0';

	for (i = 1; i < argc; i++)
        {
	    len += strlen(argv[i]) + 1;
	    if (!(cmdLine = DBG_realloc(cmdLine, len))) goto oom_leave;
	    strcat(cmdLine, argv[i]);
	    cmdLine[len - 2] = ' ';
	    cmdLine[len - 1] = '\0';
	}

	if (!DEBUG_Start(cmdLine))
        {
	    DEBUG_Printf(DBG_CHN_MESG, "Couldn't start process '%s'\n", cmdLine);
	    goto leave;
	}
	DBG_free(DEBUG_LastCmdLine);
	DEBUG_LastCmdLine = cmdLine;
    }
    /* don't save local vars in gdb mode */
    if (local_mode == gdb_mode && DEBUG_CurrPid)
        return DEBUG_GdbRemote(gdb_flags);

    DEBUG_InitConsole();

    retv = DEBUG_MainLoop();
    /* don't save modified variables in auto mode */
    if (local_mode != automatic_mode) DEBUG_IntVarsRW(FALSE);

 leave:
    return retv;

 oom_leave:
    DEBUG_Printf(DBG_CHN_MESG, "Out of memory\n");
    goto leave;
}
