【发布时间】:2020-04-20 20:17:14
【问题描述】:
我正在阅读计算机系统:程序员视角第 3 版,3.10.5 Supporting Variable-Size Stack Frames, Figure 3.43 中的程序集让我感到困惑。
本书的部分试图解释可变大小的堆栈帧是如何生成的,并给出了一个 C 代码及其汇编版本作为示例。
这里是C和汇编的代码(书中图3.43):
不知道汇编中line 8-10有什么用。为什么不在第 7 行之后使用movq %rsp, %r8?
(a) C 代码
long vframe(long n, long idx, long *q) {
long i;
long *p[n];
p[0] = &i;
for (i = 1; i < n; i++)
p[i] = q;
return *p[idx];
}
(b) 生成的汇编代码部分
vframe:
2: pushq %rbp
3: movq %rsp, %rbp
4: subq $16, %rsp
5: leaq 22(, %rdi, 8), %rax
6: andq $-16, %rax
7: subq %rax, %rsp
8: leaq 7(%rsp), %rax
9: shrq $3, %rax
10: leaq 0(, %rax, 8), %r8
11: movq %r8, %rcx
................................
12: L3:
13: movq %rdx, (%rcx, %rax, 8)
14: addq $1, %rax
15: movq %rax, -8(%rbp)
16: L2:
17: movq -8(%rbp), %rax
18: cmpq %rdi, %rax
19: jl L3
20: leave
21: ret
这是我的想法:
在第 7 行之后,%rsp 应该是 16 的倍数(%rsp 在调用 vframe 之前应该是 16 的倍数,因为堆栈帧对齐。当调用 vframe 时,%rsp 是减去 8 以保存调用者的返回地址,然后第 2 行中的 pushq 指令将 %rsp 再减去 8,并在第 4 行中减去 16。所以在第 7 行的开头,%rsp 是16的倍数。在第7行中,%rsp减去%rax。由于第6行使%rax成为16的倍数,所以第7行的结果是设置%rsp是16的倍数)这意味着%rsp 的低 4 位全为零。
然后在第 8 行,%rsp+7 存储在 %rax 中,在第 9 行,%rax 逻辑右移 3 位,在第 10 行,%rax*8 存储在 %r8 中。
在第 7 行之后,%rsp 的低 4 位全为零。在第 8 行 %rsp+7 只是使低 3 位全为 1,第 9 行截断这 3 个,在第 10 行 %rax*8 使结果左移 3 位。所以最终的结果应该就是原来的%rsp(第7行的结果)。
所以我想知道第 8-10 行是否没用。
为什么不在第 7 行之后使用 movq %rsp, %r8 并删除原来的第 8-10 行?
【问题讨论】:
-
嘿@NameNull。让一个人阅读/拥有您提到的书的可能性很小。人们不会走极端,比如找书只回答你的问题。因此,最好在此处提供所有相关信息,这些信息会在书中看到。希望您已经阅读了How To Ask 链接。
-
@RC0993,您认为缺少哪些信息?这个问题对我来说似乎很完整。
-
NameNull,你的分析是正确的。这不是很好的代码。我唯一能想到的是,编译器有一些通用的代码序列,无论是否需要,它都可以用于对齐,并且没有动力优化可变大小的数组,因为它们不常用。
-
@prl 我发现,在我提到的书中的一个实践问题中,它在第 4 行的末尾给了我一些特定的 %rsp 值,然后要求我计算以下修改的值寄存器。而那些给定的 %rsp 值并不都是 16 的倍数。所以我猜这本书并没有假设第 7 行末尾的 %rsp 是 16 的倍数。解决方案说第 8-10 行的目的是对齐%r8 到 8 的倍数,以便指针数组对齐(%r8 是数组的地址),如果 %rsp 不是 16 的倍数,这很有意义。
-
是的,这没用而且很奇怪。我能够使用 gcc4.8.5
-Og重现本书的 asm 输出。 (-O1优化了循环中i的存储/重新加载,因为p[i]实际上不能别名为i,即使它们都是long)godbolt.org/z/qwQxBu。因此,与其他一些 CS:APP 示例不同,这不是作者在伪造的 GCC 输出中编辑并弄错的情况。这种疯狂是真实的。您所询问的指令甚至在-O2甚至在当前的 GCC9.2 中都存在!是的,由于 sizeof(long)
标签: c assembly x86-64 callstack stack-frame