汇编:Y86堆栈和调用,pushl / popl和ret指令

时间:2009-06-20 16:17:38

标签: assembly stack y86

除非我将其复制错误,否则上面的代码是由学生在课堂上写的,并由老师帮助/更正:

int array[100], sum, i;

void ini() {
  for(i = 0; i < 100; i++)
    array[i] = i;
}

int main() {
  ini();

  sum = 0;

  for(i = 0; i < 100; i++)
    sum += array[i];
}

.pos 0
  irmovl Stack, %esp
  rrmovl Stack, %ebp

  jmp main

array:
.pos 430

sum: .long 0
i: .long 0

main:
  call ini                     //

  irmovl $0, %eax              // %eax = 0
  irmovl sum, %esi             // %esi = 0xsum
  rmmovl %eax, 0(%esi)         // 0(%esi) = %eax <=> 0(0xsum) = 0 [sum = 0]
  rmmovl %eax, 4(%esi)         // 4(%esi) = %eax <=> 4(0xsum) = 0 [i = 0]

compare:
  irmovl $100, %ebx            // %ebx = 100
  subl %eax, %ebx              // %ebx = %ebx - %eax <=> %ebx = 100 - i
  jle finish                   // Jumps to "finish" if SF=1 pr ZF=0

  mrmovl 0(%esi), %edx         // %edx = 0(%esi) <=> %edx = 0(0xsum) = sum
  addl %eax, %edx              // %edx = %edx + %eax <=> %edx = sum + i => sum
  rmmovl %edx, 0($esi)         // 0(%esi) = %edx <=> 0(0xsum) = sum

  irmovl $1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, 4(%esi)         // 4($esi) = %eax <=> 4(0xsum) = i

  jmp compare                  // Jumps unconditionally to "compare"

ini:
  pushl %ebp                   //
  rrmovl %esp, %ebp            //
  pushl %ebx                   //
  pushl %eax                   //

  irmovl $0, %eax              // %eax = 0
  rmmovl %eax, -8(%ebp)        //

ini_compare:
  irmovl $100, %ecx            // %ecx = 100
  subl %eax, %ecx              // %ecx = %ecx - %eax <=> %ecx = 100 - i
  jle ini_finish               // Jumps to "ini_finish" if SF=1 pr ZF=0

  rrmovl %eax, %ebx            // %ebx = %eax <=> %ebx = i
  addl %eax, $ebx              // %ebx = %ebx + %eax <=> %ebx = i + i = 2i
  addl %ebx, %ebx              // %ebx = %ebx + %ebx <=> %ecx = 2i + 2i = 4i
  rmmovl %eax, array(%ebx)     // array(%ebx) = %eax <=> array(0x4i) = i

  irmovl %1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, -8(%ebp)        //

  jmp ini_compare              // Jumps unconditionally to "ini_compare"

ini_finish:
  irmovl $4, %ebx              //
  addl %ebx, %esp              //
  popl %ebx                    //
  popl %ebp                    //

  ret                          //

.pos 600
  Stack .long 0

正如你所看到的,所有说明中都有很多评论,而且我(其中)大多数都是这些评论,令我困惑的是电话,pushl / popl和ret指令。我不太了解它们,我也不明白堆栈发生了什么以及所有记录都指向了什么。基本上,带有注释(//)的行没有写入任何内容。

我非常了解这一切是如何运作的,希望你们中的一些人可以对所有这些混乱有所了解。

关于我的评论的一些注释:

  • 0xsum:这并不意味着地址是“总和”,这是不可能的。它只是一种在不使用确切内存地址的情况下理解我正在谈论的内容的方法。
  • [sum = 0]:这意味着在我们的C代码中,变量sum将在此时设置为0.
  • i + 1 =&gt; i:这意味着我们将'i'的值递增1,而在下一行'i'实际上表示递增的值。

1 个答案:

答案 0 :(得分:13)

让我们看看一些代码:

main:
  call ini

这会将指令指针的值推送到堆栈(以便稍后可以返回到代码中的这个位置),并跳转到ini标签的地址。 'ret'指令使用存储在堆栈中的值从子例程返回。

以下是子程序的初始化顺序。它将一些寄存器的值保存在堆栈中,并通过将堆栈指针(esp)复制到基址指针寄存器(ebp)来设置堆栈帧。如果子例程具有局部变量,则堆栈指针递减以为堆栈上的变量腾出空间,并且基指针用于访问堆栈帧中的局部变量。在示例中,唯一的局部变量是(未使用的)返回值。

push指令使用要推送的数据大小递减堆栈指针(esp),然后将值存储在该地址。 pop指令执行相反的操作,首先获取值,然后递增堆栈指针。 (请注意,堆栈向下增长,因此堆栈指针地址在堆栈增长时会降低。)

ini:
  pushl %ebp             // save ebp on the stack
  rrmovl %esp, %ebp      // ebp = esp (create stack frame)
  pushl %ebx             // save ebx on the stack
  pushl %eax             // push eax on the stack (only to decrement stack pointer)
  irmovl $0, %eax        // eax = 0
  rmmovl %eax, -8(%ebp)  // store eax at ebp-8 (clear return value)

代码遵循标准模式,因此当没有局部变量时,它看起来有点尴尬,并且有一个未使用的返回值。如果存在局部变量,则使用减法来递减堆栈指针而不是推送eax。

以下是子程序的退出顺序。它将堆栈恢复到创建堆栈帧之前的位置,然后返回到调用子例程的代码。

ini_finish:
   irmovl $4, %ebx   // ebx = 4
   addl %ebx, %esp   // esp += ebx (remove stack frame)
   popl %ebx         // restore ebx from stack
   popl %ebp         // restore ebp from stack
   ret               // get return address from stack and jump there

回应你的意见:

推送并弹出ebx寄存器以保留其值。编译器显然总是将这些代码放在那里,可能是因为寄存器是非常常用的,而不是在这段代码中。同样,总是通过将esp复制到ebp来创建堆栈帧,即使它不是真的需要。

推送eax的指令只是递减堆栈指针。它以小的减量方式完成,因为它比减去堆栈指针更短更快。它保留的空间用于返回值,即使没有使用返回值,编译器显然也会这样做。

在图中,esp寄存器始终将4个字节指向内存过高。请记住,在按下一个值后,堆栈指针会递减,因此它将指向推送的值,而不是下一个值。 (内存地址也是关闭的,它类似于0x600而不是0x20,因为它是声明Stack标签的地方。)