2244 lines
68 KiB
C
2244 lines
68 KiB
C
|
/*++
|
||
|
|
||
|
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;
|
||
|
}
|