x86 过程调用内存分配

x86 Procedure Call Memory Allocation

所以我的课本(计算机系统:一个程序员的视角问题3.64)有一个问题:

它给出了这样的代码:

typedef struct {
 int a;
 int *p;
} str1;

typedef struct {
 int sum;
 int diff;
} str2;

str2 word_sum(str1 s1) {
 str2 result;
 result.sum = s1.a + *s1.p;
 result.diff = s1.a - *s1.p;
 return result;
}

int prod(int x, int y) {
 str1 s1;
 str2 s2;
 s1.a = x;
 s1.p = &y;
 s2 = word_sum(s1);
 return s2.sum * s2.diff;
}

然后是 prod 和 word_sum 函数的汇编代码:

1 word_sum:
2  pushl %ebp
3  movl %esp, %ebp
4  pushl %ebx
5  movl 8(%ebp), %eax
6  movl 12(%ebp), %ebx
7  movl 16(%ebp), %edx
8  movl (%edx), %edx
9  movl %ebx, %ecx
10 subl %edx, %ecx
11 movl %ecx, 4(%eax)
12 addl %ebx, %edx
13 movl %edx, (%eax)
14 popl %ebx
15 popl %ebp

1 prod:
2  pushl %ebp
3  movl %esp, %ebp
4  subl , %esp
5  leal 12(%ebp), %edx
6  leal -8(%ebp), %ecx
7  movl 8(%ebp), %eax
8  movl %eax, 4(%esp)
9  movl %edx, 8(%esp)
10 movl %ecx, (%esp)
11 call word_sum
12 subl , %esp
13 movl -4(%ebp), %eax
14 imull -8(%ebp), %eax
15 leave
16 ret

并且询问为什么prod在第4行汇编代码中在栈上分配了20个字节

我可以看到它会为 str1 和 str2 各分配 8 个字节,但我不知道第 5 个 4 字节内存分配是什么。

此外,你们对学习 x86 堆栈框架结构和过程调用有什么建议(视频、文章、博客文章)吗?目前在我的计算机体系结构课程中非常迷茫。

s1分配了 8 个字节,s2 分配了 8 个字节,word_sum 分配了 4 个字节用于存储结果的地址。


我是怎么想出来的?

如果我们查看 prod 的顶部,我们会看到:

5  leal 12(%ebp), %edx
6  leal -8(%ebp), %ecx
7  movl 8(%ebp), %eax

第 5 行和第 7 行是唯一访问调用者堆栈帧的指令,因此它们必须获取 xy。我们知道我们正在存储指向 y 的指针并且第 5 行是 lea 指令,因此我们可以假设 EDX 持有 &y 并且 EAX 持有 x。这仍然留下 ECX,它持有指向我们堆栈帧中某些内容的指针。

继续,我们看到它在我们的堆栈上存储 EAX、EDX 和 ECX,然后调用 word_sum:

8  movl %eax, 4(%esp)
9  movl %edx, 8(%esp)
10 movl %ecx, (%esp)
11 call word_sum

我们知道 EAX 和 EDX 保存需要存储在 s1 中的值。我们知道s1会被传递给word_sum,参数在栈顶传递。第 8 行和第 9 行将 EAX 和 EDX 存储在非常靠近堆栈顶部的位置,因此我们可以假设这是 s1.

return 结构 期望在堆栈顶部传递额外指针的函数 。这是它应该存储它的 return 值的地址。我们存储在堆栈顶部的唯一其他东西是 ECX,我们知道我们将 word_sum 的结果存储在 s2 中,因此 ECX 必须是指向 [=14] 的指针=].

我们现在已经推测出每个寄存器的内容; EAX 为 x,EDX 为 &y,ECX 为 &s2.

如果我们往下看,可以确认我们的预期:

13 movl -4(%ebp), %eax
14 imull -8(%ebp), %eax

我们知道这个函数的结果是s2.sum * s2.diff。有一个 imul 指令,我们将 s2.sum 乘以 s2.diff,所以 EBP-8 必须指向 s2.sum,EBP-4 必须指向 s2.diff .

如果我们回溯到第 6 行,我们会看到 EBP-8 存储在 ECX 中,我们正确地怀疑它是指向 s2.

的指针

一般来说,调试此类问题几乎完全是利用您对生成程序集的代码的了解来进行有根据的猜测,然后使用排除法来确认您的猜测是正确的。