Руководство по ассемблеру MASM для архитектуры Intel x86-64

Тема в разделе "MASM", создана пользователем Mikl___, 28 окт 2026 в 08:20.

  1. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364
    Взято здесь
    1. Введение в ассемблер Intel x86-64
    2. Основы ассемблера MASM для Intel x86-64
    3. Работа с данными и памятью
      • Преобразование данных
      • Символы
      • Режимы адресации. Косвенная адресация
      • Выравнивание
      • Стек
      • Указатели
      • Строки
      • Сортировка массива
      • Многомерные массивы
      • xlat и поиск в таблицах
      • Структуры
      • Объединения union
    4. Базовые конструкции
      • Условные конструкции
      • Булевые конструкции
      • Имитация конструкции switch..case
      • Машина состояний
      • Циклы
    5. Арифметика больших чисел
      • Сложение и вычитание больших чисел
      • Сравнение больших чисел
      • Умножение больших чисел
      • Деление больших чисел
      • Поразрядные операции с большими числами
      • Операции сдвига больших чисел
    6. Процедуры
      • Определение и вызов процедур
      • Сохранение регистров и переменных при вызове процедур
      • Параметры
      • Результат процедуры
      • Фрейм стека и локальные переменные
      • Указатель на процедуру
    7. Организация программы
      • Вставка кода и директива include
      • Разбиение программы на модули и подключение внешних модулей
      • Библиотеки
    8. FPU, SSE и числа с плавающей точкой
      • Регистры FPU
      • Числа с плавающей точкой
      • Загрузка данных и преобразование чисел в FPU
      • Сложение чисел с плавающей точкой в FPU
      • Вычитание чисел с плавающей точкой в FPU
      • Умножение чисел с плавающей точкой в FPU
      • Деление чисел с плавающей точкой в FPU
      • Дополнительные математические инструкции FPU
      • Сравнение чисел с плавающей точкой в FPU
      • Расширения SSE для чисел с плавающей точкой
      • Арифметика чисел с плавающей точкой в SSE
      • Сравнение чисел с плавающей точкой в SSE
      • Десятичная арифметика и двоично-десятичный формат BCD
    9. SIMD
      • Расширения SSE и AVX/AVX2
      • Копирование данных с помощью инструкций SIMD
      • Копирование чисел с плавающей точкой
      • Переупорядочивание данных
      • Логические операции SSE/AVX
      • Операции сдвига SSE/AVX
      • Сложение с помощью инструкций SSE/AVX
      • Вычитание с помощью инструкций SSE/AVX
      • Умножение с помощью инструкций SSE/AVX
      • Математические инструкции SSE/AVX
      • Сравнение целых чисел в SSE/AVX
      • Преобразования целых чисел в SSE/AVX
      • Арифметические операции с плавающей точкой в SSE/AVX
      • Сравнение чисел с плавающей точкой в SIMD
      • Преобразование чисел с плавающей точкой в целые числа и обратно
      • Загрузка в регистры константных значений
      • Сохранение состояния регистров SSE/AVX
    10. Строки
      • Операции со строками
      • Сравнение строк
      • Поиск в строке
      • Сохранение и получение элементов строк
    11. Макросы
      • Определение макросов
      • Параметры макроса
      • Метки в макросах
      • Условная компиляция и циклы
      • Генерация таблиц для поиска
    12. Взаимодействие с кодом на C/C++
      • Первая программа на MASM/C
      • Передача параметров из ассемблера в функцию C/C++
      • Возвращение результата из процедуры ассемблера в код на C/C++
      • Практически пример. Обработка консольного ввода
    13. Взаимодействие с WinAPI
      • Запись в файл и вывод на консоль
      • Считывание файла и ввод с консоли
      • Практический пример. Обработка ввода
      • Создание, открытие и закрытие файла
      • Запись в текстовый файл
      • Чтение текстового файла
      • Графическое приложение. Окно сообщения
    14. Исследование и дизассемблирование файлов
      • Формат COFF-файла
      • Исследование файла с помощью dumpbin
      • Исследование файла с помощью objdump
     
    Последнее редактирование: 28 окт 2026 в 15:05
  2. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Введение в ассемблер Intel x86-64

    Архитектура процессоров Intel x86-64 является на сегодняшний день доминирующей архитектурой для различного рода устройств - настольных компьютеров, ноутбуков, серверов. Семейство процессоров Intel обычно классифицируется как машина с архитектурой фон Неймана - такая машина, которая содержит три основных компонента: центральный процессор (ЦП), память и устройства ввода/вывода (I/0). Эти три компонента связаны между собой через системную шину (состоит из шины адреса, данных и управления). Процессор взаимодействует с памятью и устройствами ввода-вывода, передавая через адресную шину числовой адрес участка памяти или порта устройства ввода-вывода. Через шину данных процессор, память и устройства ввода-вывода обмениваются между собой данными. Через шину управления (control bus) передаются сигналы, которые определяют направление передачи данных (в или из памяти, а также в или из устройства ввода-вывода).
    Зачем изучать ассемблер в эпоху высокоуровневых языков? Ассемблер помогает лучше понять архитектуру компьютера. Знание ассемблера может помочь при реверс-инжениринге, анализе вирусов и прочих вредоносных программ, а также при их создании и поиске уязвимостей. В конце концов понимание работы ассемблера является важным навыком в низкоуровневом программировании, например, при написании операционных систем и драйверов.

    Архитектура Intel x86

    Архитектура x86 обозначает большое семейство процессоров как с 16-битной, так и с 32-битной архитектурой набора команд. История x86 началась с выходом процессора Intel 8086 в 1978 году. В 1979 году выходит функционально похожий на 8086 процессор Intel 8088. Последующие поколения этой серии процессоров получили названия 80186, 80286, 80386 и 80486, что привело к возникновению термина «x86» как сокращению для семьи процессоров. В последствии процессоры и серии процессоров Intel, которые представляли эту архитектуру, имели совершенно другие имена, например, серии Pentium, Celeron и т.д., но они принадлежали также к этой архитектуре. Кроме компании Intel процессоры на архитектуре x86 также выпускала компания AMD, в частности, это серии процессоров Athlon, Duron и т.д.
    Процессоры 8086 и 8088 были 16-битными, несмотря на 8-битную шину данных в 8088. Регистры в этих процессорах имели разрядность 16 бит, а набор инструкций работал с 16-битными данными. 8086 и 8088 не поддерживали многие функции современных процессоров, например, виртуальную память и уровни защиты. Эти процессоры имели 20 адресных линий, что ограничивало размер используемой память 1 мегабайтом. Но 20-битный адрес не мог поместиться в 16-битный регистр, поэтому для работы с адресами необходимо было использовать несколько сложную систему сегментных регистров и смещений для доступа к полному адресному пространству размером 1 МБ.
    В 1985 году компания Intel выпустила процессор 80386, который был важным шагом вперед в развитии архитектуры x86. Этот процессор был 32-битным. И адреса, регистры и АЛУ также имели разрядность в 32 бита, а инструкции изначально работали с операндами размером до 32 бит. Кроме того, он использовал защищенный режим (protected mode), в котором был реализован многоуровневый механизм привилегий из трех уровней - от 0 до 3. Уровень 0 представлял уровень с максимальными правами и предназначался для ядра операционной системы, тогда как уровень 3 предназначался для прикладных пользовательских программ. Уровни 1 и 2 - промежуточные. Стоит отметить, что операционные системы Windows и Linux до сих пор реализуют только 2 уровня - 0 и 3. 80386 поддерживал память размером 4 ГБ, в которой адреса были 32-битными, а манипуляции с сегментными регистрами и смещениями больше не требовались. Кроме того, была добавлена поддержка выгружаемой виртуальной памяти.
    После этого процессоры данной архитектуры стали 32-битными.
    Архитектура x86 имеет прямой порядок следования байтов (little-endian) что означает, что многобайтовые значения хранятся в памяти с младшим значащим байтом по младшему адресу и старшим значащим байтом по старшему адресу.

    Архитектура Intel x64

    Архитектура х64 изначально представляла расширение процессора x86 и его набора инструкций до 64 бит. Первая специафикация этой архитектуры назвалась AMD64 и была представлена компанией AMD в 2000 году. Первый процессор AMD64, Opteron, был выпущен в 2003 году.
    Компания Intel параллельно развивала собственную 64-разрядную архитектуру, которая называлась IA-64 и которая была несовместима с х86. Результатом развития этой архитектуры стал процессор Itanium, который вышел в 2001 году. Однако затем Intel решили пойти по пути AMD и также стали развивать 64-разрядную архитектуру как расширение для x86 и которая была бы совместима с AMD64, получившую название Intel 64. Первым процессором Intel на 64-разрядной архитектуре - Xeon вышел в 2004 году. В конечном счете эта архитектура стала называться x86-64, отражая эволюцию x86 до 64 бит, и, как правило, для ее названия употребляется сокращение x64.
    Стоит отметить, что первая версия операционной системы Linux, которая поддерживала архитектуру x64, была выпущена в 2001 году, задолго до появления первых процессоров x64. ОС Windows начала поддерживать архитектуру x64 в 2005 году.
    Процессоры, которые реализуют архитектуры AMD64 и Intel 64, в значительной степени совместимы на уровне набора инструкций программ пользовательского режима. Между архитектурами есть несколько различий. Как правило, компиляторы операционных систем и языков программирования управляют этими различиями, что делает их редкой проблемой для разработчиков прикладного программного обеспечения. Разработчики же системного программного обеспечения ядра, драйверов и ассемблерного кода должны учитывать эти различия.
    Основные особенности архитектуры x64:
    • x64 — это совместимое 64-битное расширение 32-битной архитектуры x86, и большинство программ, особенно прикладных приложений, написанных для 32-битной среды, должны выполняться без изменений на 64-битном процессоре.
    • Восемь 32-битных регистров общего назначения x86 расширены до 64 бит в процессорах x64. Префикс имени регистра R указывает на 64-битные регистры. Например, в x64 расширенный регистр x86 EAX называется RAX. Подкомпоненты регистра x86 EAX, AX, AH и AL по-прежнему доступны в x64.
    • Архитектура x64 реализует практически тот же набор инструкций, что и x86. При работе в 64-битном режиме архитектура x64 по умолчанию размер адреса - 64 бита, а размер операнда - 32 бита.
    • Указатель инструкций, RIP, теперь 64-битный. Регистр флагов, RFLAGS, также расширяется до 64 бит, хотя старшие 32 бита зарезервированы. Младшие 32 бита RFLAGS аналогичны EFLAGS в архитектуре x86.
    • Добавлено восемь 64-битных регистров общего назначения с именами от R8 до R15.
    • Добавлена встроенная поддержка для 64-битных целых чисел.
    • Процессоры x64 сохраняют возможность работы в режиме совместимости с x86. Этот режим позволяет использовать 32-разрядные операционные системы и позволяет любому приложению, созданному для x86, работать на процессорах x64. В 32-битном режиме совместимости 64-битные расширения недоступны.
    • Виртуальные адреса в архитектуре x64 имеют ширину 64 бита, теоретически поддерживая адресное пространство размером 16 экзабайт (EB), что эквивалентно 264 байтам. Однако современные процессоры AMD и Intel поддерживают только 48-битное виртуальное адресное пространство. Это ограничение снижает аппаратную сложность процессора, но при этом размер поддерживаемой памяти снижается до 256 терабайт виртуального адресного пространства. Процессоры текущего поколения также поддерживают максимум 48 бит физического адресного пространства. Это теоретически позволяет процессору адресовать 256 ТБ физической оперативной памяти, но современные материнские платы не поддерживают такие размеры DRAM.
    Диалекты ассемблера
    При работе следует определиться с ассемблером, который будет использоваться для сборки программ. Наиболее популярные диалекты ассемблера.
    Microsoft Macro Assembler (MASM)
    Ассемблер Microsoft Macro Assembler или сокращенно MASM является одним из старейших развиваемых ассемблеров (первая версия вышла аж в 1981 году). Его развивает компания Microsoft. MASM доступен в рамках такого инструмента для разработки приложений, как Visual Studio. Преимуществом MASM является то, что MASM использует для своих инструкций синтаксис Intel. Недостатком MASM является наличие официальной поддержки только для ОС Windows.
    Стоит отметить, что также существует неофициальный сайт, посвященный MASM, где можно найти дополнительную информацию по данному ассемблеру - https://www.masm32.com/
    GNU Assembler (GAS)
    Ассемблер GNU Assembler или сокращенно GAS поставляется как компонент набора компиляторов GCC. Поскольку компиляторы GCC довольно распространенны и являются кроссплатформенными, то GAC соответственно также можно использовать на разных платформах. Из недостатков можно отметить, что GAS использует синтаксис, отличный от синтаксиса Intel (а именно синтаксис AT&T). Хотя последние версии GCC включают параметр «-masm», который при значении "-masm=intel" позволяет встраивать код ассемблера с использованием синтаксиса Intel. Эквивалентным параметром для GAS является "-msyntax=intel" или использование директивы ".intel_syntax".
    Netwide Assembler (NASM)
    Netwide Assembler или NASM развивается как opensource-проект и использует синтаксис, который похож на синтаксис Intel. Является кросс-платформенным и работает почти на любой платформе. Официальный сайт проекта - https://www.nasm.us/
    Flat Assembler (FASM)
    Flat Assembler (FASM) является кросс-платформенным и поддерживает основные ОС (Linux, Windows и MacOS). Тоже развивается как проект open source. Используемый синтаксис похож на NASM. Примечателен тем, что написан на самом FASMe и имеет специальную небольшую IDE для написания программ. Официальный сайт проекта - https://flatassembler.net/
    YASM
    YASM - это полностью переработанный ассемблер NASM под «новой» лицензией BSD. YASM позволяет использовать синтаксисы NASN и GAS. Как и NASM, является кросс-платформенным. Официальный сайт - https://yasm.tortall.net/
     
    Последнее редактирование: 28 окт 2026 в 09:15
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Введение в ассемблер Intel x86-64

    Представление данных, биты и байты


    На аппаратном уровне вся информация в компьютере представляет последовательность электрических сигналов. Например, в какой-то определенной ячейке памяти может быть иметься сильное напряжение, или оно может быть очень слабым. Для описания состояния сигнала информатике используется термина бит. По сути бит является наименьшей единицей информации в компьютере. Бит может иметь значение 1 ("есть сигнал", соответствует напряжению от 2 до 5 Вольт) или 0 ("сигнал отсутствует" или "сигнал слабый" — от 0 до 2 Вольт).
    8 бит составляют байт. Байт — это наименьшая "адресуемая единица информации", которую можно прочитать или записать в память, или найти в памяти.
    Один бит может принимать два (=21) значения: 0 и 1. Двумя битами можно передать четыре (=22) значения: 00, 01, 10 и 11. Три бита могут принимать восемь (=23) значений: 000, 001, 010, 011, 100, 101, 110 и 111. Обобщая, группа из N битов может принимать 2N значений. Таким образом, группа из 8 бит или 1 байт может представлять 28, то есть 256 уникальных значений. Вся информация в компьютере представляет последовательность бит.
    Двоичная система
    Поскольку бит может иметь только два значения — 1 и 0, то для записи битов применяют двоичную систему исчисления. Вообще система исчисления представляет способ записи чисел. Например, в повседневной жизни мы пользуемся десятичной системой исчисления. Это значит, что основанием этой системы является число 10, а каждый символ числа может иметь 10 вариантов значений — от 0 до 9. В десятичной системе каждое число можно представить как сумму цифер чисел, умноженных на 10 в степени, соответствующей порядковому номеру цифры в этом числе (нумерация начинается с нуля). Например, стандартное число 123 можно представить следующим образом:
    12310 = 1⋅102 + 2⋅101 + 3⋅100 = 100 + 20 + 3
    Возьмем другое десятичное число - 123,45
    123,4510 = 1⋅102+2⋅101+3⋅100+4⋅10-1+ 5⋅10-2 = 100+20+3+0,4+0,05
    В двоичной системе каждый символ числа может иметь только два значения — 1 и 0, например, число 1101. Чтобы перевести число из двоичной системы в десятичную умножаем значение каждого бита (1 или 0) на число 2 в степени, равной номеру бита (нумерация битов идет от нуля):
    Переведем двоичное число 1101 в десятичную систему
    1⋅23+1⋅22+0⋅21+1⋅20 = 1⋅8+1⋅4+0⋅2+1⋅1 = 8+4+ 0+1 = 13
    Перевод десятичного числа в двоичную систему выглядит несколько сложнее. Стандартный алгоритм преобразования подразумевает деление числа и результатов последующих делений на 2 и помещение остатков от деления в результат. Переведем десятичное число 13 в двоичную систему:
    13 / 2 = 6 // остаток 1 (13 — 6⋅2 = 1)
    6 / 2 = 3 // остаток 0 (6 — 3⋅2 = 0)
    3 / 2 = 1 // остаток 1 (3 — 1⋅2 = 1)
    1 / 2 = 0 // остаток 1 (1 — 0⋅2 = 1)
    Общий алгоритм состоит в последовательном делении числа и результатов деления на 2 и получение остатков, пока не дойдем до 0. Затем выстраиваем остатки в линию в обратном порядке и таким образом формируем двоичное представление числа. Конкретно в данном случае по шагам:
    1. Делим число 13 на 2. Результат деления — 6, остаток от деления — 1 (так как 13—6⋅2 = 1)
    2. Далее делим результат предыдущей операции деления — число 6 на 2. Результат деления — 3, остаток от деления — 0
    3. Делим результат предыдущей операции деления — число 3 на 2. Результат деления — 1, остаток от деления — 1
    4. Делим результат предыдущей операции деления — число 1 на 2. Результат деления — 0, остаток от деления — 1
    5. Последний результат деления равен 0, поэтому завершаем процесс и выстраиваем остатки от операций делений, начиная с последнего — 1101
    Шестнадцатеричная система
    Если число большое, то запись двоичных чисел может быть довольно длинной и поэтому не очень удобной. Число 23410 в двоичной системе равно 111010102. И для упрощения работы с двоичными числами применяются восьмеричная и шестнадцатеричная системы представления чисел. В восьмеричной системе счисления двоичные числа делят на группы по 3 разряда (триады). При 3 разрядах в группе количество возможных значений равно 23 или 8.
    В шестнадцатеричной системе счисления двоичные числа разделены на группы по 4 разряда. При 4 разрядах в группе количество возможных значений равно 24 или 16. Первым 10 из этих цифр присваиваются значения от 0 до 9, последним 6 — буквы A-F:
    Двоичное число 11101010 можно представить более компактно, разбив его на две 4-битные группы (1110 и 1010) и записав их в виде шестнадцатеричных цифр EA.
    Алгоритм перевода из шестнадцатеричной системы в десятичную и обратно тот же, что и для двоичной, только вместо 2 используется число 16. Шестнадцатеричное число E6 можно представить следующим образом: E6 = E⋅161+6⋅160 = 14⋅16+ 6 = 23010 (десятичная система) = 1110 01102 (двоичная система)
    А чтобы получить из 10-тичного числа 16-ричное, делим число на 16 и получаем остатки:
    Переведем десятичное число 230 в шестнадцатеричную систему
    230 / 16 = 14 // остаток 6 (230 - 16 ⋅ 14 = 230 - 224 = 6)
    14 / 16 = 0 // остаток 14 или E в 16-й системе
    0E6h // результат
    Чтобы указать, что число шестнадцатеричное, после числа ставятся постфиксы H или h, если шестнадцатеричное число начинается с буквы, то перед числом ставят 0 (ноль). 4 бита, которые соответствуют одной шестнадцатеричной цифре, называется nibble, полубайт или тетрада.
    При работе с разными системами счисления легко запутаться. Например, какую систему в реальности представляет число 1010? Оно может равным образом представлять и десятичную, и двоичную, и шестнадцатеричную. И чтобы указать, что число относится к определенной системе счисления, используют различные постфиксы. Для указания, что число является двоичным, после числа ставится постфикс B, b, Y или y:
    Код (Text):
    1. 1010b — двоичное число (в десятичной системе равно 10, а в шестнадцатеричной - 0Ah)
    Чтобы указать, что число является шестнадцатеричным, после числа ставится постфикс H или h:
    Код (Text):
    1. 1010h — шестнадцатеричное число (в десятичной системе равно 4112, а в двоичной — 1000000010000)
    Представление отрицательных чисел
    Для представления отрицательных чисел обычно применяется two’s complement (дополнение до 2). Чтобы получить отрицательный аналог числа n нужно найти такое число n которое при сложении с n даст нам ноль n — n =0. Например, для получения —1 надо произвести операцию 0 — 1 = —1. С точки зрения архитектуры компьютера в качестве 0 выступает число 2N. В данном случае степень N представляет количество битов в числе.
    Например, наше число состоит из 8 бит (1 байт), наподобие 0000 0001 (1 в десятичной системе). И мы хотим получить число —1. Для этого выполняем следующую операцию:
    28 — 1 = 256 — 1 = 255
    Но 255 - это в десятичной системе. А как это будет выглядеть в двоичной системе:
    28 — 1 = 10000 0000 — 0000 0001 = 1111 1111
    Таким образом, для 8 битное отрицательное число -1 в двоичной системе будет представлять 1111 1111
    Аналогичная операция в шестнадцатеричной системе:
    162 — 1 = 100h — 1 = 0FFh
    Если же мы выполним обратную операцию — к 1111 1111 прибавим изначальное число 0000 0001, то мы получим степень двойки. Поэтому подобное представление отрицательных чисел и называется дополнение до 2-х.
    Простой способ получить из положительного числа отрицательного и наоборот (то есть фактически умножение на —1) заключается в том, чтобы инвертировать биты — биты 0 поменять на 1, а 1 на 0, и затем прибавить 1. Например, получим число —3. Для этого сначала возьмем двоичное представление числа 3:
    310 = 0000 00112
    Инвертируем биты
    ~0000 0011 = 1111 1100
    И прибавим 1
    1111 1100 + 1 = 1111 1101
    Таким образом, число 1111 1101 является двоичным представлением числа —3, что в шестнадцатеричной системе аналогично 0FDh
    Другой пример, число 1 в двоичной системе равно 0000001b. Чтобы получить число -1, сначала инвертируем биты:
    Код (ASM):
    1. ~0000 0001 = 1111 1110
    Далее прибавляем 1:
    Код (ASM):
    1. 1111 1110 + 1 = 1111 1111
    То есть число —1 в двоичной системе равно 1111 1111 или 0FFh (в шестнадцатеричной системе)
    Подобным образом можно получить обратно число 1:
    Код (ASM):
    1. ~1111 1111 = 0000 0000
    2. 0000 0000  + 1 = 0000 0001
    Соответственно, в зависимости от того, какое именно это число - положительное или отрицательно, интерпретировать это число можно по разному. Например, если число 1111 1111 рассматривается как положительное, то в десятичной системе оно равно 255. Если же оно рассматривается как отрицательное, то в десятичной системе оно равно -1.
    Таким образом, 8-битные числа со знаком охватывают диапазон от —128 до 127, а 8-битные числа без знака — от 0 до 255.
    Инструкции
    Основу программы на ассемблере составляют инструкции - некоторые действия, например, сложение двух значений, помещение в регистр значения и т.д. При выполнении программы процессор выбирает и интерпретирует каждую инструкцию. Как и все данные, каждая инструкция, каждое действие в программе представляет последовательность битов. Каждой инструкции сопоставляется определенный машинный двоичный код, который также называется кодом инструкции или кодом операции (опкод, opcode).
    Код операции — это один байт, определяющий основную операцию инструкции. Например, инструкция, которая копирует в регистр RAX число 1, имеет опкод 0C7h в шестнадцатеричной форме или 11000111b в двоичной форме. В зависимости от инструкции, ее операндов опкод меняется. Например, инструкция, которая копирует в регистр EAX число 1, имеет опкод 0B8h в шестнадцатеричной форме или 10111000 в двоичной форме. К опкодам инструкций следует добавить коды/значения операндов - регистра и чисел.
    Написание машинного кода вручную возможно, но излишне громоздко. На практике вместо опкодов применяются так называемые мнемоники - человекочитаемые названия инструкций. Например, инструкция, которая копирует в регистр некоторое значение, имеет мнемонику mov (от слова "move" - помещать, поместить). А чтобы скопировать в регистр RAX число 1, нам достаточно написано команду
    Код (ASM):
    1. mov rax, 1
    А чтобы скопировать в регистр EAX число 1, нам достаточно написано команду
    Код (ASM):
    1. mov eax, 1
    Это довольно удобнее, чем в бинарной форме вводить команды.
    Программа состоит из набора подобных инструкций. Процессор запускает программы через цикл выборки-выполнения (fetch-execute cycle). Компьютер считывает по одной инструкции за раз. Для этого процессор обращается к специальному регистру - указателю команд (или регистр IP), который также называется программным счетчиком (или PC) и который хранит адрес инструкции для выполнения. По сути, компьютер выполняет бесконечный цикл следующих операций:
    1. Считывает инструкцию с адреса памяти, указанного указателем инструкции - регистром IP/PC
    2. Декодирует инструкцию (т. е. выясняет, что означает инструкция)
    3. Перемещает указатель инструкций (регистр IP/PC) к следующей инструкции
    4. Выполняет указанную инструкцию
     
    Последнее редактирование: 29 окт 2026 в 13:51
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Введение в ассемблер Intel x86-64

    Регистры процессора


    Ключевую роль в обработке данных в процессоре играют специальные ячейки, известные как регистры. Регистры в процессоре x86-64 можно разделить на четыре категории: регистры общего назначения, специальные регистры для приложений, сегментные регистры и специальные регистры режима ядра. Здесь нас будут интересовать прежде всего регистры общего назначения (general-purpose registers), которые в основном и используются в приложениях на ассемблере.
    Начнем с того, что процессор архитектуры x86 имел восемь 32-битных регистров общего назначения, регистр флагов и указатель инструкций. Регистры общего назначения:
    • EAX (Accumulator): для арифметических операций
    • ECX (Counter): для хранения счетчика цикла
    • EDX (Data): для арифметических операций и операций ввода-вывода
    • EBX (Base): указатель на данные
    • ESP (Stack pointer): указатель на верхушку стека
    • EBP (Base pointer): указатель на базу стека внутри функции
    • ESI (Source index): указатель на источник при операциях с массивом
    • EDI (Destination index): указатель на место назначения в операциях с массивами
    • EIP: указатель адреса следующей инструкции для выполнения
    • EFLAGS: регистр флагов, содержит биты состояния процессора
    Можно получить доступ к частям 32-битных регистров с меньшей разрядностью. Например, младшие 16 бит 32-битного регистра EAX обозначаются как AX. К регистру AX можно обращаться как к отдельным байтам, используя имена AH (старший байт) и AL (младший байт).
    [​IMG]
    В архитектуре х64 эти регистры были расширены до 64 бит, а новые расширенные регистры получили имена, которые начинаются с буквы R, например, RAX, RBX и т.д. Кроме того, были добавлены 8 новых 64-битных регистров R8 - R15
    [​IMG]
    Для обращения к 32-, 16- и 8-битной части 64-разрядных регистров используются стандартные для архитектуры x86 имена регистров. Для доступа к подрегистрам новых 64-битных регистров R8 - R15 применяется соответствующий суффикс:
    • D: для получения младших 32 бит регистра, например, R11D
    • W: для получения младших 16 бит регистра, например, R11W
    • B: для получения младших 8 бит регистра, например, R11B
    Таким образом, в архитектуре x86-64 мы можем использовать следующие регистры общего пользования:
    • Шестнадцать 64-разрядных регистров RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14 и R15
    • Шестнадцать 32-разрядных регистров EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, R8D, R9D, R10D, R11D, R12D, R13D, R14D и R15D
    • Шестнадцать 16-разрядных регистров AX, BX, CX, DX, SI, DI, BP, SP, R8W, R9W, R10W, R11W, R12W, R13W, R14W и R15W
    • Шестнадцать 8-разрядных регистров AL, AH, BL, BH, CL, CH, DL, DH, DIL, SIL, BPL, SPL, R8B, R9B, R10B, R11B, R12B, R13B, R14B и R15B
    Например, запись в часть 64-битного регистра, например в регистр AL, влияет только на биты этой части. В случае AL загрузка 8-битного значения изменяет младшие 8 битов RAX, оставляя остальные 56 бит без изменений.
    Хотя эти регистры и называются общего назначения, но это не значит, что их можно использовать для любых целей. Все регистры x86-64 имеют свое особое назначение, которое ограничивает их использование в определенных контекстах.
    Регистр флагов RFLAGS, содержит биты состояния процессора:
    БитИмяназначение
    0CFФлаг переноса (Carry flag):казывает, был ли при сложении перенос или заимствование при вычитании. Используется в качестве входных данных для инструкций сложения и вычитания.
    2PFФлаг четности: устанавливается, если младшие 8 битов результата содержат четное число единиц.
    4AFФлаг настройки: указывает, был ли при сложении перенос или заимствование при вычитании младших 4 битов.
    6ZFФлаг нуля (Zero flag): устанавливается, если результат операции равен нулю
    7SFФлаг знака (Sign flag): устанавливается, если результат операции отрицательный.
    8TFФлаг прерывания выполнения (Trap flag): используется при одношаговой отладке.
    9IFФлаг разрешения прерывания: установка этого бита разрешает аппаратные прерывания.
    10DFФлаг направления: контролирует направление обработки. Если не установлен, то порядок от самого младшего до самого старшего адреса. Если установлен, то порядок обратный - от самого старшего до самого младшего адреса.
    11OFФлаг переполнения (Overflow flag): если устанавлен, то операция привела к переполнению со знаком.
    12-13IOPLУровень привилегий ввода-вывода (I/O privilege level): уровень привилегий текущего выполняемого потока. IOPL 0 — это режим ядра, а 3 — пользовательский режим.
    14NTФлаг вложенной задачи (Nested task flag): управляет цепочкой прерываний.
    16RFФлаг возобновления (Resume flag): используется для обработки исключений во время отладки.
    17VMФлаг режима виртуальной машины 8086: если установлен, режим совместимости с 8086 активен. Этот режим позволяет запускать некоторые приложения MS-DOS в контексте операционной системы в защищенном режиме.
    18ACФлаг проверки выравнивания (Alignment check flag): если установлен, проверка выравнивания памяти активна. Например, если установлен флаг AC, сохранение 16-битного значения по нечетному адресу вызывает исключение проверки выравнивания. Процессоры x86 могут выполнять невыровненный доступ к памяти, когда этот флаг не установлен, но количество требуемых командных циклов может увеличиться.
    19VIFФлаг виртуального прерывания (Virtual interrupt flag): виртуальная версия флага IF в виртуальном режиме 8086..
    20VIPФлаг ожидания виртуального прерывания: Устанавливается, когда прерывание находится в состоянии ожидания в виртуальном режиме 8086.
    21IDФлаг ID: если этот бит установлен, то поддерживается инструкция cpuid. Эта инструкция возвращает идентификатор процессора и информацию о его функциях.
    Основными являются флаги переноса, нуля, знака и переполнения, которые называют флагами состояния. Как видно, не все биты из 64-х разрядного регистра флагов имеют назначение. Неуказанные биты не используются.
    В дополнение к регистрам общего назначения x86-64 предоставляет регистры специального назначения, в том числе восемь регистров для работы с числами с плавающей точкой, реализованных в модуле x87 с плавающей точкой (floating-point unit или FPU). Intel назвала эти регистры ST0 - ST7. Каждый регистр с плавающей точкой имеет ширину 80 бит. В отличие от регистров общего назначения обычная пользовательская программа не может получить к ним прямой доступ.
    Также есть шестнадцать 128-битных регистров XMM (XMM0 - XMM15) и набор инструкций SSE/SSE2. Каждый регистр можно настроить как четыре 32-битных регистра с плавающей точкой; два 64-битных регистра двойной точности с плавающей точкой; или шестнадцать 8-битных, восемь 16-битных, четыре 32-битных, два 64-битных или один 128-битный целочисленный регистр. В более поздних вариантах семейства процессоров x86-64 AMD/Intel удвоили размер регистров до 256 бит каждый (переименовав их в YMM0-YMM15), добавив поддержки восьми 32-битных значений с плавающей точкой или четырех 64-битных значений с плавающей точкой двойной точности. (целочисленные операции по-прежнему ограничивались 128 битами).
     
    Последнее редактирование: 28 окт 2026 в 09:46
  5. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Введение в ассемблер Intel x86-64

    Ассемблер MASM. Установка и начало работы


    Для работы с MASM надо установить для Visual Studio инструменты разработки для C/C++. Установщик для среды Visual Studio можно загрузить по следующему адресу: Microsoft Visual Studio 2022. После загрузки программы установщика Visual Studio запустим ее и в окне устанавливаемых опций выберем пункт Разработка классических приложений на C++:
    [​IMG]
    Visual Studio включает как 32-разрядные, так и 64-разрядные версии MASM. 32-раздяная версия представляет файл ml.exe, а 64-разрядная - файл ml64.exe. Точное расположение файлов может варьироваться от версии Visual Studio. Обычно это папка C:\Program Files\Microsoft Visual Studio\ 2022\ Community\ VC\ Tools\ MSVC \14.35.32215 \bin\Hostx64\x64
    [​IMG]
    Для использования MASM64 перейдем к меню Пуск и в списке программ найдем пункт Visual Studio и подпункт x64 Native Tools Command Prompt for VS 2022
    [​IMG]
    Нам должна открыться консоль. Введем в нее ml64, и нам отобразится версия ассемблера и некоторая дополнительная информация:
    **********************************************************************
    ** Visual Studio 2022 Developer Command Prompt v17.5.5
    ** Copyright (c) 2022 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'

    C:\Program Files\Microsoft Visual Studio\2022\Community>ml64
    Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    usage: ML64 [ options ] filelist [ /link linkoptions]
    Run "ML64 /help" or "ML64 /?" for more info

    C:\Program Files\Microsoft Visual Studio\2022\Community>

    Стоит отметить, что запуск этой этой утилиты фактически представляет запуск файла C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat - он по сути вызывает другой файл - vcvarsall.bat, который собственно и настраивает окружение для выполнения ассемблера.
    Структура программы на ассемблере MASM
    Типичная программа на MASM содержит одну или несколько секций, которые определяют, как содержимое программы будет располагаться в памяти. Эти секции начинаются с таких директив MASM, как .code или .data. Данные, используемые в программе, обычно определяются в секции .data. Инструкции ассембра определяются в секции .code.
    В общем случае программа на диалекте MASM имеет следующий вид:
    Код (ASM):
    1. .code
    2. main proc
    3.  
    4.  ret
    5. main endp
    6. end
    Директива .code указывает MASM сгруппировать операторы, следующие за ней, в специальный раздел памяти, зарезервированный для машинных инструкций.
    Ассемблер преобразует каждую машинную инструкцию в последовательность из одного или нескольких байт. CPU интерпретирует эти значения байт как машинные инструкции во время выполнения программы.
    Далее с помощью операторов main proc определяется процедура main. Операторы main endp указывают на конец функции main. Между main proc и main endp располагаются выполняемые инструкции ассемблера. Причем в самом конце функции идет инструкция ret, с помощью которой выполнение возвращается в окружение, в котором была вызвана даннуа процедура.
    В конце файла кода идет инструкция end
    Программа может содержать комментарии, которые располагаются после точки с запятой:
    Код (ASM):
    1. .code  ; начало секции с кодом программы
    2. main proc  ; Функция main
    3.  
    4.  ret ; возвращаемся в вызывающий код
    5.  
    6. main endp ; окончание функции main
    7. end ; конец файла кода
    Комментарии на работу программы никак не влияют и при компиляции не учитываются.
    Компиляция программы
    Компиляция программы на MASM обычно происходит в командной строке. Например, воспользуемся кодом выше и напишем простейшую программу на ассемблере, которая ничего не делает. Для этого определим на жестком диске папку для файлов с исходным кодом. Допустим, она будет называться C:\asm. И в этой папке создадим новый файл, который назовем hello.asm и в котором определим следующий код:
    Код (ASM):
    1. .code  ; начало секции с кодом программы
    2. main PROC  ; Функция main
    3.  
    4.  ret ; возвращаемся в вызывающий код
    5.  
    6. main ENDP
    7. END ; конец файла кода
    Откроем программу x64 Native Tools Command Prompt for VS 2022 и перейдем в ней к папке, где располагается файл hello.asm. Затем выполним следующую команду
    ml64 hello.asm /link /entry:main
    В данном случае вызываем приложение ml64.exe и передаем ему для компиляции файл hello.asm. А флаг /link указывает MASM скомпоновать скомпилированный файл в файл приложения exe, а все дальнейшие параметры (в частности, параметр /entry:main) передаются компоновщику. Параметр /entry:main передает компоновщику имя основной процедуры/функции, с которой начинается выполнение программы. Компоновщик сохраняет этот адрес этой процедуры/функции в специальном месте исполняемого файла, чтобы Windows могла определить начальный адрес основной программы после загрузки исполняемого файла в память.
    В результате ассемблер скомпилирует ряд файлов
    **********************************************************************
    ** Visual Studio 2022 Developer Command Prompt v17.5.5
    ** Copyright (c) 2022 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'

    C:\Program Files\Microsoft Visual Studio\2022\Community>cd c:\asm

    c:\asm>ml64 hello.asm /link /entry:main
    Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    Assembling: hello.asm
    Microsoft (R) Incremental Linker Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    /OUT:hello.exe
    hello.obj
    /entry:main

    c:\asm>

    В итоге в каталоге программы будут сгенерированы объектный файл hello.obj и собственно файл программы - hello.exe.
     
    Последнее редактирование: 28 окт 2026 в 10:04
  6. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Введение в ассемблер Intel x86-64

    Первая программа на MASM

    Создадим первую программу с помощью ассемблера MASM. При создании программы на ассемблере стоит понимать, что это не высокоуровневый язык, где достаточно вызвать одну функцию, которая выполнит всю сложную работу. В ассемблере, чтобы выполнить довольно простые вещи, придется писать довольно много инструкций. И здесь есть разные подходы: мы можем написать весь код только на ассемблере - вариант, который в реальности встречается редко, либо мы можем какие-то части писать на ассемблере, а какие-то на языке высокого уровня, например, на С. В данном случае рассмотрим создание программы целиком на ассемблере, но в дальнейшем посмотрим на второй вариант на примере взаимодействия с языками С и С++.
    Итак, напишем программу на ассемблере, которая выводит строку на консоль. Для этого определим на жестком диске папку для файлов с исходным кодом. Допустим, она будет называться C:\asm. И в этой папке создадим новый файл, который назовем hello.asm и в котором определим следующий код:
    Код (ASM):
    1. includelib kernel32.lib  ; подключаем библиотеку kernel32.lib
    2. ; подключаем функции WriteFile и GetStdHandle
    3. extrn WriteFile: PROC
    4. extrn GetStdHandle: PROC
    5. .code
    6. text byte "Hello METANIT.COM!"  ; выводимая строка
    7. main proc
    8.  sub  rsp, 40  ; Для параметров функций WriteFile и GetStdHandle резервируем 40 байт (5 параметров по 8 байт)
    9.  mov  rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    10.  call GetStdHandle ; вызываем функцию GetStdHandle
    11.  mov  rcx, rax  ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли
    12.  lea  rdx, text  ; Второй параметр WriteFile - загружаем указатель на строку в регистр RDX
    13.  mov  r8d, 18  ; Третий параметр WriteFile - длина строки для записи в регистре R8D
    14.  xor  r9, r9  ; Четвертый параметр WriteFile - адрес для получения записанных байтов
    15.  mov  qword ptr [rsp + 32], 0  ; Пятый параметр WriteFile
    16.  call WriteFile ; вызываем функцию WriteFile
    17.  add  rsp, 40
    18.  ret
    19. main endp
    20. end
    Откроем программу x64 Native Tools Command Prompt for VS 2022 и перейдем в ней к папке, где располагается файл hello.asm. Затем выполним следующую команду
    ml64 hello.asm /link /entry:main

    В результате ассемблер скомпилирует ряд файлов
    **********************************************************************
    ** Visual Studio 2022 Developer Command Prompt v17.5.5
    ** Copyright (c) 2022 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'

    C:\Program Files\Microsoft Visual Studio\2022\Community>cd c:\asm

    c:\asm>ml64 hello.asm /link /entry:main
    Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    Assembling: hello.asm
    Microsoft (R) Incremental Linker Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    /OUT:hello.exe
    hello.obj
    /entry:main

    c:\asm>

    В итоге в каталоге программы будут сгенерированы объектный файл hello.obj и собственно файл программы - hello.exe. Запустим этот файл, и консоль выведет строку:
    c:\asm>hello
    Hello METANIT.COM!
    c:\asm>

    Вкратце разберем программу. Вначале идет инструкция
    Код (ASM):
    1. includelib kernel32.lib
    Эта инструкция подключает библиотеку kernel32.lib. Это библиотека Windows включает нативные функции GetStdHandle и WriteFile, которые использует программа для вывода строки на консоль. Установка Visual Studio включает этот файл, а файл vcvars64.bat при запуске должным образом установить пути к нему, чтобы компоновщик мог его найти при сборке файла программы смог бы его найти.
    Чтобы воспользоваться функциями подключаем их с помощью оператора extrn
    Код (ASM):
    1. extrn WriteFile: PROC
    2. extrn GetStdHandle: PROC
    С помощью директивы .code открываем секцию кода и затем определяем используемые в программе данные - выводимую строку
    Код (ASM):
    1. .code
    2. text byte "Hello METANIT.COM!"  ; выводимая строка
    Строка называется text, а каждый ее элемент (символ) имеет тип byte (8-битное число).
    Далее идут собственно выполняемые программой действия. Они помещаются между инструкциями main proc и main endp
    Код (ASM):
    1. main proc
    2.  ; действия программы
    3. main endp
    Далее нам надо должным образом настроить стек. В частности, резервируем в стеке 40 байт для параметров функций GetStdHandle и WriteFile и при этом учитываем выравнивание стека по 16-байтной границе. Указатель на верхушку стека хранится в регистре rsp. Поэтому вычитаем с помощью инструкции sub из значения в регистре rsp 40 байт
    Код (ASM):
    1. sub rsp, 40
    Почему 40? Прежде всего при вызове функций WinAPI (как в данном случае функций GetStdHandle и WriteFile) необходимо зарезервировать в стеке как минимум 32 байта - так называемое "shadow storage" (теневое хранилище). Далее нам надо учитывать количество параметров функции. Пеовые 4 параметра функций передаются через регистры, а параметры начиная с 5-го передаются через стек. Соответственно для 5-го и последующих параметров надо выделить в стеке область. Для каждого параметра вне зависимости от его размера выделяется 8 байт. Функция WriteFile как раз принимает 5 параметров, поэтому для нее надо выделить дополнительные 8 байт в стеке. Поэтому получаем 32 байта + 8 байт (5-й параметр WriteLine) = 40 байт. Количество параметров смотрим по функции с наибольшим количеством параметров. Третий момент - нам надо учитывать, что перед вызовом функций WinAPI стек имел выравнивание по 16 байтовой границе, то есть значение в RSP должно быть кратно 16. По умолчанию при вызове функции в стек помещается адрес возврата функии размером 8 байт. Поэтому наши 40 байт + 8 байт (адрес возврата из функции) дадут 48 байт - число кратное 16.
    Вначале нам надо использовать встроенную функцию GetStdHandle(), которая позволяет получить дескриптор на устройство ввода-вывода. Она имеет следующее определение на C:
    Код (ASM):
    1. HANDLE WINAPI GetStdHandle(
    2.  _In_ DWORD nStdHandle
    3. );
    Функция GetStdHandle() получает числовой код устройства, с которым мы хотим взаимодействовать. В нашем случае нам надо получить устройство стандартного вывода (для вывода строки), которым по умолчанию является консоль. Для обращения к консоли надо передать число -11, которое надо поместить в регистр rcx:
    Код (ASM):
    1. mov  rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    После установки параметра этой функции вызываем ее с помощью инструкции call:
    Код (ASM):
    1. call GetStdHandle
    В результате выполнения функция GetStdHandle возвращает дескриптор - объект, через который мы можем взаимодействовать с консолью. Этот дескриптор помещается в регистр rax. Получив этот дескриптор, используем его для вывода на консоль строки с помощью функции WriteFile. Для справки ее определение на С++
    Код (ASM):
    1. BOOL WriteFile(
    2.  [in]  HANDLE  hFile,
    3.  [in]  LPCVOID  lpBuffer,
    4.  [in]  DWORD  nNumberOfBytesToWrite,
    5.  [out, optional]  LPDWORD  lpNumberOfBytesWritten,
    6.  [in, out, optional] LPOVERLAPPED lpOverlapped
    7. );
    Вызов функции GetStdHandle помещает в регистр rax дескриптор консоли. И для вывода строкии на консоль с помощью функции WriteFile нам надо поместить этот дескриптор в регистр rcx
    Код (ASM):
    1. mov rcx, rax
    Затем с помощью инструкции lea загружаем в регистр rdx адрес выводимой строки
    Код (ASM):
    1. lea  rdx, text
    Далее в регистр r8d помещаем длину выводимой строки в байтах - в данном случае это 18 байт:
    Код (ASM):
    1. mov  r8d, 18
    Затем в регистре r9 устанавливаем адрес четвертого параметра функции WriteFile:
    Код (ASM):
    1. xor  r9, r9
    В данном случае нам не нужно количество считанных байтов, и с помощью инструкции xor обнуляем значение регистра r9.
    Последний - пятый параметр функции WriteFile должен иметь значение NULL, по сути 0. Поэтому устанавливаем для него значение 0, смещаясь в стеке вперед на 32 байта (4 параметра * 8):
    Код (ASM):
    1. mov qword ptr [rsp + 32], 0
    Инструкция mov помещает значение в определенное место. Здесь в качестве значения служит число 0. А место определяется выражением qword ptr [rsp + 32]. qword ptr указывает, что этот операнд описывает адрес размером в четыре слова, что означает 8 байтов (слово имеет длину 2 байта). ptr указывает, что значение операнда следует рассматривать как адрес. То есть число 0 представляет 8-байтное значение и помещается по адресу rsp + 32.
    И далее собственно вызываем функцию WriteFile:
    Код (ASM):
    1. call WriteFile
    Этот вызов должен привести к выводу строки на консоль. После этого восстанавливаем значение верхушки стека. Для этого с помощью инструкции add прибавляем к значению в регстре rsp ранее отнятые 40 байт:
    Код (ASM):
    1. add rsp, 40
    И с помощью инструкции ret выходим из программы.
     
    Последнее редактирование: 28 окт 2026 в 10:27
  7. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Определение и типы данных. Секция .data

    Ассемблер позволяет определять в программе объекты, которые хранят некоторые данные. Использование переменных значительно упрощает использование данных в программы. Объявления данных имеют следующую форму:
    Код (ASM):
    1. label directive value
    label представляет название переменной, которая может представлять любой валидный идентификатор. Например, в MASM идентификаторы могут начинаться со символа доллара ($), подчеркивания (_) или алфавитного символа, после которых может следовать произвольное количество алфавитно-цифровых символов, символа доллара или подчеркивания. Идентификатор не может состоять из одного символа $ сам по себе (это имеет особое значение для MASM).
    После названия переменной идет directive - директива, которая устанавливает тип данных. Это может быть одна из следующих директив:
    • byte (или db): 8-битное целое число без знака
    • sbyte: 8-битное целое число со знаком
    • word (или dw): 16-битное целое число без знака (или слово)
    • sword: 16-битное целое число со знаком
    • dword (или dd): 32-битное целое число без знака (или двойное слово)
    • sdword: 32-битное целое число со знаком
    • qword (или dq): 64-битное целое число без знака (или четверное слово)
    • sqword: 64-битное целое число со знаком
    • tbyte (или dt): 80-битное целое число без знака
    • oword: 128-битное целое число без знака (восьмерное слово)
    • real4: число с плавающей точкой одинарной точности (32 бита)
    • real8: число с плавающей точкой двойной точности (64 бита)
    • real10: число с плавающей точкой расширенной точности (80 бит)
    • xmmword: вектор размером 128 байт, применяется в операциях SIMD для работы с регистрами XMM
    • ymmword: вектор размером 256 байт, применяется в операциях SIMD для работы с регистрами YMM
    • zmmword: вектор размером 512 байт, применяется в операциях SIMD для работы с регистрами ZMM
    После директивы данных идет собственно значение. Например:
    Код (ASM):
    1. number byte 22
    Здесь определена переменная number, которая имеет тип byte и которая равна 22. Можно определить набор данных:
    Код (ASM):
    1. nums byte 1, 2, 3, 4, 5, 10, 0
    Здесь переменная nums представляет набор значений byte.
    С каждой переменной MASM будет ассоциировать некоторый свободный участок памяти. Например, переменная nums имеет тип byte и занимает 7 байт, соответственно MASM найдет в памяти свободные 7 байт и ассоциирует с этой переменной.
    Где определяются данные? В программе на ассемблере можно опредлять данные в различных секциях. Например, в секции .code:
    Код (ASM):
    1. .code
    2. i32 dword 123  ; определеяем объект i32
    3. main proc
    4.  mov eax, i32  ; помещаем число i32 в регистр eax
    5.  ret
    6. main endp
    7. end
    Однако секция .code больше подходит для инструкций, которые выполняют некоторые действия. Кроме того, по умолчанию, когда MASM компонует программу, он сообщает системе, что программа может выполнять инструкции и считывать данные из секции .code, но не может записывать данные в эту секцию. Поэтому операционная система сгенерирует общую ошибку защиты (general protection fault), если мы попытаемся сохранить какие-либо данные в секции кода. Поэтому для определения данных больше подходят другие секции.
    Секция .data
    Переменные определяются в секции данных, которая задается с помощью директивы .data. Эта директива сообщает MASM, что дальше (до следующей директивы, которая определяет секцию, например, .code) идут объявления данных.
    Код (ASM):
    1. .data
    2. i32 dword 5  ; определяем переменную i32
    3. .code
    4. main proc
    5.  mov eax, i32  ; помещаем значение переменной в регистр EAX
    6.  ret
    7. main endp
    8. end
    Если секция .data содержит несколько переменных, то MASM с каждой из этих переменной ассоциирует некоторый участок памяти. Причем в памяти все переменные будут расположены друг за другом. Например, возьмем следующую секцию данных
    Код (ASM):
    1. .data
    2.  i8 byte 1
    3.  i16 word 2
    4.  i32 dword 3
    5.  i64 qword 4
    Для переменной i8 выделяется 1 байт, для i16 - 2 байта, для i32 - 4 байта и для i64 - 8 байт. В итоге для секции .data будет выделен блок памяти, который занимает не менее 15 байт, где переменные будут располагаться следующим образом:
    [​IMG]
    Допустим, для переменной i8 выделен 1 байт по адресу 0x100, тогда i16 располагается по адресу 0x101, а i32 - по адресу 0x103.
    С помощью оператора dup можно определить повторяющийся набор данных:
    Код (ASM):
    1. variable type size dup (value)
    Вначале также указывается имя переменной, затем тип. Далее идет размер - сколько элементов указанного типа будет содержать переменная. В конце в скобках указывается значение, которое получит каждый элемент. Например:
    Код (ASM):
    1. numbers byte 5 dup (0)
    Здесь переменная numbers состоит из 5 байт, каждый из которых равен 0.
    Также можно определить все элементы по отдельности:
    Код (ASM):
    1. numbers byte 1, 2, 3, 4, 5
    Здесь опять же numbers состоит из 5 байт.
    При определении набора данных отдельные значения можно переносить на другую строку:
    Код (ASM):
    1. numbers byte 1, 2, 3, 4
    2.  5, 6, 7, 8
    Здесь numbers состоит из 8 байт. Подобным образом легко представлять двухмерные массивы.
    Если мы хотим определить переменную, но не хотим инициализировать ее никаким значением, то вместо конкретного значения указывается знак вопроса:
    Код (ASM):
    1. .data
    2. i64 qword ?
    3. text byte 5 dup (?)
    Здесь переменная i64 хранит произвольное значение (обычно это 0), а переменная text - набор произвольных значений (5 нулевых байтов).
    Секция .data?
    Секция .data? применяется для определения неинициализированных значений:
    Код (ASM):
    1. .data?
    2. i64 qword ?
    3. text byte 5 dup (?)
    Секция .const
    Секция .const применяется для определения таких значений, которые нельзя изменить и которые доступны только для чтения:
    Код (ASM):
    1. .const
    2.  c32 dword 22  ; константа - ее нельзя изменить
    3. .code
    4. main proc
    5.  mov eax, c32  ; помещаем значение константы в регистр EAX
    6.  ret
    7. main endp
    8. end
    Стоит отметить, что эти секции могут идти в произвольном порядке и могут определяться по нескольку раз в одном и том же файле программы:
    Код (ASM):
    1. .data
    2.  n1 dword 1
    3. .const
    4.  c1 dword 11
    5. .data
    6.  n2 dword 2
    7. .const
    8.  c2 dword 12
    9. .code
    10. main proc
    11.  mov eax, c2
    12.  ret
    13. main endp
    14. end
    typedef. Определение псевдонимов
    Директива typedef применяется для определения псевдонимов:
    Код (ASM):
    1. int32 typedef sdword
    2. long typedef sqword
    3. int16 typedef sword
    4. .data
    5.  n1 int32 32
    6.  n2 long 64
    7.  n3 int16 16
    8. .code
    9. main proc
    10.  mov eax, n1
    11.  ret
    12. main endp
    13. end
    В данном случае вместо типа sdword можно использовать его псевдоним int32, а вместо типов sqword и sword - псевдонимы long и int16. Стоит учитывать, что некоторые идентификаторы являются зарезервированными словами, например, int, поэтому их нельзя использовать в качестве псевдонимов.
    Представление чисел
    Ассемблер позволяет использовать числа в различных системах исчисления. По умолчанию все числа рассматриваются как числа десятичной системы. Чтобы указать, что число преставляет двоичную систему, после числа указывается суффикс b. А для индикации числа в шестнадцатиричной системе применяется суффикс h:
    Код (ASM):
    1. .data
    2. i64 qword 80h  ; число в 16-й системе - 128
    3. i8 byte 00001011b  ; число в двоичной системе - 11
    Здесь переменная i64 инициализирована число в шестнадцатиричной системе - 80h. В десятичной системе это 128. А переменная i8 представляет число в двоичной системе, которое в десятичной равно 11.
    Если щестнадцатеричное число начинается на алфавитный символ (A-F), то перед ним указывается 0:
    Код (ASM):
    1. .data
    2.  n dword 0FFh  ; n = 255
     
    Последнее редактирование: 28 окт 2026 в 13:05
  8. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Инструкция mov

    Инструкция mov является одной из самых используемых в ассемблере. По разным оценкам, в программе от 25% до 40% всех инструкций составляет инструкция mov. Она копирует данные из одного места в другое и имеет следующий синтаксис:
    Код (ASM):
    1. mov destination, source
    Инструкция принимает два операнда. Первый операнд - destination представляет расположение, куда надо поместить данные. В качестве такого места может выступать регистр процессора или адрес в памяти. Второй операнд - source указывает на источник данных, в качестве которого может выступать регистр процессора, адрес в памяти или непосредственный операнд - число. То инструкция mov копирует данные из source в destination. При этом оба операнда не могут быть одновременно адресами в памяти.
    Например, определим файл hello.asm со следующей программой на ассемблере:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 22
    4.  ret
    5. main endp
    6. end
    Здесь в регистр rax помещается непосредственный операнд - число 22. Все операнды, которые представляют числовые или символьные литералы (как в данном случае числовой литерал 22), называются непосредственными операндами (immediate operand).
    Проверим, что после выполнения этой программы в регистре rax действительно 22. Вывод содержимого регистра на консоль требует ряда других инструкций. Но я не зря использовал именно регистр rax. Дело в том, что при выполнении программы в регистр rax помещается код статуса выполнения программы. Обычно считается, что если этот регистр содержит 0, то программа успешно завершила свое выполнение. Другое число обычно означает код ошибки. Но в нашем случае мы можем использовать этот регистр и число в нем для проверки выполнения инструкций ассемблера. (Также можно помещать код статуса и в младшие части rax - регистры eax и al при условии, что остальная часть регистра rax содержит 0)
    Так, вначале скомпилируем файл командой
    ml64 hello.asm /link /entry:main

    Далее выполним скомпилированное приложение, введя его имя
    hello

    И для проверки результата в регистре rax после выполнения скомпилированного приложения выполним команду
    echo %ERRORLEVEL%

    Эта команда отображает код ошибки. Полный вывод:
    c:\asm>ml64 hello.asm /link /entry:main
    Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    Assembling: hello.asm
    Microsoft (R) Incremental Linker Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    /OUT:hello.exe
    hello.obj
    /entry:main

    c:\asm>hello

    c:\asm>echo %ERRORLEVEL%
    22

    c:\asm>

    Оба операнда инструкции mov должны быть одинакового размера. То есть можно поместить значение из 8-битного регистра в другой 8-битный регистр или 8-битную переменную или наоборот. Подобным образом можно копировать данные между 16-битными или между 32-битными или между 64-битными операндами, но использовать в инструкции операнды разных размеров нельзя. Константы и непосредственные операнды могут быть меньшего размера, чем регистр, в который они помещаются. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 5  ; помещаем в регистр rdx число 5
    4.  mov rax, rdx  ; помещаем в регистр rax значение из регистра rdx
    5.  ret
    6. main endp
    7. end
    Здесь вначале помещаем в регистр rdx число 5. Регистр rdx - 64-разрядный, поэтому в него можно поместить любой целочисленной литерал размером не более 64 бит. В реальности для числа 5 достаточно 8-битного регистра. И в данном случае помещаем число просто будет расширено до 64 бит.
    Далее число из регистра rdx (то есть число 5) помещается в регистр rax. Здесь у нас должно быть соответствие по разрядности регистров. Поскольку и rdx, и rax - 64-разрядные регистры, то проблемы не будет.
    То же самое касается и переменных. Например:
    Код (ASM):
    1. .data
    2.  i32 dword 4
    3. .code
    4. main proc
    5.  mov eax, i32  ; помещаем в регистр eax значение переменной i32
    6.  ret
    7. main endp
    8. end
    Здесь в секции .data определена переменная i32, которая равна 32 битам. Ее значение помещается в 32-битный регистр eax (младшие 32 бита регистра rax).
    Другой пример:
    Код (ASM):
    1. .data
    2.  i8 byte 8
    3. .code
    4. main proc
    5.  mov rax, 0  ; обнуляем регистр rax
    6.  mov al, i8  ; помещаем в регистр al значение переменной i8
    7.  ret
    8. main endp
    9. end
    Здесь значение 8-битной переменной i8 помещается в 8-битный регистр al.
    Однако, если мы смешаем операнды с разными размерами, то при компиляции ассемблер укажет нам на ошибку, как в следующем случае:
    Код (ASM):
    1. .data
    2.  i8 byte 8
    3. .code
    4. main proc
    5.  mov rax, i8  ; помещаем в регистр rax значение переменной i8
    6.  ret
    7. main endp
    8. end
    Здесь значение 8-битной переменной i8 помещается в 64-битный регистр rax. И при компиляции ассемблер отобразит нам ошибку:
    c:\asm>ml64 hello.asm /link /entry:main
    Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1
    Copyright (C) Microsoft Corporation. All rights reserved.

    Assembling: hello.asm
    hello.asm(5) : error A2022:instruction operands must be the same size

    c:\asm>

    Тоже самое касается и констант и числовых литералов, размер которых больше, чем размер регистра:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov al, 512  ; помещаем в регистр al число 512
    4.  ret
    5. main endp
    6. end
    Здесь мы пытаемся поместить в 8-разрядный регистр al число 512, которое занимает больше 8 бит. Соответственно при компиляции мы также получим ошибку.
    mov и расширение со знаком
    Инструкция mov имеет ограничение - операнды должны совпадать по размеру. Но что, если нам надо поместить в 64-разрядный регистр 8-битное значение? Архитектура x86-64 также предоставляет дополнительные расширения инструкции mov - movsx и movzx. movsx выполняет перемещение с расширением знаковым битом, которое копирует данные и расширяет данные по знаку во время их копирования. Синтаксис инструкции movsx аналогичен синтаксису mov:
    Код (ASM):
    1. movsxd dest, source  ; если dest - 64-разрядный операнд и source - 32-разрядный
    2. movsx dest, source  ; для всех остальных комбинаций операндов
    При этом первый операнд - dest должен быть по разрядности не меньше второго операнда и обязательно должен предоставлять регистр. Эта инструкция также не допускает константные значения. Например
    Код (ASM):
    1. .code
    2. main proc
    3.  mov dl, -5
    4.  movsx eax, dl  ; EAX = -5
    5.  ret
    6. main endp
    7. end
    Здесь значение из регистра DL помещается в регистр EAX. Поскольку в DL число отрицательное, то используем расширение со знаком. В результате в EAX сохранится число -5.
    Для беззнакового расширения нулями есть другая инструкция - movzx:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov dl, 5
    4.  movzx eax, dl  ; EAX = 5
    5.  ret
    6. main endp
    7. end
    Если надо расширить 32-битный регистр до 64-битного, можно просто скопировать (32-битный) регистр в самого себя:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0ffffffffffffffffh
    4.  mov eax, eax  ; RAX = 00000000ffffffffh - в RAX остались только младшие 32 бита
    5.  ret
    6. main endp
    7. end
     
    Последнее редактирование: 28 окт 2026 в 14:44
  9. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Сложение и вычитание. add и sub

    Инструкция add выполняет сложение двух операндов, а инструкция sub - вычитание. Они имеют следующий синтаксис:
    Код (ASM):
    1. add destination, source
    2. sub destination, source
    Инструкция add складывает операнды destination и source и результат помещает в операнд destination. Инструкция sub вычитает из destination операнд source и результат вычитания помещает в операнд destination.
    Код (ASM):
    1. ; для add
    2. destination = destination + source
    3. ; для sub
    4. destination = destination - source
    В качестве операндов могут участвовать регистры, переменные, константы и непосредственные операнды. Следует учитывать, что константы и непосредственные операнды могут быть размером не более 32 бита, даже если первый операнд представляет 64-битный регистр.
    Пример сложения:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 6
    4.  mov rax, 10
    5.  add rax, rdx  ; rax = rax + rdx
    6.  ret
    7. main endp
    8. end
    Здесь к числу в регистре rax прибавляем значение из регистра rdx. В итоге в rax будет число 16.
    Пример вычитания:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 6
    4.  mov rax, 10
    5.  sub rax, rdx  ; rax = rax - rdx
    6.  ret
    7. main endp
    8. end
    В данном случае из значения в регистре rax (10) вычитаем значение регистра rdx (6). В итоге в регистре rax будет число 4.
    inc и dec
    Инструкции inc и dec увеличивают и уменьшают соответственно на единицу значение операнда. В качестве операнда может передаваться регистр или переменная:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 3
    4.  mov rax, 8
    5.  inc rdx  ; RDX = RDX + 1
    6.  dec rax  ; RAX = RAX - 1
    7.  ret
    8. main endp
    9. end
    Здесь значение регистра RDX увеличивается на 1 с помощью инструкции inc, а значение регистра RAX уменьшается на 1 с помощью инструкции dec
     
    Последнее редактирование: 28 окт 2026 в 14:51
  10. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Переходы. Инструкция jmp


    Ассемблер позволяет в некоторой точке программы перейти к другой части, то есть выполнить переход. Переходы могут быть условными, то есть выполняться, если только соблюдается некоторое условие, и безусловными - для них не требуется наличие какого-либо условия. В этой статье рассмотрим безусловные переходы.
    Для выполнения безусловного перехода применяется инструкция JMP, которая имеет следующие формы:
    Код (ASM):
    1. jmp метка
    2. jmp регистр
    3. jmp переменная
    В первом варианте после инструкции указывается метка, к которой надо перейти. Названия меток произвольные. В языках высокого уровня этой инструкции соответствует оператор goto. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 11
    4.  jmp m1  ; переходим к метке m1
    5.  mov rdx, 22 ; не выполняется
    6. m1:  ; метка m1
    7.  mov rax, rdx
    8.  ret
    9. main endp
    10. end
    Здесь выполнение процедуры main начинается с копирования в регистр RDX числа 11. Затем выполняется инструкция безусловного перехода jmp, которая осуществляет переход к метке m1
    Код (ASM):
    1. jmp m1
    В итоге последующая инструкция mov, которая помещается в регистр RDX число 22, НЕ выполняется
    Код (ASM):
    1. mov rdx, 22
    А выполняются инструкции, которые помещаются после метки m1, то есть копирование в регистр RAX значения из регистра RDX. Для установки метки после ее имени указывается двоеточие:
    Код (ASM):
    1. m1:  ; метка m1
    2.  mov rax, rdx
    То есть в итоге в регистре RAX будет число 11.
    Другой пример:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 22
    4.  jmp return  ; переходим к метке return
    5.  mov rax, 33  ; не выполняется
    6. return:
    7.  ret
    8. main endp
    9. end
    Здесь после помещения в регистр RAX числа 22 происходит переход к метке return, после которой идет выход из функции с помощью инструкции ret. Поэтому инструкция
    Код (ASM):
    1. mov rax, 33
    выполняться НЕ будет.
    Переход к адресу в регистре
    Другая форма инструкции jmp в качестве операнда принимает регистр и позволяет перейти к инструкции, адрес которой указан в 64-битном регистре общего назначения:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rbx, return
    4.  mov rax, 22
    5.  jmp rbx  ; переходим к адресу из регистра RBX
    6.  mov rax, 33  ; не выполняется
    7. return:
    8.  ret
    9. main endp
    10. end
    Здесь в регистр RBX загружается адрес метки return. Поэтому когда инструкции jmp передается регистр RBX, то будет происходить переход к метке return.
    Переход к адресу в переменной
    Третий вариант безусловных переходов представляет переход к адресу, который хранится в переменной. Подобная переменная должна представлять тип qword, четверичное слово, которое занимает 64 бит, так как 8 байт - это размер адреса в архитектуре x86-64.
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 23
    4.  jmp retPtr  ; переходим к адресу из переменной retPtr
    5.  mov rax, 33  ; не выполняется
    6. return:
    7.  ret
    8. retPtr qword return ; переменная retPtr хранит адрес метки return
    9. main endp
    10. end
    Здесь переменная retPtr хранит адрес метки return. Поэтому при выполнении инструкции jmp retPtr произойдет переход к этой метке. Причем здесь важно подчеркнуть, что переменная определена в той же процедуре, где располагается метка для перехода, после остальных инструкций.
     
    Последнее редактирование: 28 окт 2026 в 15:01
  11. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Флаги состояния и условные переходы


    Также в ассемблере есть ряд инструкций, которые выполняют условные переходы в зависимости от установки битов в регистре FLAGS. В частности, учитываются четыре бита - carry (флаг переноса), overflow (флаг переполнения), sign (флаг знака) и zero (флаг нуля):
    • CF (флаг переноса): устанавливается, если происходит беззнаковое переполнение, то есть при сумме с переносом или вычитании с заимствованием (например, при сумме чисел 0FFh и 01h). Если переполнение не происходит, то флаг не устанавливается.
    • OF (флаг переполнения): установливается, если происходит переполнение со знаком, а именно когда переполняется бит, следующий за старшим знаковым битом. Например, при сумме чисел 7Fh и 01h. В двоичной системе это будет операция 0111 1111 + 0000 0001, результатом которой формально будет число 1000 0000. Таким образом, произошло изменение старшего знакового бита.
    • SF (флаг знака): устанавливается, если старший бит результата установлен. В противном случае флаг знака сброшен (то есть флаг знака отражает состояние старшего бита результата).
    • ZF (флаг нуля): устанавливается, если результат вычисления дает 0. Если результат ненулевой, флаг сброшен
    Эти флаги устанавливаются в результате различных операций. В частности, инструкции add, sub, and, or, xor и not влияют на установку флагов. А mov или lea не влияют.
    Для проверки этих флагов и выполнения условного перехода в ассемблере есть следующие инструкции:
    • jc: выполняет переход к метке, если флаг переноса установлен
    • jnc: выполняет переход к метке, если флаг переноса НЕ установлен
    • jo: выполняет переход к метке, если флаг переполнения установлен
    • jno: выполняет переход к метке, если флаг переполнения не установлен
    • js: выполняет переход к метке, если флаг знака установлен
    • jns: выполняет переход к метке, если флаг знака не установлен
    • jz: выполняет переход к метке, если флаг нуля установлен
    • jnz: выполняет переход к метке, если флаг нуля не установлен
    Все эти инструкции принимают один операнд - метку, к которой выполняется переход. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, 0FFFFFFFFh
    4.  mov ebx, 1
    5.  add eax, ebx
    6.  jc carry_set
    7.  mov eax, 0
    8.  ret
    9. carry_set:
    10.  mov eax, 1
    11.  ret
    12. main endp
    13. end
    Здесь в регистр EAX загружаем число 0FFFFFFFFh, а в регистр EBX - число 1. Затем с помощью инструкции add складываем значения EAX и EBX и результат помещаем в EAX.
    Код (ASM):
    1. mov eax, 0FFFFFFFFh
    2. mov ebx, 1
    3. add eax, ebx
    Если произошел перенос, то устанавливается флаг переноса. С помощью инструкции:
    Код (ASM):
    1. jc carry_set
    Проверям, имеет ли место перенос. И если есть переполнение, и соответственно флаг переноса установлен в 1, то выполняем переход к метке carry_set. Если флаг переноса НЕ установлен, то выполняем последующие инструкции - в регистр EAX помещаем число 0 и выходим из процедуры:
    Код (ASM):
    1. mov eax, 0
    2. ret
    Если флаг установлен, то переходим к метке carry_set и в регистр EAX помещаем число 1 и выходим из процедуры:
    Код (ASM):
    1. carry_set:
    2.  mov eax, 1
    3.  ret
    В данном случае поскольку при сложении чисел 0FFFFFFFFFh и 1 результат будет равен 100000000h, соответственно будет выполняться перенос, то будет происходить переход к метке overflow, а в регистре EAX окажется число 1.
    Подобным образом можно работать с другими инструкциями условного перехода. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 5
    4.  mov rcx, 5
    5.  sub rdx, rcx
    6.  jz zero_set
    7.  mov eax, 2
    8.  ret
    9. zero_set:
    10.  mov eax, 4
    11.  ret
    12. main endp
    13. end
    Здесь помещаем в регистры RDX и RCX некоторые числа и вычитаем из значения регистра RDX число регистра RCX. Если эти числа равны, то результатом будет число ноль, и будет усоановлен флаг нуля ZF. То есть флаг нуля позволяет проверить два числа на равенство при применении вычитания. И в данном случае мы проверяем установку флага нуля, и если он установлен, переходим к метке zero_set.
    Используя подобные инструкции, легко создать циклические конструкции:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0
    4.  mov rcx, 5
    5. main_loop:
    6.  add rax, 2
    7.  sub rcx, 1
    8.  jnz main_loop
    9.  ret
    10. main endp
    11. end
    Здесь регистр выступает в роли счетчика. После метки loop фактически идет цикл. В цикле увеличиваем значение регистра RAX на 2. И затем уменьшаем значение регистра RCX на 1. Далее с помощью инструкции jnz main_loop проверяем флаг нуля. Если флаг нуля НЕ установлен (то есть если в RCX НЕ нулевое значение), то переходим обратно к метке main_loop и повторяем действия. Если вдруг после вычитания в регистре RCX оказался ноль, то выходим из этого цикла и переходим к следующей инструкции - ret, то есть выходим из процедуры.
     
    Последнее редактирование: 28 окт 2026 в 16:28
  12. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Сравнение. Инструкция CMP

    Инструкция CMP (от слова compare - сравнить) позволяет сравнить значения и установить флаги. Благодаря чему мы можем использовать результат сравнения для выполнения условного перехода. Она принимает два операнда:
    Код (ASM):
    1. cmp left_operand, right_operand
    В качестве операндов могут участвовать регистры, переменные, константы и непосредственные операнды. Следует учитывать, оба операнда не могут быть одновременно переменными, и что константы и непосредственные операнды могут быть размером не более 32 бита, даже если первый операнд представляет 64-битный регистр. При этом оба операнда должны представлять целые числа, числа с плавающей точкой НЕ сравниваются.
    Как и инструкция SUB она также вычитает второй операнд из первого операнда и устанавливает флаги кода условия на основе результата вычитания. Отличие состоит в том, что инструкция cmp не сохраняет результат вычитания в первый операнд, поскольку цель инструкции cmp состоит лишь в том, чтобы установить флаги кода условия на основе результата вычитания. То есть фактически посредством инструкции cmp мы сравнивает первый операнд со вторым. Например, возьмем следующую инструкцию
    Код (ASM):
    1. cmp ax, bx
    Как здесь будут установлены флаги:
    • Флаг нуля ZF устанавливается, если AX = BX
    • Флаг знака SF устанавливается, если результат отрицательный. При этом установка флага НЕ означает, что AX обязательно меньше BX. Если AX = 7FFFh (32767) и BX = 0FFFFh (-1), то вычитание AX - BX дает отрицательное значение 8000h (поэтому будет установлен флаг знака). Другой пример: есть беззнаковые операнды AX = 0FFFFh и BX = 1. Здесь AX больше, чем BX, но их разница составляет 0FFFEh, что по-прежнему отрицательно. Поэтому для сравнения двух значений со знаком надо использовать вместе два флага - флаг знака и флаг переполнения:
      • Если ((SF = 0) и (OF = 1)) или ((SF = 1) и (OF = 0)), тогда AX меньше, чем BX
      • Если ((SF = 0) и (OF = 0)) или ((SF = 1) и (OF = 1)), тогда AX больше или равно BX
      Таким образом, если флаги SF и OF не равны, то AX меньше, чем BX. Если эти флаги равны, тогда AX больше или равно BX
    • Флаг переполнения OF устанавливается, если при вычитании произойдет переполнение знакового бита.
    • Флаг переноса CF устанавливается, если при вычитании AX - BX потребуется заимствование
    Рассмотрим несколько ситуаций:
    1. Сравним 0FFFFh (–1) и 0FFFEh (–2). Инструкция вычисляет выражение (–1) – (–2), что равно (+1). Результат положительный и переполнения не произошло, поэтому оба флага SF и OF равны 0. Соответственно левый операнд (-1) больше или равен правому (-2)
    2. Сравним 8000h (–32 768) и 0001h (1). Инструкция вычисляет выражение –32768 – 1, что равно -32769. Поскольку 16-разрядное целое число со знаком не может представлять это значение, значение переносится на 7FFFh (+32 767) и устанавливает флаг переполнения OF. Результат положительный, поэтому флаг знака SF сбрасывается. SF и OF не равны, левый операнд (–32768) меньше чем правый (1).
    3. Сравним 7FFFh (32767) и 0FFFFh (–1). Инструкция вычисляет выражение 32767 – (–1), что равно -32768 или 8000h. Поскольку произошло переполнение знака, то устанавливает флаг переполнения OF. И поскольку результат отрицательный, устанавливается флаг знака SF. Оба флага SF и OF равны 1. Соответственно левый операнд (32767) больше или равен правому (-1)
    4. Сравним 0FFFEh (–2) и 0FFFFh (–1). Инструкция вычисляет выражение (–2) – (–1), что равно (-1). Переполнения не произошло, поэтому флаг OF равен 0. И так как результат отрицательный, SF равен 1. Соответственно левый операнд (-2) меньше, чем правый (-1)
    Пример применения:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, 0
    4.  mov ebx, 1
    5.  cmp eax, ebx ; сраниваем значения регистра EAX и регистра EBX
    6.  jc carry_set
    7.  mov eax, 0
    8.  ret
    9. carry_set:
    10.  mov eax, 1
    11.  ret
    12. main endp
    13. end
    Здесь инструкция cmp сравнивает значение из регистра EAX со значением регистра EBX (фактически вычитает из EAX значение EBX). Поскольку по сути выполняется вычитание 0 -1, то происходит вычитание с заимствованием, что приведет к установке флага переноса. Соответственно последующая инструкция jc выполнит переход к метке carry_set.
    Инструкции перехода
    Для упрощения работы с переходами в ассемблере есть ряд инструкций, которые позволяют более точно определить результат сравнения и выполнить переход на определенную метку:
    • je: проверяет условие ZF == 1 и выполняет переход, если оба операнда равны. Фактически эквивалентна инструкции jz
    • jne: проверяет условие ZF == 0 и выполняет переход, если оба операнда НЕ равны. Фактически эквивалентна инструкции jnz
    • ja / jnbe: проверяет одновременно два условия СF == 0 и ZF == 0 (оба условия должны быть истинными). Выполняет переход, если первый операнд больше второго. Оба операнда беззнаковые.
    • jae / jnb: проверяет условие СF == 0 и выполняет переход, если первый операнд больше или равен второму. Оба операнда беззнаковые. Аналогичен инструкции jnc
    • jb / jnae: проверяет условие СF == 1 и выполняет переход, если первый операнд меньше второго. Оба операнда беззнаковые. Аналогичен инструкции jc.
    • jbe / jna: проверяет одновременно два условия СF == 1 и ZF == 1 (достаточно, чтобы выполнялось хотя бы одно из этих условий). Выполняет переход, если первый операнд меньше или равен второму. Оба операнда беззнаковые.
    • jg / jnle: проверяет одновременно два условия SF == OF и ZF == 0 (оба условия должны быть истинными). Выполняет переход, если первый операнд больше второго. Оба операнда со знаком.
    • jge / jnl: проверяет условие SF == OF и выполняет переход, если первый операнд больше или равен второму. Оба операнда со знаком.
    • jl / jnge: проверяет условие SF != OF (флаги SF и OF не должны быть равны) и выполняет переход, если первый операнд меньше второго. Оба операнда со знаком.
    • jle / jng: проверяет одновременно два условия SF != OF и ZF == 1 (достаточно, чтобы выполнялось хотя бы одно из этих условий). Выполняет переход, если первый операнд меньше или равен второму. Оба операнда со знаком.
    В этом списке стоит отметить два момента. Во-первых, инструкции сравнения для беззнаковых чисел и чисел со знаком различаются. Во-вторых, ряд инструкций имеют синонимичные конструкции, которые выполняют одно и то же действие и на бинарном уровне имеют один и тот же код, например, вместо инструкции jb можно использовать jc или jnae.
    Пример применения инструкций:
    Код (ASM):
    1. .data
    2.  n1 byte 22
    3.  n2 byte 33
    4. .code
    5. main proc
    6.  mov al, n1
    7.  cmp al, n2  ; сраниваем значения регистра AL и переменной n2
    8.  jne not_equal  ; если значения не равны, переходим к метке not_equal
    9.  mov eax, 0
    10.  ret
    11. not_equal:
    12.  mov eax, 1
    13.  ret
    14. main endp
    15. end
    В данном случае помещаем в регистр AL значение переменной n1 и затем сравниваем его в переменной n2
    Код (ASM):
    1. mov al, n1
    2. cmp al, n2
    Оба значения представляют тип byte, то есть являются 8-разрядными беззнаковыми числами. С помощью инструкции jne проверяем флаг нуля, и если он НЕ установлен (то есть числа НЕ равны), переходим к метке not_equal:
    Код (ASM):
    1. jne not_equal
     
    Последнее редактирование: 28 окт 2026 в 16:40
  13. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Инструкции условного копирования

    Нередко сравнение двух значений произодится, чтобы в зависимости от результатов сравнения загрузить в регистр некоторое значение или, наоборот, не загружать это значение, если проверка или сравнение не пройдены. Для упрощения подобных действий процессоры x86-64 поддерживают набор инструкций условного копирования. Инструкции перемещения с проверкой флагов:
    • cmovc / cmovb / cmovnae: перемещает, если флаг переноса CF = 1
    • cmovnc / cmovnb / cmovae: перемещает, если флаг переноса CF = 0
    • cmovz / cmove: перемещает, если флаг нуля ZF = 1
    • cmovnz / cmovne: перемещает, если флаг нуля ZF = 0
    • cmovs: перемещает, если флаг знака SF = 1
    • cmovns: перемещает, если флаг знака SF = 0
    • cmovo: перемещает, если флаг переполнения OF = 1
    • cmovno: перемещает, если флаг переполнения OF = 0
    Отдельная группа инструкций предназначена для сравнения с копированием. Здесь есть инструкции для сравнения беззнаковых чисел:
    • cmova: перемещает, если первый операнд больше второго (CF=0, ZF=0)
    • cmovnbe: перемещает, если первый операнд не меньше и не равен второму (CF=0, ZF=0)
    • cmovae / cmovnc / cmovnb: перемещает, если первый операнд больше или равен второму (CF=0)
    • cmovnae / cmovc / cmovb: перемещает, если первый операнд меньше второго (CF=1)
    • cmovbe: перемещает, если первый операнд меньше или равен второму (CF=1 или ZF=1)
    • cmovna: перемещает, если первый операнд не больше второго (CF=1 или ZF=1)
    И инструкции для сравнения чисел со знаком:
    • cmovg: перемещает, если первый операнд больше второго (SF=OF или ZF=0)
    • cmovnle: перемещает, если первый операнд больше второго (SF=OF или ZF=0)
    • cmovge: перемещает, если первый операнд больше или равен второму (SF=OF)
    • cmovnl: перемещает, если первый операнд больше или равен второму (SF=OF)
    • cmovl: перемещает, если первый операнд меньше второго (SF != OF)
    • cmovnge: перемещает, если первый операнд меньше второго (SF != OF)
    • cmovle: перемещает, если первый операнд меньше или равен второму (SF != OF или ZF=1)
    • cmovng: перемещает, если первый операнд меньше или равен второму (SF != OF или ZF=1)
    И две общие инструкции как для чисел со знаком, так и для беззнаковых чисел:
    • cmove: перемещает, если первый операнд равен второму (ZF=1). Аналогичен инструкции cmovz
    • cmovne: перемещает, если первый операнд не равен второму (ZF=0). Аналогичен инструкции cmovnz
    Первый параметр этих инструкций представляет регистр общего назначения (16, 32 или 64-битный). Второй параметр - либо регистр, либо переменная (также 16, 32 или 64-битные)
    Например, возьмем следующую программу:
    Код (ASM):
    1. .data
    2.  n1 dword 0FFFFFFFFh
    3.  n2 dword  1h
    4.  res0 dword 0
    5.  res1 dword 1
    6. .code
    7. main proc
    8.  mov eax, n1
    9.  add eax, n2
    10.  jc carry_set
    11.  mov eax, res0
    12.  ret
    13. carry_set:
    14.  mov eax, res1
    15.  ret
    16. main endp
    17. end
    Здесь складываем 2 числа. И если при сложении устанавливается флаг переноса, то осуществляется переход к метке carry_set, где в регистр EAX помещается 1. С помощью инструкций условного перемещения эту программу можно сократить так:
    Код (ASM):
    1. .data
    2.  n1 dword 0FFFFFFFFh
    3.  n2 dword  1h
    4.  res0 dword 0
    5.  res1 dword 1
    6. .code
    7. main proc
    8.  mov eax, n1
    9.  add eax, n2
    10.  cmovnc eax, res0  ; Если CF = 0
    11.  cmovc eax, res1  ; Если CF = 1
    12.  ret
    13. main endp
    14. end
    Другой пример - поместим в регистр EAX определенное значение в зависимости от результата сравнения:
    Код (ASM):
    1. .data
    2.  n1 dword 22
    3.  n2 dword 22
    4.  res0 dword 0
    5.  res1 dword 1
    6. .code
    7. main proc
    8.  mov eax, n1
    9.  cmp eax, n2
    10.  cmovne eax, res0  ; Если ZF = 0
    11.  cmove eax, res1  ; Если ZF = 1
    12.  ret
    13. main endp
    14. end
    В данном случае если числа n1 и n2 равны, в регистр EAX помещается число 1, иначе помещается число 0.
     
    Последнее редактирование: 28 окт 2026 в 16:43
  14. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Логические операции

    Для манипуляции отдельными битами числа ассемблер имеет ряд логических операторов:and dest, source
    or dest, source
    xor dest, source
    neg dest
    not dest

    Первые три инструкции имеют два операнда. Первый операнд - dest может представлять регистр или переменную. Второй операнд - source может представлять регистр, переменную или константу. Кроме того, операнды должны быть одинакового размера и не могут одновременно быть переменными. Если первый операнд назначения имеет длину 64 бита, а второй операнд является константой, то эта константа должна быть не больше 32 битов (при операции значение автоматически расширяется до 64 бит).
    Инструкции and, or и xor также влияют на регистр флагов. Они всегда сбрасывают флаг переноса CF и переполнения OF. Флаг нуля ZF устанавливается, если результат равен нулю. Флаг знака SF всегда получает значение старшего бита результата.
    AND
    Инструкция AND выполняет поразрядное логическое умножение. Операция логического умножения возвращает 1, если соответствующие разряды обоих операндов равны 1. Например,
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 12  ; помещаем в регистр rax  число 12 - 1100b
    4.  and rax, 6  ; помещаем в регистр rax = rax AND 6 = 1100 AND 0110 = 0100 = 4
    5.  ret
    6. main endp
    7. end
    Здесь в 64-битный регистр RAX помещается число 12, которое в бинарной форме равно 11002. Затем инструкция AND поразрядно умножает его на число 6, которое в бинарной форме равно 01102.
    1100 * 0110 = 0100
    То есть в итоге в регистре RAX будет число 01002 или 4 в десятичной системе.
    В ассемблере также есть инструкция test - она также умножает поразрядно биты обоих операндов, однако первый операнд не изменяется. Ее цель установить состояние флагов, в частности, флаг нуля:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rbx, 12
    4.  test rbx, 6  ; проверяем значение регистра rax
    5.  je zero
    6.  mov rax, 1
    7.  jmp exit
    8. zero:
    9.  mov rax, 0
    10. exit:
    11.  ret
    12. main endp
    13. end
    Здесь мы проверяем значение регистра RBX - если оно равно 0, то будет установлен флаг нуля. При этом значение регистра RBX не меняется.
    OR
    Инструкция OR выполняет поразрядное логическое сложение. Операция логического сложения возвращает 1, если хотя бы один из соответствующих разрядов обоих операндов равен 1. Например,
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 12  ; помещаем в регистр rax  число 12 - 1100
    4.  or rax, 6  ; помещаем в регистр rax = rax OR 6 = 1100 OR 0110 = 1110 = 14
    5.  ret
    6. main endp
    7. end
    Здесь в 64-битный регистр RAX помещается число 12, которое в бинарной форме равно 11002. Затем инструкция OR поразрядно складывает его с числом 6:
    1100 + 0110 = 1110
    То есть в итоге в регистре RAX будет число 11102 или 1410 в десятичной системе.
    XOR
    Инструкция XOR выполняет поразрядную операцию исключающего ИЛИ, при которой возвращается 1, если соответствующие разряды обоих операндов не равны друг другу. если соответствующие разряды обоих операндов равны, то возвращается 0. Например,
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 12  ; помещаем в регистр rax  число 12 - 1100
    4.  xor rax, 6  ; в регистре rax = rax XOR 6 = 1100 XOR 0110 = 1010 = 10
    5.  ret
    6. main endp
    7. end
    В результате в регистре RAX будет число 10102 или 10 в десятичной системе.
    1100 ^ 0110 = 1010
    Стоит отметить, что операция xor нередко применяется для обнуления содержимого регистра, например
    Код (ASM):
    1. xor rax, rax  ; rax = 0
    NOT
    Инструкция NOT выполняет поразрядное отрицание и принимает один параметр:
    Код (ASM):
    1. not dest
    Суть операции: если разряд операнда dest равен 1, то он меняется на 0. И наоборот - если разряд в dest равен 0, то он меняется на 1. Например,
    Код (ASM):
    1. .code
    2. main proc
    3.  xor rax, rax  ; rax = 0
    4.  mov eax, 12  ; помещаем в регистр eax число 12 - 00000000 00000000 00000000 00001100
    5.  not eax  ; eax =NOT(eax)=NOT(12)= 11111111 11111111 11111111 11110011
    6.  ret
    7. main endp
    8. end
    Здесь в 32-битный регистр EAX помещается число 12, которое в бинарной форме равно 11002. Однако поскольку регистр у нас 32-битный, то и число фактически будет 32-разрядное, а все передние биты будут представлять нули:
    00000000 00000000 00000000 00001100
    Поразрядное отрицание или инверсия изменяет значения битов на противоположное (изменяются все 32 бита в регистре). В итоге мы получим число
    11111111 11111111 11111111 11110011
    Что это за число, зависит от контекста. Так в дополнительном коде старший бит рассматривается как знаковый бит - если он равен 1, то число отрицательное. Соответственно инвертированное выше число будет интерпретироваться как -13. Если число рассматривается как число без знака, которое всега положительное, то результат - 4294967283
    NEG
    Кроме обычной инверсии в ассемблере есть арифметическое отрицацие, которое выполняет инструкция NEG. Она также принимает один операнд:
    Код (ASM):
    1. neg dest
    фактически значение операнда будет умножаться на -1. Таким образом, мы сможем получить из положительного числа отрицательное, а из отрицательного - положительное. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, -12
    4.  neg rax  ; rax = -1 * rax = -1 * -12 = 12
    5.  ret
    6. main endp
    7. end
     
    Последнее редактирование: 29 окт 2026 в 16:51
  15. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Сдвиг и вращение


    Инструкции сдвига и вращения также манипулируют отдельными битами числа.
    Сдвиг влево
    Для сдвига влево применяется инструкция shl, которая имеет следующий синтаксис:
    Код (ASM):
    1. shl dest, count
    Первый операнд - dest представляет число, в котором сдвигаются биты. Это может быть либо регистр, либо переменная.
    Второй операнд - count указывается на сколько разрядов сдвигаем влево число из первого операнда. Это может быть либо регистр CL, либо константа в диапазоне от 0 до n, где n на единицу меньше числа битов в первом операнде (например, n = 7 для 8-разрядных операндов, n = 15 для 16-разрядных, n = 31 для 32-разрядных и n = 63 для 64-разрядных операндов).
    Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0  ; обнуляем регистр RAX
    4.  mov al, 5  ; в AL число 5 или 00000101
    5.  shl al, 1  ; сдвигаем число в AL на 1 разряд влево = 00000101 << 1 = 00001010 = 10
    6.  ret
    7. main endp
    8. end
    Здесь в регистр AL помещаем число 5. Поскольку регистр 8-разрядный, то его значение будет равно 00000101 (число 5 в двоичной системе). С помощью инструкции shl сдвигаем число из регистра AL влево на 1 разряд:
    Код (ASM):
    1. shl al, 1
    То есть 1 крайний левый бит числа отбрасывается, а справа добавляется нулевой бит. Таким образом, мы получим
    00000101 << 1 = 00001010 = 10
    С помощью символов << обычно обозначается сдвиг влево. В регистре AL в итоге будет число 00001010, которое в десятичной системе равно 10.
    Другой пример:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0  ; обнуляем регистр RAX
    4.  mov al, 69  ; в AL число 69 или 01000101
    5.  mov cl, 2  ; в CL число 2
    6.  shl al, cl  ; сдвигаем число в AL на 2 разряда влево = 01000101 << 2 = 00010100 = 20
    7.  ret
    8. main endp
    9. end
    Для разнообразия здесь количество сдвигаемых битов - число 2 помещаем в регистр CL. В регистре AL число 69, которое в двоичной системе равно 01000101. Поскольку его сдвигаем на два разряда влево, то отбрасываем 2 крайних левых бита, а справа добавляем два нулевых бита. В итоге получим 00010100, что представляет число 20 в десятичной системе.
    Можно заметить, что при сдвиг на n разрядов влево фактически равнозначен умножению на n. Поэтому операцию сдвига влево можно использовать вместо операции умножения.
    Стоит учитывать, что при сдвиге влево последний отбрасываемый бит копируется в регистр флагов во флаг переноса CF. И при необходимости мы можем инструкциями условного перехода, которые производят переход, если флаг переноса установлен:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov al, 69  ; в AL число 69 или 01000101
    4.  shl al, 2  ; сдвигаем число в AL на 2 разряда влево = 01000101 << 2 = 00010100 = 20
    5.  jc carry_set  ; при сдвиге отброшены 2 левых бита - 01 - флаг CF установлен
    6.  mov eax, 0
    7.  ret
    8. carry_set:
    9.  mov eax, 1
    10.  ret
    11. main endp
    12. end
    Здесь также сдвигаем число 69 или 01000101 влево на 2 разряда. Это ведет к тому, что левые два бита - 01 отбрасываются. Последний из этих битов копируется во флаг переноса CF. Поскольку последний отбрасываемый бит здесь равен 1, то соответственно флаг CF будет установлен. А с помощью инструкции jc мы можем проверить установку этого флага и выполнить переход к метке carry_set, если флаг установлен.
    Кроме флага CF инструкция сдвига также устанавливает и другие флаги состояния. Флаг нуля ZF устанавливается, если в результате сдвига получилось число 0. Флаг знака устанавливается, если старший бит результата равен 1. Флаг переполнения OF устанавливается, если при сдвиге на 1 разряд меняется старший бит (с 0 на 1 или с 1 на 0).
    Логический сдвиг вправо
    Для сдвига вправо предназначена инструкция shr, которая работает во многом аналогично инструкции shl:
    Код (ASM):
    1. shr dest, count
    В качестве первого операнда также передается сдвигаемое числа, а в качестве второго операнда - на сколько разрядов надо сдвинуть число вправо. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0  ; обнуляем регистр rax
    4.  mov al, 69  ; в AL число 69 или 01000101
    5.  shr al, 2  ; сдвигаем число в AL на 2 разряда вправо = 01000101 >> 2 = 00010001 = 17
    6.  ret
    7. main endp
    8. end
    Здесь помещаем в регистр AL число 69 или 01000101 в двоичной системе и сдвигаем его на два разряда вправо. Сдвиг вправо на n разрядов приводит к тому что n правых разрядов числа отбрасываются, а с слева добавляются n нулевых битов. Поэтому при сдвиге числа 01000101 вправо отбрасываем справа 2 бита и слева добавляем 2 нуля:
    01000101 >> 2 = 00010001 = 17
    Фактически сдвиг вправо беззнаковых чисел на N разрядов эквивалентно делению числа на 2*N.
    При сдвиге вправо также устанавливаются флаги состояния. Последний отрасываемый бит копируется во флаг переноса CF. Если результат сдвига равен 0, то также устанавливается флаг нуля ZF. Если при сдвиге вправо на 1 разряд меняется старший бит (с 1 на 0), то также устанавливается флаг переполнения OF. При этом флаг знака SF всегда сбрасывается, так как слева в любом случае добавляется 0.
    Арифметический сдвиг вправо
    Кроме логического сдвига вправо в ассемблере есть арифметически сдвиг вправо, для которого применяется инструкция sar:
    Код (ASM):
    1. sar dest, count
    Она работает во многом аналогично за тем исключением, что сдвигаемое число слево дополняется не нулями, а значением из старшего бита. Это позволяет сохранить значение знакового бита. Например, возьмем сначала стандартный логический сдвиг вправо:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, -32  ; в EAX число -8 или FFFFFFE0h
    4.  shr eax, 4  ; сдвигаем число в EAX на 4 разряда вправо = FFFFFFE0 >> 4 = 0FFFFFFE = 268435454
    5.  ret
    6. main endp
    7. end
    Здесь в регистр eax помещается отрицательное число -32 (FFFFFFE0 в 16-чной системе). Когда логического сдвига сдивает на 4 байта вправо, в итоге получаем 0FFFFFFE или 268435454 в десятичной системе. С точки зрения логического сдвига это нормальный результат. Но например выше мы видели, что логический сдвиг вправо беззнакового числа на N разрядов вправо эквивалентен делению на 2*N. С числами со знаком такого не проходит. И в этом случае мы можем использовать арифметический сдвиг вправо:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, -32  ; в EAX число -8 или FFFFFFE0h
    4.  sar eax, 4  ; сдвигаем число в EAX на 4 разряда вправо = FFFFFFE0 >> 4 = FFFFFFFE = -2
    5.  ret
    6. main endp
    7. end
    При арифметическим сдвиге вправо также устанавливаются флаги состояния. Последний отрасываемый бит копируется во флаг переноса CF. Если результат сдвига равен 0 (например, сдвигается положительное число), то также устанавливается флаг нуля ZF. Но в отличие от операции логического сдвига флаг переполнения OF всегда сброшен (так как знак числа не меняется), а флаг знака SF получает значение старшего бита числа.
    Вращение
    Операции вращения влево и вправо ведут себя так же, как операции сдвига влево и вправо, за тем исключением, что бит, смещенный с одного конца, помещается обратно в другой конец. Для вращения предусмотрены две инструкции: rol (поворот влево) и ror (поворот вправо). Синтаксис этих инструкций аналогичен инструкциям сдвига:
    Код (ASM):
    1. rol dest, count
    2. ror dest, count
    Первый операнд представляет сдвигаемое число (в виде регистра или переменной), а второй операнд - количество сдвигаемых битов.
    Эти инстркции также изменяются флаги состояния. Но в отличие от инструкций сдвига, инструкции поворота не влияют на установку флагов знака или нуля. Последний сдвинутый бит копируется во флаг переноса CF. При 1-битном повороте также изменяется флаг OF: при повороте влево флаг OF получает результат операции XOR (исключающее ИЛИ) двух первых старших битов, а при повороте вправо флаг OF получает результат операции XOR двух старших битов после поворота.
    Пример вращения влево:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0
    4.  mov al, 131  ; в AL число 131 или 10000011
    5.  rol al, 2  ; вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001110 = 14
    6.  ret
    7. main endp
    8. end
    Здесь в регистр AL помещается число 131 или 100000112. Инструкция rol поворачивает это число на разряда влево, поэтому два старших бита помещаются в начало
    10000011 << 2 = 00001110
    Что в десятичной системе будет равно 14.
    Пример операции вращения вправо:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0
    4.  mov al, 131  ; в AL число 131 или 10000011
    5.  ror al, 2  ; вращаем число в AL на 2 разряда вправо = 10000011 >> 2 = 11100000 = 224
    6.  ret
    7. main endp
    8. end
    Здесь вращаем тоже самое число также на 2 разряда только вправо, поэтому два последних бита перейдут в начало, а результат будет равен 224:
    10000011 >> 2 = 11100000
    Дополнительно ассемблер предоставляет инструкции для вращения с использованием флага переноса - rcl (вращение влево с переносом) и rcr (вращение вправо с переносом):
    Код (ASM):
    1. rcl dest, count
    2. rcr dest, count
    При вращении влево смещаемый старший бит числа переходит во флаг переноса, а бит из флага переноса переходит в конец числа. При вращении вправо смещаемый младший бит переходит во флаг переноса, а бит из флага переноса переходит в начало числа.
    [​IMG]
    Пример вращения влево с переносом
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 0
    4.  mov al, 131  ; в AL число 131 или 10000011
    5.  rcl al, 2  ; вращаем число в AL на 2 разряда влево = 10000011 << 2 = 00001101 = 13
    6.  ret
    7. main endp
    8. end
    Здесь число 131 или 100000112 вращается влево на 2 разряда с переносом. Вращение на 2 разряда мы можем разбить на два этапа - по каждому разряду. Поскольку вначале программы флаг переноса не установлен, равен 0, то при вращении первого бита числа нулевой бит из флага переноса перейдет в конец числа, а старщий бит числа - во флаг переноса:
    Перед операцией
    CF = 0
    AL = 10000011
    После операции
    CF = 1
    AL =00000110
    При вращении второго бита повторяются действия:
    Перед операцией
    CF = 1
    AL = 00000110
    После операции
    CF = 0
    AL =00001101
     
    Последнее редактирование: 29 окт 2026 в 03:42
  16. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Умножение. mul и imul

    Инструкции mul и imul умножает два целых числа. imul умножает числа со знаком, а mul - беззнаковые числа. Инструкция imul имеет следующую общую форму:
    Код (ASM):
    1. imul dest, source
    Если инструкции передается два операнда, то она умножает первый операнд на второй и результат присваивает первому операнду:
    Код (ASM):
    1. dest = dest * source
    Первый операнд - всегда регистр. Второй операнд может представлять регистр, переменную или константу. Оба операнда должны совпадать по размеру и могут быть 16-, 32- и 64-разрядными, за исключением констант, которые не могут превышать 32 бит (Если первый операнд 64-разрядный, то константы автоматически расширяются со знаком до 64 бит).
    Пример умножения:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, 3
    4.  imul eax, 5  ; EAX = EAX * 5 = 3 * 5 = 15
    5.  ret
    6. main endp
    7. end
    Также инструкция imul может принимать три операнда:
    Код (ASM):
    1. imul dest, source, constant
    Теперь умножаются второй и третий операнд, а результат помещается в первый операнд:
    Код (ASM):
    1. dest = source * constant
    Первый операнд - по прежнему регистр. Второй операнд - регистр или переменная. А третий операнд - константа. При этом операнды также должны соответствовать по разрядности, и могут быть 16-, 32- и 64-разрядными, за исключением констант, которые опять же не должны превышать 32 бит. Пример:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov edx, 3
    4.  imul eax, edx, 4  ; EAX = EDX * 4 = 3 * 4 = 12
    5.  ret
    6. main endp
    7. end
    Если при умножении происходит переполнение (которое всегда является переполнением со знаком, поскольку imul умножает только целочисленные значения со знаком), то эта инструкция устанавливает флаги переноса CF и переполнения OF. Другие флаги - флаг знака SF и флаг нуля ZF не определены.
    Инструкции mul и imul поддерживают также форму с одним операндом:
    Код (ASM):
    1. mul/imul operand8  ; результат в AX
    2. mul/imul operand16  ; результат в DX:AX
    3. mul/imul operand32  ; результат в EDX:EAX
    4. mul/imul operand64  ; результат в RDX:RAX
    В качестве операнда можно передавать регистр или переменную. Единственный операнд умножается на соответствующий его размеру регистр AL/AX/EAX/RAX. Результат умножения двух n-битных значений может потребовать 2×n бит. Следовательно, если операнд - 8-битное число, то для результата может потребоваться 16 бит. Аналогично, 16-битный операнд дает 32-битный результат и т.д.
    mul/imul operand8AX = AL × operand8
    mul/imul operand16DX:AX = AX × operand16
    mul/imul operand32EDX:EAX = EAX × operand32
    mul/imul operand64RDX:RAX = RAX × operand64
    В AX/EAX/RAX помещается младшая часть результата, а в DX/EDX/RDX - старшая.
    Если результат умножения по количеству бит больше разрядности операндов, то инструкции mul и imul устанавливаются флаг переноса CF и флаг переполнения OF. Пример использования
    Код (ASM):
    1. .code
    2. main proc
    3.  xor rax, rax
    4.  mov dx, 5
    5.  mov ax, 4
    6.  mul dx  ; DX:AX = AX * DX = 4 * 5 = 20
    7.  ret
    8. main endp
    9. end
     
    Последнее редактирование: 29 окт 2026 в 03:48
  17. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Деление. Инструкции div и idiv

    Для деления чисел в архитектуре x86-64 предназначены инструкции div и idiv. idiv делит два числа со знаком, а div - беззнаковые числа. Эти инструкции принимают следующие формы:
    Код (ASM):
    1. div reg8
    2. div reg16
    3. div reg32
    4. div reg64
    5. div mem8
    6. div mem16
    7. div mem32
    8. div mem64
    9. idiv reg8
    10. idiv reg16
    11. idiv reg32
    12. idiv reg64
    13. idiv mem8
    14. idiv mem16
    15. idiv mem32
    16. idiv mem64
    Инструкции принимают в качестве единственного операнда 8-, 16-, 32-, 64-разрядные регистры или 8-, 16-, 32-, 64-разрядные переменные.
    Инструкция div делит два беззнаковых числа. Если операнд 8-разрядный, то div делит регистр AX на операнд, помещая частное в AL, а остаток (по модулю) в AH.
    Если операнд 16-разрядный, то инструкция div делит 32-разрядное число в DX:AX на операнд, помещая частное в AX, а остаток в DX.
    Если операнд 32-разрядный, div делит 64-битное число в EDX:EAX на операнд, помещая частное в EAX, а остаток в EDX.
    И если операнд 64-разрядный, div делит 128-битное число в RDX:RAX на операнд, помещая частное в RAX, а остаток в RDX.
    Инструкция idiv имеет аналогичное действие, только в качестве операндов принимает числа со знаком.
    Пример деления:
    Код (ASM):
    1. .code
    2. main proc
    3.  xor rax, rax
    4.  mov ax, 22  ; 16-разрядный регистр
    5.  mov bl, 5  ; 8-разрядный регистр
    6.  div bl  ; AL =4 (результат), AH = 2 (остаток)
    7.  movzx eax, al
    8.  ret
    9. main endp
    10. end
    Здесь делим 16-разрядное значение (регистр AX) на 8-разрядное значение (регистр BL). После деления в AL помещается результат (в данном случае число 4), а в AH - остаток (здесь число 2).
    Чуть более сложный случай - надо разделить 32-рарядное число на 16-разрядное.
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, 24003h  ; 32-разрядное число
    4.  mov edx, eax  ; копируем данные из EAX и EDX
    5.  shr edx, 16  ; сдвигаем биты вправо, чтобы старшие 16 бит из EAX оказались в DX
    6.  mov bx, 10h  ; 16-разрядный регистр
    7.  div bx  ; AX =1200h (результат), DX = 3 (остаток)
    8.  movzx eax, ax ; EAX = 1200h = 9216
    9.  ret
    10. main endp
    11. end
    Здесь мы имеем 32-разрядное число в EAX. Чтобы его разделить на 16-разрядлное число из BX, копируем EAX в EDX и затем сдвигаем скопированные биты на 16 разрядов вправо, чтобы старшие 16 бит из EAX оказались в DX.
    При этом в x86-64 нельзя просто разделить два числа одинаковой разрядности, например, одно 8-разрядное на другое 8-разрядное. Если знаменатель представляет собой 8-битное значение, числитель должен быть 16-битным значением. Если же нужно разделить одно 8-битное значение без знака на другое, то необходимо дополнить числитель нулями до 16 бит, загрузив числитель в регистр AL, а затем переместив 0 в регистр AH. Отсутствие расширения AL до нуля перед выполнением div может привести к тому, что x86-64 выдаст некорректный результат.
    Если нужно разделить два беззнаковых 16-разрядных числа, то надо расширить регистр AX (который содержит числитель) до регистра DX. Для этого достаточно загрузить 0 в регистр DX.
    Если нужно разделить одно беззнаковое 32-битное значение на другое, перед делением надо расширить регистр EAX нулями до EDX (загрузив 0 в EDX).
    И чтобы разделить одно 64-битное число на другое, перед делением нужно расширить RAX нулями до RDX (поместив 0 в RDX).
    Перед делением целочисленных значений со знаком одинаковой разрядности с помощью idiv необходимо расширить знаком AL в AX (поместив знаковый бит числа в AH), AX в DX (знаковый бит в DX), EAX в EDX (знаковый бит в EDX) или RAX в RDX (знаковый бит в RDX). Для этого можно использовать следующие инструкции cbw, cwd, cdq или cqo
    • cbw: преобразует байт в AL в слово в AX через расширение знаком
    • cwd: преобразует слово в AX в двойное слово в DX:AX через расширение знаком
    • cdq: преобразует двойное слово в EAX в четверное слово в EDX:EAX с помощью расширения знаком
    • cqo: преобразует четверное слово в RAX в восьмеричное слово в RDX:RAX через расширение знаком
    • cwde: преобразует слово в AX в двойное слово в EAX с помощью расширения знаком
    • cdqe: преобразует двойное слово в EAX в четверное слово в RAX с помощью расширения знаком
    Эти инструкции не принимают операндов. Отсутствие расширения знаком может привести к неверным результатам. Например, разделим два 16-разрядных числа со знаком:
    Код (ASM):
    1. .code
    2. main proc
    3.  ; два 16-разрядные числа
    4.  mov ax, -20
    5.  mov bx, 10
    6.  ; заполним DX знаковым битом из AX
    7.  cwd
    8.  idiv bx  ; AX =-2 (результат), DX = 0 (остаток)
    9.  movsx eax, ax ; EAX = -2
    10.  ret
    11. main endp
    12. end
    При делении стоит учитывать еще ряд моментов. Прежде всего, как и в целом в стандартной математике, на ноль делить нельзя. При попытке деления на ноль x86-64 сгенерирует исключение деления.
    Другая проблема заключается в том, что частное может быть слишком большим, чтобы поместиться в регистр RAX, EAX, AX или AL. Например, 16/8-битное деление 8000h/2 дает частное 4000h с остатком 0. 4000h не помещается в 8 бит. Если это произойдет, x86-64 сгенерирует исключение исключение целочисленного переполнения. Поэтому имеет смысл проверять значения перед делением.
    Также стоит отметить, что инструкции div и idiv не влияют на флаги переноса, переполнения, знака и нуля.
     
    Последнее редактирование: 29 окт 2026 в 03:52
  18. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Установка битов по условию. setcc


    Ряд инструкций позволяют установить однобайтовый операнд в 0 или 1 в зависимости от значений в регистре FLAGS. Основные из них:
    • setc: устанавливает биты, если флаг переноса CF=1
    • setnc: устанавливает биты, если флаг переноса CF=0
    • setz: устанавливает биты, если флаг нуля ZF=1
    • setnz: устанавливает биты, если флаг нуля ZF=0
    • sets: устанавливает биты, если флаг знака SF=1
    • setns: устанавливает биты, если флаг знака SF=0
    • seto: устанавливает биты, если флаг переполнения OF=1
    • setno: устанавливает биты, если флаг переполнения OF=0
    Эти инструкции просто проверяют флаги без каких-либо дополнительных действий. Можно, например, использовать данные инструкции для проверки флага переноса после сдвига, поворота или арифметической операции.
    Еще две дополнительные группы инструкций могут применяться после инструкции cmp. Они позволяют узнать, является ли один операнд меньше, равен или больше другого. Одна группа предназначена для беззнакового сравнения, а вторая группа - для сравнения результатов со знаком.
    Для сравнения беззнаковых чисел:
    ИнструкцияУсловиеОписание
    setaCF = 0, ZF = 0Если первый операнд больше второго
    setnbeCF = 0, ZF = 0Если первый операнд не меньше и не равен второму (по сути больше второго - аналог seta)
    setaeCF = 0Если первый операнд больше или равен второму
    setnbCF = 0Если первый операнд не меньше второго (аналог setae)
    setbCF = 1Если первый операнд меньше второго
    setnaeCF = 1Если первый операнд не больше и не равен второму (меньше второго - аналог setb)
    setbeCF = 1 или ZF = 1Если первый операнд меньше или равен второму
    setnaCF = 1 или ZF = 1Если первый операнд не больше второго (аналогичен setbe)
    seteZF = 1Если операнды равны (аналогичен setz)
    setneZF = 0Если операнды не равны (аналогичен setzn)
    Для сравнения чисел со знаком:
    ИнструкцияУсловиеОписание
    setgSF = OF и ZF = 0Если первый операнд больше второго
    setnleSF = OF и ZF = 0Если первый операнд не меньше и не равен второму (по сути больше второго - аналог setп)
    setgeSF = OFЕсли первый операнд больше или равен второму
    setnlSF = OFЕсли первый операнд не меньше второго (аналог setge)
    setlФлаги SF и OF не равныЕсли первый операнд меньше второго
    setngeФлаги SF и OF не равныЕсли первый операнд не больше и не равен второму (меньше второго - аналог setl)
    setleФлаги SF и OF не равны или ZF = 1Если первый операнд меньше или равен второму
    setngФлаги SF и OF не равны или ZF = 1Если первый операнд не больше второго (аналогичен setle)
    seteZF = 1Если операнды равны (аналогичен setz)
    setneZF = 0Если операнды не равны (аналогичен setzn)
    Общий формат инструкций:
    Код (ASM):
    1. setcc операнд8
    В качестве параметра передается 8-разрядный операнд, который может представлять регистр или переменную.
    В ряде языков программирования высокого уровня есть логический тип данных (bool, boolean), который может содержать одно из двух значений - true или false, то есть истинно или ложь. С помощью подобного типа данных можно установить, что какое-то условие верно, истинно или не верно, ложно. И подобные иИнструкции setcc позволяют преобразовывать результат сравнения в подобное логическое значение. Если условие верно, то устанавливаетя 1, если неверно - то 0. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov eax, 0
    4.  mov ecx, 6
    5.  mov edx, 6
    6.  cmp ecx, edx
    7.  sete al
    8.  ret
    9. main endp
    10. end
    Здесь сравниваем значения регистров ECX и EDX. Инструкция
    Код (ASM):
    1. sete al
    Проверяет равенство операндов из инструкции CMP, а по сути проверяет флаг нуля ZF - если он равен 1, то в регистр AL помещается значение 1. Если флаг нуля равен 0, то в регистр AL помещается 0. В данном случае операнды равны, поэтому в AL будет 1.
    В языках высокого уровня это было бы аналогично следующему выражению
    Код (ASM):
    1. bool al = ecx == edx:
    Подобным образом можно устанавливать переменные. Переменная в этом случае должна представлять тип byte
    Код (ASM):
    1. .data
    2.  bool byte ?
    3. .code
    4. main proc
    5.  mov ecx, 6
    6.  mov edx, 6
    7.  cmp ecx, edx
    8.  setne bool
    9.  movzx eax, bool
    10.  ret
    11. main endp
    12. end
    Здесь проверяем на неравенство, поэтому переменная bool будет равна 1, если значение регистров ECX и EDX не равны.
    Подобным образом можно сравнивать более сложные конструкции. Например, возьмем следующий код на си-подобном языке:
    Код (ASM):
    1. bool result = ((a <;= b) && (d == e)):
    Здесь одновременно должно быть a меньше или равно b и d равно e. В этом случае выражение будет истинным. Определим подобное сравнение на ассемблере:
    Код (ASM):
    1. .data
    2.  bool byte ?
    3. .code
    4. main proc
    5.  mov ecx, 6
    6.  mov edx, 16
    7.  cmp ecx, edx
    8.  setle bl  ; ecx <= edx
    9.  
    10.  mov r8d, 5
    11.  mov r9d, 5
    12.  cmp r8d, r9d
    13.  sete bh  ; r8d == r9d
    14.  and bh, bl  ; ecx <= edx && r8d == r9d
    15.  movzx eax, bh
    16.  ret
    17. main endp
    18. end
     
    Последнее редактирование: 29 окт 2026 в 03:59
  19. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Манипуляции битами. BMI

    Расширения BMI1 предоставляют набор инструкций по манипуляции битами. В частности, инструкция bextr позволяет извлечь биты из 32- или 64-битного регистра общего назначения:
    Код (ASM):
    1. bextr regdest, regsrc, regctrl
    2. bextr regdest, varsrc, regctrl
    Все операнды должны быть одинакового размера - 32- или 64-разрядными. В первый операнд, который должен представлять регистр, помещаются извлеченные биты. Второй операнд - источник для извлечения бит, может представлять регистр или переменную. Третий операнд - regctrl должен быть регистром и определяет параметры извлечения битов:
    • Биты с 0 по 7 в regctrl определяют начальную позицию извлекаемого бита во втором операнде (это должно быть значение в диапазоне от 0 до 31 для 32-битных операндов и от 0 до 63 для 64-битных операндов).
    • Биты с 8 по 15 регистра regctrl определяют количество битов, которые нужно извлечь из второго операнда.
    Рассмотрим пример:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rcx, 100011011b  ; помещаем в регистр rcx  число 283 - 100011011b
    4.  mov bl, 1  ; начальная позиция извлечения - со второго бита
    5.  mov bh, 4  ; сколько бит извлекаем - 4 бита
    6.  bextr rax, rcx, rbx
    7.  ret
    8. main endp
    9. end
    Исходное число, откуда извлекаем биты, хранится в регистре RCX и равно 283 или 100011011b в двоичной системе.
    В регистр RBX помещаем начальную позицию для извлечения и количество извлекаемых битов. В данном случае удобно разбить установку параметров: в регистр BL поместить начальную позицию, а в регистр BH - количество битов. То есть извлекаем со второго бита (передается позиция 1 - отсчет идет с нуля), и извлекаем 4 бита. То есть в числе 100011011b берем 4 бита начиная со второго бита и получаем последовательность 1101 или число 13, которые инструкция bextr помещает в регистр RAX.
    blsi
    В дополнение BMI1 также предоставляет инструкцию blsi, которая извлекает из набора бит с наименьшим номером:
    Код (ASM):
    1. blsi regdest, regsrc
    2. blsi regdest, varsrc
    Операнды должны быть одинакового размера и могут быть 32- или 64-битными. Эта инструкция находит младший установленный бит во втором операнде (регистре или переменной). Она копирует этот бит в той же позиции первый операнд - целевой регистр, остальные все биты в целевом регистре обнуляются.
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rcx, 100011000b  ; помещаем в регистр rcx  число 280
    4.  blsi rax, rcx
    5.  ret
    6. main endp
    7. end
    Здесь инструкция blsi извлекает первый установленный бит из числа 280 или 100011000b и сохраняет его в той же позиции в регистр RAX. То есть здесь первый установленный бит находится в позиции 3 (индексация начинается с 0). И в той же позиции копирует этот бит в регистр RAX. Поэтому в регистре RAX в конечном счете окажется число 1000 или 8.
    andn
    Инструкция andn выполняет логическую операцию AND NOT
    Код (ASM):
    1. andn regdest, regsrc1, regsrc2
    2. andn regdest, regsrc1, varsrc2
    Все операнды должны быть одинакового размера и иметь длину 32 или 64 бита. Эта инструкция выполняет логическую операцию AND NOT для второго и третьего операнда и сохраняет результат в первый операнд.
    Эту инструкцию можно использовать, например, чтобы удалить наименьший установленный бит сразу после извлечения с помощью инструкции blsi. Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rax, 100011000b  ; помещаем в регистр rax число 280
    4.  blsi rdx, rax  ; в rdx - 1000 или 8
    5.  andn rax, rdx, rax  ; rax = rax - rdx = 272
    6.  ret
    7. main endp
    8. end
    Здесь извлекаем из числа 280 наименьший установленный бит с предыдущими нулями, что представляет число 8. Затем с помощью andn исключаем извлеченное число из исходного 280. Таким образом, в RAX окажется число 272.
    Поскольку извлечение наименьшего бита и сохранение оставшихся битов представляют довольно распространенную задачу, то для этой цели в BMI1 есть специальная инструкция - blsr - она сбрасывает наименьший установленный бит:
    Код (ASM):
    1. blsr regdest, regsrc
    2. blsr regdest, varsrc
    Оба операнда должны быть одинакового размера - 32 или 64 бита. Эта инструкция получает данные из второго операнда, устанавливает наименьший бит в 0 и копирует результат в первый операнд. Если исходный операнд содержит 0, эта инструкция копирует 0 в первый операнд и устанавливает флаг переноса.
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 100011000b  ; помещаем в регистр rdx число 280
    4.  blsr rax, rdx  ; rax = 280 - 8 = 272
    5.  ret
    6. main endp
    7. end
    tzcnt
    Инструкция tzcnt посчитывает количество начальных нулей до наименьшего установленного бита:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 100011000b  ; помещаем в регистр rdx число 280
    4.  tzcnt rax, rdx  ; rax = 3
    5.  ret
    6. main endp
    7. end
    blsmsk
    Инструкция blsmsk создает битовую маску, которая состоит из всех битов 1 до самого младшего установленного бита включительно. Инструкция blsmsk устанавливает остальные биты в 0. Если исходное значение равно 0, blsmsk устанавливает все биты в регистре назначения в 1 и устанавливает флаг переноса
    Код (ASM):
    1. blsmsk regdest, regsrc
    2. blsmsk regdest, varsrc
    Например:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 100011000b  ; помещаем в регистр rdx число 280
    4.  blsmsk rax, rdx  ; rax = 1111 = 15
    5.  ret
    6. main endp
    7. end
    Наименьший установленный бит располагается по 3-му индексу - 1000, соответственно будет создана маска 1111
    Если надо, чтобы маска НЕ включала наименьший установленный бит, то можно использовать комбинацию инструкций blsi и dec:
    Код (ASM):
    1. .code
    2. main proc
    3.  mov rdx, 100011000b  ; помещаем в регистр rdx число 280
    4.  blsi rax, rdx  ; rax = 1000 = 8
    5.  dec rax  ; rax = rax-1 = 1000 - 1 = 0111 = 7
    6.  ret
    7. main endp
    8. end
     
    Последнее редактирование: 29 окт 2026 в 04:06
  20. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.364

    Основы ассемблера MASM для Intel x86-64

    Константы

    Ассемблер позволяет также определять константы - значения, которые не изменяются в процессе программы. Для этого применяется следующий синтаксис:
    Код (ASM):
    1. label = expression
    label представляет имя константы, и с помощью знака равно ей присваивается некоторое значение. Например:
    Код (ASM):
    1. number = 128
    Здесь определена константа number, которая равна 128.
    Константы можно определять в любом месте программы - в секциях .code и .data или вообще вне какой-либо секции.
    Код (ASM):
    1. number = 128  ; определяем константу
    2. .code
    3. main proc
    4.  mov eax, number ; Помещаем в регистр EAX константу number
    5.  ret
    6. main endp
    7. end
    Стоит отметить, что и константы, и данные для чтения из секции .const, представляют такие значения, которые нельзя изменить. Однако доступ к секции .const определяется операционной системой - она устанавливает для области памяти секции .const, что она доступна только для чтения. Кроме того, для переменных .const явным образом указывается тип данных при определении.
    Константы могут предоставлять значение для переменных:
    Код (ASM):
    1. number = 128
    2. .data
    3.  i32 dword number ; передаем переменной i32 значение константы number
    4. .code
    5. main proc
    6.  mov eax, i32
    7.  ret
    8. main endp
    9. end
    Директива equ
    Вместо знака равно = можно использовать директиву equ
    Код (ASM):
    1. number equ 128
    Хотя и символ = и директива equ применяются для определения констант, между этими двумя способами есть небольшие различия. Так, символ = позволяет переопределить константу в различных частях программы:
    Код (ASM):
    1. number = 128
    2. .data
    3.  i32 dword number ; передаем переменной i32 значение константы number
    4. .code
    5. number = 256  ; переопределяем константу
    6. main proc
    7.  mov eax, i32  ; EAX = 128
    8.  ret
    9. main endp
    10. end
    Другое различие состоит в том, что константы, определенные с помощью =, должны быть представлены как целые числа 64-битной или меньшей разрядности . С оператором = можно использовать даже строки длиной восемь или меньше символов (что подходит для 64-битного значения).
    Код (ASM):
    1. text = "hello"
    2. .code
    3. main proc
    4.  mov rax, text
    5.  ret
    6. main endp
    7. end
    Оператор equ не имеет такого ограничения.
    Оператор = вычисляет значение числового выражения и сохраняет это значение для подстановки везде, где эта константа появляется в программе. Директива equ, если ее операнд можно преобразовать в числовое значение, будет работать точно так же. Однако, если операнд equ не может быть преобразован в числовое значение, то директива equ сохранит свой операнд как текстовые данные и подставит эти текстовые данные вместо константы
    Код (ASM):
    1. letters equ "ABCDEFGHIJKL"
    2. .data
    3.  text byte letters
    4. .code
    5. main proc
    6.  mov rax, 0
    7.  mov al, text  ; AL = 65 - числовой код первого символа константы letters
    8.  ret
    9. main endp
    10. end
    Директива equ также позволяет задать сложные выражения. Например:
    Код (ASM):
    1. reg64 equ <rbx>
    2. .code
    3. main proc
    4.  mov rbx, 22
    5.  mov rax, reg64  ; RAX = RBX = 22
    6.  ret
    7. main endp
    8. end
    Здесь определена константа reg64, которая представляет регистр RBX. Чтобы присвоить константе выражение, применяются угловые скобки. В итоге везде, где применяется данная константа, фактически будет использоваться регистр RBX.
    Операции с константами
    Константы поддерживают ряд операций. Арифметические операции:
    • - (унарное отрицание): фактически умножает значение константы на -1
    • *: умножение константы
    • /: целочисленное деление
    • mod: возвращает остаток от деления
    • +: сложение
    • -: вычитание
    Код (ASM):
    1. n1 = 7
    2. n2 = 4
    3. .code
    4. main proc
    5.  mov rdx, n1 + n2  ; в регистре RDX число 11
    6.  mov rax, n1 mod n2  ; в регистре RAX число 3
    7.  ret
    8. main endp
    9. end
    Также поддерживается ряд операций сравнения:
    • EQ: возвращает true, если оба операнда равны.
    • NE: возвращает true, если оба операнда не равны.
    • LT: возвращает true, если левый операнд меньше правого операнда.
    • LE: возвращает true, если левый операнд меньше или равен правому операнду
    • GT: возвращает true, если левый операнд больше правого операнда.
    • GE: возвращает истину, если левый операнд больше или равен правому операнду.
    true в ассемблере означает число -1 (или 0FFFFFF...FFh), false число 0. Таким образом, мы можем сравнивать две константы, а результат сравнения - число -1 или 0 поместить в регистр:
    Код (ASM):
    1. n1 = 5
    2. n2 = 2
    3. .code
    4. main proc
    5.  mov rdx, n1 EQ n2  ; в регистре RDX число 0 - условие не верно
    6.  mov rax, n1 NE n2  ; в регистре RAX число -1 - условие верно
    7.  ret
    8. main endp
    9. end
    Здесь условие n1 EQ n2 возращает false, так как числа n1 и n2 не равны, поэтому в регистр rdx будет помещено число 0. А вот условие n1 NE n2 возвращает true, поэтому в регистре RAX будет число -1.
    Также доступны логические операции:
    • AND: возвращает результат поразрядной операции AND двух операндов
    • OR: возвращает результат поразрядной операции OR двух операндов
    • NOT: возвращает результат поразрядной инверсии операнда
    Код (ASM):
    1. n1 = 5
    2. n2 = 6
    3. .code
    4. main proc
    5.  mov rax, n1 AND n2  ; n1 AND n2 = 101 AND 110 = 100 = 4
    6.  ret
    7. main endp
    8. end
    Стоит отметить, что MASM сразу же интерпретирует значение константного выражения во время сборки. Он не заменяет константные выражения соответствующими инструкциями. Например
    Код (ASM):
    1. n1 = 4
    2. n2 = 3
    3. sum = n1 + n2
    4. .code
    5. main proc
    6.  mov eax, sum
    7.  ret
    8. main endp
    9. end
    После сборки программы здесь константа sum будет иметь значение 7 - оно вычисляется на этапе компиляции.
    Операторы this и $
    Операторы this и $ имеют специальное значение и синонимичны друг другу. Они возвращают текущее смещение относительно начала секции, которая их содержит. Данное смещение еще называется счетчик адреса или location counter. Обычно данный оператор применяется для является вычисления размера блока данных:
    Код (ASM):
    1. .data
    2.  buffer byte 1, 2, 3, 4, 5, 6
    3.  len = $-buffer ; от текущего адреса отнимаем адрес переменной buffer
    4. .code
    5. main proc
    6.  mov rax, len  ; len = 6
    7.  ret
    8. main endp
    9. end
    Здесь константа len равна результату выражения $-buffer, то от текущего адреса отнимаем адрес переменной buffer. Таким образом, len будет содержать размер блока buffer в байтах. Учитывая, что buffer представляет набор чисел byte, то фактически len будет содержать количество чисел в buffer.
    Подобным образом можно вычислять размер более сложных данных:
    Код (ASM):
    1. .data
    2.  buffer byte 1, 2, 3, 4, 5, 6
    3.  byte 7, 8, 9, 10, 11
    4.  len = $-buffer ; len = 11
    Оператор this отличается от оператора $ тем, что позволяет после себя указать тип данных - this тип_данных, где тип данных - один из стандартных типов ассемблера (byte, sbyte, word, sword и т.д.):
    Код (ASM):
    1. .data
    2.  buffer byte 1, 2, 3, 4, 5, 6
    3.  len = this dword - buffer  ; len представляет тип dword
    4. .code
    5. main proc
    6.  mov eax, len  ; len = 6
    7.  ret
    8. main endp
    9. end
     
    Последнее редактирование: 29 окт 2026 в 04:15