/*
    Copyright (C) 1995-1997
        Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld
        email: rainer@mathematik.uni-bielefeld.de

    All rights reserved
*/

#include <rsxnt.h>
#include <string.h>

static int convert_exception(DWORD fault_no)
{
    switch (fault_no) {
        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:
            return SIGFPE;

        case EXCEPTION_BREAKPOINT:
        case EXCEPTION_SINGLE_STEP:
            return SIGTRAP;

        case EXCEPTION_ACCESS_VIOLATION:
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        case EXCEPTION_IN_PAGE_ERROR:
            return SIGSEGV;

        case EXCEPTION_DATATYPE_MISALIGNMENT:
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
        case EXCEPTION_INT_OVERFLOW:
        case EXCEPTION_PRIV_INSTRUCTION:
        case EXCEPTION_ILLEGAL_INSTRUCTION:
        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        case EXCEPTION_STACK_OVERFLOW:
        case EXCEPTION_INVALID_DISPOSITION:
            return SIGILL;

        default:
            return SIGUSR1;
    }
}

int _rsxnt_wait_debuggee(int *status)
{
    static unsigned char byte, bp;
    static LPVOID entry;

    EMXPROCESS *proc = _rsxnt_get_process_ptr();
    DEBUG_EVENT de;
    int tid;

    for (;;) {          /* loop until debuggee stopps */

        if (WaitForDebugEvent (&de, INFINITE)) {

            if (proc->debuggee.dwProcessId != de.dwProcessId) {
                MessageBox(GetActiveWindow(), "Bad child process id", 0, MB_OK);
                return FALSE;
            }
            for (tid = 0; tid < MAX_CLD_TID; tid++)
                if (proc->debuggee.dwThreadId[tid] == de.dwThreadId)
                    break;

            if (tid >= MAX_CLD_TID)
                proc->debuggee.hCurrentThread = (HANDLE) 0;
            else
                proc->debuggee.hCurrentThread = proc->debuggee.hThread[tid];

            proc->debuggee.dwCurrentThreadId = de.dwThreadId;

            switch (de.dwDebugEventCode)
                {
                case CREATE_PROCESS_DEBUG_EVENT:
                   {
                    DWORD n;
                    entry = de.u.CreateProcessInfo.lpStartAddress;
                    bp = 0xCC;
                    ReadProcessMemory(proc->debuggee.hProcess,
                            (LPCVOID) entry, &byte, 1, &n);
                    WriteProcessMemory(proc->debuggee.hProcess,
                            entry, &bp, 1, &n);
                   }
                    break;

                case EXIT_PROCESS_DEBUG_EVENT:
                    /* cleanup child */
                    ContinueDebugEvent (de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
                    proc->debuggee.wait_status = *status = de.u.ExitProcess.dwExitCode << 8;
                    proc->debuggee.status = DEB_INVALID;
                    return proc->debuggee.pid;

                case EXCEPTION_DEBUG_EVENT:
                   {
                    EXCEPTION_DEBUG_INFO *edi;
                    EXCEPTION_RECORD *er;
                    int signal;
                    CONTEXT Context;

                    edi = &de.u.Exception;
                    er = &de.u.Exception.ExceptionRecord;
                    signal = convert_exception(er->ExceptionCode);

                    if (proc->debuggee.status == DEB_INIT) {
                        if (er->ExceptionAddress != entry)
                            break;
                        else if (er->ExceptionAddress == entry) {
                            DWORD n;
                            proc->debuggee.status = DEB_INIT;
                            Context.ContextFlags = CONTEXT_CONTROL;
                            GetThreadContext(proc->debuggee.hThread[tid], &Context);
                            Context.Eip = (DWORD) er->ExceptionAddress;
                            Context.ContextFlags = CONTEXT_CONTROL;
                            SetThreadContext(proc->debuggee.hThread[tid], &Context);
                            WriteProcessMemory(proc->debuggee.hProcess,
                                        entry, &byte, 1, &n);
                        }
                    }

                    proc->debuggee.wait_status = *status = (signal << 8) | 127;
                    return proc->debuggee.pid;
                   }
                case CREATE_THREAD_DEBUG_EVENT:
                    for (tid = 0; tid < MAX_CLD_TID; tid++)
                        if (!proc->debuggee.dwThreadId[tid])
                            break;
                    if (tid < MAX_CLD_TID) {
                        proc->debuggee.dwThreadId[tid] = de.dwThreadId;
                        proc->debuggee.hThread[tid] = de.u.CreateThread.hThread;
                    }
                    break;

                case EXIT_THREAD_DEBUG_EVENT:
                    for (tid = 0; tid < MAX_CLD_TID; tid++)
                        if (!proc->debuggee.dwThreadId[tid])
                            break;
                    if (tid < MAX_CLD_TID) {
                        proc->debuggee.dwThreadId[tid] = 0;
                        proc->debuggee.hThread[tid] = NULL;
                    }
                    break;

                case LOAD_DLL_DEBUG_EVENT:
                case UNLOAD_DLL_DEBUG_EVENT:
                case OUTPUT_DEBUG_STRING_EVENT:
                default:
                    break;
                }
        }
        else {
            MessageBox(GetActiveWindow(), "WaitForDebugError", NULL, MB_OK);
            return -1;
        }
        ContinueDebugEvent (de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
    }
}

#define U_AR0  0xe0000000L
#define U_USER 0x0e000000L
#define OFF(structure, member) ((int)&((structure *)0)->member)
#define R_READ  1
#define R_WRITE 2
#define R_RDWR  3

struct ptrace_regs {
    int ptrace_off;
    int context_off;
    int context_flags;
    int access;
};

#define N_REGS_INTEL 17

static struct ptrace_regs ptrace_member[N_REGS_INTEL] =
{
    { OFF(REG386, gs),     OFF(CONTEXT, SegGs), CONTEXT_SEGMENTS, R_READ},
    { OFF(REG386, fs),     OFF(CONTEXT, SegFs), CONTEXT_SEGMENTS, R_READ},
    { OFF(REG386, es),     OFF(CONTEXT, SegEs), CONTEXT_SEGMENTS, R_READ},
    { OFF(REG386, ds),     OFF(CONTEXT, SegDs), CONTEXT_SEGMENTS, R_READ},
    { OFF(REG386, edi),    OFF(CONTEXT, Edi),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, esi),    OFF(CONTEXT, Esi),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, ebp),    OFF(CONTEXT, Ebp),   CONTEXT_CONTROL, R_RDWR},
    { OFF(REG386, esp),    OFF(CONTEXT, Esp),   CONTEXT_CONTROL, R_RDWR},
    { OFF(REG386, ebx),    OFF(CONTEXT, Ebx),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, edx),    OFF(CONTEXT, Edx),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, ecx),    OFF(CONTEXT, Ecx),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, eax),    OFF(CONTEXT, Eax),   CONTEXT_INTEGER, R_RDWR},
    { OFF(REG386, eip),    OFF(CONTEXT, Eip),   CONTEXT_CONTROL, R_RDWR},
    { OFF(REG386, cs),     OFF(CONTEXT, SegCs), CONTEXT_CONTROL, R_READ},
    { OFF(REG386, eflags), OFF(CONTEXT, EFlags),CONTEXT_CONTROL, R_RDWR},
    { OFF(REG386, emx_esp),OFF(CONTEXT, Esp),   CONTEXT_CONTROL, R_RDWR},
    { OFF(REG386, emx_ss), OFF(CONTEXT, SegSs), CONTEXT_CONTROL, R_READ}
};

static int get_user_off(int addr)
{
    int i;

    for (i = 0; i < N_REGS_INTEL; ++i)
        if (addr == ptrace_member[i].ptrace_off)
            return i;

    return -1;
}

static int get_context(int i, CONTEXT *pContext)
{
    if (ptrace_member[i].access | R_READ)
        return * (int *) (((int)pContext) + ptrace_member[i].context_off);
    else
        return 0;
}

static void set_context(int i, CONTEXT *pContext, int val)
{
    if (ptrace_member[i].access | R_WRITE)
        * (int *) (((int)pContext) + ptrace_member[i].context_off) = val;
}

#define FLAG_MASK 0x00000dd9L
#define SINGLE_STEP 0x100

int _rsxnt_do_ptrace(int request, int pid, int addr, int data, int *retv)
{
    EMXPROCESS *p = _rsxnt_get_process_ptr();
    CONTEXT Context;
    DWORD bytes, n;

    if (p->debuggee.pid != pid)
        return -ESRCH;

    *retv = 0;

    switch (request) {

        case PTRACE_PEEKTEXT:
        case PTRACE_PEEKDATA:
            bytes = 0;
            if (ReadProcessMemory(p->debuggee.hProcess, (LPCVOID) addr,
                &bytes, sizeof(DWORD), &n) == FALSE) {
                return -EINVAL;
            }
            else {
                *retv = bytes;
                return 0;
            }

        case PTRACE_POKETEXT:
        case PTRACE_POKEDATA:
            bytes = data;
            if (WriteProcessMemory(p->debuggee.hProcess,
                    (LPVOID) addr, &bytes, sizeof(DWORD), &n) == FALSE) {
                return -1;
            }
            else {
                *retv = data;
                return 0;
            }

        case PTRACE_EXIT:
            return 0;

        case PTRACE_PEEKUSER:
            if (addr == 48) {       /* get user regs addr */
                *retv = U_AR0 + U_USER;
                return 0;
            }
            else if (addr == 52) {  /* fpvalid */
                *retv = 0;
                return 0;
            }
            else if (addr == 56) {  /* fpstate (108 bytes) */
                *retv = 0;
                return 0;
            }
            else if (addr == 164) { /* fpstatus */
                *retv = 0;
                return 0;
            }
            else if (addr >= U_USER) {
                int i;

                addr -= U_USER;
                i = get_user_off(addr);
                if (i >= 0) {
                    Context.ContextFlags = ptrace_member[i].context_flags;
                    GetThreadContext(p->debuggee.hCurrentThread, &Context);
                    *retv = get_context(i, &Context);
                    return 0;
                }
                else { /* floating point */
                    return -EINVAL;
                }
            }
            else
                return -EINVAL;

        case PTRACE_POKEUSER:
            if (addr > U_USER) {
                int i;

                addr -= U_USER;
                i = get_user_off(addr);
                if (i >= 0) {
                    Context.ContextFlags = ptrace_member[i].context_flags;
                    GetThreadContext(p->debuggee.hCurrentThread, &Context);
                    set_context(i, &Context, data);
                    Context.ContextFlags = ptrace_member[i].context_flags;
                    SetThreadContext(p->debuggee.hCurrentThread, &Context);
                    return 0;
                }
                else { /* floating point */
                    return -EINVAL;
                }
            }
            else
                return -EINVAL;

        case PTRACE_STEP:
            Context.ContextFlags = CONTEXT_CONTROL;
            GetThreadContext(p->debuggee.hCurrentThread, &Context);

            Context.EFlags |= SINGLE_STEP;
            Context.ContextFlags = CONTEXT_CONTROL;
            SetThreadContext(p->debuggee.hCurrentThread, &Context);

            p->debuggee.wait_status = 0;
            p->debuggee.status = DEB_RUNNING;
            ContinueDebugEvent (p->debuggee.dwProcessId,
                        p->debuggee.dwCurrentThreadId, DBG_CONTINUE);
            return 0;

        case PTRACE_RESUME:
            FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));
            p->debuggee.wait_status = 0;
            p->debuggee.status = DEB_RUNNING;
            ContinueDebugEvent (p->debuggee.dwProcessId,
                    p->debuggee.dwCurrentThreadId, DBG_CONTINUE);
            return 0;

        case PTRACE_TRACEME:
        case PTRACE_SESSION:
        default:
            return -EINVAL;
    }
}
