如何在函数调用期间保存ESP的值

Ben*_*ami 0 c x86 assembly inline-assembly

我对下面的代码有问题:


void swap(int* a, int* b) {
    __asm {
        mov eax, a;
        mov ebx, b;
        push[eax];
        push[ebx];
        pop[eax];
        pop[ebx];
    }
}
int main() {
    int a = 3, b = 6;
    printf("a: %d\tb: %d\n", a, b);
    swap(&a, &b);
    printf("a: %d\tb: %d\n", a, b);
}
Run Code Online (Sandbox Code Playgroud)

我正在 Visual Studio 中运行此代码,当我运行此代码时,它显示:

运行时检查失败 - ESP 的值未在函数调用中正确保存。这通常是用一种调用约定声明的函数和用另一种调用约定声明的函数指针调用的结果。

我缺少什么?

Pet*_*des 5

回答标题问题:确保平衡推入和弹出。(通常出错只会崩溃,而不是返回错误的 ESP)。如果您在 asm 中编写整个函数,请确保ret 0ret 8或其他内容与您应该使用的调用约定以及要弹出的堆栈参数的数量相匹配(例如 caller-pops cdeclret 0或 callee-pops stdcall ret n)。


查看编译器的 asm 输出(例如在 Godbolt 上或本地)会发现问题:push 与 pop 的操作数大小不同,MSVC 不默认为dword ptrpop。

; MSVC 19.14 (under WINE) -O0

_a$ = 8                                       ; size = 4
_b$ = 12                                                ; size = 4
void swap(int *,int *) PROC                             ; swap
        push    ebp
        mov     ebp, esp
        push    ebx                       ; save this call-preserved reg because you used it instead of ECX or EDX
        mov     eax, DWORD PTR _a$[ebp]
        mov     ebx, DWORD PTR _b$[ebp]
        push    DWORD PTR [eax]
        push    DWORD PTR [ebx]
        pop     WORD PTR [eax]
        pop     WORD PTR [ebx]
        pop     ebx
        pop     ebp
        ret     0
void swap(int *,int *) ENDP
Run Code Online (Sandbox Code Playgroud)

ret当 ESP 指向保存的 EBP(由 推动)时执行,此代码将会崩溃push ebp。大概 Visual Studio 会向编译器传递额外的调试构建选项,以便它执行更多检查而不仅仅是崩溃?

疯狂的是,MSVC 编译/汇编push [reg]push dword ptr(32 位操作数大小,每个 ESP-=4),但编译/pop [reg]汇编为pop word ptr(16 位操作数大小,每个 ESP+=2)

它甚至不会警告操作数大小不明确,这与 NASM 等优秀汇编器不同,NASM 会在push [eax]没有大小覆盖的情况下出现错误。(push 123立即数的总是默认为与模式匹配的操作数大小,但是内存操作数的push/pop在大多数汇编器中通常需要大小说明符。)

使用push dword ptr [eax]/pop dword ptr [ebx]


或者,既然您无论如何都在使用 EBX,而不是将您的函数限制为标准 32 位调用约定中的 3 个调用破坏寄存器,请使用寄存器来保存临时值而不是堆栈空间。

void swap_mov(int* a, int* b) {
    __asm {
        mov eax, a
        mov ebx, b
        mov ecx, [eax]
        mov edx, [ebx]
        mov [eax], edx
        mov [ebx], ecx
    }
}
Run Code Online (Sandbox Code Playgroud)

;(每行末尾不需要空注释。asm{}块内的语法类似于 MASM,而不是 C 语句。)