using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ProcessNET.Marshaling;
using ProcessNET.Native.Types;
using ProcessNET.Utilities;
using ThreadState = System.Diagnostics.ThreadState;
namespace ProcessNET.Threads
{
///
/// Class representing a thread in the remote process.
///
public class RemoteThread : IEquatable, IRemoteThread
{
private readonly IMarshalledValue _parameter;
private readonly Task _parameterCleaner;
protected readonly IProcess ProcessPlus;
///
/// Initializes a new instance of the class.
///
/// The reference of the object.
/// The native object.
public RemoteThread(IProcess processPlus, ProcessThread thread)
{
// Save the parameters
ProcessPlus = processPlus;
Native = thread;
// Save the thread id
Id = thread.Id;
// Open the thread
Handle = ThreadHelper.OpenThread(ThreadAccessFlags.AllAccess, Id);
}
///
/// Initializes a new instance of the class.
///
/// The reference of the object.
/// The native object.
/// The parameter passed to the thread when it was created.
public RemoteThread(IProcess processPlus, ProcessThread thread, IMarshalledValue parameter = null)
: this(processPlus, thread)
{
// Save the parameter
_parameter = parameter;
// Create the task
_parameterCleaner = new Task(() =>
{
Join();
_parameter.Dispose();
});
}
///
/// Returns a value indicating whether this instance is equal to a specified object.
///
public bool Equals(RemoteThread other)
{
if (ReferenceEquals(null, other)) return false;
return ReferenceEquals(this, other) || (Id == other.Id && ProcessPlus.Equals(other.ProcessPlus));
}
///
/// Gets or sets the full context of the thread.
/// If the thread is not already suspended, performs a and call on the
/// thread.
///
public ThreadContext Context
{
get
{
// Check if the thread is alive
if (!IsAlive)
throw new ThreadStateException(
$"Couldn't set the context of the thread #{Id} because it is terminated.");
// Check if the thread is already suspended
var isSuspended = IsSuspended;
try
{
// Suspend the thread if it wasn't
if (!isSuspended)
Suspend();
// Get the context
return ThreadHelper.GetThreadContext(Handle,
ThreadContextFlags.All | ThreadContextFlags.FloatingPoint |
ThreadContextFlags.DebugRegisters | ThreadContextFlags.ExtendedRegisters);
}
finally
{
// Resume the thread if it wasn't suspended
if (!isSuspended)
Resume();
}
// The thread is closed, cannot set the context
}
set
{
// Check if the thread is alive
if (!IsAlive) return;
// Check if the thread is already suspended
var isSuspended = IsSuspended;
try
{
// Suspend the thread if it wasn't
if (!isSuspended)
Suspend();
// Set the context
ThreadHelper.SetThreadContext(Handle, value);
}
finally
{
// Resume the thread if it wasn't suspended
if (!isSuspended)
Resume();
}
}
}
///
/// The remote thread handle opened with all rights.
///
public SafeMemoryHandle Handle { get; }
///
/// Gets the unique identifier of the thread.
///
public int Id { get; }
///
/// Gets if the thread is alive.
///
public bool IsAlive => !IsTerminated;
///
/// Gets if the thread is the main one in the remote process.
///
public bool IsMainThread => ProcessPlus.Native.Threads[0].Id == Id;
///
/// Gets if the thread is suspended.
///
public bool IsSuspended
{
get
{
// Refresh the thread info
Refresh();
// Return if the thread is suspended
return Native != null && Native.ThreadState == ThreadState.Wait &&
Native.WaitReason == ThreadWaitReason.Suspended;
}
}
///
/// Gets if the thread is terminated.
///
public bool IsTerminated
{
get
{
// Refresh the thread info
Refresh();
// Check if the thread is terminated
return Native == null;
}
}
///
/// The native object corresponding to this thread.
///
public ProcessThread Native { get; private set; }
///
/// Releases all resources used by the object.
///
public virtual void Dispose()
{
// Close the thread handle
Handle.Close();
// Avoid the finalizer
GC.SuppressFinalize(this);
}
///
/// Gets the termination status of the thread.
///
public T GetExitCode()
{
// Get the exit code of the thread (can be nullable)
var ret = ThreadHelper.GetExitCodeThread(Handle);
// Return the exit code or the default value of T if there's no exit code
return ret.HasValue ? MarshalType.PtrToObject(ProcessPlus, ret.Value) : default(T);
}
///
/// Serves as a hash function for a particular type.
///
public override int GetHashCode()
{
return Id.GetHashCode() ^ ProcessPlus.GetHashCode();
}
///
/// Gets the linear address of a specified segment.
///
/// The segment to get.
/// A pointer corresponding to the linear address of the segment.
public IntPtr GetRealSegmentAddress(SegmentRegisters segment)
{
// Get a selector entry for the segment
LdtEntry entry;
switch (segment)
{
case SegmentRegisters.Cs:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegCs);
break;
case SegmentRegisters.Ds:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegDs);
break;
case SegmentRegisters.Es:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegEs);
break;
case SegmentRegisters.Fs:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegFs);
break;
case SegmentRegisters.Gs:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegGs);
break;
case SegmentRegisters.Ss:
entry = ThreadHelper.GetThreadSelectorEntry(Handle, Context.SegSs);
break;
default:
throw new InvalidEnumArgumentException("segment");
}
// Compute the linear address
return new IntPtr(entry.BaseLow | (entry.BaseMid << 16) | (entry.BaseHi << 24));
}
///
/// Discards any information about this thread that has been cached inside the process component.
///
public void Refresh()
{
if (Native == null)
return;
// Refresh the process info
ProcessPlus.Native.Refresh();
// Get new info about the thread
Native = ProcessPlus.Native.Threads.Cast().FirstOrDefault(t => t.Id == Native.Id);
}
///
/// Blocks the calling thread until the thread terminates.
///
public void Join()
{
ThreadHelper.WaitForSingleObject(Handle);
}
///
/// Blocks the calling thread until a thread terminates or the specified time elapses.
///
/// The timeout.
/// The return value is a flag that indicates if the thread terminated or if the time elapsed.
public WaitValues Join(TimeSpan time)
{
return ThreadHelper.WaitForSingleObject(Handle, time);
}
///
/// Resumes a thread that has been suspended.
///
public void Resume()
{
// Check if the thread is still alive
if (!IsAlive) return;
// Start the thread
ThreadHelper.ResumeThread(Handle);
// Start a task to clean the memory used by the parameter if we created the thread
if (_parameter != null && !_parameterCleaner.IsCompleted)
_parameterCleaner.Start();
}
///
/// Either suspends the thread, or if the thread is already suspended, has no effect.
///
/// A new instance of the class. If this object is disposed, the thread is resumed.
public IFrozenThread Suspend()
{
if (!IsAlive) return null;
ThreadHelper.SuspendThread(Handle);
return new FrozenThread(this);
}
///
/// Terminates the thread.
///
/// The exit code of the thread to close.
public void Terminate(int exitCode = 0)
{
if (IsAlive)
ThreadHelper.TerminateThread(Handle, exitCode);
}
///
/// Frees resources and perform other cleanup operations before it is reclaimed by garbage collection.
///
~RemoteThread()
{
Dispose();
}
///
/// Determines whether the specified object is equal to the current object.
///
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == GetType() && Equals((RemoteThread) obj);
}
public static bool operator ==(RemoteThread left, RemoteThread right)
{
return Equals(left, right);
}
public static bool operator !=(RemoteThread left, RemoteThread right)
{
return !Equals(left, right);
}
///
/// Returns a string that represents the current object.
///
public override string ToString()
{
return $"Id = {Id} IsAlive = {IsAlive} IsMainThread = {IsMainThread}";
}
}
}