assembly - stdcall (callee-pops) 中变量参数的堆栈清理

标签 assembly x86 variadic-functions calling-convention

我正在学习一些汇编的乐趣(目前在 Windows 上使用 NASM),我有一个关于 stdcall calling convention 的问题和参数数量可变的函数。例如,sum 函数接受 X 个整数并将它们全部相加。

由于被调用者在使用stdcall时需要清理/重置堆栈,而ret只能使用常量值,所以我一直在想是不是哪里不对用popping 返回地址,移动esp,然后自己跳回调用者,而不是使用ret。我认为这会更慢,因为它需要更多的指令,但可以接受吗?

; int sum(count, ...)
sum:
    mov ecx, [esp+4] ; count
    
    ; calc args size
    mov eax, ecx ; vars count
    inc eax      ; + count
    mov edx, 4   ; * 4 byte per var
    mul edx
    mov edx, eax
    
    xor eax, eax ; result
    
    cmp ecx, 0   ; if count == 0
    je .done
    inc ecx      ; count++, to start with last arg
    
    .add:
        add eax, [esp+4*ecx]
        dec ecx  ; if --ecx != 1, 0 = return, 1 = count
        cmp ecx, 1
        jnz .add
    .done:
        pop ebx
        add esp,edx
        jmp ebx

我不明白为什么这不行,而且它似乎可以工作,但我读过一些文章,其中讨论了 stdcall 如何无法处理变量参数,因为函数不知道要传递给 ret 什么值。我错过了什么吗?

最佳答案

当然,如果参数的大小是常量,ret imm 会起作用。如果该函数能够在运行时确定其参数的大小,您的想法就会奏效,在本例中它是从 count 参数确定的,尽管作为 ecm points out 它可能效率低下,因为间接分支预测器是'专为此类恶作剧而设计。

但在某些情况下,被调用函数可能根本不知道参数的大小,甚至在运行时也不知道。考虑 printf。您可能会说它可以从格式字符串中推断出其参数的大小;例如,如果格式字符串是 "%d" 那么它应该知道传递了一个 int 并因此从堆栈中清除额外的 4 个字节。但是在 C 标准下调用是完全合法的

printf("%d", 123, 456, 789, 2222);

需要忽略多余的参数。但是根据你的调用约定,printf 会认为它只需要从堆栈中清除 4 个字节(加上它的非可变格式字符串参数),而它的调用者会期望它清除 16 个字节,并且程序会崩溃。

因此,除非您的调用约定将包含一个“隐藏”参数来告诉被调用函数要清理多少字节的参数,否则它无法工作。传递这样一个额外的参数需要更多的指令,而不是让调用者自己进行堆栈清理。

关于assembly - stdcall (callee-pops) 中变量参数的堆栈清理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65863892/

相关文章:

linux - 简单的加法器不工作

assembly - 微软ASM面试有疑问吗?

java - 为什么输出是int?

c++ - 使用 C++ 从 "mov rcx, qword ptr [0xAddress]"x86 指令中提取地址

c - double 型矩阵 [n]x[n] 传输至 ASSEMBLY

c++ - 为什么在单独的循环中元素加法比在组合循环中快得多?

c - 是否需要 %c fprintf 说明符来获取 int 参数

objective-c - 用于 iOS ARC 问题的 BWDB SQLite 包装器

assembly - 通过寄存器重命名器对寄存器进行微架构清零 : performance versus a mov?

c - 在 gdb 中查看 ASCII 格式的寄存器内容