/*
 * PROJECT:         ReactOS Win32 Base API
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            dll/win32/kernel32/client/debugger.c
 * PURPOSE:         Wrappers for the NT Debug Implementation
 * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
 */

/* INCLUDES *****************************************************************/

#include <k32.h>

#include <ndk/dbgkfuncs.h>

#define NDEBUG
#include <debug.h>

typedef struct _DBGSS_THREAD_DATA
{
    struct _DBGSS_THREAD_DATA *Next;
    HANDLE ThreadHandle;
    HANDLE ProcessHandle;
    DWORD ProcessId;
    DWORD ThreadId;
    BOOLEAN HandleMarked;
} DBGSS_THREAD_DATA, *PDBGSS_THREAD_DATA;

#define DbgSsSetThreadData(d) \
    NtCurrentTeb()->DbgSsReserved[0] = d

#define DbgSsGetThreadData() \
    ((PDBGSS_THREAD_DATA)NtCurrentTeb()->DbgSsReserved[0])

/* PRIVATE FUNCTIONS *********************************************************/

static
HANDLE
K32CreateDBMonMutex(void)
{
    static SID_IDENTIFIER_AUTHORITY siaNTAuth = {SECURITY_NT_AUTHORITY};
    static SID_IDENTIFIER_AUTHORITY siaWorldAuth = {SECURITY_WORLD_SID_AUTHORITY};
    HANDLE hMutex;

    /* SIDs to be used in the DACL */
    PSID psidSystem = NULL;
    PSID psidAdministrators = NULL;
    PSID psidEveryone = NULL;

    /* buffer for the DACL */
    PVOID pDaclBuf = NULL;

    /* minimum size of the DACL: an ACL descriptor and three ACCESS_ALLOWED_ACE
       headers. We'll add the size of SIDs when we'll know it
    */
    SIZE_T nDaclBufSize =
         sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE) -
                        sizeof(((ACCESS_ALLOWED_ACE*)0)->SidStart)) * 3;

    /* security descriptor of the mutex */
    SECURITY_DESCRIPTOR sdMutexSecurity;

    /* attributes of the mutex object we'll create */
    SECURITY_ATTRIBUTES saMutexAttribs = {sizeof(saMutexAttribs),
                                          &sdMutexSecurity,
                                          TRUE};

    NTSTATUS nErrCode;

    /* first, try to open the mutex */
    hMutex = OpenMutexW (SYNCHRONIZE | READ_CONTROL | MUTANT_QUERY_STATE,
                         TRUE,
                         L"DBWinMutex");

    if (hMutex != NULL)
    {
        /* success */
        return hMutex;
    }
    /* error other than the mutex not being found */
    else if (GetLastError() != ERROR_FILE_NOT_FOUND)
    {
        /* failure */
        return NULL;
    }

    /* if the mutex doesn't exist, create it */

    /* first, set up the mutex security */
    /* allocate the NT AUTHORITY\SYSTEM SID */
    nErrCode = RtlAllocateAndInitializeSid(&siaNTAuth,
                                           1,
                                           SECURITY_LOCAL_SYSTEM_RID,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           &psidSystem);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* allocate the BUILTIN\Administrators SID */
    nErrCode = RtlAllocateAndInitializeSid(&siaNTAuth,
                                           2,
                                           SECURITY_BUILTIN_DOMAIN_RID,
                                           DOMAIN_ALIAS_RID_ADMINS,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           &psidAdministrators);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* allocate the Everyone SID */
    nErrCode = RtlAllocateAndInitializeSid(&siaWorldAuth,
                                           1,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           0,
                                           &psidEveryone);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* allocate space for the SIDs too */
    nDaclBufSize += RtlLengthSid(psidSystem);
    nDaclBufSize += RtlLengthSid(psidAdministrators);
    nDaclBufSize += RtlLengthSid(psidEveryone);

    /* allocate the buffer for the DACL */
    pDaclBuf = GlobalAlloc(GMEM_FIXED, nDaclBufSize);

    /* failure */
    if (pDaclBuf == NULL) goto l_Cleanup;

    /* create the DACL */
    nErrCode = RtlCreateAcl(pDaclBuf, nDaclBufSize, ACL_REVISION);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* grant the minimum required access to Everyone */
    nErrCode = RtlAddAccessAllowedAce(pDaclBuf,
                                      ACL_REVISION,
                                      SYNCHRONIZE |
                                      READ_CONTROL |
                                      MUTANT_QUERY_STATE,
                                      psidEveryone);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* grant full access to BUILTIN\Administrators */
    nErrCode = RtlAddAccessAllowedAce(pDaclBuf,
                                      ACL_REVISION,
                                      MUTANT_ALL_ACCESS,
                                      psidAdministrators);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* grant full access to NT AUTHORITY\SYSTEM */
    nErrCode = RtlAddAccessAllowedAce(pDaclBuf,
                                      ACL_REVISION,
                                      MUTANT_ALL_ACCESS,
                                      psidSystem);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* create the security descriptor */
    nErrCode = RtlCreateSecurityDescriptor(&sdMutexSecurity,
                                           SECURITY_DESCRIPTOR_REVISION);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* set the descriptor's DACL to the ACL we created */
    nErrCode = RtlSetDaclSecurityDescriptor(&sdMutexSecurity,
                                            TRUE,
                                            pDaclBuf,
                                            FALSE);

    /* failure */
    if (!NT_SUCCESS(nErrCode)) goto l_Cleanup;

    /* create the mutex */
    hMutex = CreateMutexW(&saMutexAttribs, FALSE, L"DBWinMutex");

l_Cleanup:
    /* free the buffers */
    if (pDaclBuf) GlobalFree(pDaclBuf);
    if (psidEveryone) RtlFreeSid(psidEveryone);
    if (psidAdministrators) RtlFreeSid(psidAdministrators);
    if (psidSystem) RtlFreeSid(psidSystem);

    return hMutex;
}

VOID
WINAPI
SaveThreadHandle(IN DWORD dwProcessId,
                 IN DWORD dwThreadId,
                 IN HANDLE hThread)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Allocate a thread structure */
    ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
                                 0,
                                 sizeof(DBGSS_THREAD_DATA));
    if (!ThreadData) return;

    /* Fill it out */
    ThreadData->ThreadHandle = hThread;
    ThreadData->ProcessId = dwProcessId;
    ThreadData->ThreadId = dwThreadId;
    ThreadData->ProcessHandle = NULL;
    ThreadData->HandleMarked = FALSE;

    /* Link it */
    ThreadData->Next = DbgSsGetThreadData();
    DbgSsSetThreadData(ThreadData);
}

VOID
WINAPI
SaveProcessHandle(IN DWORD dwProcessId,
                  IN HANDLE hProcess)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Allocate a thread structure */
    ThreadData = RtlAllocateHeap(RtlGetProcessHeap(),
                                 0,
                                 sizeof(DBGSS_THREAD_DATA));
    if (!ThreadData) return;

    /* Fill it out */
    ThreadData->ProcessHandle = hProcess;
    ThreadData->ProcessId = dwProcessId;
    ThreadData->ThreadId = 0;
    ThreadData->ThreadHandle = NULL;
    ThreadData->HandleMarked = FALSE;

    /* Link it */
    ThreadData->Next = DbgSsGetThreadData();
    DbgSsSetThreadData(ThreadData);
}

VOID
WINAPI
MarkThreadHandle(IN DWORD dwThreadId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    for (ThreadData = DbgSsGetThreadData(); ThreadData; ThreadData = ThreadData->Next)
    {
        /* Check if this one matches */
        if (ThreadData->ThreadId == dwThreadId)
        {
            /* Mark the structure and break out */
            ThreadData->HandleMarked = TRUE;
            break;
        }
    }
}

VOID
WINAPI
MarkProcessHandle(IN DWORD dwProcessId)
{
    PDBGSS_THREAD_DATA ThreadData;

    /* Loop all thread data events */
    for (ThreadData = DbgSsGetThreadData(); ThreadData; ThreadData = ThreadData->Next)
    {
        /* Check if this one matches */
        if ((ThreadData->ProcessId == dwProcessId) && !(ThreadData->ThreadId))
        {
            /* Mark the structure and break out */
            ThreadData->HandleMarked = TRUE;
            break;
        }
    }
}

