Что это за код пролога/эпилога для сборки, выполняемый с помощью rbp/rsp/leave?

Я только начинаю изучать сборку для mac с помощью компилятора GCC для сборки моего кода. К сожалению, есть ОЧЕНЬ ограниченные ресурсы для обучения тому, как это сделать, если вы новичок. Наконец-то мне удалось найти простой образец кода, который я мог бы начать разорвать голову, и я получил его, чтобы собрать и запустить правильно. Вот код:

.text # start of code indicator.
.globl _main # make the main function visible to the outside.
_main: # actually label this spot as the start of our main function.
 push %rbp # save the base pointer to the stack.
 mov %rsp, %rbp # put the previous stack pointer into the base pointer.
 subl $8, %esp # Balance the stack onto a 16-byte boundary.
 movl $0, %eax # Stuff 0 into EAX, which is where result values go.
 leave # leave cleans up base and stack pointers again.
 ret

Комментарии объясняют некоторые вещи в коде (я понимаю, что делают строки 2-5), но я не понимаю, что это значит. Я понимаю основы того, что регистры и что каждый регистр здесь (rbp, rsp, esp и eax) используется и насколько они велики, я также понимаю (как правило), что такое стек, но это все еще идет по моей голове. Может ли кто-нибудь сказать мне, что именно это делает? Кроме того, может ли кто-нибудь указать мне в сторону хорошего учебника для новичков?

1 ответ

Стек - это структура данных, которая следует принцип LIFO. В то время как стеки в повседневной жизни (за пределами компьютеров, я имею в виду) растут вверх, стеки в процессорах x86 и x86-64 растут вниз. См. статья Wikibooks о стеке x86 (но учтите, что примеры кода - это 32-разрядный x86-код в синтаксисе Intel, а ваш код - 64 -битный код x86-64 в синтаксисе AT & T).

Итак, что делает ваш код (мои объяснения здесь приведены с синтаксисом Intel):

push %rbp

Накладывает rbp на стек, практически вычитая 8 из rsp (поскольку размер rbp равен 8 байтам), а затем сохраняет rbp до [ss:rsp].

Итак, в синтаксисе Intel push rbp практически это делает:

sub rsp, 8
mov [ss:rsp], rbp

Тогда:

mov %rsp, %rbp

Это очевидно. Просто сохраните значение rsp в rbp.

subl $8, %esp

Вычтите 8 из esp и сохраните его в esp. На самом деле это ошибка в коде, даже если здесь нет никаких проблем. Любая команда с 32-битным регистром (eax, ***, ecx, edx, ***, esp, esi или edi)) в качестве адресата в x86-64 устанавливает самую верхнюю 32 бита соответствующего 64-битного регистра (rax, rbx, rcx, rdx, rbp, rsp, rsi или rdi) до нуля, в результате чего указатель стека где-то ниже предела 4 гигабайта, эффективно это делает (в синтаксисе Intel):

sub rsp,8
and rsp,0x00000000ffffffff

Изменить: дополнительные последствия sub esp,8 ниже.

Однако это не создает проблем на компьютере с объемом памяти менее 4 Гбайт. На компьютерах с памятью более 4 ГБ может возникнуть ошибка сегментации. leave ниже в вашем коде возвращается значение sane на rsp. Обычно в коде x86-64 вам не нужно esp никогда (исключая, возможно, некоторые оптимизации или настройки). Чтобы исправить эту ошибку:

subq $8, %rsp

До сих пор настала стандартная последовательность ввода (замените $8 в соответствии с использованием стека). В Wikibooks есть полезная статья о функциях x86 и стековых фреймах (но обратите внимание, что она использует 32-разрядную сборку x86 с синтаксисом Intel, а не 64-разрядную x86-64 с синтаксисом AT & T).

Тогда:

movl $0, %eax

Это очевидно. Храните 0 в eax. Это не имеет никакого отношения к стеку.

leave

Это эквивалентно mov rsp, rbp, за которым следует pop rbp.

ret

И это, наконец, устанавливает rip значение, сохраненное в [ss:rsp], эффективно возвращая указатель кода обратно туда, где была вызвана эта процедура, и добавляет 8 к rsp.

licensed under cc by-sa 3.0 with attribution.