【问题标题】:Stack cleanup in stdcall (callee-pops) for variable arguments变量参数的 stdcall (callee-pops) 中的堆栈清理
【发布时间】:2021-01-23 20:10:49
【问题描述】:

我正在学习一些有趣的汇编(目前在 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 的值。我错过了什么吗?

【问题讨论】:

  • 即使在stdcall 下,变量参数也会切换到调用者清理。
  • 我认为在 Windows 上,可变参数函数永远不是标准调用。如果大多数库函数是 stdcall 或 fastcall(32 位版本的 fastcall 的 callee-pops),像 printf 这样的函数是 cdecl (caller-pops)
  • 我明白了。好吧,让我们不要挂断我称它为“stdcall”,而是关注我自己的调用约定是否有任何问题,我什至在带有可变参数的函数上清理堆栈^^
  • 除了性能和需要临时寄存器之外,是的,您可以弹出一个寄存器,调整(r/e)sp,然后跳转到该寄存器的内容。性能也可能会受到影响,因为您可能会混淆分支预测器的函数调用处理。该寄存器显然需要可用,因为您以后无法恢复它。注意add 或sub 指向堆栈指针会修改状态标志;使用lea 或pop 来避免这种情况。
  • @ecm:有趣的事实:如果您返回 push reg / ret 而不是 jmp ebx,分支预测会很好(因为您仍然返回同一个地方。返回地址预测有效通过假设 call/ret 嵌套正确(返回地址的内部堆栈式硬件数据结构),与 ESP 指向的位置无关)。

标签: assembly x86 variadic-functions calling-convention


【解决方案1】:

当然ret imm 如果参数的大小是一个常数就可以工作。如果函数能够在运行时确定其参数的大小,您的想法将起作用,在这种情况下,它是从 count 参数确定的,尽管 ecm points out 它可能效率低下,因为没有设计间接分支预测器对于这样的恶作剧。

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

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

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

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

【讨论】:

猜你喜欢
  • 2014-05-17
  • 2016-03-07
  • 2013-03-03
  • 2014-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多