我刚开始组装,我发现这些指令和类似的指令遍布代码片段:
sub esp , something
mov esp, dword ptr [esp + something]
为什么要这样做?我听说这是关于堆栈帧的初始化。您能解释一下或指出我要查找的关键字吗?
每个 C(除了一些inline
函数,而不仅仅是 C 函数)函数都有它的开始和结束。开头通常是这样的:
push ebp
mov ebp, esp
sub esp, x
第一行推送旧的堆栈帧。由于在程序运行期间调用了许多函数,因此函数可以在调用另一个函数时将其堆栈帧保存在堆栈中。堆栈帧(通常存储在EBP
寄存器中)是每个局部变量寻址的基础。假设您有以下代码:
int main() {
__volatile int localVariable = 0x10;
printf("%d");
}
变量localVariable
可以静态存储在某个地方(例如在 address 上0x410000
),但如果main
再次调用,该地址上的值将被覆盖。因此需要动态分配之类的东西。这就是堆栈帧的用途;保存先前的堆栈帧并为局部变量相对于堆栈指针的实际位置“分配”位置。在这种情况下,localVariable
应始终在位置上EBP-sizeof(int)
。
第二行和第三行实际上是分配“新”内存的那段代码。第三行从ESP
(stack grows down) -> 中减去一些值,这就是函数不会覆盖其变量的原因;每个功能都有自己的位置。第二行保存旧的堆栈指针,所以当被调用的函数从它的堆栈帧返回时,它会堆栈上一个函数的指针。
函数的标准结尾是
mov esp, ebp
pop ebp
ret
或在第二个示例中保留 ret
leave
可以替代第一个示例中的前两行,因为函数经常返回到前一个函数的执行。
寻址EBP - something
通常意味着访问被调用(当前)函数的局部变量。
寻址EBP + someting
通常意味着访问传递给函数的参数。
需要注意的是,EBP
实际上存储的地址并不指向参数,而是指向函数的返回地址,该地址是call
在调用函数时由指令推送的。存储的值EBP + 4
可以是第一个(旧做法,例如用于具有可变数量参数的函数,printf
如Object... values
它)。
乍看之下是 Intel x86。'Something' 通常是所有本地(堆栈分配的)变量的总长度,如果它出现在汇编子程序的开头。由于堆栈向下增长,即朝向较低地址,这为它们保留了空间。你确定第二行就是你写的吗?esp 已经指向空闲区域,因此在您的例程递归调用自身或调用另一个函数之前,可以将参数推送到本地人下方的堆栈中。除非它用于允许(下一个)被调用者访问调用者的堆栈帧,就像在 Pascal 中,当你有嵌套函数时,我没有看到在调整它以适应本地变量之后立即将其加载到 esp 的意义。