/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    stacks.c

Abstract:

    WinDbg Extension Api

Author:

    Adrian J. Oney (adriao) 07-28-1998

Environment:

    User Mode.

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

typedef enum _KTHREAD_STATE {
    Initialized,
    Ready,
    Running,
    Standby,
    Terminated,
    Waiting,
    Transition
    } KTHREAD_STATE;

typedef enum {

    NO_STACK_ACTION     = 0,
    SKIP_FRAME,
    SKIP_THREAD

} STACKS_ACTION, *PSTACKS_ACTION;

#define ETHREAD_NOT_READABLE    1
#define THREAD_VALID            2
#define FIRST_THREAD_VALID      3
#define NO_THREADS              4

struct _BLOCKER_TREE ;
typedef struct _BLOCKER_TREE BLOCKER_TREE, *PBLOCKER_TREE ;

BOOL
StacksValidateProcess(
    IN ULONG64 RealProcessBase
    );

BOOL
StacksValidateThread(
    IN ULONG64 RealThreadBase
    );

VOID StacksDumpProcessAndThread(
    IN ULONG64   RealProcessBase,
    IN ULONG     ThreadDesc,
    IN ULONG64   RealThreadBase,
    IN PBLOCKER_TREE BlockerTree,
    IN ULONG     Verbosity,
    IN char *    Filter
    );

VOID StacksGetThreadStateName(
    IN ULONG ThreadState,
    OUT PCHAR Dest
    );

VOID
DumpThreadBlockageInfo (
    IN char   *pad,
    IN ULONG64 RealThreadBase,
    IN ULONG   Verbosity
    );

typedef enum {

    STACK_WALK_DUMP_STARTING = 0,
    STACK_WALK_DUMP_NOT_RESIDENT,
    STACK_WALK_DUMP_ENTRY,
    STACK_WALK_DUMP_FINISHED

} WALK_STAGE;

typedef struct {

    ULONG   Verbosity;
    BOOLEAN FirstEntry;
    char*   ThreadState;
    char*   ThreadBlocker;
    ULONG   ProcessCid;
    ULONG   ThreadCid;
    ULONG64 ThreadBlockerDisplacement;

} STACK_DUMP_CONTEXT, *PSTACK_DUMP_CONTEXT;

typedef BOOLEAN (*PFN_FRAME_WALK_CALLBACK)(
    IN WALK_STAGE   WalkStage,
    IN ULONG64      RealThreadBase,
    IN PVOID        Context,
    IN char *       Buffer,
    IN ULONG64      Offset
    );

VOID
ForEachFrameOnThread(
    IN ULONG64                 RealThreadBase,
    IN PFN_FRAME_WALK_CALLBACK Callback,
    IN PVOID                   Context
    );

BOOLEAN
StacksDumpStackCallback(
    IN WALK_STAGE   WalkStage,
    IN ULONG64      RealThreadBase,
    IN PVOID        Context,
    IN char *       Buffer,
    IN ULONG64      Offset
    );

extern ULONG64 STeip, STebp, STesp;
static ULONG64 PspCidTable;

ULONG64 ProcessLastDump;
ULONG64 ThreadLastDump;

ULONG TotalProcessCommit;

struct _BLOCKER_TREE {
   char const *Symbolic ;
   STACKS_ACTION Action ;
   PBLOCKER_TREE Child ;
   PBLOCKER_TREE Sibling ;
   PBLOCKER_TREE Parent ;
   BOOL Nested ;
} ;

VOID
AnalyzeThread(
    IN  ULONG64         RealThreadBase,
    IN  PBLOCKER_TREE   BlockerTree,
    IN  char *          Filter,
    OUT PCHAR           BlockSymbol,
    OUT ULONG64        *BlockDisplacement,
    OUT BOOLEAN        *SkipThread
    );

BOOL
BlockerTreeWalk(
   IN OUT PBLOCKER_TREE *blockerHead,
   IN char *szSymbolic,
   IN STACKS_ACTION Action
   );

PBLOCKER_TREE
BlockerTreeBuild(
   VOID
   ) ;

VOID
BlockerTreeFree(
   IN PBLOCKER_TREE BlockerTree
   ) ;

DECLARE_API( stacks )

/*++

Routine Description:

    Dumps the active process list.

Arguments:

    None.

Return Value:

    None.

--*/

{
    ULONG Result;
    ULONG64 Next;
    ULONG64 NextThread;
    ULONG64 ProcessHead;
    ULONG64 Process;
    ULONG64 Thread;
    ULONG64 UserProbeAddress;
    ULONG Verbosity = 0, ThreadCount = 0;
    ULONG ActProcOffset, ThrdListOffset, ThrdEntryOffset;
    PBLOCKER_TREE blockerTree ;
    char szFilter[256];

    blockerTree = BlockerTreeBuild() ;

    if (sscanf(args, "%x %s", &Verbosity, szFilter) < 2) {

        szFilter[0] = '\0';
    }

    dprintf("Proc.Thread  .Thread  ThreadState  Blocker\n") ;

    UserProbeAddress = GetNtDebuggerDataValue(MmUserProbeAddress);
    ProcessHead = GetNtDebuggerData( PsActiveProcessHead );
    if (!ProcessHead) {
        dprintf("Unable to get value of PsActiveProcessHead!\n");
        goto Exit;
    }

    if (GetFieldValue( ProcessHead, "nt!_LIST_ENTRY", "Flink", Next )) {
        dprintf("Unable to get value of PsActiveProcessHead.Flink\n");
        goto Exit ;
    }

    if (Next == 0) {
        dprintf("PsActiveProcessHead is NULL!\n");
        goto Exit;
    }

    GetFieldOffset("nt!_EPROCESS", "ActiveProcessLinks", &ActProcOffset);
    GetFieldOffset("nt!_EPROCESS", "Pcb.ThreadListHead", &ThrdListOffset);
    GetFieldOffset("nt!_KTHREAD",  "ThreadListEntry",    &ThrdEntryOffset);

    while(Next != ProcessHead) {
        ULONG64 FirstThread;

        Process = Next - ActProcOffset;

        if (GetFieldValue( Process, "nt!_EPROCESS", "Pcb.ThreadListHead.Flink", FirstThread )) {
            dprintf("Unable to read nt!_EPROCESS at %p\n",Process);
            goto Exit;
        }

        NextThread = FirstThread;
        if (!StacksValidateProcess(Process)) {

            dprintf("Process list damaged, or maybe symbols are incorrect?\n%p\n",Process);
            goto Exit;
        }

        if (NextThread == Process + ThrdListOffset) {

            StacksDumpProcessAndThread(Process, NO_THREADS, 0, blockerTree, Verbosity, szFilter) ;

        } else {

            while ( NextThread != Process + ThrdListOffset) {
                ULONG64 ThreadFlink;

                Thread = NextThread - ThrdEntryOffset;
                if (GetFieldValue(Thread,
                                  "nt!_ETHREAD",
                                  "Tcb.ThreadListEntry.Flink",
                                  ThreadFlink)) {

                    StacksDumpProcessAndThread(Process, ETHREAD_NOT_READABLE, 0, blockerTree, Verbosity, szFilter) ;

                    dprintf("Unable to read _ETHREAD at %lx\n",Thread);
                    break;
                }

                if (!StacksValidateThread(Thread)) {

                    StacksDumpProcessAndThread( Process, ETHREAD_NOT_READABLE, 0, blockerTree, Verbosity, szFilter) ;
                } else if (NextThread == FirstThread) {

                    ThreadCount++;
                    StacksDumpProcessAndThread(Process, FIRST_THREAD_VALID, Thread, blockerTree, Verbosity, szFilter) ;
                } else {

                    ThreadCount++;
                    StacksDumpProcessAndThread(Process, THREAD_VALID, Thread, blockerTree, Verbosity, szFilter) ;
                }

                NextThread = ThreadFlink;
                if (CheckControlC()) {
                    goto Exit;
                }
            }
        }

        EXPRLastDump = Process;
        ProcessLastDump = Process;
        dprintf("\n");
        GetFieldValue( Process, "nt!_EPROCESS", "ActiveProcessLinks.Flink", Next);

        if (CheckControlC()) {
            goto Exit;
        }
    }
Exit:
   BlockerTreeFree(blockerTree) ;
   dprintf("\nThreads Processed: %d\n", ThreadCount);
   return S_OK;
}

