c - 汇编代码如何确定变量在堆栈中的位置?
问题描述
我正在尝试理解一些基本的汇编代码概念,但我一直在纠结汇编代码如何确定在堆栈上放置东西的位置以及给它多少空间。
为了开始使用它,我在 godbolt.org 的编译器资源管理器中输入了这个简单的代码。
int main(int argc, char** argv) {
int num = 1;
num++;
return num;
}
并得到了这个汇编代码
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
mov QWORD PTR [rbp-32], rsi
mov DWORD PTR [rbp-4], 1
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
pop rbp
ret
所以这里有几个问题:
参数不应该在调用之前放在堆栈上吗?为什么 argc 和 argv 放置在距当前堆栈帧的基指针的偏移量 20 和 32 处?如果我们只需要一个局部变量 num 的空间,这似乎真的很远。所有这些额外空间是否有原因?
局部变量存储在基指针下方的 4 处。因此,如果我们在堆栈中可视化它并说当前指向 0x00004000 的基指针(只是作为一个例子,不确定这是否现实),那么我们将值放在 0x00003FFC,对吗?而一个整数大小为4字节,那么是占用0x00003FFC向下到0x00003FF8的内存空间,还是占用0x00004000到0x00003FFC的内存空间呢?
看起来堆栈指针从未向下移动以为该局部变量留出空间。我们不应该
sub rsp, 4
为本地int腾出空间吗?
然后,如果我修改它以添加更多本地人:
int main(int argc, char** argv) {
int num = 1;
char *str1 = {0};
char *str2 = "some string";
num++;
return num;
}
然后我们得到
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-36], edi
mov QWORD PTR [rbp-48], rsi
mov DWORD PTR [rbp-4], 1
mov QWORD PTR [rbp-16], 0
mov QWORD PTR [rbp-24], OFFSET FLAT:.LC0
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
pop rbp
ret
所以现在主要参数被推到离基指针更远的地方。为什么前两个局部变量之间的空间是 12 个字节,而后两个局部变量之间的空间是 8 个字节?那是因为类型的大小吗?
解决方案
我只回答这部分问题:
参数不应该在调用之前放在堆栈上吗?为什么 argc 和 argv 放置在距当前堆栈帧的基指针的偏移量 20 和 32 处?
的参数main
确实是由调用的代码设置的main
。
这似乎是根据x86 的 64 位 ELF psABI编译的代码,其中任何函数的前几个参数都在寄存器中传递,而不是在堆栈上。当控制到达main:
标签时,argc
will in edi
,argv
will be in rsi
,第三个通常称为的参数envp
将 in rdx
。(你没有声明那个参数,所以你不能使用它,但是调用的代码main
是通用的并且总是设置它。)
我相信您所指的说明
mov DWORD PTR [rbp-20], edi
mov QWORD PTR [rbp-32], rsi
编译器书呆子称之为溢出argc
指令:他们将原始寄存器中的和参数的初始值复制argv
到堆栈中,以防万一这些寄存器用于其他用途。正如其他几个人指出的那样,这是未优化的代码;如果您打开了优化,这些说明是不必要的,也不会发出。当然,如果你打开了优化,你会得到根本不接触堆栈的代码:
main:
mov eax, 2
ret
在此 ABI 中,允许编译器将保存寄存器值的“溢出槽”放置在堆栈帧内的任何位置。它们的位置不一定有意义,并且可能因编译器而异,从同一编译器的补丁级别到补丁级别,或者对源代码的明显不相关的更改。
(一些 ABI 确实在某些细节上指定了堆栈帧布局,例如 IIRC,32 位 Windows ABI 这样做,以促进“展开”,但现在这并不重要。)
(为了强调 to 的参数main
在寄存器中,这是我从中得到的程序-O1
集
int main(int argc) { return argc + 1; }
:
main:
lea eax, [rdi+1]
ret
仍然没有对堆栈做任何事情!(除此之外ret
。))
推荐阅读
- cypress - 断言多个中的任何一个元素
- python - 使用 OR 或 if elif(过滤行)显示包含多个关键字的记录
- python - 在 Python 中将 .IMG(经典磁盘映像)转换为 .PNG/.JPG
- php - PHP 数组验证
- vb.net - ListView:鼠标拖动的多选项目
- azure - 我可以将 azure functionapp 存储帐户用于其他目的,例如将文件存储在 blob 存储中吗?
- java - Stream.builder() 和在 Java 中的 ArrayList 上调用 stream() 有什么区别?
- javascript - 即使我更改路线,Vue 方法也会运行,仅当我在特定路线/组件上时如何运行方法?
- java - 如何通过点击 li 元素触发 Spring boot 表单提交?
- r - 有没有更优雅的方法来用数据的平均值为 ggplot 条着色?