/*++ 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; iNested) ; 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; }