stringtranslate.com

язык ассемблера x86


Язык ассемблера x86 — это название семейства ассемблерных языков , которые обеспечивают некоторый уровень обратной совместимости с процессорами вплоть до микропроцессора Intel 8008 , выпущенного в апреле 1972 года. [1] [2] Он используется для создания объектного кода для процессоры класса x86 .

Ассемблер считается языком программирования и является машинно-специфичным и низкоуровневым . Как и все языки ассемблера, ассемблер x86 использует мнемонику для представления основных инструкций ЦП или машинного кода . [3] Языки ассемблера чаще всего используются для детальных и критичных по времени приложений, таких как небольшие встроенные системы реального времени , ядра операционных систем и драйверы устройств , но также могут использоваться и для других приложений. Компилятор иногда создает ассемблерный код в качестве промежуточного шага при трансляции программы высокого уровня в машинный код.

Ключевое слово

Зарезервированные ключевые слова языка ассемблера x86 [4] [5]

  • ЛДС
  • лес
  • лфс
  • LGS
  • лсс
  • поп
  • толкать
  • в
  • входы
  • вне
  • ауты
  • лахф
  • Сахф
  • попф
  • толчок
  • КМЦ
  • клк
  • стц
  • Кли
  • Стти
  • cld
  • стандартный
  • добавлять
  • АЦП
  • суб
  • сбб
  • cmp
  • Inc.
  • декабрь
  • тест
  • Сал
  • шл
  • сар
  • шр
  • шлд
  • осколок
  • нет
  • отрицательный
  • граница
  • и
  • или
  • исключающее ИЛИ
  • Имул
  • мул
  • делитель
  • идив
  • между прочим
  • cwtl
  • cwtd
  • клтд
  • даа
  • дас
  • ааа
  • аас
  • ааа
  • аад
  • ждать
  • ждать
  • движется
  • ЦМПС
  • стос
  • лодочки
  • скас
  • xlat
  • представитель
  • репнз
  • репс
  • позвоню
  • вызов
  • в отставку
  • лрет
  • входить
  • оставлять
  • jcxz
  • петля
  • лупнз
  • лупз
  • JMP
  • лджмп
  • интервал
  • в
  • ирет
  • sldt
  • ул.
  • ллдт
  • литр
  • верр
  • верв
  • сержант
  • Сидт
  • лгдт
  • Лидт
  • смс
  • лмсв
  • Лар
  • лсл
  • клтс
  • арпл
  • BSF
  • бср
  • БТ
  • биткойны
  • БТР
  • БТС
  • cmpxchg
  • фсин
  • fcos
  • фсинкос
  • флд
  • флдкв
  • флденв
  • фпрем
  • фуком
  • Фукомп
  • фукомпп
  • Леа
  • двигаться
  • movw
  • movsx
  • мовзб
  • папа
  • пуша
  • ркл
  • ркр
  • роль
  • рор
  • setcc
  • замена
  • xadd
  • хчг
  • wbinvd
  • инвд
  • инвлпг
  • замок
  • нет
  • хлт
  • флд
  • первый день
  • ФСТП
  • валютный курс
  • поле
  • кулак
  • кулак
  • fbld
  • фбстп
  • причуда
  • фаддп
  • Фиадд
  • fsub
  • fsubp
  • фсубр
  • fsubrp
  • фисубрп
  • фисубр
  • fmul
  • fmulp
  • фимул
  • fdiv
  • fdivp
  • fdivr
  • fdivrp
  • фидив
  • фидивр
  • fsqrt
  • fscale
  • фпрем
  • фрндинт
  • fxtract
  • фабрики
  • фчс
  • fcom
  • fcomp
  • fcompp
  • фиком
  • фикомп
  • фтст
  • fxam
  • фптан
  • фпатан
  • f2xm1
  • fyl2x
  • fyl2xp1
  • fldl2e
  • fldl2t
  • fldlg2
  • флдлн2
  • флдпи
  • флдз
  • окончание
  • фнинт
  • фноп
  • fсохранить
  • fnsave
  • фстью
  • фнстью
  • фстенв
  • фнстенв
  • фстсв
  • фнстсв
  • фрстор
  • fclex
  • fnclex
  • fdecstp
  • бесплатно
  • финкстп

Мнемоника и коды операций

Каждая инструкция ассемблера x86 представлена ​​мнемоникой , которая, часто в сочетании с одним или несколькими операндами, преобразуется в один или несколько байтов, называемых кодом операции ; например, инструкция NOP преобразуется в 0x90, а инструкция HLT — в 0xF4. [3] Существуют потенциальные коды операций без документированной мнемоники, которые разные процессоры могут интерпретировать по-разному, из-за чего использующая их программа ведет себя непоследовательно или даже генерирует исключение на некоторых процессорах. Эти коды операций часто появляются на соревнованиях по написанию кода как способ сделать код меньше, быстрее, элегантнее или просто продемонстрировать мастерство автора.

Синтаксис

Язык ассемблера x86 имеет две основные ветви синтаксиса : синтаксис Intel и синтаксис AT&T . [6] Синтаксис Intel доминирует в мире DOS и Windows , а синтаксис AT&T доминирует в мире Unix , поскольку Unix была создана в AT&T Bell Labs . [7] Вот краткое изложение основных различий между синтаксисом Intel и синтаксисом AT&T :

Многие ассемблеры 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]

Помимо общих регистров дополнительно имеются:

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

Регистры x86 можно использовать с помощью инструкций MOV . Например, в синтаксисе Intel:

Мов топор , 1234h ; копирует значение 1234hex (4660d) в регистр AX   
mov bx , топор ; копирует значение регистра AX в регистр BX   

Сегментированная адресация

Архитектура x86 в реальном и виртуальном режиме 8086 использует для адресации памяти процесс, известный как сегментация , а не плоскую модель памяти, используемую во многих других средах. Сегментация предполагает составление адреса памяти из двух частей: сегмента и смещения ; сегмент указывает на начало группы адресов размером 64 КиБ (64×2 10 ), а смещение определяет, насколько далеко от этого начального адреса находится желаемый адрес. При сегментной адресации для полного адреса памяти требуются два регистра. Один для хранения сегмента, другой для смещения. Чтобы преобразовать обратно в плоский адрес, значение сегмента сдвигается на четыре бита влево (что эквивалентно умножению на 2, 4 или 16), а затем добавляется к смещению для формирования полного адреса, что позволяет преодолеть барьер 64 КБ за счет умного выбора адресов. , хотя это значительно усложняет программирование.

Например, только в реальном режиме /защищенном режиме, если DS содержит шестнадцатеричное число 0xDEAD , а DX содержит число 0xCAFE , они вместе будут указывать на адрес памяти . Таким образом, ЦП может адресовать до 1 048 576 байт (1 МБ) в реальном режиме. Комбинируя значения сегмента и смещения , мы находим 20-битный адрес.0xDEAD * 0x10 + 0xCAFE == 0xEB5CE

Первоначальный размер программ IBM PC ограничивался 640 КБ, но спецификация расширенной памяти использовалась для реализации схемы переключения банков, которая вышла из употребления, когда более поздние операционные системы, такие как Windows, использовали более широкие диапазоны адресов новых процессоров и реализовывали собственную виртуальную память. схемы.

Защищенный режим, начиная с Intel 80286, использовался OS/2 . Ряд недостатков, таких как невозможность доступа к BIOS и невозможность вернуться в реальный режим без перезагрузки процессора, препятствовали широкому использованию. [12] 80286 также по-прежнему был ограничен адресацией памяти 16-битными сегментами, то есть одновременно можно было получить доступ только к 216 байтам (64 килобайтам ). Чтобы получить доступ к расширенным функциям 80286, операционная система перевела процессор в защищенный режим, включив 24-битную адресацию и, следовательно, 2 24 байта памяти (16 мегабайт ).

В защищенном режиме селектор сегмента можно разбить на три части: 13-битный индекс, бит индикатора таблицы , определяющий, находится ли запись в GDT или LDT , и 2-битный запрошенный уровень привилегий ; см. сегментацию памяти x86 .

При обращении к адресу с сегментом и смещением используется обозначение сегмент : смещение , поэтому в приведенном выше примере плоский адрес 0xEB5CE может быть записан как 0xDEAD:0xCAFE или как пара регистров сегмента и смещения; ДС:DX.

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

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:

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

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

Примеры

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

На компьютере под управлением UEFI прошивка UEFI (кроме CSM и устаревшего дополнительного ПЗУ ), загрузчик UEFI и ядро ​​операционной системы UEFI работают в длинном режиме.

Типы инструкций

В целом особенности современного набора команд x86 таковы:

Инструкции по стеку

Архитектура x86 имеет аппаратную поддержку механизма стека выполнения . pushТакие инструкции , как pop, callи retиспользуются с правильно настроенным стеком для передачи параметров, выделения места для локальных данных, а также для сохранения и восстановления точек возврата вызова. Инструкция ret size очень полезна для реализации соглашений об эффективном (и быстром) вызове , когда вызываемый объект отвечает за освобождение пространства стека, занятого параметрами.

При настройке кадра стека для хранения локальных данных рекурсивной процедуры есть несколько вариантов; Инструкция высокого уровня enter(введенная в 80186) принимает аргумент глубины вложенности процедуры , а также аргумент локального размера и может быть быстрее, чем более явное манипулирование регистрами (например, push bp ; mov bp, sp ; ). Будет ли это быстрее или медленнее, зависит от конкретной реализации процессора x86, а также от соглашения о вызовах, используемого компилятором, программистом или конкретным программным кодом; большая часть кода x86 предназначена для работы на процессорах x86 от нескольких производителей и на разных технологических поколениях процессоров, что подразумевает сильно различающиеся микроархитектуры и решения микрокода , а также различные варианты проектирования на уровне затворов и транзисторов .sub sp, size

Полный диапазон режимов адресации (включая немедленный и базовый+смещение ) даже для таких инструкций, как pushи pop, упрощает прямое использование стека для целочисленных данных , данных с плавающей запятой и адресных данных, а также сохраняет относительно простые спецификации и механизмы ABI по сравнению с некоторые RISC-архитектуры (требуют более подробной информации о стеке вызовов).

Целочисленные инструкции ALU

Сборка x86 имеет стандартные математические операции: add, sub, и (для целых чисел со знаком), with и (для целых чисел без знака) neg; логические операторы , , , ; битовый сдвиг арифметический и логический, / (для целых чисел со знаком), / (для целых чисел без знака); вращение с переносом и без него, / , / , дополнение арифметических инструкций BCD , , , и другие.imulidivmuldiv andorxornotsalsarshlshrrclrcrrolroraaaaaddaa

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

Язык ассемблера 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), ststst(0)st(n)st(0)st(1)st(7)fsubrfdivrfaddp st(1), stst(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, и ), которые выполняют каждую операцию до заданного размера ( для 8-битного байта, movsдля 16-битного слова, для 32-битного двойного слова). затем увеличивает/уменьшает (в зависимости от DF, флага направления) регистр неявного адреса ( for , for and , и оба for and ). Для операций загрузки, сохранения и сканирования неявный регистр назначения/источника/сравнения находится в регистре или ( в зависимости от размера). Используются неявные сегментные регистры for и for . Регистр или используется в качестве уменьшающего счетчика, и операция прекращается, когда счетчик достигает нуля или (для сканирования и сравнения) при обнаружении неравенства. К сожалению, с течением времени выполнением некоторых из этих инструкций стали пренебрегать, и в некоторых случаях теперь можно получить более быстрые результаты, написав алгоритмы самостоятельно. Однако Intel и AMD обновили некоторые инструкции, и некоторые из них теперь имеют очень приличную производительность, поэтому программисту рекомендуется прочитать последние авторитетные статьи по тестированию производительности, прежде чем использовать конкретную инструкцию из этой группы.scascmpsbwdsilodsdistosscasmovscmpsalaxeaxdssiesdicxecx

Стек — это область памяти и связанный с ней «указатель стека», указывающий на дно стека. Указатель стека уменьшается при добавлении элементов («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обычно не нужны.

Этот код является началом функции, типичной для языка высокого уровня, когда оптимизация компилятора отключена для удобства отладки:

 нажать РБП ; Сохраняем указатель кадра стека вызывающей функции (регистр rbp) mov rbp , rsp ; Создать новый кадр стека ниже стека нашего вызывающего абонента sub rsp , 32 ; Зарезервируйте 32 байта стекового пространства для локальных переменных этой функции. ; Локальные переменные будут находиться ниже rbp, и на них можно будет ссылаться относительно rbp, ; опять же, лучше всего для простоты отладки, но для лучшей производительности rbp не будет ; вообще можно использовать, а на локальные переменные будут ссылаться относительно rsp ; потому что, если не считать сохранения кода, rbp бесплатен для других целей. ; Однако если здесь изменить rbp, его значение должно сохраниться для вызывающей стороны. mov [ rbp - 8 ], rdx ; Пример доступа к локальной переменной из ячейки памяти в регистр rdx                     

... функционально эквивалентно просто:

 введите 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адрес смещения сегмента инструкции, следующей за ; извлекает это значение из стека и переходит к нему, эффективно возвращая поток управления этой части программы. В случае a база сегмента перемещается после смещения; выталкивает смещение, а затем базу сегмента для возврата.callretfar callfar ret

Есть также две аналогичные инструкции int( прерывание ), которые сохраняют текущее значение регистра (E)FLAGS в стеке, а затем выполняют операцию, far callза исключением того, что вместо адреса используется вектор прерывания , индекс в таблице обработчиков прерываний. адреса. Обычно обработчик прерываний сохраняет все остальные регистры ЦП, которые он использует, если только они не используются для возврата результата операции вызывающей программе (в программном обеспечении, называемом прерываниями). Соответствующей командой возврата из прерывания является iret, которая восстанавливает флаги после возврата. Мягкие прерывания описанного выше типа используются некоторыми операционными системами для системных вызовов , а также могут использоваться при отладке обработчиков жестких прерываний. Аппаратные прерывания запускаются внешними аппаратными событиями и должны сохранять все значения регистров, поскольку состояние выполняющейся в данный момент программы неизвестно. В защищенном режиме прерывания могут быть настроены операционной системой для запуска переключения задачи, что автоматически сохранит все регистры активной задачи.

Примеры

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

"Привет, мир!" программа для MS-DOS на ассемблере в стиле MASM

Использование инструкции программного прерывания 21h для вызова операционной системы MS-DOS для вывода на дисплей — в других примерах для записи в стандартный вывод используется процедура printf() библиотеки C libc . Обратите внимание, что первый пример — это пример 30-летней давности, использующий 16-битный режим, как на Intel 8086. Второй пример — это код Intel 386 в 32-битном режиме. Современный код будет в 64-битном режиме. [24]

.модель маленькая .стек 100h  .data msg db 'Привет, мир!$'Начало .кода : mov ax , @DATA ; Инициализирует сегмент данных mov ds , ax mov ah , 09h ; Устанавливает 8-битный регистр «ah», старший байт регистра ax, в 9, в ; выберите номер подфункции подпрограммы MS-DOS, вызываемой ниже ; через программное прерывание int 21h для отображения сообщения lea dx , msg ; Берет адрес сообщения и сохраняет его в 16-битном регистре dx int 21h ; Различные процедуры MS-DOS могут быть вызваны программным прерыванием 21h ; Наша требуемая подфункция была установлена ​​в регистре выше.               Мов топор , 4C00h ; Устанавливает в регистре ax номер подфункции для программного обеспечения MS-DOS ; прерывание int 21h для службы «завершить программу». интервал 21ч ; Вызов этой службы MS-DOS никогда не возвращает результат, так как завершает программу.    конец начало 

"Привет, мир!" программа для Windows в сборке в стиле MASM

; требуется переключатель /coff в версии 6.15 и более ранних версиях .386 .model small , c .stack 1000h  .data msg db "Привет, мир!" , 0  .code includelib libcmt.lib includelib libvcruntime.lib includelib libucrt.lib includelib Legacy_stdio_definitions.lib    extrn printf : рядом extrn выход : рядом  public main main proc push offset msg call printf push 0 вызов выхода main endp            конец

"Привет, мир!" программа для Windows в сборке в стиле NASM

; База изображения = 0x00400000 %define RVA(x) (x-0x00400000) раздел .text push dword hello call dword [ printf ] push byte + 0 call dword [ выход ] ret         раздел .data hello db "Привет, мир!"   раздел .idata dd RVA ( msvcrt_LookupTable ) dd - 1 dd 0 dd RVA ( msvcrt_string ) dd RVA ( msvcrt_imports ) раз 5 dd 0 ; завершает таблицу дескрипторов          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 ) выход dd RVA ( msvcrt_exit ) dd 0     msvcrt_printf: dw 1 dw "printf" , 0 msvcrt_exit: dw 2 dw "exit" , 0 дд 0       

.данные ; раздел для инициализированных данных str: .ascii "Hello, world!\n" ; определить строку текста, содержащую «Привет, мир!» а затем новая строка. стр_лен = . - ул ; получить длину str, вычитая его адрес         .текст ; раздел программных функций .globl _start ; экспортируйте функцию _start, чтобы ее можно было запустить _start: ; начать функцию _start movl $4 , %eax ; укажите инструкцию для 'sys_write' movl $1 , %ebx ; указать вывод на стандартный вывод, 'stdout' movl $str , %ecx ; укажите выводимый текст в нашей определенной строке movl $str_len , %edx ; укажите количество символов для записи как длину нашей определенной строки. интервал $0x80 ; вызвать системное прерывание, чтобы инициировать созданный нами системный вызов.                        movl $1 , %eax ; укажите инструкцию для 'sys_exit' movl $0 , %ebx ; укажите код выхода 0, что означает успех int $0x80 ; вызвать другое системное прерывание, чтобы завершить программу          

"Привет, мир!" программа для Linux в сборке в стиле NASM

; ; Эта программа работает в 32-битном защищенном режиме. ; сборка: nasm -f elf -F stabs name.asm ; ссылка: ld -o name name.o ; ; В 64-битном длинном режиме вы можете использовать 64-битные регистры (например, rax вместо eax, rbx вместо ebx и т. д.) ; Также замените «-f elf» на «-f elf64» в команде сборки. ; раздел .данные ; раздел для инициализированных данных str: db 'Hello world!' , 0Ач ; строка сообщения с символом новой строки в конце (10 десятичных знаков) str_len: equ $ - str ; вычисляет длину строки (в байтах) путем вычитания начального адреса строки ; от «здесь, этот адрес» (символ «$» означает «здесь»)            раздел .текст ; это участок кода (текст программы) в глобальной памяти _start ; _start является точкой входа и требует глобальной области видимости, чтобы ; linker --эквивалент функции main() в C/C++ _start: ; определение процедуры _start начинается здесь move eax , 4 ; указать код функции sys_write (из таблицы векторов ОС) mov ebx , 1 ; указать файловый дескриптор stdout --в gnu/linux все рассматривается как файл, ; даже аппаратные устройства mov ecx , str ; переместить начальный _адрес_ строкового сообщения в регистр ecx mov edx , str_len ; переместить длину сообщения (в байтах) int 80h ; прерывание ядра для выполнения только что настроенного системного вызова - ; в gnu/linux сервисы запрашиваются через ядро ​​mov eax , 1 ; указать код функции sys_exit (из таблицы векторов ОС) mov ebx , 0 ; указать код возврата для ОС (ноль означает, что ОС все прошло нормально) int 80h ; прерывание ядра для выполнения системного вызова (для выхода)                      

Для 64-битного длинного режима адресом сообщения будет «lea rcx, str», обратите внимание на 64-битный регистр rcx.

"Привет, мир!" программа для Linux на ассемблере в стиле NASM с использованием стандартной библиотеки C

; ; Эта программа работает в 32-битном защищенном режиме. ; gcc по умолчанию связывает стандартную библиотеку C; сборка: nasm -f elf -F stabs name.asm ; ссылка: gcc -o name name.o ; ; В 64-битном длинном режиме вы можете использовать 64-битные регистры (например, rax вместо eax, rbx вместо ebx и т. д.) ; Также замените «-f elf» на «-f elf64» в команде сборки. ; глобальный главный ; 'main' должен быть определен во время компиляции ; против стандартной библиотеки C extern printf ; объявляет использование внешнего символа, например printf ; printf объявлен в другом объектном модуле. ; Компоновщик разрешает этот символ позже.         сегмент.данные ; _ раздел для инициализированной строки данных db 'Hello world!' , 0Ач , 0 ; строка сообщения, заканчивающаяся символом новой строки (10 ; десятичное число) и нулевым байтом терминатора 'NUL' ; «строка» теперь относится к начальному адресу ; в котором хранится «Hello, World».          сегмент .text main: push string ; Поместите адрес «строки» в стек. ; Это уменьшает esp на 4 байта перед сохранением ; 4-байтовая адресная «строка» в память по адресу ; новый esp, новое дно стека.        ; Это будет аргументом для вызова printf() printf ; вызывает функцию C printf(). добавить ЕСП , 4 ; Увеличивает указатель стека на 4, чтобы вернуть его обратно ; туда, где это было до «толчка», который ; уменьшил его на 4 байта. РЭТ ; Вернитесь к нашему абоненту.           

"Привет, мир!" программа для 64-битного режима Linux в сборке в стиле NASM

Этот пример находится в современном 64-битном режиме.

; сборка: nasm -f elf64 -F dwarf hello.asm ; ссылка: ld -o привет привет.оОТНОС ПО УМОЛЧАНИЮ ; по умолчанию используйте режимы адресации относительно RIP, поэтому [foo] = [rel foo]  РАЗДЕЛ .rodata ; данные только для чтения должны находиться в разделе .rodata в GNU/Linux, как .rdata в Windows Hello: db "Hello world!" , 10 ; Заканчивается байтом 10 = новая строка (ASCII LF) len_Hello: equ $ - Hello ; Заставьте NASM вычислить длину как константу времени сборки ; символ «$» означает «здесь». write() принимает такую ​​длину, что ; строка в стиле C с нулевым завершением не требуется. ; Это было бы для C puts()         РАЗДЕЛ .rodata ; данные только для чтения могут находиться в разделе .rodata в GNU/Linux, как .rdata в Windows Hello: db "Hello world!" , 10 ; 10 = `\n`. len_Hello: equ $ Привет ; заставить NASM вычислить длину как константу времени сборки ;; write() принимает длину, поэтому строка в стиле C с нулевым завершением не требуется. Это было бы для путов     РАЗДЕЛ .текст глобальный _start _start: mov eax , 1 ; __NR_write номер системного вызова из Linux asm/unistd_64.h (x86_64) mov edi , 1 ; int fd = STDOUT_FILENO lea rsi , [ rel Hello ] ; x86-64 использует LEA, относящийся к RIP, для помещения статических адресов в регистры mov rdx , len_Hello ; size_t count = len_Hello системный вызов ; написать (1, Привет, len_Hello); вызов ядра для фактического выполнения системного вызова ;; возвращаемое значение в RAX. RCX и R11 также перезаписываются системным вызовом.           мов eax , 60 ; __NR_exit номер вызова (x86_64) хранится в регистре eax. хор Эди , Эди ; Это нули edi, а также rdi. ; Этот трюк xor-self является предпочтительной общей идиомой для обнуления ; регистр, и это всегда самый быстрый метод. ; Когда 32-битное значение сохраняется, например, в edx, старшие биты 63:32 равны ; автоматически обнуляется в каждом случае. Это избавит вас от необходимости устанавливать ; биты с дополнительной инструкцией, как это бывает очень часто ; необходимо, чтобы весь 64-битный регистр был заполнен 32-битным значением. ; Это устанавливает статус выхода нашей процедуры = 0 (обычный выход) syscall ; _exit(0)            

Запуск его под straceуправлением проверяет, что в процессе не выполняется никаких дополнительных системных вызовов. Версия printf будет выполнять гораздо больше системных вызовов для инициализации libc и динамического связывания . Но это статический исполняемый файл, поскольку мы компонуем его с помощью ld без -pie или каких-либо общих библиотек; единственные инструкции, которые выполняются в пользовательском пространстве, — это те, которые вы предоставляете.

$ strace  ./hello  >  /dev/null # без перенаправления стандартный вывод вашей программы смешивается с журналированием strace на stderr. Обычно это нормально execve("./hello", ["./hello"], 0x7ffc8b0b3570 /* 51 vars */) = 0 write(1, "Hello world!\n", 13) = 13 exit(0) "=" +++ завершился с 0 +++ 

Использование регистра флагов

Флаги широко используются для сравнений в архитектуре x86. Когда выполняется сравнение двух данных, ЦП устанавливает соответствующий флаг или флаги. После этого можно использовать инструкции условного перехода для проверки флагов и перехода к коду, который должен выполняться, например:

cmp eax , ebx jne do_something ; ... сделай что-нибудь: ; сделай что-нибудь здесь 

Помимо инструкций сравнения, существует множество арифметических и других инструкций, которые устанавливают биты в регистре флагов. Другими примерами являются инструкции sub, test и add, а также многие другие. Общие комбинации, такие как cmp + условный переход, внутренне «слиты» (« макрослияние ») в одну микроинструкцию (μ-op) и выполняются быстро, если процессор может угадать, в каком направлении пойдет условный переход: прыжок или продолжение.

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

Кли

К регистру флагов также можно получить прямой доступ. Младшие 8 бит регистра флагов можно загрузить ahс помощью этой lahfинструкции. Весь регистр флагов также можно перемещать в стек и из него с помощью инструкций pushfd/pushfq, popfd/popfq, int(включая into) и iret.

Математическая подсистема с плавающей запятой x87 также имеет собственный независимый регистр типа «флаги» для слова состояния fp. В 1990-х годах процедура доступа к битам флагов в этом регистре была неудобной и медленной, но на современных процессорах есть инструкции «сравнить два значения с плавающей запятой», которые можно использовать с обычными инструкциями условного перехода/ветви напрямую, без каких-либо промежуточных шагов. .

Использование регистра указателя команд

Указатель инструкции вызывается в ip16-битном режиме, eipв 32-битном режиме и ripв 64-битном режиме. Регистр указателя команд указывает на адрес следующей инструкции, которую процессор попытается выполнить. К нему нельзя получить прямой доступ в 16-битном или 32-битном режиме, но можно записать последовательность, подобную следующей, для помещения адреса next_lineв eax(32-битный код):

вызвать next_line next_line: pop eax

Запись в указатель инструкции проста — jmpинструкция сохраняет заданный целевой адрес в указателе инструкции, поэтому, например, последовательность, подобная следующей, поместит содержимое raxв rip(64-битный код):

JMP Ракс

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

Смотрите также

Рекомендации

  1. ^ «Семейство микропроцессоров Intel 8008 (i8008)» . www.cpu-world.com . Проверено 25 марта 2021 г.
  2. ^ «Интел 8008». МУЗЕЙ ЦП - МУЗЕЙ МИКРОПРОЦЕССОРОВ И ФОТОГРАФИИ МИКРОПРОЦЕССОРОВ . Проверено 25 марта 2021 г.
  3. ^ abc "ОПКОДЫ Intel 8008" . www.pastraiser.com . Проверено 25 марта 2021 г.
  4. ^ «Справочник по языку ассемблера» . www.ibm.com . Проверено 28 ноября 2022 г.
  5. ^ «Справочное руководство по языку ассемблера x86» (PDF) .
  6. ^ abcde Нараям, Рам (17 октября 2007 г.). «Ассемблеры Linux: сравнение GAS и NASM». ИБМ . Архивировано из оригинала 3 октября 2013 года . Проверено 2 июля 2008 г.
  7. ^ «Создание Unix». Архивировано из оригинала 2 апреля 2014 года.
  8. ^ Хайд, Рэндалл. «Какой ассемблер лучший?» . Проверено 18 мая 2008 г.
  9. ^ «Новости GNU Assembler, v2.1 поддерживает синтаксис Intel» . 04 апреля 2008 г. Проверено 2 июля 2008 г.
  10. ^ «i386-Ошибки (Использование как)» . Документация по Binutils . Проверено 15 января 2020 г. .
  11. ^ «Руководство по программированию на языке ассемблера Intel 8080» (PDF) . Проверено 12 мая 2023 г.
  12. Мюллер, Скотт (24 марта 2006 г.). «Процессоры P2 (286) второго поколения». Модернизация и ремонт компьютеров, 17-е издание (книга) (17-е изд.). Que. ISBN  0-7897-3404-4. Проверено 6 декабря 2017 г.
  13. ^ Кертис Медоу. «Кодирование инструкций 8086».
  14. ^ Игорь Холодов. «6. Кодирование операндов инструкций x86, байт MOD-REG-R/M».
  15. ^ «Инструкции по кодированию x86» .
  16. ^ Майкл Абраш. «Дзен языка ассемблера: Том I, Знания». «Глава 7: Адресация памяти». Раздел «Адресация mod-reg-rm».
  17. ^ Справочное руководство программиста Intel 80386. «17.2.1 Байты ModR/M и SIB»
  18. ^ «Кодировка инструкций X86-64: байты ModR/M и SIB»
  19. ^ «Рисунок 2-1. Формат инструкций для архитектур Intel 64 и IA-32» .
  20. ^ «Скрытая адресация x86» .
  21. ^ AB Стивен МакКамант. «Ручное и автоматизированное бинарное обратное проектирование».
  22. ^ «Список пожеланий для инструкций X86» .
  23. ^ Питер Кордес (18 декабря 2011 г.). «NASM (Intel) по сравнению с синтаксисом AT&T: каковы преимущества?». Переполнение стека .
  24. ^ «Я только начал сборку» . daniweb.com . 2008.

дальнейшее чтение

Руководства

Книги