/*++

Copyright (c) 2000-2001  Microsoft Corporation

Module Name:

    ext.cpp

Abstract:

    Generic cross-platform and cross-processor extensions.

Environment:

    User Mode

--*/

#include "precomp.h"
#pragma hdrstop

#include <ntverp.h>
#include <time.h>
#include <lm.h>

//
// Valid for the lifetime of the debug session.
//

WINDBG_EXTENSION_APIS   ExtensionApis;
ULONG   TargetMachine;
BOOL    Connected;
ULONG   g_TargetClass;

//
// Valid only during an extension API call
//

PDEBUG_ADVANCED       g_ExtAdvanced;
PDEBUG_CLIENT         g_ExtClient;
PDEBUG_CONTROL        g_ExtControl;
PDEBUG_DATA_SPACES    g_ExtData;
PDEBUG_REGISTERS      g_ExtRegisters;
PDEBUG_SYMBOLS        g_ExtSymbols;
PDEBUG_SYSTEM_OBJECTS g_ExtSystem;
// Version 2 Interfaces
PDEBUG_CONTROL2       g_ExtControl2;

// Queries for all debugger interfaces.
extern "C" HRESULT
ExtQuery(PDEBUG_CLIENT Client)
{
    HRESULT Status;

    if ((Status = Client->QueryInterface(__uuidof(IDebugAdvanced),
                                 (void **)&g_ExtAdvanced)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugControl),
                                 (void **)&g_ExtControl)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugDataSpaces),
                                 (void **)&g_ExtData)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugRegisters),
                                 (void **)&g_ExtRegisters)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugSymbols),
                                 (void **)&g_ExtSymbols)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugSystemObjects),
                                         (void **)&g_ExtSystem)) != S_OK)
    {
        goto Fail;
    }
    if ((Status = Client->QueryInterface(__uuidof(IDebugControl2),
                                 (void **)&g_ExtControl2)) != S_OK)
    {
        goto Fail;
    }

    g_ExtClient = Client;

    return S_OK;

 Fail:
    ExtRelease();
    return Status;
}

// Cleans up all debugger interfaces.
void
ExtRelease(void)
{
    g_ExtClient = NULL;
    EXT_RELEASE(g_ExtAdvanced);
    EXT_RELEASE(g_ExtControl);
    EXT_RELEASE(g_ExtData);
    EXT_RELEASE(g_ExtRegisters);
    EXT_RELEASE(g_ExtSymbols);
    EXT_RELEASE(g_ExtSystem);
    EXT_RELEASE(g_ExtControl2);
}

// Normal output.
void __cdecl
ExtOut(PCSTR Format, ...)
{
    va_list Args;

    va_start(Args, Format);
    g_ExtControl->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args);
    va_end(Args);
}

// Error output.
void __cdecl
ExtErr(PCSTR Format, ...)
{
    va_list Args;

    va_start(Args, Format);
    g_ExtControl->OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args);
    va_end(Args);
}

// Warning output.
void __cdecl
ExtWarn(PCSTR Format, ...)
{
    va_list Args;

    va_start(Args, Format);
    g_ExtControl->OutputVaList(DEBUG_OUTPUT_WARNING, Format, Args);
    va_end(Args);
}

// Verbose output.
void __cdecl
ExtVerb(PCSTR Format, ...)
{
    va_list Args;

    va_start(Args, Format);
    g_ExtControl->OutputVaList(DEBUG_OUTPUT_VERBOSE, Format, Args);
    va_end(Args);
}


extern "C"
HRESULT
CALLBACK
DebugExtensionInitialize(PULONG Version, PULONG Flags)
{
    IDebugClient *DebugClient;
    PDEBUG_CONTROL DebugControl;
    HRESULT Hr;

    *Version = DEBUG_EXTENSION_VERSION(1, 0);
    *Flags = 0;


    if ((Hr = DebugCreate(__uuidof(IDebugClient),
                          (void **)&DebugClient)) != S_OK)
    {
        return Hr;
    }
    if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl),
                                              (void **)&DebugControl)) != S_OK)
    {
        return Hr;
    }

    ExtensionApis.nSize = sizeof (ExtensionApis);
    if ((Hr = DebugControl->GetWindbgExtensionApis64(&ExtensionApis)) != S_OK) {
        return Hr;
    }

    DebugControl->Release();
    DebugClient->Release();
    return S_OK;
}


extern "C"
void
CALLBACK
DebugExtensionNotify(ULONG Notify, ULONG64 Argument)
{
    //
    // The first time we actually connect to a target, get the page size
    //

    if ((Notify == DEBUG_NOTIFY_SESSION_ACCESSIBLE) && (!Connected))
    {
        IDebugClient *DebugClient;
        PDEBUG_DATA_SPACES DebugDataSpaces;
        PDEBUG_CONTROL DebugControl;
        HRESULT Hr;
        ULONG64 Page;

        if ((Hr = DebugCreate(__uuidof(IDebugClient),
                              (void **)&DebugClient)) == S_OK)
        {
            //
            // Get the architecture type.
            //

            if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl),
                                                  (void **)&DebugControl)) == S_OK)
            {
                if ((Hr = DebugControl->GetActualProcessorType(
                    &TargetMachine)) == S_OK)
                {
                    Connected = TRUE;
                }
                ULONG Qualifier;
                if ((Hr = DebugControl->GetDebuggeeType(&g_TargetClass, &Qualifier)) == S_OK)
                {
                }

                DebugControl->Release();
            }

            DebugClient->Release();
        }
    }


    if (Notify == DEBUG_NOTIFY_SESSION_INACTIVE)
    {
        Connected = FALSE;
        TargetMachine = 0;
    }

    return;
}

extern "C"
void
CALLBACK
DebugExtensionUninitialize(void)
{
    return;
}


DllInit(
    HANDLE hModule,
    DWORD  dwReason,
    DWORD  dwReserved
    )
{
    switch (dwReason) {
        case DLL_THREAD_ATTACH:
            break;

        case DLL_THREAD_DETACH:
            break;

        case DLL_PROCESS_DETACH:
            break;

        case DLL_PROCESS_ATTACH:
            break;
    }

    return TRUE;
}

LegacyCommands()
{
    dprintf("\n");
    dprintf("  !cxr !exr, !trap and !tss has been replaced with the new built-in debugger \n");
    dprintf("  command .cxr, .exr, .trap and .tss.  There is also a new \".thread\" command. \n");
    dprintf("\n");
    dprintf("  These new commands no longer require symbols to work correctly.\n");
    dprintf("\n");
    dprintf("  Another change that comes with these new commands is that they actually\n");
    dprintf("  change the internal state of the debugger engine \"permanently\" (until\n");
    dprintf("  reverted).  Any other debugger or extension command issued after the \n");
    dprintf("  \".cxr\", \".trap\" or \".thread\" command will be executed with the new context.\n");
    dprintf("\n");
    dprintf("  For example, commands such as stack walk (\"k\", \"kb\", \"kv\" ), \"r\" and \"dv\"\n");
    dprintf("  (show local variables) will all work based off the new context that was\n");
    dprintf("  supplied by \".cxr\", \".trap\" or \".thread\".\n");
    dprintf("\n");
    dprintf("  \".cxr\", \".trap\" and \".thread\" also apply to WinDBG:\n");
    dprintf("  using \".cxr\" , \".trap\"  and \".thread\" will automatically show you the\n");
    dprintf("  new stack in the WinDBG stack window and allow you to click on a frame and\n");
    dprintf("  see local variables and source code (if source is available).\n");
    dprintf("\n");
    dprintf("  \".cxr\", \".trap\" or \".thread\" with no parameters will give you back the\n");
    dprintf("  default context that was in effect before the command was executed.\n");
    dprintf("\n");
    dprintf("  For example, to exactly duplicate \n");
    dprintf("\n");
    dprintf("        !cxr <foo>        !trap <foo>\n");
    dprintf("        !kb               !kb\n");
    dprintf("\n");
    dprintf("  you would now use\n");
    dprintf("\n");
    dprintf("        .cxr <foo>        .trap <foo>\n");
    dprintf("        kb                kb\n");
    dprintf("        .cxr              .trap\n");
    dprintf("\n");
    return S_OK;
}

DECLARE_API ( cxr )
{
    LegacyCommands();
    return S_OK;
}

DECLARE_API ( exr )
{
    LegacyCommands();
    return S_OK;
}

DECLARE_API ( trap )
{
    LegacyCommands();
    return S_OK;
}

DECLARE_API ( tss )
{
    LegacyCommands();
    return S_OK;
}


DECLARE_API( cpuid )

/*++

Routine Description:

    Print out the version number for all CPUs, if available.

Arguments:

    None

Return Value:

    None

--*/

{
    ULONG64 Val;
    BOOL First = TRUE;
    ULONG Processor;
    ULONG NumProcessors;
    DEBUG_PROCESSOR_IDENTIFICATION_ALL IdAll;

    INIT_API();

    if (g_ExtControl->GetNumberProcessors(&NumProcessors) != S_OK)
    {
        NumProcessors = 0;
    }

    if (GetExpressionEx(args, &Val, &args))
    {
        //
        // The user specified a procesor number.
        //

        Processor = (ULONG)Val;
        if (Processor >= NumProcessors)
        {
            dprintf("Invalid processor number specified\n");
        }
        else
        {
            NumProcessors = Processor + 1;
        }
    }
    else
    {
        //
        // Enumerate all the processors
        //

        Processor = 0;
    }

    while (Processor < NumProcessors)
    {
        if (g_ExtData->
            ReadProcessorSystemData(Processor,
                                    DEBUG_DATA_PROCESSOR_IDENTIFICATION,
                                    &IdAll, sizeof(IdAll), NULL) != S_OK)
        {
            dprintf("Unable to get processor %d ID information\n",
                    Processor);
            break;
        }
        
        switch( TargetMachine )
        {
        case IMAGE_FILE_MACHINE_I386:

            if (First)
            {
                dprintf("CP  F/M/S  Manufacturer\n");
            }

            dprintf("%2d %2d,%d,%-2d %-16.16s\n",
                    Processor,
                    IdAll.X86.Family,
                    IdAll.X86.Model,
                    IdAll.X86.Stepping,
                    IdAll.X86.VendorString);

            break;

        case IMAGE_FILE_MACHINE_AMD64:

            if (First)
            {
                dprintf("CP  F/M/S  Manufacturer\n");
            }

            dprintf("%2d %2d,%d,%-2d %-16.16s\n",
                    Processor,
                    IdAll.Amd64.Family,
                    IdAll.Amd64.Model,
                    IdAll.Amd64.Stepping,
                    IdAll.Amd64.VendorString);

            break;

        case IMAGE_FILE_MACHINE_IA64:

            if (First)
            {
                dprintf("CP M/R/F/A Manufacturer\n");
            }

            dprintf("%2d %d,%d,%d,%d %-16.16s\n",
                    Processor,
                    IdAll.Ia64.Model,
                    IdAll.Ia64.Revision,
                    IdAll.Ia64.Family,
                    IdAll.Ia64.ArchRev,
                    IdAll.Ia64.VendorString);

            break;

        default:
            dprintf("Not supported for this target machine: %ld\n",
                    TargetMachine);
            Processor = NumProcessors;
            break;
        }

        Processor++;
        First = FALSE;
    }

    EXIT_API();

    return S_OK;
}


HRESULT
PrintString(
    BOOL Unicode,
    PDEBUG_CLIENT Client,
    LPCSTR args
    )
{
    ULONG64 AddrString;
    ULONG64 Displacement;
    STRING32 String;
    UNICODE_STRING UnicodeString;
    ULONG64 AddrBuffer;
    CHAR Symbol[1024];
    LPSTR StringData;
    HRESULT hResult;
    BOOL b;


    AddrString = GetExpression(args);
    if (!AddrString)
    {
        return E_FAIL;
    }

    //
    // Get the symbolic name of the string
    //

    GetSymbol(AddrString, Symbol, &Displacement);

    //
    // Read the string from the debuggees address space into our
    // own.

    b = ReadMemory(AddrString, &String, sizeof(String), NULL);

    if ( !b )
    {
        return E_FAIL;
    }

    INIT_API();

    if (IsPtr64())
    {
        hResult = g_ExtData->ReadPointersVirtual(1,
                             AddrString + FIELD_OFFSET(STRING64, Buffer),
                             &AddrBuffer);
    }
    else
    {
        hResult = g_ExtData->ReadPointersVirtual(1,
                             AddrString + FIELD_OFFSET(STRING32, Buffer),
                             &AddrBuffer);
    }

    EXIT_API();

    if (hResult != S_OK)
    {
        return E_FAIL;
    }

    StringData = (LPSTR) LocalAlloc(LMEM_ZEROINIT,
                                    String.Length + sizeof(UNICODE_NULL));

    if (!StringData)
    {
        return E_FAIL;
    }

    dprintf("String(%d,%d)", String.Length, String.MaximumLength);
    if (Symbol[0])
    {
        dprintf(" %s+%p", Symbol, Displacement);
    }

    b = ReadMemory(AddrBuffer, StringData, String.Length, NULL);

    if ( b )
    {
        if (Unicode)
        {
            ANSI_STRING AnsiString;

            UnicodeString.Buffer = (PWSTR)StringData;
            UnicodeString.Length = String.Length;
            UnicodeString.MaximumLength = String.Length+sizeof(UNICODE_NULL);

            RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString,TRUE);

            dprintf(" at %p: %s\n", AddrString, AnsiString.Buffer);

            RtlFreeAnsiString(&AnsiString);
        }
        else
        {
            dprintf(" at %p: %s\n", AddrString, StringData);
        }

        LocalFree(StringData);
        return S_OK;
    }
    else
    {
        LocalFree(StringData);
        return E_FAIL;
    }
}

DECLARE_API( str )

/*++

Routine Description:

    This function is called to format and dump counted (ansi) string.

Arguments:

    args - Address

Return Value:

    None.

--*/
{
    return PrintString(FALSE, Client, args);
}

DECLARE_API( ustr )

/*++

Routine Description:

    This function is called to format and dump counted (unicode) string.

Arguments:

    args - Address

Return Value:

    None.

--*/

{
    return PrintString(TRUE, Client, args);
}

DECLARE_API( obja )

/*++

Routine Description:

    This function is called to format and dump an object attributes structure.

Arguments:

    args - Address

Return Value:

    None.

--*/

{
    ULONG64 AddrObja;
    ULONG64 Displacement;
    ULONG64 AddrString;
    STRING32 String;
    ULONG64 StrAddr = NULL;
    CHAR Symbol[1024];
    LPSTR StringData;
    BOOL b;
    ULONG Attr;
    HRESULT hResult;
    ULONG ObjectNameOffset;
    ULONG AttrOffset;
    ULONG StringOffset;

    if (IsPtr64())
    {
        ObjectNameOffset = FIELD_OFFSET(OBJECT_ATTRIBUTES64, ObjectName);
        AttrOffset = FIELD_OFFSET(OBJECT_ATTRIBUTES64, Attributes);
        StringOffset = FIELD_OFFSET(STRING64, Buffer);
    }
    else
    {
        ObjectNameOffset = FIELD_OFFSET(OBJECT_ATTRIBUTES32, ObjectName);
        AttrOffset = FIELD_OFFSET(OBJECT_ATTRIBUTES32, Attributes);
        StringOffset = FIELD_OFFSET(STRING32, Buffer);
    }


    AddrObja = GetExpression(args);
    if (!AddrObja)
    {
        return E_FAIL;
    }

    //
    // Get the symbolic name of the Obja
    //

    GetSymbol(AddrObja, Symbol, &Displacement);

    dprintf("Obja %s+%p at %p:\n", Symbol, Displacement, AddrObja);


    INIT_API();

    hResult = g_ExtData->ReadPointersVirtual(1,
                         AddrObja + ObjectNameOffset,
                         &AddrString);

    if (hResult != S_OK)
    {
        return E_FAIL;
    }

    if (AddrString)
    {
        b = ReadMemory(AddrString, &String, sizeof(String), NULL);

        hResult = g_ExtData->ReadPointersVirtual(1,
                             AddrString + StringOffset,
                             &StrAddr);
    }

    EXIT_API();


    if (StrAddr)
    {
        StringData = (LPSTR)LocalAlloc(LMEM_ZEROINIT,
                                       String.Length+sizeof(UNICODE_NULL));

        if (StringData)
        {

            b = ReadMemory(StrAddr, StringData, String.Length, NULL);

            if (b)
            {
                dprintf("\tName is %ws\n", StringData);
            }

            LocalFree(StringData);
        }

    }

    b = ReadMemory(AddrObja + AttrOffset, &Attr, sizeof(Attr), NULL);

    if (!b)
    {
        return E_FAIL;
    }

    if (Attr & OBJ_INHERIT )
    {
        dprintf("\tOBJ_INHERIT\n");
    }
    if (Attr & OBJ_PERMANENT )
    {
        dprintf("\tOBJ_PERMANENT\n");
    }
    if (Attr & OBJ_EXCLUSIVE )
    {
        dprintf("\tOBJ_EXCLUSIVE\n");
    }
    if (Attr & OBJ_CASE_INSENSITIVE )
    {
        dprintf("\tOBJ_CASE_INSENSITIVE\n");
    }
    if (Attr & OBJ_OPENIF )
    {
        dprintf("\tOBJ_OPENIF\n");
    }


    return S_OK;
}


VOID
DecodeErrorForMessage(
    PDEBUG_DECODE_ERROR pDecodeError
    )
{
    HANDLE Dll;
    PSTR Source;
    CHAR Message[ 512 ];
    PCHAR s;
    ULONG   Code;
    BOOL    TreatAsStatus;
    
    Code = pDecodeError->Code;
    TreatAsStatus = pDecodeError->TreatAsStatus;

    if ( !pDecodeError->TreatAsStatus )
    {
        //
        // The value "type" is not known.  Try and figure out what it
        // is.
        //

        if ( (Code & 0xC0000000) == 0xC0000000 )
        {
            //
            // Easy:  NTSTATUS failure case
            //

            Dll = GetModuleHandle( "NTDLL.DLL" );
            Source = "NTSTATUS" ;
            TreatAsStatus = TRUE ;
        }
        else if ( ( Code & 0xF0000000 ) == 0xD0000000 )
        {
            //
            // HRESULT from NTSTATUS
            //

            Dll = GetModuleHandle( "NTDLL.DLL" );
            Source = "NTSTATUS" ;
            Code &= 0xCFFFFFFF ;
            TreatAsStatus = TRUE ;
        }
        else if ( ( Code & 0x80000000 ) == 0x80000000 )
        {
            //
            // Note, this can overlap with NTSTATUS warning area.  In that
            // case, force the NTSTATUS.
            //

            Dll = GetModuleHandle( "KERNEL32.DLL" );
            Source = "HRESULT" ;

        }
        else
        {
            //
            // Sign bit is off.  Explore some known ranges:
            //

            if ( (Code >= WSABASEERR) && (Code <= WSABASEERR + 1000 ))
            {
                Dll = LoadLibrary( "wsock32.dll" );
                Source = "Winsock" ;
            }
            else if ( ( Code >= NERR_BASE ) && ( Code <= MAX_NERR ) )
            {
                Dll = LoadLibrary( "netmsg.dll" );
                Source = "NetAPI" ;
            }
            else
            {
                Dll = GetModuleHandle( "KERNEL32.DLL" );
                Source = "Win32" ;
            }
        }
    }
    else
    {
        Dll = GetModuleHandle( "NTDLL.DLL" );

        Source = "NTSTATUS" ;
    }

    if (!FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
                           FORMAT_MESSAGE_FROM_HMODULE,
                       Dll,
                       Code,
                       0,
                       Message,
                       sizeof( Message ),
                       NULL ) )
    {
        strcpy( Message, "No mapped error code" );
    }

    s = Message ;

    while (*s)
    {
        if (*s < ' ')
        {
            *s = ' ';
        }
        s++;
    }
    pDecodeError->TreatAsStatus = TreatAsStatus;
    strcpy(pDecodeError->Source, Source);
    if (strlen(Message) < sizeof(pDecodeError->Message)) {
        strcpy(pDecodeError->Message, Message);
    }
}

VOID
DecodeError(
    PSTR    Banner,
    ULONG   Code,
    BOOL    TreatAsStatus
    )
{
    DEBUG_DECODE_ERROR Err;

    Err.Code = Code;
    Err.TreatAsStatus = TreatAsStatus;
    DecodeErrorForMessage(&Err);
    if (!TreatAsStatus)
    {
        dprintf("%s: (%s) %#x (%u) - %s\n",
                Banner, Err.Source, Code, Code, Err.Message);
    }
    else
    {
        dprintf("%s: (%s) %#x - %s\n",
                Banner, Err.Source, Code, Err.Message);
    }
}

DECLARE_API( error )
{
    ULONG err ;

    err = (ULONG) GetExpression( args );
    DecodeError( "Error code", err, FALSE );

    return S_OK;
}

#if 1

DECLARE_API( gle )
{
    NTSTATUS Status;
    ULONG64 Address;
    TEB Teb;

    GetTebAddress(&Address);

    if (ReadMemory(Address, &Teb, sizeof(Teb), NULL))
    {
        DecodeError( "LastErrorValue", Teb.LastErrorValue, FALSE );
        DecodeError( "LastStatusValue", Teb.LastStatusValue, TRUE );

        return S_OK;
    }
    else
    {
        dprintf("Unable to read current thread's TEB\n" );
        return E_FAIL;
    }
}


void
DispalyTime(
    ULONG64 Time,
    PCHAR TimeString
    )
{
    if (Time) {
        ULONG seconds = (ULONG) Time;
        ULONG minutes = seconds / 60;
        ULONG hours = minutes / 60;
        ULONG days = hours / 24;

        dprintf("%s %d days %d:%02d:%02d \n",
                TimeString,
                days, hours%24, minutes%60, seconds%60);
    }
}

extern PCHAR gTargetMode[], gAllOsTypes[];

DECLARE_API( targetinfo )
{
    TARGET_DEBUG_INFO TargetInfo;
    EXT_TARGET_INFO GetTargetInfo;
    INIT_API();


    if (g_ExtControl->GetExtensionFunction(0, "GetTargetInfo", (FARPROC *)&GetTargetInfo) == S_OK) {
        TargetInfo.SizeOfStruct = sizeof(TargetInfo);
        if ((*GetTargetInfo)(Client, &TargetInfo) != S_OK) {
            dprintf("GetTargetInfo failed\n");
        } else {
            const char * time;

            dprintf("TargetInfo:\n");
            dprintf("%s\n", gTargetMode[ TargetInfo.Mode ]);
            if ((time = ctime((time_t *) &TargetInfo.CrashTime)) != NULL) {
                dprintf("\tCrashtime:      %s",       time);
            }
            if (TargetInfo.SysUpTime) {
                DispalyTime(TargetInfo.SysUpTime, 
                            "\tSystem Uptime: ");
            }
            else
            {
                dprintf("\tSystem Uptime: not available\n");
            }
            if (TargetInfo.Mode == UserModeTarget) {
                DispalyTime(TargetInfo.AppUpTime, "\tProcess Uptime: ");
            }
            if ((time = ctime((time_t *) &TargetInfo.EntryDate)) != NULL) {
                dprintf("\tEntry Date:     %s", time);
            }
            if (TargetInfo.OsInfo.Type) {
                dprintf(gAllOsTypes[TargetInfo.OsInfo.Type]);
                dprintf(" ");
            }
//          dprintf("OS Type %lx, Probcuct %lx, suite %lx\n",
//                  TargetInfo.OsInfo.Type, TargetInfo.OsInfo.ProductType,
//                  TargetInfo.OsInfo.Suite);
            dprintf("%s, %s ",
                    TargetInfo.OsInfo.OsString,
                    TargetInfo.OsInfo.ServicePackString);
            dprintf("Version %ld.%ld\n",
                    TargetInfo.OsInfo.Version.Major, TargetInfo.OsInfo.Version.Minor);
            dprintf("%d procs, %d current processor, type %lx\n",
                    TargetInfo.Cpu.NumCPUs,
                    TargetInfo.Cpu.CurrentProc,
                    TargetInfo.Cpu.Type);
            for (ULONG i =0; i<TargetInfo.Cpu.NumCPUs; i++) {
                if (TargetInfo.Cpu.Type == IMAGE_FILE_MACHINE_I386) {
                    dprintf("CPU %lx Family %lx Model %lx Ste %lx Vendor %-12.12s\n",
                            i,
                            TargetInfo.Cpu.ProcInfo[i].X86.Family,
                            TargetInfo.Cpu.ProcInfo[i].X86.Model,
                            TargetInfo.Cpu.ProcInfo[i].X86.Stepping,
                            TargetInfo.Cpu.ProcInfo[i].X86.VendorString);
                } else if (TargetInfo.Cpu.Type == IMAGE_FILE_MACHINE_IA64) {
                    dprintf("CPU %lx Family %lx Model %lx Rev %lx Vendor %-12.12s\n",
                            i,
                            TargetInfo.Cpu.ProcInfo[i].Ia64.Family,
                            TargetInfo.Cpu.ProcInfo[i].Ia64.Model,
                            TargetInfo.Cpu.ProcInfo[i].Ia64.Revision,
                            TargetInfo.Cpu.ProcInfo[i].Ia64.VendorString);
                }
            }
        }
    }

    EXIT_API();
    return S_OK;
}
#endif


BOOL
GetOwner(
    PSTR OwnerBuffer,
    ULONG OwnerBufferSize,
    PSTR SymbolName
    )
{
    PSTR Bang;
    CHAR Owner2[MAX_PATH];
    ULONG Found = 0;
    PSTR SymName;
    static CHAR szTriageFileName[MAX_PATH+50];
    static BOOL GotTriageFIleName = FALSE;


    if (!GotTriageFIleName) 
    {
        PCHAR ExeDir;

        ExeDir = &szTriageFileName[0];

        *ExeDir = 0;
            // Get the directory the debugger executable is in.
        if (!GetModuleFileName(NULL, ExeDir, MAX_PATH)) 
        {
            // Error.  Use the current directory.
            strcpy(ExeDir, ".");
        } else 
        {
            // Remove the executable name.
            PCHAR pszTmp = strrchr(ExeDir, '\\');
            if (pszTmp)
            {
                *pszTmp = 0;
            }
            GotTriageFIleName = TRUE;
        }
        strcat(ExeDir, "\\triage\\triage.ini");
    }



    //
    // First extract the module name from the symbol name.
    //

    Bang = strstr(SymbolName, "!");

    if (Bang)
    {
        *Bang = 0;
    }

    Found = GetPrivateProfileString("owners", SymbolName, "[default]",
                                    OwnerBuffer, OwnerBufferSize,
                                    szTriageFileName);

    if (!Found ||
        !strcmp(OwnerBuffer, "ignore"))
    {
        return FALSE;
    }

    if (OwnerBuffer[0] != '[')
    {
        return TRUE;
    }

    //
    // The string points to another section to handle substrings in the module
    // For each substring, starting with the longest one, search the
    // section.
    //
    PSTR End = NULL;
    SymName = NULL;
    if (Bang) 
    {
        SymName = Bang+1;
        End = SymName+ strlen(SymName);
    } 

    strcpy(Owner2, OwnerBuffer+1);
    *(Owner2+strlen(Owner2)-1) = 0;

    while (End > SymName)
    {
        Found = GetPrivateProfileString(Owner2, SymName, "ignore",
                                        OwnerBuffer, OwnerBufferSize,
                                        szTriageFileName);

        if (Found && strcmp(OwnerBuffer, "ignore"))
        {
            return TRUE;
        }

        *--End = 0;
    }

    //
    // We did not find the subcomponent - Look for the default entry.
    //

    Found = GetPrivateProfileString(Owner2, "default", "ignore",
                                    OwnerBuffer, OwnerBufferSize,
                                    szTriageFileName);

    if (Found && strcmp(OwnerBuffer, "ignore"))
    {
        return TRUE;
    }

    return FALSE;
}

BOOL
_EFN_GetTriageFollowupFromSymbol(
    IN PSTR SymbolName,
    OUT PDEBUG_TRIAGE_FOLLOWUP_INFO OwnerInfo
    )
{
    if (OwnerInfo->SizeOfStruct != sizeof(DEBUG_TRIAGE_FOLLOWUP_INFO)) 
    {
        return FALSE;
    }
    if (GetOwner(OwnerInfo->OwnerName.Buffer, OwnerInfo->OwnerName.MaximumLength, SymbolName)) {
        OwnerInfo->OwnerName.Length = (USHORT)strlen(OwnerInfo->OwnerName.Buffer);
        return TRUE;
    }
    return FALSE;
}


DECLARE_API( triage )

/*++

Routine Description:

    This function can be called to triage the owner of a stack trace.

Arguments:

    args - none

Return Value:

    None.

--*/

