通过 Marshal.GetFunctionPointerForDelegate 返回的指针调用导致访问冲突
Calling through pointer returned by Marshal.GetFunctionPointerForDelegate causes Access Violation
我正在使用 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, 0x00, 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, 0x83, 0xC4, 0x20, // 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 堆栈 usage/calling 约定。
如有任何见解,我们将不胜感激 - 您甚至可以避免一些键盘被打碎:-)
在调用函数中,堆栈是 16 字节对齐的。当它调用本机函数时,它会压入 return 地址,因此堆栈现在错位了 8 个字节。因此,在您的函数中,您需要减去 8 的 奇数 倍数以重新对齐它,然后再进行另一个调用。
Windows 还需要在调用前堆栈顶部未使用的 32 个字节的 space。 (大概这就是 sub 32 已经在那里的原因。)
所以解决方案是从 rsp 中减去 40 而不是 32。
当您扩展此函数以添加功能时,您可能需要压入寄存器 and/or 在堆栈上分配额外的内存。在这样做的同时,一定要保持 16 字节堆栈对齐,并在堆栈的 top 处保持未使用的 32 字节 space。
我正在使用 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, 0x00, 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, 0x83, 0xC4, 0x20, // 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 堆栈 usage/calling 约定。
如有任何见解,我们将不胜感激 - 您甚至可以避免一些键盘被打碎:-)
在调用函数中,堆栈是 16 字节对齐的。当它调用本机函数时,它会压入 return 地址,因此堆栈现在错位了 8 个字节。因此,在您的函数中,您需要减去 8 的 奇数 倍数以重新对齐它,然后再进行另一个调用。
Windows 还需要在调用前堆栈顶部未使用的 32 个字节的 space。 (大概这就是 sub 32 已经在那里的原因。)
所以解决方案是从 rsp 中减去 40 而不是 32。
当您扩展此函数以添加功能时,您可能需要压入寄存器 and/or 在堆栈上分配额外的内存。在这样做的同时,一定要保持 16 字节堆栈对齐,并在堆栈的 top 处保持未使用的 32 字节 space。