mirror of
https://github.com/reactos/reactos.git
synced 2024-08-31 07:38:48 +00:00
1379 lines
41 KiB
C
1379 lines
41 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ps/kill.c
|
|
* PURPOSE: Process Manager: Process and Thread Termination
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Filip Navara (xnavara@reactos.org)
|
|
* Thomas Weidenmueller (w3seek@reactos.org
|
|
*/
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
LIST_ENTRY PspReaperListHead = { NULL, NULL };
|
|
WORK_QUEUE_ITEM PspReaperWorkItem;
|
|
LARGE_INTEGER ShortTime = {{-10 * 100 * 1000, -1}};
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
VOID
|
|
NTAPI
|
|
PspCatchCriticalBreak(IN PCHAR Message,
|
|
IN PVOID ProcessOrThread,
|
|
IN PCHAR ImageName)
|
|
{
|
|
CHAR Action[2];
|
|
BOOLEAN Handled = FALSE;
|
|
PAGED_CODE();
|
|
|
|
/* Check if a debugger is enabled */
|
|
if (KdDebuggerEnabled)
|
|
{
|
|
/* Print out the message */
|
|
DbgPrint(Message, ProcessOrThread, ImageName);
|
|
do
|
|
{
|
|
/* If a debugger isn't present, don't prompt */
|
|
if (KdDebuggerNotPresent) break;
|
|
|
|
/* A debuger is active, prompt for action */
|
|
DbgPrompt("Break, or Ignore (bi)?", Action, sizeof(Action));
|
|
switch (Action[0])
|
|
{
|
|
/* Break */
|
|
case 'B': case 'b':
|
|
|
|
/* Do a breakpoint */
|
|
DbgBreakPoint();
|
|
|
|
/* Ignore */
|
|
case 'I': case 'i':
|
|
|
|
/* Handle it */
|
|
Handled = TRUE;
|
|
|
|
/* Unrecognized */
|
|
default:
|
|
break;
|
|
}
|
|
} while (!Handled);
|
|
}
|
|
|
|
/* Did we ultimately handle this? */
|
|
if (!Handled)
|
|
{
|
|
/* We didn't, bugcheck */
|
|
KeBugCheckEx(CRITICAL_OBJECT_TERMINATION,
|
|
((PKPROCESS)ProcessOrThread)->Header.Type,
|
|
(ULONG_PTR)ProcessOrThread,
|
|
(ULONG_PTR)ImageName,
|
|
(ULONG_PTR)Message);
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspTerminateProcess(IN PEPROCESS Process,
|
|
IN NTSTATUS ExitStatus)
|
|
{
|
|
PETHREAD Thread;
|
|
NTSTATUS Status = STATUS_NOTHING_TO_TERMINATE;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG,
|
|
"Process: %p ExitStatus: %d\n", Process, ExitStatus);
|
|
PSREFTRACE(Process);
|
|
|
|
/* Check if this is a Critical Process */
|
|
if (Process->BreakOnTermination)
|
|
{
|
|
/* Break to debugger */
|
|
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
|
|
Process,
|
|
Process->ImageFileName);
|
|
}
|
|
|
|
/* Set the delete flag */
|
|
InterlockedOr((PLONG)&Process->Flags, PSF_PROCESS_DELETE_BIT);
|
|
|
|
/* Get the first thread */
|
|
Thread = PsGetNextProcessThread(Process, NULL);
|
|
while (Thread)
|
|
{
|
|
/* Kill it */
|
|
PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
|
|
Thread = PsGetNextProcessThread(Process, Thread);
|
|
|
|
/* We had at least one thread, so termination is OK */
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Check if there was nothing to terminate or if we have a debug port */
|
|
if ((Status == STATUS_NOTHING_TO_TERMINATE) || (Process->DebugPort))
|
|
{
|
|
/* Clear the handle table anyway */
|
|
ObClearProcessHandleTable(Process);
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PsTerminateProcess(IN PEPROCESS Process,
|
|
IN NTSTATUS ExitStatus)
|
|
{
|
|
/* Call the internal API */
|
|
return PspTerminateProcess(Process, ExitStatus);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspShutdownProcessManager(VOID)
|
|
{
|
|
PEPROCESS Process = NULL;
|
|
|
|
/* Loop every process */
|
|
Process = PsGetNextProcess(Process);
|
|
while (Process)
|
|
{
|
|
/* Make sure this isn't the idle or initial process */
|
|
if ((Process != PsInitialSystemProcess) && (Process != PsIdleProcess))
|
|
{
|
|
/* Kill it */
|
|
PspTerminateProcess(Process, STATUS_SYSTEM_SHUTDOWN);
|
|
}
|
|
|
|
/* Get the next process */
|
|
Process = PsGetNextProcess(Process);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspExitApcRundown(IN PKAPC Apc)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
/* Free the APC */
|
|
ExFreePool(Apc);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspReapRoutine(IN PVOID Context)
|
|
{
|
|
PSINGLE_LIST_ENTRY NextEntry;
|
|
PETHREAD Thread;
|
|
PSTRACE(PS_KILL_DEBUG, "Context: %p\n", Context);
|
|
|
|
/* Start main loop */
|
|
do
|
|
{
|
|
/* Write magic value and return the next entry to process */
|
|
NextEntry = InterlockedExchangePointer((PVOID*)&PspReaperListHead.Flink,
|
|
(PVOID)1);
|
|
ASSERT((NextEntry != NULL) && (NextEntry != (PVOID)1));
|
|
|
|
/* Start inner loop */
|
|
do
|
|
{
|
|
/* Get the first Thread Entry */
|
|
Thread = CONTAINING_RECORD(NextEntry, ETHREAD, ReaperLink);
|
|
|
|
/* Delete this entry's kernel stack */
|
|
MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase,
|
|
Thread->Tcb.LargeStack);
|
|
Thread->Tcb.InitialStack = NULL;
|
|
|
|
/* Move to the next entry */
|
|
NextEntry = NextEntry->Next;
|
|
|
|
/* Dereference this thread */
|
|
ObDereferenceObject(Thread);
|
|
} while ((NextEntry != NULL) && (NextEntry != (PVOID)1));
|
|
|
|
/* Remove magic value, keep looping if it got changed */
|
|
} while (InterlockedCompareExchangePointer((PVOID*)&PspReaperListHead.Flink,
|
|
0,
|
|
(PVOID)1) != (PVOID)1);
|
|
}
|
|
|
|
#if DBG
|
|
VOID
|
|
NTAPI
|
|
PspCheckProcessList(VOID)
|
|
{
|
|
PLIST_ENTRY Entry;
|
|
|
|
KeAcquireGuardedMutex(&PspActiveProcessMutex);
|
|
DbgPrint("# checking PsActiveProcessHead @ %p\n", &PsActiveProcessHead);
|
|
for (Entry = PsActiveProcessHead.Flink;
|
|
Entry != &PsActiveProcessHead;
|
|
Entry = Entry->Flink)
|
|
{
|
|
PEPROCESS Process = CONTAINING_RECORD(Entry, EPROCESS, ActiveProcessLinks);
|
|
POBJECT_HEADER Header;
|
|
PVOID Info, HeaderLocation;
|
|
|
|
/* Get the header and assume this is what we'll free */
|
|
Header = OBJECT_TO_OBJECT_HEADER(Process);
|
|
HeaderLocation = Header;
|
|
|
|
/* To find the header, walk backwards from how we allocated */
|
|
if ((Info = OBJECT_HEADER_TO_CREATOR_INFO(Header)))
|
|
{
|
|
HeaderLocation = Info;
|
|
}
|
|
if ((Info = OBJECT_HEADER_TO_NAME_INFO(Header)))
|
|
{
|
|
HeaderLocation = Info;
|
|
}
|
|
if ((Info = OBJECT_HEADER_TO_HANDLE_INFO(Header)))
|
|
{
|
|
HeaderLocation = Info;
|
|
}
|
|
if ((Info = OBJECT_HEADER_TO_QUOTA_INFO(Header)))
|
|
{
|
|
HeaderLocation = Info;
|
|
}
|
|
|
|
ExpCheckPoolAllocation(HeaderLocation, NonPagedPool, 'corP');
|
|
}
|
|
|
|
KeReleaseGuardedMutex(&PspActiveProcessMutex);
|
|
}
|
|
#endif
|
|
|
|
VOID
|
|
NTAPI
|
|
PspDeleteProcess(IN PVOID ObjectBody)
|
|
{
|
|
PEPROCESS Process = (PEPROCESS)ObjectBody;
|
|
KAPC_STATE ApcState;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "ObjectBody: %p\n", ObjectBody);
|
|
PSREFTRACE(Process);
|
|
|
|
/* Check if it has an Active Process Link */
|
|
if (Process->ActiveProcessLinks.Flink)
|
|
{
|
|
/* Remove it from the Active List */
|
|
KeAcquireGuardedMutex(&PspActiveProcessMutex);
|
|
RemoveEntryList(&Process->ActiveProcessLinks);
|
|
Process->ActiveProcessLinks.Flink = NULL;
|
|
Process->ActiveProcessLinks.Blink = NULL;
|
|
KeReleaseGuardedMutex(&PspActiveProcessMutex);
|
|
}
|
|
|
|
/* Check for Auditing information */
|
|
if (Process->SeAuditProcessCreationInfo.ImageFileName)
|
|
{
|
|
/* Free it */
|
|
ExFreePoolWithTag(Process->SeAuditProcessCreationInfo.ImageFileName,
|
|
TAG_SEPA);
|
|
Process->SeAuditProcessCreationInfo.ImageFileName = NULL;
|
|
}
|
|
|
|
/* Check if we have a job */
|
|
if (Process->Job)
|
|
{
|
|
/* Remove the process from the job */
|
|
PspRemoveProcessFromJob(Process, Process->Job);
|
|
|
|
/* Dereference it */
|
|
ObDereferenceObject(Process->Job);
|
|
Process->Job = NULL;
|
|
}
|
|
|
|
/* Increase the stack count */
|
|
Process->Pcb.StackCount++;
|
|
|
|
/* Check if we have a debug port */
|
|
if (Process->DebugPort)
|
|
{
|
|
/* Deference the Debug Port */
|
|
ObDereferenceObject(Process->DebugPort);
|
|
Process->DebugPort = NULL;
|
|
}
|
|
|
|
/* Check if we have an exception port */
|
|
if (Process->ExceptionPort)
|
|
{
|
|
/* Deference the Exception Port */
|
|
ObDereferenceObject(Process->ExceptionPort);
|
|
Process->ExceptionPort = NULL;
|
|
}
|
|
|
|
/* Check if we have a section object */
|
|
if (Process->SectionObject)
|
|
{
|
|
/* Deference the Section Object */
|
|
ObDereferenceObject(Process->SectionObject);
|
|
Process->SectionObject = NULL;
|
|
}
|
|
|
|
#if defined(_X86_)
|
|
/* Clean Ldt and Vdm objects */
|
|
PspDeleteLdt(Process);
|
|
PspDeleteVdmObjects(Process);
|
|
#endif
|
|
|
|
/* Delete the Object Table */
|
|
if (Process->ObjectTable)
|
|
{
|
|
/* Attach to the process */
|
|
KeStackAttachProcess(&Process->Pcb, &ApcState);
|
|
|
|
/* Kill the Object Info */
|
|
ObKillProcess(Process);
|
|
|
|
/* Detach */
|
|
KeUnstackDetachProcess(&ApcState);
|
|
}
|
|
|
|
/* Check if we have an address space, and clean it */
|
|
if (Process->HasAddressSpace)
|
|
{
|
|
/* Attach to the process */
|
|
KeStackAttachProcess(&Process->Pcb, &ApcState);
|
|
|
|
/* Clean the Address Space */
|
|
PspExitProcess(FALSE, Process);
|
|
|
|
/* Detach */
|
|
KeUnstackDetachProcess(&ApcState);
|
|
|
|
/* Completely delete the Address Space */
|
|
MmDeleteProcessAddressSpace(Process);
|
|
}
|
|
|
|
/* See if we have a PID */
|
|
if (Process->UniqueProcessId)
|
|
{
|
|
/* Delete the PID */
|
|
if (!(ExDestroyHandle(PspCidTable, Process->UniqueProcessId, NULL)))
|
|
{
|
|
/* Something wrong happened, bugcheck */
|
|
KeBugCheck(CID_HANDLE_DELETION);
|
|
}
|
|
}
|
|
|
|
/* Cleanup security information */
|
|
PspDeleteProcessSecurity(Process);
|
|
|
|
/* Check if we have kept information on the Working Set */
|
|
if (Process->WorkingSetWatch)
|
|
{
|
|
/* Free it */
|
|
ExFreePool(Process->WorkingSetWatch);
|
|
|
|
/* And return the quota it was taking up */
|
|
PsReturnProcessNonPagedPoolQuota(Process, 0x2000);
|
|
}
|
|
|
|
/* Dereference the Device Map */
|
|
ObDereferenceDeviceMap(Process);
|
|
|
|
/* Destroy the Quota Block */
|
|
PspDestroyQuotaBlock(Process);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspDeleteThread(IN PVOID ObjectBody)
|
|
{
|
|
PETHREAD Thread = (PETHREAD)ObjectBody;
|
|
PEPROCESS Process = Thread->ThreadsProcess;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "ObjectBody: %p\n", ObjectBody);
|
|
PSREFTRACE(Thread);
|
|
ASSERT(Thread->Tcb.Win32Thread == NULL);
|
|
|
|
/* Check if we have a stack */
|
|
if (Thread->Tcb.InitialStack)
|
|
{
|
|
/* Release it */
|
|
MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase,
|
|
Thread->Tcb.LargeStack);
|
|
}
|
|
|
|
/* Check if we have a CID Handle */
|
|
if (Thread->Cid.UniqueThread)
|
|
{
|
|
/* Delete the CID Handle */
|
|
if (!(ExDestroyHandle(PspCidTable, Thread->Cid.UniqueThread, NULL)))
|
|
{
|
|
/* Something wrong happened, bugcheck */
|
|
KeBugCheck(CID_HANDLE_DELETION);
|
|
}
|
|
}
|
|
|
|
/* Cleanup impersionation information */
|
|
PspDeleteThreadSecurity(Thread);
|
|
|
|
/* Make sure the thread was inserted, before continuing */
|
|
if (!Process) return;
|
|
|
|
/* Check if the thread list is valid */
|
|
if (Thread->ThreadListEntry.Flink)
|
|
{
|
|
/* Lock the thread's process */
|
|
KeEnterCriticalRegion();
|
|
ExAcquirePushLockExclusive(&Process->ProcessLock);
|
|
|
|
/* Remove us from the list */
|
|
RemoveEntryList(&Thread->ThreadListEntry);
|
|
|
|
/* Release the lock */
|
|
ExReleasePushLockExclusive(&Process->ProcessLock);
|
|
KeLeaveCriticalRegion();
|
|
}
|
|
|
|
/* Dereference the Process */
|
|
ObDereferenceObject(Process);
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: Terminates the current thread
|
|
* See "Windows Internals" - Chapter 13, Page 50-53
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PspExitThread(IN NTSTATUS ExitStatus)
|
|
{
|
|
CLIENT_DIED_MSG TerminationMsg;
|
|
NTSTATUS Status;
|
|
PTEB Teb;
|
|
PEPROCESS CurrentProcess;
|
|
PETHREAD Thread, OtherThread, PreviousThread = NULL;
|
|
PVOID DeallocationStack;
|
|
SIZE_T Dummy;
|
|
BOOLEAN Last = FALSE;
|
|
PTERMINATION_PORT TerminationPort, NextPort;
|
|
PLIST_ENTRY FirstEntry, CurrentEntry;
|
|
PKAPC Apc;
|
|
PTOKEN PrimaryToken;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "ExitStatus: %d\n", ExitStatus);
|
|
|
|
/* Get the Current Thread and Process */
|
|
Thread = PsGetCurrentThread();
|
|
CurrentProcess = Thread->ThreadsProcess;
|
|
ASSERT((Thread) == PsGetCurrentThread());
|
|
|
|
/* Can't terminate a thread if it attached another process */
|
|
if (KeIsAttachedProcess())
|
|
{
|
|
/* Bugcheck */
|
|
KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
|
|
(ULONG_PTR)CurrentProcess,
|
|
(ULONG_PTR)Thread->Tcb.ApcState.Process,
|
|
(ULONG_PTR)Thread->Tcb.ApcStateIndex,
|
|
(ULONG_PTR)Thread);
|
|
}
|
|
|
|
/* Lower to Passive Level */
|
|
KeLowerIrql(PASSIVE_LEVEL);
|
|
|
|
/* Can't be a worker thread */
|
|
if (Thread->ActiveExWorker)
|
|
{
|
|
/* Bugcheck */
|
|
KeBugCheckEx(ACTIVE_EX_WORKER_THREAD_TERMINATION,
|
|
(ULONG_PTR)Thread,
|
|
0,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
/* Can't have pending APCs */
|
|
if (Thread->Tcb.CombinedApcDisable != 0)
|
|
{
|
|
/* Bugcheck */
|
|
KeBugCheckEx(KERNEL_APC_PENDING_DURING_EXIT,
|
|
0,
|
|
Thread->Tcb.CombinedApcDisable,
|
|
0,
|
|
1);
|
|
}
|
|
|
|
/* Lock the thread */
|
|
ExWaitForRundownProtectionRelease(&Thread->RundownProtect);
|
|
|
|
/* Cleanup the power state */
|
|
PopCleanupPowerState((PPOWER_STATE)&Thread->Tcb.PowerState);
|
|
|
|
/* Call the WMI Callback for Threads */
|
|
//WmiTraceThread(Thread, NULL, FALSE);
|
|
|
|
/* Run Thread Notify Routines before we desintegrate the thread */
|
|
PspRunCreateThreadNotifyRoutines(Thread, FALSE);
|
|
|
|
/* Lock the Process before we modify its thread entries */
|
|
KeEnterCriticalRegion();
|
|
ExAcquirePushLockExclusive(&CurrentProcess->ProcessLock);
|
|
|
|
/* Decrease the active thread count, and check if it's 0 */
|
|
if (!(--CurrentProcess->ActiveThreads))
|
|
{
|
|
/* Set the delete flag */
|
|
InterlockedOr((PLONG)&CurrentProcess->Flags, PSF_PROCESS_DELETE_BIT);
|
|
|
|
/* Remember we are last */
|
|
Last = TRUE;
|
|
|
|
/* Check if this termination is due to the thread dying */
|
|
if (ExitStatus == STATUS_THREAD_IS_TERMINATING)
|
|
{
|
|
/* Check if the last thread was pending */
|
|
if (CurrentProcess->ExitStatus == STATUS_PENDING)
|
|
{
|
|
/* Use the last exit status */
|
|
CurrentProcess->ExitStatus = CurrentProcess->
|
|
LastThreadExitStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Just a normal exit, write the code */
|
|
CurrentProcess->ExitStatus = ExitStatus;
|
|
}
|
|
|
|
/* Loop all the current threads */
|
|
FirstEntry = &CurrentProcess->ThreadListHead;
|
|
CurrentEntry = FirstEntry->Flink;
|
|
while (FirstEntry != CurrentEntry)
|
|
{
|
|
/* Get the thread on the list */
|
|
OtherThread = CONTAINING_RECORD(CurrentEntry,
|
|
ETHREAD,
|
|
ThreadListEntry);
|
|
|
|
/* Check if it's a thread that's still alive */
|
|
if ((OtherThread != Thread) &&
|
|
!(KeReadStateThread(&OtherThread->Tcb)) &&
|
|
(ObReferenceObjectSafe(OtherThread)))
|
|
{
|
|
/* It's a live thread and we referenced it, unlock process */
|
|
ExReleasePushLockExclusive(&CurrentProcess->ProcessLock);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Wait on the thread */
|
|
KeWaitForSingleObject(OtherThread,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
/* Check if we had a previous thread to dereference */
|
|
if (PreviousThread) ObDereferenceObject(PreviousThread);
|
|
|
|
/* Remember the thread and re-lock the process */
|
|
PreviousThread = OtherThread;
|
|
KeEnterCriticalRegion();
|
|
ExAcquirePushLockExclusive(&CurrentProcess->ProcessLock);
|
|
}
|
|
|
|
/* Go to the next thread */
|
|
CurrentEntry = CurrentEntry->Flink;
|
|
}
|
|
}
|
|
else if (ExitStatus != STATUS_THREAD_IS_TERMINATING)
|
|
{
|
|
/* Write down the exit status of the last thread to get killed */
|
|
CurrentProcess->LastThreadExitStatus = ExitStatus;
|
|
}
|
|
|
|
/* Unlock the Process */
|
|
ExReleasePushLockExclusive(&CurrentProcess->ProcessLock);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Check if we had a previous thread to dereference */
|
|
if (PreviousThread) ObDereferenceObject(PreviousThread);
|
|
|
|
/* Check if the process has a debug port and if this is a user thread */
|
|
if ((CurrentProcess->DebugPort) && !(Thread->SystemThread))
|
|
{
|
|
/* Notify the Debug API. */
|
|
Last ? DbgkExitProcess(CurrentProcess->ExitStatus) :
|
|
DbgkExitThread(ExitStatus);
|
|
}
|
|
|
|
/* Check if this is a Critical Thread */
|
|
if ((KdDebuggerEnabled) && (Thread->BreakOnTermination))
|
|
{
|
|
/* Break to debugger */
|
|
PspCatchCriticalBreak("Critical thread 0x%p (in %s) exited\n",
|
|
Thread,
|
|
CurrentProcess->ImageFileName);
|
|
}
|
|
|
|
/* Check if it's the last thread and this is a Critical Process */
|
|
if ((Last) && (CurrentProcess->BreakOnTermination))
|
|
{
|
|
/* Check if a debugger is here to handle this */
|
|
if (KdDebuggerEnabled)
|
|
{
|
|
/* Break to debugger */
|
|
PspCatchCriticalBreak("Critical process 0x%p (in %s) exited\n",
|
|
CurrentProcess,
|
|
CurrentProcess->ImageFileName);
|
|
}
|
|
else
|
|
{
|
|
/* Bugcheck, we can't allow this */
|
|
KeBugCheckEx(CRITICAL_PROCESS_DIED,
|
|
(ULONG_PTR)CurrentProcess,
|
|
0,
|
|
0,
|
|
0);
|
|
}
|
|
}
|
|
|
|
/* Sanity check */
|
|
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
|
|
|
|
/* Process the Termination Ports */
|
|
TerminationPort = Thread->TerminationPort;
|
|
if (TerminationPort)
|
|
{
|
|
/* Setup the message header */
|
|
TerminationMsg.h.u2.ZeroInit = 0;
|
|
TerminationMsg.h.u2.s2.Type = LPC_CLIENT_DIED;
|
|
TerminationMsg.h.u1.s1.TotalLength = sizeof(TerminationMsg);
|
|
TerminationMsg.h.u1.s1.DataLength = sizeof(TerminationMsg) -
|
|
sizeof(PORT_MESSAGE);
|
|
|
|
/* Loop each port */
|
|
do
|
|
{
|
|
/* Save the Create Time */
|
|
TerminationMsg.CreateTime = Thread->CreateTime;
|
|
|
|
/* Loop trying to send message */
|
|
while (TRUE)
|
|
{
|
|
/* Send the LPC Message */
|
|
Status = LpcRequestPort(TerminationPort->Port,
|
|
&TerminationMsg.h);
|
|
if ((Status == STATUS_NO_MEMORY) ||
|
|
(Status == STATUS_INSUFFICIENT_RESOURCES))
|
|
{
|
|
/* Wait a bit and try again */
|
|
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Dereference this LPC Port */
|
|
ObDereferenceObject(TerminationPort->Port);
|
|
|
|
/* Move to the next one */
|
|
NextPort = TerminationPort->Next;
|
|
|
|
/* Free the Termination Port Object */
|
|
ExFreePoolWithTag(TerminationPort, '=TsP');
|
|
|
|
/* Keep looping as long as there is a port */
|
|
TerminationPort = NextPort;
|
|
} while (TerminationPort);
|
|
}
|
|
else if (((ExitStatus == STATUS_THREAD_IS_TERMINATING) &&
|
|
(Thread->DeadThread)) ||
|
|
!(Thread->DeadThread))
|
|
{
|
|
/*
|
|
* This case is special and deserves some extra comments. What
|
|
* basically happens here is that this thread doesn't have a termination
|
|
* port, which means that it died before being fully created. Since we
|
|
* still have to notify an LPC Server, we'll use the exception port,
|
|
* which we know exists. However, we need to know how far the thread
|
|
* actually got created. We have three possibilities:
|
|
*
|
|
* - NtCreateThread returned an error really early: DeadThread is set.
|
|
* - NtCreateThread managed to create the thread: DeadThread is off.
|
|
* - NtCreateThread was creating the thread (with DeadThread set,
|
|
* but the thread got killed prematurely: STATUS_THREAD_IS_TERMINATING
|
|
* is our exit code.)
|
|
*
|
|
* For the 2 & 3rd scenarios, the thread has been created far enough to
|
|
* warrant notification to the LPC Server.
|
|
*/
|
|
|
|
/* Setup the message header */
|
|
TerminationMsg.h.u2.ZeroInit = 0;
|
|
TerminationMsg.h.u2.s2.Type = LPC_CLIENT_DIED;
|
|
TerminationMsg.h.u1.s1.TotalLength = sizeof(TerminationMsg);
|
|
TerminationMsg.h.u1.s1.DataLength = sizeof(TerminationMsg) -
|
|
sizeof(PORT_MESSAGE);
|
|
|
|
/* Make sure the process has an exception port */
|
|
if (CurrentProcess->ExceptionPort)
|
|
{
|
|
/* Save the Create Time */
|
|
TerminationMsg.CreateTime = Thread->CreateTime;
|
|
|
|
/* Loop trying to send message */
|
|
while (TRUE)
|
|
{
|
|
/* Send the LPC Message */
|
|
Status = LpcRequestPort(CurrentProcess->ExceptionPort,
|
|
&TerminationMsg.h);
|
|
if ((Status == STATUS_NO_MEMORY) ||
|
|
(Status == STATUS_INSUFFICIENT_RESOURCES))
|
|
{
|
|
/* Wait a bit and try again */
|
|
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Rundown Win32 Thread if there is one */
|
|
if (Thread->Tcb.Win32Thread) PspW32ThreadCallout(Thread,
|
|
PsW32ThreadCalloutExit);
|
|
|
|
/* If we are the last thread and have a W32 Process */
|
|
if ((Last) && (CurrentProcess->Win32Process))
|
|
{
|
|
/* Run it down too */
|
|
PspW32ProcessCallout(CurrentProcess, FALSE);
|
|
}
|
|
|
|
/* Make sure Stack Swap is enabled */
|
|
if (!Thread->Tcb.EnableStackSwap)
|
|
{
|
|
/* Stack swap really shouldn't be disabled during exit! */
|
|
KeBugCheckEx(KERNEL_STACK_LOCKED_AT_EXIT, 0, 0, 0, 0);
|
|
}
|
|
|
|
/* Cancel I/O for the thread. */
|
|
IoCancelThreadIo(Thread);
|
|
|
|
/* Rundown Timers */
|
|
ExTimerRundown();
|
|
|
|
/* FIXME: Rundown Registry Notifications (NtChangeNotify)
|
|
CmNotifyRunDown(Thread); */
|
|
|
|
/* Rundown Mutexes */
|
|
KeRundownThread();
|
|
|
|
/* Check if we have a TEB */
|
|
Teb = Thread->Tcb.Teb;
|
|
if (Teb)
|
|
{
|
|
/* Check if the thread is still alive */
|
|
if (!Thread->DeadThread)
|
|
{
|
|
/* Check if we need to free its stack */
|
|
if (Teb->FreeStackOnTermination)
|
|
{
|
|
/* Set the TEB's Deallocation Stack as the Base Address */
|
|
Dummy = 0;
|
|
DeallocationStack = Teb->DeallocationStack;
|
|
|
|
/* Free the Thread's Stack */
|
|
ZwFreeVirtualMemory(NtCurrentProcess(),
|
|
&DeallocationStack,
|
|
&Dummy,
|
|
MEM_RELEASE);
|
|
}
|
|
|
|
/* Free the debug handle */
|
|
if (Teb->DbgSsReserved[1]) ObCloseHandle(Teb->DbgSsReserved[1],
|
|
UserMode);
|
|
}
|
|
|
|
/* Decommit the TEB */
|
|
MmDeleteTeb(CurrentProcess, Teb);
|
|
Thread->Tcb.Teb = NULL;
|
|
}
|
|
|
|
/* Free LPC Data */
|
|
LpcExitThread(Thread);
|
|
|
|
/* Save the exit status and exit time */
|
|
Thread->ExitStatus = ExitStatus;
|
|
KeQuerySystemTime(&Thread->ExitTime);
|
|
|
|
/* Sanity check */
|
|
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
|
|
|
|
/* Check if this is the final thread or not */
|
|
if (Last)
|
|
{
|
|
/* Set the process exit time */
|
|
CurrentProcess->ExitTime = Thread->ExitTime;
|
|
|
|
/* Exit the process */
|
|
PspExitProcess(TRUE, CurrentProcess);
|
|
|
|
/* Get the process token and check if we need to audit */
|
|
PrimaryToken = PsReferencePrimaryToken(CurrentProcess);
|
|
if (SeDetailedAuditingWithToken(PrimaryToken))
|
|
{
|
|
/* Audit the exit */
|
|
SeAuditProcessExit(CurrentProcess);
|
|
}
|
|
|
|
/* Dereference the process token */
|
|
ObFastDereferenceObject(&CurrentProcess->Token, PrimaryToken);
|
|
|
|
/* Check if this is a VDM Process and rundown the VDM DPCs if so */
|
|
if (CurrentProcess->VdmObjects) { /* VdmRundownDpcs(CurrentProcess); */ }
|
|
|
|
/* Kill the process in the Object Manager */
|
|
ObKillProcess(CurrentProcess);
|
|
|
|
/* Check if we have a section object */
|
|
if (CurrentProcess->SectionObject)
|
|
{
|
|
/* Dereference and clear the Section Object */
|
|
ObDereferenceObject(CurrentProcess->SectionObject);
|
|
CurrentProcess->SectionObject = NULL;
|
|
}
|
|
|
|
/* Check if the process is part of a job */
|
|
if (CurrentProcess->Job)
|
|
{
|
|
/* Remove the process from the job */
|
|
PspExitProcessFromJob(CurrentProcess->Job, CurrentProcess);
|
|
}
|
|
}
|
|
|
|
/* Disable APCs */
|
|
KeEnterCriticalRegion();
|
|
|
|
/* Disable APC queueing, force a resumption */
|
|
Thread->Tcb.ApcQueueable = FALSE;
|
|
KeForceResumeThread(&Thread->Tcb);
|
|
|
|
/* Re-enable APCs */
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Flush the User APCs */
|
|
FirstEntry = KeFlushQueueApc(&Thread->Tcb, UserMode);
|
|
if (FirstEntry)
|
|
{
|
|
/* Start with the first entry */
|
|
CurrentEntry = FirstEntry;
|
|
do
|
|
{
|
|
/* Get the APC */
|
|
Apc = CONTAINING_RECORD(CurrentEntry, KAPC, ApcListEntry);
|
|
|
|
/* Move to the next one */
|
|
CurrentEntry = CurrentEntry->Flink;
|
|
|
|
/* Rundown the APC or de-allocate it */
|
|
if (Apc->RundownRoutine)
|
|
{
|
|
/* Call its own routine */
|
|
Apc->RundownRoutine(Apc);
|
|
}
|
|
else
|
|
{
|
|
/* Do it ourselves */
|
|
ExFreePool(Apc);
|
|
}
|
|
}
|
|
while (CurrentEntry != FirstEntry);
|
|
}
|
|
|
|
/* Clean address space if this was the last thread */
|
|
if (Last) MmCleanProcessAddressSpace(CurrentProcess);
|
|
|
|
/* Call the Lego routine */
|
|
if (Thread->Tcb.LegoData) PspRunLegoRoutine(&Thread->Tcb);
|
|
|
|
/* Flush the APC queue, which should be empty */
|
|
FirstEntry = KeFlushQueueApc(&Thread->Tcb, KernelMode);
|
|
if ((FirstEntry) || (Thread->Tcb.CombinedApcDisable != 0))
|
|
{
|
|
/* Bugcheck time */
|
|
KeBugCheckEx(KERNEL_APC_PENDING_DURING_EXIT,
|
|
(ULONG_PTR)FirstEntry,
|
|
Thread->Tcb.CombinedApcDisable,
|
|
KeGetCurrentIrql(),
|
|
0);
|
|
}
|
|
|
|
/* Signal the process if this was the last thread */
|
|
if (Last) KeSetProcess(&CurrentProcess->Pcb, 0, FALSE);
|
|
|
|
/* Terminate the Thread from the Scheduler */
|
|
KeTerminateThread(0);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PsExitSpecialApc(IN PKAPC Apc,
|
|
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
|
|
IN OUT PVOID* NormalContext,
|
|
IN OUT PVOID* SystemArgument1,
|
|
IN OUT PVOID* SystemArgument2)
|
|
{
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG,
|
|
"Apc: %p SystemArgument2: %p \n", Apc, SystemArgument2);
|
|
|
|
/* Don't do anything unless we are in User-Mode */
|
|
if (Apc->SystemArgument2)
|
|
{
|
|
/* Free the APC */
|
|
Status = PtrToUlong(Apc->NormalContext);
|
|
PspExitApcRundown(Apc);
|
|
|
|
/* Terminate the Thread */
|
|
PspExitThread(Status);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspExitNormalApc(IN PVOID NormalContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2)
|
|
{
|
|
PKAPC Apc = (PKAPC)SystemArgument1;
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "SystemArgument2: %p \n", SystemArgument2);
|
|
|
|
/* This should never happen */
|
|
ASSERT(!(((ULONG_PTR)SystemArgument2) & 1));
|
|
|
|
/* If we're here, this is not a System Thread, so kill it from User-Mode */
|
|
KeInitializeApc(Apc,
|
|
&Thread->Tcb,
|
|
OriginalApcEnvironment,
|
|
PsExitSpecialApc,
|
|
PspExitApcRundown,
|
|
PspExitNormalApc,
|
|
UserMode,
|
|
NormalContext);
|
|
|
|
/* Now insert the APC with the User-Mode Flag */
|
|
if (!(KeInsertQueueApc(Apc,
|
|
Apc,
|
|
(PVOID)((ULONG_PTR)SystemArgument2 | 1),
|
|
2)))
|
|
{
|
|
/* Failed to insert, free the APC */
|
|
PspExitApcRundown(Apc);
|
|
}
|
|
|
|
/* Set the APC Pending flag */
|
|
Thread->Tcb.ApcState.UserApcPending = TRUE;
|
|
}
|
|
|
|
/*
|
|
* See "Windows Internals" - Chapter 13, Page 49
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PspTerminateThreadByPointer(IN PETHREAD Thread,
|
|
IN NTSTATUS ExitStatus,
|
|
IN BOOLEAN bSelf)
|
|
{
|
|
PKAPC Apc;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG Flags;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "Thread: %p ExitStatus: %d\n", Thread, ExitStatus);
|
|
PSREFTRACE(Thread);
|
|
|
|
/* Check if this is a Critical Thread, and Bugcheck */
|
|
if (Thread->BreakOnTermination)
|
|
{
|
|
/* Break to debugger */
|
|
PspCatchCriticalBreak("Terminating critical thread 0x%p (%s)\n",
|
|
Thread,
|
|
Thread->ThreadsProcess->ImageFileName);
|
|
}
|
|
|
|
/* Check if we are already inside the thread */
|
|
if ((bSelf) || (PsGetCurrentThread() == Thread))
|
|
{
|
|
/* This should only happen at passive */
|
|
ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);
|
|
|
|
/* Mark it as terminated */
|
|
PspSetCrossThreadFlag(Thread, CT_TERMINATED_BIT);
|
|
|
|
/* Directly terminate the thread */
|
|
PspExitThread(ExitStatus);
|
|
}
|
|
|
|
/* This shouldn't be a system thread */
|
|
if (Thread->SystemThread) return STATUS_ACCESS_DENIED;
|
|
|
|
/* Allocate the APC */
|
|
Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_TERMINATE_APC);
|
|
if (!Apc) return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
/* Set the Terminated Flag */
|
|
Flags = Thread->CrossThreadFlags | CT_TERMINATED_BIT;
|
|
|
|
/* Set it, and check if it was already set while we were running */
|
|
if (!(InterlockedExchange((PLONG)&Thread->CrossThreadFlags, Flags) &
|
|
CT_TERMINATED_BIT))
|
|
{
|
|
/* Initialize a Kernel Mode APC to Kill the Thread */
|
|
KeInitializeApc(Apc,
|
|
&Thread->Tcb,
|
|
OriginalApcEnvironment,
|
|
PsExitSpecialApc,
|
|
PspExitApcRundown,
|
|
PspExitNormalApc,
|
|
KernelMode,
|
|
UlongToPtr(ExitStatus));
|
|
|
|
/* Insert it into the APC Queue */
|
|
if (!KeInsertQueueApc(Apc, Apc, NULL, 2))
|
|
{
|
|
/* The APC was already in the queue, fail */
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
else
|
|
{
|
|
/* Forcefully resume the thread and return */
|
|
KeForceResumeThread(&Thread->Tcb);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
/* We failed, free the APC */
|
|
ExFreePoolWithTag(Apc, TAG_TERMINATE_APC);
|
|
|
|
/* Return Status */
|
|
return Status;
|
|
}
|
|
|
|
BOOLEAN
|
|
NTAPI
|
|
PspIsProcessExiting(IN PEPROCESS Process)
|
|
{
|
|
return Process->Flags & PSF_PROCESS_EXITING_BIT;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspExitProcess(IN BOOLEAN LastThread,
|
|
IN PEPROCESS Process)
|
|
{
|
|
ULONG Actual;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG,
|
|
"LastThread: %u Process: %p\n", LastThread, Process);
|
|
PSREFTRACE(Process);
|
|
|
|
/* Set Process Exit flag */
|
|
InterlockedOr((PLONG)&Process->Flags, PSF_PROCESS_EXITING_BIT);
|
|
|
|
/* Check if we are the last thread */
|
|
if (LastThread)
|
|
{
|
|
/* Notify the WMI Process Callback */
|
|
//WmiTraceProcess(Process, FALSE);
|
|
|
|
/* Run the Notification Routines */
|
|
PspRunCreateProcessNotifyRoutines(Process, FALSE);
|
|
}
|
|
|
|
/* Cleanup the power state */
|
|
PopCleanupPowerState((PPOWER_STATE)&Process->Pcb.PowerState);
|
|
|
|
/* Clear the security port */
|
|
if (!Process->SecurityPort)
|
|
{
|
|
/* So we don't double-dereference */
|
|
Process->SecurityPort = (PVOID)1;
|
|
}
|
|
else if (Process->SecurityPort != (PVOID)1)
|
|
{
|
|
/* Dereference it */
|
|
ObDereferenceObject(Process->SecurityPort);
|
|
Process->SecurityPort = (PVOID)1;
|
|
}
|
|
|
|
/* Check if we are the last thread */
|
|
if (LastThread)
|
|
{
|
|
/* Check if we have to set the Timer Resolution */
|
|
if (Process->SetTimerResolution)
|
|
{
|
|
/* Set it to default */
|
|
ZwSetTimerResolution(KeMaximumIncrement, 0, &Actual);
|
|
}
|
|
|
|
/* Check if we are part of a Job that has a completion port */
|
|
if ((Process->Job) && (Process->Job->CompletionPort))
|
|
{
|
|
/* FIXME: Check job status code and do I/O completion if needed */
|
|
}
|
|
|
|
/* FIXME: Notify the Prefetcher */
|
|
}
|
|
else
|
|
{
|
|
/* Clear process' address space here */
|
|
MmCleanProcessAddressSpace(Process);
|
|
}
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsTerminateSystemThread(IN NTSTATUS ExitStatus)
|
|
{
|
|
PETHREAD Thread = PsGetCurrentThread();
|
|
|
|
/* Make sure this is a system thread */
|
|
if (!Thread->SystemThread) return STATUS_INVALID_PARAMETER;
|
|
|
|
/* Terminate it for real */
|
|
return PspTerminateThreadByPointer(Thread, ExitStatus, TRUE);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtTerminateProcess(IN HANDLE ProcessHandle OPTIONAL,
|
|
IN NTSTATUS ExitStatus)
|
|
{
|
|
NTSTATUS Status;
|
|
PEPROCESS Process, CurrentProcess = PsGetCurrentProcess();
|
|
PETHREAD Thread, CurrentThread = PsGetCurrentThread();
|
|
BOOLEAN KillByHandle;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG,
|
|
"ProcessHandle: %p ExitStatus: %d\n", ProcessHandle, ExitStatus);
|
|
|
|
/* Were we passed a process handle? */
|
|
if (ProcessHandle)
|
|
{
|
|
/* Yes we were, use it */
|
|
KillByHandle = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* We weren't... we assume this is suicide */
|
|
KillByHandle = FALSE;
|
|
ProcessHandle = NtCurrentProcess();
|
|
}
|
|
|
|
/* Get the Process Object */
|
|
Status = ObReferenceObjectByHandle(ProcessHandle,
|
|
PROCESS_TERMINATE,
|
|
PsProcessType,
|
|
KeGetPreviousMode(),
|
|
(PVOID*)&Process,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return(Status);
|
|
|
|
/* Check if this is a Critical Process, and Bugcheck */
|
|
if (Process->BreakOnTermination)
|
|
{
|
|
/* Break to debugger */
|
|
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
|
|
Process,
|
|
Process->ImageFileName);
|
|
}
|
|
|
|
/* Lock the Process */
|
|
if (!ExAcquireRundownProtection(&Process->RundownProtect))
|
|
{
|
|
/* Failed to lock, fail */
|
|
ObDereferenceObject(Process);
|
|
return STATUS_PROCESS_IS_TERMINATING;
|
|
}
|
|
|
|
/* Set the delete flag, unless the process is comitting suicide */
|
|
if (KillByHandle) PspSetProcessFlag(Process, PSF_PROCESS_DELETE_BIT);
|
|
|
|
/* Get the first thread */
|
|
Status = STATUS_NOTHING_TO_TERMINATE;
|
|
Thread = PsGetNextProcessThread(Process, NULL);
|
|
if (Thread)
|
|
{
|
|
/* We know we have at least a thread */
|
|
Status = STATUS_SUCCESS;
|
|
|
|
/* Loop and kill the others */
|
|
do
|
|
{
|
|
/* Ensure it's not ours*/
|
|
if (Thread != CurrentThread)
|
|
{
|
|
/* Kill it */
|
|
PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
|
|
}
|
|
|
|
/* Move to the next thread */
|
|
Thread = PsGetNextProcessThread(Process, Thread);
|
|
} while (Thread);
|
|
}
|
|
|
|
/* Unlock the process */
|
|
ExReleaseRundownProtection(&Process->RundownProtect);
|
|
|
|
/* Check if we are killing ourselves */
|
|
if (Process == CurrentProcess)
|
|
{
|
|
/* Also make sure the caller gave us our handle */
|
|
if (KillByHandle)
|
|
{
|
|
/* Dereference the process */
|
|
ObDereferenceObject(Process);
|
|
|
|
/* Terminate ourselves */
|
|
PspTerminateThreadByPointer(CurrentThread, ExitStatus, TRUE);
|
|
}
|
|
}
|
|
else if (ExitStatus == DBG_TERMINATE_PROCESS)
|
|
{
|
|
/* Disable debugging on this process */
|
|
DbgkClearProcessDebugObject(Process, NULL);
|
|
}
|
|
|
|
/* Check if there was nothing to terminate, or if we have a Debug Port */
|
|
if ((Status == STATUS_NOTHING_TO_TERMINATE) ||
|
|
((Process->DebugPort) && (KillByHandle)))
|
|
{
|
|
/* Clear the handle table */
|
|
ObClearProcessHandleTable(Process);
|
|
|
|
/* Return status now */
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Decrease the reference count we added */
|
|
ObDereferenceObject(Process);
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtTerminateThread(IN HANDLE ThreadHandle,
|
|
IN NTSTATUS ExitStatus)
|
|
{
|
|
PETHREAD Thread;
|
|
PETHREAD CurrentThread = PsGetCurrentThread();
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG,
|
|
"ThreadHandle: %p ExitStatus: %d\n", ThreadHandle, ExitStatus);
|
|
|
|
/* Handle the special NULL case */
|
|
if (!ThreadHandle)
|
|
{
|
|
/* Check if we're the only thread left */
|
|
if (PsGetCurrentProcess()->ActiveThreads == 1)
|
|
{
|
|
/* This is invalid */
|
|
return STATUS_CANT_TERMINATE_SELF;
|
|
}
|
|
|
|
/* Terminate us directly */
|
|
goto TerminateSelf;
|
|
}
|
|
else if (ThreadHandle == NtCurrentThread())
|
|
{
|
|
TerminateSelf:
|
|
/* Terminate this thread */
|
|
return PspTerminateThreadByPointer(CurrentThread,
|
|
ExitStatus,
|
|
TRUE);
|
|
}
|
|
|
|
/* We are terminating another thread, get the Thread Object */
|
|
Status = ObReferenceObjectByHandle(ThreadHandle,
|
|
THREAD_TERMINATE,
|
|
PsThreadType,
|
|
KeGetPreviousMode(),
|
|
(PVOID*)&Thread,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* Check to see if we're running in the same thread */
|
|
if (Thread != CurrentThread)
|
|
{
|
|
/* Terminate it */
|
|
Status = PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
|
|
|
|
/* Dereference the Thread and return */
|
|
ObDereferenceObject(Thread);
|
|
}
|
|
else
|
|
{
|
|
/* Dereference the thread and terminate ourselves */
|
|
ObDereferenceObject(Thread);
|
|
goto TerminateSelf;
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtRegisterThreadTerminatePort(IN HANDLE PortHandle)
|
|
{
|
|
NTSTATUS Status;
|
|
PTERMINATION_PORT TerminationPort;
|
|
PVOID TerminationLpcPort;
|
|
PETHREAD Thread;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_KILL_DEBUG, "PortHandle: %p\n", PortHandle);
|
|
|
|
/* Get the Port */
|
|
Status = ObReferenceObjectByHandle(PortHandle,
|
|
PORT_ALL_ACCESS,
|
|
LpcPortObjectType,
|
|
KeGetPreviousMode(),
|
|
&TerminationLpcPort,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status)) return(Status);
|
|
|
|
/* Allocate the Port and make sure it suceeded */
|
|
TerminationPort = ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(TERMINATION_PORT),
|
|
'=TsP');
|
|
if(TerminationPort)
|
|
{
|
|
/* Associate the Port */
|
|
Thread = PsGetCurrentThread();
|
|
TerminationPort->Port = TerminationLpcPort;
|
|
TerminationPort->Next = Thread->TerminationPort;
|
|
Thread->TerminationPort = TerminationPort;
|
|
|
|
/* Return success */
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Dereference and Fail */
|
|
ObDereferenceObject(TerminationLpcPort);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|