fbpx

Каталог статей

Каталог статей для размещения статей информационного характера

Как выучить

Руководство по сборке X86

Руководство по сборке x86

Это версия, адаптированная Квентином Карбонно из оригинального документа Дэвида Эванса. Синтаксис был изменен с Intel на AT&T, стандартный синтаксис в системах UNIX, и HTML-код был очищен.

Это руководство описывает основы программирования на 32-битном языке ассемблера x86, охватывая небольшое, но полезное подмножество доступных инструкций и директив ассемблера. Существует несколько различных языков ассемблера для генерации машинного кода x86. Тот, который мы будем использовать в CS421, – это ассемблер GNU Assembler (gas). Мы будем использовать стандартный синтаксис AT&T для написания ассемблерного кода x86.

Полный набор инструкций x86 большой и сложный (руководство Intel по набору инструкций x86 составляет более 2900 страниц), и мы не будем рассматривать его весь в этом руководстве. Например, существует 16-битное подмножество набора инструкций x86. Использование 16-битной модели программирования может быть довольно сложным. Она имеет сегментированную модель памяти, больше ограничений на использование регистров и так далее. В этом руководстве мы ограничимся более современными аспектами программирования на x86 и углубимся в набор инструкций лишь настолько подробно, чтобы получить базовое представление о программировании на x86.

Регистры

Современные (т.е. 386 и последующие) процессоры x86 имеют восемь 32-битных регистров общего назначения, как показано на рисунке 1. Названия регистров в основном исторические. Например, EAX раньше называли аккумулятором, поскольку он использовался в ряде арифметических операций, а ECX называли счетчиком, поскольку он использовался для хранения индекса цикла. В то время как большинство регистров утратили свое специальное назначение в современном наборе инструкций, по соглашению два регистра зарезервированы для специальных целей – указатель стека (ESP) и указатель базы (EBP).

Для регистров EAX, EBX, ECX и EDX могут использоваться подразделы. Например, младшие 2 байта EAX можно рассматривать как 16-битный регистр AX. Младший байт AX может использоваться как один 8-битный регистр под названием AL, а старший байт AX может использоваться как один 8-битный регистр под названием AH. Эти имена относятся к одному и тому же физическому регистру. Когда двухбайтовое значение помещается в DX, обновление влияет на значение DH, DL и EDX. Эти подрегистры в основном остались от старых, 16-битных версий набора инструкций. Однако иногда они удобны при работе с данными, размер которых меньше 32 бит (например, 1-байтовые символы ASCII).

Рисунок 1. Регистры x86

Память и режимы адресации

Объявление статических областей данных

Вы можете объявлять статические области данных (аналоги глобальных переменных) в ассемблере x86, используя для этого специальные директивы ассемблера. Объявлению данных должна предшествовать директива .data. После этой директивы можно использовать директивы .byte, .short и .long для объявления одного, двух,

/* Объявляем байт, обозначаемый как location var , содержащий значение 64. */
.byte 10
/* Объявляет байт без метки, содержащий значение 10. Его местоположение – var + 1. */ .short 42
/* Объявить 2-байтовое значение, инициализированное в 42, обозначенное как местоположение x. */ .long 30000
x:
/* Объявите 4-байтовое значение, называемое местоположением y, инициализированное в 30000. */ В отличие от языков высокого уровня, где массивы могут иметь много размеров и доступ к ним осуществляется по индексам, массивы в языке ассемблера x86 – это просто ряд ячеек, расположенных в памяти смежно. Массив может быть объявлен простым перечислением значений, как в первом примере ниже. Для особого случая массива байтов можно использовать строковые литералы. В случае, если большая область памяти заполнена нулями, можно использовать директиву .zero.
y:
.long 1, 2, 3 /* Объявление трех 4-байтовых значений, инициализированных как 1, 2 и 3. Значение в месте s + 8 будет равно 3. */

barr:

s:
.zero 10 /* Объявить 10 байт, начинающихся в месте barr, инициализированных в 0. */
str:
.string “hello” /* Объявляет 6 байт, начинающихся по адресу str, инициализированному значениями символов ASCII для hello, за которыми следует байт nul (0). */
Адресация памяти
Современные x86-совместимые процессоры способны адресовать до 2 32 байт памяти: адреса памяти имеют ширину 32 бита. В приведенных выше примерах, где мы использовали метки для обращения к областям памяти, эти метки фактически заменены ассемблером 32-битными величинами, указывающими адреса в памяти. В дополнение к поддержке ссылок на области памяти по меткам (т.е. константным значениям), x86 обеспечивает гибкую схему вычисления и ссылок на адреса памяти: до двух 32-битных регистров и 32-битная знаковая константа могут быть сложены вместе для вычисления адреса памяти. Один из регистров может быть предварительно умножен на 2, 4 или 8. Режимы адресации могут использоваться со многими инструкциями x86 (мы опишем их в следующем разделе). Здесь мы проиллюстрируем несколько примеров на примере инструкции mov, которая перемещает данные между регистрами и памятью. Эта инструкция имеет два операнда: первый является источником, а второй указывает место назначения.

Некоторые примеры инструкции mov с использованием вычисления адреса:

mov (%ebx), %eax

/* Загружает 4 байта из адреса памяти EBX в EAX. */

mov %ebx, var(,1)

/* Переместите содержимое EBX в 4 байта по адресу памяти var . (Обратите внимание, var – это 32-битная константа). */ mo v-4(%esi), %eax
/* Переместите 4 байта по адресу памяти ESI + (-4) в EAX. */ mov %cl, (%esi,%eax,1)
/* Переместите содержимое CL в байт по адресу ESI+EAX. */ mov (%esi,%ebx,4), %edx
/* Переместите 4 байта данных по адресу ESI+4*EBX в EDX. */ Некоторые примеры некорректных вычислений адресов включают:
mov (%ebx,%ecx,-1), %eax /* Можно только складывать значения регистров. */

mov %ebx, (%eax,%esi,%edi,1)

/* Не более 2 регистров в вычислении адреса. */ Суффиксы операций
В общем случае, предполагаемый размер элемента данных по данному адресу памяти можно определить по инструкции ассемблера, в которой он повторяется Однако в некоторых случаях размер области памяти, на которую ссылаются, неоднозначен. Рассмотрим инструкцию mov $2, (%ebx) . Должна ли эта команда переместить значение 2 в один байт по адресу EBX? Возможно, она должна переместить 32-битное целочисленное представление 2 в 4-байта, начинающиеся по адресу EBX . Поскольку любая из этих интерпретаций может быть правильной, ассемблеру необходимо явно указать, какая из них верна. Этой цели служат префиксы размеров b , w и l, указывающие на размеры 1, 2 и 4 байта соответственно.

movb $2, (%ebx)

/* Перемещение 2 в один байт по адресу, хранящемуся в EBX. */

movw $2, (%ebx)

/* Переместите 16-битное целочисленное представление 2 в 2 байта, начинающиеся по адресу в EBX. */ movl $2, (%ebx)
/* Переместите 32-битное целочисленное представление 2 в 4 байта, начинающиеся по адресу в EBX. */ Инструкции
Машинные инструкции обычно делятся на три категории: перемещение данных, арифметика/логика и поток управления. В этом разделе мы рассмотрим важные примеры инструкций x86 из каждой категории. Этот раздел не следует рассматривать как исчерпывающий список инструкций x86, а скорее как полезное подмножество. Полный список можно найти в справочнике по набору инструкций Intel. Мы используем следующие обозначения:

Любой 32-разрядный регистр ( %eax , %ebx , %ecx , %edx , %esi , %edi , %esp , или %ebp ).

Любой 16-битный регистр ( %ax , %bx , %cx , или %dx )

Любой 8-битный регистр ( %ah , %bh , %ch , %dh , %al , %bl , %cl , или %dl )

Любой регистр
Адрес памяти (например, (%eax) , 4+var(,1) , или (%eax,%ebx,1) )
Любой 32-битный непосредственный
Любой 16-битный непосредственный
Любой 8-битный непосредственный
Любой 8-, 16- или 32-битный непосредственный.
В языке ассемблера все метки и числовые константы, используемые в качестве непосредственных операндов (т.е. не в вычислении адреса, например 3(%eax,%ebx,8) ), всегда имеют префикс в виде знака доллара. При необходимости можно использовать шестнадцатеричную нотацию с префиксом 0x (например, $0xABC ). Без префикса числа интерпретируются в десятичной системе счисления.
Инструкции перемещения данных
Инструкция mov копирует элемент данных, на который ссылается ее первый операнд (т.е. содержимое регистра, памяти или постоянное значение), в место, на которое ссылается ее второй операнд (т.е. регистр или память). Перемещение из регистра в регистр возможно, а прямое перемещение из памяти в память – нет. В тех случаях, когда требуется перемещение в память, содержимое исходной памяти сначала должно быть загружено в регистр, а затем может быть сохранено по адресу памяти назначения.