VOID
WINAPI
RemoveHandles(IN DWORD dwProcessId,
              IN DWORD dwThreadId)
{
    PDBGSS_THREAD_DATA *ThreadData;
    PDBGSS_THREAD_DATA ThisData;

    /* Loop all thread data events */
    ThreadData = (PDBGSS_THREAD_DATA*)NtCurrentTeb()->DbgSsReserved;
    ThisData = *ThreadData;
    while(ThisData)
    {
        /* Check if this one matches */
        if ((ThisData->HandleMarked) &&
            ((ThisData->ProcessId == dwProcessId) || (ThisData->ThreadId == dwThreadId)))
        {
            /* Close open handles */
            if (ThisData->ThreadHandle) CloseHandle(ThisData->ThreadHandle);
            if (ThisData->ProcessHandle) CloseHandle(ThisData->ProcessHandle);

            /* Unlink the thread data */
            *ThreadData = ThisData->Next;

            /* Free it*/
            RtlFreeHeap(RtlGetProcessHeap(), 0, ThisData);
        }
        else
        {
            /* Move to the next one */
            ThreadData = &ThisData->Next;
        }
        ThisData = *ThreadData;
    }
}

VOID
WINAPI
CloseAllProcessHandles(IN DWORD dwProcessId)
{
    PDBGSS_THREAD_DATA *ThreadData;
    PDBGSS_THREAD_DATA ThisData;

    /* Loop all thread data events */
    ThreadData = (PDBGSS_THREAD_DATA*)NtCurrentTeb()->DbgSsReserved;
    ThisData = *ThreadData;
    while(ThisData)
    {
        /* Check if this one matches */
        if (ThisData->ProcessId == dwProcessId)
        {
            /* Close open handles */
            if (ThisData->ThreadHandle) CloseHandle(ThisData->ThreadHandle);
            if (ThisData->ProcessHandle) CloseHandle(ThisData->ProcessHandle);

            /* Unlink the thread data */
            *ThreadData = ThisData->Next;

            /* Free it*/
            RtlFreeHeap(RtlGetProcessHeap(), 0, ThisData);
        }
        else
        {
            /* Move to the next one */
            ThreadData = &ThisData->Next;
        }
        ThisData = *ThreadData;
    }
}

HANDLE
WINAPI
ProcessIdToHandle(IN DWORD dwProcessId)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE Handle;
    CLIENT_ID ClientId;

    /* If we don't have a PID, look it up */
    if (dwProcessId == MAXDWORD) dwProcessId = (DWORD_PTR)CsrGetProcessId();

    /* Open a handle to the process */
    ClientId.UniqueThread = NULL;
    ClientId.UniqueProcess = UlongToHandle(dwProcessId);
    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
    Status = NtOpenProcess(&Handle,
                           PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
                           PROCESS_VM_WRITE | PROCESS_VM_READ |
                           PROCESS_SUSPEND_RESUME | PROCESS_QUERY_INFORMATION,
                           &ObjectAttributes,
                           &ClientId);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        BaseSetLastNTError(Status);
        return 0;
    }

    /* Return the handle */
    return Handle;
}

/* PUBLIC FUNCTIONS **********************************************************/

/*
 * @implemented
 */
BOOL
WINAPI
CheckRemoteDebuggerPresent(IN HANDLE hProcess,
                           OUT PBOOL pbDebuggerPresent)
{
    HANDLE DebugPort;
    NTSTATUS Status;

    /* Make sure we have an output and process*/
    if (!(pbDebuggerPresent) || !(hProcess))
    {
        /* Fail */
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    /* Check if the process has a debug object/port */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessDebugPort,
                                       &DebugPort,
                                       sizeof(DebugPort),
                                       NULL);
    if (NT_SUCCESS(Status))
    {
        /* Return the current state */
        *pbDebuggerPresent = DebugPort != NULL;
        return TRUE;
    }

    /* Otherwise, fail */
    BaseSetLastNTError(Status);
    return FALSE;
}

/*
 * @implemented
 */
BOOL
WINAPI
ContinueDebugEvent(IN DWORD dwProcessId,
                   IN DWORD dwThreadId,
                   IN DWORD dwContinueStatus)
{
    CLIENT_ID ClientId;
    NTSTATUS Status;

    /* Set the Client ID */
    ClientId.UniqueProcess = UlongToHandle(dwProcessId);
    ClientId.UniqueThread = UlongToHandle(dwThreadId);

    /* Continue debugging */
    Status = DbgUiContinue(&ClientId, dwContinueStatus);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Remove the process/thread handles */
    RemoveHandles(dwProcessId, dwThreadId);

    /* Success */
    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
DebugActiveProcess(IN DWORD dwProcessId)
{
    NTSTATUS Status, Status1;
    HANDLE Handle;

    /* Connect to the debugger */
    Status = DbgUiConnectToDbg();
    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Get the process handle */
    Handle = ProcessIdToHandle(dwProcessId);
    if (!Handle) return FALSE;

    /* Now debug the process */
    Status = DbgUiDebugActiveProcess(Handle);
    
    /* Close the handle since we're done */
    Status1 = NtClose(Handle);
    ASSERT(NT_SUCCESS(Status1));

    /* Check if debugging worked */
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
DebugActiveProcessStop(IN DWORD dwProcessId)
{
    NTSTATUS Status, Status1;
    HANDLE Handle;

    /* Get the process handle */
    Handle = ProcessIdToHandle(dwProcessId);
    if (!Handle) return FALSE;

    /* Close all the process handles */
    CloseAllProcessHandles(dwProcessId);

    /* Now stop debugging the process */
    Status = DbgUiStopDebugging(Handle);
    Status1 = NtClose(Handle);
    ASSERT(NT_SUCCESS(Status1));

    /* Check for failure */
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        SetLastError(ERROR_ACCESS_DENIED);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
DebugBreakProcess(IN HANDLE Process)
{
    NTSTATUS Status;

    /* Send the breakin request */
    Status = DbgUiIssueRemoteBreakin(Process);
    if (!NT_SUCCESS(Status))
    {
        /* Failure */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
DebugSetProcessKillOnExit(IN BOOL KillOnExit)
{
    HANDLE Handle;
    NTSTATUS Status;
    ULONG State;

    /* Get the debug object */
    Handle = DbgUiGetThreadDebugObject();
    if (!Handle)
    {
        /* Fail */
        BaseSetLastNTError(STATUS_INVALID_HANDLE);
        return FALSE;
    }

    /* Now set the kill-on-exit state */
    State = KillOnExit != 0;
    Status = NtSetInformationDebugObject(Handle,
                                         DebugObjectKillProcessOnExitInformation,
                                         &State,
                                         sizeof(State),
                                         NULL);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Success */
    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
IsDebuggerPresent(VOID)
{
    return (BOOL)NtCurrentPeb()->BeingDebugged;
}

/*
 * @implemented
 */
BOOL
WINAPI
WaitForDebugEvent(IN LPDEBUG_EVENT lpDebugEvent,
                  IN DWORD dwMilliseconds)
{
    LARGE_INTEGER WaitTime;
    PLARGE_INTEGER Timeout;
    DBGUI_WAIT_STATE_CHANGE WaitStateChange;
    NTSTATUS Status;

    /* Convert to NT Timeout */
    Timeout = BaseFormatTimeOut(&WaitTime, dwMilliseconds);

    /* Loop while we keep getting interrupted */
    do
    {
        /* Call the native API */
        Status = DbgUiWaitStateChange(&WaitStateChange, Timeout);
    } while ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC));

    /* Check if the wait failed */
    if (!(NT_SUCCESS(Status)) || (Status == DBG_UNABLE_TO_PROVIDE_HANDLE))
    {
        /* Set the error code and quit */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Check if we timed out */
    if (Status == STATUS_TIMEOUT)
    {
        /* Fail with a timeout error */
        SetLastError(ERROR_SEM_TIMEOUT);
        return FALSE;
    }

    /* Convert the structure */
    Status = DbgUiConvertStateChangeStructure(&WaitStateChange, lpDebugEvent);
    if (!NT_SUCCESS(Status))
    {
        /* Set the error code and quit */
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* Check what kind of event this was */
    switch (lpDebugEvent->dwDebugEventCode)
    {
        /* New thread was created */
        case CREATE_THREAD_DEBUG_EVENT:

            /* Setup the thread data */
            SaveThreadHandle(lpDebugEvent->dwProcessId,
                             lpDebugEvent->dwThreadId,
                             lpDebugEvent->u.CreateThread.hThread);
            break;

        /* New process was created */
        case CREATE_PROCESS_DEBUG_EVENT:

            /* Setup the process data */
            SaveProcessHandle(lpDebugEvent->dwProcessId,
                              lpDebugEvent->u.CreateProcessInfo.hProcess);

            /* Setup the thread data */
            SaveThreadHandle(lpDebugEvent->dwProcessId,
                             lpDebugEvent->dwThreadId,
                             lpDebugEvent->u.CreateProcessInfo.hThread);
            break;

        /* Process was exited */
        case EXIT_PROCESS_DEBUG_EVENT:

            /* Mark the thread data as such and fall through */
            MarkProcessHandle(lpDebugEvent->dwProcessId);

        /* Thread was exited */
        case EXIT_THREAD_DEBUG_EVENT:

            /* Mark the thread data */
            MarkThreadHandle(lpDebugEvent->dwThreadId);
            break;
    
        /* Nothing to do */
        case EXCEPTION_DEBUG_EVENT:
        case LOAD_DLL_DEBUG_EVENT:
        case UNLOAD_DLL_DEBUG_EVENT:
        case OUTPUT_DEBUG_STRING_EVENT:
        case RIP_EVENT:
            break;

        /* Fail anything else */
        default:
            return FALSE;
    }

    /* Return success */
    return TRUE;
}

/*
 * @implemented
 */
VOID
WINAPI
OutputDebugStringA(IN LPCSTR _OutputString)
{
    _SEH2_TRY
    {
        ULONG_PTR a_nArgs[2];

        a_nArgs[0] = (ULONG_PTR)(strlen(_OutputString) + 1);
        a_nArgs[1] = (ULONG_PTR)_OutputString;

        /* send the string to the user-mode debugger */
        RaiseException(DBG_PRINTEXCEPTION_C, 0, 2, a_nArgs);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        /* no user-mode debugger: try the systemwide debug message monitor, or the
           kernel debugger as a last resort */

        /* mutex used to synchronize invocations of OutputDebugString */
        static HANDLE s_hDBMonMutex = NULL;
        /* true if we already attempted to open/create the mutex */
        static BOOL s_bDBMonMutexTriedOpen = FALSE;

        /* local copy of the mutex handle */
        volatile HANDLE hDBMonMutex = s_hDBMonMutex;
        /* handle to the Section of the shared buffer */
        volatile HANDLE hDBMonBuffer = NULL;

        /* pointer to the mapped view of the shared buffer. It consist of the current
           process id followed by the message string */
        struct { DWORD ProcessId; CHAR Buffer[1]; } * pDBMonBuffer = NULL;

        /* event: signaled by the debug message monitor when OutputDebugString can write
           to the shared buffer */
        volatile HANDLE hDBMonBufferReady = NULL;

        /* event: to be signaled by OutputDebugString when it's done writing to the
           shared buffer */
        volatile HANDLE hDBMonDataReady = NULL;

        /* mutex not opened, and no previous attempts to open/create it */
        if (hDBMonMutex == NULL && !s_bDBMonMutexTriedOpen)
        {
            /* open/create the mutex */
            hDBMonMutex = K32CreateDBMonMutex();
            /* store the handle */
            s_hDBMonMutex = hDBMonMutex;
        }

        _SEH2_TRY
        {
            volatile PCHAR a_cBuffer = NULL;

            /* opening the mutex failed */
            if (hDBMonMutex == NULL)
            {
                /* remember next time */
                s_bDBMonMutexTriedOpen = TRUE;
            }
            /* opening the mutex succeeded */
            else
            {
                do
                {
                    /* synchronize with other invocations of OutputDebugString */
                    WaitForSingleObject(hDBMonMutex, INFINITE);

                    /* buffer of the system-wide debug message monitor */
                    hDBMonBuffer = OpenFileMappingW(SECTION_MAP_WRITE, FALSE, L"DBWIN_BUFFER");

                    /* couldn't open the buffer: send the string to the kernel debugger */
                    if (hDBMonBuffer == NULL) break;

                    /* map the buffer */
                    pDBMonBuffer = MapViewOfFile(hDBMonBuffer,
                                                 SECTION_MAP_READ | SECTION_MAP_WRITE,
                                                 0,
                                                 0,
                                                 0);

                    /* couldn't map the buffer: send the string to the kernel debugger */
                    if (pDBMonBuffer == NULL) break;

                    /* open the event signaling that the buffer can be accessed */
                    hDBMonBufferReady = OpenEventW(SYNCHRONIZE, FALSE, L"DBWIN_BUFFER_READY");

                    /* couldn't open the event: send the string to the kernel debugger */
                    if (hDBMonBufferReady == NULL) break;

                    /* open the event to be signaled when the buffer has been filled */
                    hDBMonDataReady = OpenEventW(EVENT_MODIFY_STATE, FALSE, L"DBWIN_DATA_READY");
                }
                while(0);

                /* we couldn't connect to the system-wide debug message monitor: send the
                   string to the kernel debugger */
                if (hDBMonDataReady == NULL) ReleaseMutex(hDBMonMutex);
            }

            _SEH2_TRY
            {
                /* size of the current output block */
                volatile SIZE_T nRoundLen;

                /* size of the remainder of the string */
                volatile SIZE_T nOutputStringLen;

                /* output the whole string */
                nOutputStringLen = strlen(_OutputString);

                do
                {
                    /* we're connected to the debug monitor:
                       write the current block to the shared buffer */
                    if (hDBMonDataReady)
                    {
                        /* wait a maximum of 10 seconds for the debug monitor
                           to finish processing the shared buffer */
                        if (WaitForSingleObject(hDBMonBufferReady, 10000) != WAIT_OBJECT_0)
                        {
                            /* timeout or failure: give up */
                            break;
                        }

                        /* write the process id into the buffer */
                        pDBMonBuffer->ProcessId = GetCurrentProcessId();

                        /* write only as many bytes as they fit in the buffer */
                        if (nOutputStringLen > (PAGE_SIZE - sizeof(DWORD) - 1))
                            nRoundLen = PAGE_SIZE - sizeof(DWORD) - 1;
                        else
                            nRoundLen = nOutputStringLen;

                        /* copy the current block into the buffer */
                        memcpy(pDBMonBuffer->Buffer, _OutputString, nRoundLen);

                        /* null-terminate the current block */
                        pDBMonBuffer->Buffer[nRoundLen] = 0;

                        /* signal that the data contains meaningful data and can be read */
                        SetEvent(hDBMonDataReady);
                    }
                    /* else, send the current block to the kernel debugger */
                    else
                    {
                        /* output in blocks of 512 characters */
                        a_cBuffer = (CHAR*)HeapAlloc(GetProcessHeap(), 0, 512);

                        if (!a_cBuffer)
                        {
                            DbgPrint("OutputDebugStringA: Failed\n");
                            break;
                        }

                        /* write a maximum of 511 bytes */
                        if (nOutputStringLen > 510)
                            nRoundLen = 510;
                        else
                            nRoundLen = nOutputStringLen;

                        /* copy the current block */
                        memcpy(a_cBuffer, _OutputString, nRoundLen);

                        /* null-terminate the current block */
                        a_cBuffer[nRoundLen] = 0;

                        /* send the current block to the kernel debugger */
                        DbgPrint("%s", a_cBuffer);

                        if (a_cBuffer)
                        {
                            HeapFree(GetProcessHeap(), 0, a_cBuffer);
                            a_cBuffer = NULL;
                        }
                    }

                    /* move to the next block */
                    _OutputString += nRoundLen;
                    nOutputStringLen -= nRoundLen;
                }
                /* repeat until the string has been fully output */
                while (nOutputStringLen > 0);
            }
            /* ignore access violations and let other exceptions fall through */
            _SEH2_EXCEPT((_SEH2_GetExceptionCode() == STATUS_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
            {
                if (a_cBuffer)
                    HeapFree(GetProcessHeap(), 0, a_cBuffer);

                /* string copied verbatim from Microsoft's kernel32.dll */
                DbgPrint("\nOutputDebugString faulted during output\n");
            }
            _SEH2_END;
        }
        _SEH2_FINALLY
        {
            /* close all the still open resources */
            if (hDBMonBufferReady) CloseHandle(hDBMonBufferReady);
            if (pDBMonBuffer) UnmapViewOfFile(pDBMonBuffer);
            if (hDBMonBuffer) CloseHandle(hDBMonBuffer);
            if (hDBMonDataReady) CloseHandle(hDBMonDataReady);

            /* leave the critical section */
            if (hDBMonDataReady != NULL)
                ReleaseMutex(hDBMonMutex);
        }
        _SEH2_END;
    }
    _SEH2_END;
}

/*
 * @implemented
 */
VOID
WINAPI
OutputDebugStringW(IN LPCWSTR OutputString)
{
    UNICODE_STRING UnicodeString;
    ANSI_STRING AnsiString;
    NTSTATUS Status;

    /* convert the string in ANSI */
    RtlInitUnicodeString(&UnicodeString, OutputString);
    Status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, TRUE);

    /* OutputDebugStringW always prints something, even if conversion fails */
    if (!NT_SUCCESS(Status)) AnsiString.Buffer = "";

    /* Output the converted string */
    OutputDebugStringA(AnsiString.Buffer);

    /* free the converted string */
    if (NT_SUCCESS(Status)) RtlFreeAnsiString(&AnsiString);
}

/* EOF */