2

我得到了这个简短的 C 代码。

#include <stdint.h>
uint64_t multiply(uint32_t x, uint32_t y) {

uint64_t res;
res = x*y;
return res;
}

int main() {

uint32_t a = 3, b = 5, z;
z = multiply(a,b);
return 0;
}

上面给定的 C 代码还有一个汇编程序代码。我不了解该汇编代码的所有内容。我评论了每一行,你会在每一行的评论中找到我的问题。

汇编代码是:

.text
multiply:
     pushl  %ebp  // stores the stack frame of the calling function on the stack
     movl   %esp, %ebp // takes the current stack pointer and uses it as the frame for the called function
     subl   $16, %esp // it leaves room on the stack, but why 16Bytes. sizeof(res) = 8Bytes
     movl   8(%ebp), %eax // I don't know quite what "8(%ebp) mean? It has to do something with res, because
     imull  12(%ebp), %eax // here is the multiplication done. And again "12(%ebp).
     movl   %eax, -8(%ebp) // Now, we got a negative number in front of. How to interpret this?
     movl   $0, -4(%ebp) // here as well
     movl   -8(%ebp), %eax // and here again.
     movl   -4(%ebp), %edx // also here
     leave
     ret
main:
     pushl  %ebp // stores the stack frame of the calling function on the stack
     movl   %esp, %ebp // // takes the current stack pointer and uses it as the frame for the called function
     andl   $-8, %esp // what happens here and why?
     subl   $24, %esp // here, it leaves room for local variables, but why 24 bytes? a, b, c: the size of each of them is 4 Bytes. So 3*4 = 12
     movl   $3, 20(%esp) // 3 gets pushed on the stack
     movl   $5, 16(%esp) // 5 also get pushed on the stack
     movl   16(%esp), %eax // what does 16(%esp) mean and what happened with z?
     movl   %eax, 4(%esp) // we got the here as well
     movl   20(%esp), %eax // and also here
     movl   %eax, (%esp) // what does happen in this line?
     call   multiply  // thats clear, the function multiply gets called
     movl   %eax, 12(%esp) // it looks like the same as two lines before, except it contains the number 12
     movl   $0, %eax // I suppose, this line is because of "return 0;"
     leave
     ret
4

3 回答 3

2

相对于 %ebp 的负引用用于堆栈上的局部变量。

 movl   8(%ebp), %eax // I don't know quite what "8(%ebp) mean? It has to do something with res, because`

%eax = x

 imull  12(%ebp), %eax // here is the multiplication done. And again "12(%ebp).

%eax = %eax * y

 movl   %eax, -8(%ebp) // Now, we got a negative number in front of. How to interpret this?

(u_int32_t)res = %eax // 设置 res 的低 32 位

 movl   $0, -4(%ebp) // here as well

清除 res 的高 32 位以将 32 位乘法结果扩展到 uint64_t

 movl   -8(%ebp), %eax // and here again.
 movl   -4(%ebp), %edx // also here

返回 ret; //64 位结果作为一对 32 位寄存器返回 %edx:%eax

至于主要的,请参阅x86 调用约定,这可能有助于理解发生的事情。

 andl   $-8, %esp // what happens here and why?

堆栈边界由 8 对齐。我相信这是 ABI 要求

 subl   $24, %esp // here, it leaves room for local variables, but why 24 bytes? a, b, c: the size of each of them is 4 Bytes. So 3*4 = 12

8 的倍数(可能由于对齐要求)

 movl   $3, 20(%esp) // 3 gets pushed on the stack

a = 3

 movl   $5, 16(%esp) // 5 also get pushed on the stack

b = 5

 movl   16(%esp), %eax // what does 16(%esp) mean and what happened with z?

%eax = b

z 为 12(%esp),尚未使用。

 movl   %eax, 4(%esp) // we got the here as well

将 b 放入堆栈(multiply() 的第二个参数)

 movl   20(%esp), %eax // and also here

%eax = 一个

 movl   %eax, (%esp) // what does happen in this line?

将 a 放入堆栈(multiply() 的第一个参数)

 call   multiply  // thats clear, the function multiply gets called

乘以 %edx:%eax 返回 64 位结果

 movl   %eax, 12(%esp) // it looks like the same as two lines before, except it contains the number 12

z = (uint32_t) 乘法()

 movl   $0, %eax // I suppose, this line is because of "return 0;"

对。返回0;

于 2013-11-14T18:55:26.560 回答
1

Arguments are pushed onto the stack when the function is called. Inside the function, the stack pointer at that time is saved as the base pointer. (You got that much already.) The base pointer is used as a fixed location from which to reference arguments (which are above it, hence the positive offsets) and local variables (which are below it, hence the negative offsets).

The advantage of using a base pointer is that it is stable throughout the entire function, even when the stack pointer changes (due to function calls and new scopes).

So 8(%ebp) is one argument, and 12(%ebp) is the other.

The code is likely using more space on the stack than it needs to, because it is using temporary variables that could be optimized out of you had optimization turned on.

You might find this helpful: http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames

于 2013-11-14T18:07:03.490 回答
1

我开始输入这个作为评论,但它太长了,不适合。

您可以编译您的示例,-masm=intel以便程序集更具可读性。另外,不要将pushandpop指令与mov. push并且pop 总是esp在取消引用地址之前分别递增和递减,而mov没有。

有两种方法可以将值存储到堆栈中。您可以一次将push每个项目放到一个项目上,也可以预先分配所需的空间,然后使用mov+ 相对偏移量将每个值加载到堆栈槽中espebp

在您的示例中,gcc 选择了第二种方法,因为这通常更快,因为与第一种方法不同,您不会esp在将值保存到堆栈之前不断递增。

为了解决您在评论中提出的其他问题,x86 指令集没有mov将值从内存a位置直接复制到另一个内存位置的指令b。看到这样的代码并不少见:

  mov   eax, [esp+16]
  mov   [esp+4], eax
  mov   eax, [esp+20]
  mov   [esp], eax
  call  multiply(unsigned int, unsigned int)
  mov   [esp+12], eax

寄存器eax被用作中间临时变量,以帮助在两个堆栈位置之间复制数据。您可以将以上内容精神翻译为:

esp[4] = esp[16]; // argument 2
esp[0] = esp[20]; // argument 1
call multiply
esp[12] = eax;    // eax has return value

以下是调用之前堆栈的大致样子multiply

lower addr    esp       =>  uint32_t:a_copy = 3 <--.  arg1 to 'multiply'
              esp + 4       uint32_t:b_copy = 5 <--.  arg2 to 'multiply'
    ^         esp + 8       ????
    ^         esp + 12      uint32_t:z = ?      <--.
    |         esp + 16      uint32_t:b = 5         |  local variables in 'main'
    |         esp + 20      uint32_t:a = 3      <--.
    |         ...
    |         ...
higher addr   ebp           previous frame
于 2013-11-15T03:50:39.490 回答