Terminate the external thread

I was curious about stopping a specific thread from outside its process, so I built a simple .Net 5 application that starts a few threads. To create these threads, I’m using the most basic method available in .Net: the Thread class. Interestingly, this class only offers a ManagedId property, which doesn’t correspond to the actual OS thread ID. In fact, .Net doesn’t give us a way to get that OS Thread ID. The reason for this is explained in here:

“An operating-system ThreadId has no fixed relationship to a managed thread, because an unmanaged host can control the relationship between managed and unmanaged threads. Specifically, a sophisticated host can use the CLR Hosting API to schedule many managed threads against the same operating system thread, or to move a managed thread between different operating system threads.”

This is unexpected. I thought that while Task.Run is a high-level approach (using a thread from the Thread Pool), directly using the Thread constructor and its Start method would be comparable to the Windows API CreateThread function or Linux’s Posix pthread_create. My tests in .Net 5 on Windows and Linux show that the Thread constructor seems to create a new OS thread initially. However, it’s unclear if it might start reusing threads after a certain number of “new Thread(—).Start” calls.

In older .Net versions (2-3-4), a basic Windows console app without explicitly created threads (besides the main one) would have three threads: the main thread, a CLR thread, and a Finalizer thread. Now, running a simple .Net 5 console application on Windows or Linux shows many more threads (around 10 at startup, with more appearing and disappearing even when idle). I’m using this code to display the current threads within the app:

1
2
3
4
5
6
7
8
 `private static IEnumerable GetThreadsIDs()
        {
            var processThreads = new List();
            foreach (ProcessThread pTh in Process.GetCurrentProcess().Threads){
                processThreads.Add(pTh);
            }
            return processThreads.Select(pTh => pTh.Id.ToString());
        }` 

By printing the thread list just before and after “new Thread(delegate),” you can almost pinpoint the new thread’s OS Thread ID. I say “almost” because two or three new threads appear in the list. However, after waiting and checking the updated thread list with an external command, some threads vanish while one remains. This remaining thread seems to be the one linked to your “new Thread(delegate).”

To get the thread list for a process, I use these commands:
Linux (Thread ID is shown as “SPID” or “LWP” depending on the tool): ps -T pid

Windows Powershell: Get-Process -ID pid | Select-Object -ExpandProperty Threads | Select-Object ID

Interestingly, in Linux, the first thread’s ID matches the Process ID. This is because, in modern thread implementations like NPTL (“Native posix thread library”), each thread is represented by a task structure. The first thread’s task structure has the same value for both the taskId and ProcessId fields. Subsequent tasks get unique Thread IDs but share the Process ID.

Since we can terminate a process by its ID (kill -9, System Monitor, Task Manager), what about using the Thread ID to kill a specific thread?

In Windows, this is possible through Process Explorer, Process Hacker’s UI, or via the command line using Process Hacker (likely utilizing the TerminateThread Windows API function): ProcessHacker.exe -c -ctype thread -cobject threadId -caction terminate

For Linux, I’ve read about sending a signal (9 or 15 to kill) to the target thread using the tgkill(threadId, signal) function. However, I haven’t found a utility that does this directly. I came across this, which uses _syscall(SYS\_tgkill, tgid, tid, sig);, but it’s not working for me. So currently, I don’t have a way to kill a specific thread in another process on Linux.

Licensed under CC BY-NC-SA 4.0
Last updated on Jan 11, 2024 16:51 +0100