//----------------------------------------------------------------------------
//
// AutoDump Plus support extension DLL.
//
// Copyright (C) Microsoft Corporation, 2000-2001.
//
//----------------------------------------------------------------------------

#include "dbgexts.h"

#define STATUS_CPP_EH_EXCEPTION 0xe06d7363

#define MAX_NAME 64
#define MAX_MACHINE 64
#define MAX_COMMENT 256

char g_BaseDir[MAX_PATH - 1];
char g_Machine[MAX_MACHINE];

struct PARAM
{
    ULONG Len;
    PSTR Buf;
};

PARAM g_DirMachParams[2] =
{
    sizeof(g_BaseDir), g_BaseDir,
    sizeof(g_Machine), g_Machine,
};

union LAST_EVENT_INFO_ALL
{
    DEBUG_LAST_EVENT_INFO_BREAKPOINT Breakpoint;
    DEBUG_LAST_EVENT_INFO_EXCEPTION Exception;
    DEBUG_LAST_EVENT_INFO_EXIT_THREAD ExitThread;
    DEBUG_LAST_EVENT_INFO_EXIT_PROCESS ExitProcess;
    DEBUG_LAST_EVENT_INFO_LOAD_MODULE LoadModule;
    DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE UnloadModule;
    DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR SystemError;
};

ULONG g_LastEventType;
LAST_EVENT_INFO_ALL g_LastEventInfo;
PSTR g_LastExName;
char g_UnknownExceptionName[MAX_NAME];
PSTR g_LastExChanceStr;

ULONG g_ProcessId;
char g_ProcessName[MAX_NAME];

struct EXCEPTION_NAME
{
    PSTR Name;
    ULONG Code;
};

EXCEPTION_NAME g_ExceptionNames[] =
{
    "Access Violation", STATUS_ACCESS_VIOLATION,
    "C++ EH Exception", STATUS_CPP_EH_EXCEPTION,
    "Invalid Handle Exception", STATUS_INVALID_HANDLE,
    "Stack Overflow", STATUS_STACK_OVERFLOW,
    NULL, 0,
};

PCSTR
GetParams(PCSTR Args, ULONG Count, PARAM* Params)
{
    PCSTR Start;
    size_t Len;
    ULONG Index = 0;
    
    while (Count-- > 0)
    {
        while (*Args == ' ' || *Args == '\t')
        {
            Args++;
        }
        Start = Args;
        while (*Args && *Args != ' ' && *Args != '\t')
        {
            Args++;
        }
        Len = (ULONG)(Args - Start);
        if ((Count > 0 && !*Args) || Len >= Params[Index].Len)
        {
            ExtErr("Invalid extension command arguments\n");
            return NULL;
        }
        memcpy(Params[Index].Buf, Start, Len);
        Params[Index].Buf[Len] = 0;

        Index++;
    }

    return Args;
}

HRESULT
GetProcessInfo(void)
{
    HRESULT Status;
    
    if ((Status = g_ExtSystem->
         GetCurrentProcessSystemId(&g_ProcessId)) != S_OK)
    {
        ExtErr("Unable to get current process ID\n");
        return Status;
    }
    
    if (FAILED(g_ExtClient->
               GetRunningProcessDescription(0, g_ProcessId,
                                            DEBUG_PROC_DESC_NO_PATHS,
                                            NULL, 0, NULL,
                                            g_ProcessName, MAX_NAME,
                                            NULL)))
    {
        g_ProcessName[0] = 0;
    }
    else
    {
        PSTR Scan;
        
        // Use the MTS package name as the name if it exists.
        Scan = strstr(g_ProcessName, "MTS Packages: ");
        if (Scan)
        {
            PSTR Start;
            size_t Len;
            
            Scan += 14;
            Start = Scan;

            Scan = strchr(Start, ',');
            if (!Scan)
            {
                Scan = strchr(Start, ' ');
            }
            if (Scan)
            {
                *Scan = 0;
            }

            Len = strlen(Start) + 1;
            if (Len > 2)
            {
                memmove(g_ProcessName, Start, Len);
            }
            else
            {
                g_ProcessName[0] = 0;
            }
        }
        else
        {
            g_ProcessName[0] = 0;
        }
    }

    if (!g_ProcessName[0])
    {
        if (FAILED(g_ExtSystem->
                   GetCurrentProcessExecutableName(g_ProcessName, MAX_NAME,
                                                   NULL)))
        {
            // This can happen in some situations so handle it
            // rather than exiting.
            ExtErr("Unable to get current process name\n");
            strcpy_s(g_ProcessName, MAX_NAME, "UnknownProcess");
        }
    }

    return S_OK;
}

