【问题标题】:x86 Procedure Call Memory Allocationx86 过程调用内存分配
【发布时间】:2015-04-26 08:20:13
【问题描述】:

所以我的课本上有一个问题(计算机系统:程序员的视角问题 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 $20, %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 $4, %esp
13 movl -4(%ebp), %eax
14 imull -8(%ebp), %eax
15 leave
16 ret

它在汇编代码第 4 行询问为什么 prod 在堆栈上分配 20 个字节。

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

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

【问题讨论】:

  • 我觉得这段代码做了一点优化。什么是调用约定? cdecl?
  • @CollinDauphinee 它没有说,但我认为它是 cdecl,因为教科书告诉我们 eax、ecx 和 edx 是调用者保存的,这就是 cdecl 所具有的。
  • 是的,我知道这段代码在做什么,我只是想找出解释它的最佳方式。这是一个可怕的问题,除非您获得了word_sum 并且没有将其包含在问题中。
  • @CollinDauphinee 是的,我们得到了 word_sum。抱歉,我认为我们不需要它来回答这个问题。我会编辑帖子。

标签: c assembly struct x86 att


【解决方案1】:

s1 分配 8 个字节,s2 分配 8 个字节,还有 4 个字节用于传递 word_sum 一个地址来存储结果。


我是怎么知道的?

如果我们查看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

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

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

如果我们看起来更低,我们可以确认我们的预期:

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

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

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


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

【讨论】:

  • 您能解释一下保存 ECX 的 4 个字节吗?是否与调用者保存有关?
  • 希望你能理解这一点。这是一个很难解释的问题,因为这里正在进行一些优化。
  • 感谢您的详细编辑。如果您不介意回答,我还有几个问题: 1. 为什么它只有 s2 的地址而不是 s1 的地址? 2.如何知道哪些args存放在哪些寄存器中?
  • 我实际上错了,它保留寄存器,因为它在调用后不需要它们。额外的 4 个字节是被调用者应该存储其返回值的地址。请参阅(难以理解)解释。
  • 谢谢!这很有帮助。
猜你喜欢
  • 2019-07-19
  • 2018-05-11
  • 2013-08-18
  • 2013-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-24
  • 1970-01-01
相关资源
最近更新 更多