问题描述
我正在使用Marshal.GetDelegateForFunctionPointer()从C#调用本机x64代码。我将指针作为参数传递给本机代码。我从传递给C#委托的Marshal.GetFunctionPointerForDelegate()获取了指针。在本机代码中执行时,我尝试使用传递的指针回调到C#中。这将导致访问冲突。我相信这是因为本机代码在尝试回调之前未正确设置堆栈,但是我无法确定应如何进行。我将其简化为以下回购协议:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace AsmCallbackRepo
{
unsafe class Program
{
[DllImport("kernel32.dll",SetLastError = true,ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess,IntPtr lpAddress,uint dwSize,AllocationType flAllocationType,MemoryProtection flProtect);
[Flags]
public enum AllocationType
{
Commit = 0x1000,Reserve = 0x2000,Decommit = 0x4000,Release = 0x8000,Reset = 0x80000,Physical = 0x400000,TopDown = 0x100000,WriteWatch = 0x200000,LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,ExecuteRead = 0x20,ExecuteReadWrite = 0x40,ExecuteWritecopy = 0x80,NoAccess = 0x01,ReadOnly = 0x02,ReadWrite = 0x04,Writecopy = 0x08,GuardModifierflag = 0x100,NoCacheModifierflag = 0x200,WriteCombineModifierflag = 0x400
}
static readonly byte[] i64 = new byte[]
{
0xcc,// int 3 debug break
0x48,0x89,0xC8,// mov rax,rcx parm 1: call-back address
0x48,0xC7,0xC1,0x0F,0x00,// mov rcx,15 input parm for call-back
0x48,0x83,0xEC,0x20,// sub rsp,32 space for register home storage
0xFF,0xD0,// call rax call the managed call-back
0x48,0xC4,// add rsp,32 release register home storage space
0xC3,// ret return to managed caller
};
delegate void CallBackDel(long parm); // prototype of call-back
delegate void NativeDel(void* arg); // prototype of x64 native method
static void Main(string[] args)
{
CallBackDel callback = new CallBackDel(CallBack);
IntPtr memory = VirtualAllocEx(Process.GetCurrentProcess().Handle,IntPtr.Zero,4096,AllocationType.Commit,MemoryProtection.ExecuteReadWrite);
byte* ptr = (byte*)memory.ToPointer();
// copy x64 native code to allocated memory segment
for (int i = 0; i < i64.Length; ++i)
{
ptr[i] = i64[i];
}
// wrap native code in a delegate
NativeDel i64Action = (NativeDel)Marshal.GetDelegateForFunctionPointer(new IntPtr(ptr),typeof(NativeDel));
Debugger.Break();
// get pointer for call-back
IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(callback);
// call native x64 copied to allocated memory passing address of call-back
i64Action(callbackPtr.ToPointer());
}
static void CallBack(long parm)
{
Debugger.Break();
Console.WriteLine($"CallBack was called with value {parm}");
}
}
}
在WinDbg中进行调试在调用本机代码之前,我先击了Break,而Break在本机代码的顶部。我可以单步执行本机代码,直到以本机代码执行CALL RAX。这时,我遇到访问冲突,试图保存浮点寄存器。 该代码打算针对64位进行编译,我正在尝试使本机代码符合x64堆栈使用/调用约定。
任何见解将不胜感激-您甚至可以节省一些键盘,以免被打碎:-)
解决方法
在调用函数中,堆栈是16字节对齐的。当它调用本机函数时,它将推送返回地址,因此堆栈现在未对齐8个字节。因此,在您的函数中,您需要减去8的奇数倍数以重新对齐,然后再进行另一个调用。
Windows在调用之前还需要在堆栈顶部使用32字节的未使用空间。 (大概这就是为什么sub 32已经存在的原因。)
因此解决方案是从rsp中减去40,而不是32。
在扩展此功能以添加功能时,可能需要压入寄存器和/或在堆栈上分配其他内存。这样做时,请确保保持16字节的堆栈对齐,并在堆栈的顶部处保持32字节的未使用空间。