代码之家  ›  专栏  ›  技术社区  ›  Len Holgate

在VisualStudio中使用自定义prolog和epilog代码编写裸函数

  •  4
  • Len Holgate  · 技术社区  · 15 年前

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

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

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

    主机可以用正确的参数成功地调用我插件中的函数,并且一切都很好。。。但是,我知道a)我的函数没有像它们被定义为u stdcall函数那样清理堆栈,它们使用4字节指针,因此即使调用方将更多参数推到堆栈上,它们也总是在最后执行“ret 4”。b)我不能处理不带参数的函数,因为ret4在返回时会从堆栈中弹出太多的4字节。

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

    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                        
       }
    }
    

    这个看起来对吗?

    1 回复  |  直到 15 年前
        1
  •  4
  •   Rob Kennedy    15 年前

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

    1. 从堆栈顶部弹出返回地址,并将其存储在临时文件夹中; ECX
    2. 从堆栈中删除可变数量的参数,可以通过单独弹出每个参数,也可以通过调整 ESP
    3. 将返回地址推回到堆栈上。
    4. ret

    顺便说一句,在一般情况下,您称为(a)的问题实际上是一个问题。幸运的是,调用方似乎总是使用帧指针而不是堆栈指针引用自己的局部变量。不过,函数不需要这样做,也不能保证宿主程序的未来版本会继续以这种方式工作。编译器还可能只在调用期间在堆栈上保存一些寄存器值,然后期望能够在调用后再次弹出它们。你的代码会破坏它。