A1000 — Ошибка Assembler


Содержание

ошибка A1000 с визуальной студией MASM 2015

Я читаю книгу «Руководство по языку ассемблера» Джеймса Т. Стриба, чтобы изучить сборку, но я не могу скомпилировать тестовый пример на visual studio 2015 из-за следующей ошибки:

Я добавил это в командной строке Custom Build Tool моего файла main.asm:

Я также добавил msvcrt.lib в дополнительных зависимостях компоновщика.

Попробуйте использовать следующее для поля командной строки:

и это для поля «Выход»:

Старые макросы «Enter» устарели в Visual Studio 2015 и заменены новыми «макросами метаданных элементов», которые используют синтаксис %() .

Вот скриншот, показывающий, где я ввел эти строки. Обратите внимание, что я изменил в раскрывающийся список «Конфигурация» и «Платформа» на «Все конфигурации» и «Все платформы» соответственно, чтобы эти параметры использовались независимо от того, какая конструкция выполняется.

Распознавание ошибок Ассемблером

Большинство Ассемблеров немедленно распознает наиболее распространенные ошибки, такие как:

u Неопределенный код операции(обычно это неправильное написание или отсутствие двоеточия или метки);

u Неопределенное имя (часто это неправильное написание или отсутствие определенного имени);

u Неверный символ (например, 2 в двоичном числе или В в десятичном числе);

u Неправильное значение(обычно это число, которое слишком велико для 8 или 16 разрядов);

u Отсутствует операнд;

u Двойное определение(одному и тому же имени присваиваются два различных значения);

u Недопустимая метка(например, метка, предписанная псевдооперации, не допускающей метки);

u Отсутствие метки (например, при псевдооперации EQU, для которой требуется метка).

Эти ошибки неприятны, но они легко исправимы. Единственная трудность возникает тогда, когда ошибка (такая, как отсутствие точки с запятой у строки с комментарием) приводит Ассемблер в «замешательство», результатом чего является ряд бессмысленных сообщений об ошибках.

Существует, однако, много простых ошибок, которые Ассемблер не может распознать. Программисту следует иметь в виду, что его программа может содержать такие ошибки, даже если Ассемблер и не сообщил о них.

Типичны следующие примеры.

u Пропущенные строки.

u Пропущенные определения.

u Ошибки в написании, когда запись сама по себе допустима.

u Обозначение команд как комментариев.

u Если в команде, которая работает с парой регистров, задается одинарный регистр.

u Если вводится неправильная цифра, такая как Х в десятичном или шестнадцатеричном числе или 7 в двоичном числе.

В Ассемблере могут распознаваться только такие ошибки, которые предусмотрел его разработчик.

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

Распространенные ошибки в драйверах
ввода-вывода

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

u Смешивание портов ввода и вывода.

u Попытка выполнить операции, которые физически невозможны.

u Упущенные из вида неявных эффектов аппаратуры.

u Чтение или запись без проверки состояния.

u Игнорирование различия между вводом и выводом.

u Ошибка при сохранении копии выводимых данных.

u Чтение данных до того, как они стабилизируются, или во время их изменения.

u Отсутствие изменения полярности данных, которые передаются к устройству или от устройства, работающего с отрицательной логикой.

u Смешивание действительных портов ввода-вывода с внутренними регистрами интегральных схем ввода-вывода.

u Неправильное использование двунаправленных портов.

u Отсутствие очистки состояния после выполнения команды ввода-вывода.

Распространенные ошибки в программах прерывания

Многие ошибки, связанные с прерываниями, зависят как от аппаратного, так и программного обеспечения. Самыми распространенными ошибками являются следующие.

u Отсутствие разрешения прерываний.

u Отсутствие сохранения регистров.

u Сохранение или восстановление регистров в неправильном порядке.

u Разрешение прерываний до инициализации приоритетов и других параметров системы прерываний.

u Неучет того, что реакция на прерывание включает сохранение счетчика команд в вершине стека.

u Отсутствие запрещения прерываний во время многобайтных передач или выполнения последовательности команд, которая не должна прерываться.

u Отсутствие разрешения прерываний после последовательности команд, которая должна выполняться без прерываний.

u Отсутствие очистки сигнала, вызывающего прерывание.

u Ошибка в общении с основной программой.

u Отсутствие сохранения и восстановления приоритетов.

u Отсутствие разрешения прерываний от дополнительных аппаратурных входов, которое выполняется с помощью очистки разрядов масок в регистре I.

u Неправильное использование разрядов разрешения прерываний в командах SIM.

Лекция 19.
Введение в макроассемблер

Состав пакета

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

В состав пакета макроассемблера входят следующие программные компоненты:

u MASM — макроассемблер;

u LINK — объектный линкер;

u SYMDEB — символьный отладчик программ;

u MAPSYM — генератор символьного файла;

u CREF — утилита перекрестных ссылок;

u LIB — утилита обслуживания библиотек;

u MAKE — утилита сопровождения программ.

Линкер LINK обрабатывает выработанную MASM объектную программу с целью разрешения ссылок к другим модулям и приведения программы к виду, пригодному для загрузки в память.

Утилита LIB обеспечивает формирование и обслуживание библиотек объектных модулей, которые могут быть использованы LINK для разрешения внешних ссылок.

Отладчик SYMDEBреализует отладку сформированной программы на двух уровнях: на уровне символических имен и на уровне абсолютных адресов.

Программа MAPSYM предназначена для создания символьного файла для SYMDEB. Символьный файл формируется на основе информации, полученной от MASM, и необходим для символьной отладки.

Утилита CREF может быть использована для формирования листинга перекрестных ссылок программы, наличие которого облегчает отладку.

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

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

Пакет макроассемблера работает в операционной системе MS-DOS или PC-DOS версии 2.0 и выше и требует наличия минимум 128К памяти (использование команды SYMDEBможет потребовать дополнительной памяти).

Общие сведения

Макроассемблер MASM ассемблирует программы на языке Ассемблера и создает переместимые объектные файлы, которые могут редактироваться и выполняться в операционной системе MS-DOS.

Макроассемблер обеспечивает выполнение следующих функций:

u Анализ исходного текста на языке Ассемблера на предмет наличия в нем макрокоманд и/или макроопределений и обработка этих конструкций с соответствующей коррекцией исходного текста.

u Синтаксический анализ полученного текста и вывод необходимой диагностической информации.

u Формирование объектного модуля.

Воспринимая в качестве входа один файл с исходным текстом, макроассемблер может формировать до трех выходных файлов.

Файл листинга содержит распечатку исходного текста в соответствии со специфицированными директивами Ассемблера режимами и диагностическими сообщениями о результатах синтаксического анализа. Эти же сообщения дублируются на консоли.

Файл перекрестных ссылок содержит все используемые во входном тексте идентификаторы. В дальнейшем он может быть использован утилитой CREF.

В файле объектного кода формируется объектный модуль. Этот файл не формируется, если в тексте обнаружены ошибки.

Запуск макроассемблера

Ассемблирование исходного файла может производиться в двух режимах:

u С использованием подсказок.

u Посредством командной строки.

Для запуска макроассемблера с использованием подсказок необходимо ввести командную строку, содержащую только имя макроассемблера MASM со спецификацией подоглавления, если она требуется. MASM перейдет в диалоговый режим и серией подсказок запросит у пользователя информацию о следующих файлах (ответ заключается в наборе требуемых символов и нажатии клавиши ENTER):

1. Имя исходного файла. Если при ответе не указано расширение, предполагается ASM.

2. Имя объектного файла. Если при ответе не указано расширение, предполагается OBJ. Базовое имя объектного файла по умолчанию совпадает с базовым именем исходного файла.

3. Имя файла листинга. Если при ответе не указано расширение, предполагается LST. Базовое имя файла листинга по умолчанию NUL.

4. Имя файла перекрестных ссылок. Если при ответе не указано расширение, предполагается CRF. Базовое имя файла листинга по умолчанию NUL.

В конце любого ответа после символов / или — могут быть заданы опции макроассемблера, которые описаны ниже.

Если в каком-либо ответе специфицирован символ ; MASM выйдет из диалогового режима и установит оставшиеся имена по умолчанию из следующего списка:

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

Для запуска MASM посредством командной строки необходимо ввести командую строку следующего вида:

Символ ; может быть специфицирован в любом месте командной строки до того, как были определены все файлы.

В этом случае имена оставшихся неопределенными файлов принимаются по умолчанию из приведенного выше списка.

Из этого же списка принимаются по умолчанию имена файлов, спецификация которых в командной строке опущена (посредством лишней запятой).

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

Опции MASM могут располагаться в любом месте командной строки.

Следующие базовые имена выходных файлов MASM имеют фиксированный смысл (независимо от того, как запускается MASM):

u NUL — соответствующий файл не формируется;

u PRN — соответствующий файл направляется на печать.

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

Работа MASM может быть в любой момент прекращена нажатием клавиш CTRL-C.

Опции MASM

Опции MASM позволяют в некоторой степени управлять работой макроассемблера вне связи с исходной программой.

Каждая опция обозначается предшествующим символом /или — и может кодироваться как строчными, так и заглавными буквами.

Опции могут располагаться в любом месте командной строки или ответа на подсказку.

Ниже приведен список опций MASM с описанием выполняемых ими функций.

/A

Сегменты в объектном файле располагаются в алфавитном порядке. При отсутствии опции расположение сегментов соответствует порядку в исходном файле.

/S

Сегменты в объектном файле располагаются в порядке следования в исходном файле. Эта опция введена для совместимости с XENIX.

/B

Установить размер буфера исходного файла (в килобайтах). Увеличение размера буфера ускоряет ассемблирование, но требует больше памяти.

Размер буфера может варьироваться от 1 до 63 (К). Если опция не задана, полагается 32 (32К).

/D

Диагностические сообщения после 1-го прохода поместить в листинг программы. Многие ошибки 1-го прохода исправляются на 2-м проходе, и, если не задано /D, в листинг не попадают. Задание этой опции дает более глубокую диагностику исходного текста. При спецификации /D ошибки как 1-го, так и 2-го проходов выдаются на консоль, даже если файл листинга не создается.

/D

Определить символ. Указанный символ вводится в исходный текст как пустая строка (аналогично использованию директивы EQU) и может быть использован в директивах условного ассемблирования.

/I

Задание пути поиска файлов, подключаемых в исходный текст директивой INCLUDE без явного указания пути. Указание пути в INCLUDE более приоритетно, чем в опции /I.

/ML

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

/MX

Установить различие между строчными и заглавными буквами в общих и внешних переменных. Опция подобна /ML, но ее действие распространяется лишь на имена, используемые в директивах PUBLIC или EXTRN.

/MU

Преобразовать в общих и внешних именах строчные буквы в заглавные. Опция включена по умолчанию и введена для совместимости с XENIX.

/N

Запретить вывод в файл листинга таблиц, макроструктур, записей, сегментов и имен. На генерируемый код опция не влияет.

/P

Контроль запрещенного кода. Выполнение некоторых инструкций может привести к нежелательным последствиям (например, загрузка регистра CS). Кодирование таких инструкций может быть запрещено опцией /P, наличие которой в таких случаях вызывает генерацию ошибки с кодом 100. Директива .286p отменяет эту опцию и разрешает кодирование запрещенных инструкций.

/R

Генерация кода для процессора с плавающей точкой. Генерируются коды инструкций арифметики с плавающей точкой, которые могут быть выполнены только при наличии сопроцессоров.

/E

Генерация кода для эмуляции плавающей точки.

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

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

/V

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

/X

Выводить в листинг тела блоков IF (IF, IFE, IF1, IF2, IFDEF, IFNDEF, IFB, IFNB, IFIDN и IFDIF), для которых условия ассемблирования оказываются ложными и код по этой причине не генерируется. Следующие директивы Ассемблера влияют на действие опции /X:

u .SFCOND подавляет печать «ложных» блоков;

u .LFCOND — разрешает печать «ложных» блоков;

u .TFCOND — каждая обработка директивы меняет состояние опции на противоположное.


/Z

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

/C

Создать файл перекрестных ссылок. Файл создается, даже если он подавлен командной строкой или ответом на подсказку. В последнем случае имя файла устанавливается по умолчанию ( .CRF). Опция /C введена для совместимости с XENIX.

/L

Аналогично /C, но относится к файлу листинга (с учетом умалчиваемого имени файла).

/T

Подавить все сообщения, если в исходном тексте не встретится ошибок.

LINK: линкер модулей

Объектный линкер предназначен для создания исполнительных файлов из объектных файлов, сформированных MASM или компиляторами C или PASCAL.LINK формирует переместимый исполнительный код, снабженный информацией перемещения, используя которую, MS-DOS сможет загрузить в память и исполнить соответствующую программу. LINK может формировать программы, содержащие свыше 1Мб кода и данных. Воспринимая в качестве входа 2 файла, LINK может формировать 2 выходных файла.

(объектный файл) (исполнительный файл)

Расширения имен файлов, показанные на схеме принимаются по умолчанию.

Объектный файл содержит объектные модули программных сегментов, сформированные MASM или компилятором языка высокого уровня.

Библиотеки содержат наборы модулей, на которых могут ссылаться программные сегменты в объектном файле. Библиотечные файлы формируются при помощи утилиты LIB.

Основным результатом работы LINK является исполнительный файл, содержащий программу в виде, пригодном для загрузки в память и исполнения.

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

Файл плана содержит имена, загрузочные адреса и длины всех сегментов программы. Кроме того, сюда входят имена и загрузочные адреса групп в программе, адрес точки входа, а также сообщения о возможных ошибках.

Если задана опция /MAP, в файл включаются имена общих символов и их загрузочные адреса.

Если заданы опции /HIGH или /DSALLOCATE и объем программы и данных в совокупности не превышает 64К, план может содержать символы с необычно большими адресами сегментов. Эти адреса отражают переменные, расположенные ниже действительного начала сегмента. Пример:

Адрес TEMP — 00:920h.

Необходимо иметь ввиду, что, кроме двух выходных файлов, LINK может формировать временный файл с именем VM.TMP. Это происходит в том случае, когда линкеру не хватает оперативной памяти. Создание файла VM.TMP сопровождается сообщением на консоли и всегда осуществляется в текущем подоглавлении. В этом случае нельзя использовать опцию /PAUSE и снимать дискету, если она находится на активном драйве, до того, как LINK не уничтожит файл VM.TMP. Не рекомендуется создавать в текущем подоглавлении файл с таким именем, который в этом случае может быть испорчен.

Запуск LINK

Запуск LINK может быть осуществлен тремя способами:

u С использованием подсказок.

u При помощи командной строки DOS.

u С использованием файла ответа.

Для запуска LINK с использованием подсказок необходимо ввести командную строку, содержащую только имя линкера LINK со спецификацией подоглавления, если она требуется. LINK перейдет в диалоговый режим и серией подсказок запросит у пользователя информацию о следующих файлах (ответ заключается в наборе требуемых символов и нажатии клавиши ENTER):

1. Имя объектного файла. Если при ответе не указано расширение, предполагается OBJ. Если нужно определить несколько файлов, их имена разделяются символом +. Если все имена не помещаются на одной строке, ввод имен можно продолжить, поставив символ + в последнюю позицию текущей строки. В этом случае LINK повторит запрос для ввода дополнительных имен.

2. Имя исполнительного файла. Если при ответе не указано расширение, предполагается EXE. Базовое имя исполнительного файла по умолчанию совпадает с базовым именем объектного файла.

3. Имя файла плана модуля. Если при ответе не указано расширение, предполагается MAP. Базовое имя по умолчанию NUL.

4. Имя библиотеки. Если при ответе не указано расширение, предполагается LIB. Можно задавать несколько имен библиотек по аналогии с OBJ-файлами. Если, не вводя имени, сразу нажать ENTER, библиотеки использоваться не будут.

Если в каком-либо ответе специфицирован символ ; LINK выйдет из диалогового режима и установит оставшиеся имена по умолчанию из следующего списка:

Библиотеки не используются.

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

Для запуска LINK посредством командной строки, необходимо ввести командую строку следующего вида:

Символ ; может быть специфицирован в любом месте командной строки до того, как были определены все файлы. В этом случае имена оставшихся неопределенными файлов принимаются по умолчанию из приведенного выше списка. Из этого же списка принимаются по умолчанию имена файлов, спецификация которых в командной строке опущена (посредством лишней запятой). Если в командной строке обнаружена ошибка, об этом сообщается через консоль, и LINK переходит в диалоговый режим.

Если специфицирована хотя бы одна из опций /MAP или /LINENUMBERS, файл плана создается независимо от того, указано ли его имя в командной строке. В этом случае, если его имя не специфицировано, оно принимается по умолчанию — .MAP.

При указании нескольких объектных файлов или библиотек их имена разделяются символами +.

Если определены не все файлы (но не опущены посредством лишней запятой, и не специфицирована установка оставшихся имен по умолчанию указанием символа ;), LINK входит в диалоговый режим и запрашивает оставшиеся неопределенными имена через подсказки.

Спецификации имен файлов и опции могут быть заранее занесены в специальный файл ответа. Имя этого файла с предшествующим символом @ и указанием пути поиска, если он нужен, может быть помещено в любом месте ответа на подсказку или командной строки и трактуется, как если бы содержимое файла ответа было непосредственно вставлено в это место. Следует, однако, помнить, что комбинация символов

CARRIAGE-RETURN / LINE-FEED в файле ответа интерпретируется как ENTER в подсказке или запятая в командном файле.

Общий вид файла ответа:

Каждая группа файлов должна задаваться на отдельной строке, а файлы в группе, если их несколько, должны разделяться символом +. Если группа не помещается на одной строке, в последней позиции строки должен стоять признак продолжения — символ +. В любой строке файла ответа после символа / могут быть заданы опции LINK.

В файле ответа могут быть опущены компоненты, уже определенные ответами на подсказки или командной строкой.

При обнаружении в файле ответа символа ; остаток файла игнорируется, и оставшиеся неопределенными имена устанавливаются по умолчанию из приведенного выше списка.

При использовании файла ответа его содержимое выдается на консоль в форме подсказок. Если определены не все имена, LINK переходит в диалоговый режим.

Если файл ответа не содержит комбинации символов

CARRIAGE-RETURN / LINE-FEEDили символа; LINK выдает на консоль последнюю строку файла и ожидает нажатия ENTER.

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

Работа LINK может быть в любой момент прекращена нажатием клавиш CTRL-C.

Опции LINK

Все опции LINK обозначаются предшествующим символом / и могут быть сокращены произвольным образом, но так, чтобы код оставался уникальным среди опций.

Ниже приведены описания всех опций LINK (в скобках указаны минимальные сокращения):

/HELP (HE)

Выдать список действующих опций. Эту опцию нельзя использовать вместе с именем файла.

/PAUSE (P)

Пауза перед записью модуля в EXE-файл (и после записи в MAP-файл, если это предусмотрено). Во время этой паузы можно при необходимости переставить дискеты. Если используется файл VM.TMP, он должен находиться на той же дискете, что и EXE-файл.

/EXEPACK (E)

Установить компактную запись последовательностей одинаковых бит. Такой EXE-файл имеет меньший объем и быстрее загружается в память, но его нельзя отлаживать при помощи SYMDEB. Опция дает эффект, если программа содержит длинные потоки идентичных битов и требует большого числа (более 500) перемещений при загрузке.

/MAP (M)

Формировать MAP-файл. Файл формируется, даже если он не специфицирован при запуске LINK, и имеет в этом случае умалчиваемое имя.

/LINENUMBERS (LI)

Зафиксировать в MAP-файле номера строк исходного файла. Эта информация может в дальнейшем использоваться MAPSYM и SYMDEB. Запись номеров строк будет производиться, если создается MAP-файл и объектный модуль содержит данные о строках исходного текста. Компиляторы FORTRAN и PASCAL (версии 3.0 и выше) и C (версии 2.0 и выше) такие данные автоматически формируют; в MASM это не предус мотрено. Если MAP-файл не специфицирован, его можно создать принудительно, указав описываемую опцию в подсказке на этот файл.

/NOIGNORECASE (NOI)

Установить различие между строчными и заглавными буквами. Различие может быть установлено также опциями /ML и /MX MASM.

/NODEFAULTLIBRARYSEARCH

Игнорировать умалчиваемые (NOD) библиотеки, ссылки на которые содержатся в объектном модуле (их туда помещают компиляторы языков высокого уровня). Используются только библиотеки, специфицированные при запуске LINK.

/STACK: (ST)

Установить размер стека (в байтах). Информация о размере стека, содержащаяся в объектном модуле, игнорируется. Размер стека может быть задан в виде десятичного, 8-ричного (с предшествующим 0) или 16-ричного (с предшествующими 0 и x на малом регистре) числа в пределах от 1 до 65535. Размер стека может быть изменен утилитой EXEMOD.

/CPARMAXALLOC:

Установить максимальное число (C) 16-байтных параграфов, необходимых при загрузке программы в память. Обычно LINK устанавливает максимальное число параграфов — 65535. Указание этой опции позволяет более эффективно использовать память. Число параграфов может быть задано в виде десятичного, 8-ричного (с предшествующим 0) или 16-ричного (с предшествующими 0 и x на малом регистре) числа в пределах от 1 до 65535. Если число параграфов недостаточно для размещения программы, LINK наращивает его до минимально подходящего. Число параграфов может быть изменено утилитой EXEMOD. Кроме размещения программы, опция может понадобиться для команды ! SYMDEB.

/HIGH (H)

Установить адрес начала программы на наивысший возможный адрес свободной памяти. Без этой опции установка осуществляется на минимальный возможный адрес.

/DSALLOCATE (D)

Обработать группу с именем DGROUP. Обычно LINK присваивает младшему байту группы смещение 0000h. При задании этой опции старшему байту группы с именем DGROUP присваивается смещение FFFFh. В результате данные будут размещаться в областях программы с максимально большими адресами. Опция /D обычно применяется вместе с опцией /H для более эффективного использования незанятой памяти до старта программы. LINK предполагает, что все свободные байты в DGROUP занимают память непосредственно перед программой. Для

использования группы необходимо загрузить в регистр сегмента адрес начала DGROUP.

/NOGROUPASSOCIATION

Игнорировать группы (NOG) при присвоении адресов элементам данных и кода. Опция введена для совместимости с ранними версиями компиляторов FORTRAN и PASCAL (версии MICROSOFT 3.13 и ранее и IBM до 2.0). Не рекомендуется использовать эту опцию в других целях.

/OVERLAYINTERRUPT:

Установить номер прерывания (O) при загрузке оверлейного модуля.

Указанное число замещает номер стандартного оверлейного прерывания (03Fh). Номер может быть задан в виде десятичного, 8-ричного (с предшествующим 0) или 16-ричного (с предшествующими 0 и x на малом регистре) числа в пределах от 0 до 255. MASM не способствует созданию оверлейных программ. Поэтому только при помощи опции /O ассемблерные модули могут быть включены в оверлейные программы на языках высокого уровня, компиляторы которых поддерживают оверлей. Не рекомендуется устанавливать номер, совпадающий с каким-либо другим прерыванием.

/SEGMENTS: (SE)

Установить максимальное число сегментов, которое может обработать LINK. Число может быть задано в десятичной, 8-ричной (с предшествующим 0) или 16-ричной (с предшествующими 0 и x на малом регистре) форме в пределах от 1 до 1024. При отсутствии спецификации опции полагается 128. Память выделяется с учетом этого максимального числа сегментов.

/DOSSEG (DO)

Упорядочить сегменты в EXE-файле. При спецификации этой опции сегменты располагаются в следующей последовательности:

u сегменты с классом CODE;

u другие сегменты, не входящие в группу DGROUP;

u сегменты, входящие в DGROUP.

Особенности работы LINK

LINK создает исполнительный файл путем конкатенации кода программы и сегментов данных, соответствующих корректным инструкциям исходного текста. Эта сцепленная форма сегментов и является тем «исполнительным представлением», которое непосредственно копируется в память при загрузке программы.

Частично управлять редактированием программных сегментов можно заданием атрибутов в директиве SEGMENT или использованием директивы DGROUP для формирования группы сегментов. Эти директивы определяют целую группу ассоциаций, классов и типов выравнивания, а также определяют порядок и относительные начальные адреса сегментов программы. Эта информация является дополнительной к той, которая задается опциями LINK.

u Выравнивание сегментов

Для установки начального адреса сегмента LINK использует задаваемый директивой SEGMENT тип выравнивания: BYTE, WORD, PARA или PAGE. Эти ключевые слова обеспечивают выравнивание начала сегмента соответственно по границе байта, слова (2 байта), параграфа (16 байтов) или страницы (256 байтов). По умолчанию используется тип PARA.

Байты, пропускаемые из-за выравнивания, заполняются двоичными нулями.

Вычисляемый LINK начальный адрес сегмента зависит от типа выравнивания сегмента и размеров уже скопированных в исполнительный файл сегментов.

Этот адрес состоит из смещения и канонического номера кадра. Канонический адрес кадра определяет адрес первого параграфа в памяти, содержащего один или более байтов сегмента. Номер кадра всегда кратен 16. Смещением является расстояние в байтах от начала параграфа до первого байта сегмента.

Для типов PAGE иPARA смещение всегда нулевое, а для типов BYTE и WORD может быть ненулевым.

Номер кадра может быть получен из MAP-файла. Его содержат первые 5 16-ричных цифр start-адреса сегмента.

u Последовательность сегментов

LINK копирует сегменты в исполнительный файл в той же последовательности, в какой он их считывает из объектных файлов.

Сегменты, имеющие идентичные имена классов, считаются принадлежащими к одному типу классов и копируются в исполнительный файл как непрерывный блок.

u Комбинированные сегменты

Для определения того, будут ли два или более сегмента, имеющие одно и то же имя, соединены в один большой сегмент, LINK использует комбинации типов сегментов. В языке Ассемблера имеются следующие
типы комбинаций: PUBLIC, STACK, COMMON, MEMORY, AT и PRIVATE.

Если сегмент имеет тип комбинации PUBLIC, LINK автоматически соединяет его с другими сегментами, имеющими то же имя и принадлежащими к тому же классу. При соединении сегментов предполагается, что сегменты непрерывны и все адреса в сегментах доступны через смещение относительно адреса кадра. Результат получается таким же, как если бы полученный большой сегмент был определен в исходном файле сплошным куском.

LINK сохраняет тип выравнивания каждого сегмента. Это означает, что, хотя сегменты и включены в один большой сегмент, код и данные сегментов сохраняют свои типы выравнивания.

Если размеры соединяемых сегментов превышают 64К, выдается сообщение об ошибке.

Если сегмент имеет тип комбинации STACK, LINK выполняет ту же операцию, что и в случае PUBLIC. Различие заключается в том, что для STACK-сегментов в исполнительный файл записывается начальное значение указателя стека, которое представляет собой смещение от конца первого по порядку сегмента стека или комбинированного сегмента стека. В этом случае при использовании типа STACK для сегментов стека программисту нет необходимости предусматривать в программе загрузку регистра SS.

Если сегмент имеет тип комбинации COMMON, LINK автоматически соединяет его с другими сегментами, имеющими то же имя и принадлежащими к тому же классу. Однако, когда LINK соединяет общие сегменты, начало каждого сегмента устанавливается на один адрес, в результате чего образуются серии перекрывающихся сегментов. В итоге получается один сегмент, который по длине не превышает самый длинный из комбинируемых сегментов.

Сегменты с типом комбинации MEMORY трактуется LINK в точности так же, как и PUBLIC-сегменты. MASM обеспечивает тип MEMORYдля совместимости с линкерами, выделяющие MEMORY как особый тип комбинации.

Сегмент имеет тип комбинации PRIVATE в том случае, когда в исходном файле нет точных указаний относительно его типа комбинации. LINK не объединяет PRIVATE-сегменты.

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

Сегменты в группе не являются смежными, не принадлежат к одному классу и имеют разные типы комбинации. Но суммарный объем всех сегментов в группе не должен превышать 64К.

Группы не влияют на порядок загрузки сегментов в память. Даже если используются имена классов и объектные файлы вводятся в соответствующей последовательности, нет гарантии, что сегменты будут смежными. На практике LINK может поместить не принадлежащий группе сегмент в те же 64К памяти.

Хотя в LINK и нет строгой проверки того, помещаются ли все сегменты группы в 64К памяти, при обнаружении нарушения этого условия будет выдано сообщение о переполнении согласования.

Когда в процессе работы LINK уже известны адреса всех сегментов программы и организованы все комбинации сегментов и группы, линкер имеет возможность «согласовать» некоторые неразрешенные ссылки к меткам и переменным. Для этого LINK вычисляет соответствующие адрес сегмента и смещение и замещает временные значения, сгенерированные Ассемблером, на новые значения.

В соответствии с типами ссылок LINK реализует следующие типы согласований:

u Внутренние относительно себя.

u Внутренние относительно сегмента.

Размер вычисляемого значения зависит от типа ссылки. Если LINK обнаруживает ошибку в предсказанном размере ссылки, выдается сообщение о переполнении согласования. Это может произойти, например, когда программа пытается использовать 16-битовое смещение для доступа к инструкции в сегменте, имеющем другой адрес кадра. Это же сообщение может быть выдано, если все сегменты в группе не помещаются внутри блока памяти в 64К.

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

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ — конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой.

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

1. Регистры

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

Регистры можно разделить на регистры общего назначения, указатель команд, регистр флагов и сегментные регистры.

1.1. Регистры общего назначения

К регистрам общего назначения относится группа из 8 регистров, которые можно использовать в программе на языке ассемблера. Все регистры имеют размер 32 бита и могут быть разделены на 2 или более частей.

Как видно из рисунка, регистры ESI, EDI, ESP и EBP позволяют обращаться к младшим 16 битам по именам SI, DI, SP и BP соответственно, а регистры EAX, EBX, ECX и EDX позволяют обращаться как к младшим 16 битам (по именам AX, BX, CX и DX), так и к двум младшим байтам по отдельности (по именам AH/AL, BH/BL, CH/CL и DH/DL).

Названия регистров происходят от их назначения:

  • EAX/AX/AH/AL (accumulator register) – аккумулятор;
  • EBX/BX/BH/BL (base register) –регистр базы;
  • ECX/CX/CH/CL (counter register) – счётчик;
  • EDX/DX/DH/DL (data register) – регистр данных;
  • ESI/SI (source index register) – индекс источника;
  • EDI/DI (destination index register) – индекс приёмника (получателя);
  • ESP/SP (stack pointer register) – регистр указателя стека;
  • EBP/BP (base pointer register) – регистр указателя базы кадра стека.

Несмотря на существующую специализацию, все регистры можно использовать в любых машинных операциях. Однако надо учитывать тот факт, что некоторые команды работают только с определёнными регистрами. Например, команды умножения и деления используют регистры EAX и EDX для хранения исходных данных и результата операции. Команды управления циклом используют регистр ECX в качестве счётчика цикла.

Ещё один нюанс состоит в использовании регистров в качестве базы, т.е. хранилища адреса оперативной памяти. В качестве регистров базы можно использовать любые регистры, но желательно использовать регистры EBX, ESI, EDI или EBP. В этом случае размер машинной команды обычно бывает меньше.

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

1.2. Указатель команд

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

1.3. Регистр флагов

Флаг – это бит, принимающий значение 1 («флаг установлен»), если выполнено некоторое условие, и значение 0 («флаг сброшен») в противном случае. Процессор имеет регистр флагов, содержащий набор флагов, отражающий текущее состояние процессора.

№ бита Обозначение Название Описание Тип флага
FLAGS
0 CF Carry Flag Флаг переноса Состояние
1 1 Зарезервирован
2 PF Parity Flag Флаг чётности Состояние
3 0 Зарезервирован
4 AF Auxiliary Carry Flag Вспомогательный флаг переноса Состояние
5 0 Зарезервирован
6 ZF Zero Flag Флаг нуля Состояние
7 SF Sign Flag Флаг знака Состояние
8 TF Trap Flag Флаг трассировки Системный
9 IF Interrupt Enable Flag Флаг разрешения прерываний Системный
10 DF Direction Flag Флаг направления Управляющий
11 OF Overflow Flag Флаг переполнения Состояние
12 IOPL I/O Privilege Level Уровень приоритета ввода-вывода Системный
13
14 NT Nested Task Флаг вложенности задач Системный
15 0 Зарезервирован
EFLAGS
16 RF Resume Flag Флаг возобновления Системный
17 VM Virtual-8086 Mode Режим виртуального процессора 8086 Системный
18 AC Alignment Check Проверка выравнивания Системный
19 VIF Virtual Interrupt Flag Виртуальный флаг разрешения прерывания Системный
20 VIP Virtual Interrupt Pending Ожидающее виртуальное прерывание Системный
21 ID ID Flag Проверка на доступность инструкции CPUID Системный
22 Зарезервированы
.
31

Значение флагов CF, DF и IF можно изменять напрямую в регистре флагов с помощью специальных инструкций (например, CLD для сброса флага направления), но нет инструкций, которые позволяют обратиться к регистру флагов как к обычному регистру. Однако можно сохранять регистр флагов в стек или регистр AH и восстанавливать регистр флагов из них с помощью инструкций LAHF , SAHF , PUSHF , PUSHFD , POPF и POPFD .

1.3.1. Флаги состояния

Флаги состояния (биты 0, 2, 4, 6, 7 и 11) отражают результат выполнения арифметических инструкций, таких как ADD , SUB , MUL , DIV .

  • Флаг переноса CF устанавливается при переносе из старшего значащего бита/заёме в старший значащий бит и показывает наличие переполнения в беззнаковой целочисленной арифметике. Также используется в длинной арифметике.
  • Флаг чётности PF устанавливается, если младший значащий байт результата содержит чётное число единичных битов. Изначально этот флаг был ориентирован на использование в коммуникационных программах: при передаче данных по линиям связи для контроля мог также передаваться бит чётности и инструкции для проверки флага чётности облегчали проверку целостности данных.
  • Вспомогательный флаг переноса AF устанавливается при переносе из бита 3-го результата/заёме в 3-ий бит результата. Этот флаг ориентирован на использование в двоично-десятичной (binary coded decimal, BCD) арифметике.
  • Флаг нуля ZF устанавливается, если результат равен нулю.
  • Флаг знака SF равен значению старшего значащего бита результата, который является знаковым битом в знаковой арифметике.
  • Флаг переполнения OF устанавливается, если целочисленный результат слишком длинный для размещения в целевом операнде (регистре или ячейке памяти). Этот флаг показывает наличие переполнения в знаковой целочисленной арифметике.

Из перечисленных флагов только флаг CF можно изменять напрямую с помощью инструкций STC , CLC и CMC .

Флаги состояния позволяют одной и той же арифметической инструкции выдавать результат трёх различных типов: беззнаковое, знаковое и двоично-десятичное (BCD) целое число. Если результат считать беззнаковым числом, то флаг CF показывает условие переполнения (перенос или заём), для знакового результата перенос или заём показывает флаг OF, а для BCD-результата перенос/заём показывает флаг AF. Флаг SF отражает знак знакового результата, флаг ZF отражает и беззнаковый, и знаковый нулевой результат.

В длинной целочисленной арифметике флаг CF используется совместно с инструкциями сложения с переносом ( ADC ) и вычитания с заёмом ( SBB ) для распространения переноса или заёма из одного вычисляемого разряда длинного числа в другой.

Инструкции условного перехода Jcc (переход по условию cc ), SETcc (установить значение байта-результата в зависимости от условия cc ), LOOPcc (организация цикла) и CMOVcc (условное копирование) используют один или несколько флагов состояния для проверки условия. Например, инструкция перехода JLE (jump if less or equal – переход, если «меньше или равно») проверяет условие « ZF = 1 или SF ≠ OF ».

Флаг PF был введён для совместимости с другими микропроцессорными архитектурами и по прямому назначению используется редко. Более распространено его использование совместно с остальными флагами состояния в арифметике с плавающей запятой: инструкции сравнения ( FCOM , FCOMP и т. п.) в математическом сопроцессоре устанавливают в нём флаги-условия C0, C1, C2 и C3, и эти флаги можно скопировать в регистр флагов. Для этого рекомендуется использовать инструкцию FSTSW AX для сохранения слова состояния сопроцессора в регистре AX и инструкцию SAHF для последующего копирования содержимого регистра AH в младшие 8 битов регистра флагов, при этом C0 попадает во флаг CF, C2 – в PF, а C3 – в ZF. Флаг C2 устанавливается, например, в случае несравнимых аргументов (NaN или неподдерживаемый формат) в инструкции сравнения FUCOM .

1.3.2. Управляющий флаг

Флаг направления DF (бит 10 в регистре флагов) управляет строковыми инструкциями ( MOVS , CMPS , SCAS , LODS и STOS ) – установка флага заставляет уменьшать адреса (обрабатывать строки от старших адресов к младшим), обнуление заставляет увеличивать адреса. Инструкции STD и CLD соответственно устанавливают и сбрасывают флаг DF.


1.3.3. Системные флаги и поле IOPL

Системные флаги и поле IOPL управляют операционной средой и не предназначены для использования в прикладных программах.

  • Флаг разрешения прерываний IF – обнуление этого флага запрещает отвечать на маскируемые запросы на прерывание.
  • Флаг трассировки TF – установка этого флага разрешает пошаговый режим отладки, когда после каждой выполненной инструкции происходит прерывание программы и вызов специального обработчика прерывания.
  • Поле IOPL показывает уровень приоритета ввода-вывода исполняемой программы или задачи: чтобы программа или задача могла выполнять инструкции ввода-вывода или менять флаг IF, её текущий уровень приоритета (CPL) должен быть ≤ IOPL .
  • Флаг вложенности задач NT – этот флаг устанавливается, когда текущая задача «вложена» в другую, прерванную задачу, и сегмент состояния TSS текущей задачи обеспечивает обратную связь с TSS предыдущей задачи. Флаг NT проверяется инструкцией IRET для определения типа возврата – межзадачного или внутризадачного.
  • Флаг возобновления RF используется для маскирования ошибок отладки.
  • VM – установка этого флага в защищённом режиме вызывает переключение в режим виртуального 8086.
  • Флаг проверки выравнивания AC – установка этого флага вместе с битом AM в регистре CR0 включает контроль выравнивания операндов при обращениях к памяти: обращение к невыравненному операнду вызывает исключительную ситуацию.
  • VIF – виртуальная копия флага IF; используется совместно с флагом VIP.
  • VIP – устанавливается для указания наличия отложенного прерывания.
  • ID – возможность программно изменить этот флаг в регистре флагов указывает на поддержку инструкции CPUID.

1.4. Сегментные регистры

Процессор имеет 6 так называемых сегментных регистров: CS, DS, SS, ES, FS и GS. Их существование обусловлено спецификой организации и использования оперативной памяти.

16-битные регистры могли адресовать только 64 Кб оперативной памяти, что явно недостаточно для более или менее приличной программы. Поэтому память программе выделялась в виде нескольких сегментов, которые имели размер 64 Кб. При этом абсолютные адреса были 20-битными, что позволяло адресовать уже 1 Мб оперативной памяти. Возникает вопрос – как имея 16-битные регистры хранить 20-битные адреса? Для решения этой задачи адрес разбивался на базу и смещение. База – это адрес начала сегмента, а смещение – это номер байта внутри сегмента. На адрес начала сегмента накладывалось ограничение – он должен был быть кратен 16. При этом последние 4 бита были равны 0 и не хранились, а подразумевались. Таким образом, получались две 16-битные части адреса. Для получения абсолютного адреса к базе добавлялись четыре нулевых бита, и полученное значение складывалось со смещением.

Сегментные регистры использовались для хранения адреса начала сегмента кода (CS – code segment), сегмента данных (DS – data segment) и сегмента стека (SS – stack segment). Регистры ES, FS и GS были добавлены позже. Существовало несколько моделей памяти, каждая из которых подразумевала выделение программе одного или нескольких сегментов кода и одного или нескольких сегментов данных: tiny, small, medium, compact, large и huge. Для команд языка ассемблера существовали определённые соглашения: адреса перехода сегментировались по регистру CS, обращения к данным сегментировались по регистру DS, а обращения к стеку – по регистру SS. Если программе выделялось несколько сегментов для кода или данных, то приходилось менять значения в регистрах CS и DS для обращения к другому сегменту. Существовали так называемые «ближние» и «дальние» переходы. Если команда, на которую надо совершить переход, находилась в том же сегменте, то для перехода достаточно было изменить только значение регистра IP. Такой переход назывался ближним. Если же команда, на которую надо совершить переход, находилась в другом сегменте, то для перехода необходимо было изменить как значение регистра CS, так и значение регистра IP. Такой переход назывался дальним и осуществлялся дольше.

32-битные регистры позволяют адресовать 4 Гб памяти, что уже достаточно для любой программы. Каждую Win32-программу Windows запускает в отдельном виртуальном пространстве. Это означает, что каждая Win32-программа будет иметь 4-х гигабайтовое адресное пространство, но вовсе не означает, что каждая программа имеет 4 Гб физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах. А Windows сделает все необходимое, чтобы память, к которой программа обращается, «существовала». Конечно, программа должна придерживаться правил, установленных Windows, иначе возникает ошибка General Protection Fault.

Под архитектурой Win32 отпала необходимость в разделении адреса на базу и смещение, и необходимость в моделях памяти. На 32-битной архитектуре существует только одна модель памяти – flat (сплошная или плоская). Сегментные регистры остались, но используются по-другому 1 . Раньше необходимо было связывать отдельные части программы с тем или иным сегментным регистром и сохранять/восстанавливать регистр DS при переходе к другому сегменту данных или явно сегментировать данные по другому регистру. При 32-битной архитектуре необходимость в этом отпала, и в простейшем случае про сегментные регистры можно забыть.

1.5. Использование стека

Каждая программа имеет область памяти, называемую стеком. Стек используется для передачи параметров в процедуры и для хранения локальных данных процедур. Как известно, стек – это область памяти, при работе с которой необходимо соблюдать определённые правила, а именно: данные, которые попали в стек первыми, извлекаются оттуда последними. С другой стороны, если программе выделена некоторая память, то нет никаких физических ограничений на чтение и запись. Как же совмещаются два этих противоречивых принципа?

Пусть у нас есть функция f1, которая вызывает функцию f2, а функция f2, в свою очередь, вызывает функцию f3. При вызове функции f1 ей отводится определённое место в стеке под локальные данные. Это место отводится путём вычитания из регистра ESP значения, равного размеру требуемой памяти. Минимальный размер отводимой памяти равен 4 байтам, т.е. даже если процедуре требуется 1 байт, она должна занять 4 байта.

Функция f1 выполняет некоторые действия, после чего вызывает функцию f2. Функция f2 также отводит себе место в стеке, вычитая некоторое значение из регистра ESP. При этом локальные данные функций f1 и f2 размещаются в разных областях памяти. Далее функция f2 вызывает функцию f3, которая также отводит себе место в стеке. Функция f3 других функций не вызывает и при завершении работы должна освободить место в стеке, прибавив к регистру ESP значение, которые было вычтено при вызове функции. Если функция f3 не восстановит значение регистра ESP, то функция f2, продолжив работу, будет обращаться не к своим данным, т.к. она ищет их, основываясь на значении регистра ESP. Аналогично функция f2 должна при выходе восстановить значение регистра ESP, которое было до её вызова.

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

Каждая программа имеет область данных, где размещаются глобальные переменные. Почему же локальные данные хранятся именно в стеке? Это делается для уменьшения объёма памяти занимаемого программой. Если программа будет последовательно вызывать несколько процедур, то в каждый момент времени будет отведено место только под данные одной процедуры, т.к. стек занимается и освобождается. Область данных существует всё время работы программы. Если бы локальные данные размещались в области данных, пришлось бы отводить место под локальные данные для всех процедур программы.

Локальные данные автоматически не инициализируются. Если в вышеприведённом примере функция f2 после функции f3 вызовет функцию f4, то функция f4 займёт в стеке место, которое до этого было занято функцией f3, таким образом, функции f4 «в наследство» достанутся данные функции f3. Поэтому каждая процедура обязательно должна заботиться об инициализации своих локальных данных.

2. Основные понятия языка ассемблера

2.1. Идентификаторы

Понятие идентификатора в языке ассемблера ничем не отличается от понятия идентификатора в других языках. Можно использовать латинские буквы, цифры и знаки _ . ? @ $ , причём точка может быть только первым символом идентификатора. Большие и маленькие буквы считаются эквивалентными.

2.2. Целые числа

В программе на языке ассемблера целые числа могут быть записаны в двоичной, восьмеричной, десятичной и шестнадцатеричной системах счисления. Для задания системы счисления в конце числа ставится буква b , o/q , d или h соответственно. Шестнадцатеричные числа, которые начинаются с «буквенной» цифры, должны предваряться нулём, иначе компилятор не сможет отличить число от идентификатора. Примеры чисел см. в разделе 2.6.

2.3. Символьные данные

Символы и строки в языке ассемблера могут заключаться в апострофы или двойные кавычки. Если в качестве символа или внутри строки надо указать апостроф или кавычку, то делается это следующим образом: если символ или строка заключены в апострофы, то апостроф надо удваивать, а кавычку удваивать не надо, и наоборот, если символ или строка заключены в двойные кавычки, то надо удваивать кавычку и не надо удваивать апостроф. Все следующие примеры корректны и эквивалентны: ‘don»t’ , ‘don»t’ , «don’t» , «don»»t» .

2.4. Комментарии

Комментарии в языке ассемблера начинаются с символа «точка с запятой» и могут начинаться как в начале строки, так и после команды.

2.5. Директива эквивалентности

Директива эквивалентности позволяет описывать константы:

Все вхождения имени заменяются операндом. Операндом может быть константное выражение, строка, другое имя.

2.6. Директивы определения данных

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

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

Язык С, который создавался как высокоуровневая замена языку ассемблера, имеет гораздо менее жёсткую структуру типов. Все целочисленные типы совместимы, тип char, конечно, хранит символы, но также сопоставим с целыми типами, логический тип отсутствует в принципе (для языка С это именно так!), над указателями определены операции сложения и вычитания. Сложные типы, такие как массивы, строки и множества, не поддерживаются.

Что касается языка ассемблера, то тут вообще вряд ли можно говорить о какой-либо структуре типов. Команды языка ассемблера оперируют объектами, существующими в оперативной памяти, т.е. байтом и его производными (слово, двойное слово и т.д.). Символьный, логический тип? Какая глупость! Указатели? Вот тебе 4 байта и делай с ними, что хочешь. В итоге, конечно, и можно сделать, что хочешь, только предварительно стоит хорошо подумать, что из этого получится.

Соответственно, в языке ассемблера существует 5 (!) директив для определения данных:

  • DB (define byte) – определяет переменную размером в 1 байт;
  • DW (define word) – определяет переменную размеров в 2 байта (слово);
  • DD (define double word) – определяет переменную размером в 4 байта (двойное слово);
  • DQ (define quad word) – определяет переменную размером в 8 байт (учетверённое слово);
  • DT (define ten bytes) – определяет переменную размером в 10 байт.

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

Синтаксис директив определения данных следующий:

DB [, ] DW [, ] DD [, ] DQ [, ] DT [, ]

Операнд задаёт начальное значение переменной. В качестве операнда может использоваться число, символ или знак вопроса, с помощью которого определяются неинициализированные переменные.

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

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

a db 10011001b ; Определяем переменную размером 1 байт с начальным значением, заданным в двоичной системе счисления b db ‘!’ ; Определяем переменную в 1 байт, инициализируемую символом ‘!’ d db ‘string’,13,10 ; Определяем массив из 8 байт e db ‘string’,0 ; Определяем строку из 7 байт, заканчивающую нулём f dw 1235o ; Определяем переменную размером 2 байта с начальным значением, заданным в восьмеричной системе счисления g dd -345d ; Определяем переменную размером 4 байта с начальным значением, заданным в десятичной системе счисления h dd 0f1ah ; Определяем переменную размером 4 байта с начальным значением, заданным в шестнадцатеричной системе счисления i dd ? ; Определяем неинициализированную переменную размером 4 байта j dd 100 dup (0) ; Определяем массив из 100 двойных слов, инициализированных 0 k dq 10 dup (0, 1, 2) ; Определяем массив из 30 учетверённых слов, инициализированный повторяющимися значениями 0, 1 и 2 l dd 100 dup (?) ; Определяем массив из 100 неинициализированных двойных слов

К переменным можно применить две операции – offset и type . Первая определяет адрес переменной, а вторая – размер переменной. Однако размер переменной определяется по директиве, и даже если с директивой, например, DD определён массив из нескольких элементов, размер всё равно будет равен 4.

2.7. Команды

Команды языка ассемблера – это символьная форма записи машинных команд. Команды имеют следующий синтаксис:

Метка – это имя. Метка обязательно должна отделяться двоеточием, но может размещаться отдельно, в строке, предшествующей остальной части команды.

Метки нужны для ссылок на команды из других мест, например, в командах перехода. Компилятор языка ассемблера заменяет метки адресами команд.

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

Операнды команды, если они есть, отделяются друг от друга запятыми.

2.8. Операнды команд

В качестве операндов команд языка ассемблера могут использоваться:

  • регистры, обращение к которым осуществляется по именам;
  • непосредственные операнды – константы, записываемые непосредственно в команде;
  • ячейки памяти – в команде записывается адрес нужной ячейки.

Для задания адреса существуют следующие возможности.

  • Имя переменной, по сути, является адресом этой переменной. Встретив имя переменной в операндах команды, компилятор понимает, что нужно обратиться к оперативной памяти по определённому адресу. Обычно адрес в команде указывается в квадратных скобках, но имя переменной является исключением и может быть указано как в квадратных скобках, так и без них. Например, для обращения к переменной x в команде можно указать x или [x] .
  • Если переменная была объявлена как массив, то к элементу массива можно обратиться, указав имя и смещение. Для этого существует ряд синтаксических форм, например: [ ] и [ + ] (см. раздел 5). Однако следует понимать, что смещение – это вовсе не индекс элемента массива. Индекс элемента массива – это его номер, и этот номер не зависит от размера самого элемента. Смещение же задаётся в байтах, и при задании смещения программист сам должен учитывать размер элемента массива.
  • Адрес ячейки памяти может храниться в регистре. Для обращения к памяти по адресу, хранящемуся в регистре, в команде указывается имя регистра в квадратных скобках, например: [ebx] . Как уже говорилось, в качестве регистров базы рекомендуется использовать регистры EBX, ESI, EDI и EBP.
  • Адрес может быть вычислен по определённой формуле. Для этого в квадратных скобках можно указывать достаточно сложные выражения, например, [ebx + ecx] или [ebx + 4 * ecx] .

В описаниях команд языка ассемблера для обозначения возможных операндов используют сокращения, состоящие из буквы r (для регистров), m (для памяти) или i (для непосредственного операнда) и числа 8, 16 или 32, указывающего размер операнда. Например:

add r8/r16/r32, r8/r16/r32 ; Сложение регистра с регистром add r8/r16/r32, m8/m16/m32 ; Сложение регистра с ячейкой памяти add r8/r16/r32, i8/i16/i32 ; Сложение регистра с непосредственным операндом add m8/m16/m32, r8/r16/r32 ; Сложение ячейки памяти с регистром add m8/m16/m32, i8/i16/i32 ; Сложение ячейки памяти с непосредственным операндом

Команды языка ассемблера обычно имеют 1 или 2 операнда, или не имеют операндов вообще. Во многих, хотя не во всех, случаях операнды (если их два) должны иметь одинаковый размер. Команды языка ассемблера обычно не работают с двумя ячейками памяти.

3. Пересылка и арифметические команды

3.1. Команды пересылки и обмена

Одна из основных команд языка ассемблер – это команда пересылки. С её помощью можно записать в регистр значение другого регистра, константу или значение ячейки памяти, а также можно записать в ячейку памяти значение регистра или константу. Команда имеет следующий синтаксис:

По команде MOV значение второго операнда записывается в первый операнд. Операнды должны иметь одинаковый размер. Команда не меняет флаги.

mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX mov eax, 0ffffh ; Записываем в регистр EAX шестнадцатеричное значение ffff mov x, 0 ; Записываем в переменную x значение 0 mov eax, x ; Переслать значение из одной ячейки памяти в другую нельзя. mov y, eax ; Но можно использовать две команды MOV .

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

Для перестановки двух величин используется команда обмена:

Каждый из операндов может быть регистром или ячейкой памяти. Однако переставить содержимое двух регистров можно, а двух ячеек памяти – нет. Операнды должны иметь одинаковый размер. Команда не меняет флаги.

3.2. Оператор указания типа

Как было сказано, операнды команды MOV должны иметь одинаковый размер. В некоторых случаях компилятор может определить размер операнда. Например, регистр EAX имеет размер 32 бита, а регистр DX – 16 бит. Размер переменной определяется по директиве, указанной в её объявлении. Если можно определить размер только одного операнда, то размер второго операнда подгоняется под размер первого, если это возможно. Если же можно определить размеры обоих операндов, то они должны совпадать.

x db ? mov x, 0 ; 0 может иметь любой размер, в данном случае берётся 1 байт mov eax, 0 ; 0 может иметь любой размер, в данном случае берётся 4 байта mov al, 1000h ; Ошибка – попытка записать 2-байтное число в 1-байтный регистр mov eax, cx ; Ошибка – размеры операндов не совпадают

Однако не всегда бывает возможно определить размер пересылаемой величины по операндам команды MOV . Например, если один из операндов является ячейкой памяти, адрес которой записан в регистре, то по этому адресу можно записать и 1 байт, и 2 байта, и 4 байта. Если второй операнд является регистром, то размер пересылаемых данных определяется по размеру регистра. Если же второй операнд является константой, то размер пересылаемых данных определить нельзя, и компилятор фиксирует ошибку. Для того чтобы избежать этой ошибки, надо явно указать размер пересылаемых данных. Для этого используется оператор PTR :

В качестве типа используется BYTE , WORD или DWORD .

mov [ebx], 0 ; Ошибка, т.к. 0 может иметь любой размер mov byte ptr [ebx], 0 ; Пересылаем 1 байт mov dword ptr [ebx], 0 ; Пересылаем 4 байта

3.3. Команды сложения и вычитания

Команды сложения и вычитания реализуют хорошо всем известные арифметические операции. Единственное, что нужно учитывать при использовании этих команд – особенности сложения и вычитания, связанные с представлением чисел в памяти компьютера.

Команда ADD складывает операнды и записывает их сумму на место первого операнда. Команда SUB вычитает из первого операнда второй и записывает полученную разность на место первого операнда. Операнды должны иметь одинаковый размер. Если первый операнд – регистр, то второй может быть также регистром, ячейкой памяти и непосредственным операндом. Если первый операнд – ячейка памяти, то второй операнд может быть регистром или непосредственным операндом. Возможно сложение и вычитание как знаковых, так и беззнаковых чисел любого размера. Команды меняют флаги AF, CF, OF, PF, SF и ZF.

a dd 45d b dd -32d c dd ? mov eax, a add eax, b mov c, eax ; c = a + b

Команды инкремента и декремента увеличивают и уменьшают на 1 свой операнд.

Операндом может быть регистр или ячейка памяти любого размера. Команды меняют флаги AF, OF, PF, SF и ZF. Команды инкремента и декремента выгодны тем, что они занимают меньше места, чем соответствующие команды сложения и вычитания.

К арифметическим операциям можно также отнести команду изменения знака:

Операндом может быть регистр или ячейка памяти любого размера. Команда NEG рассматривает свой операнд как число со знаком и меняет знак операнда на противоположный. Команда меняет флаги AF, CF, OF, PF, SF и ZF.

mov ax, 1 neg ax ; AX = -1 = ffffh mov bl, -128 neg bl ; BL = -128, OF = 1

3.4. Команды умножения и деления

3.4.1. Команды умножения

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

Для беззнакового умножения используется команда MUL :

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

Местонахождение второго сомножителя и результата фиксировано, и в команде явно не указывается. Если операнд команды MUL имеет размер 1 байт, то второй сомножитель берётся из регистра AL, а результат помещается в регистр AX. Если операнд команды MUL имеет размер 2 байта, то второй сомножитель берётся из регистра AX, а результат помещается в регистровую пару DX:AX. Если операнд команды MUL имеет размер 4 байта, то второй сомножитель берётся из регистра EAX, а результат помещается в регистровую пару EDX:EAX.

Команда меняет флаги CF и OF. Если произведение имеет такой же размер, что и сомножители, то оба флага сбрасываются в 0. Если же размер произведения удваивается относительно размера сомножителей, то оба флага устанавливаются в 1.

x dw 256 mov ax, 105 mul x ; AX = AX * x, AX = 26880, CF = OF = 0 mov eax, 500000 mov ebx, 100000 mul ebx ; EDX:EAX = EAX * EBX, EDX:EAX = 50000000000, CF = OF = 1

Для знакового умножения используется команда IMUL :

IMUL IMUL , IMUL , , IMUL ,

Команда знакового умножения имеет несколько вариантов. Первый соответствует команде MUL – один из сомножителей указывается в команде, второй должен находиться в регистре EAX/AX/AL, а результат помещается в регистры EDX:EAX/DX:AX/AX.

Второй вариант команды IMUL позволяет указать регистр, который будет содержать один из сомножителей. В этот же регистр будет помещён результат. Второй сомножитель указывается непосредственно в команде.

Третий вариант команды IMUL позволяет указать и результат, и оба сомножителя. Однако результат может быть помещён только в регистр, а второй сомножитель может быть только непосредственным операндом. Первый сомножитель может быть регистром или ячейкой памяти.

Четвёртый вариант команды IMUL позволяет указать оба сомножителя. Первый должен быть регистром, а второй – регистром или ячейкой памяти. Результат помещается в регистр, являющийся первым операндом.

Команда IMUL устанавливает флаги так же, как и команда MUL . Однако расширение результата в регистр EDX/DX происходит только при использовании первого варианта команды IMUL . В остальных случаях часть произведения, не помещающаяся в регистр-результат, теряется, даже если в качестве результата указан регистр EAX/AX. При умножении двух 1-байтовых чисел, произведение которых больше байта, но меньше слова, в регистре-результате получается корректное произведение.

mov eax, 5 mov ebx, -7 imul ebx ; EAX = ffffffdd, EDX = ffffffff, CF = 0 mov ebx, 3 imul ebx, 6 ; EBX = EBX * 6 mov ebx, 500000 imul eax, ebx, 100000 ; EAX = EBX * 100000, старшая часть результата теряется x dd 40 mov eax, 55 imul eax, x ; EAX = EAX * x

3.4.2. Команды деления

Деление, как и умножение, реализуется двумя командами, предназначенными для знаковых и беззнаковых чисел:

DIV ; Беззнаковое деление IDIV ; Знаковое деление

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

Если делитель имеет размер 1 байт, то делимое берётся из регистра AX. Если делитель имеет размер 2 байта, то делимое берётся из регистровой пары DX:AX. Если же делитель имеет размер 4 байта, то делимое берётся из регистровой пары EDX:EAX.

Поскольку процессор работает с целыми числами, то в результате деления получается сразу два числа – частное и остаток. Эти два числа также помещаются в определённые регистры. Если делитель имеет размер 1 байт, то частное помещается в регистр AL, а остаток – в регистр AH. Если делитель имеет размер 2 байта, то частное помещается в регистр AX, а остаток – в регистр DX. Если же делитель имеет размер 4 байта, то частное помещается в регистр EAX, а остаток – в регистр EDX.

mov ax, 127 mov bl, 5 div bl ; AL = 19h = 25, AH = 02h = 2 mov ax, 127 mov bl, -5 >

3.5. Изменение размера числа

В операциях деления размер делимого в два раза больше, чем размер делителя. Поэтому нельзя просто загрузить данные в регистр EAX и поделить его на какое-либо значение, т.к. в операции деления будет задействован также и регистр EDX. Поэтому прежде чем выполнять деление, надо установить корректное значение в регистр EDX, иначе результат будет неправильным. Значение регистра EDX должно зависеть от значения регистра EAX. Тут возможны два варианта – для знаковых и беззнаковых чисел.

Если мы используем беззнаковые числа, то в любом случае в регистр EDX необходимо записать значение 0: aaaaaaaah → 00000000aaaaaaaah .

Если же мы используем знаковые числа, то значение регистра EDX будет зависеть от знака числа: 55555555h → 0000000055555555h , aaaaaaaah → ffffffffaaaaaaaah .

Записать значение 0 не сложно, а вот для знакового расширения необходимо анализировать знак числа. Однако нет необходимости делать это вручную, т.к. язык ассемблера имеет ряд команд, позволяющих расширять байт до слова, слово до двойного слова и двойное слово до учетверённого слова.

cbw ; Знаковое расширение AL до AX cwd ; Знаковое расширение AX до DX:AX cwde ; Знаковое расширение AX до EAX cdq ; Знаковое расширение EAX до EDX:EAX

Таким образом, если делитель имеет размер 2 или 4 байта, то нужно устанавливать значение не только регистра AX/EAX, но и регистра DX/EDX. Если же делитель имеет размер 1 байт, то можно просто записать делимое в регистр AX.

x dd ? mov eax, x ; Заносим в регистр EAX значение переменной x , которое заранее неизвестно cdq ; Знаковое расширение EAX в EDX:EAX mov ebx, 7 idiv ebx

В языке ассемблера существуют также команды, позволяющие занести в регистр значение другого регистра или ячейки памяти со знаковым или беззнаковым расширением.

MOVSX , ; Знаковое расширение – старшие биты заполняются знаковым битом MOVZX , ; Беззнаковое расширение – старшие биты заполняются нулём

Операнд1 и операнд2 могут иметь любой размер. Понятно, что операнд1 должен быть больше, чем операнд2. В случае равенства размера операндов следует использовать обычную команду пересылки MOV , которая выполняется быстрее.

Рассмотрим пример: необходимо вычислить x * x * x , где x – 1-байтовая переменная.

; Первый вариант mov al, x ; Пересылаем x в регистр AL imul al ; Умножаем регистр AL на себя, AX = x * x movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением imul bx ; Умножаем AX на BX. Но! – результат размещается в DX:AX ; Второй вариант mov al, x ; Пересылаем x в регистр AL imul al ; Умножаем регистр AL на себя, AX = x * x cwde ; Расширяем AX до EAX movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX

Рассмотрим ещё один пример.

mov eax, x mov ebx, 429496730 ; 429496730 = 4294967296 / 10 imul ebx ; EDX = x / 10. Выполняется в ≈5 раз быстрее, чем деление

Чем обусловлено получение такого результата? Всегда ли будет работать этот механизм?

4. Переходы и циклы

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

4.1. Безусловный переход

Команда безусловного перехода имеет следующий синтаксис:

Операнд указывает адрес перехода. Существует два способа указания этого адреса, соответственно различают прямой и косвенный переходы.

4.1.1. Прямой переход

Если в команде перехода указывается метка команды, на которую надо перейти, то переход называется прямым.

jmp L . L: mov eax, x

Вообще, любой переход заключается в изменении адреса следующей исполняемой команды, т.е. в изменении значения регистра EIP. Казалось бы, в команде перехода должен задаваться именно адрес перехода. Однако в команде прямого перехода задаётся не абсолютный адрес, а разность между адресом перехода и адресом команды перехода. Действие команды перехода заключается в прибавлении этой величины к текущему значению регистра EIP 2 . Операнд команды перехода рассматривается как поле со знаком, поэтому при сложении его со значением регистра EIP значение в этом регистре может как увеличиться, так и уменьшиться, т.е. возможен переход и вперёд, и назад.

Запись в команде перехода не абсолютного, а относительного адреса перехода позволяет уменьшить размер команды перехода. Абсолютный адрес должен быть 32-битным, а относительный может быть и 8-битным, и 16-битным.

4.1.2. Косвенный переход

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

4.2. Команды сравнения и условного перехода

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

Команда сравнения эквивалентна команде SUB за исключением того, что вычисленная разность никуда не заносится. Назначение команды CMP – установка и сброс флагов.

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

Все команды условного перехода можно разделить на три группы.

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

Мнемокод Название Условие перехода после команды CMP op1, op2 Значения флагов Примечание
JE Переход если равно op1 = op2 ZF = 1 Для всех чисел
JNE Переход если не равно op1 ≠ op2 ZF = 0
JL/JNGE Переход если меньше op1 op2 SF = OF и ZF = 0
JGE/JNL Переход если больше или равно op1 ≥ op2 SF = OF
JB/JNAE Переход если ниже op1 op2 CF = 0 и ZF = 0
JAE/JNB Переход если выше или равно op1 ≥ op2 CF = 0

Рассмотрим пример: даны две переменные x и y , в переменную z нужно записать максимальное из чисел x и y .

mov eax, x cmp eax, y jge/jae L ; Используем JGE для знаковых чисел и JAE – для беззнаковых mov eax, y L: mov z, eax

Во вторую группу команд условного перехода входят те, которые обычно ставятся после команд, отличных от команды сравнения, и которые реагируют на то или иное значение какого-либо флага.

Мнемокод Условие перехода Мнемокод Условие перехода
JZ ZF = 1 JNZ ZF = 0
JS SF = 1 JNS SF = 0
JC CF = 1 JNC CF = 0
JO OF = 1 JNO OF = 0
JP PF = 1 JNP PF = 0

Рассмотрим пример: пусть a , b и c – беззнаковые переменные размером 1 байт, требуется вычислить c = a * a + b , но если результат превосходит размер байта, передать управление на метку ERROR.

mov al, a mul al jc ERROR add al, b jc ERROR mov c, al

И, наконец, в третью группу входят две команды условного перехода, проверяющие не флаги, а значение регистра ECX или CX:

JCXZ ; Переход, если значение регистра CX равно 0 JECXZ ; Переход, если значение регистра ECX равно 0

Однако эта команда выполняется достаточно долго. Выгоднее провести сравнение с нулём и использовать обычную команду условного перехода.

С помощью команд перехода можно реализовать любые разветвления и циклы.

; if (x > 0) S cmp x, 0 jle L . ; S L: ; if (x) S1 else S2 cmp x, 0 je L1 . ; S1 jmp L2 L1: . ; S2 L2: ; if (a > 0 && b > 0) S cmp a, 0 jle L cmp b, 0 jle L . ; S L: ; if (a > 0 || b > 0) S cmp a, 0 jg L1 cmp b, 0 jle L2 L1: . ; S L2: ; if (a > 0 || b > 0 && c > 0) S cmp a, 0 jg L1 cmp b, 0 jle L2 cmp c, 0 jle L2 L1: . ; S L2: ; while (x > 0) do S L1: cmp x, 0 jle L2 . ; S jmp L1 L2: ; do S while (x > 0) L: . ; S cmp x, 0 jg L

4.3. Команды управления циклом

4.3.1. Команда LOOP


Команда LOOP позволяет организовать цикл с известным числом повторений:

mov ecx, n L: . . loop L

Команда LOOP требует, чтобы в качестве счётчика цикла использовался регистр ECX. Собственно, команда LOOP вычитает единицу именно из этого регистра, сравнивает полученное значение с нулём и осуществляет переход на указанную метку, если значение в регистре ECX больше 0. Метка определяет смещение перехода, которое не может превышать 128 байт.

При использовании команды LOOP следует также учитывать, что с её помощью реализуется цикл с постусловием, следовательно, тело цикла выполняется хотя бы один раз. Хуже того, если до начала цикла записать в регистр ECX значение 0, то при вычитании единицы, которое выполняется до сравнения с нулём, в регистре ECX окажется ненулевое значение, и цикл будет выполняться 2 32 раз.

Команда LOOP не относится к самым быстрым командам. В большинстве случаев её можно заменить последовательностью других команд.

4.3.2. Команды LOOPE / LOOPZ и LOOPNE / LOOPNZ

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

LOOPE ; Команды являются синонимами LOOPZ

Действие этой команды можно описать следующим образом: ECX = ECX — 1; if (ECX != 0 && ZF == 1) goto ;

До начала цикла в регистр ECX необходимо записать число повторений цикла. Команда LOOPE / LOOPZ , как и команда LOOP ставится в конце цикла, а перед ней помещается команда, которая меняет флаг ZF (обычно это команда сравнения CMP ). Команда LOOPE / LOOPZ заставляет цикл повторяться ECX раз, но только если предыдущая команда фиксирует равенство сравниваемых величин (вырабатывает нулевой результат, т.е. ZF = 1 ).

По какой именно причине произошёл выход из цикла надо проверять после цикла. Причём надо проверять флаг ZF, а не регистр ECX, т.к. условие ZF = 0 может появиться как раз на последнем шаге цикла, когда и регистр ECX стал нулевым.

Команда LOOPNE / LOOPNZ аналогична команде LOOPE / LOOPZ , но досрочный выход из цикла осуществляется, если ZF = 1 .

Рассмотрим пример: пусть в регистре ESI находится адрес начала некоторого массива двойных слов, а в переменной n – количество элементов массива, требуется проверить наличие в массиве элементов, кратных заданному числу x , и занести в переменную f значение 1, если такие элементы есть, и 0 в противном случае.

mov ebx, x mov ecx, n mov f, 1 L1: mov eax, [esi] add esi, 4 cdq idiv ebx cmp edx, 0 loopne L1 je L2 mov f, 0 L2:

5. Массивы

5.1. Модификация адресов

Как уже было сказано, массивы в языке ассемблера описываются по директивам определения данных с использованием конструкции повторения (см. раздел 2.6). Для того чтобы обратиться к элементу массива, необходимо так или иначе указать адрес начала массива и смещение элемента в массиве. Смещение первого элемента массива всегда равно 0. Смещения остальных элементов массива зависят от размера элементов.

Пусть X – некий массив. Тогда адрес элемента массива можно вычислить по следующей формуле:

адрес(X[i]) = X + (type X) * i, где i – номер элемента массива, начинающийся с 0

Напомним, что имя переменной эквивалентно её адресу (для массива – адресу начала массива), а операция type определяет размер переменной (для массива определяется размер элемента массива в соответствии с использованной директивой).

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

Однако принципиальное отличие состоит в том, в программе на языке высокого уровня мы указываем индекс элемента массива, а компилятор умножает его на размер элемента массива, получая смещение элемента массива. В программе на языке ассемблера указывается именно смещение, т.е. программист должен сам учитывать размер элемента массива. Компилятор же языка ассемблера просто прибавляет смещение к указанному адресу. Приведённые выше команды можно записать по-другому:

x + 4 [x + 4] [x] + [4] [x][4] [x + ebx] [x] + [ebx] [x][ebx]

Обратите внимание, что при использовании регистра для модификации адреса наличие квадратных скобок обязательно. В противном случае компилятор зафиксирует ошибку.

Адрес может вычисляться и по более сложной схеме:

База – это регистр или имя переменной. Индекс должен быть записан в некотором регистре. Множитель – это константа 1 (можно опустить), 2, 4 или 8. Смещение – целое положительное или отрицательное число.

mov eax, [ebx + 4 * ecx — 32] mov eax, [x + 2 * ecx]

5.2. Команда LEA

Команда LEA осуществляет загрузку в регистр так называемого эффективного адреса:

Команда не меняет флаги. В простейшем случае с помощью команды LEA можно загрузить в регистр адрес переменной или начала массива:

x dd 100 dup (0) lea ebx, x

Однако поскольку адрес может быть вычислен с использованием операций сложения и умножения, команда LEA имеет также ряд других применений (см. раздел 8.3.2).

5.3. Обработка массивов

Пусть есть массив x и переменная n , хранящая количество элементов этого массива.

x dd 100 dup(?) n dd ?

Для обработки массива можно использовать несколько способов.

  1. В регистре можно хранить смещение элемента массива.

mov eax, 0 mov ecx, n mov ebx, 0 L: add eax, x[ebx] add ebx, type x dec ecx cmp ecx, 0 jne L

  1. В регистре можно хранить номер элемента массива и умножать его на размер элемента.

mov eax, 0 mov ecx, n L: dec ecx add eax, x[ecx * type x] cmp ecx, 0 jne L

  1. В регистре можно хранить адрес элемента массива. Адрес начала массива можно записать в регистр с помощью команды LEA .

mov eax, 0 mov ecx, n lea ebx, x L: add eax, [ebx] add ebx, type x dec ecx cmp ecx, 0 jne L

  1. При необходимости можно в один регистр записать адрес начала массива, а в другой – номер или смещение элемента массива.

mov eax, 0 mov ecx, n lea ebx, x L: dec ecx add eax, [ebx + ecx * type x] cmp ecx, 0 jne L

Модификацию адреса можно производить также по двум регистрам: x[ebx][esi] . Это может быть удобно при работе со структурами данных, которые рассматриваются как матрицы. Рассмотрим для примера подсчёт количества строк матриц с положительной суммой элементов.

mov esi, 0 ; Начальное смещение строки mov ebx, 0 ; EBX будет содержать количество строк, удовлетворяющих условию mov ecx, m ; Загружаем в ECX количество строк L1: mov edi, 0 ; Начальное смещение элемента в строке mov eax, 0 ; EAX будет содержать сумму элементов строки mov edx, n ; Загружаем в EDX количество элементов в строке L2: add eax, y[esi][edi] ; Прибавляем к EAX элемент массива add edi, type y ; Прибавляем к смещению элемента в строке размер элемента dec edx ; Уменьшаем на 1 счётчик внутреннего цикла cmp edx, 0 ; Сравниваем EDX с нулём jne L2 ; Если EDX не равно 0, то переходим к началу цикла cmp eax, 0 ; После цикла сравниваем сумму элементов строки с нулём jle L3 ; Если сумма меньше или равна 0, то обходим увеличение EBX inc ebx ; Если же сумму больше 0, то увеличиваем EBX L3: mov eax, n ; Загружаем в EAX количество элементов в строке imul eax, type y ; Умножаем количество элементов в строке на размер элемента add esi, eax ; Прибавляем к смещению полученный размер строки dec ecx ; Уменьшаем на 1 счётчик внешнего цикла cmp ecx, 0 ; Сравниваем ECX с нулём jne L1 ; Если ECX не равно 0, то переходим к началу цикла

6. Поразрядные операции

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

6.1. Логические команды

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

Операция поразрядное «и» выполняет логическое умножение всех пар бит операндов.

Операция поразрядное «или» выполняет логическое сложение всех пар бит операндов.

Операция поразрядное исключающее «или» выполняет сложение по модулю 2 всех пар бит операндов.

Операции AND , OR и XOR имеют по два операнда. Первый может быть регистром или ячейкой памяти, а второй – регистром, ячейкой памяти или непосредственным операндом. Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда. Операции меняют флаги CF, OF, PF, SF и ZF.

Операция XOR имеет интересную особенность – если значения операндов совпадают, то результатом будет значение 0. Поэтому операцию XOR используют для обнуления регистров – она выполняется быстрее, чем запись нуля с помощью команды MOV .

xor eax, eax ; При любом значении EAX результат будет равен 0

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

xor eax, ebx ; EAX = EAX xor EBX xor ebx, eax ; Теперь EBX содержит исходное значение EAX xor eax, ebx ; А теперь EAX содержит исходное значение EBX

6.2. Команды сдвига

Операции сдвига вправо и сдвига влево сдвигают биты в переменной на заданное количество позиций. Каждая команда сдвига имеет две разновидности:

Первый операнд должен быть регистром или ячейкой памяти. Именно в нём осуществляется сдвиг. Второй операнд определяет количество позиций для сдвига, которое задаётся непосредственным операндом или хранится в регистре CL (и только CL).

Команды сдвига меняют флаги CF, OF, PF, SF и ZF.

Существует несколько разновидностей сдвигов, которые отличаются тем, как заполняются «освобождающиеся» биты.

6.2.1. Логические сдвиги

При логическом сдвиге «освобождающиеся» биты заполняются нулями. Последний ушедший бит сохраняется во флаге CF.

SHL , ; Логический сдвиг влево SHR , ; Логический сдвиг вправо

6.2.2. Арифметические сдвиги

Арифметический сдвиг влево эквивалентен логическому сдвигу влево (это одна и та же команда) – «освобождающие» биты заполняются нулями. При арифметическом сдвиге вправо «освобождающиеся» биты заполняются знаковым битом. Последний ушедший бит сохраняется во флаге CF.

SAL , ; Арифметический сдвиг влево SAR , ; Арифметический сдвиг вправо

6.2.3. Циклические сдвиги

При циклическом сдвиге «освобождающиеся» биты заполняются ушедшими битами. Последний ушедший бит сохраняется во флаге CF.

ROL , ; Циклический сдвиг влево ROR , ; Циклический сдвиг вправо

6.2.4. Расширенные сдвиги

Расширенные сдвиги немного отличаются от остальных сдвигов. В расширенных сдвигах участвуют два регистра или ячейка памяти и регистр, которые как бы объединяются в единое целое и «освобождающиеся» биты одного операнда заполняются битами из другого операнда.

SHLD , , ; Расширенный сдвиг влево SHRD , , ; Расширенный сдвиг вправо

Команда SHLD сдвигает влево биты операнда1 на указанное количество позиций. Младшие («освободившиеся») биты операнда1 заполняются старшими битами операнда2. Сам операнд2 не меняется.

Команда SHRD сдвигает вправо биты операнда1 на указанное количество позиций. Старшие («освободившиеся») биты операнда1 заполняются младшими битами операнда2. Сам операнд2 не меняется.

Количество, как и в других операциях сдвига, задаётся непосредственным операндом или хранится в регистре CL. Но используются только последние 5 бит операнда, определяющего количество, т.е. максимальное количество позиций сдвига равно 32.

Команды расширенного сдвига обычно используют для создания упакованных данных.

6.3. Умножение и деление с помощью поразрядных операций

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

6.3.1. Умножение

Для умножения используется сдвиг влево. Несмотря на наличие двух команда, по сути, сдвиг влево один. Он используется для умножения как знаковых, так и беззнаковых чисел. Однако результат будет правильным, только в том случае, если он умещается в регистр или ячейку памяти.

mov ax, 250 ; AX = 00fah = 250 sal ax, 4 ; Умножение на 24 = 16, AX = 0fa0h = 4000 mov ax, 1 ; AX = 1 sal ax, 10 ; Умножение на 210, AX = 0400h = 1024 mov ax, -48 ; AX = ffd0h = -48 (в дополнительном коде) sal ax, 2 ; AX = ff40h = -192 (в дополнительном коде) mov ax, 26812 ; AX = 68bch = 26812 sal ax, 1 ; AX = d178h = -11912 ; Знаковое положительное число перешло в отрицательное mov ax, 32943 ; AX = 80afh = 32943 sal ax, 2 ; AX = 02bch = 700 ; Большое беззнаковое число стало гораздо меньше

Сочетая сдвиги со сложением и вычитанием можно выполнить умножение на любое положительное число. Для умножения на отрицательное число следует добавить команду NEG .

mov ebx, x mov eax, ebx sal eax, 2 add eax, ebx ; EAX = x * 5 mov ebx, x mov eax, ebx sal eax, 3 sub eax, ebx ; EAX = x * 7 mov ebx, x mov eax, ebx sal eax, 2 add eax, ebx sal eax, 1 ; EAX = x * 10

Такой набор операций выполняется в 1.5-2 раза быстрее, чем обычное умножение. Но если оба сомножителя заранее неизвестны, то лучше использовать умножение.

6.3.2. Деление

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

Для деления беззнаковых чисел следует использовать логический сдвиг вправо.

mov ax, 43013 ; AX = a805h = 43013 shr ax, 1 ; AX = 5402h = 21506

Со знаковыми числами дело обстоит несколько сложнее. В принципе, для деления знаковых чисел следует использовать арифметический сдвиг вправо. Однако для отрицательных чисел получается не совсем корректный результат: 1 / 2 = 0, 3 / 2 = 1, но -1 / 2 = -1, -3 / 2 = -2,, т.е. результат отличается от правильного на единицу. Для того чтобы получить правильный результат, необходимо прибавить к делимому делитель, уменьшенный на 1. Однако это необходимо только для отрицательных чисел, поэтому для того, чтобы не делать проверок, используют следующий алгоритм.

; Деление на 2 mov eax, x cdq ; Расширяем двойное слово до учетверённого. Если в регистре EAX находится положительное число, ; то регистр EDX будет содержать 0, а если в регистре EAX находится отрицательное число, ; то регистр EDX будет содержать -1 (ffffffffh) sub eax, edx ; Если регистр EDX содержит 0, то регистр EAX не меняется. Если же регистр EDX содержит -1 ; (при отрицательном EAX), то к EAX будет прибавлена требуемая единица sar eax, 1 ; Деление на 2 n (в данном примере n = 3) mov eax, x cdq ; Расширяем двойное слово до учетверённого and edx, 111b ; Если EAX отрицателен, то EDX содержит делитель, уменьшенный на 1 add eax, edx ; Если EAX отрицателен, прибавляем полученное значение sar eax, 3 ; Если EAX был положителен, то EDX = 0, и предыдущие две операции ничего не меняют

Если число беззнаковое или если мы знаем, что число положительное, можно просто использовать сдвиг вправо, который выполняется примерно в 10 раз быстрее, чем деление. Если же для знакового числа не известно, положительное оно или отрицательное, то придётся использовать вышеприведённую последовательность команд, которая, однако, также выполняется примерно в 5-7 раз быстрее, чем деление.

6.3.3. Получение остатка от деления

Для беззнаковых и положительных чисел остаток от деления на 2 n – это последние n бит числа. Поэтому для получения остатка от деления на 2 n нужно выделить эти последние n бит с помощью операции AND .

mov eax, x and eax, 111b ; EAX = EAX % 2 3

Для отрицательного делимого x и положительного делителя n (x % n) = -(-x % n) .

mov eax, x neg eax and eax, 1111b ; EAX = EAX % 2 4 neg eax

7. Программа. Процедуры

7.1. Структура программы на языке ассемблера

Программа на языке ассемблера имеет следующую структуру:

.686 .model flat, stdcall option casemap: none .data .data? .const .code end

Директива .686 указывает компилятору ассемблера, что необходимо использовать набор операций процессора определённого поколения.

Директива .model позволяет указывать используемую модель памяти и соглашение о вызовах. Как уже было сказано, на архитектуре Win32 используется только одна модель памяти – flat, что и указано в приведённом примере. Соглашения о вызовах определяют порядок передачи параметров и порядок очистки стека.

Директива option casemap: none заставляет компилятор языка ассемблера различать большие и маленькие буквы в метках и именах процедур.

Директивы .data , .data? , .const и .code определяют то, что называется секциями. В Win32 нет сегментов, но адресное пространство можно поделить на логические секции. Начало одной секции отмечает конец предыдущей. Есть две группы секций: данных и кода.

Секция .data содержит инициализированные данные программы.

Секция .data? содержит неинициализированные данные программы. Иногда нужно только предварительно выделить некоторое количество памяти, не инициализируя её. Эта секция для этого и предназначается. Преимущество неинициализированных данных в том, что они не занимают места в исполняемом файле. Вы всего лишь сообщаете компилятору, сколько места вам понадобится, когда программа загрузится в память.

Секция .const содержит объявления констант, используемых программой. Константы не могут быть изменены. Попытка изменить константу вызывает аварийное завершение программы.

Задействовать все три секции не обязательно.

Есть только одна секция для кода: .code . В ней содержится весь код.

Предложения и end устанавливают границы кода. Обе метки должны быть идентичны. Весь код должен располагаться между этими предложениями.

Любая программа под Windows должна, как минимум, корректно завершится. Для этого необходимо вызвать функцию Win32 API ExitProcess.

.686 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .code program: push 0 call ExitProcess end program

Выше приведён пример минимальной программы на языке ассемблера, которая делает только одно – корректно завершается. В ней появились две новые директивы: include и includelib . Первая позволяет включать в программу файлы, содержащие прототипы процедур, а также определения констант и структур, которые могут понадобиться для программирования под Win32. Вторая директива указывает, какие библиотеки использует программа. Компоновщик должен будет прилинковать их. Без указания включаемого файла kernel2.inc и библиотеки импорта kernel32.lib невозможно будет вызвать процедуру ExitProcess. Файл windows.inc в данном случае включать не обязательно, но он требуется достаточно часто, а включаемые файлы не увеличивают размер получаемой программы.

Команда PUSH кладёт в стек параметр для процедуры ExitProcess. Этот параметр определяет код завершения. Значение 0 – это код нормального завершения программы.

Команда CALL вызывает процедуру ExitProcess.

Если вы используете компилятор MASM32, то пункт меню Project содержит команды Assemble & Link и Console Assemble & Link, которые позволяют скомпилировать обычное и консольное приложение под Windows. Приведённую программу можно откомпилировать обоими способами.

7.2. Команды работы со стеком

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

Для того чтобы положить данные в стек используется команда PUSH :

Операнд может быть регистром, ячейкой памяти или непосредственным операндом. Размер операнда должен быть 2 или 4 байта. Операнд кладётся на вершину стека, а значение регистра ESP уменьшается на размер операнда.

Для того чтобы взять данные из стека используется команда POP :

Операнд может быть регистром или ячейкой памяти. Размер операнда должен быть 2 или 4 байта. В соответствии с размером операнда из вершины стека берутся 2 или 4 байта и помещаются в указанный регистр или ячейку памяти. Значение регистра ESP увеличивается на размер операнда.

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

Команда PUSHA сохраняет в стеке содержимое регистров AX, CX, DX, BX, SP, BP, SI, DI. Команда PUSHAD сохраняет в стеке содержимое регистров EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI. Для регистра (E)SP сохраняется значение, которое было до того, как мы положили регистры в стек. После этого значение регистра (E)SP изменяется как обычно.

Эти команды противоположны предыдущим – они восстанавливают из стека значения регистров (E)DI, (E)SI, (E)BP, (E)SP, (E)BX, (E)DX, (E)CX, (E)AX. Содержимое регистра (E)SP не восстанавливается из стека, а изменяется как обычно.

Команда PUSHF сохраняет в стеке младшие 16 бит регистра флагов. Команда PUSHFD сохраняет в стеке все 32 бита регистра флагов.

Команда POPF восстанавливает из стека младшие 16 бит регистра флагов. Команда POPFD восстанавливает из стека все 32 бита регистра флагов.

7.3. Синтаксис процедуры

Описание процедуры на языке ассемблера выглядит следующим образом:

Несмотря на то, что после имени процедуры не ставится двоеточие, это имя является меткой, обозначающей первую команду процедуры.

В языке ассемблера имена и метки, описанные в процедуре, не локализуются внутри неё, поэтому они должны быть уникальны.

Размещать процедуру в программе на языке ассемблера следует таким образом, чтобы команды процедуры выполнялись не сами по себе, а только тогда, когда происходит обращение к процедуре. Обычно процедуры размещают либо в конце секции кода после вызова функции ExitProcess, либо в самом начале секции кода, сразу после директивы .code .

7.4. Вызов процедуры и возврат из процедуры

Вызов процедуры – это, по сути, передача управления на первую команду процедуры. Для передачи управления можно использовать команду безусловного перехода на метку, являющуюся именем процедуры. Можно даже не использовать директивы proc и endp , а написать обычную метку с двоеточием после вызова функции ExitProcess.

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

.686 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .code program: push L jmp Procedure L: nop push 0 call ExitProcess Procedure: pop eax jmp eax end program

Однако так обычно не делают – система команд языка ассемблера включает специальные команды для вызова процедуры и возврата из процедуры.

CALL ; Вызов процедуры RET ; Возврат из процедуры

Команда CALL записывает адрес следующей за ней команды в стек и осуществляет переход на первую команду указанной процедуры. Команда RET считывает из вершины стека адрес и выполняет переход по нему.

.686 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .code program: call Procedure push 0 call ExitProcess Procedure proc ret Procedure endp end program

7.5. Передача параметров процедуры

Существуют несколько способов передачи параметров в процедуру.

  1. Параметры можно передавать через регистры.

Если процедура получает небольшое число параметров, идеальным местом для их передачи оказываются регистры. Существуют соглашения о вызовах, предполагающие передачу параметров через регистры ECX и EDX. Этот метод самый быстрый, но он удобен только для процедур с небольшим количеством параметров.

  1. Параметры можно передавать в глобальных переменных.


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

  1. Параметры можно передавать в блоке параметров.

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

  1. Параметры можно передавать через стек.

Передача параметров через стек – наиболее распространённых способ. Именно его используют языки высокого уровня, такие как С++ и Паскаль. Параметры помещаются в стек непосредственно перед вызовом процедуры.

При внимательном анализе этого метода передачи параметров возникает сразу два вопроса: кто должен удалять параметры из стека, процедура или вызывающая её программа, и в каком порядке помещать параметры в стек. В обоих случаях оказывается, что оба варианта имеют свои «за» и «против». Если стек освобождает процедура, то код программы получается меньшим, а если за освобождение стека от параметров отвечает вызывающая программа, то становится возможным вызвать несколько функций с одними и теми же параметрами просто последовательными командами CALL . Первый способ, более строгий, используется при реализации процедур в языке Паскаль, а второй, дающий больше возможностей для оптимизации, – в языке С++.

Основное соглашение о вызовах языка Паскаль предполагает, что параметры кладутся в стек в прямом порядке. Соглашения о вызовах языка С++, в том числе одно из основных соглашений о вызовах ОС Windows stdcall, предполагают, что параметры помещаются в стек в обратном порядке. Это делает возможной реализацию функций с переменным числом параметров (как, например, printf). При этом первый параметр определяет число остальных параметров.

push . push call Procedure

В приведённом выше участке кода в стек кладутся несколько параметров и затем вызывается процедура. Следует помнить, что команда CALL также кладёт в стек адрес возврата. Таким образом, перед выполнением первой команды процедуры стек будет выглядеть следующим образом.

Адрес возврата оказывается в стеке поверх параметров. Однако поскольку в рамках своего участка стека процедура может обращаться без ограничений к любой ячейки памяти, нет необходимости перекладывать куда-то адрес возврата, а потом возвращать его обратно в стек. Для обращения к первому параметру используют адрес [ESP + 4] (прибавляем 4, т.к. на архитектуре Win32 адрес имеет размер 32 бита), для обращения ко второму параметру – адрес [ESP + 8] и т.д.

После завершения работы процедуры необходимо освободить стек. Если используется соглашение о вызовах stdcall (или любое другое, предполагающее, что стек освобождается процедурой), то в команде RET следует указать суммарный размер в байтах всех параметров процедуры. Тогда команда RET после извлечения адреса возврата прибавит к регистру ESP указанное значение, освободив таким образом стек. Если же используется соглашение о вызовах cdecl (или любое другое, предполагающее, что стек освобождается вызывающей программой), то после команды CALL следует поместить команду, которая прибавит к регистру ESP нужное значение.

; Передача параметров и возврат из процедуры с использованием соглашения о вызовах stdcall .686 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data x dd 0 y dd 4 .code program: push y ; Кладём в стек два параметра размером по 4 байта push x call Procedure push 0 call ExitProcess Procedure proc ret 8 ; В команде возврата указываем, что надо освободить 8 байт стека Procedure endp end program ; Передача параметров и возврат из процедуры с использованием соглашения о вызовах cdecl .686 .model flat, c option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data x dd 0 y dd 4 .code program: push y ; Кладём в стек два параметра размером по 4 байта push x call Procedure add esp, 8 ; Освобождаем 8 байт стека push 0 call ExitProcess Procedure proc ret ; Используем команду возврата без параметров Procedure endp end program

  1. Параметры можно передавать в потоке кода.

В этом необычном методе передаваемые процедуре данные размещаются прямо в коде программы, сразу после команды CALL . Чтобы прочитать параметр, процедура должна использовать его адрес, который автоматически передаётся в стеке как адрес возврата из процедуры. Разумеется, процедура должна будет изменить адрес возврата на первый байт после конца переданных параметров перед выполнением команды RET .

.686 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .code program: call Procedure ; Команда CALL кладёт в стек адрес следующей команды db ‘string’,0 ; В нашем случае – адрес начала строки push 0 call ExitProcess Procedure proc pop esi ; Извлекаем из стека адрес начала строки xor eax, eax ; Обнуляем EAX, в нём будет храниться количество символов L1: mov bl, [esi] ; Заносим в регистр BL байт, хранящийся по адресу ESI inc esi ; Увеличиваем значение в регистре ESI на 1 inc eax ; Увеличиваем значение в регистре EAX на 1 cmp bl, 0 ; Сравниваем прочитанный символ с нулём jne L1 ; Если не 0, переходим к началу цикла push esi ; Кладём в стек адрес байта, следующего сразу за строкой ret ; Возврат из процедуры Procedure endp end program

7.6. Передача результата процедуры

Для передачи результата процедуры обычно используется регистр EAX. Этот способ используется не только в программах на языке ассемблера, но и в программах на языке С++. Объекты, имеющие размер не более 8 байт, могут передаваться через регистровую пару EDX:EAX. Вещественные числа передаются через вершину стека вещественных регистров. Если эти способы не подходят, то следует передать в качестве параметра адрес ячейки памяти, куда будет записан результат.

; Передача параметров через стек, возврат результата через регистр EAX .686 .model flat, c option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data a dd 76 b dd -8 d dd ? .code program: push b ; Кладём параметры в стек push a call Procedure add esp, 8 ; Освобождаем 8 байт стека mov d, eax ; d = a – b push 0 call ExitProcess Procedure proc mov eax, [esp + 4] ; Заносим в регистр EAX первый параметр mov edx, [esp + 8] ; Заносим в регистр EDX второй параметр sub eax, edx ; В регистре EAX получилась разность параметров ret Procedure endp end program ; Передача параметров через стек, возврат результата по адресу .686 .model flat, c option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data a dd 76 b dd -8 d dd ? .code program: push offset d ; Кладём в стек адрес переменной, куда будет записан результат push b push a call Procedure add esp, 12 ; Освобождаем 12 байт стека push 0 call ExitProcess Procedure proc mov eax, [esp + 4] ; Заносим в регистр EAX первый параметр mov edx, [esp + 8] ; Заносим в регистр EDX второй параметр sub eax, edx ; В регистре EAX получилась разность параметров mov edx, [esp + 12] ; Заносим в регистр EDX третий параметр – адрес результата mov [edx], eax ; Записываем результат по адресу в регистре EDX ret Procedure endp end program

7.7. Сохранение регистров в процедуре

Практически любые действия в языке ассемблера требуют использования регистров. Однако регистров очень мало и даже в небольшой программе невозможно будет разделить регистры между частями программы, т.е. договориться, что основная программа использует, например, регистры EAX, ECX, EBP, ESP, а процедура – регистры EBX, EDX, ESI, EDI. В принципе, сделать так можно, но смысла в этом нет, т.к. программировать будет крайне неудобно, придётся перемещать данные из регистров в оперативную память и обратно, что замедлит выполнение программы. Кроме того, существуют правила, которые изменить нельзя – в регистре ESP хранится адрес вершины стека, а команды умножения и деления всегда используют регистры EAX и EDX. Поэтому получается, что основная программа и процедура вынуждены использовать одни и те же регистры, причём, вычисления в основной программе прерываются для того, чтобы выполнить вычисления процедуры. Таким образом, чтобы основная программа могла продолжить вычисления, процедура должна при выходе восстановить те значения регистров, которые были до начала выполнения процедуры. Естественно, для этого процедуре придётся предварительно сохранить значения регистров. Всё вышесказанное относится также к случаю, когда одна процедура вызывает другую процедуру.

Особенно внимательно следует относиться к регистрам ESI, EDI, EBP и EBX. ОС Windows использует эти регистры для своих целей и не ожидает, что вы измените их значение.

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

Где можно сохранить значения регистров? Конечно же, в стеке. Можно сохранить используемые регистры по одному с помощью команды PUSH , или все сразу с помощью команды PUSHAD . В первом случае в конце процедуры нужно будет восстановить значения сохранённых регистров с помощью команды POP в обратном порядке. Во втором случае для восстановления значений регистров используется команду POPAD .

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

; Процедура получает два параметра по 4 байта Procedure proc push esi ; Сохраняем используемые регистры push edi mov esi, [esp + 12] ; Извлекаем параметры из стека. Адрес вычисляется mov edi, [esp + 16] ; с учётом 8 байт, использованных при сохранении регистров . pop edi ; Извлекаем сохранённые регистры из стека pop esi ; в обратном порядке ret Procedure endp ; Процедура получает два параметра по 4 байта Procedure proc pushad ; Сохраняем все регистры mov eax, [esp + 4 + 32] ; Извлекаем параметры из стека. Адрес вычисляется mov ebx, [esp + 8 + 32] ; с учётом 32 байт, использованных при сохранении регистров . popad ; Извлекаем сохранённые регистры из стека ret Procedure endp

7.8. Локальные данные процедур

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

При вызове других процедур, а также в ходе выполнения текущей процедуры в стек могут быть положены другие данные. При этом значение регистра ESP изменится. Поэтому регистр ESP не является надёжной точкой отсчёта для адресов локальных переменных. Для того чтобы получить такую точку отсчёта, значение регистра ESP переписывают в регистр EBP, предварительно сохранив значение регистра EBP в стеке. В этом случае регистр EBP отмечает часть стека, занятую на момент начала работы процедуры (отсюда происходит название регистра EBP – указатель базы кадра стека). При таком подходе первый параметр процедуры всегда находится по адресу [EBP + 8]. Адреса локальных переменных отсчитываются от регистра EBP с отрицательным смещением. По окончании работы процедуры значение регистра ESP восстанавливается по регистру EBP, а значение регистра EBP – из стека.

Procedure proc var_104 = byte ptr -104h var_4 = dword ptr -4 arg_0 = dword ptr 8 arg_4 = dword ptr 0ch push ebp mov ebp, esp sub esp, 104h mov edx, [ebp + arg_0] mov eax, [ebp + arg_4] push ebx push esi push edi . pop edi pop esi pop ebx mov esp, ebp pop ebp ret Procedure endp

Такой способ позволяет также отводить различное количество места под локальные данные, и при необходимости не заботится о парности команд PUSH и POP .

7.9. Рекурсивные процедуры

Рекурсия – ресурсоёмкий способ реализации алгоритмов. Она требует много места для хранения локальных данных на каждом шаге рекурсии, кроме того, рекурсивные процедуры обычно выполняются не очень быстро. Поэтому языку ассемблера, предназначенному для написания быстрых программ, рекурсия, в общем, не свойственна. Но при желании и на ассемблере можно написать рекурсивную процедуру. Принципы реализации рекурсивной процедуры на языке ассемблера такие же, как и на других языках. В процедуре должна быть терминальная ветвь, в которой нет рекурсивного вызова, и рабочая ветвь.

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

Для примера рассмотрим рекурсивную процедуру вычисления факториала целого беззнакового числа. Процедура получает параметр через стек и возвращает результат через регистр EAX.

factorial proc mov eax, [esp + 4] ; Заносим в регистр EAX параметр процедуры test eax, eax ; Проверяем значение в регистре EAX jz END ; Если EAX = 0, то обходим рекурсивную ветвь dec eax ; Уменьшаем значение в регистре EAX на 1 push eax ; Кладём в стек параметр для следующего рекурсивного вызова call factorial ; Вызываем процедуру add esp, 4 ; Очищаем стек, т.к. процедура использует RET без параметров mul dword ptr [esp + 4] ; Умножаем EAX, хранящий результат предыдущего вызова, на параметр текущего вызова процедуры ret ; Возврат из процедуры (без параметров) END: inc eax ; Если EAX был равен 0, записываем в EAX единицу ret ; Возврат из процедуры (без параметров) factorial endp

8. Оптимизация программ, написанных на языке ассемблера

Наиболее популярным применением ассемблера обычно считается именно оптимизация программ, то есть уменьшение времени выполнения программ по сравнению с языками высокого уровня. Но если просто переписать текст, например с языка С на ассемблер, переводя каждую команду наиболее очевидным способом, часто оказывается, что процедура на языке С выполняется быстрее. Вообще говоря, ассемблер, как и любой другой язык, сам по себе не является панацеей от неэффективного программирования – чтобы действительно оптимизировать программу, требуется не только знание команд процессора, но и знание алгоритмов, навык оптимальных способов их реализации и подробная информация об архитектуре процессора.

Проблему оптимизации принято делить на три основных уровня:

  1. выбор наиболее оптимального алгоритма – высокоуровневая оптимизация;
  2. наиболее оптимальная реализация алгоритма – оптимизация среднего уровня;
  3. подсчёт тактов, тратящихся на выполнение каждой команды, и оптимизация их порядка для конкретного процессора – низкоуровневая оптимизация.

