/**
 * =========================================================================
 * File        : wdbg.cpp
 * Project     : 0 A.D.
 * Description : Win32 debug support code and exception handler.
 * =========================================================================
 */

// license: GPL; see lib/license.txt

#include "precompiled.h"
#include "wdbg.h"

#include "lib/bits.h"
#include "win.h"
#include "wutil.h"


// protects the breakpoint helper thread.
static void lock()
{
    win_lock(WDBG_CS);
}

static void unlock()
{
    win_unlock(WDBG_CS);
}


static NT_TIB* get_tib()
{
#if CPU_IA32
    NT_TIB* tib;
    __asm
    {
        mov     eax, fs:[NT_TIB.Self]
        mov     [tib], eax
    }
    return tib;
#endif
}


//-----------------------------------------------------------------------------
// debug memory allocator
//-----------------------------------------------------------------------------

// check heap integrity (independently of mmgr).
// errors are reported by the CRT or via debug_display_error.
void debug_heap_check()
{
    int ret;
    __try
    {
        ret = _heapchk();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ret = _HEAPBADNODE;
    }

    if(ret != _HEAPOK)
        DISPLAY_ERROR(L"debug_heap_check: heap is corrupt");
}


// call at any time; from then on, the specified checks will be performed.
// if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing.
void debug_heap_enable(DebugHeapChecks what)
{
    // note: if mmgr is enabled, crtdbg.h will not have been included.
    // in that case, we do nothing here - this interface is too basic to
    // control mmgr; use its API instead.
#if !CONFIG_USE_MMGR && HAVE_VC_DEBUG_ALLOC
    uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
    switch(what)
    {
    case DEBUG_HEAP_NONE:
        // note: do not set flags to zero because we might trash some
        // important flag value.
        flags &= ~(_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF |
                   _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
        break;
    case DEBUG_HEAP_NORMAL:
        flags |= _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF;
        break;
    case DEBUG_HEAP_ALL:
        flags |= (_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF |
                  _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
        break;
    default:
        debug_assert("debug_heap_enable: invalid what");
    }
    _CrtSetDbgFlag(flags);
#else
    UNUSED2(what);
#endif // HAVE_DEBUGALLOC
}


//-----------------------------------------------------------------------------
// thread suspension

// suspend a thread, execute a user callback, revive the thread.

// to avoid deadlock, be VERY CAREFUL to avoid anything that may block,
// including locks taken by the OS (e.g. malloc, GetProcAddress).
typedef LibError (*WhileSuspendedFunc)(HANDLE hThread, void* user_arg);

struct WhileSuspendedParam
{
    HANDLE hThread;
    WhileSuspendedFunc func;
    void* user_arg;
};


static void* while_suspended_thread_func(void* user_arg)
{
    debug_set_thread_name("suspender");

    WhileSuspendedParam* param = (WhileSuspendedParam*)user_arg;

    DWORD err = SuspendThread(param->hThread);
    // abort, since GetThreadContext only works if the target is suspended.
    if(err == (DWORD)-1)
    {
        debug_warn("while_suspended_thread_func: SuspendThread failed");
        return (void*)(intptr_t)-1;
    }
    // target is now guaranteed to be suspended,
    // since the Windows counter never goes negative.

    LibError ret = param->func(param->hThread, param->user_arg);

    WARN_IF_FALSE(ResumeThread(param->hThread));

    return (void*)(intptr_t)ret;
}


static LibError call_while_suspended(WhileSuspendedFunc func, void* user_arg)
{
    int err;

    // we need a real HANDLE to the target thread for use with
    // Suspend|ResumeThread and GetThreadContext.
    // alternative: DuplicateHandle on the current thread pseudo-HANDLE.
    // this way is a bit more obvious/simple.
    const DWORD access = THREAD_GET_CONTEXT|THREAD_SET_CONTEXT|THREAD_SUSPEND_RESUME;
    HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId());
    if(hThread == INVALID_HANDLE_VALUE)
        WARN_RETURN(ERR::FAIL);

    WhileSuspendedParam param = { hThread, func, user_arg };

    pthread_t thread;
    WARN_ERR(pthread_create(&thread, 0, while_suspended_thread_func, &param));

    void* ret;
    err = pthread_join(thread, &ret);
    debug_assert(err == 0 && ret == 0);

    return (LibError)(intptr_t)ret;
}


//-----------------------------------------------------------------------------
// breakpoints
//-----------------------------------------------------------------------------

// breakpoints are set by storing the address of interest in a
// debug register and marking it 'enabled'.
//
// the first problem is, they are only accessible from Ring0;
// we get around this by updating their values via SetThreadContext.
// that in turn requires we suspend the current thread,
// spawn a helper to change the registers, and resume.

// parameter passing to helper thread. currently static storage,
// but the struct simplifies switching to a queue later.
struct BreakInfo
{
    uintptr_t addr;
    DbgBreakType type;

    // determines what brk_thread_func will do.
    // set/reset by debug_remove_all_breaks.
    bool want_all_disabled;
};

static BreakInfo brk_info;

// Local Enable bits of all registers we enabled (used when restoring all).
static DWORD brk_all_local_enables;

// IA-32 limit; will need to update brk_enable_in_ctx if this changes.
static const uint MAX_BREAKPOINTS = 4;


// remove all breakpoints enabled by debug_set_break from <context>.
// called while target is suspended.
static LibError brk_disable_all_in_ctx(BreakInfo* UNUSED(bi), CONTEXT* context)
{
    context->Dr7 &= ~brk_all_local_enables;
    return INFO::OK;
}


// find a free register, set type according to <bi> and
// mark it as enabled in <context>.
// called while target is suspended.
static LibError brk_enable_in_ctx(BreakInfo* bi, CONTEXT* context)
{
    uint reg;   // index (0..3) of first free reg
    uint LE;    // local enable bit for <reg>

    // find free debug register.
    for(reg = 0; reg < MAX_BREAKPOINTS; reg++)
    {
        LE = BIT(reg*2);
        // .. this one is currently not in use.
        if((context->Dr7 & LE) == 0)
            goto have_reg;
    }
    WARN_RETURN(ERR::LIMIT);
have_reg:

    // store breakpoint address in debug register.
    DWORD addr = (DWORD)bi->addr;
    // .. note: treating Dr0..Dr3 as an array is unsafe due to
    //    possible struct member padding.
    switch(reg)
    {
    case 0: context->Dr0 = addr; break;
    case 1: context->Dr1 = addr; break;
    case 2: context->Dr2 = addr; break;
    case 3: context->Dr3 = addr; break;
    NODEFAULT;
    }

    // choose breakpoint settings:
    // .. type
    uint rw = 0;
    switch(bi->type)
    {
    case DBG_BREAK_CODE:
        rw = 0; break;
    case DBG_BREAK_DATA:
        rw = 1; break;
    case DBG_BREAK_DATA_WRITE:
        rw = 3; break;
    default:
        debug_warn("invalid type");
    }
    // .. length (determine from addr's alignment).
    //    note: IA-32 requires len=0 for code breakpoints.
    uint len = 0;
    if(bi->type != DBG_BREAK_CODE)
    {
        const uint alignment = (uint)(bi->addr % 4);
        // assume 2 byte range
        if(alignment == 2)
            len = 1;
        // assume 4 byte range
        else if(alignment == 0)
            len = 3;
        // else: 1 byte range; len already set to 0
    }

    // update Debug Control register
    const uint shift = (16 + reg*4);
    // .. clear previous contents of this reg's field
    //    (in case the previous user didn't do so on disabling).
    context->Dr7 &= ~(0xFu << shift);
    // .. write settings
    context->Dr7 |= ((len << 2)|rw) << shift;
    // .. mark as enabled
    context->Dr7 |= LE;

    brk_all_local_enables |= LE;
    return INFO::OK;
}


// carry out the request stored in the BreakInfo* parameter.
// called while target is suspended.
static LibError brk_do_request(HANDLE hThread, void* arg)
{
    LibError ret;
    BreakInfo* bi = (BreakInfo*)arg;

    CONTEXT context;
    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    if(!GetThreadContext(hThread, &context))
        WARN_RETURN(ERR::FAIL);

#if CPU_IA32
    if(bi->want_all_disabled)
        ret = brk_disable_all_in_ctx(bi, &context);
    else
        ret = brk_enable_in_ctx     (bi, &context);
#else
#error "port"
#endif

    if(!SetThreadContext(hThread, &context))
        WARN_RETURN(ERR::FAIL);

    RETURN_ERR(ret);
    return INFO::OK;
}


// arrange for a debug exception to be raised when <addr> is accessed
// according to <type>.
// for simplicity, the length (range of bytes to be checked) is
// derived from addr's alignment, and is typically 1 machine word.
// breakpoints are a limited resource (4 on IA-32); abort and
// return ERR::LIMIT if none are available.
LibError debug_set_break(void* p, DbgBreakType type)
{
    lock();

    brk_info.addr = (uintptr_t)p;
    brk_info.type = type;
    LibError ret = call_while_suspended(brk_do_request, &brk_info);

    unlock();
    return ret;
}


// remove all breakpoints that were set by debug_set_break.
// important, since these are a limited resource.
LibError debug_remove_all_breaks()
{
    lock();

    brk_info.want_all_disabled = true;
    LibError ret = call_while_suspended(brk_do_request, &brk_info);
    brk_info.want_all_disabled = false;

    unlock();
    return ret;
}


//-----------------------------------------------------------------------------

// return 1 if the pointer appears to be totally bogus, otherwise 0.
// this check is not authoritative (the pointer may be "valid" but incorrect)
// but can be used to filter out obviously wrong values in a portable manner.
int debug_is_pointer_bogus(const void* p)
{
#if CPU_IA32
    if(p < (void*)0x10000)
        return true;
    if(p >= (void*)(uintptr_t)0x80000000)
        return true;
#endif

    // notes:
    // - we don't check alignment because nothing can be assumed about a
    //   string pointer and we mustn't reject any actually valid pointers.
    // - nor do we bother checking the address against known stack/heap areas
    //   because that doesn't cover everything (e.g. DLLs, VirtualAlloc).
    // - cannot use IsBadReadPtr because it accesses the mem
    //   (false alarm for reserved address space).

    return false;
}


bool debug_is_code_ptr(void* p)
{
    uintptr_t addr = (uintptr_t)p;
    // totally invalid pointer
    if(debug_is_pointer_bogus(p))
        return false;
    // comes before load address
    static const HMODULE base = GetModuleHandle(0);
    if(addr < (uintptr_t)base)
        return false;

    return true;
}


bool debug_is_stack_ptr(void* p)
{
    uintptr_t addr = (uintptr_t)p;
    // totally invalid pointer
    if(debug_is_pointer_bogus(p))
        return false;
    // not aligned
    if(addr % sizeof(void*))
        return false;
    // out of bounds (note: IA-32 stack grows downwards)
    NT_TIB* tib = get_tib();
    if(!(tib->StackLimit < p && p < tib->StackBase))
        return false;

    return true;
}


void debug_puts(const char* text)
{
    OutputDebugStringA(text);
}


// inform the debugger of the current thread's description, which it then
// displays instead of just the thread handle.
void wdbg_set_thread_name(const char* name)
{
    // we pass information to the debugger via a special exception it
    // swallows. if not running under one, bail now to avoid
    // "first chance exception" warnings.
    if(!IsDebuggerPresent())
        return;

    // presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999.
    const struct ThreadNameInfo
    {
        DWORD type;
        const char* name;
        DWORD thread_id;    // any valid ID or -1 for current thread
        DWORD flags;
    }
    info = { 0x1000, name, (DWORD)-1, 0 };
    __try
    {
        RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        // if we get here, the debugger didn't handle the exception.
        debug_warn("thread name hack doesn't work under this debugger");
    }
}