通过Marshal.GetFunctionPointerForDelegate返回的指针进行调用会导致访问冲突

问题描述

我正在使用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字节的未使用空间。