簡體   English   中英

堆棧清理不起作用(__stdcall MASM 函數)

[英]Stack cleanup not working (__stdcall MASM function)

這里發生了一些奇怪的事情。 Visual Studio 讓我知道 ESP 值未正確保存,但我看不到代碼中有任何錯誤(32 位、windows、__stdcall)

MASM 代碼:

.MODE FLAT, STDCALL
...
    memcpy PROC dest : DWORD, source : DWORD, size : DWORD
    MOV EDI, [ESP+04H]
    MOV ESI, [ESP+08H]
    MOV ECX, [ESP+0CH]
    AGAIN_:
    LODSB
    STOSB
    LOOP AGAIN_
    RETN 0CH
    memcpy ENDP

我將 12 個字節(0xC)傳遞給堆棧然后清理它。 我通過查看符號確認了函數符號類似於“memcpy@12”,因此它確實找到了正確的符號

這是 C 原型:

extern void __stdcall * _memcpy(void*,void*,unsigned __int32);

編譯為 32 位。 function 復制了 memory(我可以在調試器中看到),但堆棧清理似乎不起作用

編輯:

MASM 代碼:

__MyMemcpy PROC _dest : DWORD, _source : DWORD, _size : DWORD
MOV EDI, DWORD PTR [ESP + 04H]
MOV ESI, DWORD PTR [ESP + 08H]
MOV ECX, DWORD PTR [ESP + 0CH]
PUSH ESI
PUSH EDI
__AGAIN:
LODSB
STOSB
LOOP __AGAIN
POP EDI
POP ESI
RETN 0CH
__MyMemcpy ENDP

C 代碼:

extern void __stdcall __MyMemcpy(void*, void*, int);

typedef struct {
 void(__stdcall*MemCpy)(void*,void*,int);
}MemFunc;

int initmemfunc(MemFunc*f){
f->MemCpy=__MyMemcpy
}

當我這樣稱呼它時,我得到了錯誤:

MemFunc mf={0};
initmemfunc(&mf);
mf.MemCpy(dest,src,size);

當我這樣稱呼它時,我不會:

__MyMemcpy(dest,src,size)

堆棧損壞的原因是MASM“秘密”將序言代碼插入您的function。 當我添加禁用它的選項時,function 現在對我有用。

當您在 C 代碼中切換到裝配模式時,您可以看到這一點,然后進入您的 function。 當已經在匯編源中時,VS 似乎沒有切換到匯編模式。

.586
.MODEL FLAT,STDCALL
OPTION PROLOGUE:NONE 

.CODE

mymemcpy PROC dest:DWORD, src:DWORD, sz:DWORD
    MOV EDI, [ESP+04H]
    MOV ESI, [ESP+08H]
    MOV ECX, [ESP+0CH]
AGAIN_:
    LODSB
    STOSB
    LOOP AGAIN_
    RETN 0CH
mymemcpy ENDP

END

由於您提供了對您的問題和評論的更新,建議您禁用使用 MASM PROC指令創建的函數的序言和結尾代碼生成,我懷疑您的代碼看起來像這樣:

.MODEL FLAT, STDCALL
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE

.CODE

__MyMemcpy PROC _dest : DWORD, _source : DWORD, _size : DWORD
    MOV EDI, DWORD PTR [ESP + 04H]
    MOV ESI, DWORD PTR [ESP + 08H]
    MOV ECX, DWORD PTR [ESP + 0CH]
    PUSH ESI
    PUSH EDI
__AGAIN:
    LODSB
    STOSB
    LOOP __AGAIN
    POP EDI
    POP ESI
    RETN 0CH
__MyMemcpy ENDP

END

關於此代碼的注釋:請注意,如果您的源緩沖區和目標緩沖區重疊,這可能會導致問題。 如果緩沖區不重疊,那么您正在做的事情應該有效。 您可以通過標記指針__restrict來避免這種情況。 __restrict是一個 MSVC/C++ 擴展,它將作為對編譯器的提示,即參數不與另一個參數重疊。 這可能允許編譯器潛在地警告這種情況,因為您的匯編代碼對於這種情況是不安全的。 你的原型可以寫成:

extern void __stdcall __MyMemcpy( void* __restrict, void* __restrict, int);

typedef struct {
    void(__stdcall* MemCpy)(void* __restrict, void* __restrict, int);
}MemFunc;

您正在使用PROC ,但沒有利用它提供(或模糊)的任何潛在功能。 您已使用OPTION指令禁用 PROLOGUE 和 EPILOGUE 生成。 您正確使用RET 0Ch從堆棧中清除 arguments 的 12 個字節。

從 STDCALL 調用約定的角度來看,您的代碼是正確的,因為它與堆棧使用有關。 存在一個嚴重的問題,即 Microsoft Windows STDCALL 調用約定要求調用者保留它使用的所有寄存器,但EAXECXEDX除外。 您破壞了EDIESI ,並且在使用它們之前都需要保存它們。 在您的代碼中,您可以在它們的內容被破壞后保存它們。 您必須先將ESIEDI都壓入堆棧。 這將需要您將 8 添加到相對於ESP的偏移量。 您的代碼應該如下所示:

__MyMemcpy PROC _dest : DWORD, _source : DWORD, _size : DWORD
    PUSH EDI                         ; Save registers first
    PUSH ESI
    MOV EDI, DWORD PTR [ESP + 0CH]   ; Arguments are offset by an additional 8 bytes
    MOV ESI, DWORD PTR [ESP + 10H]
    MOV ECX, DWORD PTR [ESP + 14H]
__AGAIN:
    LODSB
    STOSB
    LOOP __AGAIN
    POP ESI                          ; Restore the caller (non-volatile) registers
    POP EDI
    RETN 0CH
__MyMemcpy ENDP

您問了為什么您似乎收到有關 ESP 或堆棧問題的錯誤的問題。 我假設您收到類似於此的錯誤:

在此處輸入圖像描述

這可能是因為在混合 STDCALL 和 CDECL 調用約定時ESP不正確,也可能是由於保存的ESP的值被 function 破壞了。 在您的情況下,它似乎是后者。

我用這個代碼寫了一個小的 C++ 項目,它與你的 C 程序有類似的行為:

#include <iostream>

extern "C" void __stdcall __MyMemcpy( void* __restrict, void* __restrict, int);

typedef struct {
    void(__stdcall* MemCpy)(void* __restrict, void* __restrict, int);
}MemFunc;

int initmemfunc(MemFunc* f) {
    f->MemCpy = __MyMemcpy;
    return 0;
}

char buf1[] = "Testing";
char buf2[200];

int main()
{
    MemFunc mf = { 0 };
    initmemfunc(&mf);
    mf.MemCpy(buf2, buf1, strlen(buf1));
    std::cout << "Hello World!\n" << buf2;
}

當我使用像您這樣無法正確保存ESIEDI的代碼時,我在 Visual Studio C/C++ 調試器中顯示的生成的匯編代碼中發現了這一點:

在此處輸入圖像描述

我已經注釋了重要的部分。 編譯器已生成 C 運行時檢查(這些可以禁用,但它們只會隱藏問題而不修復它),包括通過 STDCALL function 調用檢查ESP 不幸的是,它依賴於將ESP的原始值(在推送參數之前)保存到寄存器ESI中。 因此,在調用__MyMemcpy后會進行運行時檢查,以查看ESPESI是否仍然是相同的值。 如果不是,您會收到有關ESP未正確保存的警告。

由於您的代碼錯誤地破壞了ESI (和EDI ),因此檢查失敗。 我已經對調試 output 進行了注釋,希望能提供更好的解釋。


您可以避免使用LODSB / STOSB循環來復制數據。 有一條指令就是這個操作 ( REP MOVSB ) 復制ESI指向的ECX字節並將它們復制到EDI 您的代碼版本可以寫成:

__MyMemcpy PROC _dest : DWORD, _source : DWORD, _size : DWORD
    PUSH EDI                         ; Save registers first
    PUSH ESI
    MOV EDI, DWORD PTR [ESP + 0CH]   ; Arguments are offset by an additional 8 bytes
    MOV ESI, DWORD PTR [ESP + 10H]
    MOV ECX, DWORD PTR [ESP + 14H]
    REP MOVSB
    POP ESI                          ; Restore the caller (non-volatile) registers
    POP EDI
    RETN 0CH
__MyMemcpy ENDP

如果您要使用PROC的功能來保存寄存器ESIEDI ,您可以USES指令列出它們。 您還可以按名稱引用堆棧上的參數位置。 您還可以通過簡單地使用ret讓 MASM 為調用約定生成正確的 EPILOGUE 序列。 這將適當地清理堆棧,並且在 STDCALL 返回的情況下,通過從堆棧中刪除指定數量的字節(即ret 0ch )在這種情況下,因為有 3 個 4 字節 arguments。

缺點是您必須生成 PROLOGUE 和 EPILOGUE 代碼,這會使事情變得更加低效:

.MODEL FLAT, STDCALL
.CODE

__MyMemcpy  PROC USES ESI EDI dest : DWORD, source : DWORD, size : DWORD
    MOV EDI, dest
    MOV ESI, source
    MOV ECX, size
    REP MOVSB                        ; Use instead of LODSB/STOSB+Loop  
    RET
__MyMemcpy  ENDP

END

匯編程序將為您生成此代碼:

PUBLIC __MyMemcpy@12
__MyMemcpy@12:
    push        ebp  
    mov         ebp,esp            ; Function prologue generate by PROC
    push        esi                ; USES caused assembler to push EDI/ESI on stack
    push        edi  
    mov         edi,dword ptr [ebp+8]  
    mov         esi,dword ptr [ebp+0Ch]  
    mov         ecx,dword ptr [ebp+10h]  
    rep movs    byte ptr es:[edi],byte ptr [esi]
    ; MASM generated this from the simple RET instruction to restore registers,
    ; clean up stack and return back to caller per the STDCALL calling convention
    pop         edi                ; Assembler
    pop         esi  
    leave  
    ret         0Ch  

有些人可能正確地爭辯說,讓匯編程序掩蓋所有這些工作會使代碼可能更難理解,因為這些人沒有意識到 MASM 可以對聲明為 function 的PROC進行特殊處理。 這可能會導致將來更難為不熟悉 MASM 細微差別的其他人維護代碼。 如果您不了解 MASM 可能生成什么,那么堅持自己編寫 function 的主體可能是更安全的選擇。 正如您所發現的,這還涉及關閉 PROLOGUE 和 EPILOGUE 代碼生成。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM