Добавлена библиотека команд ассемблера для языков программирования

This commit is contained in:
LeterZP
2026-10-10 01:12:19 +03:00
parent b87a5d51cf
commit 6e2835d301
4 changed files with 337 additions and 10 deletions
@@ -34,11 +34,11 @@
>**Переходы** - инструкции, которые явно меняют порядок выполнения программы.
Безусловный переход к необходимой инструкции по адресу выполняется с помощбю инструкции `jmp`. Инструкцию `jmp addr` можно интерпретировать как команду `mov rip, addr` (сама такая команда, к слову, исполнена быть [[#Регистры|не может]].
Безусловный переход к необходимой инструкции по адресу выполняется с помощбю [инструкции](Библиотека%20команд%20ассемблера.md#JMP) `jmp`. Команду `jmp addr` можно интерпретировать как команду `mov rip, addr` (сама такая команда, к слову, исполнена быть [[#Регистры|не может]].
Но переходы также бывают и условными, то есть исполняются только при определенном условии. Чаще всего такие условия устанавливаются по [[#Регистры|регистру]] `eflags/rflags` во время исполнения программы. Почти для каждого флага (коих всего шесть - CF (Carry Flag), PF (Parity Flag), AF (Auxiliary Carry Flag), ZF (Zero Flag), SF (Sign Flag) и OF (Overflow Flag), но AF при условных переходах не используется) существует как минимум по две инструкции перехода: если флаг установлен в ноль или в единицу. Помимо этого, существуют другие полезные инструкции, такие как `ja`, `jb` (для беззнаковых числе), `jg`, `jl` (для знаковых чисел), `jae`, `jle` и подобные.
Но переходы также бывают и условными, то есть исполняются только при определенном условии. Чаще всего такие условия устанавливаются по [[#Регистры|регистру]] `eflags/rflags` во время исполнения программы. Почти для каждого флага (коих всего шесть - CF (Carry Flag), PF (Parity Flag), AF (Auxiliary Carry Flag), ZF (Zero Flag), SF (Sign Flag) и OF (Overflow Flag), но AF при условных переходах не используется) существует как минимум по две инструкции перехода: если флаг установлен в ноль или в единицу. Помимо этого, существуют другие полезные [инструкции](Библиотека%20команд%20ассемблера.md#JMP), такие как `ja`, `jb` (для беззнаковых числе), `jg`, `jl` (для знаковых чисел), `jae`, `jle` и подобные.
Для того, чтобы поставить необходимые для перехода флаги, в основном используются инструкция `test` или инструкция `cmp`, которые эти флаги ставят. Для проверки какого-либо регистра `reg` на ноль чаще всего используют команду `test reg, reg`.
Для того, чтобы поставить необходимые для перехода флаги, в основном используются [инструкция](Библиотека%20команд%20ассемблера.md#TEST) `test` или [инструкция](Библиотека%20команд%20ассемблера.md#CMP) `cmp`, которые эти флаги ставят. Для проверки какого-либо регистра `reg` на ноль чаще всего используют команду `test reg, reg`.
### Локальные метки
Для того, чтобы выделять различные участки подпрограммы, используются локальные метки.
@@ -72,12 +72,12 @@
1. создать для нее [метку](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии);
2. выделить код функции отдельно по [метке](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии).
По факту, функции работают как [[#Переходы|переход]] с возвратом в место вызова. При вызове функции адрес возврата сохраняется в [[#Стек|стек]]. Для вызова функции используется инструкция `call` (буквально `call addr` можно разложить как `push rip` и `jmp addr`, хотя вручную такие команды выполнить [[#Регистры|не получится]]).
По факту, функции работают как [[#Переходы|переход]] с возвратом в место вызова. При вызове функции адрес возврата сохраняется в [[#Стек|стек]]. Для вызова функции используется [инструкция](Библиотека%20команд%20ассемблера.md#CALL) `call` (буквально `call addr` можно разложить как `push rip` и `jmp addr`, хотя вручную такие команды выполнить [[#Регистры|не получится]]).
Можно расписать вызов функции по шагам:
1. Сохранить все важные значения [[#Соглашение%20вызова|caller-saved]] регистров (их можно сохранить в стеке).
2. Выставить необходимые [[#Функции%20с%20аргументами|аргументы]] в нужные регистры.
3. Вызвать функцию инструкцией `call`.
3. Вызвать функцию [инструкцией](Библиотека%20команд%20ассемблера.md#CALL) `call`.
4. Сохранить [[#Возврат значений из функции|результат работы]] функции из `rax`.
5. Вернуть [[#Соглашение%20вызова|caller-saved]] регистры в исходное положение.
@@ -86,7 +86,7 @@
В качестве примера выделения функции можно рассмотреть [простейший пример](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Простейшая%20программа) программы на ассемблере. Есть смысл выделить в этой программе функцию выхода: тогда можно будет обращаться к ней из любого места программы.
Для определения функции необходимо использовать [метку](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии), которая на эту функцию будет указывать: в данном случае, очевидно, метка будет называться `exit`. После [метки](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии) идет выделенный для этой функции код. Для вызова функции в программе будет использоваться инструкция `call`.
Для определения функции необходимо использовать [метку](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии), которая на эту функцию будет указывать: в данном случае, очевидно, метка будет называться `exit`. После [метки](Среда%20выполнения.%20Основы%20языка%20ассемблера.md#Секции,%20метки%20и%20комментарии) идет выделенный для этой функции код. Для вызова функции в программе будет использоваться [инструкция](Библиотека%20команд%20ассемблера.md#CALL) `call`.
Таким образом получим новую программу.
```
@@ -111,13 +111,13 @@ _start:
```
### Возврат из функций
Вообще, подпрограмма обязана иметь возможность быть вызвана отовсюду и вернутся в место вызова. Для того, чтобы завершить функцию и вернутся к месту ее вызова, используется инструкция `ret` (буквально команду `ret` можно интерпретировать как `pop rip`).
Вообще, подпрограмма обязана иметь возможность быть вызвана отовсюду и вернутся в место вызова. Для того, чтобы завершить функцию и вернутся к месту ее вызова, используется [инструкция](Библиотека%20команд%20ассемблера.md#RET) `ret` (буквально команду `ret` можно интерпретировать как `pop rip`).
Не стоит завершать функцию, если [[#Стек|стек]] не находится в том же положении, в котором был в начале инструкции. Иначе программа в качестве адреса возврата из [[#Стек|стека]] возьмет первую попавшуюся переменную, которая в этот момент будет на вершине стека, и скорее всего переход не удастся.
При выполнении подпрограммы для завершения программы с системным выходом `exit` можно не писать возврат из подпрограммы, так как программа все равно уже не будет работать.
При этом инструкцию `call` можно заменить на инструкцию `jmp` в случае, если возврат из подпрограммы не подразумевается (к примеру, в подпрограмме выполняется системный вызов `exit`).
При этом [инструкцию](Библиотека%20команд%20ассемблера.md#CALL) `call` можно заменить на [инструкцию](Библиотека%20команд%20ассемблера.md#JMP) `jmp` в случае, если возврат из подпрограммы не подразумевается (к примеру, в подпрограмме выполняется системный вызов `exit`).
### Функции с аргументами
>**Аргумент** - кусок данных, который нужен функции для работы.
@@ -172,7 +172,7 @@ sub rsp, 24
Из-за того, что регистр `rbp` является callee-saved, его необходимо отдельно сохранять в начале вызываемой подпрограммы. Это показано в третьем способе выделения памяти в стеке.
Выделение памяти под стек также иногда называется прологом, а в ассемблере существует инструкция `enter`, которая реализует третий способ пролога. В то же время освобождение памяти в стеке также иногда называется эпилогом, и в ассемблере для него существует функция `leave`.
Выделение памяти под стек также иногда называется прологом, а в ассемблере существует [инструкция](Библиотека%20команд%20ассемблера.md#ENTER) `enter`, которая реализует третий способ пролога. В то же время освобождение памяти в стеке также иногда называется эпилогом, и в ассемблере для него существует [инструкция](Библиотека%20команд%20ассемблера.md#LEAVE) `leave`.
### Выравнивание стека
При работе со стеком принято выравнивать значение стека по 16 байтам. Это значит, что при выравнивании стека под каждое значение внутри отводится по 16 байт. В самом начале стек изначально выравнен по 16, так как в начале программы у функции `_start` нет адреса возврата.