Add thread control surfaces
Adds RemoteThread, ThreadFactory (enumeration, main-thread selection, get-by-id), and FrozenThread scoped freeze. Supports suspend/resume, 32/64-bit context round-trip, TEB query, and reverse-order resume on dispose. Closes section 2 of add-thread-region-finder.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
using System.Threading;
|
||||
using Thread = System.Threading.Thread;
|
||||
using WhiteMagic;
|
||||
using WhiteMagic.Native;
|
||||
using WhiteMagic.Thread;
|
||||
using Xunit;
|
||||
|
||||
namespace WhiteMagicTest.Thread;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="RemoteThread"/> open/suspend/resume and context round-trip.
|
||||
/// </summary>
|
||||
public sealed class RemoteThreadTests
|
||||
{
|
||||
[Fact]
|
||||
public void Open_by_id_succeeds_for_current_thread()
|
||||
{
|
||||
using var magic = Magic.OpenInProcess();
|
||||
int currentId = (int)NativeMethods.GetCurrentThreadId();
|
||||
|
||||
using var thread = new RemoteThread(magic.Memory, currentId);
|
||||
Assert.Equal(currentId, thread.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Suspend_returns_prior_count_and_stops_worker()
|
||||
{
|
||||
using var magic = Magic.OpenInProcess();
|
||||
using var cts = new CancellationTokenSource();
|
||||
var started = new ManualResetEventSlim(false);
|
||||
int osThreadId = 0;
|
||||
|
||||
var worker = new System.Threading.Thread(() =>
|
||||
{
|
||||
osThreadId = (int)NativeMethods.GetCurrentThreadId();
|
||||
started.Set();
|
||||
while (!cts.IsCancellationRequested)
|
||||
System.Threading.Thread.Sleep(10);
|
||||
});
|
||||
|
||||
worker.Start();
|
||||
started.Wait();
|
||||
|
||||
try
|
||||
{
|
||||
using var thread = new RemoteThread(magic.Memory, osThreadId);
|
||||
|
||||
uint prior = thread.Suspend();
|
||||
Assert.True(prior < 0xFFFFFFFF);
|
||||
|
||||
cts.Cancel();
|
||||
// Worker cannot observe cancellation while suspended.
|
||||
Assert.False(worker.Join(100));
|
||||
|
||||
thread.Resume();
|
||||
Assert.True(worker.Join(1000));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (worker.IsAlive)
|
||||
{
|
||||
cts.Cancel();
|
||||
using var thread = new RemoteThread(magic.Memory, osThreadId);
|
||||
thread.Resume();
|
||||
worker.Join(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Resume_restarts_a_suspended_worker()
|
||||
{
|
||||
using var magic = Magic.OpenInProcess();
|
||||
using var cts = new CancellationTokenSource();
|
||||
var started = new ManualResetEventSlim(false);
|
||||
var resumed = new ManualResetEventSlim(false);
|
||||
int osThreadId = 0;
|
||||
|
||||
var worker = new System.Threading.Thread(() =>
|
||||
{
|
||||
osThreadId = (int)NativeMethods.GetCurrentThreadId();
|
||||
started.Set();
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
resumed.Set();
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
});
|
||||
|
||||
worker.Start();
|
||||
started.Wait();
|
||||
|
||||
try
|
||||
{
|
||||
using var thread = new RemoteThread(magic.Memory, osThreadId);
|
||||
thread.Suspend();
|
||||
resumed.Reset();
|
||||
|
||||
uint prior = thread.Resume();
|
||||
Assert.True(prior < 0xFFFFFFFF);
|
||||
|
||||
// Worker must reach the resumed flag again.
|
||||
Assert.True(resumed.Wait(1000));
|
||||
cts.Cancel();
|
||||
Assert.True(worker.Join(1000));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (worker.IsAlive)
|
||||
{
|
||||
cts.Cancel();
|
||||
using var thread = new RemoteThread(magic.Memory, osThreadId);
|
||||
thread.Resume();
|
||||
worker.Join(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTeb_returns_managed_teb_for_thread()
|
||||
{
|
||||
using var magic = Magic.OpenInProcess();
|
||||
int currentId = (int)NativeMethods.GetCurrentThreadId();
|
||||
|
||||
using var thread = new RemoteThread(magic.Memory, currentId);
|
||||
using var teb = thread.GetTeb();
|
||||
|
||||
Assert.NotEqual(IntPtr.Zero, teb.ReadTebAddress());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user