试图了解堆栈帧 x86-64 的内容
Trying to understand contents of stack frame x86-64
我正在从程序员的角度研究计算机系统中的练习题 3.34,并试图准确理解正在发生的事情。问题指出“考虑一个函数 P
,它生成名为 a0-a7
的局部值。然后它调用函数 Q
使用这些生成的值作为参数。GCC 为第一部分生成以下代码P
”。我们得到以下汇编代码:
/* long P(long x)
* x in %rdi */
P:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
subq , %rsp
leaq 1(%rdi), %r15
leaq 2(%rdi), %r14
leaq 3(%rdi), %r13
leaq 4(%rdi), %r12
leaq 5(%rdi), %rbp
leaq 6(%rdi), %rax
movq %rax, (%rsp)
leaq 7(%rdi), %rdx
movq %rdx, 8(%rsp)
movl [=10=], %eax
call Q
到目前为止,这是我的理解:
指令 pushq %r15
到 pushq %rbx
被压入堆栈以保留这些值,并最终在过程 P
returns 时将它们替换到各自的寄存器中(因为它们是被叫保存的寄存器)。
我看到指令 subq , %rsp
在堆栈上分配了 24 个字节的 space。
不过我有两个问题:
- 加载有效地址线在做什么?
在我看来,它正在获取由
long x
寻址的内存位置,并将该新内存地址(在添加 1 或 2 或 ... 7 之后)存储在各种被调用者保存的寄存器中。这个对吗?我对他们存储的价值有点困惑?有什么意义吗?另外,函数 Q
将如何处理这些寄存器?它怎么知道将它们用作参数,因为它们似乎不是参数寄存器?只有 long x
作为参数传递(因为它在寄存器 %rdi
. 中
- 堆栈的内容是什么?我看到分配了 24 个字节,但我似乎无法解释所有这些 space :( 我理解堆栈看起来像这样:
???????????????????????????????????:16
The result of 7(%rdi) (argument a7):8
The result of 6(%rdi) (argument a6):0 <--- %rsp
我似乎无法解释字节 16-23 中包含的内容:(
提前谢谢你,我真的很纠结这个问题。
首先,注意这个练习题有an erratum。局部变量不作为参数传递给 Q
;而是 Q
被调用时没有参数。所以这就解释了为什么变量不在参数传递寄存器中。
(eax
奇怪的置零可能是 ; they might have accidentally declared void Q();
instead of void Q(void);
. I'm not sure why the compiler emitted movl [=16=], %eax
instead of the 解释的,貌似开启了优化,这是一个很基础的优化。)
现在至于lea
,它实际上只是一条算术指令,编译器倾向于那样使用它。参见 What's the purpose of the LEA instruction?。所以 leaq 1(%rdi), %r15
只是将 rdi
中的值加 1,并将结果写入 r15
。 rdi
中的值是否表示地址或数字或其他内容与机器无关。由于 rdi
包含参数 x
,这实际上是在做
a0 = x + 1;
a1 = x + 2;
a2 = x + 3;
// ...
备选方案类似于 movq %rdi, %r15 ; addq , %r15
,它包含更多说明。
当然,这些值被放入被调用者保存的寄存器(或内存,最后两个),这样它们就不会被调用 Q()
.
破坏
至于堆栈,。在调用 P
之前堆栈指针是 16 的倍数,而当我们 call Q
时它必须再次是 16 的倍数。我们的调用者的 call P
将 8 个字节压入堆栈,而序言中的各种寄存器压入又压入 48 个字节。因此,为了最终得到 16 的倍数,我们必须将堆栈指针调整为比 16 的倍数多 8(即 8 的奇数倍)。我们需要 16 个字节用于局部变量,因此我们必须将堆栈指针调整 24。这样就剩下 8 个字节的堆栈将不会用于任何事情,也就是 ??????
at 16(%rsp)
。
我正在从程序员的角度研究计算机系统中的练习题 3.34,并试图准确理解正在发生的事情。问题指出“考虑一个函数 P
,它生成名为 a0-a7
的局部值。然后它调用函数 Q
使用这些生成的值作为参数。GCC 为第一部分生成以下代码P
”。我们得到以下汇编代码:
/* long P(long x)
* x in %rdi */
P:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
subq , %rsp
leaq 1(%rdi), %r15
leaq 2(%rdi), %r14
leaq 3(%rdi), %r13
leaq 4(%rdi), %r12
leaq 5(%rdi), %rbp
leaq 6(%rdi), %rax
movq %rax, (%rsp)
leaq 7(%rdi), %rdx
movq %rdx, 8(%rsp)
movl [=10=], %eax
call Q
到目前为止,这是我的理解:
指令 pushq %r15
到 pushq %rbx
被压入堆栈以保留这些值,并最终在过程 P
returns 时将它们替换到各自的寄存器中(因为它们是被叫保存的寄存器)。
我看到指令 subq , %rsp
在堆栈上分配了 24 个字节的 space。
不过我有两个问题:
- 加载有效地址线在做什么?
在我看来,它正在获取由
long x
寻址的内存位置,并将该新内存地址(在添加 1 或 2 或 ... 7 之后)存储在各种被调用者保存的寄存器中。这个对吗?我对他们存储的价值有点困惑?有什么意义吗?另外,函数Q
将如何处理这些寄存器?它怎么知道将它们用作参数,因为它们似乎不是参数寄存器?只有long x
作为参数传递(因为它在寄存器%rdi
. 中
- 堆栈的内容是什么?我看到分配了 24 个字节,但我似乎无法解释所有这些 space :( 我理解堆栈看起来像这样:
???????????????????????????????????:16
The result of 7(%rdi) (argument a7):8
The result of 6(%rdi) (argument a6):0 <--- %rsp
我似乎无法解释字节 16-23 中包含的内容:(
提前谢谢你,我真的很纠结这个问题。
首先,注意这个练习题有an erratum。局部变量不作为参数传递给 Q
;而是 Q
被调用时没有参数。所以这就解释了为什么变量不在参数传递寄存器中。
(eax
奇怪的置零可能是void Q();
instead of void Q(void);
. I'm not sure why the compiler emitted movl [=16=], %eax
instead of the
现在至于lea
,它实际上只是一条算术指令,编译器倾向于那样使用它。参见 What's the purpose of the LEA instruction?。所以 leaq 1(%rdi), %r15
只是将 rdi
中的值加 1,并将结果写入 r15
。 rdi
中的值是否表示地址或数字或其他内容与机器无关。由于 rdi
包含参数 x
,这实际上是在做
a0 = x + 1;
a1 = x + 2;
a2 = x + 3;
// ...
备选方案类似于 movq %rdi, %r15 ; addq , %r15
,它包含更多说明。
当然,这些值被放入被调用者保存的寄存器(或内存,最后两个),这样它们就不会被调用 Q()
.
至于堆栈,P
之前堆栈指针是 16 的倍数,而当我们 call Q
时它必须再次是 16 的倍数。我们的调用者的 call P
将 8 个字节压入堆栈,而序言中的各种寄存器压入又压入 48 个字节。因此,为了最终得到 16 的倍数,我们必须将堆栈指针调整为比 16 的倍数多 8(即 8 的奇数倍)。我们需要 16 个字节用于局部变量,因此我们必须将堆栈指针调整 24。这样就剩下 8 个字节的堆栈将不会用于任何事情,也就是 ??????
at 16(%rsp)
。