变量参数的 stdcall (callee-pops) 中的堆栈清理 [英] Stack cleanup in stdcall (callee-pops) for variable arguments

查看:33
本文介绍了变量参数的 stdcall (callee-pops) 中的堆栈清理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习一些组装的乐趣(目前在 Windows 上使用 NASM),我有一个关于 stdcall 调用约定 和具有可变数量参数的函数.例如,一个 sum 函数接受 X 个整数并将它们加在一起.

I'm learning a bit of assembly for fun (currently using NASM on Windows), and I have a question regarding the stdcall calling convention and functions with variable numbers of arguments. For example, a sum function that takes X integers and adds them all together.

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

Since the callee needs to clean/reset the stack when using stdcall, but you can only use constant values with ret, I've been wondering if there's anything wrong with popping the return address, moving esp, and jumping back to the caller yourself, instead of using ret. I assume this would be slower, since it requires more instructions, but would it be acceptable?

; 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 什么值.我错过了什么吗?

I don't see why this wouldn't be okay, and it appears to work, but I've read articles that talked about how stdcall can't handle variable arguments, because the function can't know what value to pass to ret. Am I missing something?

推荐答案

当然 ret imm 如果参数的大小是常量就可以工作.如果函数能够在运行时确定其参数的大小,那么您的想法就会奏效,在这种情况下,它是从 count 参数中确定的,尽管作为 ecm 指出它可能效率低下,因为间接分支预测器不是为这种恶作剧而设计的.

Of course ret imm works if the size of the arguments is a constant. Your idea would work if the function is able to determine the size of its arguments at runtime, which in this case it does from the count argument, though as ecm points out it may be inefficient because the indirect branch predictor isn't designed for such shenanigans.

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

But in some cases, the size of the arguments may not be known to the called function at all, not even at runtime. Consider printf. You might say it could deduce the size of its arguments from the format string; for instance, if the format string was "%d" then it should know that one int was passed and therefore clean up an extra 4 bytes from the stack. But it is perfectly legal under the C standard to call

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

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

The excess arguments are required to be ignored. But under your calling convention, printf would think it only had to clean up 4 bytes from the stack (plus its non-variadic format string argument), whereas its caller would expect it to clean up 16, and the program will crash.

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

So unless your calling convention is going to include a "hidden" argument that tells the called function how many bytes of arguments to clean up, it can't work. And passing such an extra argument is going to require more instructions than having the caller just do the stack cleanup itself.

这篇关于变量参数的 stdcall (callee-pops) 中的堆栈清理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