Saturday, 5 June 2021

Kill External Thread

Out of curiosity I was looking into how to kill a specific thread from outside the host process. For that I wrote a basic .Net 5 application creating a couple of threads. I create those Threads with the lowest level option available in .Net, the Thread class. The first interesting thing is that the this class has just a ManagedId property, that has nothing to do with the real OS level Thread ID, and indeed .Net does not provide a way to obtain that OS Thread ID. The explanation for this is 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 a bit surprising to me. While Task.Run is clearly a high level mechanism (at this moment the code will run in a thread from the Thread Pool), using the Thread constructor and its Start method seemed to me like the Managed equivalent to the Windows API CreateThread function or to the Posix pthread_create in Linux. I've been doing some tests in .Net 5 both in Windows and Linux, and the Thread constructor seems to use a new OS thread, but well, not sure if after using a certain number of "new Thread(---).Start" calls it could begin to reuse previous threads.

In the old .Net 2-3-4 times... a simple Windows Console application in which you were not explicitly creating any threads (other than the main thread that runs the application) had indeed 3 threads: that main thread, a CLR thread and a Finalizer thread. When running a simple .Net 5 console application on Windows or Linux I can see many more threads (like 10 threads when started, and then, even while doing nothing, new threads show up and die...) I print the current threads from inside the application like this:


private static IEnumerable<string> GetThreadsIDs()
        {
            var processThreads = new List<ProcessThread>();
            foreach (ProcessThread pTh in Process.GetCurrentProcess().Threads){
                processThreads.Add(pTh);
            }
            return processThreads.Select(pTh => pTh.Id.ToString());
        }

Printing that threads list right before and right after calling into "new Thread(delegate)" you can "almost" identify the OS Thread ID of that new Thread. I say almost because indeed you'll see 2 or 3 new threads in the list, but if you wait and run some external command to get updated list of threads, you'll see how some threads come and go... but you'll also see that one of those new threads remains... so that seems to be the thread that corresponds to your "new Thread(delegate)".

To get the list of threads of a given process I use this command in linux (the Thread ID here is shown as "SPID", and other tools seem to call it "LWP"):
ps -T pid

and this one in Windows Powershell:
Get-Process -ID pid | Select-Object -ExpandProperty Threads | Select-Object ID

It's interesting to note that in Linux, the Thread ID of the first thread in one process is the same as the Process ID. My understanding is that each thread is represented by a task structure. For the first thread in the process the taskId and ProcessId fields in that task have the same value, then for the other tasks you'll have a new Thread ID, but the same Process ID. I'm talking about modern Threads implementations, NPTL ("Native posix thread library")

As we can kill a process given its process ID (kill -9, System Monitor, Task Manager...) how about using the Thread ID to kill a certain Thread?

Well, we can do that in Windows from the UI of Process Explorer or ProcessHacker, or from the command line by means again of Process Hacker, like this (I guess it's using the Windows API TerminateThread function):
ProcessHacker.exe -c -ctype thread -cobject threadId -caction terminate

In Linux I've read that you can send a signal (9 or 15 to kill it) to the target thread with the tgkill(threadId, signal) function, but have not found any utility doing that. I just found this, that is doing a syscall(SYS_tgkill, tgid, tid, sig);, but it's not working for me... So for the moment I have no way to kill a thread of another process in Linux.

No comments:

Post a Comment