问题描述
我是装配新手(NASM)。我知道 RBP 指向函数中的任何参数和局部变量。它是通过简单的偏移量实现的。如果我们想获得第一个变量我们rbp-4
,如果我们想获得第一个参数我们添加到 rbp 4。但是如果任何函数可以有任意数量的局部变量或参数,我们怎么能做到这一点?如果我们希望在一个函数中可以有 100 个变量,我们如何通过简单的常量偏移量指向任何变量?
谢谢。对不起我的英语不好。我不是母语人士。
解决方法
在像 C 这样的语言中你不能有可变数量的变量,所以编译器总是知道它把它们放在哪里。因此它知道每个变量的正确常量偏移量。
如果你在谈论像 printf(char *,...)
这样的可变参数函数,那么你有调用约定中关于它们如何布局的规则,你通常会增加一个指针。要按 arg-number 索引,您需要知道所有先前 args 的宽度。它们至少为 8,但根据调用约定可以更宽,而不是在 x86-64 System V 中使用隐藏指针。 printf 具有 long double
的转换等(比 8 个字节更宽),所以它必须支持跟踪哪个 arg 在哪里,以防转换通过数字引用 arg
如果您正在谈论使用堆栈作为堆栈数据结构,推/弹出可能在循环内部,那么您需要保留一个指针以了解您的堆栈数据结构何时为“空”,并且进一步的弹出会吃进入其他本地变量。
将某些局部变量设为可变长度数组会使事情变得更加棘手,例如 C int foo[n]
,其中 n
是另一个变量。许多 C 编译器通过发明一个指向每个 VLA 的指针来处理它。指针具有已知的固定宽度,因此编译器知道在哪里可以找到它们,并且它们可以被初始化,因为空间是为 VLA 保留的,例如 sub rsp,rax
/ mov [rbp-16],rsp
。
函数参数和局部变量的寻址取决于所选的 calling convention。您获取我们添加到 rbp 4 的第一个参数肯定是错误的,因为在 64 位模式下(暗示使用 RBP
或 RSP
进行寻址)可以将项目推入堆栈仅 64 位粒度。也许您有 32 位 StandardCall 约定,其中一个函数的典型序言如下所示:
Function: PUSH EBP
MOV EBP,ESP
SUB ESP,LocalsSize
可以在函数内部使用寄存器 EBP
来寻址无限数量的参数和局部变量(在这个例子中我使用了三个参数和四个 DWORD 局部变量):
Invokation Stack Address
PUSH Param3 Param3 EBP+4*4
PUSH Param2 Param2 EBP+3*4
PUSH Param1 Param1 EBP+2*4
CALL Function return
EBP
Local1 EBP-1*4
Local2 EBP-2*4
Local3 EBP-3*4
Local4 EBP-4*4