BOOL
StacksValidateProcess(
    IN ULONG64 RealProcessBase
    )
{
    ULONG Type;

    GetFieldValue(RealProcessBase, "nt!_EPROCESS", "Pcb.Header.Type", Type);
    if (Type != ProcessObject) {
        dprintf("TYPE mismatch for process object at %p\n",RealProcessBase);
        return FALSE;
    }
    return TRUE ;
}

VOID StacksDumpProcessAndThread(
    IN ULONG64       RealProcessBase,
    IN ULONG         ThreadDesc,
    IN ULONG64       RealThreadBase,
    IN PBLOCKER_TREE BlockerTree,
    IN ULONG         Verbosity,
    IN char *        Filter
    )
{
    ULONG NumberOfHandles;
    ULONG Result;
    CHAR  Buf[512];
    CHAR  ThreadState[13] ;
    CHAR  ThreadBlocker[256] ;
    UINT  i ;
    ULONG64 ObjectTable, ThreadBlockerDisplacement;
    CHAR *ThreadStateName ;
    ULONG UniqueProcessCid;
    STACK_DUMP_CONTEXT dumpData;
    BOOLEAN SkipThread;

    NumberOfHandles = 0;
    GetFieldValue(RealProcessBase, "nt!_EPROCESS", "ImageFileName", Buf);

    InitTypeRead(RealProcessBase, nt!_EPROCESS);

    if (ObjectTable = ReadField(ObjectTable)) {
        GetFieldValue(ObjectTable, "nt!_HANDLE_TABLE", "HandleCount", NumberOfHandles);
    }

    if (Buf[0] == '\0' ) {
        strcpy((char *)Buf,"System Process");
    }

    UniqueProcessCid = (ULONG) ReadField(UniqueProcessId);

    if (!RealThreadBase) {

        switch(ThreadDesc) {

            case NO_THREADS:

                dprintf("                            [%p %s]\n", RealProcessBase, Buf) ;

                if ((Verbosity > 0) && (Filter[0] == '\0')) {
                    dprintf("%4lx.------  NOTHREADS\n",
                        UniqueProcessCid
                        );
                }

                break ;

            case ETHREAD_NOT_READABLE:

                dprintf("%4lx.------  NO ETHREAD DATA\n",
                    UniqueProcessCid
                    );

                break ;
        }

        return;
    }

    InitTypeRead(RealThreadBase, nt!_ETHREAD);

    ASSERT(((ULONG) ReadField(Cid.UniqueProcess)) == UniqueProcessCid);

    StacksGetThreadStateName((ULONG) ReadField(Tcb.State), ThreadState) ;
    i=strlen(ThreadState) ;
    while(i<11) ThreadState[i++]=' ' ;
    ThreadState[i]='\0' ;

    AnalyzeThread(
        RealThreadBase,
        BlockerTree,
        Filter,
        ThreadBlocker,
        &ThreadBlockerDisplacement,
        &SkipThread
        );

    if (ThreadDesc == FIRST_THREAD_VALID) {

        dprintf("                            [%p %s]\n", RealProcessBase, Buf) ;
    }

    if (SkipThread && ((Verbosity == 0) || (Filter[0]))) {

        return;
    }

    dumpData.Verbosity = Verbosity;
    dumpData.ThreadState = ThreadState;
    dumpData.ThreadBlocker = ThreadBlocker;
    dumpData.ThreadBlockerDisplacement = ThreadBlockerDisplacement;
    dumpData.ProcessCid = UniqueProcessCid;
    dumpData.ThreadCid = (ULONG) ReadField(Cid.UniqueThread);

    ForEachFrameOnThread(
        RealThreadBase,
        StacksDumpStackCallback,
        (PVOID) &dumpData
        );
}

BOOLEAN
StacksDumpStackCallback(
    IN WALK_STAGE   WalkStage,
    IN ULONG64      RealThreadBase,
    IN PVOID        Context,
    IN char *       Buffer,
    IN ULONG64      Offset
    )
{
    PSTACK_DUMP_CONTEXT dumpData = (PSTACK_DUMP_CONTEXT) Context;

    switch(WalkStage) {

        case STACK_WALK_DUMP_STARTING:

            dprintf("%4lx.%06lx  %08p  %s",
                dumpData->ProcessCid,
                dumpData->ThreadCid,
                RealThreadBase,
                dumpData->ThreadState
                );

            dumpData->FirstEntry = TRUE;
            return TRUE;

        case STACK_WALK_DUMP_FINISHED:

            dumpData->FirstEntry = FALSE;
            dprintf("\n");
            return TRUE;

        case STACK_WALK_DUMP_NOT_RESIDENT:
        case STACK_WALK_DUMP_ENTRY:

            if (dumpData->FirstEntry) {

                dumpData->FirstEntry = FALSE;
            } else {

                dprintf("\n                                  ");
            }
            break;

        default:
            return FALSE;
    }

    if (WalkStage == STACK_WALK_DUMP_NOT_RESIDENT) {

        switch(dumpData->Verbosity) {

            case 0:
            case 1:
            case 2:
                dprintf("Stack paged out");
                break;
        }

        return FALSE;
    }

    switch(dumpData->Verbosity) {

        case 0:
        case 1:
            dprintf("%s", dumpData->ThreadBlocker);
            if (dumpData->ThreadBlockerDisplacement) {
                dprintf( "+0x%1p", dumpData->ThreadBlockerDisplacement );
            }
            dprintf("\n");
            return FALSE;

        case 2:
            dprintf("%s", Buffer);
            if (Offset) {
                dprintf( "+0x%1p", Offset );
            }
            break;
    }

    return TRUE;
}


UCHAR *StacksWaitReasonList[] = {
    "Executive",
    "FreePage",
    "PageIn",
    "PoolAllocation",
    "DelayExecution",
    "Suspended",
    "UserRequest",
    "WrExecutive",
    "WrFreePage",
    "WrPageIn",
    "WrPoolAllocation",
    "WrDelayExecution",
    "WrSuspended",
    "WrUserRequest",
    "WrEventPairHigh",
    "WrEventPairLow",
    "WrLpcReceive",
    "WrLpcReply",
    "WrVirtualMemory",
    "WrPageOut",
    "Spare1",
    "Spare2",
    "Spare3",
    "Spare4",
    "Spare5",
    "Spare6",
    "Spare7"};


VOID StacksGetThreadStateName(
    IN ULONG ThreadState,
    OUT PCHAR Dest
    )
{
    switch (ThreadState) {
        case Initialized: strcpy(Dest, "INITIALIZED"); break;
        case Ready:       strcpy(Dest, "READY"); break;
        case Running:     strcpy(Dest, "RUNNING"); break;
        case Standby:     strcpy(Dest, "STANDBY"); break;
        case Terminated:  strcpy(Dest, "TERMINATED"); break;
        case Waiting:     strcpy(Dest, "Blocked"); break;
        case Transition:  strcpy(Dest, "TRANSITION"); break;
        default:          strcpy(Dest, "????") ; break ;
    }
}


BOOL
StacksValidateThread (
    IN ULONG64 RealThreadBase
    )
{
    ULONG Type;

    GetFieldValue(RealThreadBase, "nt!_ETHREAD", "Tcb.Header.Type", Type);
    if (Type != ThreadObject) {
        dprintf("TYPE mismatch for thread object at %p\n",RealThreadBase);
        return FALSE;
    }
    return TRUE ;
}


VOID
DumpThreadBlockageInfo (
    IN char   *Pad,
    IN ULONG64 RealThreadBase,
    IN ULONG   Verbosity
    )
{
    #define MAX_STACK_FRAMES  40
    TIME_FIELDS Times;
    LARGE_INTEGER RunTime;
    ULONG64 Address;
    ULONG Result;
    ULONG64 WaitBlock;
    ULONG WaitOffset;
    ULONG64 Process;
    CHAR Buffer[80];
    ULONG KeTimeIncrement;
    ULONG TimeIncrement;
    ULONG frames = 0;
    ULONG i;
    ULONG displacement;
    ULONG64 WaitBlockList;
    ULONG   IrpOffset;

    InitTypeRead(RealThreadBase, nt!_ETHREAD);

    if ((ULONG) ReadField(Tcb.State) == Waiting) {
        dprintf("%s (%s) %s %s\n",
            Pad,
            StacksWaitReasonList[(ULONG) ReadField(Tcb.WaitReason)],
            ((ULONG) ReadField(Tcb.WaitMode)==KernelMode) ? "KernelMode" : "UserMode",(ULONG) ReadField(Tcb.Alertable) ? "Alertable" : "Non-Alertable");
        if ( ReadField(Tcb.SuspendCount) ) {
            dprintf("SuspendCount %lx\n",(ULONG) ReadField(Tcb.SuspendCount));
        }
        if ( ReadField(Tcb.FreezeCount) ) {
            dprintf("FreezeCount %lx\n",(ULONG) ReadField(Tcb.FreezeCount));
        }

        WaitBlockList = ReadField(Tcb.WaitBlockList);

        if (InitTypeRead(WaitBlockList,nt!KWAIT_BLOCK)) {
            dprintf("%sunable to get Wait object\n",Pad);
            goto BadWaitBlock;
        }

        do {
            ULONG64 Object, NextWaitBlock, OwnerThread;
            ULONG Limit;

            dprintf("%s    %lx  ",Pad, Object = ReadField(Object));
            NextWaitBlock = ReadField(NextWaitBlock);

            if (InitTypeRead(Object,nt!KMUTANT)) {

                dprintf("%sunable to get Wait object\n",Pad);
                break;
            }

            GetFieldValue(Object, "nt!KSEMAPHORE", "Limit", Limit);
            GetFieldValue(Object, "nt!KSEMAPHORE", "OwnerThread",OwnerThread);
            switch (ReadField(Header.Type)) {
                case EventNotificationObject:
                    dprintf("NotificationEvent\n");
                    break;
                case EventSynchronizationObject:
                    dprintf("SynchronizationEvent\n");
                    break;
                case SemaphoreObject:
                    dprintf("Semaphore Limit 0x%p\n",
                             Limit);
                    break;
                case ThreadObject:
                    dprintf("Thread\n");
                    break;
                case TimerNotificationObject:
                    dprintf("NotificationTimer\n");
                    break;
                case TimerSynchronizationObject:
                    dprintf("SynchronizationTimer\n");
                    break;
                case EventPairObject:
                    dprintf("EventPair\n");
                    break;
                case ProcessObject:
                    dprintf("ProcessObject\n");
                    break;
                case MutantObject:
                    dprintf("Mutant - owning thread %p\n",
                            OwnerThread);
                    break;
                default:
                    dprintf("Unknown\n");
                    break;
            }

            if (NextWaitBlock == WaitBlockList) {
                break;
            }

            if (InitTypeRead(NextWaitBlock,nt!KWAIT_BLOCK)) {
                dprintf("%sunable to get Wait object\n",Pad);
                break;
            }
        } while ( TRUE );
    }

BadWaitBlock:

    //
    // Re-intialize thread read
    //
    InitTypeRead(RealThreadBase, nt!_ETHREAD);

    if ( ReadField(LpcReplyMessageId) != 0) {
        dprintf("%sWaiting for reply to LPC MessageId %08x:\n",Pad, (ULONG) ReadField(LpcReplyMessageId));
    }

    if (Address = ReadField(LpcReplyMessage)) {
        ULONG64 Entry_Flink, Entry_Blink;

        dprintf("%sPending LPC Reply Message:\n",Pad);

        if (GetFieldValue(Address, "nt!_LPCP_MESSAGE", "Entry.Blink", Entry_Blink)) {
            dprintf("unable to get LPC msg\n");
        } else {
            GetFieldValue(Address, "nt!_LPCP_MESSAGE", "Entry.Flink", Entry_Flink);
            dprintf("%s    %08p: [%08p,%08p]\n",
                    Pad, Address, Entry_Blink, Entry_Flink
                   );
        }
    }

    GetFieldOffset("nt!_ETHREAD", "IrpList", &IrpOffset);

    if (ReadField(IrpList.Flink) != ReadField(IrpList.Blink) ||
        ReadField(IrpList.Flink) != RealThreadBase + IrpOffset
       ) {

        ULONG64 IrpListHead = RealThreadBase + IrpOffset;
        ULONG64 Next;
        ULONG Counter = 0;
        ULONG IrpThrdOff;

        Next = ReadField(IrpList.Flink);

        GetFieldOffset("nt!_IRP", "ThreadListEntry", &IrpThrdOff);

        dprintf("%sIRP List:\n",Pad);
        while ((Next != IrpListHead) && (Counter < 17)) {
            ULONG Irp_Type=0, Irp_Size=0, Irp_Flags=0;
            ULONG64 Irp_MdlAddress=0;

            Counter += 1;

            Address = Next - IrpThrdOff;

            GetFieldValue(Address, "nt!_IRP", "Type",          Irp_Type);
            GetFieldValue(Address, "nt!_IRP", "Size",          Irp_Size);
            GetFieldValue(Address, "nt!_IRP", "Flags",         Irp_Flags);
            GetFieldValue(Address, "nt!_IRP", "MdlAddress",    Irp_MdlAddress);
            GetFieldValue(Address, "nt!_IRP", "ThreadListEntry.Flink",  Next);

            dprintf("%s    %08p: (%04x,%04x) Flags: %08lx  Mdl: %08lp\n",
                    Pad,Address,Irp_Type,Irp_Size,Irp_Flags,Irp_MdlAddress);

        }
    }

}

VOID
ForEachFrameOnThread(
    IN ULONG64                 RealThreadBase,
    IN PFN_FRAME_WALK_CALLBACK Callback,
    IN PVOID                   Context
    )
{
    #define MAX_STACK_FRAMES  40
    TIME_FIELDS Times;
    LARGE_INTEGER RunTime;
    ULONG Address;
    ULONG Result;
    CHAR Buffer[256];
    ULONG KeTimeIncrement;
    ULONG TimeIncrement;
    ULONG frames = 0;
    ULONG i;
    ULONG64 displacement, tcb_KernelStackResident;
    EXTSTACKTRACE64 stk[MAX_STACK_FRAMES];

    InitTypeRead(RealThreadBase, nt!_ETHREAD);

    tcb_KernelStackResident = ReadField(Tcb.KernelStackResident);

    if (!tcb_KernelStackResident) {

        if (Callback(STACK_WALK_DUMP_STARTING, RealThreadBase, Context, NULL, 0)) {

            Callback(STACK_WALK_DUMP_NOT_RESIDENT, RealThreadBase, Context, NULL, 0);
            Callback(STACK_WALK_DUMP_FINISHED, RealThreadBase, Context, NULL, 0);
        }

        return;
    }

    SetThreadForOperation64( &RealThreadBase );
    frames = StackTrace( 0, 0, 0, stk, MAX_STACK_FRAMES );


    if (!Callback(STACK_WALK_DUMP_STARTING, RealThreadBase, Context, NULL, 0)) {

        return;
    }

    for (i=0; i<frames; i++) {

        Buffer[0] = '!';
        GetSymbol(stk[i].ProgramCounter, Buffer, &displacement);

        if (!Callback(
            STACK_WALK_DUMP_ENTRY,
            RealThreadBase,
            Context,
            Buffer,
            displacement)) {

            return;
        }
    }

    Callback(STACK_WALK_DUMP_FINISHED, RealThreadBase, Context, NULL, 0);
}

VOID
AnalyzeThread(
    IN  ULONG64         RealThreadBase,
    IN  PBLOCKER_TREE   BlockerTree,
    IN  char *          Filter,
    OUT PCHAR           BlockBuffer,
    OUT ULONG64        *BlockerDisplacement,
    OUT BOOLEAN        *SkipThread
    )
{
    #define MAX_STACK_FRAMES  40
    TIME_FIELDS Times;
    LARGE_INTEGER RunTime;
    ULONG Address;
    ULONG Result;
    ULONG WaitOffset;
    ULONG KeTimeIncrement;
    ULONG TimeIncrement;
    ULONG frames = 0;
    ULONG i;
    ULONG64 displacement, tcb_KernelStackResident;
    PBLOCKER_TREE blockerCur ;
    EXTSTACKTRACE64 stk[MAX_STACK_FRAMES];
    BOOLEAN filterMatch, blockerMatch;
    CHAR  tempFrame[256], lcFilter[256], lcFrame[256];

    InitTypeRead(RealThreadBase, nt!_ETHREAD);

    tcb_KernelStackResident = ReadField(Tcb.KernelStackResident);

    if (!tcb_KernelStackResident) {

        *SkipThread = TRUE;
        *BlockerDisplacement = 0;
        BlockBuffer[0] = '\0';
        return;
    }

    SetThreadForOperation64( &RealThreadBase );
    frames = StackTrace( 0, 0, 0, stk, MAX_STACK_FRAMES );

    *SkipThread = FALSE;

    BlockBuffer[0] = '!';

    if (Filter[0]) {

        strcpy(lcFilter, Filter);
        _strlwr(lcFilter);
    }

    if (frames == 0) {

         strcpy(BlockBuffer, "?? Kernel stack not resident ??") ;
         *SkipThread = TRUE;
    } else {

        if (ReadField(Tcb.State) == Running) {

            GetSymbol(stk[0].ProgramCounter, BlockBuffer, &displacement);
            *BlockerDisplacement = displacement;

        } else {

            blockerMatch = FALSE;
            filterMatch = FALSE;

            for(i=0; i<frames; i++) {

                GetSymbol(stk[i].ProgramCounter, tempFrame, &displacement);
                if ((!filterMatch) && Filter[0]) {

                    strcpy(lcFrame, tempFrame);
                    _strlwr(lcFrame);
                    if (strstr(lcFrame, lcFilter)) {

                        filterMatch = TRUE;
                    }
                }

                blockerCur = BlockerTree;
                if ((!blockerMatch) &&
                    (!BlockerTreeWalk(&blockerCur, tempFrame, SKIP_FRAME))) {

                    blockerMatch = TRUE;
                    strcpy(BlockBuffer, tempFrame);
                    *BlockerDisplacement = displacement;
                    if (filterMatch || (Filter[0]=='\0')) {
                        break;
                    }
                }
            }

            blockerCur = BlockerTree;
            if (Filter[0]) {

                if (!filterMatch) {

                    *SkipThread = TRUE;
                }

            } else {

                if (BlockerTreeWalk(&blockerCur, BlockBuffer, SKIP_THREAD)) {

                    *SkipThread = TRUE;
                }
            }
        }
    }
}

#define BEGIN_TREE()
#define END_TREE()
#define DECLARE_ENTRY(foo, action) BlockerTreeDeclareEntry(foo, action)
#define BEGIN_LIST() BlockerTreeListBegin()
#define END_LIST() BlockerTreeListEnd()

PBLOCKER_TREE gpCurrentBlocker ;

VOID
BlockerTreeListBegin(
   VOID
   )
{
   //dprintf("Nest for %x\n", gpCurrentBlocker) ;
   ASSERT(!gpCurrentBlocker->Nested) ;
   gpCurrentBlocker->Nested = TRUE ;
}

VOID
BlockerTreeListEnd(
   VOID
   )
{
   //dprintf("Unnest for %x\n", gpCurrentBlocker) ;
   gpCurrentBlocker = gpCurrentBlocker->Parent ;
   ASSERT(gpCurrentBlocker->Nested) ;
   gpCurrentBlocker->Nested = FALSE ;
}

VOID
BlockerTreeDeclareEntry(
   const char      *szSymbolic,
   STACKS_ACTION    StacksAction
   )
{
   PBLOCKER_TREE blockerEntry ;

   blockerEntry = (PBLOCKER_TREE) malloc(sizeof(BLOCKER_TREE)) ;
   if (!blockerEntry) {
      return ;
   }

   memset(blockerEntry, 0, sizeof(BLOCKER_TREE)) ;
   blockerEntry->Symbolic = szSymbolic ;
   blockerEntry->Action = StacksAction;

   if (gpCurrentBlocker->Nested) {
      ASSERT(!gpCurrentBlocker->Child) ;
      //dprintf("Child %x for %x\n", blockerEntry, gpCurrentBlocker) ;
      blockerEntry->Parent = gpCurrentBlocker ;
      gpCurrentBlocker->Child = blockerEntry ;
   } else {
      ASSERT(!gpCurrentBlocker->Sibling) ;
      //dprintf("sibling %x for %x\n", blockerEntry, gpCurrentBlocker) ;
      blockerEntry->Parent = gpCurrentBlocker->Parent ;
      gpCurrentBlocker->Sibling = blockerEntry ;
   }
   gpCurrentBlocker = blockerEntry ;
}

PBLOCKER_TREE
BlockerTreeBuild(
   VOID
   )
{
   BLOCKER_TREE blockerHead ;

   memset(&blockerHead, 0, sizeof(BLOCKER_TREE)) ;

   gpCurrentBlocker = &blockerHead ;

   //
   // Generate the list...
   //
   #include "stacks.h"

   //
   // And return it.
   //
   return blockerHead.Sibling ;
}

VOID BlockerTreeFree(
   PBLOCKER_TREE BlockerHead
   )
{
   PBLOCKER_TREE blockerCur, blockerNext ;

   for(blockerCur = BlockerHead; blockerCur; blockerCur = blockerNext) {
      if (blockerCur->Child) {
         BlockerTreeFree(blockerCur->Child) ;
      }
      blockerNext = blockerCur->Sibling ;
      free(blockerCur) ;
   }
}

BOOL
BlockerTreeWalk(
   IN OUT PBLOCKER_TREE *blockerHead,
   IN char *szSymbolic,
   IN STACKS_ACTION Action
   )
{
   PBLOCKER_TREE blockerCur ;
   const char *blockString, *curString, *strptr;
   char szStringCopy[512];

   for(blockerCur = *blockerHead; blockerCur; blockerCur = blockerCur->Sibling) {

       if (Action != blockerCur->Action) {

           continue;
       }

       blockString = blockerCur->Symbolic;
       curString = szSymbolic;

       strptr = strstr(curString, "!.");
       if (strptr) {

           //
           // This must be an ia64 symbol. Replace the !. with a nice simple !
           //
           strcpy(szStringCopy, curString);
           strcpy(szStringCopy + (strptr - curString) + 1, strptr + 2);
           curString = szStringCopy;
       }

       //
       // Special case "Our Kernel of Many Names"
       //
       if (!_strnicmp(blockString, "nt!", 3)) {

           if ((!_strnicmp(curString, "ntoskrnl!", 9)) ||
               (!_strnicmp(curString, "ntkrnlmp!", 9)) ||
               (!_strnicmp(curString, "ntkrpamp!", 9)) ||
               (!_strnicmp(curString, "ntkrnlpa!", 9))) {

               blockString += 3;
               curString += 9;
           }
       }

       if (!_strcmpi(blockString, curString)) {
           *blockerHead = blockerCur->Child;
           return TRUE;
       }
   }
   return FALSE;
}