Thread/Process Termination/Repeaing Rewrite + Fixes
[reactos.git] / reactos / ntoskrnl / ps / kill.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ps/kill.c
5 * PURPOSE: Thread Termination and Reaping
6 *
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
8 * David Welch (welch@cwcom.net)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <internal/debug.h>
16
17 /* GLOBALS *******************************************************************/
18
19 #define TAG_TERMINATE_APC TAG('T', 'A', 'P', 'C')
20
21 LIST_ENTRY PspReaperListHead;
22 WORK_QUEUE_ITEM PspReaperWorkItem;
23 BOOLEAN PspReaping = FALSE;
24 extern LIST_ENTRY PsActiveProcessHead;
25 extern FAST_MUTEX PspActiveProcessMutex;
26
27 /* FUNCTIONS *****************************************************************/
28
29 STDCALL
30 VOID
31 PspReapRoutine(PVOID Context)
32 {
33 KIRQL OldIrql;
34 PETHREAD Thread;
35 PLIST_ENTRY ListEntry;
36
37 /* Acquire lock */
38 DPRINT("Evil reaper running!!\n");
39 OldIrql = KeAcquireDispatcherDatabaseLock();
40
41 /* Loop the reap list */
42 while((ListEntry = RemoveHeadList(&PspReaperListHead)) != &PspReaperListHead) {
43
44 /* Get the Current Thread to Terminate */
45 Thread = CONTAINING_RECORD(ListEntry, ETHREAD, TerminationPortList);
46
47 /* Unlock the Dispatcher */
48 KeReleaseDispatcherDatabaseLock(OldIrql);
49
50 /* Remove the Reference */
51 ObDereferenceObject(Thread);
52
53 /* Reacquire the Lock */
54 OldIrql = KeAcquireDispatcherDatabaseLock();
55 }
56
57 PspReaping = FALSE;
58 KeReleaseDispatcherDatabaseLock(OldIrql);
59 }
60
61 VOID
62 STDCALL
63 PspTerminateProcessThreads(PEPROCESS Process,
64 NTSTATUS ExitStatus)
65 {
66 PLIST_ENTRY CurrentEntry;
67 PETHREAD Thread, CurrentThread = PsGetCurrentThread();
68
69 CurrentEntry = Process->ThreadListHead.Flink;
70 while (CurrentEntry != &Process->ThreadListHead) {
71
72 /* Get the Current Thread */
73 Thread = CONTAINING_RECORD(CurrentEntry, ETHREAD, ThreadListEntry);
74
75 /* Make sure it's not the one we're in */
76 if (Thread != CurrentThread) {
77
78 /* Make sure it didn't already terminate */
79 if (!Thread->HasTerminated) {
80
81 Thread->HasTerminated = TRUE;
82 /* Terminate it by APC */
83 PspTerminateThreadByPointer(Thread, ExitStatus);
84
85 /* Unsuspend it */
86 KeForceResumeThread(&Thread->Tcb);
87 }
88 }
89
90 /* Move to the Next Thread */
91 CurrentEntry = CurrentEntry->Flink;
92 }
93 }
94
95 VOID
96 STDCALL
97 PspDeleteProcess(PVOID ObjectBody)
98 {
99 PEPROCESS Process = (PEPROCESS)ObjectBody;
100
101 DPRINT("PiDeleteProcess(ObjectBody %x)\n",Process);
102
103 /* Delete the CID Handle */
104 if(Process->UniqueProcessId != NULL) {
105
106 PsDeleteCidHandle(Process->UniqueProcessId, PsProcessType);
107 }
108
109 /* KDB hook */
110 KDB_DELETEPROCESS_HOOK(Process);
111
112 /* Dereference the Token and release Memory Information */
113 ObDereferenceObject(Process->Token);
114 MmReleaseMmInfo(Process);
115
116 /* Delete the W32PROCESS structure if there's one associated */
117 if(Process->Win32Process != NULL) ExFreePool(Process->Win32Process);
118 }
119
120 VOID
121 STDCALL
122 PspDeleteThread(PVOID ObjectBody)
123 {
124 PETHREAD Thread = (PETHREAD)ObjectBody;
125 PEPROCESS Process = Thread->ThreadsProcess;
126
127 DPRINT("PiDeleteThread(ObjectBody %x)\n",ObjectBody);
128
129 /* Deassociate the Process */
130 Thread->ThreadsProcess = NULL;
131
132 /* Delete the CID Handle */
133 if(Thread->Cid.UniqueThread != NULL) {
134
135 PsDeleteCidHandle(Thread->Cid.UniqueThread, PsThreadType);
136 }
137
138 /* Free the W32THREAD structure if present */
139 if(Thread->Tcb.Win32Thread != NULL) ExFreePool (Thread->Tcb.Win32Thread);
140
141 /* Release the Thread */
142 KeReleaseThread(ETHREAD_TO_KTHREAD(Thread));
143
144 /* Dereference the Process */
145 ObDereferenceObject(Process);
146 }
147
148 /*
149 * FUNCTION: Terminates the current thread
150 * See "Windows Internals" - Chapter 13, Page 50-53
151 */
152 VOID
153 STDCALL
154 PspExitThread(NTSTATUS ExitStatus)
155 {
156 PETHREAD CurrentThread;
157 BOOLEAN Last;
158 PEPROCESS CurrentProcess;
159 SIZE_T Length = PAGE_SIZE;
160 PVOID TebBlock;
161 PLIST_ENTRY CurrentEntry;
162 PTERMINATION_PORT TerminationPort;
163
164 DPRINT("PsTerminateCurrentThread(ExitStatus %x)\n", ExitStatus);
165
166 /* Get the Current Thread and Process */
167 CurrentThread = PsGetCurrentThread();
168 CurrentThread->HasTerminated = TRUE;
169 CurrentProcess = CurrentThread->ThreadsProcess;
170
171 /* Can't terminate a thread if it attached another process */
172 if (KeIsAttachedProcess()) {
173
174 KEBUGCHECKEX(INVALID_PROCESS_ATTACH_ATTEMPT, (ULONG) CurrentProcess,
175 (ULONG) CurrentThread->Tcb.ApcState.Process,
176 (ULONG) CurrentThread->Tcb.ApcStateIndex,
177 (ULONG) CurrentThread);
178 }
179
180 /* Lower to Passive Level */
181 KeLowerIrql(PASSIVE_LEVEL);
182
183 /* Run Thread Notify Routines before we desintegrate the thread */
184 PspRunCreateThreadNotifyRoutines(CurrentThread, FALSE);
185
186 /* Lock the Process before we modify its thread entries */
187 PsLockProcess(CurrentProcess, FALSE);
188
189 /* Remove the thread from the thread list of its process */
190 RemoveEntryList(&CurrentThread->ThreadListEntry);
191 Last = IsListEmpty(&CurrentProcess->ThreadListHead);
192
193 /* Set the last Thread Exit Status */
194 CurrentProcess->LastThreadExitStatus = ExitStatus;
195
196 /* Unlock the Process */
197 PsUnlockProcess(CurrentProcess);
198
199 /* Check if the process has a debug port */
200 if (CurrentProcess->DebugPort) {
201
202 /* Notify the Debug API. TODO */
203 //Last ? DbgkExitProcess(ExitStatus) : DbgkExitThread(ExitStatus);
204 }
205
206 /* Process the Termination Ports */
207 while ((CurrentEntry = RemoveHeadList(&CurrentThread->TerminationPortList)) !=
208 &CurrentThread->TerminationPortList) {
209
210 /* Get the Termination Port */
211 TerminationPort = CONTAINING_RECORD(CurrentEntry,
212 TERMINATION_PORT,
213 Links);
214
215 /* Send the LPC Message */
216 LpcSendTerminationPort(TerminationPort->Port, CurrentThread->CreateTime);
217
218 /* Free the Port */
219 ExFreePool(TerminationPort);
220 }
221
222 /* Rundown Win32 Structures */
223 PsTerminateWin32Thread(CurrentThread);
224 if (Last) PsTerminateWin32Process(CurrentProcess);
225
226 /* Cancel I/O for the thread. */
227 IoCancelThreadIo(CurrentThread);
228
229 /* Rundown Timers */
230 ExTimerRundown();
231 KeCancelTimer(&CurrentThread->Tcb.Timer);
232
233 /* Rundown Registry Notifications. TODO (refers to NtChangeNotify, not Cm callbacks) */
234 //CmNotifyRunDown(CurrentThread);
235
236 /* Rundown Mutexes */
237 KeRundownThread();
238
239 /* Free the TEB */
240 if(CurrentThread->Tcb.Teb) {
241
242 DPRINT("Decommit teb at %p\n", CurrentThread->Tcb.Teb);
243 ExAcquireFastMutex(&CurrentProcess->TebLock);
244 TebBlock = MM_ROUND_DOWN(CurrentThread->Tcb.Teb, MM_VIRTMEM_GRANULARITY);
245
246 ZwFreeVirtualMemory(NtCurrentProcess(),
247 (PVOID *)&CurrentThread->Tcb.Teb,
248 &Length,
249 MEM_DECOMMIT);
250
251 DPRINT("teb %p, TebBlock %p\n", CurrentThread->Tcb.Teb, TebBlock);
252
253 if (TebBlock != CurrentProcess->TebBlock ||
254 CurrentProcess->TebBlock == CurrentProcess->TebLastAllocated) {
255
256 MmLockAddressSpace(&CurrentProcess->AddressSpace);
257 MmReleaseMemoryAreaIfDecommitted(CurrentProcess, &CurrentProcess->AddressSpace, TebBlock);
258 MmUnlockAddressSpace(&CurrentProcess->AddressSpace);
259 }
260
261 CurrentThread->Tcb.Teb = NULL;
262 ExReleaseFastMutex(&CurrentProcess->TebLock);
263 }
264
265 /* Set the Exit Status and Exit Time */
266 CurrentThread->ExitStatus = ExitStatus;
267 KeQuerySystemTime((PLARGE_INTEGER)&CurrentThread->ExitTime);
268
269 /* If the Processor Control Block's NpxThread points to the current thread
270 * unset it.
271 */
272 InterlockedCompareExchangePointer(&KeGetCurrentPrcb()->NpxThread,
273 NULL,
274 (PKPROCESS)CurrentThread);
275
276 /* The last Thread shuts down the Process */
277 if (Last) PspExitProcess(CurrentProcess);
278
279 /* Terminate the Thread from the Scheduler */
280 KeTerminateThread(0);
281 DPRINT1("Unexpected return, CurrentThread %x PsGetCurrentThread() %x\n", CurrentThread, PsGetCurrentThread());
282 KEBUGCHECK(0);
283 }
284
285 VOID
286 STDCALL
287 PsExitSpecialApc(PKAPC Apc,
288 PKNORMAL_ROUTINE* NormalRoutine,
289 PVOID* NormalContext,
290 PVOID* SystemArgument1,
291 PVOID* SystemArguemnt2)
292 {
293 NTSTATUS ExitStatus = (NTSTATUS)Apc->NormalContext;
294
295 /* Free the APC */
296 ExFreePool(Apc);
297
298 /* Terminate the Thread */
299 PspExitThread(ExitStatus);
300 }
301
302 VOID
303 STDCALL
304 PspExitNormalApc(PVOID NormalContext,
305 PVOID SystemArgument1,
306 PVOID SystemArgument2)
307 {
308 /* Not fully supported yet... must work out some issues that
309 * I don't understand yet -- Alex
310 */
311 DPRINT1("APC2\n");
312 PspExitThread((NTSTATUS)NormalContext);
313 }
314
315 /*
316 * See "Windows Internals" - Chapter 13, Page 49
317 */
318 VOID
319 STDCALL
320 PspTerminateThreadByPointer(PETHREAD Thread,
321 NTSTATUS ExitStatus)
322 {
323 PKAPC Apc;
324
325 DPRINT("PspTerminatedThreadByPointer(Thread %x, ExitStatus %x)\n",
326 Thread, ExitStatus);
327
328 /* Check if we are already in the right context */
329 if (PsGetCurrentThread() == Thread) {
330
331 /* Directly terminate the thread */
332 PspExitThread(ExitStatus);
333 }
334
335 /* Allocate the APC */
336 Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_TERMINATE_APC);
337
338 /* Initialize a Kernel Mode APC to Kill the Thread */
339 KeInitializeApc(Apc,
340 &Thread->Tcb,
341 OriginalApcEnvironment,
342 PsExitSpecialApc,
343 NULL,
344 PspExitNormalApc,
345 KernelMode,
346 (PVOID)ExitStatus);
347
348 /* Insert it into the APC Queue */
349 KeInsertQueueApc(Apc,
350 Apc,
351 NULL,
352 2);
353
354 /* Forcefully resume the thread */
355 KeForceResumeThread(&Thread->Tcb);
356 }
357
358 NTSTATUS
359 STDCALL
360 PspExitProcess(PEPROCESS Process)
361 {
362 DPRINT("PspExitProcess\n");
363
364 PspRunCreateProcessNotifyRoutines(Process, FALSE);
365
366 /* Remove it from the Active List */
367 ExAcquireFastMutex(&PspActiveProcessMutex);
368 RemoveEntryList(&Process->ProcessListEntry);
369 ExReleaseFastMutex(&PspActiveProcessMutex);
370
371 ObKillProcess(Process);
372 KeSetProcess(&Process->Pcb, IO_NO_INCREMENT);
373 return(STATUS_SUCCESS);
374 }
375
376 NTSTATUS
377 STDCALL
378 NtTerminateProcess(IN HANDLE ProcessHandle OPTIONAL,
379 IN NTSTATUS ExitStatus)
380 {
381 NTSTATUS Status;
382 PEPROCESS Process;
383
384 PAGED_CODE();
385
386 DPRINT("NtTerminateProcess(ProcessHandle %x, ExitStatus %x)\n",
387 ProcessHandle, ExitStatus);
388
389 /* Get the Process Object */
390 Status = ObReferenceObjectByHandle(ProcessHandle,
391 PROCESS_TERMINATE,
392 PsProcessType,
393 KeGetPreviousMode(),
394 (PVOID*)&Process,
395 NULL);
396 if (!NT_SUCCESS(Status)) {
397
398 DPRINT1("Invalid handle to Process\n");
399 return(Status);
400 }
401
402 if(Process->ExitTime.QuadPart) {
403
404 DPRINT1("Process has an exit time!\n");
405 KeLeaveCriticalRegion();
406 return STATUS_PROCESS_IS_TERMINATING;
407 }
408
409 /* Terminate all the Process's Threads */
410 PspTerminateProcessThreads(Process, ExitStatus);
411
412 /* Save the Exit Time */
413 KeQuerySystemTime(&Process->ExitTime);
414
415 /* Only master thread remains... kill it off */
416 if (PsGetCurrentThread()->ThreadsProcess == Process) {
417
418 /* Unlock and dereference */
419 ObDereferenceObject(Process);
420 PspExitThread(ExitStatus);
421 return(STATUS_SUCCESS);
422 }
423
424 /* If we took this path instead, then do the same as above */
425 ObDereferenceObject(Process);
426 return(STATUS_SUCCESS);
427 }
428
429 NTSTATUS
430 STDCALL
431 NtTerminateThread(IN HANDLE ThreadHandle,
432 IN NTSTATUS ExitStatus)
433 {
434 PETHREAD Thread;
435 NTSTATUS Status;
436
437 PAGED_CODE();
438
439 /* Get the Thread Object */
440 Status = ObReferenceObjectByHandle(ThreadHandle,
441 THREAD_TERMINATE,
442 PsThreadType,
443 KeGetPreviousMode(),
444 (PVOID*)&Thread,
445 NULL);
446 if (Status != STATUS_SUCCESS) {
447
448 DPRINT1("Could not reference thread object\n");
449 return(Status);
450 }
451
452 /* Make sure this is not a system thread */
453 if (PsIsSystemThread(Thread)) {
454
455 DPRINT1("Trying to Terminate a system thread!\n");
456 return STATUS_INVALID_PARAMETER;
457 }
458
459 /* Check to see if we're running in the same thread */
460 if (Thread != PsGetCurrentThread()) {
461
462 /* This isn't our thread, check if it's terminated already */
463 if (!Thread->HasTerminated) {
464
465 /* Terminate it */
466 PspTerminateThreadByPointer(Thread, ExitStatus);
467
468 /* Resume it */
469 KeForceResumeThread(&Thread->Tcb);
470 }
471
472 } else {
473
474 /* Terminate him, he's ours */
475 PspExitThread(ExitStatus);
476 }
477
478 /* Dereference the Thread and return */
479 ObDereferenceObject(Thread);
480 return(STATUS_SUCCESS);
481 }
482
483 /*
484 * @implemented
485 */
486 NTSTATUS
487 STDCALL
488 PsTerminateSystemThread(NTSTATUS ExitStatus)
489 {
490 PETHREAD Thread = PsGetCurrentThread();
491
492 /* Make sure this is a system thread */
493 if (!PsIsSystemThread(Thread)) {
494
495 DPRINT1("Trying to Terminate a non-system thread!\n");
496 return STATUS_INVALID_PARAMETER;
497 }
498
499 /* Terminate it for real */
500 PspExitThread(ExitStatus);
501 return(STATUS_SUCCESS);
502 }
503
504 NTSTATUS
505 STDCALL
506 NtRegisterThreadTerminatePort(HANDLE PortHandle)
507 {
508 NTSTATUS Status;
509 PTERMINATION_PORT TerminationPort;
510 PVOID TerminationLpcPort;
511
512 PAGED_CODE();
513
514 /* Get the Port */
515 Status = ObReferenceObjectByHandle(PortHandle,
516 PORT_ALL_ACCESS,
517 LpcPortObjectType,
518 KeGetPreviousMode(),
519 &TerminationLpcPort,
520 NULL);
521 if (!NT_SUCCESS(Status)) {
522
523 DPRINT1("Failed to reference Port\n");
524 return(Status);
525 }
526
527 /* Allocate the Port and make sure it suceeded */
528 if((TerminationPort = ExAllocatePoolWithTag(NonPagedPool,
529 sizeof(PTERMINATION_PORT),
530 TAG('P', 's', 'T', '=')))) {
531
532 /* Associate the Port */
533 TerminationPort->Port = TerminationLpcPort;
534 InsertTailList(&PsGetCurrentThread()->TerminationPortList, &TerminationPort->Links);
535
536 /* Return success */
537 return(STATUS_SUCCESS);
538
539 } else {
540
541 /* Dereference and Fail */
542 ObDereferenceObject(TerminationPort);
543 return(STATUS_INSUFFICIENT_RESOURCES);
544 }
545 }