visual-studio-2008 - 在 Visual Studio 中使用自定义 prolog 和 epilog 代码编写裸函数

标签 visual-studio-2008 plugins calling-convention stdcall

我正在一个 dll 中编写一些插件代码,该代码由我无法控制的主机调用。

主机假定插件作为 __stdcall 函数导出。主机被告知函数的名称和它期望的参数的详细信息,并通过 LoadLibrary、GetProcAddress 和手动将参数推送到堆栈上动态地整理对它的调用。

通常插件 dll 会暴露一个常量接口(interface)。我的插件公开了一个在 dll 加载时配置的接口(interface)。为了实现这一点,我的插件公开了一组在编译 dll 时定义的标准入口点,并根据需要将它们分配给正在公开的内部功能。

每个内部函数都可以采用不同的参数,但是这会与物理入口点名称一起传递给主机。我所有的物理 dll 入口点都被定义为采用单个 void * 指针,我自己通过从第一个参数的偏移量和已与主机通信的已知参数列表来编码后续参数。

主机可以使用正确的参数成功调用我的插件中的函数并且一切正常......但是,我知道a)我的函数没有像定义的那样清理堆栈作为 __stdcall 函数,它采用 4 字节指针,因此即使调用者将更多参数压入堆栈,它们也总是在末尾执行“ret 4”。 b) 我无法处理不带参数的函数,因为 ret 4 会在我返回时从堆栈中弹出太多 4 个字节。

从我的插件中追踪到主机的调用代码后,我可以看到实际上 a) 没什么大不了的;主机丢失了一些堆栈空间,直到它从调度调用返回,此时它清理了清理我的垃圾的堆栈帧;然而...

我可以通过切换到 __cdecl 而根本不清理来解决 b)。我假设我可以通过切换到裸函数并编写我自己的通用参数清理代码来解决 a)。

因为我知道刚刚调用的函数使用的参数空间量,所以我希望它会像这样简单:

extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{                                                                                                        
   size_t argumentSpaceUsed;
   {
      void *pX = RealEntryPoint(
         reinterpret_cast<ULONG_PTR>(&pArg1), 
         argumentSpaceUsed);

      __asm
      {
         mov eax, dword ptr pX
      }
   }
   __asm
   {
      ret argumentSpaceUsed
   }
}

但这不起作用,因为 ret 需要一个编译时间常数......有什么建议吗?

更新:

感谢罗布肯尼迪的建议,我得到了这个,这似乎工作......
extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{      
   __asm {                                                                                                        
      push ebp          // Set up our stack frame            
      mov ebp, esp  
      mov eax, 0x0      // Space for called func to return arg space used, init to 0            
      push eax          // Set up stack for call to real Entry point
      push esp
      lea eax, pArg1                
      push eax                      
      call RealEntryPoint   // result is left in eax, we leave it there for our caller....         
      pop ecx 
      mov esp,ebp       // remove our stack frame
      pop ebp  
      pop edx           // return address off
      add esp, ecx      // remove 'x' bytes of caller args
      push edx          // return address back on                   
      ret                        
   }
}

这看起来对吗?

最佳答案

由于ret需要一个常量参数,您需要安排您的函数具有恒定数量的参数,但这种情况仅在您准备从函数返回时才需要。因此,就在函数结束之前,执行以下操作:

  • 将返回地址从栈顶弹出,并暂存; ECX是个好地方。
  • 从堆栈中删除可变数量的参数,或者通过单独弹出每个参数,或者通过调整 ESP直接地。
  • 将返回地址推回堆栈。
  • 使用ret有一个不变的论点。

  • 顺便说一句,在一般情况下,您所说的(a)问题确实是一个问题。您很幸运,调用者似乎总是使用帧指针而不是堆栈指针来引用自己的局部变量。但是,函数不需要这样做,并且不能保证 future 版本的主机程序将继续以这种方式工作。编译器还可能仅在调用期间将一些寄存器值保存在堆栈上,然后期望能够在之后再次将它们弹出。你的代码会打破这一点。

    关于visual-studio-2008 - 在 Visual Studio 中使用自定义 prolog 和 epilog 代码编写裸函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/886836/

    相关文章:

    c# - 有没有办法确定显示器的物理尺寸?

    c# - 如何自动化定期执行作业的 WCF 服务

    c - 如何将 "goto"转换为 c 中的不同函数?

    c# - 生成licenses.licx

    asp.net - 如何使用 javascript 在客户端重新加载更新面板?

    javascript - 删除父元素并重新 append 子元素

    php - 如何在 PHP 中编写简单的插件支持?

    css - Netbeans 7.2 + Less 插件

    c++ - __cdecl、__stdcall 和 __fastcall 的调用方式完全相同?

    C++如何使用不支持的调用约定调用代码