HRESULT
GetEventInfo(void)
{
    HRESULT Status;
    ULONG ProcessId, ThreadId;
    
    if ((Status = g_ExtControl->
         GetLastEventInformation(&g_LastEventType, &ProcessId, &ThreadId,
                                 &g_LastEventInfo, sizeof(g_LastEventInfo),
                                 NULL, NULL, 0, NULL)) != S_OK)
    {
        ExtErr("Unable to get event information\n");
        return Status;
    }
    
    if ((Status = GetProcessInfo()) != S_OK)
    {
        return Status;
    }

    switch(g_LastEventType)
    {
    case DEBUG_EVENT_EXCEPTION:
        {
            EXCEPTION_NAME* ExName = g_ExceptionNames;

            while (ExName->Name != NULL)
            {
                if (ExName->Code == g_LastEventInfo.Exception.
                    ExceptionRecord.ExceptionCode)
                {
                    break;
                }

                ExName++;
            }

            if (ExName->Name != NULL)
            {
                g_LastExName = ExName->Name;
            }
            else
            {
                sprintf_s(g_UnknownExceptionName, MAX_NAME, "Unknown Exception (%08X)",
                        g_LastEventInfo.Exception.
                        ExceptionRecord.ExceptionCode);
                g_LastExName = g_UnknownExceptionName;
            }

            if (g_LastEventInfo.Exception.FirstChance)
            {
                g_LastExChanceStr = "First";
            }
            else
            {
                g_LastExChanceStr = "Second";
            }
        }
        break;
    }
    
    return S_OK;
}

void
SanitizeFileName(__inout_ecount(FileNameLen) PSTR FileName, DWORD FileNameLen)
{
    DWORD LocalNameLen = FileNameLen;

    while ((* FileName) && (LocalNameLen > 0))
    {
        switch(*FileName)
        {
        case ' ':
        case '\t':
        case '\n':
        case '\r':
            *FileName = '_';
            break;

        case '\\':
        case '/':
        case ':':
            *FileName = '-';
            break;
        }
        
        FileName ++;
        LocalNameLen --;
    }
}

void
GetDumpPath(__in PSTR NameQual, __in PSTR TypeStr, __out_ecount(PathLen) PSTR Path, DWORD PathLen)
{
    SYSTEMTIME Time;
    PSTR FilePart;

    GetLocalTime(&Time);

    strcpy_s(Path, PathLen, g_BaseDir);
    FilePart = Path + strlen(Path) - 1;
    if (*FilePart != '/' && *FilePart != '\\')
    {
        *++FilePart = '\\';
    }
    FilePart++;
    
    _snprintf_s(FilePart,
              MAX_PATH - (FilePart - Path),
              _TRUNCATE,
              "PID-%d__%s__Date_%02d-%02d-%04d__Time_%02d-%02d-%02d__%s__%s.dmp",
              g_ProcessId,
              g_ProcessName,
              Time.wMonth,
              Time.wDay,
              Time.wYear,
              Time.wHour,
              Time.wMinute,
              Time.wSecond,
              NameQual,
              TypeStr);
    Path[PathLen - 1] = 0;

    SanitizeFileName(FilePart, lstrlen(FilePart) + 1);
}
            
void
WriteDump(__in PSTR NameQual, __inout_ecount(MAX_COMMENT) PSTR Comment,
          ULONG DumpQual, ULONG DumpFormat, __in PSTR TypeStr)
{
    char Path[MAX_PATH];
    size_t Len;

    Len = strlen(Comment);
    _snprintf_s(Comment + Len, MAX_COMMENT - Len, _TRUNCATE,
              " - %s dump from %s",
              TypeStr, g_Machine);
    Comment[MAX_COMMENT - 1] = 0;
    GetDumpPath(NameQual, TypeStr, Path, MAX_PATH);

    g_ExtClient->WriteDumpFile2(Path, DumpQual, DumpFormat, Comment);
}

extern "C" HRESULT
AdpEventControlC(PDEBUG_CLIENT Client, PCSTR Args)
{
    char Comment[MAX_COMMENT];
    
    INIT_API();

    //
    // Parameters: directory, machine name.
    //

    Args = GetParams(Args, 2, g_DirMachParams);
    if (Args == NULL)
    {
        goto Exit;
    }

    //
    // Retrieve standard information.
    //

    if ((Status = GetEventInfo()) != S_OK)
    {
        goto Exit;
    }
    
    //
    // Log information.
    //
    
    ExtOut("\n\n----------------------------------------------------------------------\n");
    ExtOut("CTRL-C was pressed to stop debugging this process!\n");
    ExtOut("----------------------------------------------------------------------\n");
    ExtOut("Exiting the debugger at:\n");
    ExtExec(".time");
    ExtOut("\n\n--- Listing all thread stacks: ---\n");
    ExtExec("~*kb250");
    ExtOut("\n--- Listing loaded modules: ---\n");
    ExtExec("lmv");
    ExtOut("\n--- Modules with matching symbols:\n");
    ExtExec("lml");
    ExtOut("\n--- Listing all locks: ---\n");
    ExtExec("!locks");

    //
    // Create a dump file.
    //
    
    strcpy_s(Comment, MAX_COMMENT, "CTRL-C was pressed to stop the debugger while running in crash mode");
    WriteDump("CTRL-C", Comment,
              DEBUG_DUMP_SMALL, DEBUG_FORMAT_DEFAULT, "mini");

 Exit:
    EXIT_API();
    return Status;
}

extern "C" HRESULT
AdpEventException(PDEBUG_CLIENT Client, PCSTR Args)
{
    char Comment[MAX_COMMENT];
    char Qual[MAX_COMMENT];
    ULONG Format;
    PSTR TypeStr;
    
    INIT_API();

    //
    // Parameters: directory, machine name.
    //

    Args = GetParams(Args, 2, g_DirMachParams);
    if (Args == NULL)
    {
        goto Exit;
    }

    //
    // Retrieve standard information.
    //

    if ((Status = GetEventInfo()) != S_OK)
    {
        goto Exit;
    }
    
    if (g_LastEventType != DEBUG_EVENT_EXCEPTION)
    {
        ExtErr("Last event was not an exception\n");
        goto Exit;
    }

    if (g_LastEventInfo.Exception.FirstChance)
    {
        Format = DEBUG_FORMAT_DEFAULT;
        TypeStr = "mini";
    }
    else
    {
        Format = DEBUG_FORMAT_USER_SMALL_FULL_MEMORY |
            DEBUG_FORMAT_USER_SMALL_HANDLE_DATA;
        TypeStr = "mini full handle";
    }
    
    //
    // Log information.
    //
    
    ExtOut("\n---- %s-chance %s - Exception stack below ----\n",
           g_LastExChanceStr, g_LastExName);
    ExtExec(".time");
    ExtOut("\n");
    ExtExec("kvn250");
    ExtOut("-----------------------------------\n");

    //
    // Create a dump file.
    //
    
    _snprintf_s(Comment, sizeof(Comment), _TRUNCATE, "%s-chance %s in %s",
              g_LastExChanceStr, g_LastExName, g_ProcessName);
    Comment[sizeof(Comment) - 1] = 0;
    _snprintf_s(Qual, sizeof(Qual), _TRUNCATE, "%s-chance %s",
              g_LastExChanceStr, g_LastExName);
    Qual[sizeof(Qual) - 1] = 0;

    WriteDump(Qual, Comment, DEBUG_DUMP_SMALL, Format, TypeStr);

    ExtOut("\n\n");
    
 Exit:
    EXIT_API();
    return Status;
}

extern "C" HRESULT
AdpEventExitProcess(PDEBUG_CLIENT Client, PCSTR Args)
{
    INIT_API();

    UNREFERENCED_PARAMETER(Args);
    
    //
    // Log information.
    //
    
    ExtOut("\n\n----------------------------------------------------------------------\n");
    ExtOut("This process is shutting down!\n");
    ExtOut("\nThis can happen for the following reasons:\n");
    ExtOut("1.) Someone killed the process with Task Manager or the kill command.\n");
    ExtOut("\n2.) If this process is an MTS or COM+ server package, it could be\n");
    ExtOut("*   exiting because an MTS/COM+ server package idle limit was reached.\n");
    ExtOut("\n3.) If this process is an MTS or COM+ server package,\n");
    ExtOut("*   someone may have shutdown the package via the MTS Explorer or\n");
    ExtOut("*   Component Services MMC snap-in.\n");
    ExtOut("\n4.) If this process is an MTS or COM+ server package,\n");
    ExtOut("*   MTS or COM+ could be shutting down the process because an internal\n");
    ExtOut("*   error was detected in the process (MTS/COM+ fail fast condition).\n");
    ExtOut("----------------------------------------------------------------------\n");
    ExtOut("\nThe process was shut down at:\n");
    ExtExec(".time");
    ExtOut("\n\n");

    EXIT_API();
    return Status;
}