8.1. Высокоуровневая оптимизация

Выбор оптимального алгоритма для решения задачи всегда приводит к лучшим результатам, чем любой другой вид оптимизации. Действительно, при замене пузырьковой сортировки, время выполнения которой пропорционально n 2 , на быструю сортировку, время выполнения которой пропорционально n * log(n) , вторая программа будет выполняться быстрее в подавляющем большинстве случаев, как бы она ни была реализована. Поиск лучшего алгоритма – универсальная стадия, и она относится не только к ассемблеру, но и к любому языку программирования, поэтому будем считать, что оптимальный алгоритм уже выбран.

8.2. Оптимизация среднего уровня

Реализация алгоритма на данном конкретном языке программирования – самая ответственная стадия оптимизации. Именно здесь можно получить выигрыш в скорости в десятки раз или сделать программу в десятки раз медленнее, при серьёзных ошибках в реализации. Методы оптимизации сильно зависят от конкретного реализуемого алгоритма, поэтому невозможно описать правила на все случаи жизни, хотя, конечно, есть ряд общих приёмов, например, хранение переменных, с которыми выполняется активная работа, в регистрах, использование таблиц переходов вместо длинных последовательностей проверок и условных переходов и т.п. Тем не менее, даже плохо реализованные операции не вносят заметных замедлений в программу, если они не повторяются в цикле. Практически можно говорить, что все проблемы оптимизации на среднем уровне так или иначе связаны с циклами, и именно поэтому мы рассмотрим основные правила, которые стоит иметь в виду при реализации любого алгоритма, содержащего циклы.

8.2.1. Вычисление констант вне цикла

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

8.2.2. Перенос проверки условия в конец цикла

Циклы типа while или for, которые так часто применяются в языках высокого уровня, оказываются менее эффективными по сравнению с циклами типа until из-за того, что в них требуется лишняя команда перехода.

; for (i = start_i; i mov edi, start_i ; Начальное значение счётчика mov esi, n ; Конечное значение счётчика loop_start: cmp edi, esi ; Пока EDI inc edi jmp loop_start loop_end: ; i = start_i; do < >while (i inc edi cmp edi, esi jb loop_start ; Пока EDI

Предположим, в цикле должен быть один шаг. Тогда в цикле с предусловием будет выполнено сравнение, тело цикла, безусловный переход к началу цикла, сравнение и переход за цикл. В цикле с постусловием будет выполнено тело цикла, сравнение и нереализованный переход. Таким образом, в цикле с предусловием выполняется одно лишнее сравнение и два реализованных перехода (2 * 3 такта = 6 тактов) вместо одного нереализованного (1 такт). Вроде бы и немного, но если цикл окажется внутри другого цикла, то все эти лишние такты будут повторяться многократно. Кроме того, цикл с постусловием содержит на одну команду меньше.

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

8.2.3. Выполнение цикла задом наперёд

Циклы, в которых значение счётчика растёт от единицы или нуля до некоторой величины, можно реализовать вообще без операции сравнения, выполняя цикл в обратном направлении. Флаги меняются не только командой сравнения, но и многими другими. В частности, команда DEC меняет флаги AF, OF, PF, SF и ZF. Команда сравнения кроме этих флагов меняет также флаг CF, но для сравнения с нулём можно обойтись флагами SF и ZF.

; Цикл от 10 до 1 mov edx, 10 loop_start: dec edx ; Уменьшаем EDX на 1. Если EDX = 0, то ZF = 1 jnz loop_start ; Переход если ZF = 0. Когда EDX = 0, ZF = 1, поэтому выходим из цикла ; Цикл от 10 до 0 mov edx, 10 loop_start: dec edx ; Уменьшаем EDX на 1. Если EDX = -1, то SF = 1 jns loop_start ; Переход если SF = 0. Когда EDX = -1, SF = 1, поэтому выходим из цикла

Циклы от 0 и от 1 являются, наверное, самыми распространёнными. Конечно, не все циклы можно заставить выполняться в обратном направлении сразу. Например, иногда приходится изменять формат хранения массива данных также на обратный, иногда приходится вносить другие изменения, но в целом, если это возможно, всегда следует стремиться к циклам, выполняющимся задом наперёд.

8.2.4. Разворачивание циклов

Для небольших циклов время выполнения проверки условия и перехода на начало цикла может оказаться значительным по сравнению со временем выполнения самого тела цикла. В таких случаях можно вообще не создавать цикл, а просто повторить его тело нужное число раз (разумеется, только в случае, если нам заранее известно это число!). Для очень коротких циклов можно, например, удваивать или утраивать тело цикла, если, конечно, число повторений кратно двум или трём. Кроме того, бывает удобно часть работы сделать в цикле, а часть развернуть.

; Цикл от 10 до -1 mov edx, 10 loop_start: dec edx jns loop_start ; Выходим из цикла, когда EDX станет равны -1 ; Но повторяем тело цикла ещё раз

Естественно, эти простые методики не перечисляют все возможности оптимизации среднего уровня, более того, они не описывают и десятой доли всех её возможностей. Умение оптимизировать программы нельзя сформулировать в виде набора простых алгоритмов – слишком много существует различных ситуаций, в которых всякий алгоритм оказывается неоптимальным. При решении любой задачи оптимизации приходится пробовать десятки различных небольших изменений, далеко не все из которых оказываются полезными. Именно потому, что оптимизация всегда занимает очень много времени, рекомендуется приступать к ней только после того, как программа окончательно написана.

8.3. Низкоуровневая оптимизация

8.3.1. Основные принципы

Так как современные процессоры используют весьма сложный набор команд, большинство операций можно выполнить на низком уровне очень многими способами. При этом иногда оказывается, что наиболее очевидный способ – не самый быстрый. Часто простыми перестановками команд, зная механизм выполнения команд на современных процессорах, можно заставить ту же процедуру выполняться на 50–200% быстрее. Разумеется, переходить к этому уровню оптимизации можно только после того, как текст программы окончательно написан и максимально оптимизирован на среднем уровне.

Перечислим основные рекомендации.

  • Используйте регистр ЕАХ всюду, где возможно. Команды с непосредственным операндом, с операндом – абсолютным адресом переменной и команды XCHG с регистрами занимают на один байт меньше, если другой операнд – регистр ЕАХ.
  • Если к переменной в памяти, адресуемой со смещением, выполняется несколько обращений – загрузите её в регистр.
  • Не используйте сложные команды – ENTER , LEAVE , LOOP , строковые команды, если аналогичное действие можно выполнить небольшой последовательностью простых команд.
  • Не используйте умножение или деление на константу – его можно заменить другими командами (см. раздел 6.3).
  • Старайтесь программировать условия и переходы так, чтобы переход выполнялся по менее вероятному событию.
  • Следующее эмпирическое правило, относящееся к переходам и вызовам, очень простое: избавляться от них везде, где только можно. Для этого организуйте программу так, чтобы она исполнялась прямым, последовательным образом, с минимальным числом точек принятия решения. В результате очередь команд будет почти всегда заполнена, а вашу программу будет легче читать, сопровождать и отлаживать. Процедуры, особенно небольшие, нужно не вызывать, а встраивать. Это, конечно, увеличивает размер программы, но даёт существенный выигрыш во времени её исполнения.
  • Используйте короткую форму команды JMP , где возможно ( jmp short ).
  • Команда LEA быстро выполняется и имеет много неожиданных применений (см. раздел 8.3.2).
  • Многие одиночные команды, как это ни странно, выполняются дольше, чем две или три команды, приводящие к тому же результату. Это может быть связано с различными особенностями выполнения команд, в том числе, с возможностью/невозможность попарного выполнения команд в разных конвейерах (см. раздел 8.3.3).
  • Старайтесь выравнивать данные и метки по адресам, кратным 2/4/8/16 (см. раздел 8.3.4).
  • Если команда обращается к 32-битному регистру, например ЕАХ, сразу после команды, выполнявшей запись в соответствующий частичный регистр (АХ, AL, АН), может происходить пауза в один или несколько тактов.

8.3.2. Использование команды LEA

  • Команда LEA может использоваться для трёхоперандного сложения (но только сложения, а не вычитания).

lea eax, [ebx + edx]

  • Команда LEA может использоваться для сложения значения регистра с константой или вычитания константы из значения регистра. В данном случае вычитание возможно, т.к. оно рассматривается как сложение с отрицательной константой. Результат может быть помещён в тот же или другой регистр (кроме регистра ESP). Такой способ используется для сохранения флагов, т.к. команда LEA , в отличие от команд ADD , SUB , INC и DEC , не меняет флаги.

lea eax, [eax + 1] ; Сохраняем флаги lea eax, [ebx – 4]

  • Команда LEA может использоваться для быстрого умножения на константы 2, 3, 4, 5, 7(?), 8, 9. Адрес, загружаемый командой LEA , может быть суммой двух регистров, один из которых может быть умножен на константу 2, 4 или 8. Поэтому комбинируя умножение и сложение можно получить вышеперечисленные константы. Третье слагаемое может быть константой.

lea eax, [eax * 4 + eax] ; EAX = EAX * 5 lea eax, [ebx * 8 + ecx – 32]

8.3.3. Замена команд

  • Вместо команды AND лучше использовать команду TEST, если нужен не результат, а проверка. Команда TEST лучше спаривается. Команда TEST также может быть использована для проверки на равенство нулю.

test eax, eax jz ; Переход, если EAX = 0

  • Если за командой CALL сразу же следует команда RET , замените эти команды командой JMP . Вызываемая процедура осуществит возврат по адресу возврата, переданному вызывающей процедуре.

call dest jmp dest ret

  • Команду CBW можно заменить засылкой нуля, если расширяемое число положительное. Команду CDQ можно заменить засылкой нуля, если расширяемое число положительное, или парой команд MOV + SAR , если знак расширяемого числа не известен. Недостаток – команды XOR и SAR меняют флаги.

cdq xor edx, edx cdq mov edx, eax sar edx, 31

  • Вместо команд инкремента и декремента можно использовать команду LEA .
  • Сложение и вычитание с константой можно заменить командой LEA .
  • Вместо умножения и деления на степень числа 2 используйте сдвиги.
  • Умножение и деление на константу можно заменить командой LEA или сочетанием команд сдвига и команд сложения и вычитания.
  • Деление на константу можно заменить умножением на константу.
  • Обнуление регистров производится с помощью команды XOR .

xor eax, eax ; EAX = 0 при любом значении EAX, которое было до этой команды

  • Не используйте команду MOVZX для чтения байта – это требует 3 тактов для выполнения. Заменой может служить такая пара команд, выполняющаяся за 2 такта:

xor еах, еах mov al,

  • Засылку непосредственного операнда в ячейку памяти можно производить через регистр – такие команды лучше спариваются.

mov x, 1 mov eax, 1 mov x, eax mov [ebx], 1 mov eax, 1 mov [ebx], eax

  • Аналогично команды PUSH и POP , работающие с ячейкой памяти, можно заменить парой команд MOV + PUSH или POP + MOV .

push x mov eax, x push eax pop x pop eax mov x, eax

8.3.4. Выравнивание

  • 80-битные данные должны быть выравнены по 16-байтным границам (то есть четыре младших бита адреса должны быть равны нулю).
  • Восьмибайтные данные должны быть выравнены по восьмибайтным границам (то есть три младших бита адреса должны быть равны нулю).
  • Четырёхбайтные данные должны быть выравнены по границе двойного слова (то есть два младших бита адреса должны быть равны нулю).
  • Двухбайтные данные должны быть выравнены по границе слова.
  • Метки для переходов, особенно метки, отмечающие начало цикла, должны быть выравнены по 16-байтным границам.

Каждое невыравненное обращение к данным означает потерю тактов процессора.

Для выравнивания данных и кода используется директива ALIGN :

Число должно быть степенью двойки. Данные и команда, расположенные после директивы ALIGN , будут размещены по адресу, кратному указанному числу.

9. Примеры

  1. Процедура вычисления наибольшего общего делителя двух беззнаковых чисел. Для нахождения НОД используется алгоритм Евклида: пока числа не равны, надо вычитать из большего числа меньшее. Процедура получает параметры через регистры EAX и EDX и возвращает результат через регистр EAX.

NOD proc N1: cmp eax, edx ; Сравниваем числа je N3 ; Если числа равны, завершаем работу процедуры ja N2 ; Если первое число больше, обходим обмен ; Поскольку команды перехода не меняют флаги, оба перехода ; выполняются или не выполняются по результатам одного сравнения xchg eax, edx ; Если первое число было меньше, выполняем обмен N2: sub eax, edx ; Вычитаем из большего числа меньшее jmp N1 ; Переход к началу цикла N3: ret NOD endp

  1. Ввод и вывод в консольном приложении. В программе используются следующие функции Win32 API.
  • SetConsoleTitle – меняет заголовок окна консоли. Получает один параметр – указатель на строку, которая будет выведена в заголовке. Строка должна заканчиваться нулём.
  • GetStrHandle – возвращает идентификатор устройства ввода, устройства вывода или устройства отчёта об ошибках. Для консольного приложения всё три устройства являются консолью, но идентификаторы будут разными. Функция получает один параметр – указание, идентификатор какого устройства нужно вернуть. Чтобы получить идентификатор устройства ввода, надо передать функции число -10, чтобы получить идентификатор устройства вывода – число -11, а чтобы получить идентификатор устройства отчёта об ошибках – число -12. Функция возвращает требуемый идентификатор через регистр EAX.
  • WriteConsole – выводит строку в консоль. Получает следующие параметры – идентификатор устройства вывода, адрес выводимой строки, количество символов для вывода, адрес переменной, куда будет записано количество выведенных символов, зарезервированный указатель.
  • ReadConsole – вводит строку из консоли. Получает следующие параметры – идентификатор устройства ввода, адрес памяти, куда будет записана введённая строка, максимальное количество читаемых символов, адрес переменной, куда будет записано реальное количество введённых символов, зарезервированный указатель.

Не забывайте, что параметры кладутся в стек, начиная с последнего, и что введённая строка всегда будет содержать в конце символы с кодами 13 и 10, которые появляются при нажатии на клавишу ВВОД (без чего, однако, ввод не завершится).

.686 .model flat, c option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .data str db 256 dup(0) hStdIn dd 0 hStdOut dd 0 slength dd 0 .const sConsoleTitle db ‘Input and Output’,0 ; Заголовок окна консоли. Заканчивается нулём prompt db ‘Input a string’, 13,10 ; Приглашение для ввода. Символы с кодами 13 и 10 ; обеспечивают перевод курсора на следующую строку STD_INPUT_HANDLE equ -10d ; Определяем символические имена для констант, STD_OUTPUT_HANDLE equ -11d ; указывающих требуемое устройство .code program: ; Вывод заголовка консоли push offset sConsoleTitle ; Кладём в стек адрес начала строки заголовка консоли call SetConsoleTitle ; Вызываем функцию ; Получаем идентификатор устройства ввода push STD_INPUT_HANDLE ; Кладём в стек параметр функции GetStdHandle call GetStdHandle ; Вызываем функцию mov hStdIn, eax ; Сохраняем полученный идентификатор ; Получаем идентификатор устройства вывода push STD_OUTPUT_HANDLE call GetStdHandle mov hStdOut, eax ; Выводим приглашение push 0 ; Зарезервированный параметр, в стек кладём 0 push 0 ; Указатель на переменную для записи количества выведенных символов, ; в данном случае не нужен, поэтому в стек кладём 0 push 10h ; Количество выводимых символов push offset prompt ; Адрес выводимой строки push hStdOut ; Идентификатор устройства вывода call WriteConsole ; Вызываем функцию ; Вводим строку push 0 ; Зарезервированный параметр, в стек кладём 0 push offset slength ; Адрес переменной, куда будет записано количество введённых символов push 256 ; Максимальное количество вводимых символов push offset str ; Адрес для записи введённой строки push hStdIn ; Идентификатор устройства ввода call ReadConsole ; Вызываем функцию ; Выводим строку push 0 push 0 push slength push offset str push hStdOut call WriteConsole ; Задержка push 1800h call Sleep push 0 call ExitProcess end program

  1. Процедура ввода целого числа в 16-ричной системе счисления. Процедура предназначена для использования в консольном приложении и предполагает, что идентификатор устройства ввода был получен основной программой и сохранён в переменной hStdIn.

InputNumber proc push ebp ; Сохраняем в стеке значение регистра EBP mov ebp, esp ; Заносим в регистр EBP текущее значение вершины стека sub esp, 16 ; Резервируем 16 байт. Вводимая строка может содержать до 8 цифр. ; 2 байта требуются для символов с кодами 13 и 10. Итого 10 байт. ; 4 байта нужно для целочисленной переменной, куда будет записываться количество ; введённых символов. Итого 14 байт. Но выделим 16 байт, т.е. 4 двойных слова push ebx ; Сохраняем значения важных регистров push esi ; Вводим строку push 0 lea eax, [ebp — 16] ; 4 байта по адресу [EBP – 16] предназначены для хранения количества введённых символов push eax push 10d lea eax, [ebp — 12] ; По адресу [EBP – 12] начинается память для вводимой строки push eax push hStdIn call ReadConsole ; Преобразуем строку в число xor eax, eax ; Обнуляем регистр EAX . xor ebx, ebx ; . и регистр EBX mov ecx, [ebp — 16] ; Заносим в регистр ECX количество введённых символов sub ecx, 2 ; Символы с кодами 13 и 10 обрабатывать не надо lea esi, [ebp — 12] ; Заносим в регистр ESI адрес начала строки test ecx, ecx ; Используем команду TEST для сравнения с нулём jz L2 ; Если ECX = 0, то завершаем работу процедуры L1: mov bl, [esi] ; Заносим в регистр BL текущий символ (три старших байта EBX ; содержат 0, т.к. ранее была команда XOR EBX, EBX) lea edx, [ebx — ‘0’] ; Заносим в регистр EDX разность между кодом текущего символа и кодом символа ‘0’ cmp edx, 9 ; Сравниваем значение в регистре EDX с 9 ja M1 ; Если выше, то переходим к следующему сравнению sub bl, ‘0’ ; Иначе получаем число из кода символа jmp M3 ; Переходим к действиям, учитывающим текущую цифру M1: lea edx, [ebx — ‘a’] ; Заносим в регистр EDX разность между кодом текущего символа и кодом символа ‘a’ cmp edx, ‘f’ — ‘a’ ; Сравниваем значение в регистре EDX с 5 ja M2 ; Если выше, то переходим к следующему сравнению sub bl, ‘a’ — 10d ; Иначе получаем число из кода символа jmp M3 ; Переходим к действиям, учитывающим текущую цифру M2: lea edx, [ebx — ‘A’] ; Заносим в регистр EDX разность между кодом текущего символа и кодом символа ‘A’ cmp edx, ‘F’ — ‘A’ ; Сравниваем значение в регистре EDX с 5 ja L2 ; Если выше, то завершаем процедуру. Результат не определён, ; т.к. был введён некорректный символ sub bl, ‘A’ — 10d ; Иначе получаем число из кода символа M3: sal eax, 4 ; Умножаем EAX на 16 add eax, ebx ; Прибавляем текущую цифру inc esi ; Переходим к следующему символу dec ecx ; Уменьшаем ECX на 1 jnz L1 ; Если ECX не равно 0, продолжаем цикл L2: pop esi ; Восстанавливаем значения использовавшихся регистров pop ebx mov esp, ebp ; Освобождаем стек pop ebp ; Восстанавливаем значение регистра EBP ret InputNumber endp

  1. Процедура вывода числа в 16-ричной системе счисления. Процедура получает один параметр – выводимое число. Для вывода всегда формируется строка из 8-ми шестнадцатеричных цифр с лидирующими нулями. Поскольку количество символов заранее известно, они будут сразу же записываться в строку с конца, и инвертировать строку не придётся. Процедура предназначена для использования в консольном приложении и предполагает, что идентификатор устройства ввода был получен основной программой и сохранён в переменной hStdOut.

digits db ‘0123456789abcdef’ ; Массив шестнадцатеричных цифр OutputNumber proc push ebp ; Сохраняем в стеке значение регистра EBP mov ebp, esp ; Заносим в регистр EBP текущее значение вершины стека sub esp, 12 ; Выделяем в стеке место под формируемую строку push esi ; Преобразуем число в строку mov eax, [ebp + 8] ; Заносим в регистр EAX переданный параметр mov ecx, 8 ; Заносим в регистр ECX количество символов строки mov byte ptr [ebp — 1], 10 ; Добавляем в конец строки символы с кодами 13 и 10 для перевода курсора mov byte ptr [ebp — 2], 13 lea esi, [ebp — 3] ; Начиная с адреса [EBP — 3] будут заносится цифры L3: mov edx, eax ; Копируем значение регистра EAX в регистр EDX and edx, 1111b ; Получаем остаток от деления на 16 shr eax, 4 ; Делим исходное число на 16 mov dl, digits[edx] ; По полученному остатку от деления берём цифру . mov [esi], dl ; . и записываем её в строку dec esi ; Уменьшаем адрес, т.к. строка формируется с конца dec ecx ; Уменьшаем ECX на 1 jnz L3 ; Если ECX не равно 0, продолжаем цикл ; Выводим строку inc esi ; Регистр ESI указывает на начало строки push 0 push 0 push 10 push esi push hStdOut call WriteConsole pop esi mov esp, ebp ; Освобождаем стек pop ebp ; Восстанавливаем значение регистра EBP ret 4 ; Удаляем из стека переданный параметр и возвращаемся OutputNumber endp

  1. Функция, находящая в одномерном массиве x сумму значений f(x[i]), где f – некоторая функция одного целочисленного аргумента, адрес которой передаётся через параметры. Функции используют соглашение о вызовах cdecl.

Sum proc push ebp mov ebp, esp push esi push edi mov ecx, [ebp + 8] ; Заносим в ECX первый параметр – количество элементов массива mov esi, [ebp + 12] ; Заносим в ESI второй параметр – адрес начала массива mov edi, [ebp + 16] ; Заносим в EDI третий параметр – адрес функции xor edx, edx ; Обнуляем регистр EDX L: push [esi] ; Кладём в стек элемент массива call edi ; Вызываем функцию, адрес которой находится в регистре EDI add esp, 4 ; Освобождаем стек add edx, eax ; Прибавляем результат функции к общей сумме add esi, 4 ; Переходим к следующему элементу массива dec ecx ; Уменьшаем значение регистра ECX на 1 jnz L ; Если ECX не равно 0, продолжаем цикл mov eax, edx ; Записываем полученную сумму в регистр EAX, ; через который должен возвращаться результат функции pop edi pop esi mov esp, ebp pop ebp ret Sum endp Sqr proc mov eax, [esp + 4] imul eax ret Sqr endp Negation proc mov eax, [esp + 4] neg eax ret Negation endp

Для вызова функции Sum будет использовать следующая последовательность команд.

push Sqr push offset a push na call Sum add esp, 12 mov sa, eax push Negation push offset a push na call Sum add esp, 12 mov sa, eax

  1. Процедура, проверяющая сбалансированность круглых и квадратных скобок в строке. Строка должна заканчиваться нулём. Для проверки сбалансированности открывающие скобки будем класть в стек, а при нахождении в строке закрывающей скобки будем извлекать из стека последнюю положенную туда открывающую скобку и проверять, что она соответствует закрывающей скобке. Будем считать, что скобок в тексте меньше, чем других символов, поэтому после сравнения делаем переход «если равно», считая, что это событие менее вероятно. При любом выходе из процедуры нужно очистить стек. Поскольку мы не можем заранее знать, сколько скобок будет туда положено и сколько извлечено, восстановление значения регистра ESP можно сделать только с помощью регистра EBP. Процедура возвращает значение через регистр EAX: если скобки сбалансированы, регистр EAX будет содержать значение истина (-1), в противном случае регистр EAX будет содержать значение ложь (0).

Brackets proc push ebx ; Сохраняем регистры push ebp mov ebp, esp ; Сохраняем начальное значение регистра ESP mov ebx, [ebp + 12] ; Заносим в регистр EBX адрес начала строки mov eax, -1 ; Заносим в регистр EAX предварительное значение результата xor edx, edx ; Обнуляем регистр EDX L1: mov dl, [ebx] ; Заносим в регистр DL очередной символ test edx, edx ; Проверяем значение в регистре EDX jz E1 ; Если EDX = 0, выходим из цикла inc ebx ; Меняем адрес символа cmp dl, ‘(‘ ; Сравниваем символ с открывающей круглой скобкой je L2 ; Если равно, . cmp dl, ‘[‘ ; Сравниваем символ с открывающей квадратной скобкой je L2 ; Если равно, . cmp dl, ‘)’ ; Сравниваем символ с закрывающей круглой скобкой je L3 ; Если равно, переходим к сравнению со скобкой из стека cmp dl, ‘]’ ; Сравниваем символ с закрывающей квадратной скобкой je L4 ; Если равно, переходим к сравнению с другой скобкой из стека jmp L1 ; Если символ – не скобка, возвращаемся к началу цикла L2: push dx ; . заносим открывающую скобку в стек (один байт записать в стек нельзя) jmp L1 ; Возвращаемся к началу цикла L3: cmp ebp, esp ; Если была закрывающая скобка, прежде всего проверяем, есть ли скобки в стеке – ; если мы положили что-то в стек, значение регистра ESP будет отличаться от регистра EBP je E2 ; Если значения регистров равны, выходим из процедуры pop cx ; Извлекаем из стека последнюю открывающую скобку cmp cl, ‘(‘ ; Сравниваем jne E2 ; Если скобки не равны, выходим из процедуры jmp L1 ; Иначе возвращаемся к началу цикла L4: cmp ebp, esp ; При нахождении закрывающей квадратной скобки, je E2 ; выполняем те же действия, что и при нахождении закрывающей круглой скобки, pop cx ; только скобку из стека сравниваем с другим значением cmp cl, ‘[‘ ; Дублирование сделано для того, чтобы уменьшить jne E2 ; количество переходов jmp L1 E1: cmp ebp, esp ; При достижении конца строки, сравниваем регистры ESP и EBP je E3 ; Если значения равны, обходим обнуление регистра EAX E2: xor eax, eax ; Если была несбалансированность, обнуляем регистр EAX E3: mov esp, ebp ; Восстанавливаем значение регистра ESP pop ebp pop ebx ret Brackets endp

1 В защищённом режиме программе выделяется один сегмент размером 4 Гб для кода и один сегмент размером 4 Гб для данных (физически они обычно совпадают). Виртуальный адрес состоит из 16-битного значения, хранящегося в сегментном регистре, и 32-битного смещения. Однако преобразование виртуального адреса в физический осуществляется не путём сложения, а по более сложной схеме. Сначала процессор преобразует виртуальный адрес в линейный. При этом он обращается к таблицам дескрипторов, которые заранее строятся операционной системой. На втором этапе по линейному адресу определяется физический. В этом преобразовании участвует другой набор системных таблиц – таблицы страничной трансляции, которые также составляются операционной системой. Оба набора таблиц могут динамически меняться, обеспечивая максимальное использование оперативной памяти.

В сегментные регистры записываются не адреса сегментов, а селекторы, которые представляют собой номера ячеек специальной таблицы, содержащей дескрипторы сегментов программ. Каждый дескриптор хранит все характеристики, необходимые для обслуживания сегмента: базовый линейный адрес сегмента, границу сегмента (номер последнего байта), а также атрибуты сегмента, определяющие его свойства. Процессор с помощью селектора определяет индекс дескриптора адресуемого сегмента, извлекает из него базовый линейный 32-битный адрес сегмента и, сложив его с 32-битным смещением, получает линейный адрес адресуемой ячейки памяти. Получив линейный адрес адресуемого байта, процессор с помощью таблиц трансляции преобразует его в 32-битный физический адрес. Этот адрес зависит от объёма оперативной памяти, установленной на компьютере.

В 32-битной модели Windows предоставляет всем запущенным приложениям один и тот же селектор для сегмента кода и один и тот же селектор для сегмента данных. Базы обоих сегментов равны 0, а границы – FFFFFFFF. Другими слова, каждому приложению как бы предоставляется всё линейное пространство. Поскольку базовые линейные адреса сегментов программы равны 0, виртуальные смещения, с которыми работают приложения, совпадают с линейными адресами. Другими словами, плоское виртуальное адресное пространство программы совпадает с плоским линейным адресным пространством. При этом все приложения используют один и тот же диапазон линейных адресов. Для того чтобы при одинаковых линейных адресах приложения занимали различные участки физической памяти и не затирали друг друга, Windows при смене приложения изменяет таблицы страничной трансляции, с помощью которых как раз и происходит преобразование линейных адресов в физические.

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

3 Компьютерная программа в целом или её отдельная процедура называется реентерабельной (от англ. reentrant – повторно входимый), если она разработана таким образом, что одна и та же копия инструкций программы в памяти может быть совместно использована несколькими пользователями или процессами. При этом второй пользователь может вызвать реентерабельный код до того, как с ним завершит работу первый пользователь и это как минимум не должно привести к ошибке, а в лучшем случае не должно вызвать потери вычислений (то есть не должно появиться необходимости выполнять уже выполненные фрагменты кода).

Для обеспечения реентерабельности необходимо выполнение нескольких условий:

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

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

A1000 — Ошибка Assembler

Часовой пояс: UTC + 3 часа [ Летнее время ]

ПРЯМО СЕЙЧАС:

Информация

Запрошенной темы не существует.

Часовой пояс: UTC + 3 часа [ Летнее время ]

Экспертная система Delphi.int.ru

Сообщество программистов
Общение, помощь, обмен опытом

Delphi.int.ru Expert

Другие разделы портала

Переход к вопросу:

Статистика за сегодня:

Онлайн на сайте: 7 ( 3404)
Онлайн в IRC: 2 ( 17)
Последние новости:
30 апреля 2012
Последний вопрос:
29 ноября 2020, 09:46
Последний ответ:
29 марта 2020, 23:32

Лучшие эксперты

Вопрос # 3 190

Здравствуйте!
я использую masm 32 и у меня возникают проблемы с компиляцией: при вызове project>build all создается только объектный файл ( obj), а дальше в командной строке появляется сообщение fatal error A 1000: cannot open file. Что я делаю неправильно? Может, не настроила masm правильно? заранее спасибо.

Вопрос задала: darkness angel (статус: Посетитель)
Вопрос отправлен: 15 сентября 2009, 18:16
Состояние вопроса: открыт, ответов: .


Мини-форум вопроса

Вадим К (статус: Академик), 15 сентября 2009, 18:40 [#1] :

А какая среда используется для компиляции? параметры компиляции?

Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.

A1000 — Ошибка Assembler

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

7.1.1. ADD – команда для сложения двух чисел. Она работает как с числами со знаком, так и без знака.

Логика работы команды:

Возможные сочетания операндов для этой команды аналогичны команде MOV .

По сути дела, это – команда сложения с присвоением, аналогичная принятой в языке C / C ++:

Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда.

После выполнения команды изменяются флаги, по которым можно определить характеристики результата:

  1. Флаг CF устанавливается, если при сложении произошёл перенос из старшего разряда. Для беззнаковых чисел это будет означать, что произошло переполнение и результат получился некорректным.
  2. Флаг OF обозначает переполнение для чисел со знаком.
  3. Флаг SF равен знаковому биту результата (естественно, для чисел со знаком, а для беззнаковых он равен старшему биту и особо смысла не имеет).
  4. Флаг ZF устанавливается, если результат равен 0.
  5. Флаг PF — признак чётности, равен 1, если результат содержит нечётное число единиц.

add ax ,5 ; AX = AX + 5

add dx,cx ;DX = DX + CX

add dx,cl ;Ошибка: разный размер операндов.

7.1.2. SUB — команда для вычитания одного числа из другого. Она работает как с числами со знаком, так и без знака.

Логика работы команды:

Возможные сочетания операндов для этой команды аналогичны команде MOV .

По сути дела, это – команда вычитания с присвоением, аналогичная принятой в языке C / C ++:

Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда.

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

sub ax ,13 ; AX = AX — 13

sub ax , bx ; AX = AX + BX

sub b x,cl ;Ошибка: разный размер операндов.

7.1.3. Инкремент и декремент. Очень часто в программах используется операция прибавления или вычитания единицы. Прибавление единицы называется инкрементом, а вычитание — декрементом. Для этих операций существуют специальные команды процессора: INC и DEC. Эти команды не изменяют значение флага CF.

Эти команды содержит один операнд и имеет следующий синтаксис:

Логика работы команд:

В качестве инкремента допустимы регистры и память: reg , mem .

inc ax ; AX = AX + 1

dec ax ; AX = AX — 1

7.1.4. NEG – команда для изменения знака операнда.

Логика работы команды:

В качестве декремента допустимы регистры и память: reg , mem .

7.2. Сложение и вычитание с переносом.

В системе команд процессоров x86 имеются специальные команды сложения и вычитания с учётом флага переноса (CF). Для сложения с учётом переноса предназначена команда ADC, а для вычитания — SBB. В общем, эти команды работают почти так же, как ADD и SUB, единственное отличие в том, что к младшему разряду первого операнда прибавляется или вычитается дополнительно значение флага CF.

Они позволяют выполнять сложение и вычитание многобайтных целых чисел, длина которых больше, чем разрядность регистров процессора (в нашем случае 16 бит). Принцип программирования таких операций очень прост — длинные числа складываются (вычитаются) по частям. Младшие разряды складываются(вычитаются) с помощью обычных команд ADD и SUB, а затем последовательно складываются(вычитаются) более старшие части с помощью команд ADC и SBB. Так как эти команды учитывают перенос из старшего разряда, то мы можем быть уверены, что ни один бит не потеряется. Этот способ похож на сложение(вычитание) десятичных чисел в столбик.

На следующем рисунке показано сложение двух двоичных чисел командой ADD:

При сложении происходит перенос из 7-го разряда в 8-й, как раз на границе между байтами. Если мы будем складывать эти числа по частям командой ADD, то перенесённый бит потеряется и в результате мы получим ошибку. К счастью, перенос из старшего разряда всегда сохраняется в флаге CF. Чтобы прибавить этот перенесённый бит, достаточно применить команду ADC:

//Сложение двух чисел с учетом переноса: FFFFFFAA + FFFF

HackWare.ru

Этичный хакинг и тестирование на проникновение, информационная безопасность

Введение в Ассемблер [черновик]

[В ПРОЦЕССЕ НАПОЛНЕНИЯ И РЕДАКТИРОВАНИЯ]

Оглавление

Руководство по программированию на Ассемблер

1. Введение в Ассемблер

1.1 Для кого эти уроки по ассемблеру

Что нужно для изучения Ассемблера

Что такое язык Ассемблер?

Преимущества языка Ассемблер

2. Системы счисления

Основные характеристики аппаратной составляющей ПК

Двоичная система счисления

Шестнадцатеричная система счисления

Отрицательные двоичные числа

Адресация данных в памяти

3. Настройка рабочего окружения для Ассемблер

Настройка локального рабочего окружения

4. Основы синтаксиса Ассемблера

Компиляция и связывание (Linking) программы на Ассемблере в NASM

5. Ассемблер: сегменты памяти

6. Ассемблер: регистры (Registers)

7. Ассемблер: Системные вызовы

Системные вызовы Linux

8. Ассемблер: Режимы адресации

Адресации на регистр

Адресация на память

Прямая адресация со смещением

Косвенная адресация на память

9. Ассемблер: Переменные

Выделение пространства хранения для инициализированных данных

Выделение дискового пространства для неинициализированных данных

10. Ассемблер: Константы

11. Ассемблер: Арифметические инструкции

Инструкции ADD и SUB

12. Ассемблер: Логические инструкции

Инструкция AND (И)

13. Ассемблер: Условия

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

В целом материал является переводом «Assembly — Introduction» — небольшого учебника, в котором рассматриваются основы языка Ассемблер, но также имеются дополнения — некоторые вопросы рассмотрены более подробно.

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

Руководство по программированию на Ассемблер

Язык Ассемблер — это низкоуровневый язык программирования для компьютеров или других программируемых устройств, он специфичен для конкретной компьютерной архитектуры центрального процессора, что отличает его от большинства высокоуровневых языков программирования, которые обычно портативны среди разных систем. Язык Ассемблер преобразуется в исполняемый машинный код с помощью служебной программы, называемой ассемблером, такой как NASM, MASM и т. д.

Для кого эти уроки по ассемблеру

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

Что нужно для изучения Ассемблера

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

Что такое язык Ассемблер?

Каждый персональный компьютер имеет микропроцессор, который управляет арифметической, логической и контрольной активностью.

Каждая семья процессоров имеет свой собственный набор инструкций для обработки различных операций, таких как получения ввода с клавиатуры, отображение информации на экране и выполнения различных других работ. Этот набор инструкций называется «инструкции машинного языка» (‘machine language instructions’).

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

Преимущества языка Ассемблер

Знание языка ассемблера позволяет понять:

  • Как программы взаимодействуют с ОС, процессором и BIOS;
  • Как данные представлены в памяти и других внешних устройствах;
  • Как процессор обращается к инструкции и выполняет её;
  • Как инструкции получают доступ и обрабатывают данные;
  • Как программа обращается к внешним устройствам.

Другие преимущества использования ассемблера:

  • Программы на нём требует меньше памяти и времени выполнения;
  • Это упрощает сложные аппаратные задачи;
  • Подходит для работ, в которых время выполнения является критичным;
  • Он наиболее подходит для написания подпрограмм обработки прерываний и других программ, полностью находящихся в оперативной памяти.

Системы счисления

Основные характеристики аппаратной составляющей ПК

Каждый компьютер содержит процессор и оперативную память. Процессор содержит регистры — компоненты, которые содержат данные и адреса. Для выполнения программы, система копирует её с устройства постоянного хранения во внутреннюю память. Процессор выполняет инструкции программы.

Фундаментальной единицей компьютерного хранилища является бит. Он может быть в состоянии Включён (1) или Выключен (0). Группа из девяти связанных битов составляет байт, из которых восемь бит используются для данных, а последний используется для контроля чётности. Согласно правилу чётности, количество битов, которые Включены (1) в каждом байте, всегда должно быть нечётным.

Таким образом, бит чётности используется для того, чтобы сделать количество битов в байте нечётным. Если соотношение является чётным, система предполагает, то что произошла ошибка соотношения (хотя и редко), которая могла быть вызвана неисправностью оборудования или электрическими помехами.

Процессор поддерживает следующие размеры данных —

  • Word: 2-байтовый элемент данных
  • Doubleword: a 4-байтовый (32 бита) элемент данных
  • Quadword: 8-байтовый (64 бита) элемент данных
  • Paragraph: 16-байтовая (128 бита) область
  • Kilobyte: 1024 байт
  • Megabyte: 1,048,576 байт

Двоичная система счисления

В каждой системе счисления используются позиционные обозначения, то есть каждая позиция, в которой записана цифра, имеет различное позиционное значение. Каждая позиция — это степень базы, которая равна 2 для двоичной системы счисления, и эти степени начинаются с 0 и увеличиваются на 1.

В следующей таблице приведены позиционные значения для 8-битного двоичного числа, где все биты установлены в положение ON (Включено).

Значение бита 1 1 1 1 1 1 1 1
Значение позиции как степень основания 2 128 64 32 16 8 4 2 1
Номер бита 7 6 5 4 3 2 1

Значение двоичного числа, как и в десятичном, зависит от составляющих его цифр и расположения этих цифр. Но в двоичном числе используются только цифры 1 и 0, и расположение цифр имеет другое значение степени. Первая цифра, как и в десятичном числе, может означать 0 или 1. Вторая цифра (смотрим число справа на лево) может означать 2 (если этот бит установлен на 1) или 0 (если бит установлен на 0). Третья цифра (смотрим число справа на лево) может означать 4 (если этот бит установлен на 1) или 0 (если бит установлен на 0). И так далее. В десятичном числе значение каждого символа нужно умножить на 10 в степени порядкового номера этой цифры за минусом единицы.

То есть число 1337 это 1 * 10 3 + 3 * 10 2 + 3 * 10 1 + 7 * 10 0 = 1337

В двоичной системе всё точно также, только вместо десятки в степени порядкового номера за минусом единицы, нужно использовать двойку — вот и всё!

Допустим число 110101 и мы хотим узнать, сколько это будет в десятичной системе счисления, для этого достаточно выполнить следующее преобразование:

1 * 2 5 * + 1 * 2 4 + 0 * 2 3 + 1 * 2 2 + 0 * 2 1 + 1 * 2 0 = 1 * 32 + 1 * 16 + 0 * 8 + 1 * 4 + 0 * 2 + 1 * 1 = 53

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

1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255

Кстати, это то же самое, что и 2 8 — 1.

Шестнадцатеричная система счисления

Шестнадцатеричная система счисления использует основание 16. Цифры в этой системе варьируются от 0 до 15. По соглашению, буквы от A до F используются для представления шестнадцатеричных цифр, соответствующих десятичным значениям с 10 по 15.

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

Десятичное число Двоичный вид Шестнадцатеричный вид
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F

Чтобы преобразовать двоичное число в его шестнадцатеричный эквивалент, разбейте его на группы по 4 последовательные группы в каждой, начиная справа, и запишите эти группы в соответствующие цифры шестнадцатеричного числа.

Пример — двоичное число 1000 1100 1101 0001 эквивалентно шестнадцатеричному — 8CD1

Чтобы преобразовать шестнадцатеричное число в двоичное, просто запишите каждую шестнадцатеричную цифру в её 4-значный двоичный эквивалент.

Пример — шестнадцатеричное число FAD8 эквивалентно двоичному — 1111 1010 1101 1000

Отрицательные двоичные числа

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

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

Во-первых, нужно помнить, что если старшие биты (крайние слева), равны нулю, то их иногда не записывают. Например, восьмибитное число 10 (в десятичной системе счисления оно равно 2), также можно записать как 0000 0010. Обе эти записи означают число 2.

Если старший бит равен нулю, то это положительное число. Например, возьмём число 110. В десятичной системе счисления это 6. Данное число является положительным или отрицательным? На самом деле, однозначно на этот вопрос можно ответить только зная разрядность числа. Если это восьмиразрядное число, то его полная запись будет такой: 0000 0110. Как можно увидеть, старший бит равен нулю, следовательно, это положительное число.

Для трёхбитовых чисел было бы справедливо следующее:

Двоичное значение трёхбитового числа со знаком
(в представлении Дополнительный код)

Десятичное значение
000
1 001
2 010
3 011
-4 100
-3 101
-2 110
-1 111

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

Отрицательные двоичные числа записываются без знака минус и для получения этого же числа со знаком минус (то есть для получения числа в Дополненном коде) нужно выполнить два действия:

  1. нужно переписать его полную форму с противоположным значением битов (то есть для единиц записываются нули, а для нулей записываются единицы)
  2. и затем добавить к этому числу 1.
Число 53 00110101
Замена битов на противоположные 11001010
Добавляем 1 0000000 1
Число -53 11001011

На русском языке такая форма записи называется Дополнительный код, в англоязычной литературе это называется Two’s complement.

Примеры восьмибитного двоичного числа в Дополнительном коде (старший бит указывает на знак):

Десятичное значение Двоичное значение трёхбитового числа со знаком
(в представлении Дополнительный код)
0000 0000
1 0000 0001
2 0000 0010
126 0111 1110
127 0111 1111
−128 1000 0000
−127 1000 0001
−126 1000 0010
−2 1111 1110
−1 1111 1111

Двоичное представление (8 бит)

(в виде Дополнительного кода)


Десятичное
представление
127 0111 1111
1 0000 0001
0000 0000
-0
-1 1111 1111
-2 1111 1110
-3 1111 1101
-4 1111 1100
-5 1111 1011
-6 1111 1010
-7 1111 1001
-8 1111 1000
-9 1111 0111
-10 1111 0110
-11 1111 0101
-127 1000 0001
-128 1000 0000

Числа в дополненном коде удобно применять для вычитания — это будет показано далее.

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

Рассмотрим пример с числом -5. Запись отрицательного восьмибитного числа:

Инвертируем все разряды отрицательного числа -5, получая таким образом:

Добавив к результату 1 получим положительное число 5 в прямом коде:

И проверим, сложив с дополнительным кодом

0000 0101 + 1111 1011 = 1 0000 0000, десятый разряд выбрасывается, то есть получается 0000 0000, то есть 0. Следовательно, преобразование выполнено правильно, так как 5 + (-5) = 0.

Двоичная арифметика

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

(i) (ii) (iii) (iv)
1
1 1 1
+0 +0 +1 +1
=0 =1 =10 =11

Эту таблицу нужно читать по столбцам сверху вниз. В первом столбце складываются 0 и 0 — в результате получается 0. Во втором примере складываются 1 и 0 (или 0 и 1 — без разницы), в результате получается 1. В третьем столбце складываются две единицы — в результате в текущей позиции получается 0, но на одну позицию влево добавляется единица. Если в этой позиции уже есть единица — то применяется это же правило, то есть в позиции пишется 0, и 1 передаётся влево. В четвёртом примере складываются три единицы — в результате, в текущей позиции записывается 1, и ещё одна 1 передаётся влево.

Десятичные Двоичные
60 00111100
+42 00101010
102 01100110

Рассмотрим, как делается вычитание.

Для вычитания число, которое вычитается, записывается в форме Дополнительного кода, а затем эти два числа складываются.

Пример: Вычесть 42 из 53

Число 53 00110101
Число 42 00101010
Инвертируем биты 42 11010101
Добавляем 1 00000001
Число -42 11010110
Выполняем операцию: 53 — 42 = 11 00110101 + 11010110 = 100001011, то есть = 00001011

Бит который вызывает переполнение — крайней левый, девятый по счёту, просто отбрасывается.

Адресация данных в памяти

Процесс, посредством которого процессор управляет выполнением инструкций, называется циклом fetch-decode-execute (выборки-декодирования-выполнения) или циклом выполнения (execution cycle). Он состоит из трёх непрерывных шагов —

  • Извлечение инструкции из памяти
  • Расшифровка или идентификация инструкции
  • Выполнение инструкции

Процессор может одновременно обращаться к одному или нескольким байтам памяти. Давайте рассмотрим шестнадцатеричное число 0725H (буква H означает, что перед нами шестнадцатеричное число). Для этого числа потребуется два байта памяти. Байт старшего разряда или старший значащий байт — 07, а младший байт — 25.

Процессор хранит данные в последовательности обратного байта, то есть байт младшего разряда хранится в низком адресе памяти и байт старшего разряда в старшем адресе памяти. Таким образом, если процессор переносит значение 0725H из регистра в память, он сначала перенесёт 25 на нижний адрес памяти и 07 на следующий адрес памяти.

Когда процессор получает числовые данные из памяти для регистрации, он снова переворачивает байты. Есть два вида адресов памяти:

  • Абсолютный адрес — прямая ссылка на конкретное место.
  • Адрес сегмента (или смещение) — начальный адрес сегмента памяти со значением смещения.

Настройка рабочего окружения для Ассемблер

Настройка локального рабочего окружения

Язык ассемблера зависит от набора команд и архитектуры процессора. В этом руководстве мы сосредоточимся на процессорах Intel-32, таких как Pentium. Чтобы следовать этому уроку, вам понадобится:

  • ПК IBM или любой другой совместимый компьютер
  • Копия операционной системы Linux
  • Копия программы ассемблера NASM

Есть много хороших ассемблерных программ, таких как:

  • Microsoft Assembler (MASM)
  • Borland Turbo Assembler (TASM)
  • GNU ассемблер (GAS)

Мы будем использовать ассемблер NASM, так как он:

  • Бесплатный
  • Хорошо задокументированный — вы получите много информации в сети.
  • Может использоваться как в Linux, так и в Windows.

Установка NASM

Если вы выбираете «Инструменты разработки» при установке Linux, вы можете установить NASM вместе с операционной системой Linux, и вам не нужно загружать и устанавливать его отдельно. Чтобы проверить, установлен ли у вас NASM, сделайте следующее:

Откройте терминал Linux.

и нажмите клавишу ВВОД.

Если он уже установлен, появляется строка типа

В противном случае вы увидите просто

значит вам нужно установить NASM.

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

Например, для установки в Debian, Ubuntu, Linux Mint, Kali Linux и их производные выполните:

Для установки в Arch Linux, BlackArch и их производные выполните:

Чтобы установить NASM из исходного кода, сделайте следующее:

Проверьте веб-сайт ассемблера (NASM) на последнюю версию.

Загрузите исходный архив Linux nasm-X.XX.ta.gz, где X.XX — номер версии NASM в архиве.

Распакуйте архив в каталог, который создаст подкаталог nasm-X.XX.

Перейдите к nasm-X.XX

Этот скрипт оболочки найдёт лучший компилятор C для использования и сделает настройки в соответствии с Makefiles.

чтобы создать двоичные файлы nasm и ndisasm.

чтобы установить nasm и ndisasm в /usr/local/bin и установить справочные страницы (man).

Это должно установить NASM в вашей системе. Кроме того, вы можете использовать RPM-дистрибутив для Fedora Linux. Эта версия проще в установке, просто дважды щёлкните файл RPM.

Основы синтаксиса Ассемблера

Программу на языке Ассемблер можно разделить на три раздела:

  • Раздел data
  • Раздел bss
  • Раздел text

Раздел data

Раздел data используется для объявления инициализированных данных или констант. Эти данные не изменяются во время выполнения. В этом разделе вы можете объявить различные постоянные значения, имена файлов или размер буфера и т. д.

Синтаксис объявления раздела data:

Раздел BSS

Секция bss используется для объявления переменных. Синтаксис объявления раздела bss:

Раздел text

Раздел text используется для хранения самого кода. Этот раздел должен начинаться с объявления global _start, которое сообщает ядру, где начинается выполнение программы.

Синтаксис объявления раздела text:

Комментарии

Комментарий на ассемблере начинается с точки с запятой (;). Он может содержать любой печатный символ, включая пробел. Он может появиться в строке сам по себе, например:

или в той же строке вместе с инструкцией, например:

Операторы Ассемблера

Программы на ассемблере состоят из трёх типов операторов:

  • Исполняемые инструкции или инструкции,
  • Директивы ассемблера или псевдооперации (pseudo-ops), и
  • Макросы.

Исполняемые инструкции или просто инструкции говорят процессору, что делать. Каждая инструкция состоит из кода операции (opcode). Каждая исполняемая инструкция генерирует одну инструкцию на машинном языке.

Директивы ассемблера или псевдооперации говорят ассемблеру о различных аспектах процесса сборки. Они не являются исполняемыми и не генерируют инструкции машинного языка.

Макросы — это в основном механизм подстановки текста.

Синтаксис операторов ассемблера

Операторы языка ассемблера вводятся по одной инструкции в каждой строке. Каждое утверждение имеет следующий формат:

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

Ниже приведены некоторые примеры типичных операторов языка ассемблера.

Программа Hello World на Ассамблее

Следующий код на ассемблере выводит на экран строку «Hello World»:

Когда приведённый выше код скомпилирован и выполнен, он даст следующий результат:

Компиляция и связывание (Linking) программы на Ассемблере в NASM

Убедитесь, что вы установили путь до исполнимых файлов nasm и ld в вашей переменной окружения PATH (в Linux это уже сделано). Теперь пройдите следующие шаги для компиляции и связывания приведённой выше программы:

  • Наберите приведённый выше код используя текстовый редактор и сохраните как hello.asm.
  • Убедитесь, что вы в той же самой директории, где вы сохранили hello.asm.
  • Для сборки вашей программы выполните:
  • Если в коде присутствуют какие-либо ошибки, то на этом этапе вам будет выведено сообщение о них. Если ошибок нет, то будет создан объектный файл вашей программы с именем hello.o.
  • Для связывания объектного файла и создания исполнимого файла с именем hello выполните:

Выполните программу набрав:

Если вы всё сделали правильно, то она отобразит на экране ‘Hello, world!’.

Возможно, вас интересует, что такое связывание (Linking) и зачем оно требуется после сборки программы. Если коротко, то на этом этапе объектные файлы (если их несколько) собираются в один исполнимый файл, также благодаря этому процессу исполнимый файл теперь может использовать библиотеки. Линкеру указывается (обычно) целевой исполнимый формат. Если совсем коротко — это просто нужно. Я не буду в этом базовом курсе по ассемблеру останавливаться на этом более подробно — если вас интересует эта тема, то вы всегда сможете найти по ней дополнительную информацию в Интернете.

Ассемблер: сегменты памяти

Мы уже рассмотрели три раздела программы на ассемблере. Эти разделы также представляют различные сегменты памяти.

Обратите внимание, что если вы замените ключевое слово section на слово segment, вы получите тот же самый результат. Попробуйте этот код:

После компиляции и выполнения вышеприведённого кода он даст следующий результат:

Сегменты памяти

Модель сегментированной памяти делит системную память на группы независимых сегментов, на которые ссылаются указатели, расположенные в регистрах сегментов. Каждый сегмент используется для хранения данных определённого типа. Один сегмент используется для хранения кодов команд, другой — для хранения элементов данных, а третий — для программного стека.

В свете вышеизложенного мы можем выделить различные сегменты памяти, такие как:

  • Сегмент Data. Он представлен разделом .data и .bss. Раздел .data используется для объявления области памяти, где хранятся элементы данных для программы. Этот раздел не может быть расширен после объявления элементов данных, и он остаётся статическим во всей программе.
    Раздел .bss также является разделом статической памяти, который содержит буферы для данных, которые будут объявлены позже в программе. Эта буферная память заполнена нулями.
  • Сегмент Code. Он представлен разделом .text. Он определяет область в памяти, в которой хранятся коды команд. Это также фиксированная зона.
  • Stack — этот сегмент содержит значения данных, передаваемые функциям и процедурам в программе.

Ассемблер: регистры (Registers)

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

Для ускорения работы процессора процессор включает в себя несколько мест хранения внутренней памяти, называемых регистрами (registers).

Регистры хранят элементы данных для обработки без необходимости доступа к памяти. Ограниченное количество регистров встроено в чип процессора.

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

В архитектуре IA-32 имеется десять 32-разрядных и шесть 16-разрядных процессорных регистров. Регистры сгруппированы в три категории:

  • Общие регистры,
  • Регистры управления и
  • Сегментные регистры.

Общие регистры далее делятся на следующие группы:

  • Регистры данных,
  • Регистры указателя и
  • Индексные регистры.

Регистры данных

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

  • Как полные 32-битные регистры данных: EAX, EBX, ECX, EDX.
  • Нижние половины 32-битных регистров могут использоваться как четыре 16-битных регистра данных: AX, BX, CX и DX.
  • Нижняя и верхняя половины вышеупомянутых четырёх 16-битных регистров могут использоваться как восемь 8-битных регистров данных: AH, AL, BH, BL, CH, CL, DH и DL.

Некоторые из этих регистров данных имеют конкретное применение в арифметических операциях.

AX — основной аккумулятор; он используется во вводе/выводе и большинстве арифметических инструкций. Например, в операции умножения один операнд сохраняется в регистре EAX или AX или AL в соответствии с размером операнда.

BX известен как базовый регистр, поскольку его можно использовать при индексированной адресации.

CX известен как регистр подсчёта, так как регистры ECX, CX хранят счётчик циклов в итерационных операциях.

DX известен как регистр данных. Он также используется в операциях ввода/вывода. Он также используется с регистром AX вместе с DX для операций умножения и деления с большими значениями.

Регистры указателя

Регистры указателя являются 32-разрядными регистрами EIP, ESP и EBP и соответствующими 16-разрядными правыми частями IP, SP и BP. Есть три категории регистров указателей:

  • Указатель инструкций (IP) — 16-битный регистр IP хранит адрес смещения следующей команды, которая должна быть выполнена. IP вместе с регистром CS (как CS:IP) даёт полный адрес текущей инструкции в сегменте кода.
  • Указатель стека (SP) — 16-разрядный регистр SP обеспечивает значение смещения в программном стеке. SP в сочетании с регистром SS (SS:SP) относится к текущей позиции данных или адреса в программном стеке.
  • Базовый указатель (BP) — 16-битный регистр BP в основном помогает ссылаться на переменные параметра, передаваемые подпрограмме. Адрес в регистре SS объединяется со смещением в BP, чтобы получить местоположение параметра. BP также можно комбинировать с DI и SI в качестве базового регистра для специальной адресации.

Индексные регистры

32-разрядные индексные регистры ESI и EDI и их 16-разрядные крайние правые части. SI и DI, используются для индексированной адресации и иногда используются для сложения и вычитания. Есть два набора указателей индекса:

  • Исходный индекс (SI) — используется в качестве исходного индекса для строковых операций.
  • Указатель назначения (DI) — используется как указатель назначения для строковых операций.

Регистры управления

Регистр указателя 32-битной инструкции и регистр 32-битных флагов рассматриваются как регистры управления.

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

Популярные биты флага:

  • Флаг переполнения (OF) — указывает на переполнение старшего бита (крайнего левого бита) данных после арифметической операции со знаком.
  • Флаг направления (DF) — определяет направление влево или вправо для перемещения или сравнения строковых данных. Когда значение DF равно 0, строковая операция принимает направление слева направо, а когда значение равно 1, строковая операция принимает направление справа налево.
  • Флаг прерывания (IF) — определяет, будут ли игнорироваться или обрабатываться внешние прерывания, такие как ввод с клавиатуры и т. д. Он отключает внешнее прерывание, когда значение равно 0, и разрешает прерывания, когда установлено значение 1.
  • Trap Flag (TF) — позволяет настроить работу процессора в одношаговом режиме. Программа DEBUG, которую мы использовали, устанавливает флаг прерывания, чтобы мы могли пошагово пройтись по инструкциям — по одной инструкции за раз.
  • Флаг знака (SF) — показывает знак результата арифметической операции. Этот флаг устанавливается в соответствии со знаком элемента данных после арифметической операции. Знак указывается старшим левым битом. Положительный результат очищает значение SF до 0, а отрицательный результат устанавливает его в 1.
  • Нулевой флаг (ZF) — указывает результат арифметической операции или операции сравнения. Ненулевой результат очищает нулевой флаг до 0, а нулевой результат устанавливает его в 1.
  • Вспомогательный флаг переноса (AF) — содержит перенос с бита 3 на бит 4 после арифметической операции; используется для специализированной арифметики. AF устанавливается, когда 1-байтовая арифметическая операция вызывает перенос из бита 3 в бит 4.
  • Флаг чётности (PF) — указывает общее количество 1-битов в результате, полученном в результате арифметической операции. Чётное число 1-бит очищает флаг чётности до 0, а нечётное число 1-битов устанавливает флаг чётности в 1.
  • Флаг переноса (CF) — содержит перенос 0 или 1 из старшего бита (крайнего слева) после арифметической операции. Он также хранит содержимое последнего бита операции shift или rotate.

В следующей таблице указано положение битов флага в 16-битном регистре флагов:

Флаг: O D I T S Z A P C
Номер бита: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

Сегментные регистры

Сегменты — это специальные области, определённые в программе для хранения данных, кода и стека. Есть три основных сегмента:

  • Сегмент Code — содержит все инструкции, которые должны быть выполнены. 16-битный регистр сегмента кода или регистр CS хранит начальный адрес сегмента кода.
  • Сегмент Data — содержит данные, константы и рабочие области. 16-битный регистр сегмента данных или регистр DS хранит начальный адрес сегмента данных.
  • Сегмент Stack — содержит данные и адреса возврата процедур или подпрограмм. Он реализован в виде структуры данных стека. Регистр сегмента стека или регистр SS хранит начальный адрес стека.

Помимо регистров DS, CS и SS существуют и другие регистры дополнительных сегментов — ES (дополнительный сегмент), FS и GS, которые предоставляют дополнительные сегменты для хранения данных.

При программировании на ассемблере программе необходим доступ к ячейкам памяти. Все области памяти в сегменте относятся к начальному адресу сегмента. Сегмент начинается с адреса, равномерно делимого на 16 или в шестнадцатеричном виде числа 10. Таким образом, крайняя правая шестнадцатеричная цифра во всех таких адресах памяти равна 0, что обычно не сохраняется в регистрах сегментов.

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


Пример

Посмотрите на следующую простую программу, чтобы понять использование регистров в программировании на Ассемблере. Эта программа отображает 9 звёзд на экране вместе с простым сообщением.

После компиляции и выполнения эта программа выведет:

Ассемблер: Системные вызовы

Системные вызовы — это API для интерфейса между пространством пользователя и пространством ядра. Мы уже использовали системные вызовы sys_write и sys_exit для записи на экран и выхода из программы соответственно.

Системные вызовы Linux

Вы можете использовать системные вызовы Linux в ваших ассемблерных программах. Для использования системных вызовов Linux в вашей программе необходимо выполнить следующие шаги:

  • Поместите номер системного вызова в регистр EAX.
  • Сохраните аргументы системного вызова в регистрах EBX, ECX и т. д.
  • Вызовите соответствующее прерывание (80h).
  • Результат обычно возвращается в регистр EAX.

Существует шесть регистров, в которых хранятся аргументы используемого системного вызова. Это EBX, ECX, EDX, ESI, EDI и EBP. Эти регистры принимают последовательные аргументы, начиная с регистра EBX. Если существует более шести аргументов, ячейка памяти первого аргумента сохраняется в регистре EBX.

В следующем фрагменте кода показано использование системного вызова sys_exit:

В следующем фрагменте кода показано использование системного вызова sys_write:

Все системные вызовы перечислены в /usr/include/asm/unistd.h вместе с их номерами (значение, которое нужно указать в EAX перед вызовом int 80h). Точнее говоря, сейчас это файлы /usr/include/asm/unistd_32.h и /usr/include/asm/unistd_64.h.

Чтобы посмотреть содержимое файла /usr/include/asm/unistd_32.h:

Начало этого файла:

Чтобы получить справку по системным вызовам:

Чтобы получить справку по конкретному вызову, укажите вначале man 2, а затем название вызова. Например, чтобы узнать о вызове read:

Чтобы узнать о вызове mkdir:

В следующей таблице приведены некоторые системные вызовы, используемые в этом руководстве:

%eax Имя %ebx %ecx %edx %esx %edi
1 sys_exit int (целое число)
2 sys_fork struct pt_regs
3 sys_read unsigned int (целое беззнаковое число) char * size_t
4 sys_write unsigned int (целое беззнаковое число) const char * size_t
5 sys_open const char * int (целое число) int (целое число)
6 sys_close unsigned int (целое беззнаковое число)

Пример

Следующий пример читает число с клавиатуры и отображает его на экране:

Скомпилированный и запущенный вышеприведённый код даёт следующий результат:

Ассемблер: Режимы адресации

Большинство инструкций на ассемблере требуют обработки операндов. Адрес операнда предоставляет место, где хранятся данные, подлежащие обработке. Некоторые инструкции не требуют операнда, в то время как некоторые другие инструкции могут требовать один, два или три операнда.

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

Три основных режима адресации:

  • Адресации на регистр
  • Немедленная адресация
  • Адресация на память

Адресации на регистр

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

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

Немедленная адресация

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

Адресация на память

Когда операнды указываются в режиме адресации на память, требуется прямой доступ к основной памяти, обычно к сегменту данных. Этот способ адресации приводит к более медленной обработке данных. Чтобы найти точное местоположение данных в памяти, нам нужен начальный адрес сегмента, который обычно находится в регистре DS, и значение смещения. Это значение смещения также называется действующим адресом (effective address).

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

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

Прямая адресация со смещением

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

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

Косвенная адресация на память

В этом режиме адресации используется способность компьютера Segment:Offset (Сегмент:Смещение). Обычно для этой цели используются базовые регистры EBX, EBP (или BX, BP) и регистры индекса (DI, SI), закодированные в квадратных скобках для ссылок на память.

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

В следующем фрагменте кода показано, как получить доступ к различным элементам переменной.

Инструкция MOV

Мы уже задействовали инструкцию MOV, которая используется для перемещения данных из одного пространства хранения в другое. Инструкция MOV принимает два операнда.

Синтаксис

Синтаксис инструкции MOV:

Инструкция MOV может иметь одну из следующих пяти форм:

Пожалуйста, обратите внимание, что:

  • Оба операнда в операции MOV должны быть одинакового размера
  • Значение исходного операнда остаётся неизменным

Инструкция MOV порой вызывает двусмысленность. Например, посмотрите на утверждения:

Не ясно, хотите ли вы переместить байтовый эквивалент или словесный эквивалент числа 110. В таких случаях целесообразно использовать спецификатор типа (type specifier).

В следующей таблице приведены некоторые общие спецификаторы типов:

Спецификатор типа Байты
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10

Пример

Следующая программа иллюстрирует некоторые из концепций, обсуждённых выше. Он сохраняет имя «Zara Ali» в разделе данных памяти, затем программно меняет его значение на другое имя «Nuha Ali» и отображает оба имени.

Когда приведённый выше код скомпилирован и выполнен, он даёт следующий результат:

Ассемблер: Переменные

NASM предоставляет различные директивы определения (define directives) для резервирования места для хранения переменных. Директива определения ассемблера используется для выделения пространства хранения. Его можно использовать для резервирования, а также для инициализации одного или нескольких байтов.

Выделение пространства хранения для инициализированных данных

Синтаксис для оператора распределения памяти для инициализированных данных:

Где имя-переменной — это идентификатор для каждого пространства хранения. Ассемблер связывает значение смещения для каждого имени переменной, определённого в сегменте данных.

Существует пять основных форм директивы определения:

Директива Цель Размер хранения
DB Определить Byte выделяет 1 байт
DW Определить Word выделяет 2 байта
DD Определить Doubleword выделяет 4 байта
DQ Определить Quadword выделяет 8 байта
DT Определить Ten Bytes выделяет 10 байта

Ниже приведены некоторые примеры использования директив определения.

Пожалуйста, обратите внимание, что:

  • Каждый байт символа хранится как его значение ASCII в шестнадцатеричном формате.
  • Каждое десятичное значение автоматически преобразуется в его 16-разрядный двоичный эквивалент и сохраняется в виде шестнадцатеричного числа.
  • Процессор использует little-endian порядок байтов.
  • Отрицательные числа преобразуются в его представление Дополнительный код (рассмотрен выше).
  • Короткие и длинные числа с плавающей запятой представлены с использованием 32 или 64 бит соответственно.

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

Когда приведённый выше код компилируется и выполняется, он даёт следующий результат:

Выделение дискового пространства для неинициализированных данных

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

Существует пять основных форм директив резервирования:

Директива Цель
RESB Зарезервировать Byte
RESW Зарезервировать Word
RESD Зарезервировать Doubleword
RESQ Зарезервировать Quadword
REST Зарезервировать 10 байт

Множественность определений

Вы можете иметь несколько операторов определения данных в программе. Например:

Ассемблер выделяет непрерывную память для нескольких определений переменных.

Множественность инициализаций

Директива TIMES позволяет выполнить несколько инициализаций к одному и тому же значению. Например, массив с именем marks размера 9 может быть определён и инициализирован на начальное значение ноль с помощью следующего оператора:

Директива TIMES полезна при определении массивов и таблиц. Следующая программа отображает 9 звёздочек на экране:

Результат выполнения скомпилированной программы:

Ассемблер: Константы

NASM предоставляет несколько директив, определяющих константы. Мы уже использовали директиву EQU в предыдущих разделах. Особое внимание мы уделим трём директивам:

  • EQU
  • %assign
  • %define

Директива EQU

Директива EQU используется для определения констант. Синтаксис директивы EQU следующий:

Затем вы можете использовать это постоянное значение в вашем коде, например:

Операндом оператора EQU может быть выражение:

Приведённый фрагмент кода определит AREA как 200.

Пример

Следующий пример иллюстрирует использование директивы EQU:

Скомпилированный и выполненный код даст следующие результаты:

Кстати, в коде программы мы использовали 0xA,0xD в качестве части строк. Точнее говоря, в качестве окончания строк. Как можно догадаться, это шестнадцатеричные цифры. При выводе на экран эти шестнадцатеричные цифры трактуются как коды символов ASCII. То есть, чтобы понять их значение, нужно заглянуть в таблицу ASCII символов, например в статье «ASCII и шестнадцатеричное представление строк. Побитовые операции со строками».

Там мы можем найти, что 0xA (в той таблице он обозначен как 0A) и означает он перевод строки. Во многих языках программирования символ обозначается как «\n». Нажатие на клавишу ↵ Enter при выводе текста переводит строку.

Что касается 0xD (там в таблице он обозначен как 0D) и означает enter / carriage return — возврат каретки. Во многих языках программирования — символ «CR» обозначается как «\r».

Итак, если вы программируете на каком либо языке, то последовательность из двух шестнадцатеричных чисел 0xA,0xD, соответствует последовательности «\n\r», то есть, упрощённо говоря, это универсальный способ (чтобы срабатывал и в Linux, и в Windows) перейти на новую строку.

Директива %assign

По аналогии с директивой EQU, директива %assign может использоваться для определения числовых констант. Эта директива допускает переопределение. Например, вы можете определить постоянную TOTAL следующим образом:

Позже в коде вы можете переопределить её так:

Эта директива чувствительна к регистру.

Директива %define

Директива %define позволяет определять как числовые, так и строковые константы. Эта директива похожа на #define в C. Например, вы можете определить постоянную PTR так:

Приведённый выше код заменяет PTR на [EBP+4].

Эта директива также допускает переопределение и учитывает регистр.

Ассемблер: Арифметические инструкции

Инструкция INC

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

Инструкция INC имеет следующий синтаксис:

Операндом может быть 8-битный, 16-битный или 32-битный операнд.

Инструкция DEC

Инструкция DEC используется для уменьшения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.

Инструкция DEC имеет следующий синтаксис:

Операндом может быть 8-битный, 16-битный или 32-битный операнд.

Инструкции ADD и SUB

Команды ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером в byte, word и doubleword, т.е. для сложения или вычитания 8-битных, 16-битных или 32-битных операндов соответственно.

Инструкции ADD и SUB имеют следующий синтаксис:

Инструкция ADD/SUB может выполняться между:

  • Регистр к регистру
  • Память к регистру
  • Регистр к памяти
  • Регистр к константе
  • Память к константе

Однако, как и другие инструкции, операции с память-в-память невозможны с использованием инструкций ADD/SUB. Операция ADD или SUB устанавливает или очищает флаги переполнения (overflow) и переноса (carry).

Пример

В следующем примере программа спросит у пользователя две цифры; сохранит их в регистрах EAX и EBX, соответственно; сложит эти значения; сохранит результат в ячейке памяти «res» и, наконец, отобразит результат.

Скомпилированный и выполненный код даст следующие результаты:

Код программы сильно упрощается, если прописать значения переменных для арифметических действий прямо в самом коде:

Результат выполнения этого кода:

Инструкции MUL/IMUL

Есть две инструкции для умножения двоичных данных. Инструкция MUL (Multiply) обрабатывает беззнаковые данные, а IMUL (Integer Multiply) обрабатывает данные со знаком. Обе инструкции влияют на флаг переноса и переполнения.

Синтаксис для инструкций MUL/IMUL следующий:

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

Когда перемножаются два байта

Множимое находится в регистре AL, а множитель — это байт в памяти или в другом регистре. Результат произведения находится в AX. Старшие 8 битов произведения хранятся в AH, а младшие 8 битов хранятся в AL.

Когда умножаются два значения word

Множимое должно быть в регистре AX, а множитель — это word в памяти или в другом регистре. Например, для такой инструкции, как MUL DX, вы должны сохранить множитель в DX и множимое в AX.

В результате получается двойное word, для которого понадобятся два регистра. Часть высшего порядка (крайняя слева) сохраняется в DX, а часть нижнего порядка (крайняя справа) сохраняется в AX.

Когда умножаются два значения doubleword

Когда умножаются два значения doubleword, множимое должно быть в EAX, а множитель — это значение doubleword, хранящееся в памяти или в другом регистре. Результат умножения сохраняется в регистрах EDX:EAX, то есть 32-разрядные старшие разряды сохраняются в регистре EDX, а 32-разрядные младшие разряды сохраняются в регистре EAX.

В следующем примере 3 умножается на 2 и отображается результат:

Результат выполнения программы:

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

Инструкция DIV (Divide) используется для данных без знака, а IDIV (Integer Divide) используется для данных со знаком.

Формат для инструкции DIV/IDIV:

Делимое находится в аккумуляторе. Обе инструкции могут работать с 8-битными, 16-битными или 32-битными операндами. Операция влияет на все шесть флагов состояния. Следующий раздел объясняет три случая деления с различным размером операнда:

Номер Сценарии
1


Когда делитель равен 1 байту

Предполагается, что делимое находится в регистре AX (16 бит). После деления частное переходит в регистр AL, а остаток — в регистр AH.

Когда делителем является 1 word

Предполагается, что делимое имеют длину 32 бита и оно размещено в регистрах DX:AX. Старшие 16 битов находятся в DX, а младшие 16 битов — в AX. После деления 16-битное частное попадает в регистр AX, а 16-битное значение попадает в регистр DX.

Когда делитель doubleword

Предполагается, что размер делимого составляет 64 бита и оно размещено в регистрах EDX:EAX. Старшие 32 бита находятся в EDX, а младшие 32 бита находятся в EAX. После деления 32-битное частное попадает в регистр EAX, а 32-битный остаток попадает в регистр EDX.

В следующем примере 8 делится на 2. Делимое 8 сохраняется в 16-битном регистре AX, а делитель 2 сохраняется в 8-битном регистре BL.

Результат выполнения приведённого выше кода:

Ассемблер: Логические инструкции

Набор команд процессора содержит инструкции логики AND, OR, XOR, TEST и NOT, которые проверяют, устанавливают и очищают биты в соответствии с потребностями программы.

Формат для этих инструкций:

Номер Сценарии
1
Номер Инструкция Формат
1 AND AND операнд1, операнд2
2 OR OR операнд1, операнд2
3 XOR XOR операнд1, операнд2
4 TEST TEST операнд1, операнд2
5 NOT NOT операнд1

Первый операнд во всех случаях может быть либо в регистре, либо в памяти. Второй операнд может быть либо в регистре/памяти, либо в непосредственном (постоянном) значении. Однако операции память-и-память невозможны. Эти инструкции сравнивают или сопоставляют биты операндов и устанавливают флаги CF, OF, PF, SF и ZF.

Инструкция AND (И)

Инструкция AND используется для поддержки логических выражений путём выполнения побитовой операции AND. Побитовая операция AND возвращает 1, если совпадающие биты обоих операндов равны 1, в противном случае она возвращает 0. Например:

Операция AND может использоваться для очистки одного или нескольких битов. Например, допустим, регистр BL содержит 0011 1010. Если вам нужно очистить старшие биты до нуля, то вы выполняете операцию AND этого регистра с 0FH.

Давайте рассмотрим другой пример. Если вы хотите проверить, является ли данное число нечётным или чётным, простой тест будет проверять младший значащий бит числа. Если это 1, число нечётное, иначе число чётное.

Предполагая, что номер находится в регистре AL, мы можем написать:

Следующая программа иллюстрирует это.

Результат выполнения кода:

Измените значение в регистре ax на нечётную цифру, к примеру:

Программа будет отображать:

Точно так же очистить весь регистр вы можете сделав AND с 00H.

Инструкция OR

Инструкция OR (ИЛИ) используется для выполнения логической побитовой операции OR. Побитовый оператор OR возвращает 1, если совпадающие биты одного или обоих операндов равны единице. Возвращает 0, если оба бита равны нулю.

Операция OR может использоваться для установки одного или нескольких битов. Например, предположим, что регистр AL содержит 0011 1010, вам нужно установить на единицы четыре младших бита, тогда вы можете сделать OR со значением 0000 1111, т.е.

В следующем примере демонстрируется инструкция OR. Давайте сохраним значения 5 и 3 в регистрах AL и BL, соответственно, затем

затем в регистре AL в результате выполнения операции OR получится 7

Результат работы программы:

Инструкция XOR

Инструкция XOR реализует побитовую операцию XOR. Операция XOR устанавливает результирующий бит в 1, если и только если биты из операндов отличаются. Если биты из операндов одинаковы (оба 0 или оба 1), результирующий бит сбрасывается в 0.

XOR операнд числа с самим собой меняет операнд на 0. Это используется для очистки регистра.

Инструкция TEST

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

Инструкция NOT

Инструкция NOT реализует побитовую операцию NOT. Операция NOT меняет биты в операнде на противоположные. Операнд может быть либо в регистре, либо в памяти.

Ассемблер: Условия

Выполнение в зависимости от выполнения условия на ассемблере реализовано несколькими инструкциями зацикливания и ветвления. Эти инструкции могут изменить поток управления в программе. Условное исполнение рассматривается в двух сценариях:

Безусловный прыжок

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

Условный переход

Он выполняется с помощью набора инструкций перехода j и зависит от выполнения условия. Условные инструкции передают управление, прерывая последовательный поток, и делают это, изменяя значение смещения в IP.

Давайте обсудим инструкцию CMP, прежде чем обсуждать условные инструкции.

Инструкция CMP

Инструкция CMP сравнивает два операнда. Обычно используется в условном исполнении. Эта инструкция в основном вычитает один операнд из другого для сравнения, равны ли операнды или нет. Она не мешает операндам назначения или источника. Она используется вместе с инструкцией условного перехода для принятия решения.

CMP сравнивает два числовых поля данных. Операнд-адресат может быть либо в регистре, либо в памяти. Исходным операндом могут быть постоянные (непосредственные) данные, регистр или память.

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

Безусловный переход

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

Инструкция JMP предоставляет имя метки, куда поток управления передаётся немедленно. Синтаксис инструкции JMP:

Следующий фрагмент кода иллюстрирует инструкцию JMP:

Условный переход

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

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

Инструкции условия
1
Инструкция Описание Тестируемые флаги
JE/JZ Jump Equal or Jump Zero (равно или ноль) ZF
JNE/JNZ Jump not Equal or Jump Not Zero (не равно или не ноль) ZF
JG/JNLE Jump Greater or Jump Not Less/Equal (больше или не меньше/равно) OF, SF, ZF
JGE/JNL Jump Greater/Equal or Jump Not Less (больше/равно или не меньше) OF, SF
JL/JNGE Jump Less or Jump Not Greater/Equal (меньше или не больше/равно) OF, SF
JLE/JNG Jump Less/Equal or Jump Not Greater (меньше/равно или не больше) OF, SF, ZF

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

Инструкция Описание Тестируемые флаги
JE/JZ Jump Equal или Jump Zero (равно или ноль) ZF
JNE/JNZ Jump not Equal или Jump Not Zero (не равно или не ноль) ZF
JA/JNBE Jump Above или Jump Not Below/Equal (больше или не меньше/равно) CF, ZF
JAE/JNB Jump Above/Equal или Jump Not Below (больше/равно или не меньше) CF
JB/JNAE Jump Below или Jump Not Above/Equal (меньше или не больше/равно) CF
JBE/JNA Jump Below/Equal или Jump Not Above (меньше/равно или не больше) AF, CF

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

Инструкция Описание Тестируемый флаг
JXCZ Переход если CX равен нулю нет
JC Переход если Перенос CF
JNC Переход если нет Переноса CF
JO Переход если переполнение OF
JNO Переход если нет переполнения OF
JP/JPE Переход при наличии чётности PF
JNP/JPO Переход при отсутствии чётности PF
JS Переход при наличии знака (отрицательная величина) SF
JNS Переход при отсутствии знака (положительная величина) SF

Синтаксис для набора инструкций J :

Пример

Следующая программа отображает наибольшую из трёх переменных. Переменные являются двузначными переменными. Три переменные num1, num2 и num3 имеют значения 47, 22 и 31 соответственно:

Программные сегменты. Директива ASSUME

Структура программы на ассемблере

Рассмотрим, как правильно оформлять последовательность команд, чтобы транслятор мог их обработать, а микропроцессор выполнить.

Для того чтобы указать, что некоторая группа предложений программы на ассемблере образуют единый сегмент памяти, они оформляются как программный сегмент: перед ними ставится директива SEGMENT с операндами, после них — директива ENDS, причем в начале обеих этих директив должно быть указано одно и то же имя, играющее роль имени сегмента. Программа же в целом представляет собой последовательность таких программных сегментов, в конце которой указывается директива конца программы END, например:

DT1 SEGMENT PARA PUBLIC ‘DATA’ ; сегмент данных с именем DT1

CODE SEGMENT ; кодовый сегмент CODE

ASSUME CS:CODE, DS:DT1

MOV AX,DT1 ;инициализация сегмента

MOV DS,AX ;данных

MOV AX,4C00H ; выход из

INT 21H ; программы

MAIN ENDP ; конец процедуры MAIN

CODE ENDS ; конец сегмента кода

END MAIN ;конец программы

Важно отметить, что функциональное назначение сегмента несколько шире, чем просто разбиение программ на блоки кода, данных и стека. Сегментация является частью более общего механизма, связанного с концепцией модульного программирования, она предполагает унификацию оформления объектных модулей, создаваемых компилятором, в том числе с разных языков программирования. Это позволяет объединять программы, написанные на разных языках. Именно для реализации различных вариантов такого объединения и предназначены операнды в директиве SEGMENT:

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

BYTE – выравнивание не выполняется. Сегмент может начинаться с любого адреса.

WORD – сегмент начинается по адресу, кратному двум.

DWORD – сегмент начинается по адресу кратному четырем.

PARA — сегмент начинается по адресу, кратному 16. Принимается по умолчанию.

PAGE -сегмент начинается по адресу кратному 256.

NEMPAGE – сегмент начинается по адресу, кратному 4 Кбайт.

Следующий атрибут комбинирования сегментов(комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющих одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE. Атрибуты комбинирования могут быть следующими:

PRIVATE – сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля.

PUBLIC – компоновщик соединит все сегменты с одинаковыми именами.

COMMON – располагает все сегменты с одним и тем же именем по одному адресу.

AT xxxx – располагает сегмент по абсолютному адресу параграфа. Абсолютный адрес параграфа задается выражением xxxx.

STACK – определение сегмента стека. Заставляет компоновщик соединять все одноименные сегменты и вычислять адреса этих сегментов относительно регистра SS.

Атрибут класса сегмента (тип класса) – это заключенная в кавычки строка, помогающая компоновщику определить соответствующий порядок следования сегментов при объединении программ.

Все ссылки на предложения одного программного сегмента ассемблер сегментирует по умолчанию по одному и тому же сегментному регистру. По какому именно — устанавливается специальной директивой ASSUME. В нашем примере эта директива определяет, что все ссылки на сегмент CODE должны, если явно не указан сегментный регистр, сегментироваться по регистру CS, все ссылки на DT1 — по регистру DS, а все ссылки на DT2 — по регистру ES.

Встретив в тексте программы ссылку на какое-либо имя (например, на имя C в команде MOV AX,A), ассемблер определяет, в каком программном сегменте оно описано (у нас — в DT2). Затем по информации из директивы ASSUME узнает, какой сегментный регистр поставлен в соответствие этому сегменту (у нас — это ES), и далее образует адресную пару из данного регистра и смещения имени (у нас — ES:0), которую и записывает в формируемую машинную команду.

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

Директива ASSUME должна быть указана перед первой командой программы. В директиве ASSUME следует каждому сегменту ставить в соответствие сегментный регистр.

3.2.2 Начальная загрузка сегментных регистров

Директива ASSUME сообщает ассемблеру о том, по каким регистрам он должен сегментировать имена из каких сегментов, и «обещает», что в этих регистрах будут находиться начальные адреса этих сегментов. Однако загрузку этих адресов в регистры сама директива не осуществляет.

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

Поскольку в ассемблере нет команды пересылки непосредственного операнда в сегментный регистр (а имя, т.е. начало, сегмента — это непосредственный операнд), то такую загрузку приходится делать через какой-то другой, несегментный, регистр (например, AX):

MOV AX,DT1 ;AX:=начало сегмента DT1

Аналогично загружается и регистр ES.

Загружать регистр CS в начале программы не надо: он, как и счетчик команд IP, загружается операционной системой перед тем, как начинается выполнение программы (иначе нельзя было бы начать ее выполнение). Что же касается регистра SS, используемого для работы со стеком, то он может быть загружен так же, как и регистры DS и ES.

Это называется стандартными директивами сегментации.

3.2.3 Упрощенная директива сегментации

Для простых программ, содержащих по одному сегменту кода, данных и стека возможно использовать упрощенную директиву сегментации. Совместно с упрощенными директивами сегментации используется директива указания модели памяти MODEL, которая управляет размещением сегментов и выполняет функцию директивы ASSUME. Эта директива связывает сегменты с предопределенными именами.

Существуют модели памяти:

tiny и код и данные программы должны помещаться внутри

одного 64 Кбайтного сегмента. И код и данные —

small код программы должен помещаться внутри одного

сегмента в 64 К, и данные должны помещаться внутри

другого 64 К сегмента. И код и данные — ближние.

medium код программы может быть больше 64 К, а данные

должны помещаться внутри 64 К сегмента. Код —

дальний, а данные — ближние.

compact программный код должен помещаться внутри 64 К сегмента, а данные могут быть больше 64 К. Код — ближний, а данные — дальние. Любой массив данных не может быть больше 64 К.

large и код и данные могут быть больше 64 К, но ни один массив данных не может быть больше 64 К. И код и данные — дальние.

huge и код и данные могут быть больше 64 К, и массив данных может быть больше 64 К. И код и данные -дальние. Указатели на элементы внутри массива — дальние.

Заметим, что с точки зрения ассемблера, large и huge идентичны. Модель huge не поддерживает автоматически массивы данных больше 64 К.

Немногие ассемблерные программы требуют более 64 К кода или данных, поэтому модель smALl оптимальна для большинства программ. Вы должны использовать модель smALl, где это возможно, т.к. дальний код (модели medium, large и huge) делают выполнение программы более медленным; дальние данные (модели compact, large и huge) значительно труднее обрабатывать на ассемблере.

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

Директива MODEL требуется, если Вы используете упрощенные директивы сегментации, иначе Турбо-Ассемблер не будет знать, как установить сегменты, определенные с .CODE и .DATA. MODEL должна предшествовать директивам .CODE, .DATA, .STACK.

Приведем заготовку программы, использующей упрощенные директивы сегментации:

MASM ; режим работы TASM

MODEL SMALL ;модель памяти

.DATA ;сегмент данных

.STACK ;сегмент стека

.CODE ; сегмент кода

MOV AX,@DATA ; инициализация сегмента

MOV DS,AX ; данных

MOV AX,4C00H ;выход

INT 21H ;из программы

MAIN ENDP ;конец процедуры

ENDMAIN ;конец программы с точкой выхода main

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

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

.FARDATA позволяет Вам определить дальний сегмент данных, т.е. сегмент данных, отличный от стандартного сегмента @data, используемого всеми модулями. .FARDATA позволяет ассемблерному модулю определить его собственный сегмент данных размером более 64 К. Если .FARDATA была использована, @fardata — это имя для дальнего сегмента данных, указанного этой директивой, так же как @data — это имя сегмента данных, указанного .DATA.

.FARDATA? во многом подобна .FARDATA за исключением того, что она определяет неинициализированный дальний сегмент. Как для .FARDATA и @fardata, если была задана директива .FARDATA?, @fardata? — это имя для дальнего сегмента данных, указанного этой директивой.

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

Когда используются упрощенные директивы сегментации, доступны некоторые предопределенные метки. @FileName — это имя ассемблируемого файла. @curseg — это имя сегмента, который сейчас ассемблируется Турбо-Ассемблером. @CodeSIze — 0 для моделей памяти с ближними кодовыми сегментами (tiny, smALl и compact) и единица для моделей памяти с дальними кодовыми сегментами (medium, large и huge). Аналогично @DataSIze — 0 для моделей памяти с ближними сементами данных (tiny, smALl и medium), 1 в compact и large и 2 в huge модели.

4. Ассемблирование и компоновка программы.

Программа на ассемблере вводится с помощью текстового редактора. После ввода текста программы сохраните ее с расширением .ASM. Для выполнения программы, ее необходимо преобразовать в выполнимую форму. Это требует двух дополнительных шагов, ассемблирование и компановку.

Ассемблирование переводит команды исходной программы в промежуточную форму, называемую объектным модулем, а шаг компоновки объединяет один или более объектных модулей в выполнимую программу.

Чтобы ассемблировать программу PROG.ASM наберите

Если не указать другого имени файла, PROG.ASM будет ассемблироваться в PROG.OBJ. (Заметим, что не требуется набирать расширение имени файла; Турбо-Ассемблер добавляет .ASM сам). На экране появится

Turbo Assembler version 1.0 Copyright (C) 1988 by Borland

Assembling file: prog.asm

Error messages: none

Warning messages: none

Если в программе содержатся ошибки, они появятся на экране вместе с номером строки, в которой найдена эта ошибка. После исправления ошибок отассемблируйте программу снова.

Если в результате ассемблирования не обнаружено ошибок, то следующий шаг – компоновка объектного модуля. Для компоновки программы используйте TLINK, набрав

Не нужно вводить расширение имени, TLINK добавит .OBJ сам. Когда компоновка завершится редактор автоматически создаст .EXE файл с тем же именем, что и имя объектного файла, если Вы не укажете другого. На экране появится сообщение:

Turbo Linker version 2.0 Copyright (C) 1987, 1988 by Borland

Для отладки программы можно использовать программу TD – турбо-debuger, которая обеспечивает пошаговую трассировку и просмотр значений регистров, стека и оперативной памяти.

Не нашли то, что искали? Воспользуйтесь поиском:

Лучшие изречения: Студент — человек, постоянно откладывающий неизбежность. 10538 — | 7320 — или читать все.

188.64.174.135 © studopedia.ru Не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования. Есть нарушение авторского права? Напишите нам | Обратная связь.

Отключите adBlock!
и обновите страницу (F5)

очень нужно

первапя программа на ассемблере TASM выдает ошибки при компилировании

Начал изучать ассемблер, и вот пример первой программы из книги
data segment para public ‘data’
message db ‘Hello World! No war and bomb! Let us live friendly and learn assembler language. $’
data ends
stk segment stack
db 256 dup (‘?’)
stk ends
code segment para public ‘code’
main proc
assume cs:code,ds:data,ss:stk
mov ax, data
mov ds, ax
mov ah, 9
mov dx, offset message
int 21h
mov ax, 4c00h
int 21h
main endp
code ends
end main

проблема в 10 строке, несоответствие типов, и видает еще иногда какую то проблему относительно
слова main, обьясните пожалуйста новичку в чем может быть проблема

YASKAWA ошибки — расшифровка кодов неисправностей ПЧ

13 ноября 2020 г. 22:26

При работе промышленной электроники YASKAWA в системах вентиляции, теплоснабжения или автоматизированном производственном оборудовании часто возникают неисправности, распознать которые можно считав коды ошибок и произведя расшифровку этих кодов по инструкции на конкретную модель электронного оборудования. Наиболее частое использование в промышленном оборудовании получили следующие частотные преобразователи фирмы YASKAWA: Yaskawa V1000, Yaskawa J1000, Yaskawa A1000, Yaskawa L1000. В свою очередь серия Yaskawa V1000 включает в себя следующие модели: CIMR-VUBA0001, CIMR-VUBA0002, CIMR-VUBA0003, CIMR-VUBA0006, CIMR-VUBA0010, CIMR-VUBA0012, CIMR-VUBA0018, CIMR-VU2A0001, CIMR-VU2A0002, CIMR-VU2A0004, CIMR-VU2A0006, CIMR-VU2A0010, CIMR-VU2A0012, CIMR-VU2A0020, CIMR-VU2A0030, CIMR-VU2A0040, CIMR-VU2A0056, CIMR-VU2A0069, CIMR-VU4A0001, CIMR-VU4A0002, CIMR-VU4A0004, CIMR-VU4A0005, CIMR-VU4A0007, CIMR-VU4A0009, CIMR-VU4A0011, CIMR-VU4A0018, CIMR-VU4A0023, CIMR-VU4A0031, CIMR-VU4A0038.

Частотные преобразователи YASKAWA имеют следующие распространенные ошибки:

Наиболее частые ошибки преобразователей YASKAWA V1000:

Ошибка bUS (error bUS) — ошибка коммуникационного модуля;
Ошибка CE (error CE) — ошибка связи MEMOBUS\Modbus;
Ошибка CF (error CF) — ошибка схемы управления;
Ошибка CoF (error CoF) — ошибка датчика тока;
Ошибка CPF02 (error CPF02) — ошибка АЦП;
Ошибка CPF03 (error CPF03) — ошибка ШИМ;
Ошибка CPF06 (error CPF06) — несоответствие параметров после замены платы управления или платы входных сигналов;
Ошибка CPF07 (error CPF07) — ошибка связи с платов входных сигналов;
Ошибка CPF08 (error CPF08) — ошибка памяти EEPROM;
Ошибка CPF11 (error CPF11) — ошибка памяти RAM;
Ошибка CPF12 (error CPF12) — ошибка FLASH памяти;
Ошибка CPF13 (error CPF13) — ошибка сторожевого таймера;
Ошибка CPF14 (error CPF14) — неиспраность схемы управления;
Ошибка CPF16 (error CPF16) — неисправность тактового генератора;
Ошибка CPF17 (error CPF17) — ошибка таймера;
Ошибка CPF18 (error CPF18) — неиспраность схемы управления;
Ошибка CPF19 (error CPF19) — неисправность схемы управления;
Ошибка CPF20 (error CPF20) — аппаратная неисправность RAM, FLASH, Watchdog, Clock;
Ошибка CPF21 (error CPF21) — аппаратная неисправность RAM, FLASH, Watchdog, Clock;
Ошибка CPF22 (error CPF22) — ошибка АЦП;
Ошибка CPF23 (error CPF23) — ошибка ШИМ;
Ошибка CPF24 (error CPF24) — ошибка Drive Capacity Signal;
Ошибка CPF25 (error CPF25) — плата входных сигналов повреждена или не установлена;
Ошибка dEv (error dEv)(отображается на дисплее, как «dEu») — нестабильная скорость;
Ошибка E5 (error E5)(отображается на дисплее, как «ES») — ошибка сторожевого таймера MECHATROLINK;
Ошибка EF0 (error EF0)(отображается на дисплее, как «EO») — неиспраность внешней опциональной платы;
Ошибка dWAL (error dWAL)(отображается на дисплее, как «dLJAL») — программная ошибка функции DriveWorksEZ;
Ошибка dWFL (error dWFL)(отображается на дисплее, как «dLJFL») — ошибка функции DriveWorksEZ;
Ошибка EF1 (error EF1) — внешняя неисправность по входу S1;
Ошибка EF2 (error EF2) — внешняя неисправность по входу S2;
Ошибка EF3 (error EF3) — внешняя неисправность по входу S3;
Ошибка EF4 (error EF4) — внешняя неисправность по входу S4;
Ошибка EF5 (error EF5) — внешняя неисправность по входу S5;
Ошибка EF6 (error EF6) — внешняя неисправность по входу S6;
Ошибка EF7 (error EF7) — внешняя неисправность по входу S7;
Ошибка Err (error Err) — ошибка запяси в память EEPROM;
Ошибка FbH (error FbH) — повышенное значение сигнала обратной связи PID регулятора;
Ошибка FbL (error FbL) — пониженное значение сигнала обратной связи PID регулятора, обрыв датчика;
Ошибка GF (error GF)(отображается на дисплее, как «6F», «CF») — короткое замыкание выхода ПЧ на землю;
Ошибка LF (error LF) — обрыв фазы на выходе инвертора;
Ошибка LF2 (error LF2) — небаланс тока на выходе ПЧ;
Ошибка nSE (error nSE)(отображается на дисплее, как «п5Е», «n5E») — ошибка функции Node Setup во время запуска;
Ошибка oC (error oC)(отображается на дисплее, как «0C») — перегрузка преобразователя;
Ошибка oFA00 (error oFA00) — ошибка связи с опциональной платой;
Ошибка oFA01 (error oFA01) — неисправность опционального модуля;
Ошибка oFA03 (error oFA03) — неисправность опциональной платы;
Ошибка oFA04 (error oFA04) — неисправность опциональной платы;
Ошибка oFA30 (error oFA30) — неисправность опциональной платы id30;
Ошибка oFA31 (error oFA31) — неисправность опциональной платы id31;
Ошибка oFA32 (error oFA32) — неисправность опциональной платы id32;
Ошибка oFA33 (error oFA33) — неисправность опциональной платы id33;
Ошибка oFA34 (error oFA34) — неисправность опциональной платы id34;
Ошибка oFA35 (error oFA35) — неисправность опциональной платы id35;
Ошибка oFA36 (error oFA36) — неисправность опциональной платы id36;
Ошибка oFA37 (error oFA37) — неисправность опциональной платы id37;
Ошибка oFA38 (error oFA38) — неисправность опциональной платы id38;
Ошибка oFA39 (error oFA39) — неисправность опциональной платы id39;
Ошибка oFA40 (error oFA40) — неисправность опциональной платы id40;
Ошибка oFA41 (error oFA41) — неисправность опциональной платы id41;
Ошибка oFA42 (error oFA42) — неисправность опциональной платы id42;
Ошибка oFA43 (error oFA43) — неисправность опциональной платы id43;
Ошибка oH (error oH)(отображается на дисплее, как «0H») — перегрев радиатора инвертора;
Ошибка oH1 (error oH1)(отображается на дисплее, как «0H1») — перегрев радиатора инвертора;
Ошибка oH3 (error oH3)(отображается на дисплее, как «0H3») — перегрев двигателя по датчику PTC1;
Ошибка oH4 (error oH4)(отображается на дисплее, как «0H4») — перегрев двигателя по датчику PTC2;
Ошибка oL1 (error oL1)(отображается на дисплее, как «0L1») — перегрузка двигателя;
Ошибка oL2 (error oL2)(отображается на дисплее, как «0L2») — перегрузка привода;
Ошибка oL3 (error oL3)(отображается на дисплее, как «0L3») — перегрузка по уставкам L6-02, L6-03;
Ошибка oL4 (error oL4)(отображается на дисплее, как «0L4») — перегрузка по уставкам L6-05, L6-06;
Ошибка oL5 (error oL5)(отображается на дисплее, как «0L5») — механическая неисправность по уставке L6-08;
Ошибка oL7 (error oL7)(отображается на дисплее, как «0L7») — ошибка торможения по уставке n3-04;
Ошибка oPr (error oPr)(отображается на дисплее, как «0Pr») — ошибка связи с внешней панелью оператора;
Ошибка oS (error oS)(отображается на дисплее, как «0S», «05», «o5») — превышение заданной скорости;
Ошибка ov (error ov)(отображается на дисплее, как «ou», «0u», «0v») — перенапряжение;
Ошибка PF (error PF) — обрыв фазы на входе ПЧ;
Ошибка PGo (error PGo)(отображается на дисплее, как «PG0») — импульсный вход не подключен;
Ошибка rH (error rH) — перегрев тормозного резистора;
Ошибка rr (error rr) — неисправность встроенного тормозного транзистора;
Ошибка SC (error SC)(отображается на дисплее, как «5C») — короткое замыкание IGBT-модуля;
Ошибка SEr (error SEr)(отображается на дисплее, как «5Er») — ошибка функции поиска скорости speed search;
Ошибка STo (error STo)(отображается на дисплее, как «5Го», «5Г0», «SГo») — неправильные параметры двигателя;
Ошибка UL3 (error UL3) — пониженный ток нагрузки по уставкам L6-02, L6-03;
Ошибка UL4 (error UL4) — пониженный ток нагрузки по уставкам L6-05, L6-06;
Ошибка UL5 (error UL5)(отображается на дисплее, как «ULS») — механическая неисправность по уставке L6-08;
Ошибка Uv1 (error Uv1)(отображается на дисплее, как «Uu1») — пониженное напряжение шины постоянного тока;
Ошибка Uv2 (error Uv1)(отображается на дисплее, как «Uu2») — пониженное напряжение источника питания схемы управления;
Ошибка Uv3 (error Uv3)(отображается на дисплее, как «Uu3») — неисправность схемы защиты от бросков тока;

Узнайте условия проведения диагностики и ремонта электроники YASKAWA, отправив запрос на mail@prom-electric.ru

Время выполнения запроса: 0,00342488288879 секунд.

Цукерберг рекомендует:  Хочу всё знать. Язык ABAP
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих