/*++

Copyright (c) 1999  Microsoft Corporation

Module Name:

   triage.c

Abstract:

    This module contains the Phase 0 code to triage bugchecks and
    automatically enable various system tracing components until the
    guilty party is found.

Author:

    Landy Wang 13-Jan-1999

Revision History:

--*/

#include "mi.h"
#include "ntiodump.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,MiTriageSystem)
#pragma alloc_text(INIT,MiTriageAddDrivers)
#endif

//
// Always update this macro when adding triage support for additional bugchecks.
//

#define MI_CAN_TRIAGE_BUGCHECK(BugCheckCode) \
         ((BugCheckCode) == NO_MORE_SYSTEM_PTES || \
         (BugCheckCode) == BAD_POOL_HEADER || \
         (BugCheckCode) == DRIVER_CORRUPTED_SYSPTES || \
         (BugCheckCode) == DRIVER_CORRUPTED_EXPOOL || \
         (BugCheckCode) == DRIVER_CORRUPTED_MMPOOL)

//
// These are bugchecks that were presumably triggered by either autotriage or
// the admin's registry settings - so don't apply any new rules and in addition,
// keep the old ones unaltered so it can reproduce.
//

#define MI_HOLD_TRIAGE_BUGCHECK(BugCheckCode) \
        ((BugCheckCode) == DRIVER_USED_EXCESSIVE_PTES || \
         (BugCheckCode) == DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS || \
         (BugCheckCode) == PAGE_FAULT_IN_FREED_SPECIAL_POOL || \
         (BugCheckCode) == DRIVER_PAGE_FAULT_IN_FREED_SPECIAL_POOL || \
         (BugCheckCode) == PAGE_FAULT_BEYOND_END_OF_ALLOCATION || \
         (BugCheckCode) == DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION || \
         (BugCheckCode) == DRIVER_CAUGHT_MODIFYING_FREED_POOL || \
         (BugCheckCode) == SYSTEM_PTE_MISUSE)

#define MI_TRACKING_PTES                    0x00000002
#define MI_PROTECT_FREED_NONPAGED_POOL      0x00000004
#define MI_VERIFYING_PRENT5_DRIVERS         0x00000008
#define MI_KEEPING_PREVIOUS_SETTINGS        0x00000010

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg("INITCONST")
#pragma data_seg("INITDATA")
#endif
const PCHAR MiTriageActionStrings[] = {
    "Locked pages tracking",
    "System PTE usage tracking",
    "Making accesses to freed nonpaged pool cause bugchecks",
    "Driver Verifying Pre-Windows 2000 built drivers",
    "Keeping previous autotriage settings"
};

#if DBG
ULONG MiTriageDebug = 0;
BOOLEAN MiTriageRegardless = FALSE;
#endif

#ifdef ALLOC_DATA_PRAGMA
#pragma const_seg()
#pragma data_seg()
#endif

//
// N.B.  The debugger references this.
//

ULONG MmTriageActionTaken;

//
// The Version number must be incremented whenever the MI_TRIAGE_STORAGE
// structure is changed.  This enables usermode programs to decode the Mm
// portions of triage dumps regardless of which kernel revision created the
// dump.
//

typedef struct _MI_TRIAGE_STORAGE {
    ULONG Version;
    ULONG Size;
    ULONG MmSpecialPoolTag;
    ULONG MiTriageActionTaken;

    ULONG MmVerifyDriverLevel;
    ULONG KernelVerifier;
    ULONG_PTR MmMaximumNonPagedPool;
    ULONG_PTR MmAllocatedNonPagedPool;

    ULONG_PTR PagedPoolMaximum;
    ULONG_PTR PagedPoolAllocated;

    ULONG_PTR CommittedPages;
    ULONG_PTR CommittedPagesPeak;
    ULONG_PTR CommitLimitMaximum;

} MI_TRIAGE_STORAGE, *PMI_TRIAGE_STORAGE;

PKLDR_DATA_TABLE_ENTRY
TriageGetLoaderEntry (
    IN PVOID TriageDumpBlock,
    IN ULONG ModuleIndex
    );

LOGICAL
TriageActUpon(
    IN PVOID TriageDumpBlock
    );

PVOID
TriageGetMmInformation (
    IN PVOID TriageDumpBlock
    );


