要使 APC 回调正常工作,您需要一个线程句柄(与线程 ID 不同)。我还更新了 PInvokes 上的属性。
还要记住,线程需要处于“可警报”等待状态才能调用 APC(Thread.Sleep 会给我们)。所以如果线程忙于做事,它可能不会被调用。
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThread", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetCurrentThread();
[DllImport("kernel32.dll", EntryPoint = "QueueUserAPC", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern uint QueueUserAPC(ApcDelegate pfnAPC, IntPtr hThread, UIntPtr dwData);
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
public delegate void ApcDelegate(UIntPtr dwParam);
[DllImport("kernel32.dll", EntryPoint = "DuplicateHandle", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool DuplicateHandle([In] System.IntPtr hSourceProcessHandle, [In] System.IntPtr hSourceHandle, [In] System.IntPtr hTargetProcessHandle, out System.IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAsAttribute(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
static IntPtr hThread;
public static void SomeMethod(object value)
{
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), out hThread, 0, false, 2);
while (true)
{
Console.WriteLine(".");
Thread.Sleep(1000);
}
}
private static void APC(UIntPtr data)
{
Console.WriteLine("Callback invoked");
}
static void Main(string[] args)
{
Console.WriteLine("in Main\n");
Thread t = new Thread(Program.SomeMethod);
t.Start();
Thread.Sleep(1000); // wait until the thread fills out the hThread member -- don't do this at home, this isn't a good way to synchronize threads...
uint result = QueueUserAPC(APC, hThread, (UIntPtr)0);
Console.ReadLine();
}
编辑:
CLR如何注入异常
给定线程函数的这个循环:
while (true)
{
i = ((i + 7) * 3 ^ 0x73234) & 0xFFFF;
}
然后我.Abort
编辑了线程并查看了本机堆栈跟踪
...
ntdll!KiUserExceptionDispatcher
KERNELBASE!RaiseException
clr!RaiseComPlusException
clr!RedirectForThrowControl2
clr!RedirectForThrowControl_RspAligned
clr!RedirectForThrowControl_FixRsp
csTest.Program.SomeMethod(System.Object)
...
查看RedirectForThrowControl_FixRsp
调用的返回地址,它指向我的循环中间,没有跳转或调用:
nop
mov eax,dword ptr [rbp+8]
add eax,7 // code flow would return to execute this line
lea eax,[rax+rax*2]
xor eax,73234h
and eax,0FFFFh
mov dword ptr [rbp+8],eax
nop
mov byte ptr [rbp+18h],1
jmp 000007fe`95ba02da // loop back to the top
因此,显然 CLR 实际上是在修改相关线程的指令指针,以从正常流程中物理拉出控制。他们显然需要提供几个包装器来修复和恢复所有堆栈寄存器以使其正常工作(因此恰当命名_FixRsp
和_RspAligned
API。
在一个单独的测试中,我只是Console.Write()
在我的线程循环中进行了调用,看起来 CLR 在物理调用之前注入了一个测试WriteFile
:
KERNELBASE!RaiseException
clr!RaiseTheExceptionInternalOnly
clr! ?? ::FNODOBFM::`string'
clr!HelperMethodFrame::PushSlowHelper
clr!JIT_RareDisableHelper
mscorlib_ni!DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
mscorlib_ni!System.IO.__ConsoleStream.WriteFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean)