Сборка:Инструкции стекирования и вызова Y86, pushl / popl и ret

StackOverflow https://stackoverflow.com/questions/1021935

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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: Это не означает, что адрес - "сумма", это было бы невозможно.Это просто средство понять, о чем я говорю, без использования точного адреса памяти.
  • [сумма = 0]: Это означает, что в нашем C-коде переменная sum на этом этапе будет установлена равной 0.
  • i + 1 => i: Это означает, что мы увеличиваем значение 'i' на единицу и что в следующей строке 'i' фактически будет представлять это увеличенное значение.
Это было полезно?

Решение

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

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, поскольку именно там объявляется метка стека.)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top