【发布时间】:2021-09-08 01:32:09
【问题描述】:
我目前正在手动编写 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 总是,但我不确定这个约定是什么。
问题:我可以遵循什么模式来避免这个问题?
【问题讨论】:
-
使用标准堆栈帧(
push ebp; mov ebp, esp开头,mov esp, ebp; pop ebp或leave结尾)。但是,如果您无法跟踪您的推送,则无论如何您都无法使用这些东西,因此您应该更加注意:) -
可以使用普通的function prologue and epilogue恢复
esp。但是你推送的寄存器不会被恢复。在汇编中你必须做正确的事情,实际上没有任何高级技巧。 -
一种可能性是首先将所有被调用者保存的寄存器压入堆栈,然后建立堆栈帧。虽然不符合标准约定,但即使您忘记了推送的内容,也可以正确清理堆栈。
-
我已经用 macros 解决了这个问题,它替换了原生的
PROC/ENDP。它们隐藏了调用约定的序言和尾声,还允许为过程的参数、本地堆栈变量和返回结果值的寄存器提供正式的描述性名称,请参阅Procedure。 -
如果您无法跟踪您的推送和弹出,请不要在汇编程序中编程。
标签: assembly x86 stack-memory fasm stack-frame