{

    ULONG NumFrames = 20;
    ULONG FramesFound = 0;
    ULONG i;
    BOOL bOwner = FALSE;
    CHAR  NameBuffer[MAX_PATH + 2000 + 20];
    CHAR  CurrentOwner[MAX_PATH];

    // Allocate a separate buffer to hold the frames while
    // calling OutputStackTrace on them.  We can't just pass
    // in the state buffer pointer as resizing of the state
    // buffer may cause the data pointer to change.

    PDEBUG_STACK_FRAME RawFrames =
        (PDEBUG_STACK_FRAME)malloc(NumFrames * sizeof(DEBUG_STACK_FRAME));
    PDEBUG_STACK_FRAME CurrentFrame = RawFrames;

    if (RawFrames == NULL)
    {
        return E_OUTOFMEMORY;
    }

    INIT_API();

    Status = g_ExtControl->GetStackTrace(0, 0, 0, RawFrames, NumFrames,
                                         &FramesFound);
    if (Status == S_OK)
    {
        for(i=0; i < FramesFound; i++)
        {
            //
            // Get the symbol from the address and look it up in the
            // list of owners
            //

            Status = g_ExtSymbols->GetNameByOffset(CurrentFrame->InstructionOffset,
                                                   NameBuffer,
                                                   sizeof(NameBuffer),
                                                   NULL,
                                                   NULL);
            CurrentFrame++;

            if (Status != S_OK)
            {
                continue;
            }

            if (bOwner = GetOwner(CurrentOwner, sizeof(CurrentOwner), NameBuffer))
            {
                break;
            }
        }
    }

    if (!bOwner)
    {
        GetOwner(CurrentOwner, sizeof(CurrentOwner), "default");
    }

    g_ExtControl->OutputStackTrace(DEBUG_OUTCTL_ALL_CLIENTS,
                                   RawFrames, FramesFound, 0);

    dprintf("\n********************\nFollow-up:  %s\n********************\n\n",
             CurrentOwner);

    EXIT_API();

    free(RawFrames);
    return Status;
}


void
_EFN_DecodeError(
    PDEBUG_DECODE_ERROR pDecodeError
    )
{
    if (pDecodeError->SizeOfStruct != sizeof(DEBUG_DECODE_ERROR)) 
    {
        return;
    }
    return DecodeErrorForMessage(pDecodeError);
}

DECLARE_API( elog_str )
{
    HANDLE EventSource = NULL;
    
    INIT_API();

    if (args)
    {
        while (isspace(*args))
        {
            args++;
        }
    }
    
    if (!args || !args[0])
    {
        Status = E_INVALIDARG;
        ExtErr("Usage: elog_str string\n");
        goto Exit;
    }

    // Get a handle to the NT application log.
    EventSource = OpenEventLog(NULL, "Application");
    if (!EventSource)
    {
        Status = HRESULT_FROM_WIN32(GetLastError());
        ExtErr("Unable to open event log, 0x%08X\n", Status);
        goto Exit;
    }

    if (!ReportEvent(EventSource, EVENTLOG_ERROR_TYPE, 0, 0, NULL,
                     1, 0, &args, NULL))
    {
        Status = HRESULT_FROM_WIN32(GetLastError());
        ExtErr("Unable to report event, 0x%08X\n", Status);
        goto Exit;
    }

    Status = S_OK;

 Exit:
    if (EventSource)
    {
        CloseEventLog(EventSource);
    }
    EXIT_API();
    return Status;
}

HRESULT
AnsiToUnicode(PCSTR StrA, PWSTR* StrW)
{
    ULONG Len;

    // No input is an error.
    if (NULL == StrA)
    {
        return E_INVALIDARG;
    }

    Len = strlen(StrA) + 1;
    *StrW = (PWSTR)malloc(Len * sizeof(WCHAR));
    if (*StrW == NULL)
    {
        ExtErr("Unable to allocate memory\n");
        return E_OUTOFMEMORY;
    }

    if (0 == MultiByteToWideChar(CP_ACP, 0, StrA, Len, *StrW, Len))
    {
        HRESULT Status = HRESULT_FROM_WIN32(GetLastError());
        free(*StrW);
        ExtErr("Unable to convert string, 0x%08X\n", Status);
        return Status;
    }

    return S_OK;
}

typedef NET_API_STATUS (NET_API_FUNCTION* PFN_NetMessageBufferSend)
(
    IN  LPCWSTR  servername,
    IN  LPCWSTR  msgname,
    IN  LPCWSTR  fromname,
    IN  LPBYTE   buf,
    IN  DWORD    buflen
);

DECLARE_API( net_send )
{
    PWSTR ArgsW = NULL;
    PWSTR Tokens[4];
    ULONG i;
    HMODULE NetLib = NULL;
    PFN_NetMessageBufferSend Send;
    ULONG Result;
    PWSTR ArgsEnd;

    INIT_API();

    NetLib = LoadLibrary("netapi32.dll");
    if (!NetLib)
    {
        Status = HRESULT_FROM_WIN32(GetLastError());
        ExtErr("Platform does not support net send\n");
        goto Exit;
    }
    Send = (PFN_NetMessageBufferSend)
        GetProcAddress(NetLib, "NetMessageBufferSend");
    if (!Send)
    {
        Status = E_NOTIMPL;
        ExtErr("Platform does not support net send\n");
        goto Exit;
    }
    
    Status = AnsiToUnicode(args, &ArgsW);
    if (Status != S_OK)
    {
        goto Exit;
    }
    ArgsEnd = ArgsW + wcslen(ArgsW);

    // The message text is the entire remainder of the argument
    // string after parsing the first separate tokens, so
    // only wcstok up to the next-to-last token.
    for (i = 0; i < sizeof(Tokens) / sizeof(Tokens[0]) - 1; i++)
    {
        Tokens[i] = wcstok(i == 0 ? ArgsW : NULL, L" \t");
        if (Tokens[i] == NULL)
        {
            Status = E_INVALIDARG;
            ExtErr("USAGE: net_send <targetserver> <targetuser> "
                   "<fromuser> <msg>\n");
            goto Exit;
        }
    }

    Tokens[i] = Tokens[i - 1] + wcslen(Tokens[i - 1]) + 1;
    while (Tokens[i] < ArgsEnd &&
           (*Tokens[i] == ' ' || *Tokens[i] == '\t'))
    {
        Tokens[i]++;
    }
    if (Tokens[i] >= ArgsEnd)
    {
        Status = E_INVALIDARG;
        ExtErr("USAGE: net_send <targetserver> <targetuser> "
               "<fromuser> <msg>\n");
        goto Exit;
    }

    Result = Send(Tokens[0], Tokens[1], Tokens[2], (PBYTE)Tokens[3],
                  (wcslen(Tokens[3]) + 1) * sizeof(WCHAR));
    if (Result != NERR_Success)
    {
        Status = HRESULT_FROM_WIN32(Result);;
        ExtErr("Unable to send message, 0x%08X\n", Status);
        goto Exit;
    }

    Status = S_OK;

 Exit:
    if (ArgsW)
    {
        free(ArgsW);
    }
    if (NetLib)
    {
        FreeLibrary(NetLib);
    }
    EXIT_API();
    return Status;
}

// XXX drewb - This function just starts a mail message; the
// UI comes up and the user must finish and send the message.
// Therefore it doesn't have much value over the
// user just deciding to send a message.
#if 0

typedef ULONG (FAR PASCAL *PFN_MapiSendMail)
(
    LHANDLE lhSession,
    ULONG ulUIParam,
    lpMapiMessage lpMessage,
    FLAGS flFlags,
    ULONG ulReserved
);

DECLARE_API( mapi_send )
{
    HMODULE MapiLib = NULL;
    PFN_MapiSendMail Send;
    MapiMessage Mail;
    
    INIT_API();

    MapiLib = LoadLibrary("mapi.dll");
    if (!MapiLib)
    {
        Status = HRESULT_FROM_WIN32(GetLastError());
        ExtErr("Platform does not support MAPI\n");
        goto Exit;
    }
    Send = (PFN_MapiSendMail)
        GetProcAddress(MapiLib, "MAPISendMail");
    if (!Send)
    {
        Status = E_NOTIMPL;
        ExtErr("Platform does not support MAPI\n");
        goto Exit;
    }

    ZeroMemory(&Mail, sizeof(Mail));

    if (!Send(0,           // use implicit session.
              0,           // ulUIParam; 0 is always valid
              &Mail,       // the message being sent
              MAPI_DIALOG, // allow the user to edit the message
              0            // reserved; must be 0
              ))
    {
        Status = E_FAIL;
        ExtErr("Unable to send mail\n");
        goto Exit;
    }

    Status = S_OK;

 Exit:
    if (MapiLib)
    {
        FreeLibrary(MapiLib);
    }
    EXIT_API();
    return Status;
}

#endif

DECLARE_API( imggp )
{
    ULONG64 ImageBase;
    IMAGE_DOS_HEADER DosHdr;
    IMAGE_NT_HEADERS64 NtHdr;
    ULONG Done;

    INIT_API();
    
    ImageBase = GetExpression(args);

    if (g_ExtData->ReadVirtual(ImageBase, &DosHdr, sizeof(DosHdr),
                               &Done) != S_OK ||
        Done != sizeof(DosHdr))
    {
        ExtErr("Unable to read DOS header at %p\n", ImageBase);
        goto Exit;
    }

    if (DosHdr.e_magic != IMAGE_DOS_SIGNATURE)
    {
        ExtErr("Invalid DOS header at %p\n", ImageBase);
        goto Exit;
    }
    
    if (g_ExtData->ReadVirtual(ImageBase + DosHdr.e_lfanew,
                               &NtHdr, sizeof(NtHdr),
                               &Done) != S_OK ||
        Done != sizeof(NtHdr))
    {
        ExtErr("Unable to read NT header at %p\n",
               ImageBase + DosHdr.e_lfanew);
        goto Exit;
    }

    if (NtHdr.Signature != IMAGE_NT_SIGNATURE)
    {
        ExtErr("Invalid NT header at %p\n", ImageBase + DosHdr.e_lfanew);
        goto Exit;
    }
    if (NtHdr.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    {
        ExtErr("Image is not 64-bit\n");
        goto Exit;
    }
    if (NtHdr.OptionalHeader.NumberOfRvaAndSizes <=
        IMAGE_DIRECTORY_ENTRY_GLOBALPTR)
    {
        ExtErr("Image does not have a GP directory entry\n");
        goto Exit;
    }

    ExtOut("Image at %p has a GP value of %p\n",
           ImageBase, ImageBase +
           NtHdr.OptionalHeader.DataDirectory
           [IMAGE_DIRECTORY_ENTRY_GLOBALPTR].VirtualAddress);
    
 Exit:
    EXIT_API();
    return S_OK;
}