язык ассемблера x86 - x86 assembly language
Язык ассемблера x86 - это семейство обратно совместимых языков ассемблера , которые обеспечивают определенный уровень совместимости вплоть до Intel 8008, представленного в апреле 1972 года. Языки ассемблера x86 используются для создания объектного кода для класса процессоров x86 . Как и все языки ассемблера, он использует краткую мнемонику для представления основных инструкций, которые процессор компьютера может понять и выполнить. Компиляторы иногда создают код сборки в качестве промежуточного шага при переводе программы высокого уровня в машинный код . Кодирование на ассемблере, рассматриваемое как язык программирования , является машинно-зависимым и низкоуровневым . Ассамблея языки более обычно используются для детальнее и времени критически важных приложений , таких как небольшие в режиме реального времени встроенных систем или операционных систем ядра и драйверов устройств .
Мнемоника и коды операций
Каждая инструкция сборки x86 представлена мнемоникой, которая, часто в сочетании с одним или несколькими операндами, преобразуется в один или несколько байтов, называемых кодом операции ; NOP инструкция переводится 0x90, например , и HLT инструкция переводится 0xF4. Существуют потенциальные коды операций без задокументированной мнемоники, которые разные процессоры могут интерпретировать по-разному, заставляя программу, использующую их, вести себя непоследовательно или даже генерировать исключение на некоторых процессорах. Эти коды операций часто используются в соревнованиях по написанию кода как способ сделать код меньше, быстрее, элегантнее или просто продемонстрировать мастерство автора.
Синтаксис
x86 ассемблер имеет два основных синтаксис ветви: Intel синтаксис и AT & T синтаксис . Синтаксис Intel доминирует в мире DOS и Windows , а синтаксис AT&T доминирует в мире Unix , поскольку Unix был создан в AT&T Bell Labs . Здесь приводится краткое описание основных различий между синтаксисом Intel и AT & T синтаксис :
AT&T | Intel | |
---|---|---|
Порядок параметров | Источник перед местом назначения. movl $5, %eax
|
Пункт назначения перед источником. mov eax, 5
|
Размер параметра | Мнемоника имеет суффикс с буквой, обозначающей размер операндов: q для qword, l для long (dword), w для слова и b для байта.addl $4, %esp
|
Производный от имени используемого регистра (например, rax, eax, ax, al подразумевают q, l, w, b соответственно).add esp, 4
|
Сигилы | Непосредственные значения с префиксом «$», регистры с префиксом «%». | Ассемблер автоматически определяет тип символов; то есть регистры, константы или что-то еще. |
Эффективные адреса | Общий синтаксис DISP (BASE, INDEX, SCALE) . Пример:movl mem_location(%ebx,%ecx,4), %eax
|
Арифметические выражения в квадратных скобках; кроме того, необходимо использовать ключевые слова размера, такие как byte , word или dword , если размер не может быть определен из операндов. Пример:mov eax, [ebx + ecx*4 + mem_location]
|
Многие ассемблеры x86 используют синтаксис Intel , включая NASM , FASM , MASM , TASM и YASM . GAS , который изначально использовал синтаксис AT&T , поддерживает оба синтаксиса начиная с версии 2.10 через директиву .intel_syntax . Особенность синтаксиса AT&T для x86 состоит в том, что операнды x87 перевернуты, что является унаследованной ошибкой от исходного ассемблера AT&T.
Синтаксис AT&T почти универсален для всех других архитектур с таким же mov
порядком; Изначально это был синтаксис сборки PDP-11. Синтаксис Intel специфичен для архитектуры x86 и используется в документации платформы x86.
Регистры
Процессоры x86 имеют набор регистров, доступных для использования в качестве хранилищ двоичных данных. В совокупности регистры данных и адреса называются общими регистрами. Каждый регистр имеет особую цель в дополнение к тому, что они все могут делать:
- AX умножение / деление, загрузка и сохранение строки
- Регистр индекса BX для MOVE
- Счетчик CX для строковых операций и сдвигов
- Адрес порта DX для IN и OUT
- SP указывает на вершину стека
- BP указывает на основание кадра стека
- SI указывает на источник в потоковых операциях
- DI указывает на пункт назначения в потоковых операциях
Наряду с общими регистрами дополнительно:
- Указатель инструкции IP
- ФЛАГИ
- регистры сегментов (CS, DS, ES, FS, GS, SS), которые определяют, где начинается сегмент 64k (нет 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 и невозможность вернуться в реальный режим без перезагрузки процессора, препятствовали широкому использованию. 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 является Stack Segment , SP является указатель стека ) указывает на адрес вершины стека, то есть совсем недавно толкаемом байт.
- 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: Real Mode , Protected Mode , Long Mode , Virtual 86 Mode и System Management Mode , в которых одни инструкции доступны, а другие нет. 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 МБ памяти - на самом деле, немного больше), прямой программный доступ к периферийному оборудованию и отсутствие концепции защиты памяти или многозадачности на аппаратном уровне. В этом режиме запускаются компьютеры, использующие BIOS .
-
Защищенный режим (16 бит и 32 бит)
- Расширяет адресуемую физическую память до 16 МБ и адресуемую виртуальную память до 1 ГБ . Предоставляет уровни привилегий и защищенную память , что предотвращает разрушение программ друг друга. В 16-битном защищенном режиме (использовавшемся в конце эры DOS ) использовалась сложная, многосегментная модель памяти. В 32-битном защищенном режиме используется простая плоская модель памяти.
-
Длинный режим (64-бит)
- В основном это расширение набора 32-битных (защищенный режим) инструкций, но в отличие от перехода с 16 на 32 бит, многие инструкции были отброшены в 64-битном режиме. Первопроходец AMD .
-
Виртуальный режим 8086 (16 бит)
- Специальный гибридный режим работы, который позволяет программам и операционным системам реального режима работать под управлением операционной системы супервизора защищенного режима.
-
Режим управления системой (16 бит)
- Обрабатывает общесистемные функции, такие как управление питанием, управление аппаратным обеспечением системы и собственный код, разработанный OEM. Он предназначен для использования только системной прошивкой. Все нормальное выполнение, в том числе операционной системы , приостанавливается. Затем выполняется альтернативная программная система (которая обычно находится во встроенном ПО компьютера или в аппаратном отладчике ) с высокими привилегиями.
Режимы переключения
Процессор работает в реальном режиме сразу после включения питания, поэтому в системе операционного ядра или другой программу, необходимо явно переключиться на другой режим , если он хочет работать в чем - либо , но режиме реального времени. Переключение режимов осуществляется путем изменения определенных битов процессора регистров управления после некоторой подготовки, а также некоторые дополнительные установки может потребоваться после переключения.
Примеры
На компьютере с устаревшей версией BIOS BIOS и загрузчик работают в реальном режиме , затем ядро 64-разрядной операционной системы проверяет и переключает ЦП в длительный режим, а затем запускает новые потоки режима ядра, выполняющие 64-разрядный код.
С компьютера под управлением UEFI , с прошивкой UEFI ( за исключением КСМ и унаследованной Option ROM ), в UEFI загрузчика и ядра операционной системы UEFI все работает в режиме Long.
Типы инструкций
В общем, особенности современного набора инструкций x86 :
- Компактная кодировка
- Переменная длина и независимость от выравнивания (закодированы с прямым порядком байтов , как и все данные в архитектуре x86)
- В основном одноадресные и двухадресные инструкции, то есть первый операнд также является адресатом.
- Поддерживаются операнды памяти в качестве источника и назначения (часто используются для чтения / записи элементов стека, адресованных с использованием небольших немедленных смещений).
- Как общее, так и неявное использование регистров ; хотя все семь (подсчет
ebp
) общих регистров в 32-битном режиме и все пятнадцать (подсчетrbp
) общих регистров в 64-битном режиме могут свободно использоваться в качестве накопителей или для адресации, большинство из них также неявно используются некоторыми (более или меньше) специальные инструкции; поэтому затронутые регистры должны быть временно сохранены (обычно в стеке), если они активны во время таких последовательностей команд.
- Создает условные флаги неявно через большинство целочисленных инструкций ALU .
- Поддерживает различные режимы адресации, включая немедленный, смещение и масштабированный индекс, но не относительно ПК, за исключением переходов (введенных как улучшение в архитектуре x86-64 ).
- Включает плавающую точку в стек регистров.
- Содержит специальную поддержку для атомных чтения-модификация-запись инструкций (
xchg
,cmpxchg
/cmpxchg8b
,xadd
и целого числа команд , которые комбинируют сlock
префиксом) - SIMD- инструкции (инструкции, которые выполняют параллельные одновременные одиночные инструкции для многих операндов, закодированных в соседних ячейках более широких регистров).
Инструкции по стеку
Архитектура x86 имеет аппаратную поддержку механизма стека выполнения. Инструкции , такие как push
, pop
, call
и ret
используются с правильно настроить стек для передачи параметров, чтобы выделить место для локальных данных, а также для сохранения и восстановления вызова возврата очков. ret
Размер инструкция очень полезна для реализации космических эффективные (и быстро) соглашений о вызовах , где вызываемое несет ответственность за освоение пространства стека заняты параметрами.
При настройке кадра стека для хранения локальных данных рекурсивной процедуры есть несколько вариантов; высокий уровень enter
инструкции (введен с 80186) занимает процедура гнездящихся-глубины аргумента, а также локальный размер аргумент, и может быть быстрее , чем более явной манипуляции регистров (таких как push bp
; mov bp, sp
; ). Будет ли он быстрее или медленнее, зависит от конкретной реализации процессора x86, а также от соглашения о вызовах, используемого компилятором, программистом или конкретным программным кодом; большая часть кода x86 предназначена для работы на процессорах x86 от нескольких производителей и на различных технологических поколениях процессоров, что подразумевает сильно различающиеся микроархитектуры и решения микрокода, а также различные варианты дизайна на уровне затвора и транзистора .
sub sp, size
Полный диапазон режимов адресации (включая немедленную адресацию и базовое + смещение ) даже для таких инструкций, как push
и pop
, упрощает прямое использование стека для целочисленных данных , данных с плавающей запятой и адресных данных, а также сохраняет спецификации и механизмы ABI относительно простыми по сравнению с некоторые архитектуры RISC (требуют более подробных сведений о стеке вызовов).
Целочисленные инструкции ALU
x86 сборка имеет стандартные математические операции, add
, sub
, mul
, с idiv
; эти логические операторы and
, or
, xor
, neg
; арифметические и логические со сдвигом битов , 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 .
Регистр стека в формат стек регистра инструкций, как правило , или , где эквивалентно , и является одним из регистров стека 8 ( , ..., ). Как и целые числа, первый операнд является одновременно первым операндом источника и операндом назначения. и должны быть выделены как первая замена исходных операндов перед выполнением вычитания или деления. Инструкции сложения, вычитания, умножения, деления, сохранения и сравнения включают в себя режимы команд, которые выталкивают верхнюю часть стека после завершения их операции. Так, например, выполняет вычисление , затем удаляет его из вершины стека, таким образом делая результат на вершине стека в .
fop st, st(n)
fop st(n), st
st
st(0)
st(n)
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 также включает в себя сложные режимы адресации для адресации памяти с немедленным смещением, регистр, регистр со смещением, масштабированный регистр со смещением или без него, а также регистр с дополнительным смещением и другой масштабированный регистр. Так, например, можно закодировать mov eax, [Table + ebx + esi*4]
как одну команду, которая загружает 32 бита данных из адреса, вычисленного как (Table + ebx + esi * 4)
смещение от ds
селектора, и сохраняет их в eax
регистре. Как правило, процессоры x86 могут загружать и использовать память, соответствующую размеру любого регистра, с которым он работает. (Инструкции SIMD также включают инструкции половинной загрузки.)
Набор команд 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
регистр используется в качестве счетчика декремента, и операция останавливается , когда счетчик достигает нуля или (для сканирования и сравнения) при обнаружении неравенства.
Стек реализован с неявно уменьшающимся (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 ebp ; save calling function's stack frame (ebp)
mov ebp, esp ; make a new stack frame on top of our caller's stack
sub esp, 4 ; allocate 4 bytes of stack space for this function's local variables
... функционально эквивалентен просто:
enter 4, 0
Другие инструкции для управления стеком включают pushf
/ popf
для сохранения и извлечения регистра (E) FLAGS. В pusha
/ popa
инструкциях будут хранить и извлекать все состояние целочисленного регистра и из стека.
Предполагается, что значения для загрузки или сохранения 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-битного только). Далеко скачок является тот , который использует полную базу сегмента: значение смещения в качестве абсолютного адреса. Существуют также косвенные и индексированные формы каждого из них.
Помимо простых операций перехода, существуют инструкции call
(вызов подпрограммы) и ret
(возврат из подпрограммы). Перед передачей управления подпрограмме call
помещает адрес смещения сегмента инструкции, следующей за call
функцией, в стек; ret
извлекает это значение из стека и переходит к нему, эффективно возвращая поток управления этой части программы. В случае a far call
основание сегмента сдвигается вслед за смещением; far ret
выталкивает смещение, а затем базу сегмента для возврата.
Есть также две похожие инструкции int
( прерывание ), которые сохраняют текущее значение регистра (E) FLAGS в стеке, а затем выполняют a far call
, за исключением того, что вместо адреса он использует вектор прерывания , индекс в таблице обработчика прерываний. адреса. Обычно обработчик прерывания сохраняет все другие регистры ЦП, которые он использует, если только они не используются для возврата результата операции в вызывающую программу (в программном обеспечении, называемом прерываниями). Совпадение возврат из прерывания обучения является iret
, который восстанавливает флаги после возвращения. Мягкие Прерывания типа описанных выше, используются некоторыми операционными системами для системных вызовов , а также может быть использован при отладке жестких обработчиков прерываний. Аппаратные прерывания запускаются внешними аппаратными событиями и должны сохранять все значения регистров, поскольку состояние выполняющейся в данный момент программы неизвестно. В защищенном режиме операционная система может настроить прерывания для запуска переключения задачи, которое автоматически сохранит все регистры активной задачи.
Примеры
"Привет, мир!" программа для DOS на ассемблере в стиле MASM
Использование прерывания 21h для вывода - в других примерах используется printf библиотеки libc для вывода на стандартный вывод .
.model small
.stack 100h
.data
msg db 'Hello world!$'
.code
start:
mov ah, 09h ; Display the message
lea dx, msg
int 21h
mov ax, 4C00h ; Terminate the executable
int 21h
end start
"Привет, мир!" программа для Windows в стиле сборки MASM
; requires /coff switch on 6.15 and earlier versions
.386
.model small,c
.stack 1000h
.data
msg db "Hello world!",0
.code
includelib libcmt.lib
includelib libvcruntime.lib
includelib libucrt.lib
includelib legacy_stdio_definitions.lib
extrn printf:near
extrn exit:near
public main
main proc
push offset msg
call printf
push 0
call exit
main endp
end
"Привет, мир!" программа для Windows в стиле сборки NASM
; Image base = 0x00400000
%define RVA(x) (x-0x00400000)
section .text
push dword hello
call dword [printf]
push byte +0
call dword [exit]
ret
section .data
hello db "Hello world!"
section .idata
dd RVA(msvcrt_LookupTable)
dd -1
dd 0
dd RVA(msvcrt_string)
dd RVA(msvcrt_imports)
times 5 dd 0 ; ends the descriptor table
msvcrt_string dd "msvcrt.dll", 0
msvcrt_LookupTable:
dd RVA(msvcrt_printf)
dd RVA(msvcrt_exit)
dd 0
msvcrt_imports:
printf dd RVA(msvcrt_printf)
exit dd RVA(msvcrt_exit)
dd 0
msvcrt_printf:
dw 1
dw "printf", 0
msvcrt_exit:
dw 2
dw "exit", 0
dd 0
"Привет, мир!" программа для 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 data
str: 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 this address ($ symbol)
section .text ; this is the code section
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)
"Привет, мир!" программа для 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 use of external symbol as printf is declared in a different object-module.
;Linker resolves this symbol later.
segment .data ;section for initialized data
string db 'Hello world!', 0Ah, 0h ;message string with new-line char (10 decimal) and the NULL terminator
;string now refers to the starting address at which 'Hello, World' is stored.
segment .text
main:
push string ;push the address of first character of string onto stack. This will be argument to printf
call printf ;calls printf
add esp, 4 ;advances stack-pointer by 4 flushing out the pushed string argument
ret ;return
"Привет, мир!" программа для 64-битного режима Linux в сборке в стиле NASM
; build: nasm -f elf64 -F dwarf hello.asm
; link: ld -o hello hello.o
DEFAULT REL ; use RIP-relative addressing modes by default, so [foo] = [rel foo]
SECTION .rodata ; read-only data can go in the .rodata section on GNU/Linux, like .rdata on Windows
Hello: 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 puts
SECTION .text
global _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)
xor edi, edi ; 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 strace's logging on stderr. Which is normally fine
execve("./hello", ["./hello"], 0x7ffc8b0b3570 /* 51 vars */) = 0
write(1, "Hello world!\n", 13) = 13
exit(0) = ?
+++ exited with 0 +++
Использование регистра флагов
Флаги широко используются для сравнений в архитектуре x86. При сравнении двух данных ЦП устанавливает соответствующий флаг или флаги. После этого можно использовать инструкции условного перехода для проверки флагов и перехода к коду, который должен выполняться, например:
cmp eax, ebx
jne do_something
; ...
do_something:
; do something here
Флаги также используются в архитектуре x86 для включения и выключения определенных функций или режимов выполнения. Например, чтобы отключить все маскируемые прерывания, вы можете использовать инструкцию:
cli
К регистру флагов также можно получить прямой доступ. Младшие 8 бит регистра флага могут быть загружены с ah
помощью lahf
инструкции. Весь регистр флагов также могут быть перемещены и выключать стек , используя инструкции pushf
, popf
, int
( в том числе into
) и iret
.
Использование регистра указателя инструкций
Указатель команд вызывается ip
в 16-битном режиме, eip
в 32-битном режиме, так и rip
в 64-битном режиме. Регистр указателя команд указывает на адрес памяти, который процессор попытается выполнить в следующий раз; к нему нельзя получить прямой доступ в 16-битном или 32-битном режиме, но можно записать последовательность, подобную следующей, для помещения адреса next_line
в eax
:
call next_line
next_line:
pop eax
Эта последовательность инструкций генерирует независимый от позиции код, поскольку call
принимает непосредственный операнд, относящийся к указателю инструкции, описывающий смещение в байтах целевой инструкции от следующей инструкции (в данном случае 0).
Запись в указатель инструкции проста - jmp
инструкция устанавливает указатель инструкции на целевой адрес, поэтому, например, последовательность, подобная следующей, поместит содержимое eax
в eip
:
jmp eax
В 64-битном режиме инструкции могут ссылаться на данные относительно указателя инструкции, поэтому меньше необходимости копировать значение указателя инструкции в другой регистр.
Смотрите также
- язык ассемблера
- Списки инструкций X86
- Архитектура X86
- Конструкция процессора
- Список сборщиков
- Самомодифицирующийся код
- ДОС
использованная литература
дальнейшее чтение
Руководства
- Руководства для разработчиков программного обеспечения Intel 64 и IA-32
- Руководство программиста по архитектуре AMD64 (том 1-5)
Книги
- Эд, Йоргенсен (май 2018 г.). Программирование на языке ассемблера x86-64 с Ubuntu (PDF) (редакция 1.0.97). п. 367.