Введение

Семья обратно совместимых ассемблерных языков — это название семейства ассемблерных языков, обеспечивающих определенный уровень обратной совместимости с процессорами, начиная с микропроцессора Intel 8008, представленного в апреле 1972 года. Он используется для генерации объектного кода для процессоров семейства x86. Ассемблер, рассматриваемый как язык программирования, является машинно-зависимым и низкоуровневым. Как и все ассемблерные языки, x86 ассемблер использует мнемоники для представления базовых инструкций процессора, или машинного кода. Ассемблерные языки чаще всего применяются в задачах, требующих высокой детализации и критичных ко времени, таких как небольшие встраиваемые системы реального времени, ядра операционных систем и драйверы устройств, но также могут использоваться и в других приложениях. Компилятор иногда генерирует ассемблерный код в качестве промежуточного этапа при переводе программы высокого уровня в машинный код.

Переключение режимов

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

Примеры

При компьютере с устаревшим BIOS, BIOS и загрузчик работают в реальном режиме. Ядро 64-битной операционной системы проверяет и переключает процессор в длинный режим, а затем запускает новые потоки ядра, выполняющие 64-битный код. При компьютере с UEFI, прошивка UEFI (за исключением CSM и устаревших ROM-модулей), загрузчик UEFI и ядро операционной системы UEFI работают в длинном режиме.

Инструкции стека

Архитектура x86 имеет аппаратную поддержку механизма стека выполнения. Инструкции, такие как push, pop, call и ret, используются с правильно настроенным стеком для передачи параметров, выделения места для локальных данных и сохранения и восстановления точек возврата из подпрограмм. Инструкция ret size особенно полезна для реализации эффективных (и быстрых) соглашений о вызовах, в которых вызываемая функция отвечает за освобождение места в стеке, занятого параметрами. При создании стекового фрейма для хранения локальных данных рекурсивной процедуры существует несколько подходов. Инструкция enter высокого уровня (представленная с 80186) принимает аргумент глубины вложенности процедуры, а также аргумент размера локальных переменных и может оказаться быстрее, чем более явная работа с регистрами (например, push bp; mov bp, sp; sub sp, size). То, быстрее она или медленнее, зависит от конкретной реализации процессора x86, а также от соглашения о вызовах, используемого компилятором, программистом или конкретным кодом программы. Большинство программ для x86 предназначены для работы на процессорах x86 от разных производителей и на различных технологических поколениях, что подразумевает значительные различия в микроархитектуре и микрокоде, а также в выборе схемотехнических и транзисторных решений. Полный набор режимов адресации (включая непосредственную адресацию и base+offset) даже для инструкций, таких как push и pop, упрощает прямое использование стека для хранения целых чисел, чисел с плавающей точкой и адресов, а также позволяет поддерживать относительно простые спецификации и механизмы ABI по сравнению с некоторыми RISC-архитектурами (которым требуется более подробная информация о стеке вызовов).

Инструкции целочисленного АЛУ

Ассамблер x86 включает стандартные математические операции: сложение, вычитание, отрицание, умножение на целое (imul) и деление на целое (idiv) для знаковых целых чисел, а также умножение (mul) и деление (div) для беззнаковых целых чисел; логические операторы И, ИЛИ, исключающее ИЛИ, НЕ; арифметический и логический сдвиг, SAL/SAR (для знаковых целых чисел), SHL/SHR (для беззнаковых целых чисел); вращение с переносом и без переноса, RCL/RCR, ROL/ROR, а также набор инструкций для BCD-арифметики, включая AAA, AAD, DAA и другие.

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

Язык ассемблера x86 включает инструкции для блока операций с плавающей точкой (FPU), работающего в стековой организации. FPU был необязательным отдельным сопроцессором для процессоров 8086–80386, встроенной опцией для серии 80486 и является стандартной функцией во всех процессорах Intel x86, начиная с 80486 и включая Pentium. Инструкции FPU включают сложение, вычитание, отрицание, умножение, деление, вычисление остатка, извлечение квадратного корня, усечение целой части, усечение дробной части и масштабирование на степень двойки. Операции также включают инструкции преобразования, которые могут загружать или сохранять значение из памяти в одном из следующих форматов: двоично-десятичный код, 32-битное целое, 64-битное целое, 32-битное число с плавающей точкой, 64-битное число с плавающей точкой или 80-битное число с плавающей точкой (при загрузке значение преобразуется в текущий режим работы с плавающей точкой). x86 также включает ряд трансцендентных функций, таких как синус, косинус, тангенс, арктангенс, возведение в степень с основанием 2 и логарифмы по основанию 2, 10 или e.

Формат инструкций, оперирующих с регистрами стека, обычно выглядит как fop st, st(n) или fop st(n), st, где st эквивалентно st(0), а st(n) – один из 8 регистров стека (st(0), st(1), …, st(7)). Как и в случае целых чисел, первый операнд является одновременно первым исходным операндом и операндом назначения. Инструкции fsubr и fdivr следует выделить, поскольку они сначала меняют местами исходные операнды перед выполнением вычитания или деления. Инструкции сложения, вычитания, умножения, деления, сохранения и сравнения включают режимы, которые удаляют верхний элемент стека после завершения операции. Например, faddp st(1), st выполняет вычисление st(1) = st(1) + st(0), затем удаляет st(0) из вершины стека, в результате чего результат, ранее находившийся в st(1), становится верхним элементом стека в st(0).

