/* * Process Hacker - * thread handle * * Copyright (C) 2008-2009 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using ProcessHacker.Common; using ProcessHacker.Native.Api; using ProcessHacker.Native.Security; namespace ProcessHacker.Native.Objects { /// /// Represents a handle to a Windows thread. /// public sealed class ThreadHandle : NativeHandle, IWithToken { public delegate bool WalkStackDelegate(ThreadStackFrame stackFrame); private static readonly ThreadHandle _current = new ThreadHandle(new IntPtr(-2), false); /// /// Gets a handle to the current thread. /// public static ThreadHandle Current { get { return _current; } } public static ThreadHandle Create( ThreadAccess access, string name, ObjectFlags objectFlags, DirectoryHandle rootDirectory, ProcessHandle processHandle, out ClientId clientId, ref Context threadContext, ref InitialTeb initialTeb, bool createSuspended ) { NtStatus status; ObjectAttributes oa = new ObjectAttributes(name, objectFlags, rootDirectory); IntPtr handle; try { if ((status = Win32.NtCreateThread( out handle, access, ref oa, processHandle, out clientId, ref threadContext, ref initialTeb, createSuspended )) >= NtStatus.Error) Win32.ThrowLastError(status); } finally { oa.Dispose(); } return new ThreadHandle(handle, true); } public static ThreadHandle CreateUserThread(ProcessHandle processHandle, IntPtr startAddress, IntPtr parameter) { return CreateUserThread(processHandle, false, startAddress, parameter); } public static ThreadHandle CreateUserThread( ProcessHandle processHandle, bool createSuspended, IntPtr startAddress, IntPtr parameter ) { ClientId clientId; return CreateUserThread(processHandle, createSuspended, 0, 0, startAddress, parameter, out clientId); } public static ThreadHandle CreateUserThread( ProcessHandle processHandle, bool createSuspended, int maximumStackSize, int initialStackSize, IntPtr startAddress, IntPtr parameter, out ClientId clientId ) { NtStatus status; IntPtr threadHandle; if ((status = Win32.RtlCreateUserThread( processHandle, IntPtr.Zero, createSuspended, 0, maximumStackSize.ToIntPtr(), initialStackSize.ToIntPtr(), startAddress, parameter, out threadHandle, out clientId )) >= NtStatus.Error) Win32.ThrowLastError(status); return new ThreadHandle(threadHandle, true); } /// /// Creates a thread handle using an existing handle. /// The handle will not be closed automatically. /// /// The handle value. /// The thread handle. public static ThreadHandle FromHandle(IntPtr handle) { return new ThreadHandle(handle, false); } /// /// Gets a handle to the current thread. /// /// A thread handle. public static ThreadHandle GetCurrent() { return Current; } /// /// Gets the client ID of the current thread. /// /// A client ID. public static ClientId GetCurrentCid() { return new ClientId(ProcessHandle.GetCurrentId(), ThreadHandle.GetCurrentId()); } /// /// Gets the ID of the current thread. /// /// A thread ID. public static int GetCurrentId() { return Win32.GetCurrentThreadId(); } /// /// Gets a pointer to the current thread's environment block. /// /// A pointer to the current TEB. public unsafe static Teb* GetCurrentTeb() { return (Teb*)Win32.NtCurrentTeb(); } /// /// Opens the current thread. /// /// The desired access to the thread. /// A handle to the current thread. public static ThreadHandle OpenCurrent(ThreadAccess access) { return new ThreadHandle(GetCurrentId(), access); } public static ThreadHandle OpenWithAnyAccess(int tid) { try { return new ThreadHandle(tid, OSVersion.MinThreadQueryInfoAccess); } catch { try { return new ThreadHandle(tid, (ThreadAccess)StandardRights.Synchronize); } catch { try { return new ThreadHandle(tid, (ThreadAccess)StandardRights.ReadControl); } catch { try { return new ThreadHandle(tid, (ThreadAccess)StandardRights.WriteDac); } catch { return new ThreadHandle(tid, (ThreadAccess)StandardRights.WriteOwner); } } } } } /// /// Registers a port which will be notified when the current thread terminates. /// /// A handle to a port. public static void RegisterTerminationPort(PortHandle portHandle) { NtStatus status; if ((status = Win32.NtRegisterThreadTerminatePort(portHandle)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Sleeps the current thread. /// /// The timeout, in 100ns units. /// Whether the timeout value is relative. /// A NT status value. public static NtStatus Sleep(long timeout, bool relative) { return Sleep(false, timeout, relative); } /// /// Sleeps the current thread. /// /// /// Whether user-mode APCs can be delivered during the wait. /// /// The timeout, in 100ns units. /// Whether the timeout value is relative. /// A NT status value. public static NtStatus Sleep(bool alertable, long timeout, bool relative) { if (timeout == 0) { Yield(); return NtStatus.Success; } long realTime = relative ? -timeout : timeout; return Win32.NtDelayExecution(alertable, ref realTime); } /// /// Checks whether the current thread is in an alerted state and /// executes any pending user-mode APCs. /// /// /// NtStatus.Alerted if the current thread was in an alerted state, /// otherwise NtStatus.Success. /// public static NtStatus TestAlert() { NtStatus status; if ((status = Win32.NtTestAlert()) >= NtStatus.Error) Win32.ThrowLastError(status); return status; } /// /// Switches to another thread. /// public static void Yield() { Win32.NtYieldExecution(); } internal ThreadHandle(IntPtr handle, bool owned) : base(handle, owned) { } /// /// Opens a thread. /// /// The ID of the thread to open. public ThreadHandle(int tid) : this(tid, ThreadAccess.All) { } /// /// Opens a thread. /// /// The ID of the thread to open. /// The desired access to the thread. public ThreadHandle(int tid, ThreadAccess access) { if (KProcessHacker.Instance != null) { try { this.Handle = new IntPtr(KProcessHacker.Instance.KphOpenThread(tid, access)); } catch (WindowsException) { // Open the thread with minimum access (SYNCHRONIZE) and set the granted access. this.Handle = new IntPtr(KProcessHacker.Instance.KphOpenThread(tid, (ThreadAccess)StandardRights.Synchronize)); KProcessHacker.Instance.KphSetHandleGrantedAccess(this.Handle, (int)access); } } else { this.Handle = Win32.OpenThread(access, false, tid); } if (this.Handle == IntPtr.Zero) { this.MarkAsInvalid(); Win32.ThrowLastError(); } } public ThreadHandle( string name, ObjectFlags objectFlags, DirectoryHandle rootDirectory, ClientId clientId, ThreadAccess access ) { NtStatus status; ObjectAttributes oa = new ObjectAttributes(name, objectFlags, rootDirectory); IntPtr handle; try { if (clientId.ProcessId == 0 && clientId.ThreadId == 0) { if ((status = Win32.NtOpenThread( out handle, access, ref oa, IntPtr.Zero )) >= NtStatus.Error) Win32.ThrowLastError(status); } else { if ((status = Win32.NtOpenThread( out handle, access, ref oa, ref clientId )) >= NtStatus.Error) Win32.ThrowLastError(status); } } finally { oa.Dispose(); } this.Handle = handle; } public ThreadHandle(string name, ThreadAccess access) : this(name, 0, null, new ClientId(), access) { } /// /// Puts the thread in an alerted state. /// public void Alert() { NtStatus status; if ((status = Win32.NtAlertThread(this)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Resumes the thread in an alerted state. /// public int AlertResume() { NtStatus status; int suspendCount; if ((status = Win32.NtAlertResumeThread(this, out suspendCount)) >= NtStatus.Error) Win32.ThrowLastError(status); return suspendCount; } /// /// Captures a kernel-mode stack trace for the thread. /// /// An array of function addresses. public IntPtr[] CaptureKernelStack() { return this.CaptureKernelStack(0); } /// /// Captures a kernel-mode stack trace for the thread. /// /// The number of frames to skip. /// An array of function addresses. public IntPtr[] CaptureKernelStack(int skipCount) { IntPtr[] stack = new IntPtr[62 - skipCount]; // 62 limit for XP and Server 2003 int hash; // Capture a kernel-mode stack trace. int captured = KProcessHacker.Instance.KphCaptureStackBackTraceThread( this, skipCount, stack.Length, stack, out hash ); // Create a new array with only the frames we captured. IntPtr[] newStack = new IntPtr[captured]; Array.Copy(stack, 0, newStack, 0, captured); return newStack; } /// /// Captures a user-mode stack trace for the thread. /// /// An array of stack frames. public ThreadStackFrame[] CaptureUserStack() { return this.CaptureUserStack(0); } /// /// Captures a user-mode stack trace for the thread. /// /// The number of frames to skip. /// An array of stack frames. public ThreadStackFrame[] CaptureUserStack(int skipCount) { List frames = new List(); // Walk the stack. this.WalkStack((frame) => { frames.Add(frame); return true; }); // If we want to skip frames than we have, just return an empty array. if (frames.Count <= skipCount) return new ThreadStackFrame[0]; // Otherwise, create a new array with the frames, minus what we skipped. ThreadStackFrame[] newFrames = new ThreadStackFrame[frames.Count - skipCount]; Array.Copy(frames.ToArray(), skipCount, newFrames, 0, newFrames.Length); return newFrames; } /// /// Attempts to terminate the thread using a dangerous method. This /// operation may cause the system to crash. /// /// The exit status. public void DangerousTerminate(NtStatus exitStatus) { KProcessHacker.Instance.KphDangerousTerminateThread(this, exitStatus); } /// /// Gets the thread's base priority. /// public int GetBasePriority() { return this.GetInformationInt32(ThreadInformationClass.ThreadBasePriority); } /// /// Gets the thread's base priority. /// /// A ThreadPriorityLevel enum. public ThreadPriorityLevel GetBasePriorityWin32() { int priority = Win32.GetThreadPriority(this); if (priority == 0x7fffffff) Win32.ThrowLastError(); return (ThreadPriorityLevel)priority; } /// /// Gets the thread's basic information. /// /// A THREAD_BASIC_INFORMATION structure. public ThreadBasicInformation GetBasicInformation() { NtStatus status; ThreadBasicInformation basicInfo = new ThreadBasicInformation(); int retLen; if ((status = Win32.NtQueryInformationThread(this, ThreadInformationClass.ThreadBasicInformation, ref basicInfo, Marshal.SizeOf(basicInfo), out retLen)) >= NtStatus.Error) Win32.ThrowLastError(status); return basicInfo; } /// /// Gets the thread's context. /// /// A CONTEXT struct. public Context GetContext(ContextFlags flags) { Context context = new Context(); context.ContextFlags = flags; this.GetContext(ref context); return context; } /// /// Gets the thread's context. /// /// A Context structure. The ContextFlags must be set appropriately. public unsafe void GetContext(ref Context context) { if (KProcessHacker.Instance != null) { fixed (Context* contextPtr = &context) KProcessHacker.Instance.KphGetContextThread(this, contextPtr); } else { NtStatus status; if ((status = Win32.NtGetContextThread(this, ref context)) >= NtStatus.Error) Win32.ThrowLastError(status); } } /// /// Gets the thread's context. /// /// A CONTEXT struct. public ContextAmd64 GetContext(ContextFlagsAmd64 flags) { ContextAmd64 context = new ContextAmd64(); context.ContextFlags = flags; this.GetContext(ref context); return context; } /// /// Gets the thread's context. /// /// A Context structure. The ContextFlags must be set appropriately. public void GetContext(ref ContextAmd64 context) { NtStatus status; // HACK: To avoid a datatype misalignment error, allocate some // aligned memory. using (var data = new AlignedMemoryAlloc(Utils.SizeOf(16), 16)) { data.WriteStruct(context); if ((status = Win32.NtGetContextThread(this, data)) >= NtStatus.Error) Win32.ThrowLastError(status); context = data.ReadStruct(); } } /// /// Gets the thread's x86 context. The thread's process must be running /// under WOW64. /// /// A Context structure. The ContextFlags must be set appropriately. public void GetContextWow64(ref Context context) { NtStatus status; if ((status = Win32.RtlWow64GetThreadContext(this, ref context)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Gets the number of processor cycles consumed by the thread. /// public ulong GetCycleTime() { ulong cycles; if (!Win32.QueryThreadCycleTime(this, out cycles)) Win32.ThrowLastError(); return cycles; } /// /// Gets the thread's exit code. /// /// A number. public int GetExitCode() { int exitCode; if (!Win32.GetExitCodeThread(this, out exitCode)) Win32.ThrowLastError(); return exitCode; } /// /// Gets the thread's exit status. /// /// A NT status value. public NtStatus GetExitStatus() { return this.GetBasicInformation().ExitStatus; } private int GetInformationInt32(ThreadInformationClass infoClass) { NtStatus status; int value; int retLength; if ((status = Win32.NtQueryInformationThread( this, infoClass, out value, sizeof(int), out retLength)) >= NtStatus.Error) Win32.ThrowLastError(status); return value; } private IntPtr GetInformationIntPtr(ThreadInformationClass infoClass) { NtStatus status; IntPtr value; int retLength; if ((status = Win32.NtQueryInformationThread( this, infoClass, out value, IntPtr.Size, out retLength)) >= NtStatus.Error) Win32.ThrowLastError(status); return value; } /// /// Gets the thread's I/O priority. /// public int GetIoPriority() { return this.GetInformationInt32(ThreadInformationClass.ThreadIoPriority); } /// /// Gets the last system call the thread made. /// /// A system call number. public int GetLastSystemCall() { int firstArgument; return this.GetLastSystemCall(out firstArgument); } /// /// Gets the last system call the thread made. /// /// The first argument to the last system call. /// A system call number. public unsafe int GetLastSystemCall(out int firstArgument) { NtStatus status; int* data = stackalloc int[2]; int retLength; if ((status = Win32.NtQueryInformationThread( this, ThreadInformationClass.ThreadLastSystemCall, data, sizeof(int) * 2, out retLength)) >= NtStatus.Error) Win32.ThrowLastError(status); firstArgument = data[0]; return data[1]; } /// /// Gets the thread's page priority. /// public int GetPagePriority() { return this.GetInformationInt32(ThreadInformationClass.ThreadPagePriority); } /// /// Gets the thread's priority. /// public int GetPriority() { return this.GetInformationInt32(ThreadInformationClass.ThreadPriority); } /// /// Opens the thread's process. /// /// A process handle. public ProcessHandle GetProcess(ProcessAccess access) { return new ProcessHandle(this, access); } /// /// Gets the thread's parent process' unique identifier. /// /// A process ID. public int GetProcessId() { return this.GetBasicInformation().ClientId.ProcessId; } /// /// Gets the thread's unique identifier. /// /// A thread ID. public int GetThreadId() { return this.GetBasicInformation().ClientId.ThreadId; } /// /// Opens and returns a handle to the thread's token. /// /// A handle to the thread's token. public TokenHandle GetToken() { return GetToken(TokenAccess.All); } /// /// Opens and returns a handle to the thread's token. /// /// The desired access to the token. /// A handle to the thread's token. public TokenHandle GetToken(TokenAccess access) { return new TokenHandle(this, access); } /// /// Gets the thread's Win32 start address. /// public IntPtr GetWin32StartAddress() { return this.GetInformationIntPtr(ThreadInformationClass.ThreadQuerySetWin32StartAddress); } /// /// Causes the thread to impersonate a client thread. /// /// A handle to a client thread. /// The impersonation level to request. public void Impersonate(ThreadHandle clientThreadHandle, SecurityImpersonationLevel impersonationLevel) { NtStatus status; SecurityQualityOfService securityQos = new SecurityQualityOfService(impersonationLevel, false, false); if ((status = Win32.NtImpersonateThread(this, clientThreadHandle, ref securityQos)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Causes the thread to impersonate the anonymous account. /// public void ImpersonateAnonymous() { NtStatus status; if ((status = Win32.NtImpersonateAnonymousToken(this)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Gets whether the system will break (crash) upon the thread terminating. /// public bool IsCritical() { return this.GetInformationInt32(ThreadInformationClass.ThreadBreakOnTermination) != 0; } /// /// Gets whether any I/O request packets (IRPs) are still pending for the thread. /// public bool IsIoPending() { return this.GetInformationInt32(ThreadInformationClass.ThreadIsIoPending) != 0; } /// /// Gets whether the thread is the last in its process. /// public bool IsLastThread() { return this.GetInformationInt32(ThreadInformationClass.ThreadAmILastThread) != 0; } /// /// Gets whether priority boost is enabled for the thread. /// public bool IsPriorityBoostEnabled() { return this.GetInformationInt32(ThreadInformationClass.ThreadPriorityBoost) == 0; } /// /// Gets whether the thread has terminated. /// public bool IsTerminated() { return this.GetInformationInt32(ThreadInformationClass.ThreadIsTerminated) != 0; } /// /// Adds an user-mode asynchronous procedure call (APC) to the thread's APC queue. /// This requires THREAD_SET_CONTEXT access. /// /// The address of the APC procedure. /// The parameter to pass to the procedure. public void QueueApc(IntPtr address, IntPtr parameter) { if (!Win32.QueueUserAPC(address, this, parameter)) Win32.ThrowLastError(); } /// /// Adds an user-mode asynchronous procedure call (APC) to the thread's APC queue. /// This requires THREAD_SET_CONTEXT access. /// /// The delegate to execute.. /// The parameter to pass to the procedure. public void QueueApc(ApcRoutine action, IntPtr parameter) { if (!Win32.QueueUserAPC(action, this, parameter)) Win32.ThrowLastError(); } /// /// Queues a user-mode asynchronous procedure call (APC) to the thread. /// /// The address of the function to execute. /// The first parameter to pass to the function. /// The second parameter to pass to the function. /// The third parameter to pass to the function. public void QueueApc(IntPtr address, IntPtr param1, IntPtr param2, IntPtr param3) { NtStatus status; if ((status = Win32.NtQueueApcThread( this, address, param1, param2, param3 )) >= NtStatus.Error) Win32.ThrowLastError(status); } public void RemoteCall(IntPtr address, IntPtr[] arguments) { this.RemoteCall(address, arguments, false); } public void RemoteCall(IntPtr address, IntPtr[] arguments, bool alreadySuspended) { ProcessHandle processHandle; if (KProcessHacker.Instance != null) processHandle = this.GetProcess(ProcessAccess.VmWrite); else processHandle = new ProcessHandle(this.GetProcessId(), ProcessAccess.VmWrite); using (processHandle) this.RemoteCall(processHandle, address, arguments, alreadySuspended); } public void RemoteCall(ProcessHandle processHandle, IntPtr address, IntPtr[] arguments, bool alreadySuspended) { NtStatus status; if ((status = Win32.RtlRemoteCall( processHandle, this, address, arguments.Length, arguments, false, alreadySuspended )) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Resumes the thread. /// public int Resume() { NtStatus status; int suspendCount; if ((status = Win32.NtResumeThread(this, out suspendCount)) >= NtStatus.Error) Win32.ThrowLastError(status); return suspendCount; } /// /// Sets the thread's base priority. /// /// The thread's base priority. public void SetBasePriority(int basePriority) { this.SetInformationInt32(ThreadInformationClass.ThreadBasePriority, basePriority); } /// /// Sets the thread's base priority. /// /// The base priority of the thread. public void SetBasePriorityWin32(ThreadPriorityLevel basePriority) { if (!Win32.SetThreadPriority(this, (int)basePriority)) Win32.ThrowLastError(); } /// /// Sets the thread's context. /// /// A CONTEXT struct. public unsafe void SetContext(Context context) { if (KProcessHacker.Instance != null) { KProcessHacker.Instance.KphSetContextThread(this, &context); } else { NtStatus status; if ((status = Win32.NtSetContextThread(this, ref context)) >= NtStatus.Error) Win32.ThrowLastError(status); } } /// /// Sets the thread's context. /// /// A CONTEXT struct. public void SetContext(ContextAmd64 context) { NtStatus status; // HACK: To avoid a datatype misalignment error, allocate // some aligned memory. using (var data = new AlignedMemoryAlloc(Utils.SizeOf(16), 16)) { data.WriteStruct(context); if ((status = Win32.NtSetContextThread(this, data)) >= NtStatus.Error) Win32.ThrowLastError(status); } } /// /// Sets the thread's x86 context. The thread's process must /// be running under WOW64. /// /// A CONTEXT struct. public void SetContextWow64(Context context) { NtStatus status; if ((status = Win32.RtlWow64SetThreadContext(this, ref context)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Sets whether the thread is critical. /// /// Whether the thread should be critical. public void SetCritical(bool critical) { this.SetInformationInt32(ThreadInformationClass.ThreadBreakOnTermination, critical ? 1 : 0); } private void SetInformationInt32(ThreadInformationClass infoClass, int value) { NtStatus status; if ((status = Win32.NtSetInformationThread( this, infoClass, ref value, sizeof(int))) >= NtStatus.Error) Win32.ThrowLastError(status); } private void SetInformationIntPtr(ThreadInformationClass infoClass, IntPtr value) { NtStatus status; if ((status = Win32.NtSetInformationThread( this, infoClass, ref value, sizeof(int))) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Sets the thread's priority. /// /// The thread's priority. public void SetPriority(int priority) { this.SetInformationInt32(ThreadInformationClass.ThreadPriority, priority); } /// /// Sets the thread's priority boost. /// /// Whether priority boost will be enabled. public void SetPriorityBoost(bool enabled) { this.SetInformationInt32(ThreadInformationClass.ThreadPriorityBoost, enabled ? 0 : 1); } /// /// Sets the thread's impersonation token. /// /// /// A handle to a token. Specify null to cause the thread to stop /// impersonating. /// public void SetToken(TokenHandle tokenHandle) { this.SetInformationIntPtr(ThreadInformationClass.ThreadImpersonationToken, tokenHandle ?? IntPtr.Zero); } /// /// Suspends the thread. /// public int Suspend() { NtStatus status; int suspendCount; if ((status = Win32.NtSuspendThread(this, out suspendCount)) >= NtStatus.Error) Win32.ThrowLastError(status); return suspendCount; } /// /// Terminates the thread. /// public void Terminate() { this.Terminate(NtStatus.Success); } /// /// Terminates the thread. /// /// The exit status. public void Terminate(NtStatus exitStatus) { if (KProcessHacker.Instance != null) { try { KProcessHacker.Instance.KphTerminateThread(this, exitStatus); return; } catch (WindowsException ex) { if (ex.ErrorCode != Win32Error.NotSupported) throw ex; } } NtStatus status; if ((status = Win32.NtTerminateThread(this, exitStatus)) >= NtStatus.Error) Win32.ThrowLastError(status); } /// /// Walks the call stack for the thread. /// /// A callback to execute. public void WalkStack(WalkStackDelegate walkStackCallback) { this.WalkStack(walkStackCallback, OSVersion.Architecture); } /// /// Walks the call stack for the thread. /// /// A callback to execute. /// /// The type of stack walk. On 32-bit systems, this value is ignored. /// On 64-bit systems, this value can be set to I386 to walk the /// 32-bit stack. /// public void WalkStack(WalkStackDelegate walkStackCallback, OSArch architecture) { if (KProcessHacker.Instance != null) { // Use KPH to open the parent process. using (var phandle = this.GetProcess(ProcessAccess.QueryInformation | ProcessAccess.VmRead)) this.WalkStack(phandle, walkStackCallback, architecture); } else { // We need to duplicate the handle to get QueryInformation access. using (var dupThreadHandle = this.Duplicate(OSVersion.MinThreadQueryInfoAccess)) using (var phandle = new ProcessHandle( ThreadHandle.FromHandle(dupThreadHandle).GetBasicInformation().ClientId.ProcessId, ProcessAccess.QueryInformation | ProcessAccess.VmRead )) { this.WalkStack(phandle, walkStackCallback, architecture); } } } /// /// Walks the call stack for the thread. /// /// A handle to the thread's parent process. /// A callback to execute. public unsafe void WalkStack(ProcessHandle parentProcess, WalkStackDelegate walkStackCallback) { this.WalkStack(parentProcess, walkStackCallback, OSVersion.Architecture); } /// /// Walks the call stack for the thread. /// /// A handle to the thread's parent process. /// A callback to execute. /// /// The type of stack walk. On 32-bit systems, this value is ignored. /// On 64-bit systems, this value can be set to I386 to walk the /// 32-bit stack. /// public unsafe void WalkStack(ProcessHandle parentProcess, WalkStackDelegate walkStackCallback, OSArch architecture) { bool suspended = false; // Suspend the thread to avoid inaccurate thread stacks. try { this.Suspend(); suspended = true; } catch (WindowsException) { suspended = false; } // Use KPH for reading memory if we can. ReadProcessMemoryProc64 readMemoryProc = null; if (KProcessHacker.Instance != null) { readMemoryProc = new ReadProcessMemoryProc64( delegate(IntPtr processHandle, ulong baseAddress, IntPtr buffer, int size, out int bytesRead) { return KProcessHacker.Instance.KphReadVirtualMemorySafe( ProcessHandle.FromHandle(processHandle), (int)baseAddress, buffer, size, out bytesRead); }); } try { // x86/WOW64 stack walk. if (IntPtr.Size == 4 || (IntPtr.Size == 8 && architecture == OSArch.I386)) { Context context = new Context(); context.ContextFlags = ContextFlags.All; if (IntPtr.Size == 4) { // Get the context. this.GetContext(ref context); } else { // Get the WOW64 x86 context. this.GetContextWow64(ref context); } // Set up the initial stack frame structure. var stackFrame = new StackFrame64(); stackFrame.AddrPC.Mode = AddressMode.AddrModeFlat; stackFrame.AddrPC.Offset = (ulong)context.Eip; stackFrame.AddrStack.Mode = AddressMode.AddrModeFlat; stackFrame.AddrStack.Offset = (ulong)context.Esp; stackFrame.AddrFrame.Mode = AddressMode.AddrModeFlat; stackFrame.AddrFrame.Offset = (ulong)context.Ebp; while (true) { using (Win32.DbgHelpLock.AcquireContext()) { if (!Win32.StackWalk64( MachineType.I386, parentProcess, this, ref stackFrame, ref context, readMemoryProc, Win32.SymFunctionTableAccess64, Win32.SymGetModuleBase64, IntPtr.Zero )) break; } // If we got an invalid eip, break. if (stackFrame.AddrPC.Offset == 0) break; // Execute the callback. if (!walkStackCallback(new ThreadStackFrame(ref stackFrame))) break; } } // x64 stack walk. else if (IntPtr.Size == 8) { ContextAmd64 context = new ContextAmd64(); context.ContextFlags = ContextFlagsAmd64.All; // Get the context. this.GetContext(ref context); // Set up the initial stack frame structure. var stackFrame = new StackFrame64(); stackFrame.AddrPC.Mode = AddressMode.AddrModeFlat; stackFrame.AddrPC.Offset = (ulong)context.Rip; stackFrame.AddrStack.Mode = AddressMode.AddrModeFlat; stackFrame.AddrStack.Offset = (ulong)context.Rsp; stackFrame.AddrFrame.Mode = AddressMode.AddrModeFlat; stackFrame.AddrFrame.Offset = (ulong)context.Rbp; while (true) { using (Win32.DbgHelpLock.AcquireContext()) { if (!Win32.StackWalk64( MachineType.Amd64, parentProcess, this, ref stackFrame, ref context, readMemoryProc, Win32.SymFunctionTableAccess64, Win32.SymGetModuleBase64, IntPtr.Zero )) break; } // If we got an invalid rip, break. if (stackFrame.AddrPC.Offset == 0) break; // Execute the callback. if (!walkStackCallback(new ThreadStackFrame(ref stackFrame))) break; } } } finally { // If we suspended the thread before, resume it. if (suspended) { try { this.Resume(); } catch (WindowsException) { } } } } } public class ThreadStackFrame { private IntPtr _pcAddress; private IntPtr _returnAddress; private IntPtr _frameAddress; private IntPtr _stackAddress; private IntPtr _bStoreAddress; private IntPtr[] _params; internal ThreadStackFrame(ref StackFrame64 stackFrame) { _pcAddress = new IntPtr((long)stackFrame.AddrPC.Offset); _returnAddress = new IntPtr((long)stackFrame.AddrReturn.Offset); _frameAddress = new IntPtr((long)stackFrame.AddrFrame.Offset); _stackAddress = new IntPtr((long)stackFrame.AddrStack.Offset); _bStoreAddress = new IntPtr((long)stackFrame.AddrBStore.Offset); _params = new IntPtr[4]; for (int i = 0; i < 4; i++) _params[i] = new IntPtr(stackFrame.Params[i]); } public IntPtr PcAddress { get { return _pcAddress; } } public IntPtr ReturnAddress { get { return _returnAddress; } } public IntPtr FrameAddress { get { return _frameAddress; } } public IntPtr StackAddress { get { return _stackAddress; } } public IntPtr BStoreAddress { get { return _bStoreAddress; } } public IntPtr[] Params { get { return _params; } } } }