【问题标题】:Calling through pointer returned by Marshal.GetFunctionPointerForDelegate causes Access Violation通过 Marshal.GetFunctionPointerForDelegate 返回的指针调用导致访问冲突
【发布时间】:2020-09-02 00:14:29
【问题描述】:

我正在使用 Marshal.GetDelegateForFunctionPointer() 从 C# 调用本机 x64 代码。我将一个指针作为参数传递到本机代码中。我从 Marshal.GetFunctionPointerForDelegate() 获取指针,传入 C# 委托。在本机代码中执行时,我尝试使用传递的指针回调到 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 堆栈使用/调用约定。

任何见解都将不胜感激 - 您甚至可以避免一些键盘被打碎:-)

【问题讨论】:

  • @prl:或者只是用jmp 尾调用而不是调用/调用,除非你需要回溯来包含这个堆栈帧。还对mov rcx, 15 使用更有效的编码,特别是5 字节mov ecx, 15 编码,而不是使用REX 前缀的7 字节方式。如果您使用 NASM 将 asm 源代码转换为机器代码,NASM 将为您进行优化。
  • @Peter,我确信这个小函数是他想要开始工作的真正有用的东西的占位符,所以只适用于这个存根的建议并不是很有帮助。
  • @prl:成功了!感谢您的帮助!

标签: c# windows assembly x86-64


【解决方案1】:

在调用函数中,堆栈是 16 字节对齐的。当它调用本机函数时,它会压入返回地址,因此堆栈现在错位了 8 个字节。因此,在您的函数中,您需要减去 8 的 odd 倍数以重新对齐它,然后再进行另一个调用。

Windows 还需要调用前堆栈顶部的 32 字节未使用空间。 (大概这就是为什么 sub 32 已经在那里了。)

所以解决方案是从 rsp 中减去 40 而不是 32。

当您扩展此函数以添加功能时,您可能需要推送寄存器和/或在堆栈上分配额外的内存。这样做时,请务必保持 16 字节的堆栈对齐,并在堆栈的顶部保持 32 字节的未使用空间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    相关资源
    最近更新 更多