在Visual Studio中使用自定义的序言和Epilog代码编写裸函数 [英] Writing naked functions with custom prolog and epilog code in Visual Studio

查看:143
本文介绍了在Visual Studio中使用自定义的序言和Epilog代码编写裸函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing some plugin code in a dll that is called by a host over which I have no control.

主机假定插件已导出为__stdcall函数.会告知主机函数的名称和所需参数的详细信息,并通过LoadLibrary,GetProcAddress动态填充对它的调用,然后手动将参数推入堆栈.

The host assumes that the plugins are exported as __stdcall functions. The host is told the name of the function and the details of the arguments that it expects and dynamically crufts up a call to it via LoadLibrary, GetProcAddress and manually pushing the arguments onto the stack.

通常,插件dll会公开一个恒定的接口.我的插件公开了一个在dll加载时配置的接口.为此,我的插件提供了一组在编译dll时定义的标准入口点,并根据需要将它们分配给要公开的内部功能.

Usually plugin dlls expose a constant interface. My plugin exposes an interface that is configured at dll load time. To achieve this my plugin exposes a set of standard entry points that are defined at the time the dll is compiled and it allocates them as needed to internal functionality that's being exposed.

每个内部函数可能采用不同的参数,但这会与物理入口点名称一起传递给主机.我所有的物理dll入口点都定义为采用单个void *指针,我自己通过处理已传递给主机的第一个参数和已知参数列表的偏移量,自己从堆栈中封送后续参数.

Each of the internal functions may take different arguments but this is communicated to the host along with the physical entrypoint name. All of my physical dll entrypoints are defined to take a single void * pointer and I marshal subsequent parameters from the stack myself by working from offsets from the first argument and the known argument list that has been communicated to the host.

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

The host can successfully call the functions in my plugin with the correct arguments and all works well... However, I'm aware that a) my functions aren't cleaning up the stack as they're supposed to as they're defined as __stdcall functions that take a 4 byte pointer and so they always do a 'ret 4' at the end even if the caller has pushed more arguments onto the stack. and b) I can't deal with functions that take no arguments as the ret 4 will pop 4 bytes too many off of the stack on my return.

已经从我的插件中找到了主机的调用代码,我可以看到实际上a)没什么大不了的;主机会丢失一些堆栈空间,直到它从分派调用中返回为止,此时主机会清理其堆栈框架,从而清理我的垃圾;但是...

Having traced out of my plugin into the host's calling code I can see that actually a) isn't that big a deal; the host loses some stack space until it returns from the dispatch call at which point it cleans up its stack frame which cleans up my rubbish; however...

我可以通过切换到__cdecl而不彻底清除来解决b).我想我可以通过切换到裸函数并编写自己的通用参数清除代码来解决a).

I can solve b) by switching to __cdecl and not cleaning up at all. I assume I can solve a) by switching to naked functions and writing my own generic argument clean up code.

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

Since I know the amount of argument space used by the function that was just called I had hoped that it would be as simple as:

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需要一个编译时间常数...有什么建议吗?

But that doesn't work as ret needs a compile time constant... Any suggestions?

已更新:

感谢罗伯·肯尼迪(Rob Kennedy)的建议,这个建议似乎行得通...

Thanks to Rob Kennedy's suggestions I've got to this, which seems to work...

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需要常量参数,因此您需要安排函数具有常量数量的参数,但是这种情况仅在此时才需要您已经准备好从函数中返回.因此,在函数结束之前,请执行以下操作:

Since ret requires a constant argument, you need to arrange for your function to have a constant number of parameters, but that situation is only required at the point you're ready to return from the function. So, just before the end of the function, do this:

  1. 从堆栈顶部弹出返回地址,并将其存储在临时地址中; ECX是个好地方.
  2. 通过分别弹出每个参数或直接调整ESP,从堆栈中删除可变数量的参数.
  3. 将返回地址推回堆栈.
  4. ret与常量参数一起使用.
  1. Pop the return address off the top of the stack and store it in a temporary; ECX is a good place.
  2. Remove the variable number of arguments from the stack, either by popping each one off individually, or by adjusting ESP directly.
  3. Push the return address back onto the stack.
  4. Use ret with a constant argument.

顺便说一句,在通常情况下,您提到的问题(a)确实是一个问题.您只是很幸运,调用方似乎总是使用框架指针而不是堆栈指针来引用其自身的局部变量.但是,不需要使用函数来执行此操作,并且不能保证宿主程序的将来版本将继续以这种方式工作.编译器还可能仅在调用期间将某些寄存器值保存在堆栈中,然后期望以后能够再次弹出它们.您的代码会破坏这一点.

Incidentally, the issue you refer to as (a) really is a problem, in the general case. You've just been lucky that the caller seems to always refer to its own local variables using a frame pointer instead of the stack pointer. Functions aren't required to do that, though, and there's no guarantee that a future version of the host program will continue to work that way. The compiler is also liable to save some register values on the stack only for the duration of the call, and then expect to be able to pop them off again afterward. Your code would break that.

这篇关于在Visual Studio中使用自定义的序言和Epilog代码编写裸函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