编译器在这段汇编代码中做了什么?
What does the compiler do in this assembly code?
我试图了解 C 编译器在编译为汇编时会做什么。我编译成汇编的代码是这样的:
void main() {
int x = 10;
int y = 10;
int a = x + y;
}
生成以下程序集:
.Ltext0:
.globl main
main:
.LFB0:
0000 55 pushq %rbp
0001 4889E5 movq %rsp, %rbp
0004 C745F40A movl , -12(%rbp)
000b C745F80A movl , -8(%rbp)
0012 8B45F8 movl -8(%rbp), %eax
0015 8B55F4 movl -12(%rbp), %edx
0018 01D0 addl %edx, %eax
001a 8945FC movl %eax, -4(%rbp)
001d 5D popq %rbp
001e C3 ret
但是,我在理解这段代码中具体发生的事情时遇到了一些困难。我了解所有标签和一些组件。这是我认为它的作用:
- 推送 rbp? - 这是堆栈框架还是什么?
- 将堆栈指针设置为基指针? (即清除堆栈)
- 将 10 移入堆栈?偏移-12?为什么是 12,为什么是负数?
- 将 10 移入堆栈,但这次是 -8 而不是 -12(相差 4,可能是字节或其他?)
- 将 -8 处的值移入 eax
- 将 -12 处的值移入 edx
- 添加 eax 和 edc
- 将值从 eax 移入堆栈
- 弹出 rbp?可能是函数堆栈帧结束?
- return 来自函数??
谁能澄清这个程序集的某些要点,也许是编译器选择 -8、-12 的原因,为什么它选择 eax 和 edc 而不是其他一些寄存器,为什么它压入和弹出 rbp,等等?
push rbp? - is this for a stack frame or something?
是的。编译器为局部变量创建一个栈帧。 push %rbp
/ movq %rsp, %rbp
是执行此操作的标准方法。它允许轻松访问局部变量。
moves 10 into stack? Offset by -12? Why 12, and why is it negative?
在这种情况下,编译器选择使用堆栈中从 -12(%rbp)
到 -9(%rbp)
的 4 字节(int
大小)部分作为变量 x
.
创建堆栈帧后,您可以访问具有负偏移量的局部变量和具有正偏移量的函数参数:
------------------------------------------------------
| R |
New stack (locals) | B | Old stack (parameters)
| P |
------------------------------------------------------
^
RBP is updated to point here as well so you get negative offsets (to the left) for locals and positive offsets (to the right) for parameters.
注意,由于存储的RBP也占用了space,以及函数的return地址,所以任何一个参数偏移量都需要增加16个字节。 (32 位系统为 8 个字节)
通常,您必须先更新 RSP
,然后才能使用局部变量,例如:subq , %rsp
。离开函数时,使用addq , %rsp
或leave
。此示例更新堆栈指针以显示我们在堆栈上使用了 12 个字节。完成它们后,只需恢复堆栈指针即可。但是,在您的示例中,none 是必需的,因为该函数除了局部变量外对堆栈没有其他用途。
moves 10 into stack, though this time at -8 instead of -12
再次引用局部变量,除了这一次,编译器为变量 y
.
选择了从 -8(%rbp)
到 -5(%rbp)
的 4 字节部分
在这种情况下,pop %rbp
将函数末尾的堆栈恢复为进入前的状态:
------------------------------------------------------
| R |
New stack (locals) | B | Old stack (parameters)
| P |
------------------------------------------------------
^
RSP points here, so a `pop %rbp` will restore both RSP and RBP
编译器可能首先尝试使用 EAX
和 EDX
,因为 EAX
是为数学运算而设计的,而 EDX
是为一般数据运算而设计的。您会经常发现它们在操作中配对。
要了解编译器生成的程序集,您必须了解堆栈帧。 SP 是堆栈指针,BP 指向当前堆栈帧,用于寻址局部变量(因此将值“10”移动到 [bp-12] 和 [bp-8]。然后它将其加载到第一个可用的注册加法(本例中为 ax 和 dx)并执行加法。最后,它恢复旧堆栈和 returns。
我试图了解 C 编译器在编译为汇编时会做什么。我编译成汇编的代码是这样的:
void main() {
int x = 10;
int y = 10;
int a = x + y;
}
生成以下程序集:
.Ltext0:
.globl main
main:
.LFB0:
0000 55 pushq %rbp
0001 4889E5 movq %rsp, %rbp
0004 C745F40A movl , -12(%rbp)
000b C745F80A movl , -8(%rbp)
0012 8B45F8 movl -8(%rbp), %eax
0015 8B55F4 movl -12(%rbp), %edx
0018 01D0 addl %edx, %eax
001a 8945FC movl %eax, -4(%rbp)
001d 5D popq %rbp
001e C3 ret
但是,我在理解这段代码中具体发生的事情时遇到了一些困难。我了解所有标签和一些组件。这是我认为它的作用:
- 推送 rbp? - 这是堆栈框架还是什么?
- 将堆栈指针设置为基指针? (即清除堆栈)
- 将 10 移入堆栈?偏移-12?为什么是 12,为什么是负数?
- 将 10 移入堆栈,但这次是 -8 而不是 -12(相差 4,可能是字节或其他?)
- 将 -8 处的值移入 eax
- 将 -12 处的值移入 edx
- 添加 eax 和 edc
- 将值从 eax 移入堆栈
- 弹出 rbp?可能是函数堆栈帧结束?
- return 来自函数??
谁能澄清这个程序集的某些要点,也许是编译器选择 -8、-12 的原因,为什么它选择 eax 和 edc 而不是其他一些寄存器,为什么它压入和弹出 rbp,等等?
push rbp? - is this for a stack frame or something?
是的。编译器为局部变量创建一个栈帧。 push %rbp
/ movq %rsp, %rbp
是执行此操作的标准方法。它允许轻松访问局部变量。
moves 10 into stack? Offset by -12? Why 12, and why is it negative?
在这种情况下,编译器选择使用堆栈中从 -12(%rbp)
到 -9(%rbp)
的 4 字节(int
大小)部分作为变量 x
.
创建堆栈帧后,您可以访问具有负偏移量的局部变量和具有正偏移量的函数参数:
------------------------------------------------------
| R |
New stack (locals) | B | Old stack (parameters)
| P |
------------------------------------------------------
^
RBP is updated to point here as well so you get negative offsets (to the left) for locals and positive offsets (to the right) for parameters.
注意,由于存储的RBP也占用了space,以及函数的return地址,所以任何一个参数偏移量都需要增加16个字节。 (32 位系统为 8 个字节)
通常,您必须先更新 RSP
,然后才能使用局部变量,例如:subq , %rsp
。离开函数时,使用addq , %rsp
或leave
。此示例更新堆栈指针以显示我们在堆栈上使用了 12 个字节。完成它们后,只需恢复堆栈指针即可。但是,在您的示例中,none 是必需的,因为该函数除了局部变量外对堆栈没有其他用途。
moves 10 into stack, though this time at -8 instead of -12
再次引用局部变量,除了这一次,编译器为变量 y
.
-8(%rbp)
到 -5(%rbp)
的 4 字节部分
在这种情况下,pop %rbp
将函数末尾的堆栈恢复为进入前的状态:
------------------------------------------------------
| R |
New stack (locals) | B | Old stack (parameters)
| P |
------------------------------------------------------
^
RSP points here, so a `pop %rbp` will restore both RSP and RBP
编译器可能首先尝试使用 EAX
和 EDX
,因为 EAX
是为数学运算而设计的,而 EDX
是为一般数据运算而设计的。您会经常发现它们在操作中配对。
要了解编译器生成的程序集,您必须了解堆栈帧。 SP 是堆栈指针,BP 指向当前堆栈帧,用于寻址局部变量(因此将值“10”移动到 [bp-12] 和 [bp-8]。然后它将其加载到第一个可用的注册加法(本例中为 ax 和 dx)并执行加法。最后,它恢复旧堆栈和 returns。