язык ассемблера x86
Эта статья нуждается в дополнительных цитатах для проверки . ( март 2020 г. ) |
Язык ассемблера x86 — это название семейства языков ассемблера , которые обеспечивают некоторый уровень обратной совместимости с процессорами, начиная с микропроцессора Intel 8008 , выпущенного в апреле 1972 года. [1] [2] Он используется для создания объектного кода для процессоров класса x86 .
считается языком программирования Ассемблер и является машинно-специфичным и низкоуровневым . Как и все языки ассемблера, ассемблер x86 использует мнемонику для представления основных инструкций ЦП или машинного кода . [3] Языки ассемблера чаще всего используются для детальных и критичных по времени приложений, таких как небольшие реального времени встроенные системы , операционных систем ядра и драйверы устройств , но также могут использоваться и для других приложений. Компилятор программы иногда создает ассемблерный код в качестве промежуточного шага при трансляции высокого уровня в машинный код.
Ключевое слово
[ редактировать ]Зарезервированные ключевые слова языка ассемблера x86. [4] [5]
- ааа
- идти
- ааа
- аас
- АЦП
- добавлять
- и
- арпл
- граница
- BSF
- бср
- замена
- БТ
- биткойны
- БТР
- БТС
- вызов
- между прочим
- клк
- cld
- Кли
- клтд
- клтс
- КМЦ
- cmp
- ЦМПС
- cmpxchg
- крышка
- cwtl
- отпусти ситуацию
- тот
- декабрь
- div
- входить
- f2xm1
- фабрики
- причуда
- фаддп
- fbld
- фбстп
- фчс
- fclex
- fcom
- fcomp
- fcompp
- fcos
- fdecstp
- fdiv
- fdivp
- fdivr
- fdivrp
- бесплатно
- мерзкий
- фик
- фикомп
- фидив
- фидивр
- поле
- нить
- финкстп
- заканчивается
- кулак
- кулак
- фисубр
- фисубрп
- флд
- флд
- флдкв
- флденв
- fldl2e
- fldl2t
- fldlg2
- флдлн2
- флдпи
- флдз
- фм
- fmulp
- fnclex
- фнинт
- фноп
- fnsave
- фнстенв
- фнстью
- фнстсв
- фпатан
- фпрем
- фпрем
- фптан
- фрндинт
- фрстор
- fсохранить
- fscale
- фсин
- фсинкос
- fsqrt
- первый день
- фстенв
- фстью
- ФСП
- фстсв
- fsub
- fsubp
- фсубр
- fsubrp
- фтст
- фуком
- Фукомп
- фукомпп
- ждать
- fxam
- валютный курс
- fxtract
- fyl2x
- fyl2xp1
- хлт
- идив
- низкий
- в
- Inc.
- входы
- интервал
- в
- инвд
- инвлпг
- пошел бы
- jcxz
- JMP
- лахф
- Лар
- позвоню
- лдкс
- этот
- оставлять
- ТО
- лфс
- лгдт
- LGS
- маленький
- лджмп
- ллдт
- лмсв
- замок
- пилот
- петля
- лупз
- лупз
- кожа
- лсл
- лсс
- литр
- рис
- движется
- movsx
- movw
- мовзб
- у меня есть
- нег
- нет
- нет
- или
- вне
- ауты
- поп
- поп
- поп
- толкать
- пуша
- толчок
- ркл
- ркр
- представитель
- Представитель
- репс
- верно
- роль
- рор
- Сахф
- должен
- сар
- как
- скас
- setcc
- сержант
- шл
- шлд
- шр
- осколок
- сэр
- sldt
- SMS
- стц
- стандартный
- эти
- куча
- ул.
- суб
- тест
- худший
- ссылка
- ждать
- wbinvd
- количество
- хчг
- xlat
- бесплатно
Мнемоника и коды операций
[ редактировать ]Каждая инструкция ассемблера x86 представлена мнемоникой , которая, часто в сочетании с одним или несколькими операндами, преобразуется в один или несколько байтов, называемых кодом операции ; инструкция HLT например, инструкция NOP преобразуется в 0x90, а — в 0xF4. [3] Существуют потенциальные коды операций без документированной мнемоники, которые разные процессоры могут интерпретировать по-разному, из-за чего использующая их программа ведет себя непоследовательно или даже генерирует исключение на некоторых процессорах. Эти коды операций часто появляются на соревнованиях по написанию кода как способ сделать код меньше, быстрее, элегантнее или просто продемонстрировать мастерство автора.
Синтаксис
[ редактировать ]Язык ассемблера x86 имеет две основные ветви синтаксиса : Intel синтаксис и AT&T синтаксис . [6] Синтаксис Intel доминирует в мире DOS и Windows , а синтаксис AT&T доминирует в мире Unix , поскольку Unix была создана в AT&T Bell Labs . [7] Вот краткое изложение основных различий между синтаксисом Intel и синтаксисом AT&T :
АТ&Т | Интел | |
---|---|---|
Порядок параметров | movl $5, %eax | mov eax, 5 |
Размер параметра | addl $0x24, %espmovslq %ecx, %raxpaddd %xmm1, %xmm2 | add esp, 24hmovsxd rax, ecxpaddd xmm2, xmm1 Имена на основе ширины могут по-прежнему появляться в инструкциях, если они определяют другую операцию.
|
Сигилы | Непосредственные значения с префиксом «$», регистры с префиксом «%». [6] | Ассемблер автоматически определяет тип символов; т. е. являются ли они регистрами, константами или чем-то еще. |
Действующие адреса | movl offset(%ebx,%ecx,4), %eax | mov eax, [ebx + ecx*4 + offset] |
Многие ассемблеры x86 используют синтаксис Intel , включая FASM , MASM , NASM , TASM и YASM . GAS , изначально использовавший синтаксис AT&T , поддерживает оба синтаксиса начиная с версии 2.10 через .intel_syntax
директива. [6] [8] [9] Особенностью синтаксиса AT&T для x86 является то, что операнды x87 меняются местами, что является унаследованной ошибкой исходного ассемблера AT&T. [10]
Синтаксис AT&T практически универсален для всех других архитектур (сохраняя тот же mov
заказ); изначально это был синтаксис сборки PDP-11. Синтаксис Intel специфичен для архитектуры x86 и используется в документации платформы x86. Intel 8080 , предшествовавший x86, также использует порядок «сначала пункт назначения» для mov
. [11]
Регистры
[ редактировать ]Процессоры x86 имеют набор регистров, которые можно использовать в качестве хранилищ двоичных данных. В совокупности регистры данных и адреса называются общими регистрами. Каждый регистр имеет особое назначение в дополнение к тому, что они могут делать: [3]
- AX умножение/деление, загрузка и сохранение строки
- Индексный регистр BX для MOVE
- Количество CX для строковых операций и сдвигов
- DX Адрес порта для IN и OUT
- SP указывает на вершину стека
- BP указывает на основание кадра стека
- SI указывает на источник в потоковых операциях
- DI указывает на пункт назначения в потоковых операциях
Помимо общих регистров дополнительно имеются:
- Указатель IP-инструкции
- ФЛАГИ
- регистры сегмента (CS, DS, ES, FS, GS, SS), которые определяют, где начинается сегмент размером 64 КБ (нет FS и GS в 80286 и более ранних версиях)
- дополнительные регистры расширения ( MMX , 3DNow!, SSE и т. д.) (только для Pentium и более поздних версий).
Регистр IP указывает на смещение памяти следующей инструкции в сегменте кода (он указывает на первый байт инструкции). Программист не имеет прямого доступа к регистру IP.
Регистры x86 можно использовать с помощью инструкций MOV . Например, в синтаксисе Intel:
mov ax, 1234h ; copies the value 1234hex (4660d) into register AX
mov bx, ax ; copies the value of the AX register into the BX register
Сегментированная адресация
[ редактировать ]Архитектура x86 в реальном и виртуальном режиме 8086 процесс, известный как сегментация использует для адресации памяти , а не плоскую модель памяти, используемую во многих других средах. Сегментация предполагает составление адреса памяти из двух частей: сегмента и смещения ; сегмент указывает на начало 64 КиБ (64×2 10 ) группа адресов, а смещение определяет, насколько далеко от этого начального адреса находится желаемый адрес. При сегментной адресации для полного адреса памяти требуются два регистра. Один для хранения сегмента, другой для смещения. Чтобы преобразовать обратно в плоский адрес, значение сегмента сдвигается на четыре бита влево (что эквивалентно умножению на 2). 4 или 16), затем добавляется к смещению, чтобы сформировать полный адрес, что позволяет преодолеть барьер в 64 КБ за счет умного выбора адресов, хотя это значительно усложняет программирование.
Только в реальном режиме /защищенном режиме, например, если DS содержит шестнадцатеричное число. 0xDEAD и DX содержат номер 0xCAFE они вместе укажут на адрес памяти. 0xDEAD * 0x10 + 0xCAFE == 0xEB5CE
. Таким образом, ЦП может адресовать до 1 048 576 байт (1 МБ) в реальном режиме. Комбинируя значения сегмента и смещения, мы находим 20-битный адрес.
Исходный IBM PC ограничивал программы размером 640 КБ, но спецификация расширенной памяти использовалась для реализации схемы переключения банков, которая вышла из употребления, когда более поздние операционные системы, такие как Windows, использовали более широкие диапазоны адресов новых процессоров и реализовывали собственную виртуальную память. схемы.
Защищенный режим, начиная с Intel 80286, использовался OS/2 . Ряд недостатков, таких как невозможность доступа к BIOS и невозможность вернуться в реальный режим без перезагрузки процессора, препятствовали широкому использованию. [12] 80286 также по-прежнему был ограничен адресацией памяти 16-битными сегментами, то есть только 2 16 байты (64 килобайта ) могли быть доступны одновременно.Чтобы получить доступ к расширенным функциям 80286, операционная система переводит процессор в защищенный режим, обеспечивая 24-битную адресацию и, таким образом, 2 24 байт памяти (16 мегабайт ).
В защищенном режиме селектор сегмента можно разбить на три части: 13-битный индекс, бит индикатора таблицы , определяющий, находится ли запись в GDT или LDT , и 2-битный запрошенный уровень привилегий ; см. сегментацию памяти x86 .
обозначение сегмент : смещение При обращении к адресу с сегментом и смещением используется , поэтому в приведенном выше примере плоский адрес 0xEB5CE может быть записан как 0xDEAD:0xCAFE или как пара регистров сегмента и смещения; ДС:DX.
Существуют специальные комбинации сегментных регистров и регистров общего назначения, которые указывают на важные адреса:
- CS:IP (CS — сегмент кода , IP — указатель инструкции ) указывает на адрес, по которому процессор получит следующий байт кода.
- SS:SP (SS — сегмент стека , SP — указатель стека ) указывает на адрес вершины стека, т. е. самого последнего отправленного байта.
- SS:BP (SS — сегмент стека , BP — указатель кадра стека ) указывает на адрес вершины кадра стека, т. е. базу области данных в стеке вызовов для текущей активной подпрограммы.
- DS:SI (DS — сегмент данных , SI — индекс источника ) часто используется для указания строковых данных, которые собираются скопировать в ES:DI.
- ES:DI (ES — дополнительный сегмент , DI — индекс назначения ) обычно используется для указания места назначения для копии строки, как упоминалось выше.
Intel 80386 имел три режима работы: реальный режим, защищенный режим и виртуальный режим. Защищенный режим , который дебютировал в 80286, был расширен, чтобы позволить 80386 адресовать до 4 ГБ памяти, а совершенно новый виртуальный режим 8086 ( VM86 ) позволил запускать одну или несколько программ реального режима в защищенной среде, которая в значительной степени имитировала реальный режим, хотя некоторые программы были несовместимы (обычно из-за уловок с адресацией памяти или использования неуказанных кодов операций).
Модель 32-битной плоской памяти расширенного защищенного режима 80386 , возможно, была самым важным изменением в семействе процессоров x86 до тех пор, пока AMD не выпустила x86-64 в 2003 году, поскольку она способствовала широкомасштабному внедрению Windows 3.1 (которая опиралась на защищенный режим), поскольку Windows теперь могла запускать множество приложений одновременно, включая приложения DOS, используя виртуальную память и простую многозадачность.
Режимы выполнения
[ редактировать ]Процессоры x86 поддерживают пять режимов работы кода x86: реальный режим , защищенный режим , длительный режим , виртуальный режим 86 и режим управления системой , в которых одни инструкции доступны, а другие нет. 16-битное подмножество инструкций доступно на 16-битных процессорах x86, а именно 8086, 8088, 80186, 80188 и 80286. Эти инструкции доступны в реальном режиме на всех процессорах x86 и в 16-битном защищенном режиме. ( начиная с 80286 ), доступны дополнительные инструкции, относящиеся к защищенному режиму. В 80386 и более поздних версиях 32-битные инструкции (включая более поздние расширения) также доступны во всех режимах, включая реальный режим; на этих процессорах добавлен режим V86 и 32-битный защищенный режим, а в этих режимах предусмотрены дополнительные инструкции для управления их функциями. SMM с некоторыми собственными специальными инструкциями доступен на некоторых процессорах Intel i386SL, i486 и более поздних версиях. Наконец, в длинном режиме (начиная с AMD Opteron ) также доступны 64-битные инструкции и больше регистров. Набор команд в каждом режиме одинаков, но адресация памяти и размер слова различаются, что требует разных стратегий программирования.
Режимы, в которых может выполняться код x86:
- Реальный режим (16 бит)
- 20-битное сегментированное адресное пространство памяти (это означает, что можно адресовать только 1 МБ памяти — фактически, начиная с 80286, немного больше через HMA ), прямой программный доступ к периферийному оборудованию и отсутствие концепции защиты памяти или многозадачности на аппаратном уровне. Компьютеры, использующие BIOS, запускаются в этом режиме.
- Защищенный режим (16-битный и 32-битный)
- Расширяет адресуемую физическую память до 16 МБ и адресуемую виртуальную память до 1 ГБ . Предоставляет уровни привилегий и защищенную память , что предотвращает повреждение программ друг друга. 16-битный защищенный режим (использовавшийся в конце эпохи DOS ) использовал сложную многосегментную модель памяти. В 32-битном защищенном режиме используется простая плоская модель памяти.
- Длинный режим (64-битный)
- В основном это расширение набора команд 32-битного (защищенного режима), но в отличие от перехода с 16 на 32-битный, многие инструкции были удалены в 64-битном режиме. Впервые разработан компанией AMD .
- Виртуальный режим 8086 (16-битный)
- Специальный гибридный режим работы, который позволяет запускать программы и операционные системы в реальном режиме под контролем операционной системы супервизора защищенного режима.
- Режим управления системой (16-битный)
- Обрабатывает общесистемные функции, такие как управление питанием, управление аппаратным обеспечением системы и собственный код, разработанный OEM. Он предназначен для использования только системной прошивкой. Все нормальное выполнение, включая операционную систему , приостанавливается. компьютера Альтернативная система программного обеспечения (которая обычно находится в микропрограмме или в аппаратном отладчике ) затем запускается с высокими привилегиями.
Переключение режимов
[ редактировать ]Процессор работает в реальном режиме сразу после включения питания, поэтому операционной системы ядро или другая программа должны явно переключиться в другой режим, если они хотят работать в каком-либо другом режиме, кроме реального. процессора Переключение режимов осуществляется путем изменения определенных битов регистров управления после некоторой подготовки, и после переключения может потребоваться некоторая дополнительная настройка.
Примеры
[ редактировать ]На компьютере с устаревшей версией BIOS BIOS и загрузчик работают в реальном режиме . Ядро 64-битной операционной системы проверяет и переключает ЦП в длинный режим, а затем запускает новые потоки режима ядра, выполняющие 64-битный код.
На компьютере под управлением UEFI прошивка UEFI (кроме CSM и устаревшего дополнительного ПЗУ UEFI ), загрузчик и ядро операционной системы UEFI работают в длинном режиме.
Типы инструкций
[ редактировать ]В целом особенности современного набора команд x86 таковы:
- Компактная кодировка
- Независимая длина переменной и выравнивание (закодировано с прямым порядком байтов , как и все данные в архитектуре x86).
- В основном это одноадресные и двухадресные инструкции, то есть первый операнд также является пунктом назначения.
- Поддерживаются операнды памяти как источник, так и пункт назначения (часто используются для чтения/записи элементов стека, адресованных с использованием небольших немедленных смещений).
- Как общее, так и неявное использование регистров ; хотя все семь (считая
ebp
) общие регистры в 32-битном режиме, и все пятнадцать (считаяrbp
) регистры общего назначения в 64-битном режиме могут свободно использоваться в качестве аккумуляторов или для адресации, большинство из них также неявно используется некоторыми (более или менее) специальными инструкциями; Поэтому затронутые регистры должны быть временно сохранены (обычно сложены), если они активны во время таких последовательностей команд.
- Неявно создает условные флаги с помощью большинства целочисленных инструкций ALU .
- Поддерживает различные режимы адресации, включая непосредственный, смещенный и масштабируемый индекс, но не относительно ПК, за исключением переходов (введено как улучшение архитектуры x86-64 ).
- Включает плавающую точку в стек регистров.
- Содержит специальную поддержку атомарных инструкций чтения-изменения-записи (
xchg
,cmpxchg
/cmpxchg8b
,xadd
и целочисленные инструкции, которые сочетаются сlock
префикс) - Инструкции SIMD (инструкции, которые выполняют параллельные одновременные отдельные инструкции для многих операндов, закодированных в соседних ячейках более широких регистров).
Инструкции по стеку
[ редактировать ]Архитектура x86 имеет аппаратную поддержку механизма стека выполнения . Инструкции, такие как push
, pop
, call
и ret
используются с правильно настроенным стеком для передачи параметров, выделения места для локальных данных, а также для сохранения и восстановления точек возврата вызова. ret
Инструкция size очень полезна для реализации соглашений об эффективном (и быстром) вызове , где вызываемый объект отвечает за освобождение пространства стека, занятого параметрами.
При настройке кадра стека для хранения локальных данных рекурсивной процедуры есть несколько вариантов; высокий уровень enter
Инструкция (введенная в 80186) принимает аргумент глубины вложенности процедуры , а также аргумент локального размера и может быть быстрее, чем более явные манипуляции с регистрами (например, push bp
; mov bp, sp
; sub sp, size
). Будет ли это быстрее или медленнее, зависит от конкретной реализации процессора x86, а также от соглашения о вызовах, используемого компилятором, программистом или конкретным программным кодом; большая часть кода x86 предназначена для работы на процессорах x86 от нескольких производителей и на разных технологических поколениях процессоров, что подразумевает сильно различающиеся микроархитектуры и решения микрокода , а также различные затворов и транзисторов варианты конструкции на уровне .
Полный набор режимов адресации (включая немедленную и базовую+смещение ), даже для таких инструкций, как push
и pop
, упрощает прямое использование стека для целочисленных данных , данных с плавающей запятой и адресов , а также сохраняет спецификации и механизмы ABI относительно простыми по сравнению с некоторыми RISC-архитектурами (требуются более подробные сведения о стеке вызовов).
Целочисленные инструкции ALU
[ редактировать ]Сборка x86 имеет стандартные математические операции, add
, sub
, neg
, imul
и idiv
(для целых чисел со знаком), с mul
и div
(для беззнаковых целых чисел); логические операторы and
, or
, xor
, not
; побитовая арифметика и логика, 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
. Потоковые расширения SIMD или SSE также включают режим с плавающей запятой, в котором фактически изменяется только самое первое значение регистров (расширяется в SSE2 ). Были добавлены некоторые другие необычные инструкции, включая сумму абсолютных разностей (используется для оценки движения при сжатии видео , например, в MPEG ) и 16-битную инструкцию умножения-накопления (полезную для программного альфа-смешивания и цифровой фильтрации ). . SSE (начиная с SSE3 ) и 3DNow! расширения включают инструкции сложения и вычитания для обработки парных значений с плавающей запятой как комплексных чисел.
Эти наборы команд также включают в себя множество фиксированных подсловных инструкций для перетасовки, вставки и извлечения значений внутри регистров. Кроме того, существуют инструкции для перемещения данных между целочисленными регистрами и регистрами XMM (используется в SSE)/FPU (используется в MMX).
Инструкции памяти
[ редактировать ]Процессор x86 также включает сложные режимы адресации для адресации памяти с немедленным смещением, регистра, регистра со смещением, масштабируемого регистра со смещением или без него, а также регистра с необязательным смещением и еще одного масштабируемого регистра. Так, например, можно закодировать mov eax, [Table + ebx + esi*4]
как одна инструкция, которая загружает 32 бита данных по адресу, вычисленному как (Table + ebx + esi * 4)
компенсация от ds
селектор и сохраняет его в eax
зарегистрироваться. В общем, процессоры x86 могут загружать и использовать память, соответствующую размеру любого регистра, с которым они работают. (Инструкции SIMD также включают инструкции половинной загрузки.)
Большинство инструкций x86 с двумя операндами, включая целочисленные инструкции ALU,используйте стандартный « байт режима адресации » [13] часто называемый байтом MOD-REG-R/M . [14] [15] [16] Многие 32-битные инструкции x86 также имеют байт режима адресации SIB , который следует за байтом MOD-REG-R/M. [17] [18] [19] [20] [21]
В принципе, поскольку код операции инструкции отделен от байта режима адресации, эти инструкции ортогональны , поскольку любой из этих кодов операции можно смешивать и сопоставлять с любым режимом адресации.Однако набор команд x86 обычно считается неортогональным, поскольку многие другие коды операций имеют некоторый фиксированный режим адресации (у них нет байта режима адресации), и каждый регистр является особенным. [21] [22]
Набор инструкций x86 включает инструкции загрузки, сохранения, перемещения, сканирования и сравнения строк ( lods
, stos
, movs
, scas
и cmps
), которые выполняют каждую операцию до заданного размера ( b
для 8-битного байта, w
для 16-битного слова, d
для 32-битного двойного слова), затем увеличивает/уменьшает (в зависимости от DF, флага направления) регистр неявного адреса ( si
для lods
, di
для stos
и scas
и оба для movs
и cmps
). Для операций загрузки, сохранения и сканирования неявный регистр цели/источника/сравнения находится в al
, ax
или eax
зарегистрироваться (в зависимости от размера). Используемые неявные сегментные регистры: ds
для si
и es
для di
. cx
или ecx
Регистр используется как уменьшающий счетчик, и операция прекращается, когда счетчик достигает нуля или (для сканирования и сравнения) при обнаружении неравенства. К сожалению, с течением времени выполнением некоторых из этих инструкций стали пренебрегать, и в некоторых случаях теперь можно получить более быстрые результаты, написав алгоритмы самостоятельно. Однако Intel и AMD обновили некоторые инструкции, и некоторые из них теперь имеют очень приличную производительность, поэтому программисту рекомендуется прочитать последние авторитетные статьи по тестированию производительности, прежде чем использовать конкретную инструкцию из этой группы.
Стек — это область памяти и связанный с ней «указатель стека», указывающий на дно стека. Указатель стека уменьшается при добавлении элементов («push») и увеличивается после их удаления («pop»). В 16-битном режиме этот неявный указатель стека адресуется как SS:[SP], в 32-битном режиме — SS:[ESP], а в 64-битном режиме — [RSP]. Указатель стека фактически указывает на последнее сохраненное значение, при условии, что его размер будет соответствовать рабочему режиму процессора (т. е. 16, 32 или 64 бита), чтобы соответствовать ширине стека по умолчанию. push
/ pop
/ call
/ ret
инструкции. Также в комплекте есть инструкция enter
и leave
которые резервируют и удаляют данные из вершины стека при настройке указателя кадра стека в bp
/ ebp
/ rbp
. Однако прямая установка или сложение и вычитание sp
/ esp
/ rsp
регистр также поддерживается, поэтому enter
/ leave
инструкции вообще не нужны.
Этот код является началом функции, типичной для языка высокого уровня, когда оптимизация компилятора отключена для удобства отладки:
push rbp ; Save the calling function’s stack frame pointer (rbp register) mov rbp, rsp ; Make a new stack frame below our caller’s stack sub rsp, 32 ; Reserve 32 bytes of stack space for this function’s local variables. ; Local variables will be below rbp and can be referenced relative to rbp, ; again best for ease of debugging, but for best performance rbp will not ; be used at all, and local variables would be referenced relative to rsp ; because, apart from the code saving, rbp then is free for other uses. … … ; However, if rbp is altered here, its value should be preserved for the caller. mov [rbp-8], rdx ; Example of accessing a local variable, from memory location into register rdx
... функционально эквивалентно просто:
enter 32, 0
Другие инструкции по управлению стеком включают: pushfd
(32-бит) / pushfq
(64-разрядная версия) и popfd/popfq
для хранения и извлечения регистра EFLAGS (32-бит)/RFLAGS (64-бит).
Предполагается, что значения для загрузки или сохранения SIMD упакованы в соседние позиции для регистра SIMD и выравнивают их в последовательном порядке с прямым порядком байтов. Для правильной работы некоторых инструкций загрузки и сохранения SSE требуется выравнивание по 16 байтам. Наборы инструкций SIMD также включают инструкции «предварительной выборки», которые выполняют загрузку, но не нацелены на какой-либо регистр, используемый для загрузки кэша. Наборы инструкций SSE также включают инструкции вневременного сохранения, которые будут выполнять операции сохранения непосредственно в памяти без выполнения выделения кэша, если место назначения еще не кэшировано (в противном случае оно будет вести себя как обычное сохранение).
Большинство общих целочисленных инструкций и инструкций с плавающей запятой (но не SIMD) могут использовать один параметр в качестве комплексного адреса в качестве второго исходного параметра. Целочисленные инструкции также могут принимать один параметр памяти в качестве операнда-адресата.
Ход программы
[ редактировать ]Сборка x86 имеет операцию безусловного перехода, jmp
, который может принимать в качестве параметра непосредственный адрес, регистр или косвенный адрес (обратите внимание, что большинство RISC-процессоров поддерживают только регистр связи или короткое немедленное смещение для перехода).
Также поддерживается несколько условных переходов, в том числе jz
(прыжок на ноль), jnz
(перейти на ненулевое значение), jg
(перейти на большее, подписано), jl
(перейти меньше, подписано), ja
(перейти выше/больше, без знака), jb
(перейти ниже/меньше, без знака). Эти условные операции основаны на состоянии определенных битов в регистре (E)FLAGS . Многие арифметические и логические операции устанавливают, очищают или дополняют эти флаги в зависимости от их результата. Сравнение cmp
(сравнить) и test
инструкции устанавливают флаги так, как если бы они выполнили вычитание или побитовую операцию И соответственно, без изменения значений операндов. Также есть такие инструкции, как clc
(очистить флаг переноса) и cmc
(дополняющий флаг переноса), которые работают непосредственно с флагами. Сравнение с плавающей запятой выполняется с помощью fcom
или ficom
инструкции, которые в конечном итоге должны быть преобразованы в целочисленные флаги.
Каждая операция перехода имеет три различные формы, в зависимости от размера операнда. Короткий переход использует 8-битный знаковый операнд, который является относительным смещением относительно текущей инструкции. переход Ближайший аналогичен короткому переходу, но использует 16-битный знаковый операнд (в реальном или защищенном режиме) или 32-битный знаковый операнд (только в 32-битном защищенном режиме). Дальний переход — это переход , в котором в качестве абсолютного адреса используется полное значение сегмента base:offset. Существуют также косвенные и индексированные формы каждого из них.
Помимо простых операций перехода, существуют call
(вызов подпрограммы) и ret
(возврат из подпрограммы) инструкции. Прежде чем передать управление подпрограмме, call
помещает адрес смещения сегмента инструкции, следующей за call
в стек; ret
извлекает это значение из стека и переходит к нему, эффективно возвращая поток управления этой части программы. В случае far call
, база сегмента перемещается после смещения; far ret
выталкивает смещение, а затем базу сегмента для возврата.
Также есть две подобные инструкции, int
( прерывание ), которое сохраняет текущее значение регистра (E)FLAGS в стеке, а затем выполняет far call
, за исключением того, что вместо адреса он использует вектор прерывания , индекс в таблице адресов обработчиков прерываний. Обычно обработчик прерываний сохраняет все остальные регистры ЦП, которые он использует, если только они не используются для возврата результата операции вызывающей программе (в программном обеспечении, называемом прерываниями). Соответствующий возврат из инструкции прерывания iret
, который восстанавливает флаги после возврата. Мягкие прерывания описанного выше типа используются некоторыми операционными системами для системных вызовов , а также могут использоваться при отладке обработчиков жестких прерываний. Аппаратные прерывания запускаются внешними аппаратными событиями и должны сохранять все значения регистров, поскольку состояние выполняющейся в данный момент программы неизвестно. В защищенном режиме прерывания могут быть настроены операционной системой для запуска переключения задачи, что автоматически сохранит все регистры активной задачи.
Примеры
[ редактировать ]Эта статья , возможно, содержит оригинальные исследования . ( Март 2013 г. ) |
В следующих примерах используется так называемый вариант синтаксиса Intel , используемый ассемблерами Microsoft MASM, NASM и многими другими. (Примечание: существует также альтернативный вариант синтаксиса AT&T , в котором, помимо многих других отличий, меняется порядок операндов источника и назначения.) [23]
"Привет, мир!" программа для MS-DOS на ассемблере в стиле MASM
[ редактировать ]Использование инструкции программного прерывания 21h для вызова операционной системы MS-DOS для вывода на дисплей — в других примерах libc используется процедура printf() библиотеки C для записи в стандартный вывод . Обратите внимание, что первый пример — это пример 30-летней давности, использующий 16-битный режим, как на Intel 8086. Второй пример — это код Intel 386 в 32-битном режиме. Современный код будет в 64-битном режиме. [24]
.model small.stack 100h.datamsg db 'Hello world!$'.codestart: mov ax, @DATA ; Initializes Data segment mov ds, ax mov ah, 09h ; Sets 8-bit register ‘ah’, the high byte of register ax, to 9, to ; select a sub-function number of an MS-DOS routine called below ; via the software interrupt int 21h to display a message lea dx, msg ; Takes the address of msg, stores the address in 16-bit register dx int 21h ; Various MS-DOS routines are callable by the software interrupt 21h ; Our required sub-function was set in register ah above mov ax, 4C00h ; Sets register ax to the sub-function number for MS-DOS’s software ; interrupt int 21h for the service ‘terminate program’. int 21h ; Calling this MS-DOS service never returns, as it ends the program.end start
"Привет, мир!" программа для Windows в сборке в стиле MASM
[ редактировать ]; requires /coff switch on 6.15 and earlier versions.386.model small,c.stack 1000h.datamsg db "Hello world!",0.codeincludelib libcmt.libincludelib libvcruntime.libincludelib libucrt.libincludelib legacy_stdio_definitions.libextrn printf:nearextrn exit:nearpublic mainmain proc push offset msg call printf push 0 call exitmain endpend
"Привет, мир!" программа для Windows в сборке в стиле NASM
[ редактировать ]; Image base = 0x00400000%define RVA(x) (x-0x00400000)section .textpush dword hellocall dword [printf]push byte +0call dword [exit]retsection .datahello db "Hello world!"section .idatadd RVA(msvcrt_LookupTable)dd -1dd 0dd RVA(msvcrt_string)dd RVA(msvcrt_imports)times 5 dd 0 ; ends the descriptor tablemsvcrt_string dd "msvcrt.dll", 0msvcrt_LookupTable:dd RVA(msvcrt_printf)dd RVA(msvcrt_exit)dd 0msvcrt_imports:printf dd RVA(msvcrt_printf)exit dd RVA(msvcrt_exit)dd 0msvcrt_printf:dw 1dw "printf", 0msvcrt_exit:dw 2dw "exit", 0dd 0
"Привет, мир!" программа для Linux в родной сборке в стиле AT&T
[ редактировать ].data ; section for initialized datastr: .ascii "Hello, world!\n" ; define a string of text containing "Hello, world!" and then a new line.str_len = . - str ; get the length of str by subtracting its address.text ; section for program functions.globl _start ; export the _start function so it can be run_start: ; begin the _start function movl $4, %eax ; specify the instruction to 'sys_write' movl $1, %ebx ; specify the output to the standard output, 'stdout' movl $str, %ecx ; specify the outputted text to our defined string movl $str_len, %edx ; specify the character amount to write as the length of our defined string. int $0x80 ; call a system interrupt to initiate the syscall we have created. movl $1, %eax ; specify the instruction to 'sys_exit' movl $0, %ebx ; specify the exit code to 0, meaning success int $0x80 ; call another system interrup to end the program
"Привет, мир!" программа для Linux в сборке в стиле NASM
[ редактировать ];; This program runs in 32-bit protected mode.; build: nasm -f elf -F stabs name.asm; link: ld -o name name.o;; In 64-bit long mode you can use 64-bit registers (e.g. rax instead of eax, rbx instead of ebx, etc.); Also change "-f elf " for "-f elf64" in build command.;section .data ; section for initialized datastr: db 'Hello world!', 0Ah ; message string with new-line char at the end (10 decimal)str_len: equ $ - str ; calcs length of string (bytes) by subtracting the str's start address ; from ‘here, this address’ (‘$’ symbol meaning ‘here’)section .text ; this is the code section (program text) in memory global _start ; _start is the entry point and needs global scope to be 'seen' by the ; linker --equivalent to main() in C/C++_start: ; definition of _start procedure begins here mov eax, 4 ; specify the sys_write function code (from OS vector table) mov ebx, 1 ; specify file descriptor stdout --in gnu/linux, everything's treated as a file, ; even hardware devices mov ecx, str ; move start _address_ of string message to ecx register mov edx, str_len ; move length of message (in bytes) int 80h ; interrupt kernel to perform the system call we just set up - ; in gnu/linux services are requested through the kernel mov eax, 1 ; specify sys_exit function code (from OS vector table) mov ebx, 0 ; specify return code for OS (zero tells OS everything went fine) int 80h ; interrupt kernel to perform system call (to exit)
Для 64-битного длинного режима адресом сообщения будет «lea rcx, str», обратите внимание на 64-битный регистр rcx.
"Привет, мир!" программа для Linux на ассемблере в стиле NASM с использованием стандартной библиотеки C
[ редактировать ];; This program runs in 32-bit protected mode.; gcc links the standard-C library by default; build: nasm -f elf -F stabs name.asm; link: gcc -o name name.o;; In 64-bit long mode you can use 64-bit registers (e.g. rax instead of eax, rbx instead of ebx, etc..); Also change "-f elf " for "-f elf64" in build command.; global main ; ‘main’ must be defined, as it being compiled ; against the C Standard Library extern printf ; declares the use of external symbol, as printf ; printf is declared in a different object-module. ; The linker resolves this symbol later.segment .data ; section for initialized data string db 'Hello world!', 0Ah, 0 ; message string ending with a newline char (10 ; decimal) and the zero byte ‘NUL’ terminator ; ‘string’ now refers to the starting address ; at which 'Hello, World' is stored.segment .textmain: push string ; Push the address of ‘string’ onto the stack. ; This reduces esp by 4 bytes before storing ; the 4-byte address ‘string’ into memory at ; the new esp, the new bottom of the stack. ; This will be an argument to printf() call printf ; calls the C printf() function. add esp, 4 ; Increases the stack-pointer by 4 to put it back ; to where it was before the ‘push’, which ; reduced it by 4 bytes. ret ; Return to our caller.
"Привет, мир!" программа для 64-битного режима Linux в сборке в стиле NASM
[ редактировать ]Этот пример находится в современном 64-битном режиме.
; build: nasm -f elf64 -F dwarf hello.asm; link: ld -o hello hello.oDEFAULT REL ; use RIP-relative addressing modes by default, so [foo] = [rel foo]SECTION .rodata ; read-only data should go in the .rodata section on GNU/Linux, like .rdata on WindowsHello: db "Hello world!", 10 ; Ending with a byte 10 = newline (ASCII LF)len_Hello: equ $-Hello ; Get NASM to calculate the length as an assembly-time constant ; the ‘$’ symbol means ‘here’. write() takes a length so that ; a zero-terminated C-style string isn't needed. ; It would be for C puts()SECTION .rodata ; read-only data can go in the .rodata section on GNU/Linux, like .rdata on WindowsHello: db "Hello world!",10 ; 10 = `\n`.len_Hello: equ $-Hello ; get NASM to calculate the length as an assemble-time constant;; write() takes a length so a 0-terminated C-style string isn't needed. It would be for putsSECTION .textglobal _start_start: mov eax, 1 ; __NR_write syscall number from Linux asm/unistd_64.h (x86_64) mov edi, 1 ; int fd = STDOUT_FILENO lea rsi, [rel Hello] ; x86-64 uses RIP-relative LEA to put static addresses into regs mov rdx, len_Hello ; size_t count = len_Hello syscall ; write(1, Hello, len_Hello); call into the kernel to actually do the system call ;; return value in RAX. RCX and R11 are also overwritten by syscall mov eax, 60 ; __NR_exit call number (x86_64) is stored in register eax. xor edi, edi ; This zeros edi and also rdi. ; This xor-self trick is the preferred common idiom for zeroing ; a register, and is always by far the fastest method. ; When a 32-bit value is stored into eg edx, the high bits 63:32 are ; automatically zeroed too in every case. This saves you having to set ; the bits with an extra instruction, as this is a case very commonly ; needed, for an entire 64-bit register to be filled with a 32-bit value. ; This sets our routine’s exit status = 0 (exit normally) syscall ; _exit(0)
Запускаем его под strace проверяет, что в процессе не выполняется никаких дополнительных системных вызовов. Версия printf будет выполнять гораздо больше системных вызовов для инициализации libc и динамического связывания . Но это статический исполняемый файл, поскольку мы компонуем его с помощью ld без -pie или каких-либо общих библиотек; единственные инструкции, которые выполняются в пользовательском пространстве, — это те, которые вы предоставляете.
$ strace ./hello > /dev/null # without a redirect, your program's stdout is mixed with strace's logging on stderr. Which is normally fineexecve("./hello", ["./hello"], 0x7ffc8b0b3570 /* 51 vars */) = 0write(1, "Hello world!\n", 13) = 13exit(0) = ?+++ exited with 0 +++
Использование регистра флагов
[ редактировать ]Флаги широко используются для сравнений в архитектуре x86. Когда выполняется сравнение двух данных, ЦП устанавливает соответствующий флаг или флаги. После этого можно использовать инструкции условного перехода для проверки флагов и перехода к коду, который должен выполняться, например:
cmp eax, ebx jne do_something ; ...do_something: ; do something here
Помимо инструкций сравнения, существует множество арифметических и других инструкций, которые устанавливают биты в регистре флагов. Другими примерами являются инструкции sub, test и add, а также многие другие. Обычные комбинации, такие как cmp + условный переход, внутренне «слиты» (« макрослияние ») в одну микроинструкцию (μ-op) и выполняются быстро, если процессор может угадать, в каком направлении пойдет условный переход: прыжок или продолжение.
Регистр флагов также используется в архитектуре x86 для включения и выключения определенных функций или режимов выполнения. Например, чтобы отключить все маскируемые прерывания, можно использовать инструкцию:
cli
К регистру флагов также можно получить прямой доступ. Младшие 8 бит регистра флагов могут быть загружены в ah
используя lahf
инструкция. Весь регистр флагов также можно перемещать в стек и из него с помощью инструкций pushfd/pushfq
, popfd/popfq
, int
(включая into
) и iret
.
Математическая подсистема с плавающей запятой x87 также имеет свой собственный независимый регистр типа «флаги» для слова состояния fp. В 1990-х годах процедура доступа к битам флагов в этом регистре была неудобной и медленной, но на современных процессорах есть инструкции «сравнить два значения с плавающей запятой», которые можно использовать с обычными инструкциями условного перехода/ветви напрямую, без каких-либо промежуточных шагов. .
Использование регистра указателя команд
[ редактировать ]Указатель инструкции называется ip
в 16-битном режиме, eip
в 32-битном режиме и rip
в 64-битном режиме. Регистр указателя команд указывает на адрес следующей инструкции, которую процессор попытается выполнить. К нему нельзя получить прямой доступ в 16-битном или 32-битном режиме, но можно записать последовательность, подобную следующей, чтобы поместить адрес next_line
в eax
(32-битный код):
call next_linenext_line: pop eax
Запись в указатель инструкции проста — jmp
Инструкция сохраняет заданный целевой адрес в указателе инструкции, поэтому, например, последовательность, подобная следующей, поместит содержимое rax
в rip
(64-битный код):
jmp rax
В 64-битном режиме инструкции могут ссылаться на данные относительно указателя инструкции, поэтому нет необходимости копировать значение указателя инструкции в другой регистр.
См. также
[ редактировать ]- Язык ассемблера
- Списки инструкций X86
- Архитектура X86
- Дизайн процессора
- Список ассемблеров
- Самомодифицирующийся код
- ПРИНАДЛЕЖАЩИЙ
- ДВА API
Ссылки
[ редактировать ]- ^ «Семейство микропроцессоров Intel 8008 (i8008)» . www.cpu-world.com . Проверено 25 марта 2021 г.
- ^ «Интел 8008» . МУЗЕЙ ЦП - МУЗЕЙ МИКРОПРОЦЕССОРОВ И ФОТОГРАФИИ МИКРОПРОЦЕССОРОВ . Проверено 25 марта 2021 г.
- ^ Перейти обратно: а б с «ОПКОДЫ Intel 8008» . www.pastraiser.com . Проверено 25 марта 2021 г.
- ^ «Справочник по языку Ассемблер» . www.ibm.com . Проверено 28 ноября 2022 г.
- ^ «Справочное руководство по языку ассемблера x86» (PDF) .
- ^ Перейти обратно: а б с д и Нараям, Рам (17 октября 2007 г.). «Ассемблеры Linux: сравнение GAS и NASM» . ИБМ . Архивировано из оригинала 3 октября 2013 года . Проверено 2 июля 2008 г.
- ^ «Создание Unix» . Архивировано из оригинала 2 апреля 2014 года.
- ^ Хайд, Рэндалл. «Какой ассемблер лучший?» . Проверено 18 мая 2008 г.
- ^ «Новости GNU Assembler, v2.1 поддерживает синтаксис Intel» . 04 апреля 2008 г. Проверено 2 июля 2008 г.
- ^ «i386-Ошибки (Использование как)» . Документация по Binutils . Проверено 15 января 2020 г. .
- ^ «Руководство по программированию на языке ассемблера Intel 8080» (PDF) . Проверено 12 мая 2023 г.
- ^ Мюллер, Скотт (24 марта 2006 г.). «Процессоры P2 (286) второго поколения» . Модернизация и ремонт компьютеров, 17-е издание (книга) (17-е изд.). Que. ISBN 0-7897-3404-4 . Проверено 6 декабря 2017 г.
- ^ Кертис Медоу. «Кодирование инструкций 8086» .
- ^ Игорь Холодов. «6. Кодирование операндов инструкций x86, байт MOD-REG-R/M» .
- ^ «Инструкции по кодированию x86» .
- ^ Майкл Абраш.«Дзен языка ассемблера: Том I, Знания».«Глава 7: Адресация памяти».Раздел «Адресация mod-reg-rm» .
- ^ Справочное руководство программиста Intel 80386. «17.2.1 Байты ModR/M и SIB»
- ^ «Кодировка инструкций X86-64: байты ModR/M и SIB»
- ^ «Рисунок 2-1. Формат инструкций для архитектур Intel 64 и IA-32» .
- ^ «Скрытая адресация x86» .
- ^ Перейти обратно: а б Стивен МакКамант. «Ручное и автоматизированное бинарное обратное проектирование» .
- ^ «Список пожеланий для инструкций X86» .
- ^ Питер Кордес (18 декабря 2011 г.). «Синтаксис NASM (Intel) и AT&T: каковы преимущества?» . Переполнение стека .
- ^ «Я только начал сборку» . daniweb.com . 2008.
Дальнейшее чтение
[ редактировать ]Руководства
[ редактировать ]- Руководства для разработчиков программного обеспечения Intel 64 и IA-32
- Руководство программиста по архитектуре AMD64 (том 1–5)
Книги
[ редактировать ]- Эд, Йоргенсен (май 2018 г.). Программирование на языке ассемблера x86-64 в Ubuntu (PDF) (изд. 1.0.97). п. 367.