Инструкции SIMD

Современные процессоры x86 содержат SIMD-инструкции, которые в основном выполняют одну и ту же операцию параллельно над множеством значений, закодированных в широком SIMD-регистре. Различные технологии инструкций поддерживают различные операции над разными наборами регистров, но в совокупности (от MMX до SSE4.2) они включают общие вычисления с целыми числами или числами с плавающей точкой (сложение, вычитание, умножение, сдвиг, поиск минимума, максимума, сравнение, деление или извлечение квадратного корня). Например, инструкция `paddw mm0, mm1` выполняет 4 параллельных 16-битных (обозначается `w`) сложения целых чисел (обозначается `padd`) значений из `mm0` с `mm1` и сохраняет результат в `mm0`. Streaming SIMD Extensions, или SSE, также включает режим работы с плавающей точкой, в котором фактически изменяется только первое значение в регистре (расширенная функциональность в SSE2). Были добавлены и другие необычные инструкции, такие как вычисление суммы абсолютных разностей (используется для оценки движения при сжатии видео, например, в MPEG) и 16-битная инструкция умножения с накоплением (полезная для программной альфа-композиции и цифровой фильтрации). SSE (начиная с SSE3) и расширения 3DNow! включают инструкции сложения и вычитания для работы с парами чисел с плавающей точкой как с комплексными числами. Эти наборы инструкций также содержат множество инструкций для работы с подсловами фиксированного размера, позволяющих перемешивать, вставлять и извлекать значения внутри регистров. Кроме того, существуют инструкции для перемещения данных между целочисленными регистрами и регистрами XMM (используются в SSE) / FPU (используются в MMX).

Поток программы

Сборка x86 имеет безусловную операцию перехода, jmp, которая может принимать непосредственный адрес, регистр или косвенный адрес в качестве параметра (стоит отметить, что большинство процессоров RISC поддерживают только регистр связи или короткое непосредственное смещение для перехода). Также поддерживаются несколько условных переходов, включая jz (переход при нуле), jnz (переход при ненуле), jg (переход при больше, со знаком), jl (переход при меньше, со знаком), ja (переход при выше/больше, без знака), jb (переход при ниже/меньше, без знака). Эти условные операции основаны на состоянии определенных битов в регистре (E)FLAGS. Многие арифметические и логические операции устанавливают, очищают или инвертируют эти флаги в зависимости от результата. Инструкции сравнения cmp (compare) и test устанавливают флаги так, как если бы выполнялось вычитание или побитовое И, соответственно, без изменения значений операндов. Существуют также инструкции, такие как clc (очистить флаг переноса) и cmc (инвертировать флаг переноса), которые работают непосредственно с флагами. Сравнения чисел с плавающей точкой выполняются с помощью инструкций fcom или ficom, которые в конечном итоге должны быть преобразованы в целочисленные флаги. Каждая операция перехода имеет три различные формы в зависимости от размера операнда. Короткий переход использует 8-битный знаковый операнд, который является относительным смещением от текущей инструкции. Ближний переход аналогичен короткому переходу, но использует 16-битный знаковый операнд (в реальном или защищенном режиме) или 32-битный знаковый операнд (только в 32-битном защищенном режиме). Дальний переход использует полное значение сегмента:смещение в качестве абсолютного адреса. Существуют также косвенные и индексированные формы каждого из них. Помимо простых операций перехода, существуют инструкции call (вызов подпрограммы) и ret (возврат из подпрограммы). Перед передачей управления подпрограмме, call помещает в стек адрес смещения сегмента инструкции, следующей за вызовом; ret извлекает это значение из стека и переходит к нему, эффективно возвращая поток управления в эту часть программы. В случае дальнего вызова, база сегмента помещается в стек после смещения; far ret извлекает смещение, а затем базу сегмента для возврата. Существуют также две похожие инструкции, int (прерывание), которые сохраняют текущее значение регистра (E)FLAGS в стек, а затем выполняют дальний вызов, за исключением того, что вместо адреса используется вектор прерывания – индекс в таблицу адресов обработчиков прерываний. Обычно обработчик прерываний сохраняет все остальные регистры процессора, которые он использует, если только они не используются для возврата результата операции в вызывающую программу (в программном обеспечении называемые прерываниями). Соответствующей инструкцией возврата из прерывания является iret, которая восстанавливает флаги после возврата. Мягкие прерывания, описанные выше, используются некоторыми операционными системами для системных вызовов, а также могут использоваться для отладки обработчиков аппаратных прерываний. Аппаратные прерывания вызываются внешними аппаратными событиями и должны сохранять все значения регистров, поскольку состояние текущей выполняемой программы неизвестно. В защищенном режиме операционная система может настроить прерывания для запуска переключения задач, которое автоматически сохранит все регистры активной задачи.

Примеры

В следующих примерах используется так называемый синтаксис Intel, применяемый в ассемблерах Microsoft MASM, NASM и многих других. (Примечание: Существует также альтернативный синтаксис AT&T, в котором порядок исходного и целевого операндов изменен, наряду с множеством других отличий.)