2009-05-20 22 views
4

我在一家由一臺主機上,我無法控制調用的DLL寫一些插件代碼。編寫定製的序言和結尾代碼裸體功能在Visual Studio

主機假定插件導出爲__stdcall函數。主機被告知函數的名稱和它期望的參數的細節,並通過LoadLibrary,GetProcAddress動態地調用它,然後手動將參數推送到堆棧上。

通常plugin dlls會暴露一個常量界面。我的插件公開了一個在dll加載時配置的接口。爲了實現這一點,我的插件公開了一組標準入口點,這些入口點是在編譯dll時定義的,並根據需要將它們分配給正在公開的內部功能。

每個內部功能可以採用不同的參數,但,這是與物理入口點名稱一起傳送到主機。我所有的物理dll入口點都被定義爲採用單個void *指針,並通過從第一個參數的偏移量和已傳送給主機的已知參數列表中進行處理,從而自我編排堆棧中的後續參數。

主機可以成功地調用我的插件的功能與正確的參數和一切運作良好。不過,我知道,A)作爲他們應該爲我的功能都沒有清理堆棧它們被定義爲採用4字節指針的__stdcall函數,因此即使調用者將更多參數推送到堆棧,它們也總是在最後執行'ret 4'。和b)我不能處理沒有參數的函數,因爲ret 4會在我返回時彈出4個字節。

已經描繪出我的插件插入主機的調用代碼,我可以看到,實際上是一個)不是什麼大不了的事;主機會丟失一些堆棧空間,直到它從調度呼叫返回時清除堆棧框架,清理垃圾;但是...

我可以通過切換到__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工作需要編譯時不斷...有什麼建議?

更新:

感謝Rob肯尼迪的建議,我已經得到了這一點,這似乎工作...

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

這是否正確?

+0

我個人不會打擾幀指針,因爲它從來沒有使用過,但除此之外,你的解決方案看起來不錯。 – 2009-07-01 14:52:23

+0

本來我是從框架指針索引args空間和返回值空間,但是之後我沒有將返回值從eax移出來,然後返回到它並且很好,我猜框架指針的需要消失了。我想我應該刪除它...感謝您的幫助。 – 2009-07-01 22:05:07

回答

4

由於ret需要一個常量參數,因此您需要安排函數的參數數量不變,但只有在您準備從函數返回時才需要這種情況。因此,在函數結束之前,請執行以下操作:

  1. 彈出堆棧頂部的返回地址並將其存儲在臨時文件中; ECX是個好地方。
  2. 通過單獨彈出每個參數或直接調整ESP,從堆棧中除去可變數量的參數。
  3. 將返回地址推回堆棧。
  4. 使用ret和一個常量參數。

順便說一句,在一般情況下,您稱爲(a)的問題確實是一個問題。你很幸運,調用者似乎總是使用幀指針而不是堆棧指針來引用它自己的局部變量。儘管如此,功能並不是必需的,並且不能保證主機程序的未來版本將繼續以這種方式工作。只有在調用期間,編譯器纔有可能在棧上保存一些寄存器值,然後期望能夠在之後再次彈出它們。你的代碼會打破這個。

相關問題