Assembly 为什么我们需要堆栈清理代码以及它是如何工作的?(用汇编语言)

Assembly 为什么我们需要堆栈清理代码以及它是如何工作的?(用汇编语言),assembly,stack,Assembly,Stack,堆栈是遵循后进先出规则的数据结构。在汇编语言中,当我们调用函数时,我们需要使用“push”指令将参数推送到堆栈上。但是为什么我们需要堆栈清理代码来删除参数呢?大多数堆栈清理代码看起来像 add esp N 如何从堆栈中删除参数 堆栈指针寄存器(ESP)指向堆栈顶部。由于堆栈向下增长,因此增加ESP的值会从堆栈中删除项目。更改ESP的值是从堆栈中删除项的唯一方法,无论是显式使用ADD或LEA指令,还是隐式使用POP或RET -对于向下扩展的堆栈,这意味着它从更高的地址开始,当您执行push指令时

堆栈是遵循后进先出规则的数据结构。在汇编语言中,当我们调用函数时,我们需要使用“push”指令将参数推送到堆栈上。但是为什么我们需要堆栈清理代码来删除参数呢?大多数堆栈清理代码看起来像

add esp N
如何从堆栈中删除参数

堆栈指针寄存器(ESP)指向堆栈顶部。由于堆栈向下增长,因此增加ESP的值会从堆栈中删除项目。更改ESP的值是从堆栈中删除项的唯一方法,无论是显式使用ADD或LEA指令,还是隐式使用POP或RET


-

对于向下扩展的堆栈,这意味着它从更高的地址开始,当您执行push指令时,它会从堆栈指针中减去一些值。函数需要返回找到的堆栈(指针)。因此,您可以执行一组pop指令,在本例中,这些指令将添加到堆栈指针地址,或者您可以简单地将推送的量添加到堆栈指针。或者另一种解决方案是,您可以以不干扰调用者的方式保存堆栈指针,并简单地恢复该值。一堆pop指令通常是对指令空间和执行时间的浪费,尤其是当一条add指令可以有效地完成同样的任务时


清理堆栈绝不意味着实际恢复您在堆栈上弄乱的ram,根据定义,堆栈指针下面的ram是公平的,它只是意味着将堆栈指针返回到其调用状态。

我将首先回答OP问题的第一部分:为什么?因为对于大多数CPU来说,堆栈携带当前函数的本地存储(本地变量和参数)和控制信息(返回地址、指向上一个堆栈帧的指针等)


当您调用一个不清理堆栈的函数(例如,遵守cdecl调用约定的函数)时,调用方负责在被调用方返回后离开堆栈,与调用之前处于相同的状态。这意味着,如果调用方将N个字节推送到堆栈中,它必须从堆栈中删除N个字节,方法是弹出并扔掉它们,或者通过直接修改堆栈指针(即ADD SP,N指令)的值跳过这些字节,从而更快地删除它们。否则,未清理的每个调用函数的堆栈都会增长,最终会发生堆栈溢出。

堆栈指针寄存器(ESP)指向堆栈顶部。由于堆栈向下增长,因此增加ESP的值会从堆栈中删除项目。更改ESP的值是从堆栈中删除项的唯一方法,无论是使用ADD或LEA指令显式删除项,还是使用POP或RET隐式删除项。在调用约定中,被调用函数通常通过使用| RET N |从被调用函数返回来恢复ESP。还有一些调用约定,调用者通常会还原esp,但是会为固定数量的参数预先分配空间,并且只要调用中的实际参数数量是,被调用者函数如何在没有pop的情况下访问参数?如果更改esp的值是删除项的唯一方法,什么是流行指令?@KelvinZhang我没有写这个答案。但是,如果您想查看某些内容,请查找“堆栈帧”@KelvinZhang POP通过更改ESP的值从堆栈中删除项目。@Ross Ridge被调用函数如何在不更改堆栈的情况下读取参数?我刚刚发现,当我进行代码注入时,如果在调用带有参数的函数后不清理堆栈,注入的程序将崩溃。这是怎么发生的?我刚刚把一个整数推到堆栈上,那4个字节会使程序崩溃吗?因为如果你不让堆栈保持调用前的状态,你的RET指令将从错误的位置获取返回地址