|
|
//----------------------------------------------------------------------------
//
// Process and thread routines.
//
// Copyright (C) Microsoft Corporation, 1997-2001.
//
//----------------------------------------------------------------------------
#include "ntsdp.hpp"
ULONG g_NextProcessUserId; ULONG g_AllProcessFlags; ULONG g_NumberProcesses; ULONG g_TotalNumberThreads; ULONG g_MaxThreadsInProcess;
PPROCESS_INFO g_ProcessHead; PPROCESS_INFO g_CurrentProcess;
// Thread specified in thread commands. Used for specific
// thread stepping and execution.
PTHREAD_INFO g_SelectedThread; ULONG g_SelectExecutionThread;
PTHREAD_INFO g_RegContextThread; ULONG g_RegContextProcessor = -1; PTHREAD_INFO g_RegContextSaved; ULONG64 g_SaveImplicitThread; ULONG64 g_SaveImplicitProcess;
ULONG g_AllPendingFlags;
PPROCESS_INFO FindProcessByUserId( ULONG Id ) { PPROCESS_INFO Process;
Process = g_ProcessHead; while (Process && Process->UserId != Id) { Process = Process->Next; }
return Process; }
PTHREAD_INFO FindThreadByUserId( PPROCESS_INFO Process, ULONG Id ) { PTHREAD_INFO Thread;
if (Process != NULL) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->UserId == Id) { return Thread; } } } else { for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->UserId == Id) { return Thread; } } } }
return NULL; }
PPROCESS_INFO FindProcessBySystemId( ULONG Id ) { PPROCESS_INFO Process;
Process = g_ProcessHead; while (Process && Process->SystemId != Id) { Process = Process->Next; }
return Process; }
PTHREAD_INFO FindThreadBySystemId( PPROCESS_INFO Process, ULONG Id ) { PTHREAD_INFO Thread;
if (Process != NULL) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->SystemId == Id) { return Thread; } } } else { for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->SystemId == Id) { return Thread; } } } }
return NULL; }
PPROCESS_INFO FindProcessByHandle( ULONG64 Handle ) { PPROCESS_INFO Process;
Process = g_ProcessHead; while (Process && Process->FullHandle != Handle) { Process = Process->Next; }
return Process; }
PTHREAD_INFO FindThreadByHandle( PPROCESS_INFO Process, ULONG64 Handle ) { PTHREAD_INFO Thread;
if (Process != NULL) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->Handle == Handle) { return Thread; } } } else { for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->Handle == Handle) { return Thread; } } } }
return NULL; }
ULONG FindNextThreadUserId(void) { //
// In a dump threads are never deleted so we don't
// have to worry about trying to reuse low thread IDs.
// Just do a simple incremental ID so that large numbers
// of threads can be created quickly.
//
if (IS_DUMP_TARGET()) { return g_TotalNumberThreads; } ULONG UserId = 0; // Find the lowest unused thread ID across all threads
// in all processes. Thread user IDs are kept unique
// across all threads to prevent user confusion and also
// to give extensions a unique ID for threads.
for (;;) { PPROCESS_INFO Process; PTHREAD_INFO Thread; // Search all threads to see if the current ID is in use.
for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { for (Thread = Process->ThreadHead; Thread != NULL; Thread = Thread->Next) { if (Thread->UserId == UserId) { break; } } if (Thread != NULL) { break; } }
if (Process != NULL) { // A thread is already using the current ID.
// Try the next one.
UserId++; } else { break; } }
return UserId; }
PPROCESS_INFO AddProcess( ULONG SystemId, ULONG64 Handle, ULONG InitialThreadSystemId, ULONG64 InitialThreadHandle, ULONG64 InitialThreadDataOffset, ULONG64 StartOffset, ULONG Flags, ULONG Options, ULONG InitialThreadFlags ) { PPROCESS_INFO ProcessNew; PPROCESS_INFO Process;
ProcessNew = (PPROCESS_INFO)calloc(1, sizeof(PROCESS_INFO)); if (!ProcessNew) { ErrOut("memory allocation failed\n"); return NULL; }
if (AddThread(ProcessNew, InitialThreadSystemId, InitialThreadHandle, InitialThreadDataOffset, StartOffset, InitialThreadFlags) == NULL) { free(ProcessNew); return NULL; }
// Process IDs always increase with time to
// prevent reuse of IDs. New processes are
// therefore always at the end of the sorted
// ID list.
if (g_ProcessHead == NULL) { ProcessNew->Next = g_ProcessHead; g_ProcessHead = ProcessNew; } else { Process = g_ProcessHead; while (Process->Next) { Process = Process->Next; } ProcessNew->Next = NULL; Process->Next = ProcessNew; } ProcessNew->UserId = g_NextProcessUserId++; ProcessNew->SystemId = SystemId; ProcessNew->FullHandle = Handle; ProcessNew->Handle = (HANDLE)(ULONG_PTR)Handle; ProcessNew->InitialBreak = IS_KERNEL_TARGET() || (g_EngOptions & DEBUG_ENGOPT_INITIAL_BREAK) != 0; ProcessNew->InitialBreakWx86 = (g_EngOptions & DEBUG_ENGOPT_INITIAL_BREAK) != 0; ProcessNew->Flags = Flags; ProcessNew->Options = Options; g_AllProcessFlags |= Flags;
g_NumberProcesses++; g_Sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); g_Sym->MaxNameLength = MAX_SYMBOL_LEN; g_SymStart->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); g_SymStart->MaxNameLength = MAX_SYMBOL_LEN; SymInitialize( ProcessNew->Handle, NULL, FALSE ); SymRegisterCallback64( ProcessNew->Handle, SymbolCallbackFunction, 0 );
if (IS_USER_TARGET() && g_TargetMachineType != IMAGE_FILE_MACHINE_I386) { SymRegisterFunctionEntryCallback64 (ProcessNew->Handle, TargetInfo::DynamicFunctionTableCallback, (ULONG_PTR)g_TargetMachine); } SetSymbolSearchPath(ProcessNew);
return ProcessNew; }
PTHREAD_INFO AddThread( PPROCESS_INFO Process, ULONG SystemId, ULONG64 Handle, ULONG64 DataOffset, ULONG64 StartOffset, ULONG Flags ) { PTHREAD_INFO ThreadCurrent; PTHREAD_INFO ThreadAfter; PTHREAD_INFO ThreadNew; ULONG UserId;
ThreadNew = (PTHREAD_INFO)calloc(1, sizeof(THREAD_INFO)); if (!ThreadNew) { ErrOut("memory allocation failed\n"); return NULL; }
UserId = FindNextThreadUserId();
// Insert the thread into the process's thread list in
// sorted order.
ThreadCurrent = NULL; for (ThreadAfter = Process->ThreadHead; ThreadAfter != NULL; ThreadAfter = ThreadAfter->Next) { if (ThreadAfter->UserId > UserId) { break; }
ThreadCurrent = ThreadAfter; } ThreadNew->Next = ThreadAfter; if (ThreadCurrent == NULL) { Process->ThreadHead = ThreadNew; } else { ThreadCurrent->Next = ThreadNew; }
ThreadNew->Process = Process; Process->NumberThreads++; ThreadNew->UserId = UserId;
ThreadNew->SystemId = SystemId; ThreadNew->Handle = Handle; ThreadNew->StartAddress = StartOffset; ThreadNew->Frozen = ThreadNew->Exited = FALSE; ThreadNew->DataOffset = DataOffset; ThreadNew->Flags = Flags;
g_TotalNumberThreads++; if (Process->NumberThreads > g_MaxThreadsInProcess) { g_MaxThreadsInProcess = Process->NumberThreads; } return ThreadNew; }
void DeleteThread(PTHREAD_INFO Thread) { Thread->Process->NumberThreads--; if (Thread->Process->CurrentThread == Thread) { Thread->Process->CurrentThread = NULL; } RemoveThreadBreakpoints(Thread); if (Thread->Flags & ENG_PROC_THREAD_CLOSE_HANDLE) { DBG_ASSERT(IS_LIVE_USER_TARGET()); ((UserTargetInfo*)g_Target)->m_Services->CloseHandle(Thread->Handle); } free(Thread);
PPROCESS_INFO Process;
g_TotalNumberThreads--; g_MaxThreadsInProcess = 0; for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { if (Process->NumberThreads > g_MaxThreadsInProcess) { g_MaxThreadsInProcess = Process->NumberThreads; } } }
void UpdateAllProcessFlags(void) { PPROCESS_INFO Process; g_AllProcessFlags = 0; for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { g_AllProcessFlags |= Process->Flags; } }
void DeleteProcess(PPROCESS_INFO Process) { PDEBUG_IMAGE_INFO Image; PTHREAD_INFO Thread;
while (Process->ThreadHead != NULL) { Thread = Process->ThreadHead; Process->ThreadHead = Thread->Next; DeleteThread(Thread); } // Suppress notifications until all images are deleted.
g_EngNotify++;
while (Image = Process->ImageHead) { Process->ImageHead = Image->Next; DelImage(Process, Image); } SymCleanup(Process->Handle);
g_EngNotify--; NotifyChangeSymbolState(DEBUG_CSS_UNLOADS, 0, Process);
RemoveProcessBreakpoints(Process);
if (Process->Flags & ENG_PROC_THREAD_CLOSE_HANDLE) { DBG_ASSERT(IS_LIVE_USER_TARGET()); ((UserTargetInfo*)g_Target)->m_Services-> CloseHandle(Process->FullHandle); } free(Process); g_NumberProcesses--;
UpdateAllProcessFlags(); }
void RemoveAndDeleteProcess(PPROCESS_INFO Process, PPROCESS_INFO Prev) { if (Prev == NULL) { g_ProcessHead = Process->Next; } else { Prev->Next = Process->Next; } DeleteProcess(Process); }
BOOL DeleteExitedInfos(void) { PPROCESS_INFO Process; PPROCESS_INFO ProcessNext; PPROCESS_INFO ProcessPrev; BOOL DeletedSomething = FALSE;
ProcessPrev = NULL; for (Process = g_ProcessHead; Process != NULL; Process = ProcessNext) { ProcessNext = Process->Next; if (Process->Exited) { RemoveAndDeleteProcess(Process, ProcessPrev); DeletedSomething = TRUE; } else { PTHREAD_INFO Thread; PTHREAD_INFO ThreadPrev; PTHREAD_INFO ThreadNext; ThreadPrev = NULL; for (Thread = Process->ThreadHead; Thread != NULL; Thread = ThreadNext) { ThreadNext = Thread->Next; if (Thread->Exited) { DeleteThread(Thread); DeletedSomething = TRUE;
if (ThreadPrev == NULL) { Process->ThreadHead = ThreadNext; } else { ThreadPrev->Next = ThreadNext; } } else { ThreadPrev = Thread; } }
PDEBUG_IMAGE_INFO Image; PDEBUG_IMAGE_INFO ImagePrev; PDEBUG_IMAGE_INFO ImageNext;
ImagePrev = NULL; for (Image = Process->ImageHead; Image != NULL; Image = ImageNext) { ImageNext = Image->Next; if (Image->Unloaded) { ULONG64 ImageBase = Image->BaseOfImage; // Delay notification until the image
// is cleaned up and the image list
// repaired.
g_EngNotify++; DelImage(Process, Image); g_EngNotify--; DeletedSomething = TRUE;
if (ImagePrev == NULL) { Process->ImageHead = ImageNext; } else { ImagePrev->Next = ImageNext; }
NotifyChangeSymbolState(DEBUG_CSS_UNLOADS, ImageBase, Process); } else { ImagePrev = Image; } }
ProcessPrev = Process; } }
return DeletedSomething; }
void OutputProcessInfo( char *s ) { PPROCESS_INFO pProcess; PTHREAD_INFO pThread; PDEBUG_IMAGE_INFO pImage;
// Kernel mode only has a virtual process and threads right
// now so it isn't particularly interesting.
if (IS_KERNEL_TARGET()) { return; } VerbOut("OUTPUT_PROCESS: %s\n",s); pProcess = g_ProcessHead; while (pProcess) { VerbOut("id: %x Handle: %I64x index: %d\n", pProcess->SystemId, pProcess->FullHandle, pProcess->UserId); pThread = pProcess->ThreadHead; while (pThread) { VerbOut(" id: %x hThread: %I64x index: %d addr: %s\n", pThread->SystemId, pThread->Handle, pThread->UserId, FormatAddr64(pThread->StartAddress)); pThread = pThread->Next; } pImage = pProcess->ImageHead; while (pImage) { VerbOut(" hFile: %I64x base: %s\n", (ULONG64)((ULONG_PTR)pImage->File), FormatAddr64(pImage->BaseOfImage)); pImage = pImage->Next; } pProcess = pProcess->Next; } }
/*** ChangeRegContext - change thread register context
* * Purpose: * Update the current register context to the thread specified. * The NULL value implies no context. Update pActiveThread * to point to the thread in context. * * Input: * pNewContext - pointer to new thread context (NULL if none). * * Output: * None. * * Exceptions: * failed register context call (get or set) * * Notes: * Call with NULL argument to flush current register context * before continuing with program. * *************************************************************************/
void ChangeRegContext ( PTHREAD_INFO pThreadNew ) { if (pThreadNew != g_RegContextThread) { // Flush any old thread context.
// We need to be careful when flushing context to
// NT4 boxes at the initial module load because the
// system is in a very fragile state and writing
// back the context generally causes a bugcheck 50.
if (g_RegContextThread != NULL && g_RegContextThread->Handle != NULL && (IS_USER_TARGET() || g_ActualSystemVersion != NT_SVER_NT4 || g_LastEventType != DEBUG_EVENT_LOAD_MODULE)) { HRESULT Hr;
Hr = g_Machine->SetContext(); if (Hr == S_OK && g_Machine != g_TargetMachine) { // If we're flushing register context we need to make
// sure that all machines involved are flushed so
// that context information actually gets sent to
// the target.
Hr = g_TargetMachine->SetContext(); } if (Hr != S_OK) { ErrOut("MachineInfo::SetContext failed - pThread: %N " "Handle: %I64x Id: %x - Error == 0x%X\n", g_RegContextThread, g_RegContextThread->Handle, g_RegContextThread->SystemId, Hr); } } g_RegContextThread = pThreadNew; if (g_RegContextThread != NULL) { g_RegContextProcessor = VIRTUAL_THREAD_INDEX(g_RegContextThread->Handle); } else { g_RegContextProcessor = -1; } g_LastSelector = -1;
// We've now selected a new source of processor data so
// all machines, both emulated and direct, must be invalidated.
for (ULONG i = 0; i < MACHIDX_COUNT; i++) { g_AllMachines[i]->InvalidateContext(); } } }
void FlushRegContext(void) { PTHREAD_INFO CurThread = g_RegContextThread; ChangeRegContext(NULL); ChangeRegContext(CurThread); }
void SetCurrentThread(PTHREAD_INFO Thread, BOOL Hidden) { BOOL Changed = (!g_CurrentProcess && Thread) || (g_CurrentProcess && !Thread) || g_CurrentProcess->CurrentThread != Thread; ChangeRegContext(Thread); if (Thread != NULL) { g_CurrentProcess = Thread->Process; } else { g_CurrentProcess = NULL; } if (g_CurrentProcess != NULL) { g_CurrentProcess->CurrentThread = Thread; }
// We're switching processors so invalidate
// the implicit data pointers so they get refreshed.
ResetImplicitData(); // In kernel targets update the page directory for the current
// processor's page directory base value so that virtual
// memory mappings are done according to the current processor
// state. This only applies to full dumps because triage
// dumps only have a single processor, so there's nothing to
// switch, and summary dumps only guarantee that the crashing
// processor's page directory page is saved. A user can
// still manually change the directory through .context if
// they wish.
if (IS_KERNEL_TARGET() && IS_KERNEL_FULL_DUMP()) { if (g_TargetMachine->SetDefaultPageDirectories(PAGE_DIR_ALL) != S_OK) { WarnOut("WARNING: Unable to reset page directories\n"); } } if (!Hidden && Changed) { if (Thread != NULL) { g_Machine->GetPC(&g_AssemDefault); g_UnasmDefault = g_AssemDefault; } NotifyChangeEngineState(DEBUG_CES_CURRENT_THREAD, Thread != NULL ? Thread->UserId : DEBUG_ANY_ID, TRUE); } }
void SetCurrentProcessorThread(ULONG Processor, BOOL Hidden) { //
// Switch to the thread representing a particular processor.
// This only works with the kernel virtual threads.
//
DBG_ASSERT(IS_KERNEL_TARGET());
PTHREAD_INFO Thread = FindThreadBySystemId(g_CurrentProcess, VIRTUAL_THREAD_ID(Processor)); DBG_ASSERT(Thread != NULL);
SetCurrentThread(Thread, Hidden); }
void SaveSetCurrentProcessorThread(ULONG Processor) { // This is only used for kd sessions to conserve
// bandwidth when temporarily switching processors.
DBG_ASSERT(IS_KERNEL_TARGET());
g_RegContextSaved = g_RegContextThread; g_Machine->KdSaveProcessorState(); g_RegContextThread = NULL; g_SaveImplicitThread = g_ImplicitThreadData; g_SaveImplicitProcess = g_ImplicitProcessData;
// Don't notify on this change as it is only temporary.
g_EngNotify++; SetCurrentProcessorThread(Processor, TRUE); g_EngNotify--; }
void RestoreCurrentProcessorThread(void) { // This is only used for kd sessions to conserve
// bandwidth when temporarily switching processors.
DBG_ASSERT(IS_KERNEL_TARGET());
g_Machine->KdRestoreProcessorState(); if (g_RegContextSaved != NULL) { g_RegContextProcessor = VIRTUAL_THREAD_INDEX(g_RegContextSaved->Handle); } else { g_RegContextProcessor = -1; } g_LastSelector = -1; g_RegContextThread = g_RegContextSaved; g_ImplicitThreadData = g_SaveImplicitThread; g_ImplicitProcessData = g_SaveImplicitProcess; // Don't notify on this change as it was only temporary.
g_EngNotify++; SetCurrentThread(g_RegContextThread, TRUE); g_EngNotify--; }
#define BUFFER_THREADS 64
void SuspendAllThreads(void) { if (IS_DUMP_TARGET() || IS_KERNEL_TARGET()) { // Nothing to do.
return; } PPROCESS_INFO Process; PTHREAD_INFO Thread; ULONG64 Threads[BUFFER_THREADS]; ULONG Counts[BUFFER_THREADS]; PULONG StoreCounts[BUFFER_THREADS]; ULONG Buffered; ULONG i;
Buffered = 0; Process = g_ProcessHead; while (Process != NULL) { Thread = Process->ThreadHead; while (Thread != NULL) { if (!Process->Exited && !Thread->Exited && Thread->Handle != 0) { #ifdef DBG_SUSPEND
dprintf("** suspending thread id: %x handle: %I64x\n", Thread->SystemId, Thread->Handle); #endif
if (Thread->InternalFreezeCount > 0) { Thread->InternalFreezeCount--; } else if (Thread->FreezeCount > 0) { dprintf("thread %d can execute\n", Thread->UserId); Thread->FreezeCount--; } else { if (Buffered == BUFFER_THREADS) { if ((((UserTargetInfo*)g_Target)->m_Services-> SuspendThreads(Buffered, Threads, Counts)) != S_OK) { WarnOut("SuspendThread failed\n"); }
for (i = 0; i < Buffered; i++) { *StoreCounts[i] = Counts[i]; } Buffered = 0; }
Threads[Buffered] = Thread->Handle; StoreCounts[Buffered] = &Thread->SuspendCount; Buffered++; } } Thread = Thread->Next; } Process = Process->Next; } if (Buffered > 0) { if ((((UserTargetInfo*)g_Target)->m_Services-> SuspendThreads(Buffered, Threads, Counts)) != S_OK) { WarnOut("SuspendThread failed\n"); } for (i = 0; i < Buffered; i++) { *StoreCounts[i] = Counts[i]; } } }
BOOL ResumeAllThreads(void) { PPROCESS_INFO Process; PTHREAD_INFO Thread;
if (IS_DUMP_TARGET()) { // Nothing to do.
return TRUE; } else if (IS_KERNEL_TARGET()) { // Wipe out all thread data offsets since the set
// of threads on the processors after execution will
// not be the same.
for (Thread = g_ProcessHead->ThreadHead; Thread != NULL; Thread = Thread->Next) { Thread->DataOffset = 0; }
return TRUE; }
BOOL EventActive = FALSE; BOOL EventAlive = FALSE; ULONG64 Threads[BUFFER_THREADS]; ULONG Counts[BUFFER_THREADS]; PULONG StoreCounts[BUFFER_THREADS]; ULONG Buffered; ULONG i;
Buffered = 0; Process = g_ProcessHead; while (Process != NULL) { Thread = Process->ThreadHead; while (Thread != NULL) { if (!Process->Exited && !Thread->Exited && Thread->Handle != 0) { if (Process == g_EventProcess) { EventAlive = TRUE; } #ifdef DBG_SUSPEND
dprintf("** resuming thread id: %x handle: %I64x\n", Thread->SystemId, Thread->Handle); #endif
if ((g_EngStatus & ENG_STATUS_STOP_SESSION) == 0 && Process == g_EventProcess && ((g_SelectExecutionThread != SELTHREAD_ANY && Thread != g_SelectedThread) || (g_SelectExecutionThread == SELTHREAD_ANY && Thread->Frozen))) { if (g_SelectExecutionThread != SELTHREAD_INTERNAL_THREAD) { dprintf("thread %d not executing\n", Thread->UserId); Thread->FreezeCount++; } else { Thread->InternalFreezeCount++; } } else { if (Process == g_EventProcess) { EventActive = TRUE; } if (Buffered == BUFFER_THREADS) { if ((((UserTargetInfo*)g_Target)->m_Services-> ResumeThreads(Buffered, Threads, Counts)) != S_OK) { WarnOut("ResumeThread failed\n"); }
for (i = 0; i < Buffered; i++) { *StoreCounts[i] = Counts[i]; } Buffered = 0; } Threads[Buffered] = Thread->Handle; StoreCounts[Buffered] = &Thread->SuspendCount; Buffered++; } } Thread = Thread->Next; }
Process = Process->Next; } if (Buffered > 0) { if ((((UserTargetInfo*)g_Target)->m_Services-> ResumeThreads(Buffered, Threads, Counts)) != S_OK) { WarnOut("ResumeThread failed\n"); }
for (i = 0; i < Buffered; i++) { *StoreCounts[i] = Counts[i]; } }
if (EventAlive && !EventActive) { ErrOut("No active threads to run in event process %d\n", g_EventProcess->UserId); return FALSE; } return TRUE; }
void parseProcessCmds ( void ) { UCHAR ch; PPROCESS_INFO pProcess; ULONG UserId;
ch = PeekChar(); if (ch == '\0' || ch == ';') { fnOutputProcessInfo(NULL); } else { pProcess = g_CurrentProcess; g_CurCmd++; if (ch == '.') { ; } else if (ch == '#') { pProcess = g_EventProcess; } else if (ch == '*') { pProcess = NULL; } else if (ch >= '0' && ch <= '9') { UserId = 0; do { UserId = UserId * 10 + ch - '0'; ch = *g_CurCmd++; } while (ch >= '0' && ch <= '9'); g_CurCmd--; pProcess = FindProcessByUserId(UserId); if (pProcess == NULL) { error(BADPROCESS); } } else { g_CurCmd--; } ch = PeekChar(); if (ch == '\0' || ch == ';') { fnOutputProcessInfo(pProcess); } else { g_CurCmd++; if (tolower(ch) == 's') { if (pProcess == NULL) { error(BADPROCESS); } if (pProcess->CurrentThread == NULL) { pProcess->CurrentThread = pProcess->ThreadHead; if (pProcess->CurrentThread == NULL) { error(BADPROCESS); } } SetPromptThread(pProcess->CurrentThread, SPT_DEFAULT_OCI_FLAGS); } else { g_CurCmd--; } } } }
void SetPromptThread( PTHREAD_INFO pThread, ULONG uOciFlags ) { SetCurrentThread(pThread, FALSE); ResetCurrentScope(); OutCurInfo(uOciFlags, g_Machine->m_AllMask, DEBUG_OUTPUT_PROMPT_REGISTERS); // Assem/unasm defaults already reset so just update
// the dump default from them.
g_DumpDefault = g_AssemDefault; }
void fnOutputProcessInfo ( PPROCESS_INFO pProcessOut ) { PPROCESS_INFO pProcess;
pProcess = g_ProcessHead; while (pProcess) { if (pProcessOut == NULL || pProcessOut == pProcess) { char CurMark; PSTR DebugKind; if (pProcess == g_CurrentProcess) { CurMark = '.'; } else if (pProcess == g_EventProcess) { CurMark = '#'; } else { CurMark = ' '; }
DebugKind = "child"; if (pProcess->Exited) { DebugKind = "exited"; } else if (pProcess->Flags & ENG_PROC_ATTACHED) { DebugKind = (pProcess->Flags & ENG_PROC_SYSTEM) ? "system" : "attach"; } else if (pProcess->Flags & ENG_PROC_CREATED) { DebugKind = "create"; } else if (pProcess->Flags & ENG_PROC_EXAMINED) { DebugKind = "examine"; } dprintf("%c%3ld\tid: %lx\t%s\tname: %s\n", CurMark, pProcess->UserId, pProcess->SystemId, DebugKind, pProcess->ExecutableImage != NULL ? pProcess->ExecutableImage->ImagePath : (pProcess->ImageHead != NULL ? pProcess->ImageHead->ImagePath : "?NoImage?") ); } pProcess = pProcess->Next; } }
void SuspendResumeThreads(PPROCESS_INFO Process, BOOL Susp, PTHREAD_INFO Match) { PTHREAD_INFO Thrd;
if (Process == NULL) { ErrOut("SuspendResumeThreads given a NULL process\n"); return; } for (Thrd = Process->ThreadHead; Thrd != NULL; Thrd = Thrd->Next) { if (Match != NULL && Match != Thrd) { continue; } HRESULT Status; ULONG Count; if (Susp) { Status = ((UserTargetInfo*)g_Target)->m_Services-> SuspendThreads(1, &Thrd->Handle, &Count); } else { Status = ((UserTargetInfo*)g_Target)->m_Services-> ResumeThreads(1, &Thrd->Handle, &Count); } if (Status != S_OK) { ErrOut("Operation failed for thread %d, 0x%X\n", Thrd->UserId, Status); } else { Thrd->SuspendCount = Count; } } }
void parseThreadCmds ( DebugClient* Client ) { CHAR ch; PTHREAD_INFO pThread, Thrd, OrigThread; ULONG UserId; ULONG64 value1; ULONG64 value3; ULONG value4; ADDR value2; STACK_TRACE_TYPE traceType; BOOL fFreezeThread = FALSE; PCHAR Tmp;
ch = PeekChar(); if (ch == '\0' || ch == ';') { fnOutputThreadInfo(NULL); } else { g_CurCmd++;
pThread = g_CurrentProcess->CurrentThread; fFreezeThread = TRUE; if (ch == '.') { // Current thread is the default.
} else if (ch == '#') { pThread = g_EventThread; } else if (ch == '*') { pThread = NULL; fFreezeThread = FALSE; } else if (ch >= '0' && ch <= '9') { UserId = 0; do { UserId = UserId * 10 + ch - '0'; ch = *g_CurCmd++; } while (ch >= '0' && ch <= '9'); g_CurCmd--; pThread = FindThreadByUserId(g_CurrentProcess, UserId); if (pThread == NULL) { error(BADTHREAD); } } else { g_CurCmd--; } ch = PeekChar(); if (ch == '\0' || ch == ';') { fnOutputThreadInfo(pThread); } else { g_CurCmd++; switch (ch = (CHAR)tolower(ch)) { case 'b': ch = (CHAR)tolower(*g_CurCmd); g_CurCmd++; if (ch != 'p' && ch != 'a' && ch != 'u') { if (ch == '\0' || ch == ';') { g_CurCmd--; } error(SYNTAX); } ParseBpCmd(Client, ch, pThread); break;
case 'e': Tmp = g_CurCmd; OrigThread = g_CurrentProcess->CurrentThread; for (Thrd = g_CurrentProcess->ThreadHead; Thrd; Thrd = Thrd->Next) { if (pThread == NULL || Thrd == pThread) { g_CurCmd = Tmp; SetCurrentThread(Thrd, TRUE); ProcessCommands(Client, TRUE); if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { g_EngStatus &= ~ENG_STATUS_USER_INTERRUPT; break; } } } SetCurrentThread(OrigThread, TRUE); break; case 'f': case 'u': if (pThread == NULL) { pThread = g_CurrentProcess->ThreadHead; while (pThread) { pThread->Frozen = (BOOL)(ch == 'f'); pThread = pThread->Next; } } else { pThread->Frozen = (BOOL)(ch == 'f'); } break; case 'g': if (pThread) { ChangeRegContext(pThread); } parseGoCmd(pThread, fFreezeThread); ChangeRegContext(g_CurrentProcess->CurrentThread); break; case 'k': if (pThread == NULL) { Tmp = g_CurCmd; for (pThread = g_CurrentProcess->ThreadHead; pThread; pThread = pThread->Next) { g_CurCmd = Tmp; dprintf("\n"); fnOutputThreadInfo(pThread); ChangeRegContext(pThread); value1 = 0; if (g_EffMachine == IMAGE_FILE_MACHINE_I386) { value3 = GetRegVal64(X86_EIP); } else { value3 = 0; } ParseStackTrace(&traceType, &value2, &value1, &value3, &value4); DoStackTrace( value2.off, value1, value3, value4, traceType ); if (g_EngStatus & ENG_STATUS_USER_INTERRUPT) { break; } } } else { ChangeRegContext(pThread); value1 = 0; if (g_EffMachine == IMAGE_FILE_MACHINE_I386) { value3 = GetRegVal64(X86_EIP); } else { value3 = 0; } ParseStackTrace(&traceType, &value2, &value1, &value3, &value4); DoStackTrace( value2.off, value1, value3, value4, traceType ); }
ChangeRegContext(g_CurrentProcess->CurrentThread); break;
case 'm': case 'n': SuspendResumeThreads(g_CurrentProcess, ch == 'n', pThread); break; case 'p': case 't': if (pThread) { ChangeRegContext(pThread); } parseStepTrace(pThread, fFreezeThread, ch); ChangeRegContext(g_CurrentProcess->CurrentThread); break; case 'r': if (pThread == NULL) { Tmp = g_CurCmd; for (pThread = g_CurrentProcess->ThreadHead; pThread; pThread = pThread->Next) { g_CurCmd = Tmp; ChangeRegContext(pThread); ParseRegCmd(); } } else { ChangeRegContext(pThread); ParseRegCmd(); } ChangeRegContext(g_CurrentProcess->CurrentThread); break; case 's': if (pThread == NULL) { error(BADTHREAD); } SetPromptThread(pThread, SPT_DEFAULT_OCI_FLAGS); break; default: error(SYNTAX); } } } }
void fnOutputThreadInfo ( PTHREAD_INFO pThreadOut ) { PTHREAD_INFO pThread;
pThread = g_CurrentProcess != NULL ? g_CurrentProcess->ThreadHead : NULL; while (pThread) { if (pThreadOut == NULL || pThreadOut == pThread) { ULONG64 DataOffset; HRESULT Status; char CurMark;
Status = g_Target->GetThreadInfoDataOffset(pThread, NULL, &DataOffset); if (Status != S_OK) { WarnOut("Unable to get thread data for thread %u\n", pThread->UserId); DataOffset = 0; }
if (pThread == g_CurrentProcess->CurrentThread) { CurMark = '.'; } else if (pThread == g_EventThread) { CurMark = '#'; } else { CurMark = ' '; } dprintf("%c%3ld id: %lx.%lx Suspend: %d Teb %s ", CurMark, pThread->UserId, g_CurrentProcess->SystemId, pThread->SystemId, pThread->SuspendCount, FormatAddr64(DataOffset) ); if (pThread->Frozen) { dprintf("Frozen"); } else { dprintf("Unfrozen"); } if (pThread->Name[0]) { dprintf(" \"%s\"", pThread->Name); } dprintf("\n"); }
if (CheckUserInterrupt()) { break; } pThread = pThread->Next; } }
//----------------------------------------------------------------------------
//
// Process creation and attach functions.
//
//----------------------------------------------------------------------------
void AddPendingProcess(PPENDING_PROCESS Pending) { Pending->Next = g_ProcessPending; g_ProcessPending = Pending; g_AllPendingFlags |= Pending->Flags; }
void RemovePendingProcess(PPENDING_PROCESS Pending) { PPENDING_PROCESS Prev, Cur; ULONG AllFlags = 0;
Prev = NULL; for (Cur = g_ProcessPending; Cur != NULL; Cur = Cur->Next) { if (Cur == Pending) { break; }
Prev = Cur; AllFlags |= Cur->Flags; }
if (Cur == NULL) { DBG_ASSERT(Cur != NULL); return; }
Cur = Cur->Next; if (Prev == NULL) { g_ProcessPending = Cur; } else { Prev->Next = Cur; } DiscardPendingProcess(Pending);
while (Cur != NULL) { AllFlags |= Cur->Flags; Cur = Cur->Next; } g_AllPendingFlags = AllFlags; }
void DiscardPendingProcess(PPENDING_PROCESS Pending) { DBG_ASSERT(IS_LIVE_USER_TARGET()); PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
if (Pending->InitialThreadHandle) { Services->CloseHandle(Pending->InitialThreadHandle); } if (Pending->Handle) { Services->CloseHandle(Pending->Handle); } delete Pending; }
void DiscardPendingProcesses(void) { while (g_ProcessPending != NULL) { PPENDING_PROCESS Next = g_ProcessPending->Next; DiscardPendingProcess(g_ProcessPending); g_ProcessPending = Next; }
g_AllPendingFlags = 0; }
PPENDING_PROCESS FindPendingProcessByFlags(ULONG Flags) { PPENDING_PROCESS Cur; for (Cur = g_ProcessPending; Cur != NULL; Cur = Cur->Next) { if (Cur->Flags & Flags) { return Cur; } }
return NULL; }
PPENDING_PROCESS FindPendingProcessById(ULONG Id) { PPENDING_PROCESS Cur; for (Cur = g_ProcessPending; Cur != NULL; Cur = Cur->Next) { if (Cur->Id == Id) { return Cur; } }
return NULL; }
void VerifyPendingProcesses(void) { DBG_ASSERT(IS_LIVE_USER_TARGET()); PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
PPENDING_PROCESS Cur;
Restart: for (Cur = g_ProcessPending; Cur != NULL; Cur = Cur->Next) { ULONG ExitCode; if (Cur->Handle && Services->GetProcessExitCode(Cur->Handle, &ExitCode) == S_OK) { ErrOut("Process %d exited before attach completed\n", Cur->Id); RemovePendingProcess(Cur); goto Restart; } } }
void AddExamineToPendingAttach(void) { PPENDING_PROCESS Cur; for (Cur = g_ProcessPending; Cur != NULL; Cur = Cur->Next) { if (Cur->Flags & ENG_PROC_ATTACHED) { Cur->Flags |= ENG_PROC_EXAMINED; g_AllPendingFlags |= ENG_PROC_EXAMINED; } } } HRESULT StartAttachProcess(ULONG ProcessId, ULONG AttachFlags, PPENDING_PROCESS* Pending) { HRESULT Status; PPENDING_PROCESS Pend;
DBG_ASSERT(IS_LIVE_USER_TARGET());
if (GetCurrentThreadId() != g_SessionThread) { ErrOut("Can only attach from the primary thread\n"); return E_UNEXPECTED; }
if (ProcessId == GetCurrentProcessId()) { ErrOut("Can't debug the current process\n"); return E_INVALIDARG; } Pend = new PENDING_PROCESS; if (Pend == NULL) { ErrOut("Unable to allocate memory\n"); return E_OUTOFMEMORY; }
if ((AttachFlags & DEBUG_ATTACH_NONINVASIVE) == 0) { if ((Status = ((UserTargetInfo*)g_Target)->m_Services-> AttachProcess(ProcessId, AttachFlags, &Pend->Handle, &Pend->Options)) != S_OK) { ErrOut("Cannot debug pid %ld, %s\n \"%s\"\n", ProcessId, FormatStatusCode(Status), FormatStatus(Status)); delete Pend; return Status; }
Pend->Flags = ENG_PROC_ATTACHED; if (AttachFlags & DEBUG_ATTACH_EXISTING) { Pend->Flags |= ENG_PROC_ATTACH_EXISTING; } if (ProcessId == CSRSS_PROCESS_ID) { if (IS_LOCAL_USER_TARGET()) { g_EngOptions |= DEBUG_ENGOPT_DISALLOW_NETWORK_PATHS | DEBUG_ENGOPT_IGNORE_DBGHELP_VERSION; g_EngOptions &= ~DEBUG_ENGOPT_ALLOW_NETWORK_PATHS; }
Pend->Flags |= ENG_PROC_SYSTEM; } } else { Pend->Handle = 0; Pend->Flags = ENG_PROC_EXAMINED; Pend->Options = DEBUG_PROCESS_ONLY_THIS_PROCESS; }
Pend->Id = ProcessId; Pend->InitialThreadId = 0; Pend->InitialThreadHandle = 0; AddPendingProcess(Pend); *Pending = Pend; return S_OK; }
HRESULT StartCreateProcess(PSTR CommandLine, ULONG CreateFlags, PPENDING_PROCESS* Pending) { HRESULT Status; PPENDING_PROCESS Pend;
DBG_ASSERT(IS_LIVE_USER_TARGET());
if ((CreateFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)) == 0) { return E_INVALIDARG; }
if (GetCurrentThreadId() != g_SessionThread) { ErrOut("Can only use .create from the primary thread\n"); return E_UNEXPECTED; } Pend = new PENDING_PROCESS; if (Pend == NULL) { ErrOut("Unable to allocate memory\n"); return E_OUTOFMEMORY; } dprintf("CommandLine: %s\n", CommandLine);
if ((Status = ((UserTargetInfo*)g_Target)->m_Services-> CreateProcess(CommandLine, CreateFlags, &Pend->Id, &Pend->InitialThreadId, &Pend->Handle, &Pend->InitialThreadHandle)) != S_OK) { ErrOut("Cannot execute '%s', %s\n \"%s\"\n", CommandLine, FormatStatusCode(Status), FormatStatusArgs(Status, &CommandLine)); delete Pend; } else { Pend->Flags = ENG_PROC_CREATED; Pend->Options = (CreateFlags & DEBUG_ONLY_THIS_PROCESS) ? DEBUG_PROCESS_ONLY_THIS_PROCESS : 0; AddPendingProcess(Pend); *Pending = Pend; } return Status; }
void MarkProcessExited(PPROCESS_INFO Process) { Process->Exited = TRUE; g_EngDefer |= ENG_DEFER_DELETE_EXITED; }
HRESULT TerminateProcess(PPROCESS_INFO Process) { DBG_ASSERT(IS_LIVE_USER_TARGET()); PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services; HRESULT Status = S_OK; if (!Process->Exited) { if ((Process->Flags & ENG_PROC_EXAMINED) == 0 && (Status = Services->TerminateProcess(Process->FullHandle, E_FAIL)) != S_OK) { ErrOut("TerminateProcess failed, %s\n", FormatStatusCode(Status)); } else { MarkProcessExited(Process); } }
return Status; }
HRESULT TerminateProcesses(void) { DBG_ASSERT(IS_LIVE_USER_TARGET());
HRESULT Status; PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
Status = PrepareForSeparation(); if (Status != S_OK) { ErrOut("Unable to prepare process for termination, %s\n", FormatStatusCode(Status)); return Status; }
PPROCESS_INFO Process;
for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { if ((Status = TerminateProcess(Process)) != S_OK) { goto Exit; } }
if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { // The event's process may just been terminated so don't
// check for failures.
Services->ContinueEvent(DBG_CONTINUE); } DiscardLastEvent();
DEBUG_EVENT64 Event; ULONG EventUsed; BOOL AnyLeft; BOOL AnyExited;
for (;;) { while (Services->WaitForEvent(0, &Event, sizeof(Event), &EventUsed) == S_OK) { // Check for process exit events so we can
// mark the process infos as exited.
if (EventUsed == sizeof(DEBUG_EVENT32)) { DEBUG_EVENT32 Event32 = *(DEBUG_EVENT32*)&Event; DebugEvent32To64(&Event32, &Event); } else if (EventUsed != sizeof(DEBUG_EVENT64)) { ErrOut("Event data corrupt\n"); Status = E_FAIL; goto Exit; }
if (Event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) { Process = FindProcessBySystemId(Event.dwProcessId); if (Process != NULL) { MarkProcessExited(Process); } }
Services->ContinueEvent(DBG_CONTINUE); }
AnyLeft = FALSE; AnyExited = FALSE;
for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { if (!Process->Exited) { ULONG Code;
if ((Status = Services-> GetProcessExitCode(Process->FullHandle, &Code)) == S_OK) { MarkProcessExited(Process); AnyExited = TRUE; } else if (FAILED(Status)) { ErrOut("Unable to wait for process to terminate, %s\n", FormatStatusCode(Status)); goto Exit; } else { AnyLeft = TRUE; } } }
if (!AnyLeft) { break; }
if (!AnyExited) { // Give things time to run and exit.
Sleep(50); } }
// We've terminated everything so it's safe to assume
// we're no longer debugging any system processes.
// We do this now rather than wait for DeleteProcess
// so that shutdown can query the value immediately.
g_AllProcessFlags &= ~ENG_PROC_SYSTEM;
//
// Drain off any remaining events.
//
while (Services->WaitForEvent(10, &Event, sizeof(Event), NULL) == S_OK) { Services->ContinueEvent(DBG_CONTINUE); }
Status = S_OK;
Exit: return Status; }
HRESULT DetachProcess(PPROCESS_INFO Process) { DBG_ASSERT(IS_LIVE_USER_TARGET()); PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services; HRESULT Status = S_OK; if (!Process->Exited) { if ((Process->Flags & ENG_PROC_EXAMINED) == 0 && (Status = Services->DetachProcess(Process->SystemId)) != S_OK) { // Older systems don't support detach so
// don't show an error message in that case.
if (Status != E_NOTIMPL) { ErrOut("DebugActiveProcessStop(%d) failed, %s\n", Process->SystemId, FormatStatusCode(Status)); } } else { MarkProcessExited(Process); } }
return Status; }
HRESULT DetachProcesses(void) { DBG_ASSERT(IS_LIVE_USER_TARGET()); HRESULT Status; PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
Status = PrepareForSeparation(); if (Status != S_OK) { ErrOut("Unable to prepare process for detach, %s\n", FormatStatusCode(Status)); return Status; }
if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { if ((Status = Services->ContinueEvent(DBG_CONTINUE)) != S_OK) { ErrOut("Unable to continue terminated process, %s\n", FormatStatusCode(Status)); return Status; } } DiscardLastEvent();
PPROCESS_INFO Process;
for (Process = g_ProcessHead; Process != NULL; Process = Process->Next) { DetachProcess(Process); }
// We've terminated everything so it's safe to assume
// we're no longer debugging any system processes.
// We do this now rather than wait for DeleteProcess
// so that shutdown can query the value immediately.
g_AllProcessFlags &= ~ENG_PROC_SYSTEM;
//
// Drain off any remaining events.
//
DEBUG_EVENT64 Event;
while (Services->WaitForEvent(10, &Event, sizeof(Event), NULL) == S_OK) { Services->ContinueEvent(DBG_CONTINUE); }
return S_OK; }
HRESULT AbandonProcess(PPROCESS_INFO Process) { DBG_ASSERT(IS_LIVE_USER_TARGET()); HRESULT Status; PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
if ((Status = Services->AbandonProcess(Process->FullHandle)) != S_OK) { // Older systems don't support abandon so
// don't show an error message in that case.
if (Status != E_NOTIMPL) { ErrOut("Unable to abandon process\n"); } return Status; }
// We need to continue any existing event as it won't
// be returned from WaitForDebugEvent again. We
// do not want to resume execution, though.
if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { if ((Status = Services->ContinueEvent(DBG_CONTINUE)) != S_OK) { ErrOut("Unable to continue abandoned event, %s\n", FormatStatusCode(Status)); return Status; } } DiscardLastEvent();
return Status; }
HRESULT SeparateCurrentProcess(ULONG Mode, PSTR Description) { if (!IS_LIVE_USER_TARGET() || g_CurrentProcess == NULL) { return E_UNEXPECTED; } PUSER_DEBUG_SERVICES Services = ((UserTargetInfo*)g_Target)->m_Services;
if (Mode == SEP_DETACH && IS_CONTEXT_ACCESSIBLE()) { ADDR Pc;
// Move the PC past any current breakpoint instruction so that
// the process has a chance of running.
g_Machine->GetPC(&Pc); if (g_Machine->IsBreakpointInstruction(&Pc)) { g_Machine->AdjustPCPastBreakpointInstruction (&Pc, DEBUG_BREAKPOINT_CODE); } } // Flush any buffered changes.
if (IS_CONTEXT_ACCESSIBLE()) { FlushRegContext(); } if (g_EventProcess == g_CurrentProcess) { if (g_EngDefer & ENG_DEFER_CONTINUE_EVENT) { Services->ContinueEvent(DBG_CONTINUE); } DiscardLastEvent(); g_EventProcess = NULL; g_EventThread = NULL; } SuspendResumeThreads(g_CurrentProcess, FALSE, NULL);
HRESULT Status; PSTR Operation;
switch(Mode) { case SEP_DETACH: Status = DetachProcess(g_CurrentProcess); Operation = "Detached"; break; case SEP_TERMINATE: Status = TerminateProcess(g_CurrentProcess); if ((g_CurrentProcess->Flags & ENG_PROC_EXAMINED) == 0) { Operation = "Terminated. " "Exit thread and process events will occur."; } else { Operation = "Terminated"; } break; case SEP_ABANDON: Status = AbandonProcess(g_CurrentProcess); Operation = "Abandoned"; break; } if (Status == S_OK) { if (Description != NULL) { strcpy(Description, Operation); }
// If we're detaching or abandoning it's safe to go
// ahead and remove the process now so that it
// can't be access further.
// If we're terminating we have to wait for
// the exit events to come through so
// keep the process until that happens.
if (Mode != SEP_TERMINATE) { PPROCESS_INFO Prev, Cur;
Prev = NULL; for (Cur = g_ProcessHead; Cur != g_CurrentProcess; Cur = Cur->Next) { Prev = Cur; } RemoveAndDeleteProcess(Cur, Prev); g_CurrentProcess = g_ProcessHead; if (g_CurrentProcess != NULL) { if (g_CurrentProcess->CurrentThread == NULL) { g_CurrentProcess->CurrentThread = g_CurrentProcess->ThreadHead; } if (g_CurrentProcess->CurrentThread != NULL) { SetPromptThread(g_CurrentProcess->CurrentThread, SPT_DEFAULT_OCI_FLAGS); } } } else { g_CurrentProcess->Exited = FALSE; } } else { SuspendResumeThreads(g_CurrentProcess, TRUE, NULL); }
return Status; }
|