LOGICAL
MiTriageSystem (
    IN PLOADER_PARAMETER_BLOCK LoaderBlock
    )

/*++

Routine Description:

    This routine takes the information from the last bugcheck (if any)
    and triages it.  Various debugging options are then automatically
    enabled.

Arguments:

    LoaderBlock - Supplies a pointer to the system loader block.

Return Value:

    TRUE if triaging succeeded and options were enabled.  FALSE otherwise.

--*/

{
    PVOID TriageDumpBlock;
    ULONG_PTR BugCheckData[5];
    ULONG i;
    ULONG ModuleCount;
    NTSTATUS Status;
    PLIST_ENTRY NextEntry;
    PKLDR_DATA_TABLE_ENTRY DataTableEntry;
    PKLDR_DATA_TABLE_ENTRY DumpTableEntry;
    LOGICAL Matched;
    ULONG OldDrivers;
    ULONG OldDriversNotVerifying;
    PMI_TRIAGE_STORAGE TriageInformation;
    
    if (LoaderBlock->Extension == NULL) {
        return FALSE;
    }

    if (LoaderBlock->Extension->Size < sizeof (LOADER_PARAMETER_EXTENSION)) {
        return FALSE;
    }

    TriageDumpBlock = LoaderBlock->Extension->TriageDumpBlock;

    Status = TriageGetBugcheckData (TriageDumpBlock,
                                    (PULONG)&BugCheckData[0],
                                    (PUINT_PTR) &BugCheckData[1],
                                    (PUINT_PTR) &BugCheckData[2],
                                    (PUINT_PTR) &BugCheckData[3],
                                    (PUINT_PTR) &BugCheckData[4]);

    if (!NT_SUCCESS (Status)) {
        return FALSE;
    }

    //
    // Always display at least the bugcheck data from the previous crash.
    //

    DbgPrint ("MiTriageSystem: Previous bugcheck was %x %p %p %p %p\n",
        BugCheckData[0],
        BugCheckData[1],
        BugCheckData[2],
        BugCheckData[3],
        BugCheckData[4]);

    if (TriageActUpon (TriageDumpBlock) == FALSE) {
        DbgPrint ("MiTriageSystem: Triage disabled in registry by administrator\n");
        return FALSE;
    }

    DbgPrint ("MiTriageSystem: Triage ENABLED in registry by administrator\n");

    //
    // See if the previous bugcheck was one where action can be taken.
    // If not, bail now.  If so, then march on and verify all the loaded
    // module checksums before actually taking action on the bugcheck.
    //

    if (!MI_CAN_TRIAGE_BUGCHECK(BugCheckData[0])) {
        return FALSE;
    }

    TriageInformation = (PMI_TRIAGE_STORAGE) TriageGetMmInformation (TriageDumpBlock);

    if (TriageInformation == NULL) {
        return FALSE;
    }

    Status = TriageGetDriverCount (TriageDumpBlock, &ModuleCount);

    if (!NT_SUCCESS (Status)) {
        return FALSE;
    }

    //
    // Process module information from the triage dump.
    //

#if DBG
    if (MiTriageDebug & 0x1) {
        DbgPrint ("MiTriageSystem: printing active drivers from triage crash...\n");
    }
#endif

    OldDrivers = 0;
    OldDriversNotVerifying = 0;

    for (i = 0; i < ModuleCount; i += 1) {

        DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i);

        if (DumpTableEntry != NULL) {

            if ((DumpTableEntry->Flags & LDRP_ENTRY_NATIVE) == 0) {
                OldDrivers += 1;
                if ((DumpTableEntry->Flags & LDRP_IMAGE_VERIFYING) == 0) {

                    //
                    // An NT3 or NT4 driver is in the system and was not
                    // running under the verifier.
                    //

                    OldDriversNotVerifying += 1;
                }
            }
#if DBG
            if (MiTriageDebug & 0x1) {

                DbgPrint (" %wZ: base = %p, size = %lx, flags = %lx\n",
                          &DumpTableEntry->BaseDllName,
                          DumpTableEntry->DllBase,
                          DumpTableEntry->SizeOfImage,
                          DumpTableEntry->Flags);
            }
#endif
        }
    }

    //
    // Ensure that every driver that is currently loaded is identical to
    // the one in the triage dump before proceeding.
    //

    NextEntry = LoaderBlock->LoadOrderListHead.Flink;

    while (NextEntry != &LoaderBlock->LoadOrderListHead) {

        DataTableEntry = CONTAINING_RECORD(NextEntry,
                                           KLDR_DATA_TABLE_ENTRY,
                                           InLoadOrderLinks);

        Matched = FALSE;

        for (i = 0; i < ModuleCount; i += 1) {
    
            DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i);
    
            if (DumpTableEntry != NULL) {
    
                if (DataTableEntry->CheckSum == DumpTableEntry->CheckSum) {
                    Matched = TRUE;
                    break;
                }
            }
        }
    
        if (Matched == FALSE) {
            DbgPrint ("Matching checksum for module %wZ not found in triage dump\n",
                &DataTableEntry->BaseDllName);

#if DBG
            if (MiTriageRegardless == FALSE)
#endif
            return FALSE;
        }

        NextEntry = NextEntry->Flink;
    }

#if DBG
    if (MiTriageDebug & 0x1) {
        DbgPrint ("MiTriageSystem: OldDrivers = %u, without verification =%u\n",
            OldDrivers,
            OldDriversNotVerifying);
    }
#endif

    //
    // All boot loaded drivers matched, take action on the triage dump now.
    //

    if (MI_HOLD_TRIAGE_BUGCHECK(BugCheckData[0])) {

        //
        // The last bugcheck was presumably triggered by either autotriage or
        // the admin's registry settings - so don't apply any new rules
        // and in addition, keep the old ones unaltered so it can reproduce.
        //

        MmTriageActionTaken = TriageInformation->MiTriageActionTaken;
        MmTriageActionTaken |= MI_KEEPING_PREVIOUS_SETTINGS;
    }
    else {
    
        switch (BugCheckData[0]) {
    
            case DRIVER_CORRUPTED_SYSPTES:
    
                //
                // Turn on PTE tracking to trigger a SYSTEM_PTE_MISUSE bugcheck.
                //
    
                MmTriageActionTaken |= MI_TRACKING_PTES;
                break;
    
            case NO_MORE_SYSTEM_PTES:
    
                //
                // Turn on PTE tracking so the driver can be identified via a
                // DRIVER_USED_EXCESSIVE_PTES bugcheck.
                //
    
                if (BugCheckData[1] == SystemPteSpace) {
                    MmTriageActionTaken |= MI_TRACKING_PTES;
                }
                break;
    
            case BAD_POOL_HEADER:
            case DRIVER_CORRUPTED_EXPOOL:
    
                //
                // Turn on the driver verifier and/or special pool.
                // Start by enabling it for every driver that isn't built for NT5.
                // Override any specified driver verifier options so that only
                // special pool is enabled to minimize the performance hit.
                //
    
                if (OldDrivers != 0) {
                    if (OldDriversNotVerifying != 0) {
                        MmTriageActionTaken |= MI_VERIFYING_PRENT5_DRIVERS;
                    }
                }
    
                break;
    
            case DRIVER_CORRUPTED_MMPOOL:
    
                //
                // Protect freed nonpaged pool if the system had less than 128mb
                // of nonpaged pool anyway.  This is to trigger a
                // DRIVER_CAUGHT_MODIFYING_FREED_POOL bugcheck.
                //
    
#define MB128 ((ULONG_PTR)0x80000000 >> PAGE_SHIFT)
    
                if (TriageInformation->MmMaximumNonPagedPool < MB128) {
                    MmTriageActionTaken |= MI_PROTECT_FREED_NONPAGED_POOL;
                }
                break;
    
            case IRQL_NOT_LESS_OR_EQUAL:
            case DRIVER_IRQL_NOT_LESS_OR_EQUAL:
            default:
                break;
        }
    }

    //
    // For now always show if action was taken from the bugcheck
    // data from the crash.  This print and the space for the print strings
    // will be enabled for checked builds only prior to shipping.
    //

    if (MmTriageActionTaken != 0) {

        if (MmTriageActionTaken & MI_TRACKING_PTES) {
            MmTrackPtes |= 0x1;
        }
    
        if (MmTriageActionTaken & MI_VERIFYING_PRENT5_DRIVERS) {
            MmVerifyDriverLevel &= ~DRIVER_VERIFIER_FORCE_IRQL_CHECKING;
            MmVerifyDriverLevel |= DRIVER_VERIFIER_SPECIAL_POOLING;
        }
    
        if (MmTriageActionTaken & MI_PROTECT_FREED_NONPAGED_POOL) {
            MmProtectFreedNonPagedPool = TRUE;
        }

        DbgPrint ("MiTriageSystem: enabling options below to find who caused the last crash\n");

        for (i = 0; i < 32; i += 1) {
            if (MmTriageActionTaken & (1 << i)) {
                DbgPrint ("  %s\n", MiTriageActionStrings[i]);
            }
        }
    }

    return TRUE;
}


LOGICAL
MiTriageAddDrivers (
    IN PLOADER_PARAMETER_BLOCK LoaderBlock
    )

/*++

Routine Description:

    This routine moves the names of any drivers that autotriage has determined
    need verifying from the LoaderBlock into pool.

Arguments:

    LoaderBlock - Supplies a pointer to the system loader block.

Return Value:

    TRUE if any drivers were added, FALSE if not.

--*/

{
    ULONG i;
    ULONG ModuleCount;
    NTSTATUS Status;
    PKLDR_DATA_TABLE_ENTRY DumpTableEntry;
    PVOID TriageDumpBlock;
    ULONG NameLength;
    LOGICAL Added;
    PMI_VERIFIER_DRIVER_ENTRY VerifierDriverEntry;

    if ((MmTriageActionTaken & MI_VERIFYING_PRENT5_DRIVERS) == 0) {
        return FALSE;
    }

    TriageDumpBlock = LoaderBlock->Extension->TriageDumpBlock;

    Status = TriageGetDriverCount (TriageDumpBlock, &ModuleCount);

    if (!NT_SUCCESS (Status)) {
        return FALSE;
    }

    Added = FALSE;

    for (i = 0; i < ModuleCount; i += 1) {

        DumpTableEntry = TriageGetLoaderEntry (TriageDumpBlock, i);

        if (DumpTableEntry == NULL) {
            continue;
        }

        if (DumpTableEntry->Flags & LDRP_ENTRY_NATIVE) {
            continue;
        }

        DbgPrint ("MiTriageAddDrivers: Marking %wZ for verification when it is loaded\n", &DumpTableEntry->BaseDllName);

        NameLength = DumpTableEntry->BaseDllName.Length;

        VerifierDriverEntry = (PMI_VERIFIER_DRIVER_ENTRY)ExAllocatePoolWithTag (
                                    NonPagedPool,
                                    sizeof (MI_VERIFIER_DRIVER_ENTRY) +
                                                        NameLength,
                                    'dLmM');

        if (VerifierDriverEntry == NULL) {
            continue;
        }

        VerifierDriverEntry->Loads = 0;
        VerifierDriverEntry->Unloads = 0;
        VerifierDriverEntry->BaseName.Buffer = (PWSTR)((PCHAR)VerifierDriverEntry +
                            sizeof (MI_VERIFIER_DRIVER_ENTRY));

        VerifierDriverEntry->BaseName.Length = (USHORT)NameLength;
        VerifierDriverEntry->BaseName.MaximumLength = (USHORT)NameLength;

        RtlCopyMemory (VerifierDriverEntry->BaseName.Buffer,
                       DumpTableEntry->BaseDllName.Buffer,
                       NameLength);

        InsertHeadList (&MiSuspectDriverList, &VerifierDriverEntry->Links);
        Added = TRUE;
    }

    return Added;
}

#define MAX_UNLOADED_NAME_LENGTH    24

typedef struct _DUMP_UNLOADED_DRIVERS {
    UNICODE_STRING Name;
    WCHAR DriverName[MAX_UNLOADED_NAME_LENGTH / sizeof (WCHAR)];
    PVOID StartAddress;
    PVOID EndAddress;
} DUMP_UNLOADED_DRIVERS, *PDUMP_UNLOADED_DRIVERS;


ULONG
MmSizeOfUnloadedDriverInformation (
    VOID
    )

/*++

Routine Description:

    This routine returns the size of the Mm-internal unloaded driver
    information that is stored in the triage dump when (if?) the system crashes.

Arguments:

    None.

Return Value:

    Size of the Mm-internal unloaded driver information.

--*/

{
    if (MmUnloadedDrivers == NULL) {
        return sizeof (ULONG_PTR);
    }

    return sizeof(ULONG_PTR) + MI_UNLOADED_DRIVERS * sizeof(DUMP_UNLOADED_DRIVERS);
}


VOID
MmWriteUnloadedDriverInformation (
    IN PVOID Destination
    )

/*++

Routine Description:

    This routine stores the Mm-internal unloaded driver information into
    the triage dump.

Arguments:

    None.

Return Value:

    None.

--*/

{
    ULONG i;
    ULONG Index;
    PUNLOADED_DRIVERS Unloaded;
    PDUMP_UNLOADED_DRIVERS DumpUnloaded;

    if (MmUnloadedDrivers == NULL) {
        *(PULONG)Destination = 0;
    }
    else {

        DumpUnloaded = (PDUMP_UNLOADED_DRIVERS)((PULONG_PTR)Destination + 1);
        Unloaded = MmUnloadedDrivers;

        //
        // Write the list with the most recently unloaded driver first to the
        // least recently unloaded driver last.
        //

        Index = MmLastUnloadedDriver - 1;

        for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) {

            if (Index >= MI_UNLOADED_DRIVERS) {
                Index = MI_UNLOADED_DRIVERS - 1;
            }

            Unloaded = &MmUnloadedDrivers[Index];

            DumpUnloaded->Name = Unloaded->Name;

            if (Unloaded->Name.Buffer == NULL) {
                break;
            }

            DumpUnloaded->StartAddress = Unloaded->StartAddress;
            DumpUnloaded->EndAddress = Unloaded->EndAddress;

            if (DumpUnloaded->Name.Length > MAX_UNLOADED_NAME_LENGTH) {
                DumpUnloaded->Name.Length = MAX_UNLOADED_NAME_LENGTH;
            }

            if (DumpUnloaded->Name.MaximumLength > MAX_UNLOADED_NAME_LENGTH) {
                DumpUnloaded->Name.MaximumLength = MAX_UNLOADED_NAME_LENGTH;
            }

            DumpUnloaded->Name.Buffer = DumpUnloaded->DriverName;

            RtlCopyMemory ((PVOID)DumpUnloaded->Name.Buffer,
                           (PVOID)Unloaded->Name.Buffer,
                           DumpUnloaded->Name.MaximumLength);

            DumpUnloaded += 1;
            Index -= 1;
        }

        *(PULONG)Destination = i;
    }
}


ULONG
MmSizeOfTriageInformation (
    VOID
    )

/*++

Routine Description:

    This routine returns the size of the Mm-internal information that is
    stored in the triage dump when (if?) the system crashes.

Arguments:

    None.

Return Value:

    Size of the Mm-internal triage information.

--*/

{
    return sizeof (MI_TRIAGE_STORAGE);
}


VOID
MmWriteTriageInformation (
    IN PVOID Destination
    )

/*++

Routine Description:

    This routine stores the Mm-internal information into the triage dump.

Arguments:

    None.

Return Value:

    None.

--*/

{
    MI_TRIAGE_STORAGE TriageInformation;

    TriageInformation.Version = 1;
    TriageInformation.Size = sizeof (MI_TRIAGE_STORAGE);

    TriageInformation.MmSpecialPoolTag = MmSpecialPoolTag;
    TriageInformation.MiTriageActionTaken = MmTriageActionTaken;

    TriageInformation.MmVerifyDriverLevel = MmVerifierData.Level;
    TriageInformation.KernelVerifier = KernelVerifier;

    TriageInformation.MmMaximumNonPagedPool = MmMaximumNonPagedPoolInBytes >> PAGE_SHIFT;
    TriageInformation.MmAllocatedNonPagedPool = MmAllocatedNonPagedPool;

    TriageInformation.PagedPoolMaximum = MmSizeOfPagedPoolInBytes >> PAGE_SHIFT;
    TriageInformation.PagedPoolAllocated = MmPagedPoolInfo.AllocatedPagedPool;

    TriageInformation.CommittedPages = MmTotalCommittedPages;
    TriageInformation.CommittedPagesPeak = MmPeakCommitment;
    TriageInformation.CommitLimitMaximum = MmTotalCommitLimitMaximum;

    RtlCopyMemory (Destination,
                   (PVOID)&TriageInformation,
                   sizeof (MI_TRIAGE_STORAGE));
}