/*++ Copyright (c) 2000 Microsoft Corporation Module Name: dbgkobj.c Abstract: This module houses routines to handle the debug object Author: Neill Clift (NeillC) 26-Apr-2000 Revision History: --*/ #include "dbgkp.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, DbgkInitialize) #pragma alloc_text(PAGE, NtCreateDebugObject) #pragma alloc_text(PAGE, NtDebugActiveProcess) #pragma alloc_text(PAGE, NtRemoveProcessDebug) #pragma alloc_text(PAGE, NtWaitForDebugEvent) #pragma alloc_text(PAGE, NtDebugContinue) #pragma alloc_text(PAGE, NtSetInformationDebugObject) #pragma alloc_text(PAGE, DbgkpDeleteObject) #pragma alloc_text(PAGE, DbgkpCloseObject) #pragma alloc_text(PAGE, DbgkCopyProcessDebugPort) #pragma alloc_text(PAGE, DbgkOpenProcessDebugPort) #pragma alloc_text(PAGE, DbgkpSetProcessDebugObject) #pragma alloc_text(PAGE, DbgkpQueueMessage) #pragma alloc_text(PAGE, DbgkpOpenHandles) #pragma alloc_text(PAGE, DbgkClearProcessDebugObject) #pragma alloc_text(PAGE, DbgkpConvertKernelToUserStateChange) #pragma alloc_text(PAGE, DbgkpMarkProcessPeb) #pragma alloc_text(PAGE, DbgkpFreeDebugEvent) #pragma alloc_text(PAGE, DbgkpPostFakeProcessCreateMessages) #pragma alloc_text(PAGE, DbgkpPostFakeModuleMessages) #pragma alloc_text(PAGE, DbgkpPostFakeThreadMessages) #pragma alloc_text(PAGE, DbgkpWakeTarget) #pragma alloc_text(PAGE, DbgkpPostAdditionalThreadMessages) #endif // // Define this to not suspend threads while attaching. // This makes race conditions more prevelent. // //#define DBGK_DONT_SUSPEND // // Non-pageable data // // // This mutex protects the debug port object of processes. // FAST_MUTEX DbgkpProcessDebugPortMutex; // // Pageable data // //#ifdef ALLOC_PRAGMA //#pragma data_seg("PAGEDATA") //#endif POBJECT_TYPE DbgkDebugObjectType = NULL; //#ifdef ALLOC_PRAGMA //#pragma data_seg() //#endif NTSTATUS DbgkInitialize ( VOID ) /*++ Routine Description: Initialize the debug system Arguments: None Return Value: NTSTATUS - Status of operation --*/ { NTSTATUS Status; UNICODE_STRING Name; OBJECT_TYPE_INITIALIZER oti = {0}; GENERIC_MAPPING GenericMapping = {STANDARD_RIGHTS_READ | DEBUG_READ_EVENT, STANDARD_RIGHTS_WRITE | DEBUG_PROCESS_ASSIGN, STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE, DEBUG_ALL_ACCESS}; PAGED_CODE (); ExInitializeFastMutex (&DbgkpProcessDebugPortMutex); RtlInitUnicodeString (&Name, L"DebugObject"); oti.Length = sizeof (oti); oti.SecurityRequired = TRUE; oti.InvalidAttributes = 0; oti.PoolType = NonPagedPool; oti.DeleteProcedure = DbgkpDeleteObject; oti.CloseProcedure = DbgkpCloseObject; oti.ValidAccessMask = DEBUG_ALL_ACCESS; oti.GenericMapping = GenericMapping; oti.DefaultPagedPoolCharge = 0; oti.DefaultNonPagedPoolCharge = 0; Status = ObCreateObjectType (&Name, &oti, NULL, &DbgkDebugObjectType); if (!NT_SUCCESS (Status)) { return Status; } return Status; } VOID DbgkpDeleteObject ( IN PVOID Object ) /*++ Routine Description: Called by the object manager when the last reference to the object goes away. Arguments: Object - Debug object being deleted Return Value: None. --*/ { #if DBG PDEBUG_OBJECT DebugObject; #endif PAGED_CODE(); #if DBG DebugObject = Object; ASSERT (IsListEmpty (&DebugObject->EventList)); #else UNREFERENCED_PARAMETER(Object); #endif } VOID DbgkpMarkProcessPeb ( PEPROCESS Process ) /*++ Routine Description: This routine writes the debug variable in the PEB Arguments: Process - Process that needs its PEB modified Return Value: None. --*/ { KAPC_STATE ApcState; PAGED_CODE (); // // Acquire process rundown protection as we are about to look at the processes address space // if (ExAcquireRundownProtection (&Process->RundownProtect)) { if (Process->Peb != NULL) { KeStackAttachProcess(&Process->Pcb, &ApcState); ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); try { Process->Peb->BeingDebugged = (BOOLEAN)(Process->DebugPort != NULL ? TRUE : FALSE); #if defined(_WIN64) if (Process->Wow64Process != NULL) { PPEB32 Peb32 = (PPEB32)Process->Wow64Process->Wow64; if (Peb32 != NULL) { Peb32->BeingDebugged = Process->Peb->BeingDebugged; } } #endif } except (EXCEPTION_EXECUTE_HANDLER) { } ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); KeUnstackDetachProcess(&ApcState); } ExReleaseRundownProtection (&Process->RundownProtect); } } VOID DbgkpWakeTarget ( IN PDEBUG_EVENT DebugEvent ) { PETHREAD Thread; Thread = DebugEvent->Thread; if ((DebugEvent->Flags&DEBUG_EVENT_SUSPEND) != 0) { PsResumeThread (DebugEvent->Thread, NULL); } if (DebugEvent->Flags&DEBUG_EVENT_RELEASE) { ExReleaseRundownProtection (&Thread->RundownProtect); } // // If we have an actual thread waiting then wake it up else free the memory. // if ((DebugEvent->Flags&DEBUG_EVENT_NOWAIT) == 0) { KeSetEvent (&DebugEvent->ContinueEvent, 0, FALSE); // Wake up waiting process } else { DbgkpFreeDebugEvent (DebugEvent); } } VOID DbgkpCloseObject ( IN PEPROCESS Process, IN PVOID Object, IN ACCESS_MASK GrantedAccess, IN ULONG_PTR ProcessHandleCount, IN ULONG_PTR SystemHandleCount ) /*++ Routine Description: Called by the object manager when a handle is closed to the object. Arguments: Process - Process doing the close Object - Debug object being deleted GrantedAccess - Access ranted for this handle ProcessHandleCount - Unused and unmaintained by OB SystemHandleCount - Current handle count for this object Return Value: None. --*/ { PDEBUG_OBJECT DebugObject = Object; PDEBUG_EVENT DebugEvent; PLIST_ENTRY ListPtr; BOOLEAN Deref; PAGED_CODE (); UNREFERENCED_PARAMETER (GrantedAccess); UNREFERENCED_PARAMETER (ProcessHandleCount); // // If this isn't the last handle then do nothing. // if (SystemHandleCount > 1) { return; } ExAcquireFastMutex (&DebugObject->Mutex); // // Mark this object as going away and wake up any processes that are waiting. // DebugObject->Flags |= DEBUG_OBJECT_DELETE_PENDING; // // Remove any events and queue them to a temporary queue // ListPtr = DebugObject->EventList.Flink; InitializeListHead (&DebugObject->EventList); ExReleaseFastMutex (&DebugObject->Mutex); // // Wake anyone waiting. They need to leave this object alone now as its deleting // KeSetEvent (&DebugObject->EventsPresent, 0, FALSE); // // Loop over all processes and remove the debug port from any that still have it. // Debug port propogation was disabled by setting the delete pending flag above so we only have to do this // once. No more refs can appear now. // for (Process = PsGetNextProcess (NULL); Process != NULL; Process = PsGetNextProcess (Process)) { if (Process->DebugPort == DebugObject) { Deref = FALSE; ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); if (Process->DebugPort == DebugObject) { Process->DebugPort = NULL; Deref = TRUE; } ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (Deref) { DbgkpMarkProcessPeb (Process); // // If the caller wanted process deletion on debugger dying (old interface) then kill off the process. // if (DebugObject->Flags&DEBUG_OBJECT_KILL_ON_CLOSE) { PsTerminateProcess (Process, STATUS_DEBUGGER_INACTIVE); } ObDereferenceObject (DebugObject); } } } // // Wake up all the removed threads. // while (ListPtr != &DebugObject->EventList) { DebugEvent = CONTAINING_RECORD (ListPtr, DEBUG_EVENT, EventList); ListPtr = ListPtr->Flink; DebugEvent->Status = STATUS_DEBUGGER_INACTIVE; DbgkpWakeTarget (DebugEvent); } } VOID DbgkCopyProcessDebugPort ( IN PEPROCESS TargetProcess, IN PEPROCESS SourceProcess ) /*++ Routine Description: Copies a debug port from one process to another. Arguments: TargetProcess - Process to move port to sourceProcess - Process to move port from Return Value: None --*/ { PDEBUG_OBJECT DebugObject; PAGED_CODE (); TargetProcess->DebugPort = NULL; // New process. Needs no locks. if (SourceProcess->DebugPort != NULL) { ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = SourceProcess->DebugPort; if (DebugObject != NULL && (SourceProcess->Flags&PS_PROCESS_FLAGS_NO_DEBUG_INHERIT) == 0) { // // We must not propogate a debug port thats got no handles left. // ExAcquireFastMutex (&DebugObject->Mutex); // // If the object is delete pending then don't propogate this object. // if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) { ObReferenceObject (DebugObject); TargetProcess->DebugPort = DebugObject; } ExReleaseFastMutex (&DebugObject->Mutex); } ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); } } NTSTATUS DbgkOpenProcessDebugPort ( IN PEPROCESS Process, IN KPROCESSOR_MODE PreviousMode, OUT HANDLE *pHandle ) /*++ Routine Description: References the target processes debug port. Arguments: Process - Process to reference debug port Return Value: PDEBUG_OBJECT - Referenced object or NULL --*/ { PDEBUG_OBJECT DebugObject; NTSTATUS Status; PAGED_CODE (); Status = STATUS_PORT_NOT_SET; if (Process->DebugPort != NULL) { ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = Process->DebugPort; if (DebugObject != NULL) { ObReferenceObject (DebugObject); } ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (DebugObject != NULL) { Status = ObOpenObjectByPointer (DebugObject, 0, NULL, MAXIMUM_ALLOWED, DbgkDebugObjectType, PreviousMode, pHandle); if (!NT_SUCCESS (Status)) { ObDereferenceObject (DebugObject); } } } return Status; } NTSTATUS NtCreateDebugObject ( OUT PHANDLE DebugObjectHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN ULONG Flags ) /*++ Routine Description: Creates a new debug object that maintains the context for a single debug session. Multiple processes may be associated with a single debug object. Arguments: DebugObjectHandle - Pointer to a handle to recive the output objects handle DesiredAccess - Required handle access ObjectAttributes - Standard object attributes structure Flags - Only one flag DEBUG_KILL_ON_CLOSE Return Value: NTSTATUS - Status of call. --*/ { NTSTATUS Status; HANDLE Handle; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; PAGED_CODE(); // // Get previous processor mode and probe output arguments if necessary. // Zero the handle for error paths. // PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { ProbeForWriteHandle (DebugObjectHandle); } *DebugObjectHandle = NULL; } except (ExSystemExceptionFilter ()) { // If previous mode is kernel then don't handle the exception return GetExceptionCode (); } if (Flags & ~DEBUG_KILL_ON_CLOSE) { return STATUS_INVALID_PARAMETER; } // // Create a new debug object and initialize it. // Status = ObCreateObject (PreviousMode, DbgkDebugObjectType, ObjectAttributes, PreviousMode, NULL, sizeof (DEBUG_OBJECT), 0, 0, &DebugObject); if (!NT_SUCCESS (Status)) { return Status; } ExInitializeFastMutex (&DebugObject->Mutex); InitializeListHead (&DebugObject->EventList); KeInitializeEvent (&DebugObject->EventsPresent, NotificationEvent, FALSE); if (Flags & DEBUG_KILL_ON_CLOSE) { DebugObject->Flags = DEBUG_OBJECT_KILL_ON_CLOSE; } else { DebugObject->Flags = 0; } // // Insert the object into the handle table // Status = ObInsertObject (DebugObject, NULL, DesiredAccess, 0, NULL, &Handle); if (!NT_SUCCESS (Status)) { return Status; } try { *DebugObjectHandle = Handle; } except (ExSystemExceptionFilter ()) { // // The caller changed the page protection or deleted the memory for the handle. // No point closing the handle as process rundown will do that and we don't know its still the same handle // Status = GetExceptionCode (); } return Status; } VOID DbgkpFreeDebugEvent ( IN PDEBUG_EVENT DebugEvent ) { NTSTATUS Status; PAGED_CODE (); switch (DebugEvent->ApiMsg.ApiNumber) { case DbgKmCreateProcessApi : if (DebugEvent->ApiMsg.u.CreateProcessInfo.FileHandle != NULL) { Status = ObCloseHandle (DebugEvent->ApiMsg.u.CreateProcessInfo.FileHandle, KernelMode); } break; case DbgKmLoadDllApi : if (DebugEvent->ApiMsg.u.LoadDll.FileHandle != NULL) { Status = ObCloseHandle (DebugEvent->ApiMsg.u.LoadDll.FileHandle, KernelMode); } break; } ObDereferenceObject (DebugEvent->Process); ObDereferenceObject (DebugEvent->Thread); ExFreePool (DebugEvent); } NTSTATUS DbgkpQueueMessage ( IN PEPROCESS Process, IN PETHREAD Thread, IN OUT PDBGKM_APIMSG ApiMsg, IN ULONG Flags, IN PDEBUG_OBJECT TargetDebugObject ) /*++ Routine Description: Queues a debug message to the port for a user mode debugger to get. Arguments: Process - Process being debugged Thread - Thread making call ApiMsg - Message being sent and received NoWait - Don't wait for a responce. Buffer message and return. TargetDebugObject - Port to queue nowait messages to Return Value: NTSTATUS - Status of call. --*/ { PDEBUG_EVENT DebugEvent; DEBUG_EVENT StaticDebugEvent; PDEBUG_OBJECT DebugObject; NTSTATUS Status; PAGED_CODE (); if (Flags&DEBUG_EVENT_NOWAIT) { DebugEvent = ExAllocatePoolWithQuotaTag (NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, sizeof (*DebugEvent), 'EgbD'); if (DebugEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } DebugEvent->Flags = Flags|DEBUG_EVENT_INACTIVE; ObReferenceObject (Process); ObReferenceObject (Thread); DebugEvent->BackoutThread = PsGetCurrentThread (); DebugObject = TargetDebugObject; } else { DebugEvent = &StaticDebugEvent; DebugEvent->Flags = Flags; ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = Process->DebugPort; // // See if this create message has already been sent. // if (ApiMsg->ApiNumber == DbgKmCreateThreadApi || ApiMsg->ApiNumber == DbgKmCreateProcessApi) { if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG) { DebugObject = NULL; } } // // See if this exit message is for a thread that never had a create // if (ApiMsg->ApiNumber == DbgKmExitThreadApi || ApiMsg->ApiNumber == DbgKmExitProcessApi) { if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG) { DebugObject = NULL; } } } KeInitializeEvent (&DebugEvent->ContinueEvent, SynchronizationEvent, FALSE); DebugEvent->Process = Process; DebugEvent->Thread = Thread; DebugEvent->ApiMsg = *ApiMsg; DebugEvent->ClientId = Thread->Cid; if (DebugObject == NULL) { Status = STATUS_PORT_NOT_SET; } else { // // We must not use a debug port thats got no handles left. // ExAcquireFastMutex (&DebugObject->Mutex); // // If the object is delete pending then don't use this object. // if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) { InsertTailList (&DebugObject->EventList, &DebugEvent->EventList); // // Set the event to say there is an unread event in the object // if ((Flags&DEBUG_EVENT_NOWAIT) == 0) { KeSetEvent (&DebugObject->EventsPresent, 0, FALSE); } Status = STATUS_SUCCESS; } else { Status = STATUS_DEBUGGER_INACTIVE; } ExReleaseFastMutex (&DebugObject->Mutex); } if ((Flags&DEBUG_EVENT_NOWAIT) == 0) { ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (NT_SUCCESS (Status)) { KeWaitForSingleObject (&DebugEvent->ContinueEvent, Executive, KernelMode, FALSE, NULL); Status = DebugEvent->Status; *ApiMsg = DebugEvent->ApiMsg; } } else { if (!NT_SUCCESS (Status)) { ObDereferenceObject (Process); ObDereferenceObject (Thread); ExFreePool (DebugEvent); } } return Status; } NTSTATUS DbgkClearProcessDebugObject ( IN PEPROCESS Process, IN PDEBUG_OBJECT SourceDebugObject ) /*++ Routine Description: Remove a debug object from a process. Arguments: Process - Process to be debugged sourceDebugObject - Debug object to detach Return Value: NTSTATUS - Status of call. --*/ { NTSTATUS Status; PDEBUG_OBJECT DebugObject; PDEBUG_EVENT DebugEvent; LIST_ENTRY TempList; PLIST_ENTRY Entry; PAGED_CODE (); ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = Process->DebugPort; if (DebugObject == NULL || (DebugObject != SourceDebugObject && SourceDebugObject != NULL)) { DebugObject = NULL; Status = STATUS_PORT_NOT_SET; } else { Process->DebugPort = NULL; Status = STATUS_SUCCESS; } ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (NT_SUCCESS (Status)) { DbgkpMarkProcessPeb (Process); } // // Remove any events for this process and wake up the threads. // if (DebugObject) { // // Remove any events and queue them to a temporary queue // InitializeListHead (&TempList); ExAcquireFastMutex (&DebugObject->Mutex); for (Entry = DebugObject->EventList.Flink; Entry != &DebugObject->EventList; ) { DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); Entry = Entry->Flink; if (DebugEvent->Process == Process) { RemoveEntryList (&DebugEvent->EventList); InsertTailList (&TempList, &DebugEvent->EventList); } } ExReleaseFastMutex (&DebugObject->Mutex); ObDereferenceObject (DebugObject); // // Wake up all the removed threads. // while (!IsListEmpty (&TempList)) { Entry = RemoveHeadList (&TempList); DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); DebugEvent->Status = STATUS_DEBUGGER_INACTIVE; DbgkpWakeTarget (DebugEvent); } } return Status; } NTSTATUS DbgkpSetProcessDebugObject ( IN PEPROCESS Process, IN PDEBUG_OBJECT DebugObject, IN NTSTATUS MsgStatus, IN PETHREAD LastThread ) /*++ Routine Description: Attach a debug object to a process. Arguments: Process - Process to be debugged DebugObject - Debug object to attach MsgStatus - Status from queing the messages LastThread - Last thread seen in attach loop. Return Value: NTSTATUS - Status of call. --*/ { NTSTATUS Status; PETHREAD ThisThread; LIST_ENTRY TempList; PLIST_ENTRY Entry; PDEBUG_EVENT DebugEvent; BOOLEAN First; PETHREAD Thread; BOOLEAN GlobalHeld; PETHREAD FirstThread; PAGED_CODE (); ThisThread = PsGetCurrentThread (); InitializeListHead (&TempList); First = TRUE; GlobalHeld = FALSE; if (!NT_SUCCESS (MsgStatus)) { LastThread = NULL; Status = MsgStatus; } else { Status = STATUS_SUCCESS; } // // Pick up any threads we missed // if (NT_SUCCESS (Status)) { while (1) { // // Acquire the debug port mutex so we know that any new threads will // have to wait to behind us. // GlobalHeld = TRUE; ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); // // If the port has been set then exit now. // if (Process->DebugPort != NULL) { Status = STATUS_PORT_ALREADY_SET; break; } // // Assign the debug port to the process to pick up any new threads // Process->DebugPort = DebugObject; // // Reference the last thread so we can deref outside the lock // ObReferenceObject (LastThread); // // Search forward for new threads // Thread = PsGetNextProcessThread (Process, LastThread); if (Thread != NULL) { // // Remove the debug port from the process as we are // about to drop the lock // Process->DebugPort = NULL; ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); GlobalHeld = FALSE; ObDereferenceObject (LastThread); // // Queue any new thread messages and repeat. // Status = DbgkpPostFakeThreadMessages (Process, DebugObject, Thread, &FirstThread, &LastThread); if (!NT_SUCCESS (Status)) { LastThread = NULL; break; } ObDereferenceObject (FirstThread); } else { break; } } } // // Lock the debug object so we can check its deleted status // ExAcquireFastMutex (&DebugObject->Mutex); // // We must not propogate a debug port thats got no handles left. // if (NT_SUCCESS (Status)) { if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) { PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_NO_DEBUG_INHERIT|PS_PROCESS_FLAGS_CREATE_REPORTED); ObReferenceObject (DebugObject); } else { Process->DebugPort = NULL; Status = STATUS_DEBUGGER_INACTIVE; } } for (Entry = DebugObject->EventList.Flink; Entry != &DebugObject->EventList; ) { DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); Entry = Entry->Flink; if ((DebugEvent->Flags&DEBUG_EVENT_INACTIVE) != 0 && DebugEvent->BackoutThread == ThisThread) { Thread = DebugEvent->Thread; // // If the thread has not been inserted by CreateThread yet then don't // create a handle. We skip system threads here also // if (NT_SUCCESS (Status) && Thread->GrantedAccess != 0 && !IS_SYSTEM_THREAD (Thread)) { // // If we could not acquire rundown protection on this // thread then we need to supress its exit message. // if ((DebugEvent->Flags&DEBUG_EVENT_PROTECT_FAILED) != 0) { PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG); RemoveEntryList (&DebugEvent->EventList); InsertTailList (&TempList, &DebugEvent->EventList); } else { if (First) { DebugEvent->Flags &= ~DEBUG_EVENT_INACTIVE; KeSetEvent (&DebugObject->EventsPresent, 0, FALSE); First = FALSE; } DebugEvent->BackoutThread = NULL; PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG); } } else { RemoveEntryList (&DebugEvent->EventList); InsertTailList (&TempList, &DebugEvent->EventList); } if (DebugEvent->Flags&DEBUG_EVENT_RELEASE) { DebugEvent->Flags &= ~DEBUG_EVENT_RELEASE; ExReleaseRundownProtection (&Thread->RundownProtect); } } } ExReleaseFastMutex (&DebugObject->Mutex); if (GlobalHeld) { ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); } if (LastThread != NULL) { ObDereferenceObject (LastThread); } while (!IsListEmpty (&TempList)) { Entry = RemoveHeadList (&TempList); DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); DbgkpWakeTarget (DebugEvent); } if (NT_SUCCESS (Status)) { DbgkpMarkProcessPeb (Process); } return Status; } NTSTATUS DbgkpPostFakeThreadMessages ( IN PEPROCESS Process, IN PDEBUG_OBJECT DebugObject, IN PETHREAD StartThread, OUT PETHREAD *pFirstThread, OUT PETHREAD *pLastThread ) /*++ Routine Description: This routine posts the faked initial process create, thread create messages Arguments: Process - Process to be debugged DebugObject - Debug object to queue messages to StartThread - Thread to start search from pFirstThread - First thread found in the list pLastThread - Last thread found in the list Return Value: None. --*/ { NTSTATUS Status; PETHREAD Thread, FirstThread, LastThread; DBGKM_APIMSG ApiMsg; BOOLEAN First = TRUE; BOOLEAN IsFirstThread; PIMAGE_NT_HEADERS NtHeaders; ULONG Flags; #if !defined (DBGK_DONT_SUSPEND) NTSTATUS Status1; #endif PAGED_CODE (); LastThread = FirstThread = NULL; Status = STATUS_UNSUCCESSFUL; if (StartThread != NULL) { First = FALSE; FirstThread = StartThread; ObReferenceObject (FirstThread); } else { StartThread = PsGetNextProcessThread (Process, NULL); First = TRUE; } for (Thread = StartThread; Thread != NULL; Thread = PsGetNextProcessThread (Process, Thread)) { Flags = DEBUG_EVENT_NOWAIT; // // Keep a track ont he last thread we have seen. // We use this as a starting point for new threads after we // really attach so we can pick up any new threads. // if (LastThread != NULL) { ObDereferenceObject (LastThread); } LastThread = Thread; ObReferenceObject (LastThread); // // Acquire rundown protection of the thread. // This stops the thread exiting so we know it can't send // it's termination message // if (ExAcquireRundownProtection (&Thread->RundownProtect)) { Flags |= DEBUG_EVENT_RELEASE; // // Suspend the thread if we can for the debugger // We don't suspend terminating threads as we will not be giving details // of these to the debugger. // #if !defined (DBGK_DONT_SUSPEND) if (!IS_SYSTEM_THREAD (Thread)) { Status1 = PsSuspendThread (Thread, NULL); if (NT_SUCCESS (Status1)) { Flags |= DEBUG_EVENT_SUSPEND; } } #endif } else { // // Rundown protection failed for this thread. // This means the thread is exiting. We will mark this thread // later so it doesn't sent a thread termination message. // We can't do this now because this attach might fail. // Flags |= DEBUG_EVENT_PROTECT_FAILED; } RtlZeroMemory (&ApiMsg, sizeof (ApiMsg)); if (First && (Flags&DEBUG_EVENT_PROTECT_FAILED) == 0 && !IS_SYSTEM_THREAD (Thread) && Thread->GrantedAccess != 0) { IsFirstThread = TRUE; } else { IsFirstThread = FALSE; } if (IsFirstThread) { ApiMsg.ApiNumber = DbgKmCreateProcessApi; if (Process->SectionObject != NULL) { // system process doesn't have one of these! ApiMsg.u.CreateProcessInfo.FileHandle = DbgkpSectionToFileHandle (Process->SectionObject); } else { ApiMsg.u.CreateProcessInfo.FileHandle = NULL; } ApiMsg.u.CreateProcessInfo.BaseOfImage = Process->SectionBaseAddress; try { NtHeaders = RtlImageNtHeader(Process->SectionBaseAddress); if (NtHeaders) { ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; // Filling this in breaks MSDEV! // (PVOID)(NtHeaders->OptionalHeader.ImageBase + NtHeaders->OptionalHeader.AddressOfEntryPoint); ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable; ApiMsg.u.CreateProcessInfo.DebugInfoSize = NtHeaders->FileHeader.NumberOfSymbols; } } except (EXCEPTION_EXECUTE_HANDLER) { ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = 0; ApiMsg.u.CreateProcessInfo.DebugInfoSize = 0; } } else { ApiMsg.ApiNumber = DbgKmCreateThreadApi; ApiMsg.u.CreateThread.StartAddress = Thread->StartAddress; } Status = DbgkpQueueMessage (Process, Thread, &ApiMsg, Flags, DebugObject); if (!NT_SUCCESS (Status)) { if (Flags&DEBUG_EVENT_SUSPEND) { PsResumeThread (Thread, NULL); } if (Flags&DEBUG_EVENT_RELEASE) { ExReleaseRundownProtection (&Thread->RundownProtect); } if (ApiMsg.ApiNumber == DbgKmCreateProcessApi && ApiMsg.u.CreateProcessInfo.FileHandle != NULL) { ObCloseHandle (ApiMsg.u.CreateProcessInfo.FileHandle, KernelMode); } PsQuitNextProcessThread (Thread); break; } else if (IsFirstThread) { First = FALSE; ObReferenceObject (Thread); FirstThread = Thread; } } if (!NT_SUCCESS (Status)) { if (FirstThread) { ObDereferenceObject (FirstThread); } if (LastThread != NULL) { ObDereferenceObject (LastThread); } } else { if (FirstThread) { *pFirstThread = FirstThread; *pLastThread = LastThread; } else { Status = STATUS_UNSUCCESSFUL; } } return Status; } NTSTATUS DbgkpPostFakeModuleMessages ( IN PEPROCESS Process, IN PETHREAD Thread, IN PDEBUG_OBJECT DebugObject) /*++ Routine Description: This routine posts the faked module load messages when we debug an active process. Arguments: ProcessHandle - Handle to a process to be debugged DebugObjectHandle - Handle to a debug object Return Value: None. --*/ { PPEB Peb = Process->Peb; PPEB_LDR_DATA Ldr; PLIST_ENTRY LdrHead, LdrNext; PLDR_DATA_TABLE_ENTRY LdrEntry; DBGKM_APIMSG ApiMsg; ULONG i; OBJECT_ATTRIBUTES oa; UNICODE_STRING Name; PIMAGE_NT_HEADERS NtHeaders; NTSTATUS Status; IO_STATUS_BLOCK iosb; PAGED_CODE (); if (Peb == NULL) { return STATUS_SUCCESS; } try { Ldr = Peb->Ldr; LdrHead = &Ldr->InLoadOrderModuleList; ProbeForReadSmallStructure (LdrHead, sizeof (LIST_ENTRY), sizeof (UCHAR)); for (LdrNext = LdrHead->Flink, i = 0; LdrNext != LdrHead && i < 500; LdrNext = LdrNext->Flink, i++) { // // First image got send with process create message // if (i > 0) { RtlZeroMemory (&ApiMsg, sizeof (ApiMsg)); LdrEntry = CONTAINING_RECORD (LdrNext, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); ProbeForReadSmallStructure (LdrEntry, sizeof (LDR_DATA_TABLE_ENTRY), sizeof (UCHAR)); ApiMsg.ApiNumber = DbgKmLoadDllApi; ApiMsg.u.LoadDll.BaseOfDll = LdrEntry->DllBase; ApiMsg.u.LoadDll.NamePointer = NULL; ProbeForReadSmallStructure (ApiMsg.u.LoadDll.BaseOfDll, sizeof (IMAGE_DOS_HEADER), sizeof (UCHAR)); NtHeaders = RtlImageNtHeader (ApiMsg.u.LoadDll.BaseOfDll); if (NtHeaders) { ApiMsg.u.LoadDll.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable; ApiMsg.u.LoadDll.DebugInfoSize = NtHeaders->FileHeader.NumberOfSymbols; } Status = MmGetFileNameForAddress (NtHeaders, &Name); if (NT_SUCCESS (Status)) { InitializeObjectAttributes (&oa, &Name, OBJ_FORCE_ACCESS_CHECK|OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenFile (&ApiMsg.u.LoadDll.FileHandle, GENERIC_READ|SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); if (!NT_SUCCESS (Status)) { ApiMsg.u.LoadDll.FileHandle = NULL; } ExFreePool (Name.Buffer); } Status = DbgkpQueueMessage (Process, Thread, &ApiMsg, DEBUG_EVENT_NOWAIT, DebugObject); if (!NT_SUCCESS (Status) && ApiMsg.u.LoadDll.FileHandle != NULL) { ObCloseHandle (ApiMsg.u.LoadDll.FileHandle, KernelMode); } } ProbeForReadSmallStructure (LdrNext, sizeof (LIST_ENTRY), sizeof (UCHAR)); } } except (EXCEPTION_EXECUTE_HANDLER) { } #if defined(_WIN64) if (Process->Wow64Process != NULL && Process->Wow64Process->Wow64 != NULL) { PPEB32 Peb32; PPEB_LDR_DATA32 Ldr32; PLIST_ENTRY32 LdrHead32, LdrNext32; PLDR_DATA_TABLE_ENTRY32 LdrEntry32; PWCHAR pSys; Peb32 = (PPEB32)Process->Wow64Process->Wow64; try { Ldr32 = (PVOID) UlongToPtr(Peb32->Ldr); LdrHead32 = &Ldr32->InLoadOrderModuleList; ProbeForReadSmallStructure (LdrHead32, sizeof (LIST_ENTRY32), sizeof (UCHAR)); for (LdrNext32 = (PVOID) UlongToPtr(LdrHead32->Flink), i = 0; LdrNext32 != LdrHead32 && i < 500; LdrNext32 = (PVOID) UlongToPtr(LdrNext32->Flink), i++) { if (i > 0) { RtlZeroMemory (&ApiMsg, sizeof (ApiMsg)); LdrEntry32 = CONTAINING_RECORD (LdrNext32, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks); ProbeForReadSmallStructure (LdrEntry32, sizeof (LDR_DATA_TABLE_ENTRY32), sizeof (UCHAR)); ApiMsg.ApiNumber = DbgKmLoadDllApi; ApiMsg.u.LoadDll.BaseOfDll = (PVOID) UlongToPtr(LdrEntry32->DllBase); ApiMsg.u.LoadDll.NamePointer = NULL; ProbeForReadSmallStructure (ApiMsg.u.LoadDll.BaseOfDll, sizeof (IMAGE_DOS_HEADER), sizeof (UCHAR)); NtHeaders = RtlImageNtHeader(ApiMsg.u.LoadDll.BaseOfDll); if (NtHeaders) { ApiMsg.u.LoadDll.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable; ApiMsg.u.LoadDll.DebugInfoSize = NtHeaders->FileHeader.NumberOfSymbols; } Status = MmGetFileNameForAddress (NtHeaders, &Name); if (NT_SUCCESS (Status)) { ASSERT (sizeof (L"SYSTEM32") == sizeof (WOW64_SYSTEM_DIRECTORY_U)); pSys = wcsstr (Name.Buffer, L"\\SYSTEM32\\"); if (pSys != NULL) { RtlCopyMemory (pSys+1, WOW64_SYSTEM_DIRECTORY_U, sizeof(WOW64_SYSTEM_DIRECTORY_U) - sizeof(UNICODE_NULL)); } InitializeObjectAttributes (&oa, &Name, OBJ_FORCE_ACCESS_CHECK|OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL); Status = ZwOpenFile (&ApiMsg.u.LoadDll.FileHandle, GENERIC_READ|SYNCHRONIZE, &oa, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); if (!NT_SUCCESS (Status)) { ApiMsg.u.LoadDll.FileHandle = NULL; } ExFreePool (Name.Buffer); } Status = DbgkpQueueMessage (Process, Thread, &ApiMsg, DEBUG_EVENT_NOWAIT, DebugObject); if (!NT_SUCCESS (Status) && ApiMsg.u.LoadDll.FileHandle != NULL) { ObCloseHandle (ApiMsg.u.LoadDll.FileHandle, KernelMode); } } ProbeForReadSmallStructure (LdrNext32, sizeof (LIST_ENTRY32), sizeof (UCHAR)); } } except (EXCEPTION_EXECUTE_HANDLER) { } } #endif return STATUS_SUCCESS; } NTSTATUS DbgkpPostFakeProcessCreateMessages ( IN PEPROCESS Process, IN PDEBUG_OBJECT DebugObject, IN PETHREAD *pLastThread ) /*++ Routine Description: This routine posts the faked initial process create, thread create and mudule load messages Arguments: ProcessHandle - Handle to a process to be debugged DebugObjectHandle - Handle to a debug object Return Value: None. --*/ { NTSTATUS Status; KAPC_STATE ApcState; PETHREAD Thread; PETHREAD LastThread; PAGED_CODE (); // // Attach to the process so we can touch its address space // KeStackAttachProcess(&Process->Pcb, &ApcState); Status = DbgkpPostFakeThreadMessages (Process, DebugObject, NULL, &Thread, &LastThread); if (NT_SUCCESS (Status)) { Status = DbgkpPostFakeModuleMessages (Process, Thread, DebugObject); if (!NT_SUCCESS (Status)) { ObDereferenceObject (LastThread); LastThread = NULL; } ObDereferenceObject (Thread); } else { LastThread = NULL; } KeUnstackDetachProcess(&ApcState); *pLastThread = LastThread; return Status; } NTSTATUS NtDebugActiveProcess ( IN HANDLE ProcessHandle, IN HANDLE DebugObjectHandle ) /*++ Routine Description: Attach a debug object to a process. Arguments: ProcessHandle - Handle to a process to be debugged DebugObjectHandle - Handle to a debug object Return Value: NTSTATUS - Status of call. --*/ { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; PEPROCESS Process; PETHREAD LastThread; PAGED_CODE (); PreviousMode = KeGetPreviousMode(); Status = ObReferenceObjectByHandle (ProcessHandle, PROCESS_SET_PORT, PsProcessType, PreviousMode, &Process, NULL); if (!NT_SUCCESS (Status)) { return Status; } // // Don't let us debug ourselves or the system process. // if (Process == PsGetCurrentProcess () || Process == PsInitialSystemProcess) { ObDereferenceObject (Process); return STATUS_ACCESS_DENIED; } Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_PROCESS_ASSIGN, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (NT_SUCCESS (Status)) { // // We will be touching process address space. Block process rundown. // if (ExAcquireRundownProtection (&Process->RundownProtect)) { // // Post the fake process create messages etc. // Status = DbgkpPostFakeProcessCreateMessages (Process, DebugObject, &LastThread); // // Set the debug port. If this fails it will remove any faked messages. // Status = DbgkpSetProcessDebugObject (Process, DebugObject, Status, LastThread); ExReleaseRundownProtection (&Process->RundownProtect); } else { Status = STATUS_PROCESS_IS_TERMINATING; } ObDereferenceObject (DebugObject); } ObDereferenceObject (Process); return Status; } NTSTATUS NtRemoveProcessDebug ( IN HANDLE ProcessHandle, IN HANDLE DebugObjectHandle ) /*++ Routine Description: Remove a debug object from a process. Arguments: ProcessHandle - Handle to a process currently being debugged Return Value: NTSTATUS - Status of call. --*/ { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; PEPROCESS Process; PAGED_CODE (); PreviousMode = KeGetPreviousMode(); Status = ObReferenceObjectByHandle (ProcessHandle, PROCESS_SET_PORT, PsProcessType, PreviousMode, &Process, NULL); if (!NT_SUCCESS (Status)) { return Status; } Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_PROCESS_ASSIGN, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (NT_SUCCESS (Status)) { Status = DbgkClearProcessDebugObject (Process, DebugObject); ObDereferenceObject (DebugObject); } ObDereferenceObject (Process); return Status; } VOID DbgkpOpenHandles ( PDBGUI_WAIT_STATE_CHANGE WaitStateChange, PEPROCESS Process, PETHREAD Thread ) /*++ Routine Description: Opens up process, thread and filehandles if need be for some of the requests Arguments: WaitStateChange - User mode format change block Process - Pointer to target process Thread - Pointer to target thread Return Value: None --*/ { NTSTATUS Status; PEPROCESS CurrentProcess; HANDLE OldHandle; PAGED_CODE (); switch (WaitStateChange->NewState) { case DbgCreateThreadStateChange : // // We have the right to open up any thread in the process if we are allowed to debug it. // Use kernel mode here so we are always granted it regardless of protection. // Status = ObOpenObjectByPointer (Thread, 0, NULL, THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | \ THREAD_QUERY_INFORMATION | THREAD_SET_INFORMATION | THREAD_TERMINATE | READ_CONTROL | SYNCHRONIZE, PsThreadType, KernelMode, &WaitStateChange->StateInfo.CreateThread.HandleToThread); if (!NT_SUCCESS (Status)) { WaitStateChange->StateInfo.CreateThread.HandleToThread = NULL; } break; case DbgCreateProcessStateChange : Status = ObOpenObjectByPointer (Thread, 0, NULL, THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | \ THREAD_QUERY_INFORMATION | THREAD_SET_INFORMATION | THREAD_TERMINATE | READ_CONTROL | SYNCHRONIZE, PsThreadType, KernelMode, &WaitStateChange->StateInfo.CreateProcessInfo.HandleToThread); if (!NT_SUCCESS (Status)) { WaitStateChange->StateInfo.CreateProcessInfo.HandleToThread = NULL; } Status = ObOpenObjectByPointer (Process, 0, NULL, PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_TERMINATE | READ_CONTROL | SYNCHRONIZE, PsProcessType, KernelMode, &WaitStateChange->StateInfo.CreateProcessInfo.HandleToProcess); if (!NT_SUCCESS (Status)) { WaitStateChange->StateInfo.CreateProcessInfo.HandleToProcess = NULL; } OldHandle = WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.FileHandle; if (OldHandle != NULL) { CurrentProcess = PsGetCurrentProcess (); Status = ObDuplicateObject (CurrentProcess, OldHandle, CurrentProcess, &WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.FileHandle, 0, 0, DUPLICATE_SAME_ACCESS, KernelMode); if (!NT_SUCCESS (Status)) { WaitStateChange->StateInfo.CreateProcessInfo.NewProcess.FileHandle = NULL; } ObCloseHandle (OldHandle, KernelMode); } break; case DbgLoadDllStateChange : OldHandle = WaitStateChange->StateInfo.LoadDll.FileHandle; if (OldHandle != NULL) { CurrentProcess = PsGetCurrentProcess (); Status = ObDuplicateObject (CurrentProcess, OldHandle, CurrentProcess, &WaitStateChange->StateInfo.LoadDll.FileHandle, 0, 0, DUPLICATE_SAME_ACCESS, KernelMode); if (!NT_SUCCESS (Status)) { WaitStateChange->StateInfo.LoadDll.FileHandle = NULL; } ObCloseHandle (OldHandle, KernelMode); } break; default : break; } } VOID DbgkpConvertKernelToUserStateChange ( PDBGUI_WAIT_STATE_CHANGE WaitStateChange, PDEBUG_EVENT DebugEvent) /*++ Routine Description: Converts a kernel message to one the user expects Arguments: WaitStateChange - User mode format DebugEvent - Debug event block to copy from Return Value: None --*/ { PAGED_CODE (); WaitStateChange->AppClientId = DebugEvent->ClientId; switch (DebugEvent->ApiMsg.ApiNumber) { case DbgKmExceptionApi : switch (DebugEvent->ApiMsg.u.Exception.ExceptionRecord.ExceptionCode) { case STATUS_BREAKPOINT : WaitStateChange->NewState = DbgBreakpointStateChange; break; case STATUS_SINGLE_STEP : WaitStateChange->NewState = DbgSingleStepStateChange; break; default : WaitStateChange->NewState = DbgExceptionStateChange; break; } WaitStateChange->StateInfo.Exception = DebugEvent->ApiMsg.u.Exception; break; case DbgKmCreateThreadApi : WaitStateChange->NewState = DbgCreateThreadStateChange; WaitStateChange->StateInfo.CreateThread.NewThread = DebugEvent->ApiMsg.u.CreateThread; break; case DbgKmCreateProcessApi : WaitStateChange->NewState = DbgCreateProcessStateChange; WaitStateChange->StateInfo.CreateProcessInfo.NewProcess = DebugEvent->ApiMsg.u.CreateProcessInfo; // // clear out the handle in the message as we will close this when we duplicate. // DebugEvent->ApiMsg.u.CreateProcessInfo.FileHandle = NULL; break; case DbgKmExitThreadApi : WaitStateChange->NewState = DbgExitThreadStateChange; WaitStateChange->StateInfo.ExitThread = DebugEvent->ApiMsg.u.ExitThread; break; case DbgKmExitProcessApi : WaitStateChange->NewState = DbgExitProcessStateChange; WaitStateChange->StateInfo.ExitProcess = DebugEvent->ApiMsg.u.ExitProcess; break; case DbgKmLoadDllApi : WaitStateChange->NewState = DbgLoadDllStateChange; WaitStateChange->StateInfo.LoadDll = DebugEvent->ApiMsg.u.LoadDll; // // clear out the handle in the message as we will close this when we duplicate. // DebugEvent->ApiMsg.u.LoadDll.FileHandle = NULL; break; case DbgKmUnloadDllApi : WaitStateChange->NewState = DbgUnloadDllStateChange; WaitStateChange->StateInfo.UnloadDll = DebugEvent->ApiMsg.u.UnloadDll; break; default : ASSERT (FALSE); } } NTSTATUS NtWaitForDebugEvent ( IN HANDLE DebugObjectHandle, IN BOOLEAN Alertable, IN PLARGE_INTEGER Timeout OPTIONAL, OUT PDBGUI_WAIT_STATE_CHANGE WaitStateChange ) /*++ Routine Description: Waits for a debug event and returns it to the user if one arives Arguments: DebugObjectHandle - Handle to a debug object Alertable - TRUE is the wait is to be alertable Timeout - Operation timeout value WaitStateChange - Returned debug event Return Value: Status of operation --*/ { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; LARGE_INTEGER Tmo = {0}; LARGE_INTEGER StartTime = {0}; DBGUI_WAIT_STATE_CHANGE tWaitStateChange = {0}; PEPROCESS Process; PETHREAD Thread; PLIST_ENTRY Entry, Entry2; PDEBUG_EVENT DebugEvent, DebugEvent2; BOOLEAN GotEvent; PAGED_CODE (); PreviousMode = KeGetPreviousMode(); try { if (ARGUMENT_PRESENT (Timeout)) { if (PreviousMode != KernelMode) { ProbeForReadSmallStructure (Timeout, sizeof (*Timeout), sizeof (UCHAR)); } Tmo = *Timeout; Timeout = &Tmo; KeQuerySystemTime (&StartTime); } if (PreviousMode != KernelMode) { ProbeForWriteSmallStructure (WaitStateChange, sizeof (*WaitStateChange), sizeof (UCHAR)); } } except (ExSystemExceptionFilter ()) { // If previous mode is kernel then don't handle the exception return GetExceptionCode (); } Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_READ_EVENT, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (!NT_SUCCESS (Status)) { return Status; } Process = NULL; Thread = NULL; while (1) { Status = KeWaitForSingleObject (&DebugObject->EventsPresent, Executive, PreviousMode, Alertable, Timeout); if (!NT_SUCCESS (Status) || Status == STATUS_TIMEOUT || Status == STATUS_ALERTED || Status == STATUS_USER_APC) { break; } GotEvent = FALSE; DebugEvent = NULL; ExAcquireFastMutex (&DebugObject->Mutex); // // If the object is delete pending then return an error. // if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) { for (Entry = DebugObject->EventList.Flink; Entry != &DebugObject->EventList; Entry = Entry->Flink) { DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); // // If this event has not been given back to the user yet and is not // inactive then pass it back. // We check to see if we have any other outstanding messages for this // thread as this confuses VC. You can only get multiple events // for the same thread for the attach faked messages. // if ((DebugEvent->Flags&(DEBUG_EVENT_READ|DEBUG_EVENT_INACTIVE)) == 0) { GotEvent = TRUE; for (Entry2 = DebugObject->EventList.Flink; Entry2 != Entry; Entry2 = Entry2->Flink) { DebugEvent2 = CONTAINING_RECORD (Entry2, DEBUG_EVENT, EventList); if (DebugEvent->ClientId.UniqueProcess == DebugEvent2->ClientId.UniqueProcess) { // // This event has the same process as an earlier event. Mark it as inactive. // DebugEvent->Flags |= DEBUG_EVENT_INACTIVE; DebugEvent->BackoutThread = NULL; GotEvent = FALSE; break; } } if (GotEvent) { break; } } } if (GotEvent) { Process = DebugEvent->Process; Thread = DebugEvent->Thread; ObReferenceObject (Thread); ObReferenceObject (Process); DbgkpConvertKernelToUserStateChange (&tWaitStateChange, DebugEvent); DebugEvent->Flags |= DEBUG_EVENT_READ; } else { // // No unread events there. Clear the event. // KeClearEvent (&DebugObject->EventsPresent); } Status = STATUS_SUCCESS; } else { Status = STATUS_DEBUGGER_INACTIVE; } ExReleaseFastMutex (&DebugObject->Mutex); if (NT_SUCCESS (Status)) { // // If we woke up and found nothing // if (GotEvent == FALSE) { // // If timeout is a delta time then adjust it for the wait so far. // if (Tmo.QuadPart < 0) { LARGE_INTEGER NewTime; KeQuerySystemTime (&NewTime); Tmo.QuadPart = Tmo.QuadPart + (NewTime.QuadPart - StartTime.QuadPart); StartTime = NewTime; if (Tmo.QuadPart >= 0) { Status = STATUS_TIMEOUT; break; } } } else { // // Fixup needed handles. The caller could have guessed the thread id etc by now and made the target thread // continue. This isn't a problem as we won't do anything damaging to the system in this case. The caller // won't get the correct results but they set out to break us. // DbgkpOpenHandles (&tWaitStateChange, Process, Thread); ObDereferenceObject (Thread); ObDereferenceObject (Process); break; } } else { break; } } ObDereferenceObject (DebugObject); try { *WaitStateChange = tWaitStateChange; } except (ExSystemExceptionFilter ()) { // If previous mode is kernel then don't handle the exception Status = GetExceptionCode (); } return Status; } NTSTATUS NtDebugContinue ( IN HANDLE DebugObjectHandle, IN PCLIENT_ID ClientId, IN NTSTATUS ContinueStatus ) /*++ Routine Description: Coninues a stalled debugged thread Arguments: DebugObjectHandle - Handle to a debug object ClientId - ClientId of thread tro continue ContinueStatus - Status of continue Return Value: Status of operation --*/ { NTSTATUS Status; PDEBUG_OBJECT DebugObject; PDEBUG_EVENT DebugEvent, FoundDebugEvent; KPROCESSOR_MODE PreviousMode; CLIENT_ID Clid; PLIST_ENTRY Entry; BOOLEAN GotEvent; PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { ProbeForReadSmallStructure (ClientId, sizeof (*ClientId), sizeof (UCHAR)); } Clid = *ClientId; } except (ExSystemExceptionFilter ()) { // If previous mode is kernel then don't handle the exception return GetExceptionCode (); } switch (ContinueStatus) { case DBG_EXCEPTION_HANDLED : case DBG_EXCEPTION_NOT_HANDLED : case DBG_TERMINATE_THREAD : case DBG_TERMINATE_PROCESS : case DBG_CONTINUE : break; default : return STATUS_INVALID_PARAMETER; } Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_READ_EVENT, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (!NT_SUCCESS (Status)) { return Status; } GotEvent = FALSE; FoundDebugEvent = NULL; ExAcquireFastMutex (&DebugObject->Mutex); for (Entry = DebugObject->EventList.Flink; Entry != &DebugObject->EventList; Entry = Entry->Flink) { DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList); // // Make sure the client ID matches and that the debugger saw all the events. // We don't allow the caller to start a thread that it never saw a message for. // This would do no harm but its probably a bug in the debugger. // if (DebugEvent->ClientId.UniqueProcess == Clid.UniqueProcess) { if (!GotEvent) { if (DebugEvent->ClientId.UniqueThread == Clid.UniqueThread && (DebugEvent->Flags&DEBUG_EVENT_READ) != 0) { RemoveEntryList (Entry); FoundDebugEvent = DebugEvent; GotEvent = TRUE; } } else { // // VC breaks if it sees more than one event at a time // for the same process. // DebugEvent->Flags &= ~DEBUG_EVENT_INACTIVE; KeSetEvent (&DebugObject->EventsPresent, 0, FALSE); break; } } } ExReleaseFastMutex (&DebugObject->Mutex); ObDereferenceObject (DebugObject); if (GotEvent) { FoundDebugEvent->ApiMsg.ReturnedStatus = ContinueStatus; FoundDebugEvent->Status = STATUS_SUCCESS; DbgkpWakeTarget (FoundDebugEvent); } else { Status = STATUS_INVALID_PARAMETER; } return Status; } NTSTATUS NtSetInformationDebugObject ( IN HANDLE DebugObjectHandle, IN DEBUGOBJECTINFOCLASS DebugObjectInformationClass, IN PVOID DebugInformation, IN ULONG DebugInformationLength, OUT PULONG ReturnLength OPTIONAL ) /*++ Routine Description: This function sets the state of a debug object. Arguments: ProcessHandle - Supplies a handle to a process object. ProcessInformationClass - Supplies the class of information being set. ProcessInformation - Supplies a pointer to a record that contains the information to set. ProcessInformationLength - Supplies the length of the record that contains the information to set. Return Value: NTSTATUS - Status of call --*/ { KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PDEBUG_OBJECT DebugObject; ULONG Flags; PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { ProbeForRead (DebugInformation, DebugInformationLength, sizeof (ULONG)); if (ARGUMENT_PRESENT (ReturnLength)) { ProbeForWriteUlong (ReturnLength); } } if (ARGUMENT_PRESENT (ReturnLength)) { *ReturnLength = 0; } switch (DebugObjectInformationClass) { case DebugObjectFlags : { if (DebugInformationLength != sizeof (ULONG)) { if (ARGUMENT_PRESENT (ReturnLength)) { *ReturnLength = sizeof (ULONG); } return STATUS_INFO_LENGTH_MISMATCH; } Flags = *(PULONG) DebugInformation; break; } default : { return STATUS_INVALID_PARAMETER; } } } except (ExSystemExceptionFilter ()) { return GetExceptionCode (); } switch (DebugObjectInformationClass) { case DebugObjectFlags : { if (Flags & ~DEBUG_KILL_ON_CLOSE) { return STATUS_INVALID_PARAMETER; } Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_SET_INFORMATION, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (!NT_SUCCESS (Status)) { return Status; } ExAcquireFastMutex (&DebugObject->Mutex); if (Flags&DEBUG_KILL_ON_CLOSE) { DebugObject->Flags |= DEBUG_OBJECT_KILL_ON_CLOSE; } else { DebugObject->Flags &= ~DEBUG_OBJECT_KILL_ON_CLOSE; } ExReleaseFastMutex (&DebugObject->Mutex); ObDereferenceObject (DebugObject); } } return STATUS_SUCCESS; }