Синтаксис mov , mov , mov , mov , mov , mov ,

Примеры mov %ebx, %eax – скопировать значение в EBX в EAX movb $5, var(,1) – сохранить значение 5 в байт в месте var

push – Запихнуть в стек

Инструкция push помещает свой операнд на вершину стека, поддерживаемого аппаратным обеспечением, в память. В частности, push сначала уменьшает ESP на 4, а затем помещает свой операнд в содержимое 32-битной области по адресу (%esp) . ESP (указатель стека) уменьшается при push, поскольку стек x86 растет вниз – т.е. стек растет от старших адресов к младшим.

Синтаксис pop pop Примеры pop %edi – выгрузить верхний элемент стека в EDI. pop (%ebx) – выгрузить верхний элемент стека в память в четыре байта, начинающиеся в месте EBX.

lea – Загрузить эффективный адрес

Инструкция lea помещает адрес, указанный ее первым операндом, в регистр, указанный ее вторым операндом. Обратите внимание, содержимое ячейки памяти не загружается, вычисляется только эффективный адрес и помещается в регистр. Это полезно для получения указателя на область памяти или для выполнения простых арифметических операций.

Синтаксис lea ,

Примеры lea (%ebx,%esi,8), %edi – количество EBX+8*ESI помещается в EDI. lea val(,1), %eax – значение val помещается в EAX.

Арифметические и логические инструкции

Инструкция add складывает два своих операнда, сохраняя результат во втором операнде. Обратите внимание, что если оба операнда могут быть регистрами, то максимум один операнд может быть ячейкой памяти.

Синтаксис add , add , add , add , add , add ,

Примеры add $10, %eax – EAX устанавливается в EAX + 10 addb $10, (%eax) – добавить 10 к одному байту, хранящемуся по адресу памяти, сохраненному в EAX.

sub – Вычитание целых чисел

Инструкция sub сохраняет в значении своего второго операнда результат вычитания значения своего первого операнда из значения своего второго операнда. Как и в случае с командой add, если оба операнда могут быть регистрами, то максимум один операнд может быть ячейкой памяти.

Синтаксис sub , sub , sub , sub , sub , sub ,

Примеры sub %ah, %al – AL устанавливается в AL – AH sub $216, %eax – вычесть 216 из значения, хранящегося в EAX

inc, dec – инкремент, декремент

Инструкция inc увеличивает содержимое своего операнда на единицу. Инструкция dec уменьшает содержимое своего операнда на единицу.

Синтаксис inc inc dec dec dec

Примеры dec %eax – вычесть единицу из содержимого EAX incl var(,1) – прибавить единицу к 32-битному целому числу, хранящемуся в месте var

imul – Целочисленное умножение

Инструкция imul имеет два основных формата: двухоперандный (первые два синтаксических листинга выше) и трехоперандный (последние два синтаксических листинга выше).

Двухоперандная форма перемножает два операнда вместе и сохраняет результат во втором операнде. Операнд результата (т.е. второй операнд) должен быть регистром.

Трехоперандная форма перемножает второй и третий операнды вместе и сохраняет результат в последнем операнде. Опять же, операнд результата должен быть регистром. Кроме того, первый операнд может быть только постоянным значением.

Синтаксис imul , imul , imul , imul , imul , ,

Примеры

imul (%ebx), %eax – умножить содержимое EAX на 32-битное содержимое памяти в месте EBX. Сохраните результат в EAX.

idiv – целочисленное деление

Инструкция idiv делит содержимое 64-битного целого числа EDX:EAX (построенного путем просмотра EDX как старших четырех байтов и EAX как младших четырех байтов) на указанное значение операнда. Результат деления сохраняется в EAX, а остаток помещается в EDX.

Синтаксис idiv idiv idiv

Примеры

idiv %ebx – разделить содержимое EDX:EAX

Синтаксис и , и , и , и , и , и ,

или , или , или , или , или , или ,

xor , xor , xor , xor , xor ,

Примеры and $0x0f, %eax – очистить все биты EAX, кроме последних 4. xor %edx, %edx – установить содержимое EDX в ноль.

not – побитовое логическое не

Синтаксис и , и , и , и , и , и ,

Синтаксис not not

Пример not %eax – перевернуть все биты EAX.

neg – Отрицание

Выполняет отрицание содержимого операнда по принципу двойного дополнения.

Синтаксис neg neg

Пример neg %eax – EAX устанавливается в (- EAX).

shl, shr – Сдвиг влево и вправо

Эти инструкции сдвигают биты содержимого первого операнда влево и вправо, заполняя нулями пустые битовые позиции. Сдвинутый операнд может быть сдвинут до 31 места. Количество битов для сдвига задается вторым операндом, который может быть либо 8-битной константой, либо регистром CL. В любом случае, сдвиги на число больше 31 выполняются по модулю 32.

Синтаксис shl , shl , shl %cl, shl %cl,

shr , shr , shr %cl, shr %cl,

Примеры

shr %cl, %ebx – Сохранить в EBX пол результата деления значения EBX на 2 n, где n – значение в CL. Внимание: для отрицательных целых чисел это отличается от семантики деления в языке Си!

Инструкции потока управления

Процессор x86 поддерживает регистр указателя инструкции (EIP), который представляет собой 32-битное значение, указывающее на место в памяти, где начинается текущая инструкция. Обычно он увеличивается, чтобы указать на следующую инструкцию в памяти, начинающуюся после выполнения инструкции. Регистром EIP нельзя манипулировать напрямую, но он обновляется неявно с помощью инструкций потока управления.

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

Вторая инструкция в этом фрагменте кода имеет метку begin . В других местах кода мы можем ссылаться на область памяти, в которой находится эта инструкция, используя более удобное символическое имя begin . Эта метка – просто удобный способ выражения расположения вместо его 32-битного значения.

Передает поток управления программой на инструкцию, находящуюся в ячейке памяти, указанной операндом.

Синтаксис jmp

Пример jmp begin – Переход к инструкции с меткой begin .

j condition – Условный переход

Синтаксис и , и , и , и , и , и ,

Ряду условных ветвей даны имена, которые интуитивно основаны на последней выполненной операции, являющейся специальной инструкцией сравнения cmp (см. ниже). Например, такие условные ветви, как jle и jne, основаны на том, что сначала выполняется операция cmp над нужными операндами.

Синтаксис je (переход при равенстве) jne (переход при неравенстве) jz (переход при нулевом последнем результате) jg (переход при большем числе) jge (переход при большем или равном числе) jl (переход при меньшем числе) jle (переход при меньшем или равном числе)

Пример

Если содержимое EAX меньше или равно содержимому EBX, перейдите к метке done . В противном случае перейдите к следующей инструкции.

cmp – Сравнить

Сравнивает значения двух указанных операндов, устанавливая соответствующие коды состояния в слове состояния машины. Эта инструкция эквивалентна инструкции sub, за исключением того, что результат вычитания отбрасывается, а не заменяет первый операнд.

Синтаксис cmp , cmp , cmp , cmp , cmp ,

Пример cmpb $10, (%ebx) jeq loop

Если байт, хранящийся в ячейке памяти EBX, равен целочисленной константе 10, перейдите в ячейку с меткой loop.

call , ret – вызов и возврат подпрограммы

Эти инструкции реализуют вызов и возврат подпрограммы. Инструкция call сначала помещает текущее место кода в стек памяти с аппаратной поддержкой (подробнее см. инструкцию push), а затем выполняет безусловный переход к месту кода, указанному операндом label. В отличие от простых инструкций перехода, инструкция call сохраняет местоположение, в которое нужно вернуться после завершения подпрограммы.

Инструкция ret реализует механизм возврата подпрограммы. Сначала эта инструкция выгружает местоположение кода из стека памяти, поддерживаемого аппаратными средствами (подробнее см. инструкцию pop). Затем она выполняет безусловный переход к полученному кодовому месту.

Синтаксис call ret

Конвенция вызова

Чтобы позволить отдельным программистам совместно использовать код и разрабатывать библиотеки для использования многими программами, а также для упрощения использования подпрограмм в целом, программисты обычно принимают общую конвенцию вызова. Соглашение о вызове – это протокол о том, как вызывать и возвращаться из подпрограмм. Например, учитывая набор правил соглашения о вызове, программисту не нужно изучать определение подпрограммы, чтобы определить, как передавать параметры в эту подпрограмму. Более того, при наличии набора правил соглашения о вызове можно заставить компиляторы языка высокого уровня следовать этим правилам, что позволит ручным программам на языке ассемблера и программам на языке высокого уровня вызывать друг друга.

На практике возможно множество соглашений о вызовах. Мы опишем широко используемое соглашение о вызове в языке Си. Следование этому соглашению позволит вам писать подпрограммы на языке ассемблера, которые можно безопасно вызывать из кода на языке Си (и Си++), а также вызывать библиотечные функции языка Си из вашего кода на языке ассемблера.

Соглашение о вызовах в языке Си в значительной степени основано на использовании стека, поддерживаемого аппаратным обеспечением. Он основан на инструкциях push, pop, call и ret. Параметры подпрограмм передаются в стеке. Регистры сохраняются на стеке, а локальные переменные, используемые подпрограммами, помещаются в память на стеке. Подавляющее большинство процедурных языков высокого уровня, реализованных на большинстве процессоров

Соглашение о вызове разбивается на два набора правил. Первый набор правил используется вызывающей подпрограммой, а второй набор правил соблюдается автором подпрограммы (вызывающей стороной). Следует подчеркнуть, что ошибки в соблюдении этих правил быстро приводят к фатальным программным ошибкам, так как стек будет находиться в непоследовательном состоянии; поэтому при реализации конвенции вызова в собственных подпрограммах следует проявлять тщательную осторожность.

Стек во время вызова подпрограммы

Хорошим способом визуализации работы конвенции вызова является изображение содержимого близлежащей области стека во время выполнения подпрограммы. На рисунке выше показано содержимое стека во время выполнения подпрограммы с тремя параметрами и тремя локальными переменными. Ячейки, изображенные в стеке, представляют собой участки памяти шириной 32 бита, поэтому адреса ячеек находятся на расстоянии 4 байта друг от друга. Первый параметр находится со смещением в 8 байт от базового указателя. Над параметрами в стеке (и под базовым указателем) инструкция call поместила адрес возврата, что привело к дополнительному смещению на 4 байта от базового указателя до первого параметра. Когда инструкция ret используется для возврата из подпрограммы, она переходит на адрес возврата, хранящийся в стеке.

Правила вызывающей программы

Перед вызовом подпрограммы вызывающая сторона должна сохранить содержимое определенных регистров, которые обозначаются как caller-saved. Регистры, сохраняемые вызывающей программой, – это EAX, ECX, EDX. Поскольку вызываемой подпрограмме разрешено изменять эти регистры, если вызывающая программа полагается на их значения после возврата подпрограммы, вызывающая программа должна поместить значения этих регистров в стек (чтобы их можно было восстановить после возврата подпрограммы).

Чтобы передать параметры подпрограмме, поместите их в стек перед вызовом. Параметры следует заталкивать в инвертированном порядке (т.е. последний параметр первым). Поскольку стек растет вниз, первый параметр будет храниться по наименьшему адресу (такая инверсия параметров исторически использовалась для того, чтобы позволить функциям передавать переменное количество параметров).

Чтобы вызвать подпрограмму, используйте инструкцию call. Эта инструкция помещает адрес возврата поверх параметров в стек и выполняет переход к коду подпрограммы. Это вызовет подпрограмму, которая должна следовать правилам callee, приведенным ниже.

Удалить параметры из стека. Это восстанавливает стек в его состояние перед выполнением вызова.

Восстановить содержимое сохраненных вызывающей программой регистров (EAX, ECX, EDX), выгрузив их из стека. Вызывающая программа может считать, что никакие другие регистры не были изменены подпрограммой.

Пример

В приведенном ниже коде показан вызов функции, который следует правилам вызывающей программы. Вызывающая программа вызывает функцию myFunc, которая принимает три целочисленных параметра. Первый параметр находится в EAX, второй параметр – константа 216; третий параметр находится в ячейке памяти, хранящейся в EBX.

Обратите внимание, что после возврата вызова вызывающая сторона очищает стек с помощью инструкции addio

Занесите значение EBP в стек, а затем скопируйте значение ESP в EBP с помощью следующих инструкций: Это начальное действие сохраняет базовый указатель , EBP. Базовый указатель используется как точка отсчета для поиска параметров и локальных переменных на стеке. Когда подпрограмма выполняется, базовый указатель хранит копию значения указателя стека с момента начала выполнения подпрограммы. Параметры и локальные переменные всегда будут располагаться на известных, постоянных смещениях от значения базового указателя. Мы помещаем старое значение базового указателя в начало подпрограммы, чтобы позже восстановить соответствующее значение базового указателя для вызывающего пользователя, когда подпрограмма вернется. Помните, что вызывающая программа не ожидает, что подпрограмма изменит значение базового указателя. Затем мы перемещаем указатель стека в EBP, чтобы получить точку отсчета для доступа к параметрам и локальным переменным.

Далее выделяем локальные переменные, освобождая место на стеке. Напомним, что стек растет вниз, поэтому, чтобы освободить место на вершине стека, указатель стека должен быть уменьшен. Сумма, на которую уменьшается указатель стека, зависит от количества и размера необходимых локальных переменных. Например, если требуется 3 локальных целых числа (по 4 байта каждое), указатель стека нужно уменьшить на 12, чтобы освободить место для этих локальных переменных (т.е. sub $12, %esp ). Как и в случае с параметрами, локальные переменные будут располагаться на известных смещениях от базового указателя.

  1. Затем сохраните значения регистров, которые будут использоваться функцией. Чтобы сохранить регистры, поместите их в стек. Сохраняемыми регистрами являются EBX, EDI и ESI (ESP и EBP также будут сохранены вызывающим соглашением, но их не нужно выталкивать в стек на этом этапе).
  2. Оставьте возвращаемое значение в EAX.
  3. Восстановите старые значения всех измененных регистров, сохраненных вызовом (EDI и ESI). Содержимое регистров восстанавливается путем выталкивания их из стека. Вытаскивать регистры следует в порядке, обратном порядку их выталкивания.
  1. Деаллокация локальных переменных. Очевидным способом сделать это может быть добавление соответствующего значения к указателю стека (поскольку место было выделено путем вычитания необходимого количества из указателя стека). На практике менее опасным способом деаллокации переменных является перемещение значения базового указателя в указатель стека: mov %ebp, %esp . Это работает, потому что базовый указатель всегда содержит значение, которое указатель стека содержал непосредственно перед выделением локальных переменных.
  2. Непосредственно перед возвратом восстановите значение базового указателя вызывающей команды, выгрузив EBP из стека. Напомним, что первое, что мы сделали при входе в подпрограмму, это вытолкнули базовый указатель, чтобы сохранить его старое значение.

Наконец, вернитесь к вызывающей программе, выполнив инструкцию ret. Эта инструкция найдет и удалит из стека соответствующий адрес возврата.

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

Подпрограмма prologue выполняет стандартные действия по сохранению снимка указателя стека в EBP (базовый указатель), выделению локальных переменных путем уменьшения указателя стека и сохранению значений регистров в стеке.

В теле подпрограммы мы можем видеть использование базового указателя. И параметры, и локальные переменные находятся на постоянных смещениях от базового указателя в течение всего времени выполнения подпрограммы. В частности, мы замечаем, что поскольку параметры были помещены в стек до вызова подпрограммы, они всегда располагаются в стеке ниже базового указателя (т.е. по более высоким адресам). Первый параметр подпрограммы всегда можно найти в ячейке памяти (EBP+8), второй – (EBP+12), третий – (EBP+16). Аналогично, поскольку локальные переменные выделяются после установки базового указателя, они всегда располагаются в стеке выше базового указателя (т.е. по более низким адресам). В частности, первая локальная переменная всегда располагается по адресу (EBP-4), вторая – по адресу (EBP-8) и так далее. Такое обычное использование базового указателя позволяет быстро определить использование локальных переменных и параметров в теле функции.

Эпилог функции, по сути, является зеркальным отражением пролога функции. Значения регистров вызывающей функции восстанавливаются из стека, локальные переменные деаллокируются путем сброса указателя стека, восстанавливается значение базового указателя вызывающей функции, а инструкция ret используется для возврата в соответствующее место кода в вызывающей функции.

  1. Кредиты: Это руководство было первоначально создано Адамом Феррари много лет назад, а затем обновлено Аланом Бэтсоном, Майком Лэком и Анитой Джонс. Оно было пересмотрено для 216-й версии весной 2006 года Дэвидом Эвансом. В конце концов, оно было изменено Квентином Карбонно, чтобы использовать синтаксис AT&T для CS421 в Йеле.

Наконец, вернитесь к вызывающей программе, выполнив инструкцию ret. Эта инструкция найдет и удалит из стека соответствующий адрес возврата.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *