微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何避免在没有 POP 的情况下使用 PUSH?

如何解决如何避免在没有 POP 的情况下使用 PUSH?

我目前正在手工编写 x86 程序集 (FASM),我经常犯的一个典型错误push 堆栈上的一个参数,但在执行 pop 之前返回。

这会导致调用者的堆栈偏移量发生变化,这将导致程序崩溃。

这是一个粗略的例子来演示它:

proc MyFunction
    ; A loop:
    mov     ecx,100
.loop:
    push    ecx

    ; ==== loop content
    ...
    ; Somewhere,the decision is made to return,not just to exit the loop
    jmp    .ret
    ...
    ; ==== loop content

    pop     ecx
    loop    .loop

.ret:
    ret
endp

现在,显而易见的答案是在发出 ret 之前从堆栈中弹出适当数量的元素。然而,在 1000 多条手工组装线中很容易忽略一些东西。

我也在考虑使用 pushad / popad always,但我不确定这是什么约定。

问题:我可以遵循什么模式来避免这个问题?

解决方法

通常不要在循环内使用push/pop;像编译器一样使用 mov,这样您就不会不必要地移动 ESP。 (如果/当您为其他本地人明确引用 ESP 时,这可能会导致额外的 stack-sync uops。)

或者在这种情况下,只需为两个不同的循环选择一个不同的寄存器,或者在保留一些空间后将外循环计数器完全保留在内存中。 (sub dword [esp],1 / jnz .outer_loop。或者 [ebp-4],如果您将 EBP 设置为帧指针,而不是仅将其用作另一个调用保留寄存器。)

围绕循环内的某些内容溢出/重新加载寄存器是低效的。释放寄存器的第一步应该是将只读的东西保留在内存中,如果它们不是经常需要的话。例如像 inc edx / cmp edx,[esp+12] / jbe .outer_loop 这样的外循环计数器可以避免存储/重新加载。仅当寄存器用完时才将可变内容保留在内存中,然后当然更喜欢不经常更改的内容。


在编译器生成的代码中,您通常只会在序言中看到推送,并沿着通向 ret 的路径弹出。这使得它们很容易匹配。如果您需要保存另一个 call-preserved register 以供在函数内部使用,或者为局部变量保留更多堆栈空间,您可以更改函数顶部的推送顺序,然后更改返回路径中的结尾.

(您可以有多个方法退出一个函数,特别是如果不需要太多清理,那么尾部复制可能比 jmp 到结尾的另一个副本更好。)

您不必像编译器那样严格(或脑残),毕竟,您是在 asm 中手工编写以获得更好的性能。 (对吗?否则就让编译器为你做微优化,生成“数千行”的 asm!中到大量的代码是编译器在快速分析数据流和制作相当不错的代码的能力方面真正发挥作用的地方。 )

因此您可以例如使用 asm 堆栈作为堆栈数据结构;你无法说服编译器去做的事情。 (不过,Using the callstack to implement a stack data structure in C? 是一种不安全的尝试。)就像 push 和 pop 一样,通过指针比较进行“空”检测。在这种情况下,如果您对堆栈内存有任何其他需求,您可能希望使用 EBP 作为帧指针。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。