Давайте посмотрим на код:
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, так как именно здесь объявлена метка стека.)