Сборка: Y86 Стек и вызов, pushl / popl и ret инструкции - PullRequest
4 голосов
/ 20 июня 2009

Если я не скопировал его неправильно, приведенный выше код был написан на доске учеником с помощью / исправлениями учителя:

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

Как видите, во всех инструкциях есть куча комментариев, и я получил (я думаю) большинство из них, что меня смущает, так это инструкции call, pushl / popl и ret. Я не совсем понимаю их, и я также не понимаю, что происходит со стеком и куда указывают все записи. В основном это строки с комментариями (//), на которых ничего не написано.

Очень важно, чтобы я понял, как все это работает, надеюсь, некоторые из вас смогут пролить свет на весь этот беспорядок.

Некоторые примечания к моим комментариям:

  • 0xsum: Это не означает, что адрес является «суммой», это было бы невозможно. Это просто способ понять, о чем я говорю, не используя точный адрес памяти.
  • [sum = 0]: Это означает, что в нашем C-коде переменная sum в этой точке будет установлена ​​равной 0.
  • i + 1 => i: Это означает, что мы увеличиваем значение 'i' на единицу, и что в следующей строке 'i' будет фактически представлять это увеличенное значение.

1 Ответ

13 голосов
/ 20 июня 2009

Давайте посмотрим на код:

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 постоянно указывает на четыре байта в памяти. Помните, что указатель стека уменьшается после нажатия значения, поэтому он будет указывать на переданное значение, а не на следующее значение. (Адреса памяти также очень плохие, это что-то вроде 0x600, а не 0x20, так как именно здесь объявлена ​​метка стека.)

...