2189 lines
58 KiB
C
2189 lines
58 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
psdelete.c
|
||
|
||
Abstract:
|
||
|
||
This module implements process and thread object termination and
|
||
deletion.
|
||
|
||
Author:
|
||
|
||
Mark Lucovsky (markl) 01-May-1989
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "psp.h"
|
||
|
||
extern PEPROCESS ExpDefaultErrorPortProcess;
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
|
||
NTSTATUS
|
||
PspFreezeProcessWorker (
|
||
PEPROCESS Process,
|
||
PVOID Context
|
||
);
|
||
|
||
VOID
|
||
PspCatchCriticalBreak(
|
||
IN PCHAR Msg,
|
||
IN PVOID Object,
|
||
IN PUCHAR ImageFileName
|
||
);
|
||
|
||
#pragma alloc_text(PAGE, PsSetLegoNotifyRoutine)
|
||
#pragma alloc_text(PAGE, PspTerminateThreadByPointer)
|
||
#pragma alloc_text(PAGE, NtTerminateProcess)
|
||
#pragma alloc_text(PAGE, PsTerminateProcess)
|
||
#pragma alloc_text(PAGE, PspWaitForUsermodeExit)
|
||
#pragma alloc_text(PAGE, NtTerminateThread)
|
||
#pragma alloc_text(PAGE, PsTerminateSystemThread)
|
||
#pragma alloc_text(PAGE, PspNullSpecialApc)
|
||
#pragma alloc_text(PAGE, PsExitSpecialApc)
|
||
#pragma alloc_text(PAGE, PspExitApcRundown)
|
||
#pragma alloc_text(PAGE, PspExitNormalApc)
|
||
#pragma alloc_text(PAGE, PspCatchCriticalBreak)
|
||
#pragma alloc_text(PAGE, PspExitThread)
|
||
#pragma alloc_text(PAGE, PspExitProcess)
|
||
#pragma alloc_text(PAGE, PspProcessDelete)
|
||
#pragma alloc_text(PAGE, PspThreadDelete)
|
||
#pragma alloc_text(PAGE, NtRegisterThreadTerminatePort)
|
||
#pragma alloc_text(PAGE, PsGetProcessExitTime)
|
||
#pragma alloc_text(PAGE, PsShutdownSystem)
|
||
#pragma alloc_text(PAGE, PsWaitForAllProcesses)
|
||
#pragma alloc_text(PAGE, PspFreezeProcessWorker)
|
||
#pragma alloc_text(PAGE, PspTerminateProcess)
|
||
#endif
|
||
|
||
#ifdef ALLOC_DATA_PRAGMA
|
||
#pragma data_seg("PAGEDATA")
|
||
#endif
|
||
PLEGO_NOTIFY_ROUTINE PspLegoNotifyRoutine = NULL;
|
||
|
||
ULONG
|
||
PsSetLegoNotifyRoutine(
|
||
PLEGO_NOTIFY_ROUTINE LegoNotifyRoutine
|
||
)
|
||
{
|
||
PAGED_CODE();
|
||
|
||
PspLegoNotifyRoutine = LegoNotifyRoutine;
|
||
|
||
return FIELD_OFFSET(KTHREAD,LegoData);
|
||
}
|
||
|
||
VOID
|
||
PspReaper(
|
||
IN PVOID Context
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine implements the thread reaper. The reaper is responsible
|
||
for processing terminated threads. This includes:
|
||
|
||
- deallocating their kernel stacks
|
||
|
||
- releasing their process' CreateDelete lock
|
||
|
||
- dereferencing their process
|
||
|
||
- dereferencing themselves
|
||
|
||
Arguments:
|
||
|
||
Context - NOT USED
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL OldIrql;
|
||
PETHREAD Thread, NextThread;
|
||
|
||
UNREFERENCED_PARAMETER (Context);
|
||
|
||
//
|
||
// Remove the current list of threads from the reaper list, acquire
|
||
// the context swap lock, and then release the both the context
|
||
// swap dispatcher database locks.
|
||
//
|
||
//
|
||
// N.B. The dispatcher database lock is used to synchronize access to
|
||
// the reaper list. This is done to avoid a race condition with
|
||
// the thread termination code.
|
||
//
|
||
//
|
||
|
||
while (1) {
|
||
KiLockDispatcherDatabase (&OldIrql);
|
||
|
||
Thread = PsReaperList;
|
||
PsReaperList = NULL;
|
||
|
||
if (Thread == NULL) {
|
||
//
|
||
// Set the reaper not active and return.
|
||
//
|
||
PsReaperActive = FALSE;
|
||
KiUnlockDispatcherDatabase (OldIrql);
|
||
|
||
return;
|
||
} else {
|
||
KiUnlockDispatcherDatabase (OldIrql);
|
||
|
||
//
|
||
// The context swap lock is acquired and immediately released.
|
||
// This is necessary to ensure that the respective thread has
|
||
// completely passed through the context switch code before it
|
||
// is terminated.
|
||
//
|
||
#if !defined (NT_UP)
|
||
KiLockContextSwap (&OldIrql);
|
||
KiUnlockContextSwap (OldIrql);
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// Delete the kernel stack and dereference the thread.
|
||
//
|
||
do {
|
||
MmDeleteKernelStack (Thread->Tcb.StackBase,
|
||
(BOOLEAN)Thread->Tcb.LargeStack);
|
||
|
||
Thread->Tcb.InitialStack = NULL;
|
||
|
||
NextThread = Thread->ReaperLink;
|
||
|
||
ObDereferenceObject (Thread);
|
||
|
||
Thread = NextThread;
|
||
|
||
} while (Thread != NULL);
|
||
|
||
}
|
||
return;
|
||
}
|
||
|
||
NTSTATUS
|
||
PspTerminateThreadByPointer(
|
||
IN PETHREAD Thread,
|
||
IN NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the specified thread to terminate.
|
||
|
||
Arguments:
|
||
|
||
ThreadHandle - Supplies a referenced pointer to the thread to terminate.
|
||
|
||
ExitStatus - Supplies the exit status associated with the thread.
|
||
|
||
Return Value:
|
||
|
||
TBD
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
PKAPC ExitApc=NULL;
|
||
ULONG OldMask;
|
||
LARGE_INTEGER ShortTime = {(ULONG)(-10 * 1000 * 100), -1}; // 100 milliseconds
|
||
|
||
PAGED_CODE();
|
||
|
||
if (Thread->CrossThreadFlags
|
||
& PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
|
||
PspCatchCriticalBreak("Terminating critical thread 0x%p (in %s)\n",
|
||
Thread,
|
||
THREAD_TO_PROCESS(Thread)->ImageFileName);
|
||
}
|
||
|
||
if (Thread == PsGetCurrentThread()) {
|
||
|
||
PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);
|
||
|
||
PspExitThread (ExitStatus);
|
||
|
||
//
|
||
// Never Returns
|
||
//
|
||
|
||
} else {
|
||
//
|
||
// Cross thread deletion of system threads won't work.
|
||
//
|
||
if (IS_SYSTEM_THREAD (Thread)) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
while (1) {
|
||
ExitApc = (PKAPC) ExAllocatePoolWithTag (NonPagedPool,
|
||
sizeof(KAPC),
|
||
'xEsP');
|
||
if (ExitApc != NULL) {
|
||
break;
|
||
}
|
||
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
|
||
}
|
||
|
||
//
|
||
// Mark the thread as terminating and call the exit function.
|
||
//
|
||
OldMask = PS_TEST_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);
|
||
|
||
//
|
||
// If we are the first to set the terminating flag then queue the APC
|
||
//
|
||
|
||
if ((OldMask & PS_CROSS_THREAD_FLAGS_TERMINATED) == 0) {
|
||
|
||
KeInitializeApc (ExitApc,
|
||
PsGetKernelThread (Thread),
|
||
OriginalApcEnvironment,
|
||
PsExitSpecialApc,
|
||
PspExitApcRundown,
|
||
PspExitNormalApc,
|
||
KernelMode,
|
||
ULongToPtr (ExitStatus));
|
||
|
||
if (!KeInsertQueueApc (ExitApc, ExitApc, NULL, 2)) {
|
||
// Note that we'll get here if APC queueing has been
|
||
// disabled -- on the other hand, in that case, the thread
|
||
// is exiting anyway.
|
||
ExFreePool (ExitApc);
|
||
Status = STATUS_UNSUCCESSFUL;
|
||
} else {
|
||
//
|
||
// We queued the APC to the thread. Wake up the thread if it was suspened.
|
||
//
|
||
KeForceResumeThread (&Thread->Tcb);
|
||
|
||
}
|
||
} else {
|
||
ExFreePool (ExitApc);
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
NtTerminateProcess(
|
||
IN HANDLE ProcessHandle OPTIONAL,
|
||
IN NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the specified process and all of
|
||
its threads to terminate.
|
||
|
||
Arguments:
|
||
|
||
ProcessHandle - Supplies a handle to the process to terminate.
|
||
|
||
ExitStatus - Supplies the exit status associated with the process.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Status of operation
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PETHREAD Thread, Self;
|
||
PEPROCESS Process;
|
||
PEPROCESS CurrentProcess;
|
||
NTSTATUS st;
|
||
BOOLEAN ProcessHandleSpecified;
|
||
PAGED_CODE();
|
||
|
||
Self = PsGetCurrentThread();
|
||
CurrentProcess = PsGetCurrentProcessByThread (Self);
|
||
|
||
if (ARGUMENT_PRESENT (ProcessHandle)) {
|
||
ProcessHandleSpecified = TRUE;
|
||
} else {
|
||
ProcessHandleSpecified = FALSE;
|
||
ProcessHandle = NtCurrentProcess();
|
||
}
|
||
|
||
st = ObReferenceObjectByHandle (ProcessHandle,
|
||
PROCESS_TERMINATE,
|
||
PsProcessType,
|
||
KeGetPreviousModeByThread(&Self->Tcb),
|
||
&Process,
|
||
NULL);
|
||
|
||
if (!NT_SUCCESS (st)) {
|
||
return(st);
|
||
}
|
||
|
||
if (Process->Flags & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION) {
|
||
PspCatchCriticalBreak ("Terminating critical process 0x%p (%s)\n",
|
||
Process,
|
||
Process->ImageFileName);
|
||
}
|
||
|
||
//
|
||
// Acquire rundown protection just so we can give the right errors
|
||
//
|
||
|
||
if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
|
||
ObDereferenceObject (Process);
|
||
return STATUS_PROCESS_IS_TERMINATING;
|
||
}
|
||
|
||
//
|
||
// Mark process as deleting except for the obscure delete self case.
|
||
//
|
||
if (ProcessHandleSpecified) {
|
||
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
|
||
}
|
||
|
||
st = STATUS_NOTHING_TO_TERMINATE;
|
||
|
||
for (Thread = PsGetNextProcessThread (Process, NULL);
|
||
Thread != NULL;
|
||
Thread = PsGetNextProcessThread (Process, Thread)) {
|
||
|
||
st = STATUS_SUCCESS;
|
||
if (Thread != Self) {
|
||
PspTerminateThreadByPointer (Thread, ExitStatus);
|
||
}
|
||
}
|
||
|
||
ExReleaseRundownProtection (&Process->RundownProtect);
|
||
|
||
|
||
if (Process == CurrentProcess) {
|
||
if (ProcessHandleSpecified) {
|
||
|
||
ObDereferenceObject (Process);
|
||
|
||
//
|
||
// Never Returns
|
||
//
|
||
|
||
PspTerminateThreadByPointer (Self, ExitStatus);
|
||
}
|
||
} else if (ExitStatus == DBG_TERMINATE_PROCESS) {
|
||
DbgkClearProcessDebugObject (Process, NULL);
|
||
}
|
||
|
||
//
|
||
// If there are no threads in this process then clear out its handle table.
|
||
// Do the same for processes being debugged. This is so a process can never lock itself into the system
|
||
// by debugging itself or have a handle open to itself.
|
||
//
|
||
if (st == STATUS_NOTHING_TO_TERMINATE || (Process->DebugPort != NULL && ProcessHandleSpecified)) {
|
||
ObClearProcessHandleTable (Process);
|
||
st = STATUS_SUCCESS;
|
||
}
|
||
|
||
ObDereferenceObject(Process);
|
||
|
||
return st;
|
||
}
|
||
|
||
NTSTATUS
|
||
PsTerminateProcess(
|
||
PEPROCESS Process,
|
||
NTSTATUS Status
|
||
)
|
||
{
|
||
return PspTerminateProcess (Process, Status);
|
||
}
|
||
|
||
NTSTATUS
|
||
PspTerminateProcess(
|
||
PEPROCESS Process,
|
||
NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the specified process and all of
|
||
its threads to terminate.
|
||
|
||
Arguments:
|
||
|
||
ProcessHandle - Supplies a handle to the process to terminate.
|
||
|
||
ExitStatus - Supplies the exit status associated with the process.
|
||
|
||
Return Value:
|
||
|
||
TBD
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PETHREAD Thread;
|
||
NTSTATUS st;
|
||
|
||
PAGED_CODE();
|
||
|
||
if (Process == PsGetCurrentProcess ()) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
if (Process->Flags
|
||
& PS_PROCESS_FLAGS_BREAK_ON_TERMINATION) {
|
||
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
|
||
Process,
|
||
Process->ImageFileName);
|
||
}
|
||
|
||
//
|
||
// Mark process as deleting
|
||
//
|
||
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
|
||
|
||
st = STATUS_NOTHING_TO_TERMINATE;
|
||
|
||
for (Thread = PsGetNextProcessThread (Process, NULL);
|
||
Thread != NULL;
|
||
Thread = PsGetNextProcessThread (Process, Thread)) {
|
||
|
||
st = STATUS_SUCCESS;
|
||
|
||
PspTerminateThreadByPointer (Thread, ExitStatus);
|
||
|
||
}
|
||
|
||
//
|
||
// If there are no threads in this process then clear out its handle table.
|
||
// Do the same for processes being debugged. This is so a process can never lock itself into the system
|
||
// by debugging itself or have a handle open to itself.
|
||
//
|
||
if (st == STATUS_NOTHING_TO_TERMINATE || Process->DebugPort != NULL) {
|
||
ObClearProcessHandleTable (Process);
|
||
st = STATUS_SUCCESS;
|
||
}
|
||
return st;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
PspWaitForUsermodeExit(
|
||
IN PEPROCESS Process
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function waits for a process's usermode threads to terminate.
|
||
|
||
Arguments:
|
||
|
||
Process - Supplies a pointer to the process to wait for
|
||
|
||
WaitMode - Supplies the mode to wait in
|
||
|
||
LockMode - Supplies the way to wait for the process lock
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Status of call
|
||
|
||
--*/
|
||
{
|
||
BOOLEAN GotAThread;
|
||
PETHREAD Thread;
|
||
|
||
do {
|
||
GotAThread = FALSE;
|
||
|
||
for (Thread = PsGetNextProcessThread (Process, NULL);
|
||
Thread != NULL;
|
||
Thread = PsGetNextProcessThread (Process, Thread)) {
|
||
|
||
if (!IS_SYSTEM_THREAD (Thread) && !KeReadStateThread (&Thread->Tcb)) {
|
||
ObReferenceObject (Thread);
|
||
PsQuitNextProcessThread (Thread);
|
||
GotAThread = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
if (GotAThread) {
|
||
KeWaitForSingleObject (Thread,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
ObDereferenceObject (Thread);
|
||
}
|
||
} while (GotAThread);
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtTerminateThread(
|
||
IN HANDLE ThreadHandle OPTIONAL,
|
||
IN NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the specified thread to terminate.
|
||
|
||
Arguments:
|
||
|
||
ThreadHandle - Supplies a handle to the thread to terminate.
|
||
|
||
ExitStatus - Supplies the exit status associated with the thread.
|
||
|
||
Return Value:
|
||
|
||
TBD
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PETHREAD Thread=NULL, ThisThread;
|
||
PEPROCESS ThisProcess;
|
||
NTSTATUS Status = STATUS_SUCCESS;
|
||
BOOLEAN Self = TRUE;
|
||
|
||
PAGED_CODE();
|
||
|
||
ThisThread = PsGetCurrentThread ();
|
||
|
||
if (!ARGUMENT_PRESENT (ThreadHandle)) {
|
||
//
|
||
// This is part of the strange linkage between base\win32 and the kernel.
|
||
// This routine gets called this way first and if it returns the base
|
||
// code does an exit process call.
|
||
//
|
||
ThisProcess = PsGetCurrentProcessByThread (ThisThread);
|
||
|
||
if (ThisProcess->ActiveThreads == 1) {
|
||
return STATUS_CANT_TERMINATE_SELF;
|
||
}
|
||
Self = TRUE;
|
||
} else {
|
||
if (ThreadHandle != NtCurrentThread ()) {
|
||
Status = ObReferenceObjectByHandle (ThreadHandle,
|
||
THREAD_TERMINATE,
|
||
PsThreadType,
|
||
KeGetPreviousModeByThread(&ThisThread->Tcb),
|
||
&Thread,
|
||
NULL);
|
||
if (!NT_SUCCESS (Status)) {
|
||
return Status;
|
||
}
|
||
|
||
if (Thread == ThisThread) {
|
||
ObDereferenceObject (Thread);
|
||
} else {
|
||
Self = FALSE;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if (Self) {
|
||
PspTerminateThreadByPointer (ThisThread, ExitStatus);
|
||
} else {
|
||
Status = PspTerminateThreadByPointer (Thread, ExitStatus);
|
||
ObDereferenceObject (Thread);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
PsTerminateSystemThread(
|
||
IN NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the current thread, which must be a system
|
||
thread, to terminate.
|
||
|
||
Arguments:
|
||
|
||
ExitStatus - Supplies the exit status associated with the thread.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Status of call
|
||
|
||
--*/
|
||
|
||
{
|
||
PETHREAD Thread = PsGetCurrentThread();
|
||
|
||
if (!IS_SYSTEM_THREAD (Thread)) {
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
return PspTerminateThreadByPointer (Thread, ExitStatus);
|
||
}
|
||
|
||
|
||
VOID
|
||
PspNullSpecialApc(
|
||
IN PKAPC Apc,
|
||
IN PKNORMAL_ROUTINE *NormalRoutine,
|
||
IN PVOID *NormalContext,
|
||
IN PVOID *SystemArgument1,
|
||
IN PVOID *SystemArgument2
|
||
)
|
||
|
||
{
|
||
|
||
PAGED_CODE();
|
||
|
||
UNREFERENCED_PARAMETER(NormalRoutine);
|
||
UNREFERENCED_PARAMETER(NormalContext);
|
||
UNREFERENCED_PARAMETER(SystemArgument1);
|
||
UNREFERENCED_PARAMETER(SystemArgument2);
|
||
|
||
ExFreePool (Apc);
|
||
}
|
||
|
||
VOID
|
||
PsExitSpecialApc(
|
||
IN PKAPC Apc,
|
||
IN PKNORMAL_ROUTINE *NormalRoutine,
|
||
IN PVOID *NormalContext,
|
||
IN PVOID *SystemArgument1,
|
||
IN PVOID *SystemArgument2
|
||
)
|
||
|
||
{
|
||
NTSTATUS ExitStatus;
|
||
PETHREAD Thread;
|
||
|
||
PAGED_CODE();
|
||
|
||
UNREFERENCED_PARAMETER(NormalRoutine);
|
||
UNREFERENCED_PARAMETER(NormalContext);
|
||
UNREFERENCED_PARAMETER(SystemArgument1);
|
||
UNREFERENCED_PARAMETER(SystemArgument2);
|
||
|
||
Thread = PsGetCurrentThread();
|
||
|
||
if (((ULONG_PTR)Apc->SystemArgument2) & 1) {
|
||
ExitStatus = (NTSTATUS)((LONG_PTR)Apc->NormalContext);
|
||
PspExitApcRundown (Apc);
|
||
PspExitThread (ExitStatus);
|
||
}
|
||
|
||
}
|
||
|
||
VOID
|
||
PspExitApcRundown(
|
||
IN PKAPC Apc
|
||
)
|
||
{
|
||
PAGED_CODE();
|
||
|
||
ExFreePool(Apc);
|
||
}
|
||
|
||
VOID
|
||
PspExitNormalApc(
|
||
IN PVOID NormalContext,
|
||
IN PVOID SystemArgument1,
|
||
IN PVOID SystemArgument2
|
||
)
|
||
|
||
{
|
||
PETHREAD Thread;
|
||
PKAPC ExitApc;
|
||
|
||
PAGED_CODE();
|
||
|
||
ASSERT (!(((ULONG_PTR)SystemArgument2) & 1));
|
||
|
||
Thread = PsGetCurrentThread();
|
||
|
||
ExitApc = (PKAPC) SystemArgument1;
|
||
|
||
KeInitializeApc (ExitApc,
|
||
PsGetKernelThread(Thread),
|
||
OriginalApcEnvironment,
|
||
PsExitSpecialApc,
|
||
PspExitApcRundown,
|
||
PspExitNormalApc,
|
||
UserMode,
|
||
NormalContext);
|
||
|
||
if (!KeInsertQueueApc (ExitApc, ExitApc,
|
||
(PVOID)((ULONG_PTR)SystemArgument2 | 1),
|
||
2)) {
|
||
// Note that we'll get here if APC queueing has been
|
||
// disabled -- on the other hand, in that case, the thread
|
||
// is exiting anyway.
|
||
PspExitApcRundown (ExitApc);
|
||
}
|
||
//
|
||
// We just queued a user APC to this thread. User APC won't fire until we do an
|
||
// alertable wait so we need to set this flag here.
|
||
//
|
||
Thread->Tcb.ApcState.UserApcPending = TRUE;
|
||
}
|
||
|
||
VOID
|
||
PspCatchCriticalBreak(
|
||
IN PCHAR Msg,
|
||
IN PVOID Object,
|
||
IN PUCHAR ImageFileName
|
||
)
|
||
{
|
||
// The object is critical to the OS -- ask to break in, or bugcheck.
|
||
char Response[2];
|
||
BOOLEAN Handled;
|
||
|
||
PAGED_CODE();
|
||
|
||
Handled = FALSE;
|
||
|
||
if (KdDebuggerEnabled) {
|
||
DbgPrint(Msg,
|
||
Object,
|
||
ImageFileName);
|
||
|
||
while (! Handled
|
||
&& ! KdDebuggerNotPresent) {
|
||
DbgPrompt("Break, or Ignore (bi)? ",
|
||
Response,
|
||
sizeof(Response));
|
||
|
||
switch (Response[0]) {
|
||
case 'b':
|
||
case 'B':
|
||
DbgBreakPoint();
|
||
// Fall through
|
||
case 'i':
|
||
case 'I':
|
||
Handled = TRUE;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (! Handled) {
|
||
//
|
||
// No debugger -- bugcheck immediately
|
||
//
|
||
KeBugCheckEx(CRITICAL_OBJECT_TERMINATION,
|
||
(ULONG_PTR) ((DISPATCHER_HEADER *)Object)->Type,
|
||
(ULONG_PTR) Object,
|
||
(ULONG_PTR) ImageFileName,
|
||
(ULONG_PTR) Msg);
|
||
}
|
||
}
|
||
|
||
DECLSPEC_NORETURN
|
||
VOID
|
||
PspExitThread(
|
||
IN NTSTATUS ExitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function causes the currently executing thread to terminate. This
|
||
function is only called from within the process structure. It is called
|
||
either from mainline exit code to exit the current thread, or from
|
||
PsExitSpecialApc (as a piggyback to user-mode PspExitNormalApc).
|
||
|
||
Arguments:
|
||
|
||
ExitStatus - Supplies the exit status associated with the current thread.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
|
||
{
|
||
|
||
PETHREAD Thread;
|
||
PETHREAD WaitThread;
|
||
PETHREAD DerefThread;
|
||
PEPROCESS Process;
|
||
PKAPC Apc;
|
||
PLIST_ENTRY Entry, FirstEntry;
|
||
PTERMINATION_PORT TerminationPort, NextPort;
|
||
LPC_CLIENT_DIED_MSG CdMsg;
|
||
BOOLEAN LastThread;
|
||
PTEB Teb;
|
||
PPEB Peb;
|
||
|
||
PAGED_CODE();
|
||
|
||
Thread = PsGetCurrentThread();
|
||
Process = THREAD_TO_PROCESS(Thread);
|
||
|
||
if (Process != PsGetCurrentProcessByThread (Thread)) {
|
||
KeBugCheckEx (INVALID_PROCESS_ATTACH_ATTEMPT,
|
||
(ULONG_PTR)Process,
|
||
(ULONG_PTR)Thread->Tcb.ApcState.Process,
|
||
(ULONG)Thread->Tcb.ApcStateIndex,
|
||
(ULONG_PTR)Thread);
|
||
}
|
||
|
||
KeLowerIrql(PASSIVE_LEVEL);
|
||
|
||
if (Thread->ActiveExWorker) {
|
||
KeBugCheckEx (ACTIVE_EX_WORKER_THREAD_TERMINATION,
|
||
(ULONG_PTR)Thread,
|
||
0,
|
||
0,
|
||
0);
|
||
}
|
||
|
||
if (Thread->Tcb.Priority < LOW_REALTIME_PRIORITY) {
|
||
KeSetPriorityThread (&Thread->Tcb, LOW_REALTIME_PRIORITY);
|
||
}
|
||
//
|
||
// Its time to start turning off various cross thread references.
|
||
// Mark the thread as rundown and wait for accessors to exit.
|
||
//
|
||
ExWaitForRundownProtectionRelease (&Thread->RundownProtect);
|
||
|
||
//
|
||
// Clear any execution state associated with the thread
|
||
//
|
||
|
||
PoRundownThread(Thread);
|
||
|
||
//
|
||
// Notify registered callout routines of thread deletion.
|
||
//
|
||
|
||
PERFINFO_THREAD_DELETE(Thread);
|
||
|
||
if (PspCreateThreadNotifyRoutineCount != 0) {
|
||
ULONG i;
|
||
PEX_CALLBACK_ROUTINE_BLOCK CallBack;
|
||
PCREATE_THREAD_NOTIFY_ROUTINE Rtn;
|
||
|
||
for (i=0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
|
||
CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]);
|
||
if (CallBack != NULL) {
|
||
Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
|
||
Rtn (Process->UniqueProcessId,
|
||
Thread->Cid.UniqueThread,
|
||
FALSE);
|
||
ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i],
|
||
CallBack);
|
||
}
|
||
}
|
||
}
|
||
|
||
LastThread = FALSE;
|
||
DerefThread = NULL;
|
||
|
||
PspLockProcessExclusive (Process, Thread);
|
||
|
||
//
|
||
// Say one less active thread. If we are the last then block creates and wait for the other threads to exit.
|
||
//
|
||
Process->ActiveThreads--;
|
||
if (Process->ActiveThreads == 0) {
|
||
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
|
||
|
||
LastThread = TRUE;
|
||
if (ExitStatus == STATUS_THREAD_IS_TERMINATING) {
|
||
if (Process->ExitStatus == STATUS_PENDING) {
|
||
Process->ExitStatus = Process->LastThreadExitStatus;
|
||
}
|
||
} else {
|
||
Process->ExitStatus = ExitStatus;
|
||
}
|
||
|
||
//
|
||
// We are the last thread to leave the process. We have to wait till all the other threads have exited before we do.
|
||
//
|
||
for (Entry = Process->ThreadListHead.Flink;
|
||
Entry != &Process->ThreadListHead;
|
||
Entry = Entry->Flink) {
|
||
|
||
WaitThread = CONTAINING_RECORD (Entry, ETHREAD, ThreadListEntry);
|
||
if (WaitThread != Thread &&
|
||
!KeReadStateThread (&WaitThread->Tcb) &&
|
||
ObReferenceObjectSafe (WaitThread)) {
|
||
|
||
PspUnlockProcessExclusive (Process, Thread);
|
||
|
||
KeWaitForSingleObject (WaitThread,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
|
||
if (DerefThread != NULL) {
|
||
ObDereferenceObject (DerefThread);
|
||
}
|
||
DerefThread = WaitThread;
|
||
PspLockProcessExclusive (Process, Thread);
|
||
}
|
||
}
|
||
} else {
|
||
if (ExitStatus != STATUS_THREAD_IS_TERMINATING) {
|
||
Process->LastThreadExitStatus = ExitStatus;
|
||
}
|
||
}
|
||
|
||
PspUnlockProcessExclusive (Process, Thread);
|
||
|
||
if (DerefThread != NULL) {
|
||
ObDereferenceObject (DerefThread);
|
||
}
|
||
|
||
|
||
//
|
||
// If we need to send debug messages then do so.
|
||
//
|
||
|
||
if (Process->DebugPort != NULL) {
|
||
//
|
||
// Don't report system thread exit to the debugger as we don't report them.
|
||
//
|
||
if (!IS_SYSTEM_THREAD (Thread)) {
|
||
if (LastThread) {
|
||
DbgkExitProcess (ExitStatus);
|
||
} else {
|
||
DbgkExitThread (ExitStatus);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (KD_DEBUGGER_ENABLED) {
|
||
|
||
if (Thread->CrossThreadFlags & PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
|
||
PspCatchCriticalBreak ("Critical thread 0x%p (in %s) exited\n",
|
||
Thread,
|
||
Process->ImageFileName);
|
||
}
|
||
} // End of critical thread/process exit detect
|
||
|
||
if (LastThread &&
|
||
(Process->Flags & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION)) {
|
||
if (KD_DEBUGGER_ENABLED) {
|
||
PspCatchCriticalBreak ("Critical process 0x%p (%s) exited\n",
|
||
Process,
|
||
Process->ImageFileName);
|
||
} else {
|
||
KeBugCheckEx (CRITICAL_PROCESS_DIED,
|
||
(ULONG_PTR)Process,
|
||
0,
|
||
0,
|
||
0);
|
||
}
|
||
}
|
||
|
||
|
||
ASSERT(Thread->Tcb.KernelApcDisable == 0);
|
||
|
||
//
|
||
// Process the TerminationPort. This is only accessed from this thread
|
||
//
|
||
TerminationPort = Thread->TerminationPort;
|
||
if (TerminationPort != NULL) {
|
||
|
||
CdMsg.PortMsg.u1.s1.DataLength = sizeof(LARGE_INTEGER);
|
||
CdMsg.PortMsg.u1.s1.TotalLength = sizeof(LPC_CLIENT_DIED_MSG);
|
||
CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
|
||
CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;
|
||
|
||
do {
|
||
|
||
CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
|
||
LpcRequestPort (TerminationPort->Port, (PPORT_MESSAGE)&CdMsg);
|
||
ObDereferenceObject (TerminationPort->Port);
|
||
|
||
NextPort = TerminationPort->Next;
|
||
|
||
ExFreePoolWithTag (TerminationPort, 'pTsP'|PROTECTED_POOL);
|
||
|
||
TerminationPort = NextPort;
|
||
|
||
} while (TerminationPort != NULL);
|
||
} else {
|
||
|
||
//
|
||
// If there are no ports to send notifications to,
|
||
// but there is an exception port, then we have to
|
||
// send a client died message through the exception
|
||
// port. This will allow a server a chance to get notification
|
||
// if an app/thread dies before it even starts
|
||
//
|
||
//
|
||
// We only send the exception if the thread creation really worked.
|
||
// DeadThread is set when an NtCreateThread returns an error, but
|
||
// the thread will actually execute this path. If DeadThread is not
|
||
// set than the thread creation succeeded. The other place DeadThread
|
||
// is set is when we were terminated without having any chance to move.
|
||
// in this case, DeadThread is set and the exit status is set to
|
||
// STATUS_THREAD_IS_TERMINATING
|
||
//
|
||
|
||
if ((ExitStatus == STATUS_THREAD_IS_TERMINATING &&
|
||
(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) ||
|
||
!(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) {
|
||
|
||
CdMsg.PortMsg.u1.s1.DataLength = sizeof (LARGE_INTEGER);
|
||
CdMsg.PortMsg.u1.s1.TotalLength = sizeof (LPC_CLIENT_DIED_MSG);
|
||
CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
|
||
CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;
|
||
if (Process->ExceptionPort != NULL) {
|
||
CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
|
||
LpcRequestPort (Process->ExceptionPort, (PPORT_MESSAGE)&CdMsg);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// rundown the Win32 structures
|
||
//
|
||
|
||
if (Thread->Tcb.Win32Thread) {
|
||
(PspW32ThreadCallout) (Thread, PsW32ThreadCalloutExit);
|
||
}
|
||
|
||
if (LastThread && Process->Win32Process) {
|
||
(PspW32ProcessCallout) (Process, FALSE);
|
||
}
|
||
|
||
//
|
||
// User/Gdi has been given a chance to clean up. Now make sure they didn't
|
||
// leave the kernel stack locked which would happen if data was still live on
|
||
// this stack, but was being used by another thread
|
||
//
|
||
|
||
if (!Thread->Tcb.EnableStackSwap) {
|
||
KeBugCheckEx (KERNEL_STACK_LOCKED_AT_EXIT, 0, 0, 0, 0);
|
||
}
|
||
|
||
//
|
||
// Rundown The Lists:
|
||
//
|
||
// - Cancel Io By Thread
|
||
// - Cancel Timers
|
||
// - Cancel Registry Notify Requests pending against this thread
|
||
// - Perform kernel thread rundown
|
||
//
|
||
|
||
IoCancelThreadIo (Thread);
|
||
ExTimerRundown ();
|
||
CmNotifyRunDown (Thread);
|
||
KeRundownThread ();
|
||
|
||
//
|
||
// Delete the thread's TEB. If the address of the TEB is in user
|
||
// space, then this is a real user mode TEB. If the address is in
|
||
// system space, then this is a special system thread TEB allocated
|
||
// from paged or nonpaged pool.
|
||
//
|
||
|
||
|
||
Teb = Thread->Tcb.Teb;
|
||
if (Teb != NULL) {
|
||
PRTL_CRITICAL_SECTION Cs;
|
||
int DecrementCount;
|
||
|
||
Peb = Process->Peb;
|
||
|
||
try {
|
||
|
||
//
|
||
// The thread is a user-mode thread. Look to see if the thread
|
||
// owns the loader lock (and any other key peb-based critical
|
||
// sections. If so, do our best to release the locks.
|
||
//
|
||
// Since the LoaderLock used to be a mutant, releasing the lock
|
||
// like this is very similar to mutant abandonment and the loader
|
||
// never did anything with abandoned status anyway
|
||
//
|
||
|
||
Cs = Peb->LoaderLock;
|
||
if (Cs != NULL) {
|
||
ProbeForRead(Cs,sizeof(*Cs),4);
|
||
if (Cs->OwningThread == Thread->Cid.UniqueThread) {
|
||
|
||
//
|
||
// x86 uses a 1 based recursion count
|
||
//
|
||
|
||
#if defined(_X86_)
|
||
DecrementCount = Cs->RecursionCount;
|
||
#else
|
||
DecrementCount = Cs->RecursionCount + 1;
|
||
#endif
|
||
Cs->RecursionCount = 0;
|
||
Cs->OwningThread = 0;
|
||
|
||
//
|
||
// undo lock count increments for recursion cases
|
||
//
|
||
|
||
while(DecrementCount > 1) {
|
||
InterlockedDecrement (&Cs->LockCount);
|
||
DecrementCount--;
|
||
}
|
||
|
||
//
|
||
// undo final lock count
|
||
//
|
||
|
||
if (InterlockedDecrement (&Cs->LockCount) >= 0) {
|
||
NtSetEvent (Cs->LockSemaphore, NULL);
|
||
}
|
||
} else if (Teb->WaitingOnLoaderLock) {
|
||
|
||
//
|
||
// if the thread exited while waiting on the loader
|
||
// lock clean it up. There is still a potential race
|
||
// here since we can not safely know what happens to
|
||
// a thread after it interlocked increments the lock count
|
||
// but before it sets the waiting on loader lock flag. On the
|
||
// release side, it it safe since we mark ownership of the lock
|
||
// before clearing the flag. This triggers the first part of this
|
||
// test. The only thing out of whack is the recursion count, but this
|
||
// is also safe since in this state, recursion count is 0.
|
||
//
|
||
|
||
|
||
//
|
||
// This code isn't right. We need to bump down our lock count
|
||
// increment.
|
||
//
|
||
// A few cases to consider:
|
||
//
|
||
// Another thread releases the lock signals the event.
|
||
// We take the wait and then die before setting our ID.
|
||
// I doubt very much that this can happen because right
|
||
// after we come out of the wait, we set the owner Id
|
||
// (meaning that we would go through the other part of the if).
|
||
// Bottom line is that we should just decrement our lock count
|
||
// and get out of the way. There is no need to set the event.
|
||
// In the RAS stress failure, I saw us setting the event
|
||
// just because the lock count was >= 0. The lock was already held
|
||
// by another thread so setting the event let yet another thread
|
||
// also own the lock. Last one to release would get a
|
||
// not owner critical section failure
|
||
//
|
||
//
|
||
// if ( InterlockedDecrement(&Cs->LockCount) >= 0 ){
|
||
// NtSetEvent(Cs->LockSemaphore,NULL);
|
||
// }
|
||
//
|
||
|
||
InterlockedDecrement (&Cs->LockCount);
|
||
}
|
||
}
|
||
#if defined(_WIN64)
|
||
if (Process->Wow64Process) {
|
||
// Do the same thing for the 32-bit PEB->Ldr
|
||
PRTL_CRITICAL_SECTION32 Cs32;
|
||
PPEB32 Peb32;
|
||
|
||
Peb32 = Process->Wow64Process->Wow64;
|
||
Cs32 = (PRTL_CRITICAL_SECTION32)ULongToPtr (Peb32->LoaderLock);
|
||
if (Cs32 != NULL) {
|
||
ProbeForRead (Cs32, sizeof(*Cs32), 4);
|
||
if (Cs32->OwningThread == PtrToUlong(Thread->Cid.UniqueThread)) {
|
||
//
|
||
// x86 uses a 1 based recursion count, so the
|
||
// IA64 kernel needs to do the same, since
|
||
// the critsect is really implemented by IA32
|
||
// usermode.
|
||
//
|
||
DecrementCount = Cs32->RecursionCount;
|
||
Cs32->RecursionCount = 0;
|
||
Cs32->OwningThread = 0;
|
||
|
||
//
|
||
// undo lock count increments for recursion cases
|
||
//
|
||
while(DecrementCount > 1) {
|
||
InterlockedDecrement(&Cs32->LockCount);
|
||
DecrementCount--;
|
||
}
|
||
|
||
//
|
||
// undo final lock count
|
||
//
|
||
if (InterlockedDecrement (&Cs32->LockCount) >= 0){
|
||
NtSetEvent (LongToHandle (Cs32->LockSemaphore),NULL);
|
||
}
|
||
} else {
|
||
PTEB32 Teb32 = WOW64_GET_TEB32(Teb);
|
||
|
||
ProbeForRead (Teb32,sizeof (*Teb32), 4);
|
||
if (Teb32->WaitingOnLoaderLock) {
|
||
InterlockedDecrement(&Cs32->LockCount);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
|
||
//
|
||
// Free the user mode stack on termination if we need to.
|
||
//
|
||
|
||
if (Teb->FreeStackOnTermination &&
|
||
(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) {
|
||
SIZE_T Zero = 0;
|
||
PVOID BaseAddress = Teb->DeallocationStack;
|
||
ZwFreeVirtualMemory (NtCurrentProcess (),
|
||
&BaseAddress,
|
||
&Zero,
|
||
MEM_RELEASE);
|
||
}
|
||
|
||
//
|
||
// Close the debugger object associated with this thread if there is one.
|
||
//
|
||
if (Teb->DbgSsReserved[1] != NULL) {
|
||
ObCloseHandle (Teb->DbgSsReserved[1], UserMode);
|
||
}
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
}
|
||
|
||
MmDeleteTeb (Process, Teb);
|
||
Thread->Tcb.Teb = NULL;
|
||
}
|
||
|
||
|
||
//
|
||
// Let LPC component deal with message stack in Thread->LpcReplyMessage
|
||
// but do it after the client ID becomes invalid.
|
||
//
|
||
|
||
LpcExitThread (Thread);
|
||
|
||
Thread->ExitStatus = ExitStatus;
|
||
KeQuerySystemTime (&Thread->ExitTime);
|
||
|
||
|
||
ASSERT (Thread->Tcb.KernelApcDisable == 0);
|
||
|
||
if (LastThread) {
|
||
|
||
Process->ExitTime = Thread->ExitTime;
|
||
PspExitProcess (TRUE, Process);
|
||
|
||
if (SeDetailedAuditing && !ExFastRefObjectNull (Process->Token)) {
|
||
SeAuditProcessExit (Process);
|
||
}
|
||
|
||
#if defined(_X86_)
|
||
//
|
||
// Rundown VDM DPCs
|
||
//
|
||
if (Process->VdmObjects != NULL) {
|
||
VdmRundownDpcs (Process);
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Rundown the handle table
|
||
//
|
||
ObKillProcess (Process);
|
||
|
||
//
|
||
// Release the image section
|
||
//
|
||
if (Process->SectionObject != NULL) {
|
||
ObDereferenceObject (Process->SectionObject);
|
||
Process->SectionObject = NULL;
|
||
}
|
||
|
||
if (Process->Job != NULL) {
|
||
|
||
//
|
||
// Now we can fold the process accounting into the job. Don't need to wait for
|
||
// the delete routine.
|
||
//
|
||
|
||
PspExitProcessFromJob (Process->Job, Process);
|
||
|
||
}
|
||
|
||
//
|
||
// Signal the process
|
||
//
|
||
KeSetProcess (&Process->Pcb, 0, FALSE);
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Rundown pending APCs. Protect against being frozen after we raise IRQL but before dispatcher lock is taken.
|
||
//
|
||
KeEnterCriticalRegionThread (&Thread->Tcb);
|
||
|
||
KeDisableApcQueuingThread (&Thread->Tcb);
|
||
|
||
//
|
||
// At this point we may have been frozen and the APC is pending. First we remove the suspend/freeze bias that
|
||
// may exist and then drop IRQL. The suspend APC if present will fire and drop through. No futher suspends are
|
||
// allowed as the thread is marked to prevent APC's
|
||
//
|
||
KeForceResumeThread (&Thread->Tcb);
|
||
KeLeaveCriticalRegionThread (&Thread->Tcb);
|
||
|
||
//
|
||
// flush user-mode APC queue
|
||
//
|
||
|
||
FirstEntry = KeFlushQueueApc (&Thread->Tcb, UserMode);
|
||
|
||
if (FirstEntry != NULL) {
|
||
|
||
Entry = FirstEntry;
|
||
do {
|
||
Apc = CONTAINING_RECORD (Entry, KAPC, ApcListEntry);
|
||
Entry = Entry->Flink;
|
||
|
||
//
|
||
// If the APC has a rundown routine then call it. Otherwise
|
||
// deallocate the APC
|
||
//
|
||
|
||
if (Apc->RundownRoutine) {
|
||
(Apc->RundownRoutine) (Apc);
|
||
} else {
|
||
ExFreePool (Apc);
|
||
}
|
||
|
||
} while (Entry != FirstEntry);
|
||
}
|
||
|
||
if (LastThread) {
|
||
MmCleanProcessAddressSpace (Process);
|
||
}
|
||
|
||
if (Thread->Tcb.LegoData && PspLegoNotifyRoutine) {
|
||
(PspLegoNotifyRoutine) (&Thread->Tcb);
|
||
}
|
||
|
||
//
|
||
// flush kernel-mode APC queue
|
||
// There should never be any kernel mode APCs found this far
|
||
// into thread termination. Since we go to PASSIVE_LEVEL upon
|
||
// entering exit.
|
||
//
|
||
|
||
FirstEntry = KeFlushQueueApc (&Thread->Tcb, KernelMode);
|
||
|
||
if (FirstEntry != NULL) {
|
||
KeBugCheckEx (KERNEL_APC_PENDING_DURING_EXIT,
|
||
(ULONG_PTR)FirstEntry,
|
||
(ULONG_PTR)Thread->Tcb.KernelApcDisable,
|
||
(ULONG_PTR)KeGetCurrentIrql(),
|
||
0);
|
||
}
|
||
|
||
|
||
//
|
||
// Terminate the thread.
|
||
//
|
||
// N.B. There is no return from this call.
|
||
//
|
||
// N.B. The kernel inserts the current thread in the reaper list and
|
||
// activates a thread, if necessary, to reap the terminating thread.
|
||
//
|
||
|
||
KeTerminateThread (0L);
|
||
}
|
||
|
||
VOID
|
||
PspExitProcess(
|
||
IN BOOLEAN LastThreadExit,
|
||
IN PEPROCESS Process
|
||
)
|
||
{
|
||
ULONG ActualTime;
|
||
PEJOB Job;
|
||
PETHREAD CurrentThread;
|
||
|
||
PAGED_CODE();
|
||
|
||
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_EXITING);
|
||
|
||
if (LastThreadExit) {
|
||
|
||
PERFINFO_PROCESS_DELETE(Process);
|
||
|
||
if (PspCreateProcessNotifyRoutineCount != 0) {
|
||
ULONG i;
|
||
PEX_CALLBACK_ROUTINE_BLOCK CallBack;
|
||
PCREATE_PROCESS_NOTIFY_ROUTINE Rtn;
|
||
|
||
for (i = 0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
|
||
CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]);
|
||
if (CallBack != NULL) {
|
||
Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
|
||
Rtn (Process->InheritedFromUniqueProcessId,
|
||
Process->UniqueProcessId,
|
||
FALSE);
|
||
ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i],
|
||
CallBack);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
PoRundownProcess (Process);
|
||
|
||
//
|
||
// Dereference (close) the security port. This will stop any authentication
|
||
// or EFS requests from this process to the LSA process. The "well known"
|
||
// value of 1 will prevent the security system from try to re-establish the
|
||
// connection during the process shutdown (e.g. when the rdr deletes a handle)
|
||
//
|
||
|
||
if (Process->SecurityPort) {
|
||
|
||
if (Process->SecurityPort != ((PVOID) 1)) {
|
||
ObDereferenceObject (Process->SecurityPort);
|
||
|
||
Process->SecurityPort = (PVOID) 1 ;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
if (LastThreadExit) {
|
||
|
||
|
||
//
|
||
// If the current process has previously set the timer resolution,
|
||
// then reset it.
|
||
//
|
||
|
||
if ((Process->Flags&PS_PROCESS_FLAGS_SET_TIMER_RESOLUTION) != 0) {
|
||
ZwSetTimerResolution (KeMaximumIncrement, FALSE, &ActualTime);
|
||
}
|
||
|
||
Job = Process->Job;
|
||
if (Job != NULL && Job->CompletionPort != NULL &&
|
||
!(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) &&
|
||
!(Process->JobStatus & PS_JOB_STATUS_EXIT_PROCESS_REPORTED)) {
|
||
|
||
ULONG_PTR ExitMessageId;
|
||
|
||
switch (Process->ExitStatus) {
|
||
case STATUS_GUARD_PAGE_VIOLATION :
|
||
case STATUS_DATATYPE_MISALIGNMENT :
|
||
case STATUS_BREAKPOINT :
|
||
case STATUS_SINGLE_STEP :
|
||
case STATUS_ACCESS_VIOLATION :
|
||
case STATUS_IN_PAGE_ERROR :
|
||
case STATUS_ILLEGAL_INSTRUCTION :
|
||
case STATUS_NONCONTINUABLE_EXCEPTION :
|
||
case STATUS_INVALID_DISPOSITION :
|
||
case STATUS_ARRAY_BOUNDS_EXCEEDED :
|
||
case STATUS_FLOAT_DENORMAL_OPERAND :
|
||
case STATUS_FLOAT_DIVIDE_BY_ZERO :
|
||
case STATUS_FLOAT_INEXACT_RESULT :
|
||
case STATUS_FLOAT_INVALID_OPERATION :
|
||
case STATUS_FLOAT_OVERFLOW :
|
||
case STATUS_FLOAT_STACK_CHECK :
|
||
case STATUS_FLOAT_UNDERFLOW :
|
||
case STATUS_INTEGER_DIVIDE_BY_ZERO :
|
||
case STATUS_INTEGER_OVERFLOW :
|
||
case STATUS_PRIVILEGED_INSTRUCTION :
|
||
case STATUS_STACK_OVERFLOW :
|
||
case STATUS_CONTROL_C_EXIT :
|
||
case STATUS_FLOAT_MULTIPLE_FAULTS :
|
||
case STATUS_FLOAT_MULTIPLE_TRAPS :
|
||
case STATUS_REG_NAT_CONSUMPTION :
|
||
ExitMessageId = JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS;
|
||
break;
|
||
default:
|
||
ExitMessageId = JOB_OBJECT_MSG_EXIT_PROCESS;
|
||
break;
|
||
}
|
||
|
||
PS_SET_CLEAR_BITS (&Process->JobStatus,
|
||
PS_JOB_STATUS_EXIT_PROCESS_REPORTED,
|
||
PS_JOB_STATUS_LAST_REPORT_MEMORY);
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
|
||
KeEnterCriticalRegionThread (&CurrentThread->Tcb);
|
||
ExAcquireResourceSharedLite (&Job->JobLock, TRUE);
|
||
|
||
if (Job->CompletionPort != NULL) {
|
||
IoSetIoCompletion (Job->CompletionPort,
|
||
Job->CompletionKey,
|
||
(PVOID)Process->UniqueProcessId,
|
||
STATUS_SUCCESS,
|
||
ExitMessageId,
|
||
FALSE);
|
||
}
|
||
|
||
ExReleaseResourceLite (&Job->JobLock);
|
||
KeLeaveCriticalRegionThread (&CurrentThread->Tcb);
|
||
|
||
}
|
||
|
||
if (CCPF_IS_PREFETCHER_ACTIVE ()) {
|
||
|
||
//
|
||
// Let prefetcher know that this process is exiting.
|
||
//
|
||
|
||
CcPfProcessExitNotification (Process);
|
||
}
|
||
|
||
} else {
|
||
MmCleanProcessAddressSpace (Process);
|
||
}
|
||
|
||
}
|
||
|
||
VOID
|
||
PspProcessDelete(
|
||
IN PVOID Object
|
||
)
|
||
{
|
||
PEPROCESS Process;
|
||
PETHREAD CurrentThread;
|
||
KAPC_STATE ApcState;
|
||
|
||
PAGED_CODE();
|
||
|
||
Process = (PEPROCESS)Object;
|
||
|
||
//
|
||
// Remove the process from the global list
|
||
//
|
||
if (Process->ActiveProcessLinks.Flink != NULL) {
|
||
CurrentThread = PsGetCurrentThread ();
|
||
|
||
PspLockProcessList (CurrentThread);
|
||
RemoveEntryList (&Process->ActiveProcessLinks);
|
||
PspUnlockProcessList (CurrentThread);
|
||
}
|
||
|
||
if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) {
|
||
ExFreePool (Process->SeAuditProcessCreationInfo.ImageFileName);
|
||
Process->SeAuditProcessCreationInfo.ImageFileName = NULL;
|
||
}
|
||
|
||
if (Process->Job != NULL) {
|
||
PspRemoveProcessFromJob (Process->Job, Process);
|
||
ObDereferenceObjectDeferDelete (Process->Job);
|
||
Process->Job = NULL;
|
||
}
|
||
|
||
KeTerminateProcess (&Process->Pcb);
|
||
|
||
|
||
if (Process->DebugPort != NULL) {
|
||
ObDereferenceObject (Process->DebugPort);
|
||
Process->DebugPort = NULL;
|
||
}
|
||
if (Process->ExceptionPort != NULL) {
|
||
ObDereferenceObject (Process->ExceptionPort);
|
||
Process->ExceptionPort = NULL;
|
||
}
|
||
|
||
if (Process->SectionObject != NULL) {
|
||
ObDereferenceObject (Process->SectionObject);
|
||
Process->SectionObject = NULL;
|
||
}
|
||
|
||
PspDeleteLdt (Process );
|
||
PspDeleteVdmObjects (Process);
|
||
|
||
if (Process->ObjectTable != NULL) {
|
||
KeStackAttachProcess (&Process->Pcb, &ApcState);
|
||
ObKillProcess (Process);
|
||
KeUnstackDetachProcess (&ApcState);
|
||
}
|
||
|
||
|
||
if (Process->Flags&PS_PROCESS_FLAGS_HAS_ADDRESS_SPACE) {
|
||
|
||
//
|
||
// Clean address space of the process
|
||
//
|
||
|
||
KeStackAttachProcess (&Process->Pcb, &ApcState);
|
||
|
||
PspExitProcess (FALSE, Process);
|
||
|
||
KeUnstackDetachProcess (&ApcState);
|
||
|
||
MmDeleteProcessAddressSpace (Process);
|
||
}
|
||
|
||
if (Process->UniqueProcessId) {
|
||
if (!(ExDestroyHandle (PspCidTable, Process->UniqueProcessId, NULL))) {
|
||
KeBugCheck (CID_HANDLE_DELETION);
|
||
}
|
||
}
|
||
|
||
PspDeleteProcessSecurity (Process);
|
||
|
||
|
||
if (Process->WorkingSetWatch != NULL) {
|
||
ExFreePool (Process->WorkingSetWatch);
|
||
PsReturnProcessNonPagedPoolQuota (Process, WS_CATCH_SIZE);
|
||
}
|
||
|
||
ObDereferenceDeviceMap (Process);
|
||
PspDereferenceQuota (Process);
|
||
|
||
#if !defined(_X86_)
|
||
{
|
||
//
|
||
// Free any alignment exception tracking structures that might
|
||
// have been around to support a user-mode debugger.
|
||
//
|
||
|
||
PALIGNMENT_EXCEPTION_TABLE ExceptionTable;
|
||
PALIGNMENT_EXCEPTION_TABLE NextExceptionTable;
|
||
|
||
ExceptionTable = Process->Pcb.AlignmentExceptionTable;
|
||
while (ExceptionTable != NULL) {
|
||
|
||
NextExceptionTable = ExceptionTable->Next;
|
||
ExFreePool( ExceptionTable );
|
||
ExceptionTable = NextExceptionTable;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
}
|
||
|
||
VOID
|
||
PspThreadDelete(
|
||
IN PVOID Object
|
||
)
|
||
{
|
||
PETHREAD Thread;
|
||
PETHREAD CurrentThread;
|
||
PEPROCESS Process;
|
||
|
||
PAGED_CODE();
|
||
|
||
Thread = (PETHREAD) Object;
|
||
|
||
ASSERT(Thread->Tcb.Win32Thread == NULL);
|
||
|
||
if (Thread->Tcb.InitialStack) {
|
||
MmDeleteKernelStack(Thread->Tcb.StackBase,
|
||
(BOOLEAN)Thread->Tcb.LargeStack);
|
||
}
|
||
|
||
if (Thread->Cid.UniqueThread != NULL) {
|
||
if (!ExDestroyHandle (PspCidTable, Thread->Cid.UniqueThread, NULL)) {
|
||
KeBugCheck(CID_HANDLE_DELETION);
|
||
}
|
||
}
|
||
|
||
PspDeleteThreadSecurity (Thread);
|
||
|
||
Process = THREAD_TO_PROCESS(Thread);
|
||
if (Process) {
|
||
//
|
||
// Remove the thread from the process if it was ever inserted.
|
||
//
|
||
if (Thread->ThreadListEntry.Flink != NULL) {
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
|
||
PspLockProcessExclusive (Process, CurrentThread);
|
||
|
||
RemoveEntryList (&Thread->ThreadListEntry);
|
||
|
||
PspUnlockProcessExclusive (Process, CurrentThread);
|
||
}
|
||
|
||
ObDereferenceObject(Process);
|
||
}
|
||
}
|
||
|
||
NTSTATUS
|
||
NtRegisterThreadTerminatePort(
|
||
IN HANDLE PortHandle
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This API allows a thread to register a port to be notified upon
|
||
thread termination.
|
||
|
||
Arguments:
|
||
|
||
PortHandle - Supplies an open handle to a port object that will be
|
||
sent a termination message when the thread terminates.
|
||
|
||
Return Value:
|
||
|
||
TBD
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PVOID Port;
|
||
PTERMINATION_PORT TerminationPort;
|
||
NTSTATUS st;
|
||
PETHREAD Thread;
|
||
|
||
PAGED_CODE();
|
||
|
||
Thread = PsGetCurrentThread ();
|
||
|
||
st = ObReferenceObjectByHandle (PortHandle,
|
||
0,
|
||
LpcPortObjectType,
|
||
KeGetPreviousModeByThread(&Thread->Tcb),
|
||
&Port,
|
||
NULL);
|
||
|
||
if (!NT_SUCCESS (st)) {
|
||
return st;
|
||
}
|
||
|
||
TerminationPort = ExAllocatePoolWithQuotaTag (PagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
|
||
sizeof(TERMINATION_PORT),
|
||
'pTsP'|PROTECTED_POOL);
|
||
if (TerminationPort == NULL) {
|
||
ObDereferenceObject (Port);
|
||
return STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
TerminationPort->Port = Port;
|
||
TerminationPort->Next = Thread->TerminationPort;
|
||
|
||
Thread->TerminationPort = TerminationPort;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
LARGE_INTEGER
|
||
PsGetProcessExitTime(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the exit time for the current process.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
The function value is the exit time for the current process.
|
||
|
||
Note:
|
||
|
||
This routine assumes that the caller wants an error log entry within the
|
||
bounds of the maximum size.
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Simply return the exit time for this process.
|
||
//
|
||
|
||
return PsGetCurrentProcess()->ExitTime;
|
||
}
|
||
|
||
|
||
#undef PsIsThreadTerminating
|
||
|
||
BOOLEAN
|
||
PsIsThreadTerminating(
|
||
IN PETHREAD Thread
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns TRUE if the specified thread is in the process of
|
||
terminating.
|
||
|
||
Arguments:
|
||
|
||
Thread - Supplies a pointer to the thread to be checked for termination.
|
||
|
||
Return Value:
|
||
|
||
TRUE is returned if the thread is terminating, else FALSE is returned.
|
||
|
||
--*/
|
||
|
||
{
|
||
//
|
||
// Simply return whether or not the thread is in the process of terminating.
|
||
//
|
||
|
||
if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) {
|
||
return TRUE;
|
||
} else {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
PspFreezeProcessWorker (
|
||
PEPROCESS Process,
|
||
PVOID Context
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is the enumeration worker to suspend all processes.
|
||
|
||
Arguments:
|
||
|
||
Process - Current process being enumerated
|
||
Context - Unused context value
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - Always returns true to continue enumeration
|
||
|
||
--*/
|
||
{
|
||
|
||
UNREFERENCED_PARAMETER (Context);
|
||
|
||
if (Process != PsInitialSystemProcess &&
|
||
Process != PsIdleProcess &&
|
||
Process != ExpDefaultErrorPortProcess) {
|
||
|
||
if (Process->ExceptionPort != NULL) {
|
||
LpcDisconnectPort (Process->ExceptionPort);
|
||
}
|
||
if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) == 0) {
|
||
PsSuspendProcess (Process);
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
BOOLEAN PsContinueWaiting = FALSE;
|
||
|
||
|
||
LOGICAL
|
||
PsShutdownSystem (
|
||
VOID
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function shuts down ps, killing all non-system threads.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if all processes were terminated, FALSE if not.
|
||
|
||
--*/
|
||
|
||
{
|
||
PEPROCESS Process;
|
||
PETHREAD Thread;
|
||
ULONG NumProcs;
|
||
ULONG i;
|
||
ULONG MaxPasses;
|
||
NTSTATUS Status;
|
||
LARGE_INTEGER Timeout = {(ULONG)(-10 * 1000 * 1000 * 100), -1};
|
||
LOGICAL Retval;
|
||
|
||
#define WAIT_BATCH THREAD_WAIT_OBJECTS
|
||
PKPROCESS WaitProcs[WAIT_BATCH];
|
||
BOOLEAN First;
|
||
|
||
PAGED_CODE();
|
||
|
||
Retval = TRUE;
|
||
//
|
||
// Some processes wait for other processes to die and then initiate actions.
|
||
// Killing all processes without letting any execute any usermode code
|
||
// prevents any unwanted initiated actions.
|
||
//
|
||
|
||
Thread = PsGetCurrentThread();
|
||
|
||
if (InterlockedCompareExchangePointer(&PspShutdownThread,
|
||
Thread,
|
||
0) != 0) {
|
||
// Some other thread is already in shutdown -- bail
|
||
return FALSE;
|
||
}
|
||
|
||
PsEnumProcesses (PspFreezeProcessWorker, NULL);
|
||
|
||
|
||
//
|
||
// This loop kills all the processes and then waits for one of a subset
|
||
// of them to die. They must all be killed first (before any can be waited
|
||
// on) so that any process like a debuggee that is waiting for a debugger
|
||
// won't stall us.
|
||
//
|
||
// Driver unload won't occur until the last handle goes away for a device.
|
||
//
|
||
|
||
MaxPasses = 0;
|
||
First = TRUE;
|
||
do {
|
||
NumProcs = 0;
|
||
|
||
Status = STATUS_SUCCESS;
|
||
for (Process = PsGetNextProcess (NULL);
|
||
Process != NULL;
|
||
Process = PsGetNextProcess (Process)) {
|
||
|
||
if (Process != PsInitialSystemProcess &&
|
||
Process != PsIdleProcess &&
|
||
Process != ExpDefaultErrorPortProcess) {
|
||
|
||
ASSERT (MmGetSessionId (Process) == 0);
|
||
|
||
Status = PsTerminateProcess (Process,
|
||
STATUS_SYSTEM_SHUTDOWN);
|
||
|
||
//
|
||
// If there is space save the referenced process away so
|
||
// we can wait on it. Don't wait on processes with no
|
||
// threads as they will only exit when the last handle goes.
|
||
//
|
||
|
||
if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) == 0 &&
|
||
Status != STATUS_NOTHING_TO_TERMINATE &&
|
||
NumProcs < WAIT_BATCH) {
|
||
|
||
ObReferenceObject (Process);
|
||
WaitProcs[NumProcs++] = &Process->Pcb;
|
||
}
|
||
}
|
||
}
|
||
First = FALSE;
|
||
|
||
//
|
||
// Wait for one of a small set of the processes to exit.
|
||
//
|
||
|
||
if (NumProcs != 0) {
|
||
Status = KeWaitForMultipleObjects (NumProcs,
|
||
WaitProcs,
|
||
WaitAny,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
&Timeout,
|
||
NULL);
|
||
|
||
for (i = 0; i < NumProcs; i++) {
|
||
Process = CONTAINING_RECORD(WaitProcs[i],
|
||
EPROCESS,
|
||
Pcb);
|
||
|
||
ObDereferenceObject (Process);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Don't let an unkillable process stop shutdown from finishing.
|
||
// ASSERT on checked builds so the faulty component causing this
|
||
// can be debugged and fixed.
|
||
//
|
||
if (NumProcs > 0 && Status == STATUS_TIMEOUT) {
|
||
MaxPasses += 1;
|
||
if (MaxPasses > 10) {
|
||
ASSERT (FALSE);
|
||
if (!PsContinueWaiting) {
|
||
Retval = FALSE;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
MaxPasses = 0;
|
||
}
|
||
|
||
} while (NumProcs > 0);
|
||
|
||
if (PoCleanShutdownEnabled() && ExpDefaultErrorPortProcess) {
|
||
// Explicitly kill csrss -- we don't want to do this in the loop,
|
||
// because we don't want to wait on it, because it has system
|
||
// threads which will exit later. But we can terminate the user
|
||
// threads, now that everything else has died (we can't terminate
|
||
// them earlier, because DestroyWindowStation()/TerminateConsole()
|
||
// depends on them being around).
|
||
|
||
PsTerminateProcess(ExpDefaultErrorPortProcess,
|
||
STATUS_SYSTEM_SHUTDOWN);
|
||
|
||
// Now, make sure that csrss's usermode threads have gotten a
|
||
// chance to terminate.
|
||
PspWaitForUsermodeExit(ExpDefaultErrorPortProcess);
|
||
}
|
||
|
||
// And we're done.
|
||
|
||
PspShutdownJobLimits();
|
||
MmUnmapViewOfSection(PsInitialSystemProcess, PspSystemDll.DllBase);
|
||
ObDereferenceObject(PspSystemDll.Section);
|
||
ZwClose(PspInitialSystemProcessHandle);
|
||
PspInitialSystemProcessHandle = NULL;
|
||
|
||
// Disconnect the system process's LSA security port
|
||
if (PsInitialSystemProcess->SecurityPort) {
|
||
if (PsInitialSystemProcess->SecurityPort != ((PVOID) 1 ))
|
||
{
|
||
ObDereferenceObject(PsInitialSystemProcess->SecurityPort);
|
||
|
||
PsInitialSystemProcess->SecurityPort = (PVOID) 1 ;
|
||
}
|
||
|
||
}
|
||
|
||
return Retval;
|
||
}
|
||
|
||
BOOLEAN
|
||
PsWaitForAllProcesses (
|
||
VOID)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function waits for all the processes to terminate.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if all processes were terminated, FALSE if not.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS Status;
|
||
LARGE_INTEGER Timeout = {(ULONG)-(100 * 1000), -1};
|
||
ULONG MaxPasses;
|
||
BOOLEAN Wait;
|
||
PEPROCESS Process;
|
||
PEPROCESS WaitProcess=NULL;
|
||
|
||
MaxPasses = 0;
|
||
while (1) {
|
||
Wait = FALSE;
|
||
for (Process = PsGetNextProcess (NULL);
|
||
Process != NULL;
|
||
Process = PsGetNextProcess (Process)) {
|
||
|
||
if (Process != PsInitialSystemProcess &&
|
||
Process != PsIdleProcess &&
|
||
(Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) != 0) {
|
||
if (Process->ObjectTable != NULL) {
|
||
Wait = TRUE;
|
||
WaitProcess = Process;
|
||
ObReferenceObject (WaitProcess);
|
||
PsQuitNextProcess (WaitProcess);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Wait) {
|
||
Status = KeWaitForSingleObject (WaitProcess,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
&Timeout);
|
||
|
||
ObDereferenceObject (WaitProcess);
|
||
|
||
if (Status == STATUS_TIMEOUT) {
|
||
MaxPasses += 1;
|
||
Timeout.QuadPart *= 2;
|
||
if (MaxPasses > 13) {
|
||
KdPrint (("PS: %d process left in the system after termination\n",
|
||
PsProcessType->TotalNumberOfObjects));
|
||
// ASSERT (PsProcessType->TotalNumberOfObjects == 0);
|
||
return FALSE;
|
||
}
|
||
}
|
||
} else